Fossil SCM

Merge from trunk.

brickviking 2025-03-28 10:11 bv-infotool merge
Commit f60a7ed29171903f0df24366c041997ae1475024632454b0fb83737a1234505f
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
55
fossil
66
fossil.exe
77
win/fossil.exe
88
*shell-see.*
99
*sqlite3-see.*
10
+bld
1011
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
5 fossil
6 fossil.exe
7 win/fossil.exe
8 *shell-see.*
9 *sqlite3-see.*
 
10
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -5,5 +5,6 @@
5 fossil
6 fossil.exe
7 win/fossil.exe
8 *shell-see.*
9 *sqlite3-see.*
10 bld
11

No diff available

+86 -24
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1162,10 +1162,27 @@
11621162
}
11631163
}
11641164
return n;
11651165
}
11661166
#endif
1167
+
1168
+/*
1169
+** Check to see if z[] is a valid VT100 escape. If it is, then
1170
+** return the number of bytes in the escape sequence. Return 0 if
1171
+** z[] is not a VT100 escape.
1172
+**
1173
+** This routine assumes that z[0] is \033 (ESC).
1174
+*/
1175
+static int isVt100(const unsigned char *z){
1176
+ int i;
1177
+ if( z[1]!='[' ) return 0;
1178
+ i = 2;
1179
+ while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
1180
+ while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
1181
+ if( z[i]<0x40 || z[i]>0x7e ) return 0;
1182
+ return i+1;
1183
+}
11671184
11681185
/*
11691186
** Output string zUtf to stdout as w characters. If w is negative,
11701187
** then right-justify the text. W is the width in UTF-8 characters, not
11711188
** in bytes. This is different from the %*.*s specification in printf
@@ -1178,10 +1195,11 @@
11781195
static void utf8_width_print(FILE *out, int w, const char *zUtf){
11791196
const unsigned char *a = (const unsigned char*)zUtf;
11801197
unsigned char c;
11811198
int i = 0;
11821199
int n = 0;
1200
+ int k;
11831201
int aw = w<0 ? -w : w;
11841202
if( zUtf==0 ) zUtf = "";
11851203
while( (c = a[i])!=0 ){
11861204
if( (c&0xc0)==0xc0 ){
11871205
int u;
@@ -1190,10 +1208,12 @@
11901208
if( x+n>aw ){
11911209
break;
11921210
}
11931211
i += len;
11941212
n += x;
1213
+ }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){
1214
+ i += k;
11951215
}else if( n>=aw ){
11961216
break;
11971217
}else{
11981218
n++;
11991219
i++;
@@ -6270,12 +6290,11 @@
62706290
** start HIDDEN,
62716291
** stop HIDDEN,
62726292
** step HIDDEN
62736293
** );
62746294
**
6275
-** The virtual table also has a rowid, logically equivalent to n+1 where
6276
-** "n" is the ascending integer in the aforesaid production definition.
6295
+** The virtual table also has a rowid which is an alias for the value.
62776296
**
62786297
** Function arguments in queries against this virtual table are translated
62796298
** into equality constraints against successive hidden columns. In other
62806299
** words, the following pairs of queries are equivalent to each other:
62816300
**
@@ -6326,10 +6345,11 @@
63266345
/* #include "sqlite3ext.h" */
63276346
SQLITE_EXTENSION_INIT1
63286347
#include <assert.h>
63296348
#include <string.h>
63306349
#include <limits.h>
6350
+#include <math.h>
63316351
63326352
#ifndef SQLITE_OMIT_VIRTUALTABLE
63336353
/*
63346354
** Return that member of a generate_series(...) sequence whose 0-based
63356355
** index is ix. The 0th member is given by smBase. The sequence members
@@ -6486,10 +6506,11 @@
64866506
){
64876507
sqlite3_vtab *pNew;
64886508
int rc;
64896509
64906510
/* Column numbers */
6511
+#define SERIES_COLUMN_ROWID (-1)
64916512
#define SERIES_COLUMN_VALUE 0
64926513
#define SERIES_COLUMN_START 1
64936514
#define SERIES_COLUMN_STOP 2
64946515
#define SERIES_COLUMN_STEP 3
64956516
@@ -6573,17 +6594,15 @@
65736594
#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
65746595
#define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
65756596
#endif
65766597
65776598
/*
6578
-** Return the rowid for the current row, logically equivalent to n+1 where
6579
-** "n" is the ascending integer in the aforesaid production definition.
6599
+** The rowid is the same as the value.
65806600
*/
65816601
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
65826602
series_cursor *pCur = (series_cursor*)cur;
6583
- sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
6584
- *pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0);
6603
+ *pRowid = pCur->ss.iValueNow;
65856604
return SQLITE_OK;
65866605
}
65876606
65886607
/*
65896608
** Return TRUE if the cursor has been moved off of the last
@@ -6692,29 +6711,56 @@
66926711
if( idxNum & 0x3380 ){
66936712
/* Extract the maximum range of output values determined by
66946713
** constraints on the "value" column.
66956714
*/
66966715
if( idxNum & 0x0080 ){
6697
- iMin = iMax = sqlite3_value_int64(argv[i++]);
6716
+ if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6717
+ double r = sqlite3_value_double(argv[i++]);
6718
+ if( r==ceil(r) ){
6719
+ iMin = iMax = (sqlite3_int64)r;
6720
+ }else{
6721
+ returnNoRows = 1;
6722
+ }
6723
+ }else{
6724
+ iMin = iMax = sqlite3_value_int64(argv[i++]);
6725
+ }
66986726
}else{
66996727
if( idxNum & 0x0300 ){
6700
- iMin = sqlite3_value_int64(argv[i++]);
6701
- if( idxNum & 0x0200 ){
6702
- if( iMin==LARGEST_INT64 ){
6703
- returnNoRows = 1;
6728
+ if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6729
+ double r = sqlite3_value_double(argv[i++]);
6730
+ if( idxNum & 0x0200 && r==ceil(r) ){
6731
+ iMin = (sqlite3_int64)ceil(r+1.0);
67046732
}else{
6705
- iMin++;
6733
+ iMin = (sqlite3_int64)ceil(r);
6734
+ }
6735
+ }else{
6736
+ iMin = sqlite3_value_int64(argv[i++]);
6737
+ if( idxNum & 0x0200 ){
6738
+ if( iMin==LARGEST_INT64 ){
6739
+ returnNoRows = 1;
6740
+ }else{
6741
+ iMin++;
6742
+ }
67066743
}
67076744
}
67086745
}
67096746
if( idxNum & 0x3000 ){
6710
- iMax = sqlite3_value_int64(argv[i++]);
6711
- if( idxNum & 0x2000 ){
6712
- if( iMax==SMALLEST_INT64 ){
6713
- returnNoRows = 1;
6747
+ if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6748
+ double r = sqlite3_value_double(argv[i++]);
6749
+ if( (idxNum & 0x2000)!=0 && r==floor(r) ){
6750
+ iMax = (sqlite3_int64)(r-1.0);
67146751
}else{
6715
- iMax--;
6752
+ iMax = (sqlite3_int64)floor(r);
6753
+ }
6754
+ }else{
6755
+ iMax = sqlite3_value_int64(argv[i++]);
6756
+ if( idxNum & 0x2000 ){
6757
+ if( iMax==SMALLEST_INT64 ){
6758
+ returnNoRows = 1;
6759
+ }else{
6760
+ iMax--;
6761
+ }
67166762
}
67176763
}
67186764
}
67196765
if( iMin>iMax ){
67206766
returnNoRows = 1;
@@ -6867,11 +6913,14 @@
68676913
idxNum |= 0x40;
68686914
}
68696915
continue;
68706916
}
68716917
if( pConstraint->iColumn<SERIES_COLUMN_START ){
6872
- if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){
6918
+ if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
6919
+ pConstraint->iColumn==SERIES_COLUMN_ROWID)
6920
+ && pConstraint->usable
6921
+ ){
68736922
switch( op ){
68746923
case SQLITE_INDEX_CONSTRAINT_EQ:
68756924
case SQLITE_INDEX_CONSTRAINT_IS: {
68766925
idxNum |= 0x0080;
68776926
idxNum &= ~0x3300;
@@ -24197,13 +24246,18 @@
2419724246
j++;
2419824247
}while( (n&7)!=0 && n<mxWidth );
2419924248
i++;
2420024249
continue;
2420124250
}
24202
- n++;
24203
- j += 3;
24204
- i++;
24251
+ if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){
24252
+ i += k;
24253
+ j += k;
24254
+ }else{
24255
+ n++;
24256
+ j += 3;
24257
+ i++;
24258
+ }
2420524259
}
2420624260
if( n>=mxWidth && bWordWrap ){
2420724261
/* Perhaps try to back up to a better place to break the line */
2420824262
for(k=i; k>i/2; k--){
2420924263
if( IsSpace(z[k-1]) ) break;
@@ -24265,13 +24319,21 @@
2426524319
break;
2426624320
case SHELL_ESC_ASCII:
2426724321
zOut[j++] = '^';
2426824322
zOut[j++] = 0x40 + c;
2426924323
break;
24270
- case SHELL_ESC_OFF:
24271
- zOut[j++] = c;
24324
+ case SHELL_ESC_OFF: {
24325
+ int nn;
24326
+ if( c==0x1b && (nn = isVt100(&z[i]))>0 ){
24327
+ memcpy(&zOut[j], &z[i], nn);
24328
+ j += nn;
24329
+ i += nn - 1;
24330
+ }else{
24331
+ zOut[j++] = c;
24332
+ }
2427224333
break;
24334
+ }
2427324335
}
2427424336
i++;
2427524337
}
2427624338
zOut[j] = 0;
2427724339
return (char*)zOut;
@@ -27021,11 +27083,11 @@
2702127083
for(i=0; i<pgSz; i+=16){
2702227084
const u8 *aLine = aData+i;
2702327085
for(j=0; j<16 && aLine[j]==0; j++){}
2702427086
if( j==16 ) continue;
2702527087
if( !seenPageLabel ){
27026
- sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz);
27088
+ sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz);
2702727089
seenPageLabel = 1;
2702827090
}
2702927091
sqlite3_fprintf(p->out, "| %5d:", i);
2703027092
for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
2703127093
sqlite3_fprintf(p->out, " ");
2703227094
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1162,10 +1162,27 @@
1162 }
1163 }
1164 return n;
1165 }
1166 #endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1167
1168 /*
1169 ** Output string zUtf to stdout as w characters. If w is negative,
1170 ** then right-justify the text. W is the width in UTF-8 characters, not
1171 ** in bytes. This is different from the %*.*s specification in printf
@@ -1178,10 +1195,11 @@
1178 static void utf8_width_print(FILE *out, int w, const char *zUtf){
1179 const unsigned char *a = (const unsigned char*)zUtf;
1180 unsigned char c;
1181 int i = 0;
1182 int n = 0;
 
1183 int aw = w<0 ? -w : w;
1184 if( zUtf==0 ) zUtf = "";
1185 while( (c = a[i])!=0 ){
1186 if( (c&0xc0)==0xc0 ){
1187 int u;
@@ -1190,10 +1208,12 @@
1190 if( x+n>aw ){
1191 break;
1192 }
1193 i += len;
1194 n += x;
 
 
1195 }else if( n>=aw ){
1196 break;
1197 }else{
1198 n++;
1199 i++;
@@ -6270,12 +6290,11 @@
6270 ** start HIDDEN,
6271 ** stop HIDDEN,
6272 ** step HIDDEN
6273 ** );
6274 **
6275 ** The virtual table also has a rowid, logically equivalent to n+1 where
6276 ** "n" is the ascending integer in the aforesaid production definition.
6277 **
6278 ** Function arguments in queries against this virtual table are translated
6279 ** into equality constraints against successive hidden columns. In other
6280 ** words, the following pairs of queries are equivalent to each other:
6281 **
@@ -6326,10 +6345,11 @@
6326 /* #include "sqlite3ext.h" */
6327 SQLITE_EXTENSION_INIT1
6328 #include <assert.h>
6329 #include <string.h>
6330 #include <limits.h>
 
6331
6332 #ifndef SQLITE_OMIT_VIRTUALTABLE
6333 /*
6334 ** Return that member of a generate_series(...) sequence whose 0-based
6335 ** index is ix. The 0th member is given by smBase. The sequence members
@@ -6486,10 +6506,11 @@
6486 ){
6487 sqlite3_vtab *pNew;
6488 int rc;
6489
6490 /* Column numbers */
 
6491 #define SERIES_COLUMN_VALUE 0
6492 #define SERIES_COLUMN_START 1
6493 #define SERIES_COLUMN_STOP 2
6494 #define SERIES_COLUMN_STEP 3
6495
@@ -6573,17 +6594,15 @@
6573 #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
6574 #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
6575 #endif
6576
6577 /*
6578 ** Return the rowid for the current row, logically equivalent to n+1 where
6579 ** "n" is the ascending integer in the aforesaid production definition.
6580 */
6581 static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
6582 series_cursor *pCur = (series_cursor*)cur;
6583 sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
6584 *pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0);
6585 return SQLITE_OK;
6586 }
6587
6588 /*
6589 ** Return TRUE if the cursor has been moved off of the last
@@ -6692,29 +6711,56 @@
6692 if( idxNum & 0x3380 ){
6693 /* Extract the maximum range of output values determined by
6694 ** constraints on the "value" column.
6695 */
6696 if( idxNum & 0x0080 ){
6697 iMin = iMax = sqlite3_value_int64(argv[i++]);
 
 
 
 
 
 
 
 
 
6698 }else{
6699 if( idxNum & 0x0300 ){
6700 iMin = sqlite3_value_int64(argv[i++]);
6701 if( idxNum & 0x0200 ){
6702 if( iMin==LARGEST_INT64 ){
6703 returnNoRows = 1;
6704 }else{
6705 iMin++;
 
 
 
 
 
 
 
 
 
6706 }
6707 }
6708 }
6709 if( idxNum & 0x3000 ){
6710 iMax = sqlite3_value_int64(argv[i++]);
6711 if( idxNum & 0x2000 ){
6712 if( iMax==SMALLEST_INT64 ){
6713 returnNoRows = 1;
6714 }else{
6715 iMax--;
 
 
 
 
 
 
 
 
 
6716 }
6717 }
6718 }
6719 if( iMin>iMax ){
6720 returnNoRows = 1;
@@ -6867,11 +6913,14 @@
6867 idxNum |= 0x40;
6868 }
6869 continue;
6870 }
6871 if( pConstraint->iColumn<SERIES_COLUMN_START ){
6872 if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){
 
 
 
6873 switch( op ){
6874 case SQLITE_INDEX_CONSTRAINT_EQ:
6875 case SQLITE_INDEX_CONSTRAINT_IS: {
6876 idxNum |= 0x0080;
6877 idxNum &= ~0x3300;
@@ -24197,13 +24246,18 @@
24197 j++;
24198 }while( (n&7)!=0 && n<mxWidth );
24199 i++;
24200 continue;
24201 }
24202 n++;
24203 j += 3;
24204 i++;
 
 
 
 
 
24205 }
24206 if( n>=mxWidth && bWordWrap ){
24207 /* Perhaps try to back up to a better place to break the line */
24208 for(k=i; k>i/2; k--){
24209 if( IsSpace(z[k-1]) ) break;
@@ -24265,13 +24319,21 @@
24265 break;
24266 case SHELL_ESC_ASCII:
24267 zOut[j++] = '^';
24268 zOut[j++] = 0x40 + c;
24269 break;
24270 case SHELL_ESC_OFF:
24271 zOut[j++] = c;
 
 
 
 
 
 
 
24272 break;
 
24273 }
24274 i++;
24275 }
24276 zOut[j] = 0;
24277 return (char*)zOut;
@@ -27021,11 +27083,11 @@
27021 for(i=0; i<pgSz; i+=16){
27022 const u8 *aLine = aData+i;
27023 for(j=0; j<16 && aLine[j]==0; j++){}
27024 if( j==16 ) continue;
27025 if( !seenPageLabel ){
27026 sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz);
27027 seenPageLabel = 1;
27028 }
27029 sqlite3_fprintf(p->out, "| %5d:", i);
27030 for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
27031 sqlite3_fprintf(p->out, " ");
27032
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1162,10 +1162,27 @@
1162 }
1163 }
1164 return n;
1165 }
1166 #endif
1167
1168 /*
1169 ** Check to see if z[] is a valid VT100 escape. If it is, then
1170 ** return the number of bytes in the escape sequence. Return 0 if
1171 ** z[] is not a VT100 escape.
1172 **
1173 ** This routine assumes that z[0] is \033 (ESC).
1174 */
1175 static int isVt100(const unsigned char *z){
1176 int i;
1177 if( z[1]!='[' ) return 0;
1178 i = 2;
1179 while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
1180 while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
1181 if( z[i]<0x40 || z[i]>0x7e ) return 0;
1182 return i+1;
1183 }
1184
1185 /*
1186 ** Output string zUtf to stdout as w characters. If w is negative,
1187 ** then right-justify the text. W is the width in UTF-8 characters, not
1188 ** in bytes. This is different from the %*.*s specification in printf
@@ -1178,10 +1195,11 @@
1195 static void utf8_width_print(FILE *out, int w, const char *zUtf){
1196 const unsigned char *a = (const unsigned char*)zUtf;
1197 unsigned char c;
1198 int i = 0;
1199 int n = 0;
1200 int k;
1201 int aw = w<0 ? -w : w;
1202 if( zUtf==0 ) zUtf = "";
1203 while( (c = a[i])!=0 ){
1204 if( (c&0xc0)==0xc0 ){
1205 int u;
@@ -1190,10 +1208,12 @@
1208 if( x+n>aw ){
1209 break;
1210 }
1211 i += len;
1212 n += x;
1213 }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){
1214 i += k;
1215 }else if( n>=aw ){
1216 break;
1217 }else{
1218 n++;
1219 i++;
@@ -6270,12 +6290,11 @@
6290 ** start HIDDEN,
6291 ** stop HIDDEN,
6292 ** step HIDDEN
6293 ** );
6294 **
6295 ** The virtual table also has a rowid which is an alias for the value.
 
6296 **
6297 ** Function arguments in queries against this virtual table are translated
6298 ** into equality constraints against successive hidden columns. In other
6299 ** words, the following pairs of queries are equivalent to each other:
6300 **
@@ -6326,10 +6345,11 @@
6345 /* #include "sqlite3ext.h" */
6346 SQLITE_EXTENSION_INIT1
6347 #include <assert.h>
6348 #include <string.h>
6349 #include <limits.h>
6350 #include <math.h>
6351
6352 #ifndef SQLITE_OMIT_VIRTUALTABLE
6353 /*
6354 ** Return that member of a generate_series(...) sequence whose 0-based
6355 ** index is ix. The 0th member is given by smBase. The sequence members
@@ -6486,10 +6506,11 @@
6506 ){
6507 sqlite3_vtab *pNew;
6508 int rc;
6509
6510 /* Column numbers */
6511 #define SERIES_COLUMN_ROWID (-1)
6512 #define SERIES_COLUMN_VALUE 0
6513 #define SERIES_COLUMN_START 1
6514 #define SERIES_COLUMN_STOP 2
6515 #define SERIES_COLUMN_STEP 3
6516
@@ -6573,17 +6594,15 @@
6594 #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
6595 #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
6596 #endif
6597
6598 /*
6599 ** The rowid is the same as the value.
 
6600 */
6601 static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
6602 series_cursor *pCur = (series_cursor*)cur;
6603 *pRowid = pCur->ss.iValueNow;
 
6604 return SQLITE_OK;
6605 }
6606
6607 /*
6608 ** Return TRUE if the cursor has been moved off of the last
@@ -6692,29 +6711,56 @@
6711 if( idxNum & 0x3380 ){
6712 /* Extract the maximum range of output values determined by
6713 ** constraints on the "value" column.
6714 */
6715 if( idxNum & 0x0080 ){
6716 if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6717 double r = sqlite3_value_double(argv[i++]);
6718 if( r==ceil(r) ){
6719 iMin = iMax = (sqlite3_int64)r;
6720 }else{
6721 returnNoRows = 1;
6722 }
6723 }else{
6724 iMin = iMax = sqlite3_value_int64(argv[i++]);
6725 }
6726 }else{
6727 if( idxNum & 0x0300 ){
6728 if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6729 double r = sqlite3_value_double(argv[i++]);
6730 if( idxNum & 0x0200 && r==ceil(r) ){
6731 iMin = (sqlite3_int64)ceil(r+1.0);
6732 }else{
6733 iMin = (sqlite3_int64)ceil(r);
6734 }
6735 }else{
6736 iMin = sqlite3_value_int64(argv[i++]);
6737 if( idxNum & 0x0200 ){
6738 if( iMin==LARGEST_INT64 ){
6739 returnNoRows = 1;
6740 }else{
6741 iMin++;
6742 }
6743 }
6744 }
6745 }
6746 if( idxNum & 0x3000 ){
6747 if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
6748 double r = sqlite3_value_double(argv[i++]);
6749 if( (idxNum & 0x2000)!=0 && r==floor(r) ){
6750 iMax = (sqlite3_int64)(r-1.0);
6751 }else{
6752 iMax = (sqlite3_int64)floor(r);
6753 }
6754 }else{
6755 iMax = sqlite3_value_int64(argv[i++]);
6756 if( idxNum & 0x2000 ){
6757 if( iMax==SMALLEST_INT64 ){
6758 returnNoRows = 1;
6759 }else{
6760 iMax--;
6761 }
6762 }
6763 }
6764 }
6765 if( iMin>iMax ){
6766 returnNoRows = 1;
@@ -6867,11 +6913,14 @@
6913 idxNum |= 0x40;
6914 }
6915 continue;
6916 }
6917 if( pConstraint->iColumn<SERIES_COLUMN_START ){
6918 if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
6919 pConstraint->iColumn==SERIES_COLUMN_ROWID)
6920 && pConstraint->usable
6921 ){
6922 switch( op ){
6923 case SQLITE_INDEX_CONSTRAINT_EQ:
6924 case SQLITE_INDEX_CONSTRAINT_IS: {
6925 idxNum |= 0x0080;
6926 idxNum &= ~0x3300;
@@ -24197,13 +24246,18 @@
24246 j++;
24247 }while( (n&7)!=0 && n<mxWidth );
24248 i++;
24249 continue;
24250 }
24251 if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){
24252 i += k;
24253 j += k;
24254 }else{
24255 n++;
24256 j += 3;
24257 i++;
24258 }
24259 }
24260 if( n>=mxWidth && bWordWrap ){
24261 /* Perhaps try to back up to a better place to break the line */
24262 for(k=i; k>i/2; k--){
24263 if( IsSpace(z[k-1]) ) break;
@@ -24265,13 +24319,21 @@
24319 break;
24320 case SHELL_ESC_ASCII:
24321 zOut[j++] = '^';
24322 zOut[j++] = 0x40 + c;
24323 break;
24324 case SHELL_ESC_OFF: {
24325 int nn;
24326 if( c==0x1b && (nn = isVt100(&z[i]))>0 ){
24327 memcpy(&zOut[j], &z[i], nn);
24328 j += nn;
24329 i += nn - 1;
24330 }else{
24331 zOut[j++] = c;
24332 }
24333 break;
24334 }
24335 }
24336 i++;
24337 }
24338 zOut[j] = 0;
24339 return (char*)zOut;
@@ -27021,11 +27083,11 @@
27083 for(i=0; i<pgSz; i+=16){
27084 const u8 *aLine = aData+i;
27085 for(j=0; j<16 && aLine[j]==0; j++){}
27086 if( j==16 ) continue;
27087 if( !seenPageLabel ){
27088 sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz);
27089 seenPageLabel = 1;
27090 }
27091 sqlite3_fprintf(p->out, "| %5d:", i);
27092 for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
27093 sqlite3_fprintf(p->out, " ");
27094
+428 -115
--- 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
-** 18bda13e197e4b4ec7464b3e70012f71edc0 with changes in files:
21
+** 121f4d97f9a855131859d342bc2ade5f8c34 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.50.0"
469469
#define SQLITE_VERSION_NUMBER 3050000
470
-#define SQLITE_SOURCE_ID "2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185"
470
+#define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85"
471471
472472
/*
473473
** CAPI3REF: Run-Time Library Version Numbers
474474
** KEYWORDS: sqlite3_version sqlite3_sourceid
475475
**
@@ -30270,10 +30270,12 @@
3027030270
*/
3027130271
#include "windows.h"
3027230272
3027330273
#ifdef __CYGWIN__
3027430274
# include <sys/cygwin.h>
30275
+# include <sys/stat.h> /* amalgamator: dontcache */
30276
+# include <unistd.h> /* amalgamator: dontcache */
3027530277
# include <errno.h> /* amalgamator: dontcache */
3027630278
#endif
3027730279
3027830280
/*
3027930281
** Determine if we are dealing with Windows NT.
@@ -47726,20 +47728,20 @@
4772647728
{ "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 },
4772747729
#else
4772847730
{ "FileTimeToLocalFileTime", (SYSCALL)0, 0 },
4772947731
#endif
4773047732
47731
-#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \
47733
+#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \
4773247734
LPFILETIME))aSyscall[11].pCurrent)
4773347735
4773447736
#if SQLITE_OS_WINCE
4773547737
{ "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 },
4773647738
#else
4773747739
{ "FileTimeToSystemTime", (SYSCALL)0, 0 },
4773847740
#endif
4773947741
47740
-#define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \
47742
+#define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \
4774147743
LPSYSTEMTIME))aSyscall[12].pCurrent)
4774247744
4774347745
{ "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 },
4774447746
4774547747
#define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent)
@@ -48015,11 +48017,11 @@
4801548017
{ "LockFile", (SYSCALL)LockFile, 0 },
4801648018
#else
4801748019
{ "LockFile", (SYSCALL)0, 0 },
4801848020
#endif
4801948021
48020
-#ifndef osLockFile
48022
+#if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI)
4802148023
#define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
4802248024
DWORD))aSyscall[47].pCurrent)
4802348025
#endif
4802448026
4802548027
#if !SQLITE_OS_WINCE
@@ -48079,20 +48081,20 @@
4807948081
4808048082
#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent)
4808148083
4808248084
{ "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 },
4808348085
48084
-#define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \
48086
+#define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \
4808548087
LPFILETIME))aSyscall[56].pCurrent)
4808648088
4808748089
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
4808848090
{ "UnlockFile", (SYSCALL)UnlockFile, 0 },
4808948091
#else
4809048092
{ "UnlockFile", (SYSCALL)0, 0 },
4809148093
#endif
4809248094
48093
-#ifndef osUnlockFile
48095
+#if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI)
4809448096
#define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
4809548097
DWORD))aSyscall[57].pCurrent)
4809648098
#endif
4809748099
4809848100
#if !SQLITE_OS_WINCE
@@ -48316,10 +48318,67 @@
4831648318
{ "CancelIo", (SYSCALL)0, 0 },
4831748319
#endif
4831848320
4831948321
#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent)
4832048322
48323
+#if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32)
48324
+ { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 },
48325
+#else
48326
+ { "GetModuleHandleW", (SYSCALL)0, 0 },
48327
+#endif
48328
+
48329
+#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent)
48330
+
48331
+#ifndef _WIN32
48332
+ { "getenv", (SYSCALL)getenv, 0 },
48333
+#else
48334
+ { "getenv", (SYSCALL)0, 0 },
48335
+#endif
48336
+
48337
+#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent)
48338
+
48339
+#ifndef _WIN32
48340
+ { "getcwd", (SYSCALL)getcwd, 0 },
48341
+#else
48342
+ { "getcwd", (SYSCALL)0, 0 },
48343
+#endif
48344
+
48345
+#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent)
48346
+
48347
+#ifndef _WIN32
48348
+ { "readlink", (SYSCALL)readlink, 0 },
48349
+#else
48350
+ { "readlink", (SYSCALL)0, 0 },
48351
+#endif
48352
+
48353
+#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent)
48354
+
48355
+#ifndef _WIN32
48356
+ { "lstat", (SYSCALL)lstat, 0 },
48357
+#else
48358
+ { "lstat", (SYSCALL)0, 0 },
48359
+#endif
48360
+
48361
+#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent)
48362
+
48363
+#ifndef _WIN32
48364
+ { "__errno", (SYSCALL)__errno, 0 },
48365
+#else
48366
+ { "__errno", (SYSCALL)0, 0 },
48367
+#endif
48368
+
48369
+#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)())
48370
+
48371
+#ifndef _WIN32
48372
+ { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 },
48373
+#else
48374
+ { "cygwin_conv_path", (SYSCALL)0, 0 },
48375
+#endif
48376
+
48377
+#define osCygwin_conv_path ((size_t(*)(unsigned int, \
48378
+ const void *, void *, size_t))aSyscall[88].pCurrent)
48379
+
4832148380
}; /* End of the overrideable system calls */
4832248381
4832348382
/*
4832448383
** This is the xSetSystemCall() method of sqlite3_vfs for all of the
4832548384
** "win32" VFSes. Return SQLITE_OK upon successfully updating the
@@ -48489,10 +48548,11 @@
4848948548
sqlite3_mutex_leave(pMainMtx);
4849048549
return rc;
4849148550
}
4849248551
#endif /* SQLITE_WIN32_MALLOC */
4849348552
48553
+#ifdef _WIN32
4849448554
/*
4849548555
** This function outputs the specified (ANSI) string to the Win32 debugger
4849648556
** (if available).
4849748557
*/
4849848558
@@ -48531,10 +48591,11 @@
4853148591
}else{
4853248592
fprintf(stderr, "%s", zBuf);
4853348593
}
4853448594
#endif
4853548595
}
48596
+#endif /* _WIN32 */
4853648597
4853748598
/*
4853848599
** The following routine suspends the current thread for at least ms
4853948600
** milliseconds. This is equivalent to the Win32 Sleep() interface.
4854048601
*/
@@ -48831,10 +48892,11 @@
4883148892
SQLITE_PRIVATE void sqlite3MemSetDefault(void){
4883248893
sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
4883348894
}
4883448895
#endif /* SQLITE_WIN32_MALLOC */
4883548896
48897
+#ifdef _WIN32
4883648898
/*
4883748899
** Convert a UTF-8 string to Microsoft Unicode.
4883848900
**
4883948901
** Space to hold the returned string is obtained from sqlite3_malloc().
4884048902
*/
@@ -48856,10 +48918,11 @@
4885648918
sqlite3_free(zWideText);
4885748919
zWideText = 0;
4885848920
}
4885948921
return zWideText;
4886048922
}
48923
+#endif /* _WIN32 */
4886148924
4886248925
/*
4886348926
** Convert a Microsoft Unicode string to UTF-8.
4886448927
**
4886548928
** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48890,32 +48953,33 @@
4889048953
** code page.
4889148954
**
4889248955
** Space to hold the returned string is obtained from sqlite3_malloc().
4889348956
*/
4889448957
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
48895
- int nByte;
48958
+ int nWideChar;
4889648959
LPWSTR zMbcsText;
4889748960
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
4889848961
48899
- nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
48900
- 0)*sizeof(WCHAR);
48901
- if( nByte==0 ){
48962
+ nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
48963
+ 0);
48964
+ if( nWideChar==0 ){
4890248965
return 0;
4890348966
}
48904
- zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) );
48967
+ zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) );
4890548968
if( zMbcsText==0 ){
4890648969
return 0;
4890748970
}
48908
- nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
48909
- nByte);
48910
- if( nByte==0 ){
48971
+ nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
48972
+ nWideChar);
48973
+ if( nWideChar==0 ){
4891148974
sqlite3_free(zMbcsText);
4891248975
zMbcsText = 0;
4891348976
}
4891448977
return zMbcsText;
4891548978
}
4891648979
48980
+#ifdef _WIN32
4891748981
/*
4891848982
** Convert a Microsoft Unicode string to a multi-byte character string,
4891948983
** using the ANSI or OEM code page.
4892048984
**
4892148985
** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48939,10 +49003,11 @@
4893949003
sqlite3_free(zText);
4894049004
zText = 0;
4894149005
}
4894249006
return zText;
4894349007
}
49008
+#endif /* _WIN32 */
4894449009
4894549010
/*
4894649011
** Convert a multi-byte character string to UTF-8.
4894749012
**
4894849013
** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48958,10 +49023,11 @@
4895849023
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
4895949024
sqlite3_free(zTmpWide);
4896049025
return zTextUtf8;
4896149026
}
4896249027
49028
+#ifdef _WIN32
4896349029
/*
4896449030
** Convert a UTF-8 string to a multi-byte character string.
4896549031
**
4896649032
** Space to hold the returned string is obtained from sqlite3_malloc().
4896749033
*/
@@ -49007,10 +49073,11 @@
4900749073
#ifndef SQLITE_OMIT_AUTOINIT
4900849074
if( sqlite3_initialize() ) return 0;
4900949075
#endif
4901049076
return winUnicodeToUtf8(zWideText);
4901149077
}
49078
+#endif /* _WIN32 */
4901249079
4901349080
/*
4901449081
** This is a public wrapper for the winMbcsToUtf8() function.
4901549082
*/
4901649083
SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
@@ -49024,10 +49091,11 @@
4902449091
if( sqlite3_initialize() ) return 0;
4902549092
#endif
4902649093
return winMbcsToUtf8(zText, osAreFileApisANSI());
4902749094
}
4902849095
49096
+#ifdef _WIN32
4902949097
/*
4903049098
** This is a public wrapper for the winMbcsToUtf8() function.
4903149099
*/
4903249100
SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
4903349101
#ifdef SQLITE_ENABLE_API_ARMOR
@@ -49148,10 +49216,11 @@
4914849216
unsigned long type, /* Identifier for directory being set or reset */
4914949217
void *zValue /* New value for directory being set or reset */
4915049218
){
4915149219
return sqlite3_win32_set_directory16(type, zValue);
4915249220
}
49221
+#endif /* _WIN32 */
4915349222
4915449223
/*
4915549224
** The return value of winGetLastErrorMsg
4915649225
** is zero if the error message fits in the buffer, or non-zero
4915749226
** otherwise (if the message was truncated).
@@ -49696,13 +49765,15 @@
4969649765
OVERLAPPED ovlp;
4969749766
memset(&ovlp, 0, sizeof(OVERLAPPED));
4969849767
ovlp.Offset = offsetLow;
4969949768
ovlp.OffsetHigh = offsetHigh;
4970049769
return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
49770
+#ifdef SQLITE_WIN32_HAS_ANSI
4970149771
}else{
4970249772
return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
4970349773
numBytesHigh);
49774
+#endif
4970449775
}
4970549776
#endif
4970649777
}
4970749778
4970849779
/*
@@ -49806,13 +49877,15 @@
4980649877
OVERLAPPED ovlp;
4980749878
memset(&ovlp, 0, sizeof(OVERLAPPED));
4980849879
ovlp.Offset = offsetLow;
4980949880
ovlp.OffsetHigh = offsetHigh;
4981049881
return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp);
49882
+#ifdef SQLITE_WIN32_HAS_ANSI
4981149883
}else{
4981249884
return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
4981349885
numBytesHigh);
49886
+#endif
4981449887
}
4981549888
#endif
4981649889
}
4981749890
4981849891
/*
@@ -51222,18 +51295,95 @@
5122251295
5122351296
/*
5122451297
** Convert a UTF-8 filename into whatever form the underlying
5122551298
** operating system wants filenames in. Space to hold the result
5122651299
** is obtained from malloc and must be freed by the calling
51227
-** function.
51300
+** function
51301
+**
51302
+** On Cygwin, 3 possible input forms are accepted:
51303
+** - If the filename starts with "<drive>:/" or "<drive>:\",
51304
+** it is converted to UTF-16 as-is.
51305
+** - If the filename contains '/', it is assumed to be a
51306
+** Cygwin absolute path, it is converted to a win32
51307
+** absolute path in UTF-16.
51308
+** - Otherwise it must be a filename only, the win32 filename
51309
+** is returned in UTF-16.
51310
+** Note: If the function cygwin_conv_path() fails, only
51311
+** UTF-8 -> UTF-16 conversion will be done. This can only
51312
+** happen when the file path >32k, in which case winUtf8ToUnicode()
51313
+** will fail too.
5122851314
*/
5122951315
static void *winConvertFromUtf8Filename(const char *zFilename){
5123051316
void *zConverted = 0;
5123151317
if( osIsNT() ){
51318
+#ifdef __CYGWIN__
51319
+ int nChar;
51320
+ LPWSTR zWideFilename;
51321
+
51322
+ if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename)
51323
+ && winIsDirSep(zFilename[2])) ){
51324
+ int nByte;
51325
+ int convertflag = CCP_POSIX_TO_WIN_W;
51326
+ if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE;
51327
+ nByte = (int)osCygwin_conv_path(convertflag,
51328
+ zFilename, 0, 0);
51329
+ if( nByte>0 ){
51330
+ zConverted = sqlite3MallocZero(nByte+12);
51331
+ if ( zConverted==0 ){
51332
+ return zConverted;
51333
+ }
51334
+ zWideFilename = zConverted;
51335
+ /* Filenames should be prefixed, except when converted
51336
+ * full path already starts with "\\?\". */
51337
+ if( osCygwin_conv_path(convertflag, zFilename,
51338
+ zWideFilename+4, nByte)==0 ){
51339
+ if( (convertflag&CCP_RELATIVE) ){
51340
+ memmove(zWideFilename, zWideFilename+4, nByte);
51341
+ }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){
51342
+ memcpy(zWideFilename, L"\\\\?\\", 8);
51343
+ }else if( zWideFilename[6]!='?' ){
51344
+ memmove(zWideFilename+6, zWideFilename+4, nByte);
51345
+ memcpy(zWideFilename, L"\\\\?\\UNC", 14);
51346
+ }else{
51347
+ memmove(zWideFilename, zWideFilename+4, nByte);
51348
+ }
51349
+ return zConverted;
51350
+ }
51351
+ sqlite3_free(zConverted);
51352
+ }
51353
+ }
51354
+ nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
51355
+ if( nChar==0 ){
51356
+ return 0;
51357
+ }
51358
+ zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 );
51359
+ if( zWideFilename==0 ){
51360
+ return 0;
51361
+ }
51362
+ nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1,
51363
+ zWideFilename, nChar);
51364
+ if( nChar==0 ){
51365
+ sqlite3_free(zWideFilename);
51366
+ zWideFilename = 0;
51367
+ }else if( nChar>MAX_PATH
51368
+ && winIsDriveLetterAndColon(zFilename)
51369
+ && winIsDirSep(zFilename[2]) ){
51370
+ memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR));
51371
+ zWideFilename[2] = '\\';
51372
+ memcpy(zWideFilename, L"\\\\?\\", 8);
51373
+ }else if( nChar>MAX_PATH
51374
+ && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1])
51375
+ && zFilename[2] != '?' ){
51376
+ memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR));
51377
+ memcpy(zWideFilename, L"\\\\?\\UNC", 14);
51378
+ }
51379
+ zConverted = zWideFilename;
51380
+#else
5123251381
zConverted = winUtf8ToUnicode(zFilename);
51382
+#endif /* __CYGWIN__ */
5123351383
}
51234
-#ifdef SQLITE_WIN32_HAS_ANSI
51384
+#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32)
5123551385
else{
5123651386
zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
5123751387
}
5123851388
#endif
5123951389
/* caller will handle out of memory */
@@ -52058,11 +52208,11 @@
5205852208
**
5205952209
** This division contains the implementation of methods on the
5206052210
** sqlite3_vfs object.
5206152211
*/
5206252212
52063
-#if defined(__CYGWIN__)
52213
+#if 0 /* No longer necessary */
5206452214
/*
5206552215
** Convert a filename from whatever the underlying operating system
5206652216
** supports for filenames into UTF-8. Space to hold the result is
5206752217
** obtained from malloc and must be freed by the calling function.
5206852218
*/
@@ -52091,11 +52241,18 @@
5209152241
int nLen = sqlite3Strlen30(zBuf);
5209252242
if( nLen>0 ){
5209352243
if( winIsDirSep(zBuf[nLen-1]) ){
5209452244
return 1;
5209552245
}else if( nLen+1<nBuf ){
52096
- zBuf[nLen] = winGetDirSep();
52246
+ if( !osGetenv ){
52247
+ zBuf[nLen] = winGetDirSep();
52248
+ }else if( winIsDriveLetterAndColon(zBuf) && winIsDirSep(zBuf[2]) ){
52249
+ zBuf[nLen] = '\\';
52250
+ zBuf[2]='\\';
52251
+ }else{
52252
+ zBuf[nLen] = '/';
52253
+ }
5209752254
zBuf[nLen+1] = '\0';
5209852255
return 1;
5209952256
}
5210052257
}
5210152258
}
@@ -52118,11 +52275,11 @@
5211852275
/*
5211952276
** Create a temporary file name and store the resulting pointer into pzBuf.
5212052277
** The pointer returned in pzBuf must be freed via sqlite3_free().
5212152278
*/
5212252279
static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
52123
- static char zChars[] =
52280
+ static const char zChars[] =
5212452281
"abcdefghijklmnopqrstuvwxyz"
5212552282
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
5212652283
"0123456789";
5212752284
size_t i, j;
5212852285
DWORD pid;
@@ -52169,11 +52326,11 @@
5216952326
}
5217052327
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
5217152328
}
5217252329
5217352330
#if defined(__CYGWIN__)
52174
- else{
52331
+ else if( osGetenv!=NULL ){
5217552332
static const char *azDirs[] = {
5217652333
0, /* getenv("SQLITE_TMPDIR") */
5217752334
0, /* getenv("TMPDIR") */
5217852335
0, /* getenv("TMP") */
5217952336
0, /* getenv("TEMP") */
@@ -52185,24 +52342,24 @@
5218552342
0 /* List terminator */
5218652343
};
5218752344
unsigned int i;
5218852345
const char *zDir = 0;
5218952346
52190
- if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR");
52191
- if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR");
52192
- if( !azDirs[2] ) azDirs[2] = getenv("TMP");
52193
- if( !azDirs[3] ) azDirs[3] = getenv("TEMP");
52194
- if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE");
52347
+ if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR");
52348
+ if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR");
52349
+ if( !azDirs[2] ) azDirs[2] = osGetenv("TMP");
52350
+ if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP");
52351
+ if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE");
5219552352
for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){
5219652353
void *zConverted;
5219752354
if( zDir==0 ) continue;
5219852355
/* If the path starts with a drive letter followed by the colon
5219952356
** character, assume it is already a native Win32 path; otherwise,
5220052357
** it must be converted to a native Win32 path via the Cygwin API
5220152358
** prior to using it.
5220252359
*/
52203
- if( winIsDriveLetterAndColon(zDir) ){
52360
+ {
5220452361
zConverted = winConvertFromUtf8Filename(zDir);
5220552362
if( !zConverted ){
5220652363
sqlite3_free(zBuf);
5220752364
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
5220852365
return SQLITE_IOERR_NOMEM_BKPT;
@@ -52211,19 +52368,20 @@
5221152368
sqlite3_snprintf(nMax, zBuf, "%s", zDir);
5221252369
sqlite3_free(zConverted);
5221352370
break;
5221452371
}
5221552372
sqlite3_free(zConverted);
52373
+#if 0 /* No longer necessary */
5221652374
}else{
5221752375
zConverted = sqlite3MallocZero( nMax+1 );
5221852376
if( !zConverted ){
5221952377
sqlite3_free(zBuf);
5222052378
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
5222152379
return SQLITE_IOERR_NOMEM_BKPT;
5222252380
}
52223
- if( cygwin_conv_path(
52224
- osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir,
52381
+ if( osCygwin_conv_path(
52382
+ CCP_POSIX_TO_WIN_W, zDir,
5222552383
zConverted, nMax+1)<0 ){
5222652384
sqlite3_free(zConverted);
5222752385
sqlite3_free(zBuf);
5222852386
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n"));
5222952387
return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno,
@@ -52245,14 +52403,17 @@
5224552403
sqlite3_free(zUtf8);
5224652404
sqlite3_free(zConverted);
5224752405
break;
5224852406
}
5224952407
sqlite3_free(zConverted);
52408
+#endif /* No longer necessary */
5225052409
}
5225152410
}
5225252411
}
52253
-#elif !SQLITE_OS_WINRT && !defined(__CYGWIN__)
52412
+#endif
52413
+
52414
+#if !SQLITE_OS_WINRT && defined(_WIN32)
5225452415
else if( osIsNT() ){
5225552416
char *zMulti;
5225652417
LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) );
5225752418
if( !zWidePath ){
5225852419
sqlite3_free(zBuf);
@@ -52372,11 +52533,11 @@
5237252533
&sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
5237352534
if( !rc ){
5237452535
return 0; /* Invalid name? */
5237552536
}
5237652537
attr = sAttrData.dwFileAttributes;
52377
-#if SQLITE_OS_WINCE==0
52538
+#if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI)
5237852539
}else{
5237952540
attr = osGetFileAttributesA((char*)zConverted);
5238052541
#endif
5238152542
}
5238252543
return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY);
@@ -52388,10 +52549,16 @@
5238852549
const char *zFilename, /* Name of file to check */
5238952550
int flags, /* Type of test to make on this file */
5239052551
int *pResOut /* OUT: Result */
5239152552
);
5239252553
52554
+/*
52555
+** The Windows version of xAccess() accepts an extra bit in the flags
52556
+** parameter that prevents an anti-virus retry loop.
52557
+*/
52558
+#define NORETRY 0x4000
52559
+
5239352560
/*
5239452561
** Open a file.
5239552562
*/
5239652563
static int winOpen(
5239752564
sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
@@ -52412,10 +52579,11 @@
5241252579
winVfsAppData *pAppData;
5241352580
winFile *pFile = (winFile*)id;
5241452581
void *zConverted; /* Filename in OS encoding */
5241552582
const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
5241652583
int cnt = 0;
52584
+ int isRO = 0; /* file is known to be accessible readonly */
5241752585
5241852586
/* If argument zPath is a NULL pointer, this function is required to open
5241952587
** a temporary file. Use this buffer to store the file name in.
5242052588
*/
5242152589
char *zTmpname = 0; /* For temporary filename, if necessary. */
@@ -52576,13 +52744,13 @@
5257652744
dwShareMode,
5257752745
dwCreationDisposition,
5257852746
&extendedParameters);
5257952747
if( h!=INVALID_HANDLE_VALUE ) break;
5258052748
if( isReadWrite ){
52581
- int rc2, isRO = 0;
52749
+ int rc2;
5258252750
sqlite3BeginBenignMalloc();
52583
- rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52751
+ rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
5258452752
sqlite3EndBenignMalloc();
5258552753
if( rc2==SQLITE_OK && isRO ) break;
5258652754
}
5258752755
}while( winRetryIoerr(&cnt, &lastErrno) );
5258852756
#else
@@ -52593,13 +52761,13 @@
5259352761
dwCreationDisposition,
5259452762
dwFlagsAndAttributes,
5259552763
NULL);
5259652764
if( h!=INVALID_HANDLE_VALUE ) break;
5259752765
if( isReadWrite ){
52598
- int rc2, isRO = 0;
52766
+ int rc2;
5259952767
sqlite3BeginBenignMalloc();
52600
- rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52768
+ rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
5260152769
sqlite3EndBenignMalloc();
5260252770
if( rc2==SQLITE_OK && isRO ) break;
5260352771
}
5260452772
}while( winRetryIoerr(&cnt, &lastErrno) );
5260552773
#endif
@@ -52613,13 +52781,13 @@
5261352781
dwCreationDisposition,
5261452782
dwFlagsAndAttributes,
5261552783
NULL);
5261652784
if( h!=INVALID_HANDLE_VALUE ) break;
5261752785
if( isReadWrite ){
52618
- int rc2, isRO = 0;
52786
+ int rc2;
5261952787
sqlite3BeginBenignMalloc();
52620
- rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52788
+ rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
5262152789
sqlite3EndBenignMalloc();
5262252790
if( rc2==SQLITE_OK && isRO ) break;
5262352791
}
5262452792
}while( winRetryIoerr(&cnt, &lastErrno) );
5262552793
}
@@ -52630,11 +52798,11 @@
5263052798
dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok"));
5263152799
5263252800
if( h==INVALID_HANDLE_VALUE ){
5263352801
sqlite3_free(zConverted);
5263452802
sqlite3_free(zTmpname);
52635
- if( isReadWrite && !isExclusive ){
52803
+ if( isReadWrite && isRO && !isExclusive ){
5263652804
return winOpen(pVfs, zName, id,
5263752805
((flags|SQLITE_OPEN_READONLY) &
5263852806
~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)),
5263952807
pOutFlags);
5264052808
}else{
@@ -52832,11 +53000,17 @@
5283253000
){
5283353001
DWORD attr;
5283453002
int rc = 0;
5283553003
DWORD lastErrno = 0;
5283653004
void *zConverted;
53005
+ int noRetry = 0; /* Do not use winRetryIoerr() */
5283753006
UNUSED_PARAMETER(pVfs);
53007
+
53008
+ if( (flags & NORETRY)!=0 ){
53009
+ noRetry = 1;
53010
+ flags &= ~NORETRY;
53011
+ }
5283853012
5283953013
SimulateIOError( return SQLITE_IOERR_ACCESS; );
5284053014
OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n",
5284153015
zFilename, flags, pResOut));
5284253016
@@ -52856,11 +53030,14 @@
5285653030
int cnt = 0;
5285753031
WIN32_FILE_ATTRIBUTE_DATA sAttrData;
5285853032
memset(&sAttrData, 0, sizeof(sAttrData));
5285953033
while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
5286053034
GetFileExInfoStandard,
52861
- &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
53035
+ &sAttrData))
53036
+ && !noRetry
53037
+ && winRetryIoerr(&cnt, &lastErrno)
53038
+ ){ /* Loop until true */}
5286253039
if( rc ){
5286353040
/* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file
5286453041
** as if it does not exist.
5286553042
*/
5286653043
if( flags==SQLITE_ACCESS_EXISTS
@@ -52924,10 +53101,11 @@
5292453101
const char *zPathname
5292553102
){
5292653103
return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
5292753104
}
5292853105
53106
+#ifdef _WIN32
5292953107
/*
5293053108
** Returns non-zero if the specified path name should be used verbatim. If
5293153109
** non-zero is returned from this function, the calling function must simply
5293253110
** use the provided path name verbatim -OR- resolve it into a full path name
5293353111
** using the GetFullPathName Win32 API function (if available).
@@ -52960,10 +53138,74 @@
5296053138
** If we get to this point, the path name should almost certainly be a purely
5296153139
** relative one (i.e. not a UNC name, not absolute, and not volume relative).
5296253140
*/
5296353141
return FALSE;
5296453142
}
53143
+#endif /* _WIN32 */
53144
+
53145
+#ifdef __CYGWIN__
53146
+/*
53147
+** Simplify a filename into its canonical form
53148
+** by making the following changes:
53149
+**
53150
+** * convert any '/' to '\' (win32) or reverse (Cygwin)
53151
+** * removing any trailing and duplicate / (except for UNC paths)
53152
+** * convert /./ into just /
53153
+**
53154
+** Changes are made in-place. Return the new name length.
53155
+**
53156
+** The original filename is in z[0..]. If the path is shortened,
53157
+** no-longer used bytes will be written by '\0'.
53158
+*/
53159
+static void winSimplifyName(char *z){
53160
+ int i, j;
53161
+ for(i=j=0; z[i]; ++i){
53162
+ if( winIsDirSep(z[i]) ){
53163
+#if !defined(SQLITE_TEST)
53164
+ /* Some test-cases assume that "./foo" and "foo" are different */
53165
+ if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){
53166
+ ++i;
53167
+ continue;
53168
+ }
53169
+#endif
53170
+ if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){
53171
+ continue;
53172
+ }
53173
+ z[j++] = osGetenv?'/':'\\';
53174
+ }else{
53175
+ z[j++] = z[i];
53176
+ }
53177
+ }
53178
+ while(j<i) z[j++] = '\0';
53179
+}
53180
+
53181
+#define SQLITE_MAX_SYMLINKS 100
53182
+
53183
+static int mkFullPathname(
53184
+ const char *zPath, /* Input path */
53185
+ char *zOut, /* Output buffer */
53186
+ int nOut /* Allocated size of buffer zOut */
53187
+){
53188
+ int nPath = sqlite3Strlen30(zPath);
53189
+ int iOff = 0;
53190
+ if( zPath[0]!='/' ){
53191
+ if( osGetcwd(zOut, nOut-2)==0 ){
53192
+ return winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "getcwd", zPath);
53193
+ }
53194
+ iOff = sqlite3Strlen30(zOut);
53195
+ zOut[iOff++] = '/';
53196
+ }
53197
+ if( (iOff+nPath+1)>nOut ){
53198
+ /* SQLite assumes that xFullPathname() nul-terminates the output buffer
53199
+ ** even if it returns an error. */
53200
+ zOut[iOff] = '\0';
53201
+ return SQLITE_CANTOPEN_BKPT;
53202
+ }
53203
+ sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
53204
+ return SQLITE_OK;
53205
+}
53206
+#endif /* __CYGWIN__ */
5296553207
5296653208
/*
5296753209
** Turn a relative pathname into a full pathname. Write the full
5296853210
** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
5296953211
** bytes in size.
@@ -52972,12 +53214,12 @@
5297253214
sqlite3_vfs *pVfs, /* Pointer to vfs object */
5297353215
const char *zRelative, /* Possibly relative input path */
5297453216
int nFull, /* Size of output buffer in bytes */
5297553217
char *zFull /* Output buffer */
5297653218
){
52977
-#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__)
52978
- DWORD nByte;
53219
+#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
53220
+ int nByte;
5297953221
void *zConverted;
5298053222
char *zOut;
5298153223
#endif
5298253224
5298353225
/* If this path name begins with "/X:" or "\\?\", where "X" is any
@@ -52986,68 +53228,114 @@
5298653228
if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1)
5298753229
|| winIsLongPathPrefix(zRelative+1)) ){
5298853230
zRelative++;
5298953231
}
5299053232
52991
-#if defined(__CYGWIN__)
53233
+ SimulateIOError( return SQLITE_ERROR );
53234
+
53235
+#ifdef __CYGWIN__
53236
+ if( osGetcwd ){
53237
+ zFull[nFull-1] = '\0';
53238
+ if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){
53239
+ int rc = SQLITE_OK;
53240
+ int nLink = 1; /* Number of symbolic links followed so far */
53241
+ const char *zIn = zRelative; /* Input path for each iteration of loop */
53242
+ char *zDel = 0;
53243
+ struct stat buf;
53244
+
53245
+ UNUSED_PARAMETER(pVfs);
53246
+
53247
+ do {
53248
+ /* Call lstat() on path zIn. Set bLink to true if the path is a symbolic
53249
+ ** link, or false otherwise. */
53250
+ int bLink = 0;
53251
+ if( osLstat && osReadlink ) {
53252
+ if( osLstat(zIn, &buf)!=0 ){
53253
+ int myErrno = osErrno;
53254
+ if( myErrno!=ENOENT ){
53255
+ rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn);
53256
+ }
53257
+ }else{
53258
+ bLink = ((buf.st_mode & 0170000) == 0120000);
53259
+ }
53260
+
53261
+ if( bLink ){
53262
+ if( zDel==0 ){
53263
+ zDel = sqlite3MallocZero(nFull);
53264
+ if( zDel==0 ) rc = SQLITE_NOMEM;
53265
+ }else if( ++nLink>SQLITE_MAX_SYMLINKS ){
53266
+ rc = SQLITE_CANTOPEN_BKPT;
53267
+ }
53268
+
53269
+ if( rc==SQLITE_OK ){
53270
+ nByte = osReadlink(zIn, zDel, nFull-1);
53271
+ if( nByte ==(DWORD)-1 ){
53272
+ rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn);
53273
+ }else{
53274
+ if( zDel[0]!='/' ){
53275
+ int n;
53276
+ for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
53277
+ if( nByte+n+1>nFull ){
53278
+ rc = SQLITE_CANTOPEN_BKPT;
53279
+ }else{
53280
+ memmove(&zDel[n], zDel, nByte+1);
53281
+ memcpy(zDel, zIn, n);
53282
+ nByte += n;
53283
+ }
53284
+ }
53285
+ zDel[nByte] = '\0';
53286
+ }
53287
+ }
53288
+
53289
+ zIn = zDel;
53290
+ }
53291
+ }
53292
+
53293
+ assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' );
53294
+ if( rc==SQLITE_OK && zIn!=zFull ){
53295
+ rc = mkFullPathname(zIn, zFull, nFull);
53296
+ }
53297
+ if( bLink==0 ) break;
53298
+ zIn = zFull;
53299
+ }while( rc==SQLITE_OK );
53300
+
53301
+ sqlite3_free(zDel);
53302
+ winSimplifyName(zFull);
53303
+ return rc;
53304
+ }
53305
+ }
53306
+#endif /* __CYGWIN__ */
53307
+#if 0 /* This doesn't work correctly at all! See:
53308
+ <https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
53309
+*/
5299253310
SimulateIOError( return SQLITE_ERROR );
5299353311
UNUSED_PARAMETER(nFull);
5299453312
assert( nFull>=pVfs->mxPathname );
52995
- if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
52996
- /*
52997
- ** NOTE: We are dealing with a relative path name and the data
52998
- ** directory has been set. Therefore, use it as the basis
52999
- ** for converting the relative path name to an absolute
53000
- ** one by prepending the data directory and a slash.
53001
- */
53002
- char *zOut = sqlite3MallocZero( 1+(u64)pVfs->mxPathname );
53003
- if( !zOut ){
53004
- return SQLITE_IOERR_NOMEM_BKPT;
53005
- }
53006
- if( cygwin_conv_path(
53007
- (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) |
53008
- CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){
53009
- sqlite3_free(zOut);
53010
- return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53011
- "winFullPathname1", zRelative);
53012
- }else{
53013
- char *zUtf8 = winConvertToUtf8Filename(zOut);
53014
- if( !zUtf8 ){
53015
- sqlite3_free(zOut);
53016
- return SQLITE_IOERR_NOMEM_BKPT;
53017
- }
53018
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
53019
- sqlite3_data_directory, winGetDirSep(), zUtf8);
53020
- sqlite3_free(zUtf8);
53021
- sqlite3_free(zOut);
53022
- }
53023
- }else{
53024
- char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
53025
- if( !zOut ){
53026
- return SQLITE_IOERR_NOMEM_BKPT;
53027
- }
53028
- if( cygwin_conv_path(
53029
- (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A),
53030
- zRelative, zOut, pVfs->mxPathname+1)<0 ){
53031
- sqlite3_free(zOut);
53032
- return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53033
- "winFullPathname2", zRelative);
53034
- }else{
53035
- char *zUtf8 = winConvertToUtf8Filename(zOut);
53036
- if( !zUtf8 ){
53037
- sqlite3_free(zOut);
53038
- return SQLITE_IOERR_NOMEM_BKPT;
53039
- }
53040
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
53041
- sqlite3_free(zUtf8);
53042
- sqlite3_free(zOut);
53043
- }
53044
- }
53045
- return SQLITE_OK;
53046
-#endif
53047
-
53048
-#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__)
53313
+ char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
53314
+ if( !zOut ){
53315
+ return SQLITE_IOERR_NOMEM_BKPT;
53316
+ }
53317
+ if( osCygwin_conv_path(
53318
+ CCP_POSIX_TO_WIN_W,
53319
+ zRelative, zOut, pVfs->mxPathname+1)<0 ){
53320
+ sqlite3_free(zOut);
53321
+ return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53322
+ "winFullPathname2", zRelative);
53323
+ }else{
53324
+ char *zUtf8 = winConvertToUtf8Filename(zOut);
53325
+ if( !zUtf8 ){
53326
+ sqlite3_free(zOut);
53327
+ return SQLITE_IOERR_NOMEM_BKPT;
53328
+ }
53329
+ sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
53330
+ sqlite3_free(zUtf8);
53331
+ sqlite3_free(zOut);
53332
+ }
53333
+ return SQLITE_OK;
53334
+#endif
53335
+
53336
+#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32)
5304953337
SimulateIOError( return SQLITE_ERROR );
5305053338
/* WinCE has no concept of a relative pathname, or so I am told. */
5305153339
/* WinRT has no way to convert a relative path to an absolute one. */
5305253340
if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
5305353341
/*
@@ -53062,11 +53350,12 @@
5306253350
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
5306353351
}
5306453352
return SQLITE_OK;
5306553353
#endif
5306653354
53067
-#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__)
53355
+#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
53356
+#if defined(_WIN32)
5306853357
/* It's odd to simulate an io-error here, but really this is just
5306953358
** using the io-error infrastructure to test that SQLite handles this
5307053359
** function failing. This function could fail if, for example, the
5307153360
** current working directory has been unlinked.
5307253361
*/
@@ -53080,10 +53369,11 @@
5308053369
*/
5308153370
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
5308253371
sqlite3_data_directory, winGetDirSep(), zRelative);
5308353372
return SQLITE_OK;
5308453373
}
53374
+#endif
5308553375
zConverted = winConvertFromUtf8Filename(zRelative);
5308653376
if( zConverted==0 ){
5308753377
return SQLITE_IOERR_NOMEM_BKPT;
5308853378
}
5308953379
if( osIsNT() ){
@@ -53092,16 +53382,17 @@
5309253382
if( nByte==0 ){
5309353383
sqlite3_free(zConverted);
5309453384
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
5309553385
"winFullPathname1", zRelative);
5309653386
}
53097
- zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) + 3*sizeof(zTemp[0]) );
53387
+ nByte += 3;
53388
+ zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) );
5309853389
if( zTemp==0 ){
5309953390
sqlite3_free(zConverted);
5310053391
return SQLITE_IOERR_NOMEM_BKPT;
5310153392
}
53102
- nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte+3, zTemp, 0);
53393
+ nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0);
5310353394
if( nByte==0 ){
5310453395
sqlite3_free(zConverted);
5310553396
sqlite3_free(zTemp);
5310653397
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
5310753398
"winFullPathname2", zRelative);
@@ -53135,11 +53426,30 @@
5313553426
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
5313653427
sqlite3_free(zTemp);
5313753428
}
5313853429
#endif
5313953430
if( zOut ){
53431
+#ifdef __CYGWIN__
53432
+ if( memcmp(zOut, "\\\\?\\", 4) ){
53433
+ sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
53434
+ }else if( memcmp(zOut+4, "UNC\\", 4) ){
53435
+ sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4);
53436
+ }else{
53437
+ char *p = zOut+6;
53438
+ *p = '\\';
53439
+ if( osGetcwd ){
53440
+ /* On Cygwin, UNC paths use forward slashes */
53441
+ while( *p ){
53442
+ if( *p=='\\' ) *p = '/';
53443
+ ++p;
53444
+ }
53445
+ }
53446
+ sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6);
53447
+ }
53448
+#else
5314053449
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
53450
+#endif /* __CYGWIN__ */
5314153451
sqlite3_free(zOut);
5314253452
return SQLITE_OK;
5314353453
}else{
5314453454
return SQLITE_IOERR_NOMEM_BKPT;
5314553455
}
@@ -53165,11 +53475,13 @@
5316553475
** Interfaces for opening a shared library, finding entry points
5316653476
** within the shared library, and closing the shared library.
5316753477
*/
5316853478
static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
5316953479
HANDLE h;
53170
-#if defined(__CYGWIN__)
53480
+#if 0 /* This doesn't work correctly at all! See:
53481
+ <https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
53482
+*/
5317153483
int nFull = pVfs->mxPathname+1;
5317253484
char *zFull = sqlite3MallocZero( nFull );
5317353485
void *zConverted = 0;
5317453486
if( zFull==0 ){
5317553487
OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
@@ -53532,11 +53844,11 @@
5353253844
};
5353353845
#endif
5353453846
5353553847
/* Double-check that the aSyscall[] array has been constructed
5353653848
** correctly. See ticket [bb3a86e890c8e96ab] */
53537
- assert( ArraySize(aSyscall)==82 );
53849
+ assert( ArraySize(aSyscall)==89 );
5353853850
5353953851
/* get memory map allocation granularity */
5354053852
memset(&winSysInfo, 0, sizeof(SYSTEM_INFO));
5354153853
#if SQLITE_OS_WINRT
5354253854
osGetNativeSystemInfo(&winSysInfo);
@@ -66508,14 +66820,12 @@
6650866820
s2 = aIn[1];
6650966821
}else{
6651066822
s1 = s2 = 0;
6651166823
}
6651266824
66513
- assert( nByte>=8 );
66514
- assert( (nByte&0x00000007)==0 );
66515
- assert( nByte<=65536 );
66516
- assert( nByte%4==0 );
66825
+ /* nByte is a multiple of 8 between 8 and 65536 */
66826
+ assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 );
6651766827
6651866828
if( !nativeCksum ){
6651966829
do {
6652066830
s1 += BYTESWAP32(aData[0]) + s2;
6652166831
s2 += BYTESWAP32(aData[1]) + s1;
@@ -147785,10 +148095,11 @@
147785148095
}
147786148096
147787148097
multi_select_end:
147788148098
pDest->iSdst = dest.iSdst;
147789148099
pDest->nSdst = dest.nSdst;
148100
+ pDest->iSDParm2 = dest.iSDParm2;
147790148101
if( pDelete ){
147791148102
sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
147792148103
}
147793148104
return rc;
147794148105
}
@@ -188065,10 +188376,17 @@
188065188376
******************************************************************************
188066188377
**
188067188378
*/
188068188379
#ifndef _FTSINT_H
188069188380
#define _FTSINT_H
188381
+
188382
+/* #include <assert.h> */
188383
+/* #include <stdlib.h> */
188384
+/* #include <stddef.h> */
188385
+/* #include <stdio.h> */
188386
+/* #include <string.h> */
188387
+/* #include <stdarg.h> */
188070188388
188071188389
#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
188072188390
# define NDEBUG 1
188073188391
#endif
188074188392
@@ -189017,16 +189335,10 @@
189017189335
189018189336
#if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE)
189019189337
# define SQLITE_CORE 1
189020189338
#endif
189021189339
189022
-/* #include <assert.h> */
189023
-/* #include <stdlib.h> */
189024
-/* #include <stddef.h> */
189025
-/* #include <stdio.h> */
189026
-/* #include <string.h> */
189027
-/* #include <stdarg.h> */
189028189340
189029189341
/* #include "fts3.h" */
189030189342
#ifndef SQLITE_CORE
189031189343
/* # include "sqlite3ext.h" */
189032189344
SQLITE_EXTENSION_INIT1
@@ -227533,12 +227845,12 @@
227533227845
){
227534227846
if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){
227535227847
/* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and
227536227848
** all subsequent pages to be deleted. */
227537227849
pTab->iDbTrunc = iDb;
227538
- pgno--;
227539
- pTab->pgnoTrunc = pgno;
227850
+ pTab->pgnoTrunc = pgno-1;
227851
+ pgno = 1;
227540227852
}else{
227541227853
zErr = "bad page value";
227542227854
goto update_fail;
227543227855
}
227544227856
}
@@ -241869,11 +242181,12 @@
241869242181
sqlite3Fts5ParseError(
241870242182
pParse, "expected integer, got \"%.*s\"", p->n, p->p
241871242183
);
241872242184
return;
241873242185
}
241874
- nNear = nNear * 10 + (p->p[i] - '0');
242186
+ if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0');
242187
+ /* ^^^^^^^^^^^^^^^--- Prevent integer overflow */
241875242188
}
241876242189
}else{
241877242190
nNear = FTS5_DEFAULT_NEARDIST;
241878242191
}
241879242192
pNear->nNear = nNear;
@@ -256775,11 +257088,11 @@
256775257088
int nArg, /* Number of args */
256776257089
sqlite3_value **apUnused /* Function arguments */
256777257090
){
256778257091
assert( nArg==0 );
256779257092
UNUSED_PARAM2(nArg, apUnused);
256780
- sqlite3_result_text(pCtx, "fts5: 2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185", -1, SQLITE_TRANSIENT);
257093
+ sqlite3_result_text(pCtx, "fts5: 2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85", -1, SQLITE_TRANSIENT);
256781257094
}
256782257095
256783257096
/*
256784257097
** Implementation of fts5_locale(LOCALE, TEXT) function.
256785257098
**
256786257099
--- 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 ** 18bda13e197e4b4ec7464b3e70012f71edc0 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.50.0"
469 #define SQLITE_VERSION_NUMBER 3050000
470 #define SQLITE_SOURCE_ID "2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -30270,10 +30270,12 @@
30270 */
30271 #include "windows.h"
30272
30273 #ifdef __CYGWIN__
30274 # include <sys/cygwin.h>
 
 
30275 # include <errno.h> /* amalgamator: dontcache */
30276 #endif
30277
30278 /*
30279 ** Determine if we are dealing with Windows NT.
@@ -47726,20 +47728,20 @@
47726 { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 },
47727 #else
47728 { "FileTimeToLocalFileTime", (SYSCALL)0, 0 },
47729 #endif
47730
47731 #define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \
47732 LPFILETIME))aSyscall[11].pCurrent)
47733
47734 #if SQLITE_OS_WINCE
47735 { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 },
47736 #else
47737 { "FileTimeToSystemTime", (SYSCALL)0, 0 },
47738 #endif
47739
47740 #define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \
47741 LPSYSTEMTIME))aSyscall[12].pCurrent)
47742
47743 { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 },
47744
47745 #define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent)
@@ -48015,11 +48017,11 @@
48015 { "LockFile", (SYSCALL)LockFile, 0 },
48016 #else
48017 { "LockFile", (SYSCALL)0, 0 },
48018 #endif
48019
48020 #ifndef osLockFile
48021 #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
48022 DWORD))aSyscall[47].pCurrent)
48023 #endif
48024
48025 #if !SQLITE_OS_WINCE
@@ -48079,20 +48081,20 @@
48079
48080 #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent)
48081
48082 { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 },
48083
48084 #define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \
48085 LPFILETIME))aSyscall[56].pCurrent)
48086
48087 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
48088 { "UnlockFile", (SYSCALL)UnlockFile, 0 },
48089 #else
48090 { "UnlockFile", (SYSCALL)0, 0 },
48091 #endif
48092
48093 #ifndef osUnlockFile
48094 #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
48095 DWORD))aSyscall[57].pCurrent)
48096 #endif
48097
48098 #if !SQLITE_OS_WINCE
@@ -48316,10 +48318,67 @@
48316 { "CancelIo", (SYSCALL)0, 0 },
48317 #endif
48318
48319 #define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent)
48320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48321 }; /* End of the overrideable system calls */
48322
48323 /*
48324 ** This is the xSetSystemCall() method of sqlite3_vfs for all of the
48325 ** "win32" VFSes. Return SQLITE_OK upon successfully updating the
@@ -48489,10 +48548,11 @@
48489 sqlite3_mutex_leave(pMainMtx);
48490 return rc;
48491 }
48492 #endif /* SQLITE_WIN32_MALLOC */
48493
 
48494 /*
48495 ** This function outputs the specified (ANSI) string to the Win32 debugger
48496 ** (if available).
48497 */
48498
@@ -48531,10 +48591,11 @@
48531 }else{
48532 fprintf(stderr, "%s", zBuf);
48533 }
48534 #endif
48535 }
 
48536
48537 /*
48538 ** The following routine suspends the current thread for at least ms
48539 ** milliseconds. This is equivalent to the Win32 Sleep() interface.
48540 */
@@ -48831,10 +48892,11 @@
48831 SQLITE_PRIVATE void sqlite3MemSetDefault(void){
48832 sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
48833 }
48834 #endif /* SQLITE_WIN32_MALLOC */
48835
 
48836 /*
48837 ** Convert a UTF-8 string to Microsoft Unicode.
48838 **
48839 ** Space to hold the returned string is obtained from sqlite3_malloc().
48840 */
@@ -48856,10 +48918,11 @@
48856 sqlite3_free(zWideText);
48857 zWideText = 0;
48858 }
48859 return zWideText;
48860 }
 
48861
48862 /*
48863 ** Convert a Microsoft Unicode string to UTF-8.
48864 **
48865 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48890,32 +48953,33 @@
48890 ** code page.
48891 **
48892 ** Space to hold the returned string is obtained from sqlite3_malloc().
48893 */
48894 static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
48895 int nByte;
48896 LPWSTR zMbcsText;
48897 int codepage = useAnsi ? CP_ACP : CP_OEMCP;
48898
48899 nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
48900 0)*sizeof(WCHAR);
48901 if( nByte==0 ){
48902 return 0;
48903 }
48904 zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) );
48905 if( zMbcsText==0 ){
48906 return 0;
48907 }
48908 nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
48909 nByte);
48910 if( nByte==0 ){
48911 sqlite3_free(zMbcsText);
48912 zMbcsText = 0;
48913 }
48914 return zMbcsText;
48915 }
48916
 
48917 /*
48918 ** Convert a Microsoft Unicode string to a multi-byte character string,
48919 ** using the ANSI or OEM code page.
48920 **
48921 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48939,10 +49003,11 @@
48939 sqlite3_free(zText);
48940 zText = 0;
48941 }
48942 return zText;
48943 }
 
48944
48945 /*
48946 ** Convert a multi-byte character string to UTF-8.
48947 **
48948 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48958,10 +49023,11 @@
48958 zTextUtf8 = winUnicodeToUtf8(zTmpWide);
48959 sqlite3_free(zTmpWide);
48960 return zTextUtf8;
48961 }
48962
 
48963 /*
48964 ** Convert a UTF-8 string to a multi-byte character string.
48965 **
48966 ** Space to hold the returned string is obtained from sqlite3_malloc().
48967 */
@@ -49007,10 +49073,11 @@
49007 #ifndef SQLITE_OMIT_AUTOINIT
49008 if( sqlite3_initialize() ) return 0;
49009 #endif
49010 return winUnicodeToUtf8(zWideText);
49011 }
 
49012
49013 /*
49014 ** This is a public wrapper for the winMbcsToUtf8() function.
49015 */
49016 SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
@@ -49024,10 +49091,11 @@
49024 if( sqlite3_initialize() ) return 0;
49025 #endif
49026 return winMbcsToUtf8(zText, osAreFileApisANSI());
49027 }
49028
 
49029 /*
49030 ** This is a public wrapper for the winMbcsToUtf8() function.
49031 */
49032 SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
49033 #ifdef SQLITE_ENABLE_API_ARMOR
@@ -49148,10 +49216,11 @@
49148 unsigned long type, /* Identifier for directory being set or reset */
49149 void *zValue /* New value for directory being set or reset */
49150 ){
49151 return sqlite3_win32_set_directory16(type, zValue);
49152 }
 
49153
49154 /*
49155 ** The return value of winGetLastErrorMsg
49156 ** is zero if the error message fits in the buffer, or non-zero
49157 ** otherwise (if the message was truncated).
@@ -49696,13 +49765,15 @@
49696 OVERLAPPED ovlp;
49697 memset(&ovlp, 0, sizeof(OVERLAPPED));
49698 ovlp.Offset = offsetLow;
49699 ovlp.OffsetHigh = offsetHigh;
49700 return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
 
49701 }else{
49702 return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
49703 numBytesHigh);
 
49704 }
49705 #endif
49706 }
49707
49708 /*
@@ -49806,13 +49877,15 @@
49806 OVERLAPPED ovlp;
49807 memset(&ovlp, 0, sizeof(OVERLAPPED));
49808 ovlp.Offset = offsetLow;
49809 ovlp.OffsetHigh = offsetHigh;
49810 return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp);
 
49811 }else{
49812 return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
49813 numBytesHigh);
 
49814 }
49815 #endif
49816 }
49817
49818 /*
@@ -51222,18 +51295,95 @@
51222
51223 /*
51224 ** Convert a UTF-8 filename into whatever form the underlying
51225 ** operating system wants filenames in. Space to hold the result
51226 ** is obtained from malloc and must be freed by the calling
51227 ** function.
 
 
 
 
 
 
 
 
 
 
 
 
 
51228 */
51229 static void *winConvertFromUtf8Filename(const char *zFilename){
51230 void *zConverted = 0;
51231 if( osIsNT() ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51232 zConverted = winUtf8ToUnicode(zFilename);
 
51233 }
51234 #ifdef SQLITE_WIN32_HAS_ANSI
51235 else{
51236 zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
51237 }
51238 #endif
51239 /* caller will handle out of memory */
@@ -52058,11 +52208,11 @@
52058 **
52059 ** This division contains the implementation of methods on the
52060 ** sqlite3_vfs object.
52061 */
52062
52063 #if defined(__CYGWIN__)
52064 /*
52065 ** Convert a filename from whatever the underlying operating system
52066 ** supports for filenames into UTF-8. Space to hold the result is
52067 ** obtained from malloc and must be freed by the calling function.
52068 */
@@ -52091,11 +52241,18 @@
52091 int nLen = sqlite3Strlen30(zBuf);
52092 if( nLen>0 ){
52093 if( winIsDirSep(zBuf[nLen-1]) ){
52094 return 1;
52095 }else if( nLen+1<nBuf ){
52096 zBuf[nLen] = winGetDirSep();
 
 
 
 
 
 
 
52097 zBuf[nLen+1] = '\0';
52098 return 1;
52099 }
52100 }
52101 }
@@ -52118,11 +52275,11 @@
52118 /*
52119 ** Create a temporary file name and store the resulting pointer into pzBuf.
52120 ** The pointer returned in pzBuf must be freed via sqlite3_free().
52121 */
52122 static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
52123 static char zChars[] =
52124 "abcdefghijklmnopqrstuvwxyz"
52125 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
52126 "0123456789";
52127 size_t i, j;
52128 DWORD pid;
@@ -52169,11 +52326,11 @@
52169 }
52170 sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
52171 }
52172
52173 #if defined(__CYGWIN__)
52174 else{
52175 static const char *azDirs[] = {
52176 0, /* getenv("SQLITE_TMPDIR") */
52177 0, /* getenv("TMPDIR") */
52178 0, /* getenv("TMP") */
52179 0, /* getenv("TEMP") */
@@ -52185,24 +52342,24 @@
52185 0 /* List terminator */
52186 };
52187 unsigned int i;
52188 const char *zDir = 0;
52189
52190 if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR");
52191 if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR");
52192 if( !azDirs[2] ) azDirs[2] = getenv("TMP");
52193 if( !azDirs[3] ) azDirs[3] = getenv("TEMP");
52194 if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE");
52195 for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){
52196 void *zConverted;
52197 if( zDir==0 ) continue;
52198 /* If the path starts with a drive letter followed by the colon
52199 ** character, assume it is already a native Win32 path; otherwise,
52200 ** it must be converted to a native Win32 path via the Cygwin API
52201 ** prior to using it.
52202 */
52203 if( winIsDriveLetterAndColon(zDir) ){
52204 zConverted = winConvertFromUtf8Filename(zDir);
52205 if( !zConverted ){
52206 sqlite3_free(zBuf);
52207 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
52208 return SQLITE_IOERR_NOMEM_BKPT;
@@ -52211,19 +52368,20 @@
52211 sqlite3_snprintf(nMax, zBuf, "%s", zDir);
52212 sqlite3_free(zConverted);
52213 break;
52214 }
52215 sqlite3_free(zConverted);
 
52216 }else{
52217 zConverted = sqlite3MallocZero( nMax+1 );
52218 if( !zConverted ){
52219 sqlite3_free(zBuf);
52220 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
52221 return SQLITE_IOERR_NOMEM_BKPT;
52222 }
52223 if( cygwin_conv_path(
52224 osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir,
52225 zConverted, nMax+1)<0 ){
52226 sqlite3_free(zConverted);
52227 sqlite3_free(zBuf);
52228 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n"));
52229 return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno,
@@ -52245,14 +52403,17 @@
52245 sqlite3_free(zUtf8);
52246 sqlite3_free(zConverted);
52247 break;
52248 }
52249 sqlite3_free(zConverted);
 
52250 }
52251 }
52252 }
52253 #elif !SQLITE_OS_WINRT && !defined(__CYGWIN__)
 
 
52254 else if( osIsNT() ){
52255 char *zMulti;
52256 LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) );
52257 if( !zWidePath ){
52258 sqlite3_free(zBuf);
@@ -52372,11 +52533,11 @@
52372 &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
52373 if( !rc ){
52374 return 0; /* Invalid name? */
52375 }
52376 attr = sAttrData.dwFileAttributes;
52377 #if SQLITE_OS_WINCE==0
52378 }else{
52379 attr = osGetFileAttributesA((char*)zConverted);
52380 #endif
52381 }
52382 return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY);
@@ -52388,10 +52549,16 @@
52388 const char *zFilename, /* Name of file to check */
52389 int flags, /* Type of test to make on this file */
52390 int *pResOut /* OUT: Result */
52391 );
52392
 
 
 
 
 
 
52393 /*
52394 ** Open a file.
52395 */
52396 static int winOpen(
52397 sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
@@ -52412,10 +52579,11 @@
52412 winVfsAppData *pAppData;
52413 winFile *pFile = (winFile*)id;
52414 void *zConverted; /* Filename in OS encoding */
52415 const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
52416 int cnt = 0;
 
52417
52418 /* If argument zPath is a NULL pointer, this function is required to open
52419 ** a temporary file. Use this buffer to store the file name in.
52420 */
52421 char *zTmpname = 0; /* For temporary filename, if necessary. */
@@ -52576,13 +52744,13 @@
52576 dwShareMode,
52577 dwCreationDisposition,
52578 &extendedParameters);
52579 if( h!=INVALID_HANDLE_VALUE ) break;
52580 if( isReadWrite ){
52581 int rc2, isRO = 0;
52582 sqlite3BeginBenignMalloc();
52583 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52584 sqlite3EndBenignMalloc();
52585 if( rc2==SQLITE_OK && isRO ) break;
52586 }
52587 }while( winRetryIoerr(&cnt, &lastErrno) );
52588 #else
@@ -52593,13 +52761,13 @@
52593 dwCreationDisposition,
52594 dwFlagsAndAttributes,
52595 NULL);
52596 if( h!=INVALID_HANDLE_VALUE ) break;
52597 if( isReadWrite ){
52598 int rc2, isRO = 0;
52599 sqlite3BeginBenignMalloc();
52600 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52601 sqlite3EndBenignMalloc();
52602 if( rc2==SQLITE_OK && isRO ) break;
52603 }
52604 }while( winRetryIoerr(&cnt, &lastErrno) );
52605 #endif
@@ -52613,13 +52781,13 @@
52613 dwCreationDisposition,
52614 dwFlagsAndAttributes,
52615 NULL);
52616 if( h!=INVALID_HANDLE_VALUE ) break;
52617 if( isReadWrite ){
52618 int rc2, isRO = 0;
52619 sqlite3BeginBenignMalloc();
52620 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO);
52621 sqlite3EndBenignMalloc();
52622 if( rc2==SQLITE_OK && isRO ) break;
52623 }
52624 }while( winRetryIoerr(&cnt, &lastErrno) );
52625 }
@@ -52630,11 +52798,11 @@
52630 dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok"));
52631
52632 if( h==INVALID_HANDLE_VALUE ){
52633 sqlite3_free(zConverted);
52634 sqlite3_free(zTmpname);
52635 if( isReadWrite && !isExclusive ){
52636 return winOpen(pVfs, zName, id,
52637 ((flags|SQLITE_OPEN_READONLY) &
52638 ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)),
52639 pOutFlags);
52640 }else{
@@ -52832,11 +53000,17 @@
52832 ){
52833 DWORD attr;
52834 int rc = 0;
52835 DWORD lastErrno = 0;
52836 void *zConverted;
 
52837 UNUSED_PARAMETER(pVfs);
 
 
 
 
 
52838
52839 SimulateIOError( return SQLITE_IOERR_ACCESS; );
52840 OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n",
52841 zFilename, flags, pResOut));
52842
@@ -52856,11 +53030,14 @@
52856 int cnt = 0;
52857 WIN32_FILE_ATTRIBUTE_DATA sAttrData;
52858 memset(&sAttrData, 0, sizeof(sAttrData));
52859 while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
52860 GetFileExInfoStandard,
52861 &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
 
 
 
52862 if( rc ){
52863 /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file
52864 ** as if it does not exist.
52865 */
52866 if( flags==SQLITE_ACCESS_EXISTS
@@ -52924,10 +53101,11 @@
52924 const char *zPathname
52925 ){
52926 return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
52927 }
52928
 
52929 /*
52930 ** Returns non-zero if the specified path name should be used verbatim. If
52931 ** non-zero is returned from this function, the calling function must simply
52932 ** use the provided path name verbatim -OR- resolve it into a full path name
52933 ** using the GetFullPathName Win32 API function (if available).
@@ -52960,10 +53138,74 @@
52960 ** If we get to this point, the path name should almost certainly be a purely
52961 ** relative one (i.e. not a UNC name, not absolute, and not volume relative).
52962 */
52963 return FALSE;
52964 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52965
52966 /*
52967 ** Turn a relative pathname into a full pathname. Write the full
52968 ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
52969 ** bytes in size.
@@ -52972,12 +53214,12 @@
52972 sqlite3_vfs *pVfs, /* Pointer to vfs object */
52973 const char *zRelative, /* Possibly relative input path */
52974 int nFull, /* Size of output buffer in bytes */
52975 char *zFull /* Output buffer */
52976 ){
52977 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__)
52978 DWORD nByte;
52979 void *zConverted;
52980 char *zOut;
52981 #endif
52982
52983 /* If this path name begins with "/X:" or "\\?\", where "X" is any
@@ -52986,68 +53228,114 @@
52986 if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1)
52987 || winIsLongPathPrefix(zRelative+1)) ){
52988 zRelative++;
52989 }
52990
52991 #if defined(__CYGWIN__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52992 SimulateIOError( return SQLITE_ERROR );
52993 UNUSED_PARAMETER(nFull);
52994 assert( nFull>=pVfs->mxPathname );
52995 if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
52996 /*
52997 ** NOTE: We are dealing with a relative path name and the data
52998 ** directory has been set. Therefore, use it as the basis
52999 ** for converting the relative path name to an absolute
53000 ** one by prepending the data directory and a slash.
53001 */
53002 char *zOut = sqlite3MallocZero( 1+(u64)pVfs->mxPathname );
53003 if( !zOut ){
53004 return SQLITE_IOERR_NOMEM_BKPT;
53005 }
53006 if( cygwin_conv_path(
53007 (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) |
53008 CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){
53009 sqlite3_free(zOut);
53010 return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53011 "winFullPathname1", zRelative);
53012 }else{
53013 char *zUtf8 = winConvertToUtf8Filename(zOut);
53014 if( !zUtf8 ){
53015 sqlite3_free(zOut);
53016 return SQLITE_IOERR_NOMEM_BKPT;
53017 }
53018 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
53019 sqlite3_data_directory, winGetDirSep(), zUtf8);
53020 sqlite3_free(zUtf8);
53021 sqlite3_free(zOut);
53022 }
53023 }else{
53024 char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
53025 if( !zOut ){
53026 return SQLITE_IOERR_NOMEM_BKPT;
53027 }
53028 if( cygwin_conv_path(
53029 (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A),
53030 zRelative, zOut, pVfs->mxPathname+1)<0 ){
53031 sqlite3_free(zOut);
53032 return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53033 "winFullPathname2", zRelative);
53034 }else{
53035 char *zUtf8 = winConvertToUtf8Filename(zOut);
53036 if( !zUtf8 ){
53037 sqlite3_free(zOut);
53038 return SQLITE_IOERR_NOMEM_BKPT;
53039 }
53040 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
53041 sqlite3_free(zUtf8);
53042 sqlite3_free(zOut);
53043 }
53044 }
53045 return SQLITE_OK;
53046 #endif
53047
53048 #if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__)
53049 SimulateIOError( return SQLITE_ERROR );
53050 /* WinCE has no concept of a relative pathname, or so I am told. */
53051 /* WinRT has no way to convert a relative path to an absolute one. */
53052 if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
53053 /*
@@ -53062,11 +53350,12 @@
53062 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
53063 }
53064 return SQLITE_OK;
53065 #endif
53066
53067 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__)
 
53068 /* It's odd to simulate an io-error here, but really this is just
53069 ** using the io-error infrastructure to test that SQLite handles this
53070 ** function failing. This function could fail if, for example, the
53071 ** current working directory has been unlinked.
53072 */
@@ -53080,10 +53369,11 @@
53080 */
53081 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
53082 sqlite3_data_directory, winGetDirSep(), zRelative);
53083 return SQLITE_OK;
53084 }
 
53085 zConverted = winConvertFromUtf8Filename(zRelative);
53086 if( zConverted==0 ){
53087 return SQLITE_IOERR_NOMEM_BKPT;
53088 }
53089 if( osIsNT() ){
@@ -53092,16 +53382,17 @@
53092 if( nByte==0 ){
53093 sqlite3_free(zConverted);
53094 return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
53095 "winFullPathname1", zRelative);
53096 }
53097 zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) + 3*sizeof(zTemp[0]) );
 
53098 if( zTemp==0 ){
53099 sqlite3_free(zConverted);
53100 return SQLITE_IOERR_NOMEM_BKPT;
53101 }
53102 nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte+3, zTemp, 0);
53103 if( nByte==0 ){
53104 sqlite3_free(zConverted);
53105 sqlite3_free(zTemp);
53106 return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
53107 "winFullPathname2", zRelative);
@@ -53135,11 +53426,30 @@
53135 zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
53136 sqlite3_free(zTemp);
53137 }
53138 #endif
53139 if( zOut ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53140 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
 
53141 sqlite3_free(zOut);
53142 return SQLITE_OK;
53143 }else{
53144 return SQLITE_IOERR_NOMEM_BKPT;
53145 }
@@ -53165,11 +53475,13 @@
53165 ** Interfaces for opening a shared library, finding entry points
53166 ** within the shared library, and closing the shared library.
53167 */
53168 static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
53169 HANDLE h;
53170 #if defined(__CYGWIN__)
 
 
53171 int nFull = pVfs->mxPathname+1;
53172 char *zFull = sqlite3MallocZero( nFull );
53173 void *zConverted = 0;
53174 if( zFull==0 ){
53175 OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
@@ -53532,11 +53844,11 @@
53532 };
53533 #endif
53534
53535 /* Double-check that the aSyscall[] array has been constructed
53536 ** correctly. See ticket [bb3a86e890c8e96ab] */
53537 assert( ArraySize(aSyscall)==82 );
53538
53539 /* get memory map allocation granularity */
53540 memset(&winSysInfo, 0, sizeof(SYSTEM_INFO));
53541 #if SQLITE_OS_WINRT
53542 osGetNativeSystemInfo(&winSysInfo);
@@ -66508,14 +66820,12 @@
66508 s2 = aIn[1];
66509 }else{
66510 s1 = s2 = 0;
66511 }
66512
66513 assert( nByte>=8 );
66514 assert( (nByte&0x00000007)==0 );
66515 assert( nByte<=65536 );
66516 assert( nByte%4==0 );
66517
66518 if( !nativeCksum ){
66519 do {
66520 s1 += BYTESWAP32(aData[0]) + s2;
66521 s2 += BYTESWAP32(aData[1]) + s1;
@@ -147785,10 +148095,11 @@
147785 }
147786
147787 multi_select_end:
147788 pDest->iSdst = dest.iSdst;
147789 pDest->nSdst = dest.nSdst;
 
147790 if( pDelete ){
147791 sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
147792 }
147793 return rc;
147794 }
@@ -188065,10 +188376,17 @@
188065 ******************************************************************************
188066 **
188067 */
188068 #ifndef _FTSINT_H
188069 #define _FTSINT_H
 
 
 
 
 
 
 
188070
188071 #if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
188072 # define NDEBUG 1
188073 #endif
188074
@@ -189017,16 +189335,10 @@
189017
189018 #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE)
189019 # define SQLITE_CORE 1
189020 #endif
189021
189022 /* #include <assert.h> */
189023 /* #include <stdlib.h> */
189024 /* #include <stddef.h> */
189025 /* #include <stdio.h> */
189026 /* #include <string.h> */
189027 /* #include <stdarg.h> */
189028
189029 /* #include "fts3.h" */
189030 #ifndef SQLITE_CORE
189031 /* # include "sqlite3ext.h" */
189032 SQLITE_EXTENSION_INIT1
@@ -227533,12 +227845,12 @@
227533 ){
227534 if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){
227535 /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and
227536 ** all subsequent pages to be deleted. */
227537 pTab->iDbTrunc = iDb;
227538 pgno--;
227539 pTab->pgnoTrunc = pgno;
227540 }else{
227541 zErr = "bad page value";
227542 goto update_fail;
227543 }
227544 }
@@ -241869,11 +242181,12 @@
241869 sqlite3Fts5ParseError(
241870 pParse, "expected integer, got \"%.*s\"", p->n, p->p
241871 );
241872 return;
241873 }
241874 nNear = nNear * 10 + (p->p[i] - '0');
 
241875 }
241876 }else{
241877 nNear = FTS5_DEFAULT_NEARDIST;
241878 }
241879 pNear->nNear = nNear;
@@ -256775,11 +257088,11 @@
256775 int nArg, /* Number of args */
256776 sqlite3_value **apUnused /* Function arguments */
256777 ){
256778 assert( nArg==0 );
256779 UNUSED_PARAM2(nArg, apUnused);
256780 sqlite3_result_text(pCtx, "fts5: 2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185", -1, SQLITE_TRANSIENT);
256781 }
256782
256783 /*
256784 ** Implementation of fts5_locale(LOCALE, TEXT) function.
256785 **
256786
--- 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 ** 121f4d97f9a855131859d342bc2ade5f8c34 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.50.0"
469 #define SQLITE_VERSION_NUMBER 3050000
470 #define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -30270,10 +30270,12 @@
30270 */
30271 #include "windows.h"
30272
30273 #ifdef __CYGWIN__
30274 # include <sys/cygwin.h>
30275 # include <sys/stat.h> /* amalgamator: dontcache */
30276 # include <unistd.h> /* amalgamator: dontcache */
30277 # include <errno.h> /* amalgamator: dontcache */
30278 #endif
30279
30280 /*
30281 ** Determine if we are dealing with Windows NT.
@@ -47726,20 +47728,20 @@
47728 { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 },
47729 #else
47730 { "FileTimeToLocalFileTime", (SYSCALL)0, 0 },
47731 #endif
47732
47733 #define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \
47734 LPFILETIME))aSyscall[11].pCurrent)
47735
47736 #if SQLITE_OS_WINCE
47737 { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 },
47738 #else
47739 { "FileTimeToSystemTime", (SYSCALL)0, 0 },
47740 #endif
47741
47742 #define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \
47743 LPSYSTEMTIME))aSyscall[12].pCurrent)
47744
47745 { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 },
47746
47747 #define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent)
@@ -48015,11 +48017,11 @@
48017 { "LockFile", (SYSCALL)LockFile, 0 },
48018 #else
48019 { "LockFile", (SYSCALL)0, 0 },
48020 #endif
48021
48022 #if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI)
48023 #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
48024 DWORD))aSyscall[47].pCurrent)
48025 #endif
48026
48027 #if !SQLITE_OS_WINCE
@@ -48079,20 +48081,20 @@
48081
48082 #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent)
48083
48084 { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 },
48085
48086 #define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \
48087 LPFILETIME))aSyscall[56].pCurrent)
48088
48089 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
48090 { "UnlockFile", (SYSCALL)UnlockFile, 0 },
48091 #else
48092 { "UnlockFile", (SYSCALL)0, 0 },
48093 #endif
48094
48095 #if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI)
48096 #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
48097 DWORD))aSyscall[57].pCurrent)
48098 #endif
48099
48100 #if !SQLITE_OS_WINCE
@@ -48316,10 +48318,67 @@
48318 { "CancelIo", (SYSCALL)0, 0 },
48319 #endif
48320
48321 #define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent)
48322
48323 #if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32)
48324 { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 },
48325 #else
48326 { "GetModuleHandleW", (SYSCALL)0, 0 },
48327 #endif
48328
48329 #define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent)
48330
48331 #ifndef _WIN32
48332 { "getenv", (SYSCALL)getenv, 0 },
48333 #else
48334 { "getenv", (SYSCALL)0, 0 },
48335 #endif
48336
48337 #define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent)
48338
48339 #ifndef _WIN32
48340 { "getcwd", (SYSCALL)getcwd, 0 },
48341 #else
48342 { "getcwd", (SYSCALL)0, 0 },
48343 #endif
48344
48345 #define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent)
48346
48347 #ifndef _WIN32
48348 { "readlink", (SYSCALL)readlink, 0 },
48349 #else
48350 { "readlink", (SYSCALL)0, 0 },
48351 #endif
48352
48353 #define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent)
48354
48355 #ifndef _WIN32
48356 { "lstat", (SYSCALL)lstat, 0 },
48357 #else
48358 { "lstat", (SYSCALL)0, 0 },
48359 #endif
48360
48361 #define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent)
48362
48363 #ifndef _WIN32
48364 { "__errno", (SYSCALL)__errno, 0 },
48365 #else
48366 { "__errno", (SYSCALL)0, 0 },
48367 #endif
48368
48369 #define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)())
48370
48371 #ifndef _WIN32
48372 { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 },
48373 #else
48374 { "cygwin_conv_path", (SYSCALL)0, 0 },
48375 #endif
48376
48377 #define osCygwin_conv_path ((size_t(*)(unsigned int, \
48378 const void *, void *, size_t))aSyscall[88].pCurrent)
48379
48380 }; /* End of the overrideable system calls */
48381
48382 /*
48383 ** This is the xSetSystemCall() method of sqlite3_vfs for all of the
48384 ** "win32" VFSes. Return SQLITE_OK upon successfully updating the
@@ -48489,10 +48548,11 @@
48548 sqlite3_mutex_leave(pMainMtx);
48549 return rc;
48550 }
48551 #endif /* SQLITE_WIN32_MALLOC */
48552
48553 #ifdef _WIN32
48554 /*
48555 ** This function outputs the specified (ANSI) string to the Win32 debugger
48556 ** (if available).
48557 */
48558
@@ -48531,10 +48591,11 @@
48591 }else{
48592 fprintf(stderr, "%s", zBuf);
48593 }
48594 #endif
48595 }
48596 #endif /* _WIN32 */
48597
48598 /*
48599 ** The following routine suspends the current thread for at least ms
48600 ** milliseconds. This is equivalent to the Win32 Sleep() interface.
48601 */
@@ -48831,10 +48892,11 @@
48892 SQLITE_PRIVATE void sqlite3MemSetDefault(void){
48893 sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
48894 }
48895 #endif /* SQLITE_WIN32_MALLOC */
48896
48897 #ifdef _WIN32
48898 /*
48899 ** Convert a UTF-8 string to Microsoft Unicode.
48900 **
48901 ** Space to hold the returned string is obtained from sqlite3_malloc().
48902 */
@@ -48856,10 +48918,11 @@
48918 sqlite3_free(zWideText);
48919 zWideText = 0;
48920 }
48921 return zWideText;
48922 }
48923 #endif /* _WIN32 */
48924
48925 /*
48926 ** Convert a Microsoft Unicode string to UTF-8.
48927 **
48928 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48890,32 +48953,33 @@
48953 ** code page.
48954 **
48955 ** Space to hold the returned string is obtained from sqlite3_malloc().
48956 */
48957 static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
48958 int nWideChar;
48959 LPWSTR zMbcsText;
48960 int codepage = useAnsi ? CP_ACP : CP_OEMCP;
48961
48962 nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
48963 0);
48964 if( nWideChar==0 ){
48965 return 0;
48966 }
48967 zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) );
48968 if( zMbcsText==0 ){
48969 return 0;
48970 }
48971 nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
48972 nWideChar);
48973 if( nWideChar==0 ){
48974 sqlite3_free(zMbcsText);
48975 zMbcsText = 0;
48976 }
48977 return zMbcsText;
48978 }
48979
48980 #ifdef _WIN32
48981 /*
48982 ** Convert a Microsoft Unicode string to a multi-byte character string,
48983 ** using the ANSI or OEM code page.
48984 **
48985 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48939,10 +49003,11 @@
49003 sqlite3_free(zText);
49004 zText = 0;
49005 }
49006 return zText;
49007 }
49008 #endif /* _WIN32 */
49009
49010 /*
49011 ** Convert a multi-byte character string to UTF-8.
49012 **
49013 ** Space to hold the returned string is obtained from sqlite3_malloc().
@@ -48958,10 +49023,11 @@
49023 zTextUtf8 = winUnicodeToUtf8(zTmpWide);
49024 sqlite3_free(zTmpWide);
49025 return zTextUtf8;
49026 }
49027
49028 #ifdef _WIN32
49029 /*
49030 ** Convert a UTF-8 string to a multi-byte character string.
49031 **
49032 ** Space to hold the returned string is obtained from sqlite3_malloc().
49033 */
@@ -49007,10 +49073,11 @@
49073 #ifndef SQLITE_OMIT_AUTOINIT
49074 if( sqlite3_initialize() ) return 0;
49075 #endif
49076 return winUnicodeToUtf8(zWideText);
49077 }
49078 #endif /* _WIN32 */
49079
49080 /*
49081 ** This is a public wrapper for the winMbcsToUtf8() function.
49082 */
49083 SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
@@ -49024,10 +49091,11 @@
49091 if( sqlite3_initialize() ) return 0;
49092 #endif
49093 return winMbcsToUtf8(zText, osAreFileApisANSI());
49094 }
49095
49096 #ifdef _WIN32
49097 /*
49098 ** This is a public wrapper for the winMbcsToUtf8() function.
49099 */
49100 SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
49101 #ifdef SQLITE_ENABLE_API_ARMOR
@@ -49148,10 +49216,11 @@
49216 unsigned long type, /* Identifier for directory being set or reset */
49217 void *zValue /* New value for directory being set or reset */
49218 ){
49219 return sqlite3_win32_set_directory16(type, zValue);
49220 }
49221 #endif /* _WIN32 */
49222
49223 /*
49224 ** The return value of winGetLastErrorMsg
49225 ** is zero if the error message fits in the buffer, or non-zero
49226 ** otherwise (if the message was truncated).
@@ -49696,13 +49765,15 @@
49765 OVERLAPPED ovlp;
49766 memset(&ovlp, 0, sizeof(OVERLAPPED));
49767 ovlp.Offset = offsetLow;
49768 ovlp.OffsetHigh = offsetHigh;
49769 return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
49770 #ifdef SQLITE_WIN32_HAS_ANSI
49771 }else{
49772 return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
49773 numBytesHigh);
49774 #endif
49775 }
49776 #endif
49777 }
49778
49779 /*
@@ -49806,13 +49877,15 @@
49877 OVERLAPPED ovlp;
49878 memset(&ovlp, 0, sizeof(OVERLAPPED));
49879 ovlp.Offset = offsetLow;
49880 ovlp.OffsetHigh = offsetHigh;
49881 return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp);
49882 #ifdef SQLITE_WIN32_HAS_ANSI
49883 }else{
49884 return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
49885 numBytesHigh);
49886 #endif
49887 }
49888 #endif
49889 }
49890
49891 /*
@@ -51222,18 +51295,95 @@
51295
51296 /*
51297 ** Convert a UTF-8 filename into whatever form the underlying
51298 ** operating system wants filenames in. Space to hold the result
51299 ** is obtained from malloc and must be freed by the calling
51300 ** function
51301 **
51302 ** On Cygwin, 3 possible input forms are accepted:
51303 ** - If the filename starts with "<drive>:/" or "<drive>:\",
51304 ** it is converted to UTF-16 as-is.
51305 ** - If the filename contains '/', it is assumed to be a
51306 ** Cygwin absolute path, it is converted to a win32
51307 ** absolute path in UTF-16.
51308 ** - Otherwise it must be a filename only, the win32 filename
51309 ** is returned in UTF-16.
51310 ** Note: If the function cygwin_conv_path() fails, only
51311 ** UTF-8 -> UTF-16 conversion will be done. This can only
51312 ** happen when the file path >32k, in which case winUtf8ToUnicode()
51313 ** will fail too.
51314 */
51315 static void *winConvertFromUtf8Filename(const char *zFilename){
51316 void *zConverted = 0;
51317 if( osIsNT() ){
51318 #ifdef __CYGWIN__
51319 int nChar;
51320 LPWSTR zWideFilename;
51321
51322 if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename)
51323 && winIsDirSep(zFilename[2])) ){
51324 int nByte;
51325 int convertflag = CCP_POSIX_TO_WIN_W;
51326 if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE;
51327 nByte = (int)osCygwin_conv_path(convertflag,
51328 zFilename, 0, 0);
51329 if( nByte>0 ){
51330 zConverted = sqlite3MallocZero(nByte+12);
51331 if ( zConverted==0 ){
51332 return zConverted;
51333 }
51334 zWideFilename = zConverted;
51335 /* Filenames should be prefixed, except when converted
51336 * full path already starts with "\\?\". */
51337 if( osCygwin_conv_path(convertflag, zFilename,
51338 zWideFilename+4, nByte)==0 ){
51339 if( (convertflag&CCP_RELATIVE) ){
51340 memmove(zWideFilename, zWideFilename+4, nByte);
51341 }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){
51342 memcpy(zWideFilename, L"\\\\?\\", 8);
51343 }else if( zWideFilename[6]!='?' ){
51344 memmove(zWideFilename+6, zWideFilename+4, nByte);
51345 memcpy(zWideFilename, L"\\\\?\\UNC", 14);
51346 }else{
51347 memmove(zWideFilename, zWideFilename+4, nByte);
51348 }
51349 return zConverted;
51350 }
51351 sqlite3_free(zConverted);
51352 }
51353 }
51354 nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
51355 if( nChar==0 ){
51356 return 0;
51357 }
51358 zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 );
51359 if( zWideFilename==0 ){
51360 return 0;
51361 }
51362 nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1,
51363 zWideFilename, nChar);
51364 if( nChar==0 ){
51365 sqlite3_free(zWideFilename);
51366 zWideFilename = 0;
51367 }else if( nChar>MAX_PATH
51368 && winIsDriveLetterAndColon(zFilename)
51369 && winIsDirSep(zFilename[2]) ){
51370 memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR));
51371 zWideFilename[2] = '\\';
51372 memcpy(zWideFilename, L"\\\\?\\", 8);
51373 }else if( nChar>MAX_PATH
51374 && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1])
51375 && zFilename[2] != '?' ){
51376 memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR));
51377 memcpy(zWideFilename, L"\\\\?\\UNC", 14);
51378 }
51379 zConverted = zWideFilename;
51380 #else
51381 zConverted = winUtf8ToUnicode(zFilename);
51382 #endif /* __CYGWIN__ */
51383 }
51384 #if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32)
51385 else{
51386 zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
51387 }
51388 #endif
51389 /* caller will handle out of memory */
@@ -52058,11 +52208,11 @@
52208 **
52209 ** This division contains the implementation of methods on the
52210 ** sqlite3_vfs object.
52211 */
52212
52213 #if 0 /* No longer necessary */
52214 /*
52215 ** Convert a filename from whatever the underlying operating system
52216 ** supports for filenames into UTF-8. Space to hold the result is
52217 ** obtained from malloc and must be freed by the calling function.
52218 */
@@ -52091,11 +52241,18 @@
52241 int nLen = sqlite3Strlen30(zBuf);
52242 if( nLen>0 ){
52243 if( winIsDirSep(zBuf[nLen-1]) ){
52244 return 1;
52245 }else if( nLen+1<nBuf ){
52246 if( !osGetenv ){
52247 zBuf[nLen] = winGetDirSep();
52248 }else if( winIsDriveLetterAndColon(zBuf) && winIsDirSep(zBuf[2]) ){
52249 zBuf[nLen] = '\\';
52250 zBuf[2]='\\';
52251 }else{
52252 zBuf[nLen] = '/';
52253 }
52254 zBuf[nLen+1] = '\0';
52255 return 1;
52256 }
52257 }
52258 }
@@ -52118,11 +52275,11 @@
52275 /*
52276 ** Create a temporary file name and store the resulting pointer into pzBuf.
52277 ** The pointer returned in pzBuf must be freed via sqlite3_free().
52278 */
52279 static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
52280 static const char zChars[] =
52281 "abcdefghijklmnopqrstuvwxyz"
52282 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
52283 "0123456789";
52284 size_t i, j;
52285 DWORD pid;
@@ -52169,11 +52326,11 @@
52326 }
52327 sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
52328 }
52329
52330 #if defined(__CYGWIN__)
52331 else if( osGetenv!=NULL ){
52332 static const char *azDirs[] = {
52333 0, /* getenv("SQLITE_TMPDIR") */
52334 0, /* getenv("TMPDIR") */
52335 0, /* getenv("TMP") */
52336 0, /* getenv("TEMP") */
@@ -52185,24 +52342,24 @@
52342 0 /* List terminator */
52343 };
52344 unsigned int i;
52345 const char *zDir = 0;
52346
52347 if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR");
52348 if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR");
52349 if( !azDirs[2] ) azDirs[2] = osGetenv("TMP");
52350 if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP");
52351 if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE");
52352 for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){
52353 void *zConverted;
52354 if( zDir==0 ) continue;
52355 /* If the path starts with a drive letter followed by the colon
52356 ** character, assume it is already a native Win32 path; otherwise,
52357 ** it must be converted to a native Win32 path via the Cygwin API
52358 ** prior to using it.
52359 */
52360 {
52361 zConverted = winConvertFromUtf8Filename(zDir);
52362 if( !zConverted ){
52363 sqlite3_free(zBuf);
52364 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
52365 return SQLITE_IOERR_NOMEM_BKPT;
@@ -52211,19 +52368,20 @@
52368 sqlite3_snprintf(nMax, zBuf, "%s", zDir);
52369 sqlite3_free(zConverted);
52370 break;
52371 }
52372 sqlite3_free(zConverted);
52373 #if 0 /* No longer necessary */
52374 }else{
52375 zConverted = sqlite3MallocZero( nMax+1 );
52376 if( !zConverted ){
52377 sqlite3_free(zBuf);
52378 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
52379 return SQLITE_IOERR_NOMEM_BKPT;
52380 }
52381 if( osCygwin_conv_path(
52382 CCP_POSIX_TO_WIN_W, zDir,
52383 zConverted, nMax+1)<0 ){
52384 sqlite3_free(zConverted);
52385 sqlite3_free(zBuf);
52386 OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n"));
52387 return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno,
@@ -52245,14 +52403,17 @@
52403 sqlite3_free(zUtf8);
52404 sqlite3_free(zConverted);
52405 break;
52406 }
52407 sqlite3_free(zConverted);
52408 #endif /* No longer necessary */
52409 }
52410 }
52411 }
52412 #endif
52413
52414 #if !SQLITE_OS_WINRT && defined(_WIN32)
52415 else if( osIsNT() ){
52416 char *zMulti;
52417 LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) );
52418 if( !zWidePath ){
52419 sqlite3_free(zBuf);
@@ -52372,11 +52533,11 @@
52533 &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
52534 if( !rc ){
52535 return 0; /* Invalid name? */
52536 }
52537 attr = sAttrData.dwFileAttributes;
52538 #if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI)
52539 }else{
52540 attr = osGetFileAttributesA((char*)zConverted);
52541 #endif
52542 }
52543 return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY);
@@ -52388,10 +52549,16 @@
52549 const char *zFilename, /* Name of file to check */
52550 int flags, /* Type of test to make on this file */
52551 int *pResOut /* OUT: Result */
52552 );
52553
52554 /*
52555 ** The Windows version of xAccess() accepts an extra bit in the flags
52556 ** parameter that prevents an anti-virus retry loop.
52557 */
52558 #define NORETRY 0x4000
52559
52560 /*
52561 ** Open a file.
52562 */
52563 static int winOpen(
52564 sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
@@ -52412,10 +52579,11 @@
52579 winVfsAppData *pAppData;
52580 winFile *pFile = (winFile*)id;
52581 void *zConverted; /* Filename in OS encoding */
52582 const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
52583 int cnt = 0;
52584 int isRO = 0; /* file is known to be accessible readonly */
52585
52586 /* If argument zPath is a NULL pointer, this function is required to open
52587 ** a temporary file. Use this buffer to store the file name in.
52588 */
52589 char *zTmpname = 0; /* For temporary filename, if necessary. */
@@ -52576,13 +52744,13 @@
52744 dwShareMode,
52745 dwCreationDisposition,
52746 &extendedParameters);
52747 if( h!=INVALID_HANDLE_VALUE ) break;
52748 if( isReadWrite ){
52749 int rc2;
52750 sqlite3BeginBenignMalloc();
52751 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
52752 sqlite3EndBenignMalloc();
52753 if( rc2==SQLITE_OK && isRO ) break;
52754 }
52755 }while( winRetryIoerr(&cnt, &lastErrno) );
52756 #else
@@ -52593,13 +52761,13 @@
52761 dwCreationDisposition,
52762 dwFlagsAndAttributes,
52763 NULL);
52764 if( h!=INVALID_HANDLE_VALUE ) break;
52765 if( isReadWrite ){
52766 int rc2;
52767 sqlite3BeginBenignMalloc();
52768 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
52769 sqlite3EndBenignMalloc();
52770 if( rc2==SQLITE_OK && isRO ) break;
52771 }
52772 }while( winRetryIoerr(&cnt, &lastErrno) );
52773 #endif
@@ -52613,13 +52781,13 @@
52781 dwCreationDisposition,
52782 dwFlagsAndAttributes,
52783 NULL);
52784 if( h!=INVALID_HANDLE_VALUE ) break;
52785 if( isReadWrite ){
52786 int rc2;
52787 sqlite3BeginBenignMalloc();
52788 rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
52789 sqlite3EndBenignMalloc();
52790 if( rc2==SQLITE_OK && isRO ) break;
52791 }
52792 }while( winRetryIoerr(&cnt, &lastErrno) );
52793 }
@@ -52630,11 +52798,11 @@
52798 dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok"));
52799
52800 if( h==INVALID_HANDLE_VALUE ){
52801 sqlite3_free(zConverted);
52802 sqlite3_free(zTmpname);
52803 if( isReadWrite && isRO && !isExclusive ){
52804 return winOpen(pVfs, zName, id,
52805 ((flags|SQLITE_OPEN_READONLY) &
52806 ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)),
52807 pOutFlags);
52808 }else{
@@ -52832,11 +53000,17 @@
53000 ){
53001 DWORD attr;
53002 int rc = 0;
53003 DWORD lastErrno = 0;
53004 void *zConverted;
53005 int noRetry = 0; /* Do not use winRetryIoerr() */
53006 UNUSED_PARAMETER(pVfs);
53007
53008 if( (flags & NORETRY)!=0 ){
53009 noRetry = 1;
53010 flags &= ~NORETRY;
53011 }
53012
53013 SimulateIOError( return SQLITE_IOERR_ACCESS; );
53014 OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n",
53015 zFilename, flags, pResOut));
53016
@@ -52856,11 +53030,14 @@
53030 int cnt = 0;
53031 WIN32_FILE_ATTRIBUTE_DATA sAttrData;
53032 memset(&sAttrData, 0, sizeof(sAttrData));
53033 while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
53034 GetFileExInfoStandard,
53035 &sAttrData))
53036 && !noRetry
53037 && winRetryIoerr(&cnt, &lastErrno)
53038 ){ /* Loop until true */}
53039 if( rc ){
53040 /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file
53041 ** as if it does not exist.
53042 */
53043 if( flags==SQLITE_ACCESS_EXISTS
@@ -52924,10 +53101,11 @@
53101 const char *zPathname
53102 ){
53103 return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
53104 }
53105
53106 #ifdef _WIN32
53107 /*
53108 ** Returns non-zero if the specified path name should be used verbatim. If
53109 ** non-zero is returned from this function, the calling function must simply
53110 ** use the provided path name verbatim -OR- resolve it into a full path name
53111 ** using the GetFullPathName Win32 API function (if available).
@@ -52960,10 +53138,74 @@
53138 ** If we get to this point, the path name should almost certainly be a purely
53139 ** relative one (i.e. not a UNC name, not absolute, and not volume relative).
53140 */
53141 return FALSE;
53142 }
53143 #endif /* _WIN32 */
53144
53145 #ifdef __CYGWIN__
53146 /*
53147 ** Simplify a filename into its canonical form
53148 ** by making the following changes:
53149 **
53150 ** * convert any '/' to '\' (win32) or reverse (Cygwin)
53151 ** * removing any trailing and duplicate / (except for UNC paths)
53152 ** * convert /./ into just /
53153 **
53154 ** Changes are made in-place. Return the new name length.
53155 **
53156 ** The original filename is in z[0..]. If the path is shortened,
53157 ** no-longer used bytes will be written by '\0'.
53158 */
53159 static void winSimplifyName(char *z){
53160 int i, j;
53161 for(i=j=0; z[i]; ++i){
53162 if( winIsDirSep(z[i]) ){
53163 #if !defined(SQLITE_TEST)
53164 /* Some test-cases assume that "./foo" and "foo" are different */
53165 if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){
53166 ++i;
53167 continue;
53168 }
53169 #endif
53170 if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){
53171 continue;
53172 }
53173 z[j++] = osGetenv?'/':'\\';
53174 }else{
53175 z[j++] = z[i];
53176 }
53177 }
53178 while(j<i) z[j++] = '\0';
53179 }
53180
53181 #define SQLITE_MAX_SYMLINKS 100
53182
53183 static int mkFullPathname(
53184 const char *zPath, /* Input path */
53185 char *zOut, /* Output buffer */
53186 int nOut /* Allocated size of buffer zOut */
53187 ){
53188 int nPath = sqlite3Strlen30(zPath);
53189 int iOff = 0;
53190 if( zPath[0]!='/' ){
53191 if( osGetcwd(zOut, nOut-2)==0 ){
53192 return winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "getcwd", zPath);
53193 }
53194 iOff = sqlite3Strlen30(zOut);
53195 zOut[iOff++] = '/';
53196 }
53197 if( (iOff+nPath+1)>nOut ){
53198 /* SQLite assumes that xFullPathname() nul-terminates the output buffer
53199 ** even if it returns an error. */
53200 zOut[iOff] = '\0';
53201 return SQLITE_CANTOPEN_BKPT;
53202 }
53203 sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
53204 return SQLITE_OK;
53205 }
53206 #endif /* __CYGWIN__ */
53207
53208 /*
53209 ** Turn a relative pathname into a full pathname. Write the full
53210 ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
53211 ** bytes in size.
@@ -52972,12 +53214,12 @@
53214 sqlite3_vfs *pVfs, /* Pointer to vfs object */
53215 const char *zRelative, /* Possibly relative input path */
53216 int nFull, /* Size of output buffer in bytes */
53217 char *zFull /* Output buffer */
53218 ){
53219 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
53220 int nByte;
53221 void *zConverted;
53222 char *zOut;
53223 #endif
53224
53225 /* If this path name begins with "/X:" or "\\?\", where "X" is any
@@ -52986,68 +53228,114 @@
53228 if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1)
53229 || winIsLongPathPrefix(zRelative+1)) ){
53230 zRelative++;
53231 }
53232
53233 SimulateIOError( return SQLITE_ERROR );
53234
53235 #ifdef __CYGWIN__
53236 if( osGetcwd ){
53237 zFull[nFull-1] = '\0';
53238 if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){
53239 int rc = SQLITE_OK;
53240 int nLink = 1; /* Number of symbolic links followed so far */
53241 const char *zIn = zRelative; /* Input path for each iteration of loop */
53242 char *zDel = 0;
53243 struct stat buf;
53244
53245 UNUSED_PARAMETER(pVfs);
53246
53247 do {
53248 /* Call lstat() on path zIn. Set bLink to true if the path is a symbolic
53249 ** link, or false otherwise. */
53250 int bLink = 0;
53251 if( osLstat && osReadlink ) {
53252 if( osLstat(zIn, &buf)!=0 ){
53253 int myErrno = osErrno;
53254 if( myErrno!=ENOENT ){
53255 rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn);
53256 }
53257 }else{
53258 bLink = ((buf.st_mode & 0170000) == 0120000);
53259 }
53260
53261 if( bLink ){
53262 if( zDel==0 ){
53263 zDel = sqlite3MallocZero(nFull);
53264 if( zDel==0 ) rc = SQLITE_NOMEM;
53265 }else if( ++nLink>SQLITE_MAX_SYMLINKS ){
53266 rc = SQLITE_CANTOPEN_BKPT;
53267 }
53268
53269 if( rc==SQLITE_OK ){
53270 nByte = osReadlink(zIn, zDel, nFull-1);
53271 if( nByte ==(DWORD)-1 ){
53272 rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn);
53273 }else{
53274 if( zDel[0]!='/' ){
53275 int n;
53276 for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
53277 if( nByte+n+1>nFull ){
53278 rc = SQLITE_CANTOPEN_BKPT;
53279 }else{
53280 memmove(&zDel[n], zDel, nByte+1);
53281 memcpy(zDel, zIn, n);
53282 nByte += n;
53283 }
53284 }
53285 zDel[nByte] = '\0';
53286 }
53287 }
53288
53289 zIn = zDel;
53290 }
53291 }
53292
53293 assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' );
53294 if( rc==SQLITE_OK && zIn!=zFull ){
53295 rc = mkFullPathname(zIn, zFull, nFull);
53296 }
53297 if( bLink==0 ) break;
53298 zIn = zFull;
53299 }while( rc==SQLITE_OK );
53300
53301 sqlite3_free(zDel);
53302 winSimplifyName(zFull);
53303 return rc;
53304 }
53305 }
53306 #endif /* __CYGWIN__ */
53307 #if 0 /* This doesn't work correctly at all! See:
53308 <https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
53309 */
53310 SimulateIOError( return SQLITE_ERROR );
53311 UNUSED_PARAMETER(nFull);
53312 assert( nFull>=pVfs->mxPathname );
53313 char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
53314 if( !zOut ){
53315 return SQLITE_IOERR_NOMEM_BKPT;
53316 }
53317 if( osCygwin_conv_path(
53318 CCP_POSIX_TO_WIN_W,
53319 zRelative, zOut, pVfs->mxPathname+1)<0 ){
53320 sqlite3_free(zOut);
53321 return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
53322 "winFullPathname2", zRelative);
53323 }else{
53324 char *zUtf8 = winConvertToUtf8Filename(zOut);
53325 if( !zUtf8 ){
53326 sqlite3_free(zOut);
53327 return SQLITE_IOERR_NOMEM_BKPT;
53328 }
53329 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
53330 sqlite3_free(zUtf8);
53331 sqlite3_free(zOut);
53332 }
53333 return SQLITE_OK;
53334 #endif
53335
53336 #if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53337 SimulateIOError( return SQLITE_ERROR );
53338 /* WinCE has no concept of a relative pathname, or so I am told. */
53339 /* WinRT has no way to convert a relative path to an absolute one. */
53340 if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
53341 /*
@@ -53062,11 +53350,12 @@
53350 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
53351 }
53352 return SQLITE_OK;
53353 #endif
53354
53355 #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
53356 #if defined(_WIN32)
53357 /* It's odd to simulate an io-error here, but really this is just
53358 ** using the io-error infrastructure to test that SQLite handles this
53359 ** function failing. This function could fail if, for example, the
53360 ** current working directory has been unlinked.
53361 */
@@ -53080,10 +53369,11 @@
53369 */
53370 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
53371 sqlite3_data_directory, winGetDirSep(), zRelative);
53372 return SQLITE_OK;
53373 }
53374 #endif
53375 zConverted = winConvertFromUtf8Filename(zRelative);
53376 if( zConverted==0 ){
53377 return SQLITE_IOERR_NOMEM_BKPT;
53378 }
53379 if( osIsNT() ){
@@ -53092,16 +53382,17 @@
53382 if( nByte==0 ){
53383 sqlite3_free(zConverted);
53384 return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
53385 "winFullPathname1", zRelative);
53386 }
53387 nByte += 3;
53388 zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) );
53389 if( zTemp==0 ){
53390 sqlite3_free(zConverted);
53391 return SQLITE_IOERR_NOMEM_BKPT;
53392 }
53393 nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0);
53394 if( nByte==0 ){
53395 sqlite3_free(zConverted);
53396 sqlite3_free(zTemp);
53397 return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
53398 "winFullPathname2", zRelative);
@@ -53135,11 +53426,30 @@
53426 zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
53427 sqlite3_free(zTemp);
53428 }
53429 #endif
53430 if( zOut ){
53431 #ifdef __CYGWIN__
53432 if( memcmp(zOut, "\\\\?\\", 4) ){
53433 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
53434 }else if( memcmp(zOut+4, "UNC\\", 4) ){
53435 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4);
53436 }else{
53437 char *p = zOut+6;
53438 *p = '\\';
53439 if( osGetcwd ){
53440 /* On Cygwin, UNC paths use forward slashes */
53441 while( *p ){
53442 if( *p=='\\' ) *p = '/';
53443 ++p;
53444 }
53445 }
53446 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6);
53447 }
53448 #else
53449 sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
53450 #endif /* __CYGWIN__ */
53451 sqlite3_free(zOut);
53452 return SQLITE_OK;
53453 }else{
53454 return SQLITE_IOERR_NOMEM_BKPT;
53455 }
@@ -53165,11 +53475,13 @@
53475 ** Interfaces for opening a shared library, finding entry points
53476 ** within the shared library, and closing the shared library.
53477 */
53478 static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
53479 HANDLE h;
53480 #if 0 /* This doesn't work correctly at all! See:
53481 <https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
53482 */
53483 int nFull = pVfs->mxPathname+1;
53484 char *zFull = sqlite3MallocZero( nFull );
53485 void *zConverted = 0;
53486 if( zFull==0 ){
53487 OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
@@ -53532,11 +53844,11 @@
53844 };
53845 #endif
53846
53847 /* Double-check that the aSyscall[] array has been constructed
53848 ** correctly. See ticket [bb3a86e890c8e96ab] */
53849 assert( ArraySize(aSyscall)==89 );
53850
53851 /* get memory map allocation granularity */
53852 memset(&winSysInfo, 0, sizeof(SYSTEM_INFO));
53853 #if SQLITE_OS_WINRT
53854 osGetNativeSystemInfo(&winSysInfo);
@@ -66508,14 +66820,12 @@
66820 s2 = aIn[1];
66821 }else{
66822 s1 = s2 = 0;
66823 }
66824
66825 /* nByte is a multiple of 8 between 8 and 65536 */
66826 assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 );
 
 
66827
66828 if( !nativeCksum ){
66829 do {
66830 s1 += BYTESWAP32(aData[0]) + s2;
66831 s2 += BYTESWAP32(aData[1]) + s1;
@@ -147785,10 +148095,11 @@
148095 }
148096
148097 multi_select_end:
148098 pDest->iSdst = dest.iSdst;
148099 pDest->nSdst = dest.nSdst;
148100 pDest->iSDParm2 = dest.iSDParm2;
148101 if( pDelete ){
148102 sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
148103 }
148104 return rc;
148105 }
@@ -188065,10 +188376,17 @@
188376 ******************************************************************************
188377 **
188378 */
188379 #ifndef _FTSINT_H
188380 #define _FTSINT_H
188381
188382 /* #include <assert.h> */
188383 /* #include <stdlib.h> */
188384 /* #include <stddef.h> */
188385 /* #include <stdio.h> */
188386 /* #include <string.h> */
188387 /* #include <stdarg.h> */
188388
188389 #if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
188390 # define NDEBUG 1
188391 #endif
188392
@@ -189017,16 +189335,10 @@
189335
189336 #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE)
189337 # define SQLITE_CORE 1
189338 #endif
189339
 
 
 
 
 
 
189340
189341 /* #include "fts3.h" */
189342 #ifndef SQLITE_CORE
189343 /* # include "sqlite3ext.h" */
189344 SQLITE_EXTENSION_INIT1
@@ -227533,12 +227845,12 @@
227845 ){
227846 if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){
227847 /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and
227848 ** all subsequent pages to be deleted. */
227849 pTab->iDbTrunc = iDb;
227850 pTab->pgnoTrunc = pgno-1;
227851 pgno = 1;
227852 }else{
227853 zErr = "bad page value";
227854 goto update_fail;
227855 }
227856 }
@@ -241869,11 +242181,12 @@
242181 sqlite3Fts5ParseError(
242182 pParse, "expected integer, got \"%.*s\"", p->n, p->p
242183 );
242184 return;
242185 }
242186 if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0');
242187 /* ^^^^^^^^^^^^^^^--- Prevent integer overflow */
242188 }
242189 }else{
242190 nNear = FTS5_DEFAULT_NEARDIST;
242191 }
242192 pNear->nNear = nNear;
@@ -256775,11 +257088,11 @@
257088 int nArg, /* Number of args */
257089 sqlite3_value **apUnused /* Function arguments */
257090 ){
257091 assert( nArg==0 );
257092 UNUSED_PARAM2(nArg, apUnused);
257093 sqlite3_result_text(pCtx, "fts5: 2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85", -1, SQLITE_TRANSIENT);
257094 }
257095
257096 /*
257097 ** Implementation of fts5_locale(LOCALE, TEXT) function.
257098 **
257099
--- 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.50.0"
150150
#define SQLITE_VERSION_NUMBER 3050000
151
-#define SQLITE_SOURCE_ID "2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185"
151
+#define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85"
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.50.0"
150 #define SQLITE_VERSION_NUMBER 3050000
151 #define SQLITE_SOURCE_ID "2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185"
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.50.0"
150 #define SQLITE_VERSION_NUMBER 3050000
151 #define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
+2 -2
--- src/backlink.c
+++ src/backlink.c
@@ -431,13 +431,13 @@
431431
blob_reset(&in);
432432
}
433433
434434
435435
/*
436
-** COMMAND: test-wiki-relink
436
+** COMMAND: test-relink-wiki
437437
**
438
-** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME
438
+** Usage: %fossil test-relink-wiki WIKI-PAGE-NAME
439439
**
440440
** Run the backlink_wiki_refresh() procedure on the wiki page
441441
** named. WIKI-PAGE-NAME can be a glob pattern or a prefix
442442
** of the wiki page.
443443
*/
444444
--- src/backlink.c
+++ src/backlink.c
@@ -431,13 +431,13 @@
431 blob_reset(&in);
432 }
433
434
435 /*
436 ** COMMAND: test-wiki-relink
437 **
438 ** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME
439 **
440 ** Run the backlink_wiki_refresh() procedure on the wiki page
441 ** named. WIKI-PAGE-NAME can be a glob pattern or a prefix
442 ** of the wiki page.
443 */
444
--- src/backlink.c
+++ src/backlink.c
@@ -431,13 +431,13 @@
431 blob_reset(&in);
432 }
433
434
435 /*
436 ** COMMAND: test-relink-wiki
437 **
438 ** Usage: %fossil test-relink-wiki WIKI-PAGE-NAME
439 **
440 ** Run the backlink_wiki_refresh() procedure on the wiki page
441 ** named. WIKI-PAGE-NAME can be a glob pattern or a prefix
442 ** of the wiki page.
443 */
444
+2 -1
--- src/branch.c
+++ src/branch.c
@@ -870,11 +870,12 @@
870870
const char *zLastCkin = db_column_text(&q, 5);
871871
const char *zBgClr = db_column_text(&q, 6);
872872
char *zAge = human_readable_age(rNow - rMtime);
873873
sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
874874
if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
875
- if( zBgClr == 0 ){
875
+ if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0);
876
+ if( zBgClr==0 ){
876877
if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){
877878
zBgClr = 0;
878879
}else{
879880
zBgClr = hash_color(zBranch);
880881
}
881882
--- src/branch.c
+++ src/branch.c
@@ -870,11 +870,12 @@
870 const char *zLastCkin = db_column_text(&q, 5);
871 const char *zBgClr = db_column_text(&q, 6);
872 char *zAge = human_readable_age(rNow - rMtime);
873 sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
874 if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
875 if( zBgClr == 0 ){
 
876 if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){
877 zBgClr = 0;
878 }else{
879 zBgClr = hash_color(zBranch);
880 }
881
--- src/branch.c
+++ src/branch.c
@@ -870,11 +870,12 @@
870 const char *zLastCkin = db_column_text(&q, 5);
871 const char *zBgClr = db_column_text(&q, 6);
872 char *zAge = human_readable_age(rNow - rMtime);
873 sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
874 if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
875 if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0);
876 if( zBgClr==0 ){
877 if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){
878 zBgClr = 0;
879 }else{
880 zBgClr = hash_color(zBranch);
881 }
882
+95 -125
--- src/checkin.c
+++ src/checkin.c
@@ -1467,10 +1467,11 @@
14671467
CheckinInfo *p,
14681468
int parent_rid,
14691469
int dryRunFlag
14701470
){
14711471
Blob prompt;
1472
+ int wikiFlags;
14721473
#if defined(_WIN32) || defined(__CYGWIN__)
14731474
int bomSize;
14741475
const unsigned char *bom = get_utf8_bom(&bomSize);
14751476
blob_init(&prompt, (const char *) bom, bomSize);
14761477
if( zInit && zInit[0]){
@@ -1479,14 +1480,37 @@
14791480
#else
14801481
blob_init(&prompt, zInit, -1);
14811482
#endif
14821483
blob_append(&prompt,
14831484
"\n"
1484
- "# Enter a commit message for this check-in."
1485
- " Lines beginning with # are ignored.\n"
1486
- "#\n", -1
1485
+ "# Enter the commit message. Formatting rules:\n"
1486
+ "# * Lines beginning with # are ignored.\n",
1487
+ -1
14871488
);
1489
+ wikiFlags = wiki_convert_flags(1);
1490
+ if( wikiFlags & WIKI_LINKSONLY ){
1491
+ blob_append(&prompt,"# * Hyperlinks inside of [...]\n", -1);
1492
+ if( wikiFlags & WIKI_NEWLINE ){
1493
+ blob_append(&prompt,
1494
+ "# * Newlines are significant and are displayed as written\n", -1);
1495
+ }else{
1496
+ blob_append(&prompt,
1497
+ "# * Newlines are interpreted as ordinary spaces\n",
1498
+ -1
1499
+ );
1500
+ }
1501
+ blob_append(&prompt,
1502
+ "# * All other text will be displayed as written\n", -1);
1503
+ }else{
1504
+ blob_append(&prompt,
1505
+ "# * Hyperlinks: [target] or [target|display-text]\n"
1506
+ "# * Blank lines cause a paragraph break\n"
1507
+ "# * Other text rendered as if it where HTML\n", -1
1508
+ );
1509
+ }
1510
+ blob_append(&prompt, "#\n", 2);
1511
+
14881512
if( dryRunFlag ){
14891513
blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes "
14901514
"will be made to the repository\n#\n");
14911515
}
14921516
blob_appendf(&prompt, "# user: %s\n",
@@ -2289,134 +2313,82 @@
22892313
**
22902314
** This setting determines how much sanity checking, if any, the
22912315
** "fossil commit" and "fossil amend" commands do against check-in
22922316
** comments. Recognized values:
22932317
**
2294
-** on (Default) Check for bad syntax in check-in comments
2295
-** and offer the user a chance to continue editing for
2296
-** interactive sessions, or simply abort the commit if
2297
-** commit was entered using -m or -M
2298
-**
2299
-** off Do not do syntax checking of any kind
2300
-**
2301
-** links Similar to "on", except only check for bad hyperlinks
2302
-**
2303
-** preview Do all the same checks as "on" but also preview the
2304
-** check-in comment to the user during interactive sessions
2305
-** and provide an opportunity to accept or re-edit
2318
+** on (Default) Check for bad syntax and/or broken hyperlinks
2319
+** in check-in comments and offer the user a chance to
2320
+** continue editing for interactive sessions, or simply
2321
+** abort the commit if the comment was entered using -m or -M
2322
+**
2323
+** off Do not do syntax checking of any kind
2324
+**
2325
+** preview Do all the same checks as "on" but also always preview the
2326
+** check-in comment to the user during interactive sessions
2327
+** even if no obvious errors are found, and provide an
2328
+** opportunity to accept or re-edit
23062329
*/
23072330
23082331
#if INTERFACE
2309
-#define COMCK_LINKS 0x01 /* Check for back hyperlinks */
2310
-#define COMCK_MARKUP 0x02 /* Check markup */
2311
-#define COMCK_PREVIEW 0x04 /* Always preview, even if no issues found */
2312
-#define COMCK_NOPREVIEW 0x08 /* Never preview, even for other errors */
2332
+#define COMCK_MARKUP 0x01 /* Check for mistakes */
2333
+#define COMCK_PREVIEW 0x02 /* Always preview, even if no issues found */
23132334
#endif /* INTERFACE */
23142335
23152336
/*
23162337
** Check for possible formatting errors in the comment string pComment.
23172338
**
2318
-** If concerns are found, write a description of the problem(s) to
2319
-** stdout and return non-zero. The return value is some combination
2320
-** of the COMCK_* flags, depending on what went wrong.
2339
+** If issues are found, write an appropriate error notice, probably also
2340
+** including the complete text of the comment formatted to highlight the
2341
+** problem, to stdout and return non-zero. The return value is some
2342
+** combination of the COMCK_* flags, depending on what went wrong.
23212343
**
23222344
** If no issues are seen, do not output anything and return zero.
23232345
*/
2324
-int suspicious_comment(Blob *pComment, int mFlags){
2325
- char *zStart = blob_str(pComment);
2326
- char *z;
2327
- char *zEnd, *zEnd2;
2328
- char *zSep;
2329
- char cSave1;
2330
- int nIssue = 0;
2346
+int verify_comment(Blob *pComment, int mFlags){
2347
+ Blob in, html;
2348
+ int mResult;
23312349
int rc = mFlags & COMCK_PREVIEW;
2332
- Blob out;
2333
- static const char zSpecial[] = "\\&<*_`[";
2350
+ int wFlags;
23342351
23352352
if( mFlags==0 ) return 0;
2336
- z = zStart;
2337
- blob_init(&out, 0, 0);
2338
- if( mFlags & COMCK_LINKS ){
2339
- while( (z = strchr(z,'['))!=0 ){
2340
- zEnd = strchr(z,']');
2341
- if( zEnd==0 ){
2342
- blob_appendf(&out,"\n (%d) ", ++nIssue);
2343
- blob_appendf(&out, "Unterminated hyperlink \"%.12s...\"", z);
2344
- break;
2345
- }
2346
- if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){
2347
- blob_appendf(&out,"\n (%d) ", ++nIssue);
2348
- blob_appendf(&out, "Markdown hyperlink syntax: %.*s",
2349
- (int)(zEnd2+1-z), z);
2350
- z = zEnd2;
2351
- continue;
2352
- }
2353
- zSep = strchr(z+1,'|');
2354
- if( zSep==0 || zSep>zEnd ) zSep = zEnd;
2355
- while( zSep>z && fossil_isspace(zSep[-1]) ) zSep--;
2356
- cSave1 = zSep[0];
2357
- zSep[0] = 0;
2358
- if( !wiki_valid_link_target(z+1) ){
2359
- blob_appendf(&out,"\n (%d) ", ++nIssue);
2360
- blob_appendf(&out, "Broken hyperlink: [%s]", z+1);
2361
- }
2362
- zSep[0] = cSave1;
2363
- z = zEnd;
2364
- }
2365
- }
2366
-
2367
- if( nIssue>0
2368
- || (mFlags & COMCK_PREVIEW)!=0
2369
- || ((mFlags & COMCK_MARKUP)!=0 && strcspn(zStart,zSpecial)<strlen(zStart))
2370
- ){
2371
- char zGot[16];
2372
- int nGot = 0;
2373
- int i;
2374
- if( (mFlags & COMCK_MARKUP)!=0 ){
2375
- for(i=0; zSpecial[i]; i++){
2376
- if( strchr(zStart,zSpecial[i]) ) zGot[nGot++] = zSpecial[i];
2377
- }
2378
- }
2379
- zGot[nGot] = 0;
2380
- if( nGot>0 ) rc |= COMCK_MARKUP;
2381
- if( nGot>0 && nIssue>0 ){
2382
- blob_appendf(&out,"\n (%d) Comment uses special character%s \"%s\"",
2383
- ++nIssue, (nGot>1 ? "s" : ""), zGot);
2384
- nGot = 0;
2385
- }
2386
- if( nIssue ){
2387
- rc |= COMCK_LINKS;
2388
- fossil_print(
2389
- "Possible comment formatting error%s:%b\n",
2390
- nIssue>1 ? "s" : "", &out
2391
- );
2392
- }
2393
- if( (mFlags & COMCK_NOPREVIEW)==0 ){
2394
- Blob in, html, txt;
2395
- blob_init(&in, blob_str(pComment), -1);
2396
- blob_init(&html, 0, 0);
2397
- blob_init(&txt, 0, 0);
2398
- wiki_convert(&in, &html, WIKI_INLINE);
2399
- html_to_plaintext(blob_str(&html), &txt);
2400
- if( nGot>0 ){
2401
- fossil_print(
2402
- "The comment uses special character%s \"%s\". "
2403
- "Does it render as you expect?\n\n ",
2404
- (nGot>1 ? "s" : ""), zGot
2405
- );
2406
- }else{
2407
- fossil_print("Preview of the check-in comment:\n\n ");
2408
- }
2353
+ blob_init(&in, blob_str(pComment), -1);
2354
+ blob_init(&html, 0, 0);
2355
+ wFlags = wiki_convert_flags(0);
2356
+ wFlags &= ~WIKI_NOBADLINKS;
2357
+ wFlags |= WIKI_MARK;
2358
+ mResult = wiki_convert(&in, &html, wFlags);
2359
+ if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP;
2360
+ if( rc ){
2361
+ int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM;
2362
+ Blob txt;
2363
+ if( terminal_is_vt100() ) htot |= HTOT_VT100;
2364
+ blob_init(&txt, 0, 0);
2365
+ html_to_plaintext(blob_str(&html), &txt, htot);
2366
+ if( rc & COMCK_MARKUP ){
2367
+ fossil_print("Possible format errors in the check-in comment:\n\n ");
2368
+ }else{
2369
+ fossil_print("Preview of the check-in comment:\n\n ");
2370
+ }
2371
+ if( wFlags & WIKI_NEWLINE ){
2372
+ Blob line;
2373
+ char *zIndent = "";
2374
+ while( blob_line(&txt, &line) ){
2375
+ fossil_print("%s%b", zIndent, &line);
2376
+ zIndent = " ";
2377
+ }
2378
+ fossil_print("\n");
2379
+ }else{
24092380
comment_print(blob_str(&txt), 0, 3, -1, get_comment_format());
2410
- blob_reset(&in);
2411
- blob_reset(&html);
2412
- blob_reset(&txt);
24132381
}
2382
+ fossil_print("\n");
2383
+ fflush(stdout);
2384
+ blob_reset(&txt);
24142385
}
2415
- blob_reset(&out);
2386
+ blob_reset(&html);
2387
+ blob_reset(&in);
24162388
return rc;
2417
-}
2389
+}
24182390
24192391
/*
24202392
** COMMAND: ci#
24212393
** COMMAND: commit
24222394
**
@@ -2464,11 +2436,13 @@
24642436
** --allow-conflict Allow unresolved merge conflicts
24652437
** --allow-empty Allow a commit with no changes
24662438
** --allow-fork Allow the commit to fork
24672439
** --allow-older Allow a commit older than its ancestor
24682440
** --baseline Use a baseline manifest in the commit process
2441
+** --bgcolor COLOR Apply COLOR to this one check-in only
24692442
** --branch NEW-BRANCH-NAME Check in to this new branch
2443
+** --branchcolor COLOR Apply given COLOR to the branch
24702444
** --close Close the branch being committed
24712445
** --date-override DATETIME Make DATETIME the time of the check-in.
24722446
** Useful when importing historical check-ins
24732447
** from another version control system.
24742448
** --delta Use a delta manifest in the commit process
@@ -2556,11 +2530,11 @@
25562530
int bRecheck = 0; /* Repeat fork and closed-branch checks*/
25572531
int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
25582532
int mxSize;
25592533
char *zCurBranch = 0; /* The current branch name of checkout */
25602534
char *zNewBranch = 0; /* The branch name after update */
2561
- int ckComFlgs; /* Flags passed to suspicious_comment() */
2535
+ int ckComFlgs; /* Flags passed to verify_comment() */
25622536
25632537
memset(&sCiInfo, 0, sizeof(sCiInfo));
25642538
url_proxy_options();
25652539
/* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
25662540
useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2928,15 +2902,13 @@
29282902
}else{
29292903
const char *zVerComs = db_get("verify-comments","on");
29302904
if( is_false(zVerComs) ){
29312905
ckComFlgs = 0;
29322906
}else if( strcmp(zVerComs,"preview")==0 ){
2933
- ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP;
2934
- }else if( strcmp(zVerComs,"links")==0 ){
2935
- ckComFlgs = COMCK_LINKS;
2907
+ ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
29362908
}else{
2937
- ckComFlgs = COMCK_LINKS | COMCK_MARKUP;
2909
+ ckComFlgs = COMCK_MARKUP;
29382910
}
29392911
}
29402912
29412913
/* Get the check-in comment. This might involve prompting the
29422914
** user for the check-in comment, in which case we should resync
@@ -2943,23 +2915,21 @@
29432915
** to renew the check-in lock and repeat the checks for conflicts.
29442916
*/
29452917
if( zComment ){
29462918
blob_zero(&comment);
29472919
blob_append(&comment, zComment, -1);
2948
- ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP);
2949
- ckComFlgs |= COMCK_NOPREVIEW;
2950
- if( suspicious_comment(&comment, ckComFlgs) ){
2920
+ ckComFlgs &= ~COMCK_PREVIEW;
2921
+ if( verify_comment(&comment, ckComFlgs) ){
29512922
fossil_fatal("Commit aborted; "
29522923
"use --no-verify-comment to override");
29532924
}
29542925
}else if( zComFile ){
29552926
blob_zero(&comment);
29562927
blob_read_from_file(&comment, zComFile, ExtFILE);
29572928
blob_to_utf8_no_bom(&comment, 1);
2958
- ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP);
2959
- ckComFlgs |= COMCK_NOPREVIEW;
2960
- if( suspicious_comment(&comment, ckComFlgs) ){
2929
+ ckComFlgs &= ~COMCK_PREVIEW;
2930
+ if( verify_comment(&comment, ckComFlgs) ){
29612931
fossil_fatal("Commit aborted; "
29622932
"use --no-verify-comment to override");
29632933
}
29642934
}else if( !noPrompt ){
29652935
while( 1/*exit-by-break*/ ){
@@ -2966,23 +2936,23 @@
29662936
int rc;
29672937
char *zInit;
29682938
zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
29692939
prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
29702940
db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2971
- if( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){
2941
+ if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
29722942
if( rc==COMCK_PREVIEW ){
2973
- prompt_user("\nContinue (Y/n/e=edit)? ", &ans);
2943
+ prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans);
29742944
}else{
2975
- prompt_user("\nContinue (y/n/E=edit)? ", &ans);
2945
+ prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
29762946
}
29772947
cReply = blob_str(&ans)[0];
29782948
cReply = fossil_tolower(cReply);
29792949
blob_reset(&ans);
2980
- if( cReply=='n' ){
2950
+ if( cReply=='a' ){
29812951
fossil_fatal("Commit aborted.");
29822952
}
2983
- if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){
2953
+ if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
29842954
fossil_free(zInit);
29852955
continue;
29862956
}
29872957
}
29882958
if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
29892959
--- src/checkin.c
+++ src/checkin.c
@@ -1467,10 +1467,11 @@
1467 CheckinInfo *p,
1468 int parent_rid,
1469 int dryRunFlag
1470 ){
1471 Blob prompt;
 
1472 #if defined(_WIN32) || defined(__CYGWIN__)
1473 int bomSize;
1474 const unsigned char *bom = get_utf8_bom(&bomSize);
1475 blob_init(&prompt, (const char *) bom, bomSize);
1476 if( zInit && zInit[0]){
@@ -1479,14 +1480,37 @@
1479 #else
1480 blob_init(&prompt, zInit, -1);
1481 #endif
1482 blob_append(&prompt,
1483 "\n"
1484 "# Enter a commit message for this check-in."
1485 " Lines beginning with # are ignored.\n"
1486 "#\n", -1
1487 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1488 if( dryRunFlag ){
1489 blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes "
1490 "will be made to the repository\n#\n");
1491 }
1492 blob_appendf(&prompt, "# user: %s\n",
@@ -2289,134 +2313,82 @@
2289 **
2290 ** This setting determines how much sanity checking, if any, the
2291 ** "fossil commit" and "fossil amend" commands do against check-in
2292 ** comments. Recognized values:
2293 **
2294 ** on (Default) Check for bad syntax in check-in comments
2295 ** and offer the user a chance to continue editing for
2296 ** interactive sessions, or simply abort the commit if
2297 ** commit was entered using -m or -M
2298 **
2299 ** off Do not do syntax checking of any kind
2300 **
2301 ** links Similar to "on", except only check for bad hyperlinks
2302 **
2303 ** preview Do all the same checks as "on" but also preview the
2304 ** check-in comment to the user during interactive sessions
2305 ** and provide an opportunity to accept or re-edit
2306 */
2307
2308 #if INTERFACE
2309 #define COMCK_LINKS 0x01 /* Check for back hyperlinks */
2310 #define COMCK_MARKUP 0x02 /* Check markup */
2311 #define COMCK_PREVIEW 0x04 /* Always preview, even if no issues found */
2312 #define COMCK_NOPREVIEW 0x08 /* Never preview, even for other errors */
2313 #endif /* INTERFACE */
2314
2315 /*
2316 ** Check for possible formatting errors in the comment string pComment.
2317 **
2318 ** If concerns are found, write a description of the problem(s) to
2319 ** stdout and return non-zero. The return value is some combination
2320 ** of the COMCK_* flags, depending on what went wrong.
 
2321 **
2322 ** If no issues are seen, do not output anything and return zero.
2323 */
2324 int suspicious_comment(Blob *pComment, int mFlags){
2325 char *zStart = blob_str(pComment);
2326 char *z;
2327 char *zEnd, *zEnd2;
2328 char *zSep;
2329 char cSave1;
2330 int nIssue = 0;
2331 int rc = mFlags & COMCK_PREVIEW;
2332 Blob out;
2333 static const char zSpecial[] = "\\&<*_`[";
2334
2335 if( mFlags==0 ) return 0;
2336 z = zStart;
2337 blob_init(&out, 0, 0);
2338 if( mFlags & COMCK_LINKS ){
2339 while( (z = strchr(z,'['))!=0 ){
2340 zEnd = strchr(z,']');
2341 if( zEnd==0 ){
2342 blob_appendf(&out,"\n (%d) ", ++nIssue);
2343 blob_appendf(&out, "Unterminated hyperlink \"%.12s...\"", z);
2344 break;
2345 }
2346 if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){
2347 blob_appendf(&out,"\n (%d) ", ++nIssue);
2348 blob_appendf(&out, "Markdown hyperlink syntax: %.*s",
2349 (int)(zEnd2+1-z), z);
2350 z = zEnd2;
2351 continue;
2352 }
2353 zSep = strchr(z+1,'|');
2354 if( zSep==0 || zSep>zEnd ) zSep = zEnd;
2355 while( zSep>z && fossil_isspace(zSep[-1]) ) zSep--;
2356 cSave1 = zSep[0];
2357 zSep[0] = 0;
2358 if( !wiki_valid_link_target(z+1) ){
2359 blob_appendf(&out,"\n (%d) ", ++nIssue);
2360 blob_appendf(&out, "Broken hyperlink: [%s]", z+1);
2361 }
2362 zSep[0] = cSave1;
2363 z = zEnd;
2364 }
2365 }
2366
2367 if( nIssue>0
2368 || (mFlags & COMCK_PREVIEW)!=0
2369 || ((mFlags & COMCK_MARKUP)!=0 && strcspn(zStart,zSpecial)<strlen(zStart))
2370 ){
2371 char zGot[16];
2372 int nGot = 0;
2373 int i;
2374 if( (mFlags & COMCK_MARKUP)!=0 ){
2375 for(i=0; zSpecial[i]; i++){
2376 if( strchr(zStart,zSpecial[i]) ) zGot[nGot++] = zSpecial[i];
2377 }
2378 }
2379 zGot[nGot] = 0;
2380 if( nGot>0 ) rc |= COMCK_MARKUP;
2381 if( nGot>0 && nIssue>0 ){
2382 blob_appendf(&out,"\n (%d) Comment uses special character%s \"%s\"",
2383 ++nIssue, (nGot>1 ? "s" : ""), zGot);
2384 nGot = 0;
2385 }
2386 if( nIssue ){
2387 rc |= COMCK_LINKS;
2388 fossil_print(
2389 "Possible comment formatting error%s:%b\n",
2390 nIssue>1 ? "s" : "", &out
2391 );
2392 }
2393 if( (mFlags & COMCK_NOPREVIEW)==0 ){
2394 Blob in, html, txt;
2395 blob_init(&in, blob_str(pComment), -1);
2396 blob_init(&html, 0, 0);
2397 blob_init(&txt, 0, 0);
2398 wiki_convert(&in, &html, WIKI_INLINE);
2399 html_to_plaintext(blob_str(&html), &txt);
2400 if( nGot>0 ){
2401 fossil_print(
2402 "The comment uses special character%s \"%s\". "
2403 "Does it render as you expect?\n\n ",
2404 (nGot>1 ? "s" : ""), zGot
2405 );
2406 }else{
2407 fossil_print("Preview of the check-in comment:\n\n ");
2408 }
2409 comment_print(blob_str(&txt), 0, 3, -1, get_comment_format());
2410 blob_reset(&in);
2411 blob_reset(&html);
2412 blob_reset(&txt);
2413 }
 
 
 
2414 }
2415 blob_reset(&out);
 
2416 return rc;
2417 }
2418
2419 /*
2420 ** COMMAND: ci#
2421 ** COMMAND: commit
2422 **
@@ -2464,11 +2436,13 @@
2464 ** --allow-conflict Allow unresolved merge conflicts
2465 ** --allow-empty Allow a commit with no changes
2466 ** --allow-fork Allow the commit to fork
2467 ** --allow-older Allow a commit older than its ancestor
2468 ** --baseline Use a baseline manifest in the commit process
 
2469 ** --branch NEW-BRANCH-NAME Check in to this new branch
 
2470 ** --close Close the branch being committed
2471 ** --date-override DATETIME Make DATETIME the time of the check-in.
2472 ** Useful when importing historical check-ins
2473 ** from another version control system.
2474 ** --delta Use a delta manifest in the commit process
@@ -2556,11 +2530,11 @@
2556 int bRecheck = 0; /* Repeat fork and closed-branch checks*/
2557 int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
2558 int mxSize;
2559 char *zCurBranch = 0; /* The current branch name of checkout */
2560 char *zNewBranch = 0; /* The branch name after update */
2561 int ckComFlgs; /* Flags passed to suspicious_comment() */
2562
2563 memset(&sCiInfo, 0, sizeof(sCiInfo));
2564 url_proxy_options();
2565 /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
2566 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2928,15 +2902,13 @@
2928 }else{
2929 const char *zVerComs = db_get("verify-comments","on");
2930 if( is_false(zVerComs) ){
2931 ckComFlgs = 0;
2932 }else if( strcmp(zVerComs,"preview")==0 ){
2933 ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP;
2934 }else if( strcmp(zVerComs,"links")==0 ){
2935 ckComFlgs = COMCK_LINKS;
2936 }else{
2937 ckComFlgs = COMCK_LINKS | COMCK_MARKUP;
2938 }
2939 }
2940
2941 /* Get the check-in comment. This might involve prompting the
2942 ** user for the check-in comment, in which case we should resync
@@ -2943,23 +2915,21 @@
2943 ** to renew the check-in lock and repeat the checks for conflicts.
2944 */
2945 if( zComment ){
2946 blob_zero(&comment);
2947 blob_append(&comment, zComment, -1);
2948 ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP);
2949 ckComFlgs |= COMCK_NOPREVIEW;
2950 if( suspicious_comment(&comment, ckComFlgs) ){
2951 fossil_fatal("Commit aborted; "
2952 "use --no-verify-comment to override");
2953 }
2954 }else if( zComFile ){
2955 blob_zero(&comment);
2956 blob_read_from_file(&comment, zComFile, ExtFILE);
2957 blob_to_utf8_no_bom(&comment, 1);
2958 ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP);
2959 ckComFlgs |= COMCK_NOPREVIEW;
2960 if( suspicious_comment(&comment, ckComFlgs) ){
2961 fossil_fatal("Commit aborted; "
2962 "use --no-verify-comment to override");
2963 }
2964 }else if( !noPrompt ){
2965 while( 1/*exit-by-break*/ ){
@@ -2966,23 +2936,23 @@
2966 int rc;
2967 char *zInit;
2968 zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2969 prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2970 db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2971 if( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){
2972 if( rc==COMCK_PREVIEW ){
2973 prompt_user("\nContinue (Y/n/e=edit)? ", &ans);
2974 }else{
2975 prompt_user("\nContinue (y/n/E=edit)? ", &ans);
2976 }
2977 cReply = blob_str(&ans)[0];
2978 cReply = fossil_tolower(cReply);
2979 blob_reset(&ans);
2980 if( cReply=='n' ){
2981 fossil_fatal("Commit aborted.");
2982 }
2983 if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){
2984 fossil_free(zInit);
2985 continue;
2986 }
2987 }
2988 if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2989
--- src/checkin.c
+++ src/checkin.c
@@ -1467,10 +1467,11 @@
1467 CheckinInfo *p,
1468 int parent_rid,
1469 int dryRunFlag
1470 ){
1471 Blob prompt;
1472 int wikiFlags;
1473 #if defined(_WIN32) || defined(__CYGWIN__)
1474 int bomSize;
1475 const unsigned char *bom = get_utf8_bom(&bomSize);
1476 blob_init(&prompt, (const char *) bom, bomSize);
1477 if( zInit && zInit[0]){
@@ -1479,14 +1480,37 @@
1480 #else
1481 blob_init(&prompt, zInit, -1);
1482 #endif
1483 blob_append(&prompt,
1484 "\n"
1485 "# Enter the commit message. Formatting rules:\n"
1486 "# * Lines beginning with # are ignored.\n",
1487 -1
1488 );
1489 wikiFlags = wiki_convert_flags(1);
1490 if( wikiFlags & WIKI_LINKSONLY ){
1491 blob_append(&prompt,"# * Hyperlinks inside of [...]\n", -1);
1492 if( wikiFlags & WIKI_NEWLINE ){
1493 blob_append(&prompt,
1494 "# * Newlines are significant and are displayed as written\n", -1);
1495 }else{
1496 blob_append(&prompt,
1497 "# * Newlines are interpreted as ordinary spaces\n",
1498 -1
1499 );
1500 }
1501 blob_append(&prompt,
1502 "# * All other text will be displayed as written\n", -1);
1503 }else{
1504 blob_append(&prompt,
1505 "# * Hyperlinks: [target] or [target|display-text]\n"
1506 "# * Blank lines cause a paragraph break\n"
1507 "# * Other text rendered as if it where HTML\n", -1
1508 );
1509 }
1510 blob_append(&prompt, "#\n", 2);
1511
1512 if( dryRunFlag ){
1513 blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes "
1514 "will be made to the repository\n#\n");
1515 }
1516 blob_appendf(&prompt, "# user: %s\n",
@@ -2289,134 +2313,82 @@
2313 **
2314 ** This setting determines how much sanity checking, if any, the
2315 ** "fossil commit" and "fossil amend" commands do against check-in
2316 ** comments. Recognized values:
2317 **
2318 ** on (Default) Check for bad syntax and/or broken hyperlinks
2319 ** in check-in comments and offer the user a chance to
2320 ** continue editing for interactive sessions, or simply
2321 ** abort the commit if the comment was entered using -m or -M
2322 **
2323 ** off Do not do syntax checking of any kind
2324 **
2325 ** preview Do all the same checks as "on" but also always preview the
2326 ** check-in comment to the user during interactive sessions
2327 ** even if no obvious errors are found, and provide an
2328 ** opportunity to accept or re-edit
 
2329 */
2330
2331 #if INTERFACE
2332 #define COMCK_MARKUP 0x01 /* Check for mistakes */
2333 #define COMCK_PREVIEW 0x02 /* Always preview, even if no issues found */
 
 
2334 #endif /* INTERFACE */
2335
2336 /*
2337 ** Check for possible formatting errors in the comment string pComment.
2338 **
2339 ** If issues are found, write an appropriate error notice, probably also
2340 ** including the complete text of the comment formatted to highlight the
2341 ** problem, to stdout and return non-zero. The return value is some
2342 ** combination of the COMCK_* flags, depending on what went wrong.
2343 **
2344 ** If no issues are seen, do not output anything and return zero.
2345 */
2346 int verify_comment(Blob *pComment, int mFlags){
2347 Blob in, html;
2348 int mResult;
 
 
 
 
2349 int rc = mFlags & COMCK_PREVIEW;
2350 int wFlags;
 
2351
2352 if( mFlags==0 ) return 0;
2353 blob_init(&in, blob_str(pComment), -1);
2354 blob_init(&html, 0, 0);
2355 wFlags = wiki_convert_flags(0);
2356 wFlags &= ~WIKI_NOBADLINKS;
2357 wFlags |= WIKI_MARK;
2358 mResult = wiki_convert(&in, &html, wFlags);
2359 if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP;
2360 if( rc ){
2361 int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM;
2362 Blob txt;
2363 if( terminal_is_vt100() ) htot |= HTOT_VT100;
2364 blob_init(&txt, 0, 0);
2365 html_to_plaintext(blob_str(&html), &txt, htot);
2366 if( rc & COMCK_MARKUP ){
2367 fossil_print("Possible format errors in the check-in comment:\n\n ");
2368 }else{
2369 fossil_print("Preview of the check-in comment:\n\n ");
2370 }
2371 if( wFlags & WIKI_NEWLINE ){
2372 Blob line;
2373 char *zIndent = "";
2374 while( blob_line(&txt, &line) ){
2375 fossil_print("%s%b", zIndent, &line);
2376 zIndent = " ";
2377 }
2378 fossil_print("\n");
2379 }else{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2380 comment_print(blob_str(&txt), 0, 3, -1, get_comment_format());
 
 
 
2381 }
2382 fossil_print("\n");
2383 fflush(stdout);
2384 blob_reset(&txt);
2385 }
2386 blob_reset(&html);
2387 blob_reset(&in);
2388 return rc;
2389 }
2390
2391 /*
2392 ** COMMAND: ci#
2393 ** COMMAND: commit
2394 **
@@ -2464,11 +2436,13 @@
2436 ** --allow-conflict Allow unresolved merge conflicts
2437 ** --allow-empty Allow a commit with no changes
2438 ** --allow-fork Allow the commit to fork
2439 ** --allow-older Allow a commit older than its ancestor
2440 ** --baseline Use a baseline manifest in the commit process
2441 ** --bgcolor COLOR Apply COLOR to this one check-in only
2442 ** --branch NEW-BRANCH-NAME Check in to this new branch
2443 ** --branchcolor COLOR Apply given COLOR to the branch
2444 ** --close Close the branch being committed
2445 ** --date-override DATETIME Make DATETIME the time of the check-in.
2446 ** Useful when importing historical check-ins
2447 ** from another version control system.
2448 ** --delta Use a delta manifest in the commit process
@@ -2556,11 +2530,11 @@
2530 int bRecheck = 0; /* Repeat fork and closed-branch checks*/
2531 int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
2532 int mxSize;
2533 char *zCurBranch = 0; /* The current branch name of checkout */
2534 char *zNewBranch = 0; /* The branch name after update */
2535 int ckComFlgs; /* Flags passed to verify_comment() */
2536
2537 memset(&sCiInfo, 0, sizeof(sCiInfo));
2538 url_proxy_options();
2539 /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
2540 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2928,15 +2902,13 @@
2902 }else{
2903 const char *zVerComs = db_get("verify-comments","on");
2904 if( is_false(zVerComs) ){
2905 ckComFlgs = 0;
2906 }else if( strcmp(zVerComs,"preview")==0 ){
2907 ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
 
 
2908 }else{
2909 ckComFlgs = COMCK_MARKUP;
2910 }
2911 }
2912
2913 /* Get the check-in comment. This might involve prompting the
2914 ** user for the check-in comment, in which case we should resync
@@ -2943,23 +2915,21 @@
2915 ** to renew the check-in lock and repeat the checks for conflicts.
2916 */
2917 if( zComment ){
2918 blob_zero(&comment);
2919 blob_append(&comment, zComment, -1);
2920 ckComFlgs &= ~COMCK_PREVIEW;
2921 if( verify_comment(&comment, ckComFlgs) ){
 
2922 fossil_fatal("Commit aborted; "
2923 "use --no-verify-comment to override");
2924 }
2925 }else if( zComFile ){
2926 blob_zero(&comment);
2927 blob_read_from_file(&comment, zComFile, ExtFILE);
2928 blob_to_utf8_no_bom(&comment, 1);
2929 ckComFlgs &= ~COMCK_PREVIEW;
2930 if( verify_comment(&comment, ckComFlgs) ){
 
2931 fossil_fatal("Commit aborted; "
2932 "use --no-verify-comment to override");
2933 }
2934 }else if( !noPrompt ){
2935 while( 1/*exit-by-break*/ ){
@@ -2966,23 +2936,23 @@
2936 int rc;
2937 char *zInit;
2938 zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
2939 prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
2940 db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
2941 if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
2942 if( rc==COMCK_PREVIEW ){
2943 prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans);
2944 }else{
2945 prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
2946 }
2947 cReply = blob_str(&ans)[0];
2948 cReply = fossil_tolower(cReply);
2949 blob_reset(&ans);
2950 if( cReply=='a' ){
2951 fossil_fatal("Commit aborted.");
2952 }
2953 if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
2954 fossil_free(zInit);
2955 continue;
2956 }
2957 }
2958 if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
2959
+10 -4
--- src/checkout.c
+++ src/checkout.c
@@ -276,13 +276,14 @@
276276
**
277277
** The --latest flag can be used in place of VERSION to check-out the
278278
** latest version in the repository.
279279
**
280280
** Options:
281
-** --force Ignore edited files in the current check-out
282
-** --keep Only update the manifest file(s)
281
+** -f|--force Ignore edited files in the current check-out
282
+** -k|--keep Only update the manifest file(s)
283283
** --force-missing Force check-out even if content is missing
284
+** --prompt Prompt before overwriting when --force is used
284285
** --setmtime Set timestamps of all files to match their SCM-side
285286
** times (the timestamp of the last check-in which modified
286287
** them)
287288
**
288289
** See also: [[update]]
@@ -298,16 +299,21 @@
298299
int setmtimeFlag; /* --setmtime. Set mtimes on files */
299300
Blob cksum1, cksum1b, cksum2;
300301
301302
db_must_be_within_tree();
302303
db_begin_transaction();
303
- forceFlag = find_option("force","f",0)!=0;
304304
forceMissingFlag = find_option("force-missing",0,0)!=0;
305
- keepFlag = find_option("keep",0,0)!=0;
305
+ keepFlag = find_option("keep","k",0)!=0;
306
+ forceFlag = find_option("force","f",0)!=0;
306307
latestFlag = find_option("latest",0,0)!=0;
307308
promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
308309
setmtimeFlag = find_option("setmtime",0,0)!=0;
310
+
311
+ if( keepFlag != 0 ){
312
+ /* After flag collection, in order not to affect promptFlag */
313
+ forceFlag=1;
314
+ }
309315
310316
/* We should be done with options.. */
311317
verify_all_options();
312318
313319
if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){
314320
--- src/checkout.c
+++ src/checkout.c
@@ -276,13 +276,14 @@
276 **
277 ** The --latest flag can be used in place of VERSION to check-out the
278 ** latest version in the repository.
279 **
280 ** Options:
281 ** --force Ignore edited files in the current check-out
282 ** --keep Only update the manifest file(s)
283 ** --force-missing Force check-out even if content is missing
 
284 ** --setmtime Set timestamps of all files to match their SCM-side
285 ** times (the timestamp of the last check-in which modified
286 ** them)
287 **
288 ** See also: [[update]]
@@ -298,16 +299,21 @@
298 int setmtimeFlag; /* --setmtime. Set mtimes on files */
299 Blob cksum1, cksum1b, cksum2;
300
301 db_must_be_within_tree();
302 db_begin_transaction();
303 forceFlag = find_option("force","f",0)!=0;
304 forceMissingFlag = find_option("force-missing",0,0)!=0;
305 keepFlag = find_option("keep",0,0)!=0;
 
306 latestFlag = find_option("latest",0,0)!=0;
307 promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
308 setmtimeFlag = find_option("setmtime",0,0)!=0;
 
 
 
 
 
309
310 /* We should be done with options.. */
311 verify_all_options();
312
313 if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){
314
--- src/checkout.c
+++ src/checkout.c
@@ -276,13 +276,14 @@
276 **
277 ** The --latest flag can be used in place of VERSION to check-out the
278 ** latest version in the repository.
279 **
280 ** Options:
281 ** -f|--force Ignore edited files in the current check-out
282 ** -k|--keep Only update the manifest file(s)
283 ** --force-missing Force check-out even if content is missing
284 ** --prompt Prompt before overwriting when --force is used
285 ** --setmtime Set timestamps of all files to match their SCM-side
286 ** times (the timestamp of the last check-in which modified
287 ** them)
288 **
289 ** See also: [[update]]
@@ -298,16 +299,21 @@
299 int setmtimeFlag; /* --setmtime. Set mtimes on files */
300 Blob cksum1, cksum1b, cksum2;
301
302 db_must_be_within_tree();
303 db_begin_transaction();
 
304 forceMissingFlag = find_option("force-missing",0,0)!=0;
305 keepFlag = find_option("keep","k",0)!=0;
306 forceFlag = find_option("force","f",0)!=0;
307 latestFlag = find_option("latest",0,0)!=0;
308 promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
309 setmtimeFlag = find_option("setmtime",0,0)!=0;
310
311 if( keepFlag != 0 ){
312 /* After flag collection, in order not to affect promptFlag */
313 forceFlag=1;
314 }
315
316 /* We should be done with options.. */
317 verify_all_options();
318
319 if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){
320
+333
--- src/color.c
+++ src/color.c
@@ -20,10 +20,262 @@
2020
**
2121
*/
2222
#include "config.h"
2323
#include <string.h>
2424
#include "color.h"
25
+
26
+/*
27
+** 140 standard CSS color names and their corresponding RGB values,
28
+** in alphabetical order by name so that we can do a binary search
29
+** for lookup.
30
+*/
31
+static const struct CssColors {
32
+ const char *zName; /* CSS Color name, lower case */
33
+ unsigned int iRGB; /* Corresponding RGB value */
34
+} aCssColors[] = {
35
+ { "aliceblue", 0xf0f8ff },
36
+ { "antiquewhite", 0xfaebd7 },
37
+ { "aqua", 0x00ffff },
38
+ { "aquamarine", 0x7fffd4 },
39
+ { "azure", 0xf0ffff },
40
+ { "beige", 0xf5f5dc },
41
+ { "bisque", 0xffe4c4 },
42
+ { "black", 0x000000 },
43
+ { "blanchedalmond", 0xffebcd },
44
+ { "blue", 0x0000ff },
45
+ { "blueviolet", 0x8a2be2 },
46
+ { "brown", 0xa52a2a },
47
+ { "burlywood", 0xdeb887 },
48
+ { "cadetblue", 0x5f9ea0 },
49
+ { "chartreuse", 0x7fff00 },
50
+ { "chocolate", 0xd2691e },
51
+ { "coral", 0xff7f50 },
52
+ { "cornflowerblue", 0x6495ed },
53
+ { "cornsilk", 0xfff8dc },
54
+ { "crimson", 0xdc143c },
55
+ { "cyan", 0x00ffff },
56
+ { "darkblue", 0x00008b },
57
+ { "darkcyan", 0x008b8b },
58
+ { "darkgoldenrod", 0xb8860b },
59
+ { "darkgray", 0xa9a9a9 },
60
+ { "darkgreen", 0x006400 },
61
+ { "darkkhaki", 0xbdb76b },
62
+ { "darkmagenta", 0x8b008b },
63
+ { "darkolivegreen", 0x556b2f },
64
+ { "darkorange", 0xff8c00 },
65
+ { "darkorchid", 0x9932cc },
66
+ { "darkred", 0x8b0000 },
67
+ { "darksalmon", 0xe9967a },
68
+ { "darkseagreen", 0x8fbc8f },
69
+ { "darkslateblue", 0x483d8b },
70
+ { "darkslategray", 0x2f4f4f },
71
+ { "darkturquoise", 0x00ced1 },
72
+ { "darkviolet", 0x9400d3 },
73
+ { "deeppink", 0xff1493 },
74
+ { "deepskyblue", 0x00bfff },
75
+ { "dimgray", 0x696969 },
76
+ { "dodgerblue", 0x1e90ff },
77
+ { "firebrick", 0xb22222 },
78
+ { "floralwhite", 0xfffaf0 },
79
+ { "forestgreen", 0x228b22 },
80
+ { "fuchsia", 0xff00ff },
81
+ { "gainsboro", 0xdcdcdc },
82
+ { "ghostwhite", 0xf8f8ff },
83
+ { "gold", 0xffd700 },
84
+ { "goldenrod", 0xdaa520 },
85
+ { "gray", 0x808080 },
86
+ { "green", 0x008000 },
87
+ { "greenyellow", 0xadff2f },
88
+ { "honeydew", 0xf0fff0 },
89
+ { "hotpink", 0xff69b4 },
90
+ { "indianred", 0xcd5c5c },
91
+ { "indigo", 0x4b0082 },
92
+ { "ivory", 0xfffff0 },
93
+ { "khaki", 0xf0e68c },
94
+ { "lavender", 0xe6e6fa },
95
+ { "lavenderblush", 0xfff0f5 },
96
+ { "lawngreen", 0x7cfc00 },
97
+ { "lemonchiffon", 0xfffacd },
98
+ { "lightblue", 0xadd8e6 },
99
+ { "lightcoral", 0xf08080 },
100
+ { "lightcyan", 0xe0ffff },
101
+ { "lightgoldenrodyellow", 0xfafad2 },
102
+ { "lightgrey", 0xd3d3d3 },
103
+ { "lightgreen", 0x90ee90 },
104
+ { "lightpink", 0xffb6c1 },
105
+ { "lightsalmon", 0xffa07a },
106
+ { "lightseagreen", 0x20b2aa },
107
+ { "lightskyblue", 0x87cefa },
108
+ { "lightslategray", 0x778899 },
109
+ { "lightsteelblue", 0xb0c4de },
110
+ { "lightyellow", 0xffffe0 },
111
+ { "lime", 0x00ff00 },
112
+ { "limegreen", 0x32cd32 },
113
+ { "linen", 0xfaf0e6 },
114
+ { "magenta", 0xff00ff },
115
+ { "maroon", 0x800000 },
116
+ { "mediumaquamarine", 0x66cdaa },
117
+ { "mediumblue", 0x0000cd },
118
+ { "mediumorchid", 0xba55d3 },
119
+ { "mediumpurple", 0x9370d8 },
120
+ { "mediumseagreen", 0x3cb371 },
121
+ { "mediumslateblue", 0x7b68ee },
122
+ { "mediumspringgreen", 0x00fa9a },
123
+ { "mediumturquoise", 0x48d1cc },
124
+ { "mediumvioletred", 0xc71585 },
125
+ { "midnightblue", 0x191970 },
126
+ { "mintcream", 0xf5fffa },
127
+ { "mistyrose", 0xffe4e1 },
128
+ { "moccasin", 0xffe4b5 },
129
+ { "navajowhite", 0xffdead },
130
+ { "navy", 0x000080 },
131
+ { "oldlace", 0xfdf5e6 },
132
+ { "olive", 0x808000 },
133
+ { "olivedrab", 0x6b8e23 },
134
+ { "orange", 0xffa500 },
135
+ { "orangered", 0xff4500 },
136
+ { "orchid", 0xda70d6 },
137
+ { "palegoldenrod", 0xeee8aa },
138
+ { "palegreen", 0x98fb98 },
139
+ { "paleturquoise", 0xafeeee },
140
+ { "palevioletred", 0xd87093 },
141
+ { "papayawhip", 0xffefd5 },
142
+ { "peachpuff", 0xffdab9 },
143
+ { "peru", 0xcd853f },
144
+ { "pink", 0xffc0cb },
145
+ { "plum", 0xdda0dd },
146
+ { "powderblue", 0xb0e0e6 },
147
+ { "purple", 0x800080 },
148
+ { "red", 0xff0000 },
149
+ { "rosybrown", 0xbc8f8f },
150
+ { "royalblue", 0x4169e1 },
151
+ { "saddlebrown", 0x8b4513 },
152
+ { "salmon", 0xfa8072 },
153
+ { "sandybrown", 0xf4a460 },
154
+ { "seagreen", 0x2e8b57 },
155
+ { "seashell", 0xfff5ee },
156
+ { "sienna", 0xa0522d },
157
+ { "silver", 0xc0c0c0 },
158
+ { "skyblue", 0x87ceeb },
159
+ { "slateblue", 0x6a5acd },
160
+ { "slategray", 0x708090 },
161
+ { "snow", 0xfffafa },
162
+ { "springgreen", 0x00ff7f },
163
+ { "steelblue", 0x4682b4 },
164
+ { "tan", 0xd2b48c },
165
+ { "teal", 0x008080 },
166
+ { "thistle", 0xd8bfd8 },
167
+ { "tomato", 0xff6347 },
168
+ { "turquoise", 0x40e0d0 },
169
+ { "violet", 0xee82ee },
170
+ { "wheat", 0xf5deb3 },
171
+ { "white", 0xffffff },
172
+ { "whitesmoke", 0xf5f5f5 },
173
+ { "yellow", 0xffff00 },
174
+ { "yellowgreen", 0x9acd32 },
175
+};
176
+
177
+/*
178
+** Attempt to translate a CSS color name into an integer that
179
+** represents the equivalent RGB value. Ignore alpha if provided.
180
+** If the name cannot be translated, return -1.
181
+*/
182
+int color_name_to_rgb(const char *zName){
183
+ if( zName==0 || zName[0]==0 ) return -1;
184
+ if( zName[0]=='#' ){
185
+ int i, v = 0;
186
+ for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){
187
+ v = v*16 + fossil_hexvalue(zName[i]);
188
+ }
189
+ if( i==4 ){
190
+ v = fossil_hexvalue(zName[1])*0x110000 +
191
+ fossil_hexvalue(zName[2])*0x1100 +
192
+ fossil_hexvalue(zName[3])*0x11;
193
+ return v;
194
+ }
195
+ if( i==7 ){
196
+ return v;
197
+ }
198
+ return -1;
199
+ }else{
200
+ int iMin = 0;
201
+ int iMax = count(aCssColors)-1;
202
+ while( iMin<=iMax ){
203
+ int iMid = (iMin+iMax)/2;
204
+ int c = sqlite3_stricmp(aCssColors[iMid].zName, zName);
205
+ if( c==0 ) return aCssColors[iMid].iRGB;
206
+ if( c<0 ){
207
+ iMin = iMid+1;
208
+ }else{
209
+ iMax = iMid-1;
210
+ }
211
+ }
212
+ return -1;
213
+ }
214
+}
215
+
216
+/*
217
+** SETTING: raw-bgcolor boolean default=off
218
+**
219
+** Fossil usually tries to adjust user-specified background colors
220
+** for checkins so that the text is readable and so that the color
221
+** is not too garish. This setting disables that filter. When
222
+** this setting is on, the user-selected background colors are shown
223
+** exactly as requested.
224
+*/
225
+
226
+/*
227
+** Shift a color provided by the user so that it is suitable
228
+** for use as a background color in the current skin.
229
+**
230
+** The return value is a #HHHHHH color name contained in
231
+** static space that is overwritten on the next call.
232
+**
233
+** If we cannot make sense of the background color recommendation
234
+** that is the input, then return NULL.
235
+**
236
+** The iFgClr parameter is normally 0. But for testing purposes, set
237
+** it to 1 for a black foregrounds and 2 for a white foreground.
238
+*/
239
+const char *reasonable_bg_color(const char *zRequested, int iFgClr){
240
+ int iRGB = color_name_to_rgb(zRequested);
241
+ int r, g, b; /* RGB components of requested color */
242
+ static int systemFg = 0; /* 1==black-foreground 2==white-foreground */
243
+ int fg; /* Foreground color to actually use */
244
+ static char zColor[10]; /* Return value */
245
+
246
+ if( iFgClr ){
247
+ fg = iFgClr;
248
+ }else if( systemFg==0 ){
249
+ if( db_get_boolean("raw-bgcolor",0) ){
250
+ fg = systemFg = 3;
251
+ }else{
252
+ fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
253
+ }
254
+ }else{
255
+ fg = systemFg;
256
+ }
257
+ if( fg>=3 ) return zRequested;
258
+
259
+ if( iRGB<0 ) return 0;
260
+ r = (iRGB>>16) & 0xff;
261
+ g = (iRGB>>8) & 0xff;
262
+ b = iRGB & 0xff;
263
+ if( fg==1 ){
264
+ const int K = 70;
265
+ r = (K*r)/255 + (255-K);
266
+ g = (K*g)/255 + (255-K);
267
+ b = (K*b)/255 + (255-K);
268
+ }else{
269
+ const int K = 90;
270
+ r = (K*r)/255;
271
+ g = (K*g)/255;
272
+ b = (K*b)/255;
273
+ }
274
+ sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
275
+ return zColor;
276
+}
25277
26278
/*
27279
** Compute a hash on a branch or user name
28280
*/
29281
static unsigned int hash_of_name(const char *z){
@@ -185,5 +437,86 @@
185437
@ <input type="submit" value="Submit">
186438
@ <input type="submit" name="rand" value="Random">
187439
@ </form>
188440
style_finish_page();
189441
}
442
+
443
+/*
444
+** WEBPAGE: test-bgcolor
445
+**
446
+** Show how user-specified background colors will be rendered
447
+** using the reasonable_bg_color() algorithm.
448
+*/
449
+void test_bgcolor_page(void){
450
+ const char *zReq; /* Requested color name */
451
+ const char *zBG; /* Actual color provided */
452
+ const char *zBg1;
453
+ char zNm[10];
454
+ static const char *azDflt[] = {
455
+ "red", "orange", "yellow", "green", "blue", "indigo", "violet",
456
+ "tan", "brown", "gray"
457
+ };
458
+ int i, cnt, iClr, r, g, b;
459
+ char *zFg;
460
+ login_check_credentials();
461
+ style_set_current_feature("test");
462
+ style_header("Background Color Test");
463
+ for(i=cnt=0; i<10; i++){
464
+ sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
465
+ zReq = PD(zNm,azDflt[i]);
466
+ if( zReq==0 || zReq[0]==0 ) continue;
467
+ if( cnt==0 ){
468
+ @ <table border="1" cellspacing="0" cellpadding="10">
469
+ @ <tr>
470
+ @ <th>Requested Background
471
+ @ <th>Light mode
472
+ @ <th>Dark mode
473
+ @ </tr>
474
+ }
475
+ cnt++;
476
+ zBG = reasonable_bg_color(zReq, 0);
477
+ if( zBG==0 ){
478
+ @ <tr><td colspan="3" align="center">\
479
+ @ "%h(zReq)" is not a recognized color name</td></tr>
480
+ continue;
481
+ }
482
+ iClr = color_name_to_rgb(zReq);
483
+ r = (iClr>>16) & 0xff;
484
+ g = (iClr>>8) & 0xff;
485
+ b = iClr & 0xff;
486
+ if( 3*r + 7*g + b > 6*255 ){
487
+ zFg = "black";
488
+ }else{
489
+ zFg = "white";
490
+ }
491
+ if( zReq[0]!='#' ){
492
+ char zReqRGB[12];
493
+ sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
494
+ @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
495
+ @ Requested color "%h(zReq)" (%h(zReqRGB))</td>
496
+ }else{
497
+ @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
498
+ @ Requested color "%h(zReq)"</td>
499
+ }
500
+ zBg1 = reasonable_bg_color(zReq,1);
501
+ @ <td style='color:black;background-color:%h(zBg1);'>\
502
+ @ Background color for dark text: %h(zBg1)</td>
503
+ zBg1 = reasonable_bg_color(zReq,2);
504
+ @ <td style='color:white;background-color:%h(zBg1);'>\
505
+ @ Background color for light text: %h(zBg1)</td></tr>
506
+ }
507
+ if( cnt ){
508
+ @ </table>
509
+ @ <hr>
510
+ }
511
+ @ <form method="POST">
512
+ @ <p>Enter CSS color names below and see them shifted into corresponding
513
+ @ background colors above.</p>
514
+ for(i=0; i<10; i++){
515
+ sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
516
+ @ <input type="text" size="30" name='%s(zNm)' \
517
+ @ value='%h(PD(zNm,azDflt[i]))'><br>
518
+ }
519
+ @ <input type="submit" value="Submit">
520
+ @ </form>
521
+ style_finish_page();
522
+}
190523
--- src/color.c
+++ src/color.c
@@ -20,10 +20,262 @@
20 **
21 */
22 #include "config.h"
23 #include <string.h>
24 #include "color.h"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
26 /*
27 ** Compute a hash on a branch or user name
28 */
29 static unsigned int hash_of_name(const char *z){
@@ -185,5 +437,86 @@
185 @ <input type="submit" value="Submit">
186 @ <input type="submit" name="rand" value="Random">
187 @ </form>
188 style_finish_page();
189 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
--- src/color.c
+++ src/color.c
@@ -20,10 +20,262 @@
20 **
21 */
22 #include "config.h"
23 #include <string.h>
24 #include "color.h"
25
26 /*
27 ** 140 standard CSS color names and their corresponding RGB values,
28 ** in alphabetical order by name so that we can do a binary search
29 ** for lookup.
30 */
31 static const struct CssColors {
32 const char *zName; /* CSS Color name, lower case */
33 unsigned int iRGB; /* Corresponding RGB value */
34 } aCssColors[] = {
35 { "aliceblue", 0xf0f8ff },
36 { "antiquewhite", 0xfaebd7 },
37 { "aqua", 0x00ffff },
38 { "aquamarine", 0x7fffd4 },
39 { "azure", 0xf0ffff },
40 { "beige", 0xf5f5dc },
41 { "bisque", 0xffe4c4 },
42 { "black", 0x000000 },
43 { "blanchedalmond", 0xffebcd },
44 { "blue", 0x0000ff },
45 { "blueviolet", 0x8a2be2 },
46 { "brown", 0xa52a2a },
47 { "burlywood", 0xdeb887 },
48 { "cadetblue", 0x5f9ea0 },
49 { "chartreuse", 0x7fff00 },
50 { "chocolate", 0xd2691e },
51 { "coral", 0xff7f50 },
52 { "cornflowerblue", 0x6495ed },
53 { "cornsilk", 0xfff8dc },
54 { "crimson", 0xdc143c },
55 { "cyan", 0x00ffff },
56 { "darkblue", 0x00008b },
57 { "darkcyan", 0x008b8b },
58 { "darkgoldenrod", 0xb8860b },
59 { "darkgray", 0xa9a9a9 },
60 { "darkgreen", 0x006400 },
61 { "darkkhaki", 0xbdb76b },
62 { "darkmagenta", 0x8b008b },
63 { "darkolivegreen", 0x556b2f },
64 { "darkorange", 0xff8c00 },
65 { "darkorchid", 0x9932cc },
66 { "darkred", 0x8b0000 },
67 { "darksalmon", 0xe9967a },
68 { "darkseagreen", 0x8fbc8f },
69 { "darkslateblue", 0x483d8b },
70 { "darkslategray", 0x2f4f4f },
71 { "darkturquoise", 0x00ced1 },
72 { "darkviolet", 0x9400d3 },
73 { "deeppink", 0xff1493 },
74 { "deepskyblue", 0x00bfff },
75 { "dimgray", 0x696969 },
76 { "dodgerblue", 0x1e90ff },
77 { "firebrick", 0xb22222 },
78 { "floralwhite", 0xfffaf0 },
79 { "forestgreen", 0x228b22 },
80 { "fuchsia", 0xff00ff },
81 { "gainsboro", 0xdcdcdc },
82 { "ghostwhite", 0xf8f8ff },
83 { "gold", 0xffd700 },
84 { "goldenrod", 0xdaa520 },
85 { "gray", 0x808080 },
86 { "green", 0x008000 },
87 { "greenyellow", 0xadff2f },
88 { "honeydew", 0xf0fff0 },
89 { "hotpink", 0xff69b4 },
90 { "indianred", 0xcd5c5c },
91 { "indigo", 0x4b0082 },
92 { "ivory", 0xfffff0 },
93 { "khaki", 0xf0e68c },
94 { "lavender", 0xe6e6fa },
95 { "lavenderblush", 0xfff0f5 },
96 { "lawngreen", 0x7cfc00 },
97 { "lemonchiffon", 0xfffacd },
98 { "lightblue", 0xadd8e6 },
99 { "lightcoral", 0xf08080 },
100 { "lightcyan", 0xe0ffff },
101 { "lightgoldenrodyellow", 0xfafad2 },
102 { "lightgrey", 0xd3d3d3 },
103 { "lightgreen", 0x90ee90 },
104 { "lightpink", 0xffb6c1 },
105 { "lightsalmon", 0xffa07a },
106 { "lightseagreen", 0x20b2aa },
107 { "lightskyblue", 0x87cefa },
108 { "lightslategray", 0x778899 },
109 { "lightsteelblue", 0xb0c4de },
110 { "lightyellow", 0xffffe0 },
111 { "lime", 0x00ff00 },
112 { "limegreen", 0x32cd32 },
113 { "linen", 0xfaf0e6 },
114 { "magenta", 0xff00ff },
115 { "maroon", 0x800000 },
116 { "mediumaquamarine", 0x66cdaa },
117 { "mediumblue", 0x0000cd },
118 { "mediumorchid", 0xba55d3 },
119 { "mediumpurple", 0x9370d8 },
120 { "mediumseagreen", 0x3cb371 },
121 { "mediumslateblue", 0x7b68ee },
122 { "mediumspringgreen", 0x00fa9a },
123 { "mediumturquoise", 0x48d1cc },
124 { "mediumvioletred", 0xc71585 },
125 { "midnightblue", 0x191970 },
126 { "mintcream", 0xf5fffa },
127 { "mistyrose", 0xffe4e1 },
128 { "moccasin", 0xffe4b5 },
129 { "navajowhite", 0xffdead },
130 { "navy", 0x000080 },
131 { "oldlace", 0xfdf5e6 },
132 { "olive", 0x808000 },
133 { "olivedrab", 0x6b8e23 },
134 { "orange", 0xffa500 },
135 { "orangered", 0xff4500 },
136 { "orchid", 0xda70d6 },
137 { "palegoldenrod", 0xeee8aa },
138 { "palegreen", 0x98fb98 },
139 { "paleturquoise", 0xafeeee },
140 { "palevioletred", 0xd87093 },
141 { "papayawhip", 0xffefd5 },
142 { "peachpuff", 0xffdab9 },
143 { "peru", 0xcd853f },
144 { "pink", 0xffc0cb },
145 { "plum", 0xdda0dd },
146 { "powderblue", 0xb0e0e6 },
147 { "purple", 0x800080 },
148 { "red", 0xff0000 },
149 { "rosybrown", 0xbc8f8f },
150 { "royalblue", 0x4169e1 },
151 { "saddlebrown", 0x8b4513 },
152 { "salmon", 0xfa8072 },
153 { "sandybrown", 0xf4a460 },
154 { "seagreen", 0x2e8b57 },
155 { "seashell", 0xfff5ee },
156 { "sienna", 0xa0522d },
157 { "silver", 0xc0c0c0 },
158 { "skyblue", 0x87ceeb },
159 { "slateblue", 0x6a5acd },
160 { "slategray", 0x708090 },
161 { "snow", 0xfffafa },
162 { "springgreen", 0x00ff7f },
163 { "steelblue", 0x4682b4 },
164 { "tan", 0xd2b48c },
165 { "teal", 0x008080 },
166 { "thistle", 0xd8bfd8 },
167 { "tomato", 0xff6347 },
168 { "turquoise", 0x40e0d0 },
169 { "violet", 0xee82ee },
170 { "wheat", 0xf5deb3 },
171 { "white", 0xffffff },
172 { "whitesmoke", 0xf5f5f5 },
173 { "yellow", 0xffff00 },
174 { "yellowgreen", 0x9acd32 },
175 };
176
177 /*
178 ** Attempt to translate a CSS color name into an integer that
179 ** represents the equivalent RGB value. Ignore alpha if provided.
180 ** If the name cannot be translated, return -1.
181 */
182 int color_name_to_rgb(const char *zName){
183 if( zName==0 || zName[0]==0 ) return -1;
184 if( zName[0]=='#' ){
185 int i, v = 0;
186 for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){
187 v = v*16 + fossil_hexvalue(zName[i]);
188 }
189 if( i==4 ){
190 v = fossil_hexvalue(zName[1])*0x110000 +
191 fossil_hexvalue(zName[2])*0x1100 +
192 fossil_hexvalue(zName[3])*0x11;
193 return v;
194 }
195 if( i==7 ){
196 return v;
197 }
198 return -1;
199 }else{
200 int iMin = 0;
201 int iMax = count(aCssColors)-1;
202 while( iMin<=iMax ){
203 int iMid = (iMin+iMax)/2;
204 int c = sqlite3_stricmp(aCssColors[iMid].zName, zName);
205 if( c==0 ) return aCssColors[iMid].iRGB;
206 if( c<0 ){
207 iMin = iMid+1;
208 }else{
209 iMax = iMid-1;
210 }
211 }
212 return -1;
213 }
214 }
215
216 /*
217 ** SETTING: raw-bgcolor boolean default=off
218 **
219 ** Fossil usually tries to adjust user-specified background colors
220 ** for checkins so that the text is readable and so that the color
221 ** is not too garish. This setting disables that filter. When
222 ** this setting is on, the user-selected background colors are shown
223 ** exactly as requested.
224 */
225
226 /*
227 ** Shift a color provided by the user so that it is suitable
228 ** for use as a background color in the current skin.
229 **
230 ** The return value is a #HHHHHH color name contained in
231 ** static space that is overwritten on the next call.
232 **
233 ** If we cannot make sense of the background color recommendation
234 ** that is the input, then return NULL.
235 **
236 ** The iFgClr parameter is normally 0. But for testing purposes, set
237 ** it to 1 for a black foregrounds and 2 for a white foreground.
238 */
239 const char *reasonable_bg_color(const char *zRequested, int iFgClr){
240 int iRGB = color_name_to_rgb(zRequested);
241 int r, g, b; /* RGB components of requested color */
242 static int systemFg = 0; /* 1==black-foreground 2==white-foreground */
243 int fg; /* Foreground color to actually use */
244 static char zColor[10]; /* Return value */
245
246 if( iFgClr ){
247 fg = iFgClr;
248 }else if( systemFg==0 ){
249 if( db_get_boolean("raw-bgcolor",0) ){
250 fg = systemFg = 3;
251 }else{
252 fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
253 }
254 }else{
255 fg = systemFg;
256 }
257 if( fg>=3 ) return zRequested;
258
259 if( iRGB<0 ) return 0;
260 r = (iRGB>>16) & 0xff;
261 g = (iRGB>>8) & 0xff;
262 b = iRGB & 0xff;
263 if( fg==1 ){
264 const int K = 70;
265 r = (K*r)/255 + (255-K);
266 g = (K*g)/255 + (255-K);
267 b = (K*b)/255 + (255-K);
268 }else{
269 const int K = 90;
270 r = (K*r)/255;
271 g = (K*g)/255;
272 b = (K*b)/255;
273 }
274 sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
275 return zColor;
276 }
277
278 /*
279 ** Compute a hash on a branch or user name
280 */
281 static unsigned int hash_of_name(const char *z){
@@ -185,5 +437,86 @@
437 @ <input type="submit" value="Submit">
438 @ <input type="submit" name="rand" value="Random">
439 @ </form>
440 style_finish_page();
441 }
442
443 /*
444 ** WEBPAGE: test-bgcolor
445 **
446 ** Show how user-specified background colors will be rendered
447 ** using the reasonable_bg_color() algorithm.
448 */
449 void test_bgcolor_page(void){
450 const char *zReq; /* Requested color name */
451 const char *zBG; /* Actual color provided */
452 const char *zBg1;
453 char zNm[10];
454 static const char *azDflt[] = {
455 "red", "orange", "yellow", "green", "blue", "indigo", "violet",
456 "tan", "brown", "gray"
457 };
458 int i, cnt, iClr, r, g, b;
459 char *zFg;
460 login_check_credentials();
461 style_set_current_feature("test");
462 style_header("Background Color Test");
463 for(i=cnt=0; i<10; i++){
464 sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
465 zReq = PD(zNm,azDflt[i]);
466 if( zReq==0 || zReq[0]==0 ) continue;
467 if( cnt==0 ){
468 @ <table border="1" cellspacing="0" cellpadding="10">
469 @ <tr>
470 @ <th>Requested Background
471 @ <th>Light mode
472 @ <th>Dark mode
473 @ </tr>
474 }
475 cnt++;
476 zBG = reasonable_bg_color(zReq, 0);
477 if( zBG==0 ){
478 @ <tr><td colspan="3" align="center">\
479 @ "%h(zReq)" is not a recognized color name</td></tr>
480 continue;
481 }
482 iClr = color_name_to_rgb(zReq);
483 r = (iClr>>16) & 0xff;
484 g = (iClr>>8) & 0xff;
485 b = iClr & 0xff;
486 if( 3*r + 7*g + b > 6*255 ){
487 zFg = "black";
488 }else{
489 zFg = "white";
490 }
491 if( zReq[0]!='#' ){
492 char zReqRGB[12];
493 sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
494 @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
495 @ Requested color "%h(zReq)" (%h(zReqRGB))</td>
496 }else{
497 @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
498 @ Requested color "%h(zReq)"</td>
499 }
500 zBg1 = reasonable_bg_color(zReq,1);
501 @ <td style='color:black;background-color:%h(zBg1);'>\
502 @ Background color for dark text: %h(zBg1)</td>
503 zBg1 = reasonable_bg_color(zReq,2);
504 @ <td style='color:white;background-color:%h(zBg1);'>\
505 @ Background color for light text: %h(zBg1)</td></tr>
506 }
507 if( cnt ){
508 @ </table>
509 @ <hr>
510 }
511 @ <form method="POST">
512 @ <p>Enter CSS color names below and see them shifted into corresponding
513 @ background colors above.</p>
514 for(i=0; i<10; i++){
515 sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
516 @ <input type="text" size="30" name='%s(zNm)' \
517 @ value='%h(PD(zNm,azDflt[i]))'><br>
518 }
519 @ <input type="submit" value="Submit">
520 @ </form>
521 style_finish_page();
522 }
523
+4 -4
--- src/comformat.c
+++ src/comformat.c
@@ -266,14 +266,14 @@
266266
int i = 0; /* Counted bytes. */
267267
int cchUTF8 = 1; /* Code units consumed. */
268268
int maxUTF8 = 1; /* Expected sequence length. */
269269
char c = z[i++];
270270
if( c==0x1b && z[i]=='[' ){
271
- do{
272
- i++;
273
- }while( i<fossil_isdigit(z[i]) || z[i]==';' );
274
- if( fossil_isalpha(z[i]) ){
271
+ i++;
272
+ while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
273
+ while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
274
+ if( z[i]>=0x40 && z[i]<=0x7e ){
275275
*pCchUTF8 = i+1;
276276
*pUtf32 = 0x301; /* A zero-width character */
277277
return;
278278
}
279279
}
280280
--- src/comformat.c
+++ src/comformat.c
@@ -266,14 +266,14 @@
266 int i = 0; /* Counted bytes. */
267 int cchUTF8 = 1; /* Code units consumed. */
268 int maxUTF8 = 1; /* Expected sequence length. */
269 char c = z[i++];
270 if( c==0x1b && z[i]=='[' ){
271 do{
272 i++;
273 }while( i<fossil_isdigit(z[i]) || z[i]==';' );
274 if( fossil_isalpha(z[i]) ){
275 *pCchUTF8 = i+1;
276 *pUtf32 = 0x301; /* A zero-width character */
277 return;
278 }
279 }
280
--- src/comformat.c
+++ src/comformat.c
@@ -266,14 +266,14 @@
266 int i = 0; /* Counted bytes. */
267 int cchUTF8 = 1; /* Code units consumed. */
268 int maxUTF8 = 1; /* Expected sequence length. */
269 char c = z[i++];
270 if( c==0x1b && z[i]=='[' ){
271 i++;
272 while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
273 while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
274 if( z[i]>=0x40 && z[i]<=0x7e ){
275 *pCchUTF8 = i+1;
276 *pUtf32 = 0x301; /* A zero-width character */
277 return;
278 }
279 }
280
--- src/configure.c
+++ src/configure.c
@@ -100,11 +100,10 @@
100100
{ "logo-image", CONFIGSET_SKIN },
101101
{ "background-mimetype", CONFIGSET_SKIN },
102102
{ "background-image", CONFIGSET_SKIN },
103103
{ "icon-mimetype", CONFIGSET_SKIN },
104104
{ "icon-image", CONFIGSET_SKIN },
105
- { "timeline-block-markup", CONFIGSET_SKIN },
106105
{ "timeline-date-format", CONFIGSET_SKIN },
107106
{ "timeline-default-style", CONFIGSET_SKIN },
108107
{ "timeline-dwelltime", CONFIGSET_SKIN },
109108
{ "timeline-closetime", CONFIGSET_SKIN },
110109
{ "timeline-hard-newlines", CONFIGSET_SKIN },
111110
--- src/configure.c
+++ src/configure.c
@@ -100,11 +100,10 @@
100 { "logo-image", CONFIGSET_SKIN },
101 { "background-mimetype", CONFIGSET_SKIN },
102 { "background-image", CONFIGSET_SKIN },
103 { "icon-mimetype", CONFIGSET_SKIN },
104 { "icon-image", CONFIGSET_SKIN },
105 { "timeline-block-markup", CONFIGSET_SKIN },
106 { "timeline-date-format", CONFIGSET_SKIN },
107 { "timeline-default-style", CONFIGSET_SKIN },
108 { "timeline-dwelltime", CONFIGSET_SKIN },
109 { "timeline-closetime", CONFIGSET_SKIN },
110 { "timeline-hard-newlines", CONFIGSET_SKIN },
111
--- src/configure.c
+++ src/configure.c
@@ -100,11 +100,10 @@
100 { "logo-image", CONFIGSET_SKIN },
101 { "background-mimetype", CONFIGSET_SKIN },
102 { "background-image", CONFIGSET_SKIN },
103 { "icon-mimetype", CONFIGSET_SKIN },
104 { "icon-image", CONFIGSET_SKIN },
 
105 { "timeline-date-format", CONFIGSET_SKIN },
106 { "timeline-default-style", CONFIGSET_SKIN },
107 { "timeline-dwelltime", CONFIGSET_SKIN },
108 { "timeline-closetime", CONFIGSET_SKIN },
109 { "timeline-hard-newlines", CONFIGSET_SKIN },
110
+4 -1
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
16291629
sqlite3_create_function(db, "chat_msg_from_event", 4,
16301630
SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
16311631
chat_msg_from_event, 0, 0);
16321632
sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
16331633
file_inode_sql_func,0,0);
1634
+ sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635
+ artifact_to_json_sql_func,0,0);
16341636
16351637
}
16361638
16371639
#if USE_SEE
16381640
/*
@@ -5067,11 +5069,12 @@
50675069
**
50685070
** All repositories are searched (in lexicographical order) and the first
50695071
** repository with a non-zero "repolist-skin" value is used as the skin
50705072
** for the repository list page. If none of the repositories on the list
50715073
** have a non-zero "repolist-skin" setting then the repository list is
5072
-** displayed using unadorned HTML ("skinless").
5074
+** displayed using unadorned HTML ("skinless"), with the page title taken
5075
+** from the FOSSIL_REPOLIST_TITLE environment variable.
50735076
**
50745077
** If repolist-skin has a value of 2, then the repository is omitted from
50755078
** the list in use cases 1 through 4, but not for 5 and 6.
50765079
*/
50775080
/*
50785081
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
 
 
1634
1635 }
1636
1637 #if USE_SEE
1638 /*
@@ -5067,11 +5069,12 @@
5067 **
5068 ** All repositories are searched (in lexicographical order) and the first
5069 ** repository with a non-zero "repolist-skin" value is used as the skin
5070 ** for the repository list page. If none of the repositories on the list
5071 ** have a non-zero "repolist-skin" setting then the repository list is
5072 ** displayed using unadorned HTML ("skinless").
 
5073 **
5074 ** If repolist-skin has a value of 2, then the repository is omitted from
5075 ** the list in use cases 1 through 4, but not for 5 and 6.
5076 */
5077 /*
5078
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
1634 sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635 artifact_to_json_sql_func,0,0);
1636
1637 }
1638
1639 #if USE_SEE
1640 /*
@@ -5067,11 +5069,12 @@
5069 **
5070 ** All repositories are searched (in lexicographical order) and the first
5071 ** repository with a non-zero "repolist-skin" value is used as the skin
5072 ** for the repository list page. If none of the repositories on the list
5073 ** have a non-zero "repolist-skin" setting then the repository list is
5074 ** displayed using unadorned HTML ("skinless"), with the page title taken
5075 ** from the FOSSIL_REPOLIST_TITLE environment variable.
5076 **
5077 ** If repolist-skin has a value of 2, then the repository is omitted from
5078 ** the list in use cases 1 through 4, but not for 5 and 6.
5079 */
5080 /*
5081
+1 -1
--- src/doc.c
+++ src/doc.c
@@ -639,11 +639,11 @@
639639
** are special to HTML encoded. We need to decode these before turning
640640
** the text into a title, as the title text will be reencoded later */
641641
char *zTitle = mprintf("%.*s", nValue, zValue);
642642
int i;
643643
for(i=0; fossil_isspace(zTitle[i]); i++){}
644
- html_to_plaintext(zTitle+i, pTitle);
644
+ html_to_plaintext(zTitle+i, pTitle, 0);
645645
fossil_free(zTitle);
646646
seenTitle = 1;
647647
if( seenClass ) return 1;
648648
}
649649
}
650650
--- src/doc.c
+++ src/doc.c
@@ -639,11 +639,11 @@
639 ** are special to HTML encoded. We need to decode these before turning
640 ** the text into a title, as the title text will be reencoded later */
641 char *zTitle = mprintf("%.*s", nValue, zValue);
642 int i;
643 for(i=0; fossil_isspace(zTitle[i]); i++){}
644 html_to_plaintext(zTitle+i, pTitle);
645 fossil_free(zTitle);
646 seenTitle = 1;
647 if( seenClass ) return 1;
648 }
649 }
650
--- src/doc.c
+++ src/doc.c
@@ -639,11 +639,11 @@
639 ** are special to HTML encoded. We need to decode these before turning
640 ** the text into a title, as the title text will be reencoded later */
641 char *zTitle = mprintf("%.*s", nValue, zValue);
642 int i;
643 for(i=0; fossil_isspace(zTitle[i]); i++){}
644 html_to_plaintext(zTitle+i, pTitle, 0);
645 fossil_free(zTitle);
646 seenTitle = 1;
647 if( seenClass ) return 1;
648 }
649 }
650
+3 -3
--- src/encode.c
+++ src/encode.c
@@ -244,11 +244,11 @@
244244
}
245245
246246
/*
247247
** Convert a single HEX digit to an integer
248248
*/
249
-static int AsciiToHex(int c){
249
+int fossil_hexvalue(int c){
250250
if( c>='a' && c<='f' ){
251251
c += 10 - 'a';
252252
}else if( c>='A' && c<='F' ){
253253
c += 10 - 'A';
254254
}else if( c>='0' && c<='9' ){
@@ -272,12 +272,12 @@
272272
i = j = 0;
273273
while( z[i] ){
274274
switch( z[i] ){
275275
case '%':
276276
if( z[i+1] && z[i+2] ){
277
- z[j] = AsciiToHex(z[i+1]) << 4;
278
- z[j] |= AsciiToHex(z[i+2]);
277
+ z[j] = fossil_hexvalue(z[i+1]) << 4;
278
+ z[j] |= fossil_hexvalue(z[i+2]);
279279
i += 2;
280280
}
281281
break;
282282
case '+':
283283
z[j] = ' ';
284284
--- src/encode.c
+++ src/encode.c
@@ -244,11 +244,11 @@
244 }
245
246 /*
247 ** Convert a single HEX digit to an integer
248 */
249 static int AsciiToHex(int c){
250 if( c>='a' && c<='f' ){
251 c += 10 - 'a';
252 }else if( c>='A' && c<='F' ){
253 c += 10 - 'A';
254 }else if( c>='0' && c<='9' ){
@@ -272,12 +272,12 @@
272 i = j = 0;
273 while( z[i] ){
274 switch( z[i] ){
275 case '%':
276 if( z[i+1] && z[i+2] ){
277 z[j] = AsciiToHex(z[i+1]) << 4;
278 z[j] |= AsciiToHex(z[i+2]);
279 i += 2;
280 }
281 break;
282 case '+':
283 z[j] = ' ';
284
--- src/encode.c
+++ src/encode.c
@@ -244,11 +244,11 @@
244 }
245
246 /*
247 ** Convert a single HEX digit to an integer
248 */
249 int fossil_hexvalue(int c){
250 if( c>='a' && c<='f' ){
251 c += 10 - 'a';
252 }else if( c>='A' && c<='F' ){
253 c += 10 - 'A';
254 }else if( c>='0' && c<='9' ){
@@ -272,12 +272,12 @@
272 i = j = 0;
273 while( z[i] ){
274 switch( z[i] ){
275 case '%':
276 if( z[i+1] && z[i+2] ){
277 z[j] = fossil_hexvalue(z[i+1]) << 4;
278 z[j] |= fossil_hexvalue(z[i+2]);
279 i += 2;
280 }
281 break;
282 case '+':
283 z[j] = ' ';
284
--- src/finfo.c
+++ src/finfo.c
@@ -636,10 +636,12 @@
636636
if( zBr==0 ) zBr = "trunk";
637637
if( uBg ){
638638
zBgClr = user_color(zUser);
639639
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
640640
zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
641
+ }else if( zBgClr ){
642
+ zBgClr = reasonable_bg_color(zBgClr,0);
641643
}
642644
gidx = graph_add_row(pGraph,
643645
frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
644646
nParent, 0, aParent, zBr, zBgClr,
645647
zUuid, 0);
646648
--- src/finfo.c
+++ src/finfo.c
@@ -636,10 +636,12 @@
636 if( zBr==0 ) zBr = "trunk";
637 if( uBg ){
638 zBgClr = user_color(zUser);
639 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
640 zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
 
 
641 }
642 gidx = graph_add_row(pGraph,
643 frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
644 nParent, 0, aParent, zBr, zBgClr,
645 zUuid, 0);
646
--- src/finfo.c
+++ src/finfo.c
@@ -636,10 +636,12 @@
636 if( zBr==0 ) zBr = "trunk";
637 if( uBg ){
638 zBgClr = user_color(zUser);
639 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
640 zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
641 }else if( zBgClr ){
642 zBgClr = reasonable_bg_color(zBgClr,0);
643 }
644 gidx = graph_add_row(pGraph,
645 frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
646 nParent, 0, aParent, zBr, zBgClr,
647 zUuid, 0);
648
+29 -11
--- src/graph.c
+++ src/graph.c
@@ -59,11 +59,11 @@
5959
** check-ins and many files. For this reason, we make the identifier
6060
** a 64-bit integer, to dramatically reduce the risk of an overflow.
6161
*/
6262
typedef sqlite3_int64 GraphRowId;
6363
64
-#define GR_MAX_RAIL 40 /* Max number of "rails" to display */
64
+#define GR_MAX_RAIL 64 /* Max number of "rails" to display */
6565
6666
/* The graph appears vertically beside a timeline. Each row in the
6767
** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
6868
** the top-most row and increases moving down. Hence (in the absence of
6969
** time skew) parents have a larger index than their children.
@@ -118,10 +118,11 @@
118118
char **azBranch; /* Names of the branches */
119119
int nRow; /* Number of rows */
120120
int nHash; /* Number of slots in apHash[] */
121121
u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122122
** rail that the node */
123
+ u8 bOverfull; /* Unable to allocate sufficient rails */
123124
u64 mergeRail; /* Rails used for merge lines */
124125
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
125126
u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
126127
};
127128
@@ -336,11 +337,18 @@
336337
iBestDist = dist;
337338
iBest = i;
338339
}
339340
}
340341
}
341
- if( iBestDist>1000 ) p->nErr++;
342
+ if( iBestDist>1000 ){
343
+ p->bOverfull = 1;
344
+ iBest = GR_MAX_RAIL;
345
+ }
346
+ if( iBest>GR_MAX_RAIL ){
347
+ p->bOverfull = 1;
348
+ iBest = GR_MAX_RAIL;
349
+ }
342350
if( iBest>p->mxRail ) p->mxRail = iBest;
343351
if( bMergeRail ) p->mergeRail |= BIT(iBest);
344352
return iBest;
345353
}
346354
@@ -709,11 +717,11 @@
709717
if( pRow->iRail>=0 ) continue;
710718
if( pRow->isDup ) continue;
711719
if( pRow->nParent<0 ) continue;
712720
if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
713721
pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0);
714
- if( p->mxRail>=GR_MAX_RAIL ) return;
722
+ /* if( p->mxRail>=GR_MAX_RAIL ) return; */
715723
mask = BIT(pRow->iRail);
716724
if( !omitDescenders ){
717725
int n = RISER_MARGIN;
718726
pRow->bDescender = pRow->nParent>0;
719727
for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
@@ -744,28 +752,38 @@
744752
assert( pRow->nParent>0 );
745753
parentRid = pRow->aParent[0];
746754
pParent = hashFind(p, parentRid);
747755
if( pParent==0 ){
748756
pRow->iRail = ++p->mxRail;
749
- if( p->mxRail>=GR_MAX_RAIL ) return;
757
+ if( p->mxRail>=GR_MAX_RAIL ){
758
+ pRow->iRail = p->mxRail = GR_MAX_RAIL;
759
+ p->bOverfull = 1;
760
+ }
750761
pRow->railInUse = BIT(pRow->iRail);
751762
continue;
752763
}
753764
if( pParent->idx>pRow->idx ){
754765
/* Common case: Child occurs after parent and is above the
755766
** parent in the timeline */
756767
pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
757768
pParent->iRail, 0);
758
- if( p->mxRail>=GR_MAX_RAIL ) return;
769
+ /* if( p->mxRail>=GR_MAX_RAIL ) return; */
759770
pParent->aiRiser[pRow->iRail] = pRow->idx;
760771
}else{
761772
/* Timewarp case: Child occurs earlier in time than parent and
762773
** appears below the parent in the timeline. */
763774
int iDownRail = ++p->mxRail;
764775
if( iDownRail<1 ) iDownRail = ++p->mxRail;
776
+ if( p->mxRail>GR_MAX_RAIL ){
777
+ iDownRail = p->mxRail = GR_MAX_RAIL;
778
+ p->bOverfull = 1;
779
+ }
765780
pRow->iRail = ++p->mxRail;
766
- if( p->mxRail>=GR_MAX_RAIL ) return;
781
+ if( p->mxRail>=GR_MAX_RAIL ){
782
+ pRow->iRail = p->mxRail = GR_MAX_RAIL;
783
+ p->bOverfull = 1;
784
+ }
767785
pRow->railInUse = BIT(pRow->iRail);
768786
pParent->aiRiser[iDownRail] = pRow->idx;
769787
mask = BIT(iDownRail);
770788
for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
771789
pLoop->railInUse |= mask;
@@ -824,11 +842,11 @@
824842
break;
825843
}
826844
}
827845
if( iMrail==-1 ){
828846
iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1);
829
- if( p->mxRail>=GR_MAX_RAIL ) return;
847
+ /*if( p->mxRail>=GR_MAX_RAIL ) return;*/
830848
mergeRiserFrom[iMrail] = parentRid;
831849
}
832850
iReuseIdx = p->nRow+1;
833851
iReuseRail = iMrail;
834852
mask = BIT(iMrail);
@@ -856,11 +874,11 @@
856874
pDesc->mergeUpto = pDesc->idx;
857875
}
858876
}else{
859877
/* Create a new merge for an on-screen node */
860878
createMergeRiser(p, pDesc, pRow, isCherrypick);
861
- if( p->mxRail>=GR_MAX_RAIL ) return;
879
+ /* if( p->mxRail>=GR_MAX_RAIL ) return; */
862880
if( iReuseIdx<0
863881
&& pDesc->nMergeChild==1
864882
&& (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
865883
){
866884
iReuseIdx = pDesc->idx;
@@ -872,17 +890,17 @@
872890
}
873891
874892
/*
875893
** Insert merge rails from primaries to duplicates.
876894
*/
877
- if( hasDup ){
895
+ if( hasDup && p->mxRail<GR_MAX_RAIL ){
878896
int dupRail;
879897
int mxRail;
880898
find_max_rail(p);
881899
mxRail = p->mxRail;
882900
dupRail = mxRail+1;
883
- if( p->mxRail>=GR_MAX_RAIL ) return;
901
+ /* if( p->mxRail>=GR_MAX_RAIL ) return; */
884902
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
885903
if( !pRow->isDup ) continue;
886904
pRow->iRail = dupRail;
887905
pDesc = hashFind(p, pRow->rid);
888906
assert( pDesc!=0 && pDesc!=pRow );
@@ -893,11 +911,11 @@
893911
dupRail = mxRail+1;
894912
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
895913
if( pRow->isDup ) pRow->iRail = dupRail;
896914
}
897915
}
898
- if( mxRail>=GR_MAX_RAIL ) return;
916
+ /* if( mxRail>=GR_MAX_RAIL ) return; */
899917
}
900918
901919
/*
902920
** Find the maximum rail number.
903921
*/
904922
--- src/graph.c
+++ src/graph.c
@@ -59,11 +59,11 @@
59 ** check-ins and many files. For this reason, we make the identifier
60 ** a 64-bit integer, to dramatically reduce the risk of an overflow.
61 */
62 typedef sqlite3_int64 GraphRowId;
63
64 #define GR_MAX_RAIL 40 /* Max number of "rails" to display */
65
66 /* The graph appears vertically beside a timeline. Each row in the
67 ** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
68 ** the top-most row and increases moving down. Hence (in the absence of
69 ** time skew) parents have a larger index than their children.
@@ -118,10 +118,11 @@
118 char **azBranch; /* Names of the branches */
119 int nRow; /* Number of rows */
120 int nHash; /* Number of slots in apHash[] */
121 u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122 ** rail that the node */
 
123 u64 mergeRail; /* Rails used for merge lines */
124 GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
125 u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
126 };
127
@@ -336,11 +337,18 @@
336 iBestDist = dist;
337 iBest = i;
338 }
339 }
340 }
341 if( iBestDist>1000 ) p->nErr++;
 
 
 
 
 
 
 
342 if( iBest>p->mxRail ) p->mxRail = iBest;
343 if( bMergeRail ) p->mergeRail |= BIT(iBest);
344 return iBest;
345 }
346
@@ -709,11 +717,11 @@
709 if( pRow->iRail>=0 ) continue;
710 if( pRow->isDup ) continue;
711 if( pRow->nParent<0 ) continue;
712 if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
713 pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0);
714 if( p->mxRail>=GR_MAX_RAIL ) return;
715 mask = BIT(pRow->iRail);
716 if( !omitDescenders ){
717 int n = RISER_MARGIN;
718 pRow->bDescender = pRow->nParent>0;
719 for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
@@ -744,28 +752,38 @@
744 assert( pRow->nParent>0 );
745 parentRid = pRow->aParent[0];
746 pParent = hashFind(p, parentRid);
747 if( pParent==0 ){
748 pRow->iRail = ++p->mxRail;
749 if( p->mxRail>=GR_MAX_RAIL ) return;
 
 
 
750 pRow->railInUse = BIT(pRow->iRail);
751 continue;
752 }
753 if( pParent->idx>pRow->idx ){
754 /* Common case: Child occurs after parent and is above the
755 ** parent in the timeline */
756 pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
757 pParent->iRail, 0);
758 if( p->mxRail>=GR_MAX_RAIL ) return;
759 pParent->aiRiser[pRow->iRail] = pRow->idx;
760 }else{
761 /* Timewarp case: Child occurs earlier in time than parent and
762 ** appears below the parent in the timeline. */
763 int iDownRail = ++p->mxRail;
764 if( iDownRail<1 ) iDownRail = ++p->mxRail;
 
 
 
 
765 pRow->iRail = ++p->mxRail;
766 if( p->mxRail>=GR_MAX_RAIL ) return;
 
 
 
767 pRow->railInUse = BIT(pRow->iRail);
768 pParent->aiRiser[iDownRail] = pRow->idx;
769 mask = BIT(iDownRail);
770 for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
771 pLoop->railInUse |= mask;
@@ -824,11 +842,11 @@
824 break;
825 }
826 }
827 if( iMrail==-1 ){
828 iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1);
829 if( p->mxRail>=GR_MAX_RAIL ) return;
830 mergeRiserFrom[iMrail] = parentRid;
831 }
832 iReuseIdx = p->nRow+1;
833 iReuseRail = iMrail;
834 mask = BIT(iMrail);
@@ -856,11 +874,11 @@
856 pDesc->mergeUpto = pDesc->idx;
857 }
858 }else{
859 /* Create a new merge for an on-screen node */
860 createMergeRiser(p, pDesc, pRow, isCherrypick);
861 if( p->mxRail>=GR_MAX_RAIL ) return;
862 if( iReuseIdx<0
863 && pDesc->nMergeChild==1
864 && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
865 ){
866 iReuseIdx = pDesc->idx;
@@ -872,17 +890,17 @@
872 }
873
874 /*
875 ** Insert merge rails from primaries to duplicates.
876 */
877 if( hasDup ){
878 int dupRail;
879 int mxRail;
880 find_max_rail(p);
881 mxRail = p->mxRail;
882 dupRail = mxRail+1;
883 if( p->mxRail>=GR_MAX_RAIL ) return;
884 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
885 if( !pRow->isDup ) continue;
886 pRow->iRail = dupRail;
887 pDesc = hashFind(p, pRow->rid);
888 assert( pDesc!=0 && pDesc!=pRow );
@@ -893,11 +911,11 @@
893 dupRail = mxRail+1;
894 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
895 if( pRow->isDup ) pRow->iRail = dupRail;
896 }
897 }
898 if( mxRail>=GR_MAX_RAIL ) return;
899 }
900
901 /*
902 ** Find the maximum rail number.
903 */
904
--- src/graph.c
+++ src/graph.c
@@ -59,11 +59,11 @@
59 ** check-ins and many files. For this reason, we make the identifier
60 ** a 64-bit integer, to dramatically reduce the risk of an overflow.
61 */
62 typedef sqlite3_int64 GraphRowId;
63
64 #define GR_MAX_RAIL 64 /* Max number of "rails" to display */
65
66 /* The graph appears vertically beside a timeline. Each row in the
67 ** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
68 ** the top-most row and increases moving down. Hence (in the absence of
69 ** time skew) parents have a larger index than their children.
@@ -118,10 +118,11 @@
118 char **azBranch; /* Names of the branches */
119 int nRow; /* Number of rows */
120 int nHash; /* Number of slots in apHash[] */
121 u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122 ** rail that the node */
123 u8 bOverfull; /* Unable to allocate sufficient rails */
124 u64 mergeRail; /* Rails used for merge lines */
125 GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
126 u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
127 };
128
@@ -336,11 +337,18 @@
337 iBestDist = dist;
338 iBest = i;
339 }
340 }
341 }
342 if( iBestDist>1000 ){
343 p->bOverfull = 1;
344 iBest = GR_MAX_RAIL;
345 }
346 if( iBest>GR_MAX_RAIL ){
347 p->bOverfull = 1;
348 iBest = GR_MAX_RAIL;
349 }
350 if( iBest>p->mxRail ) p->mxRail = iBest;
351 if( bMergeRail ) p->mergeRail |= BIT(iBest);
352 return iBest;
353 }
354
@@ -709,11 +717,11 @@
717 if( pRow->iRail>=0 ) continue;
718 if( pRow->isDup ) continue;
719 if( pRow->nParent<0 ) continue;
720 if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
721 pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0);
722 /* if( p->mxRail>=GR_MAX_RAIL ) return; */
723 mask = BIT(pRow->iRail);
724 if( !omitDescenders ){
725 int n = RISER_MARGIN;
726 pRow->bDescender = pRow->nParent>0;
727 for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
@@ -744,28 +752,38 @@
752 assert( pRow->nParent>0 );
753 parentRid = pRow->aParent[0];
754 pParent = hashFind(p, parentRid);
755 if( pParent==0 ){
756 pRow->iRail = ++p->mxRail;
757 if( p->mxRail>=GR_MAX_RAIL ){
758 pRow->iRail = p->mxRail = GR_MAX_RAIL;
759 p->bOverfull = 1;
760 }
761 pRow->railInUse = BIT(pRow->iRail);
762 continue;
763 }
764 if( pParent->idx>pRow->idx ){
765 /* Common case: Child occurs after parent and is above the
766 ** parent in the timeline */
767 pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
768 pParent->iRail, 0);
769 /* if( p->mxRail>=GR_MAX_RAIL ) return; */
770 pParent->aiRiser[pRow->iRail] = pRow->idx;
771 }else{
772 /* Timewarp case: Child occurs earlier in time than parent and
773 ** appears below the parent in the timeline. */
774 int iDownRail = ++p->mxRail;
775 if( iDownRail<1 ) iDownRail = ++p->mxRail;
776 if( p->mxRail>GR_MAX_RAIL ){
777 iDownRail = p->mxRail = GR_MAX_RAIL;
778 p->bOverfull = 1;
779 }
780 pRow->iRail = ++p->mxRail;
781 if( p->mxRail>=GR_MAX_RAIL ){
782 pRow->iRail = p->mxRail = GR_MAX_RAIL;
783 p->bOverfull = 1;
784 }
785 pRow->railInUse = BIT(pRow->iRail);
786 pParent->aiRiser[iDownRail] = pRow->idx;
787 mask = BIT(iDownRail);
788 for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
789 pLoop->railInUse |= mask;
@@ -824,11 +842,11 @@
842 break;
843 }
844 }
845 if( iMrail==-1 ){
846 iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1);
847 /*if( p->mxRail>=GR_MAX_RAIL ) return;*/
848 mergeRiserFrom[iMrail] = parentRid;
849 }
850 iReuseIdx = p->nRow+1;
851 iReuseRail = iMrail;
852 mask = BIT(iMrail);
@@ -856,11 +874,11 @@
874 pDesc->mergeUpto = pDesc->idx;
875 }
876 }else{
877 /* Create a new merge for an on-screen node */
878 createMergeRiser(p, pDesc, pRow, isCherrypick);
879 /* if( p->mxRail>=GR_MAX_RAIL ) return; */
880 if( iReuseIdx<0
881 && pDesc->nMergeChild==1
882 && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
883 ){
884 iReuseIdx = pDesc->idx;
@@ -872,17 +890,17 @@
890 }
891
892 /*
893 ** Insert merge rails from primaries to duplicates.
894 */
895 if( hasDup && p->mxRail<GR_MAX_RAIL ){
896 int dupRail;
897 int mxRail;
898 find_max_rail(p);
899 mxRail = p->mxRail;
900 dupRail = mxRail+1;
901 /* if( p->mxRail>=GR_MAX_RAIL ) return; */
902 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
903 if( !pRow->isDup ) continue;
904 pRow->iRail = dupRail;
905 pDesc = hashFind(p, pRow->rid);
906 assert( pDesc!=0 && pDesc!=pRow );
@@ -893,11 +911,11 @@
911 dupRail = mxRail+1;
912 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
913 if( pRow->isDup ) pRow->iRail = dupRail;
914 }
915 }
916 /* if( mxRail>=GR_MAX_RAIL ) return; */
917 }
918
919 /*
920 ** Find the maximum rail number.
921 */
922
+10 -1
--- src/http.c
+++ src/http.c
@@ -512,10 +512,13 @@
512512
transport_send(&g.url, &hdr);
513513
transport_send(&g.url, &payload);
514514
blob_reset(&hdr);
515515
blob_reset(&payload);
516516
transport_flip(&g.url);
517
+ if( mHttpFlags & HTTP_VERBOSE ){
518
+ fossil_print("IP-Address: %s\n", g.zIpAddr);
519
+ }
517520
518521
/*
519522
** Read and interpret the server reply
520523
*/
521524
closeConnection = 1;
@@ -572,10 +575,11 @@
572575
}
573576
}else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) &&
574577
fossil_strnicmp(zLine, "location:", 9)==0 ){
575578
int i, j;
576579
int wasHttps;
580
+ int priorUrlFlags;
577581
578582
if ( --maxRedirect == 0){
579583
fossil_warning("redirect limit exceeded");
580584
goto write_err;
581585
}
@@ -596,10 +600,11 @@
596600
fossil_warning("cannot redirect from %s to %s", g.url.canonical,
597601
&zLine[i]);
598602
goto write_err;
599603
}
600604
wasHttps = g.url.isHttps;
605
+ priorUrlFlags = g.url.flags;
601606
url_parse(&zLine[i], 0);
602607
if( wasHttps && !g.url.isHttps ){
603608
fossil_warning("cannot redirect from HTTPS to HTTP");
604609
goto write_err;
605610
}
@@ -610,11 +615,14 @@
610615
transport_close(&g.url);
611616
transport_global_shutdown(&g.url);
612617
fSeenHttpAuth = 0;
613618
if( g.zHttpAuth ) free(g.zHttpAuth);
614619
g.zHttpAuth = get_httpauth();
615
- if( rc==301 || rc==308 ) url_remember();
620
+ if( (rc==301 || rc==308) && (priorUrlFlags & URL_REMEMBER)!=0 ){
621
+ g.url.flags |= URL_REMEMBER;
622
+ url_remember();
623
+ }
616624
return http_exchange(pSend, pReply, mHttpFlags,
617625
maxRedirect, zAltMimetype);
618626
}else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
619627
if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
620628
isCompressed = 0;
@@ -793,10 +801,11 @@
793801
}
794802
if( find_option("xfer",0,0)!=0 ){
795803
mHttpFlags |= HTTP_USE_LOGIN;
796804
mHttpFlags &= ~HTTP_GENERIC;
797805
}
806
+ if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
798807
verify_all_options();
799808
if( g.argc<3 || g.argc>5 ){
800809
usage("URL ?PAYLOAD? ?OUTPUT?");
801810
}
802811
zInFile = g.argc>=4 ? g.argv[3] : 0;
803812
--- src/http.c
+++ src/http.c
@@ -512,10 +512,13 @@
512 transport_send(&g.url, &hdr);
513 transport_send(&g.url, &payload);
514 blob_reset(&hdr);
515 blob_reset(&payload);
516 transport_flip(&g.url);
 
 
 
517
518 /*
519 ** Read and interpret the server reply
520 */
521 closeConnection = 1;
@@ -572,10 +575,11 @@
572 }
573 }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) &&
574 fossil_strnicmp(zLine, "location:", 9)==0 ){
575 int i, j;
576 int wasHttps;
 
577
578 if ( --maxRedirect == 0){
579 fossil_warning("redirect limit exceeded");
580 goto write_err;
581 }
@@ -596,10 +600,11 @@
596 fossil_warning("cannot redirect from %s to %s", g.url.canonical,
597 &zLine[i]);
598 goto write_err;
599 }
600 wasHttps = g.url.isHttps;
 
601 url_parse(&zLine[i], 0);
602 if( wasHttps && !g.url.isHttps ){
603 fossil_warning("cannot redirect from HTTPS to HTTP");
604 goto write_err;
605 }
@@ -610,11 +615,14 @@
610 transport_close(&g.url);
611 transport_global_shutdown(&g.url);
612 fSeenHttpAuth = 0;
613 if( g.zHttpAuth ) free(g.zHttpAuth);
614 g.zHttpAuth = get_httpauth();
615 if( rc==301 || rc==308 ) url_remember();
 
 
 
616 return http_exchange(pSend, pReply, mHttpFlags,
617 maxRedirect, zAltMimetype);
618 }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
619 if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
620 isCompressed = 0;
@@ -793,10 +801,11 @@
793 }
794 if( find_option("xfer",0,0)!=0 ){
795 mHttpFlags |= HTTP_USE_LOGIN;
796 mHttpFlags &= ~HTTP_GENERIC;
797 }
 
798 verify_all_options();
799 if( g.argc<3 || g.argc>5 ){
800 usage("URL ?PAYLOAD? ?OUTPUT?");
801 }
802 zInFile = g.argc>=4 ? g.argv[3] : 0;
803
--- src/http.c
+++ src/http.c
@@ -512,10 +512,13 @@
512 transport_send(&g.url, &hdr);
513 transport_send(&g.url, &payload);
514 blob_reset(&hdr);
515 blob_reset(&payload);
516 transport_flip(&g.url);
517 if( mHttpFlags & HTTP_VERBOSE ){
518 fossil_print("IP-Address: %s\n", g.zIpAddr);
519 }
520
521 /*
522 ** Read and interpret the server reply
523 */
524 closeConnection = 1;
@@ -572,10 +575,11 @@
575 }
576 }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) &&
577 fossil_strnicmp(zLine, "location:", 9)==0 ){
578 int i, j;
579 int wasHttps;
580 int priorUrlFlags;
581
582 if ( --maxRedirect == 0){
583 fossil_warning("redirect limit exceeded");
584 goto write_err;
585 }
@@ -596,10 +600,11 @@
600 fossil_warning("cannot redirect from %s to %s", g.url.canonical,
601 &zLine[i]);
602 goto write_err;
603 }
604 wasHttps = g.url.isHttps;
605 priorUrlFlags = g.url.flags;
606 url_parse(&zLine[i], 0);
607 if( wasHttps && !g.url.isHttps ){
608 fossil_warning("cannot redirect from HTTPS to HTTP");
609 goto write_err;
610 }
@@ -610,11 +615,14 @@
615 transport_close(&g.url);
616 transport_global_shutdown(&g.url);
617 fSeenHttpAuth = 0;
618 if( g.zHttpAuth ) free(g.zHttpAuth);
619 g.zHttpAuth = get_httpauth();
620 if( (rc==301 || rc==308) && (priorUrlFlags & URL_REMEMBER)!=0 ){
621 g.url.flags |= URL_REMEMBER;
622 url_remember();
623 }
624 return http_exchange(pSend, pReply, mHttpFlags,
625 maxRedirect, zAltMimetype);
626 }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
627 if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
628 isCompressed = 0;
@@ -793,10 +801,11 @@
801 }
802 if( find_option("xfer",0,0)!=0 ){
803 mHttpFlags |= HTTP_USE_LOGIN;
804 mHttpFlags &= ~HTTP_GENERIC;
805 }
806 if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
807 verify_all_options();
808 if( g.argc<3 || g.argc>5 ){
809 usage("URL ?PAYLOAD? ?OUTPUT?");
810 }
811 zInFile = g.argc>=4 ? g.argv[3] : 0;
812
+16 -2
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -451,11 +451,18 @@
451451
ssl_global_init_client();
452452
if( pUrlData->useProxy ){
453453
int rc;
454454
char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
455455
BIO *sBio = BIO_new_connect(connStr);
456
- free(connStr);
456
+ if( g.fIPv4 ){
457
+#ifdef BIO_FAMILY_IPV4
458
+ BIO_set_conn_ip_family(sBio, BIO_FAMILY_IPV4);
459
+#else
460
+ fossil_warning("The --ipv4 option is not supported in this build\n");
461
+#endif
462
+ }
463
+ fossil_free(connStr);
457464
if( BIO_do_connect(sBio)<=0 ){
458465
ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
459466
pUrlData->name, pUrlData->port,
460467
ERR_reason_error_string(ERR_get_error()));
461468
ssl_close_client();
@@ -503,11 +510,18 @@
503510
#endif
504511
505512
if( !pUrlData->useProxy ){
506513
char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
507514
BIO_set_conn_hostname(iBio, connStr);
508
- free(connStr);
515
+ fossil_free(connStr);
516
+ if( g.fIPv4 ){
517
+#ifdef BIO_FAMILY_IPV4
518
+ BIO_set_conn_ip_family(iBio, BIO_FAMILY_IPV4);
519
+#else
520
+ fossil_warning("The --ipv4 option is not supported in this build\n");
521
+#endif
522
+ }
509523
if( BIO_do_connect(iBio)<=0 ){
510524
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
511525
pUrlData->name, pUrlData->port,
512526
ERR_reason_error_string(ERR_get_error()));
513527
ssl_close_client();
514528
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -451,11 +451,18 @@
451 ssl_global_init_client();
452 if( pUrlData->useProxy ){
453 int rc;
454 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
455 BIO *sBio = BIO_new_connect(connStr);
456 free(connStr);
 
 
 
 
 
 
 
457 if( BIO_do_connect(sBio)<=0 ){
458 ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
459 pUrlData->name, pUrlData->port,
460 ERR_reason_error_string(ERR_get_error()));
461 ssl_close_client();
@@ -503,11 +510,18 @@
503 #endif
504
505 if( !pUrlData->useProxy ){
506 char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
507 BIO_set_conn_hostname(iBio, connStr);
508 free(connStr);
 
 
 
 
 
 
 
509 if( BIO_do_connect(iBio)<=0 ){
510 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
511 pUrlData->name, pUrlData->port,
512 ERR_reason_error_string(ERR_get_error()));
513 ssl_close_client();
514
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -451,11 +451,18 @@
451 ssl_global_init_client();
452 if( pUrlData->useProxy ){
453 int rc;
454 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
455 BIO *sBio = BIO_new_connect(connStr);
456 if( g.fIPv4 ){
457 #ifdef BIO_FAMILY_IPV4
458 BIO_set_conn_ip_family(sBio, BIO_FAMILY_IPV4);
459 #else
460 fossil_warning("The --ipv4 option is not supported in this build\n");
461 #endif
462 }
463 fossil_free(connStr);
464 if( BIO_do_connect(sBio)<=0 ){
465 ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
466 pUrlData->name, pUrlData->port,
467 ERR_reason_error_string(ERR_get_error()));
468 ssl_close_client();
@@ -503,11 +510,18 @@
510 #endif
511
512 if( !pUrlData->useProxy ){
513 char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
514 BIO_set_conn_hostname(iBio, connStr);
515 fossil_free(connStr);
516 if( g.fIPv4 ){
517 #ifdef BIO_FAMILY_IPV4
518 BIO_set_conn_ip_family(iBio, BIO_FAMILY_IPV4);
519 #else
520 fossil_warning("The --ipv4 option is not supported in this build\n");
521 #endif
522 }
523 if( BIO_do_connect(iBio)<=0 ){
524 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
525 pUrlData->name, pUrlData->port,
526 ERR_reason_error_string(ERR_get_error()));
527 ssl_close_client();
528
+68 -34
--- src/info.c
+++ src/info.c
@@ -2622,31 +2622,36 @@
26222622
26232623
/*
26242624
** WEBPAGE: artifact
26252625
** WEBPAGE: file
26262626
** WEBPAGE: whatis
2627
+** WEBPAGE: docfile
26272628
**
26282629
** Typical usage:
26292630
**
26302631
** /artifact/HASH
26312632
** /whatis/HASH
26322633
** /file/NAME
2634
+** /docfile/NAME
26332635
**
26342636
** Additional query parameters:
26352637
**
26362638
** ln - show line numbers
26372639
** ln=N - highlight line number N
26382640
** ln=M-N - highlight lines M through N inclusive
26392641
** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
26402642
** verbose - show more detail in the description
2643
+** brief - show just the document, not the metadata. The
2644
+** /docfile page is an alias for /file?brief
26412645
** download - redirect to the download (artifact page only)
26422646
** name=NAME - filename or hash as a query parameter
26432647
** filename=NAME - alternative spelling for "name="
26442648
** fn=NAME - alternative spelling for "name="
26452649
** ci=VERSION - The specific check-in to use with "name=" to
26462650
** identify the file.
26472651
** txt - Force display of unformatted source text
2652
+** hash - Output only the hash of the artifact
26482653
**
26492654
** The /artifact page show the complete content of a file
26502655
** identified by HASH. The /whatis page shows only a description
26512656
** of how the artifact is used. The /file page shows the most recent
26522657
** version of the file or directory called NAME, or a list of the
@@ -2665,18 +2670,21 @@
26652670
void artifact_page(void){
26662671
int rid = 0;
26672672
Blob content;
26682673
const char *zMime;
26692674
Blob downloadName;
2675
+ Blob uuid;
26702676
int renderAsWiki = 0;
26712677
int renderAsHtml = 0;
26722678
int renderAsSvg = 0;
26732679
int objType;
26742680
int asText;
26752681
const char *zUuid = 0;
26762682
u32 objdescFlags = OBJDESC_BASE;
26772683
int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
2684
+ int hashOnly = P("hash")!=0;
2685
+ int docOnly = P("brief")!=0;
26782686
int isFile = fossil_strcmp(g.zPath,"file")==0;
26792687
const char *zLn = P("ln");
26802688
const char *zName = P("name");
26812689
const char *zCI = P("ci");
26822690
HQuery url;
@@ -2687,10 +2695,14 @@
26872695
26882696
login_check_credentials();
26892697
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
26902698
cgi_check_for_malice();
26912699
style_set_current_feature("artifact");
2700
+ if( fossil_strcmp(g.zPath, "docfile")==0 ){
2701
+ isFile = 1;
2702
+ docOnly = 1;
2703
+ }
26922704
26932705
/* Capture and normalize the name= and ci= query parameters */
26942706
if( zName==0 ){
26952707
zName = P("filename");
26962708
if( zName==0 ){
@@ -2789,14 +2801,23 @@
27892801
url_add_parameter(&url, "verbose", "1");
27902802
objdescFlags |= OBJDESC_DETAIL;
27912803
}
27922804
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
27932805
etag_check(ETAG_HASH, zUuid);
2806
+
2807
+ if( descOnly && hashOnly ){
2808
+ blob_set(&uuid, zUuid);
2809
+ cgi_set_content_type("text/plain");
2810
+ cgi_set_content(&uuid);
2811
+ return;
2812
+ }
27942813
27952814
asText = P("txt")!=0;
27962815
if( isFile ){
2797
- if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
2816
+ if( docOnly ){
2817
+ /* No header */
2818
+ }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
27982819
zCI = "tip";
27992820
@ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a>
28002821
@ from the %z(href("%R/info/tip"))latest check-in</a></h2>
28012822
}else{
28022823
const char *zPath;
@@ -2814,17 +2835,19 @@
28142835
}else{
28152836
@ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
28162837
}
28172838
blob_reset(&path);
28182839
}
2819
- style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
28202840
zMime = mimetype_from_name(zName);
2821
- style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2822
- zName, zCI);
2823
- style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2824
- zName, zCI);
2825
- style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
2841
+ if( !docOnly ){
2842
+ style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2843
+ style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2844
+ zName, zCI);
2845
+ style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2846
+ zName, zCI);
2847
+ style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
2848
+ }
28262849
blob_init(&downloadName, zName, -1);
28272850
objType = OBJTYPE_CONTENT;
28282851
}else{
28292852
@ <h2>Artifact
28302853
style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2843,11 +2866,11 @@
28432866
cgi_redirectf("%R/raw/%s?at=%T",
28442867
db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
28452868
file_tail(blob_str(&downloadName)));
28462869
/*NOTREACHED*/
28472870
}
2848
- if( g.perm.Admin ){
2871
+ if( g.perm.Admin && !docOnly ){
28492872
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
28502873
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
28512874
style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid);
28522875
}else{
28532876
style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid);
@@ -2888,41 +2911,49 @@
28882911
const char *zIp = db_column_text(&q,2);
28892912
@ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
28902913
}
28912914
db_finalize(&q);
28922915
}
2893
- style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
2894
- if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2895
- style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
2916
+ if( !docOnly ){
2917
+ style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName));
2918
+ if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2919
+ style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
2920
+ }
28962921
}
28972922
if( zMime ){
28982923
if( fossil_strcmp(zMime, "text/html")==0 ){
28992924
if( asText ){
29002925
style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0));
29012926
}else{
29022927
renderAsHtml = 1;
2903
- style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
2928
+ if( !docOnly ){
2929
+ style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2930
+ }
29042931
}
29052932
}else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
29062933
|| fossil_strcmp(zMime, "text/x-markdown")==0
29072934
|| fossil_strcmp(zMime, "text/x-pikchr")==0 ){
29082935
if( asText ){
29092936
style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki",
29102937
"%s", url_render(&url, "txt", 0, 0, 0));
29112938
}else{
29122939
renderAsWiki = 1;
2913
- style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
2940
+ if( !docOnly ){
2941
+ style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2942
+ }
29142943
}
29152944
}else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){
29162945
if( asText ){
29172946
style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0));
29182947
}else{
29192948
renderAsSvg = 1;
2920
- style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
2949
+ if( !docOnly ){
2950
+ style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2951
+ }
29212952
}
29222953
}
2923
- if( fileedit_is_editable(zName) ){
2954
+ if( !docOnly && fileedit_is_editable(zName) ){
29242955
style_submenu_element("Edit",
29252956
"%R/fileedit?filename=%T&checkin=%!S",
29262957
zName, zCI);
29272958
}
29282959
}
@@ -2930,11 +2961,13 @@
29302961
style_submenu_element("Parsed", "%R/info/%s", zUuid);
29312962
}
29322963
if( descOnly ){
29332964
style_submenu_element("Content", "%R/artifact/%s", zUuid);
29342965
}else{
2935
- @ <hr>
2966
+ if( !docOnly || !isFile ){
2967
+ @ <hr>
2968
+ }
29362969
content_get(rid, &content);
29372970
if( renderAsWiki ){
29382971
safe_html_context(DOCSRC_FILE);
29392972
wiki_render_by_mimetype(&content, zMime);
29402973
document_emit_js();
@@ -3600,10 +3633,11 @@
36003633
zNewColorFlag = P("newclr") ? " checked" : "";
36013634
zNewTagFlag = P("newtag") ? " checked" : "";
36023635
zNewTag = PDT("tagname","");
36033636
zNewBrFlag = P("newbr") ? " checked" : "";
36043637
zNewBranch = PDT("brname","");
3638
+ zBranchName = branch_of_rid(rid);
36053639
zCloseFlag = P("close") ? " checked" : "";
36063640
zHideFlag = P("hide") ? " checked" : "";
36073641
if( P("apply") && cgi_csrf_safe(2) ){
36083642
Blob ctrl;
36093643
char *zNow;
@@ -3647,17 +3681,25 @@
36473681
zUuid[10] = 0;
36483682
style_header("Edit Check-in [%s]", zUuid);
36493683
if( P("preview") ){
36503684
Blob suffix;
36513685
int nTag = 0;
3686
+ const char *zDplyBr; /* Branch name used to determine BG color */
3687
+ if( zNewBrFlag[0] && zNewBranch[0] ){
3688
+ zDplyBr = zNewBranch;
3689
+ }else{
3690
+ zDplyBr = zBranchName;
3691
+ }
36523692
@ <b>Preview:</b>
36533693
@ <blockquote>
36543694
@ <table border=0>
36553695
if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
3656
- @ <tr><td style="background-color: %h(zNewColor);">
3696
+ @ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));">
36573697
}else if( zColor[0] ){
3658
- @ <tr><td style="background-color: %h(zColor);">
3698
+ @ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));">
3699
+ }else if( zDplyBr && fossil_strcmp(zDplyBr,"trunk")!=0 ){
3700
+ @ <tr><td style="background-color:%h(hash_color(zDplyBr));">
36593701
}else{
36603702
@ <tr><td>
36613703
}
36623704
@ %!W(blob_str(&comment))
36633705
blob_zero(&suffix);
@@ -3738,13 +3780,10 @@
37383780
@ <tr><th align="right" valign="top">Tags:</th>
37393781
@ <td valign="top">
37403782
@ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)>
37413783
@ Add the following new tag name to this check-in:</label>
37423784
@ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)">
3743
- zBranchName = db_text(0, "SELECT value FROM tagxref, tag"
3744
- " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
3745
- " AND tagxref.tagid=%d", rid, TAG_BRANCH);
37463785
db_prepare(&q,
37473786
"SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag"
37483787
" WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
37493788
" ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)"
37503789
" ELSE tagname END /*sort*/",
@@ -3937,11 +3976,11 @@
39373976
Blob comment;
39383977
char *zNow;
39393978
int nTags, nCancels;
39403979
int i;
39413980
Stmt q;
3942
- int ckComFlgs; /* Flags passed to suspicious_comment() */
3981
+ int ckComFlgs; /* Flags passed to verify_comment() */
39433982
39443983
39453984
fEditComment = find_option("edit-comment","e",0)!=0;
39463985
zNewComment = find_option("comment","m",1);
39473986
zComFile = find_option("message-file","M",1);
@@ -4030,18 +4069,13 @@
40304069
}else{
40314070
const char *zVerComs = db_get("verify-comments","on");
40324071
if( is_false(zVerComs) ){
40334072
ckComFlgs = 0;
40344073
}else if( strcmp(zVerComs,"preview")==0 ){
4035
- ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP;
4036
- }else if( strcmp(zVerComs,"links")==0 ){
4037
- ckComFlgs = COMCK_LINKS;
4074
+ ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
40384075
}else{
4039
- ckComFlgs = COMCK_LINKS | COMCK_MARKUP;
4040
- }
4041
- if( zNewComment || zComFile ){
4042
- ckComFlgs = (ckComFlgs & COMCK_LINKS) | COMCK_NOPREVIEW;
4076
+ ckComFlgs = COMCK_MARKUP;
40434077
}
40444078
}
40454079
if( fEditComment ){
40464080
prepare_amend_comment(&comment, zComment, zUuid);
40474081
}else if( zComFile ){
@@ -4052,29 +4086,29 @@
40524086
}
40534087
if( blob_size(&comment)>0
40544088
&& comment_compare(zComment, blob_str(&comment))==0
40554089
){
40564090
int rc;
4057
- while( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){
4091
+ while( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
40584092
char cReply;
40594093
Blob ans;
40604094
if( !fEditComment ){
40614095
fossil_fatal("Amend aborted; "
40624096
"use --no-verify-comment to override");
40634097
}
40644098
if( rc==COMCK_PREVIEW ){
4065
- prompt_user("\nContinue (Y/n/e=edit)? ", &ans);
4099
+ prompt_user("Continue, abort, or edit (C/a/e)? ", &ans);
40664100
}else{
4067
- prompt_user("\nContinue (y/n/E=edit)? ", &ans);
4101
+ prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
40684102
}
40694103
cReply = blob_str(&ans)[0];
40704104
cReply = fossil_tolower(cReply);
40714105
blob_reset(&ans);
4072
- if( cReply=='n' ){
4106
+ if( cReply=='a' ){
40734107
fossil_fatal("Amend aborted.");
40744108
}
4075
- if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){
4109
+ if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
40764110
char *zPrior = blob_materialize(&comment);
40774111
blob_init(&comment, 0, 0);
40784112
prepare_amend_comment(&comment, zPrior, zUuid);
40794113
fossil_free(zPrior);
40804114
continue;
40814115
--- src/info.c
+++ src/info.c
@@ -2622,31 +2622,36 @@
2622
2623 /*
2624 ** WEBPAGE: artifact
2625 ** WEBPAGE: file
2626 ** WEBPAGE: whatis
 
2627 **
2628 ** Typical usage:
2629 **
2630 ** /artifact/HASH
2631 ** /whatis/HASH
2632 ** /file/NAME
 
2633 **
2634 ** Additional query parameters:
2635 **
2636 ** ln - show line numbers
2637 ** ln=N - highlight line number N
2638 ** ln=M-N - highlight lines M through N inclusive
2639 ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
2640 ** verbose - show more detail in the description
 
 
2641 ** download - redirect to the download (artifact page only)
2642 ** name=NAME - filename or hash as a query parameter
2643 ** filename=NAME - alternative spelling for "name="
2644 ** fn=NAME - alternative spelling for "name="
2645 ** ci=VERSION - The specific check-in to use with "name=" to
2646 ** identify the file.
2647 ** txt - Force display of unformatted source text
 
2648 **
2649 ** The /artifact page show the complete content of a file
2650 ** identified by HASH. The /whatis page shows only a description
2651 ** of how the artifact is used. The /file page shows the most recent
2652 ** version of the file or directory called NAME, or a list of the
@@ -2665,18 +2670,21 @@
2665 void artifact_page(void){
2666 int rid = 0;
2667 Blob content;
2668 const char *zMime;
2669 Blob downloadName;
 
2670 int renderAsWiki = 0;
2671 int renderAsHtml = 0;
2672 int renderAsSvg = 0;
2673 int objType;
2674 int asText;
2675 const char *zUuid = 0;
2676 u32 objdescFlags = OBJDESC_BASE;
2677 int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
 
 
2678 int isFile = fossil_strcmp(g.zPath,"file")==0;
2679 const char *zLn = P("ln");
2680 const char *zName = P("name");
2681 const char *zCI = P("ci");
2682 HQuery url;
@@ -2687,10 +2695,14 @@
2687
2688 login_check_credentials();
2689 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2690 cgi_check_for_malice();
2691 style_set_current_feature("artifact");
 
 
 
 
2692
2693 /* Capture and normalize the name= and ci= query parameters */
2694 if( zName==0 ){
2695 zName = P("filename");
2696 if( zName==0 ){
@@ -2789,14 +2801,23 @@
2789 url_add_parameter(&url, "verbose", "1");
2790 objdescFlags |= OBJDESC_DETAIL;
2791 }
2792 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2793 etag_check(ETAG_HASH, zUuid);
 
 
 
 
 
 
 
2794
2795 asText = P("txt")!=0;
2796 if( isFile ){
2797 if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
 
 
2798 zCI = "tip";
2799 @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a>
2800 @ from the %z(href("%R/info/tip"))latest check-in</a></h2>
2801 }else{
2802 const char *zPath;
@@ -2814,17 +2835,19 @@
2814 }else{
2815 @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
2816 }
2817 blob_reset(&path);
2818 }
2819 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2820 zMime = mimetype_from_name(zName);
2821 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2822 zName, zCI);
2823 style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2824 zName, zCI);
2825 style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
 
 
 
2826 blob_init(&downloadName, zName, -1);
2827 objType = OBJTYPE_CONTENT;
2828 }else{
2829 @ <h2>Artifact
2830 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2843,11 +2866,11 @@
2843 cgi_redirectf("%R/raw/%s?at=%T",
2844 db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2845 file_tail(blob_str(&downloadName)));
2846 /*NOTREACHED*/
2847 }
2848 if( g.perm.Admin ){
2849 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
2850 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2851 style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid);
2852 }else{
2853 style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid);
@@ -2888,41 +2911,49 @@
2888 const char *zIp = db_column_text(&q,2);
2889 @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
2890 }
2891 db_finalize(&q);
2892 }
2893 style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
2894 if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2895 style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
 
 
2896 }
2897 if( zMime ){
2898 if( fossil_strcmp(zMime, "text/html")==0 ){
2899 if( asText ){
2900 style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0));
2901 }else{
2902 renderAsHtml = 1;
2903 style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
 
 
2904 }
2905 }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
2906 || fossil_strcmp(zMime, "text/x-markdown")==0
2907 || fossil_strcmp(zMime, "text/x-pikchr")==0 ){
2908 if( asText ){
2909 style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki",
2910 "%s", url_render(&url, "txt", 0, 0, 0));
2911 }else{
2912 renderAsWiki = 1;
2913 style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
 
 
2914 }
2915 }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){
2916 if( asText ){
2917 style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0));
2918 }else{
2919 renderAsSvg = 1;
2920 style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
 
 
2921 }
2922 }
2923 if( fileedit_is_editable(zName) ){
2924 style_submenu_element("Edit",
2925 "%R/fileedit?filename=%T&checkin=%!S",
2926 zName, zCI);
2927 }
2928 }
@@ -2930,11 +2961,13 @@
2930 style_submenu_element("Parsed", "%R/info/%s", zUuid);
2931 }
2932 if( descOnly ){
2933 style_submenu_element("Content", "%R/artifact/%s", zUuid);
2934 }else{
2935 @ <hr>
 
 
2936 content_get(rid, &content);
2937 if( renderAsWiki ){
2938 safe_html_context(DOCSRC_FILE);
2939 wiki_render_by_mimetype(&content, zMime);
2940 document_emit_js();
@@ -3600,10 +3633,11 @@
3600 zNewColorFlag = P("newclr") ? " checked" : "";
3601 zNewTagFlag = P("newtag") ? " checked" : "";
3602 zNewTag = PDT("tagname","");
3603 zNewBrFlag = P("newbr") ? " checked" : "";
3604 zNewBranch = PDT("brname","");
 
3605 zCloseFlag = P("close") ? " checked" : "";
3606 zHideFlag = P("hide") ? " checked" : "";
3607 if( P("apply") && cgi_csrf_safe(2) ){
3608 Blob ctrl;
3609 char *zNow;
@@ -3647,17 +3681,25 @@
3647 zUuid[10] = 0;
3648 style_header("Edit Check-in [%s]", zUuid);
3649 if( P("preview") ){
3650 Blob suffix;
3651 int nTag = 0;
 
 
 
 
 
 
3652 @ <b>Preview:</b>
3653 @ <blockquote>
3654 @ <table border=0>
3655 if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
3656 @ <tr><td style="background-color: %h(zNewColor);">
3657 }else if( zColor[0] ){
3658 @ <tr><td style="background-color: %h(zColor);">
 
 
3659 }else{
3660 @ <tr><td>
3661 }
3662 @ %!W(blob_str(&comment))
3663 blob_zero(&suffix);
@@ -3738,13 +3780,10 @@
3738 @ <tr><th align="right" valign="top">Tags:</th>
3739 @ <td valign="top">
3740 @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)>
3741 @ Add the following new tag name to this check-in:</label>
3742 @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)">
3743 zBranchName = db_text(0, "SELECT value FROM tagxref, tag"
3744 " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
3745 " AND tagxref.tagid=%d", rid, TAG_BRANCH);
3746 db_prepare(&q,
3747 "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag"
3748 " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
3749 " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)"
3750 " ELSE tagname END /*sort*/",
@@ -3937,11 +3976,11 @@
3937 Blob comment;
3938 char *zNow;
3939 int nTags, nCancels;
3940 int i;
3941 Stmt q;
3942 int ckComFlgs; /* Flags passed to suspicious_comment() */
3943
3944
3945 fEditComment = find_option("edit-comment","e",0)!=0;
3946 zNewComment = find_option("comment","m",1);
3947 zComFile = find_option("message-file","M",1);
@@ -4030,18 +4069,13 @@
4030 }else{
4031 const char *zVerComs = db_get("verify-comments","on");
4032 if( is_false(zVerComs) ){
4033 ckComFlgs = 0;
4034 }else if( strcmp(zVerComs,"preview")==0 ){
4035 ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP;
4036 }else if( strcmp(zVerComs,"links")==0 ){
4037 ckComFlgs = COMCK_LINKS;
4038 }else{
4039 ckComFlgs = COMCK_LINKS | COMCK_MARKUP;
4040 }
4041 if( zNewComment || zComFile ){
4042 ckComFlgs = (ckComFlgs & COMCK_LINKS) | COMCK_NOPREVIEW;
4043 }
4044 }
4045 if( fEditComment ){
4046 prepare_amend_comment(&comment, zComment, zUuid);
4047 }else if( zComFile ){
@@ -4052,29 +4086,29 @@
4052 }
4053 if( blob_size(&comment)>0
4054 && comment_compare(zComment, blob_str(&comment))==0
4055 ){
4056 int rc;
4057 while( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){
4058 char cReply;
4059 Blob ans;
4060 if( !fEditComment ){
4061 fossil_fatal("Amend aborted; "
4062 "use --no-verify-comment to override");
4063 }
4064 if( rc==COMCK_PREVIEW ){
4065 prompt_user("\nContinue (Y/n/e=edit)? ", &ans);
4066 }else{
4067 prompt_user("\nContinue (y/n/E=edit)? ", &ans);
4068 }
4069 cReply = blob_str(&ans)[0];
4070 cReply = fossil_tolower(cReply);
4071 blob_reset(&ans);
4072 if( cReply=='n' ){
4073 fossil_fatal("Amend aborted.");
4074 }
4075 if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){
4076 char *zPrior = blob_materialize(&comment);
4077 blob_init(&comment, 0, 0);
4078 prepare_amend_comment(&comment, zPrior, zUuid);
4079 fossil_free(zPrior);
4080 continue;
4081
--- src/info.c
+++ src/info.c
@@ -2622,31 +2622,36 @@
2622
2623 /*
2624 ** WEBPAGE: artifact
2625 ** WEBPAGE: file
2626 ** WEBPAGE: whatis
2627 ** WEBPAGE: docfile
2628 **
2629 ** Typical usage:
2630 **
2631 ** /artifact/HASH
2632 ** /whatis/HASH
2633 ** /file/NAME
2634 ** /docfile/NAME
2635 **
2636 ** Additional query parameters:
2637 **
2638 ** ln - show line numbers
2639 ** ln=N - highlight line number N
2640 ** ln=M-N - highlight lines M through N inclusive
2641 ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
2642 ** verbose - show more detail in the description
2643 ** brief - show just the document, not the metadata. The
2644 ** /docfile page is an alias for /file?brief
2645 ** download - redirect to the download (artifact page only)
2646 ** name=NAME - filename or hash as a query parameter
2647 ** filename=NAME - alternative spelling for "name="
2648 ** fn=NAME - alternative spelling for "name="
2649 ** ci=VERSION - The specific check-in to use with "name=" to
2650 ** identify the file.
2651 ** txt - Force display of unformatted source text
2652 ** hash - Output only the hash of the artifact
2653 **
2654 ** The /artifact page show the complete content of a file
2655 ** identified by HASH. The /whatis page shows only a description
2656 ** of how the artifact is used. The /file page shows the most recent
2657 ** version of the file or directory called NAME, or a list of the
@@ -2665,18 +2670,21 @@
2670 void artifact_page(void){
2671 int rid = 0;
2672 Blob content;
2673 const char *zMime;
2674 Blob downloadName;
2675 Blob uuid;
2676 int renderAsWiki = 0;
2677 int renderAsHtml = 0;
2678 int renderAsSvg = 0;
2679 int objType;
2680 int asText;
2681 const char *zUuid = 0;
2682 u32 objdescFlags = OBJDESC_BASE;
2683 int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
2684 int hashOnly = P("hash")!=0;
2685 int docOnly = P("brief")!=0;
2686 int isFile = fossil_strcmp(g.zPath,"file")==0;
2687 const char *zLn = P("ln");
2688 const char *zName = P("name");
2689 const char *zCI = P("ci");
2690 HQuery url;
@@ -2687,10 +2695,14 @@
2695
2696 login_check_credentials();
2697 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2698 cgi_check_for_malice();
2699 style_set_current_feature("artifact");
2700 if( fossil_strcmp(g.zPath, "docfile")==0 ){
2701 isFile = 1;
2702 docOnly = 1;
2703 }
2704
2705 /* Capture and normalize the name= and ci= query parameters */
2706 if( zName==0 ){
2707 zName = P("filename");
2708 if( zName==0 ){
@@ -2789,14 +2801,23 @@
2801 url_add_parameter(&url, "verbose", "1");
2802 objdescFlags |= OBJDESC_DETAIL;
2803 }
2804 zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
2805 etag_check(ETAG_HASH, zUuid);
2806
2807 if( descOnly && hashOnly ){
2808 blob_set(&uuid, zUuid);
2809 cgi_set_content_type("text/plain");
2810 cgi_set_content(&uuid);
2811 return;
2812 }
2813
2814 asText = P("txt")!=0;
2815 if( isFile ){
2816 if( docOnly ){
2817 /* No header */
2818 }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
2819 zCI = "tip";
2820 @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a>
2821 @ from the %z(href("%R/info/tip"))latest check-in</a></h2>
2822 }else{
2823 const char *zPath;
@@ -2814,17 +2835,19 @@
2835 }else{
2836 @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
2837 }
2838 blob_reset(&path);
2839 }
 
2840 zMime = mimetype_from_name(zName);
2841 if( !docOnly ){
2842 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2843 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
2844 zName, zCI);
2845 style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
2846 zName, zCI);
2847 style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
2848 }
2849 blob_init(&downloadName, zName, -1);
2850 objType = OBJTYPE_CONTENT;
2851 }else{
2852 @ <h2>Artifact
2853 style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
@@ -2843,11 +2866,11 @@
2866 cgi_redirectf("%R/raw/%s?at=%T",
2867 db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
2868 file_tail(blob_str(&downloadName)));
2869 /*NOTREACHED*/
2870 }
2871 if( g.perm.Admin && !docOnly ){
2872 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
2873 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
2874 style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid);
2875 }else{
2876 style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid);
@@ -2888,41 +2911,49 @@
2911 const char *zIp = db_column_text(&q,2);
2912 @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
2913 }
2914 db_finalize(&q);
2915 }
2916 if( !docOnly ){
2917 style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName));
2918 if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
2919 style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
2920 }
2921 }
2922 if( zMime ){
2923 if( fossil_strcmp(zMime, "text/html")==0 ){
2924 if( asText ){
2925 style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0));
2926 }else{
2927 renderAsHtml = 1;
2928 if( !docOnly ){
2929 style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2930 }
2931 }
2932 }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
2933 || fossil_strcmp(zMime, "text/x-markdown")==0
2934 || fossil_strcmp(zMime, "text/x-pikchr")==0 ){
2935 if( asText ){
2936 style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki",
2937 "%s", url_render(&url, "txt", 0, 0, 0));
2938 }else{
2939 renderAsWiki = 1;
2940 if( !docOnly ){
2941 style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2942 }
2943 }
2944 }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){
2945 if( asText ){
2946 style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0));
2947 }else{
2948 renderAsSvg = 1;
2949 if( !docOnly ){
2950 style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
2951 }
2952 }
2953 }
2954 if( !docOnly && fileedit_is_editable(zName) ){
2955 style_submenu_element("Edit",
2956 "%R/fileedit?filename=%T&checkin=%!S",
2957 zName, zCI);
2958 }
2959 }
@@ -2930,11 +2961,13 @@
2961 style_submenu_element("Parsed", "%R/info/%s", zUuid);
2962 }
2963 if( descOnly ){
2964 style_submenu_element("Content", "%R/artifact/%s", zUuid);
2965 }else{
2966 if( !docOnly || !isFile ){
2967 @ <hr>
2968 }
2969 content_get(rid, &content);
2970 if( renderAsWiki ){
2971 safe_html_context(DOCSRC_FILE);
2972 wiki_render_by_mimetype(&content, zMime);
2973 document_emit_js();
@@ -3600,10 +3633,11 @@
3633 zNewColorFlag = P("newclr") ? " checked" : "";
3634 zNewTagFlag = P("newtag") ? " checked" : "";
3635 zNewTag = PDT("tagname","");
3636 zNewBrFlag = P("newbr") ? " checked" : "";
3637 zNewBranch = PDT("brname","");
3638 zBranchName = branch_of_rid(rid);
3639 zCloseFlag = P("close") ? " checked" : "";
3640 zHideFlag = P("hide") ? " checked" : "";
3641 if( P("apply") && cgi_csrf_safe(2) ){
3642 Blob ctrl;
3643 char *zNow;
@@ -3647,17 +3681,25 @@
3681 zUuid[10] = 0;
3682 style_header("Edit Check-in [%s]", zUuid);
3683 if( P("preview") ){
3684 Blob suffix;
3685 int nTag = 0;
3686 const char *zDplyBr; /* Branch name used to determine BG color */
3687 if( zNewBrFlag[0] && zNewBranch[0] ){
3688 zDplyBr = zNewBranch;
3689 }else{
3690 zDplyBr = zBranchName;
3691 }
3692 @ <b>Preview:</b>
3693 @ <blockquote>
3694 @ <table border=0>
3695 if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
3696 @ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));">
3697 }else if( zColor[0] ){
3698 @ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));">
3699 }else if( zDplyBr && fossil_strcmp(zDplyBr,"trunk")!=0 ){
3700 @ <tr><td style="background-color:%h(hash_color(zDplyBr));">
3701 }else{
3702 @ <tr><td>
3703 }
3704 @ %!W(blob_str(&comment))
3705 blob_zero(&suffix);
@@ -3738,13 +3780,10 @@
3780 @ <tr><th align="right" valign="top">Tags:</th>
3781 @ <td valign="top">
3782 @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)>
3783 @ Add the following new tag name to this check-in:</label>
3784 @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)">
 
 
 
3785 db_prepare(&q,
3786 "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag"
3787 " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
3788 " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)"
3789 " ELSE tagname END /*sort*/",
@@ -3937,11 +3976,11 @@
3976 Blob comment;
3977 char *zNow;
3978 int nTags, nCancels;
3979 int i;
3980 Stmt q;
3981 int ckComFlgs; /* Flags passed to verify_comment() */
3982
3983
3984 fEditComment = find_option("edit-comment","e",0)!=0;
3985 zNewComment = find_option("comment","m",1);
3986 zComFile = find_option("message-file","M",1);
@@ -4030,18 +4069,13 @@
4069 }else{
4070 const char *zVerComs = db_get("verify-comments","on");
4071 if( is_false(zVerComs) ){
4072 ckComFlgs = 0;
4073 }else if( strcmp(zVerComs,"preview")==0 ){
4074 ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
 
 
4075 }else{
4076 ckComFlgs = COMCK_MARKUP;
 
 
 
4077 }
4078 }
4079 if( fEditComment ){
4080 prepare_amend_comment(&comment, zComment, zUuid);
4081 }else if( zComFile ){
@@ -4052,29 +4086,29 @@
4086 }
4087 if( blob_size(&comment)>0
4088 && comment_compare(zComment, blob_str(&comment))==0
4089 ){
4090 int rc;
4091 while( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
4092 char cReply;
4093 Blob ans;
4094 if( !fEditComment ){
4095 fossil_fatal("Amend aborted; "
4096 "use --no-verify-comment to override");
4097 }
4098 if( rc==COMCK_PREVIEW ){
4099 prompt_user("Continue, abort, or edit (C/a/e)? ", &ans);
4100 }else{
4101 prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
4102 }
4103 cReply = blob_str(&ans)[0];
4104 cReply = fossil_tolower(cReply);
4105 blob_reset(&ans);
4106 if( cReply=='a' ){
4107 fossil_fatal("Amend aborted.");
4108 }
4109 if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
4110 char *zPrior = blob_materialize(&comment);
4111 blob_init(&comment, 0, 0);
4112 prepare_amend_comment(&comment, zPrior, zUuid);
4113 fossil_free(zPrior);
4114 continue;
4115
--- src/json_config.c
+++ src/json_config.c
@@ -86,11 +86,10 @@
8686
{ "logo-image", CONFIGSET_SKIN },
8787
{ "background-mimetype", CONFIGSET_SKIN },
8888
{ "background-image", CONFIGSET_SKIN },
8989
{ "icon-mimetype", CONFIGSET_SKIN },
9090
{ "icon-image", CONFIGSET_SKIN },
91
-{ "timeline-block-markup", CONFIGSET_SKIN },
9291
{ "timeline-date-format", CONFIGSET_SKIN },
9392
{ "timeline-default-style", CONFIGSET_SKIN },
9493
{ "timeline-dwelltime", CONFIGSET_SKIN },
9594
{ "timeline-closetime", CONFIGSET_SKIN },
9695
{ "timeline-hard-newlines", CONFIGSET_SKIN },
9796
--- src/json_config.c
+++ src/json_config.c
@@ -86,11 +86,10 @@
86 { "logo-image", CONFIGSET_SKIN },
87 { "background-mimetype", CONFIGSET_SKIN },
88 { "background-image", CONFIGSET_SKIN },
89 { "icon-mimetype", CONFIGSET_SKIN },
90 { "icon-image", CONFIGSET_SKIN },
91 { "timeline-block-markup", CONFIGSET_SKIN },
92 { "timeline-date-format", CONFIGSET_SKIN },
93 { "timeline-default-style", CONFIGSET_SKIN },
94 { "timeline-dwelltime", CONFIGSET_SKIN },
95 { "timeline-closetime", CONFIGSET_SKIN },
96 { "timeline-hard-newlines", CONFIGSET_SKIN },
97
--- src/json_config.c
+++ src/json_config.c
@@ -86,11 +86,10 @@
86 { "logo-image", CONFIGSET_SKIN },
87 { "background-mimetype", CONFIGSET_SKIN },
88 { "background-image", CONFIGSET_SKIN },
89 { "icon-mimetype", CONFIGSET_SKIN },
90 { "icon-image", CONFIGSET_SKIN },
 
91 { "timeline-date-format", CONFIGSET_SKIN },
92 { "timeline-default-style", CONFIGSET_SKIN },
93 { "timeline-dwelltime", CONFIGSET_SKIN },
94 { "timeline-closetime", CONFIGSET_SKIN },
95 { "timeline-hard-newlines", CONFIGSET_SKIN },
96
+28 -5
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
25422542
** setenv: NAME
25432543
**
25442544
** Sets environment variable NAME to VALUE. If VALUE is omitted, then
25452545
** the environment variable is unset.
25462546
*/
2547
- blob_token(&line,&value2);
2548
- fossil_setenv(blob_str(&value), blob_str(&value2));
2547
+ char *zValue;
2548
+ blob_tail(&line,&value2);
2549
+ blob_trim(&value2);
2550
+ zValue = blob_str(&value2);
2551
+ while( fossil_isspace(zValue[0]) ){ zValue++; }
2552
+ fossil_setenv(blob_str(&value), zValue);
25492553
blob_reset(&value);
25502554
blob_reset(&value2);
25512555
continue;
25522556
}
25532557
if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
32003204
** --chroot DIR Use directory for chroot instead of repository path
32013205
** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
32023206
** /doc/ckout/...
32033207
** --create Create a new REPOSITORY if it does not already exist
32043208
** --errorlog FILE Append HTTP error messages to FILE
3209
+** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where
3210
+** DIR is the directory holding FILE and TAIL is the
3211
+** filename at the end of FILE. Only works for "ui".
32053212
** --extroot DIR Document root for the /ext extension mechanism
32063213
** --files GLOBLIST Comma-separated list of glob patterns for static files
32073214
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
32083215
** system when REPOSITORY is remote.
32093216
** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
32783285
int findServerArg = 2; /* argv index for find_server_repository() */
32793286
char *zRemote = 0; /* Remote host on which to run "fossil ui" */
32803287
const char *zJsMode; /* The --jsmode parameter */
32813288
const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
32823289
const char *zFrom; /* Value for --from */
3290
+ const char *zExtPage = 0; /* Argument to --extpage */
32833291
32843292
32853293
#if USE_SEE
32863294
db_setup_for_saved_encryption_key();
32873295
#endif
@@ -3319,12 +3327,20 @@
33193327
zFrom = find_option("from", 0, 1);
33203328
if( zFrom && zFrom==file_tail(zFrom) ){
33213329
fossil_fatal("the argument to --from must be a pathname for"
33223330
" the \"ui\" command");
33233331
}
3324
- zInitPage = find_option("page", "p", 1);
3325
- if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3332
+ zExtPage = find_option("extpage",0,1);
3333
+ if( zExtPage ){
3334
+ char *zFullPath = file_canonical_name_dup(zExtPage);
3335
+ g.zExtRoot = file_dirname(zFullPath);
3336
+ zInitPage = mprintf("ext/%s",file_tail(zFullPath));
3337
+ fossil_free(zFullPath);
3338
+ }else{
3339
+ zInitPage = find_option("page", "p", 1);
3340
+ if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3341
+ }
33263342
zFossilCmd = find_option("fossilcmd", 0, 1);
33273343
if( zFrom && zInitPage==0 ){
33283344
zInitPage = mprintf("ckout?exbase=%H", zFrom);
33293345
}
33303346
}
@@ -3493,11 +3509,18 @@
34933509
}
34943510
blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
34953511
if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
34963512
if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
34973513
if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3498
- if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3514
+ if( zExtPage ){
3515
+ if( !file_is_absolute_path(zExtPage) ){
3516
+ zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
3517
+ }
3518
+ blob_appendf(&ssh, " --extpage %$", zExtPage);
3519
+ }else if( g.zExtRoot ){
3520
+ blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3521
+ }
34993522
if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
35003523
if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
35013524
if( fCreate ) blob_appendf(&ssh, " --create");
35023525
blob_appendf(&ssh, " %$", g.argv[2]);
35033526
if( isRetry ){
35043527
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
2542 ** setenv: NAME
2543 **
2544 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then
2545 ** the environment variable is unset.
2546 */
2547 blob_token(&line,&value2);
2548 fossil_setenv(blob_str(&value), blob_str(&value2));
 
 
 
 
2549 blob_reset(&value);
2550 blob_reset(&value2);
2551 continue;
2552 }
2553 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
3200 ** --chroot DIR Use directory for chroot instead of repository path
3201 ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
3202 ** /doc/ckout/...
3203 ** --create Create a new REPOSITORY if it does not already exist
3204 ** --errorlog FILE Append HTTP error messages to FILE
 
 
 
3205 ** --extroot DIR Document root for the /ext extension mechanism
3206 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3207 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3208 ** system when REPOSITORY is remote.
3209 ** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
3278 int findServerArg = 2; /* argv index for find_server_repository() */
3279 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3280 const char *zJsMode; /* The --jsmode parameter */
3281 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3282 const char *zFrom; /* Value for --from */
 
3283
3284
3285 #if USE_SEE
3286 db_setup_for_saved_encryption_key();
3287 #endif
@@ -3319,12 +3327,20 @@
3319 zFrom = find_option("from", 0, 1);
3320 if( zFrom && zFrom==file_tail(zFrom) ){
3321 fossil_fatal("the argument to --from must be a pathname for"
3322 " the \"ui\" command");
3323 }
3324 zInitPage = find_option("page", "p", 1);
3325 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
 
 
 
 
 
 
 
 
3326 zFossilCmd = find_option("fossilcmd", 0, 1);
3327 if( zFrom && zInitPage==0 ){
3328 zInitPage = mprintf("ckout?exbase=%H", zFrom);
3329 }
3330 }
@@ -3493,11 +3509,18 @@
3493 }
3494 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3495 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3496 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3497 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3498 if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
 
 
 
 
 
 
 
3499 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3500 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3501 if( fCreate ) blob_appendf(&ssh, " --create");
3502 blob_appendf(&ssh, " %$", g.argv[2]);
3503 if( isRetry ){
3504
--- src/main.c
+++ src/main.c
@@ -2542,12 +2542,16 @@
2542 ** setenv: NAME
2543 **
2544 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then
2545 ** the environment variable is unset.
2546 */
2547 char *zValue;
2548 blob_tail(&line,&value2);
2549 blob_trim(&value2);
2550 zValue = blob_str(&value2);
2551 while( fossil_isspace(zValue[0]) ){ zValue++; }
2552 fossil_setenv(blob_str(&value), zValue);
2553 blob_reset(&value);
2554 blob_reset(&value2);
2555 continue;
2556 }
2557 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
@@ -3200,10 +3204,13 @@
3204 ** --chroot DIR Use directory for chroot instead of repository path
3205 ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
3206 ** /doc/ckout/...
3207 ** --create Create a new REPOSITORY if it does not already exist
3208 ** --errorlog FILE Append HTTP error messages to FILE
3209 ** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where
3210 ** DIR is the directory holding FILE and TAIL is the
3211 ** filename at the end of FILE. Only works for "ui".
3212 ** --extroot DIR Document root for the /ext extension mechanism
3213 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3214 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3215 ** system when REPOSITORY is remote.
3216 ** --from PATH Use PATH as the diff baseline for the /ckout page
@@ -3278,10 +3285,11 @@
3285 int findServerArg = 2; /* argv index for find_server_repository() */
3286 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3287 const char *zJsMode; /* The --jsmode parameter */
3288 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3289 const char *zFrom; /* Value for --from */
3290 const char *zExtPage = 0; /* Argument to --extpage */
3291
3292
3293 #if USE_SEE
3294 db_setup_for_saved_encryption_key();
3295 #endif
@@ -3319,12 +3327,20 @@
3327 zFrom = find_option("from", 0, 1);
3328 if( zFrom && zFrom==file_tail(zFrom) ){
3329 fossil_fatal("the argument to --from must be a pathname for"
3330 " the \"ui\" command");
3331 }
3332 zExtPage = find_option("extpage",0,1);
3333 if( zExtPage ){
3334 char *zFullPath = file_canonical_name_dup(zExtPage);
3335 g.zExtRoot = file_dirname(zFullPath);
3336 zInitPage = mprintf("ext/%s",file_tail(zFullPath));
3337 fossil_free(zFullPath);
3338 }else{
3339 zInitPage = find_option("page", "p", 1);
3340 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3341 }
3342 zFossilCmd = find_option("fossilcmd", 0, 1);
3343 if( zFrom && zInitPage==0 ){
3344 zInitPage = mprintf("ckout?exbase=%H", zFrom);
3345 }
3346 }
@@ -3493,11 +3509,18 @@
3509 }
3510 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3511 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3512 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3513 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3514 if( zExtPage ){
3515 if( !file_is_absolute_path(zExtPage) ){
3516 zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
3517 }
3518 blob_appendf(&ssh, " --extpage %$", zExtPage);
3519 }else if( g.zExtRoot ){
3520 blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3521 }
3522 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3523 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3524 if( fCreate ) blob_appendf(&ssh, " --create");
3525 blob_appendf(&ssh, " %$", g.argv[2]);
3526 if( isRetry ){
3527
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,300 @@
29112911
if( g.argc!=3 ) usage("RECORDID");
29122912
rid = name_to_rid(g.argv[2]);
29132913
content_get(rid, &content);
29142914
manifest_crosslink(rid, &content, MC_NONE);
29152915
}
2916
+
2917
+/*
2918
+** For a given CATYPE_... value, returns a human-friendly name, or
2919
+** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by
2920
+** this function are geared towards use with artifact_to_json(), and
2921
+** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts
2922
+** are called "tag" artifacts by this function.
2923
+*/
2924
+const char * artifact_type_to_name(int typeId){
2925
+ switch(typeId){
2926
+ case CFTYPE_MANIFEST: return "checkin";
2927
+ case CFTYPE_CLUSTER: return "cluster";
2928
+ case CFTYPE_CONTROL: return "tag";
2929
+ case CFTYPE_WIKI: return "wiki";
2930
+ case CFTYPE_TICKET: return "ticket";
2931
+ case CFTYPE_ATTACHMENT: return "attachment";
2932
+ case CFTYPE_EVENT: return "technote";
2933
+ case CFTYPE_FORUM: return "forumpost";
2934
+ }
2935
+ return NULL;
2936
+}
2937
+
2938
+/*
2939
+** Creates a JSON representation of p, appending it to b.
2940
+**
2941
+** b is not cleared before rendering, so the caller needs to do that
2942
+** if it's important for their use case.
2943
+**
2944
+** Pedantic note: this routine traverses p->aFile directly, rather
2945
+** than using manifest_file_next(), so that delta manifests are
2946
+** rendered as-is instead of containing their derived F-cards. If that
2947
+** policy is ever changed, p will need to be non-const.
2948
+*/
2949
+void artifact_to_json(Manifest const *p, Blob *b){
2950
+ int i;
2951
+
2952
+ blob_append_literal(b, "{");
2953
+ blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid));
2954
+ /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2955
+ blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type));
2956
+#define ISA(TYPE) if( p->type==TYPE )
2957
+#define CARD_LETTER(LETTER) \
2958
+ blob_append_literal(b, ",\"" #LETTER "\":")
2959
+#define CARD_STR(LETTER, VAL) \
2960
+ assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2961
+#define CARD_STR2(LETTER, VAL) \
2962
+ if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2963
+#define STR_OR_NULL(VAL) \
2964
+ if( VAL ) blob_appendf(b, "%!j", VAL); \
2965
+ else blob_append(b, "null", 4)
2966
+#define KVP_STR(ADDCOMMA, KEY,VAL) \
2967
+ if(ADDCOMMA) blob_append_char(b, ','); \
2968
+ blob_appendf(b, "%!j:", #KEY); \
2969
+ STR_OR_NULL(VAL)
2970
+
2971
+ ISA( CFTYPE_ATTACHMENT ){
2972
+ CARD_LETTER(A);
2973
+ blob_append_char(b, '{');
2974
+ KVP_STR(0, filename, p->zAttachName);
2975
+ KVP_STR(1, target, p->zAttachTarget);
2976
+ KVP_STR(1, source, p->zAttachSrc);
2977
+ blob_append_char(b, '}');
2978
+ }
2979
+ CARD_STR2(B, p->zBaseline);
2980
+ CARD_STR2(C, p->zComment);
2981
+ CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982
+ ISA( CFTYPE_EVENT ){
2983
+ blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}",
2984
+ p->rEventDate, p->zEventId);
2985
+ }
2986
+ ISA( CFTYPE_MANIFEST ){
2987
+ CARD_LETTER(F);
2988
+ blob_append_char(b, '[');
2989
+ for( i = 0; i < p->nFile; ++i ){
2990
+ ManifestFile const * const pF = &p->aFile[i];
2991
+ if( i>0 ) blob_append_char(b, ',');
2992
+ blob_append_char(b, '{');
2993
+ KVP_STR(0, name, pF->zName);
2994
+ KVP_STR(1, uuid, pF->zUuid);
2995
+ KVP_STR(1, perm, pF->zPerm);
2996
+ KVP_STR(1, rename, pF->zPrior);
2997
+ blob_append_char(b, '}');
2998
+ }
2999
+ /* Special case: model checkins with no F-card as having an empty
3000
+ ** array, rather than no F-cards, to hypothetically simplify
3001
+ ** handling in JSON queries. */
3002
+ blob_append_char(b, ']');
3003
+ }
3004
+ CARD_STR2(G, p->zThreadRoot);
3005
+ ISA( CFTYPE_FORUM ){
3006
+ CARD_LETTER(H);
3007
+ STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL);
3008
+ CARD_STR2(I, p->zInReplyTo);
3009
+ }
3010
+ if( p->nField ){
3011
+ CARD_LETTER(J);
3012
+ blob_append_char(b, '[');
3013
+ for( i = 0; i < p->nField; ++i ){
3014
+ const char * zName = p->aField[i].zName;
3015
+ if( i>0 ) blob_append_char(b, ',');
3016
+ blob_append_char(b, '{');
3017
+ KVP_STR(0, name, '+'==*zName ? &zName[1] : zName);
3018
+ KVP_STR(1, value, p->aField[i].zValue);
3019
+ blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false");
3020
+ blob_append_char(b, '}');
3021
+ }
3022
+ blob_append_char(b, ']');
3023
+ }
3024
+ CARD_STR2(K, p->zTicketUuid);
3025
+ CARD_STR2(L, p->zWikiTitle);
3026
+ ISA( CFTYPE_CLUSTER ){
3027
+ CARD_LETTER(M);
3028
+ blob_append_char(b, '[');
3029
+ for( int i = 0; i < p->nCChild; ++i ){
3030
+ if( i>0 ) blob_append_char(b, ',');
3031
+ blob_appendf(b, "%!j", p->azCChild[i]);
3032
+ }
3033
+ blob_append_char(b, ']');
3034
+ }
3035
+ CARD_STR2(N, p->zMimetype);
3036
+ ISA( CFTYPE_MANIFEST || p->nParent>0 ){
3037
+ CARD_LETTER(P);
3038
+ blob_append_char(b, '[');
3039
+ for( i = 0; i < p->nParent; ++i ){
3040
+ if( i>0 ) blob_append_char(b, ',');
3041
+ blob_appendf(b, "%!j", p->azParent[i]);
3042
+ }
3043
+ /* Special case: model checkins with no P-card as having an empty
3044
+ ** array, as per F-cards. */
3045
+ blob_append_char(b, ']');
3046
+ }
3047
+ if( p->nCherrypick ){
3048
+ CARD_LETTER(Q);
3049
+ blob_append_char(b, '[');
3050
+ for( i = 0; i < p->nCherrypick; ++i ){
3051
+ if( i>0 ) blob_append_char(b, ',');
3052
+ blob_append_char(b, '{');
3053
+ blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]);
3054
+ KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3055
+ KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3056
+ blob_append_char(b, '}');
3057
+ }
3058
+ blob_append_char(b, ']');
3059
+ }
3060
+ CARD_STR2(R, p->zRepoCksum);
3061
+ if( p->nTag ){
3062
+ CARD_LETTER(T);
3063
+ blob_append_char(b, '[');
3064
+ for( int i = 0; i < p->nTag; ++i ){
3065
+ const char *zName = p->aTag[i].zName;
3066
+ if( i>0 ) blob_append_char(b, ',');
3067
+ blob_append_char(b, '{');
3068
+ blob_appendf(b, "\"type\":\"%c\"", *zName);
3069
+ KVP_STR(1, name, &zName[1]);
3070
+ KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3071
+ /* We could arguably resolve the "*" as null or p's uuid. */;
3072
+ KVP_STR(1, value, p->aTag[i].zValue);
3073
+ blob_append_char(b, '}');
3074
+ }
3075
+ blob_append_char(b, ']');
3076
+ }
3077
+ CARD_STR2(U, p->zUser);
3078
+ if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type
3079
+ || CFTYPE_EVENT==p->type ){
3080
+ CARD_LETTER(W);
3081
+ STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL);
3082
+ }
3083
+ blob_append_literal(b, "}");
3084
+#undef CARD_FMT
3085
+#undef CARD_LETTER
3086
+#undef CARD_STR
3087
+#undef CARD_STR2
3088
+#undef ISA
3089
+#undef KVP_STR
3090
+#undef STR_OR_NULL
3091
+}
3092
+
3093
+/*
3094
+** Convenience wrapper around artifact_to_json() which expects rid to
3095
+** be the blob.rid of any artifact type. If it can load a Manifest
3096
+** with that rid, it returns rid, else it returns 0.
3097
+*/
3098
+int artifact_to_json_by_rid(int rid, Blob *pOut){
3099
+ Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3100
+ if( p ){
3101
+ artifact_to_json(p, pOut);
3102
+ manifest_destroy(p);
3103
+ }else{
3104
+ rid = 0;
3105
+ }
3106
+ return rid;
3107
+}
3108
+
3109
+/*
3110
+** Convenience wrapper around artifact_to_json() which accepts any
3111
+** artifact name which is legal for symbolic_name_to_rid(). On success
3112
+** it returns the rid of the artifact. Returns 0 if no such artifact
3113
+** exists and a negative value if the name is ambiguous.
3114
+**
3115
+** pOut is not cleared before rendering, so the caller needs to do
3116
+** that if it's important for their use case.
3117
+*/
3118
+int artifact_to_json_by_name(const char *zName, Blob *pOut){
3119
+ const int rid = symbolic_name_to_rid(zName, 0);
3120
+ return rid>0
3121
+ ? artifact_to_json_by_rid(rid, pOut)
3122
+ : rid;
3123
+}
3124
+
3125
+/*
3126
+** SQLite UDF for artifact_to_json(). Its single argument should be
3127
+** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3128
+** name, as per symbolic_name_to_rid(). If an artifact is found then
3129
+** the result of the UDF is that JSON as a string, else it evaluates
3130
+** to NULL.
3131
+*/
3132
+void artifact_to_json_sql_func(
3133
+ sqlite3_context *context,
3134
+ int argc,
3135
+ sqlite3_value **argv
3136
+){
3137
+ int rid = 0;
3138
+ Blob b = empty_blob;
3139
+
3140
+ if(1 != argc){
3141
+ goto error_usage;
3142
+ }
3143
+ switch( sqlite3_value_type(argv[0]) ){
3144
+ case SQLITE_INTEGER:
3145
+ rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3146
+ break;
3147
+ case SQLITE_TEXT:{
3148
+ const char * z = (const char *)sqlite3_value_text(argv[0]);
3149
+ if( z ){
3150
+ rid = artifact_to_json_by_name(z, &b);
3151
+ }
3152
+ break;
3153
+ }
3154
+ default:
3155
+ goto error_usage;
3156
+ }
3157
+ if( rid>0 ){
3158
+ sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3159
+ SQLITE_TRANSIENT);
3160
+ blob_reset(&b);
3161
+ }else{
3162
+ /* We should arguably error out if rid<0 (ambiguous name) */
3163
+ sqlite3_result_null(context);
3164
+ }
3165
+ return;
3166
+error_usage:
3167
+ sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3168
+ "artifact symbolic name", -1);
3169
+}
3170
+
3171
+
3172
+
3173
+/*
3174
+** COMMAND: test-artifact-to-json
3175
+**
3176
+** Usage: %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names]
3177
+**
3178
+** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3179
+*/
3180
+void test_manifest_to_json(void){
3181
+ int i;
3182
+ Blob b = empty_blob;
3183
+ Stmt q;
3184
+ const int bPretty = find_option("pretty","p",0)!=0;
3185
+ int nErr = 0;
3186
+
3187
+ db_find_and_open_repository(0,0);
3188
+ db_prepare(&q, "select json_pretty(:json)");
3189
+ for( i=2; i<g.argc; ++i ){
3190
+ char const *zName = g.argv[i];
3191
+ const int rc = artifact_to_json_by_name(zName, &b);
3192
+ if( rc<=0 ){
3193
+ ++nErr;
3194
+ fossil_warning("Error reading artifact %Q", zName);
3195
+ continue;
3196
+ }else if( bPretty ){
3197
+ db_bind_blob(&q, ":json", &b);
3198
+ b.nUsed = 0;
3199
+ db_step(&q);
3200
+ db_column_blob(&q, 0, &b);
3201
+ db_reset(&q);
3202
+ }
3203
+ fossil_print("%b\n", &b);
3204
+ blob_reset(&b);
3205
+ }
3206
+ db_finalize(&q);
3207
+ if( nErr ){
3208
+ fossil_warning("Error count: %d", nErr);
3209
+ }
3210
+}
29163211
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,300 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2916
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,300 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
2916
2917 /*
2918 ** For a given CATYPE_... value, returns a human-friendly name, or
2919 ** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by
2920 ** this function are geared towards use with artifact_to_json(), and
2921 ** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts
2922 ** are called "tag" artifacts by this function.
2923 */
2924 const char * artifact_type_to_name(int typeId){
2925 switch(typeId){
2926 case CFTYPE_MANIFEST: return "checkin";
2927 case CFTYPE_CLUSTER: return "cluster";
2928 case CFTYPE_CONTROL: return "tag";
2929 case CFTYPE_WIKI: return "wiki";
2930 case CFTYPE_TICKET: return "ticket";
2931 case CFTYPE_ATTACHMENT: return "attachment";
2932 case CFTYPE_EVENT: return "technote";
2933 case CFTYPE_FORUM: return "forumpost";
2934 }
2935 return NULL;
2936 }
2937
2938 /*
2939 ** Creates a JSON representation of p, appending it to b.
2940 **
2941 ** b is not cleared before rendering, so the caller needs to do that
2942 ** if it's important for their use case.
2943 **
2944 ** Pedantic note: this routine traverses p->aFile directly, rather
2945 ** than using manifest_file_next(), so that delta manifests are
2946 ** rendered as-is instead of containing their derived F-cards. If that
2947 ** policy is ever changed, p will need to be non-const.
2948 */
2949 void artifact_to_json(Manifest const *p, Blob *b){
2950 int i;
2951
2952 blob_append_literal(b, "{");
2953 blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid));
2954 /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2955 blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type));
2956 #define ISA(TYPE) if( p->type==TYPE )
2957 #define CARD_LETTER(LETTER) \
2958 blob_append_literal(b, ",\"" #LETTER "\":")
2959 #define CARD_STR(LETTER, VAL) \
2960 assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2961 #define CARD_STR2(LETTER, VAL) \
2962 if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2963 #define STR_OR_NULL(VAL) \
2964 if( VAL ) blob_appendf(b, "%!j", VAL); \
2965 else blob_append(b, "null", 4)
2966 #define KVP_STR(ADDCOMMA, KEY,VAL) \
2967 if(ADDCOMMA) blob_append_char(b, ','); \
2968 blob_appendf(b, "%!j:", #KEY); \
2969 STR_OR_NULL(VAL)
2970
2971 ISA( CFTYPE_ATTACHMENT ){
2972 CARD_LETTER(A);
2973 blob_append_char(b, '{');
2974 KVP_STR(0, filename, p->zAttachName);
2975 KVP_STR(1, target, p->zAttachTarget);
2976 KVP_STR(1, source, p->zAttachSrc);
2977 blob_append_char(b, '}');
2978 }
2979 CARD_STR2(B, p->zBaseline);
2980 CARD_STR2(C, p->zComment);
2981 CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982 ISA( CFTYPE_EVENT ){
2983 blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}",
2984 p->rEventDate, p->zEventId);
2985 }
2986 ISA( CFTYPE_MANIFEST ){
2987 CARD_LETTER(F);
2988 blob_append_char(b, '[');
2989 for( i = 0; i < p->nFile; ++i ){
2990 ManifestFile const * const pF = &p->aFile[i];
2991 if( i>0 ) blob_append_char(b, ',');
2992 blob_append_char(b, '{');
2993 KVP_STR(0, name, pF->zName);
2994 KVP_STR(1, uuid, pF->zUuid);
2995 KVP_STR(1, perm, pF->zPerm);
2996 KVP_STR(1, rename, pF->zPrior);
2997 blob_append_char(b, '}');
2998 }
2999 /* Special case: model checkins with no F-card as having an empty
3000 ** array, rather than no F-cards, to hypothetically simplify
3001 ** handling in JSON queries. */
3002 blob_append_char(b, ']');
3003 }
3004 CARD_STR2(G, p->zThreadRoot);
3005 ISA( CFTYPE_FORUM ){
3006 CARD_LETTER(H);
3007 STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL);
3008 CARD_STR2(I, p->zInReplyTo);
3009 }
3010 if( p->nField ){
3011 CARD_LETTER(J);
3012 blob_append_char(b, '[');
3013 for( i = 0; i < p->nField; ++i ){
3014 const char * zName = p->aField[i].zName;
3015 if( i>0 ) blob_append_char(b, ',');
3016 blob_append_char(b, '{');
3017 KVP_STR(0, name, '+'==*zName ? &zName[1] : zName);
3018 KVP_STR(1, value, p->aField[i].zValue);
3019 blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false");
3020 blob_append_char(b, '}');
3021 }
3022 blob_append_char(b, ']');
3023 }
3024 CARD_STR2(K, p->zTicketUuid);
3025 CARD_STR2(L, p->zWikiTitle);
3026 ISA( CFTYPE_CLUSTER ){
3027 CARD_LETTER(M);
3028 blob_append_char(b, '[');
3029 for( int i = 0; i < p->nCChild; ++i ){
3030 if( i>0 ) blob_append_char(b, ',');
3031 blob_appendf(b, "%!j", p->azCChild[i]);
3032 }
3033 blob_append_char(b, ']');
3034 }
3035 CARD_STR2(N, p->zMimetype);
3036 ISA( CFTYPE_MANIFEST || p->nParent>0 ){
3037 CARD_LETTER(P);
3038 blob_append_char(b, '[');
3039 for( i = 0; i < p->nParent; ++i ){
3040 if( i>0 ) blob_append_char(b, ',');
3041 blob_appendf(b, "%!j", p->azParent[i]);
3042 }
3043 /* Special case: model checkins with no P-card as having an empty
3044 ** array, as per F-cards. */
3045 blob_append_char(b, ']');
3046 }
3047 if( p->nCherrypick ){
3048 CARD_LETTER(Q);
3049 blob_append_char(b, '[');
3050 for( i = 0; i < p->nCherrypick; ++i ){
3051 if( i>0 ) blob_append_char(b, ',');
3052 blob_append_char(b, '{');
3053 blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]);
3054 KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3055 KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3056 blob_append_char(b, '}');
3057 }
3058 blob_append_char(b, ']');
3059 }
3060 CARD_STR2(R, p->zRepoCksum);
3061 if( p->nTag ){
3062 CARD_LETTER(T);
3063 blob_append_char(b, '[');
3064 for( int i = 0; i < p->nTag; ++i ){
3065 const char *zName = p->aTag[i].zName;
3066 if( i>0 ) blob_append_char(b, ',');
3067 blob_append_char(b, '{');
3068 blob_appendf(b, "\"type\":\"%c\"", *zName);
3069 KVP_STR(1, name, &zName[1]);
3070 KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3071 /* We could arguably resolve the "*" as null or p's uuid. */;
3072 KVP_STR(1, value, p->aTag[i].zValue);
3073 blob_append_char(b, '}');
3074 }
3075 blob_append_char(b, ']');
3076 }
3077 CARD_STR2(U, p->zUser);
3078 if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type
3079 || CFTYPE_EVENT==p->type ){
3080 CARD_LETTER(W);
3081 STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL);
3082 }
3083 blob_append_literal(b, "}");
3084 #undef CARD_FMT
3085 #undef CARD_LETTER
3086 #undef CARD_STR
3087 #undef CARD_STR2
3088 #undef ISA
3089 #undef KVP_STR
3090 #undef STR_OR_NULL
3091 }
3092
3093 /*
3094 ** Convenience wrapper around artifact_to_json() which expects rid to
3095 ** be the blob.rid of any artifact type. If it can load a Manifest
3096 ** with that rid, it returns rid, else it returns 0.
3097 */
3098 int artifact_to_json_by_rid(int rid, Blob *pOut){
3099 Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3100 if( p ){
3101 artifact_to_json(p, pOut);
3102 manifest_destroy(p);
3103 }else{
3104 rid = 0;
3105 }
3106 return rid;
3107 }
3108
3109 /*
3110 ** Convenience wrapper around artifact_to_json() which accepts any
3111 ** artifact name which is legal for symbolic_name_to_rid(). On success
3112 ** it returns the rid of the artifact. Returns 0 if no such artifact
3113 ** exists and a negative value if the name is ambiguous.
3114 **
3115 ** pOut is not cleared before rendering, so the caller needs to do
3116 ** that if it's important for their use case.
3117 */
3118 int artifact_to_json_by_name(const char *zName, Blob *pOut){
3119 const int rid = symbolic_name_to_rid(zName, 0);
3120 return rid>0
3121 ? artifact_to_json_by_rid(rid, pOut)
3122 : rid;
3123 }
3124
3125 /*
3126 ** SQLite UDF for artifact_to_json(). Its single argument should be
3127 ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3128 ** name, as per symbolic_name_to_rid(). If an artifact is found then
3129 ** the result of the UDF is that JSON as a string, else it evaluates
3130 ** to NULL.
3131 */
3132 void artifact_to_json_sql_func(
3133 sqlite3_context *context,
3134 int argc,
3135 sqlite3_value **argv
3136 ){
3137 int rid = 0;
3138 Blob b = empty_blob;
3139
3140 if(1 != argc){
3141 goto error_usage;
3142 }
3143 switch( sqlite3_value_type(argv[0]) ){
3144 case SQLITE_INTEGER:
3145 rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3146 break;
3147 case SQLITE_TEXT:{
3148 const char * z = (const char *)sqlite3_value_text(argv[0]);
3149 if( z ){
3150 rid = artifact_to_json_by_name(z, &b);
3151 }
3152 break;
3153 }
3154 default:
3155 goto error_usage;
3156 }
3157 if( rid>0 ){
3158 sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3159 SQLITE_TRANSIENT);
3160 blob_reset(&b);
3161 }else{
3162 /* We should arguably error out if rid<0 (ambiguous name) */
3163 sqlite3_result_null(context);
3164 }
3165 return;
3166 error_usage:
3167 sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3168 "artifact symbolic name", -1);
3169 }
3170
3171
3172
3173 /*
3174 ** COMMAND: test-artifact-to-json
3175 **
3176 ** Usage: %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names]
3177 **
3178 ** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3179 */
3180 void test_manifest_to_json(void){
3181 int i;
3182 Blob b = empty_blob;
3183 Stmt q;
3184 const int bPretty = find_option("pretty","p",0)!=0;
3185 int nErr = 0;
3186
3187 db_find_and_open_repository(0,0);
3188 db_prepare(&q, "select json_pretty(:json)");
3189 for( i=2; i<g.argc; ++i ){
3190 char const *zName = g.argv[i];
3191 const int rc = artifact_to_json_by_name(zName, &b);
3192 if( rc<=0 ){
3193 ++nErr;
3194 fossil_warning("Error reading artifact %Q", zName);
3195 continue;
3196 }else if( bPretty ){
3197 db_bind_blob(&q, ":json", &b);
3198 b.nUsed = 0;
3199 db_step(&q);
3200 db_column_blob(&q, 0, &b);
3201 db_reset(&q);
3202 }
3203 fossil_print("%b\n", &b);
3204 blob_reset(&b);
3205 }
3206 db_finalize(&q);
3207 if( nErr ){
3208 fossil_warning("Error count: %d", nErr);
3209 }
3210 }
3211
+56 -1
--- src/name.c
+++ src/name.c
@@ -70,11 +70,11 @@
7070
** ^^^^--- Added
7171
**
7272
** 202503171234 -> 2025-03-17 12:34:59.999
7373
** ^^^^^^^--- Added
7474
** 20250317 -> 2025-03-17 23:59:59.999
75
-** ^^^^^^^^^^^^--- Added
75
+** ^^^^^^^^^^^^^--- Added
7676
**
7777
** If the bVerifyNotAHash flag is true, then a check is made to see if
7878
** the input string is a hash prefix and NULL is returned if it is. If the
7979
** bVerifyNotAHash flag is false, then the result is determined by syntax
8080
** of the input string only, without reference to the artifact table.
@@ -718,10 +718,65 @@
718718
rid = symbolic_name_to_rid(zNew,zType);
719719
fossil_free(zNew);
720720
}
721721
return rid;
722722
}
723
+
724
+/*
725
+** Convert a symbolic name used as an argument to the a=, b=, or c=
726
+** query parameters of timeline into a julianday mtime value.
727
+**
728
+** If pzDisplay is not null, then display text for the symbolic name might
729
+** be written into *pzDisplay. But that is not guaranteed.
730
+**
731
+** If bRoundUp is true and the symbolic name is a timestamp with less
732
+** than millisecond resolution, then the timestamp is rounding up to the
733
+** largest millisecond consistent with that timestamp. If bRoundUp is
734
+** false, then the resulting time is obtained by extending the timestamp
735
+** with zeros (hence rounding down). Use bRoundUp==1 if the result
736
+** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
737
+** will be used in mtime>=$RESULT.
738
+*/
739
+double symbolic_name_to_mtime(
740
+ const char *z, /* Input symbolic name */
741
+ const char **pzDisplay, /* Perhaps write display text here, if not NULL */
742
+ int bRoundUp /* Round up if true */
743
+){
744
+ double mtime;
745
+ int rid;
746
+ const char *zDate;
747
+ if( z==0 ) return -1.0;
748
+ if( fossil_isdate(z) ){
749
+ mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
750
+ if( mtime>0.0 ) return mtime;
751
+ }
752
+ zDate = fossil_expand_datetime(z, 1, bRoundUp);
753
+ if( zDate!=0 ){
754
+ mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
755
+ bRoundUp ? fossil_roundup_date(zDate) : zDate);
756
+ if( mtime>0.0 ){
757
+ if( pzDisplay ){
758
+ zDate = fossil_expand_datetime(z,0,0);
759
+ *pzDisplay = fossil_strdup(zDate);
760
+ }
761
+ return mtime;
762
+ }
763
+ }
764
+ rid = symbolic_name_to_rid(z, "*");
765
+ if( rid ){
766
+ mtime = mtime_of_rid(rid, 0.0);
767
+ }else{
768
+ mtime = db_double(-1.0,
769
+ "SELECT max(event.mtime) FROM event, tag, tagxref"
770
+ " WHERE tag.tagname GLOB 'event-%q*'"
771
+ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
772
+ " AND event.objid=tagxref.rid",
773
+ z
774
+ );
775
+ }
776
+ return mtime;
777
+}
723778
724779
/*
725780
** This routine takes a user-entered string and tries to convert it to
726781
** an artifact hash.
727782
**
728783
--- src/name.c
+++ src/name.c
@@ -70,11 +70,11 @@
70 ** ^^^^--- Added
71 **
72 ** 202503171234 -> 2025-03-17 12:34:59.999
73 ** ^^^^^^^--- Added
74 ** 20250317 -> 2025-03-17 23:59:59.999
75 ** ^^^^^^^^^^^^--- Added
76 **
77 ** If the bVerifyNotAHash flag is true, then a check is made to see if
78 ** the input string is a hash prefix and NULL is returned if it is. If the
79 ** bVerifyNotAHash flag is false, then the result is determined by syntax
80 ** of the input string only, without reference to the artifact table.
@@ -718,10 +718,65 @@
718 rid = symbolic_name_to_rid(zNew,zType);
719 fossil_free(zNew);
720 }
721 return rid;
722 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
724 /*
725 ** This routine takes a user-entered string and tries to convert it to
726 ** an artifact hash.
727 **
728
--- src/name.c
+++ src/name.c
@@ -70,11 +70,11 @@
70 ** ^^^^--- Added
71 **
72 ** 202503171234 -> 2025-03-17 12:34:59.999
73 ** ^^^^^^^--- Added
74 ** 20250317 -> 2025-03-17 23:59:59.999
75 ** ^^^^^^^^^^^^^--- Added
76 **
77 ** If the bVerifyNotAHash flag is true, then a check is made to see if
78 ** the input string is a hash prefix and NULL is returned if it is. If the
79 ** bVerifyNotAHash flag is false, then the result is determined by syntax
80 ** of the input string only, without reference to the artifact table.
@@ -718,10 +718,65 @@
718 rid = symbolic_name_to_rid(zNew,zType);
719 fossil_free(zNew);
720 }
721 return rid;
722 }
723
724 /*
725 ** Convert a symbolic name used as an argument to the a=, b=, or c=
726 ** query parameters of timeline into a julianday mtime value.
727 **
728 ** If pzDisplay is not null, then display text for the symbolic name might
729 ** be written into *pzDisplay. But that is not guaranteed.
730 **
731 ** If bRoundUp is true and the symbolic name is a timestamp with less
732 ** than millisecond resolution, then the timestamp is rounding up to the
733 ** largest millisecond consistent with that timestamp. If bRoundUp is
734 ** false, then the resulting time is obtained by extending the timestamp
735 ** with zeros (hence rounding down). Use bRoundUp==1 if the result
736 ** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
737 ** will be used in mtime>=$RESULT.
738 */
739 double symbolic_name_to_mtime(
740 const char *z, /* Input symbolic name */
741 const char **pzDisplay, /* Perhaps write display text here, if not NULL */
742 int bRoundUp /* Round up if true */
743 ){
744 double mtime;
745 int rid;
746 const char *zDate;
747 if( z==0 ) return -1.0;
748 if( fossil_isdate(z) ){
749 mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
750 if( mtime>0.0 ) return mtime;
751 }
752 zDate = fossil_expand_datetime(z, 1, bRoundUp);
753 if( zDate!=0 ){
754 mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
755 bRoundUp ? fossil_roundup_date(zDate) : zDate);
756 if( mtime>0.0 ){
757 if( pzDisplay ){
758 zDate = fossil_expand_datetime(z,0,0);
759 *pzDisplay = fossil_strdup(zDate);
760 }
761 return mtime;
762 }
763 }
764 rid = symbolic_name_to_rid(z, "*");
765 if( rid ){
766 mtime = mtime_of_rid(rid, 0.0);
767 }else{
768 mtime = db_double(-1.0,
769 "SELECT max(event.mtime) FROM event, tag, tagxref"
770 " WHERE tag.tagname GLOB 'event-%q*'"
771 " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
772 " AND event.objid=tagxref.rid",
773 z
774 );
775 }
776 return mtime;
777 }
778
779 /*
780 ** This routine takes a user-entered string and tries to convert it to
781 ** an artifact hash.
782 **
783
+9 -24
--- src/printf.c
+++ src/printf.c
@@ -248,18 +248,10 @@
248248
** If enabled, no wiki-formatting is done for timeline comment messages.
249249
** Hyperlinks are activated, but they show up on screen using the
250250
** complete input text, not just the display text. No other formatting
251251
** is done.
252252
*/
253
-/*
254
-** SETTING: timeline-block-markup boolean default=off
255
-**
256
-** If enabled, block markup (paragraph brakes, tables, lists, headings, etc)
257
-** is enabled while rendering check-in comment message on the timeline.
258
-** This is disabled by default, because the timeline works best if the
259
-** check-in comments are short and do not take up too much vertical space.
260
-*/
261253
/*
262254
** SETTING: timeline-hard-newlines boolean default=off
263255
**
264256
** If enabled, the timeline honors newline characters in check-in comments.
265257
** In other words, newlines are coverted into <br> for HTML display.
@@ -271,36 +263,29 @@
271263
** Return an appropriate set of flags for wiki_convert() for displaying
272264
** comments on a timeline. These flag settings are determined by
273265
** configuration parameters.
274266
**
275267
** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
276
-** flags) and is false for plain "%W". The ! indicates that the text is
277
-** to be rendered on a form rather than the timeline and that block markup
278
-** is acceptable even if the "timeline-block-markup" setting is false.
268
+** flags) and is false for plain "%W". The ! flag indicates that the
269
+** formatting is for display of a check-in comment on the timeline. Such
270
+** comments used to be renderedd differently, but ever since 2020, they
271
+** have been rendered identially, so the ! flag does not make any different
272
+** in the output any more.
279273
*/
280
-static int wiki_convert_flags(int altForm2){
274
+int wiki_convert_flags(int altForm2){
281275
static int wikiFlags = 0;
276
+ (void)altForm2;
282277
if( wikiFlags==0 ){
283
- if( db_get_boolean("timeline-block-markup", 0) ){
284
- wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
285
- }else{
286
- wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS;
287
- }
278
+ wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
288279
if( db_get_boolean("timeline-plaintext", 0) ){
289280
wikiFlags |= WIKI_LINKSONLY;
290281
}
291282
if( db_get_boolean("timeline-hard-newlines", 0) ){
292283
wikiFlags |= WIKI_NEWLINE;
293284
}
294285
}
295
- if( altForm2 ){
296
- /* block markup (ex: <p>, <table>) allowed */
297
- return wikiFlags & ~WIKI_NOBLOCK;
298
- }else{
299
- /* Do not allow any block format. Everything in a <span> */
300
- return wikiFlags;
301
- }
286
+ return wikiFlags;
302287
}
303288
304289
305290
306291
/*
307292
--- src/printf.c
+++ src/printf.c
@@ -248,18 +248,10 @@
248 ** If enabled, no wiki-formatting is done for timeline comment messages.
249 ** Hyperlinks are activated, but they show up on screen using the
250 ** complete input text, not just the display text. No other formatting
251 ** is done.
252 */
253 /*
254 ** SETTING: timeline-block-markup boolean default=off
255 **
256 ** If enabled, block markup (paragraph brakes, tables, lists, headings, etc)
257 ** is enabled while rendering check-in comment message on the timeline.
258 ** This is disabled by default, because the timeline works best if the
259 ** check-in comments are short and do not take up too much vertical space.
260 */
261 /*
262 ** SETTING: timeline-hard-newlines boolean default=off
263 **
264 ** If enabled, the timeline honors newline characters in check-in comments.
265 ** In other words, newlines are coverted into <br> for HTML display.
@@ -271,36 +263,29 @@
271 ** Return an appropriate set of flags for wiki_convert() for displaying
272 ** comments on a timeline. These flag settings are determined by
273 ** configuration parameters.
274 **
275 ** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
276 ** flags) and is false for plain "%W". The ! indicates that the text is
277 ** to be rendered on a form rather than the timeline and that block markup
278 ** is acceptable even if the "timeline-block-markup" setting is false.
 
 
279 */
280 static int wiki_convert_flags(int altForm2){
281 static int wikiFlags = 0;
 
282 if( wikiFlags==0 ){
283 if( db_get_boolean("timeline-block-markup", 0) ){
284 wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
285 }else{
286 wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS;
287 }
288 if( db_get_boolean("timeline-plaintext", 0) ){
289 wikiFlags |= WIKI_LINKSONLY;
290 }
291 if( db_get_boolean("timeline-hard-newlines", 0) ){
292 wikiFlags |= WIKI_NEWLINE;
293 }
294 }
295 if( altForm2 ){
296 /* block markup (ex: <p>, <table>) allowed */
297 return wikiFlags & ~WIKI_NOBLOCK;
298 }else{
299 /* Do not allow any block format. Everything in a <span> */
300 return wikiFlags;
301 }
302 }
303
304
305
306 /*
307
--- src/printf.c
+++ src/printf.c
@@ -248,18 +248,10 @@
248 ** If enabled, no wiki-formatting is done for timeline comment messages.
249 ** Hyperlinks are activated, but they show up on screen using the
250 ** complete input text, not just the display text. No other formatting
251 ** is done.
252 */
 
 
 
 
 
 
 
 
253 /*
254 ** SETTING: timeline-hard-newlines boolean default=off
255 **
256 ** If enabled, the timeline honors newline characters in check-in comments.
257 ** In other words, newlines are coverted into <br> for HTML display.
@@ -271,36 +263,29 @@
263 ** Return an appropriate set of flags for wiki_convert() for displaying
264 ** comments on a timeline. These flag settings are determined by
265 ** configuration parameters.
266 **
267 ** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
268 ** flags) and is false for plain "%W". The ! flag indicates that the
269 ** formatting is for display of a check-in comment on the timeline. Such
270 ** comments used to be renderedd differently, but ever since 2020, they
271 ** have been rendered identially, so the ! flag does not make any different
272 ** in the output any more.
273 */
274 int wiki_convert_flags(int altForm2){
275 static int wikiFlags = 0;
276 (void)altForm2;
277 if( wikiFlags==0 ){
278 wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
 
 
 
 
279 if( db_get_boolean("timeline-plaintext", 0) ){
280 wikiFlags |= WIKI_LINKSONLY;
281 }
282 if( db_get_boolean("timeline-hard-newlines", 0) ){
283 wikiFlags |= WIKI_NEWLINE;
284 }
285 }
286 return wikiFlags;
 
 
 
 
 
 
287 }
288
289
290
291 /*
292
+3 -2
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316316
style_header("Repository List");
317317
@ %s(blob_str(&html))
318318
style_table_sorter();
319319
style_finish_page();
320320
}else{
321
+ const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List");
321322
/* If no repositories were found that had the "repolist_skin"
322323
** property set, then use a default skin */
323324
@ <html>
324325
@ <head>
325326
@ <base href="%s(g.zBaseURL)/">
326327
@ <meta name="viewport" content="width=device-width, initial-scale=1.0">
327
- @ <title>Repository List</title>
328
+ @ <title>%h(zTitle)</title>
328329
@ </head>
329330
@ <body>
330
- @ <h1 align="center">Fossil Repositories</h1>
331
+ @ <h1 align="center">%h(zTitle)</h1>
331332
@ %s(blob_str(&html))
332333
@ <script>%s(builtin_text("sorttable.js"))</script>
333334
@ </body>
334335
@ </html>
335336
}
336337
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316 style_header("Repository List");
317 @ %s(blob_str(&html))
318 style_table_sorter();
319 style_finish_page();
320 }else{
 
321 /* If no repositories were found that had the "repolist_skin"
322 ** property set, then use a default skin */
323 @ <html>
324 @ <head>
325 @ <base href="%s(g.zBaseURL)/">
326 @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
327 @ <title>Repository List</title>
328 @ </head>
329 @ <body>
330 @ <h1 align="center">Fossil Repositories</h1>
331 @ %s(blob_str(&html))
332 @ <script>%s(builtin_text("sorttable.js"))</script>
333 @ </body>
334 @ </html>
335 }
336
--- src/repolist.c
+++ src/repolist.c
@@ -316,20 +316,21 @@
316 style_header("Repository List");
317 @ %s(blob_str(&html))
318 style_table_sorter();
319 style_finish_page();
320 }else{
321 const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List");
322 /* If no repositories were found that had the "repolist_skin"
323 ** property set, then use a default skin */
324 @ <html>
325 @ <head>
326 @ <base href="%s(g.zBaseURL)/">
327 @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
328 @ <title>%h(zTitle)</title>
329 @ </head>
330 @ <body>
331 @ <h1 align="center">%h(zTitle)</h1>
332 @ %s(blob_str(&html))
333 @ <script>%s(builtin_text("sorttable.js"))</script>
334 @ </body>
335 @ </html>
336 }
337
+9 -95
--- src/search.c
+++ src/search.c
@@ -559,93 +559,10 @@
559559
search_body_sqlfunc, 0, 0);
560560
sqlite3_create_function(db, "urlencode", 1, enc, 0,
561561
search_urlencode_sqlfunc, 0, 0);
562562
}
563563
564
-/*
565
-** The pSnip input contains snippet text from a search formatted
566
-** as HTML. Attempt to make that text more readable on a TTY.
567
-**
568
-** If nTty is positive, use ANSI escape codes "\e[Nm" where N is nTty
569
-** to highly marked text.
570
-*/
571
-void search_snippet_to_plaintext(Blob *pSnip, int nTty){
572
- char *zSnip;
573
- unsigned int j, k;
574
-
575
- zSnip = pSnip->aData;
576
- for(j=k=0; j<pSnip->nUsed; j++){
577
- char c = zSnip[j];
578
- if( c=='<' ){
579
- if( memcmp(&zSnip[j],"<mark>",6)==0 ){
580
- if( nTty ){
581
- zSnip[k++] = 0x1b;
582
- zSnip[k++] = '[';
583
- if( nTty>=10 ) zSnip[k++] = (nTty/10)%10 + '0';
584
- zSnip[k++] = nTty%10 + '0';
585
- zSnip[k++] = 'm';
586
- }
587
- j += 5;
588
- }else if( memcmp(&zSnip[j],"</mark>",7)==0 ){
589
- if( nTty ){
590
- zSnip[k++] = 0x1b;
591
- zSnip[k++] = '[';
592
- zSnip[k++] = '0';
593
- zSnip[k++] = 'm';
594
- }
595
- j += 6;
596
- }else{
597
- zSnip[k++] = zSnip[j];
598
- }
599
- }else if( fossil_isspace(c) ){
600
- zSnip[k++] = ' ';
601
- while( fossil_isspace(zSnip[j+1]) ) j++;
602
- }else if( c=='&' ){
603
- if( zSnip[j+1]=='#' && fossil_isdigit(zSnip[j+2]) ){
604
- int n = 3;
605
- int x = zSnip[j+2] - '0';
606
- if( fossil_isdigit(zSnip[j+3]) ){
607
- x = x*10 + zSnip[j+3] - '0';
608
- n++;
609
- if( fossil_isdigit(zSnip[j+4]) ){
610
- x = x*10 + zSnip[j+4] - '0';
611
- n++;
612
- }
613
- }
614
- if( zSnip[j+n]==';' ){
615
- zSnip[k++] = (char)x;
616
- j += n;
617
- }else{
618
- zSnip[k++] = c;
619
- }
620
- }else if( memcmp(&zSnip[j],"&lt;",4)==0 ){
621
- zSnip[k++] = '<';
622
- j += 3;
623
- }else if( memcmp(&zSnip[j],"&gt;",4)==0 ){
624
- zSnip[k++] = '>';
625
- j += 3;
626
- }else if( memcmp(&zSnip[j],"&quot;",6)==0 ){
627
- zSnip[k++] = '"';
628
- j += 5;
629
- }else if( memcmp(&zSnip[j],"&amp;",5)==0 ){
630
- zSnip[k++] = '&';
631
- j += 4;
632
- }else{
633
- zSnip[k++] = c;
634
- }
635
- }else if( c=='%' && strncmp(&zSnip[j],"%fossil",7)==0 ){
636
- /* no-op */
637
- }else if( (c=='[' || c==']') && zSnip[j+1]==c ){
638
- j++;
639
- }else{
640
- zSnip[k++] = c;
641
- }
642
- }
643
- zSnip[k] = 0;
644
- pSnip->nUsed = k;
645
-}
646
-
647564
/*
648565
** Testing the search function.
649566
**
650567
** COMMAND: search*
651568
**
@@ -702,18 +619,11 @@
702619
int nLimit = zLimit ? atoi(zLimit) : -1000;
703620
int width;
704621
int nTty = 0; /* VT100 highlight color for matching text */
705622
const char *zHighlight = 0;
706623
707
- /* Only colorize the output if talking to a tty and NO_COLOR does not
708
- ** exist or is false. */
709
- if( fossil_isatty(1) ){
710
- char *zNoColor = fossil_getenv("NO_COLOR");
711
- if( zNoColor==0 || zNoColor[0]==0 || is_false(zNoColor) ){
712
- nTty = 91;
713
- }
714
- }
624
+ nTty = terminal_is_vt100();
715625
716626
/* Undocumented option to change highlight color */
717627
zHighlight = find_option("highlight",0,1);
718628
if( zHighlight ) nTty = atoi(zHighlight);
719629
@@ -805,19 +715,23 @@
805715
db_prepare(&q, "SELECT snip, label, score, id, date"
806716
" FROM x"
807717
" ORDER BY score DESC, date DESC;");
808718
blob_init(&com, 0, 0);
809719
blob_init(&snip, 0, 0);
810
- if( width<0 ) width = 80;
720
+ if( width<0 ) width = terminal_get_width(80);
811721
while( db_step(&q)==SQLITE_ROW ){
812722
const char *zSnippet = db_column_text(&q, 0);
813723
const char *zLabel = db_column_text(&q, 1);
814724
const char *zDate = db_column_text(&q, 4);
815725
const char *zScore = db_column_text(&q, 2);
816726
const char *zId = db_column_text(&q, 3);
727
+ char *zOrig;
817728
blob_appendf(&snip, "%s", zSnippet);
818
- search_snippet_to_plaintext(&snip, nTty);
729
+ zOrig = blob_materialize(&snip);
730
+ blob_init(&snip, 0, 0);
731
+ html_to_plaintext(zOrig, &snip, (nTty?HTOT_VT100:0)|HTOT_FLOW|HTOT_TRIM);
732
+ fossil_free(zOrig);
819733
blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate);
820734
if( bDebug ){
821735
blob_appendf(&com," score: %s id: %s", zScore, zId);
822736
}
823737
comment_print(blob_str(&com), 0, 5, width,
@@ -1557,20 +1471,20 @@
15571471
}else{
15581472
blob_append(pOut, "\n", 1);
15591473
wiki_convert(pIn, &html, 0);
15601474
}
15611475
}
1562
- html_to_plaintext(blob_str(&html), pOut);
1476
+ html_to_plaintext(blob_str(&html), pOut, 0);
15631477
}else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
15641478
markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html);
15651479
}else if( fossil_strcmp(zMimetype,"text/html")==0 ){
15661480
if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title);
15671481
pHtml = pIn;
15681482
}
15691483
blob_appendf(pOut, "%s\n", blob_str(&title));
15701484
if( blob_size(pHtml) ){
1571
- html_to_plaintext(blob_str(pHtml), pOut);
1485
+ html_to_plaintext(blob_str(pHtml), pOut, 0);
15721486
}else{
15731487
blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
15741488
}
15751489
blob_reset(&html);
15761490
blob_reset(&title);
15771491
--- src/search.c
+++ src/search.c
@@ -559,93 +559,10 @@
559 search_body_sqlfunc, 0, 0);
560 sqlite3_create_function(db, "urlencode", 1, enc, 0,
561 search_urlencode_sqlfunc, 0, 0);
562 }
563
564 /*
565 ** The pSnip input contains snippet text from a search formatted
566 ** as HTML. Attempt to make that text more readable on a TTY.
567 **
568 ** If nTty is positive, use ANSI escape codes "\e[Nm" where N is nTty
569 ** to highly marked text.
570 */
571 void search_snippet_to_plaintext(Blob *pSnip, int nTty){
572 char *zSnip;
573 unsigned int j, k;
574
575 zSnip = pSnip->aData;
576 for(j=k=0; j<pSnip->nUsed; j++){
577 char c = zSnip[j];
578 if( c=='<' ){
579 if( memcmp(&zSnip[j],"<mark>",6)==0 ){
580 if( nTty ){
581 zSnip[k++] = 0x1b;
582 zSnip[k++] = '[';
583 if( nTty>=10 ) zSnip[k++] = (nTty/10)%10 + '0';
584 zSnip[k++] = nTty%10 + '0';
585 zSnip[k++] = 'm';
586 }
587 j += 5;
588 }else if( memcmp(&zSnip[j],"</mark>",7)==0 ){
589 if( nTty ){
590 zSnip[k++] = 0x1b;
591 zSnip[k++] = '[';
592 zSnip[k++] = '0';
593 zSnip[k++] = 'm';
594 }
595 j += 6;
596 }else{
597 zSnip[k++] = zSnip[j];
598 }
599 }else if( fossil_isspace(c) ){
600 zSnip[k++] = ' ';
601 while( fossil_isspace(zSnip[j+1]) ) j++;
602 }else if( c=='&' ){
603 if( zSnip[j+1]=='#' && fossil_isdigit(zSnip[j+2]) ){
604 int n = 3;
605 int x = zSnip[j+2] - '0';
606 if( fossil_isdigit(zSnip[j+3]) ){
607 x = x*10 + zSnip[j+3] - '0';
608 n++;
609 if( fossil_isdigit(zSnip[j+4]) ){
610 x = x*10 + zSnip[j+4] - '0';
611 n++;
612 }
613 }
614 if( zSnip[j+n]==';' ){
615 zSnip[k++] = (char)x;
616 j += n;
617 }else{
618 zSnip[k++] = c;
619 }
620 }else if( memcmp(&zSnip[j],"&lt;",4)==0 ){
621 zSnip[k++] = '<';
622 j += 3;
623 }else if( memcmp(&zSnip[j],"&gt;",4)==0 ){
624 zSnip[k++] = '>';
625 j += 3;
626 }else if( memcmp(&zSnip[j],"&quot;",6)==0 ){
627 zSnip[k++] = '"';
628 j += 5;
629 }else if( memcmp(&zSnip[j],"&amp;",5)==0 ){
630 zSnip[k++] = '&';
631 j += 4;
632 }else{
633 zSnip[k++] = c;
634 }
635 }else if( c=='%' && strncmp(&zSnip[j],"%fossil",7)==0 ){
636 /* no-op */
637 }else if( (c=='[' || c==']') && zSnip[j+1]==c ){
638 j++;
639 }else{
640 zSnip[k++] = c;
641 }
642 }
643 zSnip[k] = 0;
644 pSnip->nUsed = k;
645 }
646
647 /*
648 ** Testing the search function.
649 **
650 ** COMMAND: search*
651 **
@@ -702,18 +619,11 @@
702 int nLimit = zLimit ? atoi(zLimit) : -1000;
703 int width;
704 int nTty = 0; /* VT100 highlight color for matching text */
705 const char *zHighlight = 0;
706
707 /* Only colorize the output if talking to a tty and NO_COLOR does not
708 ** exist or is false. */
709 if( fossil_isatty(1) ){
710 char *zNoColor = fossil_getenv("NO_COLOR");
711 if( zNoColor==0 || zNoColor[0]==0 || is_false(zNoColor) ){
712 nTty = 91;
713 }
714 }
715
716 /* Undocumented option to change highlight color */
717 zHighlight = find_option("highlight",0,1);
718 if( zHighlight ) nTty = atoi(zHighlight);
719
@@ -805,19 +715,23 @@
805 db_prepare(&q, "SELECT snip, label, score, id, date"
806 " FROM x"
807 " ORDER BY score DESC, date DESC;");
808 blob_init(&com, 0, 0);
809 blob_init(&snip, 0, 0);
810 if( width<0 ) width = 80;
811 while( db_step(&q)==SQLITE_ROW ){
812 const char *zSnippet = db_column_text(&q, 0);
813 const char *zLabel = db_column_text(&q, 1);
814 const char *zDate = db_column_text(&q, 4);
815 const char *zScore = db_column_text(&q, 2);
816 const char *zId = db_column_text(&q, 3);
 
817 blob_appendf(&snip, "%s", zSnippet);
818 search_snippet_to_plaintext(&snip, nTty);
 
 
 
819 blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate);
820 if( bDebug ){
821 blob_appendf(&com," score: %s id: %s", zScore, zId);
822 }
823 comment_print(blob_str(&com), 0, 5, width,
@@ -1557,20 +1471,20 @@
1557 }else{
1558 blob_append(pOut, "\n", 1);
1559 wiki_convert(pIn, &html, 0);
1560 }
1561 }
1562 html_to_plaintext(blob_str(&html), pOut);
1563 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1564 markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html);
1565 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1566 if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title);
1567 pHtml = pIn;
1568 }
1569 blob_appendf(pOut, "%s\n", blob_str(&title));
1570 if( blob_size(pHtml) ){
1571 html_to_plaintext(blob_str(pHtml), pOut);
1572 }else{
1573 blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
1574 }
1575 blob_reset(&html);
1576 blob_reset(&title);
1577
--- src/search.c
+++ src/search.c
@@ -559,93 +559,10 @@
559 search_body_sqlfunc, 0, 0);
560 sqlite3_create_function(db, "urlencode", 1, enc, 0,
561 search_urlencode_sqlfunc, 0, 0);
562 }
563
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564 /*
565 ** Testing the search function.
566 **
567 ** COMMAND: search*
568 **
@@ -702,18 +619,11 @@
619 int nLimit = zLimit ? atoi(zLimit) : -1000;
620 int width;
621 int nTty = 0; /* VT100 highlight color for matching text */
622 const char *zHighlight = 0;
623
624 nTty = terminal_is_vt100();
 
 
 
 
 
 
 
625
626 /* Undocumented option to change highlight color */
627 zHighlight = find_option("highlight",0,1);
628 if( zHighlight ) nTty = atoi(zHighlight);
629
@@ -805,19 +715,23 @@
715 db_prepare(&q, "SELECT snip, label, score, id, date"
716 " FROM x"
717 " ORDER BY score DESC, date DESC;");
718 blob_init(&com, 0, 0);
719 blob_init(&snip, 0, 0);
720 if( width<0 ) width = terminal_get_width(80);
721 while( db_step(&q)==SQLITE_ROW ){
722 const char *zSnippet = db_column_text(&q, 0);
723 const char *zLabel = db_column_text(&q, 1);
724 const char *zDate = db_column_text(&q, 4);
725 const char *zScore = db_column_text(&q, 2);
726 const char *zId = db_column_text(&q, 3);
727 char *zOrig;
728 blob_appendf(&snip, "%s", zSnippet);
729 zOrig = blob_materialize(&snip);
730 blob_init(&snip, 0, 0);
731 html_to_plaintext(zOrig, &snip, (nTty?HTOT_VT100:0)|HTOT_FLOW|HTOT_TRIM);
732 fossil_free(zOrig);
733 blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate);
734 if( bDebug ){
735 blob_appendf(&com," score: %s id: %s", zScore, zId);
736 }
737 comment_print(blob_str(&com), 0, 5, width,
@@ -1557,20 +1471,20 @@
1471 }else{
1472 blob_append(pOut, "\n", 1);
1473 wiki_convert(pIn, &html, 0);
1474 }
1475 }
1476 html_to_plaintext(blob_str(&html), pOut, 0);
1477 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1478 markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html);
1479 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1480 if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title);
1481 pHtml = pIn;
1482 }
1483 blob_appendf(pOut, "%s\n", blob_str(&title));
1484 if( blob_size(pHtml) ){
1485 html_to_plaintext(blob_str(pHtml), pOut, 0);
1486 }else{
1487 blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
1488 }
1489 blob_reset(&html);
1490 blob_reset(&title);
1491
--- src/security_audit.c
+++ src/security_audit.c
@@ -814,10 +814,11 @@
814814
**
815815
** y=0x01 Show only hack attempts
816816
** y=0x02 Show only panics and assertion faults
817817
** y=0x04 Show hung backoffice processes
818818
** y=0x08 Show POST requests from a different origin
819
+** y=0x10 Show SQLITE_AUTH and similar
819820
** y=0x40 Show other uncategorized messages
820821
**
821822
** If y is omitted or is zero, a count of the various message types is
822823
** shown.
823824
*/
@@ -824,19 +825,20 @@
824825
void errorlog_page(void){
825826
i64 szFile;
826827
FILE *in;
827828
char *zLog;
828829
const char *zType = P("y");
829
- static const int eAllTypes = 0x4f;
830
+ static const int eAllTypes = 0x5f;
830831
long eType = 0;
831832
int bOutput = 0;
832833
int prevWasTime = 0;
833834
int nHack = 0;
834835
int nPanic = 0;
835836
int nOther = 0;
836837
int nHang = 0;
837838
int nXPost = 0;
839
+ int nAuth = 0;
838840
char z[10000];
839841
char zTime[10000];
840842
841843
login_check_credentials();
842844
if( !g.perm.Admin ){
@@ -906,10 +908,13 @@
906908
@ <li>Hung backoffice processes
907909
}
908910
if( eType & 0x08 ){
909911
@ <li>POST requests from different origin
910912
}
913
+ if( eType & 0x10 ){
914
+ @ <li>SQLITE_AUTH and similar errors
915
+ }
911916
if( eType & 0x40 ){
912917
@ <li>Other uncategorized messages
913918
}
914919
@ </ul>
915920
}
@@ -933,10 +938,16 @@
933938
}else
934939
if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
935940
bOutput = (eType & 0x08)!=0;
936941
nXPost++;
937942
}else
943
+ if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
944
+ || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
945
+ ){
946
+ bOutput = (eType & 0x10)!=0;
947
+ nAuth++;
948
+ }else
938949
{
939950
bOutput = (eType & 0x40)!=0;
940951
nOther++;
941952
}
942953
if( bOutput ){
@@ -958,11 +969,11 @@
958969
fclose(in);
959970
if( eType ){
960971
@ </pre>
961972
}
962973
if( eType==0 ){
963
- int nNonHack = nPanic + nHang + nOther;
974
+ int nNonHack = nPanic + nHang + nAuth + nOther;
964975
int nTotal = nNonHack + nHack + nXPost;
965976
@ <p><table border="a" cellspacing="0" cellpadding="5">
966977
if( nPanic>0 ){
967978
@ <tr><td align="right">%d(nPanic)</td>
968979
@ <td><a href="./errorlog?y=2">Panics</a></td>
@@ -971,23 +982,23 @@
971982
@ <tr><td align="right">%d(nHack)</td>
972983
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
973984
}
974985
if( nHang>0 ){
975986
@ <tr><td align="right">%d(nHang)</td>
976
- @ <td><a href="./errorlog?y=4/">Hung Backoffice</a></td>
987
+ @ <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
977988
}
978989
if( nXPost>0 ){
979990
@ <tr><td align="right">%d(nXPost)</td>
980
- @ <td><a href="./errorlog?y=8/">POSTs from different origin</a></td>
991
+ @ <td><a href="./errorlog?y=8">POSTs from different origin</a></td>
992
+ }
993
+ if( nAuth>0 ){
994
+ @ <tr><td align="right">%d(nAuth)</td>
995
+ @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
981996
}
982997
if( nOther>0 ){
983998
@ <tr><td align="right">%d(nOther)</td>
984
- @ <td><a href="./errorlog?y=64/">Other</a></td>
985
- }
986
- if( nHack+nXPost>0 && nNonHack>0 ){
987
- @ <tr><td align="right">%d(nNonHack)</td>
988
- @ <td><a href="%R/errorlog?y=70">Other than hack attempts</a></td>
999
+ @ <td><a href="./errorlog?y=64">Other</a></td>
9891000
}
9901001
@ <tr><td align="right">%d(nTotal)</td>
9911002
if( nTotal>0 ){
9921003
@ <td><a href="./errorlog?y=255">All Messages</a></td>
9931004
}else{
9941005
--- src/security_audit.c
+++ src/security_audit.c
@@ -814,10 +814,11 @@
814 **
815 ** y=0x01 Show only hack attempts
816 ** y=0x02 Show only panics and assertion faults
817 ** y=0x04 Show hung backoffice processes
818 ** y=0x08 Show POST requests from a different origin
 
819 ** y=0x40 Show other uncategorized messages
820 **
821 ** If y is omitted or is zero, a count of the various message types is
822 ** shown.
823 */
@@ -824,19 +825,20 @@
824 void errorlog_page(void){
825 i64 szFile;
826 FILE *in;
827 char *zLog;
828 const char *zType = P("y");
829 static const int eAllTypes = 0x4f;
830 long eType = 0;
831 int bOutput = 0;
832 int prevWasTime = 0;
833 int nHack = 0;
834 int nPanic = 0;
835 int nOther = 0;
836 int nHang = 0;
837 int nXPost = 0;
 
838 char z[10000];
839 char zTime[10000];
840
841 login_check_credentials();
842 if( !g.perm.Admin ){
@@ -906,10 +908,13 @@
906 @ <li>Hung backoffice processes
907 }
908 if( eType & 0x08 ){
909 @ <li>POST requests from different origin
910 }
 
 
 
911 if( eType & 0x40 ){
912 @ <li>Other uncategorized messages
913 }
914 @ </ul>
915 }
@@ -933,10 +938,16 @@
933 }else
934 if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
935 bOutput = (eType & 0x08)!=0;
936 nXPost++;
937 }else
 
 
 
 
 
 
938 {
939 bOutput = (eType & 0x40)!=0;
940 nOther++;
941 }
942 if( bOutput ){
@@ -958,11 +969,11 @@
958 fclose(in);
959 if( eType ){
960 @ </pre>
961 }
962 if( eType==0 ){
963 int nNonHack = nPanic + nHang + nOther;
964 int nTotal = nNonHack + nHack + nXPost;
965 @ <p><table border="a" cellspacing="0" cellpadding="5">
966 if( nPanic>0 ){
967 @ <tr><td align="right">%d(nPanic)</td>
968 @ <td><a href="./errorlog?y=2">Panics</a></td>
@@ -971,23 +982,23 @@
971 @ <tr><td align="right">%d(nHack)</td>
972 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
973 }
974 if( nHang>0 ){
975 @ <tr><td align="right">%d(nHang)</td>
976 @ <td><a href="./errorlog?y=4/">Hung Backoffice</a></td>
977 }
978 if( nXPost>0 ){
979 @ <tr><td align="right">%d(nXPost)</td>
980 @ <td><a href="./errorlog?y=8/">POSTs from different origin</a></td>
 
 
 
 
981 }
982 if( nOther>0 ){
983 @ <tr><td align="right">%d(nOther)</td>
984 @ <td><a href="./errorlog?y=64/">Other</a></td>
985 }
986 if( nHack+nXPost>0 && nNonHack>0 ){
987 @ <tr><td align="right">%d(nNonHack)</td>
988 @ <td><a href="%R/errorlog?y=70">Other than hack attempts</a></td>
989 }
990 @ <tr><td align="right">%d(nTotal)</td>
991 if( nTotal>0 ){
992 @ <td><a href="./errorlog?y=255">All Messages</a></td>
993 }else{
994
--- src/security_audit.c
+++ src/security_audit.c
@@ -814,10 +814,11 @@
814 **
815 ** y=0x01 Show only hack attempts
816 ** y=0x02 Show only panics and assertion faults
817 ** y=0x04 Show hung backoffice processes
818 ** y=0x08 Show POST requests from a different origin
819 ** y=0x10 Show SQLITE_AUTH and similar
820 ** y=0x40 Show other uncategorized messages
821 **
822 ** If y is omitted or is zero, a count of the various message types is
823 ** shown.
824 */
@@ -824,19 +825,20 @@
825 void errorlog_page(void){
826 i64 szFile;
827 FILE *in;
828 char *zLog;
829 const char *zType = P("y");
830 static const int eAllTypes = 0x5f;
831 long eType = 0;
832 int bOutput = 0;
833 int prevWasTime = 0;
834 int nHack = 0;
835 int nPanic = 0;
836 int nOther = 0;
837 int nHang = 0;
838 int nXPost = 0;
839 int nAuth = 0;
840 char z[10000];
841 char zTime[10000];
842
843 login_check_credentials();
844 if( !g.perm.Admin ){
@@ -906,10 +908,13 @@
908 @ <li>Hung backoffice processes
909 }
910 if( eType & 0x08 ){
911 @ <li>POST requests from different origin
912 }
913 if( eType & 0x10 ){
914 @ <li>SQLITE_AUTH and similar errors
915 }
916 if( eType & 0x40 ){
917 @ <li>Other uncategorized messages
918 }
919 @ </ul>
920 }
@@ -933,10 +938,16 @@
938 }else
939 if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
940 bOutput = (eType & 0x08)!=0;
941 nXPost++;
942 }else
943 if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
944 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
945 ){
946 bOutput = (eType & 0x10)!=0;
947 nAuth++;
948 }else
949 {
950 bOutput = (eType & 0x40)!=0;
951 nOther++;
952 }
953 if( bOutput ){
@@ -958,11 +969,11 @@
969 fclose(in);
970 if( eType ){
971 @ </pre>
972 }
973 if( eType==0 ){
974 int nNonHack = nPanic + nHang + nAuth + nOther;
975 int nTotal = nNonHack + nHack + nXPost;
976 @ <p><table border="a" cellspacing="0" cellpadding="5">
977 if( nPanic>0 ){
978 @ <tr><td align="right">%d(nPanic)</td>
979 @ <td><a href="./errorlog?y=2">Panics</a></td>
@@ -971,23 +982,23 @@
982 @ <tr><td align="right">%d(nHack)</td>
983 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
984 }
985 if( nHang>0 ){
986 @ <tr><td align="right">%d(nHang)</td>
987 @ <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
988 }
989 if( nXPost>0 ){
990 @ <tr><td align="right">%d(nXPost)</td>
991 @ <td><a href="./errorlog?y=8">POSTs from different origin</a></td>
992 }
993 if( nAuth>0 ){
994 @ <tr><td align="right">%d(nAuth)</td>
995 @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
996 }
997 if( nOther>0 ){
998 @ <tr><td align="right">%d(nOther)</td>
999 @ <td><a href="./errorlog?y=64">Other</a></td>
 
 
 
 
1000 }
1001 @ <tr><td align="right">%d(nTotal)</td>
1002 if( nTotal>0 ){
1003 @ <td><a href="./errorlog?y=255">All Messages</a></td>
1004 }else{
1005
--- src/setup.c
+++ src/setup.c
@@ -981,17 +981,10 @@
981981
db_begin_transaction();
982982
@ <form action="%R/setup_timeline" method="post"><div>
983983
login_insert_csrf_secret();
984984
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
985985
986
- @ <hr>
987
- onoff_attribute("Allow block-markup in timeline",
988
- "timeline-block-markup", "tbm", 0, 0);
989
- @ <p>In timeline displays, check-in comments can be displayed with or
990
- @ without block markup such as paragraphs, tables, etc.
991
- @ (Property: "timeline-block-markup")</p>
992
-
993986
@ <hr>
994987
onoff_attribute("Plaintext comments on timelines",
995988
"timeline-plaintext", "tpt", 0, 0);
996989
@ <p>In timeline displays, check-in comments are displayed literally,
997990
@ without any wiki or HTML interpretation. Use CSS to change
998991
--- src/setup.c
+++ src/setup.c
@@ -981,17 +981,10 @@
981 db_begin_transaction();
982 @ <form action="%R/setup_timeline" method="post"><div>
983 login_insert_csrf_secret();
984 @ <p><input type="submit" name="submit" value="Apply Changes"></p>
985
986 @ <hr>
987 onoff_attribute("Allow block-markup in timeline",
988 "timeline-block-markup", "tbm", 0, 0);
989 @ <p>In timeline displays, check-in comments can be displayed with or
990 @ without block markup such as paragraphs, tables, etc.
991 @ (Property: "timeline-block-markup")</p>
992
993 @ <hr>
994 onoff_attribute("Plaintext comments on timelines",
995 "timeline-plaintext", "tpt", 0, 0);
996 @ <p>In timeline displays, check-in comments are displayed literally,
997 @ without any wiki or HTML interpretation. Use CSS to change
998
--- src/setup.c
+++ src/setup.c
@@ -981,17 +981,10 @@
981 db_begin_transaction();
982 @ <form action="%R/setup_timeline" method="post"><div>
983 login_insert_csrf_secret();
984 @ <p><input type="submit" name="submit" value="Apply Changes"></p>
985
 
 
 
 
 
 
 
986 @ <hr>
987 onoff_attribute("Plaintext comments on timelines",
988 "timeline-plaintext", "tpt", 0, 0);
989 @ <p>In timeline displays, check-in comments are displayed literally,
990 @ without any wiki or HTML interpretation. Use CSS to change
991
--- src/sitemap.c
+++ src/sitemap.c
@@ -293,10 +293,11 @@
293293
@ <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
294294
}
295295
@ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li>
296296
@ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li>
297297
@ <li>%z(href("%R/hash-color-test"))Hash color test</a>
298
+ @ <li>%z(href("%R/test-bgcolor"))Background color test</a>
298299
if( g.perm.Admin ){
299300
@ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li>
300301
@ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li>
301302
@ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li>
302303
@ <li>%z(href("%R/test-warning"))Error Log test page</a></li>
303304
--- src/sitemap.c
+++ src/sitemap.c
@@ -293,10 +293,11 @@
293 @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
294 }
295 @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li>
296 @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li>
297 @ <li>%z(href("%R/hash-color-test"))Hash color test</a>
 
298 if( g.perm.Admin ){
299 @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li>
300 @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li>
301 @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li>
302 @ <li>%z(href("%R/test-warning"))Error Log test page</a></li>
303
--- src/sitemap.c
+++ src/sitemap.c
@@ -293,10 +293,11 @@
293 @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
294 }
295 @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li>
296 @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li>
297 @ <li>%z(href("%R/hash-color-test"))Hash color test</a>
298 @ <li>%z(href("%R/test-bgcolor"))Background color test</a>
299 if( g.perm.Admin ){
300 @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li>
301 @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li>
302 @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li>
303 @ <li>%z(href("%R/test-warning"))Error Log test page</a></li>
304
+5 -3
--- src/statrep.c
+++ src/statrep.c
@@ -318,15 +318,16 @@
318318
&& nMaxEvents>0
319319
){
320320
/* If the timespan covered by this row contains "now", then project
321321
** the number of changes until the completion of the timespan and
322322
** show a dashed box of that projection. */
323
+ int nProj = (int)(((double)nCount)/rNowFraction);
323324
int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
324325
int nXSize = (100 * nExtra)/nMaxEvents;
325326
@ <span class='statistics-report-graph-line' \
326327
@ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
327
- @ <span class='statistics-report-graph-extra' \
328
+ @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
328329
@ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
329330
}else{
330331
@ <div class='statistics-report-graph-line' \
331332
@ style='width:%d(nSize)%%;'>&nbsp;</div> \
332333
}
@@ -747,18 +748,19 @@
747748
if( zCurrentWeek!=0
748749
&& strcmp(zWeek, zCurrentWeek)==0
749750
&& rNowFraction>0.05
750751
&& nMaxEvents>0
751752
){
752
- /* If the covered covered by this row contains "now", then project
753
+ /* If the timespan covered by this row contains "now", then project
753754
** the number of changes until the completion of the week and
754755
** show a dashed box of that projection. */
756
+ int nProj = (int)(((double)nCount)/rNowFraction);
755757
int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
756758
int nXSize = (100 * nExtra)/nMaxEvents;
757759
@ <span class='statistics-report-graph-line' \
758760
@ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
759
- @ <span class='statistics-report-graph-extra' \
761
+ @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
760762
@ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
761763
}else{
762764
@ <div class='statistics-report-graph-line' \
763765
@ style='width:%d(nSize)%%;'>&nbsp;</div> \
764766
}
765767
--- src/statrep.c
+++ src/statrep.c
@@ -318,15 +318,16 @@
318 && nMaxEvents>0
319 ){
320 /* If the timespan covered by this row contains "now", then project
321 ** the number of changes until the completion of the timespan and
322 ** show a dashed box of that projection. */
 
323 int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
324 int nXSize = (100 * nExtra)/nMaxEvents;
325 @ <span class='statistics-report-graph-line' \
326 @ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
327 @ <span class='statistics-report-graph-extra' \
328 @ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
329 }else{
330 @ <div class='statistics-report-graph-line' \
331 @ style='width:%d(nSize)%%;'>&nbsp;</div> \
332 }
@@ -747,18 +748,19 @@
747 if( zCurrentWeek!=0
748 && strcmp(zWeek, zCurrentWeek)==0
749 && rNowFraction>0.05
750 && nMaxEvents>0
751 ){
752 /* If the covered covered by this row contains "now", then project
753 ** the number of changes until the completion of the week and
754 ** show a dashed box of that projection. */
 
755 int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
756 int nXSize = (100 * nExtra)/nMaxEvents;
757 @ <span class='statistics-report-graph-line' \
758 @ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
759 @ <span class='statistics-report-graph-extra' \
760 @ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
761 }else{
762 @ <div class='statistics-report-graph-line' \
763 @ style='width:%d(nSize)%%;'>&nbsp;</div> \
764 }
765
--- src/statrep.c
+++ src/statrep.c
@@ -318,15 +318,16 @@
318 && nMaxEvents>0
319 ){
320 /* If the timespan covered by this row contains "now", then project
321 ** the number of changes until the completion of the timespan and
322 ** show a dashed box of that projection. */
323 int nProj = (int)(((double)nCount)/rNowFraction);
324 int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
325 int nXSize = (100 * nExtra)/nMaxEvents;
326 @ <span class='statistics-report-graph-line' \
327 @ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
328 @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
329 @ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
330 }else{
331 @ <div class='statistics-report-graph-line' \
332 @ style='width:%d(nSize)%%;'>&nbsp;</div> \
333 }
@@ -747,18 +748,19 @@
748 if( zCurrentWeek!=0
749 && strcmp(zWeek, zCurrentWeek)==0
750 && rNowFraction>0.05
751 && nMaxEvents>0
752 ){
753 /* If the timespan covered by this row contains "now", then project
754 ** the number of changes until the completion of the week and
755 ** show a dashed box of that projection. */
756 int nProj = (int)(((double)nCount)/rNowFraction);
757 int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
758 int nXSize = (100 * nExtra)/nMaxEvents;
759 @ <span class='statistics-report-graph-line' \
760 @ style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
761 @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
762 @ style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
763 }else{
764 @ <div class='statistics-report-graph-line' \
765 @ style='width:%d(nSize)%%;'>&nbsp;</div> \
766 }
767
--- src/terminal.c
+++ src/terminal.c
@@ -138,5 +138,57 @@
138138
void test_terminal_size_cmd(void){
139139
TerminalSize ts;
140140
terminal_get_size(&ts);
141141
fossil_print("%d %d\n", ts.nColumns, ts.nLines);
142142
}
143
+
144
+/*
145
+** Return true if it is reasonable is emit VT100 escape codes.
146
+*/
147
+int terminal_is_vt100(void){
148
+ char *zNoColor;
149
+#ifdef _WIN32
150
+ if( !win32_terminal_is_vt100(1) ) return 0;
151
+#endif /* _WIN32 */
152
+ if( !fossil_isatty(1) ) return 0;
153
+ zNoColor =fossil_getenv("NO_COLOR");
154
+ if( zNoColor==0 ) return 1;
155
+ if( zNoColor[0]==0 ) return 1;
156
+ if( is_false(zNoColor) ) return 1;
157
+ return 0;
158
+}
159
+
160
+#ifdef _WIN32
161
+/*
162
+** Return true if the Windows console supports VT100 escape codes.
163
+**
164
+** Support for VT100 escape codes is enabled by default in Windows Terminal
165
+** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles
166
+** and on older versions of Windows. Programs can turn on VT100 support for
167
+** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
168
+**
169
+** NOTE: If this function needs to be called in more complex scenarios with
170
+** reassigned stdout and stderr streams, the following CRT calls are useful
171
+** to translate from CRT streams to file descriptors and to Win32 handles:
172
+**
173
+** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>));
174
+*/
175
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
176
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
177
+#endif
178
+int win32_terminal_is_vt100(int fd){
179
+ HANDLE hConsole = NULL;
180
+ DWORD dwConsoleMode;
181
+ switch( fd ){
182
+ case 1:
183
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
184
+ break;
185
+ case 2:
186
+ hConsole = GetStdHandle(STD_ERROR_HANDLE);
187
+ break;
188
+ }
189
+ if( GetConsoleMode(hConsole,&dwConsoleMode) ){
190
+ return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0;
191
+ }
192
+ return 0;
193
+}
194
+#endif /* _WIN32 */
143195
--- src/terminal.c
+++ src/terminal.c
@@ -138,5 +138,57 @@
138 void test_terminal_size_cmd(void){
139 TerminalSize ts;
140 terminal_get_size(&ts);
141 fossil_print("%d %d\n", ts.nColumns, ts.nLines);
142 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
--- src/terminal.c
+++ src/terminal.c
@@ -138,5 +138,57 @@
138 void test_terminal_size_cmd(void){
139 TerminalSize ts;
140 terminal_get_size(&ts);
141 fossil_print("%d %d\n", ts.nColumns, ts.nLines);
142 }
143
144 /*
145 ** Return true if it is reasonable is emit VT100 escape codes.
146 */
147 int terminal_is_vt100(void){
148 char *zNoColor;
149 #ifdef _WIN32
150 if( !win32_terminal_is_vt100(1) ) return 0;
151 #endif /* _WIN32 */
152 if( !fossil_isatty(1) ) return 0;
153 zNoColor =fossil_getenv("NO_COLOR");
154 if( zNoColor==0 ) return 1;
155 if( zNoColor[0]==0 ) return 1;
156 if( is_false(zNoColor) ) return 1;
157 return 0;
158 }
159
160 #ifdef _WIN32
161 /*
162 ** Return true if the Windows console supports VT100 escape codes.
163 **
164 ** Support for VT100 escape codes is enabled by default in Windows Terminal
165 ** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles
166 ** and on older versions of Windows. Programs can turn on VT100 support for
167 ** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
168 **
169 ** NOTE: If this function needs to be called in more complex scenarios with
170 ** reassigned stdout and stderr streams, the following CRT calls are useful
171 ** to translate from CRT streams to file descriptors and to Win32 handles:
172 **
173 ** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>));
174 */
175 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
176 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
177 #endif
178 int win32_terminal_is_vt100(int fd){
179 HANDLE hConsole = NULL;
180 DWORD dwConsoleMode;
181 switch( fd ){
182 case 1:
183 hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
184 break;
185 case 2:
186 hConsole = GetStdHandle(STD_ERROR_HANDLE);
187 break;
188 }
189 if( GetConsoleMode(hConsole,&dwConsoleMode) ){
190 return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0;
191 }
192 return 0;
193 }
194 #endif /* _WIN32 */
195
+29 -72
--- src/timeline.c
+++ src/timeline.c
@@ -221,28 +221,28 @@
221221
vid = db_lget_int("checkout", 0);
222222
}
223223
zPrevDate[0] = 0;
224224
mxWikiLen = db_get_int("timeline-max-comment", 0);
225225
dateFormat = db_get_int("timeline-date-format", 0);
226
- /*
227
- ** SETTING: timeline-truncate-at-blank boolean default=off
228
- **
229
- ** If enabled, check-in comments displayed on the timeline are truncated
230
- ** at the first blank line of the comment text. The comment text after
231
- ** the first blank line is only seen in the /info or similar pages that
232
- ** show details about the check-in.
233
- */
226
+/*
227
+** SETTING: timeline-truncate-at-blank boolean default=off
228
+**
229
+** If enabled, check-in comments displayed on the timeline are truncated
230
+** at the first blank line of the comment text. The comment text after
231
+** the first blank line is only seen in the /info or similar pages that
232
+** show details about the check-in.
233
+*/
234234
bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
235
- /*
236
- ** SETTING: timeline-tslink-info boolean default=off
237
- **
238
- ** The hyperlink on the timestamp associated with each timeline entry,
239
- ** on the far left-hand side of the screen, normally targets another
240
- ** /timeline page that shows the entry in context. However, if this
241
- ** option is turned on, that hyperlink targets the /info page showing
242
- ** the details of the entry.
243
- */
235
+/*
236
+** SETTING: timeline-tslink-info boolean default=off
237
+**
238
+** The hyperlink on the timestamp associated with each timeline entry,
239
+** on the far left-hand side of the screen, normally targets another
240
+** /timeline page that shows the entry in context. However, if this
241
+** option is turned on, that hyperlink targets the /info page showing
242
+** the details of the entry.
243
+*/
244244
bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
245245
if( (tmFlags & TIMELINE_VIEWS)==0 ){
246246
tmFlags |= timeline_ss_cookie();
247247
}
248248
if( tmFlags & TIMELINE_COLUMNAR ){
@@ -402,10 +402,12 @@
402402
zDateLink = mprintf("<a>");
403403
}
404404
@ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
405405
@ <td class="timelineGraph">
406406
if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
407
+ /* Don't use the requested background color. Use the background color
408
+ ** override from query parameters instead. */
407409
if( tmFlags & TIMELINE_UCOLOR ){
408410
zBgClr = zUser ? user_color(zUser) : 0;
409411
}else if( tmFlags & TIMELINE_NOCOLOR ){
410412
zBgClr = 0;
411413
}else if( zType[0]=='c' ){
@@ -420,16 +422,21 @@
420422
}else{
421423
zBgClr = hash_color("f"); /* delta manifest */
422424
}
423425
db_reset(&qdelta);
424426
}
427
+ }else{
428
+ /* Make sure the user-specified background color is reasonable */
429
+ zBgClr = reasonable_bg_color(zBgClr, 0);
425430
}
426431
if( zType[0]=='c'
427432
&& (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
428433
){
429434
zBr = branch_of_rid(rid);
430435
if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
436
+ /* If no background color is specified, use a color based on the
437
+ ** branch name */
431438
if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
432439
}else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
433440
zBgClr = 0;
434441
}else{
435442
zBgClr = hash_color(zBr);
@@ -1106,62 +1113,10 @@
11061113
@ WHERE blob.rid=event.objid
11071114
;
11081115
return zBase;
11091116
}
11101117
1111
-/*
1112
-** Convert a symbolic name used as an argument to the a=, b=, or c=
1113
-** query parameters of timeline into a julianday mtime value.
1114
-**
1115
-** If pzDisplay is not null, then display text for the symbolic name might
1116
-** be written into *pzDisplay. But that is not guaranteed.
1117
-**
1118
-** If bRoundUp is true and the symbolic name is a timestamp with less
1119
-** than millisecond resolution, then the timestamp is rounding up to the
1120
-** largest millisecond consistent with that timestamp. If bRoundUp is
1121
-** false, then the resulting time is obtained by extending the timestamp
1122
-** with zeros (hence rounding down). Use bRoundUp==1 if the result
1123
-** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
1124
-** will be used in mtime>=$RESULT.
1125
-*/
1126
-double symbolic_name_to_mtime(
1127
- const char *z, /* Input symbolic name */
1128
- const char **pzDisplay, /* Perhaps write display text here, if not NULL */
1129
- int bRoundUp /* Round up if true */
1130
-){
1131
- double mtime;
1132
- int rid;
1133
- const char *zDate;
1134
- if( z==0 ) return -1.0;
1135
- if( fossil_isdate(z) ){
1136
- mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
1137
- if( mtime>0.0 ) return mtime;
1138
- }
1139
- zDate = fossil_expand_datetime(z, 1, bRoundUp);
1140
- if( zDate!=0 ){
1141
- mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
1142
- bRoundUp ? fossil_roundup_date(zDate) : zDate);
1143
- if( mtime>0.0 ){
1144
- if( pzDisplay ) *pzDisplay = fossil_strdup(zDate);
1145
- return mtime;
1146
- }
1147
- }
1148
- rid = symbolic_name_to_rid(z, "*");
1149
- if( rid ){
1150
- mtime = mtime_of_rid(rid, 0.0);
1151
- }else{
1152
- mtime = db_double(-1.0,
1153
- "SELECT max(event.mtime) FROM event, tag, tagxref"
1154
- " WHERE tag.tagname GLOB 'event-%q*'"
1155
- " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
1156
- " AND event.objid=tagxref.rid",
1157
- z
1158
- );
1159
- }
1160
- return mtime;
1161
-}
1162
-
11631118
/*
11641119
** zDate is a localtime date. Insert records into the
11651120
** "timeline" table to cause <hr> to be inserted on zDate.
11661121
*/
11671122
static int timeline_add_divider(double rDate){
@@ -3471,11 +3426,13 @@
34713426
}
34723427
34733428
/*
34743429
** wiki_to_text(TEXT)
34753430
**
3476
-** Return a plain-text rendering of Fossil-Wiki TEXT.
3431
+** Return a text rendering of Fossil-Wiki TEXT, intended for display
3432
+** on a timeline. The timeline-plaintext and timeline-hard-newlines
3433
+** settings are considered when doing this rendering.
34773434
*/
34783435
static void wiki_to_text_sqlfunc(
34793436
sqlite3_context *context,
34803437
int argc,
34813438
sqlite3_value **argv
@@ -3486,14 +3443,14 @@
34863443
zIn = (const char*)sqlite3_value_text(argv[0]);
34873444
if( zIn==0 ) return;
34883445
nIn = sqlite3_value_bytes(argv[0]);
34893446
blob_init(&in, zIn, nIn);
34903447
blob_init(&html, 0, 0);
3491
- wiki_convert(&in, &html, WIKI_INLINE);
3448
+ wiki_convert(&in, &html, wiki_convert_flags(0));
34923449
blob_reset(&in);
34933450
blob_init(&txt, 0, 0);
3494
- html_to_plaintext(blob_str(&html), &txt);
3451
+ html_to_plaintext(blob_str(&html), &txt, 0);
34953452
blob_reset(&html);
34963453
nOut = blob_size(&txt);
34973454
zOut = blob_str(&txt);
34983455
while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
34993456
while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
35003457
--- src/timeline.c
+++ src/timeline.c
@@ -221,28 +221,28 @@
221 vid = db_lget_int("checkout", 0);
222 }
223 zPrevDate[0] = 0;
224 mxWikiLen = db_get_int("timeline-max-comment", 0);
225 dateFormat = db_get_int("timeline-date-format", 0);
226 /*
227 ** SETTING: timeline-truncate-at-blank boolean default=off
228 **
229 ** If enabled, check-in comments displayed on the timeline are truncated
230 ** at the first blank line of the comment text. The comment text after
231 ** the first blank line is only seen in the /info or similar pages that
232 ** show details about the check-in.
233 */
234 bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
235 /*
236 ** SETTING: timeline-tslink-info boolean default=off
237 **
238 ** The hyperlink on the timestamp associated with each timeline entry,
239 ** on the far left-hand side of the screen, normally targets another
240 ** /timeline page that shows the entry in context. However, if this
241 ** option is turned on, that hyperlink targets the /info page showing
242 ** the details of the entry.
243 */
244 bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
245 if( (tmFlags & TIMELINE_VIEWS)==0 ){
246 tmFlags |= timeline_ss_cookie();
247 }
248 if( tmFlags & TIMELINE_COLUMNAR ){
@@ -402,10 +402,12 @@
402 zDateLink = mprintf("<a>");
403 }
404 @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
405 @ <td class="timelineGraph">
406 if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
 
 
407 if( tmFlags & TIMELINE_UCOLOR ){
408 zBgClr = zUser ? user_color(zUser) : 0;
409 }else if( tmFlags & TIMELINE_NOCOLOR ){
410 zBgClr = 0;
411 }else if( zType[0]=='c' ){
@@ -420,16 +422,21 @@
420 }else{
421 zBgClr = hash_color("f"); /* delta manifest */
422 }
423 db_reset(&qdelta);
424 }
 
 
 
425 }
426 if( zType[0]=='c'
427 && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
428 ){
429 zBr = branch_of_rid(rid);
430 if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
 
 
431 if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
432 }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
433 zBgClr = 0;
434 }else{
435 zBgClr = hash_color(zBr);
@@ -1106,62 +1113,10 @@
1106 @ WHERE blob.rid=event.objid
1107 ;
1108 return zBase;
1109 }
1110
1111 /*
1112 ** Convert a symbolic name used as an argument to the a=, b=, or c=
1113 ** query parameters of timeline into a julianday mtime value.
1114 **
1115 ** If pzDisplay is not null, then display text for the symbolic name might
1116 ** be written into *pzDisplay. But that is not guaranteed.
1117 **
1118 ** If bRoundUp is true and the symbolic name is a timestamp with less
1119 ** than millisecond resolution, then the timestamp is rounding up to the
1120 ** largest millisecond consistent with that timestamp. If bRoundUp is
1121 ** false, then the resulting time is obtained by extending the timestamp
1122 ** with zeros (hence rounding down). Use bRoundUp==1 if the result
1123 ** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
1124 ** will be used in mtime>=$RESULT.
1125 */
1126 double symbolic_name_to_mtime(
1127 const char *z, /* Input symbolic name */
1128 const char **pzDisplay, /* Perhaps write display text here, if not NULL */
1129 int bRoundUp /* Round up if true */
1130 ){
1131 double mtime;
1132 int rid;
1133 const char *zDate;
1134 if( z==0 ) return -1.0;
1135 if( fossil_isdate(z) ){
1136 mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
1137 if( mtime>0.0 ) return mtime;
1138 }
1139 zDate = fossil_expand_datetime(z, 1, bRoundUp);
1140 if( zDate!=0 ){
1141 mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
1142 bRoundUp ? fossil_roundup_date(zDate) : zDate);
1143 if( mtime>0.0 ){
1144 if( pzDisplay ) *pzDisplay = fossil_strdup(zDate);
1145 return mtime;
1146 }
1147 }
1148 rid = symbolic_name_to_rid(z, "*");
1149 if( rid ){
1150 mtime = mtime_of_rid(rid, 0.0);
1151 }else{
1152 mtime = db_double(-1.0,
1153 "SELECT max(event.mtime) FROM event, tag, tagxref"
1154 " WHERE tag.tagname GLOB 'event-%q*'"
1155 " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
1156 " AND event.objid=tagxref.rid",
1157 z
1158 );
1159 }
1160 return mtime;
1161 }
1162
1163 /*
1164 ** zDate is a localtime date. Insert records into the
1165 ** "timeline" table to cause <hr> to be inserted on zDate.
1166 */
1167 static int timeline_add_divider(double rDate){
@@ -3471,11 +3426,13 @@
3471 }
3472
3473 /*
3474 ** wiki_to_text(TEXT)
3475 **
3476 ** Return a plain-text rendering of Fossil-Wiki TEXT.
 
 
3477 */
3478 static void wiki_to_text_sqlfunc(
3479 sqlite3_context *context,
3480 int argc,
3481 sqlite3_value **argv
@@ -3486,14 +3443,14 @@
3486 zIn = (const char*)sqlite3_value_text(argv[0]);
3487 if( zIn==0 ) return;
3488 nIn = sqlite3_value_bytes(argv[0]);
3489 blob_init(&in, zIn, nIn);
3490 blob_init(&html, 0, 0);
3491 wiki_convert(&in, &html, WIKI_INLINE);
3492 blob_reset(&in);
3493 blob_init(&txt, 0, 0);
3494 html_to_plaintext(blob_str(&html), &txt);
3495 blob_reset(&html);
3496 nOut = blob_size(&txt);
3497 zOut = blob_str(&txt);
3498 while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
3499 while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
3500
--- src/timeline.c
+++ src/timeline.c
@@ -221,28 +221,28 @@
221 vid = db_lget_int("checkout", 0);
222 }
223 zPrevDate[0] = 0;
224 mxWikiLen = db_get_int("timeline-max-comment", 0);
225 dateFormat = db_get_int("timeline-date-format", 0);
226 /*
227 ** SETTING: timeline-truncate-at-blank boolean default=off
228 **
229 ** If enabled, check-in comments displayed on the timeline are truncated
230 ** at the first blank line of the comment text. The comment text after
231 ** the first blank line is only seen in the /info or similar pages that
232 ** show details about the check-in.
233 */
234 bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
235 /*
236 ** SETTING: timeline-tslink-info boolean default=off
237 **
238 ** The hyperlink on the timestamp associated with each timeline entry,
239 ** on the far left-hand side of the screen, normally targets another
240 ** /timeline page that shows the entry in context. However, if this
241 ** option is turned on, that hyperlink targets the /info page showing
242 ** the details of the entry.
243 */
244 bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
245 if( (tmFlags & TIMELINE_VIEWS)==0 ){
246 tmFlags |= timeline_ss_cookie();
247 }
248 if( tmFlags & TIMELINE_COLUMNAR ){
@@ -402,10 +402,12 @@
402 zDateLink = mprintf("<a>");
403 }
404 @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
405 @ <td class="timelineGraph">
406 if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
407 /* Don't use the requested background color. Use the background color
408 ** override from query parameters instead. */
409 if( tmFlags & TIMELINE_UCOLOR ){
410 zBgClr = zUser ? user_color(zUser) : 0;
411 }else if( tmFlags & TIMELINE_NOCOLOR ){
412 zBgClr = 0;
413 }else if( zType[0]=='c' ){
@@ -420,16 +422,21 @@
422 }else{
423 zBgClr = hash_color("f"); /* delta manifest */
424 }
425 db_reset(&qdelta);
426 }
427 }else{
428 /* Make sure the user-specified background color is reasonable */
429 zBgClr = reasonable_bg_color(zBgClr, 0);
430 }
431 if( zType[0]=='c'
432 && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
433 ){
434 zBr = branch_of_rid(rid);
435 if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
436 /* If no background color is specified, use a color based on the
437 ** branch name */
438 if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
439 }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
440 zBgClr = 0;
441 }else{
442 zBgClr = hash_color(zBr);
@@ -1106,62 +1113,10 @@
1113 @ WHERE blob.rid=event.objid
1114 ;
1115 return zBase;
1116 }
1117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118 /*
1119 ** zDate is a localtime date. Insert records into the
1120 ** "timeline" table to cause <hr> to be inserted on zDate.
1121 */
1122 static int timeline_add_divider(double rDate){
@@ -3471,11 +3426,13 @@
3426 }
3427
3428 /*
3429 ** wiki_to_text(TEXT)
3430 **
3431 ** Return a text rendering of Fossil-Wiki TEXT, intended for display
3432 ** on a timeline. The timeline-plaintext and timeline-hard-newlines
3433 ** settings are considered when doing this rendering.
3434 */
3435 static void wiki_to_text_sqlfunc(
3436 sqlite3_context *context,
3437 int argc,
3438 sqlite3_value **argv
@@ -3486,14 +3443,14 @@
3443 zIn = (const char*)sqlite3_value_text(argv[0]);
3444 if( zIn==0 ) return;
3445 nIn = sqlite3_value_bytes(argv[0]);
3446 blob_init(&in, zIn, nIn);
3447 blob_init(&html, 0, 0);
3448 wiki_convert(&in, &html, wiki_convert_flags(0));
3449 blob_reset(&in);
3450 blob_init(&txt, 0, 0);
3451 html_to_plaintext(blob_str(&html), &txt, 0);
3452 blob_reset(&html);
3453 nOut = blob_size(&txt);
3454 zOut = blob_str(&txt);
3455 while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
3456 while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
3457
+322 -185
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -23,23 +23,43 @@
2323
2424
#if INTERFACE
2525
/*
2626
** Allowed wiki transformation operations
2727
*/
28
-#define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */
29
-#define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */
30
-#define WIKI_NOBLOCK 0x004 /* No block markup of any kind */
31
-#define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */
32
-#define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */
33
-#define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */
34
-#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
35
-#define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
36
-#define WIKI_SAFE 0x100 /* Make the result safe for embedding */
37
-#define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */
38
-#define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */
39
-#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
40
-#endif
28
+#define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */
29
+#define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */
30
+/* avalable for reuse: 0x0004 --- formerly WIKI_NOBLOCK */
31
+#define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */
32
+#define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */
33
+#define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */
34
+#define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */
35
+#define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */
36
+#define WIKI_SAFE 0x0100 /* Make the result safe for embedding */
37
+#define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */
38
+#define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */
39
+#define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */
40
+#define WIKI_MARK 0x1000 /* Add <mark>..</mark> around problems */
41
+
42
+/*
43
+** Return values from wiki_convert
44
+*/
45
+#define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */
46
+#define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: &lt;) */
47
+#define RENDER_TAG 0x0004 /* One or more HTML tags */
48
+#define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */
49
+#define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */
50
+#define RENDER_MARK 0x0020 /* Output contains <mark>..</mark> */
51
+#define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */
52
+#define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */
53
+#define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */
54
+#define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */
55
+#define RENDER_BADHTML 0x1000 /* Bad HTML seen */
56
+#define RENDER_ERROR 0x8000 /* Some other kind of error */
57
+/* Composite values: */
58
+#define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */
59
+
60
+#endif /* INTERFACE */
4161
4262
4363
/*
4464
** These are the only markup attributes allowed.
4565
*/
@@ -445,20 +465,20 @@
445465
#define AT_NEWLINE 0x0010000 /* At start of a line */
446466
#define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */
447467
#define ALLOW_WIKI 0x0040000 /* Allow wiki markup */
448468
#define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */
449469
#define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */
450
-#define INLINE_MARKUP_ONLY 0x0200000 /* Allow only "inline" markup */
451
-#define IN_LIST 0x0400000 /* Within wiki <ul> or <ol> */
470
+#define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */
452471
453472
/*
454473
** Current state of the rendering engine
455474
*/
456475
typedef struct Renderer Renderer;
457476
struct Renderer {
458477
Blob *pOut; /* Output appended to this blob */
459478
int state; /* Flag that govern rendering */
479
+ int mRender; /* Mask of RENDER_* values to return */
460480
unsigned renderFlags; /* Flags from the client */
461481
int wikiList; /* Current wiki list type */
462482
int inVerbatim; /* True in <verbatim> mode */
463483
int preVerbState; /* Value of state prior to verbatim */
464484
int wantAutoParagraph; /* True if a <p> is desired */
@@ -680,21 +700,26 @@
680700
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
681701
int n;
682702
if( z[0]=='<' ){
683703
n = html_tag_length(z);
684704
if( n>0 ){
705
+ p->mRender |= RENDER_TAG;
685706
*pTokenType = TOKEN_MARKUP;
686707
return n;
687708
}else{
709
+ p->mRender |= RENDER_BADTAG;
710
+ *pTokenType = TOKEN_CHARACTER;
711
+ return 1;
712
+ }
713
+ }
714
+ if( z[0]=='&' ){
715
+ p->mRender |= RENDER_ENTITY;
716
+ if( (p->inVerbatim || !isElement(z)) ){
688717
*pTokenType = TOKEN_CHARACTER;
689718
return 1;
690719
}
691720
}
692
- if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
693
- *pTokenType = TOKEN_CHARACTER;
694
- return 1;
695
- }
696721
if( (p->state & ALLOW_WIKI)!=0 ){
697722
if( z[0]=='\n' ){
698723
n = paragraphBreakLength(z);
699724
if( n>0 ){
700725
*pTokenType = TOKEN_PARAGRAPH;
@@ -726,17 +751,31 @@
726751
if( n>0 ){
727752
*pTokenType = TOKEN_INDENT;
728753
return n;
729754
}
730755
}
731
- if( z[0]=='[' && (n = linkLength(z))>0 ){
756
+ if( z[0]=='[' ){
757
+ if( (n = linkLength(z))>0 ){
758
+ *pTokenType = TOKEN_LINK;
759
+ return n;
760
+ }else if( p->state & WIKI_MARK ){
761
+ blob_append_string(p->pOut, "<mark>");
762
+ p->mRender |= RENDER_BADLINK|RENDER_MARK;
763
+ }else{
764
+ p->mRender |= RENDER_BADLINK;
765
+ }
766
+ }
767
+ }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){
768
+ if( (n = linkLength(z))>0 ){
732769
*pTokenType = TOKEN_LINK;
733770
return n;
771
+ }else if( p->state & WIKI_MARK ){
772
+ blob_append_string(p->pOut, "<mark>");
773
+ p->mRender |= RENDER_BADLINK|RENDER_MARK;
774
+ }else{
775
+ p->mRender |= RENDER_BADLINK;
734776
}
735
- }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){
736
- *pTokenType = TOKEN_LINK;
737
- return n;
738777
}
739778
*pTokenType = TOKEN_TEXT;
740779
return 1 + textLength(z+1, p->state);
741780
}
742781
@@ -746,13 +785,20 @@
746785
** z points to the start of a token. Return the number of
747786
** characters in that token. Write the token type into *pTokenType.
748787
*/
749788
static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
750789
int n;
751
- if( z[0]=='[' && (n = linkLength(z))>0 ){
752
- *pTokenType = TOKEN_LINK;
753
- return n;
790
+ if( z[0]=='[' ){
791
+ if( (n = linkLength(z))>0 ){
792
+ *pTokenType = TOKEN_LINK;
793
+ return n;
794
+ }else if( p->state & WIKI_MARK ){
795
+ blob_append_string(p->pOut, "<mark>");
796
+ p->mRender |= RENDER_BADLINK|RENDER_MARK;
797
+ }else{
798
+ p->mRender |= RENDER_BADLINK;
799
+ }
754800
}
755801
*pTokenType = TOKEN_RAW;
756802
return 1 + textLength(z+1, p->state);
757803
}
758804
@@ -1249,12 +1295,16 @@
12491295
** [wiki:WikiPageName]
12501296
**
12511297
** [2010-02-27 07:13]
12521298
**
12531299
** [InterMap:Link] -> Interwiki link
1300
+**
1301
+** The return value is a mask of RENDER_* values indicating what happened.
1302
+** Probably the return value is 0 on success and RENDER_BADTARGET or
1303
+** RENDER_BADLINK if there are problems.
12541304
*/
1255
-void wiki_resolve_hyperlink(
1305
+int wiki_resolve_hyperlink(
12561306
Blob *pOut, /* Write the HTML output here */
12571307
int mFlags, /* Rendering option flags */
12581308
const char *zTarget, /* Hyperlink target; text within [...] */
12591309
char *zClose, /* Write hyperlink closing text here */
12601310
int nClose, /* Bytes available in zClose[] */
@@ -1264,10 +1314,11 @@
12641314
const char *zTerm = "</a>";
12651315
const char *z;
12661316
char *zExtra = 0;
12671317
const char *zExtraNS = 0;
12681318
char *zRemote = 0;
1319
+ int rc = 0;
12691320
12701321
if( zTitle ){
12711322
zExtra = mprintf(" title='%h'", zTitle);
12721323
zExtraNS = zExtra+1;
12731324
}else if( mFlags & WIKI_TARGET_BLANK ){
@@ -1317,14 +1368,19 @@
13171368
}
13181369
}
13191370
}else if( !in_this_repo(zTarget) ){
13201371
if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
13211372
zTerm = "";
1373
+ }else if( (mFlags & WIKI_MARK)!=0 ){
1374
+ blob_appendf(pOut, "<mark>%s", zLB);
1375
+ zTerm = "]</mark>";
1376
+ rc |= RENDER_MARK;
13221377
}else{
13231378
blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
13241379
zTerm = "]</span>";
13251380
}
1381
+ rc |= RENDER_BADTARGET;
13261382
}else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
13271383
blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
13281384
zTerm = "]</a>";
13291385
}else{
13301386
zTerm = "";
@@ -1341,33 +1397,40 @@
13411397
}else{
13421398
blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
13431399
}
13441400
}else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
13451401
&& db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1346
- /* Dates or date-and-times in ISO8610 resolve to a link to the
1402
+ /* Dates or date-and-times in ISO8601 resolve to a link to the
13471403
** timeline for that date */
13481404
blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
13491405
}else if( mFlags & WIKI_MARKDOWNLINKS ){
13501406
/* If none of the above, and if rendering links for markdown, then
13511407
** create a link to the literal text of the target */
13521408
blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
1409
+ }else if( mFlags & WIKI_MARK ){
1410
+ blob_appendf(pOut, "<mark>[");
1411
+ zTerm = "]</mark>";
1412
+ rc |= RENDER_BADTARGET|RENDER_MARK;
13531413
}else if( zOrig && zTarget>=&zOrig[2]
13541414
&& zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
13551415
/* If the hyperlink markup is not preceded by whitespace, then it
13561416
** is probably a C-language subscript or similar, not really a
13571417
** hyperlink. Just ignore it. */
13581418
zTerm = "";
13591419
}else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
13601420
/* Also ignore the link if various flags are set */
13611421
zTerm = "";
1422
+ rc |= RENDER_BADTARGET;
13621423
}else{
13631424
blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
13641425
zTerm = "</span>";
1426
+ rc |= RENDER_BADTARGET;
13651427
}
13661428
if( zExtra ) fossil_free(zExtra);
13671429
assert( (int)strlen(zTerm)<nClose );
13681430
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1431
+ return rc;
13691432
}
13701433
13711434
/*
13721435
** Check zTarget to see if it looks like a valid hyperlink target.
13731436
** Return true if it does seem valid and false if not.
@@ -1465,11 +1528,10 @@
14651528
*/
14661529
static void wiki_render(Renderer *p, char *z){
14671530
int tokenType;
14681531
ParsedMarkup markup;
14691532
int n;
1470
- int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
14711533
int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
14721534
int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
14731535
char *zOrig = z;
14741536
14751537
/* Make sure the attribute constants and names still align
@@ -1483,22 +1545,17 @@
14831545
n = nextWikiToken(z, p, &tokenType);
14841546
}
14851547
p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
14861548
switch( tokenType ){
14871549
case TOKEN_PARAGRAPH: {
1488
- if( inlineOnly ){
1489
- /* blob_append_string(p->pOut, " &para; "); */
1490
- blob_append_string(p->pOut, " &nbsp;&nbsp; ");
1491
- }else{
1492
- if( p->wikiList ){
1493
- popStackToTag(p, p->wikiList);
1494
- p->wikiList = 0;
1495
- }
1496
- endAutoParagraph(p);
1497
- blob_append_string(p->pOut, "\n\n");
1498
- p->wantAutoParagraph = 1;
1499
- }
1550
+ if( p->wikiList ){
1551
+ popStackToTag(p, p->wikiList);
1552
+ p->wikiList = 0;
1553
+ }
1554
+ endAutoParagraph(p);
1555
+ blob_append_string(p->pOut, "\n\n");
1556
+ p->wantAutoParagraph = 1;
15001557
p->state |= AT_PARAGRAPH|AT_NEWLINE;
15011558
break;
15021559
}
15031560
case TOKEN_NEWLINE: {
15041561
if( p->renderFlags & WIKI_NEWLINE ){
@@ -1508,86 +1565,91 @@
15081565
}
15091566
p->state |= AT_NEWLINE;
15101567
break;
15111568
}
15121569
case TOKEN_BUL_LI: {
1513
- if( inlineOnly ){
1514
- blob_append_string(p->pOut, " &bull; ");
1515
- }else{
1516
- if( p->wikiList!=MARKUP_UL ){
1517
- if( p->wikiList ){
1518
- popStackToTag(p, p->wikiList);
1519
- }
1520
- endAutoParagraph(p);
1521
- pushStack(p, MARKUP_UL);
1522
- blob_append_string(p->pOut, "<ul>");
1523
- p->wikiList = MARKUP_UL;
1524
- }
1525
- popStackToTag(p, MARKUP_LI);
1526
- startAutoParagraph(p);
1527
- pushStack(p, MARKUP_LI);
1528
- blob_append_string(p->pOut, "<li>");
1529
- }
1570
+ p->mRender |= RENDER_BLOCK;
1571
+ if( p->wikiList!=MARKUP_UL ){
1572
+ if( p->wikiList ){
1573
+ popStackToTag(p, p->wikiList);
1574
+ }
1575
+ endAutoParagraph(p);
1576
+ pushStack(p, MARKUP_UL);
1577
+ blob_append_string(p->pOut, "<ul>");
1578
+ p->wikiList = MARKUP_UL;
1579
+ }
1580
+ popStackToTag(p, MARKUP_LI);
1581
+ startAutoParagraph(p);
1582
+ pushStack(p, MARKUP_LI);
1583
+ blob_append_string(p->pOut, "<li>");
15301584
break;
15311585
}
15321586
case TOKEN_NUM_LI: {
1533
- if( inlineOnly ){
1534
- blob_append_string(p->pOut, " # ");
1535
- }else{
1536
- if( p->wikiList!=MARKUP_OL ){
1537
- if( p->wikiList ){
1538
- popStackToTag(p, p->wikiList);
1539
- }
1540
- endAutoParagraph(p);
1541
- pushStack(p, MARKUP_OL);
1542
- blob_append_string(p->pOut, "<ol>");
1543
- p->wikiList = MARKUP_OL;
1544
- }
1545
- popStackToTag(p, MARKUP_LI);
1546
- startAutoParagraph(p);
1547
- pushStack(p, MARKUP_LI);
1548
- blob_append_string(p->pOut, "<li>");
1549
- }
1587
+ p->mRender |= RENDER_BLOCK;
1588
+ if( p->wikiList!=MARKUP_OL ){
1589
+ if( p->wikiList ){
1590
+ popStackToTag(p, p->wikiList);
1591
+ }
1592
+ endAutoParagraph(p);
1593
+ pushStack(p, MARKUP_OL);
1594
+ blob_append_string(p->pOut, "<ol>");
1595
+ p->wikiList = MARKUP_OL;
1596
+ }
1597
+ popStackToTag(p, MARKUP_LI);
1598
+ startAutoParagraph(p);
1599
+ pushStack(p, MARKUP_LI);
1600
+ blob_append_string(p->pOut, "<li>");
15501601
break;
15511602
}
15521603
case TOKEN_ENUM: {
1553
- if( inlineOnly ){
1554
- blob_appendf(p->pOut, " (%d) ", atoi(z));
1555
- }else{
1556
- if( p->wikiList!=MARKUP_OL ){
1557
- if( p->wikiList ){
1558
- popStackToTag(p, p->wikiList);
1559
- }
1560
- endAutoParagraph(p);
1561
- pushStack(p, MARKUP_OL);
1562
- blob_append_string(p->pOut, "<ol>");
1563
- p->wikiList = MARKUP_OL;
1564
- }
1565
- popStackToTag(p, MARKUP_LI);
1566
- startAutoParagraph(p);
1567
- pushStack(p, MARKUP_LI);
1568
- blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
1569
- }
1604
+ p->mRender |= RENDER_BLOCK;
1605
+ if( p->wikiList!=MARKUP_OL ){
1606
+ if( p->wikiList ){
1607
+ popStackToTag(p, p->wikiList);
1608
+ }
1609
+ endAutoParagraph(p);
1610
+ pushStack(p, MARKUP_OL);
1611
+ blob_append_string(p->pOut, "<ol>");
1612
+ p->wikiList = MARKUP_OL;
1613
+ }
1614
+ popStackToTag(p, MARKUP_LI);
1615
+ startAutoParagraph(p);
1616
+ pushStack(p, MARKUP_LI);
1617
+ blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
15701618
break;
15711619
}
15721620
case TOKEN_INDENT: {
1573
- if( !inlineOnly ){
1574
- assert( p->wikiList==0 );
1575
- pushStack(p, MARKUP_BLOCKQUOTE);
1576
- blob_append_string(p->pOut, "<blockquote>");
1577
- p->wantAutoParagraph = 0;
1578
- p->wikiList = MARKUP_BLOCKQUOTE;
1579
- }
1621
+ p->mRender |= RENDER_BLOCK;
1622
+ assert( p->wikiList==0 );
1623
+ pushStack(p, MARKUP_BLOCKQUOTE);
1624
+ blob_append_string(p->pOut, "<blockquote>");
1625
+ p->wantAutoParagraph = 0;
1626
+ p->wikiList = MARKUP_BLOCKQUOTE;
15801627
break;
15811628
}
15821629
case TOKEN_CHARACTER: {
15831630
startAutoParagraph(p);
1631
+ if( p->state & WIKI_MARK ){
1632
+ blob_append_string(p->pOut, "<mark>");
1633
+ p->mRender |= RENDER_MARK;
1634
+ }
15841635
if( z[0]=='<' ){
1636
+ p->mRender |= RENDER_BADTAG;
15851637
blob_append_string(p->pOut, "&lt;");
15861638
}else if( z[0]=='&' ){
1639
+ p->mRender |= RENDER_BADENTITY;
15871640
blob_append_string(p->pOut, "&amp;");
15881641
}
1642
+ if( p->state & WIKI_MARK ){
1643
+ if( fossil_isalnum(z[1]) || (z[1]=='/' && fossil_isalnum(z[2])) ){
1644
+ int kk;
1645
+ for(kk=2; fossil_isalnum(z[kk]); kk++){}
1646
+ blob_append(p->pOut, &z[1], kk-1);
1647
+ n = kk;
1648
+ }
1649
+ blob_append_string(p->pOut, "</mark>");
1650
+ }
15891651
break;
15901652
}
15911653
case TOKEN_LINK: {
15921654
char *zTarget;
15931655
char *zDisplay = 0;
@@ -1596,10 +1658,11 @@
15961658
char zClose[20];
15971659
char cS1 = 0;
15981660
int iS1 = 0;
15991661
16001662
startAutoParagraph(p);
1663
+ p->mRender |= RENDER_LINK;
16011664
zTarget = &z[1];
16021665
for(i=1; z[i] && z[i]!=']'; i++){
16031666
if( z[i]=='|' && zDisplay==0 ){
16041667
zDisplay = &z[i+1];
16051668
for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
@@ -1612,11 +1675,11 @@
16121675
if( zDisplay==0 ){
16131676
zDisplay = zTarget + interwiki_removable_prefix(zTarget);
16141677
}else{
16151678
while( fossil_isspace(*zDisplay) ) zDisplay++;
16161679
}
1617
- wiki_resolve_hyperlink(p->pOut, p->state,
1680
+ p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state,
16181681
zTarget, zClose, sizeof(zClose), zOrig, 0);
16191682
if( linksOnly || zClose[0]==0 || p->inVerbatim ){
16201683
if( cS1 ) z[iS1] = cS1;
16211684
if( zClose[0]!=']' ){
16221685
blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
@@ -1702,14 +1765,22 @@
17021765
17031766
/* Render invalid markup literally. The markup appears in the
17041767
** final output as plain text.
17051768
*/
17061769
if( markup.iCode==MARKUP_INVALID ){
1770
+ p->mRender |= RENDER_BADTAG;
17071771
unparseMarkup(&markup);
17081772
startAutoParagraph(p);
1709
- blob_append_string(p->pOut, "&lt;");
1710
- n = 1;
1773
+ if( p->state & WIKI_MARK ){
1774
+ p->mRender |= RENDER_MARK;
1775
+ blob_append_string(p->pOut, "<mark>");
1776
+ htmlize_to_blob(p->pOut, z, n);
1777
+ blob_append_string(p->pOut, "</mark>");
1778
+ }else{
1779
+ blob_append_string(p->pOut, "&lt;");
1780
+ htmlize_to_blob(p->pOut, z+1, n-1);
1781
+ }
17111782
}else
17121783
17131784
/* If the markup is not font-change markup ignore it if the
17141785
** font-change-only flag is set.
17151786
*/
@@ -1723,16 +1794,10 @@
17231794
}else{
17241795
p->state &= ~ALLOW_WIKI;
17251796
}
17261797
}else
17271798
1728
- /* Ignore block markup for in-line rendering.
1729
- */
1730
- if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
1731
- /* Do nothing */
1732
- }else
1733
-
17341799
/* Generate end-tags */
17351800
if( markup.endTag ){
17361801
popStackToTag(p, markup.iCode);
17371802
}else
17381803
@@ -1814,10 +1879,11 @@
18141879
}else
18151880
{
18161881
if( markup.iType==MUTYPE_FONT ){
18171882
startAutoParagraph(p);
18181883
}else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
1884
+ p->mRender |= RENDER_BLOCKTAG;
18191885
p->wantAutoParagraph = 0;
18201886
}
18211887
if( markup.iCode==MARKUP_HR
18221888
|| markup.iCode==MARKUP_H1
18231889
|| markup.iCode==MARKUP_H2
@@ -1844,12 +1910,14 @@
18441910
** Transform the text in the pIn blob. Write the results
18451911
** into the pOut blob. The pOut blob should already be
18461912
** initialized. The output is merely appended to pOut.
18471913
** If pOut is NULL, then the output is appended to the CGI
18481914
** reply.
1915
+**
1916
+** Return a mask of RENDER_ flags indicating what happened.
18491917
*/
1850
-void wiki_convert(Blob *pIn, Blob *pOut, int flags){
1918
+int wiki_convert(Blob *pIn, Blob *pOut, int flags){
18511919
Renderer renderer;
18521920
18531921
memset(&renderer, 0, sizeof(renderer));
18541922
renderer.renderFlags = flags;
18551923
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
@@ -1873,10 +1941,11 @@
18731941
while( renderer.nStack ){
18741942
popStack(&renderer);
18751943
}
18761944
blob_append_char(renderer.pOut, '\n');
18771945
free(renderer.aStack);
1946
+ return renderer.mRender;
18781947
}
18791948
18801949
/*
18811950
** COMMAND: test-wiki-render
18821951
**
@@ -1886,45 +1955,84 @@
18861955
** the resulting HTML on standard output.
18871956
**
18881957
** Options:
18891958
** --buttons Set the WIKI_BUTTONS flag
18901959
** --dark-pikchr Render pikchrs in dark mode
1960
+** --flow Render as text using comment_format
18911961
** --htmlonly Set the WIKI_HTMLONLY flag
18921962
** --inline Set the WIKI_INLINE flag
18931963
** --linksonly Set the WIKI_LINKSONLY flag
1964
+** -m TEXT Use TEXT in place of the content of FILE
1965
+** --mark Add <mark>...</mark> around problems
18941966
** --nobadlinks Set the WIKI_NOBADLINKS flag
1895
-** --noblock Set the WIKI_NOBLOCK flag
1896
-** --text Run the output through html_to_plaintext().
1967
+** --text Run the output through html_to_plaintext()
1968
+** --type Break down the return code from wiki_convert()
18971969
*/
18981970
void test_wiki_render(void){
18991971
Blob in, out;
19001972
int flags = 0;
19011973
int bText;
1974
+ int bFlow = 0;
1975
+ int showType = 0;
1976
+ int mType;
1977
+ const char *zIn;
19021978
if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
19031979
if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
19041980
if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
19051981
if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
19061982
if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
1907
- if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;
1983
+ if( find_option("mark",0,0)!=0 ) flags |= WIKI_MARK;
19081984
if( find_option("dark-pikchr",0,0)!=0 ){
19091985
pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
19101986
}
19111987
bText = find_option("text",0,0)!=0;
1988
+ bFlow = find_option("flow",0,0)!=0;
1989
+ showType = find_option("type",0,0)!=0;
1990
+ zIn = find_option("msg","m",1);
19121991
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
19131992
verify_all_options();
1914
- if( g.argc!=3 ) usage("FILE");
1993
+ if( (zIn==0 && g.argc!=3) || (zIn!=0 && g.argc!=2) ) usage("FILE");
19151994
blob_zero(&out);
1916
- blob_read_from_file(&in, g.argv[2], ExtFILE);
1917
- wiki_convert(&in, &out, flags);
1995
+ if( zIn ){
1996
+ blob_init(&in, zIn, -1);
1997
+ }else{
1998
+ blob_read_from_file(&in, g.argv[2], ExtFILE);
1999
+ }
2000
+ mType = wiki_convert(&in, &out, flags);
19182001
if( bText ){
19192002
Blob txt;
2003
+ int htot = HTOT_TRIM;
2004
+ if( terminal_is_vt100() ) htot |= HTOT_VT100;
2005
+ if( bFlow ) htot |= HTOT_FLOW;
19202006
blob_init(&txt, 0, 0);
1921
- html_to_plaintext(blob_str(&out),&txt);
2007
+ html_to_plaintext(blob_str(&out),&txt, htot);
19222008
blob_reset(&out);
19232009
out = txt;
19242010
}
1925
- blob_write_to_file(&out, "-");
2011
+ if( bFlow ){
2012
+ fossil_print(" ");
2013
+ comment_print(blob_str(&out), 0, 3, terminal_get_width(80)-3,
2014
+ get_comment_format());
2015
+ }else{
2016
+ blob_write_to_file(&out, "-");
2017
+ }
2018
+ if( showType ){
2019
+ fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*');
2020
+ if( mType & RENDER_LINK ) fossil_print(" LINK");
2021
+ if( mType & RENDER_ENTITY ) fossil_print(" ENTITY");
2022
+ if( mType & RENDER_TAG ) fossil_print(" TAG");
2023
+ if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG");
2024
+ if( mType & RENDER_BLOCK ) fossil_print(" BLOCK");
2025
+ if( mType & RENDER_MARK ) fossil_print(" MARK");
2026
+ if( mType & RENDER_BADLINK ) fossil_print(" BADLINK");
2027
+ if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET");
2028
+ if( mType & RENDER_BADTAG ) fossil_print(" BADTAG");
2029
+ if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY");
2030
+ if( mType & RENDER_BADHTML ) fossil_print(" BADHTML");
2031
+ if( mType & RENDER_ERROR ) fossil_print(" ERROR");
2032
+ fossil_print("\n");
2033
+ }
19262034
}
19272035
19282036
/*
19292037
** COMMAND: test-markdown-render
19302038
**
@@ -1960,11 +2068,11 @@
19602068
safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
19612069
safe_html(&out);
19622070
if( bText ){
19632071
Blob txt;
19642072
blob_init(&txt, 0, 0);
1965
- html_to_plaintext(blob_str(&out), &txt);
2073
+ html_to_plaintext(blob_str(&out), &txt, HTOT_VT100);
19662074
blob_reset(&out);
19672075
out = txt;
19682076
}
19692077
blob_write_to_file(&out, "-");
19702078
blob_reset(&in);
@@ -2024,33 +2132,30 @@
20242132
** [target|...]
20252133
**
20262134
** Where "target" can be either an artifact ID prefix or a wiki page
20272135
** name. For each such hyperlink found, add an entry to the
20282136
** backlink table.
2137
+**
2138
+** The return value is a mask of RENDER_ flags.
20292139
*/
2030
-void wiki_extract_links(
2140
+int wiki_extract_links(
20312141
char *z, /* The wiki text from which to extract links */
20322142
Backlink *pBklnk, /* Backlink extraction context */
20332143
int flags /* wiki parsing flags */
20342144
){
20352145
Renderer renderer;
20362146
int tokenType;
20372147
ParsedMarkup markup;
20382148
int n;
2039
- int inlineOnly;
20402149
int wikiHtmlOnly = 0;
20412150
20422151
memset(&renderer, 0, sizeof(renderer));
20432152
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
2044
- if( flags & WIKI_NOBLOCK ){
2045
- renderer.state |= INLINE_MARKUP_ONLY;
2046
- }
20472153
if( wikiUsesHtml() ){
20482154
renderer.state |= WIKI_HTMLONLY;
20492155
wikiHtmlOnly = 1;
20502156
}
2051
- inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0;
20522157
20532158
while( z[0] ){
20542159
if( wikiHtmlOnly ){
20552160
n = nextRawToken(z, &renderer, &tokenType);
20562161
}else{
@@ -2127,16 +2232,10 @@
21272232
}else{
21282233
renderer.state &= ~ALLOW_WIKI;
21292234
}
21302235
}else
21312236
2132
- /* Ignore block markup for in-line rendering.
2133
- */
2134
- if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
2135
- /* Do nothing */
2136
- }else
2137
-
21382237
/* Generate end-tags */
21392238
if( markup.endTag ){
21402239
popStackToTag(&renderer, markup.iCode);
21412240
}else
21422241
@@ -2175,10 +2274,11 @@
21752274
}
21762275
}
21772276
z += n;
21782277
}
21792278
free(renderer.aStack);
2279
+ return renderer.mRender;
21802280
}
21812281
21822282
/*
21832283
** Return the length, in bytes, of the HTML token that z is pointing to.
21842284
*/
@@ -2427,33 +2527,61 @@
24272527
blob_reset(&in);
24282528
fossil_puts(blob_buffer(&out), 0, blob_size(&out));
24292529
blob_reset(&out);
24302530
}
24312531
}
2532
+
2533
+#if INTERFACE
2534
+/*
2535
+** Allowed flag options for html_to_plaintext().
2536
+*/
2537
+#define HTOT_VT100 0x01 /* <mark> becomes ^[[91m */
2538
+#define HTOT_FLOW 0x02 /* Collapse internal whitespace to a single space */
2539
+#define HTOT_TRIM 0x04 /* Trim off leading and trailing whitespace */
2540
+
2541
+#endif /* INTERFACE */
2542
+
2543
+/*
2544
+** Add <MARK> or </MARK> to the output, or similar VT-100 escape
2545
+** codes.
2546
+*/
2547
+static void addMark(Blob *pOut, int mFlags, int isClose){
2548
+ static const char *az[4] = { "<MARK>", "</MARK>", "\033[91m", "\033[0m" };
2549
+ int i = 0;
2550
+ if( isClose ) i++;
2551
+ if( mFlags & HTOT_VT100 ) i += 2;
2552
+ blob_append(pOut, az[i], -1);
2553
+}
24322554
24332555
/*
24342556
** Remove all HTML markup from the input text. The output written into
24352557
** pOut is pure text.
24362558
**
24372559
** Put the title on the first line, if there is any <title> markup.
24382560
** If there is no <title>, then create a blank first line.
24392561
*/
2440
-void html_to_plaintext(const char *zIn, Blob *pOut){
2562
+void html_to_plaintext(const char *zIn, Blob *pOut, int mFlags){
24412563
int n;
24422564
int i, j;
2443
- int inTitle = 0; /* True between <title>...</title> */
2444
- int seenText = 0; /* True after first non-whitespace seen */
2445
- int nNL = 0; /* Number of \n characters at the end of pOut */
2446
- int nWS = 0; /* True if pOut ends with whitespace */
2447
- while( fossil_isspace(zIn[0]) ) zIn++;
2565
+ int bFlow = 0; /* Transform internal WS into a single space */
2566
+ int prevWS = 1; /* Previous output was whitespace or start of msg */
2567
+ int nMark = 0; /* True if inside of <mark>..</mark> */
2568
+
2569
+ for(i=0; fossil_isspace(zIn[i]); i++){}
2570
+ if( i>0 && (mFlags & HTOT_TRIM)==0 ){
2571
+ blob_append(pOut, zIn, i);
2572
+ }
2573
+ zIn += i;
2574
+ if( mFlags & HTOT_FLOW ) bFlow = 1;
24482575
while( zIn[0] ){
24492576
n = html_token_length(zIn);
24502577
if( zIn[0]=='<' && n>1 ){
24512578
int isCloseTag;
24522579
int eTag;
24532580
int eType;
24542581
char zTag[32];
2582
+ prevWS = 0;
24552583
isCloseTag = zIn[1]=='/';
24562584
for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
24572585
zTag[i] = fossil_tolower(zIn[j]);
24582586
}
24592587
zTag[i] = 0;
@@ -2467,33 +2595,44 @@
24672595
zIn += n;
24682596
}
24692597
if( zIn[0]=='<' ) zIn += n;
24702598
continue;
24712599
}
2472
- if( eTag==MARKUP_TITLE ){
2473
- inTitle = !isCloseTag;
2474
- }
2475
- if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2476
- if( nNL==0 ){
2477
- blob_append_char(pOut, '\n');
2478
- nNL++;
2479
- }
2480
- nWS = 1;
2600
+ if( eTag==MARKUP_INVALID && strcmp(zTag,"mark")==0 ){
2601
+ if( isCloseTag && nMark ){
2602
+ addMark(pOut, mFlags, 1);
2603
+ nMark = 0;
2604
+ }else if( !isCloseTag && !nMark ){
2605
+ addMark(pOut, mFlags, 0);
2606
+ nMark = 1;
2607
+ }
2608
+ zIn += n;
2609
+ continue;
2610
+ }
2611
+ if( eTag==MARKUP_TITLE ){
2612
+ if( isCloseTag && (mFlags & HTOT_FLOW)==0 ){
2613
+ bFlow = 0;
2614
+ }else{
2615
+ bFlow = 1;
2616
+ }
2617
+ }
2618
+ if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2619
+ blob_append_char(pOut, '\n');
24812620
}
24822621
}else if( fossil_isspace(zIn[0]) ){
2483
- if( seenText ){
2484
- nNL = 0;
2485
- if( !inTitle ){ /* '\n' -> ' ' within <title> */
2486
- for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2487
- }
2488
- if( !nWS ){
2489
- blob_append_char(pOut, nNL ? '\n' : ' ');
2490
- nWS = 1;
2491
- }
2622
+ if( bFlow==0 ){
2623
+ if( zIn[n]==0 && (mFlags & HTOT_TRIM) ) break;
2624
+ blob_append(pOut, zIn, n);
2625
+ }else if( !prevWS ){
2626
+ prevWS = 1;
2627
+ blob_append_char(pOut, ' ');
2628
+ zIn += n;
2629
+ n = 0;
24922630
}
24932631
}else if( zIn[0]=='&' ){
24942632
u32 c = '?';
2633
+ prevWS = 0;
24952634
if( zIn[1]=='#' ){
24962635
c = atoi(&zIn[2]);
24972636
if( c==0 ) c = '?';
24982637
}else{
24992638
static const struct { int n; u32 c; char *z; } aEntity[] = {
@@ -2509,65 +2648,63 @@
25092648
c = aEntity[jj].c;
25102649
break;
25112650
}
25122651
}
25132652
}
2514
- if( fossil_isspace(c) ){
2515
- if( nWS==0 && seenText ) blob_append_char(pOut, c);
2516
- nWS = 1;
2517
- nNL = c=='\n';
2518
- }else{
2519
- if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
2520
- seenText = 1;
2521
- nNL = nWS = 0;
2522
- if( c<0x00080 ){
2523
- blob_append_char(pOut, c & 0xff);
2524
- }else if( c<0x00800 ){
2525
- blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f));
2526
- blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2527
- }else if( c<0x10000 ){
2528
- blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f));
2529
- blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2530
- blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2531
- }else{
2532
- blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07));
2533
- blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f));
2534
- blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2535
- blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2536
- }
2537
- }
2538
- }else{
2539
- if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
2540
- seenText = 1;
2541
- nNL = nWS = 0;
2653
+ if( c<0x00080 ){
2654
+ blob_append_char(pOut, c & 0xff);
2655
+ }else if( c<0x00800 ){
2656
+ blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f));
2657
+ blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2658
+ }else if( c<0x10000 ){
2659
+ blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f));
2660
+ blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2661
+ blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2662
+ }else{
2663
+ blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07));
2664
+ blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f));
2665
+ blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2666
+ blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2667
+ }
2668
+ }else{
2669
+ prevWS = 0;
25422670
blob_append(pOut, zIn, n);
25432671
}
25442672
zIn += n;
25452673
}
2546
- if( nNL==0 ) blob_append_char(pOut, '\n');
2674
+ if( nMark ){
2675
+ addMark(pOut, mFlags, 1);
2676
+ }
25472677
}
25482678
25492679
/*
25502680
** COMMAND: test-html-to-text
25512681
**
2552
-** Usage: %fossil test-html-to-text FILE ...
2682
+** Usage: %fossil test-html-to-text [OPTIONS] FILE ...
25532683
**
25542684
** Read all files named on the command-line. Convert the file
25552685
** content from HTML to text and write the results on standard
25562686
** output.
25572687
**
25582688
** This command is intended as a test and debug interface for
25592689
** the html_to_plaintext() routine.
2690
+**
2691
+** Options:
2692
+**
2693
+** --vt100 Translate <mark> and </mark> into ANSI/VT100
2694
+** escapes to highlight the contained text.
25602695
*/
25612696
void test_html_to_text(void){
25622697
Blob in, out;
25632698
int i;
2699
+ int mFlags = 0;
2700
+ if( find_option("vt100",0,0)!=0 ) mFlags |= HTOT_VT100;
25642701
25652702
for(i=2; i<g.argc; i++){
25662703
blob_read_from_file(&in, g.argv[i], ExtFILE);
25672704
blob_zero(&out);
2568
- html_to_plaintext(blob_str(&in), &out);
2705
+ html_to_plaintext(blob_str(&in), &out, mFlags);
25692706
blob_reset(&in);
25702707
fossil_puts(blob_buffer(&out), 0, blob_size(&out));
25712708
blob_reset(&out);
25722709
}
25732710
}
25742711
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -23,23 +23,43 @@
23
24 #if INTERFACE
25 /*
26 ** Allowed wiki transformation operations
27 */
28 #define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */
29 #define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */
30 #define WIKI_NOBLOCK 0x004 /* No block markup of any kind */
31 #define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */
32 #define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */
33 #define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */
34 #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
35 #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
36 #define WIKI_SAFE 0x100 /* Make the result safe for embedding */
37 #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */
38 #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */
39 #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */
40 #endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
42
43 /*
44 ** These are the only markup attributes allowed.
45 */
@@ -445,20 +465,20 @@
445 #define AT_NEWLINE 0x0010000 /* At start of a line */
446 #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */
447 #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */
448 #define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */
449 #define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */
450 #define INLINE_MARKUP_ONLY 0x0200000 /* Allow only "inline" markup */
451 #define IN_LIST 0x0400000 /* Within wiki <ul> or <ol> */
452
453 /*
454 ** Current state of the rendering engine
455 */
456 typedef struct Renderer Renderer;
457 struct Renderer {
458 Blob *pOut; /* Output appended to this blob */
459 int state; /* Flag that govern rendering */
 
460 unsigned renderFlags; /* Flags from the client */
461 int wikiList; /* Current wiki list type */
462 int inVerbatim; /* True in <verbatim> mode */
463 int preVerbState; /* Value of state prior to verbatim */
464 int wantAutoParagraph; /* True if a <p> is desired */
@@ -680,21 +700,26 @@
680 static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
681 int n;
682 if( z[0]=='<' ){
683 n = html_tag_length(z);
684 if( n>0 ){
 
685 *pTokenType = TOKEN_MARKUP;
686 return n;
687 }else{
 
 
 
 
 
 
 
 
688 *pTokenType = TOKEN_CHARACTER;
689 return 1;
690 }
691 }
692 if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
693 *pTokenType = TOKEN_CHARACTER;
694 return 1;
695 }
696 if( (p->state & ALLOW_WIKI)!=0 ){
697 if( z[0]=='\n' ){
698 n = paragraphBreakLength(z);
699 if( n>0 ){
700 *pTokenType = TOKEN_PARAGRAPH;
@@ -726,17 +751,31 @@
726 if( n>0 ){
727 *pTokenType = TOKEN_INDENT;
728 return n;
729 }
730 }
731 if( z[0]=='[' && (n = linkLength(z))>0 ){
 
 
 
 
 
 
 
 
 
 
 
 
732 *pTokenType = TOKEN_LINK;
733 return n;
 
 
 
 
 
734 }
735 }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){
736 *pTokenType = TOKEN_LINK;
737 return n;
738 }
739 *pTokenType = TOKEN_TEXT;
740 return 1 + textLength(z+1, p->state);
741 }
742
@@ -746,13 +785,20 @@
746 ** z points to the start of a token. Return the number of
747 ** characters in that token. Write the token type into *pTokenType.
748 */
749 static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
750 int n;
751 if( z[0]=='[' && (n = linkLength(z))>0 ){
752 *pTokenType = TOKEN_LINK;
753 return n;
 
 
 
 
 
 
 
754 }
755 *pTokenType = TOKEN_RAW;
756 return 1 + textLength(z+1, p->state);
757 }
758
@@ -1249,12 +1295,16 @@
1249 ** [wiki:WikiPageName]
1250 **
1251 ** [2010-02-27 07:13]
1252 **
1253 ** [InterMap:Link] -> Interwiki link
 
 
 
 
1254 */
1255 void wiki_resolve_hyperlink(
1256 Blob *pOut, /* Write the HTML output here */
1257 int mFlags, /* Rendering option flags */
1258 const char *zTarget, /* Hyperlink target; text within [...] */
1259 char *zClose, /* Write hyperlink closing text here */
1260 int nClose, /* Bytes available in zClose[] */
@@ -1264,10 +1314,11 @@
1264 const char *zTerm = "</a>";
1265 const char *z;
1266 char *zExtra = 0;
1267 const char *zExtraNS = 0;
1268 char *zRemote = 0;
 
1269
1270 if( zTitle ){
1271 zExtra = mprintf(" title='%h'", zTitle);
1272 zExtraNS = zExtra+1;
1273 }else if( mFlags & WIKI_TARGET_BLANK ){
@@ -1317,14 +1368,19 @@
1317 }
1318 }
1319 }else if( !in_this_repo(zTarget) ){
1320 if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
1321 zTerm = "";
 
 
 
 
1322 }else{
1323 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1324 zTerm = "]</span>";
1325 }
 
1326 }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
1327 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1328 zTerm = "]</a>";
1329 }else{
1330 zTerm = "";
@@ -1341,33 +1397,40 @@
1341 }else{
1342 blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
1343 }
1344 }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
1345 && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1346 /* Dates or date-and-times in ISO8610 resolve to a link to the
1347 ** timeline for that date */
1348 blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
1349 }else if( mFlags & WIKI_MARKDOWNLINKS ){
1350 /* If none of the above, and if rendering links for markdown, then
1351 ** create a link to the literal text of the target */
1352 blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
 
 
 
 
1353 }else if( zOrig && zTarget>=&zOrig[2]
1354 && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
1355 /* If the hyperlink markup is not preceded by whitespace, then it
1356 ** is probably a C-language subscript or similar, not really a
1357 ** hyperlink. Just ignore it. */
1358 zTerm = "";
1359 }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
1360 /* Also ignore the link if various flags are set */
1361 zTerm = "";
 
1362 }else{
1363 blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
1364 zTerm = "</span>";
 
1365 }
1366 if( zExtra ) fossil_free(zExtra);
1367 assert( (int)strlen(zTerm)<nClose );
1368 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
 
1369 }
1370
1371 /*
1372 ** Check zTarget to see if it looks like a valid hyperlink target.
1373 ** Return true if it does seem valid and false if not.
@@ -1465,11 +1528,10 @@
1465 */
1466 static void wiki_render(Renderer *p, char *z){
1467 int tokenType;
1468 ParsedMarkup markup;
1469 int n;
1470 int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
1471 int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
1472 int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
1473 char *zOrig = z;
1474
1475 /* Make sure the attribute constants and names still align
@@ -1483,22 +1545,17 @@
1483 n = nextWikiToken(z, p, &tokenType);
1484 }
1485 p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
1486 switch( tokenType ){
1487 case TOKEN_PARAGRAPH: {
1488 if( inlineOnly ){
1489 /* blob_append_string(p->pOut, " &para; "); */
1490 blob_append_string(p->pOut, " &nbsp;&nbsp; ");
1491 }else{
1492 if( p->wikiList ){
1493 popStackToTag(p, p->wikiList);
1494 p->wikiList = 0;
1495 }
1496 endAutoParagraph(p);
1497 blob_append_string(p->pOut, "\n\n");
1498 p->wantAutoParagraph = 1;
1499 }
1500 p->state |= AT_PARAGRAPH|AT_NEWLINE;
1501 break;
1502 }
1503 case TOKEN_NEWLINE: {
1504 if( p->renderFlags & WIKI_NEWLINE ){
@@ -1508,86 +1565,91 @@
1508 }
1509 p->state |= AT_NEWLINE;
1510 break;
1511 }
1512 case TOKEN_BUL_LI: {
1513 if( inlineOnly ){
1514 blob_append_string(p->pOut, " &bull; ");
1515 }else{
1516 if( p->wikiList!=MARKUP_UL ){
1517 if( p->wikiList ){
1518 popStackToTag(p, p->wikiList);
1519 }
1520 endAutoParagraph(p);
1521 pushStack(p, MARKUP_UL);
1522 blob_append_string(p->pOut, "<ul>");
1523 p->wikiList = MARKUP_UL;
1524 }
1525 popStackToTag(p, MARKUP_LI);
1526 startAutoParagraph(p);
1527 pushStack(p, MARKUP_LI);
1528 blob_append_string(p->pOut, "<li>");
1529 }
1530 break;
1531 }
1532 case TOKEN_NUM_LI: {
1533 if( inlineOnly ){
1534 blob_append_string(p->pOut, " # ");
1535 }else{
1536 if( p->wikiList!=MARKUP_OL ){
1537 if( p->wikiList ){
1538 popStackToTag(p, p->wikiList);
1539 }
1540 endAutoParagraph(p);
1541 pushStack(p, MARKUP_OL);
1542 blob_append_string(p->pOut, "<ol>");
1543 p->wikiList = MARKUP_OL;
1544 }
1545 popStackToTag(p, MARKUP_LI);
1546 startAutoParagraph(p);
1547 pushStack(p, MARKUP_LI);
1548 blob_append_string(p->pOut, "<li>");
1549 }
1550 break;
1551 }
1552 case TOKEN_ENUM: {
1553 if( inlineOnly ){
1554 blob_appendf(p->pOut, " (%d) ", atoi(z));
1555 }else{
1556 if( p->wikiList!=MARKUP_OL ){
1557 if( p->wikiList ){
1558 popStackToTag(p, p->wikiList);
1559 }
1560 endAutoParagraph(p);
1561 pushStack(p, MARKUP_OL);
1562 blob_append_string(p->pOut, "<ol>");
1563 p->wikiList = MARKUP_OL;
1564 }
1565 popStackToTag(p, MARKUP_LI);
1566 startAutoParagraph(p);
1567 pushStack(p, MARKUP_LI);
1568 blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
1569 }
1570 break;
1571 }
1572 case TOKEN_INDENT: {
1573 if( !inlineOnly ){
1574 assert( p->wikiList==0 );
1575 pushStack(p, MARKUP_BLOCKQUOTE);
1576 blob_append_string(p->pOut, "<blockquote>");
1577 p->wantAutoParagraph = 0;
1578 p->wikiList = MARKUP_BLOCKQUOTE;
1579 }
1580 break;
1581 }
1582 case TOKEN_CHARACTER: {
1583 startAutoParagraph(p);
 
 
 
 
1584 if( z[0]=='<' ){
 
1585 blob_append_string(p->pOut, "&lt;");
1586 }else if( z[0]=='&' ){
 
1587 blob_append_string(p->pOut, "&amp;");
1588 }
 
 
 
 
 
 
 
 
 
1589 break;
1590 }
1591 case TOKEN_LINK: {
1592 char *zTarget;
1593 char *zDisplay = 0;
@@ -1596,10 +1658,11 @@
1596 char zClose[20];
1597 char cS1 = 0;
1598 int iS1 = 0;
1599
1600 startAutoParagraph(p);
 
1601 zTarget = &z[1];
1602 for(i=1; z[i] && z[i]!=']'; i++){
1603 if( z[i]=='|' && zDisplay==0 ){
1604 zDisplay = &z[i+1];
1605 for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
@@ -1612,11 +1675,11 @@
1612 if( zDisplay==0 ){
1613 zDisplay = zTarget + interwiki_removable_prefix(zTarget);
1614 }else{
1615 while( fossil_isspace(*zDisplay) ) zDisplay++;
1616 }
1617 wiki_resolve_hyperlink(p->pOut, p->state,
1618 zTarget, zClose, sizeof(zClose), zOrig, 0);
1619 if( linksOnly || zClose[0]==0 || p->inVerbatim ){
1620 if( cS1 ) z[iS1] = cS1;
1621 if( zClose[0]!=']' ){
1622 blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
@@ -1702,14 +1765,22 @@
1702
1703 /* Render invalid markup literally. The markup appears in the
1704 ** final output as plain text.
1705 */
1706 if( markup.iCode==MARKUP_INVALID ){
 
1707 unparseMarkup(&markup);
1708 startAutoParagraph(p);
1709 blob_append_string(p->pOut, "&lt;");
1710 n = 1;
 
 
 
 
 
 
 
1711 }else
1712
1713 /* If the markup is not font-change markup ignore it if the
1714 ** font-change-only flag is set.
1715 */
@@ -1723,16 +1794,10 @@
1723 }else{
1724 p->state &= ~ALLOW_WIKI;
1725 }
1726 }else
1727
1728 /* Ignore block markup for in-line rendering.
1729 */
1730 if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
1731 /* Do nothing */
1732 }else
1733
1734 /* Generate end-tags */
1735 if( markup.endTag ){
1736 popStackToTag(p, markup.iCode);
1737 }else
1738
@@ -1814,10 +1879,11 @@
1814 }else
1815 {
1816 if( markup.iType==MUTYPE_FONT ){
1817 startAutoParagraph(p);
1818 }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
 
1819 p->wantAutoParagraph = 0;
1820 }
1821 if( markup.iCode==MARKUP_HR
1822 || markup.iCode==MARKUP_H1
1823 || markup.iCode==MARKUP_H2
@@ -1844,12 +1910,14 @@
1844 ** Transform the text in the pIn blob. Write the results
1845 ** into the pOut blob. The pOut blob should already be
1846 ** initialized. The output is merely appended to pOut.
1847 ** If pOut is NULL, then the output is appended to the CGI
1848 ** reply.
 
 
1849 */
1850 void wiki_convert(Blob *pIn, Blob *pOut, int flags){
1851 Renderer renderer;
1852
1853 memset(&renderer, 0, sizeof(renderer));
1854 renderer.renderFlags = flags;
1855 renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
@@ -1873,10 +1941,11 @@
1873 while( renderer.nStack ){
1874 popStack(&renderer);
1875 }
1876 blob_append_char(renderer.pOut, '\n');
1877 free(renderer.aStack);
 
1878 }
1879
1880 /*
1881 ** COMMAND: test-wiki-render
1882 **
@@ -1886,45 +1955,84 @@
1886 ** the resulting HTML on standard output.
1887 **
1888 ** Options:
1889 ** --buttons Set the WIKI_BUTTONS flag
1890 ** --dark-pikchr Render pikchrs in dark mode
 
1891 ** --htmlonly Set the WIKI_HTMLONLY flag
1892 ** --inline Set the WIKI_INLINE flag
1893 ** --linksonly Set the WIKI_LINKSONLY flag
 
 
1894 ** --nobadlinks Set the WIKI_NOBADLINKS flag
1895 ** --noblock Set the WIKI_NOBLOCK flag
1896 ** --text Run the output through html_to_plaintext().
1897 */
1898 void test_wiki_render(void){
1899 Blob in, out;
1900 int flags = 0;
1901 int bText;
 
 
 
 
1902 if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
1903 if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
1904 if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
1905 if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
1906 if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
1907 if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;
1908 if( find_option("dark-pikchr",0,0)!=0 ){
1909 pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
1910 }
1911 bText = find_option("text",0,0)!=0;
 
 
 
1912 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1913 verify_all_options();
1914 if( g.argc!=3 ) usage("FILE");
1915 blob_zero(&out);
1916 blob_read_from_file(&in, g.argv[2], ExtFILE);
1917 wiki_convert(&in, &out, flags);
 
 
 
 
1918 if( bText ){
1919 Blob txt;
 
 
 
1920 blob_init(&txt, 0, 0);
1921 html_to_plaintext(blob_str(&out),&txt);
1922 blob_reset(&out);
1923 out = txt;
1924 }
1925 blob_write_to_file(&out, "-");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1926 }
1927
1928 /*
1929 ** COMMAND: test-markdown-render
1930 **
@@ -1960,11 +2068,11 @@
1960 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
1961 safe_html(&out);
1962 if( bText ){
1963 Blob txt;
1964 blob_init(&txt, 0, 0);
1965 html_to_plaintext(blob_str(&out), &txt);
1966 blob_reset(&out);
1967 out = txt;
1968 }
1969 blob_write_to_file(&out, "-");
1970 blob_reset(&in);
@@ -2024,33 +2132,30 @@
2024 ** [target|...]
2025 **
2026 ** Where "target" can be either an artifact ID prefix or a wiki page
2027 ** name. For each such hyperlink found, add an entry to the
2028 ** backlink table.
 
 
2029 */
2030 void wiki_extract_links(
2031 char *z, /* The wiki text from which to extract links */
2032 Backlink *pBklnk, /* Backlink extraction context */
2033 int flags /* wiki parsing flags */
2034 ){
2035 Renderer renderer;
2036 int tokenType;
2037 ParsedMarkup markup;
2038 int n;
2039 int inlineOnly;
2040 int wikiHtmlOnly = 0;
2041
2042 memset(&renderer, 0, sizeof(renderer));
2043 renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
2044 if( flags & WIKI_NOBLOCK ){
2045 renderer.state |= INLINE_MARKUP_ONLY;
2046 }
2047 if( wikiUsesHtml() ){
2048 renderer.state |= WIKI_HTMLONLY;
2049 wikiHtmlOnly = 1;
2050 }
2051 inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0;
2052
2053 while( z[0] ){
2054 if( wikiHtmlOnly ){
2055 n = nextRawToken(z, &renderer, &tokenType);
2056 }else{
@@ -2127,16 +2232,10 @@
2127 }else{
2128 renderer.state &= ~ALLOW_WIKI;
2129 }
2130 }else
2131
2132 /* Ignore block markup for in-line rendering.
2133 */
2134 if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
2135 /* Do nothing */
2136 }else
2137
2138 /* Generate end-tags */
2139 if( markup.endTag ){
2140 popStackToTag(&renderer, markup.iCode);
2141 }else
2142
@@ -2175,10 +2274,11 @@
2175 }
2176 }
2177 z += n;
2178 }
2179 free(renderer.aStack);
 
2180 }
2181
2182 /*
2183 ** Return the length, in bytes, of the HTML token that z is pointing to.
2184 */
@@ -2427,33 +2527,61 @@
2427 blob_reset(&in);
2428 fossil_puts(blob_buffer(&out), 0, blob_size(&out));
2429 blob_reset(&out);
2430 }
2431 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2432
2433 /*
2434 ** Remove all HTML markup from the input text. The output written into
2435 ** pOut is pure text.
2436 **
2437 ** Put the title on the first line, if there is any <title> markup.
2438 ** If there is no <title>, then create a blank first line.
2439 */
2440 void html_to_plaintext(const char *zIn, Blob *pOut){
2441 int n;
2442 int i, j;
2443 int inTitle = 0; /* True between <title>...</title> */
2444 int seenText = 0; /* True after first non-whitespace seen */
2445 int nNL = 0; /* Number of \n characters at the end of pOut */
2446 int nWS = 0; /* True if pOut ends with whitespace */
2447 while( fossil_isspace(zIn[0]) ) zIn++;
 
 
 
 
 
2448 while( zIn[0] ){
2449 n = html_token_length(zIn);
2450 if( zIn[0]=='<' && n>1 ){
2451 int isCloseTag;
2452 int eTag;
2453 int eType;
2454 char zTag[32];
 
2455 isCloseTag = zIn[1]=='/';
2456 for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
2457 zTag[i] = fossil_tolower(zIn[j]);
2458 }
2459 zTag[i] = 0;
@@ -2467,33 +2595,44 @@
2467 zIn += n;
2468 }
2469 if( zIn[0]=='<' ) zIn += n;
2470 continue;
2471 }
2472 if( eTag==MARKUP_TITLE ){
2473 inTitle = !isCloseTag;
2474 }
2475 if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2476 if( nNL==0 ){
2477 blob_append_char(pOut, '\n');
2478 nNL++;
2479 }
2480 nWS = 1;
 
 
 
 
 
 
 
 
 
 
 
2481 }
2482 }else if( fossil_isspace(zIn[0]) ){
2483 if( seenText ){
2484 nNL = 0;
2485 if( !inTitle ){ /* '\n' -> ' ' within <title> */
2486 for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2487 }
2488 if( !nWS ){
2489 blob_append_char(pOut, nNL ? '\n' : ' ');
2490 nWS = 1;
2491 }
2492 }
2493 }else if( zIn[0]=='&' ){
2494 u32 c = '?';
 
2495 if( zIn[1]=='#' ){
2496 c = atoi(&zIn[2]);
2497 if( c==0 ) c = '?';
2498 }else{
2499 static const struct { int n; u32 c; char *z; } aEntity[] = {
@@ -2509,65 +2648,63 @@
2509 c = aEntity[jj].c;
2510 break;
2511 }
2512 }
2513 }
2514 if( fossil_isspace(c) ){
2515 if( nWS==0 && seenText ) blob_append_char(pOut, c);
2516 nWS = 1;
2517 nNL = c=='\n';
2518 }else{
2519 if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
2520 seenText = 1;
2521 nNL = nWS = 0;
2522 if( c<0x00080 ){
2523 blob_append_char(pOut, c & 0xff);
2524 }else if( c<0x00800 ){
2525 blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f));
2526 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2527 }else if( c<0x10000 ){
2528 blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f));
2529 blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2530 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2531 }else{
2532 blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07));
2533 blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f));
2534 blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2535 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2536 }
2537 }
2538 }else{
2539 if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
2540 seenText = 1;
2541 nNL = nWS = 0;
2542 blob_append(pOut, zIn, n);
2543 }
2544 zIn += n;
2545 }
2546 if( nNL==0 ) blob_append_char(pOut, '\n');
 
 
2547 }
2548
2549 /*
2550 ** COMMAND: test-html-to-text
2551 **
2552 ** Usage: %fossil test-html-to-text FILE ...
2553 **
2554 ** Read all files named on the command-line. Convert the file
2555 ** content from HTML to text and write the results on standard
2556 ** output.
2557 **
2558 ** This command is intended as a test and debug interface for
2559 ** the html_to_plaintext() routine.
 
 
 
 
 
2560 */
2561 void test_html_to_text(void){
2562 Blob in, out;
2563 int i;
 
 
2564
2565 for(i=2; i<g.argc; i++){
2566 blob_read_from_file(&in, g.argv[i], ExtFILE);
2567 blob_zero(&out);
2568 html_to_plaintext(blob_str(&in), &out);
2569 blob_reset(&in);
2570 fossil_puts(blob_buffer(&out), 0, blob_size(&out));
2571 blob_reset(&out);
2572 }
2573 }
2574
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -23,23 +23,43 @@
23
24 #if INTERFACE
25 /*
26 ** Allowed wiki transformation operations
27 */
28 #define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */
29 #define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */
30 /* avalable for reuse: 0x0004 --- formerly WIKI_NOBLOCK */
31 #define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */
32 #define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */
33 #define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */
34 #define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */
35 #define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */
36 #define WIKI_SAFE 0x0100 /* Make the result safe for embedding */
37 #define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */
38 #define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */
39 #define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */
40 #define WIKI_MARK 0x1000 /* Add <mark>..</mark> around problems */
41
42 /*
43 ** Return values from wiki_convert
44 */
45 #define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */
46 #define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: &lt;) */
47 #define RENDER_TAG 0x0004 /* One or more HTML tags */
48 #define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */
49 #define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */
50 #define RENDER_MARK 0x0020 /* Output contains <mark>..</mark> */
51 #define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */
52 #define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */
53 #define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */
54 #define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */
55 #define RENDER_BADHTML 0x1000 /* Bad HTML seen */
56 #define RENDER_ERROR 0x8000 /* Some other kind of error */
57 /* Composite values: */
58 #define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */
59
60 #endif /* INTERFACE */
61
62
63 /*
64 ** These are the only markup attributes allowed.
65 */
@@ -445,20 +465,20 @@
465 #define AT_NEWLINE 0x0010000 /* At start of a line */
466 #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */
467 #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */
468 #define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */
469 #define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */
470 #define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */
 
471
472 /*
473 ** Current state of the rendering engine
474 */
475 typedef struct Renderer Renderer;
476 struct Renderer {
477 Blob *pOut; /* Output appended to this blob */
478 int state; /* Flag that govern rendering */
479 int mRender; /* Mask of RENDER_* values to return */
480 unsigned renderFlags; /* Flags from the client */
481 int wikiList; /* Current wiki list type */
482 int inVerbatim; /* True in <verbatim> mode */
483 int preVerbState; /* Value of state prior to verbatim */
484 int wantAutoParagraph; /* True if a <p> is desired */
@@ -680,21 +700,26 @@
700 static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
701 int n;
702 if( z[0]=='<' ){
703 n = html_tag_length(z);
704 if( n>0 ){
705 p->mRender |= RENDER_TAG;
706 *pTokenType = TOKEN_MARKUP;
707 return n;
708 }else{
709 p->mRender |= RENDER_BADTAG;
710 *pTokenType = TOKEN_CHARACTER;
711 return 1;
712 }
713 }
714 if( z[0]=='&' ){
715 p->mRender |= RENDER_ENTITY;
716 if( (p->inVerbatim || !isElement(z)) ){
717 *pTokenType = TOKEN_CHARACTER;
718 return 1;
719 }
720 }
 
 
 
 
721 if( (p->state & ALLOW_WIKI)!=0 ){
722 if( z[0]=='\n' ){
723 n = paragraphBreakLength(z);
724 if( n>0 ){
725 *pTokenType = TOKEN_PARAGRAPH;
@@ -726,17 +751,31 @@
751 if( n>0 ){
752 *pTokenType = TOKEN_INDENT;
753 return n;
754 }
755 }
756 if( z[0]=='[' ){
757 if( (n = linkLength(z))>0 ){
758 *pTokenType = TOKEN_LINK;
759 return n;
760 }else if( p->state & WIKI_MARK ){
761 blob_append_string(p->pOut, "<mark>");
762 p->mRender |= RENDER_BADLINK|RENDER_MARK;
763 }else{
764 p->mRender |= RENDER_BADLINK;
765 }
766 }
767 }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){
768 if( (n = linkLength(z))>0 ){
769 *pTokenType = TOKEN_LINK;
770 return n;
771 }else if( p->state & WIKI_MARK ){
772 blob_append_string(p->pOut, "<mark>");
773 p->mRender |= RENDER_BADLINK|RENDER_MARK;
774 }else{
775 p->mRender |= RENDER_BADLINK;
776 }
 
 
 
777 }
778 *pTokenType = TOKEN_TEXT;
779 return 1 + textLength(z+1, p->state);
780 }
781
@@ -746,13 +785,20 @@
785 ** z points to the start of a token. Return the number of
786 ** characters in that token. Write the token type into *pTokenType.
787 */
788 static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
789 int n;
790 if( z[0]=='[' ){
791 if( (n = linkLength(z))>0 ){
792 *pTokenType = TOKEN_LINK;
793 return n;
794 }else if( p->state & WIKI_MARK ){
795 blob_append_string(p->pOut, "<mark>");
796 p->mRender |= RENDER_BADLINK|RENDER_MARK;
797 }else{
798 p->mRender |= RENDER_BADLINK;
799 }
800 }
801 *pTokenType = TOKEN_RAW;
802 return 1 + textLength(z+1, p->state);
803 }
804
@@ -1249,12 +1295,16 @@
1295 ** [wiki:WikiPageName]
1296 **
1297 ** [2010-02-27 07:13]
1298 **
1299 ** [InterMap:Link] -> Interwiki link
1300 **
1301 ** The return value is a mask of RENDER_* values indicating what happened.
1302 ** Probably the return value is 0 on success and RENDER_BADTARGET or
1303 ** RENDER_BADLINK if there are problems.
1304 */
1305 int wiki_resolve_hyperlink(
1306 Blob *pOut, /* Write the HTML output here */
1307 int mFlags, /* Rendering option flags */
1308 const char *zTarget, /* Hyperlink target; text within [...] */
1309 char *zClose, /* Write hyperlink closing text here */
1310 int nClose, /* Bytes available in zClose[] */
@@ -1264,10 +1314,11 @@
1314 const char *zTerm = "</a>";
1315 const char *z;
1316 char *zExtra = 0;
1317 const char *zExtraNS = 0;
1318 char *zRemote = 0;
1319 int rc = 0;
1320
1321 if( zTitle ){
1322 zExtra = mprintf(" title='%h'", zTitle);
1323 zExtraNS = zExtra+1;
1324 }else if( mFlags & WIKI_TARGET_BLANK ){
@@ -1317,14 +1368,19 @@
1368 }
1369 }
1370 }else if( !in_this_repo(zTarget) ){
1371 if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
1372 zTerm = "";
1373 }else if( (mFlags & WIKI_MARK)!=0 ){
1374 blob_appendf(pOut, "<mark>%s", zLB);
1375 zTerm = "]</mark>";
1376 rc |= RENDER_MARK;
1377 }else{
1378 blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
1379 zTerm = "]</span>";
1380 }
1381 rc |= RENDER_BADTARGET;
1382 }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
1383 blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
1384 zTerm = "]</a>";
1385 }else{
1386 zTerm = "";
@@ -1341,33 +1397,40 @@
1397 }else{
1398 blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
1399 }
1400 }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
1401 && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
1402 /* Dates or date-and-times in ISO8601 resolve to a link to the
1403 ** timeline for that date */
1404 blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
1405 }else if( mFlags & WIKI_MARKDOWNLINKS ){
1406 /* If none of the above, and if rendering links for markdown, then
1407 ** create a link to the literal text of the target */
1408 blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
1409 }else if( mFlags & WIKI_MARK ){
1410 blob_appendf(pOut, "<mark>[");
1411 zTerm = "]</mark>";
1412 rc |= RENDER_BADTARGET|RENDER_MARK;
1413 }else if( zOrig && zTarget>=&zOrig[2]
1414 && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
1415 /* If the hyperlink markup is not preceded by whitespace, then it
1416 ** is probably a C-language subscript or similar, not really a
1417 ** hyperlink. Just ignore it. */
1418 zTerm = "";
1419 }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
1420 /* Also ignore the link if various flags are set */
1421 zTerm = "";
1422 rc |= RENDER_BADTARGET;
1423 }else{
1424 blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
1425 zTerm = "</span>";
1426 rc |= RENDER_BADTARGET;
1427 }
1428 if( zExtra ) fossil_free(zExtra);
1429 assert( (int)strlen(zTerm)<nClose );
1430 sqlite3_snprintf(nClose, zClose, "%s", zTerm);
1431 return rc;
1432 }
1433
1434 /*
1435 ** Check zTarget to see if it looks like a valid hyperlink target.
1436 ** Return true if it does seem valid and false if not.
@@ -1465,11 +1528,10 @@
1528 */
1529 static void wiki_render(Renderer *p, char *z){
1530 int tokenType;
1531 ParsedMarkup markup;
1532 int n;
 
1533 int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
1534 int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
1535 char *zOrig = z;
1536
1537 /* Make sure the attribute constants and names still align
@@ -1483,22 +1545,17 @@
1545 n = nextWikiToken(z, p, &tokenType);
1546 }
1547 p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
1548 switch( tokenType ){
1549 case TOKEN_PARAGRAPH: {
1550 if( p->wikiList ){
1551 popStackToTag(p, p->wikiList);
1552 p->wikiList = 0;
1553 }
1554 endAutoParagraph(p);
1555 blob_append_string(p->pOut, "\n\n");
1556 p->wantAutoParagraph = 1;
 
 
 
 
 
1557 p->state |= AT_PARAGRAPH|AT_NEWLINE;
1558 break;
1559 }
1560 case TOKEN_NEWLINE: {
1561 if( p->renderFlags & WIKI_NEWLINE ){
@@ -1508,86 +1565,91 @@
1565 }
1566 p->state |= AT_NEWLINE;
1567 break;
1568 }
1569 case TOKEN_BUL_LI: {
1570 p->mRender |= RENDER_BLOCK;
1571 if( p->wikiList!=MARKUP_UL ){
1572 if( p->wikiList ){
1573 popStackToTag(p, p->wikiList);
1574 }
1575 endAutoParagraph(p);
1576 pushStack(p, MARKUP_UL);
1577 blob_append_string(p->pOut, "<ul>");
1578 p->wikiList = MARKUP_UL;
1579 }
1580 popStackToTag(p, MARKUP_LI);
1581 startAutoParagraph(p);
1582 pushStack(p, MARKUP_LI);
1583 blob_append_string(p->pOut, "<li>");
 
 
 
1584 break;
1585 }
1586 case TOKEN_NUM_LI: {
1587 p->mRender |= RENDER_BLOCK;
1588 if( p->wikiList!=MARKUP_OL ){
1589 if( p->wikiList ){
1590 popStackToTag(p, p->wikiList);
1591 }
1592 endAutoParagraph(p);
1593 pushStack(p, MARKUP_OL);
1594 blob_append_string(p->pOut, "<ol>");
1595 p->wikiList = MARKUP_OL;
1596 }
1597 popStackToTag(p, MARKUP_LI);
1598 startAutoParagraph(p);
1599 pushStack(p, MARKUP_LI);
1600 blob_append_string(p->pOut, "<li>");
 
 
 
1601 break;
1602 }
1603 case TOKEN_ENUM: {
1604 p->mRender |= RENDER_BLOCK;
1605 if( p->wikiList!=MARKUP_OL ){
1606 if( p->wikiList ){
1607 popStackToTag(p, p->wikiList);
1608 }
1609 endAutoParagraph(p);
1610 pushStack(p, MARKUP_OL);
1611 blob_append_string(p->pOut, "<ol>");
1612 p->wikiList = MARKUP_OL;
1613 }
1614 popStackToTag(p, MARKUP_LI);
1615 startAutoParagraph(p);
1616 pushStack(p, MARKUP_LI);
1617 blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
 
 
 
1618 break;
1619 }
1620 case TOKEN_INDENT: {
1621 p->mRender |= RENDER_BLOCK;
1622 assert( p->wikiList==0 );
1623 pushStack(p, MARKUP_BLOCKQUOTE);
1624 blob_append_string(p->pOut, "<blockquote>");
1625 p->wantAutoParagraph = 0;
1626 p->wikiList = MARKUP_BLOCKQUOTE;
 
1627 break;
1628 }
1629 case TOKEN_CHARACTER: {
1630 startAutoParagraph(p);
1631 if( p->state & WIKI_MARK ){
1632 blob_append_string(p->pOut, "<mark>");
1633 p->mRender |= RENDER_MARK;
1634 }
1635 if( z[0]=='<' ){
1636 p->mRender |= RENDER_BADTAG;
1637 blob_append_string(p->pOut, "&lt;");
1638 }else if( z[0]=='&' ){
1639 p->mRender |= RENDER_BADENTITY;
1640 blob_append_string(p->pOut, "&amp;");
1641 }
1642 if( p->state & WIKI_MARK ){
1643 if( fossil_isalnum(z[1]) || (z[1]=='/' && fossil_isalnum(z[2])) ){
1644 int kk;
1645 for(kk=2; fossil_isalnum(z[kk]); kk++){}
1646 blob_append(p->pOut, &z[1], kk-1);
1647 n = kk;
1648 }
1649 blob_append_string(p->pOut, "</mark>");
1650 }
1651 break;
1652 }
1653 case TOKEN_LINK: {
1654 char *zTarget;
1655 char *zDisplay = 0;
@@ -1596,10 +1658,11 @@
1658 char zClose[20];
1659 char cS1 = 0;
1660 int iS1 = 0;
1661
1662 startAutoParagraph(p);
1663 p->mRender |= RENDER_LINK;
1664 zTarget = &z[1];
1665 for(i=1; z[i] && z[i]!=']'; i++){
1666 if( z[i]=='|' && zDisplay==0 ){
1667 zDisplay = &z[i+1];
1668 for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
@@ -1612,11 +1675,11 @@
1675 if( zDisplay==0 ){
1676 zDisplay = zTarget + interwiki_removable_prefix(zTarget);
1677 }else{
1678 while( fossil_isspace(*zDisplay) ) zDisplay++;
1679 }
1680 p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state,
1681 zTarget, zClose, sizeof(zClose), zOrig, 0);
1682 if( linksOnly || zClose[0]==0 || p->inVerbatim ){
1683 if( cS1 ) z[iS1] = cS1;
1684 if( zClose[0]!=']' ){
1685 blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
@@ -1702,14 +1765,22 @@
1765
1766 /* Render invalid markup literally. The markup appears in the
1767 ** final output as plain text.
1768 */
1769 if( markup.iCode==MARKUP_INVALID ){
1770 p->mRender |= RENDER_BADTAG;
1771 unparseMarkup(&markup);
1772 startAutoParagraph(p);
1773 if( p->state & WIKI_MARK ){
1774 p->mRender |= RENDER_MARK;
1775 blob_append_string(p->pOut, "<mark>");
1776 htmlize_to_blob(p->pOut, z, n);
1777 blob_append_string(p->pOut, "</mark>");
1778 }else{
1779 blob_append_string(p->pOut, "&lt;");
1780 htmlize_to_blob(p->pOut, z+1, n-1);
1781 }
1782 }else
1783
1784 /* If the markup is not font-change markup ignore it if the
1785 ** font-change-only flag is set.
1786 */
@@ -1723,16 +1794,10 @@
1794 }else{
1795 p->state &= ~ALLOW_WIKI;
1796 }
1797 }else
1798
 
 
 
 
 
 
1799 /* Generate end-tags */
1800 if( markup.endTag ){
1801 popStackToTag(p, markup.iCode);
1802 }else
1803
@@ -1814,10 +1879,11 @@
1879 }else
1880 {
1881 if( markup.iType==MUTYPE_FONT ){
1882 startAutoParagraph(p);
1883 }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
1884 p->mRender |= RENDER_BLOCKTAG;
1885 p->wantAutoParagraph = 0;
1886 }
1887 if( markup.iCode==MARKUP_HR
1888 || markup.iCode==MARKUP_H1
1889 || markup.iCode==MARKUP_H2
@@ -1844,12 +1910,14 @@
1910 ** Transform the text in the pIn blob. Write the results
1911 ** into the pOut blob. The pOut blob should already be
1912 ** initialized. The output is merely appended to pOut.
1913 ** If pOut is NULL, then the output is appended to the CGI
1914 ** reply.
1915 **
1916 ** Return a mask of RENDER_ flags indicating what happened.
1917 */
1918 int wiki_convert(Blob *pIn, Blob *pOut, int flags){
1919 Renderer renderer;
1920
1921 memset(&renderer, 0, sizeof(renderer));
1922 renderer.renderFlags = flags;
1923 renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
@@ -1873,10 +1941,11 @@
1941 while( renderer.nStack ){
1942 popStack(&renderer);
1943 }
1944 blob_append_char(renderer.pOut, '\n');
1945 free(renderer.aStack);
1946 return renderer.mRender;
1947 }
1948
1949 /*
1950 ** COMMAND: test-wiki-render
1951 **
@@ -1886,45 +1955,84 @@
1955 ** the resulting HTML on standard output.
1956 **
1957 ** Options:
1958 ** --buttons Set the WIKI_BUTTONS flag
1959 ** --dark-pikchr Render pikchrs in dark mode
1960 ** --flow Render as text using comment_format
1961 ** --htmlonly Set the WIKI_HTMLONLY flag
1962 ** --inline Set the WIKI_INLINE flag
1963 ** --linksonly Set the WIKI_LINKSONLY flag
1964 ** -m TEXT Use TEXT in place of the content of FILE
1965 ** --mark Add <mark>...</mark> around problems
1966 ** --nobadlinks Set the WIKI_NOBADLINKS flag
1967 ** --text Run the output through html_to_plaintext()
1968 ** --type Break down the return code from wiki_convert()
1969 */
1970 void test_wiki_render(void){
1971 Blob in, out;
1972 int flags = 0;
1973 int bText;
1974 int bFlow = 0;
1975 int showType = 0;
1976 int mType;
1977 const char *zIn;
1978 if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
1979 if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
1980 if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
1981 if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
1982 if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
1983 if( find_option("mark",0,0)!=0 ) flags |= WIKI_MARK;
1984 if( find_option("dark-pikchr",0,0)!=0 ){
1985 pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
1986 }
1987 bText = find_option("text",0,0)!=0;
1988 bFlow = find_option("flow",0,0)!=0;
1989 showType = find_option("type",0,0)!=0;
1990 zIn = find_option("msg","m",1);
1991 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1992 verify_all_options();
1993 if( (zIn==0 && g.argc!=3) || (zIn!=0 && g.argc!=2) ) usage("FILE");
1994 blob_zero(&out);
1995 if( zIn ){
1996 blob_init(&in, zIn, -1);
1997 }else{
1998 blob_read_from_file(&in, g.argv[2], ExtFILE);
1999 }
2000 mType = wiki_convert(&in, &out, flags);
2001 if( bText ){
2002 Blob txt;
2003 int htot = HTOT_TRIM;
2004 if( terminal_is_vt100() ) htot |= HTOT_VT100;
2005 if( bFlow ) htot |= HTOT_FLOW;
2006 blob_init(&txt, 0, 0);
2007 html_to_plaintext(blob_str(&out),&txt, htot);
2008 blob_reset(&out);
2009 out = txt;
2010 }
2011 if( bFlow ){
2012 fossil_print(" ");
2013 comment_print(blob_str(&out), 0, 3, terminal_get_width(80)-3,
2014 get_comment_format());
2015 }else{
2016 blob_write_to_file(&out, "-");
2017 }
2018 if( showType ){
2019 fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*');
2020 if( mType & RENDER_LINK ) fossil_print(" LINK");
2021 if( mType & RENDER_ENTITY ) fossil_print(" ENTITY");
2022 if( mType & RENDER_TAG ) fossil_print(" TAG");
2023 if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG");
2024 if( mType & RENDER_BLOCK ) fossil_print(" BLOCK");
2025 if( mType & RENDER_MARK ) fossil_print(" MARK");
2026 if( mType & RENDER_BADLINK ) fossil_print(" BADLINK");
2027 if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET");
2028 if( mType & RENDER_BADTAG ) fossil_print(" BADTAG");
2029 if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY");
2030 if( mType & RENDER_BADHTML ) fossil_print(" BADHTML");
2031 if( mType & RENDER_ERROR ) fossil_print(" ERROR");
2032 fossil_print("\n");
2033 }
2034 }
2035
2036 /*
2037 ** COMMAND: test-markdown-render
2038 **
@@ -1960,11 +2068,11 @@
2068 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
2069 safe_html(&out);
2070 if( bText ){
2071 Blob txt;
2072 blob_init(&txt, 0, 0);
2073 html_to_plaintext(blob_str(&out), &txt, HTOT_VT100);
2074 blob_reset(&out);
2075 out = txt;
2076 }
2077 blob_write_to_file(&out, "-");
2078 blob_reset(&in);
@@ -2024,33 +2132,30 @@
2132 ** [target|...]
2133 **
2134 ** Where "target" can be either an artifact ID prefix or a wiki page
2135 ** name. For each such hyperlink found, add an entry to the
2136 ** backlink table.
2137 **
2138 ** The return value is a mask of RENDER_ flags.
2139 */
2140 int wiki_extract_links(
2141 char *z, /* The wiki text from which to extract links */
2142 Backlink *pBklnk, /* Backlink extraction context */
2143 int flags /* wiki parsing flags */
2144 ){
2145 Renderer renderer;
2146 int tokenType;
2147 ParsedMarkup markup;
2148 int n;
 
2149 int wikiHtmlOnly = 0;
2150
2151 memset(&renderer, 0, sizeof(renderer));
2152 renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
 
 
 
2153 if( wikiUsesHtml() ){
2154 renderer.state |= WIKI_HTMLONLY;
2155 wikiHtmlOnly = 1;
2156 }
 
2157
2158 while( z[0] ){
2159 if( wikiHtmlOnly ){
2160 n = nextRawToken(z, &renderer, &tokenType);
2161 }else{
@@ -2127,16 +2232,10 @@
2232 }else{
2233 renderer.state &= ~ALLOW_WIKI;
2234 }
2235 }else
2236
 
 
 
 
 
 
2237 /* Generate end-tags */
2238 if( markup.endTag ){
2239 popStackToTag(&renderer, markup.iCode);
2240 }else
2241
@@ -2175,10 +2274,11 @@
2274 }
2275 }
2276 z += n;
2277 }
2278 free(renderer.aStack);
2279 return renderer.mRender;
2280 }
2281
2282 /*
2283 ** Return the length, in bytes, of the HTML token that z is pointing to.
2284 */
@@ -2427,33 +2527,61 @@
2527 blob_reset(&in);
2528 fossil_puts(blob_buffer(&out), 0, blob_size(&out));
2529 blob_reset(&out);
2530 }
2531 }
2532
2533 #if INTERFACE
2534 /*
2535 ** Allowed flag options for html_to_plaintext().
2536 */
2537 #define HTOT_VT100 0x01 /* <mark> becomes ^[[91m */
2538 #define HTOT_FLOW 0x02 /* Collapse internal whitespace to a single space */
2539 #define HTOT_TRIM 0x04 /* Trim off leading and trailing whitespace */
2540
2541 #endif /* INTERFACE */
2542
2543 /*
2544 ** Add <MARK> or </MARK> to the output, or similar VT-100 escape
2545 ** codes.
2546 */
2547 static void addMark(Blob *pOut, int mFlags, int isClose){
2548 static const char *az[4] = { "<MARK>", "</MARK>", "\033[91m", "\033[0m" };
2549 int i = 0;
2550 if( isClose ) i++;
2551 if( mFlags & HTOT_VT100 ) i += 2;
2552 blob_append(pOut, az[i], -1);
2553 }
2554
2555 /*
2556 ** Remove all HTML markup from the input text. The output written into
2557 ** pOut is pure text.
2558 **
2559 ** Put the title on the first line, if there is any <title> markup.
2560 ** If there is no <title>, then create a blank first line.
2561 */
2562 void html_to_plaintext(const char *zIn, Blob *pOut, int mFlags){
2563 int n;
2564 int i, j;
2565 int bFlow = 0; /* Transform internal WS into a single space */
2566 int prevWS = 1; /* Previous output was whitespace or start of msg */
2567 int nMark = 0; /* True if inside of <mark>..</mark> */
2568
2569 for(i=0; fossil_isspace(zIn[i]); i++){}
2570 if( i>0 && (mFlags & HTOT_TRIM)==0 ){
2571 blob_append(pOut, zIn, i);
2572 }
2573 zIn += i;
2574 if( mFlags & HTOT_FLOW ) bFlow = 1;
2575 while( zIn[0] ){
2576 n = html_token_length(zIn);
2577 if( zIn[0]=='<' && n>1 ){
2578 int isCloseTag;
2579 int eTag;
2580 int eType;
2581 char zTag[32];
2582 prevWS = 0;
2583 isCloseTag = zIn[1]=='/';
2584 for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
2585 zTag[i] = fossil_tolower(zIn[j]);
2586 }
2587 zTag[i] = 0;
@@ -2467,33 +2595,44 @@
2595 zIn += n;
2596 }
2597 if( zIn[0]=='<' ) zIn += n;
2598 continue;
2599 }
2600 if( eTag==MARKUP_INVALID && strcmp(zTag,"mark")==0 ){
2601 if( isCloseTag && nMark ){
2602 addMark(pOut, mFlags, 1);
2603 nMark = 0;
2604 }else if( !isCloseTag && !nMark ){
2605 addMark(pOut, mFlags, 0);
2606 nMark = 1;
2607 }
2608 zIn += n;
2609 continue;
2610 }
2611 if( eTag==MARKUP_TITLE ){
2612 if( isCloseTag && (mFlags & HTOT_FLOW)==0 ){
2613 bFlow = 0;
2614 }else{
2615 bFlow = 1;
2616 }
2617 }
2618 if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2619 blob_append_char(pOut, '\n');
2620 }
2621 }else if( fossil_isspace(zIn[0]) ){
2622 if( bFlow==0 ){
2623 if( zIn[n]==0 && (mFlags & HTOT_TRIM) ) break;
2624 blob_append(pOut, zIn, n);
2625 }else if( !prevWS ){
2626 prevWS = 1;
2627 blob_append_char(pOut, ' ');
2628 zIn += n;
2629 n = 0;
 
2630 }
2631 }else if( zIn[0]=='&' ){
2632 u32 c = '?';
2633 prevWS = 0;
2634 if( zIn[1]=='#' ){
2635 c = atoi(&zIn[2]);
2636 if( c==0 ) c = '?';
2637 }else{
2638 static const struct { int n; u32 c; char *z; } aEntity[] = {
@@ -2509,65 +2648,63 @@
2648 c = aEntity[jj].c;
2649 break;
2650 }
2651 }
2652 }
2653 if( c<0x00080 ){
2654 blob_append_char(pOut, c & 0xff);
2655 }else if( c<0x00800 ){
2656 blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f));
2657 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2658 }else if( c<0x10000 ){
2659 blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f));
2660 blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2661 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2662 }else{
2663 blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07));
2664 blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f));
2665 blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
2666 blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
2667 }
2668 }else{
2669 prevWS = 0;
 
 
 
 
 
 
 
 
 
 
 
2670 blob_append(pOut, zIn, n);
2671 }
2672 zIn += n;
2673 }
2674 if( nMark ){
2675 addMark(pOut, mFlags, 1);
2676 }
2677 }
2678
2679 /*
2680 ** COMMAND: test-html-to-text
2681 **
2682 ** Usage: %fossil test-html-to-text [OPTIONS] FILE ...
2683 **
2684 ** Read all files named on the command-line. Convert the file
2685 ** content from HTML to text and write the results on standard
2686 ** output.
2687 **
2688 ** This command is intended as a test and debug interface for
2689 ** the html_to_plaintext() routine.
2690 **
2691 ** Options:
2692 **
2693 ** --vt100 Translate <mark> and </mark> into ANSI/VT100
2694 ** escapes to highlight the contained text.
2695 */
2696 void test_html_to_text(void){
2697 Blob in, out;
2698 int i;
2699 int mFlags = 0;
2700 if( find_option("vt100",0,0)!=0 ) mFlags |= HTOT_VT100;
2701
2702 for(i=2; i<g.argc; i++){
2703 blob_read_from_file(&in, g.argv[i], ExtFILE);
2704 blob_zero(&out);
2705 html_to_plaintext(blob_str(&in), &out, mFlags);
2706 blob_reset(&in);
2707 fossil_puts(blob_buffer(&out), 0, blob_size(&out));
2708 blob_reset(&out);
2709 }
2710 }
2711
+5 -1
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
7373
of available Fossil repositories.
7474
7575
The "skin" of the reply is determined by the first
7676
repository in the list that has a non-zero
7777
[/help?cmd=repolist-skin|repolist-skin] setting.
78
+
7879
If no repository has such a non-zero repolist-skin setting, then
79
-the repository list is generic HTML without any decoration.
80
+the repository list is generic HTML without any decoration, with
81
+the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82
+environment variable. The variable can be defined in the CGI
83
+control file using the [#setenv|<tt>setenv:</tt>] statement.
8084
8185
The repolist-generated page recurses into subdirectories and will list
8286
all <tt>*.fossil</tt> files found, with the following exceptions:
8387
8488
* Filenames starting with a period are treated as "hidden" and skipped.
8589
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
73 of available Fossil repositories.
74
75 The "skin" of the reply is determined by the first
76 repository in the list that has a non-zero
77 [/help?cmd=repolist-skin|repolist-skin] setting.
 
78 If no repository has such a non-zero repolist-skin setting, then
79 the repository list is generic HTML without any decoration.
 
 
 
80
81 The repolist-generated page recurses into subdirectories and will list
82 all <tt>*.fossil</tt> files found, with the following exceptions:
83
84 * Filenames starting with a period are treated as "hidden" and skipped.
85
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -73,12 +73,16 @@
73 of available Fossil repositories.
74
75 The "skin" of the reply is determined by the first
76 repository in the list that has a non-zero
77 [/help?cmd=repolist-skin|repolist-skin] setting.
78
79 If no repository has such a non-zero repolist-skin setting, then
80 the repository list is generic HTML without any decoration, with
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
84
85 The repolist-generated page recurses into subdirectories and will list
86 all <tt>*.fossil</tt> files found, with the following exceptions:
87
88 * Filenames starting with a period are treated as "hidden" and skipped.
89
+41 -14
--- www/changes.wiki
+++ www/changes.wiki
@@ -14,32 +14,43 @@
1414
<li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
1515
diffs of multiple files.
1616
</ol>
1717
* Added the [/help?cmd=/ckout|/ckout web page] to provide information
1818
about pending changes in a working check-out
19
- * The [/help?cmd=ui|fossil ui] command defaults to using the
20
- [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
21
- "--from PATH" option is present, the default start page becomes
22
- "/ckout?exbase=PATH".
19
+ * Enhancements to the [/help?cmd=ui|fossil ui] command:
20
+ <ol type="a">
21
+ <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
22
+ start page. Or, if the new "--from PATH" option is present, the
23
+ default start page becomes "/ckout?exbase=PATH".
24
+ <li> The new "--extpage FILENAME" option opens the named file as if it
25
+ where in a [./serverext.wiki|CGI extension]. Example usage: the
26
+ person editing this change log has
27
+ "fossil ui --extpage www/changes.wiki" running and hence can
28
+ press "Reload" on the web browser to view edits.
29
+ </ol>
2330
* Enhancements to [/help?cmd=merge|fossil merge]:
2431
<ol type="a">
25
- <li> Added the [/help?cmd=merge-info|fossil merge-info] command and especially
26
- the --tk option to that command, to provide analysis of the most recent
27
- merge or update operation.
32
+ <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
33
+ especially the --tk option to that command, to provide analysis
34
+ of the most recent merge or update operation.
2835
<li> When a merge conflict occurs, a new section is added to the conflict
2936
text that shows Fossil's suggested resolution to the conflict.
3037
</ol>
3138
* Enhancements to [/help?cmd=commit|fossil commit]:
3239
<ol type="a">
33
- <li> If Fossil sees potential formatting mistakes (bad hyperlinks)
40
+ <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
3441
in the check-in comment, it will alert the developer and give
3542
him or her the opportunity to edit the comment before continuing.
43
+ This feature is controllable by the
44
+ [/help?cmd=verify-comments|verify-comments setting].
3645
<li> The new "--if-changes" option causes the commit to become
3746
a quiet no-op if there are no pending changes.
3847
<li> Added the ability to sign check-ins with SSH keys.
3948
<li> Issue a warning if a user tries to commit on a check-in where the
4049
branch has been changed.
50
+ <li> The interactive checkin comment prompt shows the formatting rules
51
+ set for that repository.
4152
</ol>
4253
* Deprecate the --comfmtflags and --comment-format global options and
4354
no longer list them in the built-in help, but keep them working for
4455
backwards compatibility.
4556
Alternative TTY comment formatting can still be specified using the
@@ -73,19 +84,33 @@
7384
collapses long runs of check-ins on the same branch into just
7485
end-points.
7586
<li> The p= and d= parameters an reference different check-ins, which
7687
case the timeline shows those check-ins that are both ancestors
7788
of p= and descendants of d=.
89
+ <li> The saturation and intensity of user-specified checkin and branch
90
+ colors are automatically adjusted to keep the colors
91
+ compatible with the current skin, unless the
92
+ [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. The
93
+ /test-bgcolor page was added to
94
+ test and visualize how these adjustments.
7895
</ol>
96
+ * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
97
+ /doc but keeps the title of markdown documents with the document rather
98
+ that moving it up to the page title.
7999
* Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
80100
and debugging
81
- * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
82
- [/help?cmd=revert|fossil revert] operations that happened on individual
83
- files after a [/help?cmd=merge|fossil merge] to be omitted from the
84
- patch.
85
- * Added the [/help?cmd=patch|patch alias] command for managing aliases
86
- for remote checkout names.
101
+ * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
102
+ decoding of the artifact described by NAME.
103
+ * Improvements to the [/help?cmd=patch|fossil patch] command:
104
+ <ol type="a">
105
+ <li> Fix a bug in "fossil patch create" that causes
106
+ [/help?cmd=revert|fossil revert] operations that happened
107
+ on individualfiles after a [/help?cmd=merge|fossil merge]
108
+ to be omitted from the patch.
109
+ <li> Added the [/help?cmd=patch|patch alias] command for managing
110
+ aliases for remote checkout names.
111
+ </ol>
87112
* Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
88113
<ol type="a">
89114
<li> Add the ability to search the help text, either in the UI
90115
(on the [/help?cmd=/search|/search page]) or from the command-line
91116
(using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
@@ -95,10 +120,12 @@
95120
<li> The -u (--usage) option shows only the command-line syntax
96121
<li> The -o (--options) option shows only the command-line options
97122
</ol>
98123
* Added the ability to attach wiki pages to a ticket for extended
99124
descriptions.
125
+ * Added the "hash" query parameter to the
126
+ [/help?cmd=/whatis|/whatis webpage].
100127
* Add a "user elevation" [/doc/trunk/www/alerts.md|subscription]
101128
which alerts subscribers when an admin creates a new user or
102129
adds new permissions to one.
103130
* Diverse minor fixes and additions.
104131
105132
--- www/changes.wiki
+++ www/changes.wiki
@@ -14,32 +14,43 @@
14 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
15 diffs of multiple files.
16 </ol>
17 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
18 about pending changes in a working check-out
19 * The [/help?cmd=ui|fossil ui] command defaults to using the
20 [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
21 "--from PATH" option is present, the default start page becomes
22 "/ckout?exbase=PATH".
 
 
 
 
 
 
 
23 * Enhancements to [/help?cmd=merge|fossil merge]:
24 <ol type="a">
25 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and especially
26 the --tk option to that command, to provide analysis of the most recent
27 merge or update operation.
28 <li> When a merge conflict occurs, a new section is added to the conflict
29 text that shows Fossil's suggested resolution to the conflict.
30 </ol>
31 * Enhancements to [/help?cmd=commit|fossil commit]:
32 <ol type="a">
33 <li> If Fossil sees potential formatting mistakes (bad hyperlinks)
34 in the check-in comment, it will alert the developer and give
35 him or her the opportunity to edit the comment before continuing.
 
 
36 <li> The new "--if-changes" option causes the commit to become
37 a quiet no-op if there are no pending changes.
38 <li> Added the ability to sign check-ins with SSH keys.
39 <li> Issue a warning if a user tries to commit on a check-in where the
40 branch has been changed.
 
 
41 </ol>
42 * Deprecate the --comfmtflags and --comment-format global options and
43 no longer list them in the built-in help, but keep them working for
44 backwards compatibility.
45 Alternative TTY comment formatting can still be specified using the
@@ -73,19 +84,33 @@
73 collapses long runs of check-ins on the same branch into just
74 end-points.
75 <li> The p= and d= parameters an reference different check-ins, which
76 case the timeline shows those check-ins that are both ancestors
77 of p= and descendants of d=.
 
 
 
 
 
 
78 </ol>
 
 
 
79 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
80 and debugging
81 * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
82 [/help?cmd=revert|fossil revert] operations that happened on individual
83 files after a [/help?cmd=merge|fossil merge] to be omitted from the
84 patch.
85 * Added the [/help?cmd=patch|patch alias] command for managing aliases
86 for remote checkout names.
 
 
 
 
 
87 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
88 <ol type="a">
89 <li> Add the ability to search the help text, either in the UI
90 (on the [/help?cmd=/search|/search page]) or from the command-line
91 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
@@ -95,10 +120,12 @@
95 <li> The -u (--usage) option shows only the command-line syntax
96 <li> The -o (--options) option shows only the command-line options
97 </ol>
98 * Added the ability to attach wiki pages to a ticket for extended
99 descriptions.
 
 
100 * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription]
101 which alerts subscribers when an admin creates a new user or
102 adds new permissions to one.
103 * Diverse minor fixes and additions.
104
105
--- www/changes.wiki
+++ www/changes.wiki
@@ -14,32 +14,43 @@
14 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
15 diffs of multiple files.
16 </ol>
17 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
18 about pending changes in a working check-out
19 * Enhancements to the [/help?cmd=ui|fossil ui] command:
20 <ol type="a">
21 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
22 start page. Or, if the new "--from PATH" option is present, the
23 default start page becomes "/ckout?exbase=PATH".
24 <li> The new "--extpage FILENAME" option opens the named file as if it
25 where in a [./serverext.wiki|CGI extension]. Example usage: the
26 person editing this change log has
27 "fossil ui --extpage www/changes.wiki" running and hence can
28 press "Reload" on the web browser to view edits.
29 </ol>
30 * Enhancements to [/help?cmd=merge|fossil merge]:
31 <ol type="a">
32 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
33 especially the --tk option to that command, to provide analysis
34 of the most recent merge or update operation.
35 <li> When a merge conflict occurs, a new section is added to the conflict
36 text that shows Fossil's suggested resolution to the conflict.
37 </ol>
38 * Enhancements to [/help?cmd=commit|fossil commit]:
39 <ol type="a">
40 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
41 in the check-in comment, it will alert the developer and give
42 him or her the opportunity to edit the comment before continuing.
43 This feature is controllable by the
44 [/help?cmd=verify-comments|verify-comments setting].
45 <li> The new "--if-changes" option causes the commit to become
46 a quiet no-op if there are no pending changes.
47 <li> Added the ability to sign check-ins with SSH keys.
48 <li> Issue a warning if a user tries to commit on a check-in where the
49 branch has been changed.
50 <li> The interactive checkin comment prompt shows the formatting rules
51 set for that repository.
52 </ol>
53 * Deprecate the --comfmtflags and --comment-format global options and
54 no longer list them in the built-in help, but keep them working for
55 backwards compatibility.
56 Alternative TTY comment formatting can still be specified using the
@@ -73,19 +84,33 @@
84 collapses long runs of check-ins on the same branch into just
85 end-points.
86 <li> The p= and d= parameters an reference different check-ins, which
87 case the timeline shows those check-ins that are both ancestors
88 of p= and descendants of d=.
89 <li> The saturation and intensity of user-specified checkin and branch
90 colors are automatically adjusted to keep the colors
91 compatible with the current skin, unless the
92 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. The
93 /test-bgcolor page was added to
94 test and visualize how these adjustments.
95 </ol>
96 * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
97 /doc but keeps the title of markdown documents with the document rather
98 that moving it up to the page title.
99 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
100 and debugging
101 * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
102 decoding of the artifact described by NAME.
103 * Improvements to the [/help?cmd=patch|fossil patch] command:
104 <ol type="a">
105 <li> Fix a bug in "fossil patch create" that causes
106 [/help?cmd=revert|fossil revert] operations that happened
107 on individualfiles after a [/help?cmd=merge|fossil merge]
108 to be omitted from the patch.
109 <li> Added the [/help?cmd=patch|patch alias] command for managing
110 aliases for remote checkout names.
111 </ol>
112 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
113 <ol type="a">
114 <li> Add the ability to search the help text, either in the UI
115 (on the [/help?cmd=/search|/search page]) or from the command-line
116 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
@@ -95,10 +120,12 @@
120 <li> The -u (--usage) option shows only the command-line syntax
121 <li> The -o (--options) option shows only the command-line options
122 </ol>
123 * Added the ability to attach wiki pages to a ticket for extended
124 descriptions.
125 * Added the "hash" query parameter to the
126 [/help?cmd=/whatis|/whatis webpage].
127 * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription]
128 which alerts subscribers when an admin creates a new user or
129 adds new permissions to one.
130 * Diverse minor fixes and additions.
131
132
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148148
149149
150150
`FOSSIL_HOME`: Location of [configuration database][configdb].
151151
See the [configuration database location][configloc] description
152152
for additional information.
153
+
154
+`FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155
+loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156
+none of the listed repositories has the `repolist_skin` property set.
157
+Can be set from the [CGI control file][cgictlfile].
153158
154159
`FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
155160
SEE as text to be hashed into the actual encryption key. This has no
156161
effect if Fossil was not compiled with SEE support enabled.
157162
@@ -493,5 +498,6 @@
493498
On Windows platforms, it assumes that `start` is the command to open
494499
a URL in the user's configured default browser.
495500
496501
[configdb]: ./tech_overview.wiki#configdb
497502
[configloc]: ./tech_overview.wiki#configloc
503
+[cgictlfile]: ./cgi.wiki
498504
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148
149
150 `FOSSIL_HOME`: Location of [configuration database][configdb].
151 See the [configuration database location][configloc] description
152 for additional information.
 
 
 
 
 
153
154 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
155 SEE as text to be hashed into the actual encryption key. This has no
156 effect if Fossil was not compiled with SEE support enabled.
157
@@ -493,5 +498,6 @@
493 On Windows platforms, it assumes that `start` is the command to open
494 a URL in the user's configured default browser.
495
496 [configdb]: ./tech_overview.wiki#configdb
497 [configloc]: ./tech_overview.wiki#configloc
 
498
--- www/env-opts.md
+++ www/env-opts.md
@@ -148,10 +148,15 @@
148
149
150 `FOSSIL_HOME`: Location of [configuration database][configdb].
151 See the [configuration database location][configloc] description
152 for additional information.
153
154 `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155 loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156 none of the listed repositories has the `repolist_skin` property set.
157 Can be set from the [CGI control file][cgictlfile].
158
159 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
160 SEE as text to be hashed into the actual encryption key. This has no
161 effect if Fossil was not compiled with SEE support enabled.
162
@@ -493,5 +498,6 @@
498 On Windows platforms, it assumes that `start` is the command to open
499 a URL in the user's configured default browser.
500
501 [configdb]: ./tech_overview.wiki#configdb
502 [configloc]: ./tech_overview.wiki#configloc
503 [cgictlfile]: ./cgi.wiki
504
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
6161
artifacts:
6262
6363
<ul>
6464
<li> [#manifest | Manifests] </li>
6565
<li> [#cluster | Clusters] </li>
66
-<li> [#ctrl | Control Artifacts] </li>
66
+<li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li>
6767
<li> [#wikichng | Wiki Pages] </li>
6868
<li> [#tktchng | Ticket Changes] </li>
6969
<li> [#attachment | Attachments] </li>
7070
<li> [#event | TechNotes] </li>
7171
<li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276276
prior cards in the cluster. The <b>Z</b> card is required.
277277
278278
An example cluster from Fossil can be seen
279279
[/artifact/d03dbdd73a2a8 | here].
280280
281
-<h3 id="ctrl">2.3 Control Artifacts</h3>
281
+<h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3>
282282
283283
Control artifacts are used to assign properties to other artifacts
284
-within the repository.
285
-Allowed cards in a control artifact are as follows:
284
+within the repository. Allowed cards in a control artifact are as
285
+follows:
286286
287287
<div class="indent">
288288
<b>D</b> <i>time-and-date-stamp</i><br />
289289
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290290
<b>U</b> <i>user-name</i><br />
291291
<b>Z</b> <i>checksum</i><br />
292292
</div>
293
+
294
+Control articles are also referred to as Tag artifacts, but tags can
295
+also be applied via other artifact types, as described in
296
+[#summary|the Card Summary table].
293297
294298
A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
295299
one or more <b>T</b> cards. No other cards or other text is
296300
allowed in a control artifact. Control artifacts might be PGP
297301
clearsigned.
298302
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
61 artifacts:
62
63 <ul>
64 <li> [#manifest | Manifests] </li>
65 <li> [#cluster | Clusters] </li>
66 <li> [#ctrl | Control Artifacts] </li>
67 <li> [#wikichng | Wiki Pages] </li>
68 <li> [#tktchng | Ticket Changes] </li>
69 <li> [#attachment | Attachments] </li>
70 <li> [#event | TechNotes] </li>
71 <li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276 prior cards in the cluster. The <b>Z</b> card is required.
277
278 An example cluster from Fossil can be seen
279 [/artifact/d03dbdd73a2a8 | here].
280
281 <h3 id="ctrl">2.3 Control Artifacts</h3>
282
283 Control artifacts are used to assign properties to other artifacts
284 within the repository.
285 Allowed cards in a control artifact are as follows:
286
287 <div class="indent">
288 <b>D</b> <i>time-and-date-stamp</i><br />
289 <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290 <b>U</b> <i>user-name</i><br />
291 <b>Z</b> <i>checksum</i><br />
292 </div>
 
 
 
 
293
294 A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
295 one or more <b>T</b> cards. No other cards or other text is
296 allowed in a control artifact. Control artifacts might be PGP
297 clearsigned.
298
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -61,11 +61,11 @@
61 artifacts:
62
63 <ul>
64 <li> [#manifest | Manifests] </li>
65 <li> [#cluster | Clusters] </li>
66 <li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li>
67 <li> [#wikichng | Wiki Pages] </li>
68 <li> [#tktchng | Ticket Changes] </li>
69 <li> [#attachment | Attachments] </li>
70 <li> [#event | TechNotes] </li>
71 <li> [#forum | Forum Posts] </li>
@@ -276,22 +276,26 @@
276 prior cards in the cluster. The <b>Z</b> card is required.
277
278 An example cluster from Fossil can be seen
279 [/artifact/d03dbdd73a2a8 | here].
280
281 <h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3>
282
283 Control artifacts are used to assign properties to other artifacts
284 within the repository. Allowed cards in a control artifact are as
285 follows:
286
287 <div class="indent">
288 <b>D</b> <i>time-and-date-stamp</i><br />
289 <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
290 <b>U</b> <i>user-name</i><br />
291 <b>Z</b> <i>checksum</i><br />
292 </div>
293
294 Control articles are also referred to as Tag artifacts, but tags can
295 also be applied via other artifact types, as described in
296 [#summary|the Card Summary table].
297
298 A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
299 one or more <b>T</b> cards. No other cards or other text is
300 allowed in a control artifact. Control artifacts might be PGP
301 clearsigned.
302
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -32,11 +32,11 @@
3232
If the Fossil server is itself run as
3333
[./server/any/cgi.md|CGI], then add a line to the
3434
[./cgi.wiki#extroot|CGI script file] that says:
3535
3636
<pre>
37
-extroot: <i>DIRECTORY</i>
37
+ extroot: <i>DIRECTORY</i>
3838
</pre>
3939
4040
Or, if the Fossil server is being run using the
4141
"[./server/any/none.md|fossil server]" or
4242
"[./server/any/none.md|fossil ui]" or
@@ -45,18 +45,21 @@
4545
4646
The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI.
4747
Files in the DOCUMENT_ROOT are accessed via URLs like this:
4848
4949
<pre>
50
-https://example-project.org/ext/<i>FILENAME</i>
50
+ https://example-project.org/ext/<i>FILENAME</i>
5151
</pre>
5252
5353
In other words, access files in DOCUMENT_ROOT by appending the filename
5454
relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext]
5555
page of the Fossil server.
56
-Files that are readable but not executable are returned as static
57
-content. Files that are executable are run as CGI.
56
+
57
+ * Files that are readable but not executable are returned as static
58
+ content.
59
+
60
+ * Files that are executable are run as CGI.
5861
5962
<h3>2.1 Example #1</h3>
6063
6164
The source code repository for SQLite is a Fossil server that is run
6265
as CGI. The URL for the source code repository is [https://sqlite.org/src].
@@ -117,16 +120,41 @@
117120
of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based
118121
technologies like Wapp.) The fileup1 script is a demo program that lets
119122
the user upload a file using a form, and then displays that file in the reply.
120123
There is a link on the page that causes the fileup1 script to return a copy
121124
of its own source-code, so you can see how it works.
125
+
126
+<h3>2.3 Example #3</h3>
127
+
128
+For Fossil versions dated 2025-03-23 and later, the "--extpage FILENAME"
129
+option to the [/help?cmd=ui|fossil ui] command is a short cut that treats
130
+FILENAME as a CGI extension. When the ui command starts up a new web browser
131
+pages, it points that page to the FILENAME extension. So if FILENAME is
132
+a static content file (such as an HTML file or
133
+[/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the
134
+rendered content of the file is displayed. Meanwhile, the user can be
135
+editing the source text for that document in a separate window, and
136
+periodically pressing "Reload" on the web browser to instantly view the
137
+rendered results.
138
+
139
+For example, the author of this documentation page is running
140
+"<tt>fossil ui --extpage www/serverext.wiki</tt>" while editing this
141
+very paragraph, and presses Reload from time to time to view his
142
+edits.
143
+
144
+A same idea applies when developing new CGI applications using a script
145
+language (for example using [https://wapp.tcl.tk|Wapp]). Run the
146
+command "<tt>fossil ui --extpage SCRIPT</tt>" where SCRIPT is the name
147
+of the application script, while editing that script in a separate
148
+window, then press Reload periodically on the web browser to test the
149
+script.
122150
123151
<h2 id="cgi-inputs">3.0 CGI Inputs</h2>
124152
125153
The /ext extension mechanism is an ordinary CGI interface. Parameters
126154
are passed to the CGI program using environment variables. The following
127
-standard CGI environment variables are supported:
155
+standard CGI environment variables are supplied:
128156
129157
* AUTH_TYPE
130158
* AUTH_CONTENT
131159
* CONTENT_LENGTH
132160
* CONTENT_TYPE
133161
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -32,11 +32,11 @@
32 If the Fossil server is itself run as
33 [./server/any/cgi.md|CGI], then add a line to the
34 [./cgi.wiki#extroot|CGI script file] that says:
35
36 <pre>
37 extroot: <i>DIRECTORY</i>
38 </pre>
39
40 Or, if the Fossil server is being run using the
41 "[./server/any/none.md|fossil server]" or
42 "[./server/any/none.md|fossil ui]" or
@@ -45,18 +45,21 @@
45
46 The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI.
47 Files in the DOCUMENT_ROOT are accessed via URLs like this:
48
49 <pre>
50 https://example-project.org/ext/<i>FILENAME</i>
51 </pre>
52
53 In other words, access files in DOCUMENT_ROOT by appending the filename
54 relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext]
55 page of the Fossil server.
56 Files that are readable but not executable are returned as static
57 content. Files that are executable are run as CGI.
 
 
 
58
59 <h3>2.1 Example #1</h3>
60
61 The source code repository for SQLite is a Fossil server that is run
62 as CGI. The URL for the source code repository is [https://sqlite.org/src].
@@ -117,16 +120,41 @@
117 of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based
118 technologies like Wapp.) The fileup1 script is a demo program that lets
119 the user upload a file using a form, and then displays that file in the reply.
120 There is a link on the page that causes the fileup1 script to return a copy
121 of its own source-code, so you can see how it works.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
123 <h2 id="cgi-inputs">3.0 CGI Inputs</h2>
124
125 The /ext extension mechanism is an ordinary CGI interface. Parameters
126 are passed to the CGI program using environment variables. The following
127 standard CGI environment variables are supported:
128
129 * AUTH_TYPE
130 * AUTH_CONTENT
131 * CONTENT_LENGTH
132 * CONTENT_TYPE
133
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -32,11 +32,11 @@
32 If the Fossil server is itself run as
33 [./server/any/cgi.md|CGI], then add a line to the
34 [./cgi.wiki#extroot|CGI script file] that says:
35
36 <pre>
37 extroot: <i>DIRECTORY</i>
38 </pre>
39
40 Or, if the Fossil server is being run using the
41 "[./server/any/none.md|fossil server]" or
42 "[./server/any/none.md|fossil ui]" or
@@ -45,18 +45,21 @@
45
46 The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI.
47 Files in the DOCUMENT_ROOT are accessed via URLs like this:
48
49 <pre>
50 https://example-project.org/ext/<i>FILENAME</i>
51 </pre>
52
53 In other words, access files in DOCUMENT_ROOT by appending the filename
54 relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext]
55 page of the Fossil server.
56
57 * Files that are readable but not executable are returned as static
58 content.
59
60 * Files that are executable are run as CGI.
61
62 <h3>2.1 Example #1</h3>
63
64 The source code repository for SQLite is a Fossil server that is run
65 as CGI. The URL for the source code repository is [https://sqlite.org/src].
@@ -117,16 +120,41 @@
120 of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based
121 technologies like Wapp.) The fileup1 script is a demo program that lets
122 the user upload a file using a form, and then displays that file in the reply.
123 There is a link on the page that causes the fileup1 script to return a copy
124 of its own source-code, so you can see how it works.
125
126 <h3>2.3 Example #3</h3>
127
128 For Fossil versions dated 2025-03-23 and later, the "--extpage FILENAME"
129 option to the [/help?cmd=ui|fossil ui] command is a short cut that treats
130 FILENAME as a CGI extension. When the ui command starts up a new web browser
131 pages, it points that page to the FILENAME extension. So if FILENAME is
132 a static content file (such as an HTML file or
133 [/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the
134 rendered content of the file is displayed. Meanwhile, the user can be
135 editing the source text for that document in a separate window, and
136 periodically pressing "Reload" on the web browser to instantly view the
137 rendered results.
138
139 For example, the author of this documentation page is running
140 "<tt>fossil ui --extpage www/serverext.wiki</tt>" while editing this
141 very paragraph, and presses Reload from time to time to view his
142 edits.
143
144 A same idea applies when developing new CGI applications using a script
145 language (for example using [https://wapp.tcl.tk|Wapp]). Run the
146 command "<tt>fossil ui --extpage SCRIPT</tt>" where SCRIPT is the name
147 of the application script, while editing that script in a separate
148 window, then press Reload periodically on the web browser to test the
149 script.
150
151 <h2 id="cgi-inputs">3.0 CGI Inputs</h2>
152
153 The /ext extension mechanism is an ordinary CGI interface. Parameters
154 are passed to the CGI program using environment variables. The following
155 standard CGI environment variables are supplied:
156
157 * AUTH_TYPE
158 * AUTH_CONTENT
159 * CONTENT_LENGTH
160 * CONTENT_TYPE
161

Keyboard Shortcuts

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