Fossil SCM
Merge from trunk.
Commit
0ffacb525a925ba8da3152c9041fdf98e67b14f10faf30dc685dea5a8ab4e9c6
Parent
53010e9688857e0…
147 files changed
+1
-1
+826
-980
+2521
-708
+178
-42
+2
-2
+80
-25
+24
-31
+12
-8
+82
-36
+18
+16
-6
+225
+1
-1
+7
-27
+1
-1
+2
-1
+7
-1
+19
-22
+26
-4
+46
-8
+18
-3
+19
-13
+1
-1
+1
+29
-15
+3
-3
+2
-1
+18
-8
+2
-2
+9
-11
+6
-5
+4
-8
+2
-7
+3
-4
-1
+32
+2
-2
+4
-4
+16
-12
+27
-10
+73
-41
+1
-1
+131
-142
+7
-2
+13
+1
-1
+1
-1
+2
-2
+2
-2
+1
-1
+243
-92
+410
+6
-6
+4
-2
+156
-85
+3
+2
-2
+15
-5
+21
-62
+1
-1
+531
-34
+1
-1
+3
-3
+284
-164
+4
+12
-8
+9
+3
+4
-3
+42
-1
+24
-18
-139
+1
-1
+1
-1
-138
+138
+2
-2
+1
+10
-4
+13
+12
+3
-3
+11
-11
+11
-11
+147
-31
+5
-5
+2
-2
+7
-7
+6
-6
+5
-5
+2
-2
+6
-6
+1
-1
+3
-3
+2
-2
+409
-345
+8
-8
+1
-1
+1
-1
+1
-1
+2
-2
+4
-4
+1
-2
+1
-1
+4
-4
+6
-6
+5
-5
+2
-2
+1
-1
+1
-1
+1
-1
+1
-1
+9
-9
+22
-22
+24
-24
+18
-18
+5
+2
-2
+1
-1
+7
-7
+4
-4
+1
-1
+2
-2
+4
-4
+1
-1
+1
-1
+4
-4
+1
-1
+1
-1
+3
-3
+1
-1
+1
-1
+1
-1
+5
-5
+1
-1
+1
-1
+1
-1
+7
-7
+2
-2
+2
-2
+8
-8
+7
-7
+1
-1
+1
-1
+6
-6
~
VERSION
~
extsrc/shell.c
~
extsrc/sqlite3.c
~
extsrc/sqlite3.h
~
src/add.c
~
src/branch.c
~
src/browse.c
~
src/captcha.c
~
src/cgi.c
~
src/chat.c
~
src/checkin.c
~
src/checkout.c
~
src/clearsign.c
~
src/clone.c
~
src/comformat.c
~
src/content.c
~
src/cookies.c
~
src/copybtn.js
~
src/db.c
~
src/default.css
~
src/deltafunc.c
~
src/diff.c
~
src/diff.tcl
~
src/diffcmd.c
~
src/dispatch.c
~
src/doc.c
~
src/extcgi.c
~
src/finfo.c
~
src/forum.c
~
src/fossil.copybutton.js
~
src/fossil.dom.js
~
src/fossil.numbered-lines.js
~
src/fossil.page.chat.js
~
src/fossil.page.pikchrshow.js
~
src/fossil.page.pikchrshowasm.js
~
src/fossil.page.ticket.js
~
src/fossil.page.wikiedit.js
~
src/graph.js
~
src/href.js
~
src/http.c
~
src/info.c
~
src/json.c
~
src/login.c
~
src/main.c
~
src/main.mk
~
src/manifest.c
~
src/markdown.md
~
src/match.c
~
src/pikchrshow.c
~
src/printf.c
~
src/regexp.c
~
src/robot.c
~
src/search.c
~
src/security_audit.c
~
src/setup.c
~
src/sitemap.c
~
src/skins.c
~
src/stash.c
~
src/style.c
~
src/tag.c
~
src/tar.c
~
src/th_lang.c
~
src/th_main.c
~
src/timeline.c
~
src/tkt.c
~
src/tktsetup.c
~
src/unversioned.c
~
src/url.c
~
src/user.c
~
src/util.c
~
src/xfer.c
~
src/zip.c
~
test/commit-warning.test
~
test/json.test
~
test/set-manifest.test
-
test/settings.test
~
test/settings.test.off
+
test/settings.test.off
~
tools/makeheaders.c
~
tools/makemake.tcl
~
win/Makefile.dmc
~
win/Makefile.mingw
~
win/Makefile.msc
~
www/aboutcgi.wiki
~
www/aboutdownload.wiki
~
www/alerts.md
~
www/antibot.wiki
~
www/backoffice.md
~
www/backup.md
~
www/blame.wiki
~
www/blockchain.md
~
www/branching.wiki
~
www/build.wiki
~
www/caps/admin-v-setup.md
~
www/caps/login-groups.md
~
www/caps/ref.html
~
www/cgi.wiki
~
www/changes.wiki
~
www/chat.md
~
www/childprojects.wiki
~
www/chroot.md
~
www/ckout-workflows.md
~
www/co-vs-up.md
~
www/concepts.wiki
~
www/containers.md
~
www/contribute.wiki
~
www/customskin.md
~
www/defcsp.md
~
www/delta_format.wiki
~
www/embeddeddoc.wiki
~
www/env-opts.md
~
www/event.wiki
~
www/fileedit-page.md
~
www/forum.wiki
~
www/fossil-v-git.wiki
~
www/gitusers.md
~
www/globs.md
~
www/glossary.md
~
www/grep.md
~
www/hashes.md
~
www/hashpolicy.wiki
~
www/hints.wiki
~
www/index.wiki
~
www/inout.wiki
~
www/interwiki.md
~
www/javascript.md
~
www/json-api/intro.md
~
www/loadmgmt.md
~
www/makefile.wiki
~
www/mdtest/test1.md
~
www/mirrorlimitations.md
~
www/mirrortogithub.md
~
www/password.wiki
~
www/patchcmd.md
~
www/private.wiki
~
www/quickstart.wiki
~
www/rebaseharm.md
~
www/relatedwork.md
~
www/selfhost.wiki
~
www/server/index.html
~
www/server/windows/service.md
~
www/serverext.wiki
~
www/ssl-server.md
~
www/sync.wiki
~
www/tech_overview.wiki
~
www/th1.md
~
www/unvers.wiki
M
VERSION
+1
-1
| --- VERSION | ||
| +++ VERSION | ||
| @@ -1,1 +1,1 @@ | ||
| 1 | -2.27 | |
| 1 | +2.28 | |
| 2 | 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.27 |
| 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.28 |
| 2 |
+826
-980
| --- extsrc/shell.c | ||
| +++ extsrc/shell.c | ||
| @@ -122,10 +122,13 @@ | ||
| 122 | 122 | typedef sqlite3_int64 i64; |
| 123 | 123 | typedef sqlite3_uint64 u64; |
| 124 | 124 | typedef unsigned char u8; |
| 125 | 125 | #include <ctype.h> |
| 126 | 126 | #include <stdarg.h> |
| 127 | +#ifndef _WIN32 | |
| 128 | +# include <sys/time.h> | |
| 129 | +#endif | |
| 127 | 130 | |
| 128 | 131 | #if !defined(_WIN32) && !defined(WIN32) |
| 129 | 132 | # include <signal.h> |
| 130 | 133 | # if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) |
| 131 | 134 | # include <pwd.h> |
| @@ -646,12 +649,23 @@ | ||
| 646 | 649 | if( a==0 ) a = ""; |
| 647 | 650 | if( b==0 ) b = ""; |
| 648 | 651 | return strncmp(a,b,n); |
| 649 | 652 | } |
| 650 | 653 | |
| 651 | -/* Return the current wall-clock time */ | |
| 654 | +/* Return the current wall-clock time in microseconds since the | |
| 655 | +** Unix epoch (1970-01-01T00:00:00Z) | |
| 656 | +*/ | |
| 652 | 657 | static sqlite3_int64 timeOfDay(void){ |
| 658 | +#if defined(_WIN64) | |
| 659 | + sqlite3_uint64 t; | |
| 660 | + FILETIME tm; | |
| 661 | + GetSystemTimePreciseAsFileTime(&tm); | |
| 662 | + t = ((u64)tm.dwHighDateTime<<32) | (u64)tm.dwLowDateTime; | |
| 663 | + t += 116444736000000000LL; | |
| 664 | + t /= 10; | |
| 665 | + return t; | |
| 666 | +#elif defined(_WIN32) | |
| 653 | 667 | static sqlite3_vfs *clockVfs = 0; |
| 654 | 668 | sqlite3_int64 t; |
| 655 | 669 | if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); |
| 656 | 670 | if( clockVfs==0 ) return 0; /* Never actually happens */ |
| 657 | 671 | if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ |
| @@ -659,11 +673,16 @@ | ||
| 659 | 673 | }else{ |
| 660 | 674 | double r; |
| 661 | 675 | clockVfs->xCurrentTime(clockVfs, &r); |
| 662 | 676 | t = (sqlite3_int64)(r*86400000.0); |
| 663 | 677 | } |
| 664 | - return t; | |
| 678 | + return t*1000; | |
| 679 | +#else | |
| 680 | + struct timeval sNow; | |
| 681 | + (void)gettimeofday(&sNow,0); | |
| 682 | + return ((i64)sNow.tv_sec)*1000000 + sNow.tv_usec; | |
| 683 | +#endif | |
| 665 | 684 | } |
| 666 | 685 | |
| 667 | 686 | #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) |
| 668 | 687 | #include <sys/time.h> |
| 669 | 688 | #include <sys/resource.h> |
| @@ -704,12 +723,12 @@ | ||
| 704 | 723 | static void endTimer(FILE *out){ |
| 705 | 724 | if( enableTimer ){ |
| 706 | 725 | sqlite3_int64 iEnd = timeOfDay(); |
| 707 | 726 | struct rusage sEnd; |
| 708 | 727 | getrusage(RUSAGE_SELF, &sEnd); |
| 709 | - sqlite3_fprintf(out, "Run Time: real %.3f user %f sys %f\n", | |
| 710 | - (iEnd - iBegin)*0.001, | |
| 728 | + sqlite3_fprintf(out, "Run Time: real %.6f user %.6f sys %.6f\n", | |
| 729 | + (iEnd - iBegin)*0.000001, | |
| 711 | 730 | timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), |
| 712 | 731 | timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); |
| 713 | 732 | } |
| 714 | 733 | } |
| 715 | 734 | |
| @@ -783,14 +802,23 @@ | ||
| 783 | 802 | static void endTimer(FILE *out){ |
| 784 | 803 | if( enableTimer && getProcessTimesAddr){ |
| 785 | 804 | FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; |
| 786 | 805 | sqlite3_int64 ftWallEnd = timeOfDay(); |
| 787 | 806 | getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); |
| 788 | - sqlite3_fprintf(out, "Run Time: real %.3f user %f sys %f\n", | |
| 789 | - (ftWallEnd - ftWallBegin)*0.001, | |
| 807 | +#ifdef _WIN64 | |
| 808 | + /* microsecond precision on 64-bit windows */ | |
| 809 | + sqlite3_fprintf(out, "Run Time: real %.6f user %f sys %f\n", | |
| 810 | + (ftWallEnd - ftWallBegin)*0.000001, | |
| 811 | + timeDiff(&ftUserBegin, &ftUserEnd), | |
| 812 | + timeDiff(&ftKernelBegin, &ftKernelEnd)); | |
| 813 | +#else | |
| 814 | + /* millisecond precisino on 32-bit windows */ | |
| 815 | + sqlite3_fprintf(out, "Run Time: real %.3f user %.3f sys %.3f\n", | |
| 816 | + (ftWallEnd - ftWallBegin)*0.000001, | |
| 790 | 817 | timeDiff(&ftUserBegin, &ftUserEnd), |
| 791 | 818 | timeDiff(&ftKernelBegin, &ftKernelEnd)); |
| 819 | +#endif | |
| 792 | 820 | } |
| 793 | 821 | } |
| 794 | 822 | |
| 795 | 823 | #define BEGIN_TIMER beginTimer() |
| 796 | 824 | #define END_TIMER(X) endTimer(X) |
| @@ -1126,11 +1154,11 @@ | ||
| 1126 | 1154 | } |
| 1127 | 1155 | if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 |
| 1128 | 1156 | && (z[3] & 0xc0)==0x80 |
| 1129 | 1157 | ){ |
| 1130 | 1158 | *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6 |
| 1131 | - | (z[4] & 0x3f); | |
| 1159 | + | (z[3] & 0x3f); | |
| 1132 | 1160 | return 4; |
| 1133 | 1161 | } |
| 1134 | 1162 | *pU = 0; |
| 1135 | 1163 | return 1; |
| 1136 | 1164 | } |
| @@ -1189,18 +1217,28 @@ | ||
| 1189 | 1217 | ** since with %*.*s the width is measured in bytes, not characters. |
| 1190 | 1218 | ** |
| 1191 | 1219 | ** Take into account zero-width and double-width Unicode characters. |
| 1192 | 1220 | ** In other words, a zero-width character does not count toward the |
| 1193 | 1221 | ** the w limit. A double-width character counts as two. |
| 1222 | +** | |
| 1223 | +** w should normally be a small number. A couple hundred at most. This | |
| 1224 | +** routine caps w at 100 million to avoid integer overflow issues. | |
| 1194 | 1225 | */ |
| 1195 | 1226 | static void utf8_width_print(FILE *out, int w, const char *zUtf){ |
| 1196 | 1227 | const unsigned char *a = (const unsigned char*)zUtf; |
| 1228 | + static const int mxW = 10000000; | |
| 1197 | 1229 | unsigned char c; |
| 1198 | 1230 | int i = 0; |
| 1199 | 1231 | int n = 0; |
| 1200 | 1232 | int k; |
| 1201 | - int aw = w<0 ? -w : w; | |
| 1233 | + int aw; | |
| 1234 | + if( w<-mxW ){ | |
| 1235 | + w = -mxW; | |
| 1236 | + }else if( w>mxW ){ | |
| 1237 | + w= mxW; | |
| 1238 | + } | |
| 1239 | + aw = w<0 ? -w : w; | |
| 1202 | 1240 | if( zUtf==0 ) zUtf = ""; |
| 1203 | 1241 | while( (c = a[i])!=0 ){ |
| 1204 | 1242 | if( (c&0xc0)==0xc0 ){ |
| 1205 | 1243 | int u; |
| 1206 | 1244 | int len = decodeUtf8(a+i, &u); |
| @@ -1323,11 +1361,11 @@ | ||
| 1323 | 1361 | |
| 1324 | 1362 | /* |
| 1325 | 1363 | ** This routine reads a line of text from FILE in, stores |
| 1326 | 1364 | ** the text in memory obtained from malloc() and returns a pointer |
| 1327 | 1365 | ** to the text. NULL is returned at end of file, or if malloc() |
| 1328 | -** fails. | |
| 1366 | +** fails, or if the length of the line is longer than about a gigabyte. | |
| 1329 | 1367 | ** |
| 1330 | 1368 | ** If zLine is not NULL then it is a malloced buffer returned from |
| 1331 | 1369 | ** a previous call to this routine that may be reused. |
| 1332 | 1370 | */ |
| 1333 | 1371 | static char *local_getline(char *zLine, FILE *in){ |
| @@ -1334,10 +1372,14 @@ | ||
| 1334 | 1372 | int nLine = zLine==0 ? 0 : 100; |
| 1335 | 1373 | int n = 0; |
| 1336 | 1374 | |
| 1337 | 1375 | while( 1 ){ |
| 1338 | 1376 | if( n+100>nLine ){ |
| 1377 | + if( nLine>=1073741773 ){ | |
| 1378 | + free(zLine); | |
| 1379 | + return 0; | |
| 1380 | + } | |
| 1339 | 1381 | nLine = nLine*2 + 100; |
| 1340 | 1382 | zLine = realloc(zLine, nLine); |
| 1341 | 1383 | shell_check_oom(zLine); |
| 1342 | 1384 | } |
| 1343 | 1385 | if( sqlite3_fgets(&zLine[n], nLine - n, in)==0 ){ |
| @@ -1417,14 +1459,18 @@ | ||
| 1417 | 1459 | return -1; |
| 1418 | 1460 | } |
| 1419 | 1461 | |
| 1420 | 1462 | /* |
| 1421 | 1463 | ** Interpret zArg as an integer value, possibly with suffixes. |
| 1464 | +** | |
| 1465 | +** If the value specified by zArg is outside the range of values that | |
| 1466 | +** can be represented using a 64-bit twos-complement integer, then return | |
| 1467 | +** the nearest representable value. | |
| 1422 | 1468 | */ |
| 1423 | 1469 | static sqlite3_int64 integerValue(const char *zArg){ |
| 1424 | - sqlite3_int64 v = 0; | |
| 1425 | - static const struct { char *zSuffix; int iMult; } aMult[] = { | |
| 1470 | + sqlite3_uint64 v = 0; | |
| 1471 | + static const struct { char *zSuffix; unsigned int iMult; } aMult[] = { | |
| 1426 | 1472 | { "KiB", 1024 }, |
| 1427 | 1473 | { "MiB", 1024*1024 }, |
| 1428 | 1474 | { "GiB", 1024*1024*1024 }, |
| 1429 | 1475 | { "KB", 1000 }, |
| 1430 | 1476 | { "MB", 1000000 }, |
| @@ -1443,46 +1489,54 @@ | ||
| 1443 | 1489 | } |
| 1444 | 1490 | if( zArg[0]=='0' && zArg[1]=='x' ){ |
| 1445 | 1491 | int x; |
| 1446 | 1492 | zArg += 2; |
| 1447 | 1493 | while( (x = hexDigitValue(zArg[0]))>=0 ){ |
| 1494 | + if( v > 0x0fffffffffffffffULL ) goto integer_overflow; | |
| 1448 | 1495 | v = (v<<4) + x; |
| 1449 | 1496 | zArg++; |
| 1450 | 1497 | } |
| 1451 | 1498 | }else{ |
| 1452 | 1499 | while( IsDigit(zArg[0]) ){ |
| 1453 | - v = v*10 + zArg[0] - '0'; | |
| 1500 | + if( v>=922337203685477580LL ){ | |
| 1501 | + if( v>922337203685477580LL || zArg[0]>='8' ) goto integer_overflow; | |
| 1502 | + } | |
| 1503 | + v = v*10 + (zArg[0] - '0'); | |
| 1454 | 1504 | zArg++; |
| 1455 | 1505 | } |
| 1456 | 1506 | } |
| 1457 | 1507 | for(i=0; i<ArraySize(aMult); i++){ |
| 1458 | 1508 | if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){ |
| 1509 | + if( 0x7fffffffffffffffULL/aMult[i].iMult < v ) goto integer_overflow; | |
| 1459 | 1510 | v *= aMult[i].iMult; |
| 1460 | 1511 | break; |
| 1461 | 1512 | } |
| 1462 | 1513 | } |
| 1463 | - return isNeg? -v : v; | |
| 1514 | + if( isNeg && v>0x7fffffffffffffffULL ) goto integer_overflow; | |
| 1515 | + return isNeg? -(sqlite3_int64)v : (sqlite3_int64)v; | |
| 1516 | +integer_overflow: | |
| 1517 | + return isNeg ? (i64)0x8000000000000000LL : 0x7fffffffffffffffLL; | |
| 1464 | 1518 | } |
| 1465 | 1519 | |
| 1466 | 1520 | /* |
| 1467 | 1521 | ** A variable length string to which one can append text. |
| 1468 | 1522 | */ |
| 1469 | 1523 | typedef struct ShellText ShellText; |
| 1470 | 1524 | struct ShellText { |
| 1471 | - char *z; | |
| 1472 | - int n; | |
| 1473 | - int nAlloc; | |
| 1525 | + char *zTxt; /* The text */ | |
| 1526 | + i64 n; /* Number of bytes of zTxt[] actually used */ | |
| 1527 | + i64 nAlloc; /* Number of bytes allocated for zTxt[] */ | |
| 1474 | 1528 | }; |
| 1475 | 1529 | |
| 1476 | 1530 | /* |
| 1477 | 1531 | ** Initialize and destroy a ShellText object |
| 1478 | 1532 | */ |
| 1479 | 1533 | static void initText(ShellText *p){ |
| 1480 | 1534 | memset(p, 0, sizeof(*p)); |
| 1481 | 1535 | } |
| 1482 | 1536 | static void freeText(ShellText *p){ |
| 1483 | - free(p->z); | |
| 1537 | + sqlite3_free(p->zTxt); | |
| 1484 | 1538 | initText(p); |
| 1485 | 1539 | } |
| 1486 | 1540 | |
| 1487 | 1541 | /* zIn is either a pointer to a NULL-terminated string in memory obtained |
| 1488 | 1542 | ** from malloc(), or a NULL pointer. The string pointed to by zAppend is |
| @@ -1503,30 +1557,30 @@ | ||
| 1503 | 1557 | for(i=0; i<nAppend; i++){ |
| 1504 | 1558 | if( zAppend[i]==quote ) len++; |
| 1505 | 1559 | } |
| 1506 | 1560 | } |
| 1507 | 1561 | |
| 1508 | - if( p->z==0 || p->n+len>=p->nAlloc ){ | |
| 1562 | + if( p->zTxt==0 || p->n+len>=p->nAlloc ){ | |
| 1509 | 1563 | p->nAlloc = p->nAlloc*2 + len + 20; |
| 1510 | - p->z = realloc(p->z, p->nAlloc); | |
| 1511 | - shell_check_oom(p->z); | |
| 1564 | + p->zTxt = sqlite3_realloc64(p->zTxt, p->nAlloc); | |
| 1565 | + shell_check_oom(p->zTxt); | |
| 1512 | 1566 | } |
| 1513 | 1567 | |
| 1514 | 1568 | if( quote ){ |
| 1515 | - char *zCsr = p->z+p->n; | |
| 1569 | + char *zCsr = p->zTxt+p->n; | |
| 1516 | 1570 | *zCsr++ = quote; |
| 1517 | 1571 | for(i=0; i<nAppend; i++){ |
| 1518 | 1572 | *zCsr++ = zAppend[i]; |
| 1519 | 1573 | if( zAppend[i]==quote ) *zCsr++ = quote; |
| 1520 | 1574 | } |
| 1521 | 1575 | *zCsr++ = quote; |
| 1522 | - p->n = (int)(zCsr - p->z); | |
| 1576 | + p->n = (i64)(zCsr - p->zTxt); | |
| 1523 | 1577 | *zCsr = '\0'; |
| 1524 | 1578 | }else{ |
| 1525 | - memcpy(p->z+p->n, zAppend, nAppend); | |
| 1579 | + memcpy(p->zTxt+p->n, zAppend, nAppend); | |
| 1526 | 1580 | p->n += nAppend; |
| 1527 | - p->z[p->n] = '\0'; | |
| 1581 | + p->zTxt[p->n] = '\0'; | |
| 1528 | 1582 | } |
| 1529 | 1583 | } |
| 1530 | 1584 | |
| 1531 | 1585 | /* |
| 1532 | 1586 | ** Attempt to determine if identifier zName needs to be quoted, either |
| @@ -1547,10 +1601,13 @@ | ||
| 1547 | 1601 | } |
| 1548 | 1602 | |
| 1549 | 1603 | /* |
| 1550 | 1604 | ** Construct a fake object name and column list to describe the structure |
| 1551 | 1605 | ** of the view, virtual table, or table valued function zSchema.zName. |
| 1606 | +** | |
| 1607 | +** The returned string comes from sqlite3_mprintf() and should be freed | |
| 1608 | +** by the caller using sqlite3_free(). | |
| 1552 | 1609 | */ |
| 1553 | 1610 | static char *shellFakeSchema( |
| 1554 | 1611 | sqlite3 *db, /* The database connection containing the vtab */ |
| 1555 | 1612 | const char *zSchema, /* Schema of the database holding the vtab */ |
| 1556 | 1613 | const char *zName /* The name of the virtual table */ |
| @@ -1587,13 +1644,13 @@ | ||
| 1587 | 1644 | } |
| 1588 | 1645 | appendText(&s, ")", 0); |
| 1589 | 1646 | sqlite3_finalize(pStmt); |
| 1590 | 1647 | if( nRow==0 ){ |
| 1591 | 1648 | freeText(&s); |
| 1592 | - s.z = 0; | |
| 1649 | + s.zTxt = 0; | |
| 1593 | 1650 | } |
| 1594 | - return s.z; | |
| 1651 | + return s.zTxt; | |
| 1595 | 1652 | } |
| 1596 | 1653 | |
| 1597 | 1654 | /* |
| 1598 | 1655 | ** SQL function: strtod(X) |
| 1599 | 1656 | ** |
| @@ -1692,11 +1749,11 @@ | ||
| 1692 | 1749 | if( z==0 ){ |
| 1693 | 1750 | z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); |
| 1694 | 1751 | }else{ |
| 1695 | 1752 | z = sqlite3_mprintf("%z\n/* %s */", z, zFake); |
| 1696 | 1753 | } |
| 1697 | - free(zFake); | |
| 1754 | + sqlite3_free(zFake); | |
| 1698 | 1755 | } |
| 1699 | 1756 | if( z ){ |
| 1700 | 1757 | sqlite3_result_text(pCtx, z, -1, sqlite3_free); |
| 1701 | 1758 | return; |
| 1702 | 1759 | } |
| @@ -3667,11 +3724,12 @@ | ||
| 3667 | 3724 | iExp -= p->nFrac; |
| 3668 | 3725 | p->nFrac = 0; |
| 3669 | 3726 | } |
| 3670 | 3727 | } |
| 3671 | 3728 | if( iExp>0 ){ |
| 3672 | - p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); | |
| 3729 | + p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit | |
| 3730 | + + (sqlite3_int64)iExp + 1 ); | |
| 3673 | 3731 | if( p->a==0 ) goto new_from_text_failed; |
| 3674 | 3732 | memset(p->a+p->nDigit, 0, iExp); |
| 3675 | 3733 | p->nDigit += iExp; |
| 3676 | 3734 | } |
| 3677 | 3735 | }else if( iExp<0 ){ |
| @@ -3686,18 +3744,23 @@ | ||
| 3686 | 3744 | iExp -= nExtra; |
| 3687 | 3745 | p->nFrac = p->nDigit - 1; |
| 3688 | 3746 | } |
| 3689 | 3747 | } |
| 3690 | 3748 | if( iExp>0 ){ |
| 3691 | - p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); | |
| 3749 | + p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit | |
| 3750 | + + (sqlite3_int64)iExp + 1 ); | |
| 3692 | 3751 | if( p->a==0 ) goto new_from_text_failed; |
| 3693 | 3752 | memmove(p->a+iExp, p->a, p->nDigit); |
| 3694 | 3753 | memset(p->a, 0, iExp); |
| 3695 | 3754 | p->nDigit += iExp; |
| 3696 | 3755 | p->nFrac += iExp; |
| 3697 | 3756 | } |
| 3698 | 3757 | } |
| 3758 | + if( p->sign ){ | |
| 3759 | + for(i=0; i<p->nDigit && p->a[i]==0; i++){} | |
| 3760 | + if( i>=p->nDigit ) p->sign = 0; | |
| 3761 | + } | |
| 3699 | 3762 | return p; |
| 3700 | 3763 | |
| 3701 | 3764 | new_from_text_failed: |
| 3702 | 3765 | if( p ){ |
| 3703 | 3766 | if( p->a ) sqlite3_free(p->a); |
| @@ -3786,11 +3849,11 @@ | ||
| 3786 | 3849 | } |
| 3787 | 3850 | if( p->isNull ){ |
| 3788 | 3851 | sqlite3_result_null(pCtx); |
| 3789 | 3852 | return; |
| 3790 | 3853 | } |
| 3791 | - z = sqlite3_malloc( p->nDigit+4 ); | |
| 3854 | + z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+4 ); | |
| 3792 | 3855 | if( z==0 ){ |
| 3793 | 3856 | sqlite3_result_error_nomem(pCtx); |
| 3794 | 3857 | return; |
| 3795 | 3858 | } |
| 3796 | 3859 | i = 0; |
| @@ -3851,11 +3914,11 @@ | ||
| 3851 | 3914 | } |
| 3852 | 3915 | for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} |
| 3853 | 3916 | for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){} |
| 3854 | 3917 | nFrac = p->nFrac + (nDigit - p->nDigit); |
| 3855 | 3918 | nDigit -= nZero; |
| 3856 | - z = sqlite3_malloc( nDigit+20 ); | |
| 3919 | + z = sqlite3_malloc64( (sqlite3_int64)nDigit+20 ); | |
| 3857 | 3920 | if( z==0 ){ |
| 3858 | 3921 | sqlite3_result_error_nomem(pCtx); |
| 3859 | 3922 | return; |
| 3860 | 3923 | } |
| 3861 | 3924 | if( nDigit==0 ){ |
| @@ -3896,17 +3959,25 @@ | ||
| 3896 | 3959 | ** pA!=0 |
| 3897 | 3960 | ** pA->isNull==0 |
| 3898 | 3961 | ** pB!=0 |
| 3899 | 3962 | ** pB->isNull==0 |
| 3900 | 3963 | */ |
| 3901 | -static int decimal_cmp(const Decimal *pA, const Decimal *pB){ | |
| 3964 | +static int decimal_cmp(Decimal *pA, Decimal *pB){ | |
| 3902 | 3965 | int nASig, nBSig, rc, n; |
| 3966 | + while( pA->nFrac>0 && pA->a[pA->nDigit-1]==0 ){ | |
| 3967 | + pA->nDigit--; | |
| 3968 | + pA->nFrac--; | |
| 3969 | + } | |
| 3970 | + while( pB->nFrac>0 && pB->a[pB->nDigit-1]==0 ){ | |
| 3971 | + pB->nDigit--; | |
| 3972 | + pB->nFrac--; | |
| 3973 | + } | |
| 3903 | 3974 | if( pA->sign!=pB->sign ){ |
| 3904 | 3975 | return pA->sign ? -1 : +1; |
| 3905 | 3976 | } |
| 3906 | 3977 | if( pA->sign ){ |
| 3907 | - const Decimal *pTemp = pA; | |
| 3978 | + Decimal *pTemp = pA; | |
| 3908 | 3979 | pA = pB; |
| 3909 | 3980 | pB = pTemp; |
| 3910 | 3981 | } |
| 3911 | 3982 | nASig = pA->nDigit - pA->nFrac; |
| 3912 | 3983 | nBSig = pB->nDigit - pB->nFrac; |
| @@ -4064,11 +4135,12 @@ | ||
| 4064 | 4135 | if( pA==0 || pA->oom || pA->isNull |
| 4065 | 4136 | || pB==0 || pB->oom || pB->isNull |
| 4066 | 4137 | ){ |
| 4067 | 4138 | goto mul_end; |
| 4068 | 4139 | } |
| 4069 | - acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); | |
| 4140 | + acc = sqlite3_malloc64( (sqlite3_int64)pA->nDigit + | |
| 4141 | + (sqlite3_int64)pB->nDigit + 2 ); | |
| 4070 | 4142 | if( acc==0 ){ |
| 4071 | 4143 | pA->oom = 1; |
| 4072 | 4144 | goto mul_end; |
| 4073 | 4145 | } |
| 4074 | 4146 | memset(acc, 0, pA->nDigit + pB->nDigit + 2); |
| @@ -4151,11 +4223,11 @@ | ||
| 4151 | 4223 | r = -r; |
| 4152 | 4224 | }else{ |
| 4153 | 4225 | isNeg = 0; |
| 4154 | 4226 | } |
| 4155 | 4227 | memcpy(&a,&r,sizeof(a)); |
| 4156 | - if( a==0 ){ | |
| 4228 | + if( a==0 || a==(sqlite3_int64)0x8000000000000000LL){ | |
| 4157 | 4229 | e = 0; |
| 4158 | 4230 | m = 0; |
| 4159 | 4231 | }else{ |
| 4160 | 4232 | e = a>>52; |
| 4161 | 4233 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| @@ -4426,516 +4498,10 @@ | ||
| 4426 | 4498 | } |
| 4427 | 4499 | return rc; |
| 4428 | 4500 | } |
| 4429 | 4501 | |
| 4430 | 4502 | /************************* End ../ext/misc/decimal.c ********************/ |
| 4431 | -/************************* Begin ../ext/misc/percentile.c ******************/ | |
| 4432 | -/* | |
| 4433 | -** 2013-05-28 | |
| 4434 | -** | |
| 4435 | -** The author disclaims copyright to this source code. In place of | |
| 4436 | -** a legal notice, here is a blessing: | |
| 4437 | -** | |
| 4438 | -** May you do good and not evil. | |
| 4439 | -** May you find forgiveness for yourself and forgive others. | |
| 4440 | -** May you share freely, never taking more than you give. | |
| 4441 | -** | |
| 4442 | -****************************************************************************** | |
| 4443 | -** | |
| 4444 | -** This file contains code to implement the percentile(Y,P) SQL function | |
| 4445 | -** and similar as described below: | |
| 4446 | -** | |
| 4447 | -** (1) The percentile(Y,P) function is an aggregate function taking | |
| 4448 | -** exactly two arguments. | |
| 4449 | -** | |
| 4450 | -** (2) If the P argument to percentile(Y,P) is not the same for every | |
| 4451 | -** row in the aggregate then an error is thrown. The word "same" | |
| 4452 | -** in the previous sentence means that the value differ by less | |
| 4453 | -** than 0.001. | |
| 4454 | -** | |
| 4455 | -** (3) If the P argument to percentile(Y,P) evaluates to anything other | |
| 4456 | -** than a number in the range of 0.0 to 100.0 inclusive then an | |
| 4457 | -** error is thrown. | |
| 4458 | -** | |
| 4459 | -** (4) If any Y argument to percentile(Y,P) evaluates to a value that | |
| 4460 | -** is not NULL and is not numeric then an error is thrown. | |
| 4461 | -** | |
| 4462 | -** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus | |
| 4463 | -** infinity then an error is thrown. (SQLite always interprets NaN | |
| 4464 | -** values as NULL.) | |
| 4465 | -** | |
| 4466 | -** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, | |
| 4467 | -** including CASE WHEN expressions. | |
| 4468 | -** | |
| 4469 | -** (7) The percentile(Y,P) aggregate is able to handle inputs of at least | |
| 4470 | -** one million (1,000,000) rows. | |
| 4471 | -** | |
| 4472 | -** (8) If there are no non-NULL values for Y, then percentile(Y,P) | |
| 4473 | -** returns NULL. | |
| 4474 | -** | |
| 4475 | -** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) | |
| 4476 | -** returns the one Y value. | |
| 4477 | -** | |
| 4478 | -** (10) If there N non-NULL values of Y where N is two or more and | |
| 4479 | -** the Y values are ordered from least to greatest and a graph is | |
| 4480 | -** drawn from 0 to N-1 such that the height of the graph at J is | |
| 4481 | -** the J-th Y value and such that straight lines are drawn between | |
| 4482 | -** adjacent Y values, then the percentile(Y,P) function returns | |
| 4483 | -** the height of the graph at P*(N-1)/100. | |
| 4484 | -** | |
| 4485 | -** (11) The percentile(Y,P) function always returns either a floating | |
| 4486 | -** point number or NULL. | |
| 4487 | -** | |
| 4488 | -** (12) The percentile(Y,P) is implemented as a single C99 source-code | |
| 4489 | -** file that compiles into a shared-library or DLL that can be loaded | |
| 4490 | -** into SQLite using the sqlite3_load_extension() interface. | |
| 4491 | -** | |
| 4492 | -** (13) A separate median(Y) function is the equivalent percentile(Y,50). | |
| 4493 | -** | |
| 4494 | -** (14) A separate percentile_cont(Y,P) function is equivalent to | |
| 4495 | -** percentile(Y,P/100.0). In other words, the fraction value in | |
| 4496 | -** the second argument is in the range of 0 to 1 instead of 0 to 100. | |
| 4497 | -** | |
| 4498 | -** (15) A separate percentile_disc(Y,P) function is like | |
| 4499 | -** percentile_cont(Y,P) except that instead of returning the weighted | |
| 4500 | -** average of the nearest two input values, it returns the next lower | |
| 4501 | -** value. So the percentile_disc(Y,P) will always return a value | |
| 4502 | -** that was one of the inputs. | |
| 4503 | -** | |
| 4504 | -** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and | |
| 4505 | -** percentile_disc(Y,P) can be used as window functions. | |
| 4506 | -** | |
| 4507 | -** Differences from standard SQL: | |
| 4508 | -** | |
| 4509 | -** * The percentile_cont(X,P) function is equivalent to the following in | |
| 4510 | -** standard SQL: | |
| 4511 | -** | |
| 4512 | -** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) | |
| 4513 | -** | |
| 4514 | -** The SQLite syntax is much more compact. The standard SQL syntax | |
| 4515 | -** is also supported if SQLite is compiled with the | |
| 4516 | -** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. | |
| 4517 | -** | |
| 4518 | -** * No median(X) function exists in the SQL standard. App developers | |
| 4519 | -** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". | |
| 4520 | -** | |
| 4521 | -** * No percentile(Y,P) function exists in the SQL standard. Instead of | |
| 4522 | -** percential(Y,P), developers must write this: | |
| 4523 | -** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that | |
| 4524 | -** the fraction parameter to percentile() goes from 0 to 100 whereas | |
| 4525 | -** the fraction parameter in SQL standard percentile_cont() goes from | |
| 4526 | -** 0 to 1. | |
| 4527 | -** | |
| 4528 | -** Implementation notes as of 2024-08-31: | |
| 4529 | -** | |
| 4530 | -** * The regular aggregate-function versions of these routines work | |
| 4531 | -** by accumulating all values in an array of doubles, then sorting | |
| 4532 | -** that array using quicksort before computing the answer. Thus | |
| 4533 | -** the runtime is O(NlogN) where N is the number of rows of input. | |
| 4534 | -** | |
| 4535 | -** * For the window-function versions of these routines, the array of | |
| 4536 | -** inputs is sorted as soon as the first value is computed. Thereafter, | |
| 4537 | -** the array is kept in sorted order using an insert-sort. This | |
| 4538 | -** results in O(N*K) performance where K is the size of the window. | |
| 4539 | -** One can imagine alternative implementations that give O(N*logN*logK) | |
| 4540 | -** performance, but they require more complex logic and data structures. | |
| 4541 | -** The developers have elected to keep the asymptotically slower | |
| 4542 | -** algorithm for now, for simplicity, under the theory that window | |
| 4543 | -** functions are seldom used and when they are, the window size K is | |
| 4544 | -** often small. The developers might revisit that decision later, | |
| 4545 | -** should the need arise. | |
| 4546 | -*/ | |
| 4547 | -#if defined(SQLITE3_H) | |
| 4548 | - /* no-op */ | |
| 4549 | -#elif defined(SQLITE_STATIC_PERCENTILE) | |
| 4550 | -/* # include "sqlite3.h" */ | |
| 4551 | -#else | |
| 4552 | -/* # include "sqlite3ext.h" */ | |
| 4553 | - SQLITE_EXTENSION_INIT1 | |
| 4554 | -#endif | |
| 4555 | -#include <assert.h> | |
| 4556 | -#include <string.h> | |
| 4557 | -#include <stdlib.h> | |
| 4558 | - | |
| 4559 | -/* The following object is the group context for a single percentile() | |
| 4560 | -** aggregate. Remember all input Y values until the very end. | |
| 4561 | -** Those values are accumulated in the Percentile.a[] array. | |
| 4562 | -*/ | |
| 4563 | -typedef struct Percentile Percentile; | |
| 4564 | -struct Percentile { | |
| 4565 | - unsigned nAlloc; /* Number of slots allocated for a[] */ | |
| 4566 | - unsigned nUsed; /* Number of slots actually used in a[] */ | |
| 4567 | - char bSorted; /* True if a[] is already in sorted order */ | |
| 4568 | - char bKeepSorted; /* True if advantageous to keep a[] sorted */ | |
| 4569 | - char bPctValid; /* True if rPct is valid */ | |
| 4570 | - double rPct; /* Fraction. 0.0 to 1.0 */ | |
| 4571 | - double *a; /* Array of Y values */ | |
| 4572 | -}; | |
| 4573 | - | |
| 4574 | -/* Details of each function in the percentile family */ | |
| 4575 | -typedef struct PercentileFunc PercentileFunc; | |
| 4576 | -struct PercentileFunc { | |
| 4577 | - const char *zName; /* Function name */ | |
| 4578 | - char nArg; /* Number of arguments */ | |
| 4579 | - char mxFrac; /* Maximum value of the "fraction" input */ | |
| 4580 | - char bDiscrete; /* True for percentile_disc() */ | |
| 4581 | -}; | |
| 4582 | -static const PercentileFunc aPercentFunc[] = { | |
| 4583 | - { "median", 1, 1, 0 }, | |
| 4584 | - { "percentile", 2, 100, 0 }, | |
| 4585 | - { "percentile_cont", 2, 1, 0 }, | |
| 4586 | - { "percentile_disc", 2, 1, 1 }, | |
| 4587 | -}; | |
| 4588 | - | |
| 4589 | -/* | |
| 4590 | -** Return TRUE if the input floating-point number is an infinity. | |
| 4591 | -*/ | |
| 4592 | -static int percentIsInfinity(double r){ | |
| 4593 | - sqlite3_uint64 u; | |
| 4594 | - assert( sizeof(u)==sizeof(r) ); | |
| 4595 | - memcpy(&u, &r, sizeof(u)); | |
| 4596 | - return ((u>>52)&0x7ff)==0x7ff; | |
| 4597 | -} | |
| 4598 | - | |
| 4599 | -/* | |
| 4600 | -** Return TRUE if two doubles differ by 0.001 or less. | |
| 4601 | -*/ | |
| 4602 | -static int percentSameValue(double a, double b){ | |
| 4603 | - a -= b; | |
| 4604 | - return a>=-0.001 && a<=0.001; | |
| 4605 | -} | |
| 4606 | - | |
| 4607 | -/* | |
| 4608 | -** Search p (which must have p->bSorted) looking for an entry with | |
| 4609 | -** value y. Return the index of that entry. | |
| 4610 | -** | |
| 4611 | -** If bExact is true, return -1 if the entry is not found. | |
| 4612 | -** | |
| 4613 | -** If bExact is false, return the index at which a new entry with | |
| 4614 | -** value y should be insert in order to keep the values in sorted | |
| 4615 | -** order. The smallest return value in this case will be 0, and | |
| 4616 | -** the largest return value will be p->nUsed. | |
| 4617 | -*/ | |
| 4618 | -static int percentBinarySearch(Percentile *p, double y, int bExact){ | |
| 4619 | - int iFirst = 0; /* First element of search range */ | |
| 4620 | - int iLast = p->nUsed - 1; /* Last element of search range */ | |
| 4621 | - while( iLast>=iFirst ){ | |
| 4622 | - int iMid = (iFirst+iLast)/2; | |
| 4623 | - double x = p->a[iMid]; | |
| 4624 | - if( x<y ){ | |
| 4625 | - iFirst = iMid + 1; | |
| 4626 | - }else if( x>y ){ | |
| 4627 | - iLast = iMid - 1; | |
| 4628 | - }else{ | |
| 4629 | - return iMid; | |
| 4630 | - } | |
| 4631 | - } | |
| 4632 | - if( bExact ) return -1; | |
| 4633 | - return iFirst; | |
| 4634 | -} | |
| 4635 | - | |
| 4636 | -/* | |
| 4637 | -** Generate an error for a percentile function. | |
| 4638 | -** | |
| 4639 | -** The error format string must have exactly one occurrence of "%%s()" | |
| 4640 | -** (with two '%' characters). That substring will be replaced by the name | |
| 4641 | -** of the function. | |
| 4642 | -*/ | |
| 4643 | -static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ | |
| 4644 | - PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); | |
| 4645 | - char *zMsg1; | |
| 4646 | - char *zMsg2; | |
| 4647 | - va_list ap; | |
| 4648 | - | |
| 4649 | - va_start(ap, zFormat); | |
| 4650 | - zMsg1 = sqlite3_vmprintf(zFormat, ap); | |
| 4651 | - va_end(ap); | |
| 4652 | - zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, pFunc->zName) : 0; | |
| 4653 | - sqlite3_result_error(pCtx, zMsg2, -1); | |
| 4654 | - sqlite3_free(zMsg1); | |
| 4655 | - sqlite3_free(zMsg2); | |
| 4656 | -} | |
| 4657 | - | |
| 4658 | -/* | |
| 4659 | -** The "step" function for percentile(Y,P) is called once for each | |
| 4660 | -** input row. | |
| 4661 | -*/ | |
| 4662 | -static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ | |
| 4663 | - Percentile *p; | |
| 4664 | - double rPct; | |
| 4665 | - int eType; | |
| 4666 | - double y; | |
| 4667 | - assert( argc==2 || argc==1 ); | |
| 4668 | - | |
| 4669 | - if( argc==1 ){ | |
| 4670 | - /* Requirement 13: median(Y) is the same as percentile(Y,50). */ | |
| 4671 | - rPct = 0.5; | |
| 4672 | - }else{ | |
| 4673 | - /* Requirement 3: P must be a number between 0 and 100 */ | |
| 4674 | - PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); | |
| 4675 | - eType = sqlite3_value_numeric_type(argv[1]); | |
| 4676 | - rPct = sqlite3_value_double(argv[1])/(double)pFunc->mxFrac; | |
| 4677 | - if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) | |
| 4678 | - || rPct<0.0 || rPct>1.0 | |
| 4679 | - ){ | |
| 4680 | - percentError(pCtx, "the fraction argument to %%s()" | |
| 4681 | - " is not between 0.0 and %.1f", | |
| 4682 | - (double)pFunc->mxFrac); | |
| 4683 | - return; | |
| 4684 | - } | |
| 4685 | - } | |
| 4686 | - | |
| 4687 | - /* Allocate the session context. */ | |
| 4688 | - p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); | |
| 4689 | - if( p==0 ) return; | |
| 4690 | - | |
| 4691 | - /* Remember the P value. Throw an error if the P value is different | |
| 4692 | - ** from any prior row, per Requirement (2). */ | |
| 4693 | - if( !p->bPctValid ){ | |
| 4694 | - p->rPct = rPct; | |
| 4695 | - p->bPctValid = 1; | |
| 4696 | - }else if( !percentSameValue(p->rPct,rPct) ){ | |
| 4697 | - percentError(pCtx, "the fraction argument to %%s()" | |
| 4698 | - " is not the same for all input rows"); | |
| 4699 | - return; | |
| 4700 | - } | |
| 4701 | - | |
| 4702 | - /* Ignore rows for which Y is NULL */ | |
| 4703 | - eType = sqlite3_value_type(argv[0]); | |
| 4704 | - if( eType==SQLITE_NULL ) return; | |
| 4705 | - | |
| 4706 | - /* If not NULL, then Y must be numeric. Otherwise throw an error. | |
| 4707 | - ** Requirement 4 */ | |
| 4708 | - if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ | |
| 4709 | - percentError(pCtx, "input to %%s() is not numeric"); | |
| 4710 | - return; | |
| 4711 | - } | |
| 4712 | - | |
| 4713 | - /* Throw an error if the Y value is infinity or NaN */ | |
| 4714 | - y = sqlite3_value_double(argv[0]); | |
| 4715 | - if( percentIsInfinity(y) ){ | |
| 4716 | - percentError(pCtx, "Inf input to %%s()"); | |
| 4717 | - return; | |
| 4718 | - } | |
| 4719 | - | |
| 4720 | - /* Allocate and store the Y */ | |
| 4721 | - if( p->nUsed>=p->nAlloc ){ | |
| 4722 | - unsigned n = p->nAlloc*2 + 250; | |
| 4723 | - double *a = sqlite3_realloc64(p->a, sizeof(double)*n); | |
| 4724 | - if( a==0 ){ | |
| 4725 | - sqlite3_free(p->a); | |
| 4726 | - memset(p, 0, sizeof(*p)); | |
| 4727 | - sqlite3_result_error_nomem(pCtx); | |
| 4728 | - return; | |
| 4729 | - } | |
| 4730 | - p->nAlloc = n; | |
| 4731 | - p->a = a; | |
| 4732 | - } | |
| 4733 | - if( p->nUsed==0 ){ | |
| 4734 | - p->a[p->nUsed++] = y; | |
| 4735 | - p->bSorted = 1; | |
| 4736 | - }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ | |
| 4737 | - p->a[p->nUsed++] = y; | |
| 4738 | - }else if( p->bKeepSorted ){ | |
| 4739 | - int i; | |
| 4740 | - i = percentBinarySearch(p, y, 0); | |
| 4741 | - if( i<(int)p->nUsed ){ | |
| 4742 | - memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); | |
| 4743 | - } | |
| 4744 | - p->a[i] = y; | |
| 4745 | - p->nUsed++; | |
| 4746 | - }else{ | |
| 4747 | - p->a[p->nUsed++] = y; | |
| 4748 | - p->bSorted = 0; | |
| 4749 | - } | |
| 4750 | -} | |
| 4751 | - | |
| 4752 | -/* | |
| 4753 | -** Interchange two doubles. | |
| 4754 | -*/ | |
| 4755 | -#define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} | |
| 4756 | - | |
| 4757 | -/* | |
| 4758 | -** Sort an array of doubles. | |
| 4759 | -** | |
| 4760 | -** Algorithm: quicksort | |
| 4761 | -** | |
| 4762 | -** This is implemented separately rather than using the qsort() routine | |
| 4763 | -** from the standard library because: | |
| 4764 | -** | |
| 4765 | -** (1) To avoid a dependency on qsort() | |
| 4766 | -** (2) To avoid the function call to the comparison routine for each | |
| 4767 | -** comparison. | |
| 4768 | -*/ | |
| 4769 | -static void percentSort(double *a, unsigned int n){ | |
| 4770 | - int iLt; /* Entries before a[iLt] are less than rPivot */ | |
| 4771 | - int iGt; /* Entries at or after a[iGt] are greater than rPivot */ | |
| 4772 | - int i; /* Loop counter */ | |
| 4773 | - double rPivot; /* The pivot value */ | |
| 4774 | - | |
| 4775 | - assert( n>=2 ); | |
| 4776 | - if( a[0]>a[n-1] ){ | |
| 4777 | - SWAP_DOUBLE(a[0],a[n-1]) | |
| 4778 | - } | |
| 4779 | - if( n==2 ) return; | |
| 4780 | - iGt = n-1; | |
| 4781 | - i = n/2; | |
| 4782 | - if( a[0]>a[i] ){ | |
| 4783 | - SWAP_DOUBLE(a[0],a[i]) | |
| 4784 | - }else if( a[i]>a[iGt] ){ | |
| 4785 | - SWAP_DOUBLE(a[i],a[iGt]) | |
| 4786 | - } | |
| 4787 | - if( n==3 ) return; | |
| 4788 | - rPivot = a[i]; | |
| 4789 | - iLt = i = 1; | |
| 4790 | - do{ | |
| 4791 | - if( a[i]<rPivot ){ | |
| 4792 | - if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt]) | |
| 4793 | - iLt++; | |
| 4794 | - i++; | |
| 4795 | - }else if( a[i]>rPivot ){ | |
| 4796 | - do{ | |
| 4797 | - iGt--; | |
| 4798 | - }while( iGt>i && a[iGt]>rPivot ); | |
| 4799 | - SWAP_DOUBLE(a[i],a[iGt]) | |
| 4800 | - }else{ | |
| 4801 | - i++; | |
| 4802 | - } | |
| 4803 | - }while( i<iGt ); | |
| 4804 | - if( iLt>=2 ) percentSort(a, iLt); | |
| 4805 | - if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); | |
| 4806 | - | |
| 4807 | -/* Uncomment for testing */ | |
| 4808 | -#if 0 | |
| 4809 | - for(i=0; i<n-1; i++){ | |
| 4810 | - assert( a[i]<=a[i+1] ); | |
| 4811 | - } | |
| 4812 | -#endif | |
| 4813 | -} | |
| 4814 | - | |
| 4815 | - | |
| 4816 | -/* | |
| 4817 | -** The "inverse" function for percentile(Y,P) is called to remove a | |
| 4818 | -** row that was previously inserted by "step". | |
| 4819 | -*/ | |
| 4820 | -static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ | |
| 4821 | - Percentile *p; | |
| 4822 | - int eType; | |
| 4823 | - double y; | |
| 4824 | - int i; | |
| 4825 | - assert( argc==2 || argc==1 ); | |
| 4826 | - | |
| 4827 | - /* Allocate the session context. */ | |
| 4828 | - p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); | |
| 4829 | - assert( p!=0 ); | |
| 4830 | - | |
| 4831 | - /* Ignore rows for which Y is NULL */ | |
| 4832 | - eType = sqlite3_value_type(argv[0]); | |
| 4833 | - if( eType==SQLITE_NULL ) return; | |
| 4834 | - | |
| 4835 | - /* If not NULL, then Y must be numeric. Otherwise throw an error. | |
| 4836 | - ** Requirement 4 */ | |
| 4837 | - if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ | |
| 4838 | - return; | |
| 4839 | - } | |
| 4840 | - | |
| 4841 | - /* Ignore the Y value if it is infinity or NaN */ | |
| 4842 | - y = sqlite3_value_double(argv[0]); | |
| 4843 | - if( percentIsInfinity(y) ){ | |
| 4844 | - return; | |
| 4845 | - } | |
| 4846 | - if( p->bSorted==0 ){ | |
| 4847 | - assert( p->nUsed>1 ); | |
| 4848 | - percentSort(p->a, p->nUsed); | |
| 4849 | - p->bSorted = 1; | |
| 4850 | - } | |
| 4851 | - p->bKeepSorted = 1; | |
| 4852 | - | |
| 4853 | - /* Find and remove the row */ | |
| 4854 | - i = percentBinarySearch(p, y, 1); | |
| 4855 | - if( i>=0 ){ | |
| 4856 | - p->nUsed--; | |
| 4857 | - if( i<(int)p->nUsed ){ | |
| 4858 | - memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); | |
| 4859 | - } | |
| 4860 | - } | |
| 4861 | -} | |
| 4862 | - | |
| 4863 | -/* | |
| 4864 | -** Compute the final output of percentile(). Clean up all allocated | |
| 4865 | -** memory if and only if bIsFinal is true. | |
| 4866 | -*/ | |
| 4867 | -static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ | |
| 4868 | - Percentile *p; | |
| 4869 | - PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); | |
| 4870 | - unsigned i1, i2; | |
| 4871 | - double v1, v2; | |
| 4872 | - double ix, vx; | |
| 4873 | - p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); | |
| 4874 | - if( p==0 ) return; | |
| 4875 | - if( p->a==0 ) return; | |
| 4876 | - if( p->nUsed ){ | |
| 4877 | - if( p->bSorted==0 ){ | |
| 4878 | - assert( p->nUsed>1 ); | |
| 4879 | - percentSort(p->a, p->nUsed); | |
| 4880 | - p->bSorted = 1; | |
| 4881 | - } | |
| 4882 | - ix = p->rPct*(p->nUsed-1); | |
| 4883 | - i1 = (unsigned)ix; | |
| 4884 | - if( pFunc->bDiscrete ){ | |
| 4885 | - vx = p->a[i1]; | |
| 4886 | - }else{ | |
| 4887 | - i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; | |
| 4888 | - v1 = p->a[i1]; | |
| 4889 | - v2 = p->a[i2]; | |
| 4890 | - vx = v1 + (v2-v1)*(ix-i1); | |
| 4891 | - } | |
| 4892 | - sqlite3_result_double(pCtx, vx); | |
| 4893 | - } | |
| 4894 | - if( bIsFinal ){ | |
| 4895 | - sqlite3_free(p->a); | |
| 4896 | - memset(p, 0, sizeof(*p)); | |
| 4897 | - }else{ | |
| 4898 | - p->bKeepSorted = 1; | |
| 4899 | - } | |
| 4900 | -} | |
| 4901 | -static void percentFinal(sqlite3_context *pCtx){ | |
| 4902 | - percentCompute(pCtx, 1); | |
| 4903 | -} | |
| 4904 | -static void percentValue(sqlite3_context *pCtx){ | |
| 4905 | - percentCompute(pCtx, 0); | |
| 4906 | -} | |
| 4907 | - | |
| 4908 | -#if defined(_WIN32) && !defined(SQLITE3_H) && !defined(SQLITE_STATIC_PERCENTILE) | |
| 4909 | - | |
| 4910 | -#endif | |
| 4911 | -int sqlite3_percentile_init( | |
| 4912 | - sqlite3 *db, | |
| 4913 | - char **pzErrMsg, | |
| 4914 | - const sqlite3_api_routines *pApi | |
| 4915 | -){ | |
| 4916 | - int rc = SQLITE_OK; | |
| 4917 | - unsigned int i; | |
| 4918 | -#ifdef SQLITE3EXT_H | |
| 4919 | - SQLITE_EXTENSION_INIT2(pApi); | |
| 4920 | -#else | |
| 4921 | - (void)pApi; /* Unused parameter */ | |
| 4922 | -#endif | |
| 4923 | - (void)pzErrMsg; /* Unused parameter */ | |
| 4924 | - for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){ | |
| 4925 | - rc = sqlite3_create_window_function(db, | |
| 4926 | - aPercentFunc[i].zName, | |
| 4927 | - aPercentFunc[i].nArg, | |
| 4928 | - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_SELFORDER1, | |
| 4929 | - (void*)&aPercentFunc[i], | |
| 4930 | - percentStep, percentFinal, percentValue, percentInverse, 0); | |
| 4931 | - if( rc ) break; | |
| 4932 | - } | |
| 4933 | - return rc; | |
| 4934 | -} | |
| 4935 | - | |
| 4936 | -/************************* End ../ext/misc/percentile.c ********************/ | |
| 4937 | 4503 | #undef sqlite3_base_init |
| 4938 | 4504 | #define sqlite3_base_init sqlite3_base64_init |
| 4939 | 4505 | /************************* Begin ../ext/misc/base64.c ******************/ |
| 4940 | 4506 | /* |
| 4941 | 4507 | ** 2022-11-18 |
| @@ -5144,20 +4710,22 @@ | ||
| 5144 | 4710 | return pOut; |
| 5145 | 4711 | } |
| 5146 | 4712 | |
| 5147 | 4713 | /* This function does the work for the SQLite base64(x) UDF. */ |
| 5148 | 4714 | static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 5149 | - int nb, nc, nv = sqlite3_value_bytes(av[0]); | |
| 4715 | + sqlite3_int64 nb; | |
| 4716 | + sqlite3_int64 nv = sqlite3_value_bytes(av[0]); | |
| 4717 | + sqlite3_int64 nc; | |
| 5150 | 4718 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 5151 | 4719 | SQLITE_LIMIT_LENGTH, -1); |
| 5152 | 4720 | char *cBuf; |
| 5153 | 4721 | u8 *bBuf; |
| 5154 | 4722 | assert(na==1); |
| 5155 | 4723 | switch( sqlite3_value_type(av[0]) ){ |
| 5156 | 4724 | case SQLITE_BLOB: |
| 5157 | 4725 | nb = nv; |
| 5158 | - nc = 4*(nv+2/3); /* quads needed */ | |
| 4726 | + nc = 4*((nv+2)/3); /* quads needed */ | |
| 5159 | 4727 | nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ |
| 5160 | 4728 | if( nvMax < nc ){ |
| 5161 | 4729 | sqlite3_result_error(context, "blob expanded to base64 too big", -1); |
| 5162 | 4730 | return; |
| 5163 | 4731 | } |
| @@ -5524,11 +5092,11 @@ | ||
| 5524 | 5092 | } |
| 5525 | 5093 | # endif |
| 5526 | 5094 | |
| 5527 | 5095 | /* This function does the work for the SQLite base85(x) UDF. */ |
| 5528 | 5096 | static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 5529 | - int nb, nc, nv = sqlite3_value_bytes(av[0]); | |
| 5097 | + sqlite3_int64 nb, nc, nv = sqlite3_value_bytes(av[0]); | |
| 5530 | 5098 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 5531 | 5099 | SQLITE_LIMIT_LENGTH, -1); |
| 5532 | 5100 | char *cBuf; |
| 5533 | 5101 | u8 *bBuf; |
| 5534 | 5102 | assert(na==1); |
| @@ -5829,10 +5397,13 @@ | ||
| 5829 | 5397 | } |
| 5830 | 5398 | memcpy(&a,&r,sizeof(a)); |
| 5831 | 5399 | if( a==0 ){ |
| 5832 | 5400 | e = 0; |
| 5833 | 5401 | m = 0; |
| 5402 | + }else if( a==(sqlite3_int64)0x8000000000000000LL ){ | |
| 5403 | + e = -1996; | |
| 5404 | + m = -1; | |
| 5834 | 5405 | }else{ |
| 5835 | 5406 | e = a>>52; |
| 5836 | 5407 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| 5837 | 5408 | if( e==0 ){ |
| 5838 | 5409 | m <<= 1; |
| @@ -6052,23 +5623,24 @@ | ||
| 6052 | 5623 | ** Examples: |
| 6053 | 5624 | ** |
| 6054 | 5625 | ** SELECT * FROM generate_series(0,100,5); |
| 6055 | 5626 | ** |
| 6056 | 5627 | ** The query above returns integers from 0 through 100 counting by steps |
| 6057 | -** of 5. | |
| 5628 | +** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total | |
| 5629 | +** of 21 rows. | |
| 6058 | 5630 | ** |
| 6059 | 5631 | ** SELECT * FROM generate_series(0,100); |
| 6060 | 5632 | ** |
| 6061 | -** Integers from 0 through 100 with a step size of 1. | |
| 5633 | +** Integers from 0 through 100 with a step size of 1. 101 rows. | |
| 6062 | 5634 | ** |
| 6063 | 5635 | ** SELECT * FROM generate_series(20) LIMIT 10; |
| 6064 | 5636 | ** |
| 6065 | -** Integers 20 through 29. | |
| 5637 | +** Integers 20 through 29. 10 rows. | |
| 6066 | 5638 | ** |
| 6067 | 5639 | ** SELECT * FROM generate_series(0,-100,-5); |
| 6068 | 5640 | ** |
| 6069 | -** Integers 0 -5 -10 ... -100. | |
| 5641 | +** Integers 0 -5 -10 ... -100. 21 rows. | |
| 6070 | 5642 | ** |
| 6071 | 5643 | ** SELECT * FROM generate_series(0,-1); |
| 6072 | 5644 | ** |
| 6073 | 5645 | ** Empty sequence. |
| 6074 | 5646 | ** |
| @@ -6140,143 +5712,92 @@ | ||
| 6140 | 5712 | #include <string.h> |
| 6141 | 5713 | #include <limits.h> |
| 6142 | 5714 | #include <math.h> |
| 6143 | 5715 | |
| 6144 | 5716 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 6145 | -/* | |
| 6146 | -** Return that member of a generate_series(...) sequence whose 0-based | |
| 6147 | -** index is ix. The 0th member is given by smBase. The sequence members | |
| 6148 | -** progress per ix increment by smStep. | |
| 6149 | -*/ | |
| 6150 | -static sqlite3_int64 genSeqMember( | |
| 6151 | - sqlite3_int64 smBase, | |
| 6152 | - sqlite3_int64 smStep, | |
| 6153 | - sqlite3_uint64 ix | |
| 6154 | -){ | |
| 6155 | - static const sqlite3_uint64 mxI64 = | |
| 6156 | - ((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff; | |
| 6157 | - if( ix>=mxI64 ){ | |
| 6158 | - /* Get ix into signed i64 range. */ | |
| 6159 | - ix -= mxI64; | |
| 6160 | - /* With 2's complement ALU, this next can be 1 step, but is split into | |
| 6161 | - * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ | |
| 6162 | - smBase += (mxI64/2) * smStep; | |
| 6163 | - smBase += (mxI64 - mxI64/2) * smStep; | |
| 6164 | - } | |
| 6165 | - /* Under UBSAN (or on 1's complement machines), must do this last term | |
| 6166 | - * in steps to avoid the dreaded (and harmless) signed multiply overflow. */ | |
| 6167 | - if( ix>=2 ){ | |
| 6168 | - sqlite3_int64 ix2 = (sqlite3_int64)ix/2; | |
| 6169 | - smBase += ix2*smStep; | |
| 6170 | - ix -= ix2; | |
| 6171 | - } | |
| 6172 | - return smBase + ((sqlite3_int64)ix)*smStep; | |
| 6173 | -} | |
| 6174 | - | |
| 6175 | -/* typedef unsigned char u8; */ | |
| 6176 | - | |
| 6177 | -typedef struct SequenceSpec { | |
| 6178 | - sqlite3_int64 iOBase; /* Original starting value ("start") */ | |
| 6179 | - sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ | |
| 6180 | - sqlite3_int64 iBase; /* Starting value to actually use */ | |
| 6181 | - sqlite3_int64 iTerm; /* Terminal value to actually use */ | |
| 6182 | - sqlite3_int64 iStep; /* Increment ("step") */ | |
| 6183 | - sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */ | |
| 6184 | - sqlite3_uint64 uSeqIndexNow; /* Current index during generation */ | |
| 6185 | - sqlite3_int64 iValueNow; /* Current value during generation */ | |
| 6186 | - u8 isNotEOF; /* Sequence generation not exhausted */ | |
| 6187 | - u8 isReversing; /* Sequence is being reverse generated */ | |
| 6188 | -} SequenceSpec; | |
| 6189 | - | |
| 6190 | -/* | |
| 6191 | -** Prepare a SequenceSpec for use in generating an integer series | |
| 6192 | -** given initialized iBase, iTerm and iStep values. Sequence is | |
| 6193 | -** initialized per given isReversing. Other members are computed. | |
| 6194 | -*/ | |
| 6195 | -static void setupSequence( SequenceSpec *pss ){ | |
| 6196 | - int bSameSigns; | |
| 6197 | - pss->uSeqIndexMax = 0; | |
| 6198 | - pss->isNotEOF = 0; | |
| 6199 | - bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0); | |
| 6200 | - if( pss->iTerm < pss->iBase ){ | |
| 6201 | - sqlite3_uint64 nuspan = 0; | |
| 6202 | - if( bSameSigns ){ | |
| 6203 | - nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm); | |
| 6204 | - }else{ | |
| 6205 | - /* Under UBSAN (or on 1's complement machines), must do this in steps. | |
| 6206 | - * In this clause, iBase>=0 and iTerm<0 . */ | |
| 6207 | - nuspan = 1; | |
| 6208 | - nuspan += pss->iBase; | |
| 6209 | - nuspan += -(pss->iTerm+1); | |
| 6210 | - } | |
| 6211 | - if( pss->iStep<0 ){ | |
| 6212 | - pss->isNotEOF = 1; | |
| 6213 | - if( nuspan==ULONG_MAX ){ | |
| 6214 | - pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1; | |
| 6215 | - }else if( pss->iStep>LLONG_MIN ){ | |
| 6216 | - pss->uSeqIndexMax = nuspan/-pss->iStep; | |
| 6217 | - } | |
| 6218 | - } | |
| 6219 | - }else if( pss->iTerm > pss->iBase ){ | |
| 6220 | - sqlite3_uint64 puspan = 0; | |
| 6221 | - if( bSameSigns ){ | |
| 6222 | - puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase); | |
| 6223 | - }else{ | |
| 6224 | - /* Under UBSAN (or on 1's complement machines), must do this in steps. | |
| 6225 | - * In this clause, iTerm>=0 and iBase<0 . */ | |
| 6226 | - puspan = 1; | |
| 6227 | - puspan += pss->iTerm; | |
| 6228 | - puspan += -(pss->iBase+1); | |
| 6229 | - } | |
| 6230 | - if( pss->iStep>0 ){ | |
| 6231 | - pss->isNotEOF = 1; | |
| 6232 | - pss->uSeqIndexMax = puspan/pss->iStep; | |
| 6233 | - } | |
| 6234 | - }else if( pss->iTerm == pss->iBase ){ | |
| 6235 | - pss->isNotEOF = 1; | |
| 6236 | - pss->uSeqIndexMax = 0; | |
| 6237 | - } | |
| 6238 | - pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0; | |
| 6239 | - pss->iValueNow = (pss->isReversing) | |
| 6240 | - ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax) | |
| 6241 | - : pss->iBase; | |
| 6242 | -} | |
| 6243 | - | |
| 6244 | -/* | |
| 6245 | -** Progress sequence generator to yield next value, if any. | |
| 6246 | -** Leave its state to either yield next value or be at EOF. | |
| 6247 | -** Return whether there is a next value, or 0 at EOF. | |
| 6248 | -*/ | |
| 6249 | -static int progressSequence( SequenceSpec *pss ){ | |
| 6250 | - if( !pss->isNotEOF ) return 0; | |
| 6251 | - if( pss->isReversing ){ | |
| 6252 | - if( pss->uSeqIndexNow > 0 ){ | |
| 6253 | - pss->uSeqIndexNow--; | |
| 6254 | - pss->iValueNow -= pss->iStep; | |
| 6255 | - }else{ | |
| 6256 | - pss->isNotEOF = 0; | |
| 6257 | - } | |
| 6258 | - }else{ | |
| 6259 | - if( pss->uSeqIndexNow < pss->uSeqIndexMax ){ | |
| 6260 | - pss->uSeqIndexNow++; | |
| 6261 | - pss->iValueNow += pss->iStep; | |
| 6262 | - }else{ | |
| 6263 | - pss->isNotEOF = 0; | |
| 6264 | - } | |
| 6265 | - } | |
| 6266 | - return pss->isNotEOF; | |
| 6267 | -} | |
| 6268 | 5717 | |
| 6269 | 5718 | /* series_cursor is a subclass of sqlite3_vtab_cursor which will |
| 6270 | 5719 | ** serve as the underlying representation of a cursor that scans |
| 6271 | -** over rows of the result | |
| 5720 | +** over rows of the result. | |
| 5721 | +** | |
| 5722 | +** iOBase, iOTerm, and iOStep are the original values of the | |
| 5723 | +** start=, stop=, and step= constraints on the query. These are | |
| 5724 | +** the values reported by the start, stop, and step columns of the | |
| 5725 | +** virtual table. | |
| 5726 | +** | |
| 5727 | +** iBase, iTerm, iStep, and bDescp are the actual values used to generate | |
| 5728 | +** the sequence. These might be different from the iOxxxx values. | |
| 5729 | +** For example in | |
| 5730 | +** | |
| 5731 | +** SELECT value FROM generate_series(1,11,2) | |
| 5732 | +** WHERE value BETWEEN 4 AND 8; | |
| 5733 | +** | |
| 5734 | +** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7. | |
| 5735 | +** Another example: | |
| 5736 | +** | |
| 5737 | +** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC; | |
| 5738 | +** | |
| 5739 | +** The cursor initialization for the above query is: | |
| 5740 | +** | |
| 5741 | +** iOBase = 1 iBase = 13 | |
| 5742 | +** iOTerm = 15 iTerm = 1 | |
| 5743 | +** iOStep = 3 iStep = 3 bDesc = 1 | |
| 5744 | +** | |
| 5745 | +** The actual step size is unsigned so that can have a value of | |
| 5746 | +** +9223372036854775808 which is needed for querys like this: | |
| 5747 | +** | |
| 5748 | +** SELECT value | |
| 5749 | +** FROM generate_series(9223372036854775807, | |
| 5750 | +** -9223372036854775808, | |
| 5751 | +** -9223372036854775808) | |
| 5752 | +** ORDER BY value ASC; | |
| 5753 | +** | |
| 5754 | +** The setup for the previous query will be: | |
| 5755 | +** | |
| 5756 | +** iOBase = 9223372036854775807 iBase = -1 | |
| 5757 | +** iOTerm = -9223372036854775808 iTerm = 9223372036854775807 | |
| 5758 | +** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0 | |
| 6272 | 5759 | */ |
| 5760 | +/* typedef unsigned char u8; */ | |
| 6273 | 5761 | typedef struct series_cursor series_cursor; |
| 6274 | 5762 | struct series_cursor { |
| 6275 | 5763 | sqlite3_vtab_cursor base; /* Base class - must be first */ |
| 6276 | - SequenceSpec ss; /* (this) Derived class data */ | |
| 5764 | + sqlite3_int64 iOBase; /* Original starting value ("start") */ | |
| 5765 | + sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ | |
| 5766 | + sqlite3_int64 iOStep; /* Original step value */ | |
| 5767 | + sqlite3_int64 iBase; /* Starting value to actually use */ | |
| 5768 | + sqlite3_int64 iTerm; /* Terminal value to actually use */ | |
| 5769 | + sqlite3_uint64 iStep; /* The step size */ | |
| 5770 | + sqlite3_int64 iValue; /* Current value */ | |
| 5771 | + u8 bDesc; /* iStep is really negative */ | |
| 5772 | + u8 bDone; /* True if stepped past last element */ | |
| 6277 | 5773 | }; |
| 5774 | + | |
| 5775 | +/* | |
| 5776 | +** Computed the difference between two 64-bit signed integers using a | |
| 5777 | +** convoluted computation designed to work around the silly restriction | |
| 5778 | +** against signed integer overflow in C. | |
| 5779 | +*/ | |
| 5780 | +static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){ | |
| 5781 | + assert( a>=b ); | |
| 5782 | + return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b); | |
| 5783 | +} | |
| 5784 | + | |
| 5785 | +/* | |
| 5786 | +** Add or substract an unsigned 64-bit integer from a signed 64-bit integer | |
| 5787 | +** and return the new signed 64-bit integer. | |
| 5788 | +*/ | |
| 5789 | +static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){ | |
| 5790 | + sqlite3_uint64 x = *(sqlite3_uint64*)&a; | |
| 5791 | + x += b; | |
| 5792 | + return *(sqlite3_int64*)&x; | |
| 5793 | +} | |
| 5794 | +static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){ | |
| 5795 | + sqlite3_uint64 x = *(sqlite3_uint64*)&a; | |
| 5796 | + x -= b; | |
| 5797 | + return *(sqlite3_int64*)&x; | |
| 5798 | +} | |
| 6278 | 5799 | |
| 6279 | 5800 | /* |
| 6280 | 5801 | ** The seriesConnect() method is invoked to create a new |
| 6281 | 5802 | ** series_vtab that describes the generate_series virtual table. |
| 6282 | 5803 | ** |
| @@ -6354,11 +5875,19 @@ | ||
| 6354 | 5875 | /* |
| 6355 | 5876 | ** Advance a series_cursor to its next row of output. |
| 6356 | 5877 | */ |
| 6357 | 5878 | static int seriesNext(sqlite3_vtab_cursor *cur){ |
| 6358 | 5879 | series_cursor *pCur = (series_cursor*)cur; |
| 6359 | - progressSequence( & pCur->ss ); | |
| 5880 | + if( pCur->iValue==pCur->iTerm ){ | |
| 5881 | + pCur->bDone = 1; | |
| 5882 | + }else if( pCur->bDesc ){ | |
| 5883 | + pCur->iValue = sub64(pCur->iValue, pCur->iStep); | |
| 5884 | + assert( pCur->iValue>=pCur->iTerm ); | |
| 5885 | + }else{ | |
| 5886 | + pCur->iValue = add64(pCur->iValue, pCur->iStep); | |
| 5887 | + assert( pCur->iValue<=pCur->iTerm ); | |
| 5888 | + } | |
| 6360 | 5889 | return SQLITE_OK; |
| 6361 | 5890 | } |
| 6362 | 5891 | |
| 6363 | 5892 | /* |
| 6364 | 5893 | ** Return values of columns for the row at which the series_cursor |
| @@ -6370,41 +5899,41 @@ | ||
| 6370 | 5899 | int i /* Which column to return */ |
| 6371 | 5900 | ){ |
| 6372 | 5901 | series_cursor *pCur = (series_cursor*)cur; |
| 6373 | 5902 | sqlite3_int64 x = 0; |
| 6374 | 5903 | switch( i ){ |
| 6375 | - case SERIES_COLUMN_START: x = pCur->ss.iOBase; break; | |
| 6376 | - case SERIES_COLUMN_STOP: x = pCur->ss.iOTerm; break; | |
| 6377 | - case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break; | |
| 6378 | - default: x = pCur->ss.iValueNow; break; | |
| 5904 | + case SERIES_COLUMN_START: x = pCur->iOBase; break; | |
| 5905 | + case SERIES_COLUMN_STOP: x = pCur->iOTerm; break; | |
| 5906 | + case SERIES_COLUMN_STEP: x = pCur->iOStep; break; | |
| 5907 | + default: x = pCur->iValue; break; | |
| 6379 | 5908 | } |
| 6380 | 5909 | sqlite3_result_int64(ctx, x); |
| 6381 | 5910 | return SQLITE_OK; |
| 6382 | 5911 | } |
| 6383 | 5912 | |
| 6384 | 5913 | #ifndef LARGEST_UINT64 |
| 6385 | -#define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) | |
| 6386 | -#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) | |
| 6387 | -#define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) | |
| 5914 | +#define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL) | |
| 5915 | +#define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL) | |
| 5916 | +#define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL) | |
| 6388 | 5917 | #endif |
| 6389 | 5918 | |
| 6390 | 5919 | /* |
| 6391 | 5920 | ** The rowid is the same as the value. |
| 6392 | 5921 | */ |
| 6393 | 5922 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 6394 | 5923 | series_cursor *pCur = (series_cursor*)cur; |
| 6395 | - *pRowid = pCur->ss.iValueNow; | |
| 5924 | + *pRowid = pCur->iValue; | |
| 6396 | 5925 | return SQLITE_OK; |
| 6397 | 5926 | } |
| 6398 | 5927 | |
| 6399 | 5928 | /* |
| 6400 | 5929 | ** Return TRUE if the cursor has been moved off of the last |
| 6401 | 5930 | ** row of output. |
| 6402 | 5931 | */ |
| 6403 | 5932 | static int seriesEof(sqlite3_vtab_cursor *cur){ |
| 6404 | 5933 | series_cursor *pCur = (series_cursor*)cur; |
| 6405 | - return !pCur->ss.isNotEOF; | |
| 5934 | + return pCur->bDone; | |
| 6406 | 5935 | } |
| 6407 | 5936 | |
| 6408 | 5937 | /* True to cause run-time checking of the start=, stop=, and/or step= |
| 6409 | 5938 | ** parameters. The only reason to do this is for testing the |
| 6410 | 5939 | ** constraint checking logic for virtual tables in the SQLite core. |
| @@ -6411,10 +5940,63 @@ | ||
| 6411 | 5940 | */ |
| 6412 | 5941 | #ifndef SQLITE_SERIES_CONSTRAINT_VERIFY |
| 6413 | 5942 | # define SQLITE_SERIES_CONSTRAINT_VERIFY 0 |
| 6414 | 5943 | #endif |
| 6415 | 5944 | |
| 5945 | +/* | |
| 5946 | +** Return the number of steps between pCur->iBase and pCur->iTerm if | |
| 5947 | +** the step width is pCur->iStep. | |
| 5948 | +*/ | |
| 5949 | +static sqlite3_uint64 seriesSteps(series_cursor *pCur){ | |
| 5950 | + if( pCur->bDesc ){ | |
| 5951 | + assert( pCur->iBase >= pCur->iTerm ); | |
| 5952 | + return span64(pCur->iBase, pCur->iTerm)/pCur->iStep; | |
| 5953 | + }else{ | |
| 5954 | + assert( pCur->iBase <= pCur->iTerm ); | |
| 5955 | + return span64(pCur->iTerm, pCur->iBase)/pCur->iStep; | |
| 5956 | + } | |
| 5957 | +} | |
| 5958 | + | |
| 5959 | +#if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32) | |
| 5960 | +/* | |
| 5961 | +** Case 1 (the most common case): | |
| 5962 | +** The standard math library is available so use ceil() and floor() from there. | |
| 5963 | +*/ | |
| 5964 | +static double seriesCeil(double r){ return ceil(r); } | |
| 5965 | +static double seriesFloor(double r){ return floor(r); } | |
| 5966 | +#elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) | |
| 5967 | +/* | |
| 5968 | +** Case 2 (2nd most common): Use GCC/Clang builtins | |
| 5969 | +*/ | |
| 5970 | +static double seriesCeil(double r){ return __builtin_ceil(r); } | |
| 5971 | +static double seriesFloor(double r){ return __builtin_floor(r); } | |
| 5972 | +#else | |
| 5973 | +/* | |
| 5974 | +** Case 3 (rarely happens): Use home-grown ceil() and floor() routines. | |
| 5975 | +*/ | |
| 5976 | +static double seriesCeil(double r){ | |
| 5977 | + sqlite3_int64 x; | |
| 5978 | + if( r!=r ) return r; | |
| 5979 | + if( r<=(-4503599627370496.0) ) return r; | |
| 5980 | + if( r>=(+4503599627370496.0) ) return r; | |
| 5981 | + x = (sqlite3_int64)r; | |
| 5982 | + if( r==(double)x ) return r; | |
| 5983 | + if( r>(double)x ) x++; | |
| 5984 | + return (double)x; | |
| 5985 | +} | |
| 5986 | +static double seriesFloor(double r){ | |
| 5987 | + sqlite3_int64 x; | |
| 5988 | + if( r!=r ) return r; | |
| 5989 | + if( r<=(-4503599627370496.0) ) return r; | |
| 5990 | + if( r>=(+4503599627370496.0) ) return r; | |
| 5991 | + x = (sqlite3_int64)r; | |
| 5992 | + if( r==(double)x ) return r; | |
| 5993 | + if( r<(double)x ) x--; | |
| 5994 | + return (double)x; | |
| 5995 | +} | |
| 5996 | +#endif | |
| 5997 | + | |
| 6416 | 5998 | /* |
| 6417 | 5999 | ** This method is called to "rewind" the series_cursor object back |
| 6418 | 6000 | ** to the first row of output. This method is always called at least |
| 6419 | 6001 | ** once prior to any call to seriesColumn() or seriesRowid() or |
| 6420 | 6002 | ** seriesEof(). |
| @@ -6444,185 +6026,243 @@ | ||
| 6444 | 6026 | sqlite3_vtab_cursor *pVtabCursor, |
| 6445 | 6027 | int idxNum, const char *idxStrUnused, |
| 6446 | 6028 | int argc, sqlite3_value **argv |
| 6447 | 6029 | ){ |
| 6448 | 6030 | series_cursor *pCur = (series_cursor *)pVtabCursor; |
| 6449 | - int i = 0; | |
| 6450 | - int returnNoRows = 0; | |
| 6451 | - sqlite3_int64 iMin = SMALLEST_INT64; | |
| 6452 | - sqlite3_int64 iMax = LARGEST_INT64; | |
| 6453 | - sqlite3_int64 iLimit = 0; | |
| 6454 | - sqlite3_int64 iOffset = 0; | |
| 6031 | + int iArg = 0; /* Arguments used so far */ | |
| 6032 | + int i; /* Loop counter */ | |
| 6033 | + sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */ | |
| 6034 | + sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */ | |
| 6035 | + sqlite3_int64 iLimit = 0; /* if >0, the value of the LIMIT */ | |
| 6036 | + sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */ | |
| 6455 | 6037 | |
| 6456 | 6038 | (void)idxStrUnused; |
| 6039 | + | |
| 6040 | + /* If any constraints have a NULL value, then return no rows. | |
| 6041 | + ** See ticket https://sqlite.org/src/info/fac496b61722daf2 | |
| 6042 | + */ | |
| 6043 | + for(i=0; i<argc; i++){ | |
| 6044 | + if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ | |
| 6045 | + goto series_no_rows; | |
| 6046 | + } | |
| 6047 | + } | |
| 6048 | + | |
| 6049 | + /* Capture the three HIDDEN parameters to the virtual table and insert | |
| 6050 | + ** default values for any parameters that are omitted. | |
| 6051 | + */ | |
| 6457 | 6052 | if( idxNum & 0x01 ){ |
| 6458 | - pCur->ss.iBase = sqlite3_value_int64(argv[i++]); | |
| 6053 | + pCur->iOBase = sqlite3_value_int64(argv[iArg++]); | |
| 6459 | 6054 | }else{ |
| 6460 | - pCur->ss.iBase = 0; | |
| 6055 | + pCur->iOBase = 0; | |
| 6461 | 6056 | } |
| 6462 | 6057 | if( idxNum & 0x02 ){ |
| 6463 | - pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); | |
| 6058 | + pCur->iOTerm = sqlite3_value_int64(argv[iArg++]); | |
| 6464 | 6059 | }else{ |
| 6465 | - pCur->ss.iTerm = 0xffffffff; | |
| 6060 | + pCur->iOTerm = 0xffffffff; | |
| 6466 | 6061 | } |
| 6467 | 6062 | if( idxNum & 0x04 ){ |
| 6468 | - pCur->ss.iStep = sqlite3_value_int64(argv[i++]); | |
| 6469 | - if( pCur->ss.iStep==0 ){ | |
| 6470 | - pCur->ss.iStep = 1; | |
| 6471 | - }else if( pCur->ss.iStep<0 ){ | |
| 6472 | - if( (idxNum & 0x10)==0 ) idxNum |= 0x08; | |
| 6473 | - } | |
| 6063 | + pCur->iOStep = sqlite3_value_int64(argv[iArg++]); | |
| 6064 | + if( pCur->iOStep==0 ) pCur->iOStep = 1; | |
| 6474 | 6065 | }else{ |
| 6475 | - pCur->ss.iStep = 1; | |
| 6066 | + pCur->iOStep = 1; | |
| 6476 | 6067 | } |
| 6477 | 6068 | |
| 6478 | 6069 | /* If there are constraints on the value column but there are |
| 6479 | 6070 | ** no constraints on the start, stop, and step columns, then |
| 6480 | 6071 | ** initialize the default range to be the entire range of 64-bit signed |
| 6481 | 6072 | ** integers. This range will contracted by the value column constraints |
| 6482 | 6073 | ** further below. |
| 6483 | 6074 | */ |
| 6484 | 6075 | if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){ |
| 6485 | - pCur->ss.iBase = SMALLEST_INT64; | |
| 6076 | + pCur->iOBase = SMALLEST_INT64; | |
| 6486 | 6077 | } |
| 6487 | 6078 | if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){ |
| 6488 | - pCur->ss.iTerm = LARGEST_INT64; | |
| 6079 | + pCur->iOTerm = LARGEST_INT64; | |
| 6489 | 6080 | } |
| 6490 | - pCur->ss.iOBase = pCur->ss.iBase; | |
| 6491 | - pCur->ss.iOTerm = pCur->ss.iTerm; | |
| 6081 | + pCur->iBase = pCur->iOBase; | |
| 6082 | + pCur->iTerm = pCur->iOTerm; | |
| 6083 | + if( pCur->iOStep>0 ){ | |
| 6084 | + pCur->iStep = pCur->iOStep; | |
| 6085 | + }else if( pCur->iOStep>SMALLEST_INT64 ){ | |
| 6086 | + pCur->iStep = -pCur->iOStep; | |
| 6087 | + }else{ | |
| 6088 | + pCur->iStep = LARGEST_INT64; | |
| 6089 | + pCur->iStep++; | |
| 6090 | + } | |
| 6091 | + pCur->bDesc = pCur->iOStep<0; | |
| 6092 | + if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){ | |
| 6093 | + goto series_no_rows; | |
| 6094 | + } | |
| 6095 | + if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){ | |
| 6096 | + goto series_no_rows; | |
| 6097 | + } | |
| 6492 | 6098 | |
| 6493 | 6099 | /* Extract the LIMIT and OFFSET values, but do not apply them yet. |
| 6494 | 6100 | ** The range must first be constrained by the limits on value. |
| 6495 | 6101 | */ |
| 6496 | 6102 | if( idxNum & 0x20 ){ |
| 6497 | - iLimit = sqlite3_value_int64(argv[i++]); | |
| 6103 | + iLimit = sqlite3_value_int64(argv[iArg++]); | |
| 6498 | 6104 | if( idxNum & 0x40 ){ |
| 6499 | - iOffset = sqlite3_value_int64(argv[i++]); | |
| 6105 | + iOffset = sqlite3_value_int64(argv[iArg++]); | |
| 6500 | 6106 | } |
| 6501 | 6107 | } |
| 6502 | 6108 | |
| 6109 | + /* Narrow the range of iMin and iMax (the minimum and maximum outputs) | |
| 6110 | + ** based on equality and inequality constraints on the "value" column. | |
| 6111 | + */ | |
| 6503 | 6112 | if( idxNum & 0x3380 ){ |
| 6504 | - /* Extract the maximum range of output values determined by | |
| 6505 | - ** constraints on the "value" column. | |
| 6506 | - */ | |
| 6507 | - if( idxNum & 0x0080 ){ | |
| 6508 | - if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6509 | - double r = sqlite3_value_double(argv[i++]); | |
| 6510 | - if( r==ceil(r) ){ | |
| 6113 | + if( idxNum & 0x0080 ){ /* value=X */ | |
| 6114 | + if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ | |
| 6115 | + double r = sqlite3_value_double(argv[iArg++]); | |
| 6116 | + if( r==seriesCeil(r) | |
| 6117 | + && r>=(double)SMALLEST_INT64 | |
| 6118 | + && r<=(double)LARGEST_INT64 | |
| 6119 | + ){ | |
| 6511 | 6120 | iMin = iMax = (sqlite3_int64)r; |
| 6512 | 6121 | }else{ |
| 6513 | - returnNoRows = 1; | |
| 6514 | - } | |
| 6515 | - }else{ | |
| 6516 | - iMin = iMax = sqlite3_value_int64(argv[i++]); | |
| 6517 | - } | |
| 6518 | - }else{ | |
| 6519 | - if( idxNum & 0x0300 ){ | |
| 6520 | - if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6521 | - double r = sqlite3_value_double(argv[i++]); | |
| 6522 | - if( idxNum & 0x0200 && r==ceil(r) ){ | |
| 6523 | - iMin = (sqlite3_int64)ceil(r+1.0); | |
| 6524 | - }else{ | |
| 6525 | - iMin = (sqlite3_int64)ceil(r); | |
| 6526 | - } | |
| 6527 | - }else{ | |
| 6528 | - iMin = sqlite3_value_int64(argv[i++]); | |
| 6529 | - if( idxNum & 0x0200 ){ | |
| 6122 | + goto series_no_rows; | |
| 6123 | + } | |
| 6124 | + }else{ | |
| 6125 | + iMin = iMax = sqlite3_value_int64(argv[iArg++]); | |
| 6126 | + } | |
| 6127 | + }else{ | |
| 6128 | + if( idxNum & 0x0300 ){ /* value>X or value>=X */ | |
| 6129 | + if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ | |
| 6130 | + double r = sqlite3_value_double(argv[iArg++]); | |
| 6131 | + if( r<(double)SMALLEST_INT64 ){ | |
| 6132 | + iMin = SMALLEST_INT64; | |
| 6133 | + }else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){ | |
| 6134 | + iMin = (sqlite3_int64)seriesCeil(r+1.0); | |
| 6135 | + }else{ | |
| 6136 | + iMin = (sqlite3_int64)seriesCeil(r); | |
| 6137 | + } | |
| 6138 | + }else{ | |
| 6139 | + iMin = sqlite3_value_int64(argv[iArg++]); | |
| 6140 | + if( (idxNum & 0x0200)!=0 ){ | |
| 6530 | 6141 | if( iMin==LARGEST_INT64 ){ |
| 6531 | - returnNoRows = 1; | |
| 6142 | + goto series_no_rows; | |
| 6532 | 6143 | }else{ |
| 6533 | 6144 | iMin++; |
| 6534 | 6145 | } |
| 6535 | 6146 | } |
| 6536 | 6147 | } |
| 6537 | 6148 | } |
| 6538 | - if( idxNum & 0x3000 ){ | |
| 6539 | - if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6540 | - double r = sqlite3_value_double(argv[i++]); | |
| 6541 | - if( (idxNum & 0x2000)!=0 && r==floor(r) ){ | |
| 6149 | + if( idxNum & 0x3000 ){ /* value<X or value<=X */ | |
| 6150 | + if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ | |
| 6151 | + double r = sqlite3_value_double(argv[iArg++]); | |
| 6152 | + if( r>(double)LARGEST_INT64 ){ | |
| 6153 | + iMax = LARGEST_INT64; | |
| 6154 | + }else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){ | |
| 6542 | 6155 | iMax = (sqlite3_int64)(r-1.0); |
| 6543 | 6156 | }else{ |
| 6544 | - iMax = (sqlite3_int64)floor(r); | |
| 6157 | + iMax = (sqlite3_int64)seriesFloor(r); | |
| 6545 | 6158 | } |
| 6546 | 6159 | }else{ |
| 6547 | - iMax = sqlite3_value_int64(argv[i++]); | |
| 6160 | + iMax = sqlite3_value_int64(argv[iArg++]); | |
| 6548 | 6161 | if( idxNum & 0x2000 ){ |
| 6549 | 6162 | if( iMax==SMALLEST_INT64 ){ |
| 6550 | - returnNoRows = 1; | |
| 6163 | + goto series_no_rows; | |
| 6551 | 6164 | }else{ |
| 6552 | 6165 | iMax--; |
| 6553 | 6166 | } |
| 6554 | 6167 | } |
| 6555 | 6168 | } |
| 6556 | 6169 | } |
| 6557 | 6170 | if( iMin>iMax ){ |
| 6558 | - returnNoRows = 1; | |
| 6171 | + goto series_no_rows; | |
| 6559 | 6172 | } |
| 6560 | 6173 | } |
| 6561 | 6174 | |
| 6562 | 6175 | /* Try to reduce the range of values to be generated based on |
| 6563 | 6176 | ** constraints on the "value" column. |
| 6564 | 6177 | */ |
| 6565 | - if( pCur->ss.iStep>0 ){ | |
| 6566 | - sqlite3_int64 szStep = pCur->ss.iStep; | |
| 6567 | - if( pCur->ss.iBase<iMin ){ | |
| 6568 | - sqlite3_uint64 d = iMin - pCur->ss.iBase; | |
| 6569 | - pCur->ss.iBase += ((d+szStep-1)/szStep)*szStep; | |
| 6570 | - } | |
| 6571 | - if( pCur->ss.iTerm>iMax ){ | |
| 6572 | - pCur->ss.iTerm = iMax; | |
| 6178 | + if( pCur->bDesc==0 ){ | |
| 6179 | + if( pCur->iBase<iMin ){ | |
| 6180 | + sqlite3_uint64 span = span64(iMin,pCur->iBase); | |
| 6181 | + pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); | |
| 6182 | + if( pCur->iBase<iMin ){ | |
| 6183 | + if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){ | |
| 6184 | + goto series_no_rows; | |
| 6185 | + } | |
| 6186 | + pCur->iBase = add64(pCur->iBase, pCur->iStep); | |
| 6187 | + } | |
| 6188 | + } | |
| 6189 | + if( pCur->iTerm>iMax ){ | |
| 6190 | + pCur->iTerm = iMax; | |
| 6573 | 6191 | } |
| 6574 | 6192 | }else{ |
| 6575 | - sqlite3_int64 szStep = -pCur->ss.iStep; | |
| 6576 | - assert( szStep>0 ); | |
| 6577 | - if( pCur->ss.iBase>iMax ){ | |
| 6578 | - sqlite3_uint64 d = pCur->ss.iBase - iMax; | |
| 6579 | - pCur->ss.iBase -= ((d+szStep-1)/szStep)*szStep; | |
| 6580 | - } | |
| 6581 | - if( pCur->ss.iTerm<iMin ){ | |
| 6582 | - pCur->ss.iTerm = iMin; | |
| 6193 | + if( pCur->iBase>iMax ){ | |
| 6194 | + sqlite3_uint64 span = span64(pCur->iBase,iMax); | |
| 6195 | + pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); | |
| 6196 | + if( pCur->iBase>iMax ){ | |
| 6197 | + if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){ | |
| 6198 | + goto series_no_rows; | |
| 6199 | + } | |
| 6200 | + pCur->iBase = sub64(pCur->iBase, pCur->iStep); | |
| 6201 | + } | |
| 6202 | + } | |
| 6203 | + if( pCur->iTerm<iMin ){ | |
| 6204 | + pCur->iTerm = iMin; | |
| 6583 | 6205 | } |
| 6584 | 6206 | } |
| 6585 | 6207 | } |
| 6208 | + | |
| 6209 | + /* Adjust iTerm so that it is exactly the last value of the series. | |
| 6210 | + */ | |
| 6211 | + if( pCur->bDesc==0 ){ | |
| 6212 | + if( pCur->iBase>pCur->iTerm ){ | |
| 6213 | + goto series_no_rows; | |
| 6214 | + } | |
| 6215 | + pCur->iTerm = sub64(pCur->iTerm, | |
| 6216 | + span64(pCur->iTerm,pCur->iBase) % pCur->iStep); | |
| 6217 | + }else{ | |
| 6218 | + if( pCur->iBase<pCur->iTerm ){ | |
| 6219 | + goto series_no_rows; | |
| 6220 | + } | |
| 6221 | + pCur->iTerm = add64(pCur->iTerm, | |
| 6222 | + span64(pCur->iBase,pCur->iTerm) % pCur->iStep); | |
| 6223 | + } | |
| 6224 | + | |
| 6225 | + /* Transform the series generator to output values in the requested | |
| 6226 | + ** order. | |
| 6227 | + */ | |
| 6228 | + if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0) | |
| 6229 | + || ((idxNum & 0x0010)!=0 && pCur->bDesc!=0) | |
| 6230 | + ){ | |
| 6231 | + sqlite3_int64 tmp = pCur->iBase; | |
| 6232 | + pCur->iBase = pCur->iTerm; | |
| 6233 | + pCur->iTerm = tmp; | |
| 6234 | + pCur->bDesc = !pCur->bDesc; | |
| 6235 | + } | |
| 6586 | 6236 | |
| 6587 | 6237 | /* Apply LIMIT and OFFSET constraints, if any */ |
| 6238 | + assert( pCur->iStep!=0 ); | |
| 6588 | 6239 | if( idxNum & 0x20 ){ |
| 6589 | 6240 | if( iOffset>0 ){ |
| 6590 | - pCur->ss.iBase += pCur->ss.iStep*iOffset; | |
| 6591 | - } | |
| 6592 | - if( iLimit>=0 ){ | |
| 6593 | - sqlite3_int64 iTerm; | |
| 6594 | - iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep; | |
| 6595 | - if( pCur->ss.iStep<0 ){ | |
| 6596 | - if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; | |
| 6597 | - }else{ | |
| 6598 | - if( iTerm<pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; | |
| 6599 | - } | |
| 6600 | - } | |
| 6601 | - } | |
| 6602 | - | |
| 6603 | - | |
| 6604 | - for(i=0; i<argc; i++){ | |
| 6605 | - if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ | |
| 6606 | - /* If any of the constraints have a NULL value, then return no rows. | |
| 6607 | - ** See ticket https://sqlite.org/src/info/fac496b61722daf2 */ | |
| 6608 | - returnNoRows = 1; | |
| 6609 | - break; | |
| 6610 | - } | |
| 6611 | - } | |
| 6612 | - if( returnNoRows ){ | |
| 6613 | - pCur->ss.iBase = 1; | |
| 6614 | - pCur->ss.iTerm = 0; | |
| 6615 | - pCur->ss.iStep = 1; | |
| 6616 | - } | |
| 6617 | - if( idxNum & 0x08 ){ | |
| 6618 | - pCur->ss.isReversing = pCur->ss.iStep > 0; | |
| 6619 | - }else{ | |
| 6620 | - pCur->ss.isReversing = pCur->ss.iStep < 0; | |
| 6621 | - } | |
| 6622 | - setupSequence( &pCur->ss ); | |
| 6241 | + if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){ | |
| 6242 | + goto series_no_rows; | |
| 6243 | + }else if( pCur->bDesc ){ | |
| 6244 | + pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset); | |
| 6245 | + }else{ | |
| 6246 | + pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset); | |
| 6247 | + } | |
| 6248 | + } | |
| 6249 | + if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){ | |
| 6250 | + pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep); | |
| 6251 | + } | |
| 6252 | + } | |
| 6253 | + pCur->iValue = pCur->iBase; | |
| 6254 | + pCur->bDone = 0; | |
| 6623 | 6255 | return SQLITE_OK; |
| 6256 | + | |
| 6257 | +series_no_rows: | |
| 6258 | + pCur->iBase = 0; | |
| 6259 | + pCur->iTerm = 0; | |
| 6260 | + pCur->iStep = 1; | |
| 6261 | + pCur->bDesc = 0; | |
| 6262 | + pCur->bDone = 1; | |
| 6263 | + return SQLITE_OK; | |
| 6624 | 6264 | } |
| 6625 | 6265 | |
| 6626 | 6266 | /* |
| 6627 | 6267 | ** SQLite will invoke this method one or more times while planning a query |
| 6628 | 6268 | ** that uses the generate_series virtual table. This routine needs to create |
| @@ -6948,10 +6588,12 @@ | ||
| 6948 | 6588 | ** expression and M is the size of the input string. The matcher never |
| 6949 | 6589 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 6950 | 6590 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 6951 | 6591 | ** regular expression in the O(N*M) performance bound is computed after |
| 6952 | 6592 | ** this expansion. |
| 6593 | +** | |
| 6594 | +** To help prevent DoS attacks, the maximum size of the NFA is restricted. | |
| 6953 | 6595 | */ |
| 6954 | 6596 | #include <string.h> |
| 6955 | 6597 | #include <stdlib.h> |
| 6956 | 6598 | /* #include "sqlite3ext.h" */ |
| 6957 | 6599 | SQLITE_EXTENSION_INIT1 |
| @@ -6988,36 +6630,10 @@ | ||
| 6988 | 6630 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 6989 | 6631 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 6990 | 6632 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 6991 | 6633 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 6992 | 6634 | #define RE_OP_ATSTART 18 /* Currently at the start of the string */ |
| 6993 | - | |
| 6994 | -#if defined(SQLITE_DEBUG) | |
| 6995 | -/* Opcode names used for symbolic debugging */ | |
| 6996 | -static const char *ReOpName[] = { | |
| 6997 | - "EOF", | |
| 6998 | - "MATCH", | |
| 6999 | - "ANY", | |
| 7000 | - "ANYSTAR", | |
| 7001 | - "FORK", | |
| 7002 | - "GOTO", | |
| 7003 | - "ACCEPT", | |
| 7004 | - "CC_INC", | |
| 7005 | - "CC_EXC", | |
| 7006 | - "CC_VALUE", | |
| 7007 | - "CC_RANGE", | |
| 7008 | - "WORD", | |
| 7009 | - "NOTWORD", | |
| 7010 | - "DIGIT", | |
| 7011 | - "NOTDIGIT", | |
| 7012 | - "SPACE", | |
| 7013 | - "NOTSPACE", | |
| 7014 | - "BOUNDARY", | |
| 7015 | - "ATSTART", | |
| 7016 | -}; | |
| 7017 | -#endif /* SQLITE_DEBUG */ | |
| 7018 | - | |
| 7019 | 6635 | |
| 7020 | 6636 | /* Each opcode is a "state" in the NFA */ |
| 7021 | 6637 | typedef unsigned short ReStateNumber; |
| 7022 | 6638 | |
| 7023 | 6639 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -7051,10 +6667,11 @@ | ||
| 7051 | 6667 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 7052 | 6668 | unsigned char zInit[12]; /* Initial text to match */ |
| 7053 | 6669 | int nInit; /* Number of bytes in zInit */ |
| 7054 | 6670 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 7055 | 6671 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 6672 | + unsigned mxAlloc; /* Complexity limit */ | |
| 7056 | 6673 | }; |
| 7057 | 6674 | |
| 7058 | 6675 | /* Add a state to the given state set if it is not already there */ |
| 7059 | 6676 | static void re_add_state(ReStateSet *pSet, int newState){ |
| 7060 | 6677 | unsigned i; |
| @@ -7265,18 +6882,19 @@ | ||
| 7265 | 6882 | return rc; |
| 7266 | 6883 | } |
| 7267 | 6884 | |
| 7268 | 6885 | /* Resize the opcode and argument arrays for an RE under construction. |
| 7269 | 6886 | */ |
| 7270 | -static int re_resize(ReCompiled *p, int N){ | |
| 6887 | +static int re_resize(ReCompiled *p, unsigned int N){ | |
| 7271 | 6888 | char *aOp; |
| 7272 | 6889 | int *aArg; |
| 6890 | + if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; } | |
| 7273 | 6891 | aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); |
| 7274 | - if( aOp==0 ) return 1; | |
| 6892 | + if( aOp==0 ){ p->zErr = "out of memory"; return 1; } | |
| 7275 | 6893 | p->aOp = aOp; |
| 7276 | 6894 | aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); |
| 7277 | - if( aArg==0 ) return 1; | |
| 6895 | + if( aArg==0 ){ p->zErr = "out of memory"; return 1; } | |
| 7278 | 6896 | p->aArg = aArg; |
| 7279 | 6897 | p->nAlloc = N; |
| 7280 | 6898 | return 0; |
| 7281 | 6899 | } |
| 7282 | 6900 | |
| @@ -7303,11 +6921,11 @@ | ||
| 7303 | 6921 | } |
| 7304 | 6922 | |
| 7305 | 6923 | /* Make a copy of N opcodes starting at iStart onto the end of the RE |
| 7306 | 6924 | ** under construction. |
| 7307 | 6925 | */ |
| 7308 | -static void re_copy(ReCompiled *p, int iStart, int N){ | |
| 6926 | +static void re_copy(ReCompiled *p, int iStart, unsigned int N){ | |
| 7309 | 6927 | if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; |
| 7310 | 6928 | memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); |
| 7311 | 6929 | memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); |
| 7312 | 6930 | p->nState += N; |
| 7313 | 6931 | } |
| @@ -7456,22 +7074,30 @@ | ||
| 7456 | 7074 | case '^': { |
| 7457 | 7075 | re_append(p, RE_OP_ATSTART, 0); |
| 7458 | 7076 | break; |
| 7459 | 7077 | } |
| 7460 | 7078 | case '{': { |
| 7461 | - int m = 0, n = 0; | |
| 7462 | - int sz, j; | |
| 7079 | + unsigned int m = 0, n = 0; | |
| 7080 | + unsigned int sz, j; | |
| 7463 | 7081 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 7464 | - while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } | |
| 7082 | + while( (c=rePeek(p))>='0' && c<='9' ){ | |
| 7083 | + m = m*10 + c - '0'; | |
| 7084 | + if( m*2>p->mxAlloc ) return "REGEXP pattern too big"; | |
| 7085 | + p->sIn.i++; | |
| 7086 | + } | |
| 7465 | 7087 | n = m; |
| 7466 | 7088 | if( c==',' ){ |
| 7467 | 7089 | p->sIn.i++; |
| 7468 | 7090 | n = 0; |
| 7469 | - while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } | |
| 7091 | + while( (c=rePeek(p))>='0' && c<='9' ){ | |
| 7092 | + n = n*10 + c-'0'; | |
| 7093 | + if( n*2>p->mxAlloc ) return "REGEXP pattern too big"; | |
| 7094 | + p->sIn.i++; | |
| 7095 | + } | |
| 7470 | 7096 | } |
| 7471 | 7097 | if( c!='}' ) return "unmatched '{'"; |
| 7472 | - if( n>0 && n<m ) return "n less than m in '{m,n}'"; | |
| 7098 | + if( n<m ) return "n less than m in '{m,n}'"; | |
| 7473 | 7099 | p->sIn.i++; |
| 7474 | 7100 | sz = p->nState - iPrev; |
| 7475 | 7101 | if( m==0 ){ |
| 7476 | 7102 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 7477 | 7103 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| @@ -7483,11 +7109,11 @@ | ||
| 7483 | 7109 | for(j=m; j<n; j++){ |
| 7484 | 7110 | re_append(p, RE_OP_FORK, sz+1); |
| 7485 | 7111 | re_copy(p, iPrev, sz); |
| 7486 | 7112 | } |
| 7487 | 7113 | if( n==0 && m>0 ){ |
| 7488 | - re_append(p, RE_OP_FORK, -sz); | |
| 7114 | + re_append(p, RE_OP_FORK, -(int)sz); | |
| 7489 | 7115 | } |
| 7490 | 7116 | break; |
| 7491 | 7117 | } |
| 7492 | 7118 | case '[': { |
| 7493 | 7119 | unsigned int iFirst = p->nState; |
| @@ -7549,12 +7175,11 @@ | ||
| 7549 | 7175 | |
| 7550 | 7176 | /* Free and reclaim all the memory used by a previously compiled |
| 7551 | 7177 | ** regular expression. Applications should invoke this routine once |
| 7552 | 7178 | ** for every call to re_compile() to avoid memory leaks. |
| 7553 | 7179 | */ |
| 7554 | -static void re_free(void *p){ | |
| 7555 | - ReCompiled *pRe = (ReCompiled*)p; | |
| 7180 | +static void re_free(ReCompiled *pRe){ | |
| 7556 | 7181 | if( pRe ){ |
| 7557 | 7182 | sqlite3_free(pRe->aOp); |
| 7558 | 7183 | sqlite3_free(pRe->aArg); |
| 7559 | 7184 | sqlite3_free(pRe); |
| 7560 | 7185 | } |
| @@ -7564,11 +7189,16 @@ | ||
| 7564 | 7189 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 7565 | 7190 | ** expression suitable for us by re_match() and return a pointer to the |
| 7566 | 7191 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 7567 | 7192 | ** error message if something goes wrong. |
| 7568 | 7193 | */ |
| 7569 | -static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ | |
| 7194 | +static const char *re_compile( | |
| 7195 | + ReCompiled **ppRe, /* OUT: write compiled NFA here */ | |
| 7196 | + const char *zIn, /* Input regular expression */ | |
| 7197 | + int mxRe, /* Complexity limit */ | |
| 7198 | + int noCase /* True for caseless comparisons */ | |
| 7199 | +){ | |
| 7570 | 7200 | ReCompiled *pRe; |
| 7571 | 7201 | const char *zErr; |
| 7572 | 7202 | int i, j; |
| 7573 | 7203 | |
| 7574 | 7204 | *ppRe = 0; |
| @@ -7576,13 +7206,15 @@ | ||
| 7576 | 7206 | if( pRe==0 ){ |
| 7577 | 7207 | return "out of memory"; |
| 7578 | 7208 | } |
| 7579 | 7209 | memset(pRe, 0, sizeof(*pRe)); |
| 7580 | 7210 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 7211 | + pRe->mxAlloc = mxRe; | |
| 7581 | 7212 | if( re_resize(pRe, 30) ){ |
| 7213 | + zErr = pRe->zErr; | |
| 7582 | 7214 | re_free(pRe); |
| 7583 | - return "out of memory"; | |
| 7215 | + return zErr; | |
| 7584 | 7216 | } |
| 7585 | 7217 | if( zIn[0]=='^' ){ |
| 7586 | 7218 | zIn++; |
| 7587 | 7219 | }else{ |
| 7588 | 7220 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -7630,10 +7262,18 @@ | ||
| 7630 | 7262 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 7631 | 7263 | pRe->nInit = j; |
| 7632 | 7264 | } |
| 7633 | 7265 | return pRe->zErr; |
| 7634 | 7266 | } |
| 7267 | + | |
| 7268 | +/* | |
| 7269 | +** Compute a reasonable limit on the length of the REGEXP NFA. | |
| 7270 | +*/ | |
| 7271 | +static int re_maxlen(sqlite3_context *context){ | |
| 7272 | + sqlite3 *db = sqlite3_context_db_handle(context); | |
| 7273 | + return 75 + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1)/2; | |
| 7274 | +} | |
| 7635 | 7275 | |
| 7636 | 7276 | /* |
| 7637 | 7277 | ** Implementation of the regexp() SQL function. This function implements |
| 7638 | 7278 | ** the build-in REGEXP operator. The first argument to the function is the |
| 7639 | 7279 | ** pattern and the second argument is the string. So, the SQL statements: |
| @@ -7656,11 +7296,12 @@ | ||
| 7656 | 7296 | (void)argc; /* Unused */ |
| 7657 | 7297 | pRe = sqlite3_get_auxdata(context, 0); |
| 7658 | 7298 | if( pRe==0 ){ |
| 7659 | 7299 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7660 | 7300 | if( zPattern==0 ) return; |
| 7661 | - zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); | |
| 7301 | + zErr = re_compile(&pRe, zPattern, re_maxlen(context), | |
| 7302 | + sqlite3_user_data(context)!=0); | |
| 7662 | 7303 | if( zErr ){ |
| 7663 | 7304 | re_free(pRe); |
| 7664 | 7305 | sqlite3_result_error(context, zErr, -1); |
| 7665 | 7306 | return; |
| 7666 | 7307 | } |
| @@ -7698,14 +7339,36 @@ | ||
| 7698 | 7339 | sqlite3_str *pStr; |
| 7699 | 7340 | int i; |
| 7700 | 7341 | int n; |
| 7701 | 7342 | char *z; |
| 7702 | 7343 | (void)argc; |
| 7344 | + static const char *ReOpName[] = { | |
| 7345 | + "EOF", | |
| 7346 | + "MATCH", | |
| 7347 | + "ANY", | |
| 7348 | + "ANYSTAR", | |
| 7349 | + "FORK", | |
| 7350 | + "GOTO", | |
| 7351 | + "ACCEPT", | |
| 7352 | + "CC_INC", | |
| 7353 | + "CC_EXC", | |
| 7354 | + "CC_VALUE", | |
| 7355 | + "CC_RANGE", | |
| 7356 | + "WORD", | |
| 7357 | + "NOTWORD", | |
| 7358 | + "DIGIT", | |
| 7359 | + "NOTDIGIT", | |
| 7360 | + "SPACE", | |
| 7361 | + "NOTSPACE", | |
| 7362 | + "BOUNDARY", | |
| 7363 | + "ATSTART", | |
| 7364 | + }; | |
| 7703 | 7365 | |
| 7704 | 7366 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7705 | 7367 | if( zPattern==0 ) return; |
| 7706 | - zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); | |
| 7368 | + zErr = re_compile(&pRe, zPattern, re_maxlen(context), | |
| 7369 | + sqlite3_user_data(context)!=0); | |
| 7707 | 7370 | if( zErr ){ |
| 7708 | 7371 | re_free(pRe); |
| 7709 | 7372 | sqlite3_result_error(context, zErr, -1); |
| 7710 | 7373 | return; |
| 7711 | 7374 | } |
| @@ -9284,18 +8947,20 @@ | ||
| 9284 | 8947 | if( idxNum & 1 ){ |
| 9285 | 8948 | pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); |
| 9286 | 8949 | if( pCur->nPrefix>0 ){ |
| 9287 | 8950 | pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 9288 | 8951 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 8952 | + pCur->nPrefix = (int)strlen(pCur->zPrefix); | |
| 9289 | 8953 | } |
| 9290 | 8954 | iArg = 1; |
| 9291 | 8955 | } |
| 9292 | 8956 | if( idxNum & 2 ){ |
| 9293 | 8957 | pCur->nLine = sqlite3_value_bytes(argv[iArg]); |
| 9294 | 8958 | if( pCur->nLine>0 ){ |
| 9295 | 8959 | pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 9296 | 8960 | if( pCur->zLine==0 ) return SQLITE_NOMEM; |
| 8961 | + pCur->nLine = (int)strlen(pCur->zLine); | |
| 9297 | 8962 | } |
| 9298 | 8963 | } |
| 9299 | 8964 | if( pCur->zLine!=0 && pCur->zPrefix==0 ){ |
| 9300 | 8965 | int i = pCur->nLine; |
| 9301 | 8966 | while( i>0 && (IsAlnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ |
| @@ -9303,10 +8968,11 @@ | ||
| 9303 | 8968 | } |
| 9304 | 8969 | pCur->nPrefix = pCur->nLine - i; |
| 9305 | 8970 | if( pCur->nPrefix>0 ){ |
| 9306 | 8971 | pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); |
| 9307 | 8972 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 8973 | + pCur->nPrefix = (int)strlen(pCur->zPrefix); | |
| 9308 | 8974 | } |
| 9309 | 8975 | } |
| 9310 | 8976 | pCur->iRowid = 0; |
| 9311 | 8977 | pCur->ePhase = COMPLETION_FIRST_PHASE; |
| 9312 | 8978 | return completionNext(pVtabCursor); |
| @@ -10216,11 +9882,17 @@ | ||
| 10216 | 9882 | "method," /* 6: Compression method (integer) */ |
| 10217 | 9883 | "z HIDDEN" /* 7: Name of zip file */ |
| 10218 | 9884 | ") WITHOUT ROWID;"; |
| 10219 | 9885 | |
| 10220 | 9886 | #define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */ |
| 10221 | -#define ZIPFILE_BUFFER_SIZE (64*1024) | |
| 9887 | +#define ZIPFILE_MX_NAME (250) /* Windows limitation on filename size */ | |
| 9888 | + | |
| 9889 | +/* | |
| 9890 | +** The buffer should be large enough to contain 3 65536 byte strings - the | |
| 9891 | +** filename, the extra field and the file comment. | |
| 9892 | +*/ | |
| 9893 | +#define ZIPFILE_BUFFER_SIZE (200*1024) | |
| 10222 | 9894 | |
| 10223 | 9895 | |
| 10224 | 9896 | /* |
| 10225 | 9897 | ** Magic numbers used to read and write zip files. |
| 10226 | 9898 | ** |
| @@ -10773,10 +10445,11 @@ | ||
| 10773 | 10445 | pLFH->crc32 = zipfileRead32(aRead); |
| 10774 | 10446 | pLFH->szCompressed = zipfileRead32(aRead); |
| 10775 | 10447 | pLFH->szUncompressed = zipfileRead32(aRead); |
| 10776 | 10448 | pLFH->nFile = zipfileRead16(aRead); |
| 10777 | 10449 | pLFH->nExtra = zipfileRead16(aRead); |
| 10450 | + if( pLFH->nFile>ZIPFILE_MX_NAME ) rc = SQLITE_ERROR; | |
| 10778 | 10451 | } |
| 10779 | 10452 | return rc; |
| 10780 | 10453 | } |
| 10781 | 10454 | |
| 10782 | 10455 | |
| @@ -10898,10 +10571,19 @@ | ||
| 10898 | 10571 | || mUnixTime==zipfileMtime(pCds) |
| 10899 | 10572 | || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds)) |
| 10900 | 10573 | /* || (mUnixTime % 2) */ |
| 10901 | 10574 | ); |
| 10902 | 10575 | } |
| 10576 | + | |
| 10577 | +/* | |
| 10578 | +** Set (*pzErr) to point to a buffer from sqlite3_malloc() containing a | |
| 10579 | +** generic corruption message and return SQLITE_CORRUPT; | |
| 10580 | +*/ | |
| 10581 | +static int zipfileCorrupt(char **pzErr){ | |
| 10582 | + *pzErr = sqlite3_mprintf("zip archive is corrupt"); | |
| 10583 | + return SQLITE_CORRUPT; | |
| 10584 | +} | |
| 10903 | 10585 | |
| 10904 | 10586 | /* |
| 10905 | 10587 | ** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in |
| 10906 | 10588 | ** size) containing an entire zip archive image. Or, if aBlob is NULL, |
| 10907 | 10589 | ** then pFile is a file-handle open on a zip file. In either case, this |
| @@ -10921,16 +10603,19 @@ | ||
| 10921 | 10603 | ZipfileEntry **ppEntry /* OUT: Pointer to new object */ |
| 10922 | 10604 | ){ |
| 10923 | 10605 | u8 *aRead; |
| 10924 | 10606 | char **pzErr = &pTab->base.zErrMsg; |
| 10925 | 10607 | int rc = SQLITE_OK; |
| 10926 | - (void)nBlob; | |
| 10927 | 10608 | |
| 10928 | 10609 | if( aBlob==0 ){ |
| 10929 | 10610 | aRead = pTab->aBuffer; |
| 10930 | 10611 | rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); |
| 10931 | 10612 | }else{ |
| 10613 | + if( (iOff+ZIPFILE_CDS_FIXED_SZ)>nBlob ){ | |
| 10614 | + /* Not enough data for the CDS structure. Corruption. */ | |
| 10615 | + return zipfileCorrupt(pzErr); | |
| 10616 | + } | |
| 10932 | 10617 | aRead = (u8*)&aBlob[iOff]; |
| 10933 | 10618 | } |
| 10934 | 10619 | |
| 10935 | 10620 | if( rc==SQLITE_OK ){ |
| 10936 | 10621 | sqlite3_int64 nAlloc; |
| @@ -10957,10 +10642,13 @@ | ||
| 10957 | 10642 | rc = zipfileReadData( |
| 10958 | 10643 | pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr |
| 10959 | 10644 | ); |
| 10960 | 10645 | }else{ |
| 10961 | 10646 | aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ]; |
| 10647 | + if( (iOff + ZIPFILE_LFH_FIXED_SZ + nFile + nExtra)>nBlob ){ | |
| 10648 | + rc = zipfileCorrupt(pzErr); | |
| 10649 | + } | |
| 10962 | 10650 | } |
| 10963 | 10651 | } |
| 10964 | 10652 | |
| 10965 | 10653 | if( rc==SQLITE_OK ){ |
| 10966 | 10654 | u32 *pt = &pNew->mUnixTime; |
| @@ -10979,19 +10667,26 @@ | ||
| 10979 | 10667 | ZipfileLFH lfh; |
| 10980 | 10668 | if( pFile ){ |
| 10981 | 10669 | rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr); |
| 10982 | 10670 | }else{ |
| 10983 | 10671 | aRead = (u8*)&aBlob[pNew->cds.iOffset]; |
| 10672 | + if( (pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>nBlob ){ | |
| 10673 | + rc = zipfileCorrupt(pzErr); | |
| 10674 | + } | |
| 10984 | 10675 | } |
| 10985 | 10676 | |
| 10986 | 10677 | if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh); |
| 10987 | 10678 | if( rc==SQLITE_OK ){ |
| 10988 | 10679 | pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; |
| 10989 | 10680 | pNew->iDataOff += lfh.nFile + lfh.nExtra; |
| 10990 | 10681 | if( aBlob && pNew->cds.szCompressed ){ |
| 10991 | - pNew->aData = &pNew->aExtra[nExtra]; | |
| 10992 | - memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); | |
| 10682 | + if( pNew->iDataOff + pNew->cds.szCompressed > nBlob ){ | |
| 10683 | + rc = zipfileCorrupt(pzErr); | |
| 10684 | + }else{ | |
| 10685 | + pNew->aData = &pNew->aExtra[nExtra]; | |
| 10686 | + memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); | |
| 10687 | + } | |
| 10993 | 10688 | } |
| 10994 | 10689 | }else{ |
| 10995 | 10690 | *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", |
| 10996 | 10691 | (int)pNew->cds.iOffset |
| 10997 | 10692 | ); |
| @@ -11774,10 +11469,15 @@ | ||
| 11774 | 11469 | |
| 11775 | 11470 | if( rc==SQLITE_OK ){ |
| 11776 | 11471 | zPath = (const char*)sqlite3_value_text(apVal[2]); |
| 11777 | 11472 | if( zPath==0 ) zPath = ""; |
| 11778 | 11473 | nPath = (int)strlen(zPath); |
| 11474 | + if( nPath>ZIPFILE_MX_NAME ){ | |
| 11475 | + zipfileTableErr(pTab, "filename too long; max: %d bytes", | |
| 11476 | + ZIPFILE_MX_NAME); | |
| 11477 | + rc = SQLITE_CONSTRAINT; | |
| 11478 | + } | |
| 11779 | 11479 | mTime = zipfileGetTime(apVal[4]); |
| 11780 | 11480 | } |
| 11781 | 11481 | |
| 11782 | 11482 | if( rc==SQLITE_OK && bIsDir ){ |
| 11783 | 11483 | /* For a directory, check that the last character in the path is a |
| @@ -12135,10 +11835,17 @@ | ||
| 12135 | 11835 | if( zName==0 ){ |
| 12136 | 11836 | zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL"); |
| 12137 | 11837 | rc = SQLITE_ERROR; |
| 12138 | 11838 | goto zipfile_step_out; |
| 12139 | 11839 | } |
| 11840 | + if( nName>ZIPFILE_MX_NAME ){ | |
| 11841 | + zErr = sqlite3_mprintf( | |
| 11842 | + "filename argument to zipfile() too big; max: %d bytes", | |
| 11843 | + ZIPFILE_MX_NAME); | |
| 11844 | + rc = SQLITE_ERROR; | |
| 11845 | + goto zipfile_step_out; | |
| 11846 | + } | |
| 12140 | 11847 | |
| 12141 | 11848 | /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use |
| 12142 | 11849 | ** deflate compression) or NULL (choose automatically). */ |
| 12143 | 11850 | if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){ |
| 12144 | 11851 | iMethod = (int)sqlite3_value_int64(pMethod); |
| @@ -12477,10 +12184,11 @@ | ||
| 12477 | 12184 | return rc; |
| 12478 | 12185 | } |
| 12479 | 12186 | |
| 12480 | 12187 | /************************* End ../ext/misc/sqlar.c ********************/ |
| 12481 | 12188 | #endif |
| 12189 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 12482 | 12190 | /************************* Begin ../ext/expert/sqlite3expert.h ******************/ |
| 12483 | 12191 | /* |
| 12484 | 12192 | ** 2017 April 07 |
| 12485 | 12193 | ** |
| 12486 | 12194 | ** The author disclaims copyright to this source code. In place of |
| @@ -14885,10 +14593,11 @@ | ||
| 14885 | 14593 | } |
| 14886 | 14594 | |
| 14887 | 14595 | #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
| 14888 | 14596 | |
| 14889 | 14597 | /************************* End ../ext/expert/sqlite3expert.c ********************/ |
| 14598 | +#endif | |
| 14890 | 14599 | /************************* Begin ../ext/intck/sqlite3intck.h ******************/ |
| 14891 | 14600 | /* |
| 14892 | 14601 | ** 2024-02-08 |
| 14893 | 14602 | ** |
| 14894 | 14603 | ** The author disclaims copyright to this source code. In place of |
| @@ -21525,15 +21234,17 @@ | ||
| 21525 | 21234 | char **azFilter; /* Array of xFilter rejection GLOB patterns */ |
| 21526 | 21235 | sqlite3_session *p; /* The open session */ |
| 21527 | 21236 | }; |
| 21528 | 21237 | #endif |
| 21529 | 21238 | |
| 21239 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 21530 | 21240 | typedef struct ExpertInfo ExpertInfo; |
| 21531 | 21241 | struct ExpertInfo { |
| 21532 | 21242 | sqlite3expert *pExpert; |
| 21533 | 21243 | int bVerbose; |
| 21534 | 21244 | }; |
| 21245 | +#endif | |
| 21535 | 21246 | |
| 21536 | 21247 | /* A single line in the EQP output */ |
| 21537 | 21248 | typedef struct EQPGraphRow EQPGraphRow; |
| 21538 | 21249 | struct EQPGraphRow { |
| 21539 | 21250 | int iEqpId; /* ID for this row */ |
| @@ -21633,11 +21344,13 @@ | ||
| 21633 | 21344 | int *aiIndent; /* Array of indents used in MODE_Explain */ |
| 21634 | 21345 | int nIndent; /* Size of array aiIndent[] */ |
| 21635 | 21346 | int iIndent; /* Index of current op in aiIndent[] */ |
| 21636 | 21347 | char *zNonce; /* Nonce for temporary safe-mode escapes */ |
| 21637 | 21348 | EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ |
| 21349 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 21638 | 21350 | ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ |
| 21351 | +#endif | |
| 21639 | 21352 | #ifdef SQLITE_SHELL_FIDDLE |
| 21640 | 21353 | struct { |
| 21641 | 21354 | const char * zInput; /* Input string from wasm/JS proxy */ |
| 21642 | 21355 | const char * zPos; /* Cursor pos into zInput */ |
| 21643 | 21356 | const char * zDefaultDbName; /* Default name for db file */ |
| @@ -21661,13 +21374,12 @@ | ||
| 21661 | 21374 | */ |
| 21662 | 21375 | #define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ |
| 21663 | 21376 | #define SHELL_OPEN_NORMAL 1 /* Normal database file */ |
| 21664 | 21377 | #define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ |
| 21665 | 21378 | #define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ |
| 21666 | -#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ | |
| 21667 | -#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ | |
| 21668 | -#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */ | |
| 21379 | +#define SHELL_OPEN_DESERIALIZE 4 /* Open using sqlite3_deserialize() */ | |
| 21380 | +#define SHELL_OPEN_HEXDB 5 /* Use "dbtotxt" output as data source */ | |
| 21669 | 21381 | |
| 21670 | 21382 | /* Allowed values for ShellState.eTraceType |
| 21671 | 21383 | */ |
| 21672 | 21384 | #define SHELL_TRACE_PLAIN 0 /* Show input SQL text */ |
| 21673 | 21385 | #define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */ |
| @@ -22006,11 +21718,11 @@ | ||
| 22006 | 21718 | */ |
| 22007 | 21719 | static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ |
| 22008 | 21720 | int i; |
| 22009 | 21721 | unsigned char *aBlob = (unsigned char*)pBlob; |
| 22010 | 21722 | |
| 22011 | - char *zStr = sqlite3_malloc(nBlob*2 + 1); | |
| 21723 | + char *zStr = sqlite3_malloc64((i64)nBlob*2 + 1); | |
| 22012 | 21724 | shell_check_oom(zStr); |
| 22013 | 21725 | |
| 22014 | 21726 | for(i=0; i<nBlob; i++){ |
| 22015 | 21727 | static const char aHex[] = { |
| 22016 | 21728 | '0', '1', '2', '3', '4', '5', '6', '7', |
| @@ -22027,11 +21739,11 @@ | ||
| 22027 | 21739 | |
| 22028 | 21740 | /* |
| 22029 | 21741 | ** Output the given string as a quoted string using SQL quoting conventions: |
| 22030 | 21742 | ** |
| 22031 | 21743 | ** (1) Single quotes (') within the string are doubled |
| 22032 | -** (2) The whle string is enclosed in '...' | |
| 21744 | +** (2) The while string is enclosed in '...' | |
| 22033 | 21745 | ** (3) Control characters other than \n, \t, and \r\n are escaped |
| 22034 | 21746 | ** using \u00XX notation and if such substitutions occur, |
| 22035 | 21747 | ** the whole string is enclosed in unistr('...') instead of '...'. |
| 22036 | 21748 | ** |
| 22037 | 21749 | ** Step (3) is omitted if the control-character escape mode is OFF. |
| @@ -22273,11 +21985,11 @@ | ||
| 22273 | 21985 | ** |
| 22274 | 21986 | ** Escaping is needed if the string contains any control characters |
| 22275 | 21987 | ** other than \t, \n, and \r\n |
| 22276 | 21988 | ** |
| 22277 | 21989 | ** If no escaping is needed (the common case) then set *ppFree to NULL |
| 22278 | -** and return the original string. If escapingn is needed, write the | |
| 21990 | +** and return the original string. If escaping is needed, write the | |
| 22279 | 21991 | ** escaped string into memory obtained from sqlite3_malloc64() or the |
| 22280 | 21992 | ** equivalent, and return the new string and set *ppFree to the new string |
| 22281 | 21993 | ** as well. |
| 22282 | 21994 | ** |
| 22283 | 21995 | ** The caller is responsible for freeing *ppFree if it is non-NULL in order |
| @@ -23278,32 +22990,21 @@ | ||
| 23278 | 22990 | ** Set the destination table field of the ShellState structure to |
| 23279 | 22991 | ** the name of the table given. Escape any quote characters in the |
| 23280 | 22992 | ** table name. |
| 23281 | 22993 | */ |
| 23282 | 22994 | static void set_table_name(ShellState *p, const char *zName){ |
| 23283 | - int i, n; | |
| 23284 | - char cQuote; | |
| 23285 | - char *z; | |
| 23286 | - | |
| 23287 | 22995 | if( p->zDestTable ){ |
| 23288 | - free(p->zDestTable); | |
| 22996 | + sqlite3_free(p->zDestTable); | |
| 23289 | 22997 | p->zDestTable = 0; |
| 23290 | 22998 | } |
| 23291 | 22999 | if( zName==0 ) return; |
| 23292 | - cQuote = quoteChar(zName); | |
| 23293 | - n = strlen30(zName); | |
| 23294 | - if( cQuote ) n += n+2; | |
| 23295 | - z = p->zDestTable = malloc( n+1 ); | |
| 23296 | - shell_check_oom(z); | |
| 23297 | - n = 0; | |
| 23298 | - if( cQuote ) z[n++] = cQuote; | |
| 23299 | - for(i=0; zName[i]; i++){ | |
| 23300 | - z[n++] = zName[i]; | |
| 23301 | - if( zName[i]==cQuote ) z[n++] = cQuote; | |
| 23302 | - } | |
| 23303 | - if( cQuote ) z[n++] = cQuote; | |
| 23304 | - z[n] = 0; | |
| 23000 | + if( quoteChar(zName) ){ | |
| 23001 | + p->zDestTable = sqlite3_mprintf("\"%w\"", zName); | |
| 23002 | + }else{ | |
| 23003 | + p->zDestTable = sqlite3_mprintf("%s", zName); | |
| 23004 | + } | |
| 23005 | + shell_check_oom(p->zDestTable); | |
| 23305 | 23006 | } |
| 23306 | 23007 | |
| 23307 | 23008 | /* |
| 23308 | 23009 | ** Maybe construct two lines of text that point out the position of a |
| 23309 | 23010 | ** syntax error. Return a pointer to the text, in memory obtained from |
| @@ -23496,12 +23197,12 @@ | ||
| 23496 | 23197 | static int display_stats( |
| 23497 | 23198 | sqlite3 *db, /* Database to query */ |
| 23498 | 23199 | ShellState *pArg, /* Pointer to ShellState */ |
| 23499 | 23200 | int bReset /* True to reset the stats */ |
| 23500 | 23201 | ){ |
| 23501 | - int iCur; | |
| 23502 | - int iHiwtr; | |
| 23202 | + int iCur, iHiwtr; | |
| 23203 | + sqlite3_int64 iCur64, iHiwtr64; | |
| 23503 | 23204 | FILE *out; |
| 23504 | 23205 | if( pArg==0 || pArg->out==0 ) return 0; |
| 23505 | 23206 | out = pArg->out; |
| 23506 | 23207 | |
| 23507 | 23208 | if( pArg->pStmt && pArg->statsOn==2 ){ |
| @@ -23586,18 +23287,25 @@ | ||
| 23586 | 23287 | "Page cache hits: %d\n", iCur); |
| 23587 | 23288 | iHiwtr = iCur = -1; |
| 23588 | 23289 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); |
| 23589 | 23290 | sqlite3_fprintf(out, |
| 23590 | 23291 | "Page cache misses: %d\n", iCur); |
| 23292 | + iHiwtr64 = iCur64 = -1; | |
| 23293 | + sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, | |
| 23294 | + 0); | |
| 23591 | 23295 | iHiwtr = iCur = -1; |
| 23592 | 23296 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); |
| 23593 | 23297 | sqlite3_fprintf(out, |
| 23594 | 23298 | "Page cache writes: %d\n", iCur); |
| 23595 | 23299 | iHiwtr = iCur = -1; |
| 23596 | 23300 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); |
| 23597 | 23301 | sqlite3_fprintf(out, |
| 23598 | 23302 | "Page cache spills: %d\n", iCur); |
| 23303 | + sqlite3_fprintf(out, | |
| 23304 | + "Temporary data spilled to disk: %lld\n", iCur64); | |
| 23305 | + sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, | |
| 23306 | + 1); | |
| 23599 | 23307 | iHiwtr = iCur = -1; |
| 23600 | 23308 | sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); |
| 23601 | 23309 | sqlite3_fprintf(out, |
| 23602 | 23310 | "Schema Heap Usage: %d bytes\n", iCur); |
| 23603 | 23311 | iHiwtr = iCur = -1; |
| @@ -23999,10 +23707,40 @@ | ||
| 23999 | 23707 | char *zBuf = sqlite3_malloc64( szVar-5 ); |
| 24000 | 23708 | if( zBuf ){ |
| 24001 | 23709 | memcpy(zBuf, &zVar[6], szVar-5); |
| 24002 | 23710 | sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); |
| 24003 | 23711 | } |
| 23712 | +#ifdef SQLITE_ENABLE_CARRAY | |
| 23713 | + }else if( strncmp(zVar, "$carray_", 8)==0 ){ | |
| 23714 | + static char *azColorNames[] = { | |
| 23715 | + "azure", "black", "blue", "brown", "cyan", "fuchsia", "gold", | |
| 23716 | + "gray", "green", "indigo", "khaki", "lime", "magenta", "maroon", | |
| 23717 | + "navy", "olive", "orange", "pink", "purple", "red", "silver", | |
| 23718 | + "tan", "teal", "violet", "white", "yellow" | |
| 23719 | + }; | |
| 23720 | + static int aPrimes[] = { | |
| 23721 | + 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, | |
| 23722 | + 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 | |
| 23723 | + }; | |
| 23724 | + /* Special bindings: carray($carray_clr), carray($carray_primes) | |
| 23725 | + ** with --unsafe-testing: carray($carray_clr_p,26,'char*'), | |
| 23726 | + ** carray($carray_primes_p,26,'int32') | |
| 23727 | + */ | |
| 23728 | + if( strcmp(zVar+8,"clr")==0 ){ | |
| 23729 | + sqlite3_carray_bind(pStmt,i,azColorNames,26,SQLITE_CARRAY_TEXT,0); | |
| 23730 | + }else if( strcmp(zVar+8,"primes")==0 ){ | |
| 23731 | + sqlite3_carray_bind(pStmt,i,aPrimes,26,SQLITE_CARRAY_INT32,0); | |
| 23732 | + }else if( strcmp(zVar+8,"clr_p")==0 | |
| 23733 | + && ShellHasFlag(pArg,SHFLG_TestingMode) ){ | |
| 23734 | + sqlite3_bind_pointer(pStmt,i,azColorNames,"carray",0); | |
| 23735 | + }else if( strcmp(zVar+8,"primes_p")==0 | |
| 23736 | + && ShellHasFlag(pArg,SHFLG_TestingMode) ){ | |
| 23737 | + sqlite3_bind_pointer(pStmt,i,aPrimes,"carray",0); | |
| 23738 | + }else{ | |
| 23739 | + sqlite3_bind_null(pStmt, i); | |
| 23740 | + } | |
| 23741 | +#endif | |
| 24004 | 23742 | }else{ |
| 24005 | 23743 | sqlite3_bind_null(pStmt, i); |
| 24006 | 23744 | } |
| 24007 | 23745 | sqlite3_reset(pQ); |
| 24008 | 23746 | } |
| @@ -24581,11 +24319,11 @@ | ||
| 24581 | 24319 | } |
| 24582 | 24320 | } |
| 24583 | 24321 | } |
| 24584 | 24322 | } |
| 24585 | 24323 | |
| 24586 | -#ifndef SQLITE_OMIT_VIRTUALTABLE | |
| 24324 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 24587 | 24325 | /* |
| 24588 | 24326 | ** This function is called to process SQL if the previous shell command |
| 24589 | 24327 | ** was ".expert". It passes the SQL in the second argument directly to |
| 24590 | 24328 | ** the sqlite3expert object. |
| 24591 | 24329 | ** |
| @@ -24713,11 +24451,11 @@ | ||
| 24713 | 24451 | } |
| 24714 | 24452 | sqlite3_free(zErr); |
| 24715 | 24453 | |
| 24716 | 24454 | return rc; |
| 24717 | 24455 | } |
| 24718 | -#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ | |
| 24456 | +#endif /* !SQLITE_OMIT_VIRTUALTABLE && !SQLITE_OMIT_AUTHORIZATION */ | |
| 24719 | 24457 | |
| 24720 | 24458 | /* |
| 24721 | 24459 | ** Execute a statement or set of statements. Print |
| 24722 | 24460 | ** any result rows/columns depending on the current mode |
| 24723 | 24461 | ** set via the supplied callback. |
| @@ -24739,11 +24477,11 @@ | ||
| 24739 | 24477 | |
| 24740 | 24478 | if( pzErrMsg ){ |
| 24741 | 24479 | *pzErrMsg = NULL; |
| 24742 | 24480 | } |
| 24743 | 24481 | |
| 24744 | -#ifndef SQLITE_OMIT_VIRTUALTABLE | |
| 24482 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 24745 | 24483 | if( pArg->expert.pExpert ){ |
| 24746 | 24484 | rc = expertHandleSQL(pArg, zSql, pzErrMsg); |
| 24747 | 24485 | return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); |
| 24748 | 24486 | } |
| 24749 | 24487 | #endif |
| @@ -24901,11 +24639,11 @@ | ||
| 24901 | 24639 | static char **tableColumnList(ShellState *p, const char *zTab){ |
| 24902 | 24640 | char **azCol = 0; |
| 24903 | 24641 | sqlite3_stmt *pStmt; |
| 24904 | 24642 | char *zSql; |
| 24905 | 24643 | int nCol = 0; |
| 24906 | - int nAlloc = 0; | |
| 24644 | + i64 nAlloc = 0; | |
| 24907 | 24645 | int nPK = 0; /* Number of PRIMARY KEY columns seen */ |
| 24908 | 24646 | int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ |
| 24909 | 24647 | int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); |
| 24910 | 24648 | int rc; |
| 24911 | 24649 | |
| @@ -24915,11 +24653,11 @@ | ||
| 24915 | 24653 | sqlite3_free(zSql); |
| 24916 | 24654 | if( rc ) return 0; |
| 24917 | 24655 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 24918 | 24656 | if( nCol>=nAlloc-2 ){ |
| 24919 | 24657 | nAlloc = nAlloc*2 + nCol + 10; |
| 24920 | - azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0])); | |
| 24658 | + azCol = sqlite3_realloc64(azCol, nAlloc*sizeof(azCol[0])); | |
| 24921 | 24659 | shell_check_oom(azCol); |
| 24922 | 24660 | } |
| 24923 | 24661 | azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); |
| 24924 | 24662 | shell_check_oom(azCol[nCol]); |
| 24925 | 24663 | if( sqlite3_column_int(pStmt, 5) ){ |
| @@ -25106,17 +24844,17 @@ | ||
| 25106 | 24844 | appendText(&sSelect, " FROM ", 0); |
| 25107 | 24845 | appendText(&sSelect, zTable, quoteChar(zTable)); |
| 25108 | 24846 | |
| 25109 | 24847 | savedDestTable = p->zDestTable; |
| 25110 | 24848 | savedMode = p->mode; |
| 25111 | - p->zDestTable = sTable.z; | |
| 24849 | + p->zDestTable = sTable.zTxt; | |
| 25112 | 24850 | p->mode = p->cMode = MODE_Insert; |
| 25113 | - rc = shell_exec(p, sSelect.z, 0); | |
| 24851 | + rc = shell_exec(p, sSelect.zTxt, 0); | |
| 25114 | 24852 | if( (rc&0xff)==SQLITE_CORRUPT ){ |
| 25115 | 24853 | sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out); |
| 25116 | 24854 | toggleSelectOrder(p->db); |
| 25117 | - shell_exec(p, sSelect.z, 0); | |
| 24855 | + shell_exec(p, sSelect.zTxt, 0); | |
| 25118 | 24856 | toggleSelectOrder(p->db); |
| 25119 | 24857 | } |
| 25120 | 24858 | p->zDestTable = savedDestTable; |
| 25121 | 24859 | p->mode = savedMode; |
| 25122 | 24860 | freeText(&sTable); |
| @@ -25245,11 +24983,13 @@ | ||
| 25245 | 24983 | " --bom Put a UTF8 byte-order mark on intermediate file", |
| 25246 | 24984 | #endif |
| 25247 | 24985 | #ifndef SQLITE_SHELL_FIDDLE |
| 25248 | 24986 | ".exit ?CODE? Exit this program with return-code CODE", |
| 25249 | 24987 | #endif |
| 24988 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 25250 | 24989 | ".expert EXPERIMENTAL. Suggest indexes for queries", |
| 24990 | +#endif | |
| 25251 | 24991 | ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", |
| 25252 | 24992 | ".filectrl CMD ... Run various sqlite3_file_control() operations", |
| 25253 | 24993 | " --schema SCHEMA Use SCHEMA instead of \"main\"", |
| 25254 | 24994 | " --help Show CMD details", |
| 25255 | 24995 | ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", |
| @@ -25270,11 +25010,11 @@ | ||
| 25270 | 25010 | " from the \".mode\" output mode", |
| 25271 | 25011 | " * If FILE begins with \"|\" then it is a command that generates the", |
| 25272 | 25012 | " input text.", |
| 25273 | 25013 | #endif |
| 25274 | 25014 | #ifndef SQLITE_OMIT_TEST_CONTROL |
| 25275 | - ",imposter INDEX TABLE Create imposter table TABLE on index INDEX", | |
| 25015 | + ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", | |
| 25276 | 25016 | #endif |
| 25277 | 25017 | ".indexes ?TABLE? Show names of indexes", |
| 25278 | 25018 | " If TABLE is specified, only show indexes for", |
| 25279 | 25019 | " tables matching TABLE using the LIKE operator.", |
| 25280 | 25020 | ".intck ?STEPS_PER_UNLOCK? Run an incremental integrity check on the db", |
| @@ -25337,11 +25077,17 @@ | ||
| 25337 | 25077 | " Options:", |
| 25338 | 25078 | " --append Use appendvfs to append database to the end of FILE", |
| 25339 | 25079 | #endif |
| 25340 | 25080 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 25341 | 25081 | " --deserialize Load into memory using sqlite3_deserialize()", |
| 25082 | +#endif | |
| 25083 | +/*" --exclusive Set the SQLITE_OPEN_EXCLUSIVE flag", UNDOCUMENTED */ | |
| 25084 | +#ifndef SQLITE_OMIT_DESERIALIZE | |
| 25342 | 25085 | " --hexdb Load the output of \"dbtotxt\" as an in-memory db", |
| 25086 | +#endif | |
| 25087 | + " --ifexist Only open if FILE already exists", | |
| 25088 | +#ifndef SQLITE_OMIT_DESERIALIZE | |
| 25343 | 25089 | " --maxsize N Maximum size for --hexdb or --deserialized database", |
| 25344 | 25090 | #endif |
| 25345 | 25091 | " --new Initialize FILE to an empty database", |
| 25346 | 25092 | " --nofollow Do not follow symbolic links", |
| 25347 | 25093 | " --readonly Open FILE readonly", |
| @@ -25726,14 +25472,14 @@ | ||
| 25726 | 25472 | ** If p->aAuxDb[].zDbFilename is 0, then read from standard input. |
| 25727 | 25473 | */ |
| 25728 | 25474 | static unsigned char *readHexDb(ShellState *p, int *pnData){ |
| 25729 | 25475 | unsigned char *a = 0; |
| 25730 | 25476 | int nLine; |
| 25731 | - int n = 0; | |
| 25477 | + int n = 0; /* Size of db per first line of hex dump */ | |
| 25478 | + i64 sz = 0; /* n rounded up to nearest page boundary */ | |
| 25732 | 25479 | int pgsz = 0; |
| 25733 | - int iOffset = 0; | |
| 25734 | - int j, k; | |
| 25480 | + i64 iOffset = 0; | |
| 25735 | 25481 | int rc; |
| 25736 | 25482 | FILE *in; |
| 25737 | 25483 | const char *zDbFilename = p->pAuxDb->zDbFilename; |
| 25738 | 25484 | unsigned int x[16]; |
| 25739 | 25485 | char zLine[1000]; |
| @@ -25753,20 +25499,21 @@ | ||
| 25753 | 25499 | nLine++; |
| 25754 | 25500 | if( sqlite3_fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; |
| 25755 | 25501 | rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); |
| 25756 | 25502 | if( rc!=2 ) goto readHexDb_error; |
| 25757 | 25503 | if( n<0 ) goto readHexDb_error; |
| 25758 | - if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; | |
| 25759 | - n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ | |
| 25760 | - a = sqlite3_malloc( n ? n : 1 ); | |
| 25761 | - shell_check_oom(a); | |
| 25762 | - memset(a, 0, n); | |
| 25763 | 25504 | if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ |
| 25764 | 25505 | sqlite3_fputs("invalid pagesize\n", stderr); |
| 25765 | 25506 | goto readHexDb_error; |
| 25766 | 25507 | } |
| 25508 | + sz = ((i64)n+pgsz-1)&~(pgsz-1); /* Round up to nearest multiple of pgsz */ | |
| 25509 | + a = sqlite3_malloc64( sz ? sz : 1 ); | |
| 25510 | + shell_check_oom(a); | |
| 25511 | + memset(a, 0, sz); | |
| 25767 | 25512 | for(nLine++; sqlite3_fgets(zLine, sizeof(zLine), in)!=0; nLine++){ |
| 25513 | + int j = 0; /* Page number from "| page" line */ | |
| 25514 | + int k = 0; /* Offset from "| page" line */ | |
| 25768 | 25515 | rc = sscanf(zLine, "| page %d offset %d", &j, &k); |
| 25769 | 25516 | if( rc==2 ){ |
| 25770 | 25517 | iOffset = k; |
| 25771 | 25518 | continue; |
| 25772 | 25519 | } |
| @@ -25775,18 +25522,18 @@ | ||
| 25775 | 25522 | } |
| 25776 | 25523 | rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", |
| 25777 | 25524 | &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], |
| 25778 | 25525 | &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); |
| 25779 | 25526 | if( rc==17 ){ |
| 25780 | - k = iOffset+j; | |
| 25781 | - if( k+16<=n && k>=0 ){ | |
| 25527 | + i64 iOff = iOffset+j; | |
| 25528 | + if( iOff+16<=sz && iOff>=0 ){ | |
| 25782 | 25529 | int ii; |
| 25783 | - for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; | |
| 25530 | + for(ii=0; ii<16; ii++) a[iOff+ii] = x[ii]&0xff; | |
| 25784 | 25531 | } |
| 25785 | 25532 | } |
| 25786 | 25533 | } |
| 25787 | - *pnData = n; | |
| 25534 | + *pnData = sz; | |
| 25788 | 25535 | if( in!=p->in ){ |
| 25789 | 25536 | fclose(in); |
| 25790 | 25537 | }else{ |
| 25791 | 25538 | p->lineno = nLine; |
| 25792 | 25539 | } |
| @@ -25849,11 +25596,11 @@ | ||
| 25849 | 25596 | p->pLog = pSavedLog; |
| 25850 | 25597 | |
| 25851 | 25598 | if( zFake ){ |
| 25852 | 25599 | sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), |
| 25853 | 25600 | -1, sqlite3_free); |
| 25854 | - free(zFake); | |
| 25601 | + sqlite3_free(zFake); | |
| 25855 | 25602 | } |
| 25856 | 25603 | } |
| 25857 | 25604 | |
| 25858 | 25605 | /* Flags for open_db(). |
| 25859 | 25606 | ** |
| @@ -25881,14 +25628,17 @@ | ||
| 25881 | 25628 | }else{ |
| 25882 | 25629 | p->openMode = (u8)deduceDatabaseType(zDbFilename, |
| 25883 | 25630 | (openFlags & OPEN_DB_ZIPFILE)!=0); |
| 25884 | 25631 | } |
| 25885 | 25632 | } |
| 25633 | + if( (p->openFlags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE))==0 ){ | |
| 25634 | + if( p->openFlags==0 ) p->openFlags = SQLITE_OPEN_CREATE; | |
| 25635 | + p->openFlags |= SQLITE_OPEN_READWRITE; | |
| 25636 | + } | |
| 25886 | 25637 | switch( p->openMode ){ |
| 25887 | 25638 | case SHELL_OPEN_APPENDVFS: { |
| 25888 | - sqlite3_open_v2(zDbFilename, &p->db, | |
| 25889 | - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); | |
| 25639 | + sqlite3_open_v2(zDbFilename, &p->db, p->openFlags, "apndvfs"); | |
| 25890 | 25640 | break; |
| 25891 | 25641 | } |
| 25892 | 25642 | case SHELL_OPEN_HEXDB: |
| 25893 | 25643 | case SHELL_OPEN_DESERIALIZE: { |
| 25894 | 25644 | sqlite3_open(0, &p->db); |
| @@ -25896,19 +25646,13 @@ | ||
| 25896 | 25646 | } |
| 25897 | 25647 | case SHELL_OPEN_ZIPFILE: { |
| 25898 | 25648 | sqlite3_open(":memory:", &p->db); |
| 25899 | 25649 | break; |
| 25900 | 25650 | } |
| 25901 | - case SHELL_OPEN_READONLY: { | |
| 25902 | - sqlite3_open_v2(zDbFilename, &p->db, | |
| 25903 | - SQLITE_OPEN_READONLY|p->openFlags, 0); | |
| 25904 | - break; | |
| 25905 | - } | |
| 25906 | 25651 | case SHELL_OPEN_UNSPEC: |
| 25907 | 25652 | case SHELL_OPEN_NORMAL: { |
| 25908 | - sqlite3_open_v2(zDbFilename, &p->db, | |
| 25909 | - SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); | |
| 25653 | + sqlite3_open_v2(zDbFilename, &p->db, p->openFlags, 0); | |
| 25910 | 25654 | break; |
| 25911 | 25655 | } |
| 25912 | 25656 | } |
| 25913 | 25657 | if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ |
| 25914 | 25658 | sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n", |
| @@ -25944,11 +25688,10 @@ | ||
| 25944 | 25688 | sqlite3_sha_init(p->db, 0, 0); |
| 25945 | 25689 | sqlite3_shathree_init(p->db, 0, 0); |
| 25946 | 25690 | sqlite3_uint_init(p->db, 0, 0); |
| 25947 | 25691 | sqlite3_stmtrand_init(p->db, 0, 0); |
| 25948 | 25692 | sqlite3_decimal_init(p->db, 0, 0); |
| 25949 | - sqlite3_percentile_init(p->db, 0, 0); | |
| 25950 | 25693 | sqlite3_base64_init(p->db, 0, 0); |
| 25951 | 25694 | sqlite3_base85_init(p->db, 0, 0); |
| 25952 | 25695 | sqlite3_regexp_init(p->db, 0, 0); |
| 25953 | 25696 | sqlite3_ieee_init(p->db, 0, 0); |
| 25954 | 25697 | sqlite3_series_init(p->db, 0, 0); |
| @@ -26043,13 +25786,15 @@ | ||
| 26043 | 25786 | } |
| 26044 | 25787 | } |
| 26045 | 25788 | #endif |
| 26046 | 25789 | } |
| 26047 | 25790 | if( p->db!=0 ){ |
| 25791 | +#ifndef SQLITE_OMIT_AUTHORIZATION | |
| 26048 | 25792 | if( p->bSafeModePersist ){ |
| 26049 | 25793 | sqlite3_set_authorizer(p->db, safeModeAuth, p); |
| 26050 | 25794 | } |
| 25795 | +#endif | |
| 26051 | 25796 | sqlite3_db_config( |
| 26052 | 25797 | p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 |
| 26053 | 25798 | ); |
| 26054 | 25799 | } |
| 26055 | 25800 | } |
| @@ -26358,12 +26103,12 @@ | ||
| 26358 | 26103 | struct ImportCtx { |
| 26359 | 26104 | const char *zFile; /* Name of the input file */ |
| 26360 | 26105 | FILE *in; /* Read the CSV text from this input stream */ |
| 26361 | 26106 | int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ |
| 26362 | 26107 | char *z; /* Accumulated text for a field */ |
| 26363 | - int n; /* Number of bytes in z */ | |
| 26364 | - int nAlloc; /* Space allocated for z[] */ | |
| 26108 | + i64 n; /* Number of bytes in z */ | |
| 26109 | + i64 nAlloc; /* Space allocated for z[] */ | |
| 26365 | 26110 | int nLine; /* Current line number */ |
| 26366 | 26111 | int nRow; /* Number of rows imported */ |
| 26367 | 26112 | int nErr; /* Number of errors encountered */ |
| 26368 | 26113 | int bNotFirst; /* True if one or more bytes already read */ |
| 26369 | 26114 | int cTerm; /* Character that terminated the most recent field */ |
| @@ -26829,14 +26574,17 @@ | ||
| 26829 | 26574 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26830 | 26575 | /* |
| 26831 | 26576 | ** Convert a 2-byte or 4-byte big-endian integer into a native integer |
| 26832 | 26577 | */ |
| 26833 | 26578 | static unsigned int get2byteInt(unsigned char *a){ |
| 26834 | - return (a[0]<<8) + a[1]; | |
| 26579 | + return ((unsigned int)a[0]<<8) + (unsigned int)a[1]; | |
| 26835 | 26580 | } |
| 26836 | 26581 | static unsigned int get4byteInt(unsigned char *a){ |
| 26837 | - return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; | |
| 26582 | + return ((unsigned int)a[0]<<24) | |
| 26583 | + + ((unsigned int)a[1]<<16) | |
| 26584 | + + ((unsigned int)a[2]<<8) | |
| 26585 | + + (unsigned int)a[3]; | |
| 26838 | 26586 | } |
| 26839 | 26587 | |
| 26840 | 26588 | /* |
| 26841 | 26589 | ** Implementation of the ".dbinfo" command. |
| 26842 | 26590 | ** |
| @@ -26969,11 +26717,11 @@ | ||
| 26969 | 26717 | if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; |
| 26970 | 26718 | nPage = sqlite3_column_int64(pStmt, 0); |
| 26971 | 26719 | sqlite3_finalize(pStmt); |
| 26972 | 26720 | pStmt = 0; |
| 26973 | 26721 | if( nPage<1 ) goto dbtotxt_error; |
| 26974 | - rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0); | |
| 26722 | + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); | |
| 26975 | 26723 | if( rc ) goto dbtotxt_error; |
| 26976 | 26724 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 26977 | 26725 | zTail = "unk.db"; |
| 26978 | 26726 | }else{ |
| 26979 | 26727 | const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2); |
| @@ -26980,10 +26728,15 @@ | ||
| 26980 | 26728 | if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db"; |
| 26981 | 26729 | zTail = strrchr(zFilename, '/'); |
| 26982 | 26730 | #if defined(_WIN32) |
| 26983 | 26731 | if( zTail==0 ) zTail = strrchr(zFilename, '\\'); |
| 26984 | 26732 | #endif |
| 26733 | + if( zTail==0 ){ | |
| 26734 | + zTail = zFilename; | |
| 26735 | + }else if( zTail[1]!=0 ){ | |
| 26736 | + zTail++; | |
| 26737 | + } | |
| 26985 | 26738 | } |
| 26986 | 26739 | zName = strdup(zTail); |
| 26987 | 26740 | shell_check_oom(zName); |
| 26988 | 26741 | sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", |
| 26989 | 26742 | nPage*pgSz, pgSz, zName); |
| @@ -27148,10 +26901,47 @@ | ||
| 27148 | 26901 | if( zStr[0]!='-' ) return 0; |
| 27149 | 26902 | zStr++; |
| 27150 | 26903 | if( zStr[0]=='-' ) zStr++; |
| 27151 | 26904 | return cli_strcmp(zStr, zOpt)==0; |
| 27152 | 26905 | } |
| 26906 | + | |
| 26907 | +/* | |
| 26908 | +** The input zFN is guaranteed to start with "file:" and is thus a URI | |
| 26909 | +** filename. Extract the actual filename and return a pointer to that | |
| 26910 | +** filename in spaced obtained from sqlite3_malloc(). | |
| 26911 | +** | |
| 26912 | +** The caller is responsible for freeing space using sqlite3_free() when | |
| 26913 | +** it has finished with the filename. | |
| 26914 | +*/ | |
| 26915 | +static char *shellFilenameFromUri(const char *zFN){ | |
| 26916 | + char *zOut; | |
| 26917 | + int i, j, d1, d2; | |
| 26918 | + | |
| 26919 | + assert( cli_strncmp(zFN,"file:",5)==0 ); | |
| 26920 | + zOut = sqlite3_mprintf("%s", zFN+5); | |
| 26921 | + shell_check_oom(zOut); | |
| 26922 | + for(i=j=0; zOut[i]!=0 && zOut[i]!='?'; i++){ | |
| 26923 | + if( zOut[i]!='%' ){ | |
| 26924 | + zOut[j++] = zOut[i]; | |
| 26925 | + continue; | |
| 26926 | + } | |
| 26927 | + d1 = hexDigitValue(zOut[i+1]); | |
| 26928 | + if( d1<0 ){ | |
| 26929 | + zOut[j] = 0; | |
| 26930 | + break; | |
| 26931 | + } | |
| 26932 | + d2 = hexDigitValue(zOut[i+2]); | |
| 26933 | + if( d2<0 ){ | |
| 26934 | + zOut[j] = 0; | |
| 26935 | + break; | |
| 26936 | + } | |
| 26937 | + zOut[j++] = d1*16 + d2; | |
| 26938 | + i += 2; | |
| 26939 | + } | |
| 26940 | + zOut[j] = 0; | |
| 26941 | + return zOut; | |
| 26942 | +} | |
| 27153 | 26943 | |
| 27154 | 26944 | /* |
| 27155 | 26945 | ** Delete a file. |
| 27156 | 26946 | */ |
| 27157 | 26947 | int shellDeleteFile(const char *zFilename){ |
| @@ -28704,11 +28494,11 @@ | ||
| 28704 | 28494 | int nArg = 0; |
| 28705 | 28495 | int n, c; |
| 28706 | 28496 | int rc = 0; |
| 28707 | 28497 | char *azArg[52]; |
| 28708 | 28498 | |
| 28709 | -#ifndef SQLITE_OMIT_VIRTUALTABLE | |
| 28499 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 28710 | 28500 | if( p->expert.pExpert ){ |
| 28711 | 28501 | expertFinish(p, 1, 0); |
| 28712 | 28502 | } |
| 28713 | 28503 | #endif |
| 28714 | 28504 | |
| @@ -29005,11 +28795,11 @@ | ||
| 29005 | 28795 | }else{ |
| 29006 | 28796 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 29007 | 28797 | const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); |
| 29008 | 28798 | const char *zFile = (const char*)sqlite3_column_text(pStmt,2); |
| 29009 | 28799 | if( zSchema==0 || zFile==0 ) continue; |
| 29010 | - azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); | |
| 28800 | + azName = sqlite3_realloc64(azName, (nName+1)*2*sizeof(char*)); | |
| 29011 | 28801 | shell_check_oom(azName); |
| 29012 | 28802 | azName[nName*2] = strdup(zSchema); |
| 29013 | 28803 | azName[nName*2+1] = strdup(zFile); |
| 29014 | 28804 | nName++; |
| 29015 | 28805 | } |
| @@ -29208,10 +28998,11 @@ | ||
| 29208 | 28998 | rc = 1; |
| 29209 | 28999 | } |
| 29210 | 29000 | }else |
| 29211 | 29001 | |
| 29212 | 29002 | if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 29003 | + open_db(p, 0); | |
| 29213 | 29004 | rc = shell_dbtotxt_command(p, nArg, azArg); |
| 29214 | 29005 | }else |
| 29215 | 29006 | |
| 29216 | 29007 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 29217 | 29008 | if( nArg==2 ){ |
| @@ -29273,11 +29064,11 @@ | ||
| 29273 | 29064 | if( p->mode==MODE_Explain ) p->mode = p->normalMode; |
| 29274 | 29065 | p->autoExplain = 1; |
| 29275 | 29066 | } |
| 29276 | 29067 | }else |
| 29277 | 29068 | |
| 29278 | -#ifndef SQLITE_OMIT_VIRTUALTABLE | |
| 29069 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 29279 | 29070 | if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ |
| 29280 | 29071 | if( p->bSafeMode ){ |
| 29281 | 29072 | sqlite3_fprintf(stderr, |
| 29282 | 29073 | "Cannot run experimental commands such as \"%s\" in safe mode\n", |
| 29283 | 29074 | azArg[0]); |
| @@ -29840,16 +29631,10 @@ | ||
| 29840 | 29631 | sqlite3_stmt *pStmt; |
| 29841 | 29632 | int tnum = 0; |
| 29842 | 29633 | int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ |
| 29843 | 29634 | int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ |
| 29844 | 29635 | int i; |
| 29845 | - if( !ShellHasFlag(p,SHFLG_TestingMode) ){ | |
| 29846 | - sqlite3_fprintf(stderr,".%s unavailable without --unsafe-testing\n", | |
| 29847 | - "imposter"); | |
| 29848 | - rc = 1; | |
| 29849 | - goto meta_command_exit; | |
| 29850 | - } | |
| 29851 | 29636 | if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ |
| 29852 | 29637 | eputz("Usage: .imposter INDEX IMPOSTER\n" |
| 29853 | 29638 | " .imposter off\n"); |
| 29854 | 29639 | /* Also allowed, but not documented: |
| 29855 | 29640 | ** |
| @@ -29917,22 +29702,19 @@ | ||
| 29917 | 29702 | if( lenPK==0 ) lenPK = 100000; |
| 29918 | 29703 | zSql = sqlite3_mprintf( |
| 29919 | 29704 | "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", |
| 29920 | 29705 | azArg[2], zCollist, lenPK, zCollist); |
| 29921 | 29706 | sqlite3_free(zCollist); |
| 29922 | - rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); | |
| 29707 | + rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 2, tnum); | |
| 29923 | 29708 | if( rc==SQLITE_OK ){ |
| 29924 | 29709 | rc = sqlite3_exec(p->db, zSql, 0, 0, 0); |
| 29925 | 29710 | sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); |
| 29926 | 29711 | if( rc ){ |
| 29927 | 29712 | sqlite3_fprintf(stderr, |
| 29928 | 29713 | "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); |
| 29929 | 29714 | }else{ |
| 29930 | 29715 | sqlite3_fprintf(stdout, "%s;\n", zSql); |
| 29931 | - sqlite3_fprintf(stdout, | |
| 29932 | - "WARNING: writing to an imposter table will corrupt" | |
| 29933 | - " the \"%s\" %s!\n", azArg[1], isWO ? "table" : "index"); | |
| 29934 | 29716 | } |
| 29935 | 29717 | }else{ |
| 29936 | 29718 | sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); |
| 29937 | 29719 | rc = 1; |
| 29938 | 29720 | } |
| @@ -30282,10 +30064,11 @@ | ||
| 30282 | 30064 | const char *zFN = 0; /* Pointer to constant filename */ |
| 30283 | 30065 | char *zNewFilename = 0; /* Name of the database file to open */ |
| 30284 | 30066 | int iName = 1; /* Index in azArg[] of the filename */ |
| 30285 | 30067 | int newFlag = 0; /* True to delete file before opening */ |
| 30286 | 30068 | int openMode = SHELL_OPEN_UNSPEC; |
| 30069 | + int openFlags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; | |
| 30287 | 30070 | |
| 30288 | 30071 | /* Check for command-line arguments */ |
| 30289 | 30072 | for(iName=1; iName<nArg; iName++){ |
| 30290 | 30073 | const char *z = azArg[iName]; |
| 30291 | 30074 | #ifndef SQLITE_SHELL_FIDDLE |
| @@ -30296,13 +30079,18 @@ | ||
| 30296 | 30079 | openMode = SHELL_OPEN_ZIPFILE; |
| 30297 | 30080 | #endif |
| 30298 | 30081 | }else if( optionMatch(z, "append") ){ |
| 30299 | 30082 | openMode = SHELL_OPEN_APPENDVFS; |
| 30300 | 30083 | }else if( optionMatch(z, "readonly") ){ |
| 30301 | - openMode = SHELL_OPEN_READONLY; | |
| 30084 | + openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); | |
| 30085 | + openFlags |= SQLITE_OPEN_READONLY; | |
| 30086 | + }else if( optionMatch(z, "exclusive") ){ | |
| 30087 | + openFlags |= SQLITE_OPEN_EXCLUSIVE; | |
| 30088 | + }else if( optionMatch(z, "ifexists") ){ | |
| 30089 | + openFlags &= ~(SQLITE_OPEN_CREATE); | |
| 30302 | 30090 | }else if( optionMatch(z, "nofollow") ){ |
| 30303 | - p->openFlags |= SQLITE_OPEN_NOFOLLOW; | |
| 30091 | + openFlags |= SQLITE_OPEN_NOFOLLOW; | |
| 30304 | 30092 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 30305 | 30093 | }else if( optionMatch(z, "deserialize") ){ |
| 30306 | 30094 | openMode = SHELL_OPEN_DESERIALIZE; |
| 30307 | 30095 | }else if( optionMatch(z, "hexdb") ){ |
| 30308 | 30096 | openMode = SHELL_OPEN_HEXDB; |
| @@ -30330,16 +30118,25 @@ | ||
| 30330 | 30118 | p->db = 0; |
| 30331 | 30119 | p->pAuxDb->zDbFilename = 0; |
| 30332 | 30120 | sqlite3_free(p->pAuxDb->zFreeOnClose); |
| 30333 | 30121 | p->pAuxDb->zFreeOnClose = 0; |
| 30334 | 30122 | p->openMode = openMode; |
| 30335 | - p->openFlags = 0; | |
| 30123 | + p->openFlags = openFlags; | |
| 30336 | 30124 | p->szMax = 0; |
| 30337 | 30125 | |
| 30338 | 30126 | /* If a filename is specified, try to open it first */ |
| 30339 | 30127 | if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ |
| 30340 | - if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); | |
| 30128 | + if( newFlag && zFN && !p->bSafeMode ){ | |
| 30129 | + if( cli_strncmp(zFN,"file:",5)==0 ){ | |
| 30130 | + char *zDel = shellFilenameFromUri(zFN); | |
| 30131 | + shell_check_oom(zDel); | |
| 30132 | + shellDeleteFile(zDel); | |
| 30133 | + sqlite3_free(zDel); | |
| 30134 | + }else{ | |
| 30135 | + shellDeleteFile(zFN); | |
| 30136 | + } | |
| 30137 | + } | |
| 30341 | 30138 | #ifndef SQLITE_SHELL_FIDDLE |
| 30342 | 30139 | if( p->bSafeMode |
| 30343 | 30140 | && p->openMode!=SHELL_OPEN_HEXDB |
| 30344 | 30141 | && zFN |
| 30345 | 30142 | && cli_strcmp(zFN,":memory:")!=0 |
| @@ -30938,13 +30735,13 @@ | ||
| 30938 | 30735 | appendText(&sSelect, "name NOT LIKE 'sqlite__%%' ESCAPE '_' AND ", 0); |
| 30939 | 30736 | } |
| 30940 | 30737 | appendText(&sSelect, "sql IS NOT NULL" |
| 30941 | 30738 | " ORDER BY snum, rowid", 0); |
| 30942 | 30739 | if( bDebug ){ |
| 30943 | - sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.z); | |
| 30740 | + sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.zTxt); | |
| 30944 | 30741 | }else{ |
| 30945 | - rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); | |
| 30742 | + rc = sqlite3_exec(p->db, sSelect.zTxt, callback, &data, &zErrMsg); | |
| 30946 | 30743 | } |
| 30947 | 30744 | freeText(&sSelect); |
| 30948 | 30745 | } |
| 30949 | 30746 | if( zErrMsg ){ |
| 30950 | 30747 | shellEmitError(zErrMsg); |
| @@ -31072,19 +30869,20 @@ | ||
| 31072 | 30869 | |
| 31073 | 30870 | /* .session filter GLOB .... |
| 31074 | 30871 | ** Set a list of GLOB patterns of table names to be excluded. |
| 31075 | 30872 | */ |
| 31076 | 30873 | if( cli_strcmp(azCmd[0], "filter")==0 ){ |
| 31077 | - int ii, nByte; | |
| 30874 | + int ii; | |
| 30875 | + i64 nByte; | |
| 31078 | 30876 | if( nCmd<2 ) goto session_syntax_error; |
| 31079 | 30877 | if( pAuxDb->nSession ){ |
| 31080 | 30878 | for(ii=0; ii<pSession->nFilter; ii++){ |
| 31081 | 30879 | sqlite3_free(pSession->azFilter[ii]); |
| 31082 | 30880 | } |
| 31083 | 30881 | sqlite3_free(pSession->azFilter); |
| 31084 | 30882 | nByte = sizeof(pSession->azFilter[0])*(nCmd-1); |
| 31085 | - pSession->azFilter = sqlite3_malloc( nByte ); | |
| 30883 | + pSession->azFilter = sqlite3_malloc64( nByte ); | |
| 31086 | 30884 | shell_check_oom( pSession->azFilter ); |
| 31087 | 30885 | for(ii=1; ii<nCmd; ii++){ |
| 31088 | 30886 | char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); |
| 31089 | 30887 | shell_check_oom(x); |
| 31090 | 30888 | } |
| @@ -31264,26 +31062,26 @@ | ||
| 31264 | 31062 | sqlite3_fprintf(p->out, "%s\n", zSql); |
| 31265 | 31063 | }else |
| 31266 | 31064 | if( cli_strcmp(zOp,"run")==0 ){ |
| 31267 | 31065 | char *zErrMsg = 0; |
| 31268 | 31066 | str.n = 0; |
| 31269 | - str.z[0] = 0; | |
| 31067 | + str.zTxt[0] = 0; | |
| 31270 | 31068 | rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); |
| 31271 | 31069 | nTest++; |
| 31272 | 31070 | if( bVerbose ){ |
| 31273 | - sqlite3_fprintf(p->out, "Result: %s\n", str.z); | |
| 31071 | + sqlite3_fprintf(p->out, "Result: %s\n", str.zTxt); | |
| 31274 | 31072 | } |
| 31275 | 31073 | if( rc || zErrMsg ){ |
| 31276 | 31074 | nErr++; |
| 31277 | 31075 | rc = 1; |
| 31278 | 31076 | sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); |
| 31279 | 31077 | sqlite3_free(zErrMsg); |
| 31280 | - }else if( cli_strcmp(zAns,str.z)!=0 ){ | |
| 31078 | + }else if( cli_strcmp(zAns,str.zTxt)!=0 ){ | |
| 31281 | 31079 | nErr++; |
| 31282 | 31080 | rc = 1; |
| 31283 | 31081 | sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns); |
| 31284 | - sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.z); | |
| 31082 | + sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); | |
| 31285 | 31083 | } |
| 31286 | 31084 | } |
| 31287 | 31085 | else{ |
| 31288 | 31086 | sqlite3_fprintf(stderr, |
| 31289 | 31087 | "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); |
| @@ -31395,11 +31193,11 @@ | ||
| 31395 | 31193 | appendText(&sQuery, "SELECT * FROM ", 0); |
| 31396 | 31194 | appendText(&sQuery, zTab, 0); |
| 31397 | 31195 | appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); |
| 31398 | 31196 | } |
| 31399 | 31197 | appendText(&sSql, zSep, 0); |
| 31400 | - appendText(&sSql, sQuery.z, '\''); | |
| 31198 | + appendText(&sSql, sQuery.zTxt, '\''); | |
| 31401 | 31199 | sQuery.n = 0; |
| 31402 | 31200 | appendText(&sSql, ",", 0); |
| 31403 | 31201 | appendText(&sSql, zTab, '\''); |
| 31404 | 31202 | zSep = "),("; |
| 31405 | 31203 | } |
| @@ -31407,17 +31205,17 @@ | ||
| 31407 | 31205 | if( bSeparate ){ |
| 31408 | 31206 | zSql = sqlite3_mprintf( |
| 31409 | 31207 | "%s))" |
| 31410 | 31208 | " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" |
| 31411 | 31209 | " FROM [sha3sum$query]", |
| 31412 | - sSql.z, iSize); | |
| 31210 | + sSql.zTxt, iSize); | |
| 31413 | 31211 | }else{ |
| 31414 | 31212 | zSql = sqlite3_mprintf( |
| 31415 | 31213 | "%s))" |
| 31416 | 31214 | " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" |
| 31417 | 31215 | " FROM [sha3sum$query]", |
| 31418 | - sSql.z, iSize); | |
| 31216 | + sSql.zTxt, iSize); | |
| 31419 | 31217 | } |
| 31420 | 31218 | shell_check_oom(zSql); |
| 31421 | 31219 | freeText(&sQuery); |
| 31422 | 31220 | freeText(&sSql); |
| 31423 | 31221 | if( bDebug ){ |
| @@ -31613,11 +31411,11 @@ | ||
| 31613 | 31411 | goto meta_command_exit; |
| 31614 | 31412 | } |
| 31615 | 31413 | for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ |
| 31616 | 31414 | const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); |
| 31617 | 31415 | if( zDbName==0 ) continue; |
| 31618 | - if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0); | |
| 31416 | + if( s.zTxt && s.zTxt[0] ) appendText(&s, " UNION ALL ", 0); | |
| 31619 | 31417 | if( sqlite3_stricmp(zDbName, "main")==0 ){ |
| 31620 | 31418 | appendText(&s, "SELECT name FROM ", 0); |
| 31621 | 31419 | }else{ |
| 31622 | 31420 | appendText(&s, "SELECT ", 0); |
| 31623 | 31421 | appendText(&s, zDbName, '\''); |
| @@ -31635,11 +31433,11 @@ | ||
| 31635 | 31433 | } |
| 31636 | 31434 | } |
| 31637 | 31435 | rc = sqlite3_finalize(pStmt); |
| 31638 | 31436 | if( rc==SQLITE_OK ){ |
| 31639 | 31437 | appendText(&s, " ORDER BY 1", 0); |
| 31640 | - rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); | |
| 31438 | + rc = sqlite3_prepare_v2(p->db, s.zTxt, -1, &pStmt, 0); | |
| 31641 | 31439 | } |
| 31642 | 31440 | freeText(&s); |
| 31643 | 31441 | if( rc ) return shellDatabaseError(p->db); |
| 31644 | 31442 | |
| 31645 | 31443 | /* Run the SQL statement prepared by the above block. Store the results |
| @@ -31652,14 +31450,14 @@ | ||
| 31652 | 31450 | sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); |
| 31653 | 31451 | } |
| 31654 | 31452 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 31655 | 31453 | if( nRow>=nAlloc ){ |
| 31656 | 31454 | char **azNew; |
| 31657 | - int n2 = nAlloc*2 + 10; | |
| 31455 | + sqlite3_int64 n2 = 2*(sqlite3_int64)nAlloc + 10; | |
| 31658 | 31456 | azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); |
| 31659 | 31457 | shell_check_oom(azNew); |
| 31660 | - nAlloc = n2; | |
| 31458 | + nAlloc = (int)n2; | |
| 31661 | 31459 | azResult = azNew; |
| 31662 | 31460 | } |
| 31663 | 31461 | azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| 31664 | 31462 | shell_check_oom(azResult[nRow]); |
| 31665 | 31463 | nRow++; |
| @@ -31818,11 +31616,11 @@ | ||
| 31818 | 31616 | { 0x00000020, 1, "CoverIdxScan" }, |
| 31819 | 31617 | { 0x00000040, 1, "OrderByIdxJoin" }, |
| 31820 | 31618 | { 0x00000080, 1, "Transitive" }, |
| 31821 | 31619 | { 0x00000100, 1, "OmitNoopJoin" }, |
| 31822 | 31620 | { 0x00000200, 1, "CountOfView" }, |
| 31823 | - { 0x00000400, 1, "CurosrHints" }, | |
| 31621 | + { 0x00000400, 1, "CursorHints" }, | |
| 31824 | 31622 | { 0x00000800, 1, "Stat4" }, |
| 31825 | 31623 | { 0x00001000, 1, "PushDown" }, |
| 31826 | 31624 | { 0x00002000, 1, "SimplifyJoin" }, |
| 31827 | 31625 | { 0x00004000, 1, "SkipScan" }, |
| 31828 | 31626 | { 0x00008000, 1, "PropagateConst" }, |
| @@ -32365,11 +32163,14 @@ | ||
| 32365 | 32163 | p->nWidth = nArg-1; |
| 32366 | 32164 | p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); |
| 32367 | 32165 | if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); |
| 32368 | 32166 | if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; |
| 32369 | 32167 | for(j=1; j<nArg; j++){ |
| 32370 | - p->colWidth[j-1] = (int)integerValue(azArg[j]); | |
| 32168 | + i64 w = integerValue(azArg[j]); | |
| 32169 | + if( w < -30000 ) w = -30000; | |
| 32170 | + if( w > +30000 ) w = +30000; | |
| 32171 | + p->colWidth[j-1] = (int)w; | |
| 32371 | 32172 | } |
| 32372 | 32173 | }else |
| 32373 | 32174 | |
| 32374 | 32175 | { |
| 32375 | 32176 | sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: " |
| @@ -32888,76 +32689,95 @@ | ||
| 32888 | 32689 | |
| 32889 | 32690 | return home_dir; |
| 32890 | 32691 | } |
| 32891 | 32692 | |
| 32892 | 32693 | /* |
| 32893 | -** On non-Windows platforms, look for $XDG_CONFIG_HOME. | |
| 32894 | -** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return | |
| 32895 | -** the path to it. If there is no $(XDG_CONFIG_HOME) then | |
| 32896 | -** look for $(HOME)/.config/sqlite3/sqliterc and if found | |
| 32897 | -** return that. If none of these are found, return 0. | |
| 32694 | +** On non-Windows platforms, look for: | |
| 32695 | +** | |
| 32696 | +** - ${zEnvVar}/${zBaseName} | |
| 32697 | +** - ${HOME}/${zSubdir}/${zBaseName} | |
| 32698 | +** | |
| 32699 | +** $zEnvVar is intended to be the name of an XDG_... environment | |
| 32700 | +** variable, e.g. XDG_CONFIG_HOME or XDG_STATE_HOME. If zEnvVar is | |
| 32701 | +** NULL or getenv(zEnvVar) is NULL then fall back to the second | |
| 32702 | +** option. If the selected option is not found in the filesystem, | |
| 32703 | +** return 0. | |
| 32704 | +** | |
| 32705 | +** zSubdir may be NULL or empty, in which case ${HOME}/${zBaseName} | |
| 32706 | +** becomes the fallback. | |
| 32707 | +** | |
| 32708 | +** Both zSubdir and zBaseName may contain subdirectory parts. zSubdir | |
| 32709 | +** will conventionally be ".config" or ".local/state", which, not | |
| 32710 | +** coincidentally, is the typical subdir of the corresponding XDG_... | |
| 32711 | +** var with the XDG var's $HOME prefix. | |
| 32898 | 32712 | ** |
| 32899 | -** The string returned is obtained from sqlite3_malloc() and | |
| 32900 | -** should be freed by the caller. | |
| 32713 | +** The returned string is obtained from sqlite3_malloc() and should be | |
| 32714 | +** sqlite3_free()'d by the caller. | |
| 32901 | 32715 | */ |
| 32902 | -static char *find_xdg_config(void){ | |
| 32716 | +static char *find_xdg_file(const char *zEnvVar, const char *zSubdir, | |
| 32717 | + const char *zBaseName){ | |
| 32903 | 32718 | #if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ |
| 32904 | 32719 | || defined(__RTP__) || defined(_WRS_KERNEL) |
| 32905 | 32720 | return 0; |
| 32906 | 32721 | #else |
| 32907 | - char *zConfig = 0; | |
| 32908 | - const char *zXdgHome; | |
| 32909 | - | |
| 32910 | - zXdgHome = getenv("XDG_CONFIG_HOME"); | |
| 32911 | - if( zXdgHome==0 ){ | |
| 32912 | - const char *zHome = getenv("HOME"); | |
| 32913 | - if( zHome==0 ) return 0; | |
| 32914 | - zConfig = sqlite3_mprintf("%s/.config/sqlite3/sqliterc", zHome); | |
| 32722 | + char *zConfigFile = 0; | |
| 32723 | + const char *zXdgDir; | |
| 32724 | + | |
| 32725 | + zXdgDir = zEnvVar ? getenv(zEnvVar) : 0; | |
| 32726 | + if( zXdgDir ){ | |
| 32727 | + zConfigFile = sqlite3_mprintf("%s/%s", zXdgDir, zBaseName); | |
| 32915 | 32728 | }else{ |
| 32916 | - zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome); | |
| 32917 | - } | |
| 32918 | - shell_check_oom(zConfig); | |
| 32919 | - if( access(zConfig,0)!=0 ){ | |
| 32920 | - sqlite3_free(zConfig); | |
| 32921 | - zConfig = 0; | |
| 32922 | - } | |
| 32923 | - return zConfig; | |
| 32729 | + const char * zHome = find_home_dir(0); | |
| 32730 | + if( zHome==0 ) return 0; | |
| 32731 | + zConfigFile = (zSubdir && *zSubdir) | |
| 32732 | + ? sqlite3_mprintf("%s/%s/%s", zHome, zSubdir, zBaseName) | |
| 32733 | + : sqlite3_mprintf("%s/%s", zHome, zBaseName); | |
| 32734 | + } | |
| 32735 | + shell_check_oom(zConfigFile); | |
| 32736 | + if( access(zConfigFile,0)!=0 ){ | |
| 32737 | + sqlite3_free(zConfigFile); | |
| 32738 | + zConfigFile = 0; | |
| 32739 | + } | |
| 32740 | + return zConfigFile; | |
| 32924 | 32741 | #endif |
| 32925 | 32742 | } |
| 32926 | 32743 | |
| 32927 | 32744 | /* |
| 32928 | -** Read input from the file given by sqliterc_override. Or if that | |
| 32929 | -** parameter is NULL, take input from the first of find_xdg_config() | |
| 32930 | -** or ~/.sqliterc which is found. | |
| 32745 | +** Read input from the file sqliterc_override. If that parameter is | |
| 32746 | +** NULL, take it from find_xdg_file(), if found, or fall back to | |
| 32747 | +** ~/.sqliterc. | |
| 32931 | 32748 | ** |
| 32932 | -** Returns the number of errors. | |
| 32749 | +** Failure to read the config is only considered a failure if | |
| 32750 | +** sqliterc_override is not NULL, in which case this function may emit | |
| 32751 | +** a warning or, if ::bail_on_error is true, fail fatally if the file | |
| 32752 | +** named by sqliterc_override is not found. | |
| 32933 | 32753 | */ |
| 32934 | 32754 | static void process_sqliterc( |
| 32935 | 32755 | ShellState *p, /* Configuration data */ |
| 32936 | 32756 | const char *sqliterc_override /* Name of config file. NULL to use default */ |
| 32937 | 32757 | ){ |
| 32938 | 32758 | char *home_dir = NULL; |
| 32939 | - const char *sqliterc = sqliterc_override; | |
| 32940 | - char *zBuf = 0; | |
| 32759 | + char *sqliterc = (char*)sqliterc_override; | |
| 32941 | 32760 | FILE *inSaved = p->in; |
| 32942 | 32761 | int savedLineno = p->lineno; |
| 32943 | 32762 | |
| 32944 | 32763 | if( sqliterc == NULL ){ |
| 32945 | - sqliterc = zBuf = find_xdg_config(); | |
| 32764 | + sqliterc = find_xdg_file("XDG_CONFIG_HOME", | |
| 32765 | + ".config", | |
| 32766 | + "sqlite3/sqliterc"); | |
| 32946 | 32767 | } |
| 32947 | 32768 | if( sqliterc == NULL ){ |
| 32948 | 32769 | home_dir = find_home_dir(0); |
| 32949 | 32770 | if( home_dir==0 ){ |
| 32950 | 32771 | eputz("-- warning: cannot find home directory;" |
| 32951 | 32772 | " cannot read ~/.sqliterc\n"); |
| 32952 | 32773 | return; |
| 32953 | 32774 | } |
| 32954 | - zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); | |
| 32955 | - shell_check_oom(zBuf); | |
| 32956 | - sqliterc = zBuf; | |
| 32775 | + sqliterc = sqlite3_mprintf("%s/.sqliterc",home_dir); | |
| 32776 | + shell_check_oom(sqliterc); | |
| 32957 | 32777 | } |
| 32958 | - p->in = sqlite3_fopen(sqliterc,"rb"); | |
| 32778 | + p->in = sqliterc ? sqlite3_fopen(sqliterc,"rb") : 0; | |
| 32959 | 32779 | if( p->in ){ |
| 32960 | 32780 | if( stdin_is_interactive ){ |
| 32961 | 32781 | sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc); |
| 32962 | 32782 | } |
| 32963 | 32783 | if( process_input(p) && bail_on_error ) exit(1); |
| @@ -32966,11 +32786,13 @@ | ||
| 32966 | 32786 | sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc); |
| 32967 | 32787 | if( bail_on_error ) exit(1); |
| 32968 | 32788 | } |
| 32969 | 32789 | p->in = inSaved; |
| 32970 | 32790 | p->lineno = savedLineno; |
| 32971 | - sqlite3_free(zBuf); | |
| 32791 | + if( sqliterc != sqliterc_override ){ | |
| 32792 | + sqlite3_free(sqliterc); | |
| 32793 | + } | |
| 32972 | 32794 | } |
| 32973 | 32795 | |
| 32974 | 32796 | /* |
| 32975 | 32797 | ** Show available command line options |
| 32976 | 32798 | */ |
| @@ -32997,10 +32819,11 @@ | ||
| 32997 | 32819 | #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) |
| 32998 | 32820 | " -heap SIZE Size of heap for memsys3 or memsys5\n" |
| 32999 | 32821 | #endif |
| 33000 | 32822 | " -help show this message\n" |
| 33001 | 32823 | " -html set output mode to HTML\n" |
| 32824 | + " -ifexists only open if database already exists\n" | |
| 33002 | 32825 | " -interactive force interactive I/O\n" |
| 33003 | 32826 | " -json set output mode to 'json'\n" |
| 33004 | 32827 | " -line set output mode to 'line'\n" |
| 33005 | 32828 | " -list set output mode to 'list'\n" |
| 33006 | 32829 | " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" |
| @@ -33341,16 +33164,24 @@ | ||
| 33341 | 33164 | (void)cmdline_option_value(argc, argv, ++i); |
| 33342 | 33165 | #endif |
| 33343 | 33166 | }else if( cli_strcmp(z,"-pagecache")==0 ){ |
| 33344 | 33167 | sqlite3_int64 n, sz; |
| 33345 | 33168 | sz = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33346 | - if( sz>70000 ) sz = 70000; | |
| 33169 | + if( sz>65536 ) sz = 65536; | |
| 33347 | 33170 | if( sz<0 ) sz = 0; |
| 33348 | 33171 | n = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33349 | 33172 | if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){ |
| 33350 | 33173 | n = 0xffffffffffffLL/sz; |
| 33351 | 33174 | } |
| 33175 | + if( sz>0 && (sz & (sz-1))==0 ){ | |
| 33176 | + /* If SIZE is a power of two, round it up by the PCACHE_HDRSZ */ | |
| 33177 | + int szHdr = 0; | |
| 33178 | + sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &szHdr); | |
| 33179 | + sz += szHdr; | |
| 33180 | + sqlite3_fprintf(stdout, "Page cache size increased to %d to accommodate" | |
| 33181 | + " the %d-byte headers\n", (int)sz, szHdr); | |
| 33182 | + } | |
| 33352 | 33183 | verify_uninitialized(); |
| 33353 | 33184 | sqlite3_config(SQLITE_CONFIG_PAGECACHE, |
| 33354 | 33185 | (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n); |
| 33355 | 33186 | data.shellFlgs |= SHFLG_Pagecache; |
| 33356 | 33187 | }else if( cli_strcmp(z,"-lookaside")==0 ){ |
| @@ -33359,11 +33190,11 @@ | ||
| 33359 | 33190 | if( sz<0 ) sz = 0; |
| 33360 | 33191 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33361 | 33192 | if( n<0 ) n = 0; |
| 33362 | 33193 | verify_uninitialized(); |
| 33363 | 33194 | sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); |
| 33364 | - if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; | |
| 33195 | + if( (i64)sz*(i64)n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; | |
| 33365 | 33196 | }else if( cli_strcmp(z,"-threadsafe")==0 ){ |
| 33366 | 33197 | int n; |
| 33367 | 33198 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33368 | 33199 | verify_uninitialized(); |
| 33369 | 33200 | switch( n ){ |
| @@ -33401,13 +33232,19 @@ | ||
| 33401 | 33232 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33402 | 33233 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33403 | 33234 | data.szMax = integerValue(argv[++i]); |
| 33404 | 33235 | #endif |
| 33405 | 33236 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33406 | - data.openMode = SHELL_OPEN_READONLY; | |
| 33237 | + data.openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); | |
| 33238 | + data.openFlags |= SQLITE_OPEN_READONLY; | |
| 33407 | 33239 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33408 | - data.openFlags = SQLITE_OPEN_NOFOLLOW; | |
| 33240 | + data.openFlags |= SQLITE_OPEN_NOFOLLOW; | |
| 33241 | + }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ | |
| 33242 | + data.openFlags |= SQLITE_OPEN_EXCLUSIVE; | |
| 33243 | + }else if( cli_strcmp(z,"-ifexists")==0 ){ | |
| 33244 | + data.openFlags &= ~(SQLITE_OPEN_CREATE); | |
| 33245 | + if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; | |
| 33409 | 33246 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) |
| 33410 | 33247 | }else if( cli_strncmp(z, "-A",2)==0 ){ |
| 33411 | 33248 | /* All remaining command-line arguments are passed to the ".archive" |
| 33412 | 33249 | ** command, so ignore them */ |
| 33413 | 33250 | break; |
| @@ -33557,13 +33394,19 @@ | ||
| 33557 | 33394 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33558 | 33395 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33559 | 33396 | data.szMax = integerValue(argv[++i]); |
| 33560 | 33397 | #endif |
| 33561 | 33398 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33562 | - data.openMode = SHELL_OPEN_READONLY; | |
| 33399 | + data.openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); | |
| 33400 | + data.openFlags |= SQLITE_OPEN_READONLY; | |
| 33563 | 33401 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33564 | 33402 | data.openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 33403 | + }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ | |
| 33404 | + data.openFlags |= SQLITE_OPEN_EXCLUSIVE; | |
| 33405 | + }else if( cli_strcmp(z,"-ifexists")==0 ){ | |
| 33406 | + data.openFlags &= ~(SQLITE_OPEN_CREATE); | |
| 33407 | + if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; | |
| 33565 | 33408 | }else if( cli_strcmp(z,"-ascii")==0 ){ |
| 33566 | 33409 | data.mode = MODE_Ascii; |
| 33567 | 33410 | sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); |
| 33568 | 33411 | sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); |
| 33569 | 33412 | }else if( cli_strcmp(z,"-tabs")==0 ){ |
| @@ -33734,11 +33577,10 @@ | ||
| 33734 | 33577 | /* Run commands received from standard input |
| 33735 | 33578 | */ |
| 33736 | 33579 | if( stdin_is_interactive ){ |
| 33737 | 33580 | char *zHome; |
| 33738 | 33581 | char *zHistory; |
| 33739 | - int nHistory; | |
| 33740 | 33582 | sqlite3_fprintf(stdout, |
| 33741 | 33583 | "SQLite version %s %.19s\n" /*extra-version-info*/ |
| 33742 | 33584 | "Enter \".help\" for usage hints.\n", |
| 33743 | 33585 | sqlite3_libversion(), sqlite3_sourceid()); |
| 33744 | 33586 | if( warnInmemoryDb ){ |
| @@ -33747,15 +33589,19 @@ | ||
| 33747 | 33589 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33748 | 33590 | " persistent database.\n"); |
| 33749 | 33591 | } |
| 33750 | 33592 | zHistory = getenv("SQLITE_HISTORY"); |
| 33751 | 33593 | if( zHistory ){ |
| 33752 | - zHistory = strdup(zHistory); | |
| 33753 | - }else if( (zHome = find_home_dir(0))!=0 ){ | |
| 33754 | - nHistory = strlen30(zHome) + 20; | |
| 33755 | - if( (zHistory = malloc(nHistory))!=0 ){ | |
| 33756 | - sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); | |
| 33594 | + zHistory = sqlite3_mprintf("%s", zHistory); | |
| 33595 | + shell_check_oom(zHistory); | |
| 33596 | + }else{ | |
| 33597 | + zHistory = find_xdg_file("XDG_STATE_HOME", | |
| 33598 | + ".local/state", | |
| 33599 | + "sqlite_history"); | |
| 33600 | + if( 0==zHistory && (zHome = find_home_dir(0))!=0 ){ | |
| 33601 | + zHistory = sqlite3_mprintf("%s/.sqlite_history", zHome); | |
| 33602 | + shell_check_oom(zHistory); | |
| 33757 | 33603 | } |
| 33758 | 33604 | } |
| 33759 | 33605 | if( zHistory ){ shell_read_history(zHistory); } |
| 33760 | 33606 | #if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION) |
| 33761 | 33607 | rl_attempted_completion_function = readline_completion; |
| @@ -33767,21 +33613,21 @@ | ||
| 33767 | 33613 | data.in = 0; |
| 33768 | 33614 | rc = process_input(&data); |
| 33769 | 33615 | if( zHistory ){ |
| 33770 | 33616 | shell_stifle_history(2000); |
| 33771 | 33617 | shell_write_history(zHistory); |
| 33772 | - free(zHistory); | |
| 33618 | + sqlite3_free(zHistory); | |
| 33773 | 33619 | } |
| 33774 | 33620 | }else{ |
| 33775 | 33621 | data.in = stdin; |
| 33776 | 33622 | rc = process_input(&data); |
| 33777 | 33623 | } |
| 33778 | 33624 | } |
| 33779 | 33625 | #ifndef SQLITE_SHELL_FIDDLE |
| 33780 | 33626 | /* In WASM mode we have to leave the db state in place so that |
| 33781 | 33627 | ** client code can "push" SQL into it after this call returns. */ |
| 33782 | -#ifndef SQLITE_OMIT_VIRTUALTABLE | |
| 33628 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) | |
| 33783 | 33629 | if( data.expert.pExpert ){ |
| 33784 | 33630 | expertFinish(&data, 1, 0); |
| 33785 | 33631 | } |
| 33786 | 33632 | #endif |
| 33787 | 33633 | shell_main_exit: |
| 33788 | 33634 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -122,10 +122,13 @@ | |
| 122 | typedef sqlite3_int64 i64; |
| 123 | typedef sqlite3_uint64 u64; |
| 124 | typedef unsigned char u8; |
| 125 | #include <ctype.h> |
| 126 | #include <stdarg.h> |
| 127 | |
| 128 | #if !defined(_WIN32) && !defined(WIN32) |
| 129 | # include <signal.h> |
| 130 | # if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) |
| 131 | # include <pwd.h> |
| @@ -646,12 +649,23 @@ | |
| 646 | if( a==0 ) a = ""; |
| 647 | if( b==0 ) b = ""; |
| 648 | return strncmp(a,b,n); |
| 649 | } |
| 650 | |
| 651 | /* Return the current wall-clock time */ |
| 652 | static sqlite3_int64 timeOfDay(void){ |
| 653 | static sqlite3_vfs *clockVfs = 0; |
| 654 | sqlite3_int64 t; |
| 655 | if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); |
| 656 | if( clockVfs==0 ) return 0; /* Never actually happens */ |
| 657 | if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ |
| @@ -659,11 +673,16 @@ | |
| 659 | }else{ |
| 660 | double r; |
| 661 | clockVfs->xCurrentTime(clockVfs, &r); |
| 662 | t = (sqlite3_int64)(r*86400000.0); |
| 663 | } |
| 664 | return t; |
| 665 | } |
| 666 | |
| 667 | #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) |
| 668 | #include <sys/time.h> |
| 669 | #include <sys/resource.h> |
| @@ -704,12 +723,12 @@ | |
| 704 | static void endTimer(FILE *out){ |
| 705 | if( enableTimer ){ |
| 706 | sqlite3_int64 iEnd = timeOfDay(); |
| 707 | struct rusage sEnd; |
| 708 | getrusage(RUSAGE_SELF, &sEnd); |
| 709 | sqlite3_fprintf(out, "Run Time: real %.3f user %f sys %f\n", |
| 710 | (iEnd - iBegin)*0.001, |
| 711 | timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), |
| 712 | timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); |
| 713 | } |
| 714 | } |
| 715 | |
| @@ -783,14 +802,23 @@ | |
| 783 | static void endTimer(FILE *out){ |
| 784 | if( enableTimer && getProcessTimesAddr){ |
| 785 | FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; |
| 786 | sqlite3_int64 ftWallEnd = timeOfDay(); |
| 787 | getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); |
| 788 | sqlite3_fprintf(out, "Run Time: real %.3f user %f sys %f\n", |
| 789 | (ftWallEnd - ftWallBegin)*0.001, |
| 790 | timeDiff(&ftUserBegin, &ftUserEnd), |
| 791 | timeDiff(&ftKernelBegin, &ftKernelEnd)); |
| 792 | } |
| 793 | } |
| 794 | |
| 795 | #define BEGIN_TIMER beginTimer() |
| 796 | #define END_TIMER(X) endTimer(X) |
| @@ -1126,11 +1154,11 @@ | |
| 1126 | } |
| 1127 | if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 |
| 1128 | && (z[3] & 0xc0)==0x80 |
| 1129 | ){ |
| 1130 | *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6 |
| 1131 | | (z[4] & 0x3f); |
| 1132 | return 4; |
| 1133 | } |
| 1134 | *pU = 0; |
| 1135 | return 1; |
| 1136 | } |
| @@ -1189,18 +1217,28 @@ | |
| 1189 | ** since with %*.*s the width is measured in bytes, not characters. |
| 1190 | ** |
| 1191 | ** Take into account zero-width and double-width Unicode characters. |
| 1192 | ** In other words, a zero-width character does not count toward the |
| 1193 | ** the w limit. A double-width character counts as two. |
| 1194 | */ |
| 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; |
| 1206 | int len = decodeUtf8(a+i, &u); |
| @@ -1323,11 +1361,11 @@ | |
| 1323 | |
| 1324 | /* |
| 1325 | ** This routine reads a line of text from FILE in, stores |
| 1326 | ** the text in memory obtained from malloc() and returns a pointer |
| 1327 | ** to the text. NULL is returned at end of file, or if malloc() |
| 1328 | ** fails. |
| 1329 | ** |
| 1330 | ** If zLine is not NULL then it is a malloced buffer returned from |
| 1331 | ** a previous call to this routine that may be reused. |
| 1332 | */ |
| 1333 | static char *local_getline(char *zLine, FILE *in){ |
| @@ -1334,10 +1372,14 @@ | |
| 1334 | int nLine = zLine==0 ? 0 : 100; |
| 1335 | int n = 0; |
| 1336 | |
| 1337 | while( 1 ){ |
| 1338 | if( n+100>nLine ){ |
| 1339 | nLine = nLine*2 + 100; |
| 1340 | zLine = realloc(zLine, nLine); |
| 1341 | shell_check_oom(zLine); |
| 1342 | } |
| 1343 | if( sqlite3_fgets(&zLine[n], nLine - n, in)==0 ){ |
| @@ -1417,14 +1459,18 @@ | |
| 1417 | return -1; |
| 1418 | } |
| 1419 | |
| 1420 | /* |
| 1421 | ** Interpret zArg as an integer value, possibly with suffixes. |
| 1422 | */ |
| 1423 | static sqlite3_int64 integerValue(const char *zArg){ |
| 1424 | sqlite3_int64 v = 0; |
| 1425 | static const struct { char *zSuffix; int iMult; } aMult[] = { |
| 1426 | { "KiB", 1024 }, |
| 1427 | { "MiB", 1024*1024 }, |
| 1428 | { "GiB", 1024*1024*1024 }, |
| 1429 | { "KB", 1000 }, |
| 1430 | { "MB", 1000000 }, |
| @@ -1443,46 +1489,54 @@ | |
| 1443 | } |
| 1444 | if( zArg[0]=='0' && zArg[1]=='x' ){ |
| 1445 | int x; |
| 1446 | zArg += 2; |
| 1447 | while( (x = hexDigitValue(zArg[0]))>=0 ){ |
| 1448 | v = (v<<4) + x; |
| 1449 | zArg++; |
| 1450 | } |
| 1451 | }else{ |
| 1452 | while( IsDigit(zArg[0]) ){ |
| 1453 | v = v*10 + zArg[0] - '0'; |
| 1454 | zArg++; |
| 1455 | } |
| 1456 | } |
| 1457 | for(i=0; i<ArraySize(aMult); i++){ |
| 1458 | if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){ |
| 1459 | v *= aMult[i].iMult; |
| 1460 | break; |
| 1461 | } |
| 1462 | } |
| 1463 | return isNeg? -v : v; |
| 1464 | } |
| 1465 | |
| 1466 | /* |
| 1467 | ** A variable length string to which one can append text. |
| 1468 | */ |
| 1469 | typedef struct ShellText ShellText; |
| 1470 | struct ShellText { |
| 1471 | char *z; |
| 1472 | int n; |
| 1473 | int nAlloc; |
| 1474 | }; |
| 1475 | |
| 1476 | /* |
| 1477 | ** Initialize and destroy a ShellText object |
| 1478 | */ |
| 1479 | static void initText(ShellText *p){ |
| 1480 | memset(p, 0, sizeof(*p)); |
| 1481 | } |
| 1482 | static void freeText(ShellText *p){ |
| 1483 | free(p->z); |
| 1484 | initText(p); |
| 1485 | } |
| 1486 | |
| 1487 | /* zIn is either a pointer to a NULL-terminated string in memory obtained |
| 1488 | ** from malloc(), or a NULL pointer. The string pointed to by zAppend is |
| @@ -1503,30 +1557,30 @@ | |
| 1503 | for(i=0; i<nAppend; i++){ |
| 1504 | if( zAppend[i]==quote ) len++; |
| 1505 | } |
| 1506 | } |
| 1507 | |
| 1508 | if( p->z==0 || p->n+len>=p->nAlloc ){ |
| 1509 | p->nAlloc = p->nAlloc*2 + len + 20; |
| 1510 | p->z = realloc(p->z, p->nAlloc); |
| 1511 | shell_check_oom(p->z); |
| 1512 | } |
| 1513 | |
| 1514 | if( quote ){ |
| 1515 | char *zCsr = p->z+p->n; |
| 1516 | *zCsr++ = quote; |
| 1517 | for(i=0; i<nAppend; i++){ |
| 1518 | *zCsr++ = zAppend[i]; |
| 1519 | if( zAppend[i]==quote ) *zCsr++ = quote; |
| 1520 | } |
| 1521 | *zCsr++ = quote; |
| 1522 | p->n = (int)(zCsr - p->z); |
| 1523 | *zCsr = '\0'; |
| 1524 | }else{ |
| 1525 | memcpy(p->z+p->n, zAppend, nAppend); |
| 1526 | p->n += nAppend; |
| 1527 | p->z[p->n] = '\0'; |
| 1528 | } |
| 1529 | } |
| 1530 | |
| 1531 | /* |
| 1532 | ** Attempt to determine if identifier zName needs to be quoted, either |
| @@ -1547,10 +1601,13 @@ | |
| 1547 | } |
| 1548 | |
| 1549 | /* |
| 1550 | ** Construct a fake object name and column list to describe the structure |
| 1551 | ** of the view, virtual table, or table valued function zSchema.zName. |
| 1552 | */ |
| 1553 | static char *shellFakeSchema( |
| 1554 | sqlite3 *db, /* The database connection containing the vtab */ |
| 1555 | const char *zSchema, /* Schema of the database holding the vtab */ |
| 1556 | const char *zName /* The name of the virtual table */ |
| @@ -1587,13 +1644,13 @@ | |
| 1587 | } |
| 1588 | appendText(&s, ")", 0); |
| 1589 | sqlite3_finalize(pStmt); |
| 1590 | if( nRow==0 ){ |
| 1591 | freeText(&s); |
| 1592 | s.z = 0; |
| 1593 | } |
| 1594 | return s.z; |
| 1595 | } |
| 1596 | |
| 1597 | /* |
| 1598 | ** SQL function: strtod(X) |
| 1599 | ** |
| @@ -1692,11 +1749,11 @@ | |
| 1692 | if( z==0 ){ |
| 1693 | z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); |
| 1694 | }else{ |
| 1695 | z = sqlite3_mprintf("%z\n/* %s */", z, zFake); |
| 1696 | } |
| 1697 | free(zFake); |
| 1698 | } |
| 1699 | if( z ){ |
| 1700 | sqlite3_result_text(pCtx, z, -1, sqlite3_free); |
| 1701 | return; |
| 1702 | } |
| @@ -3667,11 +3724,12 @@ | |
| 3667 | iExp -= p->nFrac; |
| 3668 | p->nFrac = 0; |
| 3669 | } |
| 3670 | } |
| 3671 | if( iExp>0 ){ |
| 3672 | p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); |
| 3673 | if( p->a==0 ) goto new_from_text_failed; |
| 3674 | memset(p->a+p->nDigit, 0, iExp); |
| 3675 | p->nDigit += iExp; |
| 3676 | } |
| 3677 | }else if( iExp<0 ){ |
| @@ -3686,18 +3744,23 @@ | |
| 3686 | iExp -= nExtra; |
| 3687 | p->nFrac = p->nDigit - 1; |
| 3688 | } |
| 3689 | } |
| 3690 | if( iExp>0 ){ |
| 3691 | p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); |
| 3692 | if( p->a==0 ) goto new_from_text_failed; |
| 3693 | memmove(p->a+iExp, p->a, p->nDigit); |
| 3694 | memset(p->a, 0, iExp); |
| 3695 | p->nDigit += iExp; |
| 3696 | p->nFrac += iExp; |
| 3697 | } |
| 3698 | } |
| 3699 | return p; |
| 3700 | |
| 3701 | new_from_text_failed: |
| 3702 | if( p ){ |
| 3703 | if( p->a ) sqlite3_free(p->a); |
| @@ -3786,11 +3849,11 @@ | |
| 3786 | } |
| 3787 | if( p->isNull ){ |
| 3788 | sqlite3_result_null(pCtx); |
| 3789 | return; |
| 3790 | } |
| 3791 | z = sqlite3_malloc( p->nDigit+4 ); |
| 3792 | if( z==0 ){ |
| 3793 | sqlite3_result_error_nomem(pCtx); |
| 3794 | return; |
| 3795 | } |
| 3796 | i = 0; |
| @@ -3851,11 +3914,11 @@ | |
| 3851 | } |
| 3852 | for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} |
| 3853 | for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){} |
| 3854 | nFrac = p->nFrac + (nDigit - p->nDigit); |
| 3855 | nDigit -= nZero; |
| 3856 | z = sqlite3_malloc( nDigit+20 ); |
| 3857 | if( z==0 ){ |
| 3858 | sqlite3_result_error_nomem(pCtx); |
| 3859 | return; |
| 3860 | } |
| 3861 | if( nDigit==0 ){ |
| @@ -3896,17 +3959,25 @@ | |
| 3896 | ** pA!=0 |
| 3897 | ** pA->isNull==0 |
| 3898 | ** pB!=0 |
| 3899 | ** pB->isNull==0 |
| 3900 | */ |
| 3901 | static int decimal_cmp(const Decimal *pA, const Decimal *pB){ |
| 3902 | int nASig, nBSig, rc, n; |
| 3903 | if( pA->sign!=pB->sign ){ |
| 3904 | return pA->sign ? -1 : +1; |
| 3905 | } |
| 3906 | if( pA->sign ){ |
| 3907 | const Decimal *pTemp = pA; |
| 3908 | pA = pB; |
| 3909 | pB = pTemp; |
| 3910 | } |
| 3911 | nASig = pA->nDigit - pA->nFrac; |
| 3912 | nBSig = pB->nDigit - pB->nFrac; |
| @@ -4064,11 +4135,12 @@ | |
| 4064 | if( pA==0 || pA->oom || pA->isNull |
| 4065 | || pB==0 || pB->oom || pB->isNull |
| 4066 | ){ |
| 4067 | goto mul_end; |
| 4068 | } |
| 4069 | acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); |
| 4070 | if( acc==0 ){ |
| 4071 | pA->oom = 1; |
| 4072 | goto mul_end; |
| 4073 | } |
| 4074 | memset(acc, 0, pA->nDigit + pB->nDigit + 2); |
| @@ -4151,11 +4223,11 @@ | |
| 4151 | r = -r; |
| 4152 | }else{ |
| 4153 | isNeg = 0; |
| 4154 | } |
| 4155 | memcpy(&a,&r,sizeof(a)); |
| 4156 | if( a==0 ){ |
| 4157 | e = 0; |
| 4158 | m = 0; |
| 4159 | }else{ |
| 4160 | e = a>>52; |
| 4161 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| @@ -4426,516 +4498,10 @@ | |
| 4426 | } |
| 4427 | return rc; |
| 4428 | } |
| 4429 | |
| 4430 | /************************* End ../ext/misc/decimal.c ********************/ |
| 4431 | /************************* Begin ../ext/misc/percentile.c ******************/ |
| 4432 | /* |
| 4433 | ** 2013-05-28 |
| 4434 | ** |
| 4435 | ** The author disclaims copyright to this source code. In place of |
| 4436 | ** a legal notice, here is a blessing: |
| 4437 | ** |
| 4438 | ** May you do good and not evil. |
| 4439 | ** May you find forgiveness for yourself and forgive others. |
| 4440 | ** May you share freely, never taking more than you give. |
| 4441 | ** |
| 4442 | ****************************************************************************** |
| 4443 | ** |
| 4444 | ** This file contains code to implement the percentile(Y,P) SQL function |
| 4445 | ** and similar as described below: |
| 4446 | ** |
| 4447 | ** (1) The percentile(Y,P) function is an aggregate function taking |
| 4448 | ** exactly two arguments. |
| 4449 | ** |
| 4450 | ** (2) If the P argument to percentile(Y,P) is not the same for every |
| 4451 | ** row in the aggregate then an error is thrown. The word "same" |
| 4452 | ** in the previous sentence means that the value differ by less |
| 4453 | ** than 0.001. |
| 4454 | ** |
| 4455 | ** (3) If the P argument to percentile(Y,P) evaluates to anything other |
| 4456 | ** than a number in the range of 0.0 to 100.0 inclusive then an |
| 4457 | ** error is thrown. |
| 4458 | ** |
| 4459 | ** (4) If any Y argument to percentile(Y,P) evaluates to a value that |
| 4460 | ** is not NULL and is not numeric then an error is thrown. |
| 4461 | ** |
| 4462 | ** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus |
| 4463 | ** infinity then an error is thrown. (SQLite always interprets NaN |
| 4464 | ** values as NULL.) |
| 4465 | ** |
| 4466 | ** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, |
| 4467 | ** including CASE WHEN expressions. |
| 4468 | ** |
| 4469 | ** (7) The percentile(Y,P) aggregate is able to handle inputs of at least |
| 4470 | ** one million (1,000,000) rows. |
| 4471 | ** |
| 4472 | ** (8) If there are no non-NULL values for Y, then percentile(Y,P) |
| 4473 | ** returns NULL. |
| 4474 | ** |
| 4475 | ** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) |
| 4476 | ** returns the one Y value. |
| 4477 | ** |
| 4478 | ** (10) If there N non-NULL values of Y where N is two or more and |
| 4479 | ** the Y values are ordered from least to greatest and a graph is |
| 4480 | ** drawn from 0 to N-1 such that the height of the graph at J is |
| 4481 | ** the J-th Y value and such that straight lines are drawn between |
| 4482 | ** adjacent Y values, then the percentile(Y,P) function returns |
| 4483 | ** the height of the graph at P*(N-1)/100. |
| 4484 | ** |
| 4485 | ** (11) The percentile(Y,P) function always returns either a floating |
| 4486 | ** point number or NULL. |
| 4487 | ** |
| 4488 | ** (12) The percentile(Y,P) is implemented as a single C99 source-code |
| 4489 | ** file that compiles into a shared-library or DLL that can be loaded |
| 4490 | ** into SQLite using the sqlite3_load_extension() interface. |
| 4491 | ** |
| 4492 | ** (13) A separate median(Y) function is the equivalent percentile(Y,50). |
| 4493 | ** |
| 4494 | ** (14) A separate percentile_cont(Y,P) function is equivalent to |
| 4495 | ** percentile(Y,P/100.0). In other words, the fraction value in |
| 4496 | ** the second argument is in the range of 0 to 1 instead of 0 to 100. |
| 4497 | ** |
| 4498 | ** (15) A separate percentile_disc(Y,P) function is like |
| 4499 | ** percentile_cont(Y,P) except that instead of returning the weighted |
| 4500 | ** average of the nearest two input values, it returns the next lower |
| 4501 | ** value. So the percentile_disc(Y,P) will always return a value |
| 4502 | ** that was one of the inputs. |
| 4503 | ** |
| 4504 | ** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and |
| 4505 | ** percentile_disc(Y,P) can be used as window functions. |
| 4506 | ** |
| 4507 | ** Differences from standard SQL: |
| 4508 | ** |
| 4509 | ** * The percentile_cont(X,P) function is equivalent to the following in |
| 4510 | ** standard SQL: |
| 4511 | ** |
| 4512 | ** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) |
| 4513 | ** |
| 4514 | ** The SQLite syntax is much more compact. The standard SQL syntax |
| 4515 | ** is also supported if SQLite is compiled with the |
| 4516 | ** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. |
| 4517 | ** |
| 4518 | ** * No median(X) function exists in the SQL standard. App developers |
| 4519 | ** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". |
| 4520 | ** |
| 4521 | ** * No percentile(Y,P) function exists in the SQL standard. Instead of |
| 4522 | ** percential(Y,P), developers must write this: |
| 4523 | ** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that |
| 4524 | ** the fraction parameter to percentile() goes from 0 to 100 whereas |
| 4525 | ** the fraction parameter in SQL standard percentile_cont() goes from |
| 4526 | ** 0 to 1. |
| 4527 | ** |
| 4528 | ** Implementation notes as of 2024-08-31: |
| 4529 | ** |
| 4530 | ** * The regular aggregate-function versions of these routines work |
| 4531 | ** by accumulating all values in an array of doubles, then sorting |
| 4532 | ** that array using quicksort before computing the answer. Thus |
| 4533 | ** the runtime is O(NlogN) where N is the number of rows of input. |
| 4534 | ** |
| 4535 | ** * For the window-function versions of these routines, the array of |
| 4536 | ** inputs is sorted as soon as the first value is computed. Thereafter, |
| 4537 | ** the array is kept in sorted order using an insert-sort. This |
| 4538 | ** results in O(N*K) performance where K is the size of the window. |
| 4539 | ** One can imagine alternative implementations that give O(N*logN*logK) |
| 4540 | ** performance, but they require more complex logic and data structures. |
| 4541 | ** The developers have elected to keep the asymptotically slower |
| 4542 | ** algorithm for now, for simplicity, under the theory that window |
| 4543 | ** functions are seldom used and when they are, the window size K is |
| 4544 | ** often small. The developers might revisit that decision later, |
| 4545 | ** should the need arise. |
| 4546 | */ |
| 4547 | #if defined(SQLITE3_H) |
| 4548 | /* no-op */ |
| 4549 | #elif defined(SQLITE_STATIC_PERCENTILE) |
| 4550 | /* # include "sqlite3.h" */ |
| 4551 | #else |
| 4552 | /* # include "sqlite3ext.h" */ |
| 4553 | SQLITE_EXTENSION_INIT1 |
| 4554 | #endif |
| 4555 | #include <assert.h> |
| 4556 | #include <string.h> |
| 4557 | #include <stdlib.h> |
| 4558 | |
| 4559 | /* The following object is the group context for a single percentile() |
| 4560 | ** aggregate. Remember all input Y values until the very end. |
| 4561 | ** Those values are accumulated in the Percentile.a[] array. |
| 4562 | */ |
| 4563 | typedef struct Percentile Percentile; |
| 4564 | struct Percentile { |
| 4565 | unsigned nAlloc; /* Number of slots allocated for a[] */ |
| 4566 | unsigned nUsed; /* Number of slots actually used in a[] */ |
| 4567 | char bSorted; /* True if a[] is already in sorted order */ |
| 4568 | char bKeepSorted; /* True if advantageous to keep a[] sorted */ |
| 4569 | char bPctValid; /* True if rPct is valid */ |
| 4570 | double rPct; /* Fraction. 0.0 to 1.0 */ |
| 4571 | double *a; /* Array of Y values */ |
| 4572 | }; |
| 4573 | |
| 4574 | /* Details of each function in the percentile family */ |
| 4575 | typedef struct PercentileFunc PercentileFunc; |
| 4576 | struct PercentileFunc { |
| 4577 | const char *zName; /* Function name */ |
| 4578 | char nArg; /* Number of arguments */ |
| 4579 | char mxFrac; /* Maximum value of the "fraction" input */ |
| 4580 | char bDiscrete; /* True for percentile_disc() */ |
| 4581 | }; |
| 4582 | static const PercentileFunc aPercentFunc[] = { |
| 4583 | { "median", 1, 1, 0 }, |
| 4584 | { "percentile", 2, 100, 0 }, |
| 4585 | { "percentile_cont", 2, 1, 0 }, |
| 4586 | { "percentile_disc", 2, 1, 1 }, |
| 4587 | }; |
| 4588 | |
| 4589 | /* |
| 4590 | ** Return TRUE if the input floating-point number is an infinity. |
| 4591 | */ |
| 4592 | static int percentIsInfinity(double r){ |
| 4593 | sqlite3_uint64 u; |
| 4594 | assert( sizeof(u)==sizeof(r) ); |
| 4595 | memcpy(&u, &r, sizeof(u)); |
| 4596 | return ((u>>52)&0x7ff)==0x7ff; |
| 4597 | } |
| 4598 | |
| 4599 | /* |
| 4600 | ** Return TRUE if two doubles differ by 0.001 or less. |
| 4601 | */ |
| 4602 | static int percentSameValue(double a, double b){ |
| 4603 | a -= b; |
| 4604 | return a>=-0.001 && a<=0.001; |
| 4605 | } |
| 4606 | |
| 4607 | /* |
| 4608 | ** Search p (which must have p->bSorted) looking for an entry with |
| 4609 | ** value y. Return the index of that entry. |
| 4610 | ** |
| 4611 | ** If bExact is true, return -1 if the entry is not found. |
| 4612 | ** |
| 4613 | ** If bExact is false, return the index at which a new entry with |
| 4614 | ** value y should be insert in order to keep the values in sorted |
| 4615 | ** order. The smallest return value in this case will be 0, and |
| 4616 | ** the largest return value will be p->nUsed. |
| 4617 | */ |
| 4618 | static int percentBinarySearch(Percentile *p, double y, int bExact){ |
| 4619 | int iFirst = 0; /* First element of search range */ |
| 4620 | int iLast = p->nUsed - 1; /* Last element of search range */ |
| 4621 | while( iLast>=iFirst ){ |
| 4622 | int iMid = (iFirst+iLast)/2; |
| 4623 | double x = p->a[iMid]; |
| 4624 | if( x<y ){ |
| 4625 | iFirst = iMid + 1; |
| 4626 | }else if( x>y ){ |
| 4627 | iLast = iMid - 1; |
| 4628 | }else{ |
| 4629 | return iMid; |
| 4630 | } |
| 4631 | } |
| 4632 | if( bExact ) return -1; |
| 4633 | return iFirst; |
| 4634 | } |
| 4635 | |
| 4636 | /* |
| 4637 | ** Generate an error for a percentile function. |
| 4638 | ** |
| 4639 | ** The error format string must have exactly one occurrence of "%%s()" |
| 4640 | ** (with two '%' characters). That substring will be replaced by the name |
| 4641 | ** of the function. |
| 4642 | */ |
| 4643 | static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ |
| 4644 | PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); |
| 4645 | char *zMsg1; |
| 4646 | char *zMsg2; |
| 4647 | va_list ap; |
| 4648 | |
| 4649 | va_start(ap, zFormat); |
| 4650 | zMsg1 = sqlite3_vmprintf(zFormat, ap); |
| 4651 | va_end(ap); |
| 4652 | zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, pFunc->zName) : 0; |
| 4653 | sqlite3_result_error(pCtx, zMsg2, -1); |
| 4654 | sqlite3_free(zMsg1); |
| 4655 | sqlite3_free(zMsg2); |
| 4656 | } |
| 4657 | |
| 4658 | /* |
| 4659 | ** The "step" function for percentile(Y,P) is called once for each |
| 4660 | ** input row. |
| 4661 | */ |
| 4662 | static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ |
| 4663 | Percentile *p; |
| 4664 | double rPct; |
| 4665 | int eType; |
| 4666 | double y; |
| 4667 | assert( argc==2 || argc==1 ); |
| 4668 | |
| 4669 | if( argc==1 ){ |
| 4670 | /* Requirement 13: median(Y) is the same as percentile(Y,50). */ |
| 4671 | rPct = 0.5; |
| 4672 | }else{ |
| 4673 | /* Requirement 3: P must be a number between 0 and 100 */ |
| 4674 | PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); |
| 4675 | eType = sqlite3_value_numeric_type(argv[1]); |
| 4676 | rPct = sqlite3_value_double(argv[1])/(double)pFunc->mxFrac; |
| 4677 | if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) |
| 4678 | || rPct<0.0 || rPct>1.0 |
| 4679 | ){ |
| 4680 | percentError(pCtx, "the fraction argument to %%s()" |
| 4681 | " is not between 0.0 and %.1f", |
| 4682 | (double)pFunc->mxFrac); |
| 4683 | return; |
| 4684 | } |
| 4685 | } |
| 4686 | |
| 4687 | /* Allocate the session context. */ |
| 4688 | p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); |
| 4689 | if( p==0 ) return; |
| 4690 | |
| 4691 | /* Remember the P value. Throw an error if the P value is different |
| 4692 | ** from any prior row, per Requirement (2). */ |
| 4693 | if( !p->bPctValid ){ |
| 4694 | p->rPct = rPct; |
| 4695 | p->bPctValid = 1; |
| 4696 | }else if( !percentSameValue(p->rPct,rPct) ){ |
| 4697 | percentError(pCtx, "the fraction argument to %%s()" |
| 4698 | " is not the same for all input rows"); |
| 4699 | return; |
| 4700 | } |
| 4701 | |
| 4702 | /* Ignore rows for which Y is NULL */ |
| 4703 | eType = sqlite3_value_type(argv[0]); |
| 4704 | if( eType==SQLITE_NULL ) return; |
| 4705 | |
| 4706 | /* If not NULL, then Y must be numeric. Otherwise throw an error. |
| 4707 | ** Requirement 4 */ |
| 4708 | if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ |
| 4709 | percentError(pCtx, "input to %%s() is not numeric"); |
| 4710 | return; |
| 4711 | } |
| 4712 | |
| 4713 | /* Throw an error if the Y value is infinity or NaN */ |
| 4714 | y = sqlite3_value_double(argv[0]); |
| 4715 | if( percentIsInfinity(y) ){ |
| 4716 | percentError(pCtx, "Inf input to %%s()"); |
| 4717 | return; |
| 4718 | } |
| 4719 | |
| 4720 | /* Allocate and store the Y */ |
| 4721 | if( p->nUsed>=p->nAlloc ){ |
| 4722 | unsigned n = p->nAlloc*2 + 250; |
| 4723 | double *a = sqlite3_realloc64(p->a, sizeof(double)*n); |
| 4724 | if( a==0 ){ |
| 4725 | sqlite3_free(p->a); |
| 4726 | memset(p, 0, sizeof(*p)); |
| 4727 | sqlite3_result_error_nomem(pCtx); |
| 4728 | return; |
| 4729 | } |
| 4730 | p->nAlloc = n; |
| 4731 | p->a = a; |
| 4732 | } |
| 4733 | if( p->nUsed==0 ){ |
| 4734 | p->a[p->nUsed++] = y; |
| 4735 | p->bSorted = 1; |
| 4736 | }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ |
| 4737 | p->a[p->nUsed++] = y; |
| 4738 | }else if( p->bKeepSorted ){ |
| 4739 | int i; |
| 4740 | i = percentBinarySearch(p, y, 0); |
| 4741 | if( i<(int)p->nUsed ){ |
| 4742 | memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); |
| 4743 | } |
| 4744 | p->a[i] = y; |
| 4745 | p->nUsed++; |
| 4746 | }else{ |
| 4747 | p->a[p->nUsed++] = y; |
| 4748 | p->bSorted = 0; |
| 4749 | } |
| 4750 | } |
| 4751 | |
| 4752 | /* |
| 4753 | ** Interchange two doubles. |
| 4754 | */ |
| 4755 | #define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} |
| 4756 | |
| 4757 | /* |
| 4758 | ** Sort an array of doubles. |
| 4759 | ** |
| 4760 | ** Algorithm: quicksort |
| 4761 | ** |
| 4762 | ** This is implemented separately rather than using the qsort() routine |
| 4763 | ** from the standard library because: |
| 4764 | ** |
| 4765 | ** (1) To avoid a dependency on qsort() |
| 4766 | ** (2) To avoid the function call to the comparison routine for each |
| 4767 | ** comparison. |
| 4768 | */ |
| 4769 | static void percentSort(double *a, unsigned int n){ |
| 4770 | int iLt; /* Entries before a[iLt] are less than rPivot */ |
| 4771 | int iGt; /* Entries at or after a[iGt] are greater than rPivot */ |
| 4772 | int i; /* Loop counter */ |
| 4773 | double rPivot; /* The pivot value */ |
| 4774 | |
| 4775 | assert( n>=2 ); |
| 4776 | if( a[0]>a[n-1] ){ |
| 4777 | SWAP_DOUBLE(a[0],a[n-1]) |
| 4778 | } |
| 4779 | if( n==2 ) return; |
| 4780 | iGt = n-1; |
| 4781 | i = n/2; |
| 4782 | if( a[0]>a[i] ){ |
| 4783 | SWAP_DOUBLE(a[0],a[i]) |
| 4784 | }else if( a[i]>a[iGt] ){ |
| 4785 | SWAP_DOUBLE(a[i],a[iGt]) |
| 4786 | } |
| 4787 | if( n==3 ) return; |
| 4788 | rPivot = a[i]; |
| 4789 | iLt = i = 1; |
| 4790 | do{ |
| 4791 | if( a[i]<rPivot ){ |
| 4792 | if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt]) |
| 4793 | iLt++; |
| 4794 | i++; |
| 4795 | }else if( a[i]>rPivot ){ |
| 4796 | do{ |
| 4797 | iGt--; |
| 4798 | }while( iGt>i && a[iGt]>rPivot ); |
| 4799 | SWAP_DOUBLE(a[i],a[iGt]) |
| 4800 | }else{ |
| 4801 | i++; |
| 4802 | } |
| 4803 | }while( i<iGt ); |
| 4804 | if( iLt>=2 ) percentSort(a, iLt); |
| 4805 | if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); |
| 4806 | |
| 4807 | /* Uncomment for testing */ |
| 4808 | #if 0 |
| 4809 | for(i=0; i<n-1; i++){ |
| 4810 | assert( a[i]<=a[i+1] ); |
| 4811 | } |
| 4812 | #endif |
| 4813 | } |
| 4814 | |
| 4815 | |
| 4816 | /* |
| 4817 | ** The "inverse" function for percentile(Y,P) is called to remove a |
| 4818 | ** row that was previously inserted by "step". |
| 4819 | */ |
| 4820 | static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ |
| 4821 | Percentile *p; |
| 4822 | int eType; |
| 4823 | double y; |
| 4824 | int i; |
| 4825 | assert( argc==2 || argc==1 ); |
| 4826 | |
| 4827 | /* Allocate the session context. */ |
| 4828 | p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); |
| 4829 | assert( p!=0 ); |
| 4830 | |
| 4831 | /* Ignore rows for which Y is NULL */ |
| 4832 | eType = sqlite3_value_type(argv[0]); |
| 4833 | if( eType==SQLITE_NULL ) return; |
| 4834 | |
| 4835 | /* If not NULL, then Y must be numeric. Otherwise throw an error. |
| 4836 | ** Requirement 4 */ |
| 4837 | if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ |
| 4838 | return; |
| 4839 | } |
| 4840 | |
| 4841 | /* Ignore the Y value if it is infinity or NaN */ |
| 4842 | y = sqlite3_value_double(argv[0]); |
| 4843 | if( percentIsInfinity(y) ){ |
| 4844 | return; |
| 4845 | } |
| 4846 | if( p->bSorted==0 ){ |
| 4847 | assert( p->nUsed>1 ); |
| 4848 | percentSort(p->a, p->nUsed); |
| 4849 | p->bSorted = 1; |
| 4850 | } |
| 4851 | p->bKeepSorted = 1; |
| 4852 | |
| 4853 | /* Find and remove the row */ |
| 4854 | i = percentBinarySearch(p, y, 1); |
| 4855 | if( i>=0 ){ |
| 4856 | p->nUsed--; |
| 4857 | if( i<(int)p->nUsed ){ |
| 4858 | memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); |
| 4859 | } |
| 4860 | } |
| 4861 | } |
| 4862 | |
| 4863 | /* |
| 4864 | ** Compute the final output of percentile(). Clean up all allocated |
| 4865 | ** memory if and only if bIsFinal is true. |
| 4866 | */ |
| 4867 | static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ |
| 4868 | Percentile *p; |
| 4869 | PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx); |
| 4870 | unsigned i1, i2; |
| 4871 | double v1, v2; |
| 4872 | double ix, vx; |
| 4873 | p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); |
| 4874 | if( p==0 ) return; |
| 4875 | if( p->a==0 ) return; |
| 4876 | if( p->nUsed ){ |
| 4877 | if( p->bSorted==0 ){ |
| 4878 | assert( p->nUsed>1 ); |
| 4879 | percentSort(p->a, p->nUsed); |
| 4880 | p->bSorted = 1; |
| 4881 | } |
| 4882 | ix = p->rPct*(p->nUsed-1); |
| 4883 | i1 = (unsigned)ix; |
| 4884 | if( pFunc->bDiscrete ){ |
| 4885 | vx = p->a[i1]; |
| 4886 | }else{ |
| 4887 | i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; |
| 4888 | v1 = p->a[i1]; |
| 4889 | v2 = p->a[i2]; |
| 4890 | vx = v1 + (v2-v1)*(ix-i1); |
| 4891 | } |
| 4892 | sqlite3_result_double(pCtx, vx); |
| 4893 | } |
| 4894 | if( bIsFinal ){ |
| 4895 | sqlite3_free(p->a); |
| 4896 | memset(p, 0, sizeof(*p)); |
| 4897 | }else{ |
| 4898 | p->bKeepSorted = 1; |
| 4899 | } |
| 4900 | } |
| 4901 | static void percentFinal(sqlite3_context *pCtx){ |
| 4902 | percentCompute(pCtx, 1); |
| 4903 | } |
| 4904 | static void percentValue(sqlite3_context *pCtx){ |
| 4905 | percentCompute(pCtx, 0); |
| 4906 | } |
| 4907 | |
| 4908 | #if defined(_WIN32) && !defined(SQLITE3_H) && !defined(SQLITE_STATIC_PERCENTILE) |
| 4909 | |
| 4910 | #endif |
| 4911 | int sqlite3_percentile_init( |
| 4912 | sqlite3 *db, |
| 4913 | char **pzErrMsg, |
| 4914 | const sqlite3_api_routines *pApi |
| 4915 | ){ |
| 4916 | int rc = SQLITE_OK; |
| 4917 | unsigned int i; |
| 4918 | #ifdef SQLITE3EXT_H |
| 4919 | SQLITE_EXTENSION_INIT2(pApi); |
| 4920 | #else |
| 4921 | (void)pApi; /* Unused parameter */ |
| 4922 | #endif |
| 4923 | (void)pzErrMsg; /* Unused parameter */ |
| 4924 | for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){ |
| 4925 | rc = sqlite3_create_window_function(db, |
| 4926 | aPercentFunc[i].zName, |
| 4927 | aPercentFunc[i].nArg, |
| 4928 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_SELFORDER1, |
| 4929 | (void*)&aPercentFunc[i], |
| 4930 | percentStep, percentFinal, percentValue, percentInverse, 0); |
| 4931 | if( rc ) break; |
| 4932 | } |
| 4933 | return rc; |
| 4934 | } |
| 4935 | |
| 4936 | /************************* End ../ext/misc/percentile.c ********************/ |
| 4937 | #undef sqlite3_base_init |
| 4938 | #define sqlite3_base_init sqlite3_base64_init |
| 4939 | /************************* Begin ../ext/misc/base64.c ******************/ |
| 4940 | /* |
| 4941 | ** 2022-11-18 |
| @@ -5144,20 +4710,22 @@ | |
| 5144 | return pOut; |
| 5145 | } |
| 5146 | |
| 5147 | /* This function does the work for the SQLite base64(x) UDF. */ |
| 5148 | static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 5149 | int nb, nc, nv = sqlite3_value_bytes(av[0]); |
| 5150 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 5151 | SQLITE_LIMIT_LENGTH, -1); |
| 5152 | char *cBuf; |
| 5153 | u8 *bBuf; |
| 5154 | assert(na==1); |
| 5155 | switch( sqlite3_value_type(av[0]) ){ |
| 5156 | case SQLITE_BLOB: |
| 5157 | nb = nv; |
| 5158 | nc = 4*(nv+2/3); /* quads needed */ |
| 5159 | nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ |
| 5160 | if( nvMax < nc ){ |
| 5161 | sqlite3_result_error(context, "blob expanded to base64 too big", -1); |
| 5162 | return; |
| 5163 | } |
| @@ -5524,11 +5092,11 @@ | |
| 5524 | } |
| 5525 | # endif |
| 5526 | |
| 5527 | /* This function does the work for the SQLite base85(x) UDF. */ |
| 5528 | static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 5529 | int nb, nc, nv = sqlite3_value_bytes(av[0]); |
| 5530 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 5531 | SQLITE_LIMIT_LENGTH, -1); |
| 5532 | char *cBuf; |
| 5533 | u8 *bBuf; |
| 5534 | assert(na==1); |
| @@ -5829,10 +5397,13 @@ | |
| 5829 | } |
| 5830 | memcpy(&a,&r,sizeof(a)); |
| 5831 | if( a==0 ){ |
| 5832 | e = 0; |
| 5833 | m = 0; |
| 5834 | }else{ |
| 5835 | e = a>>52; |
| 5836 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| 5837 | if( e==0 ){ |
| 5838 | m <<= 1; |
| @@ -6052,23 +5623,24 @@ | |
| 6052 | ** Examples: |
| 6053 | ** |
| 6054 | ** SELECT * FROM generate_series(0,100,5); |
| 6055 | ** |
| 6056 | ** The query above returns integers from 0 through 100 counting by steps |
| 6057 | ** of 5. |
| 6058 | ** |
| 6059 | ** SELECT * FROM generate_series(0,100); |
| 6060 | ** |
| 6061 | ** Integers from 0 through 100 with a step size of 1. |
| 6062 | ** |
| 6063 | ** SELECT * FROM generate_series(20) LIMIT 10; |
| 6064 | ** |
| 6065 | ** Integers 20 through 29. |
| 6066 | ** |
| 6067 | ** SELECT * FROM generate_series(0,-100,-5); |
| 6068 | ** |
| 6069 | ** Integers 0 -5 -10 ... -100. |
| 6070 | ** |
| 6071 | ** SELECT * FROM generate_series(0,-1); |
| 6072 | ** |
| 6073 | ** Empty sequence. |
| 6074 | ** |
| @@ -6140,143 +5712,92 @@ | |
| 6140 | #include <string.h> |
| 6141 | #include <limits.h> |
| 6142 | #include <math.h> |
| 6143 | |
| 6144 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 6145 | /* |
| 6146 | ** Return that member of a generate_series(...) sequence whose 0-based |
| 6147 | ** index is ix. The 0th member is given by smBase. The sequence members |
| 6148 | ** progress per ix increment by smStep. |
| 6149 | */ |
| 6150 | static sqlite3_int64 genSeqMember( |
| 6151 | sqlite3_int64 smBase, |
| 6152 | sqlite3_int64 smStep, |
| 6153 | sqlite3_uint64 ix |
| 6154 | ){ |
| 6155 | static const sqlite3_uint64 mxI64 = |
| 6156 | ((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff; |
| 6157 | if( ix>=mxI64 ){ |
| 6158 | /* Get ix into signed i64 range. */ |
| 6159 | ix -= mxI64; |
| 6160 | /* With 2's complement ALU, this next can be 1 step, but is split into |
| 6161 | * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ |
| 6162 | smBase += (mxI64/2) * smStep; |
| 6163 | smBase += (mxI64 - mxI64/2) * smStep; |
| 6164 | } |
| 6165 | /* Under UBSAN (or on 1's complement machines), must do this last term |
| 6166 | * in steps to avoid the dreaded (and harmless) signed multiply overflow. */ |
| 6167 | if( ix>=2 ){ |
| 6168 | sqlite3_int64 ix2 = (sqlite3_int64)ix/2; |
| 6169 | smBase += ix2*smStep; |
| 6170 | ix -= ix2; |
| 6171 | } |
| 6172 | return smBase + ((sqlite3_int64)ix)*smStep; |
| 6173 | } |
| 6174 | |
| 6175 | /* typedef unsigned char u8; */ |
| 6176 | |
| 6177 | typedef struct SequenceSpec { |
| 6178 | sqlite3_int64 iOBase; /* Original starting value ("start") */ |
| 6179 | sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ |
| 6180 | sqlite3_int64 iBase; /* Starting value to actually use */ |
| 6181 | sqlite3_int64 iTerm; /* Terminal value to actually use */ |
| 6182 | sqlite3_int64 iStep; /* Increment ("step") */ |
| 6183 | sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */ |
| 6184 | sqlite3_uint64 uSeqIndexNow; /* Current index during generation */ |
| 6185 | sqlite3_int64 iValueNow; /* Current value during generation */ |
| 6186 | u8 isNotEOF; /* Sequence generation not exhausted */ |
| 6187 | u8 isReversing; /* Sequence is being reverse generated */ |
| 6188 | } SequenceSpec; |
| 6189 | |
| 6190 | /* |
| 6191 | ** Prepare a SequenceSpec for use in generating an integer series |
| 6192 | ** given initialized iBase, iTerm and iStep values. Sequence is |
| 6193 | ** initialized per given isReversing. Other members are computed. |
| 6194 | */ |
| 6195 | static void setupSequence( SequenceSpec *pss ){ |
| 6196 | int bSameSigns; |
| 6197 | pss->uSeqIndexMax = 0; |
| 6198 | pss->isNotEOF = 0; |
| 6199 | bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0); |
| 6200 | if( pss->iTerm < pss->iBase ){ |
| 6201 | sqlite3_uint64 nuspan = 0; |
| 6202 | if( bSameSigns ){ |
| 6203 | nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm); |
| 6204 | }else{ |
| 6205 | /* Under UBSAN (or on 1's complement machines), must do this in steps. |
| 6206 | * In this clause, iBase>=0 and iTerm<0 . */ |
| 6207 | nuspan = 1; |
| 6208 | nuspan += pss->iBase; |
| 6209 | nuspan += -(pss->iTerm+1); |
| 6210 | } |
| 6211 | if( pss->iStep<0 ){ |
| 6212 | pss->isNotEOF = 1; |
| 6213 | if( nuspan==ULONG_MAX ){ |
| 6214 | pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1; |
| 6215 | }else if( pss->iStep>LLONG_MIN ){ |
| 6216 | pss->uSeqIndexMax = nuspan/-pss->iStep; |
| 6217 | } |
| 6218 | } |
| 6219 | }else if( pss->iTerm > pss->iBase ){ |
| 6220 | sqlite3_uint64 puspan = 0; |
| 6221 | if( bSameSigns ){ |
| 6222 | puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase); |
| 6223 | }else{ |
| 6224 | /* Under UBSAN (or on 1's complement machines), must do this in steps. |
| 6225 | * In this clause, iTerm>=0 and iBase<0 . */ |
| 6226 | puspan = 1; |
| 6227 | puspan += pss->iTerm; |
| 6228 | puspan += -(pss->iBase+1); |
| 6229 | } |
| 6230 | if( pss->iStep>0 ){ |
| 6231 | pss->isNotEOF = 1; |
| 6232 | pss->uSeqIndexMax = puspan/pss->iStep; |
| 6233 | } |
| 6234 | }else if( pss->iTerm == pss->iBase ){ |
| 6235 | pss->isNotEOF = 1; |
| 6236 | pss->uSeqIndexMax = 0; |
| 6237 | } |
| 6238 | pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0; |
| 6239 | pss->iValueNow = (pss->isReversing) |
| 6240 | ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax) |
| 6241 | : pss->iBase; |
| 6242 | } |
| 6243 | |
| 6244 | /* |
| 6245 | ** Progress sequence generator to yield next value, if any. |
| 6246 | ** Leave its state to either yield next value or be at EOF. |
| 6247 | ** Return whether there is a next value, or 0 at EOF. |
| 6248 | */ |
| 6249 | static int progressSequence( SequenceSpec *pss ){ |
| 6250 | if( !pss->isNotEOF ) return 0; |
| 6251 | if( pss->isReversing ){ |
| 6252 | if( pss->uSeqIndexNow > 0 ){ |
| 6253 | pss->uSeqIndexNow--; |
| 6254 | pss->iValueNow -= pss->iStep; |
| 6255 | }else{ |
| 6256 | pss->isNotEOF = 0; |
| 6257 | } |
| 6258 | }else{ |
| 6259 | if( pss->uSeqIndexNow < pss->uSeqIndexMax ){ |
| 6260 | pss->uSeqIndexNow++; |
| 6261 | pss->iValueNow += pss->iStep; |
| 6262 | }else{ |
| 6263 | pss->isNotEOF = 0; |
| 6264 | } |
| 6265 | } |
| 6266 | return pss->isNotEOF; |
| 6267 | } |
| 6268 | |
| 6269 | /* series_cursor is a subclass of sqlite3_vtab_cursor which will |
| 6270 | ** serve as the underlying representation of a cursor that scans |
| 6271 | ** over rows of the result |
| 6272 | */ |
| 6273 | typedef struct series_cursor series_cursor; |
| 6274 | struct series_cursor { |
| 6275 | sqlite3_vtab_cursor base; /* Base class - must be first */ |
| 6276 | SequenceSpec ss; /* (this) Derived class data */ |
| 6277 | }; |
| 6278 | |
| 6279 | /* |
| 6280 | ** The seriesConnect() method is invoked to create a new |
| 6281 | ** series_vtab that describes the generate_series virtual table. |
| 6282 | ** |
| @@ -6354,11 +5875,19 @@ | |
| 6354 | /* |
| 6355 | ** Advance a series_cursor to its next row of output. |
| 6356 | */ |
| 6357 | static int seriesNext(sqlite3_vtab_cursor *cur){ |
| 6358 | series_cursor *pCur = (series_cursor*)cur; |
| 6359 | progressSequence( & pCur->ss ); |
| 6360 | return SQLITE_OK; |
| 6361 | } |
| 6362 | |
| 6363 | /* |
| 6364 | ** Return values of columns for the row at which the series_cursor |
| @@ -6370,41 +5899,41 @@ | |
| 6370 | int i /* Which column to return */ |
| 6371 | ){ |
| 6372 | series_cursor *pCur = (series_cursor*)cur; |
| 6373 | sqlite3_int64 x = 0; |
| 6374 | switch( i ){ |
| 6375 | case SERIES_COLUMN_START: x = pCur->ss.iOBase; break; |
| 6376 | case SERIES_COLUMN_STOP: x = pCur->ss.iOTerm; break; |
| 6377 | case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break; |
| 6378 | default: x = pCur->ss.iValueNow; break; |
| 6379 | } |
| 6380 | sqlite3_result_int64(ctx, x); |
| 6381 | return SQLITE_OK; |
| 6382 | } |
| 6383 | |
| 6384 | #ifndef LARGEST_UINT64 |
| 6385 | #define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) |
| 6386 | #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) |
| 6387 | #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) |
| 6388 | #endif |
| 6389 | |
| 6390 | /* |
| 6391 | ** The rowid is the same as the value. |
| 6392 | */ |
| 6393 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 6394 | series_cursor *pCur = (series_cursor*)cur; |
| 6395 | *pRowid = pCur->ss.iValueNow; |
| 6396 | return SQLITE_OK; |
| 6397 | } |
| 6398 | |
| 6399 | /* |
| 6400 | ** Return TRUE if the cursor has been moved off of the last |
| 6401 | ** row of output. |
| 6402 | */ |
| 6403 | static int seriesEof(sqlite3_vtab_cursor *cur){ |
| 6404 | series_cursor *pCur = (series_cursor*)cur; |
| 6405 | return !pCur->ss.isNotEOF; |
| 6406 | } |
| 6407 | |
| 6408 | /* True to cause run-time checking of the start=, stop=, and/or step= |
| 6409 | ** parameters. The only reason to do this is for testing the |
| 6410 | ** constraint checking logic for virtual tables in the SQLite core. |
| @@ -6411,10 +5940,63 @@ | |
| 6411 | */ |
| 6412 | #ifndef SQLITE_SERIES_CONSTRAINT_VERIFY |
| 6413 | # define SQLITE_SERIES_CONSTRAINT_VERIFY 0 |
| 6414 | #endif |
| 6415 | |
| 6416 | /* |
| 6417 | ** This method is called to "rewind" the series_cursor object back |
| 6418 | ** to the first row of output. This method is always called at least |
| 6419 | ** once prior to any call to seriesColumn() or seriesRowid() or |
| 6420 | ** seriesEof(). |
| @@ -6444,185 +6026,243 @@ | |
| 6444 | sqlite3_vtab_cursor *pVtabCursor, |
| 6445 | int idxNum, const char *idxStrUnused, |
| 6446 | int argc, sqlite3_value **argv |
| 6447 | ){ |
| 6448 | series_cursor *pCur = (series_cursor *)pVtabCursor; |
| 6449 | int i = 0; |
| 6450 | int returnNoRows = 0; |
| 6451 | sqlite3_int64 iMin = SMALLEST_INT64; |
| 6452 | sqlite3_int64 iMax = LARGEST_INT64; |
| 6453 | sqlite3_int64 iLimit = 0; |
| 6454 | sqlite3_int64 iOffset = 0; |
| 6455 | |
| 6456 | (void)idxStrUnused; |
| 6457 | if( idxNum & 0x01 ){ |
| 6458 | pCur->ss.iBase = sqlite3_value_int64(argv[i++]); |
| 6459 | }else{ |
| 6460 | pCur->ss.iBase = 0; |
| 6461 | } |
| 6462 | if( idxNum & 0x02 ){ |
| 6463 | pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); |
| 6464 | }else{ |
| 6465 | pCur->ss.iTerm = 0xffffffff; |
| 6466 | } |
| 6467 | if( idxNum & 0x04 ){ |
| 6468 | pCur->ss.iStep = sqlite3_value_int64(argv[i++]); |
| 6469 | if( pCur->ss.iStep==0 ){ |
| 6470 | pCur->ss.iStep = 1; |
| 6471 | }else if( pCur->ss.iStep<0 ){ |
| 6472 | if( (idxNum & 0x10)==0 ) idxNum |= 0x08; |
| 6473 | } |
| 6474 | }else{ |
| 6475 | pCur->ss.iStep = 1; |
| 6476 | } |
| 6477 | |
| 6478 | /* If there are constraints on the value column but there are |
| 6479 | ** no constraints on the start, stop, and step columns, then |
| 6480 | ** initialize the default range to be the entire range of 64-bit signed |
| 6481 | ** integers. This range will contracted by the value column constraints |
| 6482 | ** further below. |
| 6483 | */ |
| 6484 | if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){ |
| 6485 | pCur->ss.iBase = SMALLEST_INT64; |
| 6486 | } |
| 6487 | if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){ |
| 6488 | pCur->ss.iTerm = LARGEST_INT64; |
| 6489 | } |
| 6490 | pCur->ss.iOBase = pCur->ss.iBase; |
| 6491 | pCur->ss.iOTerm = pCur->ss.iTerm; |
| 6492 | |
| 6493 | /* Extract the LIMIT and OFFSET values, but do not apply them yet. |
| 6494 | ** The range must first be constrained by the limits on value. |
| 6495 | */ |
| 6496 | if( idxNum & 0x20 ){ |
| 6497 | iLimit = sqlite3_value_int64(argv[i++]); |
| 6498 | if( idxNum & 0x40 ){ |
| 6499 | iOffset = sqlite3_value_int64(argv[i++]); |
| 6500 | } |
| 6501 | } |
| 6502 | |
| 6503 | if( idxNum & 0x3380 ){ |
| 6504 | /* Extract the maximum range of output values determined by |
| 6505 | ** constraints on the "value" column. |
| 6506 | */ |
| 6507 | if( idxNum & 0x0080 ){ |
| 6508 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6509 | double r = sqlite3_value_double(argv[i++]); |
| 6510 | if( r==ceil(r) ){ |
| 6511 | iMin = iMax = (sqlite3_int64)r; |
| 6512 | }else{ |
| 6513 | returnNoRows = 1; |
| 6514 | } |
| 6515 | }else{ |
| 6516 | iMin = iMax = sqlite3_value_int64(argv[i++]); |
| 6517 | } |
| 6518 | }else{ |
| 6519 | if( idxNum & 0x0300 ){ |
| 6520 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6521 | double r = sqlite3_value_double(argv[i++]); |
| 6522 | if( idxNum & 0x0200 && r==ceil(r) ){ |
| 6523 | iMin = (sqlite3_int64)ceil(r+1.0); |
| 6524 | }else{ |
| 6525 | iMin = (sqlite3_int64)ceil(r); |
| 6526 | } |
| 6527 | }else{ |
| 6528 | iMin = sqlite3_value_int64(argv[i++]); |
| 6529 | if( idxNum & 0x0200 ){ |
| 6530 | if( iMin==LARGEST_INT64 ){ |
| 6531 | returnNoRows = 1; |
| 6532 | }else{ |
| 6533 | iMin++; |
| 6534 | } |
| 6535 | } |
| 6536 | } |
| 6537 | } |
| 6538 | if( idxNum & 0x3000 ){ |
| 6539 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6540 | double r = sqlite3_value_double(argv[i++]); |
| 6541 | if( (idxNum & 0x2000)!=0 && r==floor(r) ){ |
| 6542 | iMax = (sqlite3_int64)(r-1.0); |
| 6543 | }else{ |
| 6544 | iMax = (sqlite3_int64)floor(r); |
| 6545 | } |
| 6546 | }else{ |
| 6547 | iMax = sqlite3_value_int64(argv[i++]); |
| 6548 | if( idxNum & 0x2000 ){ |
| 6549 | if( iMax==SMALLEST_INT64 ){ |
| 6550 | returnNoRows = 1; |
| 6551 | }else{ |
| 6552 | iMax--; |
| 6553 | } |
| 6554 | } |
| 6555 | } |
| 6556 | } |
| 6557 | if( iMin>iMax ){ |
| 6558 | returnNoRows = 1; |
| 6559 | } |
| 6560 | } |
| 6561 | |
| 6562 | /* Try to reduce the range of values to be generated based on |
| 6563 | ** constraints on the "value" column. |
| 6564 | */ |
| 6565 | if( pCur->ss.iStep>0 ){ |
| 6566 | sqlite3_int64 szStep = pCur->ss.iStep; |
| 6567 | if( pCur->ss.iBase<iMin ){ |
| 6568 | sqlite3_uint64 d = iMin - pCur->ss.iBase; |
| 6569 | pCur->ss.iBase += ((d+szStep-1)/szStep)*szStep; |
| 6570 | } |
| 6571 | if( pCur->ss.iTerm>iMax ){ |
| 6572 | pCur->ss.iTerm = iMax; |
| 6573 | } |
| 6574 | }else{ |
| 6575 | sqlite3_int64 szStep = -pCur->ss.iStep; |
| 6576 | assert( szStep>0 ); |
| 6577 | if( pCur->ss.iBase>iMax ){ |
| 6578 | sqlite3_uint64 d = pCur->ss.iBase - iMax; |
| 6579 | pCur->ss.iBase -= ((d+szStep-1)/szStep)*szStep; |
| 6580 | } |
| 6581 | if( pCur->ss.iTerm<iMin ){ |
| 6582 | pCur->ss.iTerm = iMin; |
| 6583 | } |
| 6584 | } |
| 6585 | } |
| 6586 | |
| 6587 | /* Apply LIMIT and OFFSET constraints, if any */ |
| 6588 | if( idxNum & 0x20 ){ |
| 6589 | if( iOffset>0 ){ |
| 6590 | pCur->ss.iBase += pCur->ss.iStep*iOffset; |
| 6591 | } |
| 6592 | if( iLimit>=0 ){ |
| 6593 | sqlite3_int64 iTerm; |
| 6594 | iTerm = pCur->ss.iBase + (iLimit - 1)*pCur->ss.iStep; |
| 6595 | if( pCur->ss.iStep<0 ){ |
| 6596 | if( iTerm>pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; |
| 6597 | }else{ |
| 6598 | if( iTerm<pCur->ss.iTerm ) pCur->ss.iTerm = iTerm; |
| 6599 | } |
| 6600 | } |
| 6601 | } |
| 6602 | |
| 6603 | |
| 6604 | for(i=0; i<argc; i++){ |
| 6605 | if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ |
| 6606 | /* If any of the constraints have a NULL value, then return no rows. |
| 6607 | ** See ticket https://sqlite.org/src/info/fac496b61722daf2 */ |
| 6608 | returnNoRows = 1; |
| 6609 | break; |
| 6610 | } |
| 6611 | } |
| 6612 | if( returnNoRows ){ |
| 6613 | pCur->ss.iBase = 1; |
| 6614 | pCur->ss.iTerm = 0; |
| 6615 | pCur->ss.iStep = 1; |
| 6616 | } |
| 6617 | if( idxNum & 0x08 ){ |
| 6618 | pCur->ss.isReversing = pCur->ss.iStep > 0; |
| 6619 | }else{ |
| 6620 | pCur->ss.isReversing = pCur->ss.iStep < 0; |
| 6621 | } |
| 6622 | setupSequence( &pCur->ss ); |
| 6623 | return SQLITE_OK; |
| 6624 | } |
| 6625 | |
| 6626 | /* |
| 6627 | ** SQLite will invoke this method one or more times while planning a query |
| 6628 | ** that uses the generate_series virtual table. This routine needs to create |
| @@ -6948,10 +6588,12 @@ | |
| 6948 | ** expression and M is the size of the input string. The matcher never |
| 6949 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 6950 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 6951 | ** regular expression in the O(N*M) performance bound is computed after |
| 6952 | ** this expansion. |
| 6953 | */ |
| 6954 | #include <string.h> |
| 6955 | #include <stdlib.h> |
| 6956 | /* #include "sqlite3ext.h" */ |
| 6957 | SQLITE_EXTENSION_INIT1 |
| @@ -6988,36 +6630,10 @@ | |
| 6988 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 6989 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 6990 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 6991 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 6992 | #define RE_OP_ATSTART 18 /* Currently at the start of the string */ |
| 6993 | |
| 6994 | #if defined(SQLITE_DEBUG) |
| 6995 | /* Opcode names used for symbolic debugging */ |
| 6996 | static const char *ReOpName[] = { |
| 6997 | "EOF", |
| 6998 | "MATCH", |
| 6999 | "ANY", |
| 7000 | "ANYSTAR", |
| 7001 | "FORK", |
| 7002 | "GOTO", |
| 7003 | "ACCEPT", |
| 7004 | "CC_INC", |
| 7005 | "CC_EXC", |
| 7006 | "CC_VALUE", |
| 7007 | "CC_RANGE", |
| 7008 | "WORD", |
| 7009 | "NOTWORD", |
| 7010 | "DIGIT", |
| 7011 | "NOTDIGIT", |
| 7012 | "SPACE", |
| 7013 | "NOTSPACE", |
| 7014 | "BOUNDARY", |
| 7015 | "ATSTART", |
| 7016 | }; |
| 7017 | #endif /* SQLITE_DEBUG */ |
| 7018 | |
| 7019 | |
| 7020 | /* Each opcode is a "state" in the NFA */ |
| 7021 | typedef unsigned short ReStateNumber; |
| 7022 | |
| 7023 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -7051,10 +6667,11 @@ | |
| 7051 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 7052 | unsigned char zInit[12]; /* Initial text to match */ |
| 7053 | int nInit; /* Number of bytes in zInit */ |
| 7054 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 7055 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 7056 | }; |
| 7057 | |
| 7058 | /* Add a state to the given state set if it is not already there */ |
| 7059 | static void re_add_state(ReStateSet *pSet, int newState){ |
| 7060 | unsigned i; |
| @@ -7265,18 +6882,19 @@ | |
| 7265 | return rc; |
| 7266 | } |
| 7267 | |
| 7268 | /* Resize the opcode and argument arrays for an RE under construction. |
| 7269 | */ |
| 7270 | static int re_resize(ReCompiled *p, int N){ |
| 7271 | char *aOp; |
| 7272 | int *aArg; |
| 7273 | aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); |
| 7274 | if( aOp==0 ) return 1; |
| 7275 | p->aOp = aOp; |
| 7276 | aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); |
| 7277 | if( aArg==0 ) return 1; |
| 7278 | p->aArg = aArg; |
| 7279 | p->nAlloc = N; |
| 7280 | return 0; |
| 7281 | } |
| 7282 | |
| @@ -7303,11 +6921,11 @@ | |
| 7303 | } |
| 7304 | |
| 7305 | /* Make a copy of N opcodes starting at iStart onto the end of the RE |
| 7306 | ** under construction. |
| 7307 | */ |
| 7308 | static void re_copy(ReCompiled *p, int iStart, int N){ |
| 7309 | if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; |
| 7310 | memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); |
| 7311 | memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); |
| 7312 | p->nState += N; |
| 7313 | } |
| @@ -7456,22 +7074,30 @@ | |
| 7456 | case '^': { |
| 7457 | re_append(p, RE_OP_ATSTART, 0); |
| 7458 | break; |
| 7459 | } |
| 7460 | case '{': { |
| 7461 | int m = 0, n = 0; |
| 7462 | int sz, j; |
| 7463 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 7464 | while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } |
| 7465 | n = m; |
| 7466 | if( c==',' ){ |
| 7467 | p->sIn.i++; |
| 7468 | n = 0; |
| 7469 | while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } |
| 7470 | } |
| 7471 | if( c!='}' ) return "unmatched '{'"; |
| 7472 | if( n>0 && n<m ) return "n less than m in '{m,n}'"; |
| 7473 | p->sIn.i++; |
| 7474 | sz = p->nState - iPrev; |
| 7475 | if( m==0 ){ |
| 7476 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 7477 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| @@ -7483,11 +7109,11 @@ | |
| 7483 | for(j=m; j<n; j++){ |
| 7484 | re_append(p, RE_OP_FORK, sz+1); |
| 7485 | re_copy(p, iPrev, sz); |
| 7486 | } |
| 7487 | if( n==0 && m>0 ){ |
| 7488 | re_append(p, RE_OP_FORK, -sz); |
| 7489 | } |
| 7490 | break; |
| 7491 | } |
| 7492 | case '[': { |
| 7493 | unsigned int iFirst = p->nState; |
| @@ -7549,12 +7175,11 @@ | |
| 7549 | |
| 7550 | /* Free and reclaim all the memory used by a previously compiled |
| 7551 | ** regular expression. Applications should invoke this routine once |
| 7552 | ** for every call to re_compile() to avoid memory leaks. |
| 7553 | */ |
| 7554 | static void re_free(void *p){ |
| 7555 | ReCompiled *pRe = (ReCompiled*)p; |
| 7556 | if( pRe ){ |
| 7557 | sqlite3_free(pRe->aOp); |
| 7558 | sqlite3_free(pRe->aArg); |
| 7559 | sqlite3_free(pRe); |
| 7560 | } |
| @@ -7564,11 +7189,16 @@ | |
| 7564 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 7565 | ** expression suitable for us by re_match() and return a pointer to the |
| 7566 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 7567 | ** error message if something goes wrong. |
| 7568 | */ |
| 7569 | static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ |
| 7570 | ReCompiled *pRe; |
| 7571 | const char *zErr; |
| 7572 | int i, j; |
| 7573 | |
| 7574 | *ppRe = 0; |
| @@ -7576,13 +7206,15 @@ | |
| 7576 | if( pRe==0 ){ |
| 7577 | return "out of memory"; |
| 7578 | } |
| 7579 | memset(pRe, 0, sizeof(*pRe)); |
| 7580 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 7581 | if( re_resize(pRe, 30) ){ |
| 7582 | re_free(pRe); |
| 7583 | return "out of memory"; |
| 7584 | } |
| 7585 | if( zIn[0]=='^' ){ |
| 7586 | zIn++; |
| 7587 | }else{ |
| 7588 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -7630,10 +7262,18 @@ | |
| 7630 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 7631 | pRe->nInit = j; |
| 7632 | } |
| 7633 | return pRe->zErr; |
| 7634 | } |
| 7635 | |
| 7636 | /* |
| 7637 | ** Implementation of the regexp() SQL function. This function implements |
| 7638 | ** the build-in REGEXP operator. The first argument to the function is the |
| 7639 | ** pattern and the second argument is the string. So, the SQL statements: |
| @@ -7656,11 +7296,12 @@ | |
| 7656 | (void)argc; /* Unused */ |
| 7657 | pRe = sqlite3_get_auxdata(context, 0); |
| 7658 | if( pRe==0 ){ |
| 7659 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7660 | if( zPattern==0 ) return; |
| 7661 | zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); |
| 7662 | if( zErr ){ |
| 7663 | re_free(pRe); |
| 7664 | sqlite3_result_error(context, zErr, -1); |
| 7665 | return; |
| 7666 | } |
| @@ -7698,14 +7339,36 @@ | |
| 7698 | sqlite3_str *pStr; |
| 7699 | int i; |
| 7700 | int n; |
| 7701 | char *z; |
| 7702 | (void)argc; |
| 7703 | |
| 7704 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7705 | if( zPattern==0 ) return; |
| 7706 | zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); |
| 7707 | if( zErr ){ |
| 7708 | re_free(pRe); |
| 7709 | sqlite3_result_error(context, zErr, -1); |
| 7710 | return; |
| 7711 | } |
| @@ -9284,18 +8947,20 @@ | |
| 9284 | if( idxNum & 1 ){ |
| 9285 | pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); |
| 9286 | if( pCur->nPrefix>0 ){ |
| 9287 | pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 9288 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 9289 | } |
| 9290 | iArg = 1; |
| 9291 | } |
| 9292 | if( idxNum & 2 ){ |
| 9293 | pCur->nLine = sqlite3_value_bytes(argv[iArg]); |
| 9294 | if( pCur->nLine>0 ){ |
| 9295 | pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 9296 | if( pCur->zLine==0 ) return SQLITE_NOMEM; |
| 9297 | } |
| 9298 | } |
| 9299 | if( pCur->zLine!=0 && pCur->zPrefix==0 ){ |
| 9300 | int i = pCur->nLine; |
| 9301 | while( i>0 && (IsAlnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ |
| @@ -9303,10 +8968,11 @@ | |
| 9303 | } |
| 9304 | pCur->nPrefix = pCur->nLine - i; |
| 9305 | if( pCur->nPrefix>0 ){ |
| 9306 | pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); |
| 9307 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 9308 | } |
| 9309 | } |
| 9310 | pCur->iRowid = 0; |
| 9311 | pCur->ePhase = COMPLETION_FIRST_PHASE; |
| 9312 | return completionNext(pVtabCursor); |
| @@ -10216,11 +9882,17 @@ | |
| 10216 | "method," /* 6: Compression method (integer) */ |
| 10217 | "z HIDDEN" /* 7: Name of zip file */ |
| 10218 | ") WITHOUT ROWID;"; |
| 10219 | |
| 10220 | #define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */ |
| 10221 | #define ZIPFILE_BUFFER_SIZE (64*1024) |
| 10222 | |
| 10223 | |
| 10224 | /* |
| 10225 | ** Magic numbers used to read and write zip files. |
| 10226 | ** |
| @@ -10773,10 +10445,11 @@ | |
| 10773 | pLFH->crc32 = zipfileRead32(aRead); |
| 10774 | pLFH->szCompressed = zipfileRead32(aRead); |
| 10775 | pLFH->szUncompressed = zipfileRead32(aRead); |
| 10776 | pLFH->nFile = zipfileRead16(aRead); |
| 10777 | pLFH->nExtra = zipfileRead16(aRead); |
| 10778 | } |
| 10779 | return rc; |
| 10780 | } |
| 10781 | |
| 10782 | |
| @@ -10898,10 +10571,19 @@ | |
| 10898 | || mUnixTime==zipfileMtime(pCds) |
| 10899 | || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds)) |
| 10900 | /* || (mUnixTime % 2) */ |
| 10901 | ); |
| 10902 | } |
| 10903 | |
| 10904 | /* |
| 10905 | ** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in |
| 10906 | ** size) containing an entire zip archive image. Or, if aBlob is NULL, |
| 10907 | ** then pFile is a file-handle open on a zip file. In either case, this |
| @@ -10921,16 +10603,19 @@ | |
| 10921 | ZipfileEntry **ppEntry /* OUT: Pointer to new object */ |
| 10922 | ){ |
| 10923 | u8 *aRead; |
| 10924 | char **pzErr = &pTab->base.zErrMsg; |
| 10925 | int rc = SQLITE_OK; |
| 10926 | (void)nBlob; |
| 10927 | |
| 10928 | if( aBlob==0 ){ |
| 10929 | aRead = pTab->aBuffer; |
| 10930 | rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); |
| 10931 | }else{ |
| 10932 | aRead = (u8*)&aBlob[iOff]; |
| 10933 | } |
| 10934 | |
| 10935 | if( rc==SQLITE_OK ){ |
| 10936 | sqlite3_int64 nAlloc; |
| @@ -10957,10 +10642,13 @@ | |
| 10957 | rc = zipfileReadData( |
| 10958 | pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr |
| 10959 | ); |
| 10960 | }else{ |
| 10961 | aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ]; |
| 10962 | } |
| 10963 | } |
| 10964 | |
| 10965 | if( rc==SQLITE_OK ){ |
| 10966 | u32 *pt = &pNew->mUnixTime; |
| @@ -10979,19 +10667,26 @@ | |
| 10979 | ZipfileLFH lfh; |
| 10980 | if( pFile ){ |
| 10981 | rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr); |
| 10982 | }else{ |
| 10983 | aRead = (u8*)&aBlob[pNew->cds.iOffset]; |
| 10984 | } |
| 10985 | |
| 10986 | if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh); |
| 10987 | if( rc==SQLITE_OK ){ |
| 10988 | pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; |
| 10989 | pNew->iDataOff += lfh.nFile + lfh.nExtra; |
| 10990 | if( aBlob && pNew->cds.szCompressed ){ |
| 10991 | pNew->aData = &pNew->aExtra[nExtra]; |
| 10992 | memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); |
| 10993 | } |
| 10994 | }else{ |
| 10995 | *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", |
| 10996 | (int)pNew->cds.iOffset |
| 10997 | ); |
| @@ -11774,10 +11469,15 @@ | |
| 11774 | |
| 11775 | if( rc==SQLITE_OK ){ |
| 11776 | zPath = (const char*)sqlite3_value_text(apVal[2]); |
| 11777 | if( zPath==0 ) zPath = ""; |
| 11778 | nPath = (int)strlen(zPath); |
| 11779 | mTime = zipfileGetTime(apVal[4]); |
| 11780 | } |
| 11781 | |
| 11782 | if( rc==SQLITE_OK && bIsDir ){ |
| 11783 | /* For a directory, check that the last character in the path is a |
| @@ -12135,10 +11835,17 @@ | |
| 12135 | if( zName==0 ){ |
| 12136 | zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL"); |
| 12137 | rc = SQLITE_ERROR; |
| 12138 | goto zipfile_step_out; |
| 12139 | } |
| 12140 | |
| 12141 | /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use |
| 12142 | ** deflate compression) or NULL (choose automatically). */ |
| 12143 | if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){ |
| 12144 | iMethod = (int)sqlite3_value_int64(pMethod); |
| @@ -12477,10 +12184,11 @@ | |
| 12477 | return rc; |
| 12478 | } |
| 12479 | |
| 12480 | /************************* End ../ext/misc/sqlar.c ********************/ |
| 12481 | #endif |
| 12482 | /************************* Begin ../ext/expert/sqlite3expert.h ******************/ |
| 12483 | /* |
| 12484 | ** 2017 April 07 |
| 12485 | ** |
| 12486 | ** The author disclaims copyright to this source code. In place of |
| @@ -14885,10 +14593,11 @@ | |
| 14885 | } |
| 14886 | |
| 14887 | #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
| 14888 | |
| 14889 | /************************* End ../ext/expert/sqlite3expert.c ********************/ |
| 14890 | /************************* Begin ../ext/intck/sqlite3intck.h ******************/ |
| 14891 | /* |
| 14892 | ** 2024-02-08 |
| 14893 | ** |
| 14894 | ** The author disclaims copyright to this source code. In place of |
| @@ -21525,15 +21234,17 @@ | |
| 21525 | char **azFilter; /* Array of xFilter rejection GLOB patterns */ |
| 21526 | sqlite3_session *p; /* The open session */ |
| 21527 | }; |
| 21528 | #endif |
| 21529 | |
| 21530 | typedef struct ExpertInfo ExpertInfo; |
| 21531 | struct ExpertInfo { |
| 21532 | sqlite3expert *pExpert; |
| 21533 | int bVerbose; |
| 21534 | }; |
| 21535 | |
| 21536 | /* A single line in the EQP output */ |
| 21537 | typedef struct EQPGraphRow EQPGraphRow; |
| 21538 | struct EQPGraphRow { |
| 21539 | int iEqpId; /* ID for this row */ |
| @@ -21633,11 +21344,13 @@ | |
| 21633 | int *aiIndent; /* Array of indents used in MODE_Explain */ |
| 21634 | int nIndent; /* Size of array aiIndent[] */ |
| 21635 | int iIndent; /* Index of current op in aiIndent[] */ |
| 21636 | char *zNonce; /* Nonce for temporary safe-mode escapes */ |
| 21637 | EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ |
| 21638 | ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ |
| 21639 | #ifdef SQLITE_SHELL_FIDDLE |
| 21640 | struct { |
| 21641 | const char * zInput; /* Input string from wasm/JS proxy */ |
| 21642 | const char * zPos; /* Cursor pos into zInput */ |
| 21643 | const char * zDefaultDbName; /* Default name for db file */ |
| @@ -21661,13 +21374,12 @@ | |
| 21661 | */ |
| 21662 | #define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ |
| 21663 | #define SHELL_OPEN_NORMAL 1 /* Normal database file */ |
| 21664 | #define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ |
| 21665 | #define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ |
| 21666 | #define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ |
| 21667 | #define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ |
| 21668 | #define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */ |
| 21669 | |
| 21670 | /* Allowed values for ShellState.eTraceType |
| 21671 | */ |
| 21672 | #define SHELL_TRACE_PLAIN 0 /* Show input SQL text */ |
| 21673 | #define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */ |
| @@ -22006,11 +21718,11 @@ | |
| 22006 | */ |
| 22007 | static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ |
| 22008 | int i; |
| 22009 | unsigned char *aBlob = (unsigned char*)pBlob; |
| 22010 | |
| 22011 | char *zStr = sqlite3_malloc(nBlob*2 + 1); |
| 22012 | shell_check_oom(zStr); |
| 22013 | |
| 22014 | for(i=0; i<nBlob; i++){ |
| 22015 | static const char aHex[] = { |
| 22016 | '0', '1', '2', '3', '4', '5', '6', '7', |
| @@ -22027,11 +21739,11 @@ | |
| 22027 | |
| 22028 | /* |
| 22029 | ** Output the given string as a quoted string using SQL quoting conventions: |
| 22030 | ** |
| 22031 | ** (1) Single quotes (') within the string are doubled |
| 22032 | ** (2) The whle string is enclosed in '...' |
| 22033 | ** (3) Control characters other than \n, \t, and \r\n are escaped |
| 22034 | ** using \u00XX notation and if such substitutions occur, |
| 22035 | ** the whole string is enclosed in unistr('...') instead of '...'. |
| 22036 | ** |
| 22037 | ** Step (3) is omitted if the control-character escape mode is OFF. |
| @@ -22273,11 +21985,11 @@ | |
| 22273 | ** |
| 22274 | ** Escaping is needed if the string contains any control characters |
| 22275 | ** other than \t, \n, and \r\n |
| 22276 | ** |
| 22277 | ** If no escaping is needed (the common case) then set *ppFree to NULL |
| 22278 | ** and return the original string. If escapingn is needed, write the |
| 22279 | ** escaped string into memory obtained from sqlite3_malloc64() or the |
| 22280 | ** equivalent, and return the new string and set *ppFree to the new string |
| 22281 | ** as well. |
| 22282 | ** |
| 22283 | ** The caller is responsible for freeing *ppFree if it is non-NULL in order |
| @@ -23278,32 +22990,21 @@ | |
| 23278 | ** Set the destination table field of the ShellState structure to |
| 23279 | ** the name of the table given. Escape any quote characters in the |
| 23280 | ** table name. |
| 23281 | */ |
| 23282 | static void set_table_name(ShellState *p, const char *zName){ |
| 23283 | int i, n; |
| 23284 | char cQuote; |
| 23285 | char *z; |
| 23286 | |
| 23287 | if( p->zDestTable ){ |
| 23288 | free(p->zDestTable); |
| 23289 | p->zDestTable = 0; |
| 23290 | } |
| 23291 | if( zName==0 ) return; |
| 23292 | cQuote = quoteChar(zName); |
| 23293 | n = strlen30(zName); |
| 23294 | if( cQuote ) n += n+2; |
| 23295 | z = p->zDestTable = malloc( n+1 ); |
| 23296 | shell_check_oom(z); |
| 23297 | n = 0; |
| 23298 | if( cQuote ) z[n++] = cQuote; |
| 23299 | for(i=0; zName[i]; i++){ |
| 23300 | z[n++] = zName[i]; |
| 23301 | if( zName[i]==cQuote ) z[n++] = cQuote; |
| 23302 | } |
| 23303 | if( cQuote ) z[n++] = cQuote; |
| 23304 | z[n] = 0; |
| 23305 | } |
| 23306 | |
| 23307 | /* |
| 23308 | ** Maybe construct two lines of text that point out the position of a |
| 23309 | ** syntax error. Return a pointer to the text, in memory obtained from |
| @@ -23496,12 +23197,12 @@ | |
| 23496 | static int display_stats( |
| 23497 | sqlite3 *db, /* Database to query */ |
| 23498 | ShellState *pArg, /* Pointer to ShellState */ |
| 23499 | int bReset /* True to reset the stats */ |
| 23500 | ){ |
| 23501 | int iCur; |
| 23502 | int iHiwtr; |
| 23503 | FILE *out; |
| 23504 | if( pArg==0 || pArg->out==0 ) return 0; |
| 23505 | out = pArg->out; |
| 23506 | |
| 23507 | if( pArg->pStmt && pArg->statsOn==2 ){ |
| @@ -23586,18 +23287,25 @@ | |
| 23586 | "Page cache hits: %d\n", iCur); |
| 23587 | iHiwtr = iCur = -1; |
| 23588 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); |
| 23589 | sqlite3_fprintf(out, |
| 23590 | "Page cache misses: %d\n", iCur); |
| 23591 | iHiwtr = iCur = -1; |
| 23592 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); |
| 23593 | sqlite3_fprintf(out, |
| 23594 | "Page cache writes: %d\n", iCur); |
| 23595 | iHiwtr = iCur = -1; |
| 23596 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); |
| 23597 | sqlite3_fprintf(out, |
| 23598 | "Page cache spills: %d\n", iCur); |
| 23599 | iHiwtr = iCur = -1; |
| 23600 | sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); |
| 23601 | sqlite3_fprintf(out, |
| 23602 | "Schema Heap Usage: %d bytes\n", iCur); |
| 23603 | iHiwtr = iCur = -1; |
| @@ -23999,10 +23707,40 @@ | |
| 23999 | char *zBuf = sqlite3_malloc64( szVar-5 ); |
| 24000 | if( zBuf ){ |
| 24001 | memcpy(zBuf, &zVar[6], szVar-5); |
| 24002 | sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); |
| 24003 | } |
| 24004 | }else{ |
| 24005 | sqlite3_bind_null(pStmt, i); |
| 24006 | } |
| 24007 | sqlite3_reset(pQ); |
| 24008 | } |
| @@ -24581,11 +24319,11 @@ | |
| 24581 | } |
| 24582 | } |
| 24583 | } |
| 24584 | } |
| 24585 | |
| 24586 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 24587 | /* |
| 24588 | ** This function is called to process SQL if the previous shell command |
| 24589 | ** was ".expert". It passes the SQL in the second argument directly to |
| 24590 | ** the sqlite3expert object. |
| 24591 | ** |
| @@ -24713,11 +24451,11 @@ | |
| 24713 | } |
| 24714 | sqlite3_free(zErr); |
| 24715 | |
| 24716 | return rc; |
| 24717 | } |
| 24718 | #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
| 24719 | |
| 24720 | /* |
| 24721 | ** Execute a statement or set of statements. Print |
| 24722 | ** any result rows/columns depending on the current mode |
| 24723 | ** set via the supplied callback. |
| @@ -24739,11 +24477,11 @@ | |
| 24739 | |
| 24740 | if( pzErrMsg ){ |
| 24741 | *pzErrMsg = NULL; |
| 24742 | } |
| 24743 | |
| 24744 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 24745 | if( pArg->expert.pExpert ){ |
| 24746 | rc = expertHandleSQL(pArg, zSql, pzErrMsg); |
| 24747 | return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); |
| 24748 | } |
| 24749 | #endif |
| @@ -24901,11 +24639,11 @@ | |
| 24901 | static char **tableColumnList(ShellState *p, const char *zTab){ |
| 24902 | char **azCol = 0; |
| 24903 | sqlite3_stmt *pStmt; |
| 24904 | char *zSql; |
| 24905 | int nCol = 0; |
| 24906 | int nAlloc = 0; |
| 24907 | int nPK = 0; /* Number of PRIMARY KEY columns seen */ |
| 24908 | int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ |
| 24909 | int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); |
| 24910 | int rc; |
| 24911 | |
| @@ -24915,11 +24653,11 @@ | |
| 24915 | sqlite3_free(zSql); |
| 24916 | if( rc ) return 0; |
| 24917 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 24918 | if( nCol>=nAlloc-2 ){ |
| 24919 | nAlloc = nAlloc*2 + nCol + 10; |
| 24920 | azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0])); |
| 24921 | shell_check_oom(azCol); |
| 24922 | } |
| 24923 | azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); |
| 24924 | shell_check_oom(azCol[nCol]); |
| 24925 | if( sqlite3_column_int(pStmt, 5) ){ |
| @@ -25106,17 +24844,17 @@ | |
| 25106 | appendText(&sSelect, " FROM ", 0); |
| 25107 | appendText(&sSelect, zTable, quoteChar(zTable)); |
| 25108 | |
| 25109 | savedDestTable = p->zDestTable; |
| 25110 | savedMode = p->mode; |
| 25111 | p->zDestTable = sTable.z; |
| 25112 | p->mode = p->cMode = MODE_Insert; |
| 25113 | rc = shell_exec(p, sSelect.z, 0); |
| 25114 | if( (rc&0xff)==SQLITE_CORRUPT ){ |
| 25115 | sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out); |
| 25116 | toggleSelectOrder(p->db); |
| 25117 | shell_exec(p, sSelect.z, 0); |
| 25118 | toggleSelectOrder(p->db); |
| 25119 | } |
| 25120 | p->zDestTable = savedDestTable; |
| 25121 | p->mode = savedMode; |
| 25122 | freeText(&sTable); |
| @@ -25245,11 +24983,13 @@ | |
| 25245 | " --bom Put a UTF8 byte-order mark on intermediate file", |
| 25246 | #endif |
| 25247 | #ifndef SQLITE_SHELL_FIDDLE |
| 25248 | ".exit ?CODE? Exit this program with return-code CODE", |
| 25249 | #endif |
| 25250 | ".expert EXPERIMENTAL. Suggest indexes for queries", |
| 25251 | ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", |
| 25252 | ".filectrl CMD ... Run various sqlite3_file_control() operations", |
| 25253 | " --schema SCHEMA Use SCHEMA instead of \"main\"", |
| 25254 | " --help Show CMD details", |
| 25255 | ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", |
| @@ -25270,11 +25010,11 @@ | |
| 25270 | " from the \".mode\" output mode", |
| 25271 | " * If FILE begins with \"|\" then it is a command that generates the", |
| 25272 | " input text.", |
| 25273 | #endif |
| 25274 | #ifndef SQLITE_OMIT_TEST_CONTROL |
| 25275 | ",imposter INDEX TABLE Create imposter table TABLE on index INDEX", |
| 25276 | #endif |
| 25277 | ".indexes ?TABLE? Show names of indexes", |
| 25278 | " If TABLE is specified, only show indexes for", |
| 25279 | " tables matching TABLE using the LIKE operator.", |
| 25280 | ".intck ?STEPS_PER_UNLOCK? Run an incremental integrity check on the db", |
| @@ -25337,11 +25077,17 @@ | |
| 25337 | " Options:", |
| 25338 | " --append Use appendvfs to append database to the end of FILE", |
| 25339 | #endif |
| 25340 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 25341 | " --deserialize Load into memory using sqlite3_deserialize()", |
| 25342 | " --hexdb Load the output of \"dbtotxt\" as an in-memory db", |
| 25343 | " --maxsize N Maximum size for --hexdb or --deserialized database", |
| 25344 | #endif |
| 25345 | " --new Initialize FILE to an empty database", |
| 25346 | " --nofollow Do not follow symbolic links", |
| 25347 | " --readonly Open FILE readonly", |
| @@ -25726,14 +25472,14 @@ | |
| 25726 | ** If p->aAuxDb[].zDbFilename is 0, then read from standard input. |
| 25727 | */ |
| 25728 | static unsigned char *readHexDb(ShellState *p, int *pnData){ |
| 25729 | unsigned char *a = 0; |
| 25730 | int nLine; |
| 25731 | int n = 0; |
| 25732 | int pgsz = 0; |
| 25733 | int iOffset = 0; |
| 25734 | int j, k; |
| 25735 | int rc; |
| 25736 | FILE *in; |
| 25737 | const char *zDbFilename = p->pAuxDb->zDbFilename; |
| 25738 | unsigned int x[16]; |
| 25739 | char zLine[1000]; |
| @@ -25753,20 +25499,21 @@ | |
| 25753 | nLine++; |
| 25754 | if( sqlite3_fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; |
| 25755 | rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); |
| 25756 | if( rc!=2 ) goto readHexDb_error; |
| 25757 | if( n<0 ) goto readHexDb_error; |
| 25758 | if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error; |
| 25759 | n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */ |
| 25760 | a = sqlite3_malloc( n ? n : 1 ); |
| 25761 | shell_check_oom(a); |
| 25762 | memset(a, 0, n); |
| 25763 | if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ |
| 25764 | sqlite3_fputs("invalid pagesize\n", stderr); |
| 25765 | goto readHexDb_error; |
| 25766 | } |
| 25767 | for(nLine++; sqlite3_fgets(zLine, sizeof(zLine), in)!=0; nLine++){ |
| 25768 | rc = sscanf(zLine, "| page %d offset %d", &j, &k); |
| 25769 | if( rc==2 ){ |
| 25770 | iOffset = k; |
| 25771 | continue; |
| 25772 | } |
| @@ -25775,18 +25522,18 @@ | |
| 25775 | } |
| 25776 | rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", |
| 25777 | &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], |
| 25778 | &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); |
| 25779 | if( rc==17 ){ |
| 25780 | k = iOffset+j; |
| 25781 | if( k+16<=n && k>=0 ){ |
| 25782 | int ii; |
| 25783 | for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff; |
| 25784 | } |
| 25785 | } |
| 25786 | } |
| 25787 | *pnData = n; |
| 25788 | if( in!=p->in ){ |
| 25789 | fclose(in); |
| 25790 | }else{ |
| 25791 | p->lineno = nLine; |
| 25792 | } |
| @@ -25849,11 +25596,11 @@ | |
| 25849 | p->pLog = pSavedLog; |
| 25850 | |
| 25851 | if( zFake ){ |
| 25852 | sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), |
| 25853 | -1, sqlite3_free); |
| 25854 | free(zFake); |
| 25855 | } |
| 25856 | } |
| 25857 | |
| 25858 | /* Flags for open_db(). |
| 25859 | ** |
| @@ -25881,14 +25628,17 @@ | |
| 25881 | }else{ |
| 25882 | p->openMode = (u8)deduceDatabaseType(zDbFilename, |
| 25883 | (openFlags & OPEN_DB_ZIPFILE)!=0); |
| 25884 | } |
| 25885 | } |
| 25886 | switch( p->openMode ){ |
| 25887 | case SHELL_OPEN_APPENDVFS: { |
| 25888 | sqlite3_open_v2(zDbFilename, &p->db, |
| 25889 | SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs"); |
| 25890 | break; |
| 25891 | } |
| 25892 | case SHELL_OPEN_HEXDB: |
| 25893 | case SHELL_OPEN_DESERIALIZE: { |
| 25894 | sqlite3_open(0, &p->db); |
| @@ -25896,19 +25646,13 @@ | |
| 25896 | } |
| 25897 | case SHELL_OPEN_ZIPFILE: { |
| 25898 | sqlite3_open(":memory:", &p->db); |
| 25899 | break; |
| 25900 | } |
| 25901 | case SHELL_OPEN_READONLY: { |
| 25902 | sqlite3_open_v2(zDbFilename, &p->db, |
| 25903 | SQLITE_OPEN_READONLY|p->openFlags, 0); |
| 25904 | break; |
| 25905 | } |
| 25906 | case SHELL_OPEN_UNSPEC: |
| 25907 | case SHELL_OPEN_NORMAL: { |
| 25908 | sqlite3_open_v2(zDbFilename, &p->db, |
| 25909 | SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0); |
| 25910 | break; |
| 25911 | } |
| 25912 | } |
| 25913 | if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ |
| 25914 | sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n", |
| @@ -25944,11 +25688,10 @@ | |
| 25944 | sqlite3_sha_init(p->db, 0, 0); |
| 25945 | sqlite3_shathree_init(p->db, 0, 0); |
| 25946 | sqlite3_uint_init(p->db, 0, 0); |
| 25947 | sqlite3_stmtrand_init(p->db, 0, 0); |
| 25948 | sqlite3_decimal_init(p->db, 0, 0); |
| 25949 | sqlite3_percentile_init(p->db, 0, 0); |
| 25950 | sqlite3_base64_init(p->db, 0, 0); |
| 25951 | sqlite3_base85_init(p->db, 0, 0); |
| 25952 | sqlite3_regexp_init(p->db, 0, 0); |
| 25953 | sqlite3_ieee_init(p->db, 0, 0); |
| 25954 | sqlite3_series_init(p->db, 0, 0); |
| @@ -26043,13 +25786,15 @@ | |
| 26043 | } |
| 26044 | } |
| 26045 | #endif |
| 26046 | } |
| 26047 | if( p->db!=0 ){ |
| 26048 | if( p->bSafeModePersist ){ |
| 26049 | sqlite3_set_authorizer(p->db, safeModeAuth, p); |
| 26050 | } |
| 26051 | sqlite3_db_config( |
| 26052 | p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 |
| 26053 | ); |
| 26054 | } |
| 26055 | } |
| @@ -26358,12 +26103,12 @@ | |
| 26358 | struct ImportCtx { |
| 26359 | const char *zFile; /* Name of the input file */ |
| 26360 | FILE *in; /* Read the CSV text from this input stream */ |
| 26361 | int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ |
| 26362 | char *z; /* Accumulated text for a field */ |
| 26363 | int n; /* Number of bytes in z */ |
| 26364 | int nAlloc; /* Space allocated for z[] */ |
| 26365 | int nLine; /* Current line number */ |
| 26366 | int nRow; /* Number of rows imported */ |
| 26367 | int nErr; /* Number of errors encountered */ |
| 26368 | int bNotFirst; /* True if one or more bytes already read */ |
| 26369 | int cTerm; /* Character that terminated the most recent field */ |
| @@ -26829,14 +26574,17 @@ | |
| 26829 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26830 | /* |
| 26831 | ** Convert a 2-byte or 4-byte big-endian integer into a native integer |
| 26832 | */ |
| 26833 | static unsigned int get2byteInt(unsigned char *a){ |
| 26834 | return (a[0]<<8) + a[1]; |
| 26835 | } |
| 26836 | static unsigned int get4byteInt(unsigned char *a){ |
| 26837 | return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; |
| 26838 | } |
| 26839 | |
| 26840 | /* |
| 26841 | ** Implementation of the ".dbinfo" command. |
| 26842 | ** |
| @@ -26969,11 +26717,11 @@ | |
| 26969 | if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; |
| 26970 | nPage = sqlite3_column_int64(pStmt, 0); |
| 26971 | sqlite3_finalize(pStmt); |
| 26972 | pStmt = 0; |
| 26973 | if( nPage<1 ) goto dbtotxt_error; |
| 26974 | rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0); |
| 26975 | if( rc ) goto dbtotxt_error; |
| 26976 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 26977 | zTail = "unk.db"; |
| 26978 | }else{ |
| 26979 | const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2); |
| @@ -26980,10 +26728,15 @@ | |
| 26980 | if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db"; |
| 26981 | zTail = strrchr(zFilename, '/'); |
| 26982 | #if defined(_WIN32) |
| 26983 | if( zTail==0 ) zTail = strrchr(zFilename, '\\'); |
| 26984 | #endif |
| 26985 | } |
| 26986 | zName = strdup(zTail); |
| 26987 | shell_check_oom(zName); |
| 26988 | sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", |
| 26989 | nPage*pgSz, pgSz, zName); |
| @@ -27148,10 +26901,47 @@ | |
| 27148 | if( zStr[0]!='-' ) return 0; |
| 27149 | zStr++; |
| 27150 | if( zStr[0]=='-' ) zStr++; |
| 27151 | return cli_strcmp(zStr, zOpt)==0; |
| 27152 | } |
| 27153 | |
| 27154 | /* |
| 27155 | ** Delete a file. |
| 27156 | */ |
| 27157 | int shellDeleteFile(const char *zFilename){ |
| @@ -28704,11 +28494,11 @@ | |
| 28704 | int nArg = 0; |
| 28705 | int n, c; |
| 28706 | int rc = 0; |
| 28707 | char *azArg[52]; |
| 28708 | |
| 28709 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 28710 | if( p->expert.pExpert ){ |
| 28711 | expertFinish(p, 1, 0); |
| 28712 | } |
| 28713 | #endif |
| 28714 | |
| @@ -29005,11 +28795,11 @@ | |
| 29005 | }else{ |
| 29006 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 29007 | const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); |
| 29008 | const char *zFile = (const char*)sqlite3_column_text(pStmt,2); |
| 29009 | if( zSchema==0 || zFile==0 ) continue; |
| 29010 | azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); |
| 29011 | shell_check_oom(azName); |
| 29012 | azName[nName*2] = strdup(zSchema); |
| 29013 | azName[nName*2+1] = strdup(zFile); |
| 29014 | nName++; |
| 29015 | } |
| @@ -29208,10 +28998,11 @@ | |
| 29208 | rc = 1; |
| 29209 | } |
| 29210 | }else |
| 29211 | |
| 29212 | if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 29213 | rc = shell_dbtotxt_command(p, nArg, azArg); |
| 29214 | }else |
| 29215 | |
| 29216 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 29217 | if( nArg==2 ){ |
| @@ -29273,11 +29064,11 @@ | |
| 29273 | if( p->mode==MODE_Explain ) p->mode = p->normalMode; |
| 29274 | p->autoExplain = 1; |
| 29275 | } |
| 29276 | }else |
| 29277 | |
| 29278 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 29279 | if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ |
| 29280 | if( p->bSafeMode ){ |
| 29281 | sqlite3_fprintf(stderr, |
| 29282 | "Cannot run experimental commands such as \"%s\" in safe mode\n", |
| 29283 | azArg[0]); |
| @@ -29840,16 +29631,10 @@ | |
| 29840 | sqlite3_stmt *pStmt; |
| 29841 | int tnum = 0; |
| 29842 | int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ |
| 29843 | int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ |
| 29844 | int i; |
| 29845 | if( !ShellHasFlag(p,SHFLG_TestingMode) ){ |
| 29846 | sqlite3_fprintf(stderr,".%s unavailable without --unsafe-testing\n", |
| 29847 | "imposter"); |
| 29848 | rc = 1; |
| 29849 | goto meta_command_exit; |
| 29850 | } |
| 29851 | if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ |
| 29852 | eputz("Usage: .imposter INDEX IMPOSTER\n" |
| 29853 | " .imposter off\n"); |
| 29854 | /* Also allowed, but not documented: |
| 29855 | ** |
| @@ -29917,22 +29702,19 @@ | |
| 29917 | if( lenPK==0 ) lenPK = 100000; |
| 29918 | zSql = sqlite3_mprintf( |
| 29919 | "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", |
| 29920 | azArg[2], zCollist, lenPK, zCollist); |
| 29921 | sqlite3_free(zCollist); |
| 29922 | rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum); |
| 29923 | if( rc==SQLITE_OK ){ |
| 29924 | rc = sqlite3_exec(p->db, zSql, 0, 0, 0); |
| 29925 | sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); |
| 29926 | if( rc ){ |
| 29927 | sqlite3_fprintf(stderr, |
| 29928 | "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); |
| 29929 | }else{ |
| 29930 | sqlite3_fprintf(stdout, "%s;\n", zSql); |
| 29931 | sqlite3_fprintf(stdout, |
| 29932 | "WARNING: writing to an imposter table will corrupt" |
| 29933 | " the \"%s\" %s!\n", azArg[1], isWO ? "table" : "index"); |
| 29934 | } |
| 29935 | }else{ |
| 29936 | sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); |
| 29937 | rc = 1; |
| 29938 | } |
| @@ -30282,10 +30064,11 @@ | |
| 30282 | const char *zFN = 0; /* Pointer to constant filename */ |
| 30283 | char *zNewFilename = 0; /* Name of the database file to open */ |
| 30284 | int iName = 1; /* Index in azArg[] of the filename */ |
| 30285 | int newFlag = 0; /* True to delete file before opening */ |
| 30286 | int openMode = SHELL_OPEN_UNSPEC; |
| 30287 | |
| 30288 | /* Check for command-line arguments */ |
| 30289 | for(iName=1; iName<nArg; iName++){ |
| 30290 | const char *z = azArg[iName]; |
| 30291 | #ifndef SQLITE_SHELL_FIDDLE |
| @@ -30296,13 +30079,18 @@ | |
| 30296 | openMode = SHELL_OPEN_ZIPFILE; |
| 30297 | #endif |
| 30298 | }else if( optionMatch(z, "append") ){ |
| 30299 | openMode = SHELL_OPEN_APPENDVFS; |
| 30300 | }else if( optionMatch(z, "readonly") ){ |
| 30301 | openMode = SHELL_OPEN_READONLY; |
| 30302 | }else if( optionMatch(z, "nofollow") ){ |
| 30303 | p->openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 30304 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 30305 | }else if( optionMatch(z, "deserialize") ){ |
| 30306 | openMode = SHELL_OPEN_DESERIALIZE; |
| 30307 | }else if( optionMatch(z, "hexdb") ){ |
| 30308 | openMode = SHELL_OPEN_HEXDB; |
| @@ -30330,16 +30118,25 @@ | |
| 30330 | p->db = 0; |
| 30331 | p->pAuxDb->zDbFilename = 0; |
| 30332 | sqlite3_free(p->pAuxDb->zFreeOnClose); |
| 30333 | p->pAuxDb->zFreeOnClose = 0; |
| 30334 | p->openMode = openMode; |
| 30335 | p->openFlags = 0; |
| 30336 | p->szMax = 0; |
| 30337 | |
| 30338 | /* If a filename is specified, try to open it first */ |
| 30339 | if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ |
| 30340 | if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN); |
| 30341 | #ifndef SQLITE_SHELL_FIDDLE |
| 30342 | if( p->bSafeMode |
| 30343 | && p->openMode!=SHELL_OPEN_HEXDB |
| 30344 | && zFN |
| 30345 | && cli_strcmp(zFN,":memory:")!=0 |
| @@ -30938,13 +30735,13 @@ | |
| 30938 | appendText(&sSelect, "name NOT LIKE 'sqlite__%%' ESCAPE '_' AND ", 0); |
| 30939 | } |
| 30940 | appendText(&sSelect, "sql IS NOT NULL" |
| 30941 | " ORDER BY snum, rowid", 0); |
| 30942 | if( bDebug ){ |
| 30943 | sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.z); |
| 30944 | }else{ |
| 30945 | rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); |
| 30946 | } |
| 30947 | freeText(&sSelect); |
| 30948 | } |
| 30949 | if( zErrMsg ){ |
| 30950 | shellEmitError(zErrMsg); |
| @@ -31072,19 +30869,20 @@ | |
| 31072 | |
| 31073 | /* .session filter GLOB .... |
| 31074 | ** Set a list of GLOB patterns of table names to be excluded. |
| 31075 | */ |
| 31076 | if( cli_strcmp(azCmd[0], "filter")==0 ){ |
| 31077 | int ii, nByte; |
| 31078 | if( nCmd<2 ) goto session_syntax_error; |
| 31079 | if( pAuxDb->nSession ){ |
| 31080 | for(ii=0; ii<pSession->nFilter; ii++){ |
| 31081 | sqlite3_free(pSession->azFilter[ii]); |
| 31082 | } |
| 31083 | sqlite3_free(pSession->azFilter); |
| 31084 | nByte = sizeof(pSession->azFilter[0])*(nCmd-1); |
| 31085 | pSession->azFilter = sqlite3_malloc( nByte ); |
| 31086 | shell_check_oom( pSession->azFilter ); |
| 31087 | for(ii=1; ii<nCmd; ii++){ |
| 31088 | char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); |
| 31089 | shell_check_oom(x); |
| 31090 | } |
| @@ -31264,26 +31062,26 @@ | |
| 31264 | sqlite3_fprintf(p->out, "%s\n", zSql); |
| 31265 | }else |
| 31266 | if( cli_strcmp(zOp,"run")==0 ){ |
| 31267 | char *zErrMsg = 0; |
| 31268 | str.n = 0; |
| 31269 | str.z[0] = 0; |
| 31270 | rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); |
| 31271 | nTest++; |
| 31272 | if( bVerbose ){ |
| 31273 | sqlite3_fprintf(p->out, "Result: %s\n", str.z); |
| 31274 | } |
| 31275 | if( rc || zErrMsg ){ |
| 31276 | nErr++; |
| 31277 | rc = 1; |
| 31278 | sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); |
| 31279 | sqlite3_free(zErrMsg); |
| 31280 | }else if( cli_strcmp(zAns,str.z)!=0 ){ |
| 31281 | nErr++; |
| 31282 | rc = 1; |
| 31283 | sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns); |
| 31284 | sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.z); |
| 31285 | } |
| 31286 | } |
| 31287 | else{ |
| 31288 | sqlite3_fprintf(stderr, |
| 31289 | "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); |
| @@ -31395,11 +31193,11 @@ | |
| 31395 | appendText(&sQuery, "SELECT * FROM ", 0); |
| 31396 | appendText(&sQuery, zTab, 0); |
| 31397 | appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); |
| 31398 | } |
| 31399 | appendText(&sSql, zSep, 0); |
| 31400 | appendText(&sSql, sQuery.z, '\''); |
| 31401 | sQuery.n = 0; |
| 31402 | appendText(&sSql, ",", 0); |
| 31403 | appendText(&sSql, zTab, '\''); |
| 31404 | zSep = "),("; |
| 31405 | } |
| @@ -31407,17 +31205,17 @@ | |
| 31407 | if( bSeparate ){ |
| 31408 | zSql = sqlite3_mprintf( |
| 31409 | "%s))" |
| 31410 | " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" |
| 31411 | " FROM [sha3sum$query]", |
| 31412 | sSql.z, iSize); |
| 31413 | }else{ |
| 31414 | zSql = sqlite3_mprintf( |
| 31415 | "%s))" |
| 31416 | " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" |
| 31417 | " FROM [sha3sum$query]", |
| 31418 | sSql.z, iSize); |
| 31419 | } |
| 31420 | shell_check_oom(zSql); |
| 31421 | freeText(&sQuery); |
| 31422 | freeText(&sSql); |
| 31423 | if( bDebug ){ |
| @@ -31613,11 +31411,11 @@ | |
| 31613 | goto meta_command_exit; |
| 31614 | } |
| 31615 | for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ |
| 31616 | const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); |
| 31617 | if( zDbName==0 ) continue; |
| 31618 | if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0); |
| 31619 | if( sqlite3_stricmp(zDbName, "main")==0 ){ |
| 31620 | appendText(&s, "SELECT name FROM ", 0); |
| 31621 | }else{ |
| 31622 | appendText(&s, "SELECT ", 0); |
| 31623 | appendText(&s, zDbName, '\''); |
| @@ -31635,11 +31433,11 @@ | |
| 31635 | } |
| 31636 | } |
| 31637 | rc = sqlite3_finalize(pStmt); |
| 31638 | if( rc==SQLITE_OK ){ |
| 31639 | appendText(&s, " ORDER BY 1", 0); |
| 31640 | rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0); |
| 31641 | } |
| 31642 | freeText(&s); |
| 31643 | if( rc ) return shellDatabaseError(p->db); |
| 31644 | |
| 31645 | /* Run the SQL statement prepared by the above block. Store the results |
| @@ -31652,14 +31450,14 @@ | |
| 31652 | sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); |
| 31653 | } |
| 31654 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 31655 | if( nRow>=nAlloc ){ |
| 31656 | char **azNew; |
| 31657 | int n2 = nAlloc*2 + 10; |
| 31658 | azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); |
| 31659 | shell_check_oom(azNew); |
| 31660 | nAlloc = n2; |
| 31661 | azResult = azNew; |
| 31662 | } |
| 31663 | azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| 31664 | shell_check_oom(azResult[nRow]); |
| 31665 | nRow++; |
| @@ -31818,11 +31616,11 @@ | |
| 31818 | { 0x00000020, 1, "CoverIdxScan" }, |
| 31819 | { 0x00000040, 1, "OrderByIdxJoin" }, |
| 31820 | { 0x00000080, 1, "Transitive" }, |
| 31821 | { 0x00000100, 1, "OmitNoopJoin" }, |
| 31822 | { 0x00000200, 1, "CountOfView" }, |
| 31823 | { 0x00000400, 1, "CurosrHints" }, |
| 31824 | { 0x00000800, 1, "Stat4" }, |
| 31825 | { 0x00001000, 1, "PushDown" }, |
| 31826 | { 0x00002000, 1, "SimplifyJoin" }, |
| 31827 | { 0x00004000, 1, "SkipScan" }, |
| 31828 | { 0x00008000, 1, "PropagateConst" }, |
| @@ -32365,11 +32163,14 @@ | |
| 32365 | p->nWidth = nArg-1; |
| 32366 | p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); |
| 32367 | if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); |
| 32368 | if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; |
| 32369 | for(j=1; j<nArg; j++){ |
| 32370 | p->colWidth[j-1] = (int)integerValue(azArg[j]); |
| 32371 | } |
| 32372 | }else |
| 32373 | |
| 32374 | { |
| 32375 | sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: " |
| @@ -32888,76 +32689,95 @@ | |
| 32888 | |
| 32889 | return home_dir; |
| 32890 | } |
| 32891 | |
| 32892 | /* |
| 32893 | ** On non-Windows platforms, look for $XDG_CONFIG_HOME. |
| 32894 | ** If ${XDG_CONFIG_HOME}/sqlite3/sqliterc is found, return |
| 32895 | ** the path to it. If there is no $(XDG_CONFIG_HOME) then |
| 32896 | ** look for $(HOME)/.config/sqlite3/sqliterc and if found |
| 32897 | ** return that. If none of these are found, return 0. |
| 32898 | ** |
| 32899 | ** The string returned is obtained from sqlite3_malloc() and |
| 32900 | ** should be freed by the caller. |
| 32901 | */ |
| 32902 | static char *find_xdg_config(void){ |
| 32903 | #if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ |
| 32904 | || defined(__RTP__) || defined(_WRS_KERNEL) |
| 32905 | return 0; |
| 32906 | #else |
| 32907 | char *zConfig = 0; |
| 32908 | const char *zXdgHome; |
| 32909 | |
| 32910 | zXdgHome = getenv("XDG_CONFIG_HOME"); |
| 32911 | if( zXdgHome==0 ){ |
| 32912 | const char *zHome = getenv("HOME"); |
| 32913 | if( zHome==0 ) return 0; |
| 32914 | zConfig = sqlite3_mprintf("%s/.config/sqlite3/sqliterc", zHome); |
| 32915 | }else{ |
| 32916 | zConfig = sqlite3_mprintf("%s/sqlite3/sqliterc", zXdgHome); |
| 32917 | } |
| 32918 | shell_check_oom(zConfig); |
| 32919 | if( access(zConfig,0)!=0 ){ |
| 32920 | sqlite3_free(zConfig); |
| 32921 | zConfig = 0; |
| 32922 | } |
| 32923 | return zConfig; |
| 32924 | #endif |
| 32925 | } |
| 32926 | |
| 32927 | /* |
| 32928 | ** Read input from the file given by sqliterc_override. Or if that |
| 32929 | ** parameter is NULL, take input from the first of find_xdg_config() |
| 32930 | ** or ~/.sqliterc which is found. |
| 32931 | ** |
| 32932 | ** Returns the number of errors. |
| 32933 | */ |
| 32934 | static void process_sqliterc( |
| 32935 | ShellState *p, /* Configuration data */ |
| 32936 | const char *sqliterc_override /* Name of config file. NULL to use default */ |
| 32937 | ){ |
| 32938 | char *home_dir = NULL; |
| 32939 | const char *sqliterc = sqliterc_override; |
| 32940 | char *zBuf = 0; |
| 32941 | FILE *inSaved = p->in; |
| 32942 | int savedLineno = p->lineno; |
| 32943 | |
| 32944 | if( sqliterc == NULL ){ |
| 32945 | sqliterc = zBuf = find_xdg_config(); |
| 32946 | } |
| 32947 | if( sqliterc == NULL ){ |
| 32948 | home_dir = find_home_dir(0); |
| 32949 | if( home_dir==0 ){ |
| 32950 | eputz("-- warning: cannot find home directory;" |
| 32951 | " cannot read ~/.sqliterc\n"); |
| 32952 | return; |
| 32953 | } |
| 32954 | zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); |
| 32955 | shell_check_oom(zBuf); |
| 32956 | sqliterc = zBuf; |
| 32957 | } |
| 32958 | p->in = sqlite3_fopen(sqliterc,"rb"); |
| 32959 | if( p->in ){ |
| 32960 | if( stdin_is_interactive ){ |
| 32961 | sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc); |
| 32962 | } |
| 32963 | if( process_input(p) && bail_on_error ) exit(1); |
| @@ -32966,11 +32786,13 @@ | |
| 32966 | sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc); |
| 32967 | if( bail_on_error ) exit(1); |
| 32968 | } |
| 32969 | p->in = inSaved; |
| 32970 | p->lineno = savedLineno; |
| 32971 | sqlite3_free(zBuf); |
| 32972 | } |
| 32973 | |
| 32974 | /* |
| 32975 | ** Show available command line options |
| 32976 | */ |
| @@ -32997,10 +32819,11 @@ | |
| 32997 | #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) |
| 32998 | " -heap SIZE Size of heap for memsys3 or memsys5\n" |
| 32999 | #endif |
| 33000 | " -help show this message\n" |
| 33001 | " -html set output mode to HTML\n" |
| 33002 | " -interactive force interactive I/O\n" |
| 33003 | " -json set output mode to 'json'\n" |
| 33004 | " -line set output mode to 'line'\n" |
| 33005 | " -list set output mode to 'list'\n" |
| 33006 | " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" |
| @@ -33341,16 +33164,24 @@ | |
| 33341 | (void)cmdline_option_value(argc, argv, ++i); |
| 33342 | #endif |
| 33343 | }else if( cli_strcmp(z,"-pagecache")==0 ){ |
| 33344 | sqlite3_int64 n, sz; |
| 33345 | sz = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33346 | if( sz>70000 ) sz = 70000; |
| 33347 | if( sz<0 ) sz = 0; |
| 33348 | n = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33349 | if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){ |
| 33350 | n = 0xffffffffffffLL/sz; |
| 33351 | } |
| 33352 | verify_uninitialized(); |
| 33353 | sqlite3_config(SQLITE_CONFIG_PAGECACHE, |
| 33354 | (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n); |
| 33355 | data.shellFlgs |= SHFLG_Pagecache; |
| 33356 | }else if( cli_strcmp(z,"-lookaside")==0 ){ |
| @@ -33359,11 +33190,11 @@ | |
| 33359 | if( sz<0 ) sz = 0; |
| 33360 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33361 | if( n<0 ) n = 0; |
| 33362 | verify_uninitialized(); |
| 33363 | sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); |
| 33364 | if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; |
| 33365 | }else if( cli_strcmp(z,"-threadsafe")==0 ){ |
| 33366 | int n; |
| 33367 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33368 | verify_uninitialized(); |
| 33369 | switch( n ){ |
| @@ -33401,13 +33232,19 @@ | |
| 33401 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33402 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33403 | data.szMax = integerValue(argv[++i]); |
| 33404 | #endif |
| 33405 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33406 | data.openMode = SHELL_OPEN_READONLY; |
| 33407 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33408 | data.openFlags = SQLITE_OPEN_NOFOLLOW; |
| 33409 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) |
| 33410 | }else if( cli_strncmp(z, "-A",2)==0 ){ |
| 33411 | /* All remaining command-line arguments are passed to the ".archive" |
| 33412 | ** command, so ignore them */ |
| 33413 | break; |
| @@ -33557,13 +33394,19 @@ | |
| 33557 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33558 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33559 | data.szMax = integerValue(argv[++i]); |
| 33560 | #endif |
| 33561 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33562 | data.openMode = SHELL_OPEN_READONLY; |
| 33563 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33564 | data.openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 33565 | }else if( cli_strcmp(z,"-ascii")==0 ){ |
| 33566 | data.mode = MODE_Ascii; |
| 33567 | sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); |
| 33568 | sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); |
| 33569 | }else if( cli_strcmp(z,"-tabs")==0 ){ |
| @@ -33734,11 +33577,10 @@ | |
| 33734 | /* Run commands received from standard input |
| 33735 | */ |
| 33736 | if( stdin_is_interactive ){ |
| 33737 | char *zHome; |
| 33738 | char *zHistory; |
| 33739 | int nHistory; |
| 33740 | sqlite3_fprintf(stdout, |
| 33741 | "SQLite version %s %.19s\n" /*extra-version-info*/ |
| 33742 | "Enter \".help\" for usage hints.\n", |
| 33743 | sqlite3_libversion(), sqlite3_sourceid()); |
| 33744 | if( warnInmemoryDb ){ |
| @@ -33747,15 +33589,19 @@ | |
| 33747 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33748 | " persistent database.\n"); |
| 33749 | } |
| 33750 | zHistory = getenv("SQLITE_HISTORY"); |
| 33751 | if( zHistory ){ |
| 33752 | zHistory = strdup(zHistory); |
| 33753 | }else if( (zHome = find_home_dir(0))!=0 ){ |
| 33754 | nHistory = strlen30(zHome) + 20; |
| 33755 | if( (zHistory = malloc(nHistory))!=0 ){ |
| 33756 | sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); |
| 33757 | } |
| 33758 | } |
| 33759 | if( zHistory ){ shell_read_history(zHistory); } |
| 33760 | #if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION) |
| 33761 | rl_attempted_completion_function = readline_completion; |
| @@ -33767,21 +33613,21 @@ | |
| 33767 | data.in = 0; |
| 33768 | rc = process_input(&data); |
| 33769 | if( zHistory ){ |
| 33770 | shell_stifle_history(2000); |
| 33771 | shell_write_history(zHistory); |
| 33772 | free(zHistory); |
| 33773 | } |
| 33774 | }else{ |
| 33775 | data.in = stdin; |
| 33776 | rc = process_input(&data); |
| 33777 | } |
| 33778 | } |
| 33779 | #ifndef SQLITE_SHELL_FIDDLE |
| 33780 | /* In WASM mode we have to leave the db state in place so that |
| 33781 | ** client code can "push" SQL into it after this call returns. */ |
| 33782 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 33783 | if( data.expert.pExpert ){ |
| 33784 | expertFinish(&data, 1, 0); |
| 33785 | } |
| 33786 | #endif |
| 33787 | shell_main_exit: |
| 33788 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -122,10 +122,13 @@ | |
| 122 | typedef sqlite3_int64 i64; |
| 123 | typedef sqlite3_uint64 u64; |
| 124 | typedef unsigned char u8; |
| 125 | #include <ctype.h> |
| 126 | #include <stdarg.h> |
| 127 | #ifndef _WIN32 |
| 128 | # include <sys/time.h> |
| 129 | #endif |
| 130 | |
| 131 | #if !defined(_WIN32) && !defined(WIN32) |
| 132 | # include <signal.h> |
| 133 | # if !defined(__RTP__) && !defined(_WRS_KERNEL) && !defined(SQLITE_WASI) |
| 134 | # include <pwd.h> |
| @@ -646,12 +649,23 @@ | |
| 649 | if( a==0 ) a = ""; |
| 650 | if( b==0 ) b = ""; |
| 651 | return strncmp(a,b,n); |
| 652 | } |
| 653 | |
| 654 | /* Return the current wall-clock time in microseconds since the |
| 655 | ** Unix epoch (1970-01-01T00:00:00Z) |
| 656 | */ |
| 657 | static sqlite3_int64 timeOfDay(void){ |
| 658 | #if defined(_WIN64) |
| 659 | sqlite3_uint64 t; |
| 660 | FILETIME tm; |
| 661 | GetSystemTimePreciseAsFileTime(&tm); |
| 662 | t = ((u64)tm.dwHighDateTime<<32) | (u64)tm.dwLowDateTime; |
| 663 | t += 116444736000000000LL; |
| 664 | t /= 10; |
| 665 | return t; |
| 666 | #elif defined(_WIN32) |
| 667 | static sqlite3_vfs *clockVfs = 0; |
| 668 | sqlite3_int64 t; |
| 669 | if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); |
| 670 | if( clockVfs==0 ) return 0; /* Never actually happens */ |
| 671 | if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ |
| @@ -659,11 +673,16 @@ | |
| 673 | }else{ |
| 674 | double r; |
| 675 | clockVfs->xCurrentTime(clockVfs, &r); |
| 676 | t = (sqlite3_int64)(r*86400000.0); |
| 677 | } |
| 678 | return t*1000; |
| 679 | #else |
| 680 | struct timeval sNow; |
| 681 | (void)gettimeofday(&sNow,0); |
| 682 | return ((i64)sNow.tv_sec)*1000000 + sNow.tv_usec; |
| 683 | #endif |
| 684 | } |
| 685 | |
| 686 | #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) |
| 687 | #include <sys/time.h> |
| 688 | #include <sys/resource.h> |
| @@ -704,12 +723,12 @@ | |
| 723 | static void endTimer(FILE *out){ |
| 724 | if( enableTimer ){ |
| 725 | sqlite3_int64 iEnd = timeOfDay(); |
| 726 | struct rusage sEnd; |
| 727 | getrusage(RUSAGE_SELF, &sEnd); |
| 728 | sqlite3_fprintf(out, "Run Time: real %.6f user %.6f sys %.6f\n", |
| 729 | (iEnd - iBegin)*0.000001, |
| 730 | timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), |
| 731 | timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); |
| 732 | } |
| 733 | } |
| 734 | |
| @@ -783,14 +802,23 @@ | |
| 802 | static void endTimer(FILE *out){ |
| 803 | if( enableTimer && getProcessTimesAddr){ |
| 804 | FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; |
| 805 | sqlite3_int64 ftWallEnd = timeOfDay(); |
| 806 | getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); |
| 807 | #ifdef _WIN64 |
| 808 | /* microsecond precision on 64-bit windows */ |
| 809 | sqlite3_fprintf(out, "Run Time: real %.6f user %f sys %f\n", |
| 810 | (ftWallEnd - ftWallBegin)*0.000001, |
| 811 | timeDiff(&ftUserBegin, &ftUserEnd), |
| 812 | timeDiff(&ftKernelBegin, &ftKernelEnd)); |
| 813 | #else |
| 814 | /* millisecond precisino on 32-bit windows */ |
| 815 | sqlite3_fprintf(out, "Run Time: real %.3f user %.3f sys %.3f\n", |
| 816 | (ftWallEnd - ftWallBegin)*0.000001, |
| 817 | timeDiff(&ftUserBegin, &ftUserEnd), |
| 818 | timeDiff(&ftKernelBegin, &ftKernelEnd)); |
| 819 | #endif |
| 820 | } |
| 821 | } |
| 822 | |
| 823 | #define BEGIN_TIMER beginTimer() |
| 824 | #define END_TIMER(X) endTimer(X) |
| @@ -1126,11 +1154,11 @@ | |
| 1154 | } |
| 1155 | if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 |
| 1156 | && (z[3] & 0xc0)==0x80 |
| 1157 | ){ |
| 1158 | *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6 |
| 1159 | | (z[3] & 0x3f); |
| 1160 | return 4; |
| 1161 | } |
| 1162 | *pU = 0; |
| 1163 | return 1; |
| 1164 | } |
| @@ -1189,18 +1217,28 @@ | |
| 1217 | ** since with %*.*s the width is measured in bytes, not characters. |
| 1218 | ** |
| 1219 | ** Take into account zero-width and double-width Unicode characters. |
| 1220 | ** In other words, a zero-width character does not count toward the |
| 1221 | ** the w limit. A double-width character counts as two. |
| 1222 | ** |
| 1223 | ** w should normally be a small number. A couple hundred at most. This |
| 1224 | ** routine caps w at 100 million to avoid integer overflow issues. |
| 1225 | */ |
| 1226 | static void utf8_width_print(FILE *out, int w, const char *zUtf){ |
| 1227 | const unsigned char *a = (const unsigned char*)zUtf; |
| 1228 | static const int mxW = 10000000; |
| 1229 | unsigned char c; |
| 1230 | int i = 0; |
| 1231 | int n = 0; |
| 1232 | int k; |
| 1233 | int aw; |
| 1234 | if( w<-mxW ){ |
| 1235 | w = -mxW; |
| 1236 | }else if( w>mxW ){ |
| 1237 | w= mxW; |
| 1238 | } |
| 1239 | aw = w<0 ? -w : w; |
| 1240 | if( zUtf==0 ) zUtf = ""; |
| 1241 | while( (c = a[i])!=0 ){ |
| 1242 | if( (c&0xc0)==0xc0 ){ |
| 1243 | int u; |
| 1244 | int len = decodeUtf8(a+i, &u); |
| @@ -1323,11 +1361,11 @@ | |
| 1361 | |
| 1362 | /* |
| 1363 | ** This routine reads a line of text from FILE in, stores |
| 1364 | ** the text in memory obtained from malloc() and returns a pointer |
| 1365 | ** to the text. NULL is returned at end of file, or if malloc() |
| 1366 | ** fails, or if the length of the line is longer than about a gigabyte. |
| 1367 | ** |
| 1368 | ** If zLine is not NULL then it is a malloced buffer returned from |
| 1369 | ** a previous call to this routine that may be reused. |
| 1370 | */ |
| 1371 | static char *local_getline(char *zLine, FILE *in){ |
| @@ -1334,10 +1372,14 @@ | |
| 1372 | int nLine = zLine==0 ? 0 : 100; |
| 1373 | int n = 0; |
| 1374 | |
| 1375 | while( 1 ){ |
| 1376 | if( n+100>nLine ){ |
| 1377 | if( nLine>=1073741773 ){ |
| 1378 | free(zLine); |
| 1379 | return 0; |
| 1380 | } |
| 1381 | nLine = nLine*2 + 100; |
| 1382 | zLine = realloc(zLine, nLine); |
| 1383 | shell_check_oom(zLine); |
| 1384 | } |
| 1385 | if( sqlite3_fgets(&zLine[n], nLine - n, in)==0 ){ |
| @@ -1417,14 +1459,18 @@ | |
| 1459 | return -1; |
| 1460 | } |
| 1461 | |
| 1462 | /* |
| 1463 | ** Interpret zArg as an integer value, possibly with suffixes. |
| 1464 | ** |
| 1465 | ** If the value specified by zArg is outside the range of values that |
| 1466 | ** can be represented using a 64-bit twos-complement integer, then return |
| 1467 | ** the nearest representable value. |
| 1468 | */ |
| 1469 | static sqlite3_int64 integerValue(const char *zArg){ |
| 1470 | sqlite3_uint64 v = 0; |
| 1471 | static const struct { char *zSuffix; unsigned int iMult; } aMult[] = { |
| 1472 | { "KiB", 1024 }, |
| 1473 | { "MiB", 1024*1024 }, |
| 1474 | { "GiB", 1024*1024*1024 }, |
| 1475 | { "KB", 1000 }, |
| 1476 | { "MB", 1000000 }, |
| @@ -1443,46 +1489,54 @@ | |
| 1489 | } |
| 1490 | if( zArg[0]=='0' && zArg[1]=='x' ){ |
| 1491 | int x; |
| 1492 | zArg += 2; |
| 1493 | while( (x = hexDigitValue(zArg[0]))>=0 ){ |
| 1494 | if( v > 0x0fffffffffffffffULL ) goto integer_overflow; |
| 1495 | v = (v<<4) + x; |
| 1496 | zArg++; |
| 1497 | } |
| 1498 | }else{ |
| 1499 | while( IsDigit(zArg[0]) ){ |
| 1500 | if( v>=922337203685477580LL ){ |
| 1501 | if( v>922337203685477580LL || zArg[0]>='8' ) goto integer_overflow; |
| 1502 | } |
| 1503 | v = v*10 + (zArg[0] - '0'); |
| 1504 | zArg++; |
| 1505 | } |
| 1506 | } |
| 1507 | for(i=0; i<ArraySize(aMult); i++){ |
| 1508 | if( sqlite3_stricmp(aMult[i].zSuffix, zArg)==0 ){ |
| 1509 | if( 0x7fffffffffffffffULL/aMult[i].iMult < v ) goto integer_overflow; |
| 1510 | v *= aMult[i].iMult; |
| 1511 | break; |
| 1512 | } |
| 1513 | } |
| 1514 | if( isNeg && v>0x7fffffffffffffffULL ) goto integer_overflow; |
| 1515 | return isNeg? -(sqlite3_int64)v : (sqlite3_int64)v; |
| 1516 | integer_overflow: |
| 1517 | return isNeg ? (i64)0x8000000000000000LL : 0x7fffffffffffffffLL; |
| 1518 | } |
| 1519 | |
| 1520 | /* |
| 1521 | ** A variable length string to which one can append text. |
| 1522 | */ |
| 1523 | typedef struct ShellText ShellText; |
| 1524 | struct ShellText { |
| 1525 | char *zTxt; /* The text */ |
| 1526 | i64 n; /* Number of bytes of zTxt[] actually used */ |
| 1527 | i64 nAlloc; /* Number of bytes allocated for zTxt[] */ |
| 1528 | }; |
| 1529 | |
| 1530 | /* |
| 1531 | ** Initialize and destroy a ShellText object |
| 1532 | */ |
| 1533 | static void initText(ShellText *p){ |
| 1534 | memset(p, 0, sizeof(*p)); |
| 1535 | } |
| 1536 | static void freeText(ShellText *p){ |
| 1537 | sqlite3_free(p->zTxt); |
| 1538 | initText(p); |
| 1539 | } |
| 1540 | |
| 1541 | /* zIn is either a pointer to a NULL-terminated string in memory obtained |
| 1542 | ** from malloc(), or a NULL pointer. The string pointed to by zAppend is |
| @@ -1503,30 +1557,30 @@ | |
| 1557 | for(i=0; i<nAppend; i++){ |
| 1558 | if( zAppend[i]==quote ) len++; |
| 1559 | } |
| 1560 | } |
| 1561 | |
| 1562 | if( p->zTxt==0 || p->n+len>=p->nAlloc ){ |
| 1563 | p->nAlloc = p->nAlloc*2 + len + 20; |
| 1564 | p->zTxt = sqlite3_realloc64(p->zTxt, p->nAlloc); |
| 1565 | shell_check_oom(p->zTxt); |
| 1566 | } |
| 1567 | |
| 1568 | if( quote ){ |
| 1569 | char *zCsr = p->zTxt+p->n; |
| 1570 | *zCsr++ = quote; |
| 1571 | for(i=0; i<nAppend; i++){ |
| 1572 | *zCsr++ = zAppend[i]; |
| 1573 | if( zAppend[i]==quote ) *zCsr++ = quote; |
| 1574 | } |
| 1575 | *zCsr++ = quote; |
| 1576 | p->n = (i64)(zCsr - p->zTxt); |
| 1577 | *zCsr = '\0'; |
| 1578 | }else{ |
| 1579 | memcpy(p->zTxt+p->n, zAppend, nAppend); |
| 1580 | p->n += nAppend; |
| 1581 | p->zTxt[p->n] = '\0'; |
| 1582 | } |
| 1583 | } |
| 1584 | |
| 1585 | /* |
| 1586 | ** Attempt to determine if identifier zName needs to be quoted, either |
| @@ -1547,10 +1601,13 @@ | |
| 1601 | } |
| 1602 | |
| 1603 | /* |
| 1604 | ** Construct a fake object name and column list to describe the structure |
| 1605 | ** of the view, virtual table, or table valued function zSchema.zName. |
| 1606 | ** |
| 1607 | ** The returned string comes from sqlite3_mprintf() and should be freed |
| 1608 | ** by the caller using sqlite3_free(). |
| 1609 | */ |
| 1610 | static char *shellFakeSchema( |
| 1611 | sqlite3 *db, /* The database connection containing the vtab */ |
| 1612 | const char *zSchema, /* Schema of the database holding the vtab */ |
| 1613 | const char *zName /* The name of the virtual table */ |
| @@ -1587,13 +1644,13 @@ | |
| 1644 | } |
| 1645 | appendText(&s, ")", 0); |
| 1646 | sqlite3_finalize(pStmt); |
| 1647 | if( nRow==0 ){ |
| 1648 | freeText(&s); |
| 1649 | s.zTxt = 0; |
| 1650 | } |
| 1651 | return s.zTxt; |
| 1652 | } |
| 1653 | |
| 1654 | /* |
| 1655 | ** SQL function: strtod(X) |
| 1656 | ** |
| @@ -1692,11 +1749,11 @@ | |
| 1749 | if( z==0 ){ |
| 1750 | z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); |
| 1751 | }else{ |
| 1752 | z = sqlite3_mprintf("%z\n/* %s */", z, zFake); |
| 1753 | } |
| 1754 | sqlite3_free(zFake); |
| 1755 | } |
| 1756 | if( z ){ |
| 1757 | sqlite3_result_text(pCtx, z, -1, sqlite3_free); |
| 1758 | return; |
| 1759 | } |
| @@ -3667,11 +3724,12 @@ | |
| 3724 | iExp -= p->nFrac; |
| 3725 | p->nFrac = 0; |
| 3726 | } |
| 3727 | } |
| 3728 | if( iExp>0 ){ |
| 3729 | p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit |
| 3730 | + (sqlite3_int64)iExp + 1 ); |
| 3731 | if( p->a==0 ) goto new_from_text_failed; |
| 3732 | memset(p->a+p->nDigit, 0, iExp); |
| 3733 | p->nDigit += iExp; |
| 3734 | } |
| 3735 | }else if( iExp<0 ){ |
| @@ -3686,18 +3744,23 @@ | |
| 3744 | iExp -= nExtra; |
| 3745 | p->nFrac = p->nDigit - 1; |
| 3746 | } |
| 3747 | } |
| 3748 | if( iExp>0 ){ |
| 3749 | p->a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit |
| 3750 | + (sqlite3_int64)iExp + 1 ); |
| 3751 | if( p->a==0 ) goto new_from_text_failed; |
| 3752 | memmove(p->a+iExp, p->a, p->nDigit); |
| 3753 | memset(p->a, 0, iExp); |
| 3754 | p->nDigit += iExp; |
| 3755 | p->nFrac += iExp; |
| 3756 | } |
| 3757 | } |
| 3758 | if( p->sign ){ |
| 3759 | for(i=0; i<p->nDigit && p->a[i]==0; i++){} |
| 3760 | if( i>=p->nDigit ) p->sign = 0; |
| 3761 | } |
| 3762 | return p; |
| 3763 | |
| 3764 | new_from_text_failed: |
| 3765 | if( p ){ |
| 3766 | if( p->a ) sqlite3_free(p->a); |
| @@ -3786,11 +3849,11 @@ | |
| 3849 | } |
| 3850 | if( p->isNull ){ |
| 3851 | sqlite3_result_null(pCtx); |
| 3852 | return; |
| 3853 | } |
| 3854 | z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+4 ); |
| 3855 | if( z==0 ){ |
| 3856 | sqlite3_result_error_nomem(pCtx); |
| 3857 | return; |
| 3858 | } |
| 3859 | i = 0; |
| @@ -3851,11 +3914,11 @@ | |
| 3914 | } |
| 3915 | for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} |
| 3916 | for(nZero=0; nZero<nDigit && p->a[nZero]==0; nZero++){} |
| 3917 | nFrac = p->nFrac + (nDigit - p->nDigit); |
| 3918 | nDigit -= nZero; |
| 3919 | z = sqlite3_malloc64( (sqlite3_int64)nDigit+20 ); |
| 3920 | if( z==0 ){ |
| 3921 | sqlite3_result_error_nomem(pCtx); |
| 3922 | return; |
| 3923 | } |
| 3924 | if( nDigit==0 ){ |
| @@ -3896,17 +3959,25 @@ | |
| 3959 | ** pA!=0 |
| 3960 | ** pA->isNull==0 |
| 3961 | ** pB!=0 |
| 3962 | ** pB->isNull==0 |
| 3963 | */ |
| 3964 | static int decimal_cmp(Decimal *pA, Decimal *pB){ |
| 3965 | int nASig, nBSig, rc, n; |
| 3966 | while( pA->nFrac>0 && pA->a[pA->nDigit-1]==0 ){ |
| 3967 | pA->nDigit--; |
| 3968 | pA->nFrac--; |
| 3969 | } |
| 3970 | while( pB->nFrac>0 && pB->a[pB->nDigit-1]==0 ){ |
| 3971 | pB->nDigit--; |
| 3972 | pB->nFrac--; |
| 3973 | } |
| 3974 | if( pA->sign!=pB->sign ){ |
| 3975 | return pA->sign ? -1 : +1; |
| 3976 | } |
| 3977 | if( pA->sign ){ |
| 3978 | Decimal *pTemp = pA; |
| 3979 | pA = pB; |
| 3980 | pB = pTemp; |
| 3981 | } |
| 3982 | nASig = pA->nDigit - pA->nFrac; |
| 3983 | nBSig = pB->nDigit - pB->nFrac; |
| @@ -4064,11 +4135,12 @@ | |
| 4135 | if( pA==0 || pA->oom || pA->isNull |
| 4136 | || pB==0 || pB->oom || pB->isNull |
| 4137 | ){ |
| 4138 | goto mul_end; |
| 4139 | } |
| 4140 | acc = sqlite3_malloc64( (sqlite3_int64)pA->nDigit + |
| 4141 | (sqlite3_int64)pB->nDigit + 2 ); |
| 4142 | if( acc==0 ){ |
| 4143 | pA->oom = 1; |
| 4144 | goto mul_end; |
| 4145 | } |
| 4146 | memset(acc, 0, pA->nDigit + pB->nDigit + 2); |
| @@ -4151,11 +4223,11 @@ | |
| 4223 | r = -r; |
| 4224 | }else{ |
| 4225 | isNeg = 0; |
| 4226 | } |
| 4227 | memcpy(&a,&r,sizeof(a)); |
| 4228 | if( a==0 || a==(sqlite3_int64)0x8000000000000000LL){ |
| 4229 | e = 0; |
| 4230 | m = 0; |
| 4231 | }else{ |
| 4232 | e = a>>52; |
| 4233 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| @@ -4426,516 +4498,10 @@ | |
| 4498 | } |
| 4499 | return rc; |
| 4500 | } |
| 4501 | |
| 4502 | /************************* End ../ext/misc/decimal.c ********************/ |
| 4503 | #undef sqlite3_base_init |
| 4504 | #define sqlite3_base_init sqlite3_base64_init |
| 4505 | /************************* Begin ../ext/misc/base64.c ******************/ |
| 4506 | /* |
| 4507 | ** 2022-11-18 |
| @@ -5144,20 +4710,22 @@ | |
| 4710 | return pOut; |
| 4711 | } |
| 4712 | |
| 4713 | /* This function does the work for the SQLite base64(x) UDF. */ |
| 4714 | static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 4715 | sqlite3_int64 nb; |
| 4716 | sqlite3_int64 nv = sqlite3_value_bytes(av[0]); |
| 4717 | sqlite3_int64 nc; |
| 4718 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 4719 | SQLITE_LIMIT_LENGTH, -1); |
| 4720 | char *cBuf; |
| 4721 | u8 *bBuf; |
| 4722 | assert(na==1); |
| 4723 | switch( sqlite3_value_type(av[0]) ){ |
| 4724 | case SQLITE_BLOB: |
| 4725 | nb = nv; |
| 4726 | nc = 4*((nv+2)/3); /* quads needed */ |
| 4727 | nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ |
| 4728 | if( nvMax < nc ){ |
| 4729 | sqlite3_result_error(context, "blob expanded to base64 too big", -1); |
| 4730 | return; |
| 4731 | } |
| @@ -5524,11 +5092,11 @@ | |
| 5092 | } |
| 5093 | # endif |
| 5094 | |
| 5095 | /* This function does the work for the SQLite base85(x) UDF. */ |
| 5096 | static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ |
| 5097 | sqlite3_int64 nb, nc, nv = sqlite3_value_bytes(av[0]); |
| 5098 | int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), |
| 5099 | SQLITE_LIMIT_LENGTH, -1); |
| 5100 | char *cBuf; |
| 5101 | u8 *bBuf; |
| 5102 | assert(na==1); |
| @@ -5829,10 +5397,13 @@ | |
| 5397 | } |
| 5398 | memcpy(&a,&r,sizeof(a)); |
| 5399 | if( a==0 ){ |
| 5400 | e = 0; |
| 5401 | m = 0; |
| 5402 | }else if( a==(sqlite3_int64)0x8000000000000000LL ){ |
| 5403 | e = -1996; |
| 5404 | m = -1; |
| 5405 | }else{ |
| 5406 | e = a>>52; |
| 5407 | m = a & ((((sqlite3_int64)1)<<52)-1); |
| 5408 | if( e==0 ){ |
| 5409 | m <<= 1; |
| @@ -6052,23 +5623,24 @@ | |
| 5623 | ** Examples: |
| 5624 | ** |
| 5625 | ** SELECT * FROM generate_series(0,100,5); |
| 5626 | ** |
| 5627 | ** The query above returns integers from 0 through 100 counting by steps |
| 5628 | ** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total |
| 5629 | ** of 21 rows. |
| 5630 | ** |
| 5631 | ** SELECT * FROM generate_series(0,100); |
| 5632 | ** |
| 5633 | ** Integers from 0 through 100 with a step size of 1. 101 rows. |
| 5634 | ** |
| 5635 | ** SELECT * FROM generate_series(20) LIMIT 10; |
| 5636 | ** |
| 5637 | ** Integers 20 through 29. 10 rows. |
| 5638 | ** |
| 5639 | ** SELECT * FROM generate_series(0,-100,-5); |
| 5640 | ** |
| 5641 | ** Integers 0 -5 -10 ... -100. 21 rows. |
| 5642 | ** |
| 5643 | ** SELECT * FROM generate_series(0,-1); |
| 5644 | ** |
| 5645 | ** Empty sequence. |
| 5646 | ** |
| @@ -6140,143 +5712,92 @@ | |
| 5712 | #include <string.h> |
| 5713 | #include <limits.h> |
| 5714 | #include <math.h> |
| 5715 | |
| 5716 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 5717 | |
| 5718 | /* series_cursor is a subclass of sqlite3_vtab_cursor which will |
| 5719 | ** serve as the underlying representation of a cursor that scans |
| 5720 | ** over rows of the result. |
| 5721 | ** |
| 5722 | ** iOBase, iOTerm, and iOStep are the original values of the |
| 5723 | ** start=, stop=, and step= constraints on the query. These are |
| 5724 | ** the values reported by the start, stop, and step columns of the |
| 5725 | ** virtual table. |
| 5726 | ** |
| 5727 | ** iBase, iTerm, iStep, and bDescp are the actual values used to generate |
| 5728 | ** the sequence. These might be different from the iOxxxx values. |
| 5729 | ** For example in |
| 5730 | ** |
| 5731 | ** SELECT value FROM generate_series(1,11,2) |
| 5732 | ** WHERE value BETWEEN 4 AND 8; |
| 5733 | ** |
| 5734 | ** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7. |
| 5735 | ** Another example: |
| 5736 | ** |
| 5737 | ** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC; |
| 5738 | ** |
| 5739 | ** The cursor initialization for the above query is: |
| 5740 | ** |
| 5741 | ** iOBase = 1 iBase = 13 |
| 5742 | ** iOTerm = 15 iTerm = 1 |
| 5743 | ** iOStep = 3 iStep = 3 bDesc = 1 |
| 5744 | ** |
| 5745 | ** The actual step size is unsigned so that can have a value of |
| 5746 | ** +9223372036854775808 which is needed for querys like this: |
| 5747 | ** |
| 5748 | ** SELECT value |
| 5749 | ** FROM generate_series(9223372036854775807, |
| 5750 | ** -9223372036854775808, |
| 5751 | ** -9223372036854775808) |
| 5752 | ** ORDER BY value ASC; |
| 5753 | ** |
| 5754 | ** The setup for the previous query will be: |
| 5755 | ** |
| 5756 | ** iOBase = 9223372036854775807 iBase = -1 |
| 5757 | ** iOTerm = -9223372036854775808 iTerm = 9223372036854775807 |
| 5758 | ** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0 |
| 5759 | */ |
| 5760 | /* typedef unsigned char u8; */ |
| 5761 | typedef struct series_cursor series_cursor; |
| 5762 | struct series_cursor { |
| 5763 | sqlite3_vtab_cursor base; /* Base class - must be first */ |
| 5764 | sqlite3_int64 iOBase; /* Original starting value ("start") */ |
| 5765 | sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ |
| 5766 | sqlite3_int64 iOStep; /* Original step value */ |
| 5767 | sqlite3_int64 iBase; /* Starting value to actually use */ |
| 5768 | sqlite3_int64 iTerm; /* Terminal value to actually use */ |
| 5769 | sqlite3_uint64 iStep; /* The step size */ |
| 5770 | sqlite3_int64 iValue; /* Current value */ |
| 5771 | u8 bDesc; /* iStep is really negative */ |
| 5772 | u8 bDone; /* True if stepped past last element */ |
| 5773 | }; |
| 5774 | |
| 5775 | /* |
| 5776 | ** Computed the difference between two 64-bit signed integers using a |
| 5777 | ** convoluted computation designed to work around the silly restriction |
| 5778 | ** against signed integer overflow in C. |
| 5779 | */ |
| 5780 | static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){ |
| 5781 | assert( a>=b ); |
| 5782 | return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b); |
| 5783 | } |
| 5784 | |
| 5785 | /* |
| 5786 | ** Add or substract an unsigned 64-bit integer from a signed 64-bit integer |
| 5787 | ** and return the new signed 64-bit integer. |
| 5788 | */ |
| 5789 | static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){ |
| 5790 | sqlite3_uint64 x = *(sqlite3_uint64*)&a; |
| 5791 | x += b; |
| 5792 | return *(sqlite3_int64*)&x; |
| 5793 | } |
| 5794 | static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){ |
| 5795 | sqlite3_uint64 x = *(sqlite3_uint64*)&a; |
| 5796 | x -= b; |
| 5797 | return *(sqlite3_int64*)&x; |
| 5798 | } |
| 5799 | |
| 5800 | /* |
| 5801 | ** The seriesConnect() method is invoked to create a new |
| 5802 | ** series_vtab that describes the generate_series virtual table. |
| 5803 | ** |
| @@ -6354,11 +5875,19 @@ | |
| 5875 | /* |
| 5876 | ** Advance a series_cursor to its next row of output. |
| 5877 | */ |
| 5878 | static int seriesNext(sqlite3_vtab_cursor *cur){ |
| 5879 | series_cursor *pCur = (series_cursor*)cur; |
| 5880 | if( pCur->iValue==pCur->iTerm ){ |
| 5881 | pCur->bDone = 1; |
| 5882 | }else if( pCur->bDesc ){ |
| 5883 | pCur->iValue = sub64(pCur->iValue, pCur->iStep); |
| 5884 | assert( pCur->iValue>=pCur->iTerm ); |
| 5885 | }else{ |
| 5886 | pCur->iValue = add64(pCur->iValue, pCur->iStep); |
| 5887 | assert( pCur->iValue<=pCur->iTerm ); |
| 5888 | } |
| 5889 | return SQLITE_OK; |
| 5890 | } |
| 5891 | |
| 5892 | /* |
| 5893 | ** Return values of columns for the row at which the series_cursor |
| @@ -6370,41 +5899,41 @@ | |
| 5899 | int i /* Which column to return */ |
| 5900 | ){ |
| 5901 | series_cursor *pCur = (series_cursor*)cur; |
| 5902 | sqlite3_int64 x = 0; |
| 5903 | switch( i ){ |
| 5904 | case SERIES_COLUMN_START: x = pCur->iOBase; break; |
| 5905 | case SERIES_COLUMN_STOP: x = pCur->iOTerm; break; |
| 5906 | case SERIES_COLUMN_STEP: x = pCur->iOStep; break; |
| 5907 | default: x = pCur->iValue; break; |
| 5908 | } |
| 5909 | sqlite3_result_int64(ctx, x); |
| 5910 | return SQLITE_OK; |
| 5911 | } |
| 5912 | |
| 5913 | #ifndef LARGEST_UINT64 |
| 5914 | #define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL) |
| 5915 | #define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL) |
| 5916 | #define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL) |
| 5917 | #endif |
| 5918 | |
| 5919 | /* |
| 5920 | ** The rowid is the same as the value. |
| 5921 | */ |
| 5922 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 5923 | series_cursor *pCur = (series_cursor*)cur; |
| 5924 | *pRowid = pCur->iValue; |
| 5925 | return SQLITE_OK; |
| 5926 | } |
| 5927 | |
| 5928 | /* |
| 5929 | ** Return TRUE if the cursor has been moved off of the last |
| 5930 | ** row of output. |
| 5931 | */ |
| 5932 | static int seriesEof(sqlite3_vtab_cursor *cur){ |
| 5933 | series_cursor *pCur = (series_cursor*)cur; |
| 5934 | return pCur->bDone; |
| 5935 | } |
| 5936 | |
| 5937 | /* True to cause run-time checking of the start=, stop=, and/or step= |
| 5938 | ** parameters. The only reason to do this is for testing the |
| 5939 | ** constraint checking logic for virtual tables in the SQLite core. |
| @@ -6411,10 +5940,63 @@ | |
| 5940 | */ |
| 5941 | #ifndef SQLITE_SERIES_CONSTRAINT_VERIFY |
| 5942 | # define SQLITE_SERIES_CONSTRAINT_VERIFY 0 |
| 5943 | #endif |
| 5944 | |
| 5945 | /* |
| 5946 | ** Return the number of steps between pCur->iBase and pCur->iTerm if |
| 5947 | ** the step width is pCur->iStep. |
| 5948 | */ |
| 5949 | static sqlite3_uint64 seriesSteps(series_cursor *pCur){ |
| 5950 | if( pCur->bDesc ){ |
| 5951 | assert( pCur->iBase >= pCur->iTerm ); |
| 5952 | return span64(pCur->iBase, pCur->iTerm)/pCur->iStep; |
| 5953 | }else{ |
| 5954 | assert( pCur->iBase <= pCur->iTerm ); |
| 5955 | return span64(pCur->iTerm, pCur->iBase)/pCur->iStep; |
| 5956 | } |
| 5957 | } |
| 5958 | |
| 5959 | #if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32) |
| 5960 | /* |
| 5961 | ** Case 1 (the most common case): |
| 5962 | ** The standard math library is available so use ceil() and floor() from there. |
| 5963 | */ |
| 5964 | static double seriesCeil(double r){ return ceil(r); } |
| 5965 | static double seriesFloor(double r){ return floor(r); } |
| 5966 | #elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) |
| 5967 | /* |
| 5968 | ** Case 2 (2nd most common): Use GCC/Clang builtins |
| 5969 | */ |
| 5970 | static double seriesCeil(double r){ return __builtin_ceil(r); } |
| 5971 | static double seriesFloor(double r){ return __builtin_floor(r); } |
| 5972 | #else |
| 5973 | /* |
| 5974 | ** Case 3 (rarely happens): Use home-grown ceil() and floor() routines. |
| 5975 | */ |
| 5976 | static double seriesCeil(double r){ |
| 5977 | sqlite3_int64 x; |
| 5978 | if( r!=r ) return r; |
| 5979 | if( r<=(-4503599627370496.0) ) return r; |
| 5980 | if( r>=(+4503599627370496.0) ) return r; |
| 5981 | x = (sqlite3_int64)r; |
| 5982 | if( r==(double)x ) return r; |
| 5983 | if( r>(double)x ) x++; |
| 5984 | return (double)x; |
| 5985 | } |
| 5986 | static double seriesFloor(double r){ |
| 5987 | sqlite3_int64 x; |
| 5988 | if( r!=r ) return r; |
| 5989 | if( r<=(-4503599627370496.0) ) return r; |
| 5990 | if( r>=(+4503599627370496.0) ) return r; |
| 5991 | x = (sqlite3_int64)r; |
| 5992 | if( r==(double)x ) return r; |
| 5993 | if( r<(double)x ) x--; |
| 5994 | return (double)x; |
| 5995 | } |
| 5996 | #endif |
| 5997 | |
| 5998 | /* |
| 5999 | ** This method is called to "rewind" the series_cursor object back |
| 6000 | ** to the first row of output. This method is always called at least |
| 6001 | ** once prior to any call to seriesColumn() or seriesRowid() or |
| 6002 | ** seriesEof(). |
| @@ -6444,185 +6026,243 @@ | |
| 6026 | sqlite3_vtab_cursor *pVtabCursor, |
| 6027 | int idxNum, const char *idxStrUnused, |
| 6028 | int argc, sqlite3_value **argv |
| 6029 | ){ |
| 6030 | series_cursor *pCur = (series_cursor *)pVtabCursor; |
| 6031 | int iArg = 0; /* Arguments used so far */ |
| 6032 | int i; /* Loop counter */ |
| 6033 | sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */ |
| 6034 | sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */ |
| 6035 | sqlite3_int64 iLimit = 0; /* if >0, the value of the LIMIT */ |
| 6036 | sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */ |
| 6037 | |
| 6038 | (void)idxStrUnused; |
| 6039 | |
| 6040 | /* If any constraints have a NULL value, then return no rows. |
| 6041 | ** See ticket https://sqlite.org/src/info/fac496b61722daf2 |
| 6042 | */ |
| 6043 | for(i=0; i<argc; i++){ |
| 6044 | if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ |
| 6045 | goto series_no_rows; |
| 6046 | } |
| 6047 | } |
| 6048 | |
| 6049 | /* Capture the three HIDDEN parameters to the virtual table and insert |
| 6050 | ** default values for any parameters that are omitted. |
| 6051 | */ |
| 6052 | if( idxNum & 0x01 ){ |
| 6053 | pCur->iOBase = sqlite3_value_int64(argv[iArg++]); |
| 6054 | }else{ |
| 6055 | pCur->iOBase = 0; |
| 6056 | } |
| 6057 | if( idxNum & 0x02 ){ |
| 6058 | pCur->iOTerm = sqlite3_value_int64(argv[iArg++]); |
| 6059 | }else{ |
| 6060 | pCur->iOTerm = 0xffffffff; |
| 6061 | } |
| 6062 | if( idxNum & 0x04 ){ |
| 6063 | pCur->iOStep = sqlite3_value_int64(argv[iArg++]); |
| 6064 | if( pCur->iOStep==0 ) pCur->iOStep = 1; |
| 6065 | }else{ |
| 6066 | pCur->iOStep = 1; |
| 6067 | } |
| 6068 | |
| 6069 | /* If there are constraints on the value column but there are |
| 6070 | ** no constraints on the start, stop, and step columns, then |
| 6071 | ** initialize the default range to be the entire range of 64-bit signed |
| 6072 | ** integers. This range will contracted by the value column constraints |
| 6073 | ** further below. |
| 6074 | */ |
| 6075 | if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){ |
| 6076 | pCur->iOBase = SMALLEST_INT64; |
| 6077 | } |
| 6078 | if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){ |
| 6079 | pCur->iOTerm = LARGEST_INT64; |
| 6080 | } |
| 6081 | pCur->iBase = pCur->iOBase; |
| 6082 | pCur->iTerm = pCur->iOTerm; |
| 6083 | if( pCur->iOStep>0 ){ |
| 6084 | pCur->iStep = pCur->iOStep; |
| 6085 | }else if( pCur->iOStep>SMALLEST_INT64 ){ |
| 6086 | pCur->iStep = -pCur->iOStep; |
| 6087 | }else{ |
| 6088 | pCur->iStep = LARGEST_INT64; |
| 6089 | pCur->iStep++; |
| 6090 | } |
| 6091 | pCur->bDesc = pCur->iOStep<0; |
| 6092 | if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){ |
| 6093 | goto series_no_rows; |
| 6094 | } |
| 6095 | if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){ |
| 6096 | goto series_no_rows; |
| 6097 | } |
| 6098 | |
| 6099 | /* Extract the LIMIT and OFFSET values, but do not apply them yet. |
| 6100 | ** The range must first be constrained by the limits on value. |
| 6101 | */ |
| 6102 | if( idxNum & 0x20 ){ |
| 6103 | iLimit = sqlite3_value_int64(argv[iArg++]); |
| 6104 | if( idxNum & 0x40 ){ |
| 6105 | iOffset = sqlite3_value_int64(argv[iArg++]); |
| 6106 | } |
| 6107 | } |
| 6108 | |
| 6109 | /* Narrow the range of iMin and iMax (the minimum and maximum outputs) |
| 6110 | ** based on equality and inequality constraints on the "value" column. |
| 6111 | */ |
| 6112 | if( idxNum & 0x3380 ){ |
| 6113 | if( idxNum & 0x0080 ){ /* value=X */ |
| 6114 | if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| 6115 | double r = sqlite3_value_double(argv[iArg++]); |
| 6116 | if( r==seriesCeil(r) |
| 6117 | && r>=(double)SMALLEST_INT64 |
| 6118 | && r<=(double)LARGEST_INT64 |
| 6119 | ){ |
| 6120 | iMin = iMax = (sqlite3_int64)r; |
| 6121 | }else{ |
| 6122 | goto series_no_rows; |
| 6123 | } |
| 6124 | }else{ |
| 6125 | iMin = iMax = sqlite3_value_int64(argv[iArg++]); |
| 6126 | } |
| 6127 | }else{ |
| 6128 | if( idxNum & 0x0300 ){ /* value>X or value>=X */ |
| 6129 | if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| 6130 | double r = sqlite3_value_double(argv[iArg++]); |
| 6131 | if( r<(double)SMALLEST_INT64 ){ |
| 6132 | iMin = SMALLEST_INT64; |
| 6133 | }else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){ |
| 6134 | iMin = (sqlite3_int64)seriesCeil(r+1.0); |
| 6135 | }else{ |
| 6136 | iMin = (sqlite3_int64)seriesCeil(r); |
| 6137 | } |
| 6138 | }else{ |
| 6139 | iMin = sqlite3_value_int64(argv[iArg++]); |
| 6140 | if( (idxNum & 0x0200)!=0 ){ |
| 6141 | if( iMin==LARGEST_INT64 ){ |
| 6142 | goto series_no_rows; |
| 6143 | }else{ |
| 6144 | iMin++; |
| 6145 | } |
| 6146 | } |
| 6147 | } |
| 6148 | } |
| 6149 | if( idxNum & 0x3000 ){ /* value<X or value<=X */ |
| 6150 | if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| 6151 | double r = sqlite3_value_double(argv[iArg++]); |
| 6152 | if( r>(double)LARGEST_INT64 ){ |
| 6153 | iMax = LARGEST_INT64; |
| 6154 | }else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){ |
| 6155 | iMax = (sqlite3_int64)(r-1.0); |
| 6156 | }else{ |
| 6157 | iMax = (sqlite3_int64)seriesFloor(r); |
| 6158 | } |
| 6159 | }else{ |
| 6160 | iMax = sqlite3_value_int64(argv[iArg++]); |
| 6161 | if( idxNum & 0x2000 ){ |
| 6162 | if( iMax==SMALLEST_INT64 ){ |
| 6163 | goto series_no_rows; |
| 6164 | }else{ |
| 6165 | iMax--; |
| 6166 | } |
| 6167 | } |
| 6168 | } |
| 6169 | } |
| 6170 | if( iMin>iMax ){ |
| 6171 | goto series_no_rows; |
| 6172 | } |
| 6173 | } |
| 6174 | |
| 6175 | /* Try to reduce the range of values to be generated based on |
| 6176 | ** constraints on the "value" column. |
| 6177 | */ |
| 6178 | if( pCur->bDesc==0 ){ |
| 6179 | if( pCur->iBase<iMin ){ |
| 6180 | sqlite3_uint64 span = span64(iMin,pCur->iBase); |
| 6181 | pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); |
| 6182 | if( pCur->iBase<iMin ){ |
| 6183 | if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){ |
| 6184 | goto series_no_rows; |
| 6185 | } |
| 6186 | pCur->iBase = add64(pCur->iBase, pCur->iStep); |
| 6187 | } |
| 6188 | } |
| 6189 | if( pCur->iTerm>iMax ){ |
| 6190 | pCur->iTerm = iMax; |
| 6191 | } |
| 6192 | }else{ |
| 6193 | if( pCur->iBase>iMax ){ |
| 6194 | sqlite3_uint64 span = span64(pCur->iBase,iMax); |
| 6195 | pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); |
| 6196 | if( pCur->iBase>iMax ){ |
| 6197 | if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){ |
| 6198 | goto series_no_rows; |
| 6199 | } |
| 6200 | pCur->iBase = sub64(pCur->iBase, pCur->iStep); |
| 6201 | } |
| 6202 | } |
| 6203 | if( pCur->iTerm<iMin ){ |
| 6204 | pCur->iTerm = iMin; |
| 6205 | } |
| 6206 | } |
| 6207 | } |
| 6208 | |
| 6209 | /* Adjust iTerm so that it is exactly the last value of the series. |
| 6210 | */ |
| 6211 | if( pCur->bDesc==0 ){ |
| 6212 | if( pCur->iBase>pCur->iTerm ){ |
| 6213 | goto series_no_rows; |
| 6214 | } |
| 6215 | pCur->iTerm = sub64(pCur->iTerm, |
| 6216 | span64(pCur->iTerm,pCur->iBase) % pCur->iStep); |
| 6217 | }else{ |
| 6218 | if( pCur->iBase<pCur->iTerm ){ |
| 6219 | goto series_no_rows; |
| 6220 | } |
| 6221 | pCur->iTerm = add64(pCur->iTerm, |
| 6222 | span64(pCur->iBase,pCur->iTerm) % pCur->iStep); |
| 6223 | } |
| 6224 | |
| 6225 | /* Transform the series generator to output values in the requested |
| 6226 | ** order. |
| 6227 | */ |
| 6228 | if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0) |
| 6229 | || ((idxNum & 0x0010)!=0 && pCur->bDesc!=0) |
| 6230 | ){ |
| 6231 | sqlite3_int64 tmp = pCur->iBase; |
| 6232 | pCur->iBase = pCur->iTerm; |
| 6233 | pCur->iTerm = tmp; |
| 6234 | pCur->bDesc = !pCur->bDesc; |
| 6235 | } |
| 6236 | |
| 6237 | /* Apply LIMIT and OFFSET constraints, if any */ |
| 6238 | assert( pCur->iStep!=0 ); |
| 6239 | if( idxNum & 0x20 ){ |
| 6240 | if( iOffset>0 ){ |
| 6241 | if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){ |
| 6242 | goto series_no_rows; |
| 6243 | }else if( pCur->bDesc ){ |
| 6244 | pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset); |
| 6245 | }else{ |
| 6246 | pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset); |
| 6247 | } |
| 6248 | } |
| 6249 | if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){ |
| 6250 | pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep); |
| 6251 | } |
| 6252 | } |
| 6253 | pCur->iValue = pCur->iBase; |
| 6254 | pCur->bDone = 0; |
| 6255 | return SQLITE_OK; |
| 6256 | |
| 6257 | series_no_rows: |
| 6258 | pCur->iBase = 0; |
| 6259 | pCur->iTerm = 0; |
| 6260 | pCur->iStep = 1; |
| 6261 | pCur->bDesc = 0; |
| 6262 | pCur->bDone = 1; |
| 6263 | return SQLITE_OK; |
| 6264 | } |
| 6265 | |
| 6266 | /* |
| 6267 | ** SQLite will invoke this method one or more times while planning a query |
| 6268 | ** that uses the generate_series virtual table. This routine needs to create |
| @@ -6948,10 +6588,12 @@ | |
| 6588 | ** expression and M is the size of the input string. The matcher never |
| 6589 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 6590 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 6591 | ** regular expression in the O(N*M) performance bound is computed after |
| 6592 | ** this expansion. |
| 6593 | ** |
| 6594 | ** To help prevent DoS attacks, the maximum size of the NFA is restricted. |
| 6595 | */ |
| 6596 | #include <string.h> |
| 6597 | #include <stdlib.h> |
| 6598 | /* #include "sqlite3ext.h" */ |
| 6599 | SQLITE_EXTENSION_INIT1 |
| @@ -6988,36 +6630,10 @@ | |
| 6630 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 6631 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 6632 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 6633 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 6634 | #define RE_OP_ATSTART 18 /* Currently at the start of the string */ |
| 6635 | |
| 6636 | /* Each opcode is a "state" in the NFA */ |
| 6637 | typedef unsigned short ReStateNumber; |
| 6638 | |
| 6639 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -7051,10 +6667,11 @@ | |
| 6667 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 6668 | unsigned char zInit[12]; /* Initial text to match */ |
| 6669 | int nInit; /* Number of bytes in zInit */ |
| 6670 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 6671 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 6672 | unsigned mxAlloc; /* Complexity limit */ |
| 6673 | }; |
| 6674 | |
| 6675 | /* Add a state to the given state set if it is not already there */ |
| 6676 | static void re_add_state(ReStateSet *pSet, int newState){ |
| 6677 | unsigned i; |
| @@ -7265,18 +6882,19 @@ | |
| 6882 | return rc; |
| 6883 | } |
| 6884 | |
| 6885 | /* Resize the opcode and argument arrays for an RE under construction. |
| 6886 | */ |
| 6887 | static int re_resize(ReCompiled *p, unsigned int N){ |
| 6888 | char *aOp; |
| 6889 | int *aArg; |
| 6890 | if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; } |
| 6891 | aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); |
| 6892 | if( aOp==0 ){ p->zErr = "out of memory"; return 1; } |
| 6893 | p->aOp = aOp; |
| 6894 | aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); |
| 6895 | if( aArg==0 ){ p->zErr = "out of memory"; return 1; } |
| 6896 | p->aArg = aArg; |
| 6897 | p->nAlloc = N; |
| 6898 | return 0; |
| 6899 | } |
| 6900 | |
| @@ -7303,11 +6921,11 @@ | |
| 6921 | } |
| 6922 | |
| 6923 | /* Make a copy of N opcodes starting at iStart onto the end of the RE |
| 6924 | ** under construction. |
| 6925 | */ |
| 6926 | static void re_copy(ReCompiled *p, int iStart, unsigned int N){ |
| 6927 | if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; |
| 6928 | memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); |
| 6929 | memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); |
| 6930 | p->nState += N; |
| 6931 | } |
| @@ -7456,22 +7074,30 @@ | |
| 7074 | case '^': { |
| 7075 | re_append(p, RE_OP_ATSTART, 0); |
| 7076 | break; |
| 7077 | } |
| 7078 | case '{': { |
| 7079 | unsigned int m = 0, n = 0; |
| 7080 | unsigned int sz, j; |
| 7081 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 7082 | while( (c=rePeek(p))>='0' && c<='9' ){ |
| 7083 | m = m*10 + c - '0'; |
| 7084 | if( m*2>p->mxAlloc ) return "REGEXP pattern too big"; |
| 7085 | p->sIn.i++; |
| 7086 | } |
| 7087 | n = m; |
| 7088 | if( c==',' ){ |
| 7089 | p->sIn.i++; |
| 7090 | n = 0; |
| 7091 | while( (c=rePeek(p))>='0' && c<='9' ){ |
| 7092 | n = n*10 + c-'0'; |
| 7093 | if( n*2>p->mxAlloc ) return "REGEXP pattern too big"; |
| 7094 | p->sIn.i++; |
| 7095 | } |
| 7096 | } |
| 7097 | if( c!='}' ) return "unmatched '{'"; |
| 7098 | if( n<m ) return "n less than m in '{m,n}'"; |
| 7099 | p->sIn.i++; |
| 7100 | sz = p->nState - iPrev; |
| 7101 | if( m==0 ){ |
| 7102 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 7103 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| @@ -7483,11 +7109,11 @@ | |
| 7109 | for(j=m; j<n; j++){ |
| 7110 | re_append(p, RE_OP_FORK, sz+1); |
| 7111 | re_copy(p, iPrev, sz); |
| 7112 | } |
| 7113 | if( n==0 && m>0 ){ |
| 7114 | re_append(p, RE_OP_FORK, -(int)sz); |
| 7115 | } |
| 7116 | break; |
| 7117 | } |
| 7118 | case '[': { |
| 7119 | unsigned int iFirst = p->nState; |
| @@ -7549,12 +7175,11 @@ | |
| 7175 | |
| 7176 | /* Free and reclaim all the memory used by a previously compiled |
| 7177 | ** regular expression. Applications should invoke this routine once |
| 7178 | ** for every call to re_compile() to avoid memory leaks. |
| 7179 | */ |
| 7180 | static void re_free(ReCompiled *pRe){ |
| 7181 | if( pRe ){ |
| 7182 | sqlite3_free(pRe->aOp); |
| 7183 | sqlite3_free(pRe->aArg); |
| 7184 | sqlite3_free(pRe); |
| 7185 | } |
| @@ -7564,11 +7189,16 @@ | |
| 7189 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 7190 | ** expression suitable for us by re_match() and return a pointer to the |
| 7191 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 7192 | ** error message if something goes wrong. |
| 7193 | */ |
| 7194 | static const char *re_compile( |
| 7195 | ReCompiled **ppRe, /* OUT: write compiled NFA here */ |
| 7196 | const char *zIn, /* Input regular expression */ |
| 7197 | int mxRe, /* Complexity limit */ |
| 7198 | int noCase /* True for caseless comparisons */ |
| 7199 | ){ |
| 7200 | ReCompiled *pRe; |
| 7201 | const char *zErr; |
| 7202 | int i, j; |
| 7203 | |
| 7204 | *ppRe = 0; |
| @@ -7576,13 +7206,15 @@ | |
| 7206 | if( pRe==0 ){ |
| 7207 | return "out of memory"; |
| 7208 | } |
| 7209 | memset(pRe, 0, sizeof(*pRe)); |
| 7210 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 7211 | pRe->mxAlloc = mxRe; |
| 7212 | if( re_resize(pRe, 30) ){ |
| 7213 | zErr = pRe->zErr; |
| 7214 | re_free(pRe); |
| 7215 | return zErr; |
| 7216 | } |
| 7217 | if( zIn[0]=='^' ){ |
| 7218 | zIn++; |
| 7219 | }else{ |
| 7220 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -7630,10 +7262,18 @@ | |
| 7262 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 7263 | pRe->nInit = j; |
| 7264 | } |
| 7265 | return pRe->zErr; |
| 7266 | } |
| 7267 | |
| 7268 | /* |
| 7269 | ** Compute a reasonable limit on the length of the REGEXP NFA. |
| 7270 | */ |
| 7271 | static int re_maxlen(sqlite3_context *context){ |
| 7272 | sqlite3 *db = sqlite3_context_db_handle(context); |
| 7273 | return 75 + sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH,-1)/2; |
| 7274 | } |
| 7275 | |
| 7276 | /* |
| 7277 | ** Implementation of the regexp() SQL function. This function implements |
| 7278 | ** the build-in REGEXP operator. The first argument to the function is the |
| 7279 | ** pattern and the second argument is the string. So, the SQL statements: |
| @@ -7656,11 +7296,12 @@ | |
| 7296 | (void)argc; /* Unused */ |
| 7297 | pRe = sqlite3_get_auxdata(context, 0); |
| 7298 | if( pRe==0 ){ |
| 7299 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7300 | if( zPattern==0 ) return; |
| 7301 | zErr = re_compile(&pRe, zPattern, re_maxlen(context), |
| 7302 | sqlite3_user_data(context)!=0); |
| 7303 | if( zErr ){ |
| 7304 | re_free(pRe); |
| 7305 | sqlite3_result_error(context, zErr, -1); |
| 7306 | return; |
| 7307 | } |
| @@ -7698,14 +7339,36 @@ | |
| 7339 | sqlite3_str *pStr; |
| 7340 | int i; |
| 7341 | int n; |
| 7342 | char *z; |
| 7343 | (void)argc; |
| 7344 | static const char *ReOpName[] = { |
| 7345 | "EOF", |
| 7346 | "MATCH", |
| 7347 | "ANY", |
| 7348 | "ANYSTAR", |
| 7349 | "FORK", |
| 7350 | "GOTO", |
| 7351 | "ACCEPT", |
| 7352 | "CC_INC", |
| 7353 | "CC_EXC", |
| 7354 | "CC_VALUE", |
| 7355 | "CC_RANGE", |
| 7356 | "WORD", |
| 7357 | "NOTWORD", |
| 7358 | "DIGIT", |
| 7359 | "NOTDIGIT", |
| 7360 | "SPACE", |
| 7361 | "NOTSPACE", |
| 7362 | "BOUNDARY", |
| 7363 | "ATSTART", |
| 7364 | }; |
| 7365 | |
| 7366 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 7367 | if( zPattern==0 ) return; |
| 7368 | zErr = re_compile(&pRe, zPattern, re_maxlen(context), |
| 7369 | sqlite3_user_data(context)!=0); |
| 7370 | if( zErr ){ |
| 7371 | re_free(pRe); |
| 7372 | sqlite3_result_error(context, zErr, -1); |
| 7373 | return; |
| 7374 | } |
| @@ -9284,18 +8947,20 @@ | |
| 8947 | if( idxNum & 1 ){ |
| 8948 | pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); |
| 8949 | if( pCur->nPrefix>0 ){ |
| 8950 | pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 8951 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 8952 | pCur->nPrefix = (int)strlen(pCur->zPrefix); |
| 8953 | } |
| 8954 | iArg = 1; |
| 8955 | } |
| 8956 | if( idxNum & 2 ){ |
| 8957 | pCur->nLine = sqlite3_value_bytes(argv[iArg]); |
| 8958 | if( pCur->nLine>0 ){ |
| 8959 | pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); |
| 8960 | if( pCur->zLine==0 ) return SQLITE_NOMEM; |
| 8961 | pCur->nLine = (int)strlen(pCur->zLine); |
| 8962 | } |
| 8963 | } |
| 8964 | if( pCur->zLine!=0 && pCur->zPrefix==0 ){ |
| 8965 | int i = pCur->nLine; |
| 8966 | while( i>0 && (IsAlnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ |
| @@ -9303,10 +8968,11 @@ | |
| 8968 | } |
| 8969 | pCur->nPrefix = pCur->nLine - i; |
| 8970 | if( pCur->nPrefix>0 ){ |
| 8971 | pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); |
| 8972 | if( pCur->zPrefix==0 ) return SQLITE_NOMEM; |
| 8973 | pCur->nPrefix = (int)strlen(pCur->zPrefix); |
| 8974 | } |
| 8975 | } |
| 8976 | pCur->iRowid = 0; |
| 8977 | pCur->ePhase = COMPLETION_FIRST_PHASE; |
| 8978 | return completionNext(pVtabCursor); |
| @@ -10216,11 +9882,17 @@ | |
| 9882 | "method," /* 6: Compression method (integer) */ |
| 9883 | "z HIDDEN" /* 7: Name of zip file */ |
| 9884 | ") WITHOUT ROWID;"; |
| 9885 | |
| 9886 | #define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */ |
| 9887 | #define ZIPFILE_MX_NAME (250) /* Windows limitation on filename size */ |
| 9888 | |
| 9889 | /* |
| 9890 | ** The buffer should be large enough to contain 3 65536 byte strings - the |
| 9891 | ** filename, the extra field and the file comment. |
| 9892 | */ |
| 9893 | #define ZIPFILE_BUFFER_SIZE (200*1024) |
| 9894 | |
| 9895 | |
| 9896 | /* |
| 9897 | ** Magic numbers used to read and write zip files. |
| 9898 | ** |
| @@ -10773,10 +10445,11 @@ | |
| 10445 | pLFH->crc32 = zipfileRead32(aRead); |
| 10446 | pLFH->szCompressed = zipfileRead32(aRead); |
| 10447 | pLFH->szUncompressed = zipfileRead32(aRead); |
| 10448 | pLFH->nFile = zipfileRead16(aRead); |
| 10449 | pLFH->nExtra = zipfileRead16(aRead); |
| 10450 | if( pLFH->nFile>ZIPFILE_MX_NAME ) rc = SQLITE_ERROR; |
| 10451 | } |
| 10452 | return rc; |
| 10453 | } |
| 10454 | |
| 10455 | |
| @@ -10898,10 +10571,19 @@ | |
| 10571 | || mUnixTime==zipfileMtime(pCds) |
| 10572 | || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds)) |
| 10573 | /* || (mUnixTime % 2) */ |
| 10574 | ); |
| 10575 | } |
| 10576 | |
| 10577 | /* |
| 10578 | ** Set (*pzErr) to point to a buffer from sqlite3_malloc() containing a |
| 10579 | ** generic corruption message and return SQLITE_CORRUPT; |
| 10580 | */ |
| 10581 | static int zipfileCorrupt(char **pzErr){ |
| 10582 | *pzErr = sqlite3_mprintf("zip archive is corrupt"); |
| 10583 | return SQLITE_CORRUPT; |
| 10584 | } |
| 10585 | |
| 10586 | /* |
| 10587 | ** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in |
| 10588 | ** size) containing an entire zip archive image. Or, if aBlob is NULL, |
| 10589 | ** then pFile is a file-handle open on a zip file. In either case, this |
| @@ -10921,16 +10603,19 @@ | |
| 10603 | ZipfileEntry **ppEntry /* OUT: Pointer to new object */ |
| 10604 | ){ |
| 10605 | u8 *aRead; |
| 10606 | char **pzErr = &pTab->base.zErrMsg; |
| 10607 | int rc = SQLITE_OK; |
| 10608 | |
| 10609 | if( aBlob==0 ){ |
| 10610 | aRead = pTab->aBuffer; |
| 10611 | rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr); |
| 10612 | }else{ |
| 10613 | if( (iOff+ZIPFILE_CDS_FIXED_SZ)>nBlob ){ |
| 10614 | /* Not enough data for the CDS structure. Corruption. */ |
| 10615 | return zipfileCorrupt(pzErr); |
| 10616 | } |
| 10617 | aRead = (u8*)&aBlob[iOff]; |
| 10618 | } |
| 10619 | |
| 10620 | if( rc==SQLITE_OK ){ |
| 10621 | sqlite3_int64 nAlloc; |
| @@ -10957,10 +10642,13 @@ | |
| 10642 | rc = zipfileReadData( |
| 10643 | pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr |
| 10644 | ); |
| 10645 | }else{ |
| 10646 | aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ]; |
| 10647 | if( (iOff + ZIPFILE_LFH_FIXED_SZ + nFile + nExtra)>nBlob ){ |
| 10648 | rc = zipfileCorrupt(pzErr); |
| 10649 | } |
| 10650 | } |
| 10651 | } |
| 10652 | |
| 10653 | if( rc==SQLITE_OK ){ |
| 10654 | u32 *pt = &pNew->mUnixTime; |
| @@ -10979,19 +10667,26 @@ | |
| 10667 | ZipfileLFH lfh; |
| 10668 | if( pFile ){ |
| 10669 | rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr); |
| 10670 | }else{ |
| 10671 | aRead = (u8*)&aBlob[pNew->cds.iOffset]; |
| 10672 | if( (pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ)>nBlob ){ |
| 10673 | rc = zipfileCorrupt(pzErr); |
| 10674 | } |
| 10675 | } |
| 10676 | |
| 10677 | if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh); |
| 10678 | if( rc==SQLITE_OK ){ |
| 10679 | pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ; |
| 10680 | pNew->iDataOff += lfh.nFile + lfh.nExtra; |
| 10681 | if( aBlob && pNew->cds.szCompressed ){ |
| 10682 | if( pNew->iDataOff + pNew->cds.szCompressed > nBlob ){ |
| 10683 | rc = zipfileCorrupt(pzErr); |
| 10684 | }else{ |
| 10685 | pNew->aData = &pNew->aExtra[nExtra]; |
| 10686 | memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed); |
| 10687 | } |
| 10688 | } |
| 10689 | }else{ |
| 10690 | *pzErr = sqlite3_mprintf("failed to read LFH at offset %d", |
| 10691 | (int)pNew->cds.iOffset |
| 10692 | ); |
| @@ -11774,10 +11469,15 @@ | |
| 11469 | |
| 11470 | if( rc==SQLITE_OK ){ |
| 11471 | zPath = (const char*)sqlite3_value_text(apVal[2]); |
| 11472 | if( zPath==0 ) zPath = ""; |
| 11473 | nPath = (int)strlen(zPath); |
| 11474 | if( nPath>ZIPFILE_MX_NAME ){ |
| 11475 | zipfileTableErr(pTab, "filename too long; max: %d bytes", |
| 11476 | ZIPFILE_MX_NAME); |
| 11477 | rc = SQLITE_CONSTRAINT; |
| 11478 | } |
| 11479 | mTime = zipfileGetTime(apVal[4]); |
| 11480 | } |
| 11481 | |
| 11482 | if( rc==SQLITE_OK && bIsDir ){ |
| 11483 | /* For a directory, check that the last character in the path is a |
| @@ -12135,10 +11835,17 @@ | |
| 11835 | if( zName==0 ){ |
| 11836 | zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL"); |
| 11837 | rc = SQLITE_ERROR; |
| 11838 | goto zipfile_step_out; |
| 11839 | } |
| 11840 | if( nName>ZIPFILE_MX_NAME ){ |
| 11841 | zErr = sqlite3_mprintf( |
| 11842 | "filename argument to zipfile() too big; max: %d bytes", |
| 11843 | ZIPFILE_MX_NAME); |
| 11844 | rc = SQLITE_ERROR; |
| 11845 | goto zipfile_step_out; |
| 11846 | } |
| 11847 | |
| 11848 | /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use |
| 11849 | ** deflate compression) or NULL (choose automatically). */ |
| 11850 | if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){ |
| 11851 | iMethod = (int)sqlite3_value_int64(pMethod); |
| @@ -12477,10 +12184,11 @@ | |
| 12184 | return rc; |
| 12185 | } |
| 12186 | |
| 12187 | /************************* End ../ext/misc/sqlar.c ********************/ |
| 12188 | #endif |
| 12189 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 12190 | /************************* Begin ../ext/expert/sqlite3expert.h ******************/ |
| 12191 | /* |
| 12192 | ** 2017 April 07 |
| 12193 | ** |
| 12194 | ** The author disclaims copyright to this source code. In place of |
| @@ -14885,10 +14593,11 @@ | |
| 14593 | } |
| 14594 | |
| 14595 | #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
| 14596 | |
| 14597 | /************************* End ../ext/expert/sqlite3expert.c ********************/ |
| 14598 | #endif |
| 14599 | /************************* Begin ../ext/intck/sqlite3intck.h ******************/ |
| 14600 | /* |
| 14601 | ** 2024-02-08 |
| 14602 | ** |
| 14603 | ** The author disclaims copyright to this source code. In place of |
| @@ -21525,15 +21234,17 @@ | |
| 21234 | char **azFilter; /* Array of xFilter rejection GLOB patterns */ |
| 21235 | sqlite3_session *p; /* The open session */ |
| 21236 | }; |
| 21237 | #endif |
| 21238 | |
| 21239 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 21240 | typedef struct ExpertInfo ExpertInfo; |
| 21241 | struct ExpertInfo { |
| 21242 | sqlite3expert *pExpert; |
| 21243 | int bVerbose; |
| 21244 | }; |
| 21245 | #endif |
| 21246 | |
| 21247 | /* A single line in the EQP output */ |
| 21248 | typedef struct EQPGraphRow EQPGraphRow; |
| 21249 | struct EQPGraphRow { |
| 21250 | int iEqpId; /* ID for this row */ |
| @@ -21633,11 +21344,13 @@ | |
| 21344 | int *aiIndent; /* Array of indents used in MODE_Explain */ |
| 21345 | int nIndent; /* Size of array aiIndent[] */ |
| 21346 | int iIndent; /* Index of current op in aiIndent[] */ |
| 21347 | char *zNonce; /* Nonce for temporary safe-mode escapes */ |
| 21348 | EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ |
| 21349 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 21350 | ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ |
| 21351 | #endif |
| 21352 | #ifdef SQLITE_SHELL_FIDDLE |
| 21353 | struct { |
| 21354 | const char * zInput; /* Input string from wasm/JS proxy */ |
| 21355 | const char * zPos; /* Cursor pos into zInput */ |
| 21356 | const char * zDefaultDbName; /* Default name for db file */ |
| @@ -21661,13 +21374,12 @@ | |
| 21374 | */ |
| 21375 | #define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */ |
| 21376 | #define SHELL_OPEN_NORMAL 1 /* Normal database file */ |
| 21377 | #define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ |
| 21378 | #define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ |
| 21379 | #define SHELL_OPEN_DESERIALIZE 4 /* Open using sqlite3_deserialize() */ |
| 21380 | #define SHELL_OPEN_HEXDB 5 /* Use "dbtotxt" output as data source */ |
| 21381 | |
| 21382 | /* Allowed values for ShellState.eTraceType |
| 21383 | */ |
| 21384 | #define SHELL_TRACE_PLAIN 0 /* Show input SQL text */ |
| 21385 | #define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */ |
| @@ -22006,11 +21718,11 @@ | |
| 21718 | */ |
| 21719 | static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ |
| 21720 | int i; |
| 21721 | unsigned char *aBlob = (unsigned char*)pBlob; |
| 21722 | |
| 21723 | char *zStr = sqlite3_malloc64((i64)nBlob*2 + 1); |
| 21724 | shell_check_oom(zStr); |
| 21725 | |
| 21726 | for(i=0; i<nBlob; i++){ |
| 21727 | static const char aHex[] = { |
| 21728 | '0', '1', '2', '3', '4', '5', '6', '7', |
| @@ -22027,11 +21739,11 @@ | |
| 21739 | |
| 21740 | /* |
| 21741 | ** Output the given string as a quoted string using SQL quoting conventions: |
| 21742 | ** |
| 21743 | ** (1) Single quotes (') within the string are doubled |
| 21744 | ** (2) The while string is enclosed in '...' |
| 21745 | ** (3) Control characters other than \n, \t, and \r\n are escaped |
| 21746 | ** using \u00XX notation and if such substitutions occur, |
| 21747 | ** the whole string is enclosed in unistr('...') instead of '...'. |
| 21748 | ** |
| 21749 | ** Step (3) is omitted if the control-character escape mode is OFF. |
| @@ -22273,11 +21985,11 @@ | |
| 21985 | ** |
| 21986 | ** Escaping is needed if the string contains any control characters |
| 21987 | ** other than \t, \n, and \r\n |
| 21988 | ** |
| 21989 | ** If no escaping is needed (the common case) then set *ppFree to NULL |
| 21990 | ** and return the original string. If escaping is needed, write the |
| 21991 | ** escaped string into memory obtained from sqlite3_malloc64() or the |
| 21992 | ** equivalent, and return the new string and set *ppFree to the new string |
| 21993 | ** as well. |
| 21994 | ** |
| 21995 | ** The caller is responsible for freeing *ppFree if it is non-NULL in order |
| @@ -23278,32 +22990,21 @@ | |
| 22990 | ** Set the destination table field of the ShellState structure to |
| 22991 | ** the name of the table given. Escape any quote characters in the |
| 22992 | ** table name. |
| 22993 | */ |
| 22994 | static void set_table_name(ShellState *p, const char *zName){ |
| 22995 | if( p->zDestTable ){ |
| 22996 | sqlite3_free(p->zDestTable); |
| 22997 | p->zDestTable = 0; |
| 22998 | } |
| 22999 | if( zName==0 ) return; |
| 23000 | if( quoteChar(zName) ){ |
| 23001 | p->zDestTable = sqlite3_mprintf("\"%w\"", zName); |
| 23002 | }else{ |
| 23003 | p->zDestTable = sqlite3_mprintf("%s", zName); |
| 23004 | } |
| 23005 | shell_check_oom(p->zDestTable); |
| 23006 | } |
| 23007 | |
| 23008 | /* |
| 23009 | ** Maybe construct two lines of text that point out the position of a |
| 23010 | ** syntax error. Return a pointer to the text, in memory obtained from |
| @@ -23496,12 +23197,12 @@ | |
| 23197 | static int display_stats( |
| 23198 | sqlite3 *db, /* Database to query */ |
| 23199 | ShellState *pArg, /* Pointer to ShellState */ |
| 23200 | int bReset /* True to reset the stats */ |
| 23201 | ){ |
| 23202 | int iCur, iHiwtr; |
| 23203 | sqlite3_int64 iCur64, iHiwtr64; |
| 23204 | FILE *out; |
| 23205 | if( pArg==0 || pArg->out==0 ) return 0; |
| 23206 | out = pArg->out; |
| 23207 | |
| 23208 | if( pArg->pStmt && pArg->statsOn==2 ){ |
| @@ -23586,18 +23287,25 @@ | |
| 23287 | "Page cache hits: %d\n", iCur); |
| 23288 | iHiwtr = iCur = -1; |
| 23289 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); |
| 23290 | sqlite3_fprintf(out, |
| 23291 | "Page cache misses: %d\n", iCur); |
| 23292 | iHiwtr64 = iCur64 = -1; |
| 23293 | sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, |
| 23294 | 0); |
| 23295 | iHiwtr = iCur = -1; |
| 23296 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); |
| 23297 | sqlite3_fprintf(out, |
| 23298 | "Page cache writes: %d\n", iCur); |
| 23299 | iHiwtr = iCur = -1; |
| 23300 | sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); |
| 23301 | sqlite3_fprintf(out, |
| 23302 | "Page cache spills: %d\n", iCur); |
| 23303 | sqlite3_fprintf(out, |
| 23304 | "Temporary data spilled to disk: %lld\n", iCur64); |
| 23305 | sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64, |
| 23306 | 1); |
| 23307 | iHiwtr = iCur = -1; |
| 23308 | sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); |
| 23309 | sqlite3_fprintf(out, |
| 23310 | "Schema Heap Usage: %d bytes\n", iCur); |
| 23311 | iHiwtr = iCur = -1; |
| @@ -23999,10 +23707,40 @@ | |
| 23707 | char *zBuf = sqlite3_malloc64( szVar-5 ); |
| 23708 | if( zBuf ){ |
| 23709 | memcpy(zBuf, &zVar[6], szVar-5); |
| 23710 | sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); |
| 23711 | } |
| 23712 | #ifdef SQLITE_ENABLE_CARRAY |
| 23713 | }else if( strncmp(zVar, "$carray_", 8)==0 ){ |
| 23714 | static char *azColorNames[] = { |
| 23715 | "azure", "black", "blue", "brown", "cyan", "fuchsia", "gold", |
| 23716 | "gray", "green", "indigo", "khaki", "lime", "magenta", "maroon", |
| 23717 | "navy", "olive", "orange", "pink", "purple", "red", "silver", |
| 23718 | "tan", "teal", "violet", "white", "yellow" |
| 23719 | }; |
| 23720 | static int aPrimes[] = { |
| 23721 | 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, |
| 23722 | 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 |
| 23723 | }; |
| 23724 | /* Special bindings: carray($carray_clr), carray($carray_primes) |
| 23725 | ** with --unsafe-testing: carray($carray_clr_p,26,'char*'), |
| 23726 | ** carray($carray_primes_p,26,'int32') |
| 23727 | */ |
| 23728 | if( strcmp(zVar+8,"clr")==0 ){ |
| 23729 | sqlite3_carray_bind(pStmt,i,azColorNames,26,SQLITE_CARRAY_TEXT,0); |
| 23730 | }else if( strcmp(zVar+8,"primes")==0 ){ |
| 23731 | sqlite3_carray_bind(pStmt,i,aPrimes,26,SQLITE_CARRAY_INT32,0); |
| 23732 | }else if( strcmp(zVar+8,"clr_p")==0 |
| 23733 | && ShellHasFlag(pArg,SHFLG_TestingMode) ){ |
| 23734 | sqlite3_bind_pointer(pStmt,i,azColorNames,"carray",0); |
| 23735 | }else if( strcmp(zVar+8,"primes_p")==0 |
| 23736 | && ShellHasFlag(pArg,SHFLG_TestingMode) ){ |
| 23737 | sqlite3_bind_pointer(pStmt,i,aPrimes,"carray",0); |
| 23738 | }else{ |
| 23739 | sqlite3_bind_null(pStmt, i); |
| 23740 | } |
| 23741 | #endif |
| 23742 | }else{ |
| 23743 | sqlite3_bind_null(pStmt, i); |
| 23744 | } |
| 23745 | sqlite3_reset(pQ); |
| 23746 | } |
| @@ -24581,11 +24319,11 @@ | |
| 24319 | } |
| 24320 | } |
| 24321 | } |
| 24322 | } |
| 24323 | |
| 24324 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 24325 | /* |
| 24326 | ** This function is called to process SQL if the previous shell command |
| 24327 | ** was ".expert". It passes the SQL in the second argument directly to |
| 24328 | ** the sqlite3expert object. |
| 24329 | ** |
| @@ -24713,11 +24451,11 @@ | |
| 24451 | } |
| 24452 | sqlite3_free(zErr); |
| 24453 | |
| 24454 | return rc; |
| 24455 | } |
| 24456 | #endif /* !SQLITE_OMIT_VIRTUALTABLE && !SQLITE_OMIT_AUTHORIZATION */ |
| 24457 | |
| 24458 | /* |
| 24459 | ** Execute a statement or set of statements. Print |
| 24460 | ** any result rows/columns depending on the current mode |
| 24461 | ** set via the supplied callback. |
| @@ -24739,11 +24477,11 @@ | |
| 24477 | |
| 24478 | if( pzErrMsg ){ |
| 24479 | *pzErrMsg = NULL; |
| 24480 | } |
| 24481 | |
| 24482 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 24483 | if( pArg->expert.pExpert ){ |
| 24484 | rc = expertHandleSQL(pArg, zSql, pzErrMsg); |
| 24485 | return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg); |
| 24486 | } |
| 24487 | #endif |
| @@ -24901,11 +24639,11 @@ | |
| 24639 | static char **tableColumnList(ShellState *p, const char *zTab){ |
| 24640 | char **azCol = 0; |
| 24641 | sqlite3_stmt *pStmt; |
| 24642 | char *zSql; |
| 24643 | int nCol = 0; |
| 24644 | i64 nAlloc = 0; |
| 24645 | int nPK = 0; /* Number of PRIMARY KEY columns seen */ |
| 24646 | int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ |
| 24647 | int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid); |
| 24648 | int rc; |
| 24649 | |
| @@ -24915,11 +24653,11 @@ | |
| 24653 | sqlite3_free(zSql); |
| 24654 | if( rc ) return 0; |
| 24655 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 24656 | if( nCol>=nAlloc-2 ){ |
| 24657 | nAlloc = nAlloc*2 + nCol + 10; |
| 24658 | azCol = sqlite3_realloc64(azCol, nAlloc*sizeof(azCol[0])); |
| 24659 | shell_check_oom(azCol); |
| 24660 | } |
| 24661 | azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); |
| 24662 | shell_check_oom(azCol[nCol]); |
| 24663 | if( sqlite3_column_int(pStmt, 5) ){ |
| @@ -25106,17 +24844,17 @@ | |
| 24844 | appendText(&sSelect, " FROM ", 0); |
| 24845 | appendText(&sSelect, zTable, quoteChar(zTable)); |
| 24846 | |
| 24847 | savedDestTable = p->zDestTable; |
| 24848 | savedMode = p->mode; |
| 24849 | p->zDestTable = sTable.zTxt; |
| 24850 | p->mode = p->cMode = MODE_Insert; |
| 24851 | rc = shell_exec(p, sSelect.zTxt, 0); |
| 24852 | if( (rc&0xff)==SQLITE_CORRUPT ){ |
| 24853 | sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out); |
| 24854 | toggleSelectOrder(p->db); |
| 24855 | shell_exec(p, sSelect.zTxt, 0); |
| 24856 | toggleSelectOrder(p->db); |
| 24857 | } |
| 24858 | p->zDestTable = savedDestTable; |
| 24859 | p->mode = savedMode; |
| 24860 | freeText(&sTable); |
| @@ -25245,11 +24983,13 @@ | |
| 24983 | " --bom Put a UTF8 byte-order mark on intermediate file", |
| 24984 | #endif |
| 24985 | #ifndef SQLITE_SHELL_FIDDLE |
| 24986 | ".exit ?CODE? Exit this program with return-code CODE", |
| 24987 | #endif |
| 24988 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 24989 | ".expert EXPERIMENTAL. Suggest indexes for queries", |
| 24990 | #endif |
| 24991 | ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto", |
| 24992 | ".filectrl CMD ... Run various sqlite3_file_control() operations", |
| 24993 | " --schema SCHEMA Use SCHEMA instead of \"main\"", |
| 24994 | " --help Show CMD details", |
| 24995 | ".fullschema ?--indent? Show schema and the content of sqlite_stat tables", |
| @@ -25270,11 +25010,11 @@ | |
| 25010 | " from the \".mode\" output mode", |
| 25011 | " * If FILE begins with \"|\" then it is a command that generates the", |
| 25012 | " input text.", |
| 25013 | #endif |
| 25014 | #ifndef SQLITE_OMIT_TEST_CONTROL |
| 25015 | ".imposter INDEX TABLE Create imposter table TABLE on index INDEX", |
| 25016 | #endif |
| 25017 | ".indexes ?TABLE? Show names of indexes", |
| 25018 | " If TABLE is specified, only show indexes for", |
| 25019 | " tables matching TABLE using the LIKE operator.", |
| 25020 | ".intck ?STEPS_PER_UNLOCK? Run an incremental integrity check on the db", |
| @@ -25337,11 +25077,17 @@ | |
| 25077 | " Options:", |
| 25078 | " --append Use appendvfs to append database to the end of FILE", |
| 25079 | #endif |
| 25080 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 25081 | " --deserialize Load into memory using sqlite3_deserialize()", |
| 25082 | #endif |
| 25083 | /*" --exclusive Set the SQLITE_OPEN_EXCLUSIVE flag", UNDOCUMENTED */ |
| 25084 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 25085 | " --hexdb Load the output of \"dbtotxt\" as an in-memory db", |
| 25086 | #endif |
| 25087 | " --ifexist Only open if FILE already exists", |
| 25088 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 25089 | " --maxsize N Maximum size for --hexdb or --deserialized database", |
| 25090 | #endif |
| 25091 | " --new Initialize FILE to an empty database", |
| 25092 | " --nofollow Do not follow symbolic links", |
| 25093 | " --readonly Open FILE readonly", |
| @@ -25726,14 +25472,14 @@ | |
| 25472 | ** If p->aAuxDb[].zDbFilename is 0, then read from standard input. |
| 25473 | */ |
| 25474 | static unsigned char *readHexDb(ShellState *p, int *pnData){ |
| 25475 | unsigned char *a = 0; |
| 25476 | int nLine; |
| 25477 | int n = 0; /* Size of db per first line of hex dump */ |
| 25478 | i64 sz = 0; /* n rounded up to nearest page boundary */ |
| 25479 | int pgsz = 0; |
| 25480 | i64 iOffset = 0; |
| 25481 | int rc; |
| 25482 | FILE *in; |
| 25483 | const char *zDbFilename = p->pAuxDb->zDbFilename; |
| 25484 | unsigned int x[16]; |
| 25485 | char zLine[1000]; |
| @@ -25753,20 +25499,21 @@ | |
| 25499 | nLine++; |
| 25500 | if( sqlite3_fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error; |
| 25501 | rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz); |
| 25502 | if( rc!=2 ) goto readHexDb_error; |
| 25503 | if( n<0 ) goto readHexDb_error; |
| 25504 | if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){ |
| 25505 | sqlite3_fputs("invalid pagesize\n", stderr); |
| 25506 | goto readHexDb_error; |
| 25507 | } |
| 25508 | sz = ((i64)n+pgsz-1)&~(pgsz-1); /* Round up to nearest multiple of pgsz */ |
| 25509 | a = sqlite3_malloc64( sz ? sz : 1 ); |
| 25510 | shell_check_oom(a); |
| 25511 | memset(a, 0, sz); |
| 25512 | for(nLine++; sqlite3_fgets(zLine, sizeof(zLine), in)!=0; nLine++){ |
| 25513 | int j = 0; /* Page number from "| page" line */ |
| 25514 | int k = 0; /* Offset from "| page" line */ |
| 25515 | rc = sscanf(zLine, "| page %d offset %d", &j, &k); |
| 25516 | if( rc==2 ){ |
| 25517 | iOffset = k; |
| 25518 | continue; |
| 25519 | } |
| @@ -25775,18 +25522,18 @@ | |
| 25522 | } |
| 25523 | rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", |
| 25524 | &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], |
| 25525 | &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]); |
| 25526 | if( rc==17 ){ |
| 25527 | i64 iOff = iOffset+j; |
| 25528 | if( iOff+16<=sz && iOff>=0 ){ |
| 25529 | int ii; |
| 25530 | for(ii=0; ii<16; ii++) a[iOff+ii] = x[ii]&0xff; |
| 25531 | } |
| 25532 | } |
| 25533 | } |
| 25534 | *pnData = sz; |
| 25535 | if( in!=p->in ){ |
| 25536 | fclose(in); |
| 25537 | }else{ |
| 25538 | p->lineno = nLine; |
| 25539 | } |
| @@ -25849,11 +25596,11 @@ | |
| 25596 | p->pLog = pSavedLog; |
| 25597 | |
| 25598 | if( zFake ){ |
| 25599 | sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), |
| 25600 | -1, sqlite3_free); |
| 25601 | sqlite3_free(zFake); |
| 25602 | } |
| 25603 | } |
| 25604 | |
| 25605 | /* Flags for open_db(). |
| 25606 | ** |
| @@ -25881,14 +25628,17 @@ | |
| 25628 | }else{ |
| 25629 | p->openMode = (u8)deduceDatabaseType(zDbFilename, |
| 25630 | (openFlags & OPEN_DB_ZIPFILE)!=0); |
| 25631 | } |
| 25632 | } |
| 25633 | if( (p->openFlags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE))==0 ){ |
| 25634 | if( p->openFlags==0 ) p->openFlags = SQLITE_OPEN_CREATE; |
| 25635 | p->openFlags |= SQLITE_OPEN_READWRITE; |
| 25636 | } |
| 25637 | switch( p->openMode ){ |
| 25638 | case SHELL_OPEN_APPENDVFS: { |
| 25639 | sqlite3_open_v2(zDbFilename, &p->db, p->openFlags, "apndvfs"); |
| 25640 | break; |
| 25641 | } |
| 25642 | case SHELL_OPEN_HEXDB: |
| 25643 | case SHELL_OPEN_DESERIALIZE: { |
| 25644 | sqlite3_open(0, &p->db); |
| @@ -25896,19 +25646,13 @@ | |
| 25646 | } |
| 25647 | case SHELL_OPEN_ZIPFILE: { |
| 25648 | sqlite3_open(":memory:", &p->db); |
| 25649 | break; |
| 25650 | } |
| 25651 | case SHELL_OPEN_UNSPEC: |
| 25652 | case SHELL_OPEN_NORMAL: { |
| 25653 | sqlite3_open_v2(zDbFilename, &p->db, p->openFlags, 0); |
| 25654 | break; |
| 25655 | } |
| 25656 | } |
| 25657 | if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ |
| 25658 | sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n", |
| @@ -25944,11 +25688,10 @@ | |
| 25688 | sqlite3_sha_init(p->db, 0, 0); |
| 25689 | sqlite3_shathree_init(p->db, 0, 0); |
| 25690 | sqlite3_uint_init(p->db, 0, 0); |
| 25691 | sqlite3_stmtrand_init(p->db, 0, 0); |
| 25692 | sqlite3_decimal_init(p->db, 0, 0); |
| 25693 | sqlite3_base64_init(p->db, 0, 0); |
| 25694 | sqlite3_base85_init(p->db, 0, 0); |
| 25695 | sqlite3_regexp_init(p->db, 0, 0); |
| 25696 | sqlite3_ieee_init(p->db, 0, 0); |
| 25697 | sqlite3_series_init(p->db, 0, 0); |
| @@ -26043,13 +25786,15 @@ | |
| 25786 | } |
| 25787 | } |
| 25788 | #endif |
| 25789 | } |
| 25790 | if( p->db!=0 ){ |
| 25791 | #ifndef SQLITE_OMIT_AUTHORIZATION |
| 25792 | if( p->bSafeModePersist ){ |
| 25793 | sqlite3_set_authorizer(p->db, safeModeAuth, p); |
| 25794 | } |
| 25795 | #endif |
| 25796 | sqlite3_db_config( |
| 25797 | p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 |
| 25798 | ); |
| 25799 | } |
| 25800 | } |
| @@ -26358,12 +26103,12 @@ | |
| 26103 | struct ImportCtx { |
| 26104 | const char *zFile; /* Name of the input file */ |
| 26105 | FILE *in; /* Read the CSV text from this input stream */ |
| 26106 | int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */ |
| 26107 | char *z; /* Accumulated text for a field */ |
| 26108 | i64 n; /* Number of bytes in z */ |
| 26109 | i64 nAlloc; /* Space allocated for z[] */ |
| 26110 | int nLine; /* Current line number */ |
| 26111 | int nRow; /* Number of rows imported */ |
| 26112 | int nErr; /* Number of errors encountered */ |
| 26113 | int bNotFirst; /* True if one or more bytes already read */ |
| 26114 | int cTerm; /* Character that terminated the most recent field */ |
| @@ -26829,14 +26574,17 @@ | |
| 26574 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26575 | /* |
| 26576 | ** Convert a 2-byte or 4-byte big-endian integer into a native integer |
| 26577 | */ |
| 26578 | static unsigned int get2byteInt(unsigned char *a){ |
| 26579 | return ((unsigned int)a[0]<<8) + (unsigned int)a[1]; |
| 26580 | } |
| 26581 | static unsigned int get4byteInt(unsigned char *a){ |
| 26582 | return ((unsigned int)a[0]<<24) |
| 26583 | + ((unsigned int)a[1]<<16) |
| 26584 | + ((unsigned int)a[2]<<8) |
| 26585 | + (unsigned int)a[3]; |
| 26586 | } |
| 26587 | |
| 26588 | /* |
| 26589 | ** Implementation of the ".dbinfo" command. |
| 26590 | ** |
| @@ -26969,11 +26717,11 @@ | |
| 26717 | if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; |
| 26718 | nPage = sqlite3_column_int64(pStmt, 0); |
| 26719 | sqlite3_finalize(pStmt); |
| 26720 | pStmt = 0; |
| 26721 | if( nPage<1 ) goto dbtotxt_error; |
| 26722 | rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); |
| 26723 | if( rc ) goto dbtotxt_error; |
| 26724 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 26725 | zTail = "unk.db"; |
| 26726 | }else{ |
| 26727 | const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2); |
| @@ -26980,10 +26728,15 @@ | |
| 26728 | if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db"; |
| 26729 | zTail = strrchr(zFilename, '/'); |
| 26730 | #if defined(_WIN32) |
| 26731 | if( zTail==0 ) zTail = strrchr(zFilename, '\\'); |
| 26732 | #endif |
| 26733 | if( zTail==0 ){ |
| 26734 | zTail = zFilename; |
| 26735 | }else if( zTail[1]!=0 ){ |
| 26736 | zTail++; |
| 26737 | } |
| 26738 | } |
| 26739 | zName = strdup(zTail); |
| 26740 | shell_check_oom(zName); |
| 26741 | sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", |
| 26742 | nPage*pgSz, pgSz, zName); |
| @@ -27148,10 +26901,47 @@ | |
| 26901 | if( zStr[0]!='-' ) return 0; |
| 26902 | zStr++; |
| 26903 | if( zStr[0]=='-' ) zStr++; |
| 26904 | return cli_strcmp(zStr, zOpt)==0; |
| 26905 | } |
| 26906 | |
| 26907 | /* |
| 26908 | ** The input zFN is guaranteed to start with "file:" and is thus a URI |
| 26909 | ** filename. Extract the actual filename and return a pointer to that |
| 26910 | ** filename in spaced obtained from sqlite3_malloc(). |
| 26911 | ** |
| 26912 | ** The caller is responsible for freeing space using sqlite3_free() when |
| 26913 | ** it has finished with the filename. |
| 26914 | */ |
| 26915 | static char *shellFilenameFromUri(const char *zFN){ |
| 26916 | char *zOut; |
| 26917 | int i, j, d1, d2; |
| 26918 | |
| 26919 | assert( cli_strncmp(zFN,"file:",5)==0 ); |
| 26920 | zOut = sqlite3_mprintf("%s", zFN+5); |
| 26921 | shell_check_oom(zOut); |
| 26922 | for(i=j=0; zOut[i]!=0 && zOut[i]!='?'; i++){ |
| 26923 | if( zOut[i]!='%' ){ |
| 26924 | zOut[j++] = zOut[i]; |
| 26925 | continue; |
| 26926 | } |
| 26927 | d1 = hexDigitValue(zOut[i+1]); |
| 26928 | if( d1<0 ){ |
| 26929 | zOut[j] = 0; |
| 26930 | break; |
| 26931 | } |
| 26932 | d2 = hexDigitValue(zOut[i+2]); |
| 26933 | if( d2<0 ){ |
| 26934 | zOut[j] = 0; |
| 26935 | break; |
| 26936 | } |
| 26937 | zOut[j++] = d1*16 + d2; |
| 26938 | i += 2; |
| 26939 | } |
| 26940 | zOut[j] = 0; |
| 26941 | return zOut; |
| 26942 | } |
| 26943 | |
| 26944 | /* |
| 26945 | ** Delete a file. |
| 26946 | */ |
| 26947 | int shellDeleteFile(const char *zFilename){ |
| @@ -28704,11 +28494,11 @@ | |
| 28494 | int nArg = 0; |
| 28495 | int n, c; |
| 28496 | int rc = 0; |
| 28497 | char *azArg[52]; |
| 28498 | |
| 28499 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 28500 | if( p->expert.pExpert ){ |
| 28501 | expertFinish(p, 1, 0); |
| 28502 | } |
| 28503 | #endif |
| 28504 | |
| @@ -29005,11 +28795,11 @@ | |
| 28795 | }else{ |
| 28796 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 28797 | const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); |
| 28798 | const char *zFile = (const char*)sqlite3_column_text(pStmt,2); |
| 28799 | if( zSchema==0 || zFile==0 ) continue; |
| 28800 | azName = sqlite3_realloc64(azName, (nName+1)*2*sizeof(char*)); |
| 28801 | shell_check_oom(azName); |
| 28802 | azName[nName*2] = strdup(zSchema); |
| 28803 | azName[nName*2+1] = strdup(zFile); |
| 28804 | nName++; |
| 28805 | } |
| @@ -29208,10 +28998,11 @@ | |
| 28998 | rc = 1; |
| 28999 | } |
| 29000 | }else |
| 29001 | |
| 29002 | if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 29003 | open_db(p, 0); |
| 29004 | rc = shell_dbtotxt_command(p, nArg, azArg); |
| 29005 | }else |
| 29006 | |
| 29007 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 29008 | if( nArg==2 ){ |
| @@ -29273,11 +29064,11 @@ | |
| 29064 | if( p->mode==MODE_Explain ) p->mode = p->normalMode; |
| 29065 | p->autoExplain = 1; |
| 29066 | } |
| 29067 | }else |
| 29068 | |
| 29069 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 29070 | if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ |
| 29071 | if( p->bSafeMode ){ |
| 29072 | sqlite3_fprintf(stderr, |
| 29073 | "Cannot run experimental commands such as \"%s\" in safe mode\n", |
| 29074 | azArg[0]); |
| @@ -29840,16 +29631,10 @@ | |
| 29631 | sqlite3_stmt *pStmt; |
| 29632 | int tnum = 0; |
| 29633 | int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */ |
| 29634 | int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ |
| 29635 | int i; |
| 29636 | if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ |
| 29637 | eputz("Usage: .imposter INDEX IMPOSTER\n" |
| 29638 | " .imposter off\n"); |
| 29639 | /* Also allowed, but not documented: |
| 29640 | ** |
| @@ -29917,22 +29702,19 @@ | |
| 29702 | if( lenPK==0 ) lenPK = 100000; |
| 29703 | zSql = sqlite3_mprintf( |
| 29704 | "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID", |
| 29705 | azArg[2], zCollist, lenPK, zCollist); |
| 29706 | sqlite3_free(zCollist); |
| 29707 | rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 2, tnum); |
| 29708 | if( rc==SQLITE_OK ){ |
| 29709 | rc = sqlite3_exec(p->db, zSql, 0, 0, 0); |
| 29710 | sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); |
| 29711 | if( rc ){ |
| 29712 | sqlite3_fprintf(stderr, |
| 29713 | "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); |
| 29714 | }else{ |
| 29715 | sqlite3_fprintf(stdout, "%s;\n", zSql); |
| 29716 | } |
| 29717 | }else{ |
| 29718 | sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); |
| 29719 | rc = 1; |
| 29720 | } |
| @@ -30282,10 +30064,11 @@ | |
| 30064 | const char *zFN = 0; /* Pointer to constant filename */ |
| 30065 | char *zNewFilename = 0; /* Name of the database file to open */ |
| 30066 | int iName = 1; /* Index in azArg[] of the filename */ |
| 30067 | int newFlag = 0; /* True to delete file before opening */ |
| 30068 | int openMode = SHELL_OPEN_UNSPEC; |
| 30069 | int openFlags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; |
| 30070 | |
| 30071 | /* Check for command-line arguments */ |
| 30072 | for(iName=1; iName<nArg; iName++){ |
| 30073 | const char *z = azArg[iName]; |
| 30074 | #ifndef SQLITE_SHELL_FIDDLE |
| @@ -30296,13 +30079,18 @@ | |
| 30079 | openMode = SHELL_OPEN_ZIPFILE; |
| 30080 | #endif |
| 30081 | }else if( optionMatch(z, "append") ){ |
| 30082 | openMode = SHELL_OPEN_APPENDVFS; |
| 30083 | }else if( optionMatch(z, "readonly") ){ |
| 30084 | openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); |
| 30085 | openFlags |= SQLITE_OPEN_READONLY; |
| 30086 | }else if( optionMatch(z, "exclusive") ){ |
| 30087 | openFlags |= SQLITE_OPEN_EXCLUSIVE; |
| 30088 | }else if( optionMatch(z, "ifexists") ){ |
| 30089 | openFlags &= ~(SQLITE_OPEN_CREATE); |
| 30090 | }else if( optionMatch(z, "nofollow") ){ |
| 30091 | openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 30092 | #ifndef SQLITE_OMIT_DESERIALIZE |
| 30093 | }else if( optionMatch(z, "deserialize") ){ |
| 30094 | openMode = SHELL_OPEN_DESERIALIZE; |
| 30095 | }else if( optionMatch(z, "hexdb") ){ |
| 30096 | openMode = SHELL_OPEN_HEXDB; |
| @@ -30330,16 +30118,25 @@ | |
| 30118 | p->db = 0; |
| 30119 | p->pAuxDb->zDbFilename = 0; |
| 30120 | sqlite3_free(p->pAuxDb->zFreeOnClose); |
| 30121 | p->pAuxDb->zFreeOnClose = 0; |
| 30122 | p->openMode = openMode; |
| 30123 | p->openFlags = openFlags; |
| 30124 | p->szMax = 0; |
| 30125 | |
| 30126 | /* If a filename is specified, try to open it first */ |
| 30127 | if( zFN || p->openMode==SHELL_OPEN_HEXDB ){ |
| 30128 | if( newFlag && zFN && !p->bSafeMode ){ |
| 30129 | if( cli_strncmp(zFN,"file:",5)==0 ){ |
| 30130 | char *zDel = shellFilenameFromUri(zFN); |
| 30131 | shell_check_oom(zDel); |
| 30132 | shellDeleteFile(zDel); |
| 30133 | sqlite3_free(zDel); |
| 30134 | }else{ |
| 30135 | shellDeleteFile(zFN); |
| 30136 | } |
| 30137 | } |
| 30138 | #ifndef SQLITE_SHELL_FIDDLE |
| 30139 | if( p->bSafeMode |
| 30140 | && p->openMode!=SHELL_OPEN_HEXDB |
| 30141 | && zFN |
| 30142 | && cli_strcmp(zFN,":memory:")!=0 |
| @@ -30938,13 +30735,13 @@ | |
| 30735 | appendText(&sSelect, "name NOT LIKE 'sqlite__%%' ESCAPE '_' AND ", 0); |
| 30736 | } |
| 30737 | appendText(&sSelect, "sql IS NOT NULL" |
| 30738 | " ORDER BY snum, rowid", 0); |
| 30739 | if( bDebug ){ |
| 30740 | sqlite3_fprintf(p->out, "SQL: %s;\n", sSelect.zTxt); |
| 30741 | }else{ |
| 30742 | rc = sqlite3_exec(p->db, sSelect.zTxt, callback, &data, &zErrMsg); |
| 30743 | } |
| 30744 | freeText(&sSelect); |
| 30745 | } |
| 30746 | if( zErrMsg ){ |
| 30747 | shellEmitError(zErrMsg); |
| @@ -31072,19 +30869,20 @@ | |
| 30869 | |
| 30870 | /* .session filter GLOB .... |
| 30871 | ** Set a list of GLOB patterns of table names to be excluded. |
| 30872 | */ |
| 30873 | if( cli_strcmp(azCmd[0], "filter")==0 ){ |
| 30874 | int ii; |
| 30875 | i64 nByte; |
| 30876 | if( nCmd<2 ) goto session_syntax_error; |
| 30877 | if( pAuxDb->nSession ){ |
| 30878 | for(ii=0; ii<pSession->nFilter; ii++){ |
| 30879 | sqlite3_free(pSession->azFilter[ii]); |
| 30880 | } |
| 30881 | sqlite3_free(pSession->azFilter); |
| 30882 | nByte = sizeof(pSession->azFilter[0])*(nCmd-1); |
| 30883 | pSession->azFilter = sqlite3_malloc64( nByte ); |
| 30884 | shell_check_oom( pSession->azFilter ); |
| 30885 | for(ii=1; ii<nCmd; ii++){ |
| 30886 | char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); |
| 30887 | shell_check_oom(x); |
| 30888 | } |
| @@ -31264,26 +31062,26 @@ | |
| 31062 | sqlite3_fprintf(p->out, "%s\n", zSql); |
| 31063 | }else |
| 31064 | if( cli_strcmp(zOp,"run")==0 ){ |
| 31065 | char *zErrMsg = 0; |
| 31066 | str.n = 0; |
| 31067 | str.zTxt[0] = 0; |
| 31068 | rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); |
| 31069 | nTest++; |
| 31070 | if( bVerbose ){ |
| 31071 | sqlite3_fprintf(p->out, "Result: %s\n", str.zTxt); |
| 31072 | } |
| 31073 | if( rc || zErrMsg ){ |
| 31074 | nErr++; |
| 31075 | rc = 1; |
| 31076 | sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg); |
| 31077 | sqlite3_free(zErrMsg); |
| 31078 | }else if( cli_strcmp(zAns,str.zTxt)!=0 ){ |
| 31079 | nErr++; |
| 31080 | rc = 1; |
| 31081 | sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns); |
| 31082 | sqlite3_fprintf(p->out, "%d: Got: [%s]\n", tno, str.zTxt); |
| 31083 | } |
| 31084 | } |
| 31085 | else{ |
| 31086 | sqlite3_fprintf(stderr, |
| 31087 | "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); |
| @@ -31395,11 +31193,11 @@ | |
| 31193 | appendText(&sQuery, "SELECT * FROM ", 0); |
| 31194 | appendText(&sQuery, zTab, 0); |
| 31195 | appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0); |
| 31196 | } |
| 31197 | appendText(&sSql, zSep, 0); |
| 31198 | appendText(&sSql, sQuery.zTxt, '\''); |
| 31199 | sQuery.n = 0; |
| 31200 | appendText(&sSql, ",", 0); |
| 31201 | appendText(&sSql, zTab, '\''); |
| 31202 | zSep = "),("; |
| 31203 | } |
| @@ -31407,17 +31205,17 @@ | |
| 31205 | if( bSeparate ){ |
| 31206 | zSql = sqlite3_mprintf( |
| 31207 | "%s))" |
| 31208 | " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label" |
| 31209 | " FROM [sha3sum$query]", |
| 31210 | sSql.zTxt, iSize); |
| 31211 | }else{ |
| 31212 | zSql = sqlite3_mprintf( |
| 31213 | "%s))" |
| 31214 | " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash" |
| 31215 | " FROM [sha3sum$query]", |
| 31216 | sSql.zTxt, iSize); |
| 31217 | } |
| 31218 | shell_check_oom(zSql); |
| 31219 | freeText(&sQuery); |
| 31220 | freeText(&sSql); |
| 31221 | if( bDebug ){ |
| @@ -31613,11 +31411,11 @@ | |
| 31411 | goto meta_command_exit; |
| 31412 | } |
| 31413 | for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){ |
| 31414 | const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); |
| 31415 | if( zDbName==0 ) continue; |
| 31416 | if( s.zTxt && s.zTxt[0] ) appendText(&s, " UNION ALL ", 0); |
| 31417 | if( sqlite3_stricmp(zDbName, "main")==0 ){ |
| 31418 | appendText(&s, "SELECT name FROM ", 0); |
| 31419 | }else{ |
| 31420 | appendText(&s, "SELECT ", 0); |
| 31421 | appendText(&s, zDbName, '\''); |
| @@ -31635,11 +31433,11 @@ | |
| 31433 | } |
| 31434 | } |
| 31435 | rc = sqlite3_finalize(pStmt); |
| 31436 | if( rc==SQLITE_OK ){ |
| 31437 | appendText(&s, " ORDER BY 1", 0); |
| 31438 | rc = sqlite3_prepare_v2(p->db, s.zTxt, -1, &pStmt, 0); |
| 31439 | } |
| 31440 | freeText(&s); |
| 31441 | if( rc ) return shellDatabaseError(p->db); |
| 31442 | |
| 31443 | /* Run the SQL statement prepared by the above block. Store the results |
| @@ -31652,14 +31450,14 @@ | |
| 31450 | sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC); |
| 31451 | } |
| 31452 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 31453 | if( nRow>=nAlloc ){ |
| 31454 | char **azNew; |
| 31455 | sqlite3_int64 n2 = 2*(sqlite3_int64)nAlloc + 10; |
| 31456 | azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); |
| 31457 | shell_check_oom(azNew); |
| 31458 | nAlloc = (int)n2; |
| 31459 | azResult = azNew; |
| 31460 | } |
| 31461 | azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| 31462 | shell_check_oom(azResult[nRow]); |
| 31463 | nRow++; |
| @@ -31818,11 +31616,11 @@ | |
| 31616 | { 0x00000020, 1, "CoverIdxScan" }, |
| 31617 | { 0x00000040, 1, "OrderByIdxJoin" }, |
| 31618 | { 0x00000080, 1, "Transitive" }, |
| 31619 | { 0x00000100, 1, "OmitNoopJoin" }, |
| 31620 | { 0x00000200, 1, "CountOfView" }, |
| 31621 | { 0x00000400, 1, "CursorHints" }, |
| 31622 | { 0x00000800, 1, "Stat4" }, |
| 31623 | { 0x00001000, 1, "PushDown" }, |
| 31624 | { 0x00002000, 1, "SimplifyJoin" }, |
| 31625 | { 0x00004000, 1, "SkipScan" }, |
| 31626 | { 0x00008000, 1, "PropagateConst" }, |
| @@ -32365,11 +32163,14 @@ | |
| 32163 | p->nWidth = nArg-1; |
| 32164 | p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2); |
| 32165 | if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); |
| 32166 | if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; |
| 32167 | for(j=1; j<nArg; j++){ |
| 32168 | i64 w = integerValue(azArg[j]); |
| 32169 | if( w < -30000 ) w = -30000; |
| 32170 | if( w > +30000 ) w = +30000; |
| 32171 | p->colWidth[j-1] = (int)w; |
| 32172 | } |
| 32173 | }else |
| 32174 | |
| 32175 | { |
| 32176 | sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: " |
| @@ -32888,76 +32689,95 @@ | |
| 32689 | |
| 32690 | return home_dir; |
| 32691 | } |
| 32692 | |
| 32693 | /* |
| 32694 | ** On non-Windows platforms, look for: |
| 32695 | ** |
| 32696 | ** - ${zEnvVar}/${zBaseName} |
| 32697 | ** - ${HOME}/${zSubdir}/${zBaseName} |
| 32698 | ** |
| 32699 | ** $zEnvVar is intended to be the name of an XDG_... environment |
| 32700 | ** variable, e.g. XDG_CONFIG_HOME or XDG_STATE_HOME. If zEnvVar is |
| 32701 | ** NULL or getenv(zEnvVar) is NULL then fall back to the second |
| 32702 | ** option. If the selected option is not found in the filesystem, |
| 32703 | ** return 0. |
| 32704 | ** |
| 32705 | ** zSubdir may be NULL or empty, in which case ${HOME}/${zBaseName} |
| 32706 | ** becomes the fallback. |
| 32707 | ** |
| 32708 | ** Both zSubdir and zBaseName may contain subdirectory parts. zSubdir |
| 32709 | ** will conventionally be ".config" or ".local/state", which, not |
| 32710 | ** coincidentally, is the typical subdir of the corresponding XDG_... |
| 32711 | ** var with the XDG var's $HOME prefix. |
| 32712 | ** |
| 32713 | ** The returned string is obtained from sqlite3_malloc() and should be |
| 32714 | ** sqlite3_free()'d by the caller. |
| 32715 | */ |
| 32716 | static char *find_xdg_file(const char *zEnvVar, const char *zSubdir, |
| 32717 | const char *zBaseName){ |
| 32718 | #if defined(_WIN32) || defined(WIN32) || defined(_WIN32_WCE) \ |
| 32719 | || defined(__RTP__) || defined(_WRS_KERNEL) |
| 32720 | return 0; |
| 32721 | #else |
| 32722 | char *zConfigFile = 0; |
| 32723 | const char *zXdgDir; |
| 32724 | |
| 32725 | zXdgDir = zEnvVar ? getenv(zEnvVar) : 0; |
| 32726 | if( zXdgDir ){ |
| 32727 | zConfigFile = sqlite3_mprintf("%s/%s", zXdgDir, zBaseName); |
| 32728 | }else{ |
| 32729 | const char * zHome = find_home_dir(0); |
| 32730 | if( zHome==0 ) return 0; |
| 32731 | zConfigFile = (zSubdir && *zSubdir) |
| 32732 | ? sqlite3_mprintf("%s/%s/%s", zHome, zSubdir, zBaseName) |
| 32733 | : sqlite3_mprintf("%s/%s", zHome, zBaseName); |
| 32734 | } |
| 32735 | shell_check_oom(zConfigFile); |
| 32736 | if( access(zConfigFile,0)!=0 ){ |
| 32737 | sqlite3_free(zConfigFile); |
| 32738 | zConfigFile = 0; |
| 32739 | } |
| 32740 | return zConfigFile; |
| 32741 | #endif |
| 32742 | } |
| 32743 | |
| 32744 | /* |
| 32745 | ** Read input from the file sqliterc_override. If that parameter is |
| 32746 | ** NULL, take it from find_xdg_file(), if found, or fall back to |
| 32747 | ** ~/.sqliterc. |
| 32748 | ** |
| 32749 | ** Failure to read the config is only considered a failure if |
| 32750 | ** sqliterc_override is not NULL, in which case this function may emit |
| 32751 | ** a warning or, if ::bail_on_error is true, fail fatally if the file |
| 32752 | ** named by sqliterc_override is not found. |
| 32753 | */ |
| 32754 | static void process_sqliterc( |
| 32755 | ShellState *p, /* Configuration data */ |
| 32756 | const char *sqliterc_override /* Name of config file. NULL to use default */ |
| 32757 | ){ |
| 32758 | char *home_dir = NULL; |
| 32759 | char *sqliterc = (char*)sqliterc_override; |
| 32760 | FILE *inSaved = p->in; |
| 32761 | int savedLineno = p->lineno; |
| 32762 | |
| 32763 | if( sqliterc == NULL ){ |
| 32764 | sqliterc = find_xdg_file("XDG_CONFIG_HOME", |
| 32765 | ".config", |
| 32766 | "sqlite3/sqliterc"); |
| 32767 | } |
| 32768 | if( sqliterc == NULL ){ |
| 32769 | home_dir = find_home_dir(0); |
| 32770 | if( home_dir==0 ){ |
| 32771 | eputz("-- warning: cannot find home directory;" |
| 32772 | " cannot read ~/.sqliterc\n"); |
| 32773 | return; |
| 32774 | } |
| 32775 | sqliterc = sqlite3_mprintf("%s/.sqliterc",home_dir); |
| 32776 | shell_check_oom(sqliterc); |
| 32777 | } |
| 32778 | p->in = sqliterc ? sqlite3_fopen(sqliterc,"rb") : 0; |
| 32779 | if( p->in ){ |
| 32780 | if( stdin_is_interactive ){ |
| 32781 | sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc); |
| 32782 | } |
| 32783 | if( process_input(p) && bail_on_error ) exit(1); |
| @@ -32966,11 +32786,13 @@ | |
| 32786 | sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc); |
| 32787 | if( bail_on_error ) exit(1); |
| 32788 | } |
| 32789 | p->in = inSaved; |
| 32790 | p->lineno = savedLineno; |
| 32791 | if( sqliterc != sqliterc_override ){ |
| 32792 | sqlite3_free(sqliterc); |
| 32793 | } |
| 32794 | } |
| 32795 | |
| 32796 | /* |
| 32797 | ** Show available command line options |
| 32798 | */ |
| @@ -32997,10 +32819,11 @@ | |
| 32819 | #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) |
| 32820 | " -heap SIZE Size of heap for memsys3 or memsys5\n" |
| 32821 | #endif |
| 32822 | " -help show this message\n" |
| 32823 | " -html set output mode to HTML\n" |
| 32824 | " -ifexists only open if database already exists\n" |
| 32825 | " -interactive force interactive I/O\n" |
| 32826 | " -json set output mode to 'json'\n" |
| 32827 | " -line set output mode to 'line'\n" |
| 32828 | " -list set output mode to 'list'\n" |
| 32829 | " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" |
| @@ -33341,16 +33164,24 @@ | |
| 33164 | (void)cmdline_option_value(argc, argv, ++i); |
| 33165 | #endif |
| 33166 | }else if( cli_strcmp(z,"-pagecache")==0 ){ |
| 33167 | sqlite3_int64 n, sz; |
| 33168 | sz = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33169 | if( sz>65536 ) sz = 65536; |
| 33170 | if( sz<0 ) sz = 0; |
| 33171 | n = integerValue(cmdline_option_value(argc,argv,++i)); |
| 33172 | if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){ |
| 33173 | n = 0xffffffffffffLL/sz; |
| 33174 | } |
| 33175 | if( sz>0 && (sz & (sz-1))==0 ){ |
| 33176 | /* If SIZE is a power of two, round it up by the PCACHE_HDRSZ */ |
| 33177 | int szHdr = 0; |
| 33178 | sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &szHdr); |
| 33179 | sz += szHdr; |
| 33180 | sqlite3_fprintf(stdout, "Page cache size increased to %d to accommodate" |
| 33181 | " the %d-byte headers\n", (int)sz, szHdr); |
| 33182 | } |
| 33183 | verify_uninitialized(); |
| 33184 | sqlite3_config(SQLITE_CONFIG_PAGECACHE, |
| 33185 | (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n); |
| 33186 | data.shellFlgs |= SHFLG_Pagecache; |
| 33187 | }else if( cli_strcmp(z,"-lookaside")==0 ){ |
| @@ -33359,11 +33190,11 @@ | |
| 33190 | if( sz<0 ) sz = 0; |
| 33191 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33192 | if( n<0 ) n = 0; |
| 33193 | verify_uninitialized(); |
| 33194 | sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n); |
| 33195 | if( (i64)sz*(i64)n==0 ) data.shellFlgs &= ~SHFLG_Lookaside; |
| 33196 | }else if( cli_strcmp(z,"-threadsafe")==0 ){ |
| 33197 | int n; |
| 33198 | n = (int)integerValue(cmdline_option_value(argc,argv,++i)); |
| 33199 | verify_uninitialized(); |
| 33200 | switch( n ){ |
| @@ -33401,13 +33232,19 @@ | |
| 33232 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33233 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33234 | data.szMax = integerValue(argv[++i]); |
| 33235 | #endif |
| 33236 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33237 | data.openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); |
| 33238 | data.openFlags |= SQLITE_OPEN_READONLY; |
| 33239 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33240 | data.openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 33241 | }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ |
| 33242 | data.openFlags |= SQLITE_OPEN_EXCLUSIVE; |
| 33243 | }else if( cli_strcmp(z,"-ifexists")==0 ){ |
| 33244 | data.openFlags &= ~(SQLITE_OPEN_CREATE); |
| 33245 | if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; |
| 33246 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) |
| 33247 | }else if( cli_strncmp(z, "-A",2)==0 ){ |
| 33248 | /* All remaining command-line arguments are passed to the ".archive" |
| 33249 | ** command, so ignore them */ |
| 33250 | break; |
| @@ -33557,13 +33394,19 @@ | |
| 33394 | data.openMode = SHELL_OPEN_DESERIALIZE; |
| 33395 | }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){ |
| 33396 | data.szMax = integerValue(argv[++i]); |
| 33397 | #endif |
| 33398 | }else if( cli_strcmp(z,"-readonly")==0 ){ |
| 33399 | data.openFlags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); |
| 33400 | data.openFlags |= SQLITE_OPEN_READONLY; |
| 33401 | }else if( cli_strcmp(z,"-nofollow")==0 ){ |
| 33402 | data.openFlags |= SQLITE_OPEN_NOFOLLOW; |
| 33403 | }else if( cli_strcmp(z,"-exclusive")==0 ){ /* UNDOCUMENTED */ |
| 33404 | data.openFlags |= SQLITE_OPEN_EXCLUSIVE; |
| 33405 | }else if( cli_strcmp(z,"-ifexists")==0 ){ |
| 33406 | data.openFlags &= ~(SQLITE_OPEN_CREATE); |
| 33407 | if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE; |
| 33408 | }else if( cli_strcmp(z,"-ascii")==0 ){ |
| 33409 | data.mode = MODE_Ascii; |
| 33410 | sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit); |
| 33411 | sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record); |
| 33412 | }else if( cli_strcmp(z,"-tabs")==0 ){ |
| @@ -33734,11 +33577,10 @@ | |
| 33577 | /* Run commands received from standard input |
| 33578 | */ |
| 33579 | if( stdin_is_interactive ){ |
| 33580 | char *zHome; |
| 33581 | char *zHistory; |
| 33582 | sqlite3_fprintf(stdout, |
| 33583 | "SQLite version %s %.19s\n" /*extra-version-info*/ |
| 33584 | "Enter \".help\" for usage hints.\n", |
| 33585 | sqlite3_libversion(), sqlite3_sourceid()); |
| 33586 | if( warnInmemoryDb ){ |
| @@ -33747,15 +33589,19 @@ | |
| 33589 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33590 | " persistent database.\n"); |
| 33591 | } |
| 33592 | zHistory = getenv("SQLITE_HISTORY"); |
| 33593 | if( zHistory ){ |
| 33594 | zHistory = sqlite3_mprintf("%s", zHistory); |
| 33595 | shell_check_oom(zHistory); |
| 33596 | }else{ |
| 33597 | zHistory = find_xdg_file("XDG_STATE_HOME", |
| 33598 | ".local/state", |
| 33599 | "sqlite_history"); |
| 33600 | if( 0==zHistory && (zHome = find_home_dir(0))!=0 ){ |
| 33601 | zHistory = sqlite3_mprintf("%s/.sqlite_history", zHome); |
| 33602 | shell_check_oom(zHistory); |
| 33603 | } |
| 33604 | } |
| 33605 | if( zHistory ){ shell_read_history(zHistory); } |
| 33606 | #if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION) |
| 33607 | rl_attempted_completion_function = readline_completion; |
| @@ -33767,21 +33613,21 @@ | |
| 33613 | data.in = 0; |
| 33614 | rc = process_input(&data); |
| 33615 | if( zHistory ){ |
| 33616 | shell_stifle_history(2000); |
| 33617 | shell_write_history(zHistory); |
| 33618 | sqlite3_free(zHistory); |
| 33619 | } |
| 33620 | }else{ |
| 33621 | data.in = stdin; |
| 33622 | rc = process_input(&data); |
| 33623 | } |
| 33624 | } |
| 33625 | #ifndef SQLITE_SHELL_FIDDLE |
| 33626 | /* In WASM mode we have to leave the db state in place so that |
| 33627 | ** client code can "push" SQL into it after this call returns. */ |
| 33628 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) |
| 33629 | if( data.expert.pExpert ){ |
| 33630 | expertFinish(&data, 1, 0); |
| 33631 | } |
| 33632 | #endif |
| 33633 | shell_main_exit: |
| 33634 |
+2521
-708
| --- extsrc/sqlite3.c | ||
| +++ extsrc/sqlite3.c | ||
| @@ -16,11 +16,11 @@ | ||
| 16 | 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | 19 | ** |
| 20 | 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | -** 9f184f8dfa5ef6d57e10376adc30e0060ced with changes in files: | |
| 21 | +** 5cbccab499bc3983aac1f57355552db607de with changes in files: | |
| 22 | 22 | ** |
| 23 | 23 | ** |
| 24 | 24 | */ |
| 25 | 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | 26 | #define SQLITE_CORE 1 |
| @@ -168,11 +168,13 @@ | ||
| 168 | 168 | #define SQLITE_OMIT_LOAD_EXTENSION 1 |
| 169 | 169 | #define SQLITE_ENABLE_LOCKING_STYLE 0 |
| 170 | 170 | #define HAVE_UTIME 1 |
| 171 | 171 | #else |
| 172 | 172 | /* This is not VxWorks. */ |
| 173 | -#define OS_VXWORKS 0 | |
| 173 | +#ifndef OS_VXWORKS | |
| 174 | +# define OS_VXWORKS 0 | |
| 175 | +#endif | |
| 174 | 176 | #define HAVE_FCHOWN 1 |
| 175 | 177 | #define HAVE_READLINK 1 |
| 176 | 178 | #define HAVE_LSTAT 1 |
| 177 | 179 | #endif /* defined(_WRS_KERNEL) */ |
| 178 | 180 | |
| @@ -465,11 +467,14 @@ | ||
| 465 | 467 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | 468 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | 469 | */ |
| 468 | 470 | #define SQLITE_VERSION "3.51.0" |
| 469 | 471 | #define SQLITE_VERSION_NUMBER 3051000 |
| 470 | -#define SQLITE_SOURCE_ID "2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839" | |
| 472 | +#define SQLITE_SOURCE_ID "2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70" | |
| 473 | +#define SQLITE_SCM_BRANCH "trunk" | |
| 474 | +#define SQLITE_SCM_TAGS "" | |
| 475 | +#define SQLITE_SCM_DATETIME "2025-10-15T10:52:45.276Z" | |
| 471 | 476 | |
| 472 | 477 | /* |
| 473 | 478 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | 479 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | 480 | ** |
| @@ -814,10 +819,13 @@ | ||
| 814 | 819 | ** [sqlite3_extended_errcode()]. |
| 815 | 820 | */ |
| 816 | 821 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 817 | 822 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 818 | 823 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 824 | +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) | |
| 825 | +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) | |
| 826 | +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) | |
| 819 | 827 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 820 | 828 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 821 | 829 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 822 | 830 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 823 | 831 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -848,10 +856,12 @@ | ||
| 848 | 856 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 849 | 857 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 850 | 858 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 851 | 859 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 852 | 860 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 861 | +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) | |
| 862 | +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) | |
| 853 | 863 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 854 | 864 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 855 | 865 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 856 | 866 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 857 | 867 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2652,21 +2662,24 @@ | ||
| 2652 | 2662 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2653 | 2663 | ** databases.)^ </dd> |
| 2654 | 2664 | ** |
| 2655 | 2665 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2656 | 2666 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2657 | -** <dd> ^This option is used to enable or disable the | |
| 2658 | -** [fts3_tokenizer()] function which is part of the | |
| 2659 | -** [FTS3] full-text search engine extension. | |
| 2660 | -** There must be two additional arguments. | |
| 2661 | -** The first argument is an integer which is 0 to disable fts3_tokenizer() or | |
| 2662 | -** positive to enable fts3_tokenizer() or negative to leave the setting | |
| 2663 | -** unchanged. | |
| 2664 | -** The second parameter is a pointer to an integer into which | |
| 2665 | -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled | |
| 2666 | -** following this call. The second parameter may be a NULL pointer, in | |
| 2667 | -** which case the new setting is not reported back. </dd> | |
| 2667 | +** <dd> ^This option is used to enable or disable using the | |
| 2668 | +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine | |
| 2669 | +** extension - without using bound parameters as the parameters. Doing so | |
| 2670 | +** is disabled by default. There must be two additional arguments. The first | |
| 2671 | +** argument is an integer. If it is passed 0, then using fts3_tokenizer() | |
| 2672 | +** without bound parameters is disabled. If it is passed a positive value, | |
| 2673 | +** then calling fts3_tokenizer without bound parameters is enabled. If it | |
| 2674 | +** is passed a negative value, this setting is not modified - this can be | |
| 2675 | +** used to query for the current setting. The second parameter is a pointer | |
| 2676 | +** to an integer into which is written 0 or 1 to indicate the current value | |
| 2677 | +** of this setting (after it is modified, if applicable). The second | |
| 2678 | +** parameter may be a NULL pointer, in which case the value of the setting | |
| 2679 | +** is not reported back. Refer to [FTS3] documentation for further details. | |
| 2680 | +** </dd> | |
| 2668 | 2681 | ** |
| 2669 | 2682 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2670 | 2683 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2671 | 2684 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2672 | 2685 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4512,10 +4525,38 @@ | ||
| 4512 | 4525 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4513 | 4526 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4514 | 4527 | SQLITE_API const char *sqlite3_errstr(int); |
| 4515 | 4528 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4516 | 4529 | |
| 4530 | +/* | |
| 4531 | +** CAPI3REF: Set Error Codes And Message | |
| 4532 | +** METHOD: sqlite3 | |
| 4533 | +** | |
| 4534 | +** Set the error code of the database handle passed as the first argument | |
| 4535 | +** to errcode, and the error message to a copy of nul-terminated string | |
| 4536 | +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to | |
| 4537 | +** the default message associated with the supplied error code. Subsequent | |
| 4538 | +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will | |
| 4539 | +** return the values set by this routine in place of what was previously | |
| 4540 | +** set by SQLite itself. | |
| 4541 | +** | |
| 4542 | +** This function returns SQLITE_OK if the error code and error message are | |
| 4543 | +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if | |
| 4544 | +** the database handle is NULL or invalid. | |
| 4545 | +** | |
| 4546 | +** The error code and message set by this routine remains in effect until | |
| 4547 | +** they are changed, either by another call to this routine or until they are | |
| 4548 | +** changed to by SQLite itself to reflect the result of some subsquent | |
| 4549 | +** API call. | |
| 4550 | +** | |
| 4551 | +** This function is intended for use by SQLite extensions or wrappers. The | |
| 4552 | +** idea is that an extension or wrapper can use this routine to set error | |
| 4553 | +** messages and error codes and thus behave more like a core SQLite | |
| 4554 | +** feature from the point of view of an application. | |
| 4555 | +*/ | |
| 4556 | +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); | |
| 4557 | + | |
| 4517 | 4558 | /* |
| 4518 | 4559 | ** CAPI3REF: Prepared Statement Object |
| 4519 | 4560 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4520 | 4561 | ** |
| 4521 | 4562 | ** An instance of this object represents a single SQL statement that |
| @@ -6522,10 +6563,11 @@ | ||
| 6522 | 6563 | ** to be attached to [database connection] D using name N. Subsequent |
| 6523 | 6564 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6524 | 6565 | ** or a NULL pointer if there were no prior calls to |
| 6525 | 6566 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6526 | 6567 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6568 | +** It returns 0 on success and SQLITE_NOMEM on allocation failure. | |
| 6527 | 6569 | ** |
| 6528 | 6570 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6529 | 6571 | ** argument P on the first of the following occurrences: |
| 6530 | 6572 | ** <ul> |
| 6531 | 6573 | ** <li> An out-of-memory error occurs during the call to |
| @@ -9197,14 +9239,23 @@ | ||
| 9197 | 9239 | ** the resetFlg is true, then the highest instantaneous value is |
| 9198 | 9240 | ** reset back down to the current value. |
| 9199 | 9241 | ** |
| 9200 | 9242 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 9201 | 9243 | ** non-zero [error code] on failure. |
| 9244 | +** | |
| 9245 | +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same | |
| 9246 | +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H | |
| 9247 | +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead | |
| 9248 | +** of pointers to 32-bit integers, which allows larger status values | |
| 9249 | +** to be returned. If a status value exceeds 2,147,483,647 then | |
| 9250 | +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() | |
| 9251 | +** will return the full value. | |
| 9202 | 9252 | ** |
| 9203 | 9253 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 9204 | 9254 | */ |
| 9205 | 9255 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 9256 | +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); | |
| 9206 | 9257 | |
| 9207 | 9258 | /* |
| 9208 | 9259 | ** CAPI3REF: Status Parameters for database connections |
| 9209 | 9260 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 9210 | 9261 | ** |
| @@ -9297,10 +9348,14 @@ | ||
| 9297 | 9348 | ** database file in rollback mode databases. Any pages written as part of |
| 9298 | 9349 | ** transaction rollback or database recovery operations are not included. |
| 9299 | 9350 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 9300 | 9351 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 9301 | 9352 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 9353 | +** <p> | |
| 9354 | +** ^(There is overlap between the quantities measured by this parameter | |
| 9355 | +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. | |
| 9356 | +** Resetting one will reduce the other.)^ | |
| 9302 | 9357 | ** </dd> |
| 9303 | 9358 | ** |
| 9304 | 9359 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 9305 | 9360 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 9306 | 9361 | ** been written to disk in the middle of a transaction due to the page |
| @@ -9312,10 +9367,22 @@ | ||
| 9312 | 9367 | ** |
| 9313 | 9368 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 9314 | 9369 | ** <dd>This parameter returns zero for the current value if and only if |
| 9315 | 9370 | ** all foreign key constraints (deferred or immediate) have been |
| 9316 | 9371 | ** resolved.)^ ^The highwater mark is always 0. |
| 9372 | +** | |
| 9373 | +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> | |
| 9374 | +** <dd>^(This parameter returns the number of bytes written to temporary | |
| 9375 | +** files on disk that could have been kept in memory had sufficient memory | |
| 9376 | +** been available. This value includes writes to intermediate tables that | |
| 9377 | +** are part of complex queries, external sorts that spill to disk, and | |
| 9378 | +** writes to TEMP tables.)^ | |
| 9379 | +** ^The highwater mark is always 0. | |
| 9380 | +** <p> | |
| 9381 | +** ^(There is overlap between the quantities measured by this parameter | |
| 9382 | +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. | |
| 9383 | +** Resetting one will reduce the other.)^ | |
| 9317 | 9384 | ** </dd> |
| 9318 | 9385 | ** </dl> |
| 9319 | 9386 | */ |
| 9320 | 9387 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9321 | 9388 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9328,11 +9395,12 @@ | ||
| 9328 | 9395 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9329 | 9396 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9330 | 9397 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9331 | 9398 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9332 | 9399 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9333 | -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ | |
| 9400 | +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 | |
| 9401 | +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ | |
| 9334 | 9402 | |
| 9335 | 9403 | |
| 9336 | 9404 | /* |
| 9337 | 9405 | ** CAPI3REF: Prepared Statement Status |
| 9338 | 9406 | ** METHOD: sqlite3_stmt |
| @@ -10093,25 +10161,38 @@ | ||
| 10093 | 10161 | ** ^The third parameter is the name of the database that was written to - |
| 10094 | 10162 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 10095 | 10163 | ** is the number of pages currently in the write-ahead log file, |
| 10096 | 10164 | ** including those that were just committed. |
| 10097 | 10165 | ** |
| 10098 | -** The callback function should normally return [SQLITE_OK]. ^If an error | |
| 10166 | +** ^The callback function should normally return [SQLITE_OK]. ^If an error | |
| 10099 | 10167 | ** code is returned, that error will propagate back up through the |
| 10100 | 10168 | ** SQLite code base to cause the statement that provoked the callback |
| 10101 | 10169 | ** to report an error, though the commit will have still occurred. If the |
| 10102 | 10170 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 10103 | 10171 | ** that does not correspond to any valid SQLite error code, the results |
| 10104 | 10172 | ** are undefined. |
| 10105 | 10173 | ** |
| 10106 | -** A single database handle may have at most a single write-ahead log callback | |
| 10107 | -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any | |
| 10108 | -** previously registered write-ahead log callback. ^The return value is | |
| 10109 | -** a copy of the third parameter from the previous call, if any, or 0. | |
| 10110 | -** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the | |
| 10111 | -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will | |
| 10112 | -** overwrite any prior [sqlite3_wal_hook()] settings. | |
| 10174 | +** ^A single database handle may have at most a single write-ahead log | |
| 10175 | +** callback registered at one time. ^Calling [sqlite3_wal_hook()] | |
| 10176 | +** replaces the default behavior or previously registered write-ahead | |
| 10177 | +** log callback. | |
| 10178 | +** | |
| 10179 | +** ^The return value is a copy of the third parameter from the | |
| 10180 | +** previous call, if any, or 0. | |
| 10181 | +** | |
| 10182 | +** ^The [sqlite3_wal_autocheckpoint()] interface and the | |
| 10183 | +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and | |
| 10184 | +** will overwrite any prior [sqlite3_wal_hook()] settings. | |
| 10185 | +** | |
| 10186 | +** ^If a write-ahead log callback is set using this function then | |
| 10187 | +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] | |
| 10188 | +** should be invoked periodically to keep the write-ahead log file | |
| 10189 | +** from growing without bound. | |
| 10190 | +** | |
| 10191 | +** ^Passing a NULL pointer for the callback disables automatic | |
| 10192 | +** checkpointing entirely. To re-enable the default behavior, call | |
| 10193 | +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. | |
| 10113 | 10194 | */ |
| 10114 | 10195 | SQLITE_API void *sqlite3_wal_hook( |
| 10115 | 10196 | sqlite3*, |
| 10116 | 10197 | int(*)(void *,sqlite3*,const char*,int), |
| 10117 | 10198 | void* |
| @@ -10124,11 +10205,11 @@ | ||
| 10124 | 10205 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 10125 | 10206 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 10126 | 10207 | ** to automatically [checkpoint] |
| 10127 | 10208 | ** after committing a transaction if there are N or |
| 10128 | 10209 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 10129 | -** a negative value as the nFrame parameter disables automatic | |
| 10210 | +** a negative value as the N parameter disables automatic | |
| 10130 | 10211 | ** checkpoints entirely. |
| 10131 | 10212 | ** |
| 10132 | 10213 | ** ^The callback registered by this function replaces any existing callback |
| 10133 | 10214 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 10134 | 10215 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -10140,13 +10221,14 @@ | ||
| 10140 | 10221 | ** ^Checkpoints initiated by this mechanism are |
| 10141 | 10222 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 10142 | 10223 | ** |
| 10143 | 10224 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 10144 | 10225 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 10145 | -** pages. The use of this interface | |
| 10146 | -** is only necessary if the default setting is found to be suboptimal | |
| 10147 | -** for a particular application. | |
| 10226 | +** pages. | |
| 10227 | +** | |
| 10228 | +** ^The use of this interface is only necessary if the default setting | |
| 10229 | +** is found to be suboptimal for a particular application. | |
| 10148 | 10230 | */ |
| 10149 | 10231 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 10150 | 10232 | |
| 10151 | 10233 | /* |
| 10152 | 10234 | ** CAPI3REF: Checkpoint a database |
| @@ -10207,10 +10289,15 @@ | ||
| 10207 | 10289 | ** |
| 10208 | 10290 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 10209 | 10291 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 10210 | 10292 | ** addition that it also truncates the log file to zero bytes just prior |
| 10211 | 10293 | ** to a successful return. |
| 10294 | +** | |
| 10295 | +** <dt>SQLITE_CHECKPOINT_NOOP<dd> | |
| 10296 | +** ^This mode always checkpoints zero frames. The only reason to invoke | |
| 10297 | +** a NOOP checkpoint is to access the values returned by | |
| 10298 | +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. | |
| 10212 | 10299 | ** </dl> |
| 10213 | 10300 | ** |
| 10214 | 10301 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 10215 | 10302 | ** the log file or to -1 if the checkpoint could not run because |
| 10216 | 10303 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -10277,10 +10364,11 @@ | ||
| 10277 | 10364 | ** These constants define all valid values for the "checkpoint mode" passed |
| 10278 | 10365 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 10279 | 10366 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 10280 | 10367 | ** meaning of each of these checkpoint modes. |
| 10281 | 10368 | */ |
| 10369 | +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ | |
| 10282 | 10370 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 10283 | 10371 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 10284 | 10372 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 10285 | 10373 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 10286 | 10374 | |
| @@ -11104,11 +11192,11 @@ | ||
| 11104 | 11192 | ** to avoid a memory leak. |
| 11105 | 11193 | ** |
| 11106 | 11194 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 11107 | 11195 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11108 | 11196 | */ |
| 11109 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( | |
| 11197 | +SQLITE_API int sqlite3_snapshot_get( | |
| 11110 | 11198 | sqlite3 *db, |
| 11111 | 11199 | const char *zSchema, |
| 11112 | 11200 | sqlite3_snapshot **ppSnapshot |
| 11113 | 11201 | ); |
| 11114 | 11202 | |
| @@ -11153,11 +11241,11 @@ | ||
| 11153 | 11241 | ** database connection in order to make it ready to use snapshots.) |
| 11154 | 11242 | ** |
| 11155 | 11243 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 11156 | 11244 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11157 | 11245 | */ |
| 11158 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( | |
| 11246 | +SQLITE_API int sqlite3_snapshot_open( | |
| 11159 | 11247 | sqlite3 *db, |
| 11160 | 11248 | const char *zSchema, |
| 11161 | 11249 | sqlite3_snapshot *pSnapshot |
| 11162 | 11250 | ); |
| 11163 | 11251 | |
| @@ -11170,11 +11258,11 @@ | ||
| 11170 | 11258 | ** using this routine to avoid a memory leak. |
| 11171 | 11259 | ** |
| 11172 | 11260 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 11173 | 11261 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11174 | 11262 | */ |
| 11175 | -SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); | |
| 11263 | +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); | |
| 11176 | 11264 | |
| 11177 | 11265 | /* |
| 11178 | 11266 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 11179 | 11267 | ** METHOD: sqlite3_snapshot |
| 11180 | 11268 | ** |
| @@ -11197,11 +11285,11 @@ | ||
| 11197 | 11285 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 11198 | 11286 | ** |
| 11199 | 11287 | ** This interface is only available if SQLite is compiled with the |
| 11200 | 11288 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11201 | 11289 | */ |
| 11202 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( | |
| 11290 | +SQLITE_API int sqlite3_snapshot_cmp( | |
| 11203 | 11291 | sqlite3_snapshot *p1, |
| 11204 | 11292 | sqlite3_snapshot *p2 |
| 11205 | 11293 | ); |
| 11206 | 11294 | |
| 11207 | 11295 | /* |
| @@ -11225,11 +11313,11 @@ | ||
| 11225 | 11313 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 11226 | 11314 | ** |
| 11227 | 11315 | ** This interface is only available if SQLite is compiled with the |
| 11228 | 11316 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11229 | 11317 | */ |
| 11230 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); | |
| 11318 | +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); | |
| 11231 | 11319 | |
| 11232 | 11320 | /* |
| 11233 | 11321 | ** CAPI3REF: Serialize a database |
| 11234 | 11322 | ** |
| 11235 | 11323 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -11299,16 +11387,17 @@ | ||
| 11299 | 11387 | /* |
| 11300 | 11388 | ** CAPI3REF: Deserialize a database |
| 11301 | 11389 | ** |
| 11302 | 11390 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 11303 | 11391 | ** [database connection] D to disconnect from database S and then |
| 11304 | -** reopen S as an in-memory database based on the serialization contained | |
| 11305 | -** in P. The serialized database P is N bytes in size. M is the size of | |
| 11306 | -** the buffer P, which might be larger than N. If M is larger than N, and | |
| 11307 | -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is | |
| 11308 | -** permitted to add content to the in-memory database as long as the total | |
| 11309 | -** size does not exceed M bytes. | |
| 11392 | +** reopen S as an in-memory database based on the serialization | |
| 11393 | +** contained in P. If S is a NULL pointer, the main database is | |
| 11394 | +** used. The serialized database P is N bytes in size. M is the size | |
| 11395 | +** of the buffer P, which might be larger than N. If M is larger than | |
| 11396 | +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then | |
| 11397 | +** SQLite is permitted to add content to the in-memory database as | |
| 11398 | +** long as the total size does not exceed M bytes. | |
| 11310 | 11399 | ** |
| 11311 | 11400 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 11312 | 11401 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 11313 | 11402 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 11314 | 11403 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11371,10 +11460,56 @@ | ||
| 11371 | 11460 | */ |
| 11372 | 11461 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11373 | 11462 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11374 | 11463 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11375 | 11464 | |
| 11465 | +/* | |
| 11466 | +** CAPI3REF: Bind array values to the CARRAY table-valued function | |
| 11467 | +** | |
| 11468 | +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to | |
| 11469 | +** one of the first argument of the [carray() table-valued function]. The | |
| 11470 | +** S parameter is a pointer to the [prepared statement] that uses the carray() | |
| 11471 | +** functions. I is the parameter index to be bound. P is a pointer to the | |
| 11472 | +** array to be bound, and N is the number of eements in the array. The | |
| 11473 | +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], | |
| 11474 | +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to | |
| 11475 | +** indicate the datatype of the array being bound. The X argument is not a | |
| 11476 | +** NULL pointer, then SQLite will invoke the function X on the P parameter | |
| 11477 | +** after it has finished using P. | |
| 11478 | +*/ | |
| 11479 | +SQLITE_API SQLITE_API int sqlite3_carray_bind( | |
| 11480 | + sqlite3_stmt *pStmt, /* Statement to be bound */ | |
| 11481 | + int i, /* Parameter index */ | |
| 11482 | + void *aData, /* Pointer to array data */ | |
| 11483 | + int nData, /* Number of data elements */ | |
| 11484 | + int mFlags, /* CARRAY flags */ | |
| 11485 | + void (*xDel)(void*) /* Destructor for aData */ | |
| 11486 | +); | |
| 11487 | + | |
| 11488 | +/* | |
| 11489 | +** CAPI3REF: Datatypes for the CARRAY table-valued funtion | |
| 11490 | +** | |
| 11491 | +** The fifth argument to the [sqlite3_carray_bind()] interface musts be | |
| 11492 | +** one of the following constants, to specify the datatype of the array | |
| 11493 | +** that is being bound into the [carray table-valued function]. | |
| 11494 | +*/ | |
| 11495 | +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ | |
| 11496 | +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ | |
| 11497 | +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ | |
| 11498 | +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ | |
| 11499 | +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ | |
| 11500 | + | |
| 11501 | +/* | |
| 11502 | +** Versions of the above #defines that omit the initial SQLITE_, for | |
| 11503 | +** legacy compatibility. | |
| 11504 | +*/ | |
| 11505 | +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ | |
| 11506 | +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ | |
| 11507 | +#define CARRAY_DOUBLE 2 /* Data is doubles */ | |
| 11508 | +#define CARRAY_TEXT 3 /* Data is char* */ | |
| 11509 | +#define CARRAY_BLOB 4 /* Data is struct iovec */ | |
| 11510 | + | |
| 11376 | 11511 | /* |
| 11377 | 11512 | ** Undo the hack that converts floating point types to integer for |
| 11378 | 11513 | ** builds on processors without floating point support. |
| 11379 | 11514 | */ |
| 11380 | 11515 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12629,10 +12764,19 @@ | ||
| 12629 | 12764 | ** CAPI3REF: Apply A Changeset To A Database |
| 12630 | 12765 | ** |
| 12631 | 12766 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12632 | 12767 | ** update the "main" database attached to handle db with the changes found in |
| 12633 | 12768 | ** the changeset passed via the second and third arguments. |
| 12769 | +** | |
| 12770 | +** All changes made by these functions are enclosed in a savepoint transaction. | |
| 12771 | +** If any other error (aside from a constraint failure when attempting to | |
| 12772 | +** write to the target database) occurs, then the savepoint transaction is | |
| 12773 | +** rolled back, restoring the target database to its original state, and an | |
| 12774 | +** SQLite error code returned. Additionally, starting with version 3.51.0, | |
| 12775 | +** an error code and error message that may be accessed using the | |
| 12776 | +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database | |
| 12777 | +** handle. | |
| 12634 | 12778 | ** |
| 12635 | 12779 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12636 | 12780 | ** callback". This may be passed NULL, in which case all changes in the |
| 12637 | 12781 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12638 | 12782 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12767,16 +12911,10 @@ | ||
| 12767 | 12911 | ** It is safe to execute SQL statements, including those that write to the |
| 12768 | 12912 | ** table that the callback related to, from within the xConflict callback. |
| 12769 | 12913 | ** This can be used to further customize the application's conflict |
| 12770 | 12914 | ** resolution strategy. |
| 12771 | 12915 | ** |
| 12772 | -** All changes made by these functions are enclosed in a savepoint transaction. | |
| 12773 | -** If any other error (aside from a constraint failure when attempting to | |
| 12774 | -** write to the target database) occurs, then the savepoint transaction is | |
| 12775 | -** rolled back, restoring the target database to its original state, and an | |
| 12776 | -** SQLite error code returned. | |
| 12777 | -** | |
| 12778 | 12916 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12779 | 12917 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12780 | 12918 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12781 | 12919 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12782 | 12920 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| @@ -14351,11 +14489,11 @@ | ||
| 14351 | 14489 | |
| 14352 | 14490 | /* |
| 14353 | 14491 | ** Maximum number of pages in one database file. |
| 14354 | 14492 | ** |
| 14355 | 14493 | ** This is really just the default value for the max_page_count pragma. |
| 14356 | -** This value can be lowered (or raised) at run-time using that the | |
| 14494 | +** This value can be lowered (or raised) at run-time using the | |
| 14357 | 14495 | ** max_page_count macro. |
| 14358 | 14496 | */ |
| 14359 | 14497 | #ifndef SQLITE_MAX_PAGE_COUNT |
| 14360 | 14498 | # define SQLITE_MAX_PAGE_COUNT 0xfffffffe /* 4294967294 */ |
| 14361 | 14499 | #endif |
| @@ -15947,12 +16085,12 @@ | ||
| 15947 | 16085 | ** |
| 15948 | 16086 | ** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application |
| 15949 | 16087 | ** must provide its own VFS implementation together with sqlite3_os_init() |
| 15950 | 16088 | ** and sqlite3_os_end() routines. |
| 15951 | 16089 | */ |
| 15952 | -#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \ | |
| 15953 | - !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN) | |
| 16090 | +#if SQLITE_OS_KV+1<=1 && SQLITE_OS_OTHER+1<=1 && \ | |
| 16091 | + SQLITE_OS_WIN+1<=1 && SQLITE_OS_UNIX+1<=1 | |
| 15954 | 16092 | # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ |
| 15955 | 16093 | defined(__MINGW32__) || defined(__BORLANDC__) |
| 15956 | 16094 | # define SQLITE_OS_WIN 1 |
| 15957 | 16095 | # define SQLITE_OS_UNIX 0 |
| 15958 | 16096 | # else |
| @@ -17450,10 +17588,13 @@ | ||
| 17450 | 17588 | #ifndef SQLITE_OMIT_TRACE |
| 17451 | 17589 | SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); |
| 17452 | 17590 | #endif |
| 17453 | 17591 | SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); |
| 17454 | 17592 | SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); |
| 17593 | +#ifdef SQLITE_ENABLE_PERCENTILE | |
| 17594 | +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context*); | |
| 17595 | +#endif | |
| 17455 | 17596 | |
| 17456 | 17597 | SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*); |
| 17457 | 17598 | SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); |
| 17458 | 17599 | SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); |
| 17459 | 17600 | SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); |
| @@ -18122,11 +18263,11 @@ | ||
| 18122 | 18263 | struct sqlite3InitInfo { /* Information used during initialization */ |
| 18123 | 18264 | Pgno newTnum; /* Rootpage of table being initialized */ |
| 18124 | 18265 | u8 iDb; /* Which db file is being initialized */ |
| 18125 | 18266 | u8 busy; /* TRUE if currently initializing */ |
| 18126 | 18267 | unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ |
| 18127 | - unsigned imposterTable : 1; /* Building an imposter table */ | |
| 18268 | + unsigned imposterTable : 2; /* Building an imposter table */ | |
| 18128 | 18269 | unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ |
| 18129 | 18270 | const char **azInit; /* "type", "name", and "tbl_name" columns */ |
| 18130 | 18271 | } init; |
| 18131 | 18272 | int nVdbeActive; /* Number of VDBEs currently running */ |
| 18132 | 18273 | int nVdbeRead; /* Number of active VDBEs that read or write */ |
| @@ -18205,10 +18346,11 @@ | ||
| 18205 | 18346 | int nStatement; /* Number of nested statement-transactions */ |
| 18206 | 18347 | i64 nDeferredCons; /* Net deferred constraints this transaction. */ |
| 18207 | 18348 | i64 nDeferredImmCons; /* Net deferred immediate constraints */ |
| 18208 | 18349 | int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ |
| 18209 | 18350 | DbClientData *pDbData; /* sqlite3_set_clientdata() content */ |
| 18351 | + u64 nSpill; /* TEMP content spilled to disk */ | |
| 18210 | 18352 | #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY |
| 18211 | 18353 | /* The following variables are all protected by the STATIC_MAIN |
| 18212 | 18354 | ** mutex, not by sqlite3.mutex. They are used by code in notify.c. |
| 18213 | 18355 | ** |
| 18214 | 18356 | ** When X.pUnlockConnection==Y, that means that X is waiting for Y to |
| @@ -18915,10 +19057,11 @@ | ||
| 18915 | 19057 | #define TF_Shadow 0x00001000 /* True for a shadow table */ |
| 18916 | 19058 | #define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ |
| 18917 | 19059 | #define TF_Ephemeral 0x00004000 /* An ephemeral table */ |
| 18918 | 19060 | #define TF_Eponymous 0x00008000 /* An eponymous virtual table */ |
| 18919 | 19061 | #define TF_Strict 0x00010000 /* STRICT mode */ |
| 19062 | +#define TF_Imposter 0x00020000 /* An imposter table */ | |
| 18920 | 19063 | |
| 18921 | 19064 | /* |
| 18922 | 19065 | ** Allowed values for Table.eTabType |
| 18923 | 19066 | */ |
| 18924 | 19067 | #define TABTYP_NORM 0 /* Ordinary table */ |
| @@ -19503,10 +19646,11 @@ | ||
| 19503 | 19646 | AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ |
| 19504 | 19647 | union { |
| 19505 | 19648 | Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL |
| 19506 | 19649 | ** for a column of an index on an expression */ |
| 19507 | 19650 | Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */ |
| 19651 | + int nReg; /* TK_NULLS: Number of registers to NULL out */ | |
| 19508 | 19652 | struct { /* TK_IN, TK_SELECT, and TK_EXISTS */ |
| 19509 | 19653 | int iAddr; /* Subroutine entry address */ |
| 19510 | 19654 | int regReturn; /* Register used to hold return address */ |
| 19511 | 19655 | } sub; |
| 19512 | 19656 | } y; |
| @@ -20080,10 +20224,11 @@ | ||
| 20080 | 20224 | #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ |
| 20081 | 20225 | #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ |
| 20082 | 20226 | #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ |
| 20083 | 20227 | #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ |
| 20084 | 20228 | #define SF_Correlated 0x20000000 /* True if references the outer context */ |
| 20229 | +#define SF_OnToWhere 0x40000000 /* One or more ON clauses moved to WHERE */ | |
| 20085 | 20230 | |
| 20086 | 20231 | /* True if SrcItem X is a subquery that has SF_NestedFrom */ |
| 20087 | 20232 | #define IsNestedFrom(X) \ |
| 20088 | 20233 | ((X)->fg.isSubquery && \ |
| 20089 | 20234 | ((X)->u4.pSubq->pSelect->selFlags&SF_NestedFrom)!=0) |
| @@ -20833,10 +20978,11 @@ | ||
| 20833 | 20978 | struct Table *pTab; /* Table of generated column */ |
| 20834 | 20979 | struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ |
| 20835 | 20980 | SrcItem *pSrcItem; /* A single FROM clause item */ |
| 20836 | 20981 | DbFixer *pFix; /* See sqlite3FixSelect() */ |
| 20837 | 20982 | Mem *aMem; /* See sqlite3BtreeCursorHint() */ |
| 20983 | + struct CheckOnCtx *pCheckOnCtx; /* See selectCheckOnClauses() */ | |
| 20838 | 20984 | } u; |
| 20839 | 20985 | }; |
| 20840 | 20986 | |
| 20841 | 20987 | /* |
| 20842 | 20988 | ** The following structure contains information used by the sqliteFix... |
| @@ -21540,10 +21686,11 @@ | ||
| 21540 | 21686 | SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); |
| 21541 | 21687 | #endif |
| 21542 | 21688 | SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); |
| 21543 | 21689 | SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); |
| 21544 | 21690 | SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); |
| 21691 | +SQLITE_PRIVATE void sqlite3ExprNullRegisterRange(Parse*, int, int); | |
| 21545 | 21692 | SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); |
| 21546 | 21693 | SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); |
| 21547 | 21694 | SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); |
| 21548 | 21695 | #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ |
| 21549 | 21696 | #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ |
| @@ -21636,16 +21783,20 @@ | ||
| 21636 | 21783 | SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); |
| 21637 | 21784 | SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); |
| 21638 | 21785 | SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); |
| 21639 | 21786 | SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); |
| 21640 | 21787 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 21641 | -SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*); | |
| 21788 | +SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3*,const char*); | |
| 21642 | 21789 | #endif |
| 21643 | 21790 | SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); |
| 21644 | 21791 | SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); |
| 21645 | 21792 | SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); |
| 21646 | 21793 | SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); |
| 21794 | + | |
| 21795 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) | |
| 21796 | +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3*); | |
| 21797 | +#endif | |
| 21647 | 21798 | |
| 21648 | 21799 | #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) |
| 21649 | 21800 | SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); |
| 21650 | 21801 | #endif |
| 21651 | 21802 | |
| @@ -22628,10 +22779,13 @@ | ||
| 22628 | 22779 | #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE |
| 22629 | 22780 | "ENABLE_BATCH_ATOMIC_WRITE", |
| 22630 | 22781 | #endif |
| 22631 | 22782 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 22632 | 22783 | "ENABLE_BYTECODE_VTAB", |
| 22784 | +#endif | |
| 22785 | +#ifdef SQLITE_ENABLE_CARRAY | |
| 22786 | + "ENABLE_CARRAY", | |
| 22633 | 22787 | #endif |
| 22634 | 22788 | #ifdef SQLITE_ENABLE_CEROD |
| 22635 | 22789 | "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), |
| 22636 | 22790 | #endif |
| 22637 | 22791 | #ifdef SQLITE_ENABLE_COLUMN_METADATA |
| @@ -22718,10 +22872,13 @@ | ||
| 22718 | 22872 | #ifdef SQLITE_ENABLE_ORDERED_SET_AGGREGATES |
| 22719 | 22873 | "ENABLE_ORDERED_SET_AGGREGATES", |
| 22720 | 22874 | #endif |
| 22721 | 22875 | #ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK |
| 22722 | 22876 | "ENABLE_OVERSIZE_CELL_CHECK", |
| 22877 | +#endif | |
| 22878 | +#ifdef SQLITE_ENABLE_PERCENTILE | |
| 22879 | + "ENABLE_PERCENTILE", | |
| 22723 | 22880 | #endif |
| 22724 | 22881 | #ifdef SQLITE_ENABLE_PREUPDATE_HOOK |
| 22725 | 22882 | "ENABLE_PREUPDATE_HOOK", |
| 22726 | 22883 | #endif |
| 22727 | 22884 | #ifdef SQLITE_ENABLE_QPSG |
| @@ -24202,11 +24359,14 @@ | ||
| 24202 | 24359 | Mem oldipk; /* Memory cell holding "old" IPK value */ |
| 24203 | 24360 | Mem *aNew; /* Array of new.* values */ |
| 24204 | 24361 | Table *pTab; /* Schema object being updated */ |
| 24205 | 24362 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 24206 | 24363 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 24207 | - u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ | |
| 24364 | + union { | |
| 24365 | + KeyInfo sKey; | |
| 24366 | + u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ | |
| 24367 | + } uKey; | |
| 24208 | 24368 | }; |
| 24209 | 24369 | |
| 24210 | 24370 | /* |
| 24211 | 24371 | ** An instance of this object is used to pass an vector of values into |
| 24212 | 24372 | ** OP_VFilter, the xFilter method of a virtual table. The vector is the |
| @@ -24366,13 +24526,15 @@ | ||
| 24366 | 24526 | SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*); |
| 24367 | 24527 | SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); |
| 24368 | 24528 | #endif |
| 24369 | 24529 | |
| 24370 | 24530 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 24371 | -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); | |
| 24531 | +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe*); | |
| 24532 | +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe*); | |
| 24372 | 24533 | #else |
| 24373 | -# define sqlite3VdbeCheckFk(p,i) 0 | |
| 24534 | +# define sqlite3VdbeCheckFkImmediate(p) 0 | |
| 24535 | +# define sqlite3VdbeCheckFkDeferred(p) 0 | |
| 24374 | 24536 | #endif |
| 24375 | 24537 | |
| 24376 | 24538 | #ifdef SQLITE_DEBUG |
| 24377 | 24539 | SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); |
| 24378 | 24540 | SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr); |
| @@ -24577,27 +24739,29 @@ | ||
| 24577 | 24739 | } |
| 24578 | 24740 | |
| 24579 | 24741 | /* |
| 24580 | 24742 | ** Query status information for a single database connection |
| 24581 | 24743 | */ |
| 24582 | -SQLITE_API int sqlite3_db_status( | |
| 24583 | - sqlite3 *db, /* The database connection whose status is desired */ | |
| 24584 | - int op, /* Status verb */ | |
| 24585 | - int *pCurrent, /* Write current value here */ | |
| 24586 | - int *pHighwater, /* Write high-water mark here */ | |
| 24587 | - int resetFlag /* Reset high-water mark if true */ | |
| 24744 | +SQLITE_API int sqlite3_db_status64( | |
| 24745 | + sqlite3 *db, /* The database connection whose status is desired */ | |
| 24746 | + int op, /* Status verb */ | |
| 24747 | + sqlite3_int64 *pCurrent, /* Write current value here */ | |
| 24748 | + sqlite3_int64 *pHighwtr, /* Write high-water mark here */ | |
| 24749 | + int resetFlag /* Reset high-water mark if true */ | |
| 24588 | 24750 | ){ |
| 24589 | 24751 | int rc = SQLITE_OK; /* Return code */ |
| 24590 | 24752 | #ifdef SQLITE_ENABLE_API_ARMOR |
| 24591 | - if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ | |
| 24753 | + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ | |
| 24592 | 24754 | return SQLITE_MISUSE_BKPT; |
| 24593 | 24755 | } |
| 24594 | 24756 | #endif |
| 24595 | 24757 | sqlite3_mutex_enter(db->mutex); |
| 24596 | 24758 | switch( op ){ |
| 24597 | 24759 | case SQLITE_DBSTATUS_LOOKASIDE_USED: { |
| 24598 | - *pCurrent = sqlite3LookasideUsed(db, pHighwater); | |
| 24760 | + int H = 0; | |
| 24761 | + *pCurrent = sqlite3LookasideUsed(db, &H); | |
| 24762 | + *pHighwtr = H; | |
| 24599 | 24763 | if( resetFlag ){ |
| 24600 | 24764 | LookasideSlot *p = db->lookaside.pFree; |
| 24601 | 24765 | if( p ){ |
| 24602 | 24766 | while( p->pNext ) p = p->pNext; |
| 24603 | 24767 | p->pNext = db->lookaside.pInit; |
| @@ -24624,11 +24788,11 @@ | ||
| 24624 | 24788 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE ); |
| 24625 | 24789 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL ); |
| 24626 | 24790 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); |
| 24627 | 24791 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); |
| 24628 | 24792 | *pCurrent = 0; |
| 24629 | - *pHighwater = (int)db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; | |
| 24793 | + *pHighwtr = db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; | |
| 24630 | 24794 | if( resetFlag ){ |
| 24631 | 24795 | db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; |
| 24632 | 24796 | } |
| 24633 | 24797 | break; |
| 24634 | 24798 | } |
| @@ -24638,11 +24802,11 @@ | ||
| 24638 | 24802 | ** by all pagers associated with the given database connection. The |
| 24639 | 24803 | ** highwater mark is meaningless and is returned as zero. |
| 24640 | 24804 | */ |
| 24641 | 24805 | case SQLITE_DBSTATUS_CACHE_USED_SHARED: |
| 24642 | 24806 | case SQLITE_DBSTATUS_CACHE_USED: { |
| 24643 | - int totalUsed = 0; | |
| 24807 | + sqlite3_int64 totalUsed = 0; | |
| 24644 | 24808 | int i; |
| 24645 | 24809 | sqlite3BtreeEnterAll(db); |
| 24646 | 24810 | for(i=0; i<db->nDb; i++){ |
| 24647 | 24811 | Btree *pBt = db->aDb[i].pBt; |
| 24648 | 24812 | if( pBt ){ |
| @@ -24654,22 +24818,22 @@ | ||
| 24654 | 24818 | totalUsed += nByte; |
| 24655 | 24819 | } |
| 24656 | 24820 | } |
| 24657 | 24821 | sqlite3BtreeLeaveAll(db); |
| 24658 | 24822 | *pCurrent = totalUsed; |
| 24659 | - *pHighwater = 0; | |
| 24823 | + *pHighwtr = 0; | |
| 24660 | 24824 | break; |
| 24661 | 24825 | } |
| 24662 | 24826 | |
| 24663 | 24827 | /* |
| 24664 | 24828 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24665 | 24829 | ** to store the schema for all databases (main, temp, and any ATTACHed |
| 24666 | - ** databases. *pHighwater is set to zero. | |
| 24830 | + ** databases. *pHighwtr is set to zero. | |
| 24667 | 24831 | */ |
| 24668 | 24832 | case SQLITE_DBSTATUS_SCHEMA_USED: { |
| 24669 | - int i; /* Used to iterate through schemas */ | |
| 24670 | - int nByte = 0; /* Used to accumulate return value */ | |
| 24833 | + int i; /* Used to iterate through schemas */ | |
| 24834 | + int nByte = 0; /* Used to accumulate return value */ | |
| 24671 | 24835 | |
| 24672 | 24836 | sqlite3BtreeEnterAll(db); |
| 24673 | 24837 | db->pnBytesFreed = &nByte; |
| 24674 | 24838 | assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); |
| 24675 | 24839 | db->lookaside.pEnd = db->lookaside.pStart; |
| @@ -24699,19 +24863,19 @@ | ||
| 24699 | 24863 | } |
| 24700 | 24864 | db->pnBytesFreed = 0; |
| 24701 | 24865 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24702 | 24866 | sqlite3BtreeLeaveAll(db); |
| 24703 | 24867 | |
| 24704 | - *pHighwater = 0; | |
| 24868 | + *pHighwtr = 0; | |
| 24705 | 24869 | *pCurrent = nByte; |
| 24706 | 24870 | break; |
| 24707 | 24871 | } |
| 24708 | 24872 | |
| 24709 | 24873 | /* |
| 24710 | 24874 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24711 | 24875 | ** to store all prepared statements. |
| 24712 | - ** *pHighwater is set to zero. | |
| 24876 | + ** *pHighwtr is set to zero. | |
| 24713 | 24877 | */ |
| 24714 | 24878 | case SQLITE_DBSTATUS_STMT_USED: { |
| 24715 | 24879 | struct Vdbe *pVdbe; /* Used to iterate through VMs */ |
| 24716 | 24880 | int nByte = 0; /* Used to accumulate return value */ |
| 24717 | 24881 | |
| @@ -24722,19 +24886,19 @@ | ||
| 24722 | 24886 | sqlite3VdbeDelete(pVdbe); |
| 24723 | 24887 | } |
| 24724 | 24888 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24725 | 24889 | db->pnBytesFreed = 0; |
| 24726 | 24890 | |
| 24727 | - *pHighwater = 0; /* IMP: R-64479-57858 */ | |
| 24891 | + *pHighwtr = 0; /* IMP: R-64479-57858 */ | |
| 24728 | 24892 | *pCurrent = nByte; |
| 24729 | 24893 | |
| 24730 | 24894 | break; |
| 24731 | 24895 | } |
| 24732 | 24896 | |
| 24733 | 24897 | /* |
| 24734 | 24898 | ** Set *pCurrent to the total cache hits or misses encountered by all |
| 24735 | - ** pagers the database handle is connected to. *pHighwater is always set | |
| 24899 | + ** pagers the database handle is connected to. *pHighwtr is always set | |
| 24736 | 24900 | ** to zero. |
| 24737 | 24901 | */ |
| 24738 | 24902 | case SQLITE_DBSTATUS_CACHE_SPILL: |
| 24739 | 24903 | op = SQLITE_DBSTATUS_CACHE_WRITE+1; |
| 24740 | 24904 | /* no break */ deliberate_fall_through |
| @@ -24750,23 +24914,43 @@ | ||
| 24750 | 24914 | if( db->aDb[i].pBt ){ |
| 24751 | 24915 | Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); |
| 24752 | 24916 | sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); |
| 24753 | 24917 | } |
| 24754 | 24918 | } |
| 24755 | - *pHighwater = 0; /* IMP: R-42420-56072 */ | |
| 24919 | + *pHighwtr = 0; /* IMP: R-42420-56072 */ | |
| 24756 | 24920 | /* IMP: R-54100-20147 */ |
| 24757 | 24921 | /* IMP: R-29431-39229 */ |
| 24758 | - *pCurrent = (int)nRet & 0x7fffffff; | |
| 24922 | + *pCurrent = nRet; | |
| 24923 | + break; | |
| 24924 | + } | |
| 24925 | + | |
| 24926 | + /* Set *pCurrent to the number of bytes that the db database connection | |
| 24927 | + ** has spilled to the filesystem in temporary files that could have been | |
| 24928 | + ** stored in memory, had sufficient memory been available. | |
| 24929 | + ** The *pHighwater is always set to zero. | |
| 24930 | + */ | |
| 24931 | + case SQLITE_DBSTATUS_TEMPBUF_SPILL: { | |
| 24932 | + u64 nRet = 0; | |
| 24933 | + if( db->aDb[1].pBt ){ | |
| 24934 | + Pager *pPager = sqlite3BtreePager(db->aDb[1].pBt); | |
| 24935 | + sqlite3PagerCacheStat(pPager, SQLITE_DBSTATUS_CACHE_WRITE, | |
| 24936 | + resetFlag, &nRet); | |
| 24937 | + nRet *= sqlite3BtreeGetPageSize(db->aDb[1].pBt); | |
| 24938 | + } | |
| 24939 | + nRet += db->nSpill; | |
| 24940 | + if( resetFlag ) db->nSpill = 0; | |
| 24941 | + *pHighwtr = 0; | |
| 24942 | + *pCurrent = nRet; | |
| 24759 | 24943 | break; |
| 24760 | 24944 | } |
| 24761 | 24945 | |
| 24762 | 24946 | /* Set *pCurrent to non-zero if there are unresolved deferred foreign |
| 24763 | 24947 | ** key constraints. Set *pCurrent to zero if all foreign key constraints |
| 24764 | - ** have been satisfied. The *pHighwater is always set to zero. | |
| 24948 | + ** have been satisfied. The *pHighwtr is always set to zero. | |
| 24765 | 24949 | */ |
| 24766 | 24950 | case SQLITE_DBSTATUS_DEFERRED_FKS: { |
| 24767 | - *pHighwater = 0; /* IMP: R-11967-56545 */ | |
| 24951 | + *pHighwtr = 0; /* IMP: R-11967-56545 */ | |
| 24768 | 24952 | *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; |
| 24769 | 24953 | break; |
| 24770 | 24954 | } |
| 24771 | 24955 | |
| 24772 | 24956 | default: { |
| @@ -24774,10 +24958,35 @@ | ||
| 24774 | 24958 | } |
| 24775 | 24959 | } |
| 24776 | 24960 | sqlite3_mutex_leave(db->mutex); |
| 24777 | 24961 | return rc; |
| 24778 | 24962 | } |
| 24963 | + | |
| 24964 | +/* | |
| 24965 | +** 32-bit variant of sqlite3_db_status64() | |
| 24966 | +*/ | |
| 24967 | +SQLITE_API int sqlite3_db_status( | |
| 24968 | + sqlite3 *db, /* The database connection whose status is desired */ | |
| 24969 | + int op, /* Status verb */ | |
| 24970 | + int *pCurrent, /* Write current value here */ | |
| 24971 | + int *pHighwtr, /* Write high-water mark here */ | |
| 24972 | + int resetFlag /* Reset high-water mark if true */ | |
| 24973 | +){ | |
| 24974 | + sqlite3_int64 C = 0, H = 0; | |
| 24975 | + int rc; | |
| 24976 | +#ifdef SQLITE_ENABLE_API_ARMOR | |
| 24977 | + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ | |
| 24978 | + return SQLITE_MISUSE_BKPT; | |
| 24979 | + } | |
| 24980 | +#endif | |
| 24981 | + rc = sqlite3_db_status64(db, op, &C, &H, resetFlag); | |
| 24982 | + if( rc==0 ){ | |
| 24983 | + *pCurrent = C & 0x7fffffff; | |
| 24984 | + *pHighwtr = H & 0x7fffffff; | |
| 24985 | + } | |
| 24986 | + return rc; | |
| 24987 | +} | |
| 24779 | 24988 | |
| 24780 | 24989 | /************** End of status.c **********************************************/ |
| 24781 | 24990 | /************** Begin file date.c ********************************************/ |
| 24782 | 24991 | /* |
| 24783 | 24992 | ** 2003 October 31 |
| @@ -24967,10 +25176,14 @@ | ||
| 24967 | 25176 | if( getDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){ |
| 24968 | 25177 | return 1; |
| 24969 | 25178 | } |
| 24970 | 25179 | zDate += 5; |
| 24971 | 25180 | p->tz = sgn*(nMn + nHr*60); |
| 25181 | + if( p->tz==0 ){ /* Forum post 2025-09-17T10:12:14z */ | |
| 25182 | + p->isLocal = 0; | |
| 25183 | + p->isUtc = 1; | |
| 25184 | + } | |
| 24972 | 25185 | zulu_time: |
| 24973 | 25186 | while( sqlite3Isspace(*zDate) ){ zDate++; } |
| 24974 | 25187 | return *zDate!=0; |
| 24975 | 25188 | } |
| 24976 | 25189 | |
| @@ -26162,12 +26375,12 @@ | ||
| 26162 | 26375 | ** %j day of year 001-366 |
| 26163 | 26376 | ** %J ** julian day number |
| 26164 | 26377 | ** %l hour 1-12 (leading zero converted to space) |
| 26165 | 26378 | ** %m month 01-12 |
| 26166 | 26379 | ** %M minute 00-59 |
| 26167 | -** %p "am" or "pm" | |
| 26168 | -** %P "AM" or "PM" | |
| 26380 | +** %p "AM" or "PM" | |
| 26381 | +** %P "am" or "pm" | |
| 26169 | 26382 | ** %R time as HH:MM |
| 26170 | 26383 | ** %s seconds since 1970-01-01 |
| 26171 | 26384 | ** %S seconds 00-59 |
| 26172 | 26385 | ** %T time as HH:MM:SS |
| 26173 | 26386 | ** %u day of week 1-7 Monday==1, Sunday==7 |
| @@ -31770,56 +31983,74 @@ | ||
| 31770 | 31983 | etByte base; /* The base for radix conversion */ |
| 31771 | 31984 | etByte flags; /* One or more of FLAG_ constants below */ |
| 31772 | 31985 | etByte type; /* Conversion paradigm */ |
| 31773 | 31986 | etByte charset; /* Offset into aDigits[] of the digits string */ |
| 31774 | 31987 | etByte prefix; /* Offset into aPrefix[] of the prefix string */ |
| 31988 | + char iNxt; /* Next with same hash, or 0 for end of chain */ | |
| 31775 | 31989 | } et_info; |
| 31776 | 31990 | |
| 31777 | 31991 | /* |
| 31778 | 31992 | ** Allowed values for et_info.flags |
| 31779 | 31993 | */ |
| 31780 | 31994 | #define FLAG_SIGNED 1 /* True if the value to convert is signed */ |
| 31781 | 31995 | #define FLAG_STRING 4 /* Allow infinite precision */ |
| 31782 | 31996 | |
| 31997 | +/* | |
| 31998 | +** The table is searched by hash. In the case of %C where C is the character | |
| 31999 | +** and that character has ASCII value j, then the hash is j%23. | |
| 32000 | +** | |
| 32001 | +** The order of the entries in fmtinfo[] and the hash chain was entered | |
| 32002 | +** manually, but based on the output of the following TCL script: | |
| 32003 | +*/ | |
| 32004 | +#if 0 /***** Beginning of script ******/ | |
| 32005 | +foreach c {d s g z q Q w c o u x X f e E G i n % p T S r} { | |
| 32006 | + scan $c %c x | |
| 32007 | + set n($c) $x | |
| 32008 | +} | |
| 32009 | +set mx [llength [array names n]] | |
| 32010 | +puts "count: $mx" | |
| 31783 | 32011 | |
| 31784 | -/* | |
| 31785 | -** The following table is searched linearly, so it is good to put the | |
| 31786 | -** most frequently used conversion types first. | |
| 31787 | -*/ | |
| 32012 | +set mx 27 | |
| 32013 | +puts "*********** mx=$mx ************" | |
| 32014 | +for {set r 0} {$r<$mx} {incr r} { | |
| 32015 | + puts -nonewline [format %2d: $r] | |
| 32016 | + foreach c [array names n] { | |
| 32017 | + if {($n($c))%$mx==$r} {puts -nonewline " $c"} | |
| 32018 | + } | |
| 32019 | + puts "" | |
| 32020 | +} | |
| 32021 | +#endif /***** End of script ********/ | |
| 32022 | + | |
| 31788 | 32023 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 31789 | 32024 | static const char aPrefix[] = "-x0\000X0"; |
| 31790 | -static const et_info fmtinfo[] = { | |
| 31791 | - { 'd', 10, 1, etDECIMAL, 0, 0 }, | |
| 31792 | - { 's', 0, 4, etSTRING, 0, 0 }, | |
| 31793 | - { 'g', 0, 1, etGENERIC, 30, 0 }, | |
| 31794 | - { 'z', 0, 4, etDYNSTRING, 0, 0 }, | |
| 31795 | - { 'q', 0, 4, etESCAPE_q, 0, 0 }, | |
| 31796 | - { 'Q', 0, 4, etESCAPE_Q, 0, 0 }, | |
| 31797 | - { 'w', 0, 4, etESCAPE_w, 0, 0 }, | |
| 31798 | - { 'c', 0, 0, etCHARX, 0, 0 }, | |
| 31799 | - { 'o', 8, 0, etRADIX, 0, 2 }, | |
| 31800 | - { 'u', 10, 0, etDECIMAL, 0, 0 }, | |
| 31801 | - { 'x', 16, 0, etRADIX, 16, 1 }, | |
| 31802 | - { 'X', 16, 0, etRADIX, 0, 4 }, | |
| 31803 | -#ifndef SQLITE_OMIT_FLOATING_POINT | |
| 31804 | - { 'f', 0, 1, etFLOAT, 0, 0 }, | |
| 31805 | - { 'e', 0, 1, etEXP, 30, 0 }, | |
| 31806 | - { 'E', 0, 1, etEXP, 14, 0 }, | |
| 31807 | - { 'G', 0, 1, etGENERIC, 14, 0 }, | |
| 31808 | -#endif | |
| 31809 | - { 'i', 10, 1, etDECIMAL, 0, 0 }, | |
| 31810 | - { 'n', 0, 0, etSIZE, 0, 0 }, | |
| 31811 | - { '%', 0, 0, etPERCENT, 0, 0 }, | |
| 31812 | - { 'p', 16, 0, etPOINTER, 0, 1 }, | |
| 31813 | - | |
| 31814 | - /* All the rest are undocumented and are for internal use only */ | |
| 31815 | - { 'T', 0, 0, etTOKEN, 0, 0 }, | |
| 31816 | - { 'S', 0, 0, etSRCITEM, 0, 0 }, | |
| 31817 | - { 'r', 10, 1, etORDINAL, 0, 0 }, | |
| 32025 | +static const et_info fmtinfo[23] = { | |
| 32026 | + /* 0 */ { 's', 0, 4, etSTRING, 0, 0, 1 }, | |
| 32027 | + /* 1 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 0 */ | |
| 32028 | + /* 2 */ { 'u', 10, 0, etDECIMAL, 0, 0, 3 }, | |
| 32029 | + /* 3 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 2 */ | |
| 32030 | + /* 4 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 }, | |
| 32031 | + /* 5 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, | |
| 32032 | + /* 6 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, /* Hash: 7 */ | |
| 32033 | + /* 7 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 6 }, | |
| 32034 | + /* 8 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, | |
| 32035 | + /* 9 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, | |
| 32036 | + /* 10 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, | |
| 32037 | + /* 11 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, | |
| 32038 | + /* 12 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 0 }, | |
| 32039 | + /* 13 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, | |
| 32040 | + /* 14 */ { '%', 0, 0, etPERCENT, 0, 0, 16 }, | |
| 32041 | + /* 15 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, | |
| 32042 | + /* 16 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, /* Hash: 14 */ | |
| 32043 | + /* 17 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 19 */ | |
| 32044 | + /* 18 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, | |
| 32045 | + /* 19 */ { 'o', 8, 0, etRADIX, 0, 2, 17 }, | |
| 32046 | + /* 20 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 }, | |
| 32047 | + /* 21 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 }, | |
| 32048 | + /* 22 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 } | |
| 31818 | 32049 | }; |
| 31819 | 32050 | |
| 31820 | -/* Notes: | |
| 32051 | +/* Additional Notes: | |
| 31821 | 32052 | ** |
| 31822 | 32053 | ** %S Takes a pointer to SrcItem. Shows name or database.name |
| 31823 | 32054 | ** %!S Like %S but prefer the zName over the zAlias |
| 31824 | 32055 | */ |
| 31825 | 32056 | |
| @@ -31942,11 +32173,14 @@ | ||
| 31942 | 32173 | if( c!='%' ){ |
| 31943 | 32174 | bufpt = (char *)fmt; |
| 31944 | 32175 | #if HAVE_STRCHRNUL |
| 31945 | 32176 | fmt = strchrnul(fmt, '%'); |
| 31946 | 32177 | #else |
| 31947 | - do{ fmt++; }while( *fmt && *fmt != '%' ); | |
| 32178 | + fmt = strchr(fmt, '%'); | |
| 32179 | + if( fmt==0 ){ | |
| 32180 | + fmt = bufpt + strlen(bufpt); | |
| 32181 | + } | |
| 31948 | 32182 | #endif |
| 31949 | 32183 | sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); |
| 31950 | 32184 | if( *fmt==0 ) break; |
| 31951 | 32185 | } |
| 31952 | 32186 | if( (c=(*++fmt))==0 ){ |
| @@ -32056,19 +32290,36 @@ | ||
| 32056 | 32290 | } |
| 32057 | 32291 | } |
| 32058 | 32292 | }while( !done && (c=(*++fmt))!=0 ); |
| 32059 | 32293 | |
| 32060 | 32294 | /* Fetch the info entry for the field */ |
| 32295 | +#ifdef SQLITE_EBCDIC | |
| 32296 | + /* The hash table only works for ASCII. For EBCDIC, we need to do | |
| 32297 | + ** a linear search of the table */ | |
| 32061 | 32298 | infop = &fmtinfo[0]; |
| 32062 | 32299 | xtype = etINVALID; |
| 32063 | 32300 | for(idx=0; idx<ArraySize(fmtinfo); idx++){ |
| 32064 | 32301 | if( c==fmtinfo[idx].fmttype ){ |
| 32065 | 32302 | infop = &fmtinfo[idx]; |
| 32066 | 32303 | xtype = infop->type; |
| 32067 | 32304 | break; |
| 32068 | 32305 | } |
| 32069 | 32306 | } |
| 32307 | +#else | |
| 32308 | + /* Fast hash-table lookup */ | |
| 32309 | + assert( ArraySize(fmtinfo)==23 ); | |
| 32310 | + idx = ((unsigned)c) % 23; | |
| 32311 | + if( fmtinfo[idx].fmttype==c | |
| 32312 | + || fmtinfo[idx = fmtinfo[idx].iNxt].fmttype==c | |
| 32313 | + ){ | |
| 32314 | + infop = &fmtinfo[idx]; | |
| 32315 | + xtype = infop->type; | |
| 32316 | + }else{ | |
| 32317 | + infop = &fmtinfo[0]; | |
| 32318 | + xtype = etINVALID; | |
| 32319 | + } | |
| 32320 | +#endif | |
| 32070 | 32321 | |
| 32071 | 32322 | /* |
| 32072 | 32323 | ** At this point, variables are initialized as follows: |
| 32073 | 32324 | ** |
| 32074 | 32325 | ** flag_alternateform TRUE if a '#' is present. |
| @@ -32252,11 +32503,25 @@ | ||
| 32252 | 32503 | length = sqlite3Strlen30(bufpt); |
| 32253 | 32504 | break; |
| 32254 | 32505 | } |
| 32255 | 32506 | } |
| 32256 | 32507 | if( s.sign=='-' ){ |
| 32257 | - prefix = '-'; | |
| 32508 | + if( flag_alternateform | |
| 32509 | + && !flag_prefix | |
| 32510 | + && xtype==etFLOAT | |
| 32511 | + && s.iDP<=iRound | |
| 32512 | + ){ | |
| 32513 | + /* Suppress the minus sign if all of the following are true: | |
| 32514 | + ** * The value displayed is zero | |
| 32515 | + ** * The '#' flag is used | |
| 32516 | + ** * The '+' flag is not used, and | |
| 32517 | + ** * The format is %f | |
| 32518 | + */ | |
| 32519 | + prefix = 0; | |
| 32520 | + }else{ | |
| 32521 | + prefix = '-'; | |
| 32522 | + } | |
| 32258 | 32523 | }else{ |
| 32259 | 32524 | prefix = flag_prefix; |
| 32260 | 32525 | } |
| 32261 | 32526 | |
| 32262 | 32527 | exp = s.iDP-1; |
| @@ -33463,13 +33728,17 @@ | ||
| 33463 | 33728 | sqlite3StrAccumFinish(&x); |
| 33464 | 33729 | sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1); |
| 33465 | 33730 | n = 0; |
| 33466 | 33731 | if( pItem->fg.isSubquery ) n++; |
| 33467 | 33732 | if( pItem->fg.isTabFunc ) n++; |
| 33468 | - if( pItem->fg.isUsing ) n++; | |
| 33733 | + if( pItem->fg.isUsing || pItem->u3.pOn!=0 ) n++; | |
| 33469 | 33734 | if( pItem->fg.isUsing ){ |
| 33470 | 33735 | sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); |
| 33736 | + }else if( pItem->u3.pOn!=0 ){ | |
| 33737 | + sqlite3TreeViewItem(pView, "ON", (--n)>0); | |
| 33738 | + sqlite3TreeViewExpr(pView, pItem->u3.pOn, 0); | |
| 33739 | + sqlite3TreeViewPop(&pView); | |
| 33471 | 33740 | } |
| 33472 | 33741 | if( pItem->fg.isSubquery ){ |
| 33473 | 33742 | assert( n==1 ); |
| 33474 | 33743 | if( pItem->pSTab ){ |
| 33475 | 33744 | Table *pTab = pItem->pSTab; |
| @@ -38108,11 +38377,11 @@ | ||
| 38108 | 38377 | static int kvstorageDelete(const char*, const char *zKey); |
| 38109 | 38378 | static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); |
| 38110 | 38379 | #define KVSTORAGE_KEY_SZ 32 |
| 38111 | 38380 | |
| 38112 | 38381 | /* Expand the key name with an appropriate prefix and put the result |
| 38113 | -** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least | |
| 38382 | +** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least | |
| 38114 | 38383 | ** KVSTORAGE_KEY_SZ bytes. |
| 38115 | 38384 | */ |
| 38116 | 38385 | static void kvstorageMakeKey( |
| 38117 | 38386 | const char *zClass, |
| 38118 | 38387 | const char *zKeyIn, |
| @@ -38167,14 +38436,16 @@ | ||
| 38167 | 38436 | ** enough to hold it all. The value put into zBuf must always be zero |
| 38168 | 38437 | ** terminated, even if it gets truncated because nBuf is not large enough. |
| 38169 | 38438 | ** |
| 38170 | 38439 | ** Return the total number of bytes in the data, without truncation, and |
| 38171 | 38440 | ** not counting the final zero terminator. Return -1 if the key does |
| 38172 | -** not exist. | |
| 38441 | +** not exist or its key cannot be read. | |
| 38173 | 38442 | ** |
| 38174 | -** If nBuf<=0 then this routine simply returns the size of the data without | |
| 38175 | -** actually reading it. | |
| 38443 | +** If nBuf<=0 then this routine simply returns the size of the data | |
| 38444 | +** without actually reading it. Similarly, if nBuf==1 then it | |
| 38445 | +** zero-terminates zBuf at zBuf[0] and returns the size of the data | |
| 38446 | +** without reading it. | |
| 38176 | 38447 | */ |
| 38177 | 38448 | static int kvstorageRead( |
| 38178 | 38449 | const char *zClass, |
| 38179 | 38450 | const char *zKey, |
| 38180 | 38451 | char *zBuf, |
| @@ -38219,15 +38490,13 @@ | ||
| 38219 | 38490 | /* |
| 38220 | 38491 | ** An internal level of indirection which enables us to replace the |
| 38221 | 38492 | ** kvvfs i/o methods with JavaScript implementations in WASM builds. |
| 38222 | 38493 | ** Maintenance reminder: if this struct changes in any way, the JSON |
| 38223 | 38494 | ** rendering of its structure must be updated in |
| 38224 | -** sqlite3_wasm_enum_json(). There are no binary compatibility | |
| 38225 | -** concerns, so it does not need an iVersion member. This file is | |
| 38226 | -** necessarily always compiled together with sqlite3_wasm_enum_json(), | |
| 38227 | -** and JS code dynamically creates the mapping of members based on | |
| 38228 | -** that JSON description. | |
| 38495 | +** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary | |
| 38496 | +** compatibility concerns, so it does not need an iVersion | |
| 38497 | +** member. | |
| 38229 | 38498 | */ |
| 38230 | 38499 | typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; |
| 38231 | 38500 | struct sqlite3_kvvfs_methods { |
| 38232 | 38501 | int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); |
| 38233 | 38502 | int (*xWrite)(const char *zClass, const char *zKey, const char *zData); |
| @@ -38240,12 +38509,12 @@ | ||
| 38240 | 38509 | ** for JavaScript-side implementations in WASM builds. In such builds |
| 38241 | 38510 | ** it cannot be const, but in native builds it should be so that |
| 38242 | 38511 | ** the compiler can hopefully optimize this level of indirection out. |
| 38243 | 38512 | ** That said, kvvfs is intended primarily for use in WASM builds. |
| 38244 | 38513 | ** |
| 38245 | -** Note that this is not explicitly flagged as static because the | |
| 38246 | -** amalgamation build will tag it with SQLITE_PRIVATE. | |
| 38514 | +** This is not explicitly flagged as static because the amalgamation | |
| 38515 | +** build will tag it with SQLITE_PRIVATE. | |
| 38247 | 38516 | */ |
| 38248 | 38517 | #ifndef SQLITE_WASM |
| 38249 | 38518 | const |
| 38250 | 38519 | #endif |
| 38251 | 38520 | SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { |
| @@ -39414,14 +39683,15 @@ | ||
| 39414 | 39683 | #define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ |
| 39415 | 39684 | aSyscall[13].pCurrent) |
| 39416 | 39685 | |
| 39417 | 39686 | #if defined(HAVE_FCHMOD) |
| 39418 | 39687 | { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, |
| 39688 | +#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) | |
| 39419 | 39689 | #else |
| 39420 | 39690 | { "fchmod", (sqlite3_syscall_ptr)0, 0 }, |
| 39691 | +#define osFchmod(FID,MODE) 0 | |
| 39421 | 39692 | #endif |
| 39422 | -#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) | |
| 39423 | 39693 | |
| 39424 | 39694 | #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE |
| 39425 | 39695 | { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, |
| 39426 | 39696 | #else |
| 39427 | 39697 | { "fallocate", (sqlite3_syscall_ptr)0, 0 }, |
| @@ -39675,13 +39945,12 @@ | ||
| 39675 | 39945 | return fd; |
| 39676 | 39946 | } |
| 39677 | 39947 | |
| 39678 | 39948 | /* |
| 39679 | 39949 | ** Helper functions to obtain and relinquish the global mutex. The |
| 39680 | -** global mutex is used to protect the unixInodeInfo and | |
| 39681 | -** vxworksFileId objects used by this file, all of which may be | |
| 39682 | -** shared by multiple threads. | |
| 39950 | +** global mutex is used to protect the unixInodeInfo objects used by | |
| 39951 | +** this file, all of which may be shared by multiple threads. | |
| 39683 | 39952 | ** |
| 39684 | 39953 | ** Function unixMutexHeld() is used to assert() that the global mutex |
| 39685 | 39954 | ** is held when required. This function is only used as part of assert() |
| 39686 | 39955 | ** statements. e.g. |
| 39687 | 39956 | ** |
| @@ -39879,10 +40148,11 @@ | ||
| 39879 | 40148 | /* |
| 39880 | 40149 | ** All unique filenames are held on a linked list headed by this |
| 39881 | 40150 | ** variable: |
| 39882 | 40151 | */ |
| 39883 | 40152 | static struct vxworksFileId *vxworksFileList = 0; |
| 40153 | +static sqlite3_mutex *vxworksMutex = 0; | |
| 39884 | 40154 | |
| 39885 | 40155 | /* |
| 39886 | 40156 | ** Simplify a filename into its canonical form |
| 39887 | 40157 | ** by making the following changes: |
| 39888 | 40158 | ** |
| @@ -39944,47 +40214,47 @@ | ||
| 39944 | 40214 | |
| 39945 | 40215 | /* Search for an existing entry that matching the canonical name. |
| 39946 | 40216 | ** If found, increment the reference count and return a pointer to |
| 39947 | 40217 | ** the existing file ID. |
| 39948 | 40218 | */ |
| 39949 | - unixEnterMutex(); | |
| 40219 | + sqlite3_mutex_enter(vxworksMutex); | |
| 39950 | 40220 | for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ |
| 39951 | 40221 | if( pCandidate->nName==n |
| 39952 | 40222 | && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 |
| 39953 | 40223 | ){ |
| 39954 | 40224 | sqlite3_free(pNew); |
| 39955 | 40225 | pCandidate->nRef++; |
| 39956 | - unixLeaveMutex(); | |
| 40226 | + sqlite3_mutex_leave(vxworksMutex); | |
| 39957 | 40227 | return pCandidate; |
| 39958 | 40228 | } |
| 39959 | 40229 | } |
| 39960 | 40230 | |
| 39961 | 40231 | /* No match was found. We will make a new file ID */ |
| 39962 | 40232 | pNew->nRef = 1; |
| 39963 | 40233 | pNew->nName = n; |
| 39964 | 40234 | pNew->pNext = vxworksFileList; |
| 39965 | 40235 | vxworksFileList = pNew; |
| 39966 | - unixLeaveMutex(); | |
| 40236 | + sqlite3_mutex_leave(vxworksMutex); | |
| 39967 | 40237 | return pNew; |
| 39968 | 40238 | } |
| 39969 | 40239 | |
| 39970 | 40240 | /* |
| 39971 | 40241 | ** Decrement the reference count on a vxworksFileId object. Free |
| 39972 | 40242 | ** the object when the reference count reaches zero. |
| 39973 | 40243 | */ |
| 39974 | 40244 | static void vxworksReleaseFileId(struct vxworksFileId *pId){ |
| 39975 | - unixEnterMutex(); | |
| 40245 | + sqlite3_mutex_enter(vxworksMutex); | |
| 39976 | 40246 | assert( pId->nRef>0 ); |
| 39977 | 40247 | pId->nRef--; |
| 39978 | 40248 | if( pId->nRef==0 ){ |
| 39979 | 40249 | struct vxworksFileId **pp; |
| 39980 | 40250 | for(pp=&vxworksFileList; *pp && *pp!=pId; pp = &((*pp)->pNext)){} |
| 39981 | 40251 | assert( *pp==pId ); |
| 39982 | 40252 | *pp = pId->pNext; |
| 39983 | 40253 | sqlite3_free(pId); |
| 39984 | 40254 | } |
| 39985 | - unixLeaveMutex(); | |
| 40255 | + sqlite3_mutex_leave(vxworksMutex); | |
| 39986 | 40256 | } |
| 39987 | 40257 | #endif /* OS_VXWORKS */ |
| 39988 | 40258 | /*************** End of Unique File ID Utility Used By VxWorks **************** |
| 39989 | 40259 | ******************************************************************************/ |
| 39990 | 40260 | |
| @@ -40368,10 +40638,14 @@ | ||
| 40368 | 40638 | do{ rc = osWrite(fd, "S", 1); }while( rc<0 && errno==EINTR ); |
| 40369 | 40639 | if( rc!=1 ){ |
| 40370 | 40640 | storeLastErrno(pFile, errno); |
| 40371 | 40641 | return SQLITE_IOERR; |
| 40372 | 40642 | } |
| 40643 | + if( fsync(fd) ){ | |
| 40644 | + storeLastErrno(pFile, errno); | |
| 40645 | + return SQLITE_IOERR_FSYNC; | |
| 40646 | + } | |
| 40373 | 40647 | rc = osFstat(fd, &statbuf); |
| 40374 | 40648 | if( rc!=0 ){ |
| 40375 | 40649 | storeLastErrno(pFile, errno); |
| 40376 | 40650 | return SQLITE_IOERR; |
| 40377 | 40651 | } |
| @@ -40537,22 +40811,46 @@ | ||
| 40537 | 40811 | static int osSetPosixAdvisoryLock( |
| 40538 | 40812 | int h, /* The file descriptor on which to take the lock */ |
| 40539 | 40813 | struct flock *pLock, /* The description of the lock */ |
| 40540 | 40814 | unixFile *pFile /* Structure holding timeout value */ |
| 40541 | 40815 | ){ |
| 40542 | - int tm = pFile->iBusyTimeout; | |
| 40543 | - int rc = osFcntl(h,F_SETLK,pLock); | |
| 40544 | - while( rc<0 && tm>0 ){ | |
| 40545 | - /* On systems that support some kind of blocking file lock with a timeout, | |
| 40546 | - ** make appropriate changes here to invoke that blocking file lock. On | |
| 40547 | - ** generic posix, however, there is no such API. So we simply try the | |
| 40548 | - ** lock once every millisecond until either the timeout expires, or until | |
| 40549 | - ** the lock is obtained. */ | |
| 40550 | - unixSleep(0,1000); | |
| 40816 | + int rc = 0; | |
| 40817 | + | |
| 40818 | + if( pFile->iBusyTimeout==0 ){ | |
| 40819 | + /* unixFile->iBusyTimeout is set to 0. In this case, attempt a | |
| 40820 | + ** non-blocking lock. */ | |
| 40821 | + rc = osFcntl(h,F_SETLK,pLock); | |
| 40822 | + }else{ | |
| 40823 | + /* unixFile->iBusyTimeout is set to greater than zero. In this case, | |
| 40824 | + ** attempt a blocking-lock with a unixFile->iBusyTimeout ms timeout. | |
| 40825 | + ** | |
| 40826 | + ** On systems that support some kind of blocking file lock operation, | |
| 40827 | + ** this block should be replaced by code to attempt a blocking lock | |
| 40828 | + ** with a timeout of unixFile->iBusyTimeout ms. The code below is | |
| 40829 | + ** placeholder code. If SQLITE_TEST is defined, the placeholder code | |
| 40830 | + ** retries the lock once every 1ms until it succeeds or the timeout | |
| 40831 | + ** is reached. Or, if SQLITE_TEST is not defined, the placeholder | |
| 40832 | + ** code attempts a non-blocking lock and sets unixFile->iBusyTimeout | |
| 40833 | + ** to 0. This causes the caller to return SQLITE_BUSY, instead of | |
| 40834 | + ** SQLITE_BUSY_TIMEOUT to SQLite - as required by a VFS that does not | |
| 40835 | + ** support blocking locks. | |
| 40836 | + */ | |
| 40837 | +#ifdef SQLITE_TEST | |
| 40838 | + int tm = pFile->iBusyTimeout; | |
| 40839 | + while( tm>0 ){ | |
| 40840 | + rc = osFcntl(h,F_SETLK,pLock); | |
| 40841 | + if( rc==0 ) break; | |
| 40842 | + unixSleep(0,1000); | |
| 40843 | + tm--; | |
| 40844 | + } | |
| 40845 | +#else | |
| 40551 | 40846 | rc = osFcntl(h,F_SETLK,pLock); |
| 40552 | - tm--; | |
| 40847 | + pFile->iBusyTimeout = 0; | |
| 40848 | +#endif | |
| 40849 | + /* End of code to replace with real blocking-locks code. */ | |
| 40553 | 40850 | } |
| 40851 | + | |
| 40554 | 40852 | return rc; |
| 40555 | 40853 | } |
| 40556 | 40854 | #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ |
| 40557 | 40855 | |
| 40558 | 40856 | |
| @@ -44869,14 +45167,21 @@ | ||
| 44869 | 45167 | #endif |
| 44870 | 45168 | |
| 44871 | 45169 | storeLastErrno(pNew, 0); |
| 44872 | 45170 | #if OS_VXWORKS |
| 44873 | 45171 | if( rc!=SQLITE_OK ){ |
| 44874 | - if( h>=0 ) robust_close(pNew, h, __LINE__); | |
| 44875 | - h = -1; | |
| 44876 | - osUnlink(zFilename); | |
| 44877 | - pNew->ctrlFlags |= UNIXFILE_DELETE; | |
| 45172 | + if( h>=0 ){ | |
| 45173 | + robust_close(pNew, h, __LINE__); | |
| 45174 | + h = -1; | |
| 45175 | + } | |
| 45176 | + if( pNew->ctrlFlags & UNIXFILE_DELETE ){ | |
| 45177 | + osUnlink(zFilename); | |
| 45178 | + } | |
| 45179 | + if( pNew->pId ){ | |
| 45180 | + vxworksReleaseFileId(pNew->pId); | |
| 45181 | + pNew->pId = 0; | |
| 45182 | + } | |
| 44878 | 45183 | } |
| 44879 | 45184 | #endif |
| 44880 | 45185 | if( rc!=SQLITE_OK ){ |
| 44881 | 45186 | if( h>=0 ) robust_close(pNew, h, __LINE__); |
| 44882 | 45187 | }else{ |
| @@ -44916,10 +45221,13 @@ | ||
| 44916 | 45221 | struct stat buf; |
| 44917 | 45222 | const char *zDir = sqlite3_temp_directory; |
| 44918 | 45223 | |
| 44919 | 45224 | while(1){ |
| 44920 | 45225 | if( zDir!=0 |
| 45226 | +#if OS_VXWORKS | |
| 45227 | + && zDir[0]=='/' | |
| 45228 | +#endif | |
| 44921 | 45229 | && osStat(zDir, &buf)==0 |
| 44922 | 45230 | && S_ISDIR(buf.st_mode) |
| 44923 | 45231 | && osAccess(zDir, 03)==0 |
| 44924 | 45232 | ){ |
| 44925 | 45233 | return zDir; |
| @@ -45229,10 +45537,16 @@ | ||
| 45229 | 45537 | assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB |
| 45230 | 45538 | || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL |
| 45231 | 45539 | || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL |
| 45232 | 45540 | || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL |
| 45233 | 45541 | ); |
| 45542 | + | |
| 45543 | +#if OS_VXWORKS | |
| 45544 | + /* The file-ID mechanism used in Vxworks requires that all pathnames | |
| 45545 | + ** provided to unixOpen must be absolute pathnames. */ | |
| 45546 | + if( zPath!=0 && zPath[0]!='/' ){ return SQLITE_CANTOPEN; } | |
| 45547 | +#endif | |
| 45234 | 45548 | |
| 45235 | 45549 | /* Detect a pid change and reset the PRNG. There is a race condition |
| 45236 | 45550 | ** here such that two or more threads all trying to open databases at |
| 45237 | 45551 | ** the same instant might all reset the PRNG. But multiple resets |
| 45238 | 45552 | ** are harmless. |
| @@ -45430,12 +45744,15 @@ | ||
| 45430 | 45744 | goto open_finished; |
| 45431 | 45745 | } |
| 45432 | 45746 | } |
| 45433 | 45747 | #endif |
| 45434 | 45748 | |
| 45435 | - assert( zPath==0 || zPath[0]=='/' | |
| 45436 | - || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL | |
| 45749 | + assert( zPath==0 | |
| 45750 | + || zPath[0]=='/' | |
| 45751 | + || eType==SQLITE_OPEN_SUPER_JOURNAL | |
| 45752 | + || eType==SQLITE_OPEN_MAIN_JOURNAL | |
| 45753 | + || eType==SQLITE_OPEN_TEMP_JOURNAL | |
| 45437 | 45754 | ); |
| 45438 | 45755 | rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); |
| 45439 | 45756 | |
| 45440 | 45757 | open_finished: |
| 45441 | 45758 | if( rc!=SQLITE_OK ){ |
| @@ -47160,10 +47477,13 @@ | ||
| 47160 | 47477 | } |
| 47161 | 47478 | #ifdef SQLITE_OS_KV_OPTIONAL |
| 47162 | 47479 | sqlite3KvvfsInit(); |
| 47163 | 47480 | #endif |
| 47164 | 47481 | unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); |
| 47482 | +#if OS_VXWORKS | |
| 47483 | + vxworksMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS2); | |
| 47484 | +#endif | |
| 47165 | 47485 | |
| 47166 | 47486 | #ifndef SQLITE_OMIT_WAL |
| 47167 | 47487 | /* Validate lock assumptions */ |
| 47168 | 47488 | assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ |
| 47169 | 47489 | assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ |
| @@ -47194,10 +47514,13 @@ | ||
| 47194 | 47514 | ** to release dynamically allocated objects. But not on unix. |
| 47195 | 47515 | ** This routine is a no-op for unix. |
| 47196 | 47516 | */ |
| 47197 | 47517 | SQLITE_API int sqlite3_os_end(void){ |
| 47198 | 47518 | unixBigLock = 0; |
| 47519 | +#if OS_VXWORKS | |
| 47520 | + vxworksMutex = 0; | |
| 47521 | +#endif | |
| 47199 | 47522 | return SQLITE_OK; |
| 47200 | 47523 | } |
| 47201 | 47524 | |
| 47202 | 47525 | #endif /* SQLITE_OS_UNIX */ |
| 47203 | 47526 | |
| @@ -51192,204 +51515,10 @@ | ||
| 51192 | 51515 | ** on allocation size granularity boundaries. |
| 51193 | 51516 | ** During sqlite3_os_init() we do a GetSystemInfo() |
| 51194 | 51517 | ** to get the granularity size. |
| 51195 | 51518 | */ |
| 51196 | 51519 | static SYSTEM_INFO winSysInfo; |
| 51197 | - | |
| 51198 | -#ifndef SQLITE_OMIT_WAL | |
| 51199 | - | |
| 51200 | -/* | |
| 51201 | -** Helper functions to obtain and relinquish the global mutex. The | |
| 51202 | -** global mutex is used to protect the winLockInfo objects used by | |
| 51203 | -** this file, all of which may be shared by multiple threads. | |
| 51204 | -** | |
| 51205 | -** Function winShmMutexHeld() is used to assert() that the global mutex | |
| 51206 | -** is held when required. This function is only used as part of assert() | |
| 51207 | -** statements. e.g. | |
| 51208 | -** | |
| 51209 | -** winShmEnterMutex() | |
| 51210 | -** assert( winShmMutexHeld() ); | |
| 51211 | -** winShmLeaveMutex() | |
| 51212 | -*/ | |
| 51213 | -static sqlite3_mutex *winBigLock = 0; | |
| 51214 | -static void winShmEnterMutex(void){ | |
| 51215 | - sqlite3_mutex_enter(winBigLock); | |
| 51216 | -} | |
| 51217 | -static void winShmLeaveMutex(void){ | |
| 51218 | - sqlite3_mutex_leave(winBigLock); | |
| 51219 | -} | |
| 51220 | -#ifndef NDEBUG | |
| 51221 | -static int winShmMutexHeld(void) { | |
| 51222 | - return sqlite3_mutex_held(winBigLock); | |
| 51223 | -} | |
| 51224 | -#endif | |
| 51225 | - | |
| 51226 | -/* | |
| 51227 | -** Object used to represent a single file opened and mmapped to provide | |
| 51228 | -** shared memory. When multiple threads all reference the same | |
| 51229 | -** log-summary, each thread has its own winFile object, but they all | |
| 51230 | -** point to a single instance of this object. In other words, each | |
| 51231 | -** log-summary is opened only once per process. | |
| 51232 | -** | |
| 51233 | -** winShmMutexHeld() must be true when creating or destroying | |
| 51234 | -** this object or while reading or writing the following fields: | |
| 51235 | -** | |
| 51236 | -** nRef | |
| 51237 | -** pNext | |
| 51238 | -** | |
| 51239 | -** The following fields are read-only after the object is created: | |
| 51240 | -** | |
| 51241 | -** zFilename | |
| 51242 | -** | |
| 51243 | -** Either winShmNode.mutex must be held or winShmNode.nRef==0 and | |
| 51244 | -** winShmMutexHeld() is true when reading or writing any other field | |
| 51245 | -** in this structure. | |
| 51246 | -** | |
| 51247 | -** File-handle hSharedShm is used to (a) take the DMS lock, (b) truncate | |
| 51248 | -** the *-shm file if the DMS-locking protocol demands it, and (c) map | |
| 51249 | -** regions of the *-shm file into memory using MapViewOfFile() or | |
| 51250 | -** similar. Other locks are taken by individual clients using the | |
| 51251 | -** winShm.hShm handles. | |
| 51252 | -*/ | |
| 51253 | -struct winShmNode { | |
| 51254 | - sqlite3_mutex *mutex; /* Mutex to access this object */ | |
| 51255 | - char *zFilename; /* Name of the file */ | |
| 51256 | - HANDLE hSharedShm; /* File handle open on zFilename */ | |
| 51257 | - | |
| 51258 | - int isUnlocked; /* DMS lock has not yet been obtained */ | |
| 51259 | - int isReadonly; /* True if read-only */ | |
| 51260 | - int szRegion; /* Size of shared-memory regions */ | |
| 51261 | - int nRegion; /* Size of array apRegion */ | |
| 51262 | - | |
| 51263 | - struct ShmRegion { | |
| 51264 | - HANDLE hMap; /* File handle from CreateFileMapping */ | |
| 51265 | - void *pMap; | |
| 51266 | - } *aRegion; | |
| 51267 | - DWORD lastErrno; /* The Windows errno from the last I/O error */ | |
| 51268 | - | |
| 51269 | - int nRef; /* Number of winShm objects pointing to this */ | |
| 51270 | - winShmNode *pNext; /* Next in list of all winShmNode objects */ | |
| 51271 | -#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) | |
| 51272 | - u8 nextShmId; /* Next available winShm.id value */ | |
| 51273 | -#endif | |
| 51274 | -}; | |
| 51275 | - | |
| 51276 | -/* | |
| 51277 | -** A global array of all winShmNode objects. | |
| 51278 | -** | |
| 51279 | -** The winShmMutexHeld() must be true while reading or writing this list. | |
| 51280 | -*/ | |
| 51281 | -static winShmNode *winShmNodeList = 0; | |
| 51282 | - | |
| 51283 | -/* | |
| 51284 | -** Structure used internally by this VFS to record the state of an | |
| 51285 | -** open shared memory connection. There is one such structure for each | |
| 51286 | -** winFile open on a wal mode database. | |
| 51287 | -*/ | |
| 51288 | -struct winShm { | |
| 51289 | - winShmNode *pShmNode; /* The underlying winShmNode object */ | |
| 51290 | - u16 sharedMask; /* Mask of shared locks held */ | |
| 51291 | - u16 exclMask; /* Mask of exclusive locks held */ | |
| 51292 | - HANDLE hShm; /* File-handle on *-shm file. For locking. */ | |
| 51293 | - int bReadonly; /* True if hShm is opened read-only */ | |
| 51294 | -#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) | |
| 51295 | - u8 id; /* Id of this connection with its winShmNode */ | |
| 51296 | -#endif | |
| 51297 | -}; | |
| 51298 | - | |
| 51299 | -/* | |
| 51300 | -** Constants used for locking | |
| 51301 | -*/ | |
| 51302 | -#define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ | |
| 51303 | -#define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ | |
| 51304 | - | |
| 51305 | -/* Forward references to VFS methods */ | |
| 51306 | -static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); | |
| 51307 | -static int winDelete(sqlite3_vfs *,const char*,int); | |
| 51308 | - | |
| 51309 | -/* | |
| 51310 | -** Purge the winShmNodeList list of all entries with winShmNode.nRef==0. | |
| 51311 | -** | |
| 51312 | -** This is not a VFS shared-memory method; it is a utility function called | |
| 51313 | -** by VFS shared-memory methods. | |
| 51314 | -*/ | |
| 51315 | -static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ | |
| 51316 | - winShmNode **pp; | |
| 51317 | - winShmNode *p; | |
| 51318 | - assert( winShmMutexHeld() ); | |
| 51319 | - OSTRACE(("SHM-PURGE pid=%lu, deleteFlag=%d\n", | |
| 51320 | - osGetCurrentProcessId(), deleteFlag)); | |
| 51321 | - pp = &winShmNodeList; | |
| 51322 | - while( (p = *pp)!=0 ){ | |
| 51323 | - if( p->nRef==0 ){ | |
| 51324 | - int i; | |
| 51325 | - if( p->mutex ){ sqlite3_mutex_free(p->mutex); } | |
| 51326 | - for(i=0; i<p->nRegion; i++){ | |
| 51327 | - BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); | |
| 51328 | - OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", | |
| 51329 | - osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); | |
| 51330 | - UNUSED_VARIABLE_VALUE(bRc); | |
| 51331 | - bRc = osCloseHandle(p->aRegion[i].hMap); | |
| 51332 | - OSTRACE(("SHM-PURGE-CLOSE pid=%lu, region=%d, rc=%s\n", | |
| 51333 | - osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); | |
| 51334 | - UNUSED_VARIABLE_VALUE(bRc); | |
| 51335 | - } | |
| 51336 | - winHandleClose(p->hSharedShm); | |
| 51337 | - if( deleteFlag ){ | |
| 51338 | - SimulateIOErrorBenign(1); | |
| 51339 | - sqlite3BeginBenignMalloc(); | |
| 51340 | - winDelete(pVfs, p->zFilename, 0); | |
| 51341 | - sqlite3EndBenignMalloc(); | |
| 51342 | - SimulateIOErrorBenign(0); | |
| 51343 | - } | |
| 51344 | - *pp = p->pNext; | |
| 51345 | - sqlite3_free(p->aRegion); | |
| 51346 | - sqlite3_free(p); | |
| 51347 | - }else{ | |
| 51348 | - pp = &p->pNext; | |
| 51349 | - } | |
| 51350 | - } | |
| 51351 | -} | |
| 51352 | - | |
| 51353 | -/* | |
| 51354 | -** The DMS lock has not yet been taken on the shm file associated with | |
| 51355 | -** pShmNode. Take the lock. Truncate the *-shm file if required. | |
| 51356 | -** Return SQLITE_OK if successful, or an SQLite error code otherwise. | |
| 51357 | -*/ | |
| 51358 | -static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ | |
| 51359 | - HANDLE h = pShmNode->hSharedShm; | |
| 51360 | - int rc = SQLITE_OK; | |
| 51361 | - | |
| 51362 | - assert( sqlite3_mutex_held(pShmNode->mutex) ); | |
| 51363 | - rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 1, 0); | |
| 51364 | - if( rc==SQLITE_OK ){ | |
| 51365 | - /* We have an EXCLUSIVE lock on the DMS byte. This means that this | |
| 51366 | - ** is the first process to open the file. Truncate it to zero bytes | |
| 51367 | - ** in this case. */ | |
| 51368 | - if( pShmNode->isReadonly ){ | |
| 51369 | - rc = SQLITE_READONLY_CANTINIT; | |
| 51370 | - }else{ | |
| 51371 | - rc = winHandleTruncate(h, 0); | |
| 51372 | - } | |
| 51373 | - | |
| 51374 | - /* Release the EXCLUSIVE lock acquired above. */ | |
| 51375 | - winUnlockFile(&h, WIN_SHM_DMS, 0, 1, 0); | |
| 51376 | - }else if( (rc & 0xFF)==SQLITE_BUSY ){ | |
| 51377 | - rc = SQLITE_OK; | |
| 51378 | - } | |
| 51379 | - | |
| 51380 | - if( rc==SQLITE_OK ){ | |
| 51381 | - /* Take a SHARED lock on the DMS byte. */ | |
| 51382 | - rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 0, nMs); | |
| 51383 | - if( rc==SQLITE_OK ){ | |
| 51384 | - pShmNode->isUnlocked = 0; | |
| 51385 | - } | |
| 51386 | - } | |
| 51387 | - | |
| 51388 | - return rc; | |
| 51389 | -} | |
| 51390 | - | |
| 51391 | 51520 | |
| 51392 | 51521 | /* |
| 51393 | 51522 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51394 | 51523 | ** operating system wants filenames in. Space to hold the result |
| 51395 | 51524 | ** is obtained from malloc and must be freed by the calling |
| @@ -51483,10 +51612,212 @@ | ||
| 51483 | 51612 | } |
| 51484 | 51613 | #endif |
| 51485 | 51614 | /* caller will handle out of memory */ |
| 51486 | 51615 | return zConverted; |
| 51487 | 51616 | } |
| 51617 | + | |
| 51618 | +#ifndef SQLITE_OMIT_WAL | |
| 51619 | + | |
| 51620 | +/* | |
| 51621 | +** Helper functions to obtain and relinquish the global mutex. The | |
| 51622 | +** global mutex is used to protect the winLockInfo objects used by | |
| 51623 | +** this file, all of which may be shared by multiple threads. | |
| 51624 | +** | |
| 51625 | +** Function winShmMutexHeld() is used to assert() that the global mutex | |
| 51626 | +** is held when required. This function is only used as part of assert() | |
| 51627 | +** statements. e.g. | |
| 51628 | +** | |
| 51629 | +** winShmEnterMutex() | |
| 51630 | +** assert( winShmMutexHeld() ); | |
| 51631 | +** winShmLeaveMutex() | |
| 51632 | +*/ | |
| 51633 | +static sqlite3_mutex *winBigLock = 0; | |
| 51634 | +static void winShmEnterMutex(void){ | |
| 51635 | + sqlite3_mutex_enter(winBigLock); | |
| 51636 | +} | |
| 51637 | +static void winShmLeaveMutex(void){ | |
| 51638 | + sqlite3_mutex_leave(winBigLock); | |
| 51639 | +} | |
| 51640 | +#ifndef NDEBUG | |
| 51641 | +static int winShmMutexHeld(void) { | |
| 51642 | + return sqlite3_mutex_held(winBigLock); | |
| 51643 | +} | |
| 51644 | +#endif | |
| 51645 | + | |
| 51646 | +/* | |
| 51647 | +** Object used to represent a single file opened and mmapped to provide | |
| 51648 | +** shared memory. When multiple threads all reference the same | |
| 51649 | +** log-summary, each thread has its own winFile object, but they all | |
| 51650 | +** point to a single instance of this object. In other words, each | |
| 51651 | +** log-summary is opened only once per process. | |
| 51652 | +** | |
| 51653 | +** winShmMutexHeld() must be true when creating or destroying | |
| 51654 | +** this object, or while editing the global linked list that starts | |
| 51655 | +** at winShmNodeList. | |
| 51656 | +** | |
| 51657 | +** When reading or writing the linked list starting at winShmNode.pWinShmList, | |
| 51658 | +** pShmNode->mutex must be held. | |
| 51659 | +** | |
| 51660 | +** The following fields are constant after the object is created: | |
| 51661 | +** | |
| 51662 | +** zFilename | |
| 51663 | +** hSharedShm | |
| 51664 | +** mutex | |
| 51665 | +** bUseSharedLockHandle | |
| 51666 | +** | |
| 51667 | +** Either winShmNode.mutex must be held or winShmNode.pWinShmList==0 and | |
| 51668 | +** winShmMutexHeld() is true when reading or writing any other field | |
| 51669 | +** in this structure. | |
| 51670 | +** | |
| 51671 | +** File-handle hSharedShm is always used to (a) take the DMS lock, (b) | |
| 51672 | +** truncate the *-shm file if the DMS-locking protocol demands it, and | |
| 51673 | +** (c) map regions of the *-shm file into memory using MapViewOfFile() | |
| 51674 | +** or similar. If bUseSharedLockHandle is true, then other locks are also | |
| 51675 | +** taken on hSharedShm. Or, if bUseSharedLockHandle is false, then other | |
| 51676 | +** locks are taken using each connection's winShm.hShm handles. | |
| 51677 | +*/ | |
| 51678 | +struct winShmNode { | |
| 51679 | + sqlite3_mutex *mutex; /* Mutex to access this object */ | |
| 51680 | + char *zFilename; /* Name of the file */ | |
| 51681 | + HANDLE hSharedShm; /* File handle open on zFilename */ | |
| 51682 | + int bUseSharedLockHandle; /* True to use hSharedShm for everything */ | |
| 51683 | + | |
| 51684 | + int isUnlocked; /* DMS lock has not yet been obtained */ | |
| 51685 | + int isReadonly; /* True if read-only */ | |
| 51686 | + int szRegion; /* Size of shared-memory regions */ | |
| 51687 | + int nRegion; /* Size of array apRegion */ | |
| 51688 | + | |
| 51689 | + struct ShmRegion { | |
| 51690 | + HANDLE hMap; /* File handle from CreateFileMapping */ | |
| 51691 | + void *pMap; | |
| 51692 | + } *aRegion; | |
| 51693 | + DWORD lastErrno; /* The Windows errno from the last I/O error */ | |
| 51694 | + | |
| 51695 | + winShm *pWinShmList; /* List of winShm objects with ptrs to this */ | |
| 51696 | + | |
| 51697 | + winShmNode *pNext; /* Next in list of all winShmNode objects */ | |
| 51698 | +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) | |
| 51699 | + u8 nextShmId; /* Next available winShm.id value */ | |
| 51700 | +#endif | |
| 51701 | +}; | |
| 51702 | + | |
| 51703 | +/* | |
| 51704 | +** A global array of all winShmNode objects. | |
| 51705 | +** | |
| 51706 | +** The winShmMutexHeld() must be true while reading or writing this list. | |
| 51707 | +*/ | |
| 51708 | +static winShmNode *winShmNodeList = 0; | |
| 51709 | + | |
| 51710 | +/* | |
| 51711 | +** Structure used internally by this VFS to record the state of an | |
| 51712 | +** open shared memory connection. There is one such structure for each | |
| 51713 | +** winFile open on a wal mode database. | |
| 51714 | +*/ | |
| 51715 | +struct winShm { | |
| 51716 | + winShmNode *pShmNode; /* The underlying winShmNode object */ | |
| 51717 | + u16 sharedMask; /* Mask of shared locks held */ | |
| 51718 | + u16 exclMask; /* Mask of exclusive locks held */ | |
| 51719 | + HANDLE hShm; /* File-handle on *-shm file. For locking. */ | |
| 51720 | + int bReadonly; /* True if hShm is opened read-only */ | |
| 51721 | +#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) | |
| 51722 | + u8 id; /* Id of this connection with its winShmNode */ | |
| 51723 | +#endif | |
| 51724 | + winShm *pWinShmNext; /* Next winShm object on same winShmNode */ | |
| 51725 | +}; | |
| 51726 | + | |
| 51727 | +/* | |
| 51728 | +** Constants used for locking | |
| 51729 | +*/ | |
| 51730 | +#define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ | |
| 51731 | +#define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ | |
| 51732 | + | |
| 51733 | +/* Forward references to VFS methods */ | |
| 51734 | +static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); | |
| 51735 | +static int winDelete(sqlite3_vfs *,const char*,int); | |
| 51736 | + | |
| 51737 | +/* | |
| 51738 | +** Purge the winShmNodeList list of all entries with winShmNode.pWinShmList==0. | |
| 51739 | +** | |
| 51740 | +** This is not a VFS shared-memory method; it is a utility function called | |
| 51741 | +** by VFS shared-memory methods. | |
| 51742 | +*/ | |
| 51743 | +static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ | |
| 51744 | + winShmNode **pp; | |
| 51745 | + winShmNode *p; | |
| 51746 | + assert( winShmMutexHeld() ); | |
| 51747 | + OSTRACE(("SHM-PURGE pid=%lu, deleteFlag=%d\n", | |
| 51748 | + osGetCurrentProcessId(), deleteFlag)); | |
| 51749 | + pp = &winShmNodeList; | |
| 51750 | + while( (p = *pp)!=0 ){ | |
| 51751 | + if( p->pWinShmList==0 ){ | |
| 51752 | + int i; | |
| 51753 | + if( p->mutex ){ sqlite3_mutex_free(p->mutex); } | |
| 51754 | + for(i=0; i<p->nRegion; i++){ | |
| 51755 | + BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); | |
| 51756 | + OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", | |
| 51757 | + osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); | |
| 51758 | + UNUSED_VARIABLE_VALUE(bRc); | |
| 51759 | + bRc = osCloseHandle(p->aRegion[i].hMap); | |
| 51760 | + OSTRACE(("SHM-PURGE-CLOSE pid=%lu, region=%d, rc=%s\n", | |
| 51761 | + osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); | |
| 51762 | + UNUSED_VARIABLE_VALUE(bRc); | |
| 51763 | + } | |
| 51764 | + winHandleClose(p->hSharedShm); | |
| 51765 | + if( deleteFlag ){ | |
| 51766 | + SimulateIOErrorBenign(1); | |
| 51767 | + sqlite3BeginBenignMalloc(); | |
| 51768 | + winDelete(pVfs, p->zFilename, 0); | |
| 51769 | + sqlite3EndBenignMalloc(); | |
| 51770 | + SimulateIOErrorBenign(0); | |
| 51771 | + } | |
| 51772 | + *pp = p->pNext; | |
| 51773 | + sqlite3_free(p->aRegion); | |
| 51774 | + sqlite3_free(p); | |
| 51775 | + }else{ | |
| 51776 | + pp = &p->pNext; | |
| 51777 | + } | |
| 51778 | + } | |
| 51779 | +} | |
| 51780 | + | |
| 51781 | +/* | |
| 51782 | +** The DMS lock has not yet been taken on the shm file associated with | |
| 51783 | +** pShmNode. Take the lock. Truncate the *-shm file if required. | |
| 51784 | +** Return SQLITE_OK if successful, or an SQLite error code otherwise. | |
| 51785 | +*/ | |
| 51786 | +static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ | |
| 51787 | + HANDLE h = pShmNode->hSharedShm; | |
| 51788 | + int rc = SQLITE_OK; | |
| 51789 | + | |
| 51790 | + assert( sqlite3_mutex_held(pShmNode->mutex) ); | |
| 51791 | + rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 1, 0); | |
| 51792 | + if( rc==SQLITE_OK ){ | |
| 51793 | + /* We have an EXCLUSIVE lock on the DMS byte. This means that this | |
| 51794 | + ** is the first process to open the file. Truncate it to zero bytes | |
| 51795 | + ** in this case. */ | |
| 51796 | + if( pShmNode->isReadonly ){ | |
| 51797 | + rc = SQLITE_READONLY_CANTINIT; | |
| 51798 | + }else{ | |
| 51799 | + rc = winHandleTruncate(h, 0); | |
| 51800 | + } | |
| 51801 | + | |
| 51802 | + /* Release the EXCLUSIVE lock acquired above. */ | |
| 51803 | + winUnlockFile(&h, WIN_SHM_DMS, 0, 1, 0); | |
| 51804 | + }else if( (rc & 0xFF)==SQLITE_BUSY ){ | |
| 51805 | + rc = SQLITE_OK; | |
| 51806 | + } | |
| 51807 | + | |
| 51808 | + if( rc==SQLITE_OK ){ | |
| 51809 | + /* Take a SHARED lock on the DMS byte. */ | |
| 51810 | + rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 0, nMs); | |
| 51811 | + if( rc==SQLITE_OK ){ | |
| 51812 | + pShmNode->isUnlocked = 0; | |
| 51813 | + } | |
| 51814 | + } | |
| 51815 | + | |
| 51816 | + return rc; | |
| 51817 | +} | |
| 51818 | + | |
| 51488 | 51819 | |
| 51489 | 51820 | /* |
| 51490 | 51821 | ** This function is used to open a handle on a *-shm file. |
| 51491 | 51822 | ** |
| 51492 | 51823 | ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file |
| @@ -51579,10 +51910,64 @@ | ||
| 51579 | 51910 | *pbReadonly = bReadonly; |
| 51580 | 51911 | *ph = h; |
| 51581 | 51912 | return rc; |
| 51582 | 51913 | } |
| 51583 | 51914 | |
| 51915 | +/* | |
| 51916 | +** Close pDbFd's connection to shared-memory. Delete the underlying | |
| 51917 | +** *-shm file if deleteFlag is true. | |
| 51918 | +*/ | |
| 51919 | +static int winCloseSharedMemory(winFile *pDbFd, int deleteFlag){ | |
| 51920 | + winShm *p; /* The connection to be closed */ | |
| 51921 | + winShm **pp; /* Iterator for pShmNode->pWinShmList */ | |
| 51922 | + winShmNode *pShmNode; /* The underlying shared-memory file */ | |
| 51923 | + | |
| 51924 | + p = pDbFd->pShm; | |
| 51925 | + if( p==0 ) return SQLITE_OK; | |
| 51926 | + if( p->hShm!=INVALID_HANDLE_VALUE ){ | |
| 51927 | + osCloseHandle(p->hShm); | |
| 51928 | + } | |
| 51929 | + | |
| 51930 | + winShmEnterMutex(); | |
| 51931 | + pShmNode = p->pShmNode; | |
| 51932 | + | |
| 51933 | + /* Remove this connection from the winShmNode.pWinShmList list */ | |
| 51934 | + sqlite3_mutex_enter(pShmNode->mutex); | |
| 51935 | + for(pp=&pShmNode->pWinShmList; *pp!=p; pp=&(*pp)->pWinShmNext){} | |
| 51936 | + *pp = p->pWinShmNext; | |
| 51937 | + sqlite3_mutex_leave(pShmNode->mutex); | |
| 51938 | + | |
| 51939 | + winShmPurge(pDbFd->pVfs, deleteFlag); | |
| 51940 | + winShmLeaveMutex(); | |
| 51941 | + | |
| 51942 | + /* Free the connection p */ | |
| 51943 | + sqlite3_free(p); | |
| 51944 | + pDbFd->pShm = 0; | |
| 51945 | + return SQLITE_OK; | |
| 51946 | +} | |
| 51947 | + | |
| 51948 | +/* | |
| 51949 | +** testfixture builds may set this global variable to true via a | |
| 51950 | +** Tcl interface. This forces the VFS to use the locking normally | |
| 51951 | +** only used for UNC paths for all files. | |
| 51952 | +*/ | |
| 51953 | +#ifdef SQLITE_TEST | |
| 51954 | +SQLITE_API int sqlite3_win_test_unc_locking = 0; | |
| 51955 | +#else | |
| 51956 | +# define sqlite3_win_test_unc_locking 0 | |
| 51957 | +#endif | |
| 51958 | + | |
| 51959 | +/* | |
| 51960 | +** Return true if the string passed as the only argument is likely | |
| 51961 | +** to be a UNC path. In other words, if it starts with "\\". | |
| 51962 | +*/ | |
| 51963 | +static int winIsUNCPath(const char *zFile){ | |
| 51964 | + if( zFile[0]=='\\' && zFile[1]=='\\' ){ | |
| 51965 | + return 1; | |
| 51966 | + } | |
| 51967 | + return sqlite3_win_test_unc_locking; | |
| 51968 | +} | |
| 51584 | 51969 | |
| 51585 | 51970 | /* |
| 51586 | 51971 | ** Open the shared-memory area associated with database file pDbFd. |
| 51587 | 51972 | */ |
| 51588 | 51973 | static int winOpenSharedMemory(winFile *pDbFd){ |
| @@ -51605,19 +51990,14 @@ | ||
| 51605 | 51990 | return SQLITE_IOERR_NOMEM_BKPT; |
| 51606 | 51991 | } |
| 51607 | 51992 | pNew->zFilename = (char*)&pNew[1]; |
| 51608 | 51993 | pNew->hSharedShm = INVALID_HANDLE_VALUE; |
| 51609 | 51994 | pNew->isUnlocked = 1; |
| 51995 | + pNew->bUseSharedLockHandle = winIsUNCPath(pDbFd->zPath); | |
| 51610 | 51996 | sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); |
| 51611 | 51997 | sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); |
| 51612 | 51998 | |
| 51613 | - /* Open a file-handle on the *-shm file for this connection. This file-handle | |
| 51614 | - ** is only used for locking. The mapping of the *-shm file is created using | |
| 51615 | - ** the shared file handle in winShmNode.hSharedShm. */ | |
| 51616 | - p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); | |
| 51617 | - rc = winHandleOpen(pNew->zFilename, &p->bReadonly, &p->hShm); | |
| 51618 | - | |
| 51619 | 51999 | /* Look to see if there is an existing winShmNode that can be used. |
| 51620 | 52000 | ** If no matching winShmNode currently exists, then create a new one. */ |
| 51621 | 52001 | winShmEnterMutex(); |
| 51622 | 52002 | for(pShmNode = winShmNodeList; pShmNode; pShmNode=pShmNode->pNext){ |
| 51623 | 52003 | /* TBD need to come up with better match here. Perhaps |
| @@ -51634,11 +52014,11 @@ | ||
| 51634 | 52014 | } |
| 51635 | 52015 | |
| 51636 | 52016 | /* Open a file-handle to use for mappings, and for the DMS lock. */ |
| 51637 | 52017 | if( rc==SQLITE_OK ){ |
| 51638 | 52018 | HANDLE h = INVALID_HANDLE_VALUE; |
| 51639 | - pShmNode->isReadonly = p->bReadonly; | |
| 52019 | + pShmNode->isReadonly = sqlite3_uri_boolean(pDbFd->zPath,"readonly_shm",0); | |
| 51640 | 52020 | rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h); |
| 51641 | 52021 | pShmNode->hSharedShm = h; |
| 51642 | 52022 | } |
| 51643 | 52023 | |
| 51644 | 52024 | /* If successful, link the new winShmNode into the global list. If an |
| @@ -51656,24 +52036,39 @@ | ||
| 51656 | 52036 | } |
| 51657 | 52037 | |
| 51658 | 52038 | /* If no error has occurred, link the winShm object to the winShmNode and |
| 51659 | 52039 | ** the winShm to pDbFd. */ |
| 51660 | 52040 | if( rc==SQLITE_OK ){ |
| 52041 | + sqlite3_mutex_enter(pShmNode->mutex); | |
| 51661 | 52042 | p->pShmNode = pShmNode; |
| 51662 | - pShmNode->nRef++; | |
| 52043 | + p->pWinShmNext = pShmNode->pWinShmList; | |
| 52044 | + pShmNode->pWinShmList = p; | |
| 51663 | 52045 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51664 | 52046 | p->id = pShmNode->nextShmId++; |
| 51665 | 52047 | #endif |
| 51666 | 52048 | pDbFd->pShm = p; |
| 52049 | + sqlite3_mutex_leave(pShmNode->mutex); | |
| 51667 | 52050 | }else if( p ){ |
| 51668 | - winHandleClose(p->hShm); | |
| 51669 | 52051 | sqlite3_free(p); |
| 51670 | 52052 | } |
| 51671 | 52053 | |
| 51672 | 52054 | assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 ); |
| 51673 | 52055 | winShmLeaveMutex(); |
| 51674 | 52056 | sqlite3_free(pNew); |
| 52057 | + | |
| 52058 | + /* Open a file-handle on the *-shm file for this connection. This file-handle | |
| 52059 | + ** is only used for locking. The mapping of the *-shm file is created using | |
| 52060 | + ** the shared file handle in winShmNode.hSharedShm. */ | |
| 52061 | + if( rc==SQLITE_OK && pShmNode->bUseSharedLockHandle==0 ){ | |
| 52062 | + p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); | |
| 52063 | + rc = winHandleOpen(pShmNode->zFilename, &p->bReadonly, &p->hShm); | |
| 52064 | + if( rc!=SQLITE_OK ){ | |
| 52065 | + assert( p->hShm==INVALID_HANDLE_VALUE ); | |
| 52066 | + winCloseSharedMemory(pDbFd, 0); | |
| 52067 | + } | |
| 52068 | + } | |
| 52069 | + | |
| 51675 | 52070 | return rc; |
| 51676 | 52071 | } |
| 51677 | 52072 | |
| 51678 | 52073 | /* |
| 51679 | 52074 | ** Close a connection to shared-memory. Delete the underlying |
| @@ -51681,37 +52076,11 @@ | ||
| 51681 | 52076 | */ |
| 51682 | 52077 | static int winShmUnmap( |
| 51683 | 52078 | sqlite3_file *fd, /* Database holding shared memory */ |
| 51684 | 52079 | int deleteFlag /* Delete after closing if true */ |
| 51685 | 52080 | ){ |
| 51686 | - winFile *pDbFd; /* Database holding shared-memory */ | |
| 51687 | - winShm *p; /* The connection to be closed */ | |
| 51688 | - winShmNode *pShmNode; /* The underlying shared-memory file */ | |
| 51689 | - | |
| 51690 | - pDbFd = (winFile*)fd; | |
| 51691 | - p = pDbFd->pShm; | |
| 51692 | - if( p==0 ) return SQLITE_OK; | |
| 51693 | - if( p->hShm!=INVALID_HANDLE_VALUE ){ | |
| 51694 | - osCloseHandle(p->hShm); | |
| 51695 | - } | |
| 51696 | - | |
| 51697 | - pShmNode = p->pShmNode; | |
| 51698 | - winShmEnterMutex(); | |
| 51699 | - | |
| 51700 | - /* If pShmNode->nRef has reached 0, then close the underlying | |
| 51701 | - ** shared-memory file, too. */ | |
| 51702 | - assert( pShmNode->nRef>0 ); | |
| 51703 | - pShmNode->nRef--; | |
| 51704 | - if( pShmNode->nRef==0 ){ | |
| 51705 | - winShmPurge(pDbFd->pVfs, deleteFlag); | |
| 51706 | - } | |
| 51707 | - winShmLeaveMutex(); | |
| 51708 | - | |
| 51709 | - /* Free the connection p */ | |
| 51710 | - sqlite3_free(p); | |
| 51711 | - pDbFd->pShm = 0; | |
| 51712 | - return SQLITE_OK; | |
| 52081 | + return winCloseSharedMemory((winFile*)fd, deleteFlag); | |
| 51713 | 52082 | } |
| 51714 | 52083 | |
| 51715 | 52084 | /* |
| 51716 | 52085 | ** Change the lock state for a shared-memory segment. |
| 51717 | 52086 | */ |
| @@ -51776,29 +52145,75 @@ | ||
| 51776 | 52145 | ); |
| 51777 | 52146 | if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) |
| 51778 | 52147 | || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) |
| 51779 | 52148 | || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) |
| 51780 | 52149 | ){ |
| 52150 | + HANDLE h = p->hShm; | |
| 51781 | 52151 | |
| 51782 | 52152 | if( flags & SQLITE_SHM_UNLOCK ){ |
| 51783 | 52153 | /* Case (a) - unlock. */ |
| 51784 | 52154 | |
| 51785 | 52155 | assert( (p->exclMask & p->sharedMask)==0 ); |
| 51786 | 52156 | assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); |
| 51787 | 52157 | assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); |
| 51788 | 52158 | |
| 51789 | - rc = winHandleUnlock(p->hShm, ofst+WIN_SHM_BASE, n); | |
| 52159 | + assert( !(flags & SQLITE_SHM_SHARED) || n==1 ); | |
| 52160 | + if( pShmNode->bUseSharedLockHandle ){ | |
| 52161 | + h = pShmNode->hSharedShm; | |
| 52162 | + if( flags & SQLITE_SHM_SHARED ){ | |
| 52163 | + winShm *pShm; | |
| 52164 | + sqlite3_mutex_enter(pShmNode->mutex); | |
| 52165 | + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ | |
| 52166 | + if( pShm!=p && (pShm->sharedMask & mask) ){ | |
| 52167 | + /* Another connection within this process is also holding this | |
| 52168 | + ** SHARED lock. So do not actually release the OS lock. */ | |
| 52169 | + h = INVALID_HANDLE_VALUE; | |
| 52170 | + break; | |
| 52171 | + } | |
| 52172 | + } | |
| 52173 | + sqlite3_mutex_leave(pShmNode->mutex); | |
| 52174 | + } | |
| 52175 | + } | |
| 52176 | + | |
| 52177 | + if( h!=INVALID_HANDLE_VALUE ){ | |
| 52178 | + rc = winHandleUnlock(h, ofst+WIN_SHM_BASE, n); | |
| 52179 | + } | |
| 51790 | 52180 | |
| 51791 | 52181 | /* If successful, also clear the bits in sharedMask/exclMask */ |
| 51792 | 52182 | if( rc==SQLITE_OK ){ |
| 51793 | 52183 | p->exclMask = (p->exclMask & ~mask); |
| 51794 | 52184 | p->sharedMask = (p->sharedMask & ~mask); |
| 51795 | 52185 | } |
| 51796 | 52186 | }else{ |
| 51797 | 52187 | int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0); |
| 51798 | 52188 | DWORD nMs = winFileBusyTimeout(pDbFd); |
| 51799 | - rc = winHandleLockTimeout(p->hShm, ofst+WIN_SHM_BASE, n, bExcl, nMs); | |
| 52189 | + | |
| 52190 | + if( pShmNode->bUseSharedLockHandle ){ | |
| 52191 | + winShm *pShm; | |
| 52192 | + h = pShmNode->hSharedShm; | |
| 52193 | + sqlite3_mutex_enter(pShmNode->mutex); | |
| 52194 | + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ | |
| 52195 | + if( bExcl ){ | |
| 52196 | + if( (pShm->sharedMask|pShm->exclMask) & mask ){ | |
| 52197 | + rc = SQLITE_BUSY; | |
| 52198 | + h = INVALID_HANDLE_VALUE; | |
| 52199 | + } | |
| 52200 | + }else{ | |
| 52201 | + if( pShm->sharedMask & mask ){ | |
| 52202 | + h = INVALID_HANDLE_VALUE; | |
| 52203 | + }else if( pShm->exclMask & mask ){ | |
| 52204 | + rc = SQLITE_BUSY; | |
| 52205 | + h = INVALID_HANDLE_VALUE; | |
| 52206 | + } | |
| 52207 | + } | |
| 52208 | + } | |
| 52209 | + sqlite3_mutex_leave(pShmNode->mutex); | |
| 52210 | + } | |
| 52211 | + | |
| 52212 | + if( h!=INVALID_HANDLE_VALUE ){ | |
| 52213 | + rc = winHandleLockTimeout(h, ofst+WIN_SHM_BASE, n, bExcl, nMs); | |
| 52214 | + } | |
| 51800 | 52215 | if( rc==SQLITE_OK ){ |
| 51801 | 52216 | if( bExcl ){ |
| 51802 | 52217 | p->exclMask = (p->exclMask | mask); |
| 51803 | 52218 | }else{ |
| 51804 | 52219 | p->sharedMask = (p->sharedMask | mask); |
| @@ -61825,18 +62240,31 @@ | ||
| 61825 | 62240 | SQLITE_PRIVATE void sqlite3PagerSetFlags( |
| 61826 | 62241 | Pager *pPager, /* The pager to set safety level for */ |
| 61827 | 62242 | unsigned pgFlags /* Various flags */ |
| 61828 | 62243 | ){ |
| 61829 | 62244 | unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; |
| 61830 | - if( pPager->tempFile ){ | |
| 62245 | + if( pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF ){ | |
| 61831 | 62246 | pPager->noSync = 1; |
| 61832 | 62247 | pPager->fullSync = 0; |
| 61833 | 62248 | pPager->extraSync = 0; |
| 61834 | 62249 | }else{ |
| 61835 | - pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; | |
| 62250 | + pPager->noSync = 0; | |
| 61836 | 62251 | pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; |
| 61837 | - pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; | |
| 62252 | + | |
| 62253 | + /* Set Pager.extraSync if "PRAGMA synchronous=EXTRA" is requested, or | |
| 62254 | + ** if the file-system supports F2FS style atomic writes. If this flag | |
| 62255 | + ** is set, SQLite syncs the directory to disk immediately after deleting | |
| 62256 | + ** a journal file in "PRAGMA journal_mode=DELETE" mode. */ | |
| 62257 | + if( level==PAGER_SYNCHRONOUS_EXTRA | |
| 62258 | +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE | |
| 62259 | + || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_BATCH_ATOMIC) | |
| 62260 | +#endif | |
| 62261 | + ){ | |
| 62262 | + pPager->extraSync = 1; | |
| 62263 | + }else{ | |
| 62264 | + pPager->extraSync = 0; | |
| 62265 | + } | |
| 61838 | 62266 | } |
| 61839 | 62267 | if( pPager->noSync ){ |
| 61840 | 62268 | pPager->syncFlags = 0; |
| 61841 | 62269 | }else if( pgFlags & PAGER_FULLFSYNC ){ |
| 61842 | 62270 | pPager->syncFlags = SQLITE_SYNC_FULL; |
| @@ -65725,11 +66153,11 @@ | ||
| 65725 | 66153 | */ |
| 65726 | 66154 | sqlite3_exec(db, "PRAGMA table_list",0,0,0); |
| 65727 | 66155 | } |
| 65728 | 66156 | if( pPager->pWal ){ |
| 65729 | 66157 | rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, |
| 65730 | - (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), | |
| 66158 | + (eMode<=SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), | |
| 65731 | 66159 | pPager->pBusyHandlerArg, |
| 65732 | 66160 | pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, |
| 65733 | 66161 | pnLog, pnCkpt |
| 65734 | 66162 | ); |
| 65735 | 66163 | } |
| @@ -70330,11 +70758,12 @@ | ||
| 70330 | 70758 | assert( pWal->ckptLock==0 ); |
| 70331 | 70759 | assert( pWal->writeLock==0 ); |
| 70332 | 70760 | |
| 70333 | 70761 | /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked |
| 70334 | 70762 | ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ |
| 70335 | - assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); | |
| 70763 | + assert( SQLITE_CHECKPOINT_NOOP<SQLITE_CHECKPOINT_PASSIVE ); | |
| 70764 | + assert( eMode>SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); | |
| 70336 | 70765 | |
| 70337 | 70766 | if( pWal->readOnly ) return SQLITE_READONLY; |
| 70338 | 70767 | WALTRACE(("WAL%p: checkpoint begins\n", pWal)); |
| 70339 | 70768 | |
| 70340 | 70769 | /* Enable blocking locks, if possible. */ |
| @@ -70347,35 +70776,39 @@ | ||
| 70347 | 70776 | ** checkpoint operation at the same time, the lock cannot be obtained and |
| 70348 | 70777 | ** SQLITE_BUSY is returned. |
| 70349 | 70778 | ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, |
| 70350 | 70779 | ** it will not be invoked in this case. |
| 70351 | 70780 | */ |
| 70352 | - rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); | |
| 70353 | - testcase( rc==SQLITE_BUSY ); | |
| 70354 | - testcase( rc!=SQLITE_OK && xBusy2!=0 ); | |
| 70355 | - if( rc==SQLITE_OK ){ | |
| 70356 | - pWal->ckptLock = 1; | |
| 70357 | - | |
| 70358 | - /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and | |
| 70359 | - ** TRUNCATE modes also obtain the exclusive "writer" lock on the database | |
| 70360 | - ** file. | |
| 70361 | - ** | |
| 70362 | - ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained | |
| 70363 | - ** immediately, and a busy-handler is configured, it is invoked and the | |
| 70364 | - ** writer lock retried until either the busy-handler returns 0 or the | |
| 70365 | - ** lock is successfully obtained. | |
| 70366 | - */ | |
| 70367 | - if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ | |
| 70368 | - rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); | |
| 70369 | - if( rc==SQLITE_OK ){ | |
| 70370 | - pWal->writeLock = 1; | |
| 70371 | - }else if( rc==SQLITE_BUSY ){ | |
| 70372 | - eMode2 = SQLITE_CHECKPOINT_PASSIVE; | |
| 70373 | - xBusy2 = 0; | |
| 70374 | - rc = SQLITE_OK; | |
| 70375 | - } | |
| 70376 | - } | |
| 70781 | + if( eMode!=SQLITE_CHECKPOINT_NOOP ){ | |
| 70782 | + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); | |
| 70783 | + testcase( rc==SQLITE_BUSY ); | |
| 70784 | + testcase( rc!=SQLITE_OK && xBusy2!=0 ); | |
| 70785 | + if( rc==SQLITE_OK ){ | |
| 70786 | + pWal->ckptLock = 1; | |
| 70787 | + | |
| 70788 | + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART | |
| 70789 | + ** and TRUNCATE modes also obtain the exclusive "writer" lock on the | |
| 70790 | + ** database file. | |
| 70791 | + ** | |
| 70792 | + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained | |
| 70793 | + ** immediately, and a busy-handler is configured, it is invoked and the | |
| 70794 | + ** writer lock retried until either the busy-handler returns 0 or the | |
| 70795 | + ** lock is successfully obtained. | |
| 70796 | + */ | |
| 70797 | + if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ | |
| 70798 | + rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); | |
| 70799 | + if( rc==SQLITE_OK ){ | |
| 70800 | + pWal->writeLock = 1; | |
| 70801 | + }else if( rc==SQLITE_BUSY ){ | |
| 70802 | + eMode2 = SQLITE_CHECKPOINT_PASSIVE; | |
| 70803 | + xBusy2 = 0; | |
| 70804 | + rc = SQLITE_OK; | |
| 70805 | + } | |
| 70806 | + } | |
| 70807 | + } | |
| 70808 | + }else{ | |
| 70809 | + rc = SQLITE_OK; | |
| 70377 | 70810 | } |
| 70378 | 70811 | |
| 70379 | 70812 | |
| 70380 | 70813 | /* Read the wal-index header. */ |
| 70381 | 70814 | SEH_TRY { |
| @@ -70385,21 +70818,21 @@ | ||
| 70385 | 70818 | ** or invoke the busy handler. The only lock such a checkpoint may |
| 70386 | 70819 | ** attempt to obtain is a lock on a read-slot, and it should give up |
| 70387 | 70820 | ** immediately and do a partial checkpoint if it cannot obtain it. */ |
| 70388 | 70821 | walDisableBlocking(pWal); |
| 70389 | 70822 | rc = walIndexReadHdr(pWal, &isChanged); |
| 70390 | - if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); | |
| 70823 | + if( eMode2>SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); | |
| 70391 | 70824 | if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ |
| 70392 | 70825 | sqlite3OsUnfetch(pWal->pDbFd, 0, 0); |
| 70393 | 70826 | } |
| 70394 | 70827 | } |
| 70395 | 70828 | |
| 70396 | 70829 | /* Copy data from the log to the database file. */ |
| 70397 | 70830 | if( rc==SQLITE_OK ){ |
| 70398 | 70831 | if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ |
| 70399 | 70832 | rc = SQLITE_CORRUPT_BKPT; |
| 70400 | - }else{ | |
| 70833 | + }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){ | |
| 70401 | 70834 | rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); |
| 70402 | 70835 | } |
| 70403 | 70836 | |
| 70404 | 70837 | /* If no error occurred, set the output variables. */ |
| 70405 | 70838 | if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ |
| @@ -89078,14 +89511,16 @@ | ||
| 89078 | 89511 | ** simple case then too. |
| 89079 | 89512 | */ |
| 89080 | 89513 | if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) |
| 89081 | 89514 | || nTrans<=1 |
| 89082 | 89515 | ){ |
| 89083 | - for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ | |
| 89084 | - Btree *pBt = db->aDb[i].pBt; | |
| 89085 | - if( pBt ){ | |
| 89086 | - rc = sqlite3BtreeCommitPhaseOne(pBt, 0); | |
| 89516 | + if( needXcommit ){ | |
| 89517 | + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ | |
| 89518 | + Btree *pBt = db->aDb[i].pBt; | |
| 89519 | + if( sqlite3BtreeTxnState(pBt)>=SQLITE_TXN_WRITE ){ | |
| 89520 | + rc = sqlite3BtreeCommitPhaseOne(pBt, 0); | |
| 89521 | + } | |
| 89087 | 89522 | } |
| 89088 | 89523 | } |
| 89089 | 89524 | |
| 89090 | 89525 | /* Do the commit only if all databases successfully complete phase 1. |
| 89091 | 89526 | ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an |
| @@ -89092,11 +89527,13 @@ | ||
| 89092 | 89527 | ** IO error while deleting or truncating a journal file. It is unlikely, |
| 89093 | 89528 | ** but could happen. In this case abandon processing and return the error. |
| 89094 | 89529 | */ |
| 89095 | 89530 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 89096 | 89531 | Btree *pBt = db->aDb[i].pBt; |
| 89097 | - if( pBt ){ | |
| 89532 | + int txn = sqlite3BtreeTxnState(pBt); | |
| 89533 | + if( txn!=SQLITE_TXN_NONE ){ | |
| 89534 | + assert( needXcommit || txn==SQLITE_TXN_READ ); | |
| 89098 | 89535 | rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); |
| 89099 | 89536 | } |
| 89100 | 89537 | } |
| 89101 | 89538 | if( rc==SQLITE_OK ){ |
| 89102 | 89539 | sqlite3VtabCommit(db); |
| @@ -89347,32 +89784,35 @@ | ||
| 89347 | 89784 | return SQLITE_OK; |
| 89348 | 89785 | } |
| 89349 | 89786 | |
| 89350 | 89787 | |
| 89351 | 89788 | /* |
| 89352 | -** This function is called when a transaction opened by the database | |
| 89789 | +** These functions are called when a transaction opened by the database | |
| 89353 | 89790 | ** handle associated with the VM passed as an argument is about to be |
| 89354 | -** committed. If there are outstanding deferred foreign key constraint | |
| 89355 | -** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. | |
| 89791 | +** committed. If there are outstanding foreign key constraint violations | |
| 89792 | +** return an error code. Otherwise, SQLITE_OK. | |
| 89356 | 89793 | ** |
| 89357 | 89794 | ** If there are outstanding FK violations and this function returns |
| 89358 | -** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY | |
| 89359 | -** and write an error message to it. Then return SQLITE_ERROR. | |
| 89795 | +** non-zero, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY | |
| 89796 | +** and write an error message to it. | |
| 89360 | 89797 | */ |
| 89361 | 89798 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 89362 | -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ | |
| 89799 | +static SQLITE_NOINLINE int vdbeFkError(Vdbe *p){ | |
| 89800 | + p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; | |
| 89801 | + p->errorAction = OE_Abort; | |
| 89802 | + sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); | |
| 89803 | + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; | |
| 89804 | + return SQLITE_CONSTRAINT_FOREIGNKEY; | |
| 89805 | +} | |
| 89806 | +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe *p){ | |
| 89807 | + if( p->nFkConstraint==0 ) return SQLITE_OK; | |
| 89808 | + return vdbeFkError(p); | |
| 89809 | +} | |
| 89810 | +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe *p){ | |
| 89363 | 89811 | sqlite3 *db = p->db; |
| 89364 | - if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) | |
| 89365 | - || (!deferred && p->nFkConstraint>0) | |
| 89366 | - ){ | |
| 89367 | - p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; | |
| 89368 | - p->errorAction = OE_Abort; | |
| 89369 | - sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); | |
| 89370 | - if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; | |
| 89371 | - return SQLITE_CONSTRAINT_FOREIGNKEY; | |
| 89372 | - } | |
| 89373 | - return SQLITE_OK; | |
| 89812 | + if( (db->nDeferredCons+db->nDeferredImmCons)==0 ) return SQLITE_OK; | |
| 89813 | + return vdbeFkError(p); | |
| 89374 | 89814 | } |
| 89375 | 89815 | #endif |
| 89376 | 89816 | |
| 89377 | 89817 | /* |
| 89378 | 89818 | ** This routine is called the when a VDBE tries to halt. If the VDBE |
| @@ -89462,11 +89902,11 @@ | ||
| 89462 | 89902 | } |
| 89463 | 89903 | } |
| 89464 | 89904 | |
| 89465 | 89905 | /* Check for immediate foreign key violations. */ |
| 89466 | 89906 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89467 | - (void)sqlite3VdbeCheckFk(p, 0); | |
| 89907 | + (void)sqlite3VdbeCheckFkImmediate(p); | |
| 89468 | 89908 | } |
| 89469 | 89909 | |
| 89470 | 89910 | /* If the auto-commit flag is set and this is the only active writer |
| 89471 | 89911 | ** VM, then we do either a commit or rollback of the current transaction. |
| 89472 | 89912 | ** |
| @@ -89476,11 +89916,11 @@ | ||
| 89476 | 89916 | if( !sqlite3VtabInSync(db) |
| 89477 | 89917 | && db->autoCommit |
| 89478 | 89918 | && db->nVdbeWrite==(p->readOnly==0) |
| 89479 | 89919 | ){ |
| 89480 | 89920 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89481 | - rc = sqlite3VdbeCheckFk(p, 1); | |
| 89921 | + rc = sqlite3VdbeCheckFkDeferred(p); | |
| 89482 | 89922 | if( rc!=SQLITE_OK ){ |
| 89483 | 89923 | if( NEVER(p->readOnly) ){ |
| 89484 | 89924 | sqlite3VdbeLeave(p); |
| 89485 | 89925 | return SQLITE_ERROR; |
| 89486 | 89926 | } |
| @@ -90341,19 +90781,19 @@ | ||
| 90341 | 90781 | /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ |
| 90342 | 90782 | pMem->szMalloc = 0; |
| 90343 | 90783 | pMem->z = 0; |
| 90344 | 90784 | sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); |
| 90345 | 90785 | d += sqlite3VdbeSerialTypeLen(serial_type); |
| 90346 | - pMem++; | |
| 90347 | 90786 | if( (++u)>=p->nField ) break; |
| 90787 | + pMem++; | |
| 90348 | 90788 | } |
| 90349 | 90789 | if( d>(u32)nKey && u ){ |
| 90350 | 90790 | assert( CORRUPT_DB ); |
| 90351 | 90791 | /* In a corrupt record entry, the last pMem might have been set up using |
| 90352 | 90792 | ** uninitialized memory. Overwrite its value with NULL, to prevent |
| 90353 | 90793 | ** warnings from MSAN. */ |
| 90354 | - sqlite3VdbeMemSetNull(pMem-1); | |
| 90794 | + sqlite3VdbeMemSetNull(pMem-(u<p->nField)); | |
| 90355 | 90795 | } |
| 90356 | 90796 | testcase( u == pKeyInfo->nKeyField + 1 ); |
| 90357 | 90797 | testcase( u < pKeyInfo->nKeyField + 1 ); |
| 90358 | 90798 | assert( u<=pKeyInfo->nKeyField + 1 ); |
| 90359 | 90799 | p->nField = u; |
| @@ -90520,10 +90960,36 @@ | ||
| 90520 | 90960 | ** Both *pMem1 and *pMem2 contain string values. Compare the two values |
| 90521 | 90961 | ** using the collation sequence pColl. As usual, return a negative , zero |
| 90522 | 90962 | ** or positive value if *pMem1 is less than, equal to or greater than |
| 90523 | 90963 | ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". |
| 90524 | 90964 | */ |
| 90965 | +static SQLITE_NOINLINE int vdbeCompareMemStringWithEncodingChange( | |
| 90966 | + const Mem *pMem1, | |
| 90967 | + const Mem *pMem2, | |
| 90968 | + const CollSeq *pColl, | |
| 90969 | + u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ | |
| 90970 | +){ | |
| 90971 | + int rc; | |
| 90972 | + const void *v1, *v2; | |
| 90973 | + Mem c1; | |
| 90974 | + Mem c2; | |
| 90975 | + sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); | |
| 90976 | + sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); | |
| 90977 | + sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); | |
| 90978 | + sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); | |
| 90979 | + v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); | |
| 90980 | + v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); | |
| 90981 | + if( (v1==0 || v2==0) ){ | |
| 90982 | + if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; | |
| 90983 | + rc = 0; | |
| 90984 | + }else{ | |
| 90985 | + rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); | |
| 90986 | + } | |
| 90987 | + sqlite3VdbeMemReleaseMalloc(&c1); | |
| 90988 | + sqlite3VdbeMemReleaseMalloc(&c2); | |
| 90989 | + return rc; | |
| 90990 | +} | |
| 90525 | 90991 | static int vdbeCompareMemString( |
| 90526 | 90992 | const Mem *pMem1, |
| 90527 | 90993 | const Mem *pMem2, |
| 90528 | 90994 | const CollSeq *pColl, |
| 90529 | 90995 | u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ |
| @@ -90531,29 +90997,11 @@ | ||
| 90531 | 90997 | if( pMem1->enc==pColl->enc ){ |
| 90532 | 90998 | /* The strings are already in the correct encoding. Call the |
| 90533 | 90999 | ** comparison function directly */ |
| 90534 | 91000 | return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); |
| 90535 | 91001 | }else{ |
| 90536 | - int rc; | |
| 90537 | - const void *v1, *v2; | |
| 90538 | - Mem c1; | |
| 90539 | - Mem c2; | |
| 90540 | - sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); | |
| 90541 | - sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); | |
| 90542 | - sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); | |
| 90543 | - sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); | |
| 90544 | - v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); | |
| 90545 | - v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); | |
| 90546 | - if( (v1==0 || v2==0) ){ | |
| 90547 | - if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; | |
| 90548 | - rc = 0; | |
| 90549 | - }else{ | |
| 90550 | - rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); | |
| 90551 | - } | |
| 90552 | - sqlite3VdbeMemReleaseMalloc(&c1); | |
| 90553 | - sqlite3VdbeMemReleaseMalloc(&c2); | |
| 90554 | - return rc; | |
| 91002 | + return vdbeCompareMemStringWithEncodingChange(pMem1,pMem2,pColl,prcErr); | |
| 90555 | 91003 | } |
| 90556 | 91004 | } |
| 90557 | 91005 | |
| 90558 | 91006 | /* |
| 90559 | 91007 | ** The input pBlob is guaranteed to be a Blob that is not marked |
| @@ -91607,11 +92055,11 @@ | ||
| 91607 | 92055 | |
| 91608 | 92056 | preupdate.v = v; |
| 91609 | 92057 | preupdate.pCsr = pCsr; |
| 91610 | 92058 | preupdate.op = op; |
| 91611 | 92059 | preupdate.iNewReg = iReg; |
| 91612 | - preupdate.pKeyinfo = (KeyInfo*)&preupdate.keyinfoSpace; | |
| 92060 | + preupdate.pKeyinfo = &preupdate.uKey.sKey; | |
| 91613 | 92061 | preupdate.pKeyinfo->db = db; |
| 91614 | 92062 | preupdate.pKeyinfo->enc = ENC(db); |
| 91615 | 92063 | preupdate.pKeyinfo->nKeyField = pTab->nCol; |
| 91616 | 92064 | preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */ |
| 91617 | 92065 | preupdate.iKey1 = iKey1; |
| @@ -91641,10 +92089,21 @@ | ||
| 91641 | 92089 | sqlite3DbFree(db, preupdate.apDflt); |
| 91642 | 92090 | } |
| 91643 | 92091 | } |
| 91644 | 92092 | #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 91645 | 92093 | |
| 92094 | +#ifdef SQLITE_ENABLE_PERCENTILE | |
| 92095 | +/* | |
| 92096 | +** Return the name of an SQL function associated with the sqlite3_context. | |
| 92097 | +*/ | |
| 92098 | +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ | |
| 92099 | + assert( pCtx!=0 ); | |
| 92100 | + assert( pCtx->pFunc!=0 ); | |
| 92101 | + return pCtx->pFunc->zName; | |
| 92102 | +} | |
| 92103 | +#endif /* SQLITE_ENABLE_PERCENTILE */ | |
| 92104 | + | |
| 91646 | 92105 | /************** End of vdbeaux.c *********************************************/ |
| 91647 | 92106 | /************** Begin file vdbeapi.c *****************************************/ |
| 91648 | 92107 | /* |
| 91649 | 92108 | ** 2004 May 26 |
| 91650 | 92109 | ** |
| @@ -93338,12 +93797,16 @@ | ||
| 93338 | 93797 | if( rc==SQLITE_OK ){ |
| 93339 | 93798 | assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ |
| 93340 | 93799 | if( zData!=0 ){ |
| 93341 | 93800 | pVar = &p->aVar[i-1]; |
| 93342 | 93801 | rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); |
| 93343 | - if( rc==SQLITE_OK && encoding!=0 ){ | |
| 93344 | - rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); | |
| 93802 | + if( rc==SQLITE_OK ){ | |
| 93803 | + if( encoding==0 ){ | |
| 93804 | + pVar->enc = ENC(p->db); | |
| 93805 | + }else{ | |
| 93806 | + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); | |
| 93807 | + } | |
| 93345 | 93808 | } |
| 93346 | 93809 | if( rc ){ |
| 93347 | 93810 | sqlite3Error(p->db, rc); |
| 93348 | 93811 | rc = sqlite3ApiExit(p->db, rc); |
| 93349 | 93812 | } |
| @@ -95247,11 +95710,11 @@ | ||
| 95247 | 95710 | ** to run faster. |
| 95248 | 95711 | */ |
| 95249 | 95712 | static SQLITE_NOINLINE int vdbeColumnFromOverflow( |
| 95250 | 95713 | VdbeCursor *pC, /* The BTree cursor from which we are reading */ |
| 95251 | 95714 | int iCol, /* The column to read */ |
| 95252 | - int t, /* The serial-type code for the column value */ | |
| 95715 | + u32 t, /* The serial-type code for the column value */ | |
| 95253 | 95716 | i64 iOffset, /* Offset to the start of the content value */ |
| 95254 | 95717 | u32 cacheStatus, /* Current Vdbe.cacheCtr value */ |
| 95255 | 95718 | u32 colCacheCtr, /* Current value of the column cache counter */ |
| 95256 | 95719 | Mem *pDest /* Store the value into this register. */ |
| 95257 | 95720 | ){ |
| @@ -96256,11 +96719,11 @@ | ||
| 96256 | 96719 | ** exits. This opcode is used to raise foreign key constraint errors prior |
| 96257 | 96720 | ** to returning results such as a row change count or the result of a |
| 96258 | 96721 | ** RETURNING clause. |
| 96259 | 96722 | */ |
| 96260 | 96723 | case OP_FkCheck: { |
| 96261 | - if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ | |
| 96724 | + if( (rc = sqlite3VdbeCheckFkImmediate(p))!=SQLITE_OK ){ | |
| 96262 | 96725 | goto abort_due_to_error; |
| 96263 | 96726 | } |
| 96264 | 96727 | break; |
| 96265 | 96728 | } |
| 96266 | 96729 | |
| @@ -96348,11 +96811,12 @@ | ||
| 96348 | 96811 | flags2 = pIn2->flags & ~MEM_Str; |
| 96349 | 96812 | }else if( (flags2 & MEM_Zero)!=0 ){ |
| 96350 | 96813 | if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; |
| 96351 | 96814 | flags2 = pIn2->flags & ~MEM_Str; |
| 96352 | 96815 | } |
| 96353 | - nByte = pIn1->n + pIn2->n; | |
| 96816 | + nByte = pIn1->n; | |
| 96817 | + nByte += pIn2->n; | |
| 96354 | 96818 | if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ |
| 96355 | 96819 | goto too_big; |
| 96356 | 96820 | } |
| 96357 | 96821 | if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ |
| 96358 | 96822 | goto no_mem; |
| @@ -98173,11 +98637,11 @@ | ||
| 98173 | 98637 | assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) ); |
| 98174 | 98638 | assert( pRec->n>=0 ); |
| 98175 | 98639 | len = (u32)pRec->n; |
| 98176 | 98640 | serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); |
| 98177 | 98641 | if( pRec->flags & MEM_Zero ){ |
| 98178 | - serial_type += pRec->u.nZero*2; | |
| 98642 | + serial_type += (u32)pRec->u.nZero*2; | |
| 98179 | 98643 | if( nData ){ |
| 98180 | 98644 | if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; |
| 98181 | 98645 | len += pRec->u.nZero; |
| 98182 | 98646 | }else{ |
| 98183 | 98647 | nZero += pRec->u.nZero; |
| @@ -98440,11 +98904,11 @@ | ||
| 98440 | 98904 | ** and this is a RELEASE command, then the current transaction |
| 98441 | 98905 | ** is committed. |
| 98442 | 98906 | */ |
| 98443 | 98907 | int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; |
| 98444 | 98908 | if( isTransaction && p1==SAVEPOINT_RELEASE ){ |
| 98445 | - if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ | |
| 98909 | + if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ | |
| 98446 | 98910 | goto vdbe_return; |
| 98447 | 98911 | } |
| 98448 | 98912 | db->autoCommit = 1; |
| 98449 | 98913 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| 98450 | 98914 | p->pc = (int)(pOp - aOp); |
| @@ -98558,11 +99022,11 @@ | ||
| 98558 | 99022 | */ |
| 98559 | 99023 | sqlite3VdbeError(p, "cannot commit transaction - " |
| 98560 | 99024 | "SQL statements in progress"); |
| 98561 | 99025 | rc = SQLITE_BUSY; |
| 98562 | 99026 | goto abort_due_to_error; |
| 98563 | - }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ | |
| 99027 | + }else if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ | |
| 98564 | 99028 | goto vdbe_return; |
| 98565 | 99029 | }else{ |
| 98566 | 99030 | db->autoCommit = (u8)desiredAutoCommit; |
| 98567 | 99031 | } |
| 98568 | 99032 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| @@ -102490,10 +102954,11 @@ | ||
| 102490 | 102954 | aRes[1] = aRes[2] = -1; |
| 102491 | 102955 | assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE |
| 102492 | 102956 | || pOp->p2==SQLITE_CHECKPOINT_FULL |
| 102493 | 102957 | || pOp->p2==SQLITE_CHECKPOINT_RESTART |
| 102494 | 102958 | || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE |
| 102959 | + || pOp->p2==SQLITE_CHECKPOINT_NOOP | |
| 102495 | 102960 | ); |
| 102496 | 102961 | rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); |
| 102497 | 102962 | if( rc ){ |
| 102498 | 102963 | if( rc!=SQLITE_BUSY ) goto abort_due_to_error; |
| 102499 | 102964 | rc = SQLITE_OK; |
| @@ -104689,10 +105154,11 @@ | ||
| 104689 | 105154 | UnpackedRecord *pUnpacked; /* Space to unpack a record */ |
| 104690 | 105155 | SorterList list; /* List for thread to write to a PMA */ |
| 104691 | 105156 | SorterCompare xCompare; /* Compare function to use */ |
| 104692 | 105157 | SorterFile file; /* Temp file for level-0 PMAs */ |
| 104693 | 105158 | SorterFile file2; /* Space for other PMAs */ |
| 105159 | + u64 nSpill; /* Total bytes written by this task */ | |
| 104694 | 105160 | }; |
| 104695 | 105161 | |
| 104696 | 105162 | |
| 104697 | 105163 | /* |
| 104698 | 105164 | ** Main sorter structure. A single instance of this is allocated for each |
| @@ -104809,10 +105275,11 @@ | ||
| 104809 | 105275 | int nBuffer; /* Size of write buffer in bytes */ |
| 104810 | 105276 | int iBufStart; /* First byte of buffer to write */ |
| 104811 | 105277 | int iBufEnd; /* Last byte of buffer to write */ |
| 104812 | 105278 | i64 iWriteOff; /* Offset of start of buffer in file */ |
| 104813 | 105279 | sqlite3_file *pFd; /* File handle to write to */ |
| 105280 | + u64 nPmaSpill; /* Total number of bytes written */ | |
| 104814 | 105281 | }; |
| 104815 | 105282 | |
| 104816 | 105283 | /* |
| 104817 | 105284 | ** This object is the header on a single record while that record is being |
| 104818 | 105285 | ** held in memory and prior to being written out as part of a PMA. |
| @@ -105667,10 +106134,16 @@ | ||
| 105667 | 106134 | SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ |
| 105668 | 106135 | VdbeSorter *pSorter; |
| 105669 | 106136 | assert( pCsr->eCurType==CURTYPE_SORTER ); |
| 105670 | 106137 | pSorter = pCsr->uc.pSorter; |
| 105671 | 106138 | if( pSorter ){ |
| 106139 | + /* Increment db->nSpill by the total number of bytes of data written | |
| 106140 | + ** to temp files by this sort operation. */ | |
| 106141 | + int ii; | |
| 106142 | + for(ii=0; ii<pSorter->nTask; ii++){ | |
| 106143 | + db->nSpill += pSorter->aTask[ii].nSpill; | |
| 106144 | + } | |
| 105672 | 106145 | sqlite3VdbeSorterReset(db, pSorter); |
| 105673 | 106146 | sqlite3_free(pSorter->list.aMemory); |
| 105674 | 106147 | sqlite3DbFree(db, pSorter); |
| 105675 | 106148 | pCsr->uc.pSorter = 0; |
| 105676 | 106149 | } |
| @@ -105892,10 +106365,11 @@ | ||
| 105892 | 106365 | if( p->iBufEnd==p->nBuffer ){ |
| 105893 | 106366 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 105894 | 106367 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 105895 | 106368 | p->iWriteOff + p->iBufStart |
| 105896 | 106369 | ); |
| 106370 | + p->nPmaSpill += (p->iBufEnd - p->iBufStart); | |
| 105897 | 106371 | p->iBufStart = p->iBufEnd = 0; |
| 105898 | 106372 | p->iWriteOff += p->nBuffer; |
| 105899 | 106373 | } |
| 105900 | 106374 | assert( p->iBufEnd<p->nBuffer ); |
| 105901 | 106375 | |
| @@ -105908,21 +106382,24 @@ | ||
| 105908 | 106382 | ** The results of using the PMA-writer after this call are undefined. |
| 105909 | 106383 | ** Return SQLITE_OK if flushing the buffered data succeeds or is not |
| 105910 | 106384 | ** required. Otherwise, return an SQLite error code. |
| 105911 | 106385 | ** |
| 105912 | 106386 | ** Before returning, set *piEof to the offset immediately following the |
| 105913 | -** last byte written to the file. | |
| 106387 | +** last byte written to the file. Also, increment (*pnSpill) by the total | |
| 106388 | +** number of bytes written to the file. | |
| 105914 | 106389 | */ |
| 105915 | -static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ | |
| 106390 | +static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof, u64 *pnSpill){ | |
| 105916 | 106391 | int rc; |
| 105917 | 106392 | if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ |
| 105918 | 106393 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 105919 | 106394 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 105920 | 106395 | p->iWriteOff + p->iBufStart |
| 105921 | 106396 | ); |
| 106397 | + p->nPmaSpill += (p->iBufEnd - p->iBufStart); | |
| 105922 | 106398 | } |
| 105923 | 106399 | *piEof = (p->iWriteOff + p->iBufEnd); |
| 106400 | + *pnSpill += p->nPmaSpill; | |
| 105924 | 106401 | sqlite3_free(p->aBuffer); |
| 105925 | 106402 | rc = p->eFWErr; |
| 105926 | 106403 | memset(p, 0, sizeof(PmaWriter)); |
| 105927 | 106404 | return rc; |
| 105928 | 106405 | } |
| @@ -105998,11 +106475,11 @@ | ||
| 105998 | 106475 | vdbePmaWriteVarint(&writer, p->nVal); |
| 105999 | 106476 | vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal); |
| 106000 | 106477 | if( pList->aMemory==0 ) sqlite3_free(p); |
| 106001 | 106478 | } |
| 106002 | 106479 | pList->pList = p; |
| 106003 | - rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); | |
| 106480 | + rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof, &pTask->nSpill); | |
| 106004 | 106481 | } |
| 106005 | 106482 | |
| 106006 | 106483 | vdbeSorterWorkDebug(pTask, "exit"); |
| 106007 | 106484 | assert( rc!=SQLITE_OK || pList->pList==0 ); |
| 106008 | 106485 | assert( rc!=SQLITE_OK || pTask->file.iEof==iSz ); |
| @@ -106312,11 +106789,11 @@ | ||
| 106312 | 106789 | vdbePmaWriteBlob(&writer, pReader->aKey, nKey); |
| 106313 | 106790 | assert( pIncr->pMerger->pTask==pTask ); |
| 106314 | 106791 | rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); |
| 106315 | 106792 | } |
| 106316 | 106793 | |
| 106317 | - rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); | |
| 106794 | + rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof, &pTask->nSpill); | |
| 106318 | 106795 | if( rc==SQLITE_OK ) rc = rc2; |
| 106319 | 106796 | vdbeSorterPopulateDebug(pTask, "exit"); |
| 106320 | 106797 | return rc; |
| 106321 | 106798 | } |
| 106322 | 106799 | |
| @@ -109256,12 +109733,12 @@ | ||
| 109256 | 109733 | assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ |
| 109257 | 109734 | if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); |
| 109258 | 109735 | |
| 109259 | 109736 | /* |
| 109260 | 109737 | ** Expression p should encode a floating point value between 1.0 and 0.0. |
| 109261 | -** Return 1024 times this value. Or return -1 if p is not a floating point | |
| 109262 | -** value between 1.0 and 0.0. | |
| 109738 | +** Return 134,217,728 (2^27) times this value. Or return -1 if p is not | |
| 109739 | +** a floating point value between 1.0 and 0.0. | |
| 109263 | 109740 | */ |
| 109264 | 109741 | static int exprProbability(Expr *p){ |
| 109265 | 109742 | double r = -1.0; |
| 109266 | 109743 | if( p->op!=TK_FLOAT ) return -1; |
| 109267 | 109744 | assert( !ExprHasProperty(p, EP_IntValue) ); |
| @@ -110613,18 +111090,21 @@ | ||
| 110613 | 111090 | ExprList *pList /* Expression list to resolve. May be NULL. */ |
| 110614 | 111091 | ){ |
| 110615 | 111092 | SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */ |
| 110616 | 111093 | NameContext sNC; /* Name context for pParse->pNewTable */ |
| 110617 | 111094 | int rc; |
| 110618 | - u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ | |
| 111095 | + union { | |
| 111096 | + SrcList sSrc; | |
| 111097 | + u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ | |
| 111098 | + } uSrc; | |
| 110619 | 111099 | |
| 110620 | 111100 | assert( type==0 || pTab!=0 ); |
| 110621 | 111101 | assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr |
| 110622 | 111102 | || type==NC_GenCol || pTab==0 ); |
| 110623 | 111103 | memset(&sNC, 0, sizeof(sNC)); |
| 110624 | - pSrc = (SrcList*)srcSpace; | |
| 110625 | - memset(pSrc, 0, SZ_SRCLIST_1); | |
| 111104 | + memset(&uSrc, 0, sizeof(uSrc)); | |
| 111105 | + pSrc = &uSrc.sSrc; | |
| 110626 | 111106 | if( pTab ){ |
| 110627 | 111107 | pSrc->nSrc = 1; |
| 110628 | 111108 | pSrc->a[0].zName = pTab->zName; |
| 110629 | 111109 | pSrc->a[0].pSTab = pTab; |
| 110630 | 111110 | pSrc->a[0].iCursor = -1; |
| @@ -111883,10 +112363,15 @@ | ||
| 111883 | 112363 | if( IsWindowFunc(pExpr) ){ |
| 111884 | 112364 | sqlite3ExprOrderByAggregateError(pParse, pExpr); |
| 111885 | 112365 | sqlite3ExprListDelete(db, pOrderBy); |
| 111886 | 112366 | return; |
| 111887 | 112367 | } |
| 112368 | + if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ | |
| 112369 | + sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause"); | |
| 112370 | + sqlite3ExprListDelete(db, pOrderBy); | |
| 112371 | + return; | |
| 112372 | + } | |
| 111888 | 112373 | |
| 111889 | 112374 | pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); |
| 111890 | 112375 | if( pOB==0 ){ |
| 111891 | 112376 | sqlite3ExprListDelete(db, pOrderBy); |
| 111892 | 112377 | return; |
| @@ -113079,13 +113564,12 @@ | ||
| 113079 | 113564 | } |
| 113080 | 113565 | r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1); |
| 113081 | 113566 | if( addrIsNull==0 ){ |
| 113082 | 113567 | /* |
| 113083 | 113568 | ** If the right operand contains a subquery and the left operand does not |
| 113084 | - ** and the left operand might be NULL, then check the left operand do | |
| 113085 | - ** an IsNull check on the left operand before computing the right | |
| 113086 | - ** operand. | |
| 113569 | + ** and the left operand might be NULL, then do an IsNull check | |
| 113570 | + ** check on the left operand before computing the right operand. | |
| 113087 | 113571 | */ |
| 113088 | 113572 | if( ExprHasProperty(pExpr->pRight, EP_Subquery) |
| 113089 | 113573 | && sqlite3ExprCanBeNull(pExpr->pLeft) |
| 113090 | 113574 | ){ |
| 113091 | 113575 | addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1); |
| @@ -114645,11 +115129,10 @@ | ||
| 114645 | 115129 | int destIfNull /* Jump here if the results are unknown due to NULLs */ |
| 114646 | 115130 | ){ |
| 114647 | 115131 | int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ |
| 114648 | 115132 | int eType; /* Type of the RHS */ |
| 114649 | 115133 | int rLhs; /* Register(s) holding the LHS values */ |
| 114650 | - int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ | |
| 114651 | 115134 | Vdbe *v; /* Statement under construction */ |
| 114652 | 115135 | int *aiMap = 0; /* Map from vector field to index column */ |
| 114653 | 115136 | char *zAff = 0; /* Affinity string for comparisons */ |
| 114654 | 115137 | int nVector; /* Size of vectors for this IN operator */ |
| 114655 | 115138 | int iDummy; /* Dummy parameter to exprCodeVector() */ |
| @@ -114708,23 +115191,12 @@ | ||
| 114708 | 115191 | ** Avoid factoring the LHS of the IN(...) expression out of the loop, |
| 114709 | 115192 | ** even if it is constant, as OP_Affinity may be used on the register |
| 114710 | 115193 | ** by code generated below. */ |
| 114711 | 115194 | assert( pParse->okConstFactor==okConstFactor ); |
| 114712 | 115195 | pParse->okConstFactor = 0; |
| 114713 | - rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); | |
| 115196 | + rLhs = exprCodeVector(pParse, pLeft, &iDummy); | |
| 114714 | 115197 | pParse->okConstFactor = okConstFactor; |
| 114715 | - for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ | |
| 114716 | - if( i==nVector ){ | |
| 114717 | - /* LHS fields are not reordered */ | |
| 114718 | - rLhs = rLhsOrig; | |
| 114719 | - }else{ | |
| 114720 | - /* Need to reorder the LHS fields according to aiMap */ | |
| 114721 | - rLhs = sqlite3GetTempRange(pParse, nVector); | |
| 114722 | - for(i=0; i<nVector; i++){ | |
| 114723 | - sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); | |
| 114724 | - } | |
| 114725 | - } | |
| 114726 | 115198 | |
| 114727 | 115199 | /* If sqlite3FindInIndex() did not find or create an index that is |
| 114728 | 115200 | ** suitable for evaluating the IN operator, then evaluate using a |
| 114729 | 115201 | ** sequence of comparisons. |
| 114730 | 115202 | ** |
| @@ -114735,10 +115207,11 @@ | ||
| 114735 | 115207 | CollSeq *pColl; |
| 114736 | 115208 | int labelOk = sqlite3VdbeMakeLabel(pParse); |
| 114737 | 115209 | int r2, regToFree; |
| 114738 | 115210 | int regCkNull = 0; |
| 114739 | 115211 | int ii; |
| 115212 | + assert( nVector==1 ); | |
| 114740 | 115213 | assert( ExprUseXList(pExpr) ); |
| 114741 | 115214 | pList = pExpr->x.pList; |
| 114742 | 115215 | pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); |
| 114743 | 115216 | if( destIfNull!=destIfFalse ){ |
| 114744 | 115217 | regCkNull = sqlite3GetTempReg(pParse); |
| @@ -114775,10 +115248,30 @@ | ||
| 114775 | 115248 | } |
| 114776 | 115249 | sqlite3VdbeResolveLabel(v, labelOk); |
| 114777 | 115250 | sqlite3ReleaseTempReg(pParse, regCkNull); |
| 114778 | 115251 | goto sqlite3ExprCodeIN_finished; |
| 114779 | 115252 | } |
| 115253 | + | |
| 115254 | + if( eType!=IN_INDEX_ROWID ){ | |
| 115255 | + /* If this IN operator will use an index, then the order of columns in the | |
| 115256 | + ** vector might be different from the order in the index. In that case, | |
| 115257 | + ** we need to reorder the LHS values to be in index order. Run Affinity | |
| 115258 | + ** before reordering the columns, so that the affinity is correct. | |
| 115259 | + */ | |
| 115260 | + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); | |
| 115261 | + for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ | |
| 115262 | + if( i!=nVector ){ | |
| 115263 | + /* Need to reorder the LHS fields according to aiMap */ | |
| 115264 | + int rLhsOrig = rLhs; | |
| 115265 | + rLhs = sqlite3GetTempRange(pParse, nVector); | |
| 115266 | + for(i=0; i<nVector; i++){ | |
| 115267 | + sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); | |
| 115268 | + } | |
| 115269 | + sqlite3ReleaseTempReg(pParse, rLhsOrig); | |
| 115270 | + } | |
| 115271 | + } | |
| 115272 | + | |
| 114780 | 115273 | |
| 114781 | 115274 | /* Step 2: Check to see if the LHS contains any NULL columns. If the |
| 114782 | 115275 | ** LHS does contain NULLs then the result must be either FALSE or NULL. |
| 114783 | 115276 | ** We will then skip the binary search of the RHS. |
| 114784 | 115277 | */ |
| @@ -114802,15 +115295,15 @@ | ||
| 114802 | 115295 | */ |
| 114803 | 115296 | if( eType==IN_INDEX_ROWID ){ |
| 114804 | 115297 | /* In this case, the RHS is the ROWID of table b-tree and so we also |
| 114805 | 115298 | ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 |
| 114806 | 115299 | ** into a single opcode. */ |
| 115300 | + assert( nVector==1 ); | |
| 114807 | 115301 | sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); |
| 114808 | 115302 | VdbeCoverage(v); |
| 114809 | 115303 | addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ |
| 114810 | 115304 | }else{ |
| 114811 | - sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); | |
| 114812 | 115305 | if( destIfFalse==destIfNull ){ |
| 114813 | 115306 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 114814 | 115307 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 114815 | 115308 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 114816 | 115309 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| @@ -114884,11 +115377,10 @@ | ||
| 114884 | 115377 | |
| 114885 | 115378 | /* Jumps here in order to return true. */ |
| 114886 | 115379 | sqlite3VdbeJumpHere(v, addrTruthOp); |
| 114887 | 115380 | |
| 114888 | 115381 | sqlite3ExprCodeIN_finished: |
| 114889 | - if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); | |
| 114890 | 115382 | VdbeComment((v, "end IN expr")); |
| 114891 | 115383 | sqlite3ExprCodeIN_oom_error: |
| 114892 | 115384 | sqlite3DbFree(pParse->db, aiMap); |
| 114893 | 115385 | sqlite3DbFree(pParse->db, zAff); |
| 114894 | 115386 | } |
| @@ -115442,10 +115934,84 @@ | ||
| 115442 | 115934 | } |
| 115443 | 115935 | } |
| 115444 | 115936 | return 0; |
| 115445 | 115937 | } |
| 115446 | 115938 | |
| 115939 | +/* | |
| 115940 | +** Generate code that evaluates an AND or OR operator leaving a | |
| 115941 | +** boolean result in a register. pExpr is the AND/OR expression. | |
| 115942 | +** Store the result in the "target" register. Use short-circuit | |
| 115943 | +** evaluation to avoid computing both operands, if possible. | |
| 115944 | +** | |
| 115945 | +** The code generated might require the use of a temporary register. | |
| 115946 | +** If it does, then write the number of that temporary register | |
| 115947 | +** into *pTmpReg. If not, leave *pTmpReg unchanged. | |
| 115948 | +*/ | |
| 115949 | +static SQLITE_NOINLINE int exprCodeTargetAndOr( | |
| 115950 | + Parse *pParse, /* Parsing context */ | |
| 115951 | + Expr *pExpr, /* AND or OR expression to be coded */ | |
| 115952 | + int target, /* Put result in this register, guaranteed */ | |
| 115953 | + int *pTmpReg /* Write a temporary register here */ | |
| 115954 | +){ | |
| 115955 | + int op; /* The opcode. TK_AND or TK_OR */ | |
| 115956 | + int skipOp; /* Opcode for the branch that skips one operand */ | |
| 115957 | + int addrSkip; /* Branch instruction that skips one of the operands */ | |
| 115958 | + int regSS = 0; /* Register holding computed operand when other omitted */ | |
| 115959 | + int r1, r2; /* Registers for left and right operands, respectively */ | |
| 115960 | + Expr *pAlt; /* Alternative, simplified expression */ | |
| 115961 | + Vdbe *v; /* statement being coded */ | |
| 115962 | + | |
| 115963 | + assert( pExpr!=0 ); | |
| 115964 | + op = pExpr->op; | |
| 115965 | + assert( op==TK_AND || op==TK_OR ); | |
| 115966 | + assert( TK_AND==OP_And ); testcase( op==TK_AND ); | |
| 115967 | + assert( TK_OR==OP_Or ); testcase( op==TK_OR ); | |
| 115968 | + assert( pParse->pVdbe!=0 ); | |
| 115969 | + v = pParse->pVdbe; | |
| 115970 | + pAlt = sqlite3ExprSimplifiedAndOr(pExpr); | |
| 115971 | + if( pAlt!=pExpr ){ | |
| 115972 | + r1 = sqlite3ExprCodeTarget(pParse, pAlt, target); | |
| 115973 | + sqlite3VdbeAddOp3(v, OP_BitAnd, r1, r1, target); | |
| 115974 | + return target; | |
| 115975 | + } | |
| 115976 | + skipOp = op==TK_AND ? OP_IfNot : OP_If; | |
| 115977 | + if( exprEvalRhsFirst(pExpr) ){ | |
| 115978 | + /* Compute the right operand first. Skip the computation of the left | |
| 115979 | + ** operand if the right operand fully determines the result */ | |
| 115980 | + r2 = regSS = sqlite3ExprCodeTarget(pParse, pExpr->pRight, target); | |
| 115981 | + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r2); | |
| 115982 | + VdbeComment((v, "skip left operand")); | |
| 115983 | + VdbeCoverage(v); | |
| 115984 | + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pTmpReg); | |
| 115985 | + }else{ | |
| 115986 | + /* Compute the left operand first */ | |
| 115987 | + r1 = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); | |
| 115988 | + if( ExprHasProperty(pExpr->pRight, EP_Subquery) ){ | |
| 115989 | + /* Skip over the computation of the right operand if the right | |
| 115990 | + ** operand is a subquery and the left operand completely determines | |
| 115991 | + ** the result */ | |
| 115992 | + regSS = r1; | |
| 115993 | + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r1); | |
| 115994 | + VdbeComment((v, "skip right operand")); | |
| 115995 | + VdbeCoverage(v); | |
| 115996 | + }else{ | |
| 115997 | + addrSkip = regSS = 0; | |
| 115998 | + } | |
| 115999 | + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pTmpReg); | |
| 116000 | + } | |
| 116001 | + sqlite3VdbeAddOp3(v, op, r2, r1, target); | |
| 116002 | + testcase( (*pTmpReg)==0 ); | |
| 116003 | + if( addrSkip ){ | |
| 116004 | + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); | |
| 116005 | + sqlite3VdbeJumpHere(v, addrSkip); | |
| 116006 | + sqlite3VdbeAddOp3(v, OP_Or, regSS, regSS, target); | |
| 116007 | + VdbeComment((v, "short-circut value")); | |
| 116008 | + } | |
| 116009 | + return target; | |
| 116010 | +} | |
| 116011 | + | |
| 116012 | + | |
| 115447 | 116013 | |
| 115448 | 116014 | /* |
| 115449 | 116015 | ** Generate code into the current Vdbe to evaluate the given |
| 115450 | 116016 | ** expression. Attempt to store the results in register "target". |
| 115451 | 116017 | ** Return the register where results are stored. |
| @@ -115633,10 +116199,16 @@ | ||
| 115633 | 116199 | case TK_STRING: { |
| 115634 | 116200 | assert( !ExprHasProperty(pExpr, EP_IntValue) ); |
| 115635 | 116201 | sqlite3VdbeLoadString(v, target, pExpr->u.zToken); |
| 115636 | 116202 | return target; |
| 115637 | 116203 | } |
| 116204 | + case TK_NULLS: { | |
| 116205 | + /* Set a range of registers to NULL. pExpr->y.nReg registers starting | |
| 116206 | + ** with target */ | |
| 116207 | + sqlite3VdbeAddOp3(v, OP_Null, 0, target, target + pExpr->y.nReg - 1); | |
| 116208 | + return target; | |
| 116209 | + } | |
| 115638 | 116210 | default: { |
| 115639 | 116211 | /* Make NULL the default case so that if a bug causes an illegal |
| 115640 | 116212 | ** Expr node to be passed into this function, it will be handled |
| 115641 | 116213 | ** sanely and not crash. But keep the assert() to bring the problem |
| 115642 | 116214 | ** to the attention of the developers. */ |
| @@ -115724,16 +116296,18 @@ | ||
| 115724 | 116296 | sqlite3VdbeAddOp2(v, OP_Null, 0, inReg); |
| 115725 | 116297 | } |
| 115726 | 116298 | } |
| 115727 | 116299 | testcase( regFree1==0 ); |
| 115728 | 116300 | testcase( regFree2==0 ); |
| 115729 | - | |
| 115730 | 116301 | } |
| 115731 | 116302 | break; |
| 115732 | 116303 | } |
| 115733 | 116304 | case TK_AND: |
| 115734 | - case TK_OR: | |
| 116305 | + case TK_OR: { | |
| 116306 | + inReg = exprCodeTargetAndOr(pParse, pExpr, target, ®Free1); | |
| 116307 | + break; | |
| 116308 | + } | |
| 115735 | 116309 | case TK_PLUS: |
| 115736 | 116310 | case TK_STAR: |
| 115737 | 116311 | case TK_MINUS: |
| 115738 | 116312 | case TK_REM: |
| 115739 | 116313 | case TK_BITAND: |
| @@ -115741,12 +116315,10 @@ | ||
| 115741 | 116315 | case TK_SLASH: |
| 115742 | 116316 | case TK_LSHIFT: |
| 115743 | 116317 | case TK_RSHIFT: |
| 115744 | 116318 | case TK_CONCAT: { |
| 115745 | 116319 | int addrIsNull; |
| 115746 | - assert( TK_AND==OP_And ); testcase( op==TK_AND ); | |
| 115747 | - assert( TK_OR==OP_Or ); testcase( op==TK_OR ); | |
| 115748 | 116320 | assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); |
| 115749 | 116321 | assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); |
| 115750 | 116322 | assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); |
| 115751 | 116323 | assert( TK_BITAND==OP_BitAnd ); testcase( op==TK_BITAND ); |
| 115752 | 116324 | assert( TK_BITOR==OP_BitOr ); testcase( op==TK_BITOR ); |
| @@ -116341,10 +116913,29 @@ | ||
| 116341 | 116913 | } |
| 116342 | 116914 | pParse->pConstExpr = p; |
| 116343 | 116915 | } |
| 116344 | 116916 | return regDest; |
| 116345 | 116917 | } |
| 116918 | + | |
| 116919 | +/* | |
| 116920 | +** Make arrangements to invoke OP_Null on a range of registers | |
| 116921 | +** during initialization. | |
| 116922 | +*/ | |
| 116923 | +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3ExprNullRegisterRange( | |
| 116924 | + Parse *pParse, /* Parsing context */ | |
| 116925 | + int iReg, /* First register to set to NULL */ | |
| 116926 | + int nReg /* Number of sequential registers to NULL out */ | |
| 116927 | +){ | |
| 116928 | + u8 okConstFactor = pParse->okConstFactor; | |
| 116929 | + Expr t; | |
| 116930 | + memset(&t, 0, sizeof(t)); | |
| 116931 | + t.op = TK_NULLS; | |
| 116932 | + t.y.nReg = nReg; | |
| 116933 | + pParse->okConstFactor = 1; | |
| 116934 | + sqlite3ExprCodeRunJustOnce(pParse, &t, iReg); | |
| 116935 | + pParse->okConstFactor = okConstFactor; | |
| 116936 | +} | |
| 116346 | 116937 | |
| 116347 | 116938 | /* |
| 116348 | 116939 | ** Generate code to evaluate an expression and store the results |
| 116349 | 116940 | ** into a register. Return the register number where the results |
| 116350 | 116941 | ** are stored. |
| @@ -123872,10 +124463,20 @@ | ||
| 123872 | 124463 | if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ |
| 123873 | 124464 | Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); |
| 123874 | 124465 | if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ |
| 123875 | 124466 | pMod = sqlite3PragmaVtabRegister(db, zName); |
| 123876 | 124467 | } |
| 124468 | +#ifndef SQLITE_OMIT_JSON | |
| 124469 | + if( pMod==0 && sqlite3_strnicmp(zName, "json", 4)==0 ){ | |
| 124470 | + pMod = sqlite3JsonVtabRegister(db, zName); | |
| 124471 | + } | |
| 124472 | +#endif | |
| 124473 | +#ifdef SQLITE_ENABLE_CARRAY | |
| 124474 | + if( pMod==0 && sqlite3_stricmp(zName, "carray")==0 ){ | |
| 124475 | + pMod = sqlite3CarrayRegister(db); | |
| 124476 | + } | |
| 124477 | +#endif | |
| 123877 | 124478 | if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ |
| 123878 | 124479 | testcase( pMod->pEpoTab==0 ); |
| 123879 | 124480 | return pMod->pEpoTab; |
| 123880 | 124481 | } |
| 123881 | 124482 | } |
| @@ -124510,11 +125111,11 @@ | ||
| 124510 | 125111 | */ |
| 124511 | 125112 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 124512 | 125113 | int i; |
| 124513 | 125114 | i16 iCol16; |
| 124514 | 125115 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 124515 | - assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 ); | |
| 125116 | + assert( pIdx->nColumn<=SQLITE_MAX_COLUMN*2 ); | |
| 124516 | 125117 | iCol16 = iCol; |
| 124517 | 125118 | for(i=0; i<pIdx->nColumn; i++){ |
| 124518 | 125119 | if( iCol16==pIdx->aiColumn[i] ){ |
| 124519 | 125120 | return i; |
| 124520 | 125121 | } |
| @@ -124807,10 +125408,13 @@ | ||
| 124807 | 125408 | sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); |
| 124808 | 125409 | sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC); |
| 124809 | 125410 | sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); |
| 124810 | 125411 | sqlite3VdbeChangeP5(v, OPFLAG_APPEND); |
| 124811 | 125412 | sqlite3VdbeAddOp0(v, OP_Close); |
| 125413 | + }else if( db->init.imposterTable ){ | |
| 125414 | + pTable->tabFlags |= TF_Imposter; | |
| 125415 | + if( db->init.imposterTable>=2 ) pTable->tabFlags |= TF_Readonly; | |
| 124812 | 125416 | } |
| 124813 | 125417 | |
| 124814 | 125418 | /* Normal (non-error) return. */ |
| 124815 | 125419 | return; |
| 124816 | 125420 | |
| @@ -129102,18 +129706,23 @@ | ||
| 129102 | 129706 | pKey->aSortFlags[i] = pIdx->aSortOrder[i]; |
| 129103 | 129707 | assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) ); |
| 129104 | 129708 | } |
| 129105 | 129709 | if( pParse->nErr ){ |
| 129106 | 129710 | assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); |
| 129107 | - if( pIdx->bNoQuery==0 ){ | |
| 129711 | + if( pIdx->bNoQuery==0 | |
| 129712 | + && sqlite3HashFind(&pIdx->pSchema->idxHash, pIdx->zName) | |
| 129713 | + ){ | |
| 129108 | 129714 | /* Deactivate the index because it contains an unknown collating |
| 129109 | 129715 | ** sequence. The only way to reactive the index is to reload the |
| 129110 | 129716 | ** schema. Adding the missing collating sequence later does not |
| 129111 | 129717 | ** reactive the index. The application had the chance to register |
| 129112 | 129718 | ** the missing index using the collation-needed callback. For |
| 129113 | 129719 | ** simplicity, SQLite will not give the application a second chance. |
| 129114 | - */ | |
| 129720 | + ** | |
| 129721 | + ** Except, do not do this if the index is not in the schema hash | |
| 129722 | + ** table. In this case the index is currently being constructed | |
| 129723 | + ** by a CREATE INDEX statement, and retrying will not help. */ | |
| 129115 | 129724 | pIdx->bNoQuery = 1; |
| 129116 | 129725 | pParse->rc = SQLITE_ERROR_RETRY; |
| 129117 | 129726 | } |
| 129118 | 129727 | sqlite3KeyInfoUnref(pKey); |
| 129119 | 129728 | pKey = 0; |
| @@ -131977,11 +132586,11 @@ | ||
| 131977 | 132586 | ** a decoding of those digits into *pVal. Or return false if any |
| 131978 | 132587 | ** one of the first N characters in z[] is not a hexadecimal digit. |
| 131979 | 132588 | */ |
| 131980 | 132589 | static int isNHex(const char *z, int N, u32 *pVal){ |
| 131981 | 132590 | int i; |
| 131982 | - int v = 0; | |
| 132591 | + u32 v = 0; | |
| 131983 | 132592 | for(i=0; i<N; i++){ |
| 131984 | 132593 | if( !sqlite3Isxdigit(z[i]) ) return 0; |
| 131985 | 132594 | v = (v<<4) + sqlite3HexToInt(z[i]); |
| 131986 | 132595 | } |
| 131987 | 132596 | *pVal = v; |
| @@ -132490,10 +133099,11 @@ | ||
| 132490 | 133099 | int nSep, |
| 132491 | 133100 | const char *zSep |
| 132492 | 133101 | ){ |
| 132493 | 133102 | i64 j, n = 0; |
| 132494 | 133103 | int i; |
| 133104 | + int bNotNull = 0; /* True after at least NOT NULL argument seen */ | |
| 132495 | 133105 | char *z; |
| 132496 | 133106 | for(i=0; i<argc; i++){ |
| 132497 | 133107 | n += sqlite3_value_bytes(argv[i]); |
| 132498 | 133108 | } |
| 132499 | 133109 | n += (argc-1)*(i64)nSep; |
| @@ -132506,16 +133116,17 @@ | ||
| 132506 | 133116 | for(i=0; i<argc; i++){ |
| 132507 | 133117 | if( sqlite3_value_type(argv[i])!=SQLITE_NULL ){ |
| 132508 | 133118 | int k = sqlite3_value_bytes(argv[i]); |
| 132509 | 133119 | const char *v = (const char*)sqlite3_value_text(argv[i]); |
| 132510 | 133120 | if( v!=0 ){ |
| 132511 | - if( j>0 && nSep>0 ){ | |
| 133121 | + if( bNotNull && nSep>0 ){ | |
| 132512 | 133122 | memcpy(&z[j], zSep, nSep); |
| 132513 | 133123 | j += nSep; |
| 132514 | 133124 | } |
| 132515 | 133125 | memcpy(&z[j], v, k); |
| 132516 | 133126 | j += k; |
| 133127 | + bNotNull = 1; | |
| 132517 | 133128 | } |
| 132518 | 133129 | } |
| 132519 | 133130 | } |
| 132520 | 133131 | z[j] = 0; |
| 132521 | 133132 | assert( j<=n ); |
| @@ -133456,10 +134067,456 @@ | ||
| 133456 | 134067 | type0 = sqlite3_value_numeric_type(argv[0]); |
| 133457 | 134068 | if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; |
| 133458 | 134069 | x = sqlite3_value_double(argv[0]); |
| 133459 | 134070 | sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); |
| 133460 | 134071 | } |
| 134072 | + | |
| 134073 | +#if defined(SQLITE_ENABLE_PERCENTILE) | |
| 134074 | +/*********************************************************************** | |
| 134075 | +** This section implements the percentile(Y,P) SQL function and similar. | |
| 134076 | +** Requirements: | |
| 134077 | +** | |
| 134078 | +** (1) The percentile(Y,P) function is an aggregate function taking | |
| 134079 | +** exactly two arguments. | |
| 134080 | +** | |
| 134081 | +** (2) If the P argument to percentile(Y,P) is not the same for every | |
| 134082 | +** row in the aggregate then an error is thrown. The word "same" | |
| 134083 | +** in the previous sentence means that the value differ by less | |
| 134084 | +** than 0.001. | |
| 134085 | +** | |
| 134086 | +** (3) If the P argument to percentile(Y,P) evaluates to anything other | |
| 134087 | +** than a number in the range of 0.0 to 100.0 inclusive then an | |
| 134088 | +** error is thrown. | |
| 134089 | +** | |
| 134090 | +** (4) If any Y argument to percentile(Y,P) evaluates to a value that | |
| 134091 | +** is not NULL and is not numeric then an error is thrown. | |
| 134092 | +** | |
| 134093 | +** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus | |
| 134094 | +** infinity then an error is thrown. (SQLite always interprets NaN | |
| 134095 | +** values as NULL.) | |
| 134096 | +** | |
| 134097 | +** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, | |
| 134098 | +** including CASE WHEN expressions. | |
| 134099 | +** | |
| 134100 | +** (7) The percentile(Y,P) aggregate is able to handle inputs of at least | |
| 134101 | +** one million (1,000,000) rows. | |
| 134102 | +** | |
| 134103 | +** (8) If there are no non-NULL values for Y, then percentile(Y,P) | |
| 134104 | +** returns NULL. | |
| 134105 | +** | |
| 134106 | +** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) | |
| 134107 | +** returns the one Y value. | |
| 134108 | +** | |
| 134109 | +** (10) If there N non-NULL values of Y where N is two or more and | |
| 134110 | +** the Y values are ordered from least to greatest and a graph is | |
| 134111 | +** drawn from 0 to N-1 such that the height of the graph at J is | |
| 134112 | +** the J-th Y value and such that straight lines are drawn between | |
| 134113 | +** adjacent Y values, then the percentile(Y,P) function returns | |
| 134114 | +** the height of the graph at P*(N-1)/100. | |
| 134115 | +** | |
| 134116 | +** (11) The percentile(Y,P) function always returns either a floating | |
| 134117 | +** point number or NULL. | |
| 134118 | +** | |
| 134119 | +** (12) The percentile(Y,P) is implemented as a single C99 source-code | |
| 134120 | +** file that compiles into a shared-library or DLL that can be loaded | |
| 134121 | +** into SQLite using the sqlite3_load_extension() interface. | |
| 134122 | +** | |
| 134123 | +** (13) A separate median(Y) function is the equivalent percentile(Y,50). | |
| 134124 | +** | |
| 134125 | +** (14) A separate percentile_cont(Y,P) function is equivalent to | |
| 134126 | +** percentile(Y,P/100.0). In other words, the fraction value in | |
| 134127 | +** the second argument is in the range of 0 to 1 instead of 0 to 100. | |
| 134128 | +** | |
| 134129 | +** (15) A separate percentile_disc(Y,P) function is like | |
| 134130 | +** percentile_cont(Y,P) except that instead of returning the weighted | |
| 134131 | +** average of the nearest two input values, it returns the next lower | |
| 134132 | +** value. So the percentile_disc(Y,P) will always return a value | |
| 134133 | +** that was one of the inputs. | |
| 134134 | +** | |
| 134135 | +** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and | |
| 134136 | +** percentile_disc(Y,P) can be used as window functions. | |
| 134137 | +** | |
| 134138 | +** Differences from standard SQL: | |
| 134139 | +** | |
| 134140 | +** * The percentile_cont(X,P) function is equivalent to the following in | |
| 134141 | +** standard SQL: | |
| 134142 | +** | |
| 134143 | +** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) | |
| 134144 | +** | |
| 134145 | +** The SQLite syntax is much more compact. The standard SQL syntax | |
| 134146 | +** is also supported if SQLite is compiled with the | |
| 134147 | +** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. | |
| 134148 | +** | |
| 134149 | +** * No median(X) function exists in the SQL standard. App developers | |
| 134150 | +** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". | |
| 134151 | +** | |
| 134152 | +** * No percentile(Y,P) function exists in the SQL standard. Instead of | |
| 134153 | +** percential(Y,P), developers must write this: | |
| 134154 | +** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that | |
| 134155 | +** the fraction parameter to percentile() goes from 0 to 100 whereas | |
| 134156 | +** the fraction parameter in SQL standard percentile_cont() goes from | |
| 134157 | +** 0 to 1. | |
| 134158 | +** | |
| 134159 | +** Implementation notes as of 2024-08-31: | |
| 134160 | +** | |
| 134161 | +** * The regular aggregate-function versions of these routines work | |
| 134162 | +** by accumulating all values in an array of doubles, then sorting | |
| 134163 | +** that array using quicksort before computing the answer. Thus | |
| 134164 | +** the runtime is O(NlogN) where N is the number of rows of input. | |
| 134165 | +** | |
| 134166 | +** * For the window-function versions of these routines, the array of | |
| 134167 | +** inputs is sorted as soon as the first value is computed. Thereafter, | |
| 134168 | +** the array is kept in sorted order using an insert-sort. This | |
| 134169 | +** results in O(N*K) performance where K is the size of the window. | |
| 134170 | +** One can imagine alternative implementations that give O(N*logN*logK) | |
| 134171 | +** performance, but they require more complex logic and data structures. | |
| 134172 | +** The developers have elected to keep the asymptotically slower | |
| 134173 | +** algorithm for now, for simplicity, under the theory that window | |
| 134174 | +** functions are seldom used and when they are, the window size K is | |
| 134175 | +** often small. The developers might revisit that decision later, | |
| 134176 | +** should the need arise. | |
| 134177 | +*/ | |
| 134178 | + | |
| 134179 | +/* The following object is the group context for a single percentile() | |
| 134180 | +** aggregate. Remember all input Y values until the very end. | |
| 134181 | +** Those values are accumulated in the Percentile.a[] array. | |
| 134182 | +*/ | |
| 134183 | +typedef struct Percentile Percentile; | |
| 134184 | +struct Percentile { | |
| 134185 | + unsigned nAlloc; /* Number of slots allocated for a[] */ | |
| 134186 | + unsigned nUsed; /* Number of slots actually used in a[] */ | |
| 134187 | + char bSorted; /* True if a[] is already in sorted order */ | |
| 134188 | + char bKeepSorted; /* True if advantageous to keep a[] sorted */ | |
| 134189 | + char bPctValid; /* True if rPct is valid */ | |
| 134190 | + double rPct; /* Fraction. 0.0 to 1.0 */ | |
| 134191 | + double *a; /* Array of Y values */ | |
| 134192 | +}; | |
| 134193 | + | |
| 134194 | +/* | |
| 134195 | +** Return TRUE if the input floating-point number is an infinity. | |
| 134196 | +*/ | |
| 134197 | +static int percentIsInfinity(double r){ | |
| 134198 | + sqlite3_uint64 u; | |
| 134199 | + assert( sizeof(u)==sizeof(r) ); | |
| 134200 | + memcpy(&u, &r, sizeof(u)); | |
| 134201 | + return ((u>>52)&0x7ff)==0x7ff; | |
| 134202 | +} | |
| 134203 | + | |
| 134204 | +/* | |
| 134205 | +** Return TRUE if two doubles differ by 0.001 or less. | |
| 134206 | +*/ | |
| 134207 | +static int percentSameValue(double a, double b){ | |
| 134208 | + a -= b; | |
| 134209 | + return a>=-0.001 && a<=0.001; | |
| 134210 | +} | |
| 134211 | + | |
| 134212 | +/* | |
| 134213 | +** Search p (which must have p->bSorted) looking for an entry with | |
| 134214 | +** value y. Return the index of that entry. | |
| 134215 | +** | |
| 134216 | +** If bExact is true, return -1 if the entry is not found. | |
| 134217 | +** | |
| 134218 | +** If bExact is false, return the index at which a new entry with | |
| 134219 | +** value y should be insert in order to keep the values in sorted | |
| 134220 | +** order. The smallest return value in this case will be 0, and | |
| 134221 | +** the largest return value will be p->nUsed. | |
| 134222 | +*/ | |
| 134223 | +static int percentBinarySearch(Percentile *p, double y, int bExact){ | |
| 134224 | + int iFirst = 0; /* First element of search range */ | |
| 134225 | + int iLast = p->nUsed - 1; /* Last element of search range */ | |
| 134226 | + while( iLast>=iFirst ){ | |
| 134227 | + int iMid = (iFirst+iLast)/2; | |
| 134228 | + double x = p->a[iMid]; | |
| 134229 | + if( x<y ){ | |
| 134230 | + iFirst = iMid + 1; | |
| 134231 | + }else if( x>y ){ | |
| 134232 | + iLast = iMid - 1; | |
| 134233 | + }else{ | |
| 134234 | + return iMid; | |
| 134235 | + } | |
| 134236 | + } | |
| 134237 | + if( bExact ) return -1; | |
| 134238 | + return iFirst; | |
| 134239 | +} | |
| 134240 | + | |
| 134241 | +/* | |
| 134242 | +** Generate an error for a percentile function. | |
| 134243 | +** | |
| 134244 | +** The error format string must have exactly one occurrence of "%%s()" | |
| 134245 | +** (with two '%' characters). That substring will be replaced by the name | |
| 134246 | +** of the function. | |
| 134247 | +*/ | |
| 134248 | +static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ | |
| 134249 | + char *zMsg1; | |
| 134250 | + char *zMsg2; | |
| 134251 | + va_list ap; | |
| 134252 | + | |
| 134253 | + va_start(ap, zFormat); | |
| 134254 | + zMsg1 = sqlite3_vmprintf(zFormat, ap); | |
| 134255 | + va_end(ap); | |
| 134256 | + zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, sqlite3VdbeFuncName(pCtx)) : 0; | |
| 134257 | + sqlite3_result_error(pCtx, zMsg2, -1); | |
| 134258 | + sqlite3_free(zMsg1); | |
| 134259 | + sqlite3_free(zMsg2); | |
| 134260 | +} | |
| 134261 | + | |
| 134262 | +/* | |
| 134263 | +** The "step" function for percentile(Y,P) is called once for each | |
| 134264 | +** input row. | |
| 134265 | +*/ | |
| 134266 | +static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ | |
| 134267 | + Percentile *p; | |
| 134268 | + double rPct; | |
| 134269 | + int eType; | |
| 134270 | + double y; | |
| 134271 | + assert( argc==2 || argc==1 ); | |
| 134272 | + | |
| 134273 | + if( argc==1 ){ | |
| 134274 | + /* Requirement 13: median(Y) is the same as percentile(Y,50). */ | |
| 134275 | + rPct = 0.5; | |
| 134276 | + }else{ | |
| 134277 | + /* P must be a number between 0 and 100 for percentile() or between | |
| 134278 | + ** 0.0 and 1.0 for percentile_cont() and percentile_disc(). | |
| 134279 | + ** | |
| 134280 | + ** The user-data is an integer which is 10 times the upper bound. | |
| 134281 | + */ | |
| 134282 | + double mxFrac = (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&2)? 100.0 : 1.0; | |
| 134283 | + eType = sqlite3_value_numeric_type(argv[1]); | |
| 134284 | + rPct = sqlite3_value_double(argv[1])/mxFrac; | |
| 134285 | + if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) | |
| 134286 | + || rPct<0.0 || rPct>1.0 | |
| 134287 | + ){ | |
| 134288 | + percentError(pCtx, "the fraction argument to %%s()" | |
| 134289 | + " is not between 0.0 and %.1f", | |
| 134290 | + (double)mxFrac); | |
| 134291 | + return; | |
| 134292 | + } | |
| 134293 | + } | |
| 134294 | + | |
| 134295 | + /* Allocate the session context. */ | |
| 134296 | + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); | |
| 134297 | + if( p==0 ) return; | |
| 134298 | + | |
| 134299 | + /* Remember the P value. Throw an error if the P value is different | |
| 134300 | + ** from any prior row, per Requirement (2). */ | |
| 134301 | + if( !p->bPctValid ){ | |
| 134302 | + p->rPct = rPct; | |
| 134303 | + p->bPctValid = 1; | |
| 134304 | + }else if( !percentSameValue(p->rPct,rPct) ){ | |
| 134305 | + percentError(pCtx, "the fraction argument to %%s()" | |
| 134306 | + " is not the same for all input rows"); | |
| 134307 | + return; | |
| 134308 | + } | |
| 134309 | + | |
| 134310 | + /* Ignore rows for which Y is NULL */ | |
| 134311 | + eType = sqlite3_value_type(argv[0]); | |
| 134312 | + if( eType==SQLITE_NULL ) return; | |
| 134313 | + | |
| 134314 | + /* If not NULL, then Y must be numeric. Otherwise throw an error. | |
| 134315 | + ** Requirement 4 */ | |
| 134316 | + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ | |
| 134317 | + percentError(pCtx, "input to %%s() is not numeric"); | |
| 134318 | + return; | |
| 134319 | + } | |
| 134320 | + | |
| 134321 | + /* Throw an error if the Y value is infinity or NaN */ | |
| 134322 | + y = sqlite3_value_double(argv[0]); | |
| 134323 | + if( percentIsInfinity(y) ){ | |
| 134324 | + percentError(pCtx, "Inf input to %%s()"); | |
| 134325 | + return; | |
| 134326 | + } | |
| 134327 | + | |
| 134328 | + /* Allocate and store the Y */ | |
| 134329 | + if( p->nUsed>=p->nAlloc ){ | |
| 134330 | + unsigned n = p->nAlloc*2 + 250; | |
| 134331 | + double *a = sqlite3_realloc64(p->a, sizeof(double)*n); | |
| 134332 | + if( a==0 ){ | |
| 134333 | + sqlite3_free(p->a); | |
| 134334 | + memset(p, 0, sizeof(*p)); | |
| 134335 | + sqlite3_result_error_nomem(pCtx); | |
| 134336 | + return; | |
| 134337 | + } | |
| 134338 | + p->nAlloc = n; | |
| 134339 | + p->a = a; | |
| 134340 | + } | |
| 134341 | + if( p->nUsed==0 ){ | |
| 134342 | + p->a[p->nUsed++] = y; | |
| 134343 | + p->bSorted = 1; | |
| 134344 | + }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ | |
| 134345 | + p->a[p->nUsed++] = y; | |
| 134346 | + }else if( p->bKeepSorted ){ | |
| 134347 | + int i; | |
| 134348 | + i = percentBinarySearch(p, y, 0); | |
| 134349 | + if( i<(int)p->nUsed ){ | |
| 134350 | + memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); | |
| 134351 | + } | |
| 134352 | + p->a[i] = y; | |
| 134353 | + p->nUsed++; | |
| 134354 | + }else{ | |
| 134355 | + p->a[p->nUsed++] = y; | |
| 134356 | + p->bSorted = 0; | |
| 134357 | + } | |
| 134358 | +} | |
| 134359 | + | |
| 134360 | +/* | |
| 134361 | +** Interchange two doubles. | |
| 134362 | +*/ | |
| 134363 | +#define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} | |
| 134364 | + | |
| 134365 | +/* | |
| 134366 | +** Sort an array of doubles. | |
| 134367 | +** | |
| 134368 | +** Algorithm: quicksort | |
| 134369 | +** | |
| 134370 | +** This is implemented separately rather than using the qsort() routine | |
| 134371 | +** from the standard library because: | |
| 134372 | +** | |
| 134373 | +** (1) To avoid a dependency on qsort() | |
| 134374 | +** (2) To avoid the function call to the comparison routine for each | |
| 134375 | +** comparison. | |
| 134376 | +*/ | |
| 134377 | +static void percentSort(double *a, unsigned int n){ | |
| 134378 | + int iLt; /* Entries before a[iLt] are less than rPivot */ | |
| 134379 | + int iGt; /* Entries at or after a[iGt] are greater than rPivot */ | |
| 134380 | + int i; /* Loop counter */ | |
| 134381 | + double rPivot; /* The pivot value */ | |
| 134382 | + | |
| 134383 | + assert( n>=2 ); | |
| 134384 | + if( a[0]>a[n-1] ){ | |
| 134385 | + SWAP_DOUBLE(a[0],a[n-1]) | |
| 134386 | + } | |
| 134387 | + if( n==2 ) return; | |
| 134388 | + iGt = n-1; | |
| 134389 | + i = n/2; | |
| 134390 | + if( a[0]>a[i] ){ | |
| 134391 | + SWAP_DOUBLE(a[0],a[i]) | |
| 134392 | + }else if( a[i]>a[iGt] ){ | |
| 134393 | + SWAP_DOUBLE(a[i],a[iGt]) | |
| 134394 | + } | |
| 134395 | + if( n==3 ) return; | |
| 134396 | + rPivot = a[i]; | |
| 134397 | + iLt = i = 1; | |
| 134398 | + do{ | |
| 134399 | + if( a[i]<rPivot ){ | |
| 134400 | + if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt]) | |
| 134401 | + iLt++; | |
| 134402 | + i++; | |
| 134403 | + }else if( a[i]>rPivot ){ | |
| 134404 | + do{ | |
| 134405 | + iGt--; | |
| 134406 | + }while( iGt>i && a[iGt]>rPivot ); | |
| 134407 | + SWAP_DOUBLE(a[i],a[iGt]) | |
| 134408 | + }else{ | |
| 134409 | + i++; | |
| 134410 | + } | |
| 134411 | + }while( i<iGt ); | |
| 134412 | + if( iLt>=2 ) percentSort(a, iLt); | |
| 134413 | + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); | |
| 134414 | + | |
| 134415 | +/* Uncomment for testing */ | |
| 134416 | +#if 0 | |
| 134417 | + for(i=0; i<n-1; i++){ | |
| 134418 | + assert( a[i]<=a[i+1] ); | |
| 134419 | + } | |
| 134420 | +#endif | |
| 134421 | +} | |
| 134422 | + | |
| 134423 | + | |
| 134424 | +/* | |
| 134425 | +** The "inverse" function for percentile(Y,P) is called to remove a | |
| 134426 | +** row that was previously inserted by "step". | |
| 134427 | +*/ | |
| 134428 | +static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ | |
| 134429 | + Percentile *p; | |
| 134430 | + int eType; | |
| 134431 | + double y; | |
| 134432 | + int i; | |
| 134433 | + assert( argc==2 || argc==1 ); | |
| 134434 | + | |
| 134435 | + /* Allocate the session context. */ | |
| 134436 | + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); | |
| 134437 | + assert( p!=0 ); | |
| 134438 | + | |
| 134439 | + /* Ignore rows for which Y is NULL */ | |
| 134440 | + eType = sqlite3_value_type(argv[0]); | |
| 134441 | + if( eType==SQLITE_NULL ) return; | |
| 134442 | + | |
| 134443 | + /* If not NULL, then Y must be numeric. Otherwise throw an error. | |
| 134444 | + ** Requirement 4 */ | |
| 134445 | + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ | |
| 134446 | + return; | |
| 134447 | + } | |
| 134448 | + | |
| 134449 | + /* Ignore the Y value if it is infinity or NaN */ | |
| 134450 | + y = sqlite3_value_double(argv[0]); | |
| 134451 | + if( percentIsInfinity(y) ){ | |
| 134452 | + return; | |
| 134453 | + } | |
| 134454 | + if( p->bSorted==0 ){ | |
| 134455 | + assert( p->nUsed>1 ); | |
| 134456 | + percentSort(p->a, p->nUsed); | |
| 134457 | + p->bSorted = 1; | |
| 134458 | + } | |
| 134459 | + p->bKeepSorted = 1; | |
| 134460 | + | |
| 134461 | + /* Find and remove the row */ | |
| 134462 | + i = percentBinarySearch(p, y, 1); | |
| 134463 | + if( i>=0 ){ | |
| 134464 | + p->nUsed--; | |
| 134465 | + if( i<(int)p->nUsed ){ | |
| 134466 | + memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); | |
| 134467 | + } | |
| 134468 | + } | |
| 134469 | +} | |
| 134470 | + | |
| 134471 | +/* | |
| 134472 | +** Compute the final output of percentile(). Clean up all allocated | |
| 134473 | +** memory if and only if bIsFinal is true. | |
| 134474 | +*/ | |
| 134475 | +static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ | |
| 134476 | + Percentile *p; | |
| 134477 | + int settings = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&1; /* Discrete? */ | |
| 134478 | + unsigned i1, i2; | |
| 134479 | + double v1, v2; | |
| 134480 | + double ix, vx; | |
| 134481 | + p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); | |
| 134482 | + if( p==0 ) return; | |
| 134483 | + if( p->a==0 ) return; | |
| 134484 | + if( p->nUsed ){ | |
| 134485 | + if( p->bSorted==0 ){ | |
| 134486 | + assert( p->nUsed>1 ); | |
| 134487 | + percentSort(p->a, p->nUsed); | |
| 134488 | + p->bSorted = 1; | |
| 134489 | + } | |
| 134490 | + ix = p->rPct*(p->nUsed-1); | |
| 134491 | + i1 = (unsigned)ix; | |
| 134492 | + if( settings & 1 ){ | |
| 134493 | + vx = p->a[i1]; | |
| 134494 | + }else{ | |
| 134495 | + i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; | |
| 134496 | + v1 = p->a[i1]; | |
| 134497 | + v2 = p->a[i2]; | |
| 134498 | + vx = v1 + (v2-v1)*(ix-i1); | |
| 134499 | + } | |
| 134500 | + sqlite3_result_double(pCtx, vx); | |
| 134501 | + } | |
| 134502 | + if( bIsFinal ){ | |
| 134503 | + sqlite3_free(p->a); | |
| 134504 | + memset(p, 0, sizeof(*p)); | |
| 134505 | + }else{ | |
| 134506 | + p->bKeepSorted = 1; | |
| 134507 | + } | |
| 134508 | +} | |
| 134509 | +static void percentFinal(sqlite3_context *pCtx){ | |
| 134510 | + percentCompute(pCtx, 1); | |
| 134511 | +} | |
| 134512 | +static void percentValue(sqlite3_context *pCtx){ | |
| 134513 | + percentCompute(pCtx, 0); | |
| 134514 | +} | |
| 134515 | +/****** End of percentile family of functions ******/ | |
| 134516 | +#endif /* SQLITE_ENABLE_PERCENTILE */ | |
| 134517 | + | |
| 133461 | 134518 | |
| 133462 | 134519 | #ifdef SQLITE_DEBUG |
| 133463 | 134520 | /* |
| 133464 | 134521 | ** Implementation of fpdecode(x,y,z) function. |
| 133465 | 134522 | ** |
| @@ -133687,10 +134744,25 @@ | ||
| 133687 | 134744 | WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, |
| 133688 | 134745 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 133689 | 134746 | WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep, |
| 133690 | 134747 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 133691 | 134748 | |
| 134749 | +#ifdef SQLITE_ENABLE_PERCENTILE | |
| 134750 | + WAGGREGATE(median, 1, 0,0, percentStep, | |
| 134751 | + percentFinal, percentValue, percentInverse, | |
| 134752 | + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), | |
| 134753 | + WAGGREGATE(percentile, 2, 0x2,0, percentStep, | |
| 134754 | + percentFinal, percentValue, percentInverse, | |
| 134755 | + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), | |
| 134756 | + WAGGREGATE(percentile_cont, 2, 0,0, percentStep, | |
| 134757 | + percentFinal, percentValue, percentInverse, | |
| 134758 | + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), | |
| 134759 | + WAGGREGATE(percentile_disc, 2, 0x1,0, percentStep, | |
| 134760 | + percentFinal, percentValue, percentInverse, | |
| 134761 | + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), | |
| 134762 | +#endif /* SQLITE_ENABLE_PERCENTILE */ | |
| 134763 | + | |
| 133692 | 134764 | LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133693 | 134765 | #ifdef SQLITE_CASE_SENSITIVE_LIKE |
| 133694 | 134766 | LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133695 | 134767 | LIKEFUNC(like, 3, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133696 | 134768 | #else |
| @@ -139184,10 +140256,14 @@ | ||
| 139184 | 140256 | /* Version 3.44.0 and later */ |
| 139185 | 140257 | void *(*get_clientdata)(sqlite3*,const char*); |
| 139186 | 140258 | int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); |
| 139187 | 140259 | /* Version 3.50.0 and later */ |
| 139188 | 140260 | int (*setlk_timeout)(sqlite3*,int,int); |
| 140261 | + /* Version 3.51.0 and later */ | |
| 140262 | + int (*set_errmsg)(sqlite3*,int,const char*); | |
| 140263 | + int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); | |
| 140264 | + | |
| 139189 | 140265 | }; |
| 139190 | 140266 | |
| 139191 | 140267 | /* |
| 139192 | 140268 | ** This is the function signature used for all extension entry points. It |
| 139193 | 140269 | ** is also defined in the file "loadext.c". |
| @@ -139519,10 +140595,13 @@ | ||
| 139519 | 140595 | /* Version 3.44.0 and later */ |
| 139520 | 140596 | #define sqlite3_get_clientdata sqlite3_api->get_clientdata |
| 139521 | 140597 | #define sqlite3_set_clientdata sqlite3_api->set_clientdata |
| 139522 | 140598 | /* Version 3.50.0 and later */ |
| 139523 | 140599 | #define sqlite3_setlk_timeout sqlite3_api->setlk_timeout |
| 140600 | +/* Version 3.51.0 and later */ | |
| 140601 | +#define sqlite3_set_errmsg sqlite3_api->set_errmsg | |
| 140602 | +#define sqlite3_db_status64 sqlite3_api->db_status64 | |
| 139524 | 140603 | #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ |
| 139525 | 140604 | |
| 139526 | 140605 | #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) |
| 139527 | 140606 | /* This case when the file really is being compiled as a loadable |
| 139528 | 140607 | ** extension */ |
| @@ -140042,11 +141121,14 @@ | ||
| 140042 | 141121 | sqlite3_stmt_explain, |
| 140043 | 141122 | /* Version 3.44.0 and later */ |
| 140044 | 141123 | sqlite3_get_clientdata, |
| 140045 | 141124 | sqlite3_set_clientdata, |
| 140046 | 141125 | /* Version 3.50.0 and later */ |
| 140047 | - sqlite3_setlk_timeout | |
| 141126 | + sqlite3_setlk_timeout, | |
| 141127 | + /* Version 3.51.0 and later */ | |
| 141128 | + sqlite3_set_errmsg, | |
| 141129 | + sqlite3_db_status64 | |
| 140048 | 141130 | }; |
| 140049 | 141131 | |
| 140050 | 141132 | /* True if x is the directory separator character |
| 140051 | 141133 | */ |
| 140052 | 141134 | #if SQLITE_OS_WIN |
| @@ -141503,10 +142585,26 @@ | ||
| 141503 | 142585 | addr = sqlite3VdbeAddOp3(v, OP_IfPos, 1, sqlite3VdbeCurrentAddr(v)+2, 1); |
| 141504 | 142586 | VdbeCoverage(v); |
| 141505 | 142587 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 141506 | 142588 | return addr; |
| 141507 | 142589 | } |
| 142590 | + | |
| 142591 | +/* | |
| 142592 | +** Should table pTab be skipped when doing an integrity_check? | |
| 142593 | +** Return true or false. | |
| 142594 | +** | |
| 142595 | +** If pObjTab is not null, the return true if pTab matches pObjTab. | |
| 142596 | +** | |
| 142597 | +** If pObjTab is null, then return true only if pTab is an imposter table. | |
| 142598 | +*/ | |
| 142599 | +static int tableSkipIntegrityCheck(const Table *pTab, const Table *pObjTab){ | |
| 142600 | + if( pObjTab ){ | |
| 142601 | + return pTab!=pObjTab; | |
| 142602 | + }else{ | |
| 142603 | + return (pTab->tabFlags & TF_Imposter)!=0; | |
| 142604 | + } | |
| 142605 | +} | |
| 141508 | 142606 | |
| 141509 | 142607 | /* |
| 141510 | 142608 | ** Process a pragma statement. |
| 141511 | 142609 | ** |
| 141512 | 142610 | ** Pragmas are of this form: |
| @@ -142849,11 +143947,11 @@ | ||
| 142849 | 143947 | pTbls = &db->aDb[i].pSchema->tblHash; |
| 142850 | 143948 | for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142851 | 143949 | Table *pTab = sqliteHashData(x); /* Current table */ |
| 142852 | 143950 | Index *pIdx; /* An index on pTab */ |
| 142853 | 143951 | int nIdx; /* Number of indexes on pTab */ |
| 142854 | - if( pObjTab && pObjTab!=pTab ) continue; | |
| 143952 | + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; | |
| 142855 | 143953 | if( HasRowid(pTab) ) cnt++; |
| 142856 | 143954 | for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } |
| 142857 | 143955 | } |
| 142858 | 143956 | if( cnt==0 ) continue; |
| 142859 | 143957 | if( pObjTab ) cnt++; |
| @@ -142862,11 +143960,11 @@ | ||
| 142862 | 143960 | cnt = 0; |
| 142863 | 143961 | if( pObjTab ) aRoot[++cnt] = 0; |
| 142864 | 143962 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142865 | 143963 | Table *pTab = sqliteHashData(x); |
| 142866 | 143964 | Index *pIdx; |
| 142867 | - if( pObjTab && pObjTab!=pTab ) continue; | |
| 143965 | + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; | |
| 142868 | 143966 | if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; |
| 142869 | 143967 | for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ |
| 142870 | 143968 | aRoot[++cnt] = pIdx->tnum; |
| 142871 | 143969 | } |
| 142872 | 143970 | } |
| @@ -142893,11 +143991,11 @@ | ||
| 142893 | 143991 | sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); |
| 142894 | 143992 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142895 | 143993 | int iTab = 0; |
| 142896 | 143994 | Table *pTab = sqliteHashData(x); |
| 142897 | 143995 | Index *pIdx; |
| 142898 | - if( pObjTab && pObjTab!=pTab ) continue; | |
| 143996 | + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; | |
| 142899 | 143997 | if( HasRowid(pTab) ){ |
| 142900 | 143998 | iTab = cnt++; |
| 142901 | 143999 | }else{ |
| 142902 | 144000 | iTab = cnt; |
| 142903 | 144001 | for(pIdx=pTab->pIndex; ALWAYS(pIdx); pIdx=pIdx->pNext){ |
| @@ -142929,11 +144027,11 @@ | ||
| 142929 | 144027 | int r1 = -1; |
| 142930 | 144028 | int bStrict; /* True for a STRICT table */ |
| 142931 | 144029 | int r2; /* Previous key for WITHOUT ROWID tables */ |
| 142932 | 144030 | int mxCol; /* Maximum non-virtual column number */ |
| 142933 | 144031 | |
| 142934 | - if( pObjTab && pObjTab!=pTab ) continue; | |
| 144032 | + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; | |
| 142935 | 144033 | if( !IsOrdinaryTable(pTab) ) continue; |
| 142936 | 144034 | if( isQuick || HasRowid(pTab) ){ |
| 142937 | 144035 | pPk = 0; |
| 142938 | 144036 | r2 = 0; |
| 142939 | 144037 | }else{ |
| @@ -143253,11 +144351,11 @@ | ||
| 143253 | 144351 | */ |
| 143254 | 144352 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 143255 | 144353 | Table *pTab = sqliteHashData(x); |
| 143256 | 144354 | sqlite3_vtab *pVTab; |
| 143257 | 144355 | int a1; |
| 143258 | - if( pObjTab && pObjTab!=pTab ) continue; | |
| 144356 | + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; | |
| 143259 | 144357 | if( IsOrdinaryTable(pTab) ) continue; |
| 143260 | 144358 | if( !IsVirtual(pTab) ) continue; |
| 143261 | 144359 | if( pTab->nCol<=0 ){ |
| 143262 | 144360 | const char *zMod = pTab->u.vtab.azArg[0]; |
| 143263 | 144361 | if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; |
| @@ -143485,10 +144583,12 @@ | ||
| 143485 | 144583 | eMode = SQLITE_CHECKPOINT_FULL; |
| 143486 | 144584 | }else if( sqlite3StrICmp(zRight, "restart")==0 ){ |
| 143487 | 144585 | eMode = SQLITE_CHECKPOINT_RESTART; |
| 143488 | 144586 | }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ |
| 143489 | 144587 | eMode = SQLITE_CHECKPOINT_TRUNCATE; |
| 144588 | + }else if( sqlite3StrICmp(zRight, "noop")==0 ){ | |
| 144589 | + eMode = SQLITE_CHECKPOINT_NOOP; | |
| 143490 | 144590 | } |
| 143491 | 144591 | } |
| 143492 | 144592 | pParse->nMem = 3; |
| 143493 | 144593 | sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); |
| 143494 | 144594 | sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); |
| @@ -145051,13 +146151,15 @@ | ||
| 145051 | 146151 | ** or encounters a permanent error. A schema problem after one schema |
| 145052 | 146152 | ** reset is considered a permanent error. */ |
| 145053 | 146153 | rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); |
| 145054 | 146154 | assert( rc==SQLITE_OK || *ppStmt==0 ); |
| 145055 | 146155 | if( rc==SQLITE_OK || db->mallocFailed ) break; |
| 145056 | - }while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY) | |
| 145057 | - || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); | |
| 146156 | + cnt++; | |
| 146157 | + }while( (rc==SQLITE_ERROR_RETRY && ALWAYS(cnt<=SQLITE_MAX_PREPARE_RETRY)) | |
| 146158 | + || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt)==1) ); | |
| 145058 | 146159 | sqlite3BtreeLeaveAll(db); |
| 146160 | + assert( rc!=SQLITE_ERROR_RETRY ); | |
| 145059 | 146161 | rc = sqlite3ApiExit(db, rc); |
| 145060 | 146162 | assert( (rc&db->errMask)==rc ); |
| 145061 | 146163 | db->busyHandler.nBusy = 0; |
| 145062 | 146164 | sqlite3_mutex_leave(db->mutex); |
| 145063 | 146165 | assert( rc==SQLITE_OK || (*ppStmt)==0 ); |
| @@ -145727,12 +146829,11 @@ | ||
| 145727 | 146829 | while( p ){ |
| 145728 | 146830 | ExprSetProperty(p, joinFlag); |
| 145729 | 146831 | assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); |
| 145730 | 146832 | ExprSetVVAProperty(p, EP_NoReduce); |
| 145731 | 146833 | p->w.iJoin = iTable; |
| 145732 | - if( p->op==TK_FUNCTION ){ | |
| 145733 | - assert( ExprUseXList(p) ); | |
| 146834 | + if( ExprUseXList(p) ){ | |
| 145734 | 146835 | if( p->x.pList ){ |
| 145735 | 146836 | int i; |
| 145736 | 146837 | for(i=0; i<p->x.pList->nExpr; i++){ |
| 145737 | 146838 | sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); |
| 145738 | 146839 | } |
| @@ -145944,10 +147045,11 @@ | ||
| 145944 | 147045 | else if( pRight->u3.pOn ){ |
| 145945 | 147046 | sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); |
| 145946 | 147047 | p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); |
| 145947 | 147048 | pRight->u3.pOn = 0; |
| 145948 | 147049 | pRight->fg.isOn = 1; |
| 147050 | + p->selFlags |= SF_OnToWhere; | |
| 145949 | 147051 | } |
| 145950 | 147052 | } |
| 145951 | 147053 | return 0; |
| 145952 | 147054 | } |
| 145953 | 147055 | |
| @@ -146830,11 +147932,14 @@ | ||
| 146830 | 147932 | ** Allocate a KeyInfo object sufficient for an index of N key columns and |
| 146831 | 147933 | ** X extra columns. |
| 146832 | 147934 | */ |
| 146833 | 147935 | SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ |
| 146834 | 147936 | int nExtra = (N+X)*(sizeof(CollSeq*)+1); |
| 146835 | - KeyInfo *p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); | |
| 147937 | + KeyInfo *p; | |
| 147938 | + assert( X>=0 ); | |
| 147939 | + if( NEVER(N+X>0xffff) ) return (KeyInfo*)sqlite3OomFault(db); | |
| 147940 | + p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); | |
| 146836 | 147941 | if( p ){ |
| 146837 | 147942 | p->aSortFlags = (u8*)&p->aColl[N+X]; |
| 146838 | 147943 | p->nKeyField = (u16)N; |
| 146839 | 147944 | p->nAllField = (u16)(N+X); |
| 146840 | 147945 | p->enc = ENC(db); |
| @@ -149149,11 +150254,11 @@ | ||
| 149149 | 150254 | ** expressions in pEList. |
| 149150 | 150255 | ** |
| 149151 | 150256 | ** ## About "isOuterJoin": |
| 149152 | 150257 | ** |
| 149153 | 150258 | ** The isOuterJoin column indicates that the replacement will occur into a |
| 149154 | -** position in the parent that NULL-able due to an OUTER JOIN. Either the | |
| 150259 | +** position in the parent that is NULL-able due to an OUTER JOIN. Either the | |
| 149155 | 150260 | ** target slot in the parent is the right operand of a LEFT JOIN, or one of |
| 149156 | 150261 | ** the left operands of a RIGHT JOIN. In either case, we need to potentially |
| 149157 | 150262 | ** bypass the substituted expression with OP_IfNullRow. |
| 149158 | 150263 | ** |
| 149159 | 150264 | ** Suppose the original expression is an integer constant. Even though the table |
| @@ -149987,21 +151092,16 @@ | ||
| 149987 | 151092 | ** elements we are now copying in. |
| 149988 | 151093 | */ |
| 149989 | 151094 | pSub = pSub1; |
| 149990 | 151095 | for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ |
| 149991 | 151096 | int nSubSrc; |
| 149992 | - u8 jointype = 0; | |
| 149993 | - u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; | |
| 151097 | + u8 jointype = pSubitem->fg.jointype; | |
| 149994 | 151098 | assert( pSub!=0 ); |
| 149995 | 151099 | pSubSrc = pSub->pSrc; /* FROM clause of subquery */ |
| 149996 | 151100 | nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ |
| 149997 | 151101 | pSrc = pParent->pSrc; /* FROM clause of the outer query */ |
| 149998 | 151102 | |
| 149999 | - if( pParent==p ){ | |
| 150000 | - jointype = pSubitem->fg.jointype; /* First time through the loop */ | |
| 150001 | - } | |
| 150002 | - | |
| 150003 | 151103 | /* The subquery uses a single slot of the FROM clause of the outer |
| 150004 | 151104 | ** query. If the subquery has more than one element in its FROM clause, |
| 150005 | 151105 | ** then expand the outer query to make space for it to hold all elements |
| 150006 | 151106 | ** of the subquery. |
| 150007 | 151107 | ** |
| @@ -150017,10 +151117,11 @@ | ||
| 150017 | 151117 | */ |
| 150018 | 151118 | if( nSubSrc>1 ){ |
| 150019 | 151119 | pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); |
| 150020 | 151120 | if( pSrc==0 ) break; |
| 150021 | 151121 | pParent->pSrc = pSrc; |
| 151122 | + pSubitem = &pSrc->a[iFrom]; | |
| 150022 | 151123 | } |
| 150023 | 151124 | |
| 150024 | 151125 | /* Transfer the FROM clause terms from the subquery into the |
| 150025 | 151126 | ** outer query. |
| 150026 | 151127 | */ |
| @@ -150031,15 +151132,14 @@ | ||
| 150031 | 151132 | assert( pItem->fg.isSubquery |
| 150032 | 151133 | || pItem->fg.fixedSchema |
| 150033 | 151134 | || pItem->u4.zDatabase==0 ); |
| 150034 | 151135 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 150035 | 151136 | *pItem = pSubSrc->a[i]; |
| 150036 | - pItem->fg.jointype |= ltorj; | |
| 151137 | + pItem->fg.jointype |= (jointype & JT_LTORJ); | |
| 150037 | 151138 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 150038 | 151139 | } |
| 150039 | - pSrc->a[iFrom].fg.jointype &= JT_LTORJ; | |
| 150040 | - pSrc->a[iFrom].fg.jointype |= jointype | ltorj; | |
| 151140 | + pSubitem->fg.jointype |= jointype; | |
| 150041 | 151141 | |
| 150042 | 151142 | /* Now begin substituting subquery result set expressions for |
| 150043 | 151143 | ** references to the iParent in the outer query. |
| 150044 | 151144 | ** |
| 150045 | 151145 | ** Example: |
| @@ -152746,10 +153846,11 @@ | ||
| 152746 | 153846 | Select *pSub = pWhere->x.pSelect; |
| 152747 | 153847 | Expr *pSubWhere = pSub->pWhere; |
| 152748 | 153848 | if( pSub->pSrc->nSrc==1 |
| 152749 | 153849 | && (pSub->selFlags & SF_Aggregate)==0 |
| 152750 | 153850 | && !pSub->pSrc->a[0].fg.isSubquery |
| 153851 | + && pSub->pLimit==0 | |
| 152751 | 153852 | ){ |
| 152752 | 153853 | memset(pWhere, 0, sizeof(*pWhere)); |
| 152753 | 153854 | pWhere->op = TK_INTEGER; |
| 152754 | 153855 | pWhere->u.iValue = 1; |
| 152755 | 153856 | ExprSetProperty(pWhere, EP_IntValue); |
| @@ -152774,10 +153875,119 @@ | ||
| 152774 | 153875 | existsToJoin(pParse, p, pSubWhere); |
| 152775 | 153876 | } |
| 152776 | 153877 | } |
| 152777 | 153878 | } |
| 152778 | 153879 | } |
| 153880 | + | |
| 153881 | +/* | |
| 153882 | +** Type used for Walker callbacks by selectCheckOnClauses(). | |
| 153883 | +*/ | |
| 153884 | +typedef struct CheckOnCtx CheckOnCtx; | |
| 153885 | +struct CheckOnCtx { | |
| 153886 | + SrcList *pSrc; /* SrcList for this context */ | |
| 153887 | + int iJoin; /* Cursor numbers must be =< than this */ | |
| 153888 | + CheckOnCtx *pParent; /* Parent context */ | |
| 153889 | +}; | |
| 153890 | + | |
| 153891 | +/* | |
| 153892 | +** True if the SrcList passed as the only argument contains at least | |
| 153893 | +** one RIGHT or FULL JOIN. False otherwise. | |
| 153894 | +*/ | |
| 153895 | +#define hasRightJoin(pSrc) (((pSrc)->a[0].fg.jointype & JT_LTORJ)!=0) | |
| 153896 | + | |
| 153897 | +/* | |
| 153898 | +** The xExpr callback for the search of invalid ON clause terms. | |
| 153899 | +*/ | |
| 153900 | +static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ | |
| 153901 | + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; | |
| 153902 | + | |
| 153903 | + /* Check if pExpr is root or near-root of an ON clause constraint that needs | |
| 153904 | + ** to be checked to ensure that it does not refer to tables in its FROM | |
| 153905 | + ** clause to the right of itself. i.e. it is either: | |
| 153906 | + ** | |
| 153907 | + ** + an ON clause on an OUTER join, or | |
| 153908 | + ** + an ON clause on an INNER join within a FROM that features at | |
| 153909 | + ** least one RIGHT or FULL join. | |
| 153910 | + */ | |
| 153911 | + if( (ExprHasProperty(pExpr, EP_OuterON)) | |
| 153912 | + || (ExprHasProperty(pExpr, EP_InnerON) && hasRightJoin(pCtx->pSrc)) | |
| 153913 | + ){ | |
| 153914 | + /* If CheckOnCtx.iJoin is already set, then fall through and process | |
| 153915 | + ** this expression node as normal. Or, if CheckOnCtx.iJoin is still 0, | |
| 153916 | + ** set it to the cursor number of the RHS of the join to which this | |
| 153917 | + ** ON expression was attached and then iterate through the entire | |
| 153918 | + ** expression. */ | |
| 153919 | + assert( pCtx->iJoin==0 || pCtx->iJoin==pExpr->w.iJoin ); | |
| 153920 | + if( pCtx->iJoin==0 ){ | |
| 153921 | + pCtx->iJoin = pExpr->w.iJoin; | |
| 153922 | + sqlite3WalkExprNN(pWalker, pExpr); | |
| 153923 | + pCtx->iJoin = 0; | |
| 153924 | + return WRC_Prune; | |
| 153925 | + } | |
| 153926 | + } | |
| 153927 | + | |
| 153928 | + if( pExpr->op==TK_COLUMN ){ | |
| 153929 | + /* A column expression. Find the SrcList (if any) to which it refers. | |
| 153930 | + ** Then, if CheckOnCtx.iJoin indicates that this expression is part of an | |
| 153931 | + ** ON clause from that SrcList (i.e. if iJoin is non-zero), check that it | |
| 153932 | + ** does not refer to a table to the right of CheckOnCtx.iJoin. */ | |
| 153933 | + do { | |
| 153934 | + SrcList *pSrc = pCtx->pSrc; | |
| 153935 | + int iTab = pExpr->iTable; | |
| 153936 | + if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ | |
| 153937 | + if( pCtx->iJoin && iTab>pCtx->iJoin ){ | |
| 153938 | + sqlite3ErrorMsg(pWalker->pParse, | |
| 153939 | + "ON clause references tables to its right"); | |
| 153940 | + return WRC_Abort; | |
| 153941 | + } | |
| 153942 | + break; | |
| 153943 | + } | |
| 153944 | + pCtx = pCtx->pParent; | |
| 153945 | + }while( pCtx ); | |
| 153946 | + } | |
| 153947 | + return WRC_Continue; | |
| 153948 | +} | |
| 153949 | + | |
| 153950 | +/* | |
| 153951 | +** The xSelect callback for the search of invalid ON clause terms. | |
| 153952 | +*/ | |
| 153953 | +static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ | |
| 153954 | + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; | |
| 153955 | + if( pSelect->pSrc==pCtx->pSrc || pSelect->pSrc->nSrc==0 ){ | |
| 153956 | + return WRC_Continue; | |
| 153957 | + }else{ | |
| 153958 | + CheckOnCtx sCtx; | |
| 153959 | + memset(&sCtx, 0, sizeof(sCtx)); | |
| 153960 | + sCtx.pSrc = pSelect->pSrc; | |
| 153961 | + sCtx.pParent = pCtx; | |
| 153962 | + pWalker->u.pCheckOnCtx = &sCtx; | |
| 153963 | + sqlite3WalkSelect(pWalker, pSelect); | |
| 153964 | + pWalker->u.pCheckOnCtx = pCtx; | |
| 153965 | + pSelect->selFlags &= ~SF_OnToWhere; | |
| 153966 | + return WRC_Prune; | |
| 153967 | + } | |
| 153968 | +} | |
| 153969 | + | |
| 153970 | +/* | |
| 153971 | +** Check all ON clauses in pSelect to verify that they do not reference | |
| 153972 | +** columns to the right. | |
| 153973 | +*/ | |
| 153974 | +static void selectCheckOnClauses(Parse *pParse, Select *pSelect){ | |
| 153975 | + Walker w; | |
| 153976 | + CheckOnCtx sCtx; | |
| 153977 | + assert( pSelect->selFlags & SF_OnToWhere ); | |
| 153978 | + assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); | |
| 153979 | + memset(&w, 0, sizeof(w)); | |
| 153980 | + w.pParse = pParse; | |
| 153981 | + w.xExprCallback = selectCheckOnClausesExpr; | |
| 153982 | + w.xSelectCallback = selectCheckOnClausesSelect; | |
| 153983 | + w.u.pCheckOnCtx = &sCtx; | |
| 153984 | + memset(&sCtx, 0, sizeof(sCtx)); | |
| 153985 | + sCtx.pSrc = pSelect->pSrc; | |
| 153986 | + sqlite3WalkExprNN(&w, pSelect->pWhere); | |
| 153987 | + pSelect->selFlags &= ~SF_OnToWhere; | |
| 153988 | +} | |
| 152779 | 153989 | |
| 152780 | 153990 | /* |
| 152781 | 153991 | ** Generate byte-code for the SELECT statement given in the p argument. |
| 152782 | 153992 | ** |
| 152783 | 153993 | ** The results are returned according to the SelectDest structure. |
| @@ -152901,10 +154111,22 @@ | ||
| 152901 | 154111 | if( sqlite3TreeTrace & 0x10 ){ |
| 152902 | 154112 | TREETRACE(0x10,pParse,p, ("after name resolution:\n")); |
| 152903 | 154113 | sqlite3TreeViewSelect(0, p, 0); |
| 152904 | 154114 | } |
| 152905 | 154115 | #endif |
| 154116 | + | |
| 154117 | + /* If the SELECT statement contains ON clauses that were moved into | |
| 154118 | + ** the WHERE clause, go through and verify that none of the terms | |
| 154119 | + ** in the ON clauses reference tables to the right of the ON clause. | |
| 154120 | + ** Do this now, after name resolution, but before query flattening | |
| 154121 | + */ | |
| 154122 | + if( p->selFlags & SF_OnToWhere ){ | |
| 154123 | + selectCheckOnClauses(pParse, p); | |
| 154124 | + if( pParse->nErr ){ | |
| 154125 | + goto select_end; | |
| 154126 | + } | |
| 154127 | + } | |
| 152906 | 154128 | |
| 152907 | 154129 | /* If the SF_UFSrcCheck flag is set, then this function is being called |
| 152908 | 154130 | ** as part of populating the temp table for an UPDATE...FROM statement. |
| 152909 | 154131 | ** In this case, it is an error if the target object (pSrc->a[0]) name |
| 152910 | 154132 | ** or alias is duplicated within FROM clause (pSrc->a[1..n]). |
| @@ -153766,10 +154988,11 @@ | ||
| 153766 | 154988 | iBMem = pParse->nMem + 1; |
| 153767 | 154989 | pParse->nMem += pGroupBy->nExpr; |
| 153768 | 154990 | sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag); |
| 153769 | 154991 | VdbeComment((v, "clear abort flag")); |
| 153770 | 154992 | sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1); |
| 154993 | + sqlite3ExprNullRegisterRange(pParse, iAMem, pGroupBy->nExpr); | |
| 153771 | 154994 | |
| 153772 | 154995 | /* Begin a loop that will extract all source rows in GROUP BY order. |
| 153773 | 154996 | ** This might involve two separate loops with an OP_Sort in between, or |
| 153774 | 154997 | ** it might be a single loop that uses an index to extract information |
| 153775 | 154998 | ** in the right order to begin with. |
| @@ -155466,11 +156689,14 @@ | ||
| 155466 | 156689 | sqlite3 *db = pParse->db; |
| 155467 | 156690 | ExprList *pNew; |
| 155468 | 156691 | Returning *pReturning; |
| 155469 | 156692 | Select sSelect; |
| 155470 | 156693 | SrcList *pFrom; |
| 155471 | - u8 fromSpace[SZ_SRCLIST_1]; | |
| 156694 | + union { | |
| 156695 | + SrcList sSrc; | |
| 156696 | + u8 fromSpace[SZ_SRCLIST_1]; | |
| 156697 | + } uSrc; | |
| 155472 | 156698 | |
| 155473 | 156699 | assert( v!=0 ); |
| 155474 | 156700 | if( !pParse->bReturning ){ |
| 155475 | 156701 | /* This RETURNING trigger must be for a different statement as |
| 155476 | 156702 | ** this statement lacks a RETURNING clause. */ |
| @@ -155482,12 +156708,12 @@ | ||
| 155482 | 156708 | if( pTrigger != &(pReturning->retTrig) ){ |
| 155483 | 156709 | /* This RETURNING trigger is for a different statement */ |
| 155484 | 156710 | return; |
| 155485 | 156711 | } |
| 155486 | 156712 | memset(&sSelect, 0, sizeof(sSelect)); |
| 155487 | - pFrom = (SrcList*)fromSpace; | |
| 155488 | - memset(pFrom, 0, SZ_SRCLIST_1); | |
| 156713 | + memset(&uSrc, 0, sizeof(uSrc)); | |
| 156714 | + pFrom = &uSrc.sSrc; | |
| 155489 | 156715 | sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); |
| 155490 | 156716 | sSelect.pSrc = pFrom; |
| 155491 | 156717 | pFrom->nSrc = 1; |
| 155492 | 156718 | pFrom->a[0].pSTab = pTab; |
| 155493 | 156719 | pFrom->a[0].zName = pTab->zName; /* tag-20240424-1 */ |
| @@ -163014,11 +164240,14 @@ | ||
| 163014 | 164240 | WhereClause *pWC = &pWInfo->sWC; |
| 163015 | 164241 | WhereInfo *pSubWInfo; |
| 163016 | 164242 | WhereLoop *pLoop = pLevel->pWLoop; |
| 163017 | 164243 | SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; |
| 163018 | 164244 | SrcList *pFrom; |
| 163019 | - u8 fromSpace[SZ_SRCLIST_1]; | |
| 164245 | + union { | |
| 164246 | + SrcList sSrc; | |
| 164247 | + u8 fromSpace[SZ_SRCLIST_1]; | |
| 164248 | + } uSrc; | |
| 163020 | 164249 | Bitmask mAll = 0; |
| 163021 | 164250 | int k; |
| 163022 | 164251 | |
| 163023 | 164252 | ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pSTab->zName)); |
| 163024 | 164253 | sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, |
| @@ -163058,11 +164287,11 @@ | ||
| 163058 | 164287 | if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; |
| 163059 | 164288 | pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, |
| 163060 | 164289 | sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); |
| 163061 | 164290 | } |
| 163062 | 164291 | } |
| 163063 | - pFrom = (SrcList*)fromSpace; | |
| 164292 | + pFrom = &uSrc.sSrc; | |
| 163064 | 164293 | pFrom->nSrc = 1; |
| 163065 | 164294 | pFrom->nAlloc = 1; |
| 163066 | 164295 | memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem)); |
| 163067 | 164296 | pFrom->a[0].fg.jointype = 0; |
| 163068 | 164297 | assert( pParse->withinRJSubrtn < 100 ); |
| @@ -164275,25 +165504,11 @@ | ||
| 164275 | 165504 | Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); |
| 164276 | 165505 | if( ExprHasProperty(pExpr, EP_OuterON) ){ |
| 164277 | 165506 | prereqAll |= x; |
| 164278 | 165507 | extraRight = x-1; /* ON clause terms may not be used with an index |
| 164279 | 165508 | ** on left table of a LEFT JOIN. Ticket #3015 */ |
| 164280 | - if( (prereqAll>>1)>=x ){ | |
| 164281 | - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); | |
| 164282 | - return; | |
| 164283 | - } | |
| 164284 | 165509 | }else if( (prereqAll>>1)>=x ){ |
| 164285 | - /* The ON clause of an INNER JOIN references a table to its right. | |
| 164286 | - ** Most other SQL database engines raise an error. But SQLite versions | |
| 164287 | - ** 3.0 through 3.38 just put the ON clause constraint into the WHERE | |
| 164288 | - ** clause and carried on. Beginning with 3.39, raise an error only | |
| 164289 | - ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite | |
| 164290 | - ** more like other systems, and also preserves legacy. */ | |
| 164291 | - if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ | |
| 164292 | - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); | |
| 164293 | - return; | |
| 164294 | - } | |
| 164295 | 165510 | ExprClearProperty(pExpr, EP_InnerON); |
| 164296 | 165511 | } |
| 164297 | 165512 | } |
| 164298 | 165513 | pTerm->prereqAll = prereqAll; |
| 164299 | 165514 | pTerm->leftCursor = -1; |
| @@ -169084,10 +170299,11 @@ | ||
| 169084 | 170299 | if( pProbe->bNoQuery ) continue; |
| 169085 | 170300 | rSize = pProbe->aiRowLogEst[0]; |
| 169086 | 170301 | pNew->u.btree.nEq = 0; |
| 169087 | 170302 | pNew->u.btree.nBtm = 0; |
| 169088 | 170303 | pNew->u.btree.nTop = 0; |
| 170304 | + pNew->u.btree.nDistinctCol = 0; | |
| 169089 | 170305 | pNew->nSkip = 0; |
| 169090 | 170306 | pNew->nLTerm = 0; |
| 169091 | 170307 | pNew->iSortIdx = 0; |
| 169092 | 170308 | pNew->rSetup = 0; |
| 169093 | 170309 | pNew->prereq = mPrereq; |
| @@ -170150,14 +171366,16 @@ | ||
| 170150 | 171366 | if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ |
| 170151 | 171367 | if( pLoop->u.vtab.isOrdered |
| 170152 | 171368 | && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) |
| 170153 | 171369 | ){ |
| 170154 | 171370 | obSat = obDone; |
| 171371 | + }else{ | |
| 171372 | + /* No further ORDER BY terms may be matched. So this call should | |
| 171373 | + ** return >=0, not -1. Clear isOrderDistinct to ensure it does so. */ | |
| 171374 | + isOrderDistinct = 0; | |
| 170155 | 171375 | } |
| 170156 | 171376 | break; |
| 170157 | - }else if( wctrlFlags & WHERE_DISTINCTBY ){ | |
| 170158 | - pLoop->u.btree.nDistinctCol = 0; | |
| 170159 | 171377 | } |
| 170160 | 171378 | iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; |
| 170161 | 171379 | |
| 170162 | 171380 | /* Mark off any ORDER BY term X that is a column in the table of |
| 170163 | 171381 | ** the current loop for which there is term in the WHERE |
| @@ -170897,21 +172115,28 @@ | ||
| 170897 | 172115 | |
| 170898 | 172116 | /* Check to see if pWLoop should be added to the set of |
| 170899 | 172117 | ** mxChoice best-so-far paths. |
| 170900 | 172118 | ** |
| 170901 | 172119 | ** First look for an existing path among best-so-far paths |
| 170902 | - ** that covers the same set of loops and has the same isOrdered | |
| 170903 | - ** setting as the current path candidate. | |
| 172120 | + ** that: | |
| 172121 | + ** (1) covers the same set of loops, and | |
| 172122 | + ** (2) has a compatible isOrdered value. | |
| 172123 | + ** | |
| 172124 | + ** "Compatible isOrdered value" means either | |
| 172125 | + ** (A) both have isOrdered==-1, or | |
| 172126 | + ** (B) both have isOrder>=0, or | |
| 172127 | + ** (C) ordering does not matter because this is the last round | |
| 172128 | + ** of the solver. | |
| 170904 | 172129 | ** |
| 170905 | 172130 | ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent |
| 170906 | 172131 | ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range |
| 170907 | 172132 | ** of legal values for isOrdered, -1..64. |
| 170908 | 172133 | */ |
| 170909 | 172134 | testcase( nTo==0 ); |
| 170910 | 172135 | for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ |
| 170911 | 172136 | if( pTo->maskLoop==maskNew |
| 170912 | - && ((pTo->isOrdered^isOrdered)&0x80)==0 | |
| 172137 | + && ( ((pTo->isOrdered^isOrdered)&0x80)==0 || iLoop==nLoop-1 ) | |
| 170913 | 172138 | ){ |
| 170914 | 172139 | testcase( jj==nTo-1 ); |
| 170915 | 172140 | break; |
| 170916 | 172141 | } |
| 170917 | 172142 | } |
| @@ -171062,15 +172287,14 @@ | ||
| 171062 | 172287 | sqlite3ErrorMsg(pParse, "no query solution"); |
| 171063 | 172288 | sqlite3StackFreeNN(pParse->db, pSpace); |
| 171064 | 172289 | return SQLITE_ERROR; |
| 171065 | 172290 | } |
| 171066 | 172291 | |
| 171067 | - /* Find the lowest cost path. pFrom will be left pointing to that path */ | |
| 172292 | + /* Only one path is available, which is the best path */ | |
| 172293 | + assert( nFrom==1 ); | |
| 171068 | 172294 | pFrom = aFrom; |
| 171069 | - for(ii=1; ii<nFrom; ii++){ | |
| 171070 | - if( pFrom->rCost>aFrom[ii].rCost ) pFrom = &aFrom[ii]; | |
| 171071 | - } | |
| 172295 | + | |
| 171072 | 172296 | assert( pWInfo->nLevel==nLoop ); |
| 171073 | 172297 | /* Load the lowest cost path into pWInfo */ |
| 171074 | 172298 | for(iLoop=0; iLoop<nLoop; iLoop++){ |
| 171075 | 172299 | WhereLevel *pLevel = pWInfo->a + iLoop; |
| 171076 | 172300 | pLevel->pWLoop = pWLoop = pFrom->aLoop[iLoop]; |
| @@ -171199,11 +172423,14 @@ | ||
| 171199 | 172423 | int once = 0; |
| 171200 | 172424 | #endif |
| 171201 | 172425 | for(i=0; i<pWInfo->nLevel; i++){ |
| 171202 | 172426 | WhereLoop *p = pWInfo->a[i].pWLoop; |
| 171203 | 172427 | if( p==0 ) break; |
| 171204 | - if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue; | |
| 172428 | + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ | |
| 172429 | + /* Treat a vtab scan as similar to a full-table scan */ | |
| 172430 | + break; | |
| 172431 | + } | |
| 171205 | 172432 | if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ |
| 171206 | 172433 | u8 iTab = p->iTab; |
| 171207 | 172434 | WhereLoop *pLoop; |
| 171208 | 172435 | for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ |
| 171209 | 172436 | if( pLoop->iTab!=iTab ) continue; |
| @@ -175312,11 +176539,11 @@ | ||
| 175312 | 176539 | ** RETURN_ROW |
| 175313 | 176540 | ** |
| 175314 | 176541 | ** |
| 175315 | 176542 | ** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING |
| 175316 | 176543 | ** |
| 175317 | -** ... loop started by sqlite3WhereBegin() ... | |
| 176544 | +** ... loop started by sqlite3WhereBegin() ... | |
| 175318 | 176545 | ** if( new partition ){ |
| 175319 | 176546 | ** Gosub flush |
| 175320 | 176547 | ** } |
| 175321 | 176548 | ** Insert new row into eph table. |
| 175322 | 176549 | ** if( first row of partition ){ |
| @@ -175830,10 +177057,16 @@ | ||
| 175830 | 177057 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 175831 | 177058 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1); |
| 175832 | 177059 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); |
| 175833 | 177060 | }else{ |
| 175834 | 177061 | assert( pMWin->eEnd==TK_FOLLOWING ); |
| 177062 | + /* assert( regStart>=0 ); | |
| 177063 | + ** regEnd = regEnd - regStart; | |
| 177064 | + ** regStart = 0; */ | |
| 177065 | + sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd); | |
| 177066 | + sqlite3VdbeAddOp2(v, OP_Integer, 0, regStart); | |
| 177067 | + | |
| 175835 | 177068 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 175836 | 177069 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); |
| 175837 | 177070 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); |
| 175838 | 177071 | } |
| 175839 | 177072 | sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); |
| @@ -183317,13 +184550,10 @@ | ||
| 183317 | 184550 | #endif |
| 183318 | 184551 | #ifdef SQLITE_ENABLE_DBSTAT_VTAB |
| 183319 | 184552 | sqlite3DbstatRegister, |
| 183320 | 184553 | #endif |
| 183321 | 184554 | sqlite3TestExtInit, |
| 183322 | -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) | |
| 183323 | - sqlite3JsonTableFunctions, | |
| 183324 | -#endif | |
| 183325 | 184555 | #ifdef SQLITE_ENABLE_STMTVTAB |
| 183326 | 184556 | sqlite3StmtVtabInit, |
| 183327 | 184557 | #endif |
| 183328 | 184558 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 183329 | 184559 | sqlite3VdbeBytecodeVtabInit, |
| @@ -184775,10 +186005,13 @@ | ||
| 184775 | 186005 | for(i=0; i<2 && zName==0; i++, rc &= 0xff){ |
| 184776 | 186006 | switch( rc ){ |
| 184777 | 186007 | case SQLITE_OK: zName = "SQLITE_OK"; break; |
| 184778 | 186008 | case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; |
| 184779 | 186009 | case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; |
| 186010 | + case SQLITE_ERROR_RETRY: zName = "SQLITE_ERROR_RETRY"; break; | |
| 186011 | + case SQLITE_ERROR_MISSING_COLLSEQ: | |
| 186012 | + zName = "SQLITE_ERROR_MISSING_COLLSEQ"; break; | |
| 184780 | 186013 | case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; |
| 184781 | 186014 | case SQLITE_PERM: zName = "SQLITE_PERM"; break; |
| 184782 | 186015 | case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; |
| 184783 | 186016 | case SQLITE_ABORT_ROLLBACK: zName = "SQLITE_ABORT_ROLLBACK"; break; |
| 184784 | 186017 | case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; |
| @@ -185955,10 +187188,33 @@ | ||
| 185955 | 187188 | } |
| 185956 | 187189 | } |
| 185957 | 187190 | sqlite3_mutex_leave(db->mutex); |
| 185958 | 187191 | return z; |
| 185959 | 187192 | } |
| 187193 | + | |
| 187194 | +/* | |
| 187195 | +** Set the error code and error message associated with the database handle. | |
| 187196 | +** | |
| 187197 | +** This routine is intended to be called by outside extensions (ex: the | |
| 187198 | +** Session extension). Internal logic should invoke sqlite3Error() or | |
| 187199 | +** sqlite3ErrorWithMsg() directly. | |
| 187200 | +*/ | |
| 187201 | +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ | |
| 187202 | + int rc = SQLITE_OK; | |
| 187203 | + if( !sqlite3SafetyCheckSickOrOk(db) ){ | |
| 187204 | + return SQLITE_MISUSE_BKPT; | |
| 187205 | + } | |
| 187206 | + sqlite3_mutex_enter(db->mutex); | |
| 187207 | + if( zMsg ){ | |
| 187208 | + sqlite3ErrorWithMsg(db, errcode, "%s", zMsg); | |
| 187209 | + }else{ | |
| 187210 | + sqlite3Error(db, errcode); | |
| 187211 | + } | |
| 187212 | + rc = sqlite3ApiExit(db, rc); | |
| 187213 | + sqlite3_mutex_leave(db->mutex); | |
| 187214 | + return rc; | |
| 187215 | +} | |
| 185960 | 187216 | |
| 185961 | 187217 | /* |
| 185962 | 187218 | ** Return the byte offset of the most recent error |
| 185963 | 187219 | */ |
| 185964 | 187220 | SQLITE_API int sqlite3_error_offset(sqlite3 *db){ |
| @@ -187780,17 +189036,19 @@ | ||
| 187780 | 189036 | case SQLITE_TESTCTRL_ISINIT: { |
| 187781 | 189037 | if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; |
| 187782 | 189038 | break; |
| 187783 | 189039 | } |
| 187784 | 189040 | |
| 187785 | - /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); | |
| 189041 | + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, mode, tnum); | |
| 187786 | 189042 | ** |
| 187787 | 189043 | ** This test control is used to create imposter tables. "db" is a pointer |
| 187788 | 189044 | ** to the database connection. dbName is the database name (ex: "main" or |
| 187789 | - ** "temp") which will receive the imposter. "onOff" turns imposter mode on | |
| 187790 | - ** or off. "tnum" is the root page of the b-tree to which the imposter | |
| 187791 | - ** table should connect. | |
| 189045 | + ** "temp") which will receive the imposter. "mode" turns imposter mode on | |
| 189046 | + ** or off. mode==0 means imposter mode is off. mode==1 means imposter mode | |
| 189047 | + ** is on. mode==2 means imposter mode is on but results in an imposter | |
| 189048 | + ** table that is read-only unless writable_schema is on. "tnum" is the | |
| 189049 | + ** root page of the b-tree to which the imposter table should connect. | |
| 187792 | 189050 | ** |
| 187793 | 189051 | ** Enable imposter mode only when the schema has already been parsed. Then |
| 187794 | 189052 | ** run a single CREATE TABLE statement to construct the imposter table in |
| 187795 | 189053 | ** the parsed schema. Then turn imposter mode back off again. |
| 187796 | 189054 | ** |
| @@ -189023,21 +190281,24 @@ | ||
| 189023 | 190281 | ** |
| 189024 | 190282 | */ |
| 189025 | 190283 | #ifndef _FTSINT_H |
| 189026 | 190284 | #define _FTSINT_H |
| 189027 | 190285 | |
| 190286 | +/* | |
| 190287 | +** Activate assert() only if SQLITE_TEST is enabled. | |
| 190288 | +*/ | |
| 190289 | +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) | |
| 190290 | +# define NDEBUG 1 | |
| 190291 | +#endif | |
| 190292 | + | |
| 189028 | 190293 | /* #include <assert.h> */ |
| 189029 | 190294 | /* #include <stdlib.h> */ |
| 189030 | 190295 | /* #include <stddef.h> */ |
| 189031 | 190296 | /* #include <stdio.h> */ |
| 189032 | 190297 | /* #include <string.h> */ |
| 189033 | 190298 | /* #include <stdarg.h> */ |
| 189034 | 190299 | |
| 189035 | -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) | |
| 189036 | -# define NDEBUG 1 | |
| 189037 | -#endif | |
| 189038 | - | |
| 189039 | 190300 | /* FTS3/FTS4 require virtual tables */ |
| 189040 | 190301 | #ifdef SQLITE_OMIT_VIRTUALTABLE |
| 189041 | 190302 | # undef SQLITE_ENABLE_FTS3 |
| 189042 | 190303 | # undef SQLITE_ENABLE_FTS4 |
| 189043 | 190304 | #endif |
| @@ -189476,17 +190737,10 @@ | ||
| 189476 | 190737 | /* |
| 189477 | 190738 | ** Macro used to suppress compiler warnings for unused parameters. |
| 189478 | 190739 | */ |
| 189479 | 190740 | #define UNUSED_PARAMETER(x) (void)(x) |
| 189480 | 190741 | |
| 189481 | -/* | |
| 189482 | -** Activate assert() only if SQLITE_TEST is enabled. | |
| 189483 | -*/ | |
| 189484 | -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) | |
| 189485 | -# define NDEBUG 1 | |
| 189486 | -#endif | |
| 189487 | - | |
| 189488 | 190742 | /* |
| 189489 | 190743 | ** The TESTONLY macro is used to enclose variable declarations or |
| 189490 | 190744 | ** other bits of code that are needed to support the arguments |
| 189491 | 190745 | ** within testcase() and assert() macros. |
| 189492 | 190746 | */ |
| @@ -203757,12 +205011,12 @@ | ||
| 203757 | 205011 | /* |
| 203758 | 205012 | ** An object of this type contains the state required to create or append |
| 203759 | 205013 | ** to an appendable b-tree segment. |
| 203760 | 205014 | */ |
| 203761 | 205015 | struct IncrmergeWriter { |
| 203762 | - int nLeafEst; /* Space allocated for leaf blocks */ | |
| 203763 | - int nWork; /* Number of leaf pages flushed */ | |
| 205016 | + i64 nLeafEst; /* Space allocated for leaf blocks */ | |
| 205017 | + i64 nWork; /* Number of leaf pages flushed */ | |
| 203764 | 205018 | sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ |
| 203765 | 205019 | int iIdx; /* Index of *output* segment in iAbsLevel+1 */ |
| 203766 | 205020 | sqlite3_int64 iStart; /* Block number of first allocated block */ |
| 203767 | 205021 | sqlite3_int64 iEnd; /* Block number of last allocated block */ |
| 203768 | 205022 | sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ |
| @@ -204504,21 +205758,21 @@ | ||
| 204504 | 205758 | Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */ |
| 204505 | 205759 | IncrmergeWriter *pWriter /* Populate this object */ |
| 204506 | 205760 | ){ |
| 204507 | 205761 | int rc; /* Return Code */ |
| 204508 | 205762 | int i; /* Iterator variable */ |
| 204509 | - int nLeafEst = 0; /* Blocks allocated for leaf nodes */ | |
| 205763 | + i64 nLeafEst = 0; /* Blocks allocated for leaf nodes */ | |
| 204510 | 205764 | sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ |
| 204511 | 205765 | sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ |
| 204512 | 205766 | |
| 204513 | 205767 | /* Calculate nLeafEst. */ |
| 204514 | 205768 | rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0); |
| 204515 | 205769 | if( rc==SQLITE_OK ){ |
| 204516 | 205770 | sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); |
| 204517 | 205771 | sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); |
| 204518 | 205772 | if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ |
| 204519 | - nLeafEst = sqlite3_column_int(pLeafEst, 0); | |
| 205773 | + nLeafEst = sqlite3_column_int64(pLeafEst, 0); | |
| 204520 | 205774 | } |
| 204521 | 205775 | rc = sqlite3_reset(pLeafEst); |
| 204522 | 205776 | } |
| 204523 | 205777 | if( rc!=SQLITE_OK ) return rc; |
| 204524 | 205778 | |
| @@ -205897,14 +207151,10 @@ | ||
| 205897 | 207151 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) |
| 205898 | 207152 | |
| 205899 | 207153 | /* #include <string.h> */ |
| 205900 | 207154 | /* #include <assert.h> */ |
| 205901 | 207155 | |
| 205902 | -#ifndef SQLITE_AMALGAMATION | |
| 205903 | -typedef sqlite3_int64 i64; | |
| 205904 | -#endif | |
| 205905 | - | |
| 205906 | 207156 | /* |
| 205907 | 207157 | ** Characters that may appear in the second argument to matchinfo(). |
| 205908 | 207158 | */ |
| 205909 | 207159 | #define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ |
| 205910 | 207160 | #define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ |
| @@ -210754,11 +212004,11 @@ | ||
| 210754 | 212004 | switch( (u8)zIn[1] ){ |
| 210755 | 212005 | case '\'': |
| 210756 | 212006 | jsonAppendChar(pOut, '\''); |
| 210757 | 212007 | break; |
| 210758 | 212008 | case 'v': |
| 210759 | - jsonAppendRawNZ(pOut, "\\u0009", 6); | |
| 212009 | + jsonAppendRawNZ(pOut, "\\u000b", 6); | |
| 210760 | 212010 | break; |
| 210761 | 212011 | case 'x': |
| 210762 | 212012 | if( sz2<4 ){ |
| 210763 | 212013 | pOut->eErr |= JSTRING_MALFORMED; |
| 210764 | 212014 | sz2 = 2; |
| @@ -211604,23 +212854,31 @@ | ||
| 211604 | 212854 | /* |
| 211605 | 212855 | ** Return the value of the BLOB node at index i. |
| 211606 | 212856 | ** |
| 211607 | 212857 | ** If the value is a primitive, return it as an SQL value. |
| 211608 | 212858 | ** If the value is an array or object, return it as either |
| 211609 | -** JSON text or the BLOB encoding, depending on the JSON_B flag | |
| 211610 | -** on the userdata. | |
| 212859 | +** JSON text or the BLOB encoding, depending on the eMode flag | |
| 212860 | +** as follows: | |
| 212861 | +** | |
| 212862 | +** eMode==0 JSONB if the JSON_B flag is set in userdata or | |
| 212863 | +** text if the JSON_B flag is omitted from userdata. | |
| 212864 | +** | |
| 212865 | +** eMode==1 Text | |
| 212866 | +** | |
| 212867 | +** eMode==2 JSONB | |
| 211611 | 212868 | */ |
| 211612 | 212869 | static void jsonReturnFromBlob( |
| 211613 | 212870 | JsonParse *pParse, /* Complete JSON parse tree */ |
| 211614 | 212871 | u32 i, /* Index of the node */ |
| 211615 | 212872 | sqlite3_context *pCtx, /* Return value for this function */ |
| 211616 | - int textOnly /* return text JSON. Disregard user-data */ | |
| 212873 | + int eMode /* Format of return: text of JSONB */ | |
| 211617 | 212874 | ){ |
| 211618 | 212875 | u32 n, sz; |
| 211619 | 212876 | int rc; |
| 211620 | 212877 | sqlite3 *db = sqlite3_context_db_handle(pCtx); |
| 211621 | 212878 | |
| 212879 | + assert( eMode>=0 && eMode<=2 ); | |
| 211622 | 212880 | n = jsonbPayloadSize(pParse, i, &sz); |
| 211623 | 212881 | if( n==0 ){ |
| 211624 | 212882 | sqlite3_result_error(pCtx, "malformed JSON", -1); |
| 211625 | 212883 | return; |
| 211626 | 212884 | } |
| @@ -211657,11 +212915,23 @@ | ||
| 211657 | 212915 | z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); |
| 211658 | 212916 | if( z==0 ) goto returnfromblob_oom; |
| 211659 | 212917 | rc = sqlite3DecOrHexToI64(z, &iRes); |
| 211660 | 212918 | sqlite3DbFree(db, z); |
| 211661 | 212919 | if( rc==0 ){ |
| 211662 | - sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); | |
| 212920 | + if( iRes<0 ){ | |
| 212921 | + /* A hexadecimal literal with 16 significant digits and with the | |
| 212922 | + ** high-order bit set is a negative integer in SQLite (and hence | |
| 212923 | + ** iRes comes back as negative) but should be interpreted as a | |
| 212924 | + ** positive value if it occurs within JSON. The value is too | |
| 212925 | + ** large to appear as an SQLite integer so it must be converted | |
| 212926 | + ** into floating point. */ | |
| 212927 | + double r; | |
| 212928 | + r = (double)*(sqlite3_uint64*)&iRes; | |
| 212929 | + sqlite3_result_double(pCtx, bNeg ? -r : r); | |
| 212930 | + }else{ | |
| 212931 | + sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); | |
| 212932 | + } | |
| 211663 | 212933 | }else if( rc==3 && bNeg ){ |
| 211664 | 212934 | sqlite3_result_int64(pCtx, SMALLEST_INT64); |
| 211665 | 212935 | }else if( rc==1 ){ |
| 211666 | 212936 | goto returnfromblob_malformed; |
| 211667 | 212937 | }else{ |
| @@ -211735,12 +213005,18 @@ | ||
| 211735 | 213005 | sqlite3_result_text(pCtx, zOut, iOut, SQLITE_DYNAMIC); |
| 211736 | 213006 | break; |
| 211737 | 213007 | } |
| 211738 | 213008 | case JSONB_ARRAY: |
| 211739 | 213009 | case JSONB_OBJECT: { |
| 211740 | - int flags = textOnly ? 0 : SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)); | |
| 211741 | - if( flags & JSON_BLOB ){ | |
| 213010 | + if( eMode==0 ){ | |
| 213011 | + if( (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)) & JSON_BLOB)!=0 ){ | |
| 213012 | + eMode = 2; | |
| 213013 | + }else{ | |
| 213014 | + eMode = 1; | |
| 213015 | + } | |
| 213016 | + } | |
| 213017 | + if( eMode==2 ){ | |
| 211742 | 213018 | sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); |
| 211743 | 213019 | }else{ |
| 211744 | 213020 | jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); |
| 211745 | 213021 | } |
| 211746 | 213022 | break; |
| @@ -213383,10 +214659,11 @@ | ||
| 213383 | 214659 | u32 i; /* Index in sParse.aBlob[] of current row */ |
| 213384 | 214660 | u32 iEnd; /* EOF when i equals or exceeds this value */ |
| 213385 | 214661 | u32 nRoot; /* Size of the root path in bytes */ |
| 213386 | 214662 | u8 eType; /* Type of the container for element i */ |
| 213387 | 214663 | u8 bRecursive; /* True for json_tree(). False for json_each() */ |
| 214664 | + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ | |
| 213388 | 214665 | u32 nParent; /* Current nesting depth */ |
| 213389 | 214666 | u32 nParentAlloc; /* Space allocated for aParent[] */ |
| 213390 | 214667 | JsonParent *aParent; /* Parent elements of i */ |
| 213391 | 214668 | sqlite3 *db; /* Database connection */ |
| 213392 | 214669 | JsonString path; /* Current path */ |
| @@ -213394,10 +214671,12 @@ | ||
| 213394 | 214671 | }; |
| 213395 | 214672 | typedef struct JsonEachConnection JsonEachConnection; |
| 213396 | 214673 | struct JsonEachConnection { |
| 213397 | 214674 | sqlite3_vtab base; /* Base class - must be first */ |
| 213398 | 214675 | sqlite3 *db; /* Database connection */ |
| 214676 | + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ | |
| 214677 | + u8 bRecursive; /* True for json_tree(). False for json_each() */ | |
| 213399 | 214678 | }; |
| 213400 | 214679 | |
| 213401 | 214680 | |
| 213402 | 214681 | /* Constructor for the json_each virtual table */ |
| 213403 | 214682 | static int jsonEachConnect( |
| @@ -213436,10 +214715,12 @@ | ||
| 213436 | 214715 | pNew = (JsonEachConnection*)sqlite3DbMallocZero(db, sizeof(*pNew)); |
| 213437 | 214716 | *ppVtab = (sqlite3_vtab*)pNew; |
| 213438 | 214717 | if( pNew==0 ) return SQLITE_NOMEM; |
| 213439 | 214718 | sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
| 213440 | 214719 | pNew->db = db; |
| 214720 | + pNew->eMode = argv[0][4]=='b' ? 2 : 1; | |
| 214721 | + pNew->bRecursive = argv[0][4+pNew->eMode]=='t'; | |
| 213441 | 214722 | } |
| 213442 | 214723 | return rc; |
| 213443 | 214724 | } |
| 213444 | 214725 | |
| 213445 | 214726 | /* destructor for json_each virtual table */ |
| @@ -213447,34 +214728,26 @@ | ||
| 213447 | 214728 | JsonEachConnection *p = (JsonEachConnection*)pVtab; |
| 213448 | 214729 | sqlite3DbFree(p->db, pVtab); |
| 213449 | 214730 | return SQLITE_OK; |
| 213450 | 214731 | } |
| 213451 | 214732 | |
| 213452 | -/* constructor for a JsonEachCursor object for json_each(). */ | |
| 213453 | -static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ | |
| 214733 | +/* constructor for a JsonEachCursor object for json_each()/json_tree(). */ | |
| 214734 | +static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ | |
| 213454 | 214735 | JsonEachConnection *pVtab = (JsonEachConnection*)p; |
| 213455 | 214736 | JsonEachCursor *pCur; |
| 213456 | 214737 | |
| 213457 | 214738 | UNUSED_PARAMETER(p); |
| 213458 | 214739 | pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); |
| 213459 | 214740 | if( pCur==0 ) return SQLITE_NOMEM; |
| 213460 | 214741 | pCur->db = pVtab->db; |
| 214742 | + pCur->eMode = pVtab->eMode; | |
| 214743 | + pCur->bRecursive = pVtab->bRecursive; | |
| 213461 | 214744 | jsonStringZero(&pCur->path); |
| 213462 | 214745 | *ppCursor = &pCur->base; |
| 213463 | 214746 | return SQLITE_OK; |
| 213464 | 214747 | } |
| 213465 | 214748 | |
| 213466 | -/* constructor for a JsonEachCursor object for json_tree(). */ | |
| 213467 | -static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ | |
| 213468 | - int rc = jsonEachOpenEach(p, ppCursor); | |
| 213469 | - if( rc==SQLITE_OK ){ | |
| 213470 | - JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor; | |
| 213471 | - pCur->bRecursive = 1; | |
| 213472 | - } | |
| 213473 | - return rc; | |
| 213474 | -} | |
| 213475 | - | |
| 213476 | 214749 | /* Reset a JsonEachCursor back to its original state. Free any memory |
| 213477 | 214750 | ** held. */ |
| 213478 | 214751 | static void jsonEachCursorReset(JsonEachCursor *p){ |
| 213479 | 214752 | jsonParseReset(&p->sParse); |
| 213480 | 214753 | jsonStringReset(&p->path); |
| @@ -213675,11 +214948,11 @@ | ||
| 213675 | 214948 | } |
| 213676 | 214949 | break; |
| 213677 | 214950 | } |
| 213678 | 214951 | case JEACH_VALUE: { |
| 213679 | 214952 | u32 i = jsonSkipLabel(p); |
| 213680 | - jsonReturnFromBlob(&p->sParse, i, ctx, 1); | |
| 214953 | + jsonReturnFromBlob(&p->sParse, i, ctx, p->eMode); | |
| 213681 | 214954 | if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ |
| 213682 | 214955 | sqlite3_result_subtype(ctx, JSON_SUBTYPE); |
| 213683 | 214956 | } |
| 213684 | 214957 | break; |
| 213685 | 214958 | } |
| @@ -213919,40 +215192,11 @@ | ||
| 213919 | 215192 | 0, /* xCreate */ |
| 213920 | 215193 | jsonEachConnect, /* xConnect */ |
| 213921 | 215194 | jsonEachBestIndex, /* xBestIndex */ |
| 213922 | 215195 | jsonEachDisconnect, /* xDisconnect */ |
| 213923 | 215196 | 0, /* xDestroy */ |
| 213924 | - jsonEachOpenEach, /* xOpen - open a cursor */ | |
| 213925 | - jsonEachClose, /* xClose - close a cursor */ | |
| 213926 | - jsonEachFilter, /* xFilter - configure scan constraints */ | |
| 213927 | - jsonEachNext, /* xNext - advance a cursor */ | |
| 213928 | - jsonEachEof, /* xEof - check for end of scan */ | |
| 213929 | - jsonEachColumn, /* xColumn - read data */ | |
| 213930 | - jsonEachRowid, /* xRowid - read data */ | |
| 213931 | - 0, /* xUpdate */ | |
| 213932 | - 0, /* xBegin */ | |
| 213933 | - 0, /* xSync */ | |
| 213934 | - 0, /* xCommit */ | |
| 213935 | - 0, /* xRollback */ | |
| 213936 | - 0, /* xFindMethod */ | |
| 213937 | - 0, /* xRename */ | |
| 213938 | - 0, /* xSavepoint */ | |
| 213939 | - 0, /* xRelease */ | |
| 213940 | - 0, /* xRollbackTo */ | |
| 213941 | - 0, /* xShadowName */ | |
| 213942 | - 0 /* xIntegrity */ | |
| 213943 | -}; | |
| 213944 | - | |
| 213945 | -/* The methods of the json_tree virtual table. */ | |
| 213946 | -static sqlite3_module jsonTreeModule = { | |
| 213947 | - 0, /* iVersion */ | |
| 213948 | - 0, /* xCreate */ | |
| 213949 | - jsonEachConnect, /* xConnect */ | |
| 213950 | - jsonEachBestIndex, /* xBestIndex */ | |
| 213951 | - jsonEachDisconnect, /* xDisconnect */ | |
| 213952 | - 0, /* xDestroy */ | |
| 213953 | - jsonEachOpenTree, /* xOpen - open a cursor */ | |
| 215197 | + jsonEachOpen, /* xOpen - open a cursor */ | |
| 213954 | 215198 | jsonEachClose, /* xClose - close a cursor */ |
| 213955 | 215199 | jsonEachFilter, /* xFilter - configure scan constraints */ |
| 213956 | 215200 | jsonEachNext, /* xNext - advance a cursor */ |
| 213957 | 215201 | jsonEachEof, /* xEof - check for end of scan */ |
| 213958 | 215202 | jsonEachColumn, /* xColumn - read data */ |
| @@ -214037,26 +215281,25 @@ | ||
| 214037 | 215281 | #endif |
| 214038 | 215282 | } |
| 214039 | 215283 | |
| 214040 | 215284 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 214041 | 215285 | /* |
| 214042 | -** Register the JSON table-valued functions | |
| 215286 | +** Register the JSON table-valued function named zName and return a | |
| 215287 | +** pointer to its Module object. Return NULL if something goes wrong. | |
| 214043 | 215288 | */ |
| 214044 | -SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ | |
| 214045 | - int rc = SQLITE_OK; | |
| 214046 | - static const struct { | |
| 214047 | - const char *zName; | |
| 214048 | - sqlite3_module *pModule; | |
| 214049 | - } aMod[] = { | |
| 214050 | - { "json_each", &jsonEachModule }, | |
| 214051 | - { "json_tree", &jsonTreeModule }, | |
| 214052 | - }; | |
| 215289 | +SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3 *db, const char *zName){ | |
| 214053 | 215290 | unsigned int i; |
| 214054 | - for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){ | |
| 214055 | - rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0); | |
| 215291 | + static const char *azModule[] = { | |
| 215292 | + "json_each", "json_tree", "jsonb_each", "jsonb_tree" | |
| 215293 | + }; | |
| 215294 | + assert( sqlite3HashFind(&db->aModule, zName)==0 ); | |
| 215295 | + for(i=0; i<sizeof(azModule)/sizeof(azModule[0]); i++){ | |
| 215296 | + if( sqlite3StrICmp(azModule[i],zName)==0 ){ | |
| 215297 | + return sqlite3VtabCreateModule(db, azModule[i], &jsonEachModule, 0, 0); | |
| 215298 | + } | |
| 214056 | 215299 | } |
| 214057 | - return rc; | |
| 215300 | + return 0; | |
| 214058 | 215301 | } |
| 214059 | 215302 | #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */ |
| 214060 | 215303 | |
| 214061 | 215304 | /************** End of json.c ************************************************/ |
| 214062 | 215305 | /************** Begin file rtree.c *******************************************/ |
| @@ -228289,12 +229532,12 @@ | ||
| 228289 | 229532 | typedef struct DbpageTable DbpageTable; |
| 228290 | 229533 | typedef struct DbpageCursor DbpageCursor; |
| 228291 | 229534 | |
| 228292 | 229535 | struct DbpageCursor { |
| 228293 | 229536 | sqlite3_vtab_cursor base; /* Base class. Must be first */ |
| 228294 | - int pgno; /* Current page number */ | |
| 228295 | - int mxPgno; /* Last page to visit on this scan */ | |
| 229537 | + Pgno pgno; /* Current page number */ | |
| 229538 | + Pgno mxPgno; /* Last page to visit on this scan */ | |
| 228296 | 229539 | Pager *pPager; /* Pager being read/written */ |
| 228297 | 229540 | DbPage *pPage1; /* Page 1 of the database */ |
| 228298 | 229541 | int iDb; /* Index of database to analyze */ |
| 228299 | 229542 | int szPage; /* Size of each page in bytes */ |
| 228300 | 229543 | }; |
| @@ -228427,11 +229670,11 @@ | ||
| 228427 | 229670 | if( pCsr==0 ){ |
| 228428 | 229671 | return SQLITE_NOMEM_BKPT; |
| 228429 | 229672 | }else{ |
| 228430 | 229673 | memset(pCsr, 0, sizeof(DbpageCursor)); |
| 228431 | 229674 | pCsr->base.pVtab = pVTab; |
| 228432 | - pCsr->pgno = -1; | |
| 229675 | + pCsr->pgno = 0; | |
| 228433 | 229676 | } |
| 228434 | 229677 | |
| 228435 | 229678 | *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| 228436 | 229679 | return SQLITE_OK; |
| 228437 | 229680 | } |
| @@ -228527,16 +229770,16 @@ | ||
| 228527 | 229770 | ){ |
| 228528 | 229771 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 228529 | 229772 | int rc = SQLITE_OK; |
| 228530 | 229773 | switch( i ){ |
| 228531 | 229774 | case 0: { /* pgno */ |
| 228532 | - sqlite3_result_int(ctx, pCsr->pgno); | |
| 229775 | + sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno); | |
| 228533 | 229776 | break; |
| 228534 | 229777 | } |
| 228535 | 229778 | case 1: { /* data */ |
| 228536 | 229779 | DbPage *pDbPage = 0; |
| 228537 | - if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ | |
| 229780 | + if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){ | |
| 228538 | 229781 | /* The pending byte page. Assume it is zeroed out. Attempting to |
| 228539 | 229782 | ** request this page from the page is an SQLITE_CORRUPT error. */ |
| 228540 | 229783 | sqlite3_result_zeroblob(ctx, pCsr->szPage); |
| 228541 | 229784 | }else{ |
| 228542 | 229785 | rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); |
| @@ -228606,14 +229849,14 @@ | ||
| 228606 | 229849 | if( argc==1 ){ |
| 228607 | 229850 | zErr = "cannot delete"; |
| 228608 | 229851 | goto update_fail; |
| 228609 | 229852 | } |
| 228610 | 229853 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ |
| 228611 | - pgno = (Pgno)sqlite3_value_int(argv[2]); | |
| 229854 | + pgno = (Pgno)sqlite3_value_int64(argv[2]); | |
| 228612 | 229855 | isInsert = 1; |
| 228613 | 229856 | }else{ |
| 228614 | - pgno = sqlite3_value_int(argv[0]); | |
| 229857 | + pgno = (Pgno)sqlite3_value_int64(argv[0]); | |
| 228615 | 229858 | if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ |
| 228616 | 229859 | zErr = "cannot insert"; |
| 228617 | 229860 | goto update_fail; |
| 228618 | 229861 | } |
| 228619 | 229862 | isInsert = 0; |
| @@ -228744,10 +229987,539 @@ | ||
| 228744 | 229987 | #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| 228745 | 229988 | SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } |
| 228746 | 229989 | #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ |
| 228747 | 229990 | |
| 228748 | 229991 | /************** End of dbpage.c **********************************************/ |
| 229992 | +/************** Begin file carray.c ******************************************/ | |
| 229993 | +/* | |
| 229994 | +** 2016-06-29 | |
| 229995 | +** | |
| 229996 | +** The author disclaims copyright to this source code. In place of | |
| 229997 | +** a legal notice, here is a blessing: | |
| 229998 | +** | |
| 229999 | +** May you do good and not evil. | |
| 230000 | +** May you find forgiveness for yourself and forgive others. | |
| 230001 | +** May you share freely, never taking more than you give. | |
| 230002 | +** | |
| 230003 | +************************************************************************* | |
| 230004 | +** | |
| 230005 | +** This file implements a table-valued-function that | |
| 230006 | +** returns the values in a C-language array. | |
| 230007 | +** Examples: | |
| 230008 | +** | |
| 230009 | +** SELECT * FROM carray($ptr,5) | |
| 230010 | +** | |
| 230011 | +** The query above returns 5 integers contained in a C-language array | |
| 230012 | +** at the address $ptr. $ptr is a pointer to the array of integers. | |
| 230013 | +** The pointer value must be assigned to $ptr using the | |
| 230014 | +** sqlite3_bind_pointer() interface with a pointer type of "carray". | |
| 230015 | +** For example: | |
| 230016 | +** | |
| 230017 | +** static int aX[] = { 53, 9, 17, 2231, 4, 99 }; | |
| 230018 | +** int i = sqlite3_bind_parameter_index(pStmt, "$ptr"); | |
| 230019 | +** sqlite3_bind_pointer(pStmt, i, aX, "carray", 0); | |
| 230020 | +** | |
| 230021 | +** There is an optional third parameter to determine the datatype of | |
| 230022 | +** the C-language array. Allowed values of the third parameter are | |
| 230023 | +** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example: | |
| 230024 | +** | |
| 230025 | +** SELECT * FROM carray($ptr,10,'char*'); | |
| 230026 | +** | |
| 230027 | +** The default value of the third parameter is 'int32'. | |
| 230028 | +** | |
| 230029 | +** HOW IT WORKS | |
| 230030 | +** | |
| 230031 | +** The carray "function" is really a virtual table with the | |
| 230032 | +** following schema: | |
| 230033 | +** | |
| 230034 | +** CREATE TABLE carray( | |
| 230035 | +** value, | |
| 230036 | +** pointer HIDDEN, | |
| 230037 | +** count HIDDEN, | |
| 230038 | +** ctype TEXT HIDDEN | |
| 230039 | +** ); | |
| 230040 | +** | |
| 230041 | +** If the hidden columns "pointer" and "count" are unconstrained, then | |
| 230042 | +** the virtual table has no rows. Otherwise, the virtual table interprets | |
| 230043 | +** the integer value of "pointer" as a pointer to the array and "count" | |
| 230044 | +** as the number of elements in the array. The virtual table steps through | |
| 230045 | +** the array, element by element. | |
| 230046 | +*/ | |
| 230047 | +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) | |
| 230048 | +/* #include "sqliteInt.h" */ | |
| 230049 | +#if defined(_WIN32) || defined(__RTP__) || defined(_WRS_KERNEL) | |
| 230050 | + struct iovec { | |
| 230051 | + void *iov_base; | |
| 230052 | + size_t iov_len; | |
| 230053 | + }; | |
| 230054 | +#else | |
| 230055 | +# include <sys/uio.h> | |
| 230056 | +#endif | |
| 230057 | + | |
| 230058 | +/* | |
| 230059 | +** Names of allowed datatypes | |
| 230060 | +*/ | |
| 230061 | +static const char *azType[] = { "int32", "int64", "double", "char*", | |
| 230062 | + "struct iovec" }; | |
| 230063 | + | |
| 230064 | +/* | |
| 230065 | +** Structure used to hold the sqlite3_carray_bind() information | |
| 230066 | +*/ | |
| 230067 | +typedef struct carray_bind carray_bind; | |
| 230068 | +struct carray_bind { | |
| 230069 | + void *aData; /* The data */ | |
| 230070 | + int nData; /* Number of elements */ | |
| 230071 | + int mFlags; /* Control flags */ | |
| 230072 | + void (*xDel)(void*); /* Destructor for aData */ | |
| 230073 | +}; | |
| 230074 | + | |
| 230075 | + | |
| 230076 | +/* carray_cursor is a subclass of sqlite3_vtab_cursor which will | |
| 230077 | +** serve as the underlying representation of a cursor that scans | |
| 230078 | +** over rows of the result | |
| 230079 | +*/ | |
| 230080 | +typedef struct carray_cursor carray_cursor; | |
| 230081 | +struct carray_cursor { | |
| 230082 | + sqlite3_vtab_cursor base; /* Base class - must be first */ | |
| 230083 | + sqlite3_int64 iRowid; /* The rowid */ | |
| 230084 | + void *pPtr; /* Pointer to the array of values */ | |
| 230085 | + sqlite3_int64 iCnt; /* Number of integers in the array */ | |
| 230086 | + unsigned char eType; /* One of the CARRAY_type values */ | |
| 230087 | +}; | |
| 230088 | + | |
| 230089 | +/* | |
| 230090 | +** The carrayConnect() method is invoked to create a new | |
| 230091 | +** carray_vtab that describes the carray virtual table. | |
| 230092 | +** | |
| 230093 | +** Think of this routine as the constructor for carray_vtab objects. | |
| 230094 | +** | |
| 230095 | +** All this routine needs to do is: | |
| 230096 | +** | |
| 230097 | +** (1) Allocate the carray_vtab object and initialize all fields. | |
| 230098 | +** | |
| 230099 | +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the | |
| 230100 | +** result set of queries against carray will look like. | |
| 230101 | +*/ | |
| 230102 | +static int carrayConnect( | |
| 230103 | + sqlite3 *db, | |
| 230104 | + void *pAux, | |
| 230105 | + int argc, const char *const*argv, | |
| 230106 | + sqlite3_vtab **ppVtab, | |
| 230107 | + char **pzErr | |
| 230108 | +){ | |
| 230109 | + sqlite3_vtab *pNew; | |
| 230110 | + int rc; | |
| 230111 | + | |
| 230112 | +/* Column numbers */ | |
| 230113 | +#define CARRAY_COLUMN_VALUE 0 | |
| 230114 | +#define CARRAY_COLUMN_POINTER 1 | |
| 230115 | +#define CARRAY_COLUMN_COUNT 2 | |
| 230116 | +#define CARRAY_COLUMN_CTYPE 3 | |
| 230117 | + | |
| 230118 | + rc = sqlite3_declare_vtab(db, | |
| 230119 | + "CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)"); | |
| 230120 | + if( rc==SQLITE_OK ){ | |
| 230121 | + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); | |
| 230122 | + if( pNew==0 ) return SQLITE_NOMEM; | |
| 230123 | + memset(pNew, 0, sizeof(*pNew)); | |
| 230124 | + } | |
| 230125 | + return rc; | |
| 230126 | +} | |
| 230127 | + | |
| 230128 | +/* | |
| 230129 | +** This method is the destructor for carray_cursor objects. | |
| 230130 | +*/ | |
| 230131 | +static int carrayDisconnect(sqlite3_vtab *pVtab){ | |
| 230132 | + sqlite3_free(pVtab); | |
| 230133 | + return SQLITE_OK; | |
| 230134 | +} | |
| 230135 | + | |
| 230136 | +/* | |
| 230137 | +** Constructor for a new carray_cursor object. | |
| 230138 | +*/ | |
| 230139 | +static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ | |
| 230140 | + carray_cursor *pCur; | |
| 230141 | + pCur = sqlite3_malloc( sizeof(*pCur) ); | |
| 230142 | + if( pCur==0 ) return SQLITE_NOMEM; | |
| 230143 | + memset(pCur, 0, sizeof(*pCur)); | |
| 230144 | + *ppCursor = &pCur->base; | |
| 230145 | + return SQLITE_OK; | |
| 230146 | +} | |
| 230147 | + | |
| 230148 | +/* | |
| 230149 | +** Destructor for a carray_cursor. | |
| 230150 | +*/ | |
| 230151 | +static int carrayClose(sqlite3_vtab_cursor *cur){ | |
| 230152 | + sqlite3_free(cur); | |
| 230153 | + return SQLITE_OK; | |
| 230154 | +} | |
| 230155 | + | |
| 230156 | + | |
| 230157 | +/* | |
| 230158 | +** Advance a carray_cursor to its next row of output. | |
| 230159 | +*/ | |
| 230160 | +static int carrayNext(sqlite3_vtab_cursor *cur){ | |
| 230161 | + carray_cursor *pCur = (carray_cursor*)cur; | |
| 230162 | + pCur->iRowid++; | |
| 230163 | + return SQLITE_OK; | |
| 230164 | +} | |
| 230165 | + | |
| 230166 | +/* | |
| 230167 | +** Return values of columns for the row at which the carray_cursor | |
| 230168 | +** is currently pointing. | |
| 230169 | +*/ | |
| 230170 | +static int carrayColumn( | |
| 230171 | + sqlite3_vtab_cursor *cur, /* The cursor */ | |
| 230172 | + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ | |
| 230173 | + int i /* Which column to return */ | |
| 230174 | +){ | |
| 230175 | + carray_cursor *pCur = (carray_cursor*)cur; | |
| 230176 | + sqlite3_int64 x = 0; | |
| 230177 | + switch( i ){ | |
| 230178 | + case CARRAY_COLUMN_POINTER: return SQLITE_OK; | |
| 230179 | + case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break; | |
| 230180 | + case CARRAY_COLUMN_CTYPE: { | |
| 230181 | + sqlite3_result_text(ctx, azType[pCur->eType], -1, SQLITE_STATIC); | |
| 230182 | + return SQLITE_OK; | |
| 230183 | + } | |
| 230184 | + default: { | |
| 230185 | + switch( pCur->eType ){ | |
| 230186 | + case CARRAY_INT32: { | |
| 230187 | + int *p = (int*)pCur->pPtr; | |
| 230188 | + sqlite3_result_int(ctx, p[pCur->iRowid-1]); | |
| 230189 | + return SQLITE_OK; | |
| 230190 | + } | |
| 230191 | + case CARRAY_INT64: { | |
| 230192 | + sqlite3_int64 *p = (sqlite3_int64*)pCur->pPtr; | |
| 230193 | + sqlite3_result_int64(ctx, p[pCur->iRowid-1]); | |
| 230194 | + return SQLITE_OK; | |
| 230195 | + } | |
| 230196 | + case CARRAY_DOUBLE: { | |
| 230197 | + double *p = (double*)pCur->pPtr; | |
| 230198 | + sqlite3_result_double(ctx, p[pCur->iRowid-1]); | |
| 230199 | + return SQLITE_OK; | |
| 230200 | + } | |
| 230201 | + case CARRAY_TEXT: { | |
| 230202 | + const char **p = (const char**)pCur->pPtr; | |
| 230203 | + sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT); | |
| 230204 | + return SQLITE_OK; | |
| 230205 | + } | |
| 230206 | + default: { | |
| 230207 | + const struct iovec *p = (struct iovec*)pCur->pPtr; | |
| 230208 | + assert( pCur->eType==CARRAY_BLOB ); | |
| 230209 | + sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base, | |
| 230210 | + (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT); | |
| 230211 | + return SQLITE_OK; | |
| 230212 | + } | |
| 230213 | + } | |
| 230214 | + } | |
| 230215 | + } | |
| 230216 | + sqlite3_result_int64(ctx, x); | |
| 230217 | + return SQLITE_OK; | |
| 230218 | +} | |
| 230219 | + | |
| 230220 | +/* | |
| 230221 | +** Return the rowid for the current row. In this implementation, the | |
| 230222 | +** rowid is the same as the output value. | |
| 230223 | +*/ | |
| 230224 | +static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ | |
| 230225 | + carray_cursor *pCur = (carray_cursor*)cur; | |
| 230226 | + *pRowid = pCur->iRowid; | |
| 230227 | + return SQLITE_OK; | |
| 230228 | +} | |
| 230229 | + | |
| 230230 | +/* | |
| 230231 | +** Return TRUE if the cursor has been moved off of the last | |
| 230232 | +** row of output. | |
| 230233 | +*/ | |
| 230234 | +static int carrayEof(sqlite3_vtab_cursor *cur){ | |
| 230235 | + carray_cursor *pCur = (carray_cursor*)cur; | |
| 230236 | + return pCur->iRowid>pCur->iCnt; | |
| 230237 | +} | |
| 230238 | + | |
| 230239 | +/* | |
| 230240 | +** This method is called to "rewind" the carray_cursor object back | |
| 230241 | +** to the first row of output. | |
| 230242 | +*/ | |
| 230243 | +static int carrayFilter( | |
| 230244 | + sqlite3_vtab_cursor *pVtabCursor, | |
| 230245 | + int idxNum, const char *idxStr, | |
| 230246 | + int argc, sqlite3_value **argv | |
| 230247 | +){ | |
| 230248 | + carray_cursor *pCur = (carray_cursor *)pVtabCursor; | |
| 230249 | + pCur->pPtr = 0; | |
| 230250 | + pCur->iCnt = 0; | |
| 230251 | + switch( idxNum ){ | |
| 230252 | + case 1: { | |
| 230253 | + carray_bind *pBind = sqlite3_value_pointer(argv[0], "carray-bind"); | |
| 230254 | + if( pBind==0 ) break; | |
| 230255 | + pCur->pPtr = pBind->aData; | |
| 230256 | + pCur->iCnt = pBind->nData; | |
| 230257 | + pCur->eType = pBind->mFlags & 0x07; | |
| 230258 | + break; | |
| 230259 | + } | |
| 230260 | + case 2: | |
| 230261 | + case 3: { | |
| 230262 | + pCur->pPtr = sqlite3_value_pointer(argv[0], "carray"); | |
| 230263 | + pCur->iCnt = pCur->pPtr ? sqlite3_value_int64(argv[1]) : 0; | |
| 230264 | + if( idxNum<3 ){ | |
| 230265 | + pCur->eType = CARRAY_INT32; | |
| 230266 | + }else{ | |
| 230267 | + unsigned char i; | |
| 230268 | + const char *zType = (const char*)sqlite3_value_text(argv[2]); | |
| 230269 | + for(i=0; i<sizeof(azType)/sizeof(azType[0]); i++){ | |
| 230270 | + if( sqlite3_stricmp(zType, azType[i])==0 ) break; | |
| 230271 | + } | |
| 230272 | + if( i>=sizeof(azType)/sizeof(azType[0]) ){ | |
| 230273 | + pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf( | |
| 230274 | + "unknown datatype: %Q", zType); | |
| 230275 | + return SQLITE_ERROR; | |
| 230276 | + }else{ | |
| 230277 | + pCur->eType = i; | |
| 230278 | + } | |
| 230279 | + } | |
| 230280 | + break; | |
| 230281 | + } | |
| 230282 | + } | |
| 230283 | + pCur->iRowid = 1; | |
| 230284 | + return SQLITE_OK; | |
| 230285 | +} | |
| 230286 | + | |
| 230287 | +/* | |
| 230288 | +** SQLite will invoke this method one or more times while planning a query | |
| 230289 | +** that uses the carray virtual table. This routine needs to create | |
| 230290 | +** a query plan for each invocation and compute an estimated cost for that | |
| 230291 | +** plan. | |
| 230292 | +** | |
| 230293 | +** In this implementation idxNum is used to represent the | |
| 230294 | +** query plan. idxStr is unused. | |
| 230295 | +** | |
| 230296 | +** idxNum is: | |
| 230297 | +** | |
| 230298 | +** 1 If only the pointer= constraint exists. In this case, the | |
| 230299 | +** parameter must be bound using sqlite3_carray_bind(). | |
| 230300 | +** | |
| 230301 | +** 2 if the pointer= and count= constraints exist. | |
| 230302 | +** | |
| 230303 | +** 3 if the ctype= constraint also exists. | |
| 230304 | +** | |
| 230305 | +** idxNum is 0 otherwise and carray becomes an empty table. | |
| 230306 | +*/ | |
| 230307 | +static int carrayBestIndex( | |
| 230308 | + sqlite3_vtab *tab, | |
| 230309 | + sqlite3_index_info *pIdxInfo | |
| 230310 | +){ | |
| 230311 | + int i; /* Loop over constraints */ | |
| 230312 | + int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */ | |
| 230313 | + int cntIdx = -1; /* Index of the count= constraint, or -1 if none */ | |
| 230314 | + int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */ | |
| 230315 | + unsigned seen = 0; /* Bitmask of == constrainted columns */ | |
| 230316 | + | |
| 230317 | + const struct sqlite3_index_constraint *pConstraint; | |
| 230318 | + pConstraint = pIdxInfo->aConstraint; | |
| 230319 | + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ | |
| 230320 | + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; | |
| 230321 | + if( pConstraint->iColumn>=0 ) seen |= 1 << pConstraint->iColumn; | |
| 230322 | + if( pConstraint->usable==0 ) continue; | |
| 230323 | + switch( pConstraint->iColumn ){ | |
| 230324 | + case CARRAY_COLUMN_POINTER: | |
| 230325 | + ptrIdx = i; | |
| 230326 | + break; | |
| 230327 | + case CARRAY_COLUMN_COUNT: | |
| 230328 | + cntIdx = i; | |
| 230329 | + break; | |
| 230330 | + case CARRAY_COLUMN_CTYPE: | |
| 230331 | + ctypeIdx = i; | |
| 230332 | + break; | |
| 230333 | + } | |
| 230334 | + } | |
| 230335 | + if( ptrIdx>=0 ){ | |
| 230336 | + pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1; | |
| 230337 | + pIdxInfo->aConstraintUsage[ptrIdx].omit = 1; | |
| 230338 | + pIdxInfo->estimatedCost = (double)1; | |
| 230339 | + pIdxInfo->estimatedRows = 100; | |
| 230340 | + pIdxInfo->idxNum = 1; | |
| 230341 | + if( cntIdx>=0 ){ | |
| 230342 | + pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2; | |
| 230343 | + pIdxInfo->aConstraintUsage[cntIdx].omit = 1; | |
| 230344 | + pIdxInfo->idxNum = 2; | |
| 230345 | + if( ctypeIdx>=0 ){ | |
| 230346 | + pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3; | |
| 230347 | + pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1; | |
| 230348 | + pIdxInfo->idxNum = 3; | |
| 230349 | + }else if( seen & (1<<CARRAY_COLUMN_CTYPE) ){ | |
| 230350 | + /* In a three-argument carray(), we need to know the value of all | |
| 230351 | + ** three arguments */ | |
| 230352 | + return SQLITE_CONSTRAINT; | |
| 230353 | + } | |
| 230354 | + }else if( seen & (1<<CARRAY_COLUMN_COUNT) ){ | |
| 230355 | + /* In a two-argument carray(), we need to know the value of both | |
| 230356 | + ** arguments */ | |
| 230357 | + return SQLITE_CONSTRAINT; | |
| 230358 | + } | |
| 230359 | + }else{ | |
| 230360 | + pIdxInfo->estimatedCost = (double)2147483647; | |
| 230361 | + pIdxInfo->estimatedRows = 2147483647; | |
| 230362 | + pIdxInfo->idxNum = 0; | |
| 230363 | + } | |
| 230364 | + return SQLITE_OK; | |
| 230365 | +} | |
| 230366 | + | |
| 230367 | +/* | |
| 230368 | +** This following structure defines all the methods for the | |
| 230369 | +** carray virtual table. | |
| 230370 | +*/ | |
| 230371 | +static sqlite3_module carrayModule = { | |
| 230372 | + 0, /* iVersion */ | |
| 230373 | + 0, /* xCreate */ | |
| 230374 | + carrayConnect, /* xConnect */ | |
| 230375 | + carrayBestIndex, /* xBestIndex */ | |
| 230376 | + carrayDisconnect, /* xDisconnect */ | |
| 230377 | + 0, /* xDestroy */ | |
| 230378 | + carrayOpen, /* xOpen - open a cursor */ | |
| 230379 | + carrayClose, /* xClose - close a cursor */ | |
| 230380 | + carrayFilter, /* xFilter - configure scan constraints */ | |
| 230381 | + carrayNext, /* xNext - advance a cursor */ | |
| 230382 | + carrayEof, /* xEof - check for end of scan */ | |
| 230383 | + carrayColumn, /* xColumn - read data */ | |
| 230384 | + carrayRowid, /* xRowid - read data */ | |
| 230385 | + 0, /* xUpdate */ | |
| 230386 | + 0, /* xBegin */ | |
| 230387 | + 0, /* xSync */ | |
| 230388 | + 0, /* xCommit */ | |
| 230389 | + 0, /* xRollback */ | |
| 230390 | + 0, /* xFindMethod */ | |
| 230391 | + 0, /* xRename */ | |
| 230392 | + 0, /* xSavepoint */ | |
| 230393 | + 0, /* xRelease */ | |
| 230394 | + 0, /* xRollbackTo */ | |
| 230395 | + 0, /* xShadow */ | |
| 230396 | + 0 /* xIntegrity */ | |
| 230397 | +}; | |
| 230398 | + | |
| 230399 | +/* | |
| 230400 | +** Destructor for the carray_bind object | |
| 230401 | +*/ | |
| 230402 | +static void carrayBindDel(void *pPtr){ | |
| 230403 | + carray_bind *p = (carray_bind*)pPtr; | |
| 230404 | + if( p->xDel!=SQLITE_STATIC ){ | |
| 230405 | + p->xDel(p->aData); | |
| 230406 | + } | |
| 230407 | + sqlite3_free(p); | |
| 230408 | +} | |
| 230409 | + | |
| 230410 | +/* | |
| 230411 | +** Invoke this interface in order to bind to the single-argument | |
| 230412 | +** version of CARRAY(). | |
| 230413 | +*/ | |
| 230414 | +SQLITE_API int sqlite3_carray_bind( | |
| 230415 | + sqlite3_stmt *pStmt, | |
| 230416 | + int idx, | |
| 230417 | + void *aData, | |
| 230418 | + int nData, | |
| 230419 | + int mFlags, | |
| 230420 | + void (*xDestroy)(void*) | |
| 230421 | +){ | |
| 230422 | + carray_bind *pNew = 0; | |
| 230423 | + int i; | |
| 230424 | + int rc = SQLITE_OK; | |
| 230425 | + | |
| 230426 | + /* Ensure that the mFlags value is acceptable. */ | |
| 230427 | + assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 ); | |
| 230428 | + assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 ); | |
| 230429 | + if( mFlags<CARRAY_INT32 || mFlags>CARRAY_BLOB ){ | |
| 230430 | + rc = SQLITE_ERROR; | |
| 230431 | + goto carray_bind_error; | |
| 230432 | + } | |
| 230433 | + | |
| 230434 | + pNew = sqlite3_malloc64(sizeof(*pNew)); | |
| 230435 | + if( pNew==0 ){ | |
| 230436 | + rc = SQLITE_NOMEM; | |
| 230437 | + goto carray_bind_error; | |
| 230438 | + } | |
| 230439 | + | |
| 230440 | + pNew->nData = nData; | |
| 230441 | + pNew->mFlags = mFlags; | |
| 230442 | + if( xDestroy==SQLITE_TRANSIENT ){ | |
| 230443 | + sqlite3_int64 sz = nData; | |
| 230444 | + switch( mFlags ){ | |
| 230445 | + case CARRAY_INT32: sz *= 4; break; | |
| 230446 | + case CARRAY_INT64: sz *= 8; break; | |
| 230447 | + case CARRAY_DOUBLE: sz *= 8; break; | |
| 230448 | + case CARRAY_TEXT: sz *= sizeof(char*); break; | |
| 230449 | + default: sz *= sizeof(struct iovec); break; | |
| 230450 | + } | |
| 230451 | + if( mFlags==CARRAY_TEXT ){ | |
| 230452 | + for(i=0; i<nData; i++){ | |
| 230453 | + const char *z = ((char**)aData)[i]; | |
| 230454 | + if( z ) sz += strlen(z) + 1; | |
| 230455 | + } | |
| 230456 | + }else if( mFlags==CARRAY_BLOB ){ | |
| 230457 | + for(i=0; i<nData; i++){ | |
| 230458 | + sz += ((struct iovec*)aData)[i].iov_len; | |
| 230459 | + } | |
| 230460 | + } | |
| 230461 | + | |
| 230462 | + pNew->aData = sqlite3_malloc64( sz ); | |
| 230463 | + if( pNew->aData==0 ){ | |
| 230464 | + rc = SQLITE_NOMEM; | |
| 230465 | + goto carray_bind_error; | |
| 230466 | + } | |
| 230467 | + | |
| 230468 | + if( mFlags==CARRAY_TEXT ){ | |
| 230469 | + char **az = (char**)pNew->aData; | |
| 230470 | + char *z = (char*)&az[nData]; | |
| 230471 | + for(i=0; i<nData; i++){ | |
| 230472 | + const char *zData = ((char**)aData)[i]; | |
| 230473 | + sqlite3_int64 n; | |
| 230474 | + if( zData==0 ){ | |
| 230475 | + az[i] = 0; | |
| 230476 | + continue; | |
| 230477 | + } | |
| 230478 | + az[i] = z; | |
| 230479 | + n = strlen(zData); | |
| 230480 | + memcpy(z, zData, n+1); | |
| 230481 | + z += n+1; | |
| 230482 | + } | |
| 230483 | + }else if( mFlags==CARRAY_BLOB ){ | |
| 230484 | + struct iovec *p = (struct iovec*)pNew->aData; | |
| 230485 | + unsigned char *z = (unsigned char*)&p[nData]; | |
| 230486 | + for(i=0; i<nData; i++){ | |
| 230487 | + size_t n = ((struct iovec*)aData)[i].iov_len; | |
| 230488 | + p[i].iov_len = n; | |
| 230489 | + p[i].iov_base = z; | |
| 230490 | + z += n; | |
| 230491 | + memcpy(p[i].iov_base, ((struct iovec*)aData)[i].iov_base, n); | |
| 230492 | + } | |
| 230493 | + }else{ | |
| 230494 | + memcpy(pNew->aData, aData, sz); | |
| 230495 | + } | |
| 230496 | + pNew->xDel = sqlite3_free; | |
| 230497 | + }else{ | |
| 230498 | + pNew->aData = aData; | |
| 230499 | + pNew->xDel = xDestroy; | |
| 230500 | + } | |
| 230501 | + return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); | |
| 230502 | + | |
| 230503 | + carray_bind_error: | |
| 230504 | + if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ | |
| 230505 | + xDestroy(aData); | |
| 230506 | + } | |
| 230507 | + sqlite3_free(pNew); | |
| 230508 | + return rc; | |
| 230509 | +} | |
| 230510 | + | |
| 230511 | +/* | |
| 230512 | +** Invoke this routine to register the carray() function. | |
| 230513 | +*/ | |
| 230514 | +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3 *db){ | |
| 230515 | + return sqlite3VtabCreateModule(db, "carray", &carrayModule, 0, 0); | |
| 230516 | +} | |
| 230517 | + | |
| 230518 | +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) */ | |
| 230519 | + | |
| 230520 | +/************** End of carray.c **********************************************/ | |
| 228749 | 230521 | /************** Begin file sqlite3session.c **********************************/ |
| 228750 | 230522 | |
| 228751 | 230523 | #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) |
| 228752 | 230524 | /* #include "sqlite3session.h" */ |
| 228753 | 230525 | /* #include <assert.h> */ |
| @@ -231561,10 +233333,23 @@ | ||
| 231561 | 233333 | assert( (a - p->aRecord)==p->nRecord ); |
| 231562 | 233334 | } |
| 231563 | 233335 | |
| 231564 | 233336 | return rc; |
| 231565 | 233337 | } |
| 233338 | + | |
| 233339 | +static int sessionPrepare( | |
| 233340 | + sqlite3 *db, | |
| 233341 | + sqlite3_stmt **pp, | |
| 233342 | + char **pzErrmsg, | |
| 233343 | + const char *zSql | |
| 233344 | +){ | |
| 233345 | + int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0); | |
| 233346 | + if( pzErrmsg && rc!=SQLITE_OK ){ | |
| 233347 | + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); | |
| 233348 | + } | |
| 233349 | + return rc; | |
| 233350 | +} | |
| 231566 | 233351 | |
| 231567 | 233352 | /* |
| 231568 | 233353 | ** Formulate and prepare a SELECT statement to retrieve a row from table |
| 231569 | 233354 | ** zTab in database zDb based on its primary key. i.e. |
| 231570 | 233355 | ** |
| @@ -231583,16 +233368,16 @@ | ||
| 231583 | 233368 | const char *zTab, /* Table name */ |
| 231584 | 233369 | int bRowid, |
| 231585 | 233370 | int nCol, /* Number of columns in table */ |
| 231586 | 233371 | const char **azCol, /* Names of table columns */ |
| 231587 | 233372 | u8 *abPK, /* PRIMARY KEY array */ |
| 231588 | - sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ | |
| 233373 | + sqlite3_stmt **ppStmt, /* OUT: Prepared SELECT statement */ | |
| 233374 | + char **pzErrmsg /* OUT: Error message */ | |
| 231589 | 233375 | ){ |
| 231590 | 233376 | int rc = SQLITE_OK; |
| 231591 | 233377 | char *zSql = 0; |
| 231592 | 233378 | const char *zSep = ""; |
| 231593 | - int nSql = -1; | |
| 231594 | 233379 | int i; |
| 231595 | 233380 | |
| 231596 | 233381 | SessionBuffer cols = {0, 0, 0}; |
| 231597 | 233382 | SessionBuffer nooptest = {0, 0, 0}; |
| 231598 | 233383 | SessionBuffer pkfield = {0, 0, 0}; |
| @@ -231668,11 +233453,11 @@ | ||
| 231668 | 233453 | nSql = buf.nBuf; |
| 231669 | 233454 | } |
| 231670 | 233455 | #endif |
| 231671 | 233456 | |
| 231672 | 233457 | if( rc==SQLITE_OK ){ |
| 231673 | - rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0); | |
| 233458 | + rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql); | |
| 231674 | 233459 | } |
| 231675 | 233460 | sqlite3_free(zSql); |
| 231676 | 233461 | sqlite3_free(nooptest.aBuf); |
| 231677 | 233462 | sqlite3_free(pkfield.aBuf); |
| 231678 | 233463 | sqlite3_free(pkvar.aBuf); |
| @@ -231832,11 +233617,11 @@ | ||
| 231832 | 233617 | sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); |
| 231833 | 233618 | |
| 231834 | 233619 | /* Build and compile a statement to execute: */ |
| 231835 | 233620 | if( rc==SQLITE_OK ){ |
| 231836 | 233621 | rc = sessionSelectStmt(db, 0, pSession->zDb, |
| 231837 | - zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel | |
| 233622 | + zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0 | |
| 231838 | 233623 | ); |
| 231839 | 233624 | } |
| 231840 | 233625 | |
| 231841 | 233626 | nNoop = buf.nBuf; |
| 231842 | 233627 | for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){ |
| @@ -233041,10 +234826,11 @@ | ||
| 233041 | 234826 | SessionBuffer rebase; /* Rebase information (if any) here */ |
| 233042 | 234827 | u8 bRebaseStarted; /* If table header is already in rebase */ |
| 233043 | 234828 | u8 bRebase; /* True to collect rebase information */ |
| 233044 | 234829 | u8 bIgnoreNoop; /* True to ignore no-op conflicts */ |
| 233045 | 234830 | int bRowid; |
| 234831 | + char *zErr; /* Error message, if any */ | |
| 233046 | 234832 | }; |
| 233047 | 234833 | |
| 233048 | 234834 | /* Number of prepared UPDATE statements to cache. */ |
| 233049 | 234835 | #define SESSION_UPDATE_CACHE_SZ 12 |
| 233050 | 234836 | |
| @@ -233266,11 +235052,11 @@ | ||
| 233266 | 235052 | } |
| 233267 | 235053 | sessionAppendStr(&buf, ")", &rc); |
| 233268 | 235054 | } |
| 233269 | 235055 | |
| 233270 | 235056 | if( rc==SQLITE_OK ){ |
| 233271 | - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); | |
| 235057 | + rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf); | |
| 233272 | 235058 | } |
| 233273 | 235059 | sqlite3_free(buf.aBuf); |
| 233274 | 235060 | |
| 233275 | 235061 | return rc; |
| 233276 | 235062 | } |
| @@ -233293,11 +235079,11 @@ | ||
| 233293 | 235079 | const char *zTab, /* Table name */ |
| 233294 | 235080 | SessionApplyCtx *p /* Session changeset-apply context */ |
| 233295 | 235081 | ){ |
| 233296 | 235082 | /* TODO */ |
| 233297 | 235083 | return sessionSelectStmt(db, p->bIgnoreNoop, |
| 233298 | - "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect | |
| 235084 | + "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr | |
| 233299 | 235085 | ); |
| 233300 | 235086 | } |
| 233301 | 235087 | |
| 233302 | 235088 | /* |
| 233303 | 235089 | ** Formulate and prepare an INSERT statement to add a record to table zTab. |
| @@ -233330,37 +235116,33 @@ | ||
| 233330 | 235116 | sessionAppendStr(&buf, ", ?", &rc); |
| 233331 | 235117 | } |
| 233332 | 235118 | sessionAppendStr(&buf, ")", &rc); |
| 233333 | 235119 | |
| 233334 | 235120 | if( rc==SQLITE_OK ){ |
| 233335 | - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); | |
| 235121 | + rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf); | |
| 233336 | 235122 | } |
| 233337 | 235123 | sqlite3_free(buf.aBuf); |
| 233338 | 235124 | return rc; |
| 233339 | 235125 | } |
| 233340 | 235126 | |
| 233341 | -static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ | |
| 233342 | - return sqlite3_prepare_v2(db, zSql, -1, pp, 0); | |
| 233343 | -} | |
| 233344 | - | |
| 233345 | 235127 | /* |
| 233346 | 235128 | ** Prepare statements for applying changes to the sqlite_stat1 table. |
| 233347 | 235129 | ** These are similar to those created by sessionSelectRow(), |
| 233348 | 235130 | ** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for |
| 233349 | 235131 | ** other tables. |
| 233350 | 235132 | */ |
| 233351 | 235133 | static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ |
| 233352 | 235134 | int rc = sessionSelectRow(db, "sqlite_stat1", p); |
| 233353 | 235135 | if( rc==SQLITE_OK ){ |
| 233354 | - rc = sessionPrepare(db, &p->pInsert, | |
| 235136 | + rc = sessionPrepare(db, &p->pInsert, 0, | |
| 233355 | 235137 | "INSERT INTO main.sqlite_stat1 VALUES(?1, " |
| 233356 | 235138 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " |
| 233357 | 235139 | "?3)" |
| 233358 | 235140 | ); |
| 233359 | 235141 | } |
| 233360 | 235142 | if( rc==SQLITE_OK ){ |
| 233361 | - rc = sessionPrepare(db, &p->pDelete, | |
| 235143 | + rc = sessionPrepare(db, &p->pDelete, 0, | |
| 233362 | 235144 | "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " |
| 233363 | 235145 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " |
| 233364 | 235146 | "AND (?4 OR stat IS ?3)" |
| 233365 | 235147 | ); |
| 233366 | 235148 | } |
| @@ -233580,11 +235362,11 @@ | ||
| 233580 | 235362 | sqlite3_changeset_iter *pIter, /* Changeset iterator */ |
| 233581 | 235363 | int(*xConflict)(void *, int, sqlite3_changeset_iter*), |
| 233582 | 235364 | void *pCtx, /* First argument for conflict handler */ |
| 233583 | 235365 | int *pbReplace /* OUT: Set to true if PK row is found */ |
| 233584 | 235366 | ){ |
| 233585 | - int res = 0; /* Value returned by conflict handler */ | |
| 235367 | + int res = SQLITE_CHANGESET_OMIT;/* Value returned by conflict handler */ | |
| 233586 | 235368 | int rc; |
| 233587 | 235369 | int nCol; |
| 233588 | 235370 | int op; |
| 233589 | 235371 | const char *zDummy; |
| 233590 | 235372 | |
| @@ -233601,15 +235383,13 @@ | ||
| 233601 | 235383 | rc = SQLITE_OK; |
| 233602 | 235384 | } |
| 233603 | 235385 | |
| 233604 | 235386 | if( rc==SQLITE_ROW ){ |
| 233605 | 235387 | /* There exists another row with the new.* primary key. */ |
| 233606 | - if( p->bIgnoreNoop | |
| 233607 | - && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) | |
| 235388 | + if( 0==p->bIgnoreNoop | |
| 235389 | + || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) | |
| 233608 | 235390 | ){ |
| 233609 | - res = SQLITE_CHANGESET_OMIT; | |
| 233610 | - }else{ | |
| 233611 | 235391 | pIter->pConflict = p->pSelect; |
| 233612 | 235392 | res = xConflict(pCtx, eType, pIter); |
| 233613 | 235393 | pIter->pConflict = 0; |
| 233614 | 235394 | } |
| 233615 | 235395 | rc = sqlite3_reset(p->pSelect); |
| @@ -233619,11 +235399,13 @@ | ||
| 233619 | 235399 | ** to the SessionApplyCtx.constraints buffer. */ |
| 233620 | 235400 | u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; |
| 233621 | 235401 | int nBlob = pIter->in.iNext - pIter->in.iCurrent; |
| 233622 | 235402 | sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); |
| 233623 | 235403 | return SQLITE_OK; |
| 233624 | - }else{ | |
| 235404 | + }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE | |
| 235405 | + || eType==SQLITE_CHANGESET_CONFLICT | |
| 235406 | + ){ | |
| 233625 | 235407 | /* No other row with the new.* primary key. */ |
| 233626 | 235408 | res = xConflict(pCtx, eType+1, pIter); |
| 233627 | 235409 | if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; |
| 233628 | 235410 | } |
| 233629 | 235411 | } |
| @@ -233717,11 +235499,11 @@ | ||
| 233717 | 235499 | } |
| 233718 | 235500 | if( rc!=SQLITE_OK ) return rc; |
| 233719 | 235501 | |
| 233720 | 235502 | sqlite3_step(p->pDelete); |
| 233721 | 235503 | rc = sqlite3_reset(p->pDelete); |
| 233722 | - if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ | |
| 235504 | + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ | |
| 233723 | 235505 | rc = sessionConflictHandler( |
| 233724 | 235506 | SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry |
| 233725 | 235507 | ); |
| 233726 | 235508 | }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ |
| 233727 | 235509 | rc = sessionConflictHandler( |
| @@ -234122,10 +235904,11 @@ | ||
| 234122 | 235904 | } |
| 234123 | 235905 | } |
| 234124 | 235906 | |
| 234125 | 235907 | assert( sApply.bRebase || sApply.rebase.nBuf==0 ); |
| 234126 | 235908 | if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){ |
| 235909 | + assert( ppRebase!=0 && pnRebase!=0 ); | |
| 234127 | 235910 | *ppRebase = (void*)sApply.rebase.aBuf; |
| 234128 | 235911 | *pnRebase = sApply.rebase.nBuf; |
| 234129 | 235912 | sApply.rebase.aBuf = 0; |
| 234130 | 235913 | } |
| 234131 | 235914 | sessionUpdateFree(&sApply); |
| @@ -234139,10 +235922,15 @@ | ||
| 234139 | 235922 | if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){ |
| 234140 | 235923 | assert( db->flags & SQLITE_FkNoAction ); |
| 234141 | 235924 | db->flags &= ~((u64)SQLITE_FkNoAction); |
| 234142 | 235925 | db->aDb[0].pSchema->schema_cookie -= 32; |
| 234143 | 235926 | } |
| 235927 | + | |
| 235928 | + assert( rc!=SQLITE_OK || sApply.zErr==0 ); | |
| 235929 | + sqlite3_set_errmsg(db, rc, sApply.zErr); | |
| 235930 | + sqlite3_free(sApply.zErr); | |
| 235931 | + | |
| 234144 | 235932 | sqlite3_mutex_leave(sqlite3_db_mutex(db)); |
| 234145 | 235933 | return rc; |
| 234146 | 235934 | } |
| 234147 | 235935 | |
| 234148 | 235936 | /* |
| @@ -236348,25 +238136,18 @@ | ||
| 236348 | 238136 | ** Constants for the largest and smallest possible 64-bit signed integers. |
| 236349 | 238137 | */ |
| 236350 | 238138 | # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) |
| 236351 | 238139 | # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) |
| 236352 | 238140 | |
| 236353 | -/* The uptr type is an unsigned integer large enough to hold a pointer | |
| 238141 | +/* | |
| 238142 | +** This macro is used in a single assert() within fts5 to check that an | |
| 238143 | +** allocation is aligned to an 8-byte boundary. But it is a complicated | |
| 238144 | +** macro to get right for multiple platforms without generating warnings. | |
| 238145 | +** So instead of reproducing the entire definition from sqliteInt.h, we | |
| 238146 | +** just do without this assert() for the rare non-amalgamation builds. | |
| 236354 | 238147 | */ |
| 236355 | -#if defined(HAVE_STDINT_H) | |
| 236356 | - typedef uintptr_t uptr; | |
| 236357 | -#elif SQLITE_PTRSIZE==4 | |
| 236358 | - typedef u32 uptr; | |
| 236359 | -#else | |
| 236360 | - typedef u64 uptr; | |
| 236361 | -#endif | |
| 236362 | - | |
| 236363 | -#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC | |
| 236364 | -# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) | |
| 236365 | -#else | |
| 236366 | -# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) | |
| 236367 | -#endif | |
| 238148 | +#define EIGHT_BYTE_ALIGNMENT(x) 1 | |
| 236368 | 238149 | |
| 236369 | 238150 | /* |
| 236370 | 238151 | ** Macros needed to provide flexible arrays in a portable way |
| 236371 | 238152 | */ |
| 236372 | 238153 | #ifndef offsetof |
| @@ -237110,11 +238891,11 @@ | ||
| 237110 | 238891 | ** ){ |
| 237111 | 238892 | ** // The document with rowid iRowid matches the expression! |
| 237112 | 238893 | ** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); |
| 237113 | 238894 | ** } |
| 237114 | 238895 | */ |
| 237115 | -static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); | |
| 238896 | +static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc); | |
| 237116 | 238897 | static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); |
| 237117 | 238898 | static int sqlite3Fts5ExprEof(Fts5Expr*); |
| 237118 | 238899 | static i64 sqlite3Fts5ExprRowid(Fts5Expr*); |
| 237119 | 238900 | |
| 237120 | 238901 | static void sqlite3Fts5ExprFree(Fts5Expr*); |
| @@ -242679,11 +244460,17 @@ | ||
| 242679 | 244460 | ** equal to iFirst. |
| 242680 | 244461 | ** |
| 242681 | 244462 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It |
| 242682 | 244463 | ** is not considered an error if the query does not match any documents. |
| 242683 | 244464 | */ |
| 242684 | -static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ | |
| 244465 | +static int sqlite3Fts5ExprFirst( | |
| 244466 | + Fts5Expr *p, | |
| 244467 | + Fts5Index *pIdx, | |
| 244468 | + i64 iFirst, | |
| 244469 | + i64 iLast, | |
| 244470 | + int bDesc | |
| 244471 | +){ | |
| 242685 | 244472 | Fts5ExprNode *pRoot = p->pRoot; |
| 242686 | 244473 | int rc; /* Return code */ |
| 242687 | 244474 | |
| 242688 | 244475 | p->pIndex = pIdx; |
| 242689 | 244476 | p->bDesc = bDesc; |
| @@ -242700,10 +244487,13 @@ | ||
| 242700 | 244487 | |
| 242701 | 244488 | /* If the iterator is not at a real match, skip forward until it is. */ |
| 242702 | 244489 | while( pRoot->bNomatch && rc==SQLITE_OK ){ |
| 242703 | 244490 | assert( pRoot->bEof==0 ); |
| 242704 | 244491 | rc = fts5ExprNodeNext(p, pRoot, 0, 0); |
| 244492 | + } | |
| 244493 | + if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ | |
| 244494 | + pRoot->bEof = 1; | |
| 242705 | 244495 | } |
| 242706 | 244496 | return rc; |
| 242707 | 244497 | } |
| 242708 | 244498 | |
| 242709 | 244499 | /* |
| @@ -245876,13 +247666,13 @@ | ||
| 245876 | 247666 | ** backing store corruption. */ |
| 245877 | 247667 | if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid); |
| 245878 | 247668 | |
| 245879 | 247669 | if( rc==SQLITE_OK ){ |
| 245880 | 247670 | u8 *aOut = 0; /* Read blob data into this buffer */ |
| 245881 | - int nByte = sqlite3_blob_bytes(p->pReader); | |
| 245882 | - int szData = (sizeof(Fts5Data) + 7) & ~7; | |
| 245883 | - sqlite3_int64 nAlloc = szData + nByte + FTS5_DATA_PADDING; | |
| 247671 | + i64 nByte = sqlite3_blob_bytes(p->pReader); | |
| 247672 | + i64 szData = (sizeof(Fts5Data) + 7) & ~7; | |
| 247673 | + i64 nAlloc = szData + nByte + FTS5_DATA_PADDING; | |
| 245884 | 247674 | pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); |
| 245885 | 247675 | if( pRet ){ |
| 245886 | 247676 | pRet->nn = nByte; |
| 245887 | 247677 | aOut = pRet->p = (u8*)pRet + szData; |
| 245888 | 247678 | }else{ |
| @@ -251821,15 +253611,18 @@ | ||
| 251821 | 253611 | ** function populates it with the initial structure objects for each index, |
| 251822 | 253612 | ** and the initial version of the "averages" record (a zero-byte blob). |
| 251823 | 253613 | */ |
| 251824 | 253614 | static int sqlite3Fts5IndexReinit(Fts5Index *p){ |
| 251825 | 253615 | Fts5Structure *pTmp; |
| 251826 | - u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; | |
| 253616 | + union { | |
| 253617 | + Fts5Structure sFts; | |
| 253618 | + u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; | |
| 253619 | + } uFts; | |
| 251827 | 253620 | fts5StructureInvalidate(p); |
| 251828 | 253621 | fts5IndexDiscardData(p); |
| 251829 | - pTmp = (Fts5Structure*)tmpSpace; | |
| 251830 | - memset(pTmp, 0, SZ_FTS5STRUCTURE(1)); | |
| 253622 | + pTmp = &uFts.sFts; | |
| 253623 | + memset(uFts.tmpSpace, 0, sizeof(uFts.tmpSpace)); | |
| 251831 | 253624 | if( p->pConfig->bContentlessDelete ){ |
| 251832 | 253625 | pTmp->nOriginCntr = 1; |
| 251833 | 253626 | } |
| 251834 | 253627 | fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); |
| 251835 | 253628 | fts5StructureWrite(p, pTmp); |
| @@ -255045,10 +256838,21 @@ | ||
| 255045 | 256838 | { |
| 255046 | 256839 | pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; |
| 255047 | 256840 | } |
| 255048 | 256841 | #endif |
| 255049 | 256842 | } |
| 256843 | + | |
| 256844 | +static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ | |
| 256845 | +#if SQLITE_VERSION_NUMBER>=3008002 | |
| 256846 | +#ifndef SQLITE_CORE | |
| 256847 | + if( sqlite3_libversion_number()>=3008002 ) | |
| 256848 | +#endif | |
| 256849 | + { | |
| 256850 | + pIdxInfo->estimatedRows = nRow; | |
| 256851 | + } | |
| 256852 | +#endif | |
| 256853 | +} | |
| 255050 | 256854 | |
| 255051 | 256855 | static int fts5UsePatternMatch( |
| 255052 | 256856 | Fts5Config *pConfig, |
| 255053 | 256857 | struct sqlite3_index_constraint *p |
| 255054 | 256858 | ){ |
| @@ -255181,11 +256985,11 @@ | ||
| 255181 | 256985 | bSeenRank = 1; |
| 255182 | 256986 | }else{ |
| 255183 | 256987 | nSeenMatch++; |
| 255184 | 256988 | idxStr[iIdxStr++] = 'M'; |
| 255185 | 256989 | sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); |
| 255186 | - idxStr += strlen(&idxStr[iIdxStr]); | |
| 256990 | + iIdxStr += (int)strlen(&idxStr[iIdxStr]); | |
| 255187 | 256991 | assert( idxStr[iIdxStr]=='\0' ); |
| 255188 | 256992 | } |
| 255189 | 256993 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 255190 | 256994 | pInfo->aConstraintUsage[i].omit = 1; |
| 255191 | 256995 | } |
| @@ -255200,10 +257004,11 @@ | ||
| 255200 | 257004 | nSeenMatch++; |
| 255201 | 257005 | }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ |
| 255202 | 257006 | idxStr[iIdxStr++] = '='; |
| 255203 | 257007 | bSeenEq = 1; |
| 255204 | 257008 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 257009 | + pInfo->aConstraintUsage[i].omit = 1; | |
| 255205 | 257010 | } |
| 255206 | 257011 | } |
| 255207 | 257012 | } |
| 255208 | 257013 | |
| 255209 | 257014 | if( bSeenEq==0 ){ |
| @@ -255247,21 +257052,25 @@ | ||
| 255247 | 257052 | } |
| 255248 | 257053 | } |
| 255249 | 257054 | |
| 255250 | 257055 | /* Calculate the estimated cost based on the flags set in idxFlags. */ |
| 255251 | 257056 | if( bSeenEq ){ |
| 255252 | - pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0; | |
| 255253 | - if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo); | |
| 255254 | - }else if( bSeenLt && bSeenGt ){ | |
| 255255 | - pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0; | |
| 255256 | - }else if( bSeenLt || bSeenGt ){ | |
| 255257 | - pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0; | |
| 257057 | + pInfo->estimatedCost = nSeenMatch ? 1000.0 : 25.0; | |
| 257058 | + fts5SetUniqueFlag(pInfo); | |
| 257059 | + fts5SetEstimatedRows(pInfo, 1); | |
| 255258 | 257060 | }else{ |
| 255259 | - pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0; | |
| 255260 | - } | |
| 255261 | - for(i=1; i<nSeenMatch; i++){ | |
| 255262 | - pInfo->estimatedCost *= 0.4; | |
| 257061 | + if( bSeenLt && bSeenGt ){ | |
| 257062 | + pInfo->estimatedCost = nSeenMatch ? 5000.0 : 750000.0; | |
| 257063 | + }else if( bSeenLt || bSeenGt ){ | |
| 257064 | + pInfo->estimatedCost = nSeenMatch ? 7500.0 : 2250000.0; | |
| 257065 | + }else{ | |
| 257066 | + pInfo->estimatedCost = nSeenMatch ? 10000.0 : 3000000.0; | |
| 257067 | + } | |
| 257068 | + for(i=1; i<nSeenMatch; i++){ | |
| 257069 | + pInfo->estimatedCost *= 0.4; | |
| 257070 | + } | |
| 257071 | + fts5SetEstimatedRows(pInfo, (i64)(pInfo->estimatedCost / 4.0)); | |
| 255263 | 257072 | } |
| 255264 | 257073 | |
| 255265 | 257074 | pInfo->idxNum = idxFlags; |
| 255266 | 257075 | return SQLITE_OK; |
| 255267 | 257076 | } |
| @@ -255456,11 +257265,13 @@ | ||
| 255456 | 257265 | if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){ |
| 255457 | 257266 | Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); |
| 255458 | 257267 | int bDesc = pCsr->bDesc; |
| 255459 | 257268 | i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); |
| 255460 | 257269 | |
| 255461 | - rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc); | |
| 257270 | + rc = sqlite3Fts5ExprFirst( | |
| 257271 | + pCsr->pExpr, pTab->p.pIndex, iRowid, pCsr->iLastRowid, bDesc | |
| 257272 | + ); | |
| 255462 | 257273 | if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ |
| 255463 | 257274 | *pbSkip = 1; |
| 255464 | 257275 | } |
| 255465 | 257276 | |
| 255466 | 257277 | CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK); |
| @@ -255628,11 +257439,13 @@ | ||
| 255628 | 257439 | } |
| 255629 | 257440 | |
| 255630 | 257441 | static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ |
| 255631 | 257442 | int rc; |
| 255632 | 257443 | Fts5Expr *pExpr = pCsr->pExpr; |
| 255633 | - rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc); | |
| 257444 | + rc = sqlite3Fts5ExprFirst( | |
| 257445 | + pExpr, pTab->p.pIndex, pCsr->iFirstRowid, pCsr->iLastRowid, bDesc | |
| 257446 | + ); | |
| 255634 | 257447 | if( sqlite3Fts5ExprEof(pExpr) ){ |
| 255635 | 257448 | CsrFlagSet(pCsr, FTS5CSR_EOF); |
| 255636 | 257449 | } |
| 255637 | 257450 | fts5CsrNewrow(pCsr); |
| 255638 | 257451 | return rc; |
| @@ -258113,11 +259926,11 @@ | ||
| 258113 | 259926 | int nArg, /* Number of args */ |
| 258114 | 259927 | sqlite3_value **apUnused /* Function arguments */ |
| 258115 | 259928 | ){ |
| 258116 | 259929 | assert( nArg==0 ); |
| 258117 | 259930 | UNUSED_PARAM2(nArg, apUnused); |
| 258118 | - sqlite3_result_text(pCtx, "fts5: 2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839", -1, SQLITE_TRANSIENT); | |
| 259931 | + sqlite3_result_text(pCtx, "fts5: 2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70", -1, SQLITE_TRANSIENT); | |
| 258119 | 259932 | } |
| 258120 | 259933 | |
| 258121 | 259934 | /* |
| 258122 | 259935 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 258123 | 259936 | ** |
| @@ -258136,13 +259949,13 @@ | ||
| 258136 | 259949 | sqlite3_context *pCtx, /* Function call context */ |
| 258137 | 259950 | int nArg, /* Number of args */ |
| 258138 | 259951 | sqlite3_value **apArg /* Function arguments */ |
| 258139 | 259952 | ){ |
| 258140 | 259953 | const char *zLocale = 0; |
| 258141 | - int nLocale = 0; | |
| 259954 | + i64 nLocale = 0; | |
| 258142 | 259955 | const char *zText = 0; |
| 258143 | - int nText = 0; | |
| 259956 | + i64 nText = 0; | |
| 258144 | 259957 | |
| 258145 | 259958 | assert( nArg==2 ); |
| 258146 | 259959 | UNUSED_PARAM(nArg); |
| 258147 | 259960 | |
| 258148 | 259961 | zLocale = (const char*)sqlite3_value_text(apArg[0]); |
| @@ -258155,14 +259968,14 @@ | ||
| 258155 | 259968 | sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT); |
| 258156 | 259969 | }else{ |
| 258157 | 259970 | Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); |
| 258158 | 259971 | u8 *pBlob = 0; |
| 258159 | 259972 | u8 *pCsr = 0; |
| 258160 | - int nBlob = 0; | |
| 259973 | + i64 nBlob = 0; | |
| 258161 | 259974 | |
| 258162 | 259975 | nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; |
| 258163 | - pBlob = (u8*)sqlite3_malloc(nBlob); | |
| 259976 | + pBlob = (u8*)sqlite3_malloc64(nBlob); | |
| 258164 | 259977 | if( pBlob==0 ){ |
| 258165 | 259978 | sqlite3_result_error_nomem(pCtx); |
| 258166 | 259979 | return; |
| 258167 | 259980 | } |
| 258168 | 259981 | |
| 258169 | 259982 |
| --- 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 | ** 9f184f8dfa5ef6d57e10376adc30e0060ced with changes in files: |
| 22 | ** |
| 23 | ** |
| 24 | */ |
| 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | #define SQLITE_CORE 1 |
| @@ -168,11 +168,13 @@ | |
| 168 | #define SQLITE_OMIT_LOAD_EXTENSION 1 |
| 169 | #define SQLITE_ENABLE_LOCKING_STYLE 0 |
| 170 | #define HAVE_UTIME 1 |
| 171 | #else |
| 172 | /* This is not VxWorks. */ |
| 173 | #define OS_VXWORKS 0 |
| 174 | #define HAVE_FCHOWN 1 |
| 175 | #define HAVE_READLINK 1 |
| 176 | #define HAVE_LSTAT 1 |
| 177 | #endif /* defined(_WRS_KERNEL) */ |
| 178 | |
| @@ -465,11 +467,14 @@ | |
| 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | */ |
| 468 | #define SQLITE_VERSION "3.51.0" |
| 469 | #define SQLITE_VERSION_NUMBER 3051000 |
| 470 | #define SQLITE_SOURCE_ID "2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -814,10 +819,13 @@ | |
| 814 | ** [sqlite3_extended_errcode()]. |
| 815 | */ |
| 816 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 817 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 818 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 819 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 820 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 821 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 822 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 823 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -848,10 +856,12 @@ | |
| 848 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 849 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 850 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 851 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 852 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 853 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 854 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 855 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 856 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 857 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2652,21 +2662,24 @@ | |
| 2652 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2653 | ** databases.)^ </dd> |
| 2654 | ** |
| 2655 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2656 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2657 | ** <dd> ^This option is used to enable or disable the |
| 2658 | ** [fts3_tokenizer()] function which is part of the |
| 2659 | ** [FTS3] full-text search engine extension. |
| 2660 | ** There must be two additional arguments. |
| 2661 | ** The first argument is an integer which is 0 to disable fts3_tokenizer() or |
| 2662 | ** positive to enable fts3_tokenizer() or negative to leave the setting |
| 2663 | ** unchanged. |
| 2664 | ** The second parameter is a pointer to an integer into which |
| 2665 | ** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled |
| 2666 | ** following this call. The second parameter may be a NULL pointer, in |
| 2667 | ** which case the new setting is not reported back. </dd> |
| 2668 | ** |
| 2669 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2670 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2671 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2672 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4512,10 +4525,38 @@ | |
| 4512 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4513 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4514 | SQLITE_API const char *sqlite3_errstr(int); |
| 4515 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4516 | |
| 4517 | /* |
| 4518 | ** CAPI3REF: Prepared Statement Object |
| 4519 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4520 | ** |
| 4521 | ** An instance of this object represents a single SQL statement that |
| @@ -6522,10 +6563,11 @@ | |
| 6522 | ** to be attached to [database connection] D using name N. Subsequent |
| 6523 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6524 | ** or a NULL pointer if there were no prior calls to |
| 6525 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6526 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6527 | ** |
| 6528 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6529 | ** argument P on the first of the following occurrences: |
| 6530 | ** <ul> |
| 6531 | ** <li> An out-of-memory error occurs during the call to |
| @@ -9197,14 +9239,23 @@ | |
| 9197 | ** the resetFlg is true, then the highest instantaneous value is |
| 9198 | ** reset back down to the current value. |
| 9199 | ** |
| 9200 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 9201 | ** non-zero [error code] on failure. |
| 9202 | ** |
| 9203 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 9204 | */ |
| 9205 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 9206 | |
| 9207 | /* |
| 9208 | ** CAPI3REF: Status Parameters for database connections |
| 9209 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 9210 | ** |
| @@ -9297,10 +9348,14 @@ | |
| 9297 | ** database file in rollback mode databases. Any pages written as part of |
| 9298 | ** transaction rollback or database recovery operations are not included. |
| 9299 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 9300 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 9301 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 9302 | ** </dd> |
| 9303 | ** |
| 9304 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 9305 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 9306 | ** been written to disk in the middle of a transaction due to the page |
| @@ -9312,10 +9367,22 @@ | |
| 9312 | ** |
| 9313 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 9314 | ** <dd>This parameter returns zero for the current value if and only if |
| 9315 | ** all foreign key constraints (deferred or immediate) have been |
| 9316 | ** resolved.)^ ^The highwater mark is always 0. |
| 9317 | ** </dd> |
| 9318 | ** </dl> |
| 9319 | */ |
| 9320 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9321 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9328,11 +9395,12 @@ | |
| 9328 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9329 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9330 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9331 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9332 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9333 | #define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ |
| 9334 | |
| 9335 | |
| 9336 | /* |
| 9337 | ** CAPI3REF: Prepared Statement Status |
| 9338 | ** METHOD: sqlite3_stmt |
| @@ -10093,25 +10161,38 @@ | |
| 10093 | ** ^The third parameter is the name of the database that was written to - |
| 10094 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 10095 | ** is the number of pages currently in the write-ahead log file, |
| 10096 | ** including those that were just committed. |
| 10097 | ** |
| 10098 | ** The callback function should normally return [SQLITE_OK]. ^If an error |
| 10099 | ** code is returned, that error will propagate back up through the |
| 10100 | ** SQLite code base to cause the statement that provoked the callback |
| 10101 | ** to report an error, though the commit will have still occurred. If the |
| 10102 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 10103 | ** that does not correspond to any valid SQLite error code, the results |
| 10104 | ** are undefined. |
| 10105 | ** |
| 10106 | ** A single database handle may have at most a single write-ahead log callback |
| 10107 | ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any |
| 10108 | ** previously registered write-ahead log callback. ^The return value is |
| 10109 | ** a copy of the third parameter from the previous call, if any, or 0. |
| 10110 | ** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the |
| 10111 | ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will |
| 10112 | ** overwrite any prior [sqlite3_wal_hook()] settings. |
| 10113 | */ |
| 10114 | SQLITE_API void *sqlite3_wal_hook( |
| 10115 | sqlite3*, |
| 10116 | int(*)(void *,sqlite3*,const char*,int), |
| 10117 | void* |
| @@ -10124,11 +10205,11 @@ | |
| 10124 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 10125 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 10126 | ** to automatically [checkpoint] |
| 10127 | ** after committing a transaction if there are N or |
| 10128 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 10129 | ** a negative value as the nFrame parameter disables automatic |
| 10130 | ** checkpoints entirely. |
| 10131 | ** |
| 10132 | ** ^The callback registered by this function replaces any existing callback |
| 10133 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 10134 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -10140,13 +10221,14 @@ | |
| 10140 | ** ^Checkpoints initiated by this mechanism are |
| 10141 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 10142 | ** |
| 10143 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 10144 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 10145 | ** pages. The use of this interface |
| 10146 | ** is only necessary if the default setting is found to be suboptimal |
| 10147 | ** for a particular application. |
| 10148 | */ |
| 10149 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 10150 | |
| 10151 | /* |
| 10152 | ** CAPI3REF: Checkpoint a database |
| @@ -10207,10 +10289,15 @@ | |
| 10207 | ** |
| 10208 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 10209 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 10210 | ** addition that it also truncates the log file to zero bytes just prior |
| 10211 | ** to a successful return. |
| 10212 | ** </dl> |
| 10213 | ** |
| 10214 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 10215 | ** the log file or to -1 if the checkpoint could not run because |
| 10216 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -10277,10 +10364,11 @@ | |
| 10277 | ** These constants define all valid values for the "checkpoint mode" passed |
| 10278 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 10279 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 10280 | ** meaning of each of these checkpoint modes. |
| 10281 | */ |
| 10282 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 10283 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 10284 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 10285 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 10286 | |
| @@ -11104,11 +11192,11 @@ | |
| 11104 | ** to avoid a memory leak. |
| 11105 | ** |
| 11106 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 11107 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11108 | */ |
| 11109 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( |
| 11110 | sqlite3 *db, |
| 11111 | const char *zSchema, |
| 11112 | sqlite3_snapshot **ppSnapshot |
| 11113 | ); |
| 11114 | |
| @@ -11153,11 +11241,11 @@ | |
| 11153 | ** database connection in order to make it ready to use snapshots.) |
| 11154 | ** |
| 11155 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 11156 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11157 | */ |
| 11158 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( |
| 11159 | sqlite3 *db, |
| 11160 | const char *zSchema, |
| 11161 | sqlite3_snapshot *pSnapshot |
| 11162 | ); |
| 11163 | |
| @@ -11170,11 +11258,11 @@ | |
| 11170 | ** using this routine to avoid a memory leak. |
| 11171 | ** |
| 11172 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 11173 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11174 | */ |
| 11175 | SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); |
| 11176 | |
| 11177 | /* |
| 11178 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 11179 | ** METHOD: sqlite3_snapshot |
| 11180 | ** |
| @@ -11197,11 +11285,11 @@ | |
| 11197 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 11198 | ** |
| 11199 | ** This interface is only available if SQLite is compiled with the |
| 11200 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11201 | */ |
| 11202 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( |
| 11203 | sqlite3_snapshot *p1, |
| 11204 | sqlite3_snapshot *p2 |
| 11205 | ); |
| 11206 | |
| 11207 | /* |
| @@ -11225,11 +11313,11 @@ | |
| 11225 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 11226 | ** |
| 11227 | ** This interface is only available if SQLite is compiled with the |
| 11228 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11229 | */ |
| 11230 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); |
| 11231 | |
| 11232 | /* |
| 11233 | ** CAPI3REF: Serialize a database |
| 11234 | ** |
| 11235 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -11299,16 +11387,17 @@ | |
| 11299 | /* |
| 11300 | ** CAPI3REF: Deserialize a database |
| 11301 | ** |
| 11302 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 11303 | ** [database connection] D to disconnect from database S and then |
| 11304 | ** reopen S as an in-memory database based on the serialization contained |
| 11305 | ** in P. The serialized database P is N bytes in size. M is the size of |
| 11306 | ** the buffer P, which might be larger than N. If M is larger than N, and |
| 11307 | ** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is |
| 11308 | ** permitted to add content to the in-memory database as long as the total |
| 11309 | ** size does not exceed M bytes. |
| 11310 | ** |
| 11311 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 11312 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 11313 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 11314 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11371,10 +11460,56 @@ | |
| 11371 | */ |
| 11372 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11373 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11374 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11375 | |
| 11376 | /* |
| 11377 | ** Undo the hack that converts floating point types to integer for |
| 11378 | ** builds on processors without floating point support. |
| 11379 | */ |
| 11380 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12629,10 +12764,19 @@ | |
| 12629 | ** CAPI3REF: Apply A Changeset To A Database |
| 12630 | ** |
| 12631 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12632 | ** update the "main" database attached to handle db with the changes found in |
| 12633 | ** the changeset passed via the second and third arguments. |
| 12634 | ** |
| 12635 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12636 | ** callback". This may be passed NULL, in which case all changes in the |
| 12637 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12638 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12767,16 +12911,10 @@ | |
| 12767 | ** It is safe to execute SQL statements, including those that write to the |
| 12768 | ** table that the callback related to, from within the xConflict callback. |
| 12769 | ** This can be used to further customize the application's conflict |
| 12770 | ** resolution strategy. |
| 12771 | ** |
| 12772 | ** All changes made by these functions are enclosed in a savepoint transaction. |
| 12773 | ** If any other error (aside from a constraint failure when attempting to |
| 12774 | ** write to the target database) occurs, then the savepoint transaction is |
| 12775 | ** rolled back, restoring the target database to its original state, and an |
| 12776 | ** SQLite error code returned. |
| 12777 | ** |
| 12778 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12779 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12780 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12781 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12782 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| @@ -14351,11 +14489,11 @@ | |
| 14351 | |
| 14352 | /* |
| 14353 | ** Maximum number of pages in one database file. |
| 14354 | ** |
| 14355 | ** This is really just the default value for the max_page_count pragma. |
| 14356 | ** This value can be lowered (or raised) at run-time using that the |
| 14357 | ** max_page_count macro. |
| 14358 | */ |
| 14359 | #ifndef SQLITE_MAX_PAGE_COUNT |
| 14360 | # define SQLITE_MAX_PAGE_COUNT 0xfffffffe /* 4294967294 */ |
| 14361 | #endif |
| @@ -15947,12 +16085,12 @@ | |
| 15947 | ** |
| 15948 | ** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application |
| 15949 | ** must provide its own VFS implementation together with sqlite3_os_init() |
| 15950 | ** and sqlite3_os_end() routines. |
| 15951 | */ |
| 15952 | #if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \ |
| 15953 | !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN) |
| 15954 | # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ |
| 15955 | defined(__MINGW32__) || defined(__BORLANDC__) |
| 15956 | # define SQLITE_OS_WIN 1 |
| 15957 | # define SQLITE_OS_UNIX 0 |
| 15958 | # else |
| @@ -17450,10 +17588,13 @@ | |
| 17450 | #ifndef SQLITE_OMIT_TRACE |
| 17451 | SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); |
| 17452 | #endif |
| 17453 | SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); |
| 17454 | SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); |
| 17455 | |
| 17456 | SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*); |
| 17457 | SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); |
| 17458 | SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); |
| 17459 | SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); |
| @@ -18122,11 +18263,11 @@ | |
| 18122 | struct sqlite3InitInfo { /* Information used during initialization */ |
| 18123 | Pgno newTnum; /* Rootpage of table being initialized */ |
| 18124 | u8 iDb; /* Which db file is being initialized */ |
| 18125 | u8 busy; /* TRUE if currently initializing */ |
| 18126 | unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ |
| 18127 | unsigned imposterTable : 1; /* Building an imposter table */ |
| 18128 | unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ |
| 18129 | const char **azInit; /* "type", "name", and "tbl_name" columns */ |
| 18130 | } init; |
| 18131 | int nVdbeActive; /* Number of VDBEs currently running */ |
| 18132 | int nVdbeRead; /* Number of active VDBEs that read or write */ |
| @@ -18205,10 +18346,11 @@ | |
| 18205 | int nStatement; /* Number of nested statement-transactions */ |
| 18206 | i64 nDeferredCons; /* Net deferred constraints this transaction. */ |
| 18207 | i64 nDeferredImmCons; /* Net deferred immediate constraints */ |
| 18208 | int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ |
| 18209 | DbClientData *pDbData; /* sqlite3_set_clientdata() content */ |
| 18210 | #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY |
| 18211 | /* The following variables are all protected by the STATIC_MAIN |
| 18212 | ** mutex, not by sqlite3.mutex. They are used by code in notify.c. |
| 18213 | ** |
| 18214 | ** When X.pUnlockConnection==Y, that means that X is waiting for Y to |
| @@ -18915,10 +19057,11 @@ | |
| 18915 | #define TF_Shadow 0x00001000 /* True for a shadow table */ |
| 18916 | #define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ |
| 18917 | #define TF_Ephemeral 0x00004000 /* An ephemeral table */ |
| 18918 | #define TF_Eponymous 0x00008000 /* An eponymous virtual table */ |
| 18919 | #define TF_Strict 0x00010000 /* STRICT mode */ |
| 18920 | |
| 18921 | /* |
| 18922 | ** Allowed values for Table.eTabType |
| 18923 | */ |
| 18924 | #define TABTYP_NORM 0 /* Ordinary table */ |
| @@ -19503,10 +19646,11 @@ | |
| 19503 | AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ |
| 19504 | union { |
| 19505 | Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL |
| 19506 | ** for a column of an index on an expression */ |
| 19507 | Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */ |
| 19508 | struct { /* TK_IN, TK_SELECT, and TK_EXISTS */ |
| 19509 | int iAddr; /* Subroutine entry address */ |
| 19510 | int regReturn; /* Register used to hold return address */ |
| 19511 | } sub; |
| 19512 | } y; |
| @@ -20080,10 +20224,11 @@ | |
| 20080 | #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ |
| 20081 | #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ |
| 20082 | #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ |
| 20083 | #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ |
| 20084 | #define SF_Correlated 0x20000000 /* True if references the outer context */ |
| 20085 | |
| 20086 | /* True if SrcItem X is a subquery that has SF_NestedFrom */ |
| 20087 | #define IsNestedFrom(X) \ |
| 20088 | ((X)->fg.isSubquery && \ |
| 20089 | ((X)->u4.pSubq->pSelect->selFlags&SF_NestedFrom)!=0) |
| @@ -20833,10 +20978,11 @@ | |
| 20833 | struct Table *pTab; /* Table of generated column */ |
| 20834 | struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ |
| 20835 | SrcItem *pSrcItem; /* A single FROM clause item */ |
| 20836 | DbFixer *pFix; /* See sqlite3FixSelect() */ |
| 20837 | Mem *aMem; /* See sqlite3BtreeCursorHint() */ |
| 20838 | } u; |
| 20839 | }; |
| 20840 | |
| 20841 | /* |
| 20842 | ** The following structure contains information used by the sqliteFix... |
| @@ -21540,10 +21686,11 @@ | |
| 21540 | SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); |
| 21541 | #endif |
| 21542 | SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); |
| 21543 | SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); |
| 21544 | SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); |
| 21545 | SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); |
| 21546 | SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); |
| 21547 | SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); |
| 21548 | #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ |
| 21549 | #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ |
| @@ -21636,16 +21783,20 @@ | |
| 21636 | SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); |
| 21637 | SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); |
| 21638 | SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); |
| 21639 | SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); |
| 21640 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 21641 | SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*); |
| 21642 | #endif |
| 21643 | SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); |
| 21644 | SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); |
| 21645 | SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); |
| 21646 | SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); |
| 21647 | |
| 21648 | #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) |
| 21649 | SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); |
| 21650 | #endif |
| 21651 | |
| @@ -22628,10 +22779,13 @@ | |
| 22628 | #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE |
| 22629 | "ENABLE_BATCH_ATOMIC_WRITE", |
| 22630 | #endif |
| 22631 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 22632 | "ENABLE_BYTECODE_VTAB", |
| 22633 | #endif |
| 22634 | #ifdef SQLITE_ENABLE_CEROD |
| 22635 | "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), |
| 22636 | #endif |
| 22637 | #ifdef SQLITE_ENABLE_COLUMN_METADATA |
| @@ -22718,10 +22872,13 @@ | |
| 22718 | #ifdef SQLITE_ENABLE_ORDERED_SET_AGGREGATES |
| 22719 | "ENABLE_ORDERED_SET_AGGREGATES", |
| 22720 | #endif |
| 22721 | #ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK |
| 22722 | "ENABLE_OVERSIZE_CELL_CHECK", |
| 22723 | #endif |
| 22724 | #ifdef SQLITE_ENABLE_PREUPDATE_HOOK |
| 22725 | "ENABLE_PREUPDATE_HOOK", |
| 22726 | #endif |
| 22727 | #ifdef SQLITE_ENABLE_QPSG |
| @@ -24202,11 +24359,14 @@ | |
| 24202 | Mem oldipk; /* Memory cell holding "old" IPK value */ |
| 24203 | Mem *aNew; /* Array of new.* values */ |
| 24204 | Table *pTab; /* Schema object being updated */ |
| 24205 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 24206 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 24207 | u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ |
| 24208 | }; |
| 24209 | |
| 24210 | /* |
| 24211 | ** An instance of this object is used to pass an vector of values into |
| 24212 | ** OP_VFilter, the xFilter method of a virtual table. The vector is the |
| @@ -24366,13 +24526,15 @@ | |
| 24366 | SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*); |
| 24367 | SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); |
| 24368 | #endif |
| 24369 | |
| 24370 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 24371 | SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); |
| 24372 | #else |
| 24373 | # define sqlite3VdbeCheckFk(p,i) 0 |
| 24374 | #endif |
| 24375 | |
| 24376 | #ifdef SQLITE_DEBUG |
| 24377 | SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); |
| 24378 | SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr); |
| @@ -24577,27 +24739,29 @@ | |
| 24577 | } |
| 24578 | |
| 24579 | /* |
| 24580 | ** Query status information for a single database connection |
| 24581 | */ |
| 24582 | SQLITE_API int sqlite3_db_status( |
| 24583 | sqlite3 *db, /* The database connection whose status is desired */ |
| 24584 | int op, /* Status verb */ |
| 24585 | int *pCurrent, /* Write current value here */ |
| 24586 | int *pHighwater, /* Write high-water mark here */ |
| 24587 | int resetFlag /* Reset high-water mark if true */ |
| 24588 | ){ |
| 24589 | int rc = SQLITE_OK; /* Return code */ |
| 24590 | #ifdef SQLITE_ENABLE_API_ARMOR |
| 24591 | if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ |
| 24592 | return SQLITE_MISUSE_BKPT; |
| 24593 | } |
| 24594 | #endif |
| 24595 | sqlite3_mutex_enter(db->mutex); |
| 24596 | switch( op ){ |
| 24597 | case SQLITE_DBSTATUS_LOOKASIDE_USED: { |
| 24598 | *pCurrent = sqlite3LookasideUsed(db, pHighwater); |
| 24599 | if( resetFlag ){ |
| 24600 | LookasideSlot *p = db->lookaside.pFree; |
| 24601 | if( p ){ |
| 24602 | while( p->pNext ) p = p->pNext; |
| 24603 | p->pNext = db->lookaside.pInit; |
| @@ -24624,11 +24788,11 @@ | |
| 24624 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE ); |
| 24625 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL ); |
| 24626 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); |
| 24627 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); |
| 24628 | *pCurrent = 0; |
| 24629 | *pHighwater = (int)db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; |
| 24630 | if( resetFlag ){ |
| 24631 | db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; |
| 24632 | } |
| 24633 | break; |
| 24634 | } |
| @@ -24638,11 +24802,11 @@ | |
| 24638 | ** by all pagers associated with the given database connection. The |
| 24639 | ** highwater mark is meaningless and is returned as zero. |
| 24640 | */ |
| 24641 | case SQLITE_DBSTATUS_CACHE_USED_SHARED: |
| 24642 | case SQLITE_DBSTATUS_CACHE_USED: { |
| 24643 | int totalUsed = 0; |
| 24644 | int i; |
| 24645 | sqlite3BtreeEnterAll(db); |
| 24646 | for(i=0; i<db->nDb; i++){ |
| 24647 | Btree *pBt = db->aDb[i].pBt; |
| 24648 | if( pBt ){ |
| @@ -24654,22 +24818,22 @@ | |
| 24654 | totalUsed += nByte; |
| 24655 | } |
| 24656 | } |
| 24657 | sqlite3BtreeLeaveAll(db); |
| 24658 | *pCurrent = totalUsed; |
| 24659 | *pHighwater = 0; |
| 24660 | break; |
| 24661 | } |
| 24662 | |
| 24663 | /* |
| 24664 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24665 | ** to store the schema for all databases (main, temp, and any ATTACHed |
| 24666 | ** databases. *pHighwater is set to zero. |
| 24667 | */ |
| 24668 | case SQLITE_DBSTATUS_SCHEMA_USED: { |
| 24669 | int i; /* Used to iterate through schemas */ |
| 24670 | int nByte = 0; /* Used to accumulate return value */ |
| 24671 | |
| 24672 | sqlite3BtreeEnterAll(db); |
| 24673 | db->pnBytesFreed = &nByte; |
| 24674 | assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); |
| 24675 | db->lookaside.pEnd = db->lookaside.pStart; |
| @@ -24699,19 +24863,19 @@ | |
| 24699 | } |
| 24700 | db->pnBytesFreed = 0; |
| 24701 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24702 | sqlite3BtreeLeaveAll(db); |
| 24703 | |
| 24704 | *pHighwater = 0; |
| 24705 | *pCurrent = nByte; |
| 24706 | break; |
| 24707 | } |
| 24708 | |
| 24709 | /* |
| 24710 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24711 | ** to store all prepared statements. |
| 24712 | ** *pHighwater is set to zero. |
| 24713 | */ |
| 24714 | case SQLITE_DBSTATUS_STMT_USED: { |
| 24715 | struct Vdbe *pVdbe; /* Used to iterate through VMs */ |
| 24716 | int nByte = 0; /* Used to accumulate return value */ |
| 24717 | |
| @@ -24722,19 +24886,19 @@ | |
| 24722 | sqlite3VdbeDelete(pVdbe); |
| 24723 | } |
| 24724 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24725 | db->pnBytesFreed = 0; |
| 24726 | |
| 24727 | *pHighwater = 0; /* IMP: R-64479-57858 */ |
| 24728 | *pCurrent = nByte; |
| 24729 | |
| 24730 | break; |
| 24731 | } |
| 24732 | |
| 24733 | /* |
| 24734 | ** Set *pCurrent to the total cache hits or misses encountered by all |
| 24735 | ** pagers the database handle is connected to. *pHighwater is always set |
| 24736 | ** to zero. |
| 24737 | */ |
| 24738 | case SQLITE_DBSTATUS_CACHE_SPILL: |
| 24739 | op = SQLITE_DBSTATUS_CACHE_WRITE+1; |
| 24740 | /* no break */ deliberate_fall_through |
| @@ -24750,23 +24914,43 @@ | |
| 24750 | if( db->aDb[i].pBt ){ |
| 24751 | Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); |
| 24752 | sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); |
| 24753 | } |
| 24754 | } |
| 24755 | *pHighwater = 0; /* IMP: R-42420-56072 */ |
| 24756 | /* IMP: R-54100-20147 */ |
| 24757 | /* IMP: R-29431-39229 */ |
| 24758 | *pCurrent = (int)nRet & 0x7fffffff; |
| 24759 | break; |
| 24760 | } |
| 24761 | |
| 24762 | /* Set *pCurrent to non-zero if there are unresolved deferred foreign |
| 24763 | ** key constraints. Set *pCurrent to zero if all foreign key constraints |
| 24764 | ** have been satisfied. The *pHighwater is always set to zero. |
| 24765 | */ |
| 24766 | case SQLITE_DBSTATUS_DEFERRED_FKS: { |
| 24767 | *pHighwater = 0; /* IMP: R-11967-56545 */ |
| 24768 | *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; |
| 24769 | break; |
| 24770 | } |
| 24771 | |
| 24772 | default: { |
| @@ -24774,10 +24958,35 @@ | |
| 24774 | } |
| 24775 | } |
| 24776 | sqlite3_mutex_leave(db->mutex); |
| 24777 | return rc; |
| 24778 | } |
| 24779 | |
| 24780 | /************** End of status.c **********************************************/ |
| 24781 | /************** Begin file date.c ********************************************/ |
| 24782 | /* |
| 24783 | ** 2003 October 31 |
| @@ -24967,10 +25176,14 @@ | |
| 24967 | if( getDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){ |
| 24968 | return 1; |
| 24969 | } |
| 24970 | zDate += 5; |
| 24971 | p->tz = sgn*(nMn + nHr*60); |
| 24972 | zulu_time: |
| 24973 | while( sqlite3Isspace(*zDate) ){ zDate++; } |
| 24974 | return *zDate!=0; |
| 24975 | } |
| 24976 | |
| @@ -26162,12 +26375,12 @@ | |
| 26162 | ** %j day of year 001-366 |
| 26163 | ** %J ** julian day number |
| 26164 | ** %l hour 1-12 (leading zero converted to space) |
| 26165 | ** %m month 01-12 |
| 26166 | ** %M minute 00-59 |
| 26167 | ** %p "am" or "pm" |
| 26168 | ** %P "AM" or "PM" |
| 26169 | ** %R time as HH:MM |
| 26170 | ** %s seconds since 1970-01-01 |
| 26171 | ** %S seconds 00-59 |
| 26172 | ** %T time as HH:MM:SS |
| 26173 | ** %u day of week 1-7 Monday==1, Sunday==7 |
| @@ -31770,56 +31983,74 @@ | |
| 31770 | etByte base; /* The base for radix conversion */ |
| 31771 | etByte flags; /* One or more of FLAG_ constants below */ |
| 31772 | etByte type; /* Conversion paradigm */ |
| 31773 | etByte charset; /* Offset into aDigits[] of the digits string */ |
| 31774 | etByte prefix; /* Offset into aPrefix[] of the prefix string */ |
| 31775 | } et_info; |
| 31776 | |
| 31777 | /* |
| 31778 | ** Allowed values for et_info.flags |
| 31779 | */ |
| 31780 | #define FLAG_SIGNED 1 /* True if the value to convert is signed */ |
| 31781 | #define FLAG_STRING 4 /* Allow infinite precision */ |
| 31782 | |
| 31783 | |
| 31784 | /* |
| 31785 | ** The following table is searched linearly, so it is good to put the |
| 31786 | ** most frequently used conversion types first. |
| 31787 | */ |
| 31788 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 31789 | static const char aPrefix[] = "-x0\000X0"; |
| 31790 | static const et_info fmtinfo[] = { |
| 31791 | { 'd', 10, 1, etDECIMAL, 0, 0 }, |
| 31792 | { 's', 0, 4, etSTRING, 0, 0 }, |
| 31793 | { 'g', 0, 1, etGENERIC, 30, 0 }, |
| 31794 | { 'z', 0, 4, etDYNSTRING, 0, 0 }, |
| 31795 | { 'q', 0, 4, etESCAPE_q, 0, 0 }, |
| 31796 | { 'Q', 0, 4, etESCAPE_Q, 0, 0 }, |
| 31797 | { 'w', 0, 4, etESCAPE_w, 0, 0 }, |
| 31798 | { 'c', 0, 0, etCHARX, 0, 0 }, |
| 31799 | { 'o', 8, 0, etRADIX, 0, 2 }, |
| 31800 | { 'u', 10, 0, etDECIMAL, 0, 0 }, |
| 31801 | { 'x', 16, 0, etRADIX, 16, 1 }, |
| 31802 | { 'X', 16, 0, etRADIX, 0, 4 }, |
| 31803 | #ifndef SQLITE_OMIT_FLOATING_POINT |
| 31804 | { 'f', 0, 1, etFLOAT, 0, 0 }, |
| 31805 | { 'e', 0, 1, etEXP, 30, 0 }, |
| 31806 | { 'E', 0, 1, etEXP, 14, 0 }, |
| 31807 | { 'G', 0, 1, etGENERIC, 14, 0 }, |
| 31808 | #endif |
| 31809 | { 'i', 10, 1, etDECIMAL, 0, 0 }, |
| 31810 | { 'n', 0, 0, etSIZE, 0, 0 }, |
| 31811 | { '%', 0, 0, etPERCENT, 0, 0 }, |
| 31812 | { 'p', 16, 0, etPOINTER, 0, 1 }, |
| 31813 | |
| 31814 | /* All the rest are undocumented and are for internal use only */ |
| 31815 | { 'T', 0, 0, etTOKEN, 0, 0 }, |
| 31816 | { 'S', 0, 0, etSRCITEM, 0, 0 }, |
| 31817 | { 'r', 10, 1, etORDINAL, 0, 0 }, |
| 31818 | }; |
| 31819 | |
| 31820 | /* Notes: |
| 31821 | ** |
| 31822 | ** %S Takes a pointer to SrcItem. Shows name or database.name |
| 31823 | ** %!S Like %S but prefer the zName over the zAlias |
| 31824 | */ |
| 31825 | |
| @@ -31942,11 +32173,14 @@ | |
| 31942 | if( c!='%' ){ |
| 31943 | bufpt = (char *)fmt; |
| 31944 | #if HAVE_STRCHRNUL |
| 31945 | fmt = strchrnul(fmt, '%'); |
| 31946 | #else |
| 31947 | do{ fmt++; }while( *fmt && *fmt != '%' ); |
| 31948 | #endif |
| 31949 | sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); |
| 31950 | if( *fmt==0 ) break; |
| 31951 | } |
| 31952 | if( (c=(*++fmt))==0 ){ |
| @@ -32056,19 +32290,36 @@ | |
| 32056 | } |
| 32057 | } |
| 32058 | }while( !done && (c=(*++fmt))!=0 ); |
| 32059 | |
| 32060 | /* Fetch the info entry for the field */ |
| 32061 | infop = &fmtinfo[0]; |
| 32062 | xtype = etINVALID; |
| 32063 | for(idx=0; idx<ArraySize(fmtinfo); idx++){ |
| 32064 | if( c==fmtinfo[idx].fmttype ){ |
| 32065 | infop = &fmtinfo[idx]; |
| 32066 | xtype = infop->type; |
| 32067 | break; |
| 32068 | } |
| 32069 | } |
| 32070 | |
| 32071 | /* |
| 32072 | ** At this point, variables are initialized as follows: |
| 32073 | ** |
| 32074 | ** flag_alternateform TRUE if a '#' is present. |
| @@ -32252,11 +32503,25 @@ | |
| 32252 | length = sqlite3Strlen30(bufpt); |
| 32253 | break; |
| 32254 | } |
| 32255 | } |
| 32256 | if( s.sign=='-' ){ |
| 32257 | prefix = '-'; |
| 32258 | }else{ |
| 32259 | prefix = flag_prefix; |
| 32260 | } |
| 32261 | |
| 32262 | exp = s.iDP-1; |
| @@ -33463,13 +33728,17 @@ | |
| 33463 | sqlite3StrAccumFinish(&x); |
| 33464 | sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1); |
| 33465 | n = 0; |
| 33466 | if( pItem->fg.isSubquery ) n++; |
| 33467 | if( pItem->fg.isTabFunc ) n++; |
| 33468 | if( pItem->fg.isUsing ) n++; |
| 33469 | if( pItem->fg.isUsing ){ |
| 33470 | sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); |
| 33471 | } |
| 33472 | if( pItem->fg.isSubquery ){ |
| 33473 | assert( n==1 ); |
| 33474 | if( pItem->pSTab ){ |
| 33475 | Table *pTab = pItem->pSTab; |
| @@ -38108,11 +38377,11 @@ | |
| 38108 | static int kvstorageDelete(const char*, const char *zKey); |
| 38109 | static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); |
| 38110 | #define KVSTORAGE_KEY_SZ 32 |
| 38111 | |
| 38112 | /* Expand the key name with an appropriate prefix and put the result |
| 38113 | ** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least |
| 38114 | ** KVSTORAGE_KEY_SZ bytes. |
| 38115 | */ |
| 38116 | static void kvstorageMakeKey( |
| 38117 | const char *zClass, |
| 38118 | const char *zKeyIn, |
| @@ -38167,14 +38436,16 @@ | |
| 38167 | ** enough to hold it all. The value put into zBuf must always be zero |
| 38168 | ** terminated, even if it gets truncated because nBuf is not large enough. |
| 38169 | ** |
| 38170 | ** Return the total number of bytes in the data, without truncation, and |
| 38171 | ** not counting the final zero terminator. Return -1 if the key does |
| 38172 | ** not exist. |
| 38173 | ** |
| 38174 | ** If nBuf<=0 then this routine simply returns the size of the data without |
| 38175 | ** actually reading it. |
| 38176 | */ |
| 38177 | static int kvstorageRead( |
| 38178 | const char *zClass, |
| 38179 | const char *zKey, |
| 38180 | char *zBuf, |
| @@ -38219,15 +38490,13 @@ | |
| 38219 | /* |
| 38220 | ** An internal level of indirection which enables us to replace the |
| 38221 | ** kvvfs i/o methods with JavaScript implementations in WASM builds. |
| 38222 | ** Maintenance reminder: if this struct changes in any way, the JSON |
| 38223 | ** rendering of its structure must be updated in |
| 38224 | ** sqlite3_wasm_enum_json(). There are no binary compatibility |
| 38225 | ** concerns, so it does not need an iVersion member. This file is |
| 38226 | ** necessarily always compiled together with sqlite3_wasm_enum_json(), |
| 38227 | ** and JS code dynamically creates the mapping of members based on |
| 38228 | ** that JSON description. |
| 38229 | */ |
| 38230 | typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; |
| 38231 | struct sqlite3_kvvfs_methods { |
| 38232 | int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); |
| 38233 | int (*xWrite)(const char *zClass, const char *zKey, const char *zData); |
| @@ -38240,12 +38509,12 @@ | |
| 38240 | ** for JavaScript-side implementations in WASM builds. In such builds |
| 38241 | ** it cannot be const, but in native builds it should be so that |
| 38242 | ** the compiler can hopefully optimize this level of indirection out. |
| 38243 | ** That said, kvvfs is intended primarily for use in WASM builds. |
| 38244 | ** |
| 38245 | ** Note that this is not explicitly flagged as static because the |
| 38246 | ** amalgamation build will tag it with SQLITE_PRIVATE. |
| 38247 | */ |
| 38248 | #ifndef SQLITE_WASM |
| 38249 | const |
| 38250 | #endif |
| 38251 | SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { |
| @@ -39414,14 +39683,15 @@ | |
| 39414 | #define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ |
| 39415 | aSyscall[13].pCurrent) |
| 39416 | |
| 39417 | #if defined(HAVE_FCHMOD) |
| 39418 | { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, |
| 39419 | #else |
| 39420 | { "fchmod", (sqlite3_syscall_ptr)0, 0 }, |
| 39421 | #endif |
| 39422 | #define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) |
| 39423 | |
| 39424 | #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE |
| 39425 | { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, |
| 39426 | #else |
| 39427 | { "fallocate", (sqlite3_syscall_ptr)0, 0 }, |
| @@ -39675,13 +39945,12 @@ | |
| 39675 | return fd; |
| 39676 | } |
| 39677 | |
| 39678 | /* |
| 39679 | ** Helper functions to obtain and relinquish the global mutex. The |
| 39680 | ** global mutex is used to protect the unixInodeInfo and |
| 39681 | ** vxworksFileId objects used by this file, all of which may be |
| 39682 | ** shared by multiple threads. |
| 39683 | ** |
| 39684 | ** Function unixMutexHeld() is used to assert() that the global mutex |
| 39685 | ** is held when required. This function is only used as part of assert() |
| 39686 | ** statements. e.g. |
| 39687 | ** |
| @@ -39879,10 +40148,11 @@ | |
| 39879 | /* |
| 39880 | ** All unique filenames are held on a linked list headed by this |
| 39881 | ** variable: |
| 39882 | */ |
| 39883 | static struct vxworksFileId *vxworksFileList = 0; |
| 39884 | |
| 39885 | /* |
| 39886 | ** Simplify a filename into its canonical form |
| 39887 | ** by making the following changes: |
| 39888 | ** |
| @@ -39944,47 +40214,47 @@ | |
| 39944 | |
| 39945 | /* Search for an existing entry that matching the canonical name. |
| 39946 | ** If found, increment the reference count and return a pointer to |
| 39947 | ** the existing file ID. |
| 39948 | */ |
| 39949 | unixEnterMutex(); |
| 39950 | for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ |
| 39951 | if( pCandidate->nName==n |
| 39952 | && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 |
| 39953 | ){ |
| 39954 | sqlite3_free(pNew); |
| 39955 | pCandidate->nRef++; |
| 39956 | unixLeaveMutex(); |
| 39957 | return pCandidate; |
| 39958 | } |
| 39959 | } |
| 39960 | |
| 39961 | /* No match was found. We will make a new file ID */ |
| 39962 | pNew->nRef = 1; |
| 39963 | pNew->nName = n; |
| 39964 | pNew->pNext = vxworksFileList; |
| 39965 | vxworksFileList = pNew; |
| 39966 | unixLeaveMutex(); |
| 39967 | return pNew; |
| 39968 | } |
| 39969 | |
| 39970 | /* |
| 39971 | ** Decrement the reference count on a vxworksFileId object. Free |
| 39972 | ** the object when the reference count reaches zero. |
| 39973 | */ |
| 39974 | static void vxworksReleaseFileId(struct vxworksFileId *pId){ |
| 39975 | unixEnterMutex(); |
| 39976 | assert( pId->nRef>0 ); |
| 39977 | pId->nRef--; |
| 39978 | if( pId->nRef==0 ){ |
| 39979 | struct vxworksFileId **pp; |
| 39980 | for(pp=&vxworksFileList; *pp && *pp!=pId; pp = &((*pp)->pNext)){} |
| 39981 | assert( *pp==pId ); |
| 39982 | *pp = pId->pNext; |
| 39983 | sqlite3_free(pId); |
| 39984 | } |
| 39985 | unixLeaveMutex(); |
| 39986 | } |
| 39987 | #endif /* OS_VXWORKS */ |
| 39988 | /*************** End of Unique File ID Utility Used By VxWorks **************** |
| 39989 | ******************************************************************************/ |
| 39990 | |
| @@ -40368,10 +40638,14 @@ | |
| 40368 | do{ rc = osWrite(fd, "S", 1); }while( rc<0 && errno==EINTR ); |
| 40369 | if( rc!=1 ){ |
| 40370 | storeLastErrno(pFile, errno); |
| 40371 | return SQLITE_IOERR; |
| 40372 | } |
| 40373 | rc = osFstat(fd, &statbuf); |
| 40374 | if( rc!=0 ){ |
| 40375 | storeLastErrno(pFile, errno); |
| 40376 | return SQLITE_IOERR; |
| 40377 | } |
| @@ -40537,22 +40811,46 @@ | |
| 40537 | static int osSetPosixAdvisoryLock( |
| 40538 | int h, /* The file descriptor on which to take the lock */ |
| 40539 | struct flock *pLock, /* The description of the lock */ |
| 40540 | unixFile *pFile /* Structure holding timeout value */ |
| 40541 | ){ |
| 40542 | int tm = pFile->iBusyTimeout; |
| 40543 | int rc = osFcntl(h,F_SETLK,pLock); |
| 40544 | while( rc<0 && tm>0 ){ |
| 40545 | /* On systems that support some kind of blocking file lock with a timeout, |
| 40546 | ** make appropriate changes here to invoke that blocking file lock. On |
| 40547 | ** generic posix, however, there is no such API. So we simply try the |
| 40548 | ** lock once every millisecond until either the timeout expires, or until |
| 40549 | ** the lock is obtained. */ |
| 40550 | unixSleep(0,1000); |
| 40551 | rc = osFcntl(h,F_SETLK,pLock); |
| 40552 | tm--; |
| 40553 | } |
| 40554 | return rc; |
| 40555 | } |
| 40556 | #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ |
| 40557 | |
| 40558 | |
| @@ -44869,14 +45167,21 @@ | |
| 44869 | #endif |
| 44870 | |
| 44871 | storeLastErrno(pNew, 0); |
| 44872 | #if OS_VXWORKS |
| 44873 | if( rc!=SQLITE_OK ){ |
| 44874 | if( h>=0 ) robust_close(pNew, h, __LINE__); |
| 44875 | h = -1; |
| 44876 | osUnlink(zFilename); |
| 44877 | pNew->ctrlFlags |= UNIXFILE_DELETE; |
| 44878 | } |
| 44879 | #endif |
| 44880 | if( rc!=SQLITE_OK ){ |
| 44881 | if( h>=0 ) robust_close(pNew, h, __LINE__); |
| 44882 | }else{ |
| @@ -44916,10 +45221,13 @@ | |
| 44916 | struct stat buf; |
| 44917 | const char *zDir = sqlite3_temp_directory; |
| 44918 | |
| 44919 | while(1){ |
| 44920 | if( zDir!=0 |
| 44921 | && osStat(zDir, &buf)==0 |
| 44922 | && S_ISDIR(buf.st_mode) |
| 44923 | && osAccess(zDir, 03)==0 |
| 44924 | ){ |
| 44925 | return zDir; |
| @@ -45229,10 +45537,16 @@ | |
| 45229 | assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB |
| 45230 | || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL |
| 45231 | || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL |
| 45232 | || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL |
| 45233 | ); |
| 45234 | |
| 45235 | /* Detect a pid change and reset the PRNG. There is a race condition |
| 45236 | ** here such that two or more threads all trying to open databases at |
| 45237 | ** the same instant might all reset the PRNG. But multiple resets |
| 45238 | ** are harmless. |
| @@ -45430,12 +45744,15 @@ | |
| 45430 | goto open_finished; |
| 45431 | } |
| 45432 | } |
| 45433 | #endif |
| 45434 | |
| 45435 | assert( zPath==0 || zPath[0]=='/' |
| 45436 | || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL |
| 45437 | ); |
| 45438 | rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); |
| 45439 | |
| 45440 | open_finished: |
| 45441 | if( rc!=SQLITE_OK ){ |
| @@ -47160,10 +47477,13 @@ | |
| 47160 | } |
| 47161 | #ifdef SQLITE_OS_KV_OPTIONAL |
| 47162 | sqlite3KvvfsInit(); |
| 47163 | #endif |
| 47164 | unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); |
| 47165 | |
| 47166 | #ifndef SQLITE_OMIT_WAL |
| 47167 | /* Validate lock assumptions */ |
| 47168 | assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ |
| 47169 | assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ |
| @@ -47194,10 +47514,13 @@ | |
| 47194 | ** to release dynamically allocated objects. But not on unix. |
| 47195 | ** This routine is a no-op for unix. |
| 47196 | */ |
| 47197 | SQLITE_API int sqlite3_os_end(void){ |
| 47198 | unixBigLock = 0; |
| 47199 | return SQLITE_OK; |
| 47200 | } |
| 47201 | |
| 47202 | #endif /* SQLITE_OS_UNIX */ |
| 47203 | |
| @@ -51192,204 +51515,10 @@ | |
| 51192 | ** on allocation size granularity boundaries. |
| 51193 | ** During sqlite3_os_init() we do a GetSystemInfo() |
| 51194 | ** to get the granularity size. |
| 51195 | */ |
| 51196 | static SYSTEM_INFO winSysInfo; |
| 51197 | |
| 51198 | #ifndef SQLITE_OMIT_WAL |
| 51199 | |
| 51200 | /* |
| 51201 | ** Helper functions to obtain and relinquish the global mutex. The |
| 51202 | ** global mutex is used to protect the winLockInfo objects used by |
| 51203 | ** this file, all of which may be shared by multiple threads. |
| 51204 | ** |
| 51205 | ** Function winShmMutexHeld() is used to assert() that the global mutex |
| 51206 | ** is held when required. This function is only used as part of assert() |
| 51207 | ** statements. e.g. |
| 51208 | ** |
| 51209 | ** winShmEnterMutex() |
| 51210 | ** assert( winShmMutexHeld() ); |
| 51211 | ** winShmLeaveMutex() |
| 51212 | */ |
| 51213 | static sqlite3_mutex *winBigLock = 0; |
| 51214 | static void winShmEnterMutex(void){ |
| 51215 | sqlite3_mutex_enter(winBigLock); |
| 51216 | } |
| 51217 | static void winShmLeaveMutex(void){ |
| 51218 | sqlite3_mutex_leave(winBigLock); |
| 51219 | } |
| 51220 | #ifndef NDEBUG |
| 51221 | static int winShmMutexHeld(void) { |
| 51222 | return sqlite3_mutex_held(winBigLock); |
| 51223 | } |
| 51224 | #endif |
| 51225 | |
| 51226 | /* |
| 51227 | ** Object used to represent a single file opened and mmapped to provide |
| 51228 | ** shared memory. When multiple threads all reference the same |
| 51229 | ** log-summary, each thread has its own winFile object, but they all |
| 51230 | ** point to a single instance of this object. In other words, each |
| 51231 | ** log-summary is opened only once per process. |
| 51232 | ** |
| 51233 | ** winShmMutexHeld() must be true when creating or destroying |
| 51234 | ** this object or while reading or writing the following fields: |
| 51235 | ** |
| 51236 | ** nRef |
| 51237 | ** pNext |
| 51238 | ** |
| 51239 | ** The following fields are read-only after the object is created: |
| 51240 | ** |
| 51241 | ** zFilename |
| 51242 | ** |
| 51243 | ** Either winShmNode.mutex must be held or winShmNode.nRef==0 and |
| 51244 | ** winShmMutexHeld() is true when reading or writing any other field |
| 51245 | ** in this structure. |
| 51246 | ** |
| 51247 | ** File-handle hSharedShm is used to (a) take the DMS lock, (b) truncate |
| 51248 | ** the *-shm file if the DMS-locking protocol demands it, and (c) map |
| 51249 | ** regions of the *-shm file into memory using MapViewOfFile() or |
| 51250 | ** similar. Other locks are taken by individual clients using the |
| 51251 | ** winShm.hShm handles. |
| 51252 | */ |
| 51253 | struct winShmNode { |
| 51254 | sqlite3_mutex *mutex; /* Mutex to access this object */ |
| 51255 | char *zFilename; /* Name of the file */ |
| 51256 | HANDLE hSharedShm; /* File handle open on zFilename */ |
| 51257 | |
| 51258 | int isUnlocked; /* DMS lock has not yet been obtained */ |
| 51259 | int isReadonly; /* True if read-only */ |
| 51260 | int szRegion; /* Size of shared-memory regions */ |
| 51261 | int nRegion; /* Size of array apRegion */ |
| 51262 | |
| 51263 | struct ShmRegion { |
| 51264 | HANDLE hMap; /* File handle from CreateFileMapping */ |
| 51265 | void *pMap; |
| 51266 | } *aRegion; |
| 51267 | DWORD lastErrno; /* The Windows errno from the last I/O error */ |
| 51268 | |
| 51269 | int nRef; /* Number of winShm objects pointing to this */ |
| 51270 | winShmNode *pNext; /* Next in list of all winShmNode objects */ |
| 51271 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51272 | u8 nextShmId; /* Next available winShm.id value */ |
| 51273 | #endif |
| 51274 | }; |
| 51275 | |
| 51276 | /* |
| 51277 | ** A global array of all winShmNode objects. |
| 51278 | ** |
| 51279 | ** The winShmMutexHeld() must be true while reading or writing this list. |
| 51280 | */ |
| 51281 | static winShmNode *winShmNodeList = 0; |
| 51282 | |
| 51283 | /* |
| 51284 | ** Structure used internally by this VFS to record the state of an |
| 51285 | ** open shared memory connection. There is one such structure for each |
| 51286 | ** winFile open on a wal mode database. |
| 51287 | */ |
| 51288 | struct winShm { |
| 51289 | winShmNode *pShmNode; /* The underlying winShmNode object */ |
| 51290 | u16 sharedMask; /* Mask of shared locks held */ |
| 51291 | u16 exclMask; /* Mask of exclusive locks held */ |
| 51292 | HANDLE hShm; /* File-handle on *-shm file. For locking. */ |
| 51293 | int bReadonly; /* True if hShm is opened read-only */ |
| 51294 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51295 | u8 id; /* Id of this connection with its winShmNode */ |
| 51296 | #endif |
| 51297 | }; |
| 51298 | |
| 51299 | /* |
| 51300 | ** Constants used for locking |
| 51301 | */ |
| 51302 | #define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ |
| 51303 | #define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ |
| 51304 | |
| 51305 | /* Forward references to VFS methods */ |
| 51306 | static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); |
| 51307 | static int winDelete(sqlite3_vfs *,const char*,int); |
| 51308 | |
| 51309 | /* |
| 51310 | ** Purge the winShmNodeList list of all entries with winShmNode.nRef==0. |
| 51311 | ** |
| 51312 | ** This is not a VFS shared-memory method; it is a utility function called |
| 51313 | ** by VFS shared-memory methods. |
| 51314 | */ |
| 51315 | static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ |
| 51316 | winShmNode **pp; |
| 51317 | winShmNode *p; |
| 51318 | assert( winShmMutexHeld() ); |
| 51319 | OSTRACE(("SHM-PURGE pid=%lu, deleteFlag=%d\n", |
| 51320 | osGetCurrentProcessId(), deleteFlag)); |
| 51321 | pp = &winShmNodeList; |
| 51322 | while( (p = *pp)!=0 ){ |
| 51323 | if( p->nRef==0 ){ |
| 51324 | int i; |
| 51325 | if( p->mutex ){ sqlite3_mutex_free(p->mutex); } |
| 51326 | for(i=0; i<p->nRegion; i++){ |
| 51327 | BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); |
| 51328 | OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", |
| 51329 | osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); |
| 51330 | UNUSED_VARIABLE_VALUE(bRc); |
| 51331 | bRc = osCloseHandle(p->aRegion[i].hMap); |
| 51332 | OSTRACE(("SHM-PURGE-CLOSE pid=%lu, region=%d, rc=%s\n", |
| 51333 | osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); |
| 51334 | UNUSED_VARIABLE_VALUE(bRc); |
| 51335 | } |
| 51336 | winHandleClose(p->hSharedShm); |
| 51337 | if( deleteFlag ){ |
| 51338 | SimulateIOErrorBenign(1); |
| 51339 | sqlite3BeginBenignMalloc(); |
| 51340 | winDelete(pVfs, p->zFilename, 0); |
| 51341 | sqlite3EndBenignMalloc(); |
| 51342 | SimulateIOErrorBenign(0); |
| 51343 | } |
| 51344 | *pp = p->pNext; |
| 51345 | sqlite3_free(p->aRegion); |
| 51346 | sqlite3_free(p); |
| 51347 | }else{ |
| 51348 | pp = &p->pNext; |
| 51349 | } |
| 51350 | } |
| 51351 | } |
| 51352 | |
| 51353 | /* |
| 51354 | ** The DMS lock has not yet been taken on the shm file associated with |
| 51355 | ** pShmNode. Take the lock. Truncate the *-shm file if required. |
| 51356 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. |
| 51357 | */ |
| 51358 | static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ |
| 51359 | HANDLE h = pShmNode->hSharedShm; |
| 51360 | int rc = SQLITE_OK; |
| 51361 | |
| 51362 | assert( sqlite3_mutex_held(pShmNode->mutex) ); |
| 51363 | rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 1, 0); |
| 51364 | if( rc==SQLITE_OK ){ |
| 51365 | /* We have an EXCLUSIVE lock on the DMS byte. This means that this |
| 51366 | ** is the first process to open the file. Truncate it to zero bytes |
| 51367 | ** in this case. */ |
| 51368 | if( pShmNode->isReadonly ){ |
| 51369 | rc = SQLITE_READONLY_CANTINIT; |
| 51370 | }else{ |
| 51371 | rc = winHandleTruncate(h, 0); |
| 51372 | } |
| 51373 | |
| 51374 | /* Release the EXCLUSIVE lock acquired above. */ |
| 51375 | winUnlockFile(&h, WIN_SHM_DMS, 0, 1, 0); |
| 51376 | }else if( (rc & 0xFF)==SQLITE_BUSY ){ |
| 51377 | rc = SQLITE_OK; |
| 51378 | } |
| 51379 | |
| 51380 | if( rc==SQLITE_OK ){ |
| 51381 | /* Take a SHARED lock on the DMS byte. */ |
| 51382 | rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 0, nMs); |
| 51383 | if( rc==SQLITE_OK ){ |
| 51384 | pShmNode->isUnlocked = 0; |
| 51385 | } |
| 51386 | } |
| 51387 | |
| 51388 | return rc; |
| 51389 | } |
| 51390 | |
| 51391 | |
| 51392 | /* |
| 51393 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51394 | ** operating system wants filenames in. Space to hold the result |
| 51395 | ** is obtained from malloc and must be freed by the calling |
| @@ -51483,10 +51612,212 @@ | |
| 51483 | } |
| 51484 | #endif |
| 51485 | /* caller will handle out of memory */ |
| 51486 | return zConverted; |
| 51487 | } |
| 51488 | |
| 51489 | /* |
| 51490 | ** This function is used to open a handle on a *-shm file. |
| 51491 | ** |
| 51492 | ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file |
| @@ -51579,10 +51910,64 @@ | |
| 51579 | *pbReadonly = bReadonly; |
| 51580 | *ph = h; |
| 51581 | return rc; |
| 51582 | } |
| 51583 | |
| 51584 | |
| 51585 | /* |
| 51586 | ** Open the shared-memory area associated with database file pDbFd. |
| 51587 | */ |
| 51588 | static int winOpenSharedMemory(winFile *pDbFd){ |
| @@ -51605,19 +51990,14 @@ | |
| 51605 | return SQLITE_IOERR_NOMEM_BKPT; |
| 51606 | } |
| 51607 | pNew->zFilename = (char*)&pNew[1]; |
| 51608 | pNew->hSharedShm = INVALID_HANDLE_VALUE; |
| 51609 | pNew->isUnlocked = 1; |
| 51610 | sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); |
| 51611 | sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); |
| 51612 | |
| 51613 | /* Open a file-handle on the *-shm file for this connection. This file-handle |
| 51614 | ** is only used for locking. The mapping of the *-shm file is created using |
| 51615 | ** the shared file handle in winShmNode.hSharedShm. */ |
| 51616 | p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); |
| 51617 | rc = winHandleOpen(pNew->zFilename, &p->bReadonly, &p->hShm); |
| 51618 | |
| 51619 | /* Look to see if there is an existing winShmNode that can be used. |
| 51620 | ** If no matching winShmNode currently exists, then create a new one. */ |
| 51621 | winShmEnterMutex(); |
| 51622 | for(pShmNode = winShmNodeList; pShmNode; pShmNode=pShmNode->pNext){ |
| 51623 | /* TBD need to come up with better match here. Perhaps |
| @@ -51634,11 +52014,11 @@ | |
| 51634 | } |
| 51635 | |
| 51636 | /* Open a file-handle to use for mappings, and for the DMS lock. */ |
| 51637 | if( rc==SQLITE_OK ){ |
| 51638 | HANDLE h = INVALID_HANDLE_VALUE; |
| 51639 | pShmNode->isReadonly = p->bReadonly; |
| 51640 | rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h); |
| 51641 | pShmNode->hSharedShm = h; |
| 51642 | } |
| 51643 | |
| 51644 | /* If successful, link the new winShmNode into the global list. If an |
| @@ -51656,24 +52036,39 @@ | |
| 51656 | } |
| 51657 | |
| 51658 | /* If no error has occurred, link the winShm object to the winShmNode and |
| 51659 | ** the winShm to pDbFd. */ |
| 51660 | if( rc==SQLITE_OK ){ |
| 51661 | p->pShmNode = pShmNode; |
| 51662 | pShmNode->nRef++; |
| 51663 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51664 | p->id = pShmNode->nextShmId++; |
| 51665 | #endif |
| 51666 | pDbFd->pShm = p; |
| 51667 | }else if( p ){ |
| 51668 | winHandleClose(p->hShm); |
| 51669 | sqlite3_free(p); |
| 51670 | } |
| 51671 | |
| 51672 | assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 ); |
| 51673 | winShmLeaveMutex(); |
| 51674 | sqlite3_free(pNew); |
| 51675 | return rc; |
| 51676 | } |
| 51677 | |
| 51678 | /* |
| 51679 | ** Close a connection to shared-memory. Delete the underlying |
| @@ -51681,37 +52076,11 @@ | |
| 51681 | */ |
| 51682 | static int winShmUnmap( |
| 51683 | sqlite3_file *fd, /* Database holding shared memory */ |
| 51684 | int deleteFlag /* Delete after closing if true */ |
| 51685 | ){ |
| 51686 | winFile *pDbFd; /* Database holding shared-memory */ |
| 51687 | winShm *p; /* The connection to be closed */ |
| 51688 | winShmNode *pShmNode; /* The underlying shared-memory file */ |
| 51689 | |
| 51690 | pDbFd = (winFile*)fd; |
| 51691 | p = pDbFd->pShm; |
| 51692 | if( p==0 ) return SQLITE_OK; |
| 51693 | if( p->hShm!=INVALID_HANDLE_VALUE ){ |
| 51694 | osCloseHandle(p->hShm); |
| 51695 | } |
| 51696 | |
| 51697 | pShmNode = p->pShmNode; |
| 51698 | winShmEnterMutex(); |
| 51699 | |
| 51700 | /* If pShmNode->nRef has reached 0, then close the underlying |
| 51701 | ** shared-memory file, too. */ |
| 51702 | assert( pShmNode->nRef>0 ); |
| 51703 | pShmNode->nRef--; |
| 51704 | if( pShmNode->nRef==0 ){ |
| 51705 | winShmPurge(pDbFd->pVfs, deleteFlag); |
| 51706 | } |
| 51707 | winShmLeaveMutex(); |
| 51708 | |
| 51709 | /* Free the connection p */ |
| 51710 | sqlite3_free(p); |
| 51711 | pDbFd->pShm = 0; |
| 51712 | return SQLITE_OK; |
| 51713 | } |
| 51714 | |
| 51715 | /* |
| 51716 | ** Change the lock state for a shared-memory segment. |
| 51717 | */ |
| @@ -51776,29 +52145,75 @@ | |
| 51776 | ); |
| 51777 | if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) |
| 51778 | || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) |
| 51779 | || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) |
| 51780 | ){ |
| 51781 | |
| 51782 | if( flags & SQLITE_SHM_UNLOCK ){ |
| 51783 | /* Case (a) - unlock. */ |
| 51784 | |
| 51785 | assert( (p->exclMask & p->sharedMask)==0 ); |
| 51786 | assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); |
| 51787 | assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); |
| 51788 | |
| 51789 | rc = winHandleUnlock(p->hShm, ofst+WIN_SHM_BASE, n); |
| 51790 | |
| 51791 | /* If successful, also clear the bits in sharedMask/exclMask */ |
| 51792 | if( rc==SQLITE_OK ){ |
| 51793 | p->exclMask = (p->exclMask & ~mask); |
| 51794 | p->sharedMask = (p->sharedMask & ~mask); |
| 51795 | } |
| 51796 | }else{ |
| 51797 | int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0); |
| 51798 | DWORD nMs = winFileBusyTimeout(pDbFd); |
| 51799 | rc = winHandleLockTimeout(p->hShm, ofst+WIN_SHM_BASE, n, bExcl, nMs); |
| 51800 | if( rc==SQLITE_OK ){ |
| 51801 | if( bExcl ){ |
| 51802 | p->exclMask = (p->exclMask | mask); |
| 51803 | }else{ |
| 51804 | p->sharedMask = (p->sharedMask | mask); |
| @@ -61825,18 +62240,31 @@ | |
| 61825 | SQLITE_PRIVATE void sqlite3PagerSetFlags( |
| 61826 | Pager *pPager, /* The pager to set safety level for */ |
| 61827 | unsigned pgFlags /* Various flags */ |
| 61828 | ){ |
| 61829 | unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; |
| 61830 | if( pPager->tempFile ){ |
| 61831 | pPager->noSync = 1; |
| 61832 | pPager->fullSync = 0; |
| 61833 | pPager->extraSync = 0; |
| 61834 | }else{ |
| 61835 | pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; |
| 61836 | pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; |
| 61837 | pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; |
| 61838 | } |
| 61839 | if( pPager->noSync ){ |
| 61840 | pPager->syncFlags = 0; |
| 61841 | }else if( pgFlags & PAGER_FULLFSYNC ){ |
| 61842 | pPager->syncFlags = SQLITE_SYNC_FULL; |
| @@ -65725,11 +66153,11 @@ | |
| 65725 | */ |
| 65726 | sqlite3_exec(db, "PRAGMA table_list",0,0,0); |
| 65727 | } |
| 65728 | if( pPager->pWal ){ |
| 65729 | rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, |
| 65730 | (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), |
| 65731 | pPager->pBusyHandlerArg, |
| 65732 | pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, |
| 65733 | pnLog, pnCkpt |
| 65734 | ); |
| 65735 | } |
| @@ -70330,11 +70758,12 @@ | |
| 70330 | assert( pWal->ckptLock==0 ); |
| 70331 | assert( pWal->writeLock==0 ); |
| 70332 | |
| 70333 | /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked |
| 70334 | ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ |
| 70335 | assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); |
| 70336 | |
| 70337 | if( pWal->readOnly ) return SQLITE_READONLY; |
| 70338 | WALTRACE(("WAL%p: checkpoint begins\n", pWal)); |
| 70339 | |
| 70340 | /* Enable blocking locks, if possible. */ |
| @@ -70347,35 +70776,39 @@ | |
| 70347 | ** checkpoint operation at the same time, the lock cannot be obtained and |
| 70348 | ** SQLITE_BUSY is returned. |
| 70349 | ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, |
| 70350 | ** it will not be invoked in this case. |
| 70351 | */ |
| 70352 | rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); |
| 70353 | testcase( rc==SQLITE_BUSY ); |
| 70354 | testcase( rc!=SQLITE_OK && xBusy2!=0 ); |
| 70355 | if( rc==SQLITE_OK ){ |
| 70356 | pWal->ckptLock = 1; |
| 70357 | |
| 70358 | /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and |
| 70359 | ** TRUNCATE modes also obtain the exclusive "writer" lock on the database |
| 70360 | ** file. |
| 70361 | ** |
| 70362 | ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained |
| 70363 | ** immediately, and a busy-handler is configured, it is invoked and the |
| 70364 | ** writer lock retried until either the busy-handler returns 0 or the |
| 70365 | ** lock is successfully obtained. |
| 70366 | */ |
| 70367 | if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ |
| 70368 | rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); |
| 70369 | if( rc==SQLITE_OK ){ |
| 70370 | pWal->writeLock = 1; |
| 70371 | }else if( rc==SQLITE_BUSY ){ |
| 70372 | eMode2 = SQLITE_CHECKPOINT_PASSIVE; |
| 70373 | xBusy2 = 0; |
| 70374 | rc = SQLITE_OK; |
| 70375 | } |
| 70376 | } |
| 70377 | } |
| 70378 | |
| 70379 | |
| 70380 | /* Read the wal-index header. */ |
| 70381 | SEH_TRY { |
| @@ -70385,21 +70818,21 @@ | |
| 70385 | ** or invoke the busy handler. The only lock such a checkpoint may |
| 70386 | ** attempt to obtain is a lock on a read-slot, and it should give up |
| 70387 | ** immediately and do a partial checkpoint if it cannot obtain it. */ |
| 70388 | walDisableBlocking(pWal); |
| 70389 | rc = walIndexReadHdr(pWal, &isChanged); |
| 70390 | if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); |
| 70391 | if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ |
| 70392 | sqlite3OsUnfetch(pWal->pDbFd, 0, 0); |
| 70393 | } |
| 70394 | } |
| 70395 | |
| 70396 | /* Copy data from the log to the database file. */ |
| 70397 | if( rc==SQLITE_OK ){ |
| 70398 | if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ |
| 70399 | rc = SQLITE_CORRUPT_BKPT; |
| 70400 | }else{ |
| 70401 | rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); |
| 70402 | } |
| 70403 | |
| 70404 | /* If no error occurred, set the output variables. */ |
| 70405 | if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ |
| @@ -89078,14 +89511,16 @@ | |
| 89078 | ** simple case then too. |
| 89079 | */ |
| 89080 | if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) |
| 89081 | || nTrans<=1 |
| 89082 | ){ |
| 89083 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 89084 | Btree *pBt = db->aDb[i].pBt; |
| 89085 | if( pBt ){ |
| 89086 | rc = sqlite3BtreeCommitPhaseOne(pBt, 0); |
| 89087 | } |
| 89088 | } |
| 89089 | |
| 89090 | /* Do the commit only if all databases successfully complete phase 1. |
| 89091 | ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an |
| @@ -89092,11 +89527,13 @@ | |
| 89092 | ** IO error while deleting or truncating a journal file. It is unlikely, |
| 89093 | ** but could happen. In this case abandon processing and return the error. |
| 89094 | */ |
| 89095 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 89096 | Btree *pBt = db->aDb[i].pBt; |
| 89097 | if( pBt ){ |
| 89098 | rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); |
| 89099 | } |
| 89100 | } |
| 89101 | if( rc==SQLITE_OK ){ |
| 89102 | sqlite3VtabCommit(db); |
| @@ -89347,32 +89784,35 @@ | |
| 89347 | return SQLITE_OK; |
| 89348 | } |
| 89349 | |
| 89350 | |
| 89351 | /* |
| 89352 | ** This function is called when a transaction opened by the database |
| 89353 | ** handle associated with the VM passed as an argument is about to be |
| 89354 | ** committed. If there are outstanding deferred foreign key constraint |
| 89355 | ** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. |
| 89356 | ** |
| 89357 | ** If there are outstanding FK violations and this function returns |
| 89358 | ** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY |
| 89359 | ** and write an error message to it. Then return SQLITE_ERROR. |
| 89360 | */ |
| 89361 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 89362 | SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ |
| 89363 | sqlite3 *db = p->db; |
| 89364 | if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) |
| 89365 | || (!deferred && p->nFkConstraint>0) |
| 89366 | ){ |
| 89367 | p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; |
| 89368 | p->errorAction = OE_Abort; |
| 89369 | sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); |
| 89370 | if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; |
| 89371 | return SQLITE_CONSTRAINT_FOREIGNKEY; |
| 89372 | } |
| 89373 | return SQLITE_OK; |
| 89374 | } |
| 89375 | #endif |
| 89376 | |
| 89377 | /* |
| 89378 | ** This routine is called the when a VDBE tries to halt. If the VDBE |
| @@ -89462,11 +89902,11 @@ | |
| 89462 | } |
| 89463 | } |
| 89464 | |
| 89465 | /* Check for immediate foreign key violations. */ |
| 89466 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89467 | (void)sqlite3VdbeCheckFk(p, 0); |
| 89468 | } |
| 89469 | |
| 89470 | /* If the auto-commit flag is set and this is the only active writer |
| 89471 | ** VM, then we do either a commit or rollback of the current transaction. |
| 89472 | ** |
| @@ -89476,11 +89916,11 @@ | |
| 89476 | if( !sqlite3VtabInSync(db) |
| 89477 | && db->autoCommit |
| 89478 | && db->nVdbeWrite==(p->readOnly==0) |
| 89479 | ){ |
| 89480 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89481 | rc = sqlite3VdbeCheckFk(p, 1); |
| 89482 | if( rc!=SQLITE_OK ){ |
| 89483 | if( NEVER(p->readOnly) ){ |
| 89484 | sqlite3VdbeLeave(p); |
| 89485 | return SQLITE_ERROR; |
| 89486 | } |
| @@ -90341,19 +90781,19 @@ | |
| 90341 | /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ |
| 90342 | pMem->szMalloc = 0; |
| 90343 | pMem->z = 0; |
| 90344 | sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); |
| 90345 | d += sqlite3VdbeSerialTypeLen(serial_type); |
| 90346 | pMem++; |
| 90347 | if( (++u)>=p->nField ) break; |
| 90348 | } |
| 90349 | if( d>(u32)nKey && u ){ |
| 90350 | assert( CORRUPT_DB ); |
| 90351 | /* In a corrupt record entry, the last pMem might have been set up using |
| 90352 | ** uninitialized memory. Overwrite its value with NULL, to prevent |
| 90353 | ** warnings from MSAN. */ |
| 90354 | sqlite3VdbeMemSetNull(pMem-1); |
| 90355 | } |
| 90356 | testcase( u == pKeyInfo->nKeyField + 1 ); |
| 90357 | testcase( u < pKeyInfo->nKeyField + 1 ); |
| 90358 | assert( u<=pKeyInfo->nKeyField + 1 ); |
| 90359 | p->nField = u; |
| @@ -90520,10 +90960,36 @@ | |
| 90520 | ** Both *pMem1 and *pMem2 contain string values. Compare the two values |
| 90521 | ** using the collation sequence pColl. As usual, return a negative , zero |
| 90522 | ** or positive value if *pMem1 is less than, equal to or greater than |
| 90523 | ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". |
| 90524 | */ |
| 90525 | static int vdbeCompareMemString( |
| 90526 | const Mem *pMem1, |
| 90527 | const Mem *pMem2, |
| 90528 | const CollSeq *pColl, |
| 90529 | u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ |
| @@ -90531,29 +90997,11 @@ | |
| 90531 | if( pMem1->enc==pColl->enc ){ |
| 90532 | /* The strings are already in the correct encoding. Call the |
| 90533 | ** comparison function directly */ |
| 90534 | return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); |
| 90535 | }else{ |
| 90536 | int rc; |
| 90537 | const void *v1, *v2; |
| 90538 | Mem c1; |
| 90539 | Mem c2; |
| 90540 | sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); |
| 90541 | sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); |
| 90542 | sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); |
| 90543 | sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); |
| 90544 | v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); |
| 90545 | v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); |
| 90546 | if( (v1==0 || v2==0) ){ |
| 90547 | if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; |
| 90548 | rc = 0; |
| 90549 | }else{ |
| 90550 | rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); |
| 90551 | } |
| 90552 | sqlite3VdbeMemReleaseMalloc(&c1); |
| 90553 | sqlite3VdbeMemReleaseMalloc(&c2); |
| 90554 | return rc; |
| 90555 | } |
| 90556 | } |
| 90557 | |
| 90558 | /* |
| 90559 | ** The input pBlob is guaranteed to be a Blob that is not marked |
| @@ -91607,11 +92055,11 @@ | |
| 91607 | |
| 91608 | preupdate.v = v; |
| 91609 | preupdate.pCsr = pCsr; |
| 91610 | preupdate.op = op; |
| 91611 | preupdate.iNewReg = iReg; |
| 91612 | preupdate.pKeyinfo = (KeyInfo*)&preupdate.keyinfoSpace; |
| 91613 | preupdate.pKeyinfo->db = db; |
| 91614 | preupdate.pKeyinfo->enc = ENC(db); |
| 91615 | preupdate.pKeyinfo->nKeyField = pTab->nCol; |
| 91616 | preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */ |
| 91617 | preupdate.iKey1 = iKey1; |
| @@ -91641,10 +92089,21 @@ | |
| 91641 | sqlite3DbFree(db, preupdate.apDflt); |
| 91642 | } |
| 91643 | } |
| 91644 | #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 91645 | |
| 91646 | /************** End of vdbeaux.c *********************************************/ |
| 91647 | /************** Begin file vdbeapi.c *****************************************/ |
| 91648 | /* |
| 91649 | ** 2004 May 26 |
| 91650 | ** |
| @@ -93338,12 +93797,16 @@ | |
| 93338 | if( rc==SQLITE_OK ){ |
| 93339 | assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ |
| 93340 | if( zData!=0 ){ |
| 93341 | pVar = &p->aVar[i-1]; |
| 93342 | rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); |
| 93343 | if( rc==SQLITE_OK && encoding!=0 ){ |
| 93344 | rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); |
| 93345 | } |
| 93346 | if( rc ){ |
| 93347 | sqlite3Error(p->db, rc); |
| 93348 | rc = sqlite3ApiExit(p->db, rc); |
| 93349 | } |
| @@ -95247,11 +95710,11 @@ | |
| 95247 | ** to run faster. |
| 95248 | */ |
| 95249 | static SQLITE_NOINLINE int vdbeColumnFromOverflow( |
| 95250 | VdbeCursor *pC, /* The BTree cursor from which we are reading */ |
| 95251 | int iCol, /* The column to read */ |
| 95252 | int t, /* The serial-type code for the column value */ |
| 95253 | i64 iOffset, /* Offset to the start of the content value */ |
| 95254 | u32 cacheStatus, /* Current Vdbe.cacheCtr value */ |
| 95255 | u32 colCacheCtr, /* Current value of the column cache counter */ |
| 95256 | Mem *pDest /* Store the value into this register. */ |
| 95257 | ){ |
| @@ -96256,11 +96719,11 @@ | |
| 96256 | ** exits. This opcode is used to raise foreign key constraint errors prior |
| 96257 | ** to returning results such as a row change count or the result of a |
| 96258 | ** RETURNING clause. |
| 96259 | */ |
| 96260 | case OP_FkCheck: { |
| 96261 | if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ |
| 96262 | goto abort_due_to_error; |
| 96263 | } |
| 96264 | break; |
| 96265 | } |
| 96266 | |
| @@ -96348,11 +96811,12 @@ | |
| 96348 | flags2 = pIn2->flags & ~MEM_Str; |
| 96349 | }else if( (flags2 & MEM_Zero)!=0 ){ |
| 96350 | if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; |
| 96351 | flags2 = pIn2->flags & ~MEM_Str; |
| 96352 | } |
| 96353 | nByte = pIn1->n + pIn2->n; |
| 96354 | if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ |
| 96355 | goto too_big; |
| 96356 | } |
| 96357 | if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ |
| 96358 | goto no_mem; |
| @@ -98173,11 +98637,11 @@ | |
| 98173 | assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) ); |
| 98174 | assert( pRec->n>=0 ); |
| 98175 | len = (u32)pRec->n; |
| 98176 | serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); |
| 98177 | if( pRec->flags & MEM_Zero ){ |
| 98178 | serial_type += pRec->u.nZero*2; |
| 98179 | if( nData ){ |
| 98180 | if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; |
| 98181 | len += pRec->u.nZero; |
| 98182 | }else{ |
| 98183 | nZero += pRec->u.nZero; |
| @@ -98440,11 +98904,11 @@ | |
| 98440 | ** and this is a RELEASE command, then the current transaction |
| 98441 | ** is committed. |
| 98442 | */ |
| 98443 | int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; |
| 98444 | if( isTransaction && p1==SAVEPOINT_RELEASE ){ |
| 98445 | if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ |
| 98446 | goto vdbe_return; |
| 98447 | } |
| 98448 | db->autoCommit = 1; |
| 98449 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| 98450 | p->pc = (int)(pOp - aOp); |
| @@ -98558,11 +99022,11 @@ | |
| 98558 | */ |
| 98559 | sqlite3VdbeError(p, "cannot commit transaction - " |
| 98560 | "SQL statements in progress"); |
| 98561 | rc = SQLITE_BUSY; |
| 98562 | goto abort_due_to_error; |
| 98563 | }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ |
| 98564 | goto vdbe_return; |
| 98565 | }else{ |
| 98566 | db->autoCommit = (u8)desiredAutoCommit; |
| 98567 | } |
| 98568 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| @@ -102490,10 +102954,11 @@ | |
| 102490 | aRes[1] = aRes[2] = -1; |
| 102491 | assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE |
| 102492 | || pOp->p2==SQLITE_CHECKPOINT_FULL |
| 102493 | || pOp->p2==SQLITE_CHECKPOINT_RESTART |
| 102494 | || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE |
| 102495 | ); |
| 102496 | rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); |
| 102497 | if( rc ){ |
| 102498 | if( rc!=SQLITE_BUSY ) goto abort_due_to_error; |
| 102499 | rc = SQLITE_OK; |
| @@ -104689,10 +105154,11 @@ | |
| 104689 | UnpackedRecord *pUnpacked; /* Space to unpack a record */ |
| 104690 | SorterList list; /* List for thread to write to a PMA */ |
| 104691 | SorterCompare xCompare; /* Compare function to use */ |
| 104692 | SorterFile file; /* Temp file for level-0 PMAs */ |
| 104693 | SorterFile file2; /* Space for other PMAs */ |
| 104694 | }; |
| 104695 | |
| 104696 | |
| 104697 | /* |
| 104698 | ** Main sorter structure. A single instance of this is allocated for each |
| @@ -104809,10 +105275,11 @@ | |
| 104809 | int nBuffer; /* Size of write buffer in bytes */ |
| 104810 | int iBufStart; /* First byte of buffer to write */ |
| 104811 | int iBufEnd; /* Last byte of buffer to write */ |
| 104812 | i64 iWriteOff; /* Offset of start of buffer in file */ |
| 104813 | sqlite3_file *pFd; /* File handle to write to */ |
| 104814 | }; |
| 104815 | |
| 104816 | /* |
| 104817 | ** This object is the header on a single record while that record is being |
| 104818 | ** held in memory and prior to being written out as part of a PMA. |
| @@ -105667,10 +106134,16 @@ | |
| 105667 | SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ |
| 105668 | VdbeSorter *pSorter; |
| 105669 | assert( pCsr->eCurType==CURTYPE_SORTER ); |
| 105670 | pSorter = pCsr->uc.pSorter; |
| 105671 | if( pSorter ){ |
| 105672 | sqlite3VdbeSorterReset(db, pSorter); |
| 105673 | sqlite3_free(pSorter->list.aMemory); |
| 105674 | sqlite3DbFree(db, pSorter); |
| 105675 | pCsr->uc.pSorter = 0; |
| 105676 | } |
| @@ -105892,10 +106365,11 @@ | |
| 105892 | if( p->iBufEnd==p->nBuffer ){ |
| 105893 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 105894 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 105895 | p->iWriteOff + p->iBufStart |
| 105896 | ); |
| 105897 | p->iBufStart = p->iBufEnd = 0; |
| 105898 | p->iWriteOff += p->nBuffer; |
| 105899 | } |
| 105900 | assert( p->iBufEnd<p->nBuffer ); |
| 105901 | |
| @@ -105908,21 +106382,24 @@ | |
| 105908 | ** The results of using the PMA-writer after this call are undefined. |
| 105909 | ** Return SQLITE_OK if flushing the buffered data succeeds or is not |
| 105910 | ** required. Otherwise, return an SQLite error code. |
| 105911 | ** |
| 105912 | ** Before returning, set *piEof to the offset immediately following the |
| 105913 | ** last byte written to the file. |
| 105914 | */ |
| 105915 | static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ |
| 105916 | int rc; |
| 105917 | if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ |
| 105918 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 105919 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 105920 | p->iWriteOff + p->iBufStart |
| 105921 | ); |
| 105922 | } |
| 105923 | *piEof = (p->iWriteOff + p->iBufEnd); |
| 105924 | sqlite3_free(p->aBuffer); |
| 105925 | rc = p->eFWErr; |
| 105926 | memset(p, 0, sizeof(PmaWriter)); |
| 105927 | return rc; |
| 105928 | } |
| @@ -105998,11 +106475,11 @@ | |
| 105998 | vdbePmaWriteVarint(&writer, p->nVal); |
| 105999 | vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal); |
| 106000 | if( pList->aMemory==0 ) sqlite3_free(p); |
| 106001 | } |
| 106002 | pList->pList = p; |
| 106003 | rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); |
| 106004 | } |
| 106005 | |
| 106006 | vdbeSorterWorkDebug(pTask, "exit"); |
| 106007 | assert( rc!=SQLITE_OK || pList->pList==0 ); |
| 106008 | assert( rc!=SQLITE_OK || pTask->file.iEof==iSz ); |
| @@ -106312,11 +106789,11 @@ | |
| 106312 | vdbePmaWriteBlob(&writer, pReader->aKey, nKey); |
| 106313 | assert( pIncr->pMerger->pTask==pTask ); |
| 106314 | rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); |
| 106315 | } |
| 106316 | |
| 106317 | rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); |
| 106318 | if( rc==SQLITE_OK ) rc = rc2; |
| 106319 | vdbeSorterPopulateDebug(pTask, "exit"); |
| 106320 | return rc; |
| 106321 | } |
| 106322 | |
| @@ -109256,12 +109733,12 @@ | |
| 109256 | assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ |
| 109257 | if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); |
| 109258 | |
| 109259 | /* |
| 109260 | ** Expression p should encode a floating point value between 1.0 and 0.0. |
| 109261 | ** Return 1024 times this value. Or return -1 if p is not a floating point |
| 109262 | ** value between 1.0 and 0.0. |
| 109263 | */ |
| 109264 | static int exprProbability(Expr *p){ |
| 109265 | double r = -1.0; |
| 109266 | if( p->op!=TK_FLOAT ) return -1; |
| 109267 | assert( !ExprHasProperty(p, EP_IntValue) ); |
| @@ -110613,18 +111090,21 @@ | |
| 110613 | ExprList *pList /* Expression list to resolve. May be NULL. */ |
| 110614 | ){ |
| 110615 | SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */ |
| 110616 | NameContext sNC; /* Name context for pParse->pNewTable */ |
| 110617 | int rc; |
| 110618 | u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ |
| 110619 | |
| 110620 | assert( type==0 || pTab!=0 ); |
| 110621 | assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr |
| 110622 | || type==NC_GenCol || pTab==0 ); |
| 110623 | memset(&sNC, 0, sizeof(sNC)); |
| 110624 | pSrc = (SrcList*)srcSpace; |
| 110625 | memset(pSrc, 0, SZ_SRCLIST_1); |
| 110626 | if( pTab ){ |
| 110627 | pSrc->nSrc = 1; |
| 110628 | pSrc->a[0].zName = pTab->zName; |
| 110629 | pSrc->a[0].pSTab = pTab; |
| 110630 | pSrc->a[0].iCursor = -1; |
| @@ -111883,10 +112363,15 @@ | |
| 111883 | if( IsWindowFunc(pExpr) ){ |
| 111884 | sqlite3ExprOrderByAggregateError(pParse, pExpr); |
| 111885 | sqlite3ExprListDelete(db, pOrderBy); |
| 111886 | return; |
| 111887 | } |
| 111888 | |
| 111889 | pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); |
| 111890 | if( pOB==0 ){ |
| 111891 | sqlite3ExprListDelete(db, pOrderBy); |
| 111892 | return; |
| @@ -113079,13 +113564,12 @@ | |
| 113079 | } |
| 113080 | r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1); |
| 113081 | if( addrIsNull==0 ){ |
| 113082 | /* |
| 113083 | ** If the right operand contains a subquery and the left operand does not |
| 113084 | ** and the left operand might be NULL, then check the left operand do |
| 113085 | ** an IsNull check on the left operand before computing the right |
| 113086 | ** operand. |
| 113087 | */ |
| 113088 | if( ExprHasProperty(pExpr->pRight, EP_Subquery) |
| 113089 | && sqlite3ExprCanBeNull(pExpr->pLeft) |
| 113090 | ){ |
| 113091 | addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1); |
| @@ -114645,11 +115129,10 @@ | |
| 114645 | int destIfNull /* Jump here if the results are unknown due to NULLs */ |
| 114646 | ){ |
| 114647 | int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ |
| 114648 | int eType; /* Type of the RHS */ |
| 114649 | int rLhs; /* Register(s) holding the LHS values */ |
| 114650 | int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ |
| 114651 | Vdbe *v; /* Statement under construction */ |
| 114652 | int *aiMap = 0; /* Map from vector field to index column */ |
| 114653 | char *zAff = 0; /* Affinity string for comparisons */ |
| 114654 | int nVector; /* Size of vectors for this IN operator */ |
| 114655 | int iDummy; /* Dummy parameter to exprCodeVector() */ |
| @@ -114708,23 +115191,12 @@ | |
| 114708 | ** Avoid factoring the LHS of the IN(...) expression out of the loop, |
| 114709 | ** even if it is constant, as OP_Affinity may be used on the register |
| 114710 | ** by code generated below. */ |
| 114711 | assert( pParse->okConstFactor==okConstFactor ); |
| 114712 | pParse->okConstFactor = 0; |
| 114713 | rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); |
| 114714 | pParse->okConstFactor = okConstFactor; |
| 114715 | for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ |
| 114716 | if( i==nVector ){ |
| 114717 | /* LHS fields are not reordered */ |
| 114718 | rLhs = rLhsOrig; |
| 114719 | }else{ |
| 114720 | /* Need to reorder the LHS fields according to aiMap */ |
| 114721 | rLhs = sqlite3GetTempRange(pParse, nVector); |
| 114722 | for(i=0; i<nVector; i++){ |
| 114723 | sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); |
| 114724 | } |
| 114725 | } |
| 114726 | |
| 114727 | /* If sqlite3FindInIndex() did not find or create an index that is |
| 114728 | ** suitable for evaluating the IN operator, then evaluate using a |
| 114729 | ** sequence of comparisons. |
| 114730 | ** |
| @@ -114735,10 +115207,11 @@ | |
| 114735 | CollSeq *pColl; |
| 114736 | int labelOk = sqlite3VdbeMakeLabel(pParse); |
| 114737 | int r2, regToFree; |
| 114738 | int regCkNull = 0; |
| 114739 | int ii; |
| 114740 | assert( ExprUseXList(pExpr) ); |
| 114741 | pList = pExpr->x.pList; |
| 114742 | pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); |
| 114743 | if( destIfNull!=destIfFalse ){ |
| 114744 | regCkNull = sqlite3GetTempReg(pParse); |
| @@ -114775,10 +115248,30 @@ | |
| 114775 | } |
| 114776 | sqlite3VdbeResolveLabel(v, labelOk); |
| 114777 | sqlite3ReleaseTempReg(pParse, regCkNull); |
| 114778 | goto sqlite3ExprCodeIN_finished; |
| 114779 | } |
| 114780 | |
| 114781 | /* Step 2: Check to see if the LHS contains any NULL columns. If the |
| 114782 | ** LHS does contain NULLs then the result must be either FALSE or NULL. |
| 114783 | ** We will then skip the binary search of the RHS. |
| 114784 | */ |
| @@ -114802,15 +115295,15 @@ | |
| 114802 | */ |
| 114803 | if( eType==IN_INDEX_ROWID ){ |
| 114804 | /* In this case, the RHS is the ROWID of table b-tree and so we also |
| 114805 | ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 |
| 114806 | ** into a single opcode. */ |
| 114807 | sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); |
| 114808 | VdbeCoverage(v); |
| 114809 | addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ |
| 114810 | }else{ |
| 114811 | sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); |
| 114812 | if( destIfFalse==destIfNull ){ |
| 114813 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 114814 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 114815 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 114816 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| @@ -114884,11 +115377,10 @@ | |
| 114884 | |
| 114885 | /* Jumps here in order to return true. */ |
| 114886 | sqlite3VdbeJumpHere(v, addrTruthOp); |
| 114887 | |
| 114888 | sqlite3ExprCodeIN_finished: |
| 114889 | if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); |
| 114890 | VdbeComment((v, "end IN expr")); |
| 114891 | sqlite3ExprCodeIN_oom_error: |
| 114892 | sqlite3DbFree(pParse->db, aiMap); |
| 114893 | sqlite3DbFree(pParse->db, zAff); |
| 114894 | } |
| @@ -115442,10 +115934,84 @@ | |
| 115442 | } |
| 115443 | } |
| 115444 | return 0; |
| 115445 | } |
| 115446 | |
| 115447 | |
| 115448 | /* |
| 115449 | ** Generate code into the current Vdbe to evaluate the given |
| 115450 | ** expression. Attempt to store the results in register "target". |
| 115451 | ** Return the register where results are stored. |
| @@ -115633,10 +116199,16 @@ | |
| 115633 | case TK_STRING: { |
| 115634 | assert( !ExprHasProperty(pExpr, EP_IntValue) ); |
| 115635 | sqlite3VdbeLoadString(v, target, pExpr->u.zToken); |
| 115636 | return target; |
| 115637 | } |
| 115638 | default: { |
| 115639 | /* Make NULL the default case so that if a bug causes an illegal |
| 115640 | ** Expr node to be passed into this function, it will be handled |
| 115641 | ** sanely and not crash. But keep the assert() to bring the problem |
| 115642 | ** to the attention of the developers. */ |
| @@ -115724,16 +116296,18 @@ | |
| 115724 | sqlite3VdbeAddOp2(v, OP_Null, 0, inReg); |
| 115725 | } |
| 115726 | } |
| 115727 | testcase( regFree1==0 ); |
| 115728 | testcase( regFree2==0 ); |
| 115729 | |
| 115730 | } |
| 115731 | break; |
| 115732 | } |
| 115733 | case TK_AND: |
| 115734 | case TK_OR: |
| 115735 | case TK_PLUS: |
| 115736 | case TK_STAR: |
| 115737 | case TK_MINUS: |
| 115738 | case TK_REM: |
| 115739 | case TK_BITAND: |
| @@ -115741,12 +116315,10 @@ | |
| 115741 | case TK_SLASH: |
| 115742 | case TK_LSHIFT: |
| 115743 | case TK_RSHIFT: |
| 115744 | case TK_CONCAT: { |
| 115745 | int addrIsNull; |
| 115746 | assert( TK_AND==OP_And ); testcase( op==TK_AND ); |
| 115747 | assert( TK_OR==OP_Or ); testcase( op==TK_OR ); |
| 115748 | assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); |
| 115749 | assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); |
| 115750 | assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); |
| 115751 | assert( TK_BITAND==OP_BitAnd ); testcase( op==TK_BITAND ); |
| 115752 | assert( TK_BITOR==OP_BitOr ); testcase( op==TK_BITOR ); |
| @@ -116341,10 +116913,29 @@ | |
| 116341 | } |
| 116342 | pParse->pConstExpr = p; |
| 116343 | } |
| 116344 | return regDest; |
| 116345 | } |
| 116346 | |
| 116347 | /* |
| 116348 | ** Generate code to evaluate an expression and store the results |
| 116349 | ** into a register. Return the register number where the results |
| 116350 | ** are stored. |
| @@ -123872,10 +124463,20 @@ | |
| 123872 | if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ |
| 123873 | Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); |
| 123874 | if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ |
| 123875 | pMod = sqlite3PragmaVtabRegister(db, zName); |
| 123876 | } |
| 123877 | if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ |
| 123878 | testcase( pMod->pEpoTab==0 ); |
| 123879 | return pMod->pEpoTab; |
| 123880 | } |
| 123881 | } |
| @@ -124510,11 +125111,11 @@ | |
| 124510 | */ |
| 124511 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 124512 | int i; |
| 124513 | i16 iCol16; |
| 124514 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 124515 | assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 ); |
| 124516 | iCol16 = iCol; |
| 124517 | for(i=0; i<pIdx->nColumn; i++){ |
| 124518 | if( iCol16==pIdx->aiColumn[i] ){ |
| 124519 | return i; |
| 124520 | } |
| @@ -124807,10 +125408,13 @@ | |
| 124807 | sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); |
| 124808 | sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC); |
| 124809 | sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); |
| 124810 | sqlite3VdbeChangeP5(v, OPFLAG_APPEND); |
| 124811 | sqlite3VdbeAddOp0(v, OP_Close); |
| 124812 | } |
| 124813 | |
| 124814 | /* Normal (non-error) return. */ |
| 124815 | return; |
| 124816 | |
| @@ -129102,18 +129706,23 @@ | |
| 129102 | pKey->aSortFlags[i] = pIdx->aSortOrder[i]; |
| 129103 | assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) ); |
| 129104 | } |
| 129105 | if( pParse->nErr ){ |
| 129106 | assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); |
| 129107 | if( pIdx->bNoQuery==0 ){ |
| 129108 | /* Deactivate the index because it contains an unknown collating |
| 129109 | ** sequence. The only way to reactive the index is to reload the |
| 129110 | ** schema. Adding the missing collating sequence later does not |
| 129111 | ** reactive the index. The application had the chance to register |
| 129112 | ** the missing index using the collation-needed callback. For |
| 129113 | ** simplicity, SQLite will not give the application a second chance. |
| 129114 | */ |
| 129115 | pIdx->bNoQuery = 1; |
| 129116 | pParse->rc = SQLITE_ERROR_RETRY; |
| 129117 | } |
| 129118 | sqlite3KeyInfoUnref(pKey); |
| 129119 | pKey = 0; |
| @@ -131977,11 +132586,11 @@ | |
| 131977 | ** a decoding of those digits into *pVal. Or return false if any |
| 131978 | ** one of the first N characters in z[] is not a hexadecimal digit. |
| 131979 | */ |
| 131980 | static int isNHex(const char *z, int N, u32 *pVal){ |
| 131981 | int i; |
| 131982 | int v = 0; |
| 131983 | for(i=0; i<N; i++){ |
| 131984 | if( !sqlite3Isxdigit(z[i]) ) return 0; |
| 131985 | v = (v<<4) + sqlite3HexToInt(z[i]); |
| 131986 | } |
| 131987 | *pVal = v; |
| @@ -132490,10 +133099,11 @@ | |
| 132490 | int nSep, |
| 132491 | const char *zSep |
| 132492 | ){ |
| 132493 | i64 j, n = 0; |
| 132494 | int i; |
| 132495 | char *z; |
| 132496 | for(i=0; i<argc; i++){ |
| 132497 | n += sqlite3_value_bytes(argv[i]); |
| 132498 | } |
| 132499 | n += (argc-1)*(i64)nSep; |
| @@ -132506,16 +133116,17 @@ | |
| 132506 | for(i=0; i<argc; i++){ |
| 132507 | if( sqlite3_value_type(argv[i])!=SQLITE_NULL ){ |
| 132508 | int k = sqlite3_value_bytes(argv[i]); |
| 132509 | const char *v = (const char*)sqlite3_value_text(argv[i]); |
| 132510 | if( v!=0 ){ |
| 132511 | if( j>0 && nSep>0 ){ |
| 132512 | memcpy(&z[j], zSep, nSep); |
| 132513 | j += nSep; |
| 132514 | } |
| 132515 | memcpy(&z[j], v, k); |
| 132516 | j += k; |
| 132517 | } |
| 132518 | } |
| 132519 | } |
| 132520 | z[j] = 0; |
| 132521 | assert( j<=n ); |
| @@ -133456,10 +134067,456 @@ | |
| 133456 | type0 = sqlite3_value_numeric_type(argv[0]); |
| 133457 | if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; |
| 133458 | x = sqlite3_value_double(argv[0]); |
| 133459 | sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); |
| 133460 | } |
| 133461 | |
| 133462 | #ifdef SQLITE_DEBUG |
| 133463 | /* |
| 133464 | ** Implementation of fpdecode(x,y,z) function. |
| 133465 | ** |
| @@ -133687,10 +134744,25 @@ | |
| 133687 | WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, |
| 133688 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 133689 | WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep, |
| 133690 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 133691 | |
| 133692 | LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133693 | #ifdef SQLITE_CASE_SENSITIVE_LIKE |
| 133694 | LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133695 | LIKEFUNC(like, 3, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 133696 | #else |
| @@ -139184,10 +140256,14 @@ | |
| 139184 | /* Version 3.44.0 and later */ |
| 139185 | void *(*get_clientdata)(sqlite3*,const char*); |
| 139186 | int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); |
| 139187 | /* Version 3.50.0 and later */ |
| 139188 | int (*setlk_timeout)(sqlite3*,int,int); |
| 139189 | }; |
| 139190 | |
| 139191 | /* |
| 139192 | ** This is the function signature used for all extension entry points. It |
| 139193 | ** is also defined in the file "loadext.c". |
| @@ -139519,10 +140595,13 @@ | |
| 139519 | /* Version 3.44.0 and later */ |
| 139520 | #define sqlite3_get_clientdata sqlite3_api->get_clientdata |
| 139521 | #define sqlite3_set_clientdata sqlite3_api->set_clientdata |
| 139522 | /* Version 3.50.0 and later */ |
| 139523 | #define sqlite3_setlk_timeout sqlite3_api->setlk_timeout |
| 139524 | #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ |
| 139525 | |
| 139526 | #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) |
| 139527 | /* This case when the file really is being compiled as a loadable |
| 139528 | ** extension */ |
| @@ -140042,11 +141121,14 @@ | |
| 140042 | sqlite3_stmt_explain, |
| 140043 | /* Version 3.44.0 and later */ |
| 140044 | sqlite3_get_clientdata, |
| 140045 | sqlite3_set_clientdata, |
| 140046 | /* Version 3.50.0 and later */ |
| 140047 | sqlite3_setlk_timeout |
| 140048 | }; |
| 140049 | |
| 140050 | /* True if x is the directory separator character |
| 140051 | */ |
| 140052 | #if SQLITE_OS_WIN |
| @@ -141503,10 +142585,26 @@ | |
| 141503 | addr = sqlite3VdbeAddOp3(v, OP_IfPos, 1, sqlite3VdbeCurrentAddr(v)+2, 1); |
| 141504 | VdbeCoverage(v); |
| 141505 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 141506 | return addr; |
| 141507 | } |
| 141508 | |
| 141509 | /* |
| 141510 | ** Process a pragma statement. |
| 141511 | ** |
| 141512 | ** Pragmas are of this form: |
| @@ -142849,11 +143947,11 @@ | |
| 142849 | pTbls = &db->aDb[i].pSchema->tblHash; |
| 142850 | for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142851 | Table *pTab = sqliteHashData(x); /* Current table */ |
| 142852 | Index *pIdx; /* An index on pTab */ |
| 142853 | int nIdx; /* Number of indexes on pTab */ |
| 142854 | if( pObjTab && pObjTab!=pTab ) continue; |
| 142855 | if( HasRowid(pTab) ) cnt++; |
| 142856 | for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } |
| 142857 | } |
| 142858 | if( cnt==0 ) continue; |
| 142859 | if( pObjTab ) cnt++; |
| @@ -142862,11 +143960,11 @@ | |
| 142862 | cnt = 0; |
| 142863 | if( pObjTab ) aRoot[++cnt] = 0; |
| 142864 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142865 | Table *pTab = sqliteHashData(x); |
| 142866 | Index *pIdx; |
| 142867 | if( pObjTab && pObjTab!=pTab ) continue; |
| 142868 | if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; |
| 142869 | for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ |
| 142870 | aRoot[++cnt] = pIdx->tnum; |
| 142871 | } |
| 142872 | } |
| @@ -142893,11 +143991,11 @@ | |
| 142893 | sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); |
| 142894 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 142895 | int iTab = 0; |
| 142896 | Table *pTab = sqliteHashData(x); |
| 142897 | Index *pIdx; |
| 142898 | if( pObjTab && pObjTab!=pTab ) continue; |
| 142899 | if( HasRowid(pTab) ){ |
| 142900 | iTab = cnt++; |
| 142901 | }else{ |
| 142902 | iTab = cnt; |
| 142903 | for(pIdx=pTab->pIndex; ALWAYS(pIdx); pIdx=pIdx->pNext){ |
| @@ -142929,11 +144027,11 @@ | |
| 142929 | int r1 = -1; |
| 142930 | int bStrict; /* True for a STRICT table */ |
| 142931 | int r2; /* Previous key for WITHOUT ROWID tables */ |
| 142932 | int mxCol; /* Maximum non-virtual column number */ |
| 142933 | |
| 142934 | if( pObjTab && pObjTab!=pTab ) continue; |
| 142935 | if( !IsOrdinaryTable(pTab) ) continue; |
| 142936 | if( isQuick || HasRowid(pTab) ){ |
| 142937 | pPk = 0; |
| 142938 | r2 = 0; |
| 142939 | }else{ |
| @@ -143253,11 +144351,11 @@ | |
| 143253 | */ |
| 143254 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 143255 | Table *pTab = sqliteHashData(x); |
| 143256 | sqlite3_vtab *pVTab; |
| 143257 | int a1; |
| 143258 | if( pObjTab && pObjTab!=pTab ) continue; |
| 143259 | if( IsOrdinaryTable(pTab) ) continue; |
| 143260 | if( !IsVirtual(pTab) ) continue; |
| 143261 | if( pTab->nCol<=0 ){ |
| 143262 | const char *zMod = pTab->u.vtab.azArg[0]; |
| 143263 | if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; |
| @@ -143485,10 +144583,12 @@ | |
| 143485 | eMode = SQLITE_CHECKPOINT_FULL; |
| 143486 | }else if( sqlite3StrICmp(zRight, "restart")==0 ){ |
| 143487 | eMode = SQLITE_CHECKPOINT_RESTART; |
| 143488 | }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ |
| 143489 | eMode = SQLITE_CHECKPOINT_TRUNCATE; |
| 143490 | } |
| 143491 | } |
| 143492 | pParse->nMem = 3; |
| 143493 | sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); |
| 143494 | sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); |
| @@ -145051,13 +146151,15 @@ | |
| 145051 | ** or encounters a permanent error. A schema problem after one schema |
| 145052 | ** reset is considered a permanent error. */ |
| 145053 | rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); |
| 145054 | assert( rc==SQLITE_OK || *ppStmt==0 ); |
| 145055 | if( rc==SQLITE_OK || db->mallocFailed ) break; |
| 145056 | }while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY) |
| 145057 | || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); |
| 145058 | sqlite3BtreeLeaveAll(db); |
| 145059 | rc = sqlite3ApiExit(db, rc); |
| 145060 | assert( (rc&db->errMask)==rc ); |
| 145061 | db->busyHandler.nBusy = 0; |
| 145062 | sqlite3_mutex_leave(db->mutex); |
| 145063 | assert( rc==SQLITE_OK || (*ppStmt)==0 ); |
| @@ -145727,12 +146829,11 @@ | |
| 145727 | while( p ){ |
| 145728 | ExprSetProperty(p, joinFlag); |
| 145729 | assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); |
| 145730 | ExprSetVVAProperty(p, EP_NoReduce); |
| 145731 | p->w.iJoin = iTable; |
| 145732 | if( p->op==TK_FUNCTION ){ |
| 145733 | assert( ExprUseXList(p) ); |
| 145734 | if( p->x.pList ){ |
| 145735 | int i; |
| 145736 | for(i=0; i<p->x.pList->nExpr; i++){ |
| 145737 | sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); |
| 145738 | } |
| @@ -145944,10 +147045,11 @@ | |
| 145944 | else if( pRight->u3.pOn ){ |
| 145945 | sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); |
| 145946 | p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); |
| 145947 | pRight->u3.pOn = 0; |
| 145948 | pRight->fg.isOn = 1; |
| 145949 | } |
| 145950 | } |
| 145951 | return 0; |
| 145952 | } |
| 145953 | |
| @@ -146830,11 +147932,14 @@ | |
| 146830 | ** Allocate a KeyInfo object sufficient for an index of N key columns and |
| 146831 | ** X extra columns. |
| 146832 | */ |
| 146833 | SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ |
| 146834 | int nExtra = (N+X)*(sizeof(CollSeq*)+1); |
| 146835 | KeyInfo *p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); |
| 146836 | if( p ){ |
| 146837 | p->aSortFlags = (u8*)&p->aColl[N+X]; |
| 146838 | p->nKeyField = (u16)N; |
| 146839 | p->nAllField = (u16)(N+X); |
| 146840 | p->enc = ENC(db); |
| @@ -149149,11 +150254,11 @@ | |
| 149149 | ** expressions in pEList. |
| 149150 | ** |
| 149151 | ** ## About "isOuterJoin": |
| 149152 | ** |
| 149153 | ** The isOuterJoin column indicates that the replacement will occur into a |
| 149154 | ** position in the parent that NULL-able due to an OUTER JOIN. Either the |
| 149155 | ** target slot in the parent is the right operand of a LEFT JOIN, or one of |
| 149156 | ** the left operands of a RIGHT JOIN. In either case, we need to potentially |
| 149157 | ** bypass the substituted expression with OP_IfNullRow. |
| 149158 | ** |
| 149159 | ** Suppose the original expression is an integer constant. Even though the table |
| @@ -149987,21 +151092,16 @@ | |
| 149987 | ** elements we are now copying in. |
| 149988 | */ |
| 149989 | pSub = pSub1; |
| 149990 | for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ |
| 149991 | int nSubSrc; |
| 149992 | u8 jointype = 0; |
| 149993 | u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; |
| 149994 | assert( pSub!=0 ); |
| 149995 | pSubSrc = pSub->pSrc; /* FROM clause of subquery */ |
| 149996 | nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ |
| 149997 | pSrc = pParent->pSrc; /* FROM clause of the outer query */ |
| 149998 | |
| 149999 | if( pParent==p ){ |
| 150000 | jointype = pSubitem->fg.jointype; /* First time through the loop */ |
| 150001 | } |
| 150002 | |
| 150003 | /* The subquery uses a single slot of the FROM clause of the outer |
| 150004 | ** query. If the subquery has more than one element in its FROM clause, |
| 150005 | ** then expand the outer query to make space for it to hold all elements |
| 150006 | ** of the subquery. |
| 150007 | ** |
| @@ -150017,10 +151117,11 @@ | |
| 150017 | */ |
| 150018 | if( nSubSrc>1 ){ |
| 150019 | pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); |
| 150020 | if( pSrc==0 ) break; |
| 150021 | pParent->pSrc = pSrc; |
| 150022 | } |
| 150023 | |
| 150024 | /* Transfer the FROM clause terms from the subquery into the |
| 150025 | ** outer query. |
| 150026 | */ |
| @@ -150031,15 +151132,14 @@ | |
| 150031 | assert( pItem->fg.isSubquery |
| 150032 | || pItem->fg.fixedSchema |
| 150033 | || pItem->u4.zDatabase==0 ); |
| 150034 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 150035 | *pItem = pSubSrc->a[i]; |
| 150036 | pItem->fg.jointype |= ltorj; |
| 150037 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 150038 | } |
| 150039 | pSrc->a[iFrom].fg.jointype &= JT_LTORJ; |
| 150040 | pSrc->a[iFrom].fg.jointype |= jointype | ltorj; |
| 150041 | |
| 150042 | /* Now begin substituting subquery result set expressions for |
| 150043 | ** references to the iParent in the outer query. |
| 150044 | ** |
| 150045 | ** Example: |
| @@ -152746,10 +153846,11 @@ | |
| 152746 | Select *pSub = pWhere->x.pSelect; |
| 152747 | Expr *pSubWhere = pSub->pWhere; |
| 152748 | if( pSub->pSrc->nSrc==1 |
| 152749 | && (pSub->selFlags & SF_Aggregate)==0 |
| 152750 | && !pSub->pSrc->a[0].fg.isSubquery |
| 152751 | ){ |
| 152752 | memset(pWhere, 0, sizeof(*pWhere)); |
| 152753 | pWhere->op = TK_INTEGER; |
| 152754 | pWhere->u.iValue = 1; |
| 152755 | ExprSetProperty(pWhere, EP_IntValue); |
| @@ -152774,10 +153875,119 @@ | |
| 152774 | existsToJoin(pParse, p, pSubWhere); |
| 152775 | } |
| 152776 | } |
| 152777 | } |
| 152778 | } |
| 152779 | |
| 152780 | /* |
| 152781 | ** Generate byte-code for the SELECT statement given in the p argument. |
| 152782 | ** |
| 152783 | ** The results are returned according to the SelectDest structure. |
| @@ -152901,10 +154111,22 @@ | |
| 152901 | if( sqlite3TreeTrace & 0x10 ){ |
| 152902 | TREETRACE(0x10,pParse,p, ("after name resolution:\n")); |
| 152903 | sqlite3TreeViewSelect(0, p, 0); |
| 152904 | } |
| 152905 | #endif |
| 152906 | |
| 152907 | /* If the SF_UFSrcCheck flag is set, then this function is being called |
| 152908 | ** as part of populating the temp table for an UPDATE...FROM statement. |
| 152909 | ** In this case, it is an error if the target object (pSrc->a[0]) name |
| 152910 | ** or alias is duplicated within FROM clause (pSrc->a[1..n]). |
| @@ -153766,10 +154988,11 @@ | |
| 153766 | iBMem = pParse->nMem + 1; |
| 153767 | pParse->nMem += pGroupBy->nExpr; |
| 153768 | sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag); |
| 153769 | VdbeComment((v, "clear abort flag")); |
| 153770 | sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1); |
| 153771 | |
| 153772 | /* Begin a loop that will extract all source rows in GROUP BY order. |
| 153773 | ** This might involve two separate loops with an OP_Sort in between, or |
| 153774 | ** it might be a single loop that uses an index to extract information |
| 153775 | ** in the right order to begin with. |
| @@ -155466,11 +156689,14 @@ | |
| 155466 | sqlite3 *db = pParse->db; |
| 155467 | ExprList *pNew; |
| 155468 | Returning *pReturning; |
| 155469 | Select sSelect; |
| 155470 | SrcList *pFrom; |
| 155471 | u8 fromSpace[SZ_SRCLIST_1]; |
| 155472 | |
| 155473 | assert( v!=0 ); |
| 155474 | if( !pParse->bReturning ){ |
| 155475 | /* This RETURNING trigger must be for a different statement as |
| 155476 | ** this statement lacks a RETURNING clause. */ |
| @@ -155482,12 +156708,12 @@ | |
| 155482 | if( pTrigger != &(pReturning->retTrig) ){ |
| 155483 | /* This RETURNING trigger is for a different statement */ |
| 155484 | return; |
| 155485 | } |
| 155486 | memset(&sSelect, 0, sizeof(sSelect)); |
| 155487 | pFrom = (SrcList*)fromSpace; |
| 155488 | memset(pFrom, 0, SZ_SRCLIST_1); |
| 155489 | sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); |
| 155490 | sSelect.pSrc = pFrom; |
| 155491 | pFrom->nSrc = 1; |
| 155492 | pFrom->a[0].pSTab = pTab; |
| 155493 | pFrom->a[0].zName = pTab->zName; /* tag-20240424-1 */ |
| @@ -163014,11 +164240,14 @@ | |
| 163014 | WhereClause *pWC = &pWInfo->sWC; |
| 163015 | WhereInfo *pSubWInfo; |
| 163016 | WhereLoop *pLoop = pLevel->pWLoop; |
| 163017 | SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; |
| 163018 | SrcList *pFrom; |
| 163019 | u8 fromSpace[SZ_SRCLIST_1]; |
| 163020 | Bitmask mAll = 0; |
| 163021 | int k; |
| 163022 | |
| 163023 | ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pSTab->zName)); |
| 163024 | sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, |
| @@ -163058,11 +164287,11 @@ | |
| 163058 | if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; |
| 163059 | pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, |
| 163060 | sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); |
| 163061 | } |
| 163062 | } |
| 163063 | pFrom = (SrcList*)fromSpace; |
| 163064 | pFrom->nSrc = 1; |
| 163065 | pFrom->nAlloc = 1; |
| 163066 | memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem)); |
| 163067 | pFrom->a[0].fg.jointype = 0; |
| 163068 | assert( pParse->withinRJSubrtn < 100 ); |
| @@ -164275,25 +165504,11 @@ | |
| 164275 | Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); |
| 164276 | if( ExprHasProperty(pExpr, EP_OuterON) ){ |
| 164277 | prereqAll |= x; |
| 164278 | extraRight = x-1; /* ON clause terms may not be used with an index |
| 164279 | ** on left table of a LEFT JOIN. Ticket #3015 */ |
| 164280 | if( (prereqAll>>1)>=x ){ |
| 164281 | sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); |
| 164282 | return; |
| 164283 | } |
| 164284 | }else if( (prereqAll>>1)>=x ){ |
| 164285 | /* The ON clause of an INNER JOIN references a table to its right. |
| 164286 | ** Most other SQL database engines raise an error. But SQLite versions |
| 164287 | ** 3.0 through 3.38 just put the ON clause constraint into the WHERE |
| 164288 | ** clause and carried on. Beginning with 3.39, raise an error only |
| 164289 | ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite |
| 164290 | ** more like other systems, and also preserves legacy. */ |
| 164291 | if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ |
| 164292 | sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); |
| 164293 | return; |
| 164294 | } |
| 164295 | ExprClearProperty(pExpr, EP_InnerON); |
| 164296 | } |
| 164297 | } |
| 164298 | pTerm->prereqAll = prereqAll; |
| 164299 | pTerm->leftCursor = -1; |
| @@ -169084,10 +170299,11 @@ | |
| 169084 | if( pProbe->bNoQuery ) continue; |
| 169085 | rSize = pProbe->aiRowLogEst[0]; |
| 169086 | pNew->u.btree.nEq = 0; |
| 169087 | pNew->u.btree.nBtm = 0; |
| 169088 | pNew->u.btree.nTop = 0; |
| 169089 | pNew->nSkip = 0; |
| 169090 | pNew->nLTerm = 0; |
| 169091 | pNew->iSortIdx = 0; |
| 169092 | pNew->rSetup = 0; |
| 169093 | pNew->prereq = mPrereq; |
| @@ -170150,14 +171366,16 @@ | |
| 170150 | if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ |
| 170151 | if( pLoop->u.vtab.isOrdered |
| 170152 | && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) |
| 170153 | ){ |
| 170154 | obSat = obDone; |
| 170155 | } |
| 170156 | break; |
| 170157 | }else if( wctrlFlags & WHERE_DISTINCTBY ){ |
| 170158 | pLoop->u.btree.nDistinctCol = 0; |
| 170159 | } |
| 170160 | iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; |
| 170161 | |
| 170162 | /* Mark off any ORDER BY term X that is a column in the table of |
| 170163 | ** the current loop for which there is term in the WHERE |
| @@ -170897,21 +172115,28 @@ | |
| 170897 | |
| 170898 | /* Check to see if pWLoop should be added to the set of |
| 170899 | ** mxChoice best-so-far paths. |
| 170900 | ** |
| 170901 | ** First look for an existing path among best-so-far paths |
| 170902 | ** that covers the same set of loops and has the same isOrdered |
| 170903 | ** setting as the current path candidate. |
| 170904 | ** |
| 170905 | ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent |
| 170906 | ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range |
| 170907 | ** of legal values for isOrdered, -1..64. |
| 170908 | */ |
| 170909 | testcase( nTo==0 ); |
| 170910 | for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ |
| 170911 | if( pTo->maskLoop==maskNew |
| 170912 | && ((pTo->isOrdered^isOrdered)&0x80)==0 |
| 170913 | ){ |
| 170914 | testcase( jj==nTo-1 ); |
| 170915 | break; |
| 170916 | } |
| 170917 | } |
| @@ -171062,15 +172287,14 @@ | |
| 171062 | sqlite3ErrorMsg(pParse, "no query solution"); |
| 171063 | sqlite3StackFreeNN(pParse->db, pSpace); |
| 171064 | return SQLITE_ERROR; |
| 171065 | } |
| 171066 | |
| 171067 | /* Find the lowest cost path. pFrom will be left pointing to that path */ |
| 171068 | pFrom = aFrom; |
| 171069 | for(ii=1; ii<nFrom; ii++){ |
| 171070 | if( pFrom->rCost>aFrom[ii].rCost ) pFrom = &aFrom[ii]; |
| 171071 | } |
| 171072 | assert( pWInfo->nLevel==nLoop ); |
| 171073 | /* Load the lowest cost path into pWInfo */ |
| 171074 | for(iLoop=0; iLoop<nLoop; iLoop++){ |
| 171075 | WhereLevel *pLevel = pWInfo->a + iLoop; |
| 171076 | pLevel->pWLoop = pWLoop = pFrom->aLoop[iLoop]; |
| @@ -171199,11 +172423,14 @@ | |
| 171199 | int once = 0; |
| 171200 | #endif |
| 171201 | for(i=0; i<pWInfo->nLevel; i++){ |
| 171202 | WhereLoop *p = pWInfo->a[i].pWLoop; |
| 171203 | if( p==0 ) break; |
| 171204 | if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue; |
| 171205 | if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ |
| 171206 | u8 iTab = p->iTab; |
| 171207 | WhereLoop *pLoop; |
| 171208 | for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ |
| 171209 | if( pLoop->iTab!=iTab ) continue; |
| @@ -175312,11 +176539,11 @@ | |
| 175312 | ** RETURN_ROW |
| 175313 | ** |
| 175314 | ** |
| 175315 | ** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING |
| 175316 | ** |
| 175317 | ** ... loop started by sqlite3WhereBegin() ... |
| 175318 | ** if( new partition ){ |
| 175319 | ** Gosub flush |
| 175320 | ** } |
| 175321 | ** Insert new row into eph table. |
| 175322 | ** if( first row of partition ){ |
| @@ -175830,10 +177057,16 @@ | |
| 175830 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 175831 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1); |
| 175832 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); |
| 175833 | }else{ |
| 175834 | assert( pMWin->eEnd==TK_FOLLOWING ); |
| 175835 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 175836 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); |
| 175837 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); |
| 175838 | } |
| 175839 | sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); |
| @@ -183317,13 +184550,10 @@ | |
| 183317 | #endif |
| 183318 | #ifdef SQLITE_ENABLE_DBSTAT_VTAB |
| 183319 | sqlite3DbstatRegister, |
| 183320 | #endif |
| 183321 | sqlite3TestExtInit, |
| 183322 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 183323 | sqlite3JsonTableFunctions, |
| 183324 | #endif |
| 183325 | #ifdef SQLITE_ENABLE_STMTVTAB |
| 183326 | sqlite3StmtVtabInit, |
| 183327 | #endif |
| 183328 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 183329 | sqlite3VdbeBytecodeVtabInit, |
| @@ -184775,10 +186005,13 @@ | |
| 184775 | for(i=0; i<2 && zName==0; i++, rc &= 0xff){ |
| 184776 | switch( rc ){ |
| 184777 | case SQLITE_OK: zName = "SQLITE_OK"; break; |
| 184778 | case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; |
| 184779 | case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; |
| 184780 | case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; |
| 184781 | case SQLITE_PERM: zName = "SQLITE_PERM"; break; |
| 184782 | case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; |
| 184783 | case SQLITE_ABORT_ROLLBACK: zName = "SQLITE_ABORT_ROLLBACK"; break; |
| 184784 | case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; |
| @@ -185955,10 +187188,33 @@ | |
| 185955 | } |
| 185956 | } |
| 185957 | sqlite3_mutex_leave(db->mutex); |
| 185958 | return z; |
| 185959 | } |
| 185960 | |
| 185961 | /* |
| 185962 | ** Return the byte offset of the most recent error |
| 185963 | */ |
| 185964 | SQLITE_API int sqlite3_error_offset(sqlite3 *db){ |
| @@ -187780,17 +189036,19 @@ | |
| 187780 | case SQLITE_TESTCTRL_ISINIT: { |
| 187781 | if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; |
| 187782 | break; |
| 187783 | } |
| 187784 | |
| 187785 | /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); |
| 187786 | ** |
| 187787 | ** This test control is used to create imposter tables. "db" is a pointer |
| 187788 | ** to the database connection. dbName is the database name (ex: "main" or |
| 187789 | ** "temp") which will receive the imposter. "onOff" turns imposter mode on |
| 187790 | ** or off. "tnum" is the root page of the b-tree to which the imposter |
| 187791 | ** table should connect. |
| 187792 | ** |
| 187793 | ** Enable imposter mode only when the schema has already been parsed. Then |
| 187794 | ** run a single CREATE TABLE statement to construct the imposter table in |
| 187795 | ** the parsed schema. Then turn imposter mode back off again. |
| 187796 | ** |
| @@ -189023,21 +190281,24 @@ | |
| 189023 | ** |
| 189024 | */ |
| 189025 | #ifndef _FTSINT_H |
| 189026 | #define _FTSINT_H |
| 189027 | |
| 189028 | /* #include <assert.h> */ |
| 189029 | /* #include <stdlib.h> */ |
| 189030 | /* #include <stddef.h> */ |
| 189031 | /* #include <stdio.h> */ |
| 189032 | /* #include <string.h> */ |
| 189033 | /* #include <stdarg.h> */ |
| 189034 | |
| 189035 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 189036 | # define NDEBUG 1 |
| 189037 | #endif |
| 189038 | |
| 189039 | /* FTS3/FTS4 require virtual tables */ |
| 189040 | #ifdef SQLITE_OMIT_VIRTUALTABLE |
| 189041 | # undef SQLITE_ENABLE_FTS3 |
| 189042 | # undef SQLITE_ENABLE_FTS4 |
| 189043 | #endif |
| @@ -189476,17 +190737,10 @@ | |
| 189476 | /* |
| 189477 | ** Macro used to suppress compiler warnings for unused parameters. |
| 189478 | */ |
| 189479 | #define UNUSED_PARAMETER(x) (void)(x) |
| 189480 | |
| 189481 | /* |
| 189482 | ** Activate assert() only if SQLITE_TEST is enabled. |
| 189483 | */ |
| 189484 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 189485 | # define NDEBUG 1 |
| 189486 | #endif |
| 189487 | |
| 189488 | /* |
| 189489 | ** The TESTONLY macro is used to enclose variable declarations or |
| 189490 | ** other bits of code that are needed to support the arguments |
| 189491 | ** within testcase() and assert() macros. |
| 189492 | */ |
| @@ -203757,12 +205011,12 @@ | |
| 203757 | /* |
| 203758 | ** An object of this type contains the state required to create or append |
| 203759 | ** to an appendable b-tree segment. |
| 203760 | */ |
| 203761 | struct IncrmergeWriter { |
| 203762 | int nLeafEst; /* Space allocated for leaf blocks */ |
| 203763 | int nWork; /* Number of leaf pages flushed */ |
| 203764 | sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ |
| 203765 | int iIdx; /* Index of *output* segment in iAbsLevel+1 */ |
| 203766 | sqlite3_int64 iStart; /* Block number of first allocated block */ |
| 203767 | sqlite3_int64 iEnd; /* Block number of last allocated block */ |
| 203768 | sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ |
| @@ -204504,21 +205758,21 @@ | |
| 204504 | Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */ |
| 204505 | IncrmergeWriter *pWriter /* Populate this object */ |
| 204506 | ){ |
| 204507 | int rc; /* Return Code */ |
| 204508 | int i; /* Iterator variable */ |
| 204509 | int nLeafEst = 0; /* Blocks allocated for leaf nodes */ |
| 204510 | sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ |
| 204511 | sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ |
| 204512 | |
| 204513 | /* Calculate nLeafEst. */ |
| 204514 | rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0); |
| 204515 | if( rc==SQLITE_OK ){ |
| 204516 | sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); |
| 204517 | sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); |
| 204518 | if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ |
| 204519 | nLeafEst = sqlite3_column_int(pLeafEst, 0); |
| 204520 | } |
| 204521 | rc = sqlite3_reset(pLeafEst); |
| 204522 | } |
| 204523 | if( rc!=SQLITE_OK ) return rc; |
| 204524 | |
| @@ -205897,14 +207151,10 @@ | |
| 205897 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) |
| 205898 | |
| 205899 | /* #include <string.h> */ |
| 205900 | /* #include <assert.h> */ |
| 205901 | |
| 205902 | #ifndef SQLITE_AMALGAMATION |
| 205903 | typedef sqlite3_int64 i64; |
| 205904 | #endif |
| 205905 | |
| 205906 | /* |
| 205907 | ** Characters that may appear in the second argument to matchinfo(). |
| 205908 | */ |
| 205909 | #define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ |
| 205910 | #define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ |
| @@ -210754,11 +212004,11 @@ | |
| 210754 | switch( (u8)zIn[1] ){ |
| 210755 | case '\'': |
| 210756 | jsonAppendChar(pOut, '\''); |
| 210757 | break; |
| 210758 | case 'v': |
| 210759 | jsonAppendRawNZ(pOut, "\\u0009", 6); |
| 210760 | break; |
| 210761 | case 'x': |
| 210762 | if( sz2<4 ){ |
| 210763 | pOut->eErr |= JSTRING_MALFORMED; |
| 210764 | sz2 = 2; |
| @@ -211604,23 +212854,31 @@ | |
| 211604 | /* |
| 211605 | ** Return the value of the BLOB node at index i. |
| 211606 | ** |
| 211607 | ** If the value is a primitive, return it as an SQL value. |
| 211608 | ** If the value is an array or object, return it as either |
| 211609 | ** JSON text or the BLOB encoding, depending on the JSON_B flag |
| 211610 | ** on the userdata. |
| 211611 | */ |
| 211612 | static void jsonReturnFromBlob( |
| 211613 | JsonParse *pParse, /* Complete JSON parse tree */ |
| 211614 | u32 i, /* Index of the node */ |
| 211615 | sqlite3_context *pCtx, /* Return value for this function */ |
| 211616 | int textOnly /* return text JSON. Disregard user-data */ |
| 211617 | ){ |
| 211618 | u32 n, sz; |
| 211619 | int rc; |
| 211620 | sqlite3 *db = sqlite3_context_db_handle(pCtx); |
| 211621 | |
| 211622 | n = jsonbPayloadSize(pParse, i, &sz); |
| 211623 | if( n==0 ){ |
| 211624 | sqlite3_result_error(pCtx, "malformed JSON", -1); |
| 211625 | return; |
| 211626 | } |
| @@ -211657,11 +212915,23 @@ | |
| 211657 | z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); |
| 211658 | if( z==0 ) goto returnfromblob_oom; |
| 211659 | rc = sqlite3DecOrHexToI64(z, &iRes); |
| 211660 | sqlite3DbFree(db, z); |
| 211661 | if( rc==0 ){ |
| 211662 | sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); |
| 211663 | }else if( rc==3 && bNeg ){ |
| 211664 | sqlite3_result_int64(pCtx, SMALLEST_INT64); |
| 211665 | }else if( rc==1 ){ |
| 211666 | goto returnfromblob_malformed; |
| 211667 | }else{ |
| @@ -211735,12 +213005,18 @@ | |
| 211735 | sqlite3_result_text(pCtx, zOut, iOut, SQLITE_DYNAMIC); |
| 211736 | break; |
| 211737 | } |
| 211738 | case JSONB_ARRAY: |
| 211739 | case JSONB_OBJECT: { |
| 211740 | int flags = textOnly ? 0 : SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)); |
| 211741 | if( flags & JSON_BLOB ){ |
| 211742 | sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); |
| 211743 | }else{ |
| 211744 | jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); |
| 211745 | } |
| 211746 | break; |
| @@ -213383,10 +214659,11 @@ | |
| 213383 | u32 i; /* Index in sParse.aBlob[] of current row */ |
| 213384 | u32 iEnd; /* EOF when i equals or exceeds this value */ |
| 213385 | u32 nRoot; /* Size of the root path in bytes */ |
| 213386 | u8 eType; /* Type of the container for element i */ |
| 213387 | u8 bRecursive; /* True for json_tree(). False for json_each() */ |
| 213388 | u32 nParent; /* Current nesting depth */ |
| 213389 | u32 nParentAlloc; /* Space allocated for aParent[] */ |
| 213390 | JsonParent *aParent; /* Parent elements of i */ |
| 213391 | sqlite3 *db; /* Database connection */ |
| 213392 | JsonString path; /* Current path */ |
| @@ -213394,10 +214671,12 @@ | |
| 213394 | }; |
| 213395 | typedef struct JsonEachConnection JsonEachConnection; |
| 213396 | struct JsonEachConnection { |
| 213397 | sqlite3_vtab base; /* Base class - must be first */ |
| 213398 | sqlite3 *db; /* Database connection */ |
| 213399 | }; |
| 213400 | |
| 213401 | |
| 213402 | /* Constructor for the json_each virtual table */ |
| 213403 | static int jsonEachConnect( |
| @@ -213436,10 +214715,12 @@ | |
| 213436 | pNew = (JsonEachConnection*)sqlite3DbMallocZero(db, sizeof(*pNew)); |
| 213437 | *ppVtab = (sqlite3_vtab*)pNew; |
| 213438 | if( pNew==0 ) return SQLITE_NOMEM; |
| 213439 | sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
| 213440 | pNew->db = db; |
| 213441 | } |
| 213442 | return rc; |
| 213443 | } |
| 213444 | |
| 213445 | /* destructor for json_each virtual table */ |
| @@ -213447,34 +214728,26 @@ | |
| 213447 | JsonEachConnection *p = (JsonEachConnection*)pVtab; |
| 213448 | sqlite3DbFree(p->db, pVtab); |
| 213449 | return SQLITE_OK; |
| 213450 | } |
| 213451 | |
| 213452 | /* constructor for a JsonEachCursor object for json_each(). */ |
| 213453 | static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
| 213454 | JsonEachConnection *pVtab = (JsonEachConnection*)p; |
| 213455 | JsonEachCursor *pCur; |
| 213456 | |
| 213457 | UNUSED_PARAMETER(p); |
| 213458 | pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); |
| 213459 | if( pCur==0 ) return SQLITE_NOMEM; |
| 213460 | pCur->db = pVtab->db; |
| 213461 | jsonStringZero(&pCur->path); |
| 213462 | *ppCursor = &pCur->base; |
| 213463 | return SQLITE_OK; |
| 213464 | } |
| 213465 | |
| 213466 | /* constructor for a JsonEachCursor object for json_tree(). */ |
| 213467 | static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
| 213468 | int rc = jsonEachOpenEach(p, ppCursor); |
| 213469 | if( rc==SQLITE_OK ){ |
| 213470 | JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor; |
| 213471 | pCur->bRecursive = 1; |
| 213472 | } |
| 213473 | return rc; |
| 213474 | } |
| 213475 | |
| 213476 | /* Reset a JsonEachCursor back to its original state. Free any memory |
| 213477 | ** held. */ |
| 213478 | static void jsonEachCursorReset(JsonEachCursor *p){ |
| 213479 | jsonParseReset(&p->sParse); |
| 213480 | jsonStringReset(&p->path); |
| @@ -213675,11 +214948,11 @@ | |
| 213675 | } |
| 213676 | break; |
| 213677 | } |
| 213678 | case JEACH_VALUE: { |
| 213679 | u32 i = jsonSkipLabel(p); |
| 213680 | jsonReturnFromBlob(&p->sParse, i, ctx, 1); |
| 213681 | if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ |
| 213682 | sqlite3_result_subtype(ctx, JSON_SUBTYPE); |
| 213683 | } |
| 213684 | break; |
| 213685 | } |
| @@ -213919,40 +215192,11 @@ | |
| 213919 | 0, /* xCreate */ |
| 213920 | jsonEachConnect, /* xConnect */ |
| 213921 | jsonEachBestIndex, /* xBestIndex */ |
| 213922 | jsonEachDisconnect, /* xDisconnect */ |
| 213923 | 0, /* xDestroy */ |
| 213924 | jsonEachOpenEach, /* xOpen - open a cursor */ |
| 213925 | jsonEachClose, /* xClose - close a cursor */ |
| 213926 | jsonEachFilter, /* xFilter - configure scan constraints */ |
| 213927 | jsonEachNext, /* xNext - advance a cursor */ |
| 213928 | jsonEachEof, /* xEof - check for end of scan */ |
| 213929 | jsonEachColumn, /* xColumn - read data */ |
| 213930 | jsonEachRowid, /* xRowid - read data */ |
| 213931 | 0, /* xUpdate */ |
| 213932 | 0, /* xBegin */ |
| 213933 | 0, /* xSync */ |
| 213934 | 0, /* xCommit */ |
| 213935 | 0, /* xRollback */ |
| 213936 | 0, /* xFindMethod */ |
| 213937 | 0, /* xRename */ |
| 213938 | 0, /* xSavepoint */ |
| 213939 | 0, /* xRelease */ |
| 213940 | 0, /* xRollbackTo */ |
| 213941 | 0, /* xShadowName */ |
| 213942 | 0 /* xIntegrity */ |
| 213943 | }; |
| 213944 | |
| 213945 | /* The methods of the json_tree virtual table. */ |
| 213946 | static sqlite3_module jsonTreeModule = { |
| 213947 | 0, /* iVersion */ |
| 213948 | 0, /* xCreate */ |
| 213949 | jsonEachConnect, /* xConnect */ |
| 213950 | jsonEachBestIndex, /* xBestIndex */ |
| 213951 | jsonEachDisconnect, /* xDisconnect */ |
| 213952 | 0, /* xDestroy */ |
| 213953 | jsonEachOpenTree, /* xOpen - open a cursor */ |
| 213954 | jsonEachClose, /* xClose - close a cursor */ |
| 213955 | jsonEachFilter, /* xFilter - configure scan constraints */ |
| 213956 | jsonEachNext, /* xNext - advance a cursor */ |
| 213957 | jsonEachEof, /* xEof - check for end of scan */ |
| 213958 | jsonEachColumn, /* xColumn - read data */ |
| @@ -214037,26 +215281,25 @@ | |
| 214037 | #endif |
| 214038 | } |
| 214039 | |
| 214040 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 214041 | /* |
| 214042 | ** Register the JSON table-valued functions |
| 214043 | */ |
| 214044 | SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ |
| 214045 | int rc = SQLITE_OK; |
| 214046 | static const struct { |
| 214047 | const char *zName; |
| 214048 | sqlite3_module *pModule; |
| 214049 | } aMod[] = { |
| 214050 | { "json_each", &jsonEachModule }, |
| 214051 | { "json_tree", &jsonTreeModule }, |
| 214052 | }; |
| 214053 | unsigned int i; |
| 214054 | for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){ |
| 214055 | rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0); |
| 214056 | } |
| 214057 | return rc; |
| 214058 | } |
| 214059 | #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */ |
| 214060 | |
| 214061 | /************** End of json.c ************************************************/ |
| 214062 | /************** Begin file rtree.c *******************************************/ |
| @@ -228289,12 +229532,12 @@ | |
| 228289 | typedef struct DbpageTable DbpageTable; |
| 228290 | typedef struct DbpageCursor DbpageCursor; |
| 228291 | |
| 228292 | struct DbpageCursor { |
| 228293 | sqlite3_vtab_cursor base; /* Base class. Must be first */ |
| 228294 | int pgno; /* Current page number */ |
| 228295 | int mxPgno; /* Last page to visit on this scan */ |
| 228296 | Pager *pPager; /* Pager being read/written */ |
| 228297 | DbPage *pPage1; /* Page 1 of the database */ |
| 228298 | int iDb; /* Index of database to analyze */ |
| 228299 | int szPage; /* Size of each page in bytes */ |
| 228300 | }; |
| @@ -228427,11 +229670,11 @@ | |
| 228427 | if( pCsr==0 ){ |
| 228428 | return SQLITE_NOMEM_BKPT; |
| 228429 | }else{ |
| 228430 | memset(pCsr, 0, sizeof(DbpageCursor)); |
| 228431 | pCsr->base.pVtab = pVTab; |
| 228432 | pCsr->pgno = -1; |
| 228433 | } |
| 228434 | |
| 228435 | *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| 228436 | return SQLITE_OK; |
| 228437 | } |
| @@ -228527,16 +229770,16 @@ | |
| 228527 | ){ |
| 228528 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 228529 | int rc = SQLITE_OK; |
| 228530 | switch( i ){ |
| 228531 | case 0: { /* pgno */ |
| 228532 | sqlite3_result_int(ctx, pCsr->pgno); |
| 228533 | break; |
| 228534 | } |
| 228535 | case 1: { /* data */ |
| 228536 | DbPage *pDbPage = 0; |
| 228537 | if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ |
| 228538 | /* The pending byte page. Assume it is zeroed out. Attempting to |
| 228539 | ** request this page from the page is an SQLITE_CORRUPT error. */ |
| 228540 | sqlite3_result_zeroblob(ctx, pCsr->szPage); |
| 228541 | }else{ |
| 228542 | rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); |
| @@ -228606,14 +229849,14 @@ | |
| 228606 | if( argc==1 ){ |
| 228607 | zErr = "cannot delete"; |
| 228608 | goto update_fail; |
| 228609 | } |
| 228610 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ |
| 228611 | pgno = (Pgno)sqlite3_value_int(argv[2]); |
| 228612 | isInsert = 1; |
| 228613 | }else{ |
| 228614 | pgno = sqlite3_value_int(argv[0]); |
| 228615 | if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ |
| 228616 | zErr = "cannot insert"; |
| 228617 | goto update_fail; |
| 228618 | } |
| 228619 | isInsert = 0; |
| @@ -228744,10 +229987,539 @@ | |
| 228744 | #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| 228745 | SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } |
| 228746 | #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ |
| 228747 | |
| 228748 | /************** End of dbpage.c **********************************************/ |
| 228749 | /************** Begin file sqlite3session.c **********************************/ |
| 228750 | |
| 228751 | #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) |
| 228752 | /* #include "sqlite3session.h" */ |
| 228753 | /* #include <assert.h> */ |
| @@ -231561,10 +233333,23 @@ | |
| 231561 | assert( (a - p->aRecord)==p->nRecord ); |
| 231562 | } |
| 231563 | |
| 231564 | return rc; |
| 231565 | } |
| 231566 | |
| 231567 | /* |
| 231568 | ** Formulate and prepare a SELECT statement to retrieve a row from table |
| 231569 | ** zTab in database zDb based on its primary key. i.e. |
| 231570 | ** |
| @@ -231583,16 +233368,16 @@ | |
| 231583 | const char *zTab, /* Table name */ |
| 231584 | int bRowid, |
| 231585 | int nCol, /* Number of columns in table */ |
| 231586 | const char **azCol, /* Names of table columns */ |
| 231587 | u8 *abPK, /* PRIMARY KEY array */ |
| 231588 | sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ |
| 231589 | ){ |
| 231590 | int rc = SQLITE_OK; |
| 231591 | char *zSql = 0; |
| 231592 | const char *zSep = ""; |
| 231593 | int nSql = -1; |
| 231594 | int i; |
| 231595 | |
| 231596 | SessionBuffer cols = {0, 0, 0}; |
| 231597 | SessionBuffer nooptest = {0, 0, 0}; |
| 231598 | SessionBuffer pkfield = {0, 0, 0}; |
| @@ -231668,11 +233453,11 @@ | |
| 231668 | nSql = buf.nBuf; |
| 231669 | } |
| 231670 | #endif |
| 231671 | |
| 231672 | if( rc==SQLITE_OK ){ |
| 231673 | rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0); |
| 231674 | } |
| 231675 | sqlite3_free(zSql); |
| 231676 | sqlite3_free(nooptest.aBuf); |
| 231677 | sqlite3_free(pkfield.aBuf); |
| 231678 | sqlite3_free(pkvar.aBuf); |
| @@ -231832,11 +233617,11 @@ | |
| 231832 | sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); |
| 231833 | |
| 231834 | /* Build and compile a statement to execute: */ |
| 231835 | if( rc==SQLITE_OK ){ |
| 231836 | rc = sessionSelectStmt(db, 0, pSession->zDb, |
| 231837 | zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel |
| 231838 | ); |
| 231839 | } |
| 231840 | |
| 231841 | nNoop = buf.nBuf; |
| 231842 | for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){ |
| @@ -233041,10 +234826,11 @@ | |
| 233041 | SessionBuffer rebase; /* Rebase information (if any) here */ |
| 233042 | u8 bRebaseStarted; /* If table header is already in rebase */ |
| 233043 | u8 bRebase; /* True to collect rebase information */ |
| 233044 | u8 bIgnoreNoop; /* True to ignore no-op conflicts */ |
| 233045 | int bRowid; |
| 233046 | }; |
| 233047 | |
| 233048 | /* Number of prepared UPDATE statements to cache. */ |
| 233049 | #define SESSION_UPDATE_CACHE_SZ 12 |
| 233050 | |
| @@ -233266,11 +235052,11 @@ | |
| 233266 | } |
| 233267 | sessionAppendStr(&buf, ")", &rc); |
| 233268 | } |
| 233269 | |
| 233270 | if( rc==SQLITE_OK ){ |
| 233271 | rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); |
| 233272 | } |
| 233273 | sqlite3_free(buf.aBuf); |
| 233274 | |
| 233275 | return rc; |
| 233276 | } |
| @@ -233293,11 +235079,11 @@ | |
| 233293 | const char *zTab, /* Table name */ |
| 233294 | SessionApplyCtx *p /* Session changeset-apply context */ |
| 233295 | ){ |
| 233296 | /* TODO */ |
| 233297 | return sessionSelectStmt(db, p->bIgnoreNoop, |
| 233298 | "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect |
| 233299 | ); |
| 233300 | } |
| 233301 | |
| 233302 | /* |
| 233303 | ** Formulate and prepare an INSERT statement to add a record to table zTab. |
| @@ -233330,37 +235116,33 @@ | |
| 233330 | sessionAppendStr(&buf, ", ?", &rc); |
| 233331 | } |
| 233332 | sessionAppendStr(&buf, ")", &rc); |
| 233333 | |
| 233334 | if( rc==SQLITE_OK ){ |
| 233335 | rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); |
| 233336 | } |
| 233337 | sqlite3_free(buf.aBuf); |
| 233338 | return rc; |
| 233339 | } |
| 233340 | |
| 233341 | static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ |
| 233342 | return sqlite3_prepare_v2(db, zSql, -1, pp, 0); |
| 233343 | } |
| 233344 | |
| 233345 | /* |
| 233346 | ** Prepare statements for applying changes to the sqlite_stat1 table. |
| 233347 | ** These are similar to those created by sessionSelectRow(), |
| 233348 | ** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for |
| 233349 | ** other tables. |
| 233350 | */ |
| 233351 | static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ |
| 233352 | int rc = sessionSelectRow(db, "sqlite_stat1", p); |
| 233353 | if( rc==SQLITE_OK ){ |
| 233354 | rc = sessionPrepare(db, &p->pInsert, |
| 233355 | "INSERT INTO main.sqlite_stat1 VALUES(?1, " |
| 233356 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " |
| 233357 | "?3)" |
| 233358 | ); |
| 233359 | } |
| 233360 | if( rc==SQLITE_OK ){ |
| 233361 | rc = sessionPrepare(db, &p->pDelete, |
| 233362 | "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " |
| 233363 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " |
| 233364 | "AND (?4 OR stat IS ?3)" |
| 233365 | ); |
| 233366 | } |
| @@ -233580,11 +235362,11 @@ | |
| 233580 | sqlite3_changeset_iter *pIter, /* Changeset iterator */ |
| 233581 | int(*xConflict)(void *, int, sqlite3_changeset_iter*), |
| 233582 | void *pCtx, /* First argument for conflict handler */ |
| 233583 | int *pbReplace /* OUT: Set to true if PK row is found */ |
| 233584 | ){ |
| 233585 | int res = 0; /* Value returned by conflict handler */ |
| 233586 | int rc; |
| 233587 | int nCol; |
| 233588 | int op; |
| 233589 | const char *zDummy; |
| 233590 | |
| @@ -233601,15 +235383,13 @@ | |
| 233601 | rc = SQLITE_OK; |
| 233602 | } |
| 233603 | |
| 233604 | if( rc==SQLITE_ROW ){ |
| 233605 | /* There exists another row with the new.* primary key. */ |
| 233606 | if( p->bIgnoreNoop |
| 233607 | && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) |
| 233608 | ){ |
| 233609 | res = SQLITE_CHANGESET_OMIT; |
| 233610 | }else{ |
| 233611 | pIter->pConflict = p->pSelect; |
| 233612 | res = xConflict(pCtx, eType, pIter); |
| 233613 | pIter->pConflict = 0; |
| 233614 | } |
| 233615 | rc = sqlite3_reset(p->pSelect); |
| @@ -233619,11 +235399,13 @@ | |
| 233619 | ** to the SessionApplyCtx.constraints buffer. */ |
| 233620 | u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; |
| 233621 | int nBlob = pIter->in.iNext - pIter->in.iCurrent; |
| 233622 | sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); |
| 233623 | return SQLITE_OK; |
| 233624 | }else{ |
| 233625 | /* No other row with the new.* primary key. */ |
| 233626 | res = xConflict(pCtx, eType+1, pIter); |
| 233627 | if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; |
| 233628 | } |
| 233629 | } |
| @@ -233717,11 +235499,11 @@ | |
| 233717 | } |
| 233718 | if( rc!=SQLITE_OK ) return rc; |
| 233719 | |
| 233720 | sqlite3_step(p->pDelete); |
| 233721 | rc = sqlite3_reset(p->pDelete); |
| 233722 | if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ |
| 233723 | rc = sessionConflictHandler( |
| 233724 | SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry |
| 233725 | ); |
| 233726 | }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ |
| 233727 | rc = sessionConflictHandler( |
| @@ -234122,10 +235904,11 @@ | |
| 234122 | } |
| 234123 | } |
| 234124 | |
| 234125 | assert( sApply.bRebase || sApply.rebase.nBuf==0 ); |
| 234126 | if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){ |
| 234127 | *ppRebase = (void*)sApply.rebase.aBuf; |
| 234128 | *pnRebase = sApply.rebase.nBuf; |
| 234129 | sApply.rebase.aBuf = 0; |
| 234130 | } |
| 234131 | sessionUpdateFree(&sApply); |
| @@ -234139,10 +235922,15 @@ | |
| 234139 | if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){ |
| 234140 | assert( db->flags & SQLITE_FkNoAction ); |
| 234141 | db->flags &= ~((u64)SQLITE_FkNoAction); |
| 234142 | db->aDb[0].pSchema->schema_cookie -= 32; |
| 234143 | } |
| 234144 | sqlite3_mutex_leave(sqlite3_db_mutex(db)); |
| 234145 | return rc; |
| 234146 | } |
| 234147 | |
| 234148 | /* |
| @@ -236348,25 +238136,18 @@ | |
| 236348 | ** Constants for the largest and smallest possible 64-bit signed integers. |
| 236349 | */ |
| 236350 | # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) |
| 236351 | # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) |
| 236352 | |
| 236353 | /* The uptr type is an unsigned integer large enough to hold a pointer |
| 236354 | */ |
| 236355 | #if defined(HAVE_STDINT_H) |
| 236356 | typedef uintptr_t uptr; |
| 236357 | #elif SQLITE_PTRSIZE==4 |
| 236358 | typedef u32 uptr; |
| 236359 | #else |
| 236360 | typedef u64 uptr; |
| 236361 | #endif |
| 236362 | |
| 236363 | #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC |
| 236364 | # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) |
| 236365 | #else |
| 236366 | # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) |
| 236367 | #endif |
| 236368 | |
| 236369 | /* |
| 236370 | ** Macros needed to provide flexible arrays in a portable way |
| 236371 | */ |
| 236372 | #ifndef offsetof |
| @@ -237110,11 +238891,11 @@ | |
| 237110 | ** ){ |
| 237111 | ** // The document with rowid iRowid matches the expression! |
| 237112 | ** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); |
| 237113 | ** } |
| 237114 | */ |
| 237115 | static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); |
| 237116 | static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); |
| 237117 | static int sqlite3Fts5ExprEof(Fts5Expr*); |
| 237118 | static i64 sqlite3Fts5ExprRowid(Fts5Expr*); |
| 237119 | |
| 237120 | static void sqlite3Fts5ExprFree(Fts5Expr*); |
| @@ -242679,11 +244460,17 @@ | |
| 242679 | ** equal to iFirst. |
| 242680 | ** |
| 242681 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It |
| 242682 | ** is not considered an error if the query does not match any documents. |
| 242683 | */ |
| 242684 | static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ |
| 242685 | Fts5ExprNode *pRoot = p->pRoot; |
| 242686 | int rc; /* Return code */ |
| 242687 | |
| 242688 | p->pIndex = pIdx; |
| 242689 | p->bDesc = bDesc; |
| @@ -242700,10 +244487,13 @@ | |
| 242700 | |
| 242701 | /* If the iterator is not at a real match, skip forward until it is. */ |
| 242702 | while( pRoot->bNomatch && rc==SQLITE_OK ){ |
| 242703 | assert( pRoot->bEof==0 ); |
| 242704 | rc = fts5ExprNodeNext(p, pRoot, 0, 0); |
| 242705 | } |
| 242706 | return rc; |
| 242707 | } |
| 242708 | |
| 242709 | /* |
| @@ -245876,13 +247666,13 @@ | |
| 245876 | ** backing store corruption. */ |
| 245877 | if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid); |
| 245878 | |
| 245879 | if( rc==SQLITE_OK ){ |
| 245880 | u8 *aOut = 0; /* Read blob data into this buffer */ |
| 245881 | int nByte = sqlite3_blob_bytes(p->pReader); |
| 245882 | int szData = (sizeof(Fts5Data) + 7) & ~7; |
| 245883 | sqlite3_int64 nAlloc = szData + nByte + FTS5_DATA_PADDING; |
| 245884 | pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); |
| 245885 | if( pRet ){ |
| 245886 | pRet->nn = nByte; |
| 245887 | aOut = pRet->p = (u8*)pRet + szData; |
| 245888 | }else{ |
| @@ -251821,15 +253611,18 @@ | |
| 251821 | ** function populates it with the initial structure objects for each index, |
| 251822 | ** and the initial version of the "averages" record (a zero-byte blob). |
| 251823 | */ |
| 251824 | static int sqlite3Fts5IndexReinit(Fts5Index *p){ |
| 251825 | Fts5Structure *pTmp; |
| 251826 | u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; |
| 251827 | fts5StructureInvalidate(p); |
| 251828 | fts5IndexDiscardData(p); |
| 251829 | pTmp = (Fts5Structure*)tmpSpace; |
| 251830 | memset(pTmp, 0, SZ_FTS5STRUCTURE(1)); |
| 251831 | if( p->pConfig->bContentlessDelete ){ |
| 251832 | pTmp->nOriginCntr = 1; |
| 251833 | } |
| 251834 | fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); |
| 251835 | fts5StructureWrite(p, pTmp); |
| @@ -255045,10 +256838,21 @@ | |
| 255045 | { |
| 255046 | pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; |
| 255047 | } |
| 255048 | #endif |
| 255049 | } |
| 255050 | |
| 255051 | static int fts5UsePatternMatch( |
| 255052 | Fts5Config *pConfig, |
| 255053 | struct sqlite3_index_constraint *p |
| 255054 | ){ |
| @@ -255181,11 +256985,11 @@ | |
| 255181 | bSeenRank = 1; |
| 255182 | }else{ |
| 255183 | nSeenMatch++; |
| 255184 | idxStr[iIdxStr++] = 'M'; |
| 255185 | sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); |
| 255186 | idxStr += strlen(&idxStr[iIdxStr]); |
| 255187 | assert( idxStr[iIdxStr]=='\0' ); |
| 255188 | } |
| 255189 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 255190 | pInfo->aConstraintUsage[i].omit = 1; |
| 255191 | } |
| @@ -255200,10 +257004,11 @@ | |
| 255200 | nSeenMatch++; |
| 255201 | }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ |
| 255202 | idxStr[iIdxStr++] = '='; |
| 255203 | bSeenEq = 1; |
| 255204 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 255205 | } |
| 255206 | } |
| 255207 | } |
| 255208 | |
| 255209 | if( bSeenEq==0 ){ |
| @@ -255247,21 +257052,25 @@ | |
| 255247 | } |
| 255248 | } |
| 255249 | |
| 255250 | /* Calculate the estimated cost based on the flags set in idxFlags. */ |
| 255251 | if( bSeenEq ){ |
| 255252 | pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0; |
| 255253 | if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo); |
| 255254 | }else if( bSeenLt && bSeenGt ){ |
| 255255 | pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0; |
| 255256 | }else if( bSeenLt || bSeenGt ){ |
| 255257 | pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0; |
| 255258 | }else{ |
| 255259 | pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0; |
| 255260 | } |
| 255261 | for(i=1; i<nSeenMatch; i++){ |
| 255262 | pInfo->estimatedCost *= 0.4; |
| 255263 | } |
| 255264 | |
| 255265 | pInfo->idxNum = idxFlags; |
| 255266 | return SQLITE_OK; |
| 255267 | } |
| @@ -255456,11 +257265,13 @@ | |
| 255456 | if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){ |
| 255457 | Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); |
| 255458 | int bDesc = pCsr->bDesc; |
| 255459 | i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); |
| 255460 | |
| 255461 | rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc); |
| 255462 | if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ |
| 255463 | *pbSkip = 1; |
| 255464 | } |
| 255465 | |
| 255466 | CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK); |
| @@ -255628,11 +257439,13 @@ | |
| 255628 | } |
| 255629 | |
| 255630 | static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ |
| 255631 | int rc; |
| 255632 | Fts5Expr *pExpr = pCsr->pExpr; |
| 255633 | rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc); |
| 255634 | if( sqlite3Fts5ExprEof(pExpr) ){ |
| 255635 | CsrFlagSet(pCsr, FTS5CSR_EOF); |
| 255636 | } |
| 255637 | fts5CsrNewrow(pCsr); |
| 255638 | return rc; |
| @@ -258113,11 +259926,11 @@ | |
| 258113 | int nArg, /* Number of args */ |
| 258114 | sqlite3_value **apUnused /* Function arguments */ |
| 258115 | ){ |
| 258116 | assert( nArg==0 ); |
| 258117 | UNUSED_PARAM2(nArg, apUnused); |
| 258118 | sqlite3_result_text(pCtx, "fts5: 2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839", -1, SQLITE_TRANSIENT); |
| 258119 | } |
| 258120 | |
| 258121 | /* |
| 258122 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 258123 | ** |
| @@ -258136,13 +259949,13 @@ | |
| 258136 | sqlite3_context *pCtx, /* Function call context */ |
| 258137 | int nArg, /* Number of args */ |
| 258138 | sqlite3_value **apArg /* Function arguments */ |
| 258139 | ){ |
| 258140 | const char *zLocale = 0; |
| 258141 | int nLocale = 0; |
| 258142 | const char *zText = 0; |
| 258143 | int nText = 0; |
| 258144 | |
| 258145 | assert( nArg==2 ); |
| 258146 | UNUSED_PARAM(nArg); |
| 258147 | |
| 258148 | zLocale = (const char*)sqlite3_value_text(apArg[0]); |
| @@ -258155,14 +259968,14 @@ | |
| 258155 | sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT); |
| 258156 | }else{ |
| 258157 | Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); |
| 258158 | u8 *pBlob = 0; |
| 258159 | u8 *pCsr = 0; |
| 258160 | int nBlob = 0; |
| 258161 | |
| 258162 | nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; |
| 258163 | pBlob = (u8*)sqlite3_malloc(nBlob); |
| 258164 | if( pBlob==0 ){ |
| 258165 | sqlite3_result_error_nomem(pCtx); |
| 258166 | return; |
| 258167 | } |
| 258168 | |
| 258169 |
| --- 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 | ** 5cbccab499bc3983aac1f57355552db607de with changes in files: |
| 22 | ** |
| 23 | ** |
| 24 | */ |
| 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | #define SQLITE_CORE 1 |
| @@ -168,11 +168,13 @@ | |
| 168 | #define SQLITE_OMIT_LOAD_EXTENSION 1 |
| 169 | #define SQLITE_ENABLE_LOCKING_STYLE 0 |
| 170 | #define HAVE_UTIME 1 |
| 171 | #else |
| 172 | /* This is not VxWorks. */ |
| 173 | #ifndef OS_VXWORKS |
| 174 | # define OS_VXWORKS 0 |
| 175 | #endif |
| 176 | #define HAVE_FCHOWN 1 |
| 177 | #define HAVE_READLINK 1 |
| 178 | #define HAVE_LSTAT 1 |
| 179 | #endif /* defined(_WRS_KERNEL) */ |
| 180 | |
| @@ -465,11 +467,14 @@ | |
| 467 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 468 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 469 | */ |
| 470 | #define SQLITE_VERSION "3.51.0" |
| 471 | #define SQLITE_VERSION_NUMBER 3051000 |
| 472 | #define SQLITE_SOURCE_ID "2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70" |
| 473 | #define SQLITE_SCM_BRANCH "trunk" |
| 474 | #define SQLITE_SCM_TAGS "" |
| 475 | #define SQLITE_SCM_DATETIME "2025-10-15T10:52:45.276Z" |
| 476 | |
| 477 | /* |
| 478 | ** CAPI3REF: Run-Time Library Version Numbers |
| 479 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 480 | ** |
| @@ -814,10 +819,13 @@ | |
| 819 | ** [sqlite3_extended_errcode()]. |
| 820 | */ |
| 821 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 822 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 823 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 824 | #define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) |
| 825 | #define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) |
| 826 | #define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) |
| 827 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 828 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 829 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 830 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 831 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -848,10 +856,12 @@ | |
| 856 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 857 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 858 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 859 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 860 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 861 | #define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) |
| 862 | #define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) |
| 863 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 864 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 865 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 866 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 867 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2652,21 +2662,24 @@ | |
| 2662 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2663 | ** databases.)^ </dd> |
| 2664 | ** |
| 2665 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2666 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2667 | ** <dd> ^This option is used to enable or disable using the |
| 2668 | ** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine |
| 2669 | ** extension - without using bound parameters as the parameters. Doing so |
| 2670 | ** is disabled by default. There must be two additional arguments. The first |
| 2671 | ** argument is an integer. If it is passed 0, then using fts3_tokenizer() |
| 2672 | ** without bound parameters is disabled. If it is passed a positive value, |
| 2673 | ** then calling fts3_tokenizer without bound parameters is enabled. If it |
| 2674 | ** is passed a negative value, this setting is not modified - this can be |
| 2675 | ** used to query for the current setting. The second parameter is a pointer |
| 2676 | ** to an integer into which is written 0 or 1 to indicate the current value |
| 2677 | ** of this setting (after it is modified, if applicable). The second |
| 2678 | ** parameter may be a NULL pointer, in which case the value of the setting |
| 2679 | ** is not reported back. Refer to [FTS3] documentation for further details. |
| 2680 | ** </dd> |
| 2681 | ** |
| 2682 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2683 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2684 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2685 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4512,10 +4525,38 @@ | |
| 4525 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4526 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4527 | SQLITE_API const char *sqlite3_errstr(int); |
| 4528 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4529 | |
| 4530 | /* |
| 4531 | ** CAPI3REF: Set Error Codes And Message |
| 4532 | ** METHOD: sqlite3 |
| 4533 | ** |
| 4534 | ** Set the error code of the database handle passed as the first argument |
| 4535 | ** to errcode, and the error message to a copy of nul-terminated string |
| 4536 | ** zErrMsg. If zErrMsg is passed NULL, then the error message is set to |
| 4537 | ** the default message associated with the supplied error code. Subsequent |
| 4538 | ** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will |
| 4539 | ** return the values set by this routine in place of what was previously |
| 4540 | ** set by SQLite itself. |
| 4541 | ** |
| 4542 | ** This function returns SQLITE_OK if the error code and error message are |
| 4543 | ** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if |
| 4544 | ** the database handle is NULL or invalid. |
| 4545 | ** |
| 4546 | ** The error code and message set by this routine remains in effect until |
| 4547 | ** they are changed, either by another call to this routine or until they are |
| 4548 | ** changed to by SQLite itself to reflect the result of some subsquent |
| 4549 | ** API call. |
| 4550 | ** |
| 4551 | ** This function is intended for use by SQLite extensions or wrappers. The |
| 4552 | ** idea is that an extension or wrapper can use this routine to set error |
| 4553 | ** messages and error codes and thus behave more like a core SQLite |
| 4554 | ** feature from the point of view of an application. |
| 4555 | */ |
| 4556 | SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); |
| 4557 | |
| 4558 | /* |
| 4559 | ** CAPI3REF: Prepared Statement Object |
| 4560 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4561 | ** |
| 4562 | ** An instance of this object represents a single SQL statement that |
| @@ -6522,10 +6563,11 @@ | |
| 6563 | ** to be attached to [database connection] D using name N. Subsequent |
| 6564 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6565 | ** or a NULL pointer if there were no prior calls to |
| 6566 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6567 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6568 | ** It returns 0 on success and SQLITE_NOMEM on allocation failure. |
| 6569 | ** |
| 6570 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6571 | ** argument P on the first of the following occurrences: |
| 6572 | ** <ul> |
| 6573 | ** <li> An out-of-memory error occurs during the call to |
| @@ -9197,14 +9239,23 @@ | |
| 9239 | ** the resetFlg is true, then the highest instantaneous value is |
| 9240 | ** reset back down to the current value. |
| 9241 | ** |
| 9242 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 9243 | ** non-zero [error code] on failure. |
| 9244 | ** |
| 9245 | ** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same |
| 9246 | ** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H |
| 9247 | ** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead |
| 9248 | ** of pointers to 32-bit integers, which allows larger status values |
| 9249 | ** to be returned. If a status value exceeds 2,147,483,647 then |
| 9250 | ** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() |
| 9251 | ** will return the full value. |
| 9252 | ** |
| 9253 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 9254 | */ |
| 9255 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 9256 | SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); |
| 9257 | |
| 9258 | /* |
| 9259 | ** CAPI3REF: Status Parameters for database connections |
| 9260 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 9261 | ** |
| @@ -9297,10 +9348,14 @@ | |
| 9348 | ** database file in rollback mode databases. Any pages written as part of |
| 9349 | ** transaction rollback or database recovery operations are not included. |
| 9350 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 9351 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 9352 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 9353 | ** <p> |
| 9354 | ** ^(There is overlap between the quantities measured by this parameter |
| 9355 | ** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. |
| 9356 | ** Resetting one will reduce the other.)^ |
| 9357 | ** </dd> |
| 9358 | ** |
| 9359 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 9360 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 9361 | ** been written to disk in the middle of a transaction due to the page |
| @@ -9312,10 +9367,22 @@ | |
| 9367 | ** |
| 9368 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 9369 | ** <dd>This parameter returns zero for the current value if and only if |
| 9370 | ** all foreign key constraints (deferred or immediate) have been |
| 9371 | ** resolved.)^ ^The highwater mark is always 0. |
| 9372 | ** |
| 9373 | ** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> |
| 9374 | ** <dd>^(This parameter returns the number of bytes written to temporary |
| 9375 | ** files on disk that could have been kept in memory had sufficient memory |
| 9376 | ** been available. This value includes writes to intermediate tables that |
| 9377 | ** are part of complex queries, external sorts that spill to disk, and |
| 9378 | ** writes to TEMP tables.)^ |
| 9379 | ** ^The highwater mark is always 0. |
| 9380 | ** <p> |
| 9381 | ** ^(There is overlap between the quantities measured by this parameter |
| 9382 | ** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. |
| 9383 | ** Resetting one will reduce the other.)^ |
| 9384 | ** </dd> |
| 9385 | ** </dl> |
| 9386 | */ |
| 9387 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9388 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9328,11 +9395,12 @@ | |
| 9395 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9396 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9397 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9398 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9399 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9400 | #define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 |
| 9401 | #define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ |
| 9402 | |
| 9403 | |
| 9404 | /* |
| 9405 | ** CAPI3REF: Prepared Statement Status |
| 9406 | ** METHOD: sqlite3_stmt |
| @@ -10093,25 +10161,38 @@ | |
| 10161 | ** ^The third parameter is the name of the database that was written to - |
| 10162 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 10163 | ** is the number of pages currently in the write-ahead log file, |
| 10164 | ** including those that were just committed. |
| 10165 | ** |
| 10166 | ** ^The callback function should normally return [SQLITE_OK]. ^If an error |
| 10167 | ** code is returned, that error will propagate back up through the |
| 10168 | ** SQLite code base to cause the statement that provoked the callback |
| 10169 | ** to report an error, though the commit will have still occurred. If the |
| 10170 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 10171 | ** that does not correspond to any valid SQLite error code, the results |
| 10172 | ** are undefined. |
| 10173 | ** |
| 10174 | ** ^A single database handle may have at most a single write-ahead log |
| 10175 | ** callback registered at one time. ^Calling [sqlite3_wal_hook()] |
| 10176 | ** replaces the default behavior or previously registered write-ahead |
| 10177 | ** log callback. |
| 10178 | ** |
| 10179 | ** ^The return value is a copy of the third parameter from the |
| 10180 | ** previous call, if any, or 0. |
| 10181 | ** |
| 10182 | ** ^The [sqlite3_wal_autocheckpoint()] interface and the |
| 10183 | ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and |
| 10184 | ** will overwrite any prior [sqlite3_wal_hook()] settings. |
| 10185 | ** |
| 10186 | ** ^If a write-ahead log callback is set using this function then |
| 10187 | ** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] |
| 10188 | ** should be invoked periodically to keep the write-ahead log file |
| 10189 | ** from growing without bound. |
| 10190 | ** |
| 10191 | ** ^Passing a NULL pointer for the callback disables automatic |
| 10192 | ** checkpointing entirely. To re-enable the default behavior, call |
| 10193 | ** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. |
| 10194 | */ |
| 10195 | SQLITE_API void *sqlite3_wal_hook( |
| 10196 | sqlite3*, |
| 10197 | int(*)(void *,sqlite3*,const char*,int), |
| 10198 | void* |
| @@ -10124,11 +10205,11 @@ | |
| 10205 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 10206 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 10207 | ** to automatically [checkpoint] |
| 10208 | ** after committing a transaction if there are N or |
| 10209 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 10210 | ** a negative value as the N parameter disables automatic |
| 10211 | ** checkpoints entirely. |
| 10212 | ** |
| 10213 | ** ^The callback registered by this function replaces any existing callback |
| 10214 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 10215 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -10140,13 +10221,14 @@ | |
| 10221 | ** ^Checkpoints initiated by this mechanism are |
| 10222 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 10223 | ** |
| 10224 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 10225 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 10226 | ** pages. |
| 10227 | ** |
| 10228 | ** ^The use of this interface is only necessary if the default setting |
| 10229 | ** is found to be suboptimal for a particular application. |
| 10230 | */ |
| 10231 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 10232 | |
| 10233 | /* |
| 10234 | ** CAPI3REF: Checkpoint a database |
| @@ -10207,10 +10289,15 @@ | |
| 10289 | ** |
| 10290 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 10291 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 10292 | ** addition that it also truncates the log file to zero bytes just prior |
| 10293 | ** to a successful return. |
| 10294 | ** |
| 10295 | ** <dt>SQLITE_CHECKPOINT_NOOP<dd> |
| 10296 | ** ^This mode always checkpoints zero frames. The only reason to invoke |
| 10297 | ** a NOOP checkpoint is to access the values returned by |
| 10298 | ** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. |
| 10299 | ** </dl> |
| 10300 | ** |
| 10301 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 10302 | ** the log file or to -1 if the checkpoint could not run because |
| 10303 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -10277,10 +10364,11 @@ | |
| 10364 | ** These constants define all valid values for the "checkpoint mode" passed |
| 10365 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 10366 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 10367 | ** meaning of each of these checkpoint modes. |
| 10368 | */ |
| 10369 | #define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ |
| 10370 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 10371 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 10372 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 10373 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 10374 | |
| @@ -11104,11 +11192,11 @@ | |
| 11192 | ** to avoid a memory leak. |
| 11193 | ** |
| 11194 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 11195 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11196 | */ |
| 11197 | SQLITE_API int sqlite3_snapshot_get( |
| 11198 | sqlite3 *db, |
| 11199 | const char *zSchema, |
| 11200 | sqlite3_snapshot **ppSnapshot |
| 11201 | ); |
| 11202 | |
| @@ -11153,11 +11241,11 @@ | |
| 11241 | ** database connection in order to make it ready to use snapshots.) |
| 11242 | ** |
| 11243 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 11244 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11245 | */ |
| 11246 | SQLITE_API int sqlite3_snapshot_open( |
| 11247 | sqlite3 *db, |
| 11248 | const char *zSchema, |
| 11249 | sqlite3_snapshot *pSnapshot |
| 11250 | ); |
| 11251 | |
| @@ -11170,11 +11258,11 @@ | |
| 11258 | ** using this routine to avoid a memory leak. |
| 11259 | ** |
| 11260 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 11261 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 11262 | */ |
| 11263 | SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); |
| 11264 | |
| 11265 | /* |
| 11266 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 11267 | ** METHOD: sqlite3_snapshot |
| 11268 | ** |
| @@ -11197,11 +11285,11 @@ | |
| 11285 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 11286 | ** |
| 11287 | ** This interface is only available if SQLite is compiled with the |
| 11288 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11289 | */ |
| 11290 | SQLITE_API int sqlite3_snapshot_cmp( |
| 11291 | sqlite3_snapshot *p1, |
| 11292 | sqlite3_snapshot *p2 |
| 11293 | ); |
| 11294 | |
| 11295 | /* |
| @@ -11225,11 +11313,11 @@ | |
| 11313 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 11314 | ** |
| 11315 | ** This interface is only available if SQLite is compiled with the |
| 11316 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 11317 | */ |
| 11318 | SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); |
| 11319 | |
| 11320 | /* |
| 11321 | ** CAPI3REF: Serialize a database |
| 11322 | ** |
| 11323 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -11299,16 +11387,17 @@ | |
| 11387 | /* |
| 11388 | ** CAPI3REF: Deserialize a database |
| 11389 | ** |
| 11390 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 11391 | ** [database connection] D to disconnect from database S and then |
| 11392 | ** reopen S as an in-memory database based on the serialization |
| 11393 | ** contained in P. If S is a NULL pointer, the main database is |
| 11394 | ** used. The serialized database P is N bytes in size. M is the size |
| 11395 | ** of the buffer P, which might be larger than N. If M is larger than |
| 11396 | ** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then |
| 11397 | ** SQLite is permitted to add content to the in-memory database as |
| 11398 | ** long as the total size does not exceed M bytes. |
| 11399 | ** |
| 11400 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 11401 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 11402 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 11403 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11371,10 +11460,56 @@ | |
| 11460 | */ |
| 11461 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11462 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11463 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11464 | |
| 11465 | /* |
| 11466 | ** CAPI3REF: Bind array values to the CARRAY table-valued function |
| 11467 | ** |
| 11468 | ** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to |
| 11469 | ** one of the first argument of the [carray() table-valued function]. The |
| 11470 | ** S parameter is a pointer to the [prepared statement] that uses the carray() |
| 11471 | ** functions. I is the parameter index to be bound. P is a pointer to the |
| 11472 | ** array to be bound, and N is the number of eements in the array. The |
| 11473 | ** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], |
| 11474 | ** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to |
| 11475 | ** indicate the datatype of the array being bound. The X argument is not a |
| 11476 | ** NULL pointer, then SQLite will invoke the function X on the P parameter |
| 11477 | ** after it has finished using P. |
| 11478 | */ |
| 11479 | SQLITE_API SQLITE_API int sqlite3_carray_bind( |
| 11480 | sqlite3_stmt *pStmt, /* Statement to be bound */ |
| 11481 | int i, /* Parameter index */ |
| 11482 | void *aData, /* Pointer to array data */ |
| 11483 | int nData, /* Number of data elements */ |
| 11484 | int mFlags, /* CARRAY flags */ |
| 11485 | void (*xDel)(void*) /* Destructor for aData */ |
| 11486 | ); |
| 11487 | |
| 11488 | /* |
| 11489 | ** CAPI3REF: Datatypes for the CARRAY table-valued funtion |
| 11490 | ** |
| 11491 | ** The fifth argument to the [sqlite3_carray_bind()] interface musts be |
| 11492 | ** one of the following constants, to specify the datatype of the array |
| 11493 | ** that is being bound into the [carray table-valued function]. |
| 11494 | */ |
| 11495 | #define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ |
| 11496 | #define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ |
| 11497 | #define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ |
| 11498 | #define SQLITE_CARRAY_TEXT 3 /* Data is char* */ |
| 11499 | #define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ |
| 11500 | |
| 11501 | /* |
| 11502 | ** Versions of the above #defines that omit the initial SQLITE_, for |
| 11503 | ** legacy compatibility. |
| 11504 | */ |
| 11505 | #define CARRAY_INT32 0 /* Data is 32-bit signed integers */ |
| 11506 | #define CARRAY_INT64 1 /* Data is 64-bit signed integers */ |
| 11507 | #define CARRAY_DOUBLE 2 /* Data is doubles */ |
| 11508 | #define CARRAY_TEXT 3 /* Data is char* */ |
| 11509 | #define CARRAY_BLOB 4 /* Data is struct iovec */ |
| 11510 | |
| 11511 | /* |
| 11512 | ** Undo the hack that converts floating point types to integer for |
| 11513 | ** builds on processors without floating point support. |
| 11514 | */ |
| 11515 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12629,10 +12764,19 @@ | |
| 12764 | ** CAPI3REF: Apply A Changeset To A Database |
| 12765 | ** |
| 12766 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12767 | ** update the "main" database attached to handle db with the changes found in |
| 12768 | ** the changeset passed via the second and third arguments. |
| 12769 | ** |
| 12770 | ** All changes made by these functions are enclosed in a savepoint transaction. |
| 12771 | ** If any other error (aside from a constraint failure when attempting to |
| 12772 | ** write to the target database) occurs, then the savepoint transaction is |
| 12773 | ** rolled back, restoring the target database to its original state, and an |
| 12774 | ** SQLite error code returned. Additionally, starting with version 3.51.0, |
| 12775 | ** an error code and error message that may be accessed using the |
| 12776 | ** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database |
| 12777 | ** handle. |
| 12778 | ** |
| 12779 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12780 | ** callback". This may be passed NULL, in which case all changes in the |
| 12781 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12782 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12767,16 +12911,10 @@ | |
| 12911 | ** It is safe to execute SQL statements, including those that write to the |
| 12912 | ** table that the callback related to, from within the xConflict callback. |
| 12913 | ** This can be used to further customize the application's conflict |
| 12914 | ** resolution strategy. |
| 12915 | ** |
| 12916 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12917 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12918 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12919 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12920 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| @@ -14351,11 +14489,11 @@ | |
| 14489 | |
| 14490 | /* |
| 14491 | ** Maximum number of pages in one database file. |
| 14492 | ** |
| 14493 | ** This is really just the default value for the max_page_count pragma. |
| 14494 | ** This value can be lowered (or raised) at run-time using the |
| 14495 | ** max_page_count macro. |
| 14496 | */ |
| 14497 | #ifndef SQLITE_MAX_PAGE_COUNT |
| 14498 | # define SQLITE_MAX_PAGE_COUNT 0xfffffffe /* 4294967294 */ |
| 14499 | #endif |
| @@ -15947,12 +16085,12 @@ | |
| 16085 | ** |
| 16086 | ** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application |
| 16087 | ** must provide its own VFS implementation together with sqlite3_os_init() |
| 16088 | ** and sqlite3_os_end() routines. |
| 16089 | */ |
| 16090 | #if SQLITE_OS_KV+1<=1 && SQLITE_OS_OTHER+1<=1 && \ |
| 16091 | SQLITE_OS_WIN+1<=1 && SQLITE_OS_UNIX+1<=1 |
| 16092 | # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ |
| 16093 | defined(__MINGW32__) || defined(__BORLANDC__) |
| 16094 | # define SQLITE_OS_WIN 1 |
| 16095 | # define SQLITE_OS_UNIX 0 |
| 16096 | # else |
| @@ -17450,10 +17588,13 @@ | |
| 17588 | #ifndef SQLITE_OMIT_TRACE |
| 17589 | SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); |
| 17590 | #endif |
| 17591 | SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); |
| 17592 | SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); |
| 17593 | #ifdef SQLITE_ENABLE_PERCENTILE |
| 17594 | SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context*); |
| 17595 | #endif |
| 17596 | |
| 17597 | SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*); |
| 17598 | SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); |
| 17599 | SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); |
| 17600 | SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); |
| @@ -18122,11 +18263,11 @@ | |
| 18263 | struct sqlite3InitInfo { /* Information used during initialization */ |
| 18264 | Pgno newTnum; /* Rootpage of table being initialized */ |
| 18265 | u8 iDb; /* Which db file is being initialized */ |
| 18266 | u8 busy; /* TRUE if currently initializing */ |
| 18267 | unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ |
| 18268 | unsigned imposterTable : 2; /* Building an imposter table */ |
| 18269 | unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ |
| 18270 | const char **azInit; /* "type", "name", and "tbl_name" columns */ |
| 18271 | } init; |
| 18272 | int nVdbeActive; /* Number of VDBEs currently running */ |
| 18273 | int nVdbeRead; /* Number of active VDBEs that read or write */ |
| @@ -18205,10 +18346,11 @@ | |
| 18346 | int nStatement; /* Number of nested statement-transactions */ |
| 18347 | i64 nDeferredCons; /* Net deferred constraints this transaction. */ |
| 18348 | i64 nDeferredImmCons; /* Net deferred immediate constraints */ |
| 18349 | int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ |
| 18350 | DbClientData *pDbData; /* sqlite3_set_clientdata() content */ |
| 18351 | u64 nSpill; /* TEMP content spilled to disk */ |
| 18352 | #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY |
| 18353 | /* The following variables are all protected by the STATIC_MAIN |
| 18354 | ** mutex, not by sqlite3.mutex. They are used by code in notify.c. |
| 18355 | ** |
| 18356 | ** When X.pUnlockConnection==Y, that means that X is waiting for Y to |
| @@ -18915,10 +19057,11 @@ | |
| 19057 | #define TF_Shadow 0x00001000 /* True for a shadow table */ |
| 19058 | #define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ |
| 19059 | #define TF_Ephemeral 0x00004000 /* An ephemeral table */ |
| 19060 | #define TF_Eponymous 0x00008000 /* An eponymous virtual table */ |
| 19061 | #define TF_Strict 0x00010000 /* STRICT mode */ |
| 19062 | #define TF_Imposter 0x00020000 /* An imposter table */ |
| 19063 | |
| 19064 | /* |
| 19065 | ** Allowed values for Table.eTabType |
| 19066 | */ |
| 19067 | #define TABTYP_NORM 0 /* Ordinary table */ |
| @@ -19503,10 +19646,11 @@ | |
| 19646 | AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ |
| 19647 | union { |
| 19648 | Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL |
| 19649 | ** for a column of an index on an expression */ |
| 19650 | Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */ |
| 19651 | int nReg; /* TK_NULLS: Number of registers to NULL out */ |
| 19652 | struct { /* TK_IN, TK_SELECT, and TK_EXISTS */ |
| 19653 | int iAddr; /* Subroutine entry address */ |
| 19654 | int regReturn; /* Register used to hold return address */ |
| 19655 | } sub; |
| 19656 | } y; |
| @@ -20080,10 +20224,11 @@ | |
| 20224 | #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ |
| 20225 | #define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ |
| 20226 | #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ |
| 20227 | #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ |
| 20228 | #define SF_Correlated 0x20000000 /* True if references the outer context */ |
| 20229 | #define SF_OnToWhere 0x40000000 /* One or more ON clauses moved to WHERE */ |
| 20230 | |
| 20231 | /* True if SrcItem X is a subquery that has SF_NestedFrom */ |
| 20232 | #define IsNestedFrom(X) \ |
| 20233 | ((X)->fg.isSubquery && \ |
| 20234 | ((X)->u4.pSubq->pSelect->selFlags&SF_NestedFrom)!=0) |
| @@ -20833,10 +20978,11 @@ | |
| 20978 | struct Table *pTab; /* Table of generated column */ |
| 20979 | struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ |
| 20980 | SrcItem *pSrcItem; /* A single FROM clause item */ |
| 20981 | DbFixer *pFix; /* See sqlite3FixSelect() */ |
| 20982 | Mem *aMem; /* See sqlite3BtreeCursorHint() */ |
| 20983 | struct CheckOnCtx *pCheckOnCtx; /* See selectCheckOnClauses() */ |
| 20984 | } u; |
| 20985 | }; |
| 20986 | |
| 20987 | /* |
| 20988 | ** The following structure contains information used by the sqliteFix... |
| @@ -21540,10 +21686,11 @@ | |
| 21686 | SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); |
| 21687 | #endif |
| 21688 | SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); |
| 21689 | SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); |
| 21690 | SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); |
| 21691 | SQLITE_PRIVATE void sqlite3ExprNullRegisterRange(Parse*, int, int); |
| 21692 | SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); |
| 21693 | SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); |
| 21694 | SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); |
| 21695 | #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ |
| 21696 | #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ |
| @@ -21636,16 +21783,20 @@ | |
| 21783 | SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); |
| 21784 | SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); |
| 21785 | SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); |
| 21786 | SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); |
| 21787 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 21788 | SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3*,const char*); |
| 21789 | #endif |
| 21790 | SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); |
| 21791 | SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); |
| 21792 | SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); |
| 21793 | SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); |
| 21794 | |
| 21795 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) |
| 21796 | SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3*); |
| 21797 | #endif |
| 21798 | |
| 21799 | #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) |
| 21800 | SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); |
| 21801 | #endif |
| 21802 | |
| @@ -22628,10 +22779,13 @@ | |
| 22779 | #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE |
| 22780 | "ENABLE_BATCH_ATOMIC_WRITE", |
| 22781 | #endif |
| 22782 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 22783 | "ENABLE_BYTECODE_VTAB", |
| 22784 | #endif |
| 22785 | #ifdef SQLITE_ENABLE_CARRAY |
| 22786 | "ENABLE_CARRAY", |
| 22787 | #endif |
| 22788 | #ifdef SQLITE_ENABLE_CEROD |
| 22789 | "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), |
| 22790 | #endif |
| 22791 | #ifdef SQLITE_ENABLE_COLUMN_METADATA |
| @@ -22718,10 +22872,13 @@ | |
| 22872 | #ifdef SQLITE_ENABLE_ORDERED_SET_AGGREGATES |
| 22873 | "ENABLE_ORDERED_SET_AGGREGATES", |
| 22874 | #endif |
| 22875 | #ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK |
| 22876 | "ENABLE_OVERSIZE_CELL_CHECK", |
| 22877 | #endif |
| 22878 | #ifdef SQLITE_ENABLE_PERCENTILE |
| 22879 | "ENABLE_PERCENTILE", |
| 22880 | #endif |
| 22881 | #ifdef SQLITE_ENABLE_PREUPDATE_HOOK |
| 22882 | "ENABLE_PREUPDATE_HOOK", |
| 22883 | #endif |
| 22884 | #ifdef SQLITE_ENABLE_QPSG |
| @@ -24202,11 +24359,14 @@ | |
| 24359 | Mem oldipk; /* Memory cell holding "old" IPK value */ |
| 24360 | Mem *aNew; /* Array of new.* values */ |
| 24361 | Table *pTab; /* Schema object being updated */ |
| 24362 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 24363 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 24364 | union { |
| 24365 | KeyInfo sKey; |
| 24366 | u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ |
| 24367 | } uKey; |
| 24368 | }; |
| 24369 | |
| 24370 | /* |
| 24371 | ** An instance of this object is used to pass an vector of values into |
| 24372 | ** OP_VFilter, the xFilter method of a virtual table. The vector is the |
| @@ -24366,13 +24526,15 @@ | |
| 24526 | SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*); |
| 24527 | SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); |
| 24528 | #endif |
| 24529 | |
| 24530 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 24531 | SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe*); |
| 24532 | SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe*); |
| 24533 | #else |
| 24534 | # define sqlite3VdbeCheckFkImmediate(p) 0 |
| 24535 | # define sqlite3VdbeCheckFkDeferred(p) 0 |
| 24536 | #endif |
| 24537 | |
| 24538 | #ifdef SQLITE_DEBUG |
| 24539 | SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); |
| 24540 | SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr); |
| @@ -24577,27 +24739,29 @@ | |
| 24739 | } |
| 24740 | |
| 24741 | /* |
| 24742 | ** Query status information for a single database connection |
| 24743 | */ |
| 24744 | SQLITE_API int sqlite3_db_status64( |
| 24745 | sqlite3 *db, /* The database connection whose status is desired */ |
| 24746 | int op, /* Status verb */ |
| 24747 | sqlite3_int64 *pCurrent, /* Write current value here */ |
| 24748 | sqlite3_int64 *pHighwtr, /* Write high-water mark here */ |
| 24749 | int resetFlag /* Reset high-water mark if true */ |
| 24750 | ){ |
| 24751 | int rc = SQLITE_OK; /* Return code */ |
| 24752 | #ifdef SQLITE_ENABLE_API_ARMOR |
| 24753 | if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ |
| 24754 | return SQLITE_MISUSE_BKPT; |
| 24755 | } |
| 24756 | #endif |
| 24757 | sqlite3_mutex_enter(db->mutex); |
| 24758 | switch( op ){ |
| 24759 | case SQLITE_DBSTATUS_LOOKASIDE_USED: { |
| 24760 | int H = 0; |
| 24761 | *pCurrent = sqlite3LookasideUsed(db, &H); |
| 24762 | *pHighwtr = H; |
| 24763 | if( resetFlag ){ |
| 24764 | LookasideSlot *p = db->lookaside.pFree; |
| 24765 | if( p ){ |
| 24766 | while( p->pNext ) p = p->pNext; |
| 24767 | p->pNext = db->lookaside.pInit; |
| @@ -24624,11 +24788,11 @@ | |
| 24788 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE ); |
| 24789 | testcase( op==SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL ); |
| 24790 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); |
| 24791 | assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); |
| 24792 | *pCurrent = 0; |
| 24793 | *pHighwtr = db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; |
| 24794 | if( resetFlag ){ |
| 24795 | db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; |
| 24796 | } |
| 24797 | break; |
| 24798 | } |
| @@ -24638,11 +24802,11 @@ | |
| 24802 | ** by all pagers associated with the given database connection. The |
| 24803 | ** highwater mark is meaningless and is returned as zero. |
| 24804 | */ |
| 24805 | case SQLITE_DBSTATUS_CACHE_USED_SHARED: |
| 24806 | case SQLITE_DBSTATUS_CACHE_USED: { |
| 24807 | sqlite3_int64 totalUsed = 0; |
| 24808 | int i; |
| 24809 | sqlite3BtreeEnterAll(db); |
| 24810 | for(i=0; i<db->nDb; i++){ |
| 24811 | Btree *pBt = db->aDb[i].pBt; |
| 24812 | if( pBt ){ |
| @@ -24654,22 +24818,22 @@ | |
| 24818 | totalUsed += nByte; |
| 24819 | } |
| 24820 | } |
| 24821 | sqlite3BtreeLeaveAll(db); |
| 24822 | *pCurrent = totalUsed; |
| 24823 | *pHighwtr = 0; |
| 24824 | break; |
| 24825 | } |
| 24826 | |
| 24827 | /* |
| 24828 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24829 | ** to store the schema for all databases (main, temp, and any ATTACHed |
| 24830 | ** databases. *pHighwtr is set to zero. |
| 24831 | */ |
| 24832 | case SQLITE_DBSTATUS_SCHEMA_USED: { |
| 24833 | int i; /* Used to iterate through schemas */ |
| 24834 | int nByte = 0; /* Used to accumulate return value */ |
| 24835 | |
| 24836 | sqlite3BtreeEnterAll(db); |
| 24837 | db->pnBytesFreed = &nByte; |
| 24838 | assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); |
| 24839 | db->lookaside.pEnd = db->lookaside.pStart; |
| @@ -24699,19 +24863,19 @@ | |
| 24863 | } |
| 24864 | db->pnBytesFreed = 0; |
| 24865 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24866 | sqlite3BtreeLeaveAll(db); |
| 24867 | |
| 24868 | *pHighwtr = 0; |
| 24869 | *pCurrent = nByte; |
| 24870 | break; |
| 24871 | } |
| 24872 | |
| 24873 | /* |
| 24874 | ** *pCurrent gets an accurate estimate of the amount of memory used |
| 24875 | ** to store all prepared statements. |
| 24876 | ** *pHighwtr is set to zero. |
| 24877 | */ |
| 24878 | case SQLITE_DBSTATUS_STMT_USED: { |
| 24879 | struct Vdbe *pVdbe; /* Used to iterate through VMs */ |
| 24880 | int nByte = 0; /* Used to accumulate return value */ |
| 24881 | |
| @@ -24722,19 +24886,19 @@ | |
| 24886 | sqlite3VdbeDelete(pVdbe); |
| 24887 | } |
| 24888 | db->lookaside.pEnd = db->lookaside.pTrueEnd; |
| 24889 | db->pnBytesFreed = 0; |
| 24890 | |
| 24891 | *pHighwtr = 0; /* IMP: R-64479-57858 */ |
| 24892 | *pCurrent = nByte; |
| 24893 | |
| 24894 | break; |
| 24895 | } |
| 24896 | |
| 24897 | /* |
| 24898 | ** Set *pCurrent to the total cache hits or misses encountered by all |
| 24899 | ** pagers the database handle is connected to. *pHighwtr is always set |
| 24900 | ** to zero. |
| 24901 | */ |
| 24902 | case SQLITE_DBSTATUS_CACHE_SPILL: |
| 24903 | op = SQLITE_DBSTATUS_CACHE_WRITE+1; |
| 24904 | /* no break */ deliberate_fall_through |
| @@ -24750,23 +24914,43 @@ | |
| 24914 | if( db->aDb[i].pBt ){ |
| 24915 | Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt); |
| 24916 | sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); |
| 24917 | } |
| 24918 | } |
| 24919 | *pHighwtr = 0; /* IMP: R-42420-56072 */ |
| 24920 | /* IMP: R-54100-20147 */ |
| 24921 | /* IMP: R-29431-39229 */ |
| 24922 | *pCurrent = nRet; |
| 24923 | break; |
| 24924 | } |
| 24925 | |
| 24926 | /* Set *pCurrent to the number of bytes that the db database connection |
| 24927 | ** has spilled to the filesystem in temporary files that could have been |
| 24928 | ** stored in memory, had sufficient memory been available. |
| 24929 | ** The *pHighwater is always set to zero. |
| 24930 | */ |
| 24931 | case SQLITE_DBSTATUS_TEMPBUF_SPILL: { |
| 24932 | u64 nRet = 0; |
| 24933 | if( db->aDb[1].pBt ){ |
| 24934 | Pager *pPager = sqlite3BtreePager(db->aDb[1].pBt); |
| 24935 | sqlite3PagerCacheStat(pPager, SQLITE_DBSTATUS_CACHE_WRITE, |
| 24936 | resetFlag, &nRet); |
| 24937 | nRet *= sqlite3BtreeGetPageSize(db->aDb[1].pBt); |
| 24938 | } |
| 24939 | nRet += db->nSpill; |
| 24940 | if( resetFlag ) db->nSpill = 0; |
| 24941 | *pHighwtr = 0; |
| 24942 | *pCurrent = nRet; |
| 24943 | break; |
| 24944 | } |
| 24945 | |
| 24946 | /* Set *pCurrent to non-zero if there are unresolved deferred foreign |
| 24947 | ** key constraints. Set *pCurrent to zero if all foreign key constraints |
| 24948 | ** have been satisfied. The *pHighwtr is always set to zero. |
| 24949 | */ |
| 24950 | case SQLITE_DBSTATUS_DEFERRED_FKS: { |
| 24951 | *pHighwtr = 0; /* IMP: R-11967-56545 */ |
| 24952 | *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; |
| 24953 | break; |
| 24954 | } |
| 24955 | |
| 24956 | default: { |
| @@ -24774,10 +24958,35 @@ | |
| 24958 | } |
| 24959 | } |
| 24960 | sqlite3_mutex_leave(db->mutex); |
| 24961 | return rc; |
| 24962 | } |
| 24963 | |
| 24964 | /* |
| 24965 | ** 32-bit variant of sqlite3_db_status64() |
| 24966 | */ |
| 24967 | SQLITE_API int sqlite3_db_status( |
| 24968 | sqlite3 *db, /* The database connection whose status is desired */ |
| 24969 | int op, /* Status verb */ |
| 24970 | int *pCurrent, /* Write current value here */ |
| 24971 | int *pHighwtr, /* Write high-water mark here */ |
| 24972 | int resetFlag /* Reset high-water mark if true */ |
| 24973 | ){ |
| 24974 | sqlite3_int64 C = 0, H = 0; |
| 24975 | int rc; |
| 24976 | #ifdef SQLITE_ENABLE_API_ARMOR |
| 24977 | if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ |
| 24978 | return SQLITE_MISUSE_BKPT; |
| 24979 | } |
| 24980 | #endif |
| 24981 | rc = sqlite3_db_status64(db, op, &C, &H, resetFlag); |
| 24982 | if( rc==0 ){ |
| 24983 | *pCurrent = C & 0x7fffffff; |
| 24984 | *pHighwtr = H & 0x7fffffff; |
| 24985 | } |
| 24986 | return rc; |
| 24987 | } |
| 24988 | |
| 24989 | /************** End of status.c **********************************************/ |
| 24990 | /************** Begin file date.c ********************************************/ |
| 24991 | /* |
| 24992 | ** 2003 October 31 |
| @@ -24967,10 +25176,14 @@ | |
| 25176 | if( getDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){ |
| 25177 | return 1; |
| 25178 | } |
| 25179 | zDate += 5; |
| 25180 | p->tz = sgn*(nMn + nHr*60); |
| 25181 | if( p->tz==0 ){ /* Forum post 2025-09-17T10:12:14z */ |
| 25182 | p->isLocal = 0; |
| 25183 | p->isUtc = 1; |
| 25184 | } |
| 25185 | zulu_time: |
| 25186 | while( sqlite3Isspace(*zDate) ){ zDate++; } |
| 25187 | return *zDate!=0; |
| 25188 | } |
| 25189 | |
| @@ -26162,12 +26375,12 @@ | |
| 26375 | ** %j day of year 001-366 |
| 26376 | ** %J ** julian day number |
| 26377 | ** %l hour 1-12 (leading zero converted to space) |
| 26378 | ** %m month 01-12 |
| 26379 | ** %M minute 00-59 |
| 26380 | ** %p "AM" or "PM" |
| 26381 | ** %P "am" or "pm" |
| 26382 | ** %R time as HH:MM |
| 26383 | ** %s seconds since 1970-01-01 |
| 26384 | ** %S seconds 00-59 |
| 26385 | ** %T time as HH:MM:SS |
| 26386 | ** %u day of week 1-7 Monday==1, Sunday==7 |
| @@ -31770,56 +31983,74 @@ | |
| 31983 | etByte base; /* The base for radix conversion */ |
| 31984 | etByte flags; /* One or more of FLAG_ constants below */ |
| 31985 | etByte type; /* Conversion paradigm */ |
| 31986 | etByte charset; /* Offset into aDigits[] of the digits string */ |
| 31987 | etByte prefix; /* Offset into aPrefix[] of the prefix string */ |
| 31988 | char iNxt; /* Next with same hash, or 0 for end of chain */ |
| 31989 | } et_info; |
| 31990 | |
| 31991 | /* |
| 31992 | ** Allowed values for et_info.flags |
| 31993 | */ |
| 31994 | #define FLAG_SIGNED 1 /* True if the value to convert is signed */ |
| 31995 | #define FLAG_STRING 4 /* Allow infinite precision */ |
| 31996 | |
| 31997 | /* |
| 31998 | ** The table is searched by hash. In the case of %C where C is the character |
| 31999 | ** and that character has ASCII value j, then the hash is j%23. |
| 32000 | ** |
| 32001 | ** The order of the entries in fmtinfo[] and the hash chain was entered |
| 32002 | ** manually, but based on the output of the following TCL script: |
| 32003 | */ |
| 32004 | #if 0 /***** Beginning of script ******/ |
| 32005 | foreach c {d s g z q Q w c o u x X f e E G i n % p T S r} { |
| 32006 | scan $c %c x |
| 32007 | set n($c) $x |
| 32008 | } |
| 32009 | set mx [llength [array names n]] |
| 32010 | puts "count: $mx" |
| 32011 | |
| 32012 | set mx 27 |
| 32013 | puts "*********** mx=$mx ************" |
| 32014 | for {set r 0} {$r<$mx} {incr r} { |
| 32015 | puts -nonewline [format %2d: $r] |
| 32016 | foreach c [array names n] { |
| 32017 | if {($n($c))%$mx==$r} {puts -nonewline " $c"} |
| 32018 | } |
| 32019 | puts "" |
| 32020 | } |
| 32021 | #endif /***** End of script ********/ |
| 32022 | |
| 32023 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 32024 | static const char aPrefix[] = "-x0\000X0"; |
| 32025 | static const et_info fmtinfo[23] = { |
| 32026 | /* 0 */ { 's', 0, 4, etSTRING, 0, 0, 1 }, |
| 32027 | /* 1 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 0 */ |
| 32028 | /* 2 */ { 'u', 10, 0, etDECIMAL, 0, 0, 3 }, |
| 32029 | /* 3 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 2 */ |
| 32030 | /* 4 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 }, |
| 32031 | /* 5 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, |
| 32032 | /* 6 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, /* Hash: 7 */ |
| 32033 | /* 7 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 6 }, |
| 32034 | /* 8 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, |
| 32035 | /* 9 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, |
| 32036 | /* 10 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, |
| 32037 | /* 11 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, |
| 32038 | /* 12 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 0 }, |
| 32039 | /* 13 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, |
| 32040 | /* 14 */ { '%', 0, 0, etPERCENT, 0, 0, 16 }, |
| 32041 | /* 15 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, |
| 32042 | /* 16 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, /* Hash: 14 */ |
| 32043 | /* 17 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 19 */ |
| 32044 | /* 18 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, |
| 32045 | /* 19 */ { 'o', 8, 0, etRADIX, 0, 2, 17 }, |
| 32046 | /* 20 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 }, |
| 32047 | /* 21 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 }, |
| 32048 | /* 22 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 } |
| 32049 | }; |
| 32050 | |
| 32051 | /* Additional Notes: |
| 32052 | ** |
| 32053 | ** %S Takes a pointer to SrcItem. Shows name or database.name |
| 32054 | ** %!S Like %S but prefer the zName over the zAlias |
| 32055 | */ |
| 32056 | |
| @@ -31942,11 +32173,14 @@ | |
| 32173 | if( c!='%' ){ |
| 32174 | bufpt = (char *)fmt; |
| 32175 | #if HAVE_STRCHRNUL |
| 32176 | fmt = strchrnul(fmt, '%'); |
| 32177 | #else |
| 32178 | fmt = strchr(fmt, '%'); |
| 32179 | if( fmt==0 ){ |
| 32180 | fmt = bufpt + strlen(bufpt); |
| 32181 | } |
| 32182 | #endif |
| 32183 | sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); |
| 32184 | if( *fmt==0 ) break; |
| 32185 | } |
| 32186 | if( (c=(*++fmt))==0 ){ |
| @@ -32056,19 +32290,36 @@ | |
| 32290 | } |
| 32291 | } |
| 32292 | }while( !done && (c=(*++fmt))!=0 ); |
| 32293 | |
| 32294 | /* Fetch the info entry for the field */ |
| 32295 | #ifdef SQLITE_EBCDIC |
| 32296 | /* The hash table only works for ASCII. For EBCDIC, we need to do |
| 32297 | ** a linear search of the table */ |
| 32298 | infop = &fmtinfo[0]; |
| 32299 | xtype = etINVALID; |
| 32300 | for(idx=0; idx<ArraySize(fmtinfo); idx++){ |
| 32301 | if( c==fmtinfo[idx].fmttype ){ |
| 32302 | infop = &fmtinfo[idx]; |
| 32303 | xtype = infop->type; |
| 32304 | break; |
| 32305 | } |
| 32306 | } |
| 32307 | #else |
| 32308 | /* Fast hash-table lookup */ |
| 32309 | assert( ArraySize(fmtinfo)==23 ); |
| 32310 | idx = ((unsigned)c) % 23; |
| 32311 | if( fmtinfo[idx].fmttype==c |
| 32312 | || fmtinfo[idx = fmtinfo[idx].iNxt].fmttype==c |
| 32313 | ){ |
| 32314 | infop = &fmtinfo[idx]; |
| 32315 | xtype = infop->type; |
| 32316 | }else{ |
| 32317 | infop = &fmtinfo[0]; |
| 32318 | xtype = etINVALID; |
| 32319 | } |
| 32320 | #endif |
| 32321 | |
| 32322 | /* |
| 32323 | ** At this point, variables are initialized as follows: |
| 32324 | ** |
| 32325 | ** flag_alternateform TRUE if a '#' is present. |
| @@ -32252,11 +32503,25 @@ | |
| 32503 | length = sqlite3Strlen30(bufpt); |
| 32504 | break; |
| 32505 | } |
| 32506 | } |
| 32507 | if( s.sign=='-' ){ |
| 32508 | if( flag_alternateform |
| 32509 | && !flag_prefix |
| 32510 | && xtype==etFLOAT |
| 32511 | && s.iDP<=iRound |
| 32512 | ){ |
| 32513 | /* Suppress the minus sign if all of the following are true: |
| 32514 | ** * The value displayed is zero |
| 32515 | ** * The '#' flag is used |
| 32516 | ** * The '+' flag is not used, and |
| 32517 | ** * The format is %f |
| 32518 | */ |
| 32519 | prefix = 0; |
| 32520 | }else{ |
| 32521 | prefix = '-'; |
| 32522 | } |
| 32523 | }else{ |
| 32524 | prefix = flag_prefix; |
| 32525 | } |
| 32526 | |
| 32527 | exp = s.iDP-1; |
| @@ -33463,13 +33728,17 @@ | |
| 33728 | sqlite3StrAccumFinish(&x); |
| 33729 | sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1); |
| 33730 | n = 0; |
| 33731 | if( pItem->fg.isSubquery ) n++; |
| 33732 | if( pItem->fg.isTabFunc ) n++; |
| 33733 | if( pItem->fg.isUsing || pItem->u3.pOn!=0 ) n++; |
| 33734 | if( pItem->fg.isUsing ){ |
| 33735 | sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); |
| 33736 | }else if( pItem->u3.pOn!=0 ){ |
| 33737 | sqlite3TreeViewItem(pView, "ON", (--n)>0); |
| 33738 | sqlite3TreeViewExpr(pView, pItem->u3.pOn, 0); |
| 33739 | sqlite3TreeViewPop(&pView); |
| 33740 | } |
| 33741 | if( pItem->fg.isSubquery ){ |
| 33742 | assert( n==1 ); |
| 33743 | if( pItem->pSTab ){ |
| 33744 | Table *pTab = pItem->pSTab; |
| @@ -38108,11 +38377,11 @@ | |
| 38377 | static int kvstorageDelete(const char*, const char *zKey); |
| 38378 | static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); |
| 38379 | #define KVSTORAGE_KEY_SZ 32 |
| 38380 | |
| 38381 | /* Expand the key name with an appropriate prefix and put the result |
| 38382 | ** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least |
| 38383 | ** KVSTORAGE_KEY_SZ bytes. |
| 38384 | */ |
| 38385 | static void kvstorageMakeKey( |
| 38386 | const char *zClass, |
| 38387 | const char *zKeyIn, |
| @@ -38167,14 +38436,16 @@ | |
| 38436 | ** enough to hold it all. The value put into zBuf must always be zero |
| 38437 | ** terminated, even if it gets truncated because nBuf is not large enough. |
| 38438 | ** |
| 38439 | ** Return the total number of bytes in the data, without truncation, and |
| 38440 | ** not counting the final zero terminator. Return -1 if the key does |
| 38441 | ** not exist or its key cannot be read. |
| 38442 | ** |
| 38443 | ** If nBuf<=0 then this routine simply returns the size of the data |
| 38444 | ** without actually reading it. Similarly, if nBuf==1 then it |
| 38445 | ** zero-terminates zBuf at zBuf[0] and returns the size of the data |
| 38446 | ** without reading it. |
| 38447 | */ |
| 38448 | static int kvstorageRead( |
| 38449 | const char *zClass, |
| 38450 | const char *zKey, |
| 38451 | char *zBuf, |
| @@ -38219,15 +38490,13 @@ | |
| 38490 | /* |
| 38491 | ** An internal level of indirection which enables us to replace the |
| 38492 | ** kvvfs i/o methods with JavaScript implementations in WASM builds. |
| 38493 | ** Maintenance reminder: if this struct changes in any way, the JSON |
| 38494 | ** rendering of its structure must be updated in |
| 38495 | ** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary |
| 38496 | ** compatibility concerns, so it does not need an iVersion |
| 38497 | ** member. |
| 38498 | */ |
| 38499 | typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; |
| 38500 | struct sqlite3_kvvfs_methods { |
| 38501 | int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); |
| 38502 | int (*xWrite)(const char *zClass, const char *zKey, const char *zData); |
| @@ -38240,12 +38509,12 @@ | |
| 38509 | ** for JavaScript-side implementations in WASM builds. In such builds |
| 38510 | ** it cannot be const, but in native builds it should be so that |
| 38511 | ** the compiler can hopefully optimize this level of indirection out. |
| 38512 | ** That said, kvvfs is intended primarily for use in WASM builds. |
| 38513 | ** |
| 38514 | ** This is not explicitly flagged as static because the amalgamation |
| 38515 | ** build will tag it with SQLITE_PRIVATE. |
| 38516 | */ |
| 38517 | #ifndef SQLITE_WASM |
| 38518 | const |
| 38519 | #endif |
| 38520 | SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { |
| @@ -39414,14 +39683,15 @@ | |
| 39683 | #define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ |
| 39684 | aSyscall[13].pCurrent) |
| 39685 | |
| 39686 | #if defined(HAVE_FCHMOD) |
| 39687 | { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, |
| 39688 | #define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) |
| 39689 | #else |
| 39690 | { "fchmod", (sqlite3_syscall_ptr)0, 0 }, |
| 39691 | #define osFchmod(FID,MODE) 0 |
| 39692 | #endif |
| 39693 | |
| 39694 | #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE |
| 39695 | { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, |
| 39696 | #else |
| 39697 | { "fallocate", (sqlite3_syscall_ptr)0, 0 }, |
| @@ -39675,13 +39945,12 @@ | |
| 39945 | return fd; |
| 39946 | } |
| 39947 | |
| 39948 | /* |
| 39949 | ** Helper functions to obtain and relinquish the global mutex. The |
| 39950 | ** global mutex is used to protect the unixInodeInfo objects used by |
| 39951 | ** this file, all of which may be shared by multiple threads. |
| 39952 | ** |
| 39953 | ** Function unixMutexHeld() is used to assert() that the global mutex |
| 39954 | ** is held when required. This function is only used as part of assert() |
| 39955 | ** statements. e.g. |
| 39956 | ** |
| @@ -39879,10 +40148,11 @@ | |
| 40148 | /* |
| 40149 | ** All unique filenames are held on a linked list headed by this |
| 40150 | ** variable: |
| 40151 | */ |
| 40152 | static struct vxworksFileId *vxworksFileList = 0; |
| 40153 | static sqlite3_mutex *vxworksMutex = 0; |
| 40154 | |
| 40155 | /* |
| 40156 | ** Simplify a filename into its canonical form |
| 40157 | ** by making the following changes: |
| 40158 | ** |
| @@ -39944,47 +40214,47 @@ | |
| 40214 | |
| 40215 | /* Search for an existing entry that matching the canonical name. |
| 40216 | ** If found, increment the reference count and return a pointer to |
| 40217 | ** the existing file ID. |
| 40218 | */ |
| 40219 | sqlite3_mutex_enter(vxworksMutex); |
| 40220 | for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ |
| 40221 | if( pCandidate->nName==n |
| 40222 | && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 |
| 40223 | ){ |
| 40224 | sqlite3_free(pNew); |
| 40225 | pCandidate->nRef++; |
| 40226 | sqlite3_mutex_leave(vxworksMutex); |
| 40227 | return pCandidate; |
| 40228 | } |
| 40229 | } |
| 40230 | |
| 40231 | /* No match was found. We will make a new file ID */ |
| 40232 | pNew->nRef = 1; |
| 40233 | pNew->nName = n; |
| 40234 | pNew->pNext = vxworksFileList; |
| 40235 | vxworksFileList = pNew; |
| 40236 | sqlite3_mutex_leave(vxworksMutex); |
| 40237 | return pNew; |
| 40238 | } |
| 40239 | |
| 40240 | /* |
| 40241 | ** Decrement the reference count on a vxworksFileId object. Free |
| 40242 | ** the object when the reference count reaches zero. |
| 40243 | */ |
| 40244 | static void vxworksReleaseFileId(struct vxworksFileId *pId){ |
| 40245 | sqlite3_mutex_enter(vxworksMutex); |
| 40246 | assert( pId->nRef>0 ); |
| 40247 | pId->nRef--; |
| 40248 | if( pId->nRef==0 ){ |
| 40249 | struct vxworksFileId **pp; |
| 40250 | for(pp=&vxworksFileList; *pp && *pp!=pId; pp = &((*pp)->pNext)){} |
| 40251 | assert( *pp==pId ); |
| 40252 | *pp = pId->pNext; |
| 40253 | sqlite3_free(pId); |
| 40254 | } |
| 40255 | sqlite3_mutex_leave(vxworksMutex); |
| 40256 | } |
| 40257 | #endif /* OS_VXWORKS */ |
| 40258 | /*************** End of Unique File ID Utility Used By VxWorks **************** |
| 40259 | ******************************************************************************/ |
| 40260 | |
| @@ -40368,10 +40638,14 @@ | |
| 40638 | do{ rc = osWrite(fd, "S", 1); }while( rc<0 && errno==EINTR ); |
| 40639 | if( rc!=1 ){ |
| 40640 | storeLastErrno(pFile, errno); |
| 40641 | return SQLITE_IOERR; |
| 40642 | } |
| 40643 | if( fsync(fd) ){ |
| 40644 | storeLastErrno(pFile, errno); |
| 40645 | return SQLITE_IOERR_FSYNC; |
| 40646 | } |
| 40647 | rc = osFstat(fd, &statbuf); |
| 40648 | if( rc!=0 ){ |
| 40649 | storeLastErrno(pFile, errno); |
| 40650 | return SQLITE_IOERR; |
| 40651 | } |
| @@ -40537,22 +40811,46 @@ | |
| 40811 | static int osSetPosixAdvisoryLock( |
| 40812 | int h, /* The file descriptor on which to take the lock */ |
| 40813 | struct flock *pLock, /* The description of the lock */ |
| 40814 | unixFile *pFile /* Structure holding timeout value */ |
| 40815 | ){ |
| 40816 | int rc = 0; |
| 40817 | |
| 40818 | if( pFile->iBusyTimeout==0 ){ |
| 40819 | /* unixFile->iBusyTimeout is set to 0. In this case, attempt a |
| 40820 | ** non-blocking lock. */ |
| 40821 | rc = osFcntl(h,F_SETLK,pLock); |
| 40822 | }else{ |
| 40823 | /* unixFile->iBusyTimeout is set to greater than zero. In this case, |
| 40824 | ** attempt a blocking-lock with a unixFile->iBusyTimeout ms timeout. |
| 40825 | ** |
| 40826 | ** On systems that support some kind of blocking file lock operation, |
| 40827 | ** this block should be replaced by code to attempt a blocking lock |
| 40828 | ** with a timeout of unixFile->iBusyTimeout ms. The code below is |
| 40829 | ** placeholder code. If SQLITE_TEST is defined, the placeholder code |
| 40830 | ** retries the lock once every 1ms until it succeeds or the timeout |
| 40831 | ** is reached. Or, if SQLITE_TEST is not defined, the placeholder |
| 40832 | ** code attempts a non-blocking lock and sets unixFile->iBusyTimeout |
| 40833 | ** to 0. This causes the caller to return SQLITE_BUSY, instead of |
| 40834 | ** SQLITE_BUSY_TIMEOUT to SQLite - as required by a VFS that does not |
| 40835 | ** support blocking locks. |
| 40836 | */ |
| 40837 | #ifdef SQLITE_TEST |
| 40838 | int tm = pFile->iBusyTimeout; |
| 40839 | while( tm>0 ){ |
| 40840 | rc = osFcntl(h,F_SETLK,pLock); |
| 40841 | if( rc==0 ) break; |
| 40842 | unixSleep(0,1000); |
| 40843 | tm--; |
| 40844 | } |
| 40845 | #else |
| 40846 | rc = osFcntl(h,F_SETLK,pLock); |
| 40847 | pFile->iBusyTimeout = 0; |
| 40848 | #endif |
| 40849 | /* End of code to replace with real blocking-locks code. */ |
| 40850 | } |
| 40851 | |
| 40852 | return rc; |
| 40853 | } |
| 40854 | #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ |
| 40855 | |
| 40856 | |
| @@ -44869,14 +45167,21 @@ | |
| 45167 | #endif |
| 45168 | |
| 45169 | storeLastErrno(pNew, 0); |
| 45170 | #if OS_VXWORKS |
| 45171 | if( rc!=SQLITE_OK ){ |
| 45172 | if( h>=0 ){ |
| 45173 | robust_close(pNew, h, __LINE__); |
| 45174 | h = -1; |
| 45175 | } |
| 45176 | if( pNew->ctrlFlags & UNIXFILE_DELETE ){ |
| 45177 | osUnlink(zFilename); |
| 45178 | } |
| 45179 | if( pNew->pId ){ |
| 45180 | vxworksReleaseFileId(pNew->pId); |
| 45181 | pNew->pId = 0; |
| 45182 | } |
| 45183 | } |
| 45184 | #endif |
| 45185 | if( rc!=SQLITE_OK ){ |
| 45186 | if( h>=0 ) robust_close(pNew, h, __LINE__); |
| 45187 | }else{ |
| @@ -44916,10 +45221,13 @@ | |
| 45221 | struct stat buf; |
| 45222 | const char *zDir = sqlite3_temp_directory; |
| 45223 | |
| 45224 | while(1){ |
| 45225 | if( zDir!=0 |
| 45226 | #if OS_VXWORKS |
| 45227 | && zDir[0]=='/' |
| 45228 | #endif |
| 45229 | && osStat(zDir, &buf)==0 |
| 45230 | && S_ISDIR(buf.st_mode) |
| 45231 | && osAccess(zDir, 03)==0 |
| 45232 | ){ |
| 45233 | return zDir; |
| @@ -45229,10 +45537,16 @@ | |
| 45537 | assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB |
| 45538 | || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL |
| 45539 | || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL |
| 45540 | || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL |
| 45541 | ); |
| 45542 | |
| 45543 | #if OS_VXWORKS |
| 45544 | /* The file-ID mechanism used in Vxworks requires that all pathnames |
| 45545 | ** provided to unixOpen must be absolute pathnames. */ |
| 45546 | if( zPath!=0 && zPath[0]!='/' ){ return SQLITE_CANTOPEN; } |
| 45547 | #endif |
| 45548 | |
| 45549 | /* Detect a pid change and reset the PRNG. There is a race condition |
| 45550 | ** here such that two or more threads all trying to open databases at |
| 45551 | ** the same instant might all reset the PRNG. But multiple resets |
| 45552 | ** are harmless. |
| @@ -45430,12 +45744,15 @@ | |
| 45744 | goto open_finished; |
| 45745 | } |
| 45746 | } |
| 45747 | #endif |
| 45748 | |
| 45749 | assert( zPath==0 |
| 45750 | || zPath[0]=='/' |
| 45751 | || eType==SQLITE_OPEN_SUPER_JOURNAL |
| 45752 | || eType==SQLITE_OPEN_MAIN_JOURNAL |
| 45753 | || eType==SQLITE_OPEN_TEMP_JOURNAL |
| 45754 | ); |
| 45755 | rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); |
| 45756 | |
| 45757 | open_finished: |
| 45758 | if( rc!=SQLITE_OK ){ |
| @@ -47160,10 +47477,13 @@ | |
| 47477 | } |
| 47478 | #ifdef SQLITE_OS_KV_OPTIONAL |
| 47479 | sqlite3KvvfsInit(); |
| 47480 | #endif |
| 47481 | unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); |
| 47482 | #if OS_VXWORKS |
| 47483 | vxworksMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS2); |
| 47484 | #endif |
| 47485 | |
| 47486 | #ifndef SQLITE_OMIT_WAL |
| 47487 | /* Validate lock assumptions */ |
| 47488 | assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ |
| 47489 | assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ |
| @@ -47194,10 +47514,13 @@ | |
| 47514 | ** to release dynamically allocated objects. But not on unix. |
| 47515 | ** This routine is a no-op for unix. |
| 47516 | */ |
| 47517 | SQLITE_API int sqlite3_os_end(void){ |
| 47518 | unixBigLock = 0; |
| 47519 | #if OS_VXWORKS |
| 47520 | vxworksMutex = 0; |
| 47521 | #endif |
| 47522 | return SQLITE_OK; |
| 47523 | } |
| 47524 | |
| 47525 | #endif /* SQLITE_OS_UNIX */ |
| 47526 | |
| @@ -51192,204 +51515,10 @@ | |
| 51515 | ** on allocation size granularity boundaries. |
| 51516 | ** During sqlite3_os_init() we do a GetSystemInfo() |
| 51517 | ** to get the granularity size. |
| 51518 | */ |
| 51519 | static SYSTEM_INFO winSysInfo; |
| 51520 | |
| 51521 | /* |
| 51522 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51523 | ** operating system wants filenames in. Space to hold the result |
| 51524 | ** is obtained from malloc and must be freed by the calling |
| @@ -51483,10 +51612,212 @@ | |
| 51612 | } |
| 51613 | #endif |
| 51614 | /* caller will handle out of memory */ |
| 51615 | return zConverted; |
| 51616 | } |
| 51617 | |
| 51618 | #ifndef SQLITE_OMIT_WAL |
| 51619 | |
| 51620 | /* |
| 51621 | ** Helper functions to obtain and relinquish the global mutex. The |
| 51622 | ** global mutex is used to protect the winLockInfo objects used by |
| 51623 | ** this file, all of which may be shared by multiple threads. |
| 51624 | ** |
| 51625 | ** Function winShmMutexHeld() is used to assert() that the global mutex |
| 51626 | ** is held when required. This function is only used as part of assert() |
| 51627 | ** statements. e.g. |
| 51628 | ** |
| 51629 | ** winShmEnterMutex() |
| 51630 | ** assert( winShmMutexHeld() ); |
| 51631 | ** winShmLeaveMutex() |
| 51632 | */ |
| 51633 | static sqlite3_mutex *winBigLock = 0; |
| 51634 | static void winShmEnterMutex(void){ |
| 51635 | sqlite3_mutex_enter(winBigLock); |
| 51636 | } |
| 51637 | static void winShmLeaveMutex(void){ |
| 51638 | sqlite3_mutex_leave(winBigLock); |
| 51639 | } |
| 51640 | #ifndef NDEBUG |
| 51641 | static int winShmMutexHeld(void) { |
| 51642 | return sqlite3_mutex_held(winBigLock); |
| 51643 | } |
| 51644 | #endif |
| 51645 | |
| 51646 | /* |
| 51647 | ** Object used to represent a single file opened and mmapped to provide |
| 51648 | ** shared memory. When multiple threads all reference the same |
| 51649 | ** log-summary, each thread has its own winFile object, but they all |
| 51650 | ** point to a single instance of this object. In other words, each |
| 51651 | ** log-summary is opened only once per process. |
| 51652 | ** |
| 51653 | ** winShmMutexHeld() must be true when creating or destroying |
| 51654 | ** this object, or while editing the global linked list that starts |
| 51655 | ** at winShmNodeList. |
| 51656 | ** |
| 51657 | ** When reading or writing the linked list starting at winShmNode.pWinShmList, |
| 51658 | ** pShmNode->mutex must be held. |
| 51659 | ** |
| 51660 | ** The following fields are constant after the object is created: |
| 51661 | ** |
| 51662 | ** zFilename |
| 51663 | ** hSharedShm |
| 51664 | ** mutex |
| 51665 | ** bUseSharedLockHandle |
| 51666 | ** |
| 51667 | ** Either winShmNode.mutex must be held or winShmNode.pWinShmList==0 and |
| 51668 | ** winShmMutexHeld() is true when reading or writing any other field |
| 51669 | ** in this structure. |
| 51670 | ** |
| 51671 | ** File-handle hSharedShm is always used to (a) take the DMS lock, (b) |
| 51672 | ** truncate the *-shm file if the DMS-locking protocol demands it, and |
| 51673 | ** (c) map regions of the *-shm file into memory using MapViewOfFile() |
| 51674 | ** or similar. If bUseSharedLockHandle is true, then other locks are also |
| 51675 | ** taken on hSharedShm. Or, if bUseSharedLockHandle is false, then other |
| 51676 | ** locks are taken using each connection's winShm.hShm handles. |
| 51677 | */ |
| 51678 | struct winShmNode { |
| 51679 | sqlite3_mutex *mutex; /* Mutex to access this object */ |
| 51680 | char *zFilename; /* Name of the file */ |
| 51681 | HANDLE hSharedShm; /* File handle open on zFilename */ |
| 51682 | int bUseSharedLockHandle; /* True to use hSharedShm for everything */ |
| 51683 | |
| 51684 | int isUnlocked; /* DMS lock has not yet been obtained */ |
| 51685 | int isReadonly; /* True if read-only */ |
| 51686 | int szRegion; /* Size of shared-memory regions */ |
| 51687 | int nRegion; /* Size of array apRegion */ |
| 51688 | |
| 51689 | struct ShmRegion { |
| 51690 | HANDLE hMap; /* File handle from CreateFileMapping */ |
| 51691 | void *pMap; |
| 51692 | } *aRegion; |
| 51693 | DWORD lastErrno; /* The Windows errno from the last I/O error */ |
| 51694 | |
| 51695 | winShm *pWinShmList; /* List of winShm objects with ptrs to this */ |
| 51696 | |
| 51697 | winShmNode *pNext; /* Next in list of all winShmNode objects */ |
| 51698 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51699 | u8 nextShmId; /* Next available winShm.id value */ |
| 51700 | #endif |
| 51701 | }; |
| 51702 | |
| 51703 | /* |
| 51704 | ** A global array of all winShmNode objects. |
| 51705 | ** |
| 51706 | ** The winShmMutexHeld() must be true while reading or writing this list. |
| 51707 | */ |
| 51708 | static winShmNode *winShmNodeList = 0; |
| 51709 | |
| 51710 | /* |
| 51711 | ** Structure used internally by this VFS to record the state of an |
| 51712 | ** open shared memory connection. There is one such structure for each |
| 51713 | ** winFile open on a wal mode database. |
| 51714 | */ |
| 51715 | struct winShm { |
| 51716 | winShmNode *pShmNode; /* The underlying winShmNode object */ |
| 51717 | u16 sharedMask; /* Mask of shared locks held */ |
| 51718 | u16 exclMask; /* Mask of exclusive locks held */ |
| 51719 | HANDLE hShm; /* File-handle on *-shm file. For locking. */ |
| 51720 | int bReadonly; /* True if hShm is opened read-only */ |
| 51721 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 51722 | u8 id; /* Id of this connection with its winShmNode */ |
| 51723 | #endif |
| 51724 | winShm *pWinShmNext; /* Next winShm object on same winShmNode */ |
| 51725 | }; |
| 51726 | |
| 51727 | /* |
| 51728 | ** Constants used for locking |
| 51729 | */ |
| 51730 | #define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ |
| 51731 | #define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ |
| 51732 | |
| 51733 | /* Forward references to VFS methods */ |
| 51734 | static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); |
| 51735 | static int winDelete(sqlite3_vfs *,const char*,int); |
| 51736 | |
| 51737 | /* |
| 51738 | ** Purge the winShmNodeList list of all entries with winShmNode.pWinShmList==0. |
| 51739 | ** |
| 51740 | ** This is not a VFS shared-memory method; it is a utility function called |
| 51741 | ** by VFS shared-memory methods. |
| 51742 | */ |
| 51743 | static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ |
| 51744 | winShmNode **pp; |
| 51745 | winShmNode *p; |
| 51746 | assert( winShmMutexHeld() ); |
| 51747 | OSTRACE(("SHM-PURGE pid=%lu, deleteFlag=%d\n", |
| 51748 | osGetCurrentProcessId(), deleteFlag)); |
| 51749 | pp = &winShmNodeList; |
| 51750 | while( (p = *pp)!=0 ){ |
| 51751 | if( p->pWinShmList==0 ){ |
| 51752 | int i; |
| 51753 | if( p->mutex ){ sqlite3_mutex_free(p->mutex); } |
| 51754 | for(i=0; i<p->nRegion; i++){ |
| 51755 | BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap); |
| 51756 | OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n", |
| 51757 | osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); |
| 51758 | UNUSED_VARIABLE_VALUE(bRc); |
| 51759 | bRc = osCloseHandle(p->aRegion[i].hMap); |
| 51760 | OSTRACE(("SHM-PURGE-CLOSE pid=%lu, region=%d, rc=%s\n", |
| 51761 | osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); |
| 51762 | UNUSED_VARIABLE_VALUE(bRc); |
| 51763 | } |
| 51764 | winHandleClose(p->hSharedShm); |
| 51765 | if( deleteFlag ){ |
| 51766 | SimulateIOErrorBenign(1); |
| 51767 | sqlite3BeginBenignMalloc(); |
| 51768 | winDelete(pVfs, p->zFilename, 0); |
| 51769 | sqlite3EndBenignMalloc(); |
| 51770 | SimulateIOErrorBenign(0); |
| 51771 | } |
| 51772 | *pp = p->pNext; |
| 51773 | sqlite3_free(p->aRegion); |
| 51774 | sqlite3_free(p); |
| 51775 | }else{ |
| 51776 | pp = &p->pNext; |
| 51777 | } |
| 51778 | } |
| 51779 | } |
| 51780 | |
| 51781 | /* |
| 51782 | ** The DMS lock has not yet been taken on the shm file associated with |
| 51783 | ** pShmNode. Take the lock. Truncate the *-shm file if required. |
| 51784 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. |
| 51785 | */ |
| 51786 | static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ |
| 51787 | HANDLE h = pShmNode->hSharedShm; |
| 51788 | int rc = SQLITE_OK; |
| 51789 | |
| 51790 | assert( sqlite3_mutex_held(pShmNode->mutex) ); |
| 51791 | rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 1, 0); |
| 51792 | if( rc==SQLITE_OK ){ |
| 51793 | /* We have an EXCLUSIVE lock on the DMS byte. This means that this |
| 51794 | ** is the first process to open the file. Truncate it to zero bytes |
| 51795 | ** in this case. */ |
| 51796 | if( pShmNode->isReadonly ){ |
| 51797 | rc = SQLITE_READONLY_CANTINIT; |
| 51798 | }else{ |
| 51799 | rc = winHandleTruncate(h, 0); |
| 51800 | } |
| 51801 | |
| 51802 | /* Release the EXCLUSIVE lock acquired above. */ |
| 51803 | winUnlockFile(&h, WIN_SHM_DMS, 0, 1, 0); |
| 51804 | }else if( (rc & 0xFF)==SQLITE_BUSY ){ |
| 51805 | rc = SQLITE_OK; |
| 51806 | } |
| 51807 | |
| 51808 | if( rc==SQLITE_OK ){ |
| 51809 | /* Take a SHARED lock on the DMS byte. */ |
| 51810 | rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 0, nMs); |
| 51811 | if( rc==SQLITE_OK ){ |
| 51812 | pShmNode->isUnlocked = 0; |
| 51813 | } |
| 51814 | } |
| 51815 | |
| 51816 | return rc; |
| 51817 | } |
| 51818 | |
| 51819 | |
| 51820 | /* |
| 51821 | ** This function is used to open a handle on a *-shm file. |
| 51822 | ** |
| 51823 | ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file |
| @@ -51579,10 +51910,64 @@ | |
| 51910 | *pbReadonly = bReadonly; |
| 51911 | *ph = h; |
| 51912 | return rc; |
| 51913 | } |
| 51914 | |
| 51915 | /* |
| 51916 | ** Close pDbFd's connection to shared-memory. Delete the underlying |
| 51917 | ** *-shm file if deleteFlag is true. |
| 51918 | */ |
| 51919 | static int winCloseSharedMemory(winFile *pDbFd, int deleteFlag){ |
| 51920 | winShm *p; /* The connection to be closed */ |
| 51921 | winShm **pp; /* Iterator for pShmNode->pWinShmList */ |
| 51922 | winShmNode *pShmNode; /* The underlying shared-memory file */ |
| 51923 | |
| 51924 | p = pDbFd->pShm; |
| 51925 | if( p==0 ) return SQLITE_OK; |
| 51926 | if( p->hShm!=INVALID_HANDLE_VALUE ){ |
| 51927 | osCloseHandle(p->hShm); |
| 51928 | } |
| 51929 | |
| 51930 | winShmEnterMutex(); |
| 51931 | pShmNode = p->pShmNode; |
| 51932 | |
| 51933 | /* Remove this connection from the winShmNode.pWinShmList list */ |
| 51934 | sqlite3_mutex_enter(pShmNode->mutex); |
| 51935 | for(pp=&pShmNode->pWinShmList; *pp!=p; pp=&(*pp)->pWinShmNext){} |
| 51936 | *pp = p->pWinShmNext; |
| 51937 | sqlite3_mutex_leave(pShmNode->mutex); |
| 51938 | |
| 51939 | winShmPurge(pDbFd->pVfs, deleteFlag); |
| 51940 | winShmLeaveMutex(); |
| 51941 | |
| 51942 | /* Free the connection p */ |
| 51943 | sqlite3_free(p); |
| 51944 | pDbFd->pShm = 0; |
| 51945 | return SQLITE_OK; |
| 51946 | } |
| 51947 | |
| 51948 | /* |
| 51949 | ** testfixture builds may set this global variable to true via a |
| 51950 | ** Tcl interface. This forces the VFS to use the locking normally |
| 51951 | ** only used for UNC paths for all files. |
| 51952 | */ |
| 51953 | #ifdef SQLITE_TEST |
| 51954 | SQLITE_API int sqlite3_win_test_unc_locking = 0; |
| 51955 | #else |
| 51956 | # define sqlite3_win_test_unc_locking 0 |
| 51957 | #endif |
| 51958 | |
| 51959 | /* |
| 51960 | ** Return true if the string passed as the only argument is likely |
| 51961 | ** to be a UNC path. In other words, if it starts with "\\". |
| 51962 | */ |
| 51963 | static int winIsUNCPath(const char *zFile){ |
| 51964 | if( zFile[0]=='\\' && zFile[1]=='\\' ){ |
| 51965 | return 1; |
| 51966 | } |
| 51967 | return sqlite3_win_test_unc_locking; |
| 51968 | } |
| 51969 | |
| 51970 | /* |
| 51971 | ** Open the shared-memory area associated with database file pDbFd. |
| 51972 | */ |
| 51973 | static int winOpenSharedMemory(winFile *pDbFd){ |
| @@ -51605,19 +51990,14 @@ | |
| 51990 | return SQLITE_IOERR_NOMEM_BKPT; |
| 51991 | } |
| 51992 | pNew->zFilename = (char*)&pNew[1]; |
| 51993 | pNew->hSharedShm = INVALID_HANDLE_VALUE; |
| 51994 | pNew->isUnlocked = 1; |
| 51995 | pNew->bUseSharedLockHandle = winIsUNCPath(pDbFd->zPath); |
| 51996 | sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); |
| 51997 | sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); |
| 51998 | |
| 51999 | /* Look to see if there is an existing winShmNode that can be used. |
| 52000 | ** If no matching winShmNode currently exists, then create a new one. */ |
| 52001 | winShmEnterMutex(); |
| 52002 | for(pShmNode = winShmNodeList; pShmNode; pShmNode=pShmNode->pNext){ |
| 52003 | /* TBD need to come up with better match here. Perhaps |
| @@ -51634,11 +52014,11 @@ | |
| 52014 | } |
| 52015 | |
| 52016 | /* Open a file-handle to use for mappings, and for the DMS lock. */ |
| 52017 | if( rc==SQLITE_OK ){ |
| 52018 | HANDLE h = INVALID_HANDLE_VALUE; |
| 52019 | pShmNode->isReadonly = sqlite3_uri_boolean(pDbFd->zPath,"readonly_shm",0); |
| 52020 | rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h); |
| 52021 | pShmNode->hSharedShm = h; |
| 52022 | } |
| 52023 | |
| 52024 | /* If successful, link the new winShmNode into the global list. If an |
| @@ -51656,24 +52036,39 @@ | |
| 52036 | } |
| 52037 | |
| 52038 | /* If no error has occurred, link the winShm object to the winShmNode and |
| 52039 | ** the winShm to pDbFd. */ |
| 52040 | if( rc==SQLITE_OK ){ |
| 52041 | sqlite3_mutex_enter(pShmNode->mutex); |
| 52042 | p->pShmNode = pShmNode; |
| 52043 | p->pWinShmNext = pShmNode->pWinShmList; |
| 52044 | pShmNode->pWinShmList = p; |
| 52045 | #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) |
| 52046 | p->id = pShmNode->nextShmId++; |
| 52047 | #endif |
| 52048 | pDbFd->pShm = p; |
| 52049 | sqlite3_mutex_leave(pShmNode->mutex); |
| 52050 | }else if( p ){ |
| 52051 | sqlite3_free(p); |
| 52052 | } |
| 52053 | |
| 52054 | assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 ); |
| 52055 | winShmLeaveMutex(); |
| 52056 | sqlite3_free(pNew); |
| 52057 | |
| 52058 | /* Open a file-handle on the *-shm file for this connection. This file-handle |
| 52059 | ** is only used for locking. The mapping of the *-shm file is created using |
| 52060 | ** the shared file handle in winShmNode.hSharedShm. */ |
| 52061 | if( rc==SQLITE_OK && pShmNode->bUseSharedLockHandle==0 ){ |
| 52062 | p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); |
| 52063 | rc = winHandleOpen(pShmNode->zFilename, &p->bReadonly, &p->hShm); |
| 52064 | if( rc!=SQLITE_OK ){ |
| 52065 | assert( p->hShm==INVALID_HANDLE_VALUE ); |
| 52066 | winCloseSharedMemory(pDbFd, 0); |
| 52067 | } |
| 52068 | } |
| 52069 | |
| 52070 | return rc; |
| 52071 | } |
| 52072 | |
| 52073 | /* |
| 52074 | ** Close a connection to shared-memory. Delete the underlying |
| @@ -51681,37 +52076,11 @@ | |
| 52076 | */ |
| 52077 | static int winShmUnmap( |
| 52078 | sqlite3_file *fd, /* Database holding shared memory */ |
| 52079 | int deleteFlag /* Delete after closing if true */ |
| 52080 | ){ |
| 52081 | return winCloseSharedMemory((winFile*)fd, deleteFlag); |
| 52082 | } |
| 52083 | |
| 52084 | /* |
| 52085 | ** Change the lock state for a shared-memory segment. |
| 52086 | */ |
| @@ -51776,29 +52145,75 @@ | |
| 52145 | ); |
| 52146 | if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) |
| 52147 | || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) |
| 52148 | || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) |
| 52149 | ){ |
| 52150 | HANDLE h = p->hShm; |
| 52151 | |
| 52152 | if( flags & SQLITE_SHM_UNLOCK ){ |
| 52153 | /* Case (a) - unlock. */ |
| 52154 | |
| 52155 | assert( (p->exclMask & p->sharedMask)==0 ); |
| 52156 | assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); |
| 52157 | assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); |
| 52158 | |
| 52159 | assert( !(flags & SQLITE_SHM_SHARED) || n==1 ); |
| 52160 | if( pShmNode->bUseSharedLockHandle ){ |
| 52161 | h = pShmNode->hSharedShm; |
| 52162 | if( flags & SQLITE_SHM_SHARED ){ |
| 52163 | winShm *pShm; |
| 52164 | sqlite3_mutex_enter(pShmNode->mutex); |
| 52165 | for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ |
| 52166 | if( pShm!=p && (pShm->sharedMask & mask) ){ |
| 52167 | /* Another connection within this process is also holding this |
| 52168 | ** SHARED lock. So do not actually release the OS lock. */ |
| 52169 | h = INVALID_HANDLE_VALUE; |
| 52170 | break; |
| 52171 | } |
| 52172 | } |
| 52173 | sqlite3_mutex_leave(pShmNode->mutex); |
| 52174 | } |
| 52175 | } |
| 52176 | |
| 52177 | if( h!=INVALID_HANDLE_VALUE ){ |
| 52178 | rc = winHandleUnlock(h, ofst+WIN_SHM_BASE, n); |
| 52179 | } |
| 52180 | |
| 52181 | /* If successful, also clear the bits in sharedMask/exclMask */ |
| 52182 | if( rc==SQLITE_OK ){ |
| 52183 | p->exclMask = (p->exclMask & ~mask); |
| 52184 | p->sharedMask = (p->sharedMask & ~mask); |
| 52185 | } |
| 52186 | }else{ |
| 52187 | int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0); |
| 52188 | DWORD nMs = winFileBusyTimeout(pDbFd); |
| 52189 | |
| 52190 | if( pShmNode->bUseSharedLockHandle ){ |
| 52191 | winShm *pShm; |
| 52192 | h = pShmNode->hSharedShm; |
| 52193 | sqlite3_mutex_enter(pShmNode->mutex); |
| 52194 | for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ |
| 52195 | if( bExcl ){ |
| 52196 | if( (pShm->sharedMask|pShm->exclMask) & mask ){ |
| 52197 | rc = SQLITE_BUSY; |
| 52198 | h = INVALID_HANDLE_VALUE; |
| 52199 | } |
| 52200 | }else{ |
| 52201 | if( pShm->sharedMask & mask ){ |
| 52202 | h = INVALID_HANDLE_VALUE; |
| 52203 | }else if( pShm->exclMask & mask ){ |
| 52204 | rc = SQLITE_BUSY; |
| 52205 | h = INVALID_HANDLE_VALUE; |
| 52206 | } |
| 52207 | } |
| 52208 | } |
| 52209 | sqlite3_mutex_leave(pShmNode->mutex); |
| 52210 | } |
| 52211 | |
| 52212 | if( h!=INVALID_HANDLE_VALUE ){ |
| 52213 | rc = winHandleLockTimeout(h, ofst+WIN_SHM_BASE, n, bExcl, nMs); |
| 52214 | } |
| 52215 | if( rc==SQLITE_OK ){ |
| 52216 | if( bExcl ){ |
| 52217 | p->exclMask = (p->exclMask | mask); |
| 52218 | }else{ |
| 52219 | p->sharedMask = (p->sharedMask | mask); |
| @@ -61825,18 +62240,31 @@ | |
| 62240 | SQLITE_PRIVATE void sqlite3PagerSetFlags( |
| 62241 | Pager *pPager, /* The pager to set safety level for */ |
| 62242 | unsigned pgFlags /* Various flags */ |
| 62243 | ){ |
| 62244 | unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; |
| 62245 | if( pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF ){ |
| 62246 | pPager->noSync = 1; |
| 62247 | pPager->fullSync = 0; |
| 62248 | pPager->extraSync = 0; |
| 62249 | }else{ |
| 62250 | pPager->noSync = 0; |
| 62251 | pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; |
| 62252 | |
| 62253 | /* Set Pager.extraSync if "PRAGMA synchronous=EXTRA" is requested, or |
| 62254 | ** if the file-system supports F2FS style atomic writes. If this flag |
| 62255 | ** is set, SQLite syncs the directory to disk immediately after deleting |
| 62256 | ** a journal file in "PRAGMA journal_mode=DELETE" mode. */ |
| 62257 | if( level==PAGER_SYNCHRONOUS_EXTRA |
| 62258 | #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE |
| 62259 | || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_BATCH_ATOMIC) |
| 62260 | #endif |
| 62261 | ){ |
| 62262 | pPager->extraSync = 1; |
| 62263 | }else{ |
| 62264 | pPager->extraSync = 0; |
| 62265 | } |
| 62266 | } |
| 62267 | if( pPager->noSync ){ |
| 62268 | pPager->syncFlags = 0; |
| 62269 | }else if( pgFlags & PAGER_FULLFSYNC ){ |
| 62270 | pPager->syncFlags = SQLITE_SYNC_FULL; |
| @@ -65725,11 +66153,11 @@ | |
| 66153 | */ |
| 66154 | sqlite3_exec(db, "PRAGMA table_list",0,0,0); |
| 66155 | } |
| 66156 | if( pPager->pWal ){ |
| 66157 | rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, |
| 66158 | (eMode<=SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), |
| 66159 | pPager->pBusyHandlerArg, |
| 66160 | pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, |
| 66161 | pnLog, pnCkpt |
| 66162 | ); |
| 66163 | } |
| @@ -70330,11 +70758,12 @@ | |
| 70758 | assert( pWal->ckptLock==0 ); |
| 70759 | assert( pWal->writeLock==0 ); |
| 70760 | |
| 70761 | /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked |
| 70762 | ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ |
| 70763 | assert( SQLITE_CHECKPOINT_NOOP<SQLITE_CHECKPOINT_PASSIVE ); |
| 70764 | assert( eMode>SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); |
| 70765 | |
| 70766 | if( pWal->readOnly ) return SQLITE_READONLY; |
| 70767 | WALTRACE(("WAL%p: checkpoint begins\n", pWal)); |
| 70768 | |
| 70769 | /* Enable blocking locks, if possible. */ |
| @@ -70347,35 +70776,39 @@ | |
| 70776 | ** checkpoint operation at the same time, the lock cannot be obtained and |
| 70777 | ** SQLITE_BUSY is returned. |
| 70778 | ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, |
| 70779 | ** it will not be invoked in this case. |
| 70780 | */ |
| 70781 | if( eMode!=SQLITE_CHECKPOINT_NOOP ){ |
| 70782 | rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); |
| 70783 | testcase( rc==SQLITE_BUSY ); |
| 70784 | testcase( rc!=SQLITE_OK && xBusy2!=0 ); |
| 70785 | if( rc==SQLITE_OK ){ |
| 70786 | pWal->ckptLock = 1; |
| 70787 | |
| 70788 | /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART |
| 70789 | ** and TRUNCATE modes also obtain the exclusive "writer" lock on the |
| 70790 | ** database file. |
| 70791 | ** |
| 70792 | ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained |
| 70793 | ** immediately, and a busy-handler is configured, it is invoked and the |
| 70794 | ** writer lock retried until either the busy-handler returns 0 or the |
| 70795 | ** lock is successfully obtained. |
| 70796 | */ |
| 70797 | if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ |
| 70798 | rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); |
| 70799 | if( rc==SQLITE_OK ){ |
| 70800 | pWal->writeLock = 1; |
| 70801 | }else if( rc==SQLITE_BUSY ){ |
| 70802 | eMode2 = SQLITE_CHECKPOINT_PASSIVE; |
| 70803 | xBusy2 = 0; |
| 70804 | rc = SQLITE_OK; |
| 70805 | } |
| 70806 | } |
| 70807 | } |
| 70808 | }else{ |
| 70809 | rc = SQLITE_OK; |
| 70810 | } |
| 70811 | |
| 70812 | |
| 70813 | /* Read the wal-index header. */ |
| 70814 | SEH_TRY { |
| @@ -70385,21 +70818,21 @@ | |
| 70818 | ** or invoke the busy handler. The only lock such a checkpoint may |
| 70819 | ** attempt to obtain is a lock on a read-slot, and it should give up |
| 70820 | ** immediately and do a partial checkpoint if it cannot obtain it. */ |
| 70821 | walDisableBlocking(pWal); |
| 70822 | rc = walIndexReadHdr(pWal, &isChanged); |
| 70823 | if( eMode2>SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); |
| 70824 | if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ |
| 70825 | sqlite3OsUnfetch(pWal->pDbFd, 0, 0); |
| 70826 | } |
| 70827 | } |
| 70828 | |
| 70829 | /* Copy data from the log to the database file. */ |
| 70830 | if( rc==SQLITE_OK ){ |
| 70831 | if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ |
| 70832 | rc = SQLITE_CORRUPT_BKPT; |
| 70833 | }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){ |
| 70834 | rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); |
| 70835 | } |
| 70836 | |
| 70837 | /* If no error occurred, set the output variables. */ |
| 70838 | if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ |
| @@ -89078,14 +89511,16 @@ | |
| 89511 | ** simple case then too. |
| 89512 | */ |
| 89513 | if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) |
| 89514 | || nTrans<=1 |
| 89515 | ){ |
| 89516 | if( needXcommit ){ |
| 89517 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 89518 | Btree *pBt = db->aDb[i].pBt; |
| 89519 | if( sqlite3BtreeTxnState(pBt)>=SQLITE_TXN_WRITE ){ |
| 89520 | rc = sqlite3BtreeCommitPhaseOne(pBt, 0); |
| 89521 | } |
| 89522 | } |
| 89523 | } |
| 89524 | |
| 89525 | /* Do the commit only if all databases successfully complete phase 1. |
| 89526 | ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an |
| @@ -89092,11 +89527,13 @@ | |
| 89527 | ** IO error while deleting or truncating a journal file. It is unlikely, |
| 89528 | ** but could happen. In this case abandon processing and return the error. |
| 89529 | */ |
| 89530 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 89531 | Btree *pBt = db->aDb[i].pBt; |
| 89532 | int txn = sqlite3BtreeTxnState(pBt); |
| 89533 | if( txn!=SQLITE_TXN_NONE ){ |
| 89534 | assert( needXcommit || txn==SQLITE_TXN_READ ); |
| 89535 | rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); |
| 89536 | } |
| 89537 | } |
| 89538 | if( rc==SQLITE_OK ){ |
| 89539 | sqlite3VtabCommit(db); |
| @@ -89347,32 +89784,35 @@ | |
| 89784 | return SQLITE_OK; |
| 89785 | } |
| 89786 | |
| 89787 | |
| 89788 | /* |
| 89789 | ** These functions are called when a transaction opened by the database |
| 89790 | ** handle associated with the VM passed as an argument is about to be |
| 89791 | ** committed. If there are outstanding foreign key constraint violations |
| 89792 | ** return an error code. Otherwise, SQLITE_OK. |
| 89793 | ** |
| 89794 | ** If there are outstanding FK violations and this function returns |
| 89795 | ** non-zero, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY |
| 89796 | ** and write an error message to it. |
| 89797 | */ |
| 89798 | #ifndef SQLITE_OMIT_FOREIGN_KEY |
| 89799 | static SQLITE_NOINLINE int vdbeFkError(Vdbe *p){ |
| 89800 | p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; |
| 89801 | p->errorAction = OE_Abort; |
| 89802 | sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); |
| 89803 | if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; |
| 89804 | return SQLITE_CONSTRAINT_FOREIGNKEY; |
| 89805 | } |
| 89806 | SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe *p){ |
| 89807 | if( p->nFkConstraint==0 ) return SQLITE_OK; |
| 89808 | return vdbeFkError(p); |
| 89809 | } |
| 89810 | SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe *p){ |
| 89811 | sqlite3 *db = p->db; |
| 89812 | if( (db->nDeferredCons+db->nDeferredImmCons)==0 ) return SQLITE_OK; |
| 89813 | return vdbeFkError(p); |
| 89814 | } |
| 89815 | #endif |
| 89816 | |
| 89817 | /* |
| 89818 | ** This routine is called the when a VDBE tries to halt. If the VDBE |
| @@ -89462,11 +89902,11 @@ | |
| 89902 | } |
| 89903 | } |
| 89904 | |
| 89905 | /* Check for immediate foreign key violations. */ |
| 89906 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89907 | (void)sqlite3VdbeCheckFkImmediate(p); |
| 89908 | } |
| 89909 | |
| 89910 | /* If the auto-commit flag is set and this is the only active writer |
| 89911 | ** VM, then we do either a commit or rollback of the current transaction. |
| 89912 | ** |
| @@ -89476,11 +89916,11 @@ | |
| 89916 | if( !sqlite3VtabInSync(db) |
| 89917 | && db->autoCommit |
| 89918 | && db->nVdbeWrite==(p->readOnly==0) |
| 89919 | ){ |
| 89920 | if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ |
| 89921 | rc = sqlite3VdbeCheckFkDeferred(p); |
| 89922 | if( rc!=SQLITE_OK ){ |
| 89923 | if( NEVER(p->readOnly) ){ |
| 89924 | sqlite3VdbeLeave(p); |
| 89925 | return SQLITE_ERROR; |
| 89926 | } |
| @@ -90341,19 +90781,19 @@ | |
| 90781 | /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ |
| 90782 | pMem->szMalloc = 0; |
| 90783 | pMem->z = 0; |
| 90784 | sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); |
| 90785 | d += sqlite3VdbeSerialTypeLen(serial_type); |
| 90786 | if( (++u)>=p->nField ) break; |
| 90787 | pMem++; |
| 90788 | } |
| 90789 | if( d>(u32)nKey && u ){ |
| 90790 | assert( CORRUPT_DB ); |
| 90791 | /* In a corrupt record entry, the last pMem might have been set up using |
| 90792 | ** uninitialized memory. Overwrite its value with NULL, to prevent |
| 90793 | ** warnings from MSAN. */ |
| 90794 | sqlite3VdbeMemSetNull(pMem-(u<p->nField)); |
| 90795 | } |
| 90796 | testcase( u == pKeyInfo->nKeyField + 1 ); |
| 90797 | testcase( u < pKeyInfo->nKeyField + 1 ); |
| 90798 | assert( u<=pKeyInfo->nKeyField + 1 ); |
| 90799 | p->nField = u; |
| @@ -90520,10 +90960,36 @@ | |
| 90960 | ** Both *pMem1 and *pMem2 contain string values. Compare the two values |
| 90961 | ** using the collation sequence pColl. As usual, return a negative , zero |
| 90962 | ** or positive value if *pMem1 is less than, equal to or greater than |
| 90963 | ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". |
| 90964 | */ |
| 90965 | static SQLITE_NOINLINE int vdbeCompareMemStringWithEncodingChange( |
| 90966 | const Mem *pMem1, |
| 90967 | const Mem *pMem2, |
| 90968 | const CollSeq *pColl, |
| 90969 | u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ |
| 90970 | ){ |
| 90971 | int rc; |
| 90972 | const void *v1, *v2; |
| 90973 | Mem c1; |
| 90974 | Mem c2; |
| 90975 | sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); |
| 90976 | sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); |
| 90977 | sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); |
| 90978 | sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); |
| 90979 | v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); |
| 90980 | v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); |
| 90981 | if( (v1==0 || v2==0) ){ |
| 90982 | if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; |
| 90983 | rc = 0; |
| 90984 | }else{ |
| 90985 | rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); |
| 90986 | } |
| 90987 | sqlite3VdbeMemReleaseMalloc(&c1); |
| 90988 | sqlite3VdbeMemReleaseMalloc(&c2); |
| 90989 | return rc; |
| 90990 | } |
| 90991 | static int vdbeCompareMemString( |
| 90992 | const Mem *pMem1, |
| 90993 | const Mem *pMem2, |
| 90994 | const CollSeq *pColl, |
| 90995 | u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ |
| @@ -90531,29 +90997,11 @@ | |
| 90997 | if( pMem1->enc==pColl->enc ){ |
| 90998 | /* The strings are already in the correct encoding. Call the |
| 90999 | ** comparison function directly */ |
| 91000 | return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); |
| 91001 | }else{ |
| 91002 | return vdbeCompareMemStringWithEncodingChange(pMem1,pMem2,pColl,prcErr); |
| 91003 | } |
| 91004 | } |
| 91005 | |
| 91006 | /* |
| 91007 | ** The input pBlob is guaranteed to be a Blob that is not marked |
| @@ -91607,11 +92055,11 @@ | |
| 92055 | |
| 92056 | preupdate.v = v; |
| 92057 | preupdate.pCsr = pCsr; |
| 92058 | preupdate.op = op; |
| 92059 | preupdate.iNewReg = iReg; |
| 92060 | preupdate.pKeyinfo = &preupdate.uKey.sKey; |
| 92061 | preupdate.pKeyinfo->db = db; |
| 92062 | preupdate.pKeyinfo->enc = ENC(db); |
| 92063 | preupdate.pKeyinfo->nKeyField = pTab->nCol; |
| 92064 | preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */ |
| 92065 | preupdate.iKey1 = iKey1; |
| @@ -91641,10 +92089,21 @@ | |
| 92089 | sqlite3DbFree(db, preupdate.apDflt); |
| 92090 | } |
| 92091 | } |
| 92092 | #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 92093 | |
| 92094 | #ifdef SQLITE_ENABLE_PERCENTILE |
| 92095 | /* |
| 92096 | ** Return the name of an SQL function associated with the sqlite3_context. |
| 92097 | */ |
| 92098 | SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ |
| 92099 | assert( pCtx!=0 ); |
| 92100 | assert( pCtx->pFunc!=0 ); |
| 92101 | return pCtx->pFunc->zName; |
| 92102 | } |
| 92103 | #endif /* SQLITE_ENABLE_PERCENTILE */ |
| 92104 | |
| 92105 | /************** End of vdbeaux.c *********************************************/ |
| 92106 | /************** Begin file vdbeapi.c *****************************************/ |
| 92107 | /* |
| 92108 | ** 2004 May 26 |
| 92109 | ** |
| @@ -93338,12 +93797,16 @@ | |
| 93797 | if( rc==SQLITE_OK ){ |
| 93798 | assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ |
| 93799 | if( zData!=0 ){ |
| 93800 | pVar = &p->aVar[i-1]; |
| 93801 | rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); |
| 93802 | if( rc==SQLITE_OK ){ |
| 93803 | if( encoding==0 ){ |
| 93804 | pVar->enc = ENC(p->db); |
| 93805 | }else{ |
| 93806 | rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); |
| 93807 | } |
| 93808 | } |
| 93809 | if( rc ){ |
| 93810 | sqlite3Error(p->db, rc); |
| 93811 | rc = sqlite3ApiExit(p->db, rc); |
| 93812 | } |
| @@ -95247,11 +95710,11 @@ | |
| 95710 | ** to run faster. |
| 95711 | */ |
| 95712 | static SQLITE_NOINLINE int vdbeColumnFromOverflow( |
| 95713 | VdbeCursor *pC, /* The BTree cursor from which we are reading */ |
| 95714 | int iCol, /* The column to read */ |
| 95715 | u32 t, /* The serial-type code for the column value */ |
| 95716 | i64 iOffset, /* Offset to the start of the content value */ |
| 95717 | u32 cacheStatus, /* Current Vdbe.cacheCtr value */ |
| 95718 | u32 colCacheCtr, /* Current value of the column cache counter */ |
| 95719 | Mem *pDest /* Store the value into this register. */ |
| 95720 | ){ |
| @@ -96256,11 +96719,11 @@ | |
| 96719 | ** exits. This opcode is used to raise foreign key constraint errors prior |
| 96720 | ** to returning results such as a row change count or the result of a |
| 96721 | ** RETURNING clause. |
| 96722 | */ |
| 96723 | case OP_FkCheck: { |
| 96724 | if( (rc = sqlite3VdbeCheckFkImmediate(p))!=SQLITE_OK ){ |
| 96725 | goto abort_due_to_error; |
| 96726 | } |
| 96727 | break; |
| 96728 | } |
| 96729 | |
| @@ -96348,11 +96811,12 @@ | |
| 96811 | flags2 = pIn2->flags & ~MEM_Str; |
| 96812 | }else if( (flags2 & MEM_Zero)!=0 ){ |
| 96813 | if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; |
| 96814 | flags2 = pIn2->flags & ~MEM_Str; |
| 96815 | } |
| 96816 | nByte = pIn1->n; |
| 96817 | nByte += pIn2->n; |
| 96818 | if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ |
| 96819 | goto too_big; |
| 96820 | } |
| 96821 | if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ |
| 96822 | goto no_mem; |
| @@ -98173,11 +98637,11 @@ | |
| 98637 | assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) ); |
| 98638 | assert( pRec->n>=0 ); |
| 98639 | len = (u32)pRec->n; |
| 98640 | serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); |
| 98641 | if( pRec->flags & MEM_Zero ){ |
| 98642 | serial_type += (u32)pRec->u.nZero*2; |
| 98643 | if( nData ){ |
| 98644 | if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; |
| 98645 | len += pRec->u.nZero; |
| 98646 | }else{ |
| 98647 | nZero += pRec->u.nZero; |
| @@ -98440,11 +98904,11 @@ | |
| 98904 | ** and this is a RELEASE command, then the current transaction |
| 98905 | ** is committed. |
| 98906 | */ |
| 98907 | int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; |
| 98908 | if( isTransaction && p1==SAVEPOINT_RELEASE ){ |
| 98909 | if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ |
| 98910 | goto vdbe_return; |
| 98911 | } |
| 98912 | db->autoCommit = 1; |
| 98913 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| 98914 | p->pc = (int)(pOp - aOp); |
| @@ -98558,11 +99022,11 @@ | |
| 99022 | */ |
| 99023 | sqlite3VdbeError(p, "cannot commit transaction - " |
| 99024 | "SQL statements in progress"); |
| 99025 | rc = SQLITE_BUSY; |
| 99026 | goto abort_due_to_error; |
| 99027 | }else if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ |
| 99028 | goto vdbe_return; |
| 99029 | }else{ |
| 99030 | db->autoCommit = (u8)desiredAutoCommit; |
| 99031 | } |
| 99032 | if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){ |
| @@ -102490,10 +102954,11 @@ | |
| 102954 | aRes[1] = aRes[2] = -1; |
| 102955 | assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE |
| 102956 | || pOp->p2==SQLITE_CHECKPOINT_FULL |
| 102957 | || pOp->p2==SQLITE_CHECKPOINT_RESTART |
| 102958 | || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE |
| 102959 | || pOp->p2==SQLITE_CHECKPOINT_NOOP |
| 102960 | ); |
| 102961 | rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); |
| 102962 | if( rc ){ |
| 102963 | if( rc!=SQLITE_BUSY ) goto abort_due_to_error; |
| 102964 | rc = SQLITE_OK; |
| @@ -104689,10 +105154,11 @@ | |
| 105154 | UnpackedRecord *pUnpacked; /* Space to unpack a record */ |
| 105155 | SorterList list; /* List for thread to write to a PMA */ |
| 105156 | SorterCompare xCompare; /* Compare function to use */ |
| 105157 | SorterFile file; /* Temp file for level-0 PMAs */ |
| 105158 | SorterFile file2; /* Space for other PMAs */ |
| 105159 | u64 nSpill; /* Total bytes written by this task */ |
| 105160 | }; |
| 105161 | |
| 105162 | |
| 105163 | /* |
| 105164 | ** Main sorter structure. A single instance of this is allocated for each |
| @@ -104809,10 +105275,11 @@ | |
| 105275 | int nBuffer; /* Size of write buffer in bytes */ |
| 105276 | int iBufStart; /* First byte of buffer to write */ |
| 105277 | int iBufEnd; /* Last byte of buffer to write */ |
| 105278 | i64 iWriteOff; /* Offset of start of buffer in file */ |
| 105279 | sqlite3_file *pFd; /* File handle to write to */ |
| 105280 | u64 nPmaSpill; /* Total number of bytes written */ |
| 105281 | }; |
| 105282 | |
| 105283 | /* |
| 105284 | ** This object is the header on a single record while that record is being |
| 105285 | ** held in memory and prior to being written out as part of a PMA. |
| @@ -105667,10 +106134,16 @@ | |
| 106134 | SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ |
| 106135 | VdbeSorter *pSorter; |
| 106136 | assert( pCsr->eCurType==CURTYPE_SORTER ); |
| 106137 | pSorter = pCsr->uc.pSorter; |
| 106138 | if( pSorter ){ |
| 106139 | /* Increment db->nSpill by the total number of bytes of data written |
| 106140 | ** to temp files by this sort operation. */ |
| 106141 | int ii; |
| 106142 | for(ii=0; ii<pSorter->nTask; ii++){ |
| 106143 | db->nSpill += pSorter->aTask[ii].nSpill; |
| 106144 | } |
| 106145 | sqlite3VdbeSorterReset(db, pSorter); |
| 106146 | sqlite3_free(pSorter->list.aMemory); |
| 106147 | sqlite3DbFree(db, pSorter); |
| 106148 | pCsr->uc.pSorter = 0; |
| 106149 | } |
| @@ -105892,10 +106365,11 @@ | |
| 106365 | if( p->iBufEnd==p->nBuffer ){ |
| 106366 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 106367 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 106368 | p->iWriteOff + p->iBufStart |
| 106369 | ); |
| 106370 | p->nPmaSpill += (p->iBufEnd - p->iBufStart); |
| 106371 | p->iBufStart = p->iBufEnd = 0; |
| 106372 | p->iWriteOff += p->nBuffer; |
| 106373 | } |
| 106374 | assert( p->iBufEnd<p->nBuffer ); |
| 106375 | |
| @@ -105908,21 +106382,24 @@ | |
| 106382 | ** The results of using the PMA-writer after this call are undefined. |
| 106383 | ** Return SQLITE_OK if flushing the buffered data succeeds or is not |
| 106384 | ** required. Otherwise, return an SQLite error code. |
| 106385 | ** |
| 106386 | ** Before returning, set *piEof to the offset immediately following the |
| 106387 | ** last byte written to the file. Also, increment (*pnSpill) by the total |
| 106388 | ** number of bytes written to the file. |
| 106389 | */ |
| 106390 | static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof, u64 *pnSpill){ |
| 106391 | int rc; |
| 106392 | if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ |
| 106393 | p->eFWErr = sqlite3OsWrite(p->pFd, |
| 106394 | &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, |
| 106395 | p->iWriteOff + p->iBufStart |
| 106396 | ); |
| 106397 | p->nPmaSpill += (p->iBufEnd - p->iBufStart); |
| 106398 | } |
| 106399 | *piEof = (p->iWriteOff + p->iBufEnd); |
| 106400 | *pnSpill += p->nPmaSpill; |
| 106401 | sqlite3_free(p->aBuffer); |
| 106402 | rc = p->eFWErr; |
| 106403 | memset(p, 0, sizeof(PmaWriter)); |
| 106404 | return rc; |
| 106405 | } |
| @@ -105998,11 +106475,11 @@ | |
| 106475 | vdbePmaWriteVarint(&writer, p->nVal); |
| 106476 | vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal); |
| 106477 | if( pList->aMemory==0 ) sqlite3_free(p); |
| 106478 | } |
| 106479 | pList->pList = p; |
| 106480 | rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof, &pTask->nSpill); |
| 106481 | } |
| 106482 | |
| 106483 | vdbeSorterWorkDebug(pTask, "exit"); |
| 106484 | assert( rc!=SQLITE_OK || pList->pList==0 ); |
| 106485 | assert( rc!=SQLITE_OK || pTask->file.iEof==iSz ); |
| @@ -106312,11 +106789,11 @@ | |
| 106789 | vdbePmaWriteBlob(&writer, pReader->aKey, nKey); |
| 106790 | assert( pIncr->pMerger->pTask==pTask ); |
| 106791 | rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); |
| 106792 | } |
| 106793 | |
| 106794 | rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof, &pTask->nSpill); |
| 106795 | if( rc==SQLITE_OK ) rc = rc2; |
| 106796 | vdbeSorterPopulateDebug(pTask, "exit"); |
| 106797 | return rc; |
| 106798 | } |
| 106799 | |
| @@ -109256,12 +109733,12 @@ | |
| 109733 | assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ |
| 109734 | if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); |
| 109735 | |
| 109736 | /* |
| 109737 | ** Expression p should encode a floating point value between 1.0 and 0.0. |
| 109738 | ** Return 134,217,728 (2^27) times this value. Or return -1 if p is not |
| 109739 | ** a floating point value between 1.0 and 0.0. |
| 109740 | */ |
| 109741 | static int exprProbability(Expr *p){ |
| 109742 | double r = -1.0; |
| 109743 | if( p->op!=TK_FLOAT ) return -1; |
| 109744 | assert( !ExprHasProperty(p, EP_IntValue) ); |
| @@ -110613,18 +111090,21 @@ | |
| 111090 | ExprList *pList /* Expression list to resolve. May be NULL. */ |
| 111091 | ){ |
| 111092 | SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */ |
| 111093 | NameContext sNC; /* Name context for pParse->pNewTable */ |
| 111094 | int rc; |
| 111095 | union { |
| 111096 | SrcList sSrc; |
| 111097 | u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ |
| 111098 | } uSrc; |
| 111099 | |
| 111100 | assert( type==0 || pTab!=0 ); |
| 111101 | assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr |
| 111102 | || type==NC_GenCol || pTab==0 ); |
| 111103 | memset(&sNC, 0, sizeof(sNC)); |
| 111104 | memset(&uSrc, 0, sizeof(uSrc)); |
| 111105 | pSrc = &uSrc.sSrc; |
| 111106 | if( pTab ){ |
| 111107 | pSrc->nSrc = 1; |
| 111108 | pSrc->a[0].zName = pTab->zName; |
| 111109 | pSrc->a[0].pSTab = pTab; |
| 111110 | pSrc->a[0].iCursor = -1; |
| @@ -111883,10 +112363,15 @@ | |
| 112363 | if( IsWindowFunc(pExpr) ){ |
| 112364 | sqlite3ExprOrderByAggregateError(pParse, pExpr); |
| 112365 | sqlite3ExprListDelete(db, pOrderBy); |
| 112366 | return; |
| 112367 | } |
| 112368 | if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ |
| 112369 | sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause"); |
| 112370 | sqlite3ExprListDelete(db, pOrderBy); |
| 112371 | return; |
| 112372 | } |
| 112373 | |
| 112374 | pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); |
| 112375 | if( pOB==0 ){ |
| 112376 | sqlite3ExprListDelete(db, pOrderBy); |
| 112377 | return; |
| @@ -113079,13 +113564,12 @@ | |
| 113564 | } |
| 113565 | r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1); |
| 113566 | if( addrIsNull==0 ){ |
| 113567 | /* |
| 113568 | ** If the right operand contains a subquery and the left operand does not |
| 113569 | ** and the left operand might be NULL, then do an IsNull check |
| 113570 | ** check on the left operand before computing the right operand. |
| 113571 | */ |
| 113572 | if( ExprHasProperty(pExpr->pRight, EP_Subquery) |
| 113573 | && sqlite3ExprCanBeNull(pExpr->pLeft) |
| 113574 | ){ |
| 113575 | addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1); |
| @@ -114645,11 +115129,10 @@ | |
| 115129 | int destIfNull /* Jump here if the results are unknown due to NULLs */ |
| 115130 | ){ |
| 115131 | int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ |
| 115132 | int eType; /* Type of the RHS */ |
| 115133 | int rLhs; /* Register(s) holding the LHS values */ |
| 115134 | Vdbe *v; /* Statement under construction */ |
| 115135 | int *aiMap = 0; /* Map from vector field to index column */ |
| 115136 | char *zAff = 0; /* Affinity string for comparisons */ |
| 115137 | int nVector; /* Size of vectors for this IN operator */ |
| 115138 | int iDummy; /* Dummy parameter to exprCodeVector() */ |
| @@ -114708,23 +115191,12 @@ | |
| 115191 | ** Avoid factoring the LHS of the IN(...) expression out of the loop, |
| 115192 | ** even if it is constant, as OP_Affinity may be used on the register |
| 115193 | ** by code generated below. */ |
| 115194 | assert( pParse->okConstFactor==okConstFactor ); |
| 115195 | pParse->okConstFactor = 0; |
| 115196 | rLhs = exprCodeVector(pParse, pLeft, &iDummy); |
| 115197 | pParse->okConstFactor = okConstFactor; |
| 115198 | |
| 115199 | /* If sqlite3FindInIndex() did not find or create an index that is |
| 115200 | ** suitable for evaluating the IN operator, then evaluate using a |
| 115201 | ** sequence of comparisons. |
| 115202 | ** |
| @@ -114735,10 +115207,11 @@ | |
| 115207 | CollSeq *pColl; |
| 115208 | int labelOk = sqlite3VdbeMakeLabel(pParse); |
| 115209 | int r2, regToFree; |
| 115210 | int regCkNull = 0; |
| 115211 | int ii; |
| 115212 | assert( nVector==1 ); |
| 115213 | assert( ExprUseXList(pExpr) ); |
| 115214 | pList = pExpr->x.pList; |
| 115215 | pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); |
| 115216 | if( destIfNull!=destIfFalse ){ |
| 115217 | regCkNull = sqlite3GetTempReg(pParse); |
| @@ -114775,10 +115248,30 @@ | |
| 115248 | } |
| 115249 | sqlite3VdbeResolveLabel(v, labelOk); |
| 115250 | sqlite3ReleaseTempReg(pParse, regCkNull); |
| 115251 | goto sqlite3ExprCodeIN_finished; |
| 115252 | } |
| 115253 | |
| 115254 | if( eType!=IN_INDEX_ROWID ){ |
| 115255 | /* If this IN operator will use an index, then the order of columns in the |
| 115256 | ** vector might be different from the order in the index. In that case, |
| 115257 | ** we need to reorder the LHS values to be in index order. Run Affinity |
| 115258 | ** before reordering the columns, so that the affinity is correct. |
| 115259 | */ |
| 115260 | sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); |
| 115261 | for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ |
| 115262 | if( i!=nVector ){ |
| 115263 | /* Need to reorder the LHS fields according to aiMap */ |
| 115264 | int rLhsOrig = rLhs; |
| 115265 | rLhs = sqlite3GetTempRange(pParse, nVector); |
| 115266 | for(i=0; i<nVector; i++){ |
| 115267 | sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); |
| 115268 | } |
| 115269 | sqlite3ReleaseTempReg(pParse, rLhsOrig); |
| 115270 | } |
| 115271 | } |
| 115272 | |
| 115273 | |
| 115274 | /* Step 2: Check to see if the LHS contains any NULL columns. If the |
| 115275 | ** LHS does contain NULLs then the result must be either FALSE or NULL. |
| 115276 | ** We will then skip the binary search of the RHS. |
| 115277 | */ |
| @@ -114802,15 +115295,15 @@ | |
| 115295 | */ |
| 115296 | if( eType==IN_INDEX_ROWID ){ |
| 115297 | /* In this case, the RHS is the ROWID of table b-tree and so we also |
| 115298 | ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 |
| 115299 | ** into a single opcode. */ |
| 115300 | assert( nVector==1 ); |
| 115301 | sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); |
| 115302 | VdbeCoverage(v); |
| 115303 | addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ |
| 115304 | }else{ |
| 115305 | if( destIfFalse==destIfNull ){ |
| 115306 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 115307 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 115308 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 115309 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| @@ -114884,11 +115377,10 @@ | |
| 115377 | |
| 115378 | /* Jumps here in order to return true. */ |
| 115379 | sqlite3VdbeJumpHere(v, addrTruthOp); |
| 115380 | |
| 115381 | sqlite3ExprCodeIN_finished: |
| 115382 | VdbeComment((v, "end IN expr")); |
| 115383 | sqlite3ExprCodeIN_oom_error: |
| 115384 | sqlite3DbFree(pParse->db, aiMap); |
| 115385 | sqlite3DbFree(pParse->db, zAff); |
| 115386 | } |
| @@ -115442,10 +115934,84 @@ | |
| 115934 | } |
| 115935 | } |
| 115936 | return 0; |
| 115937 | } |
| 115938 | |
| 115939 | /* |
| 115940 | ** Generate code that evaluates an AND or OR operator leaving a |
| 115941 | ** boolean result in a register. pExpr is the AND/OR expression. |
| 115942 | ** Store the result in the "target" register. Use short-circuit |
| 115943 | ** evaluation to avoid computing both operands, if possible. |
| 115944 | ** |
| 115945 | ** The code generated might require the use of a temporary register. |
| 115946 | ** If it does, then write the number of that temporary register |
| 115947 | ** into *pTmpReg. If not, leave *pTmpReg unchanged. |
| 115948 | */ |
| 115949 | static SQLITE_NOINLINE int exprCodeTargetAndOr( |
| 115950 | Parse *pParse, /* Parsing context */ |
| 115951 | Expr *pExpr, /* AND or OR expression to be coded */ |
| 115952 | int target, /* Put result in this register, guaranteed */ |
| 115953 | int *pTmpReg /* Write a temporary register here */ |
| 115954 | ){ |
| 115955 | int op; /* The opcode. TK_AND or TK_OR */ |
| 115956 | int skipOp; /* Opcode for the branch that skips one operand */ |
| 115957 | int addrSkip; /* Branch instruction that skips one of the operands */ |
| 115958 | int regSS = 0; /* Register holding computed operand when other omitted */ |
| 115959 | int r1, r2; /* Registers for left and right operands, respectively */ |
| 115960 | Expr *pAlt; /* Alternative, simplified expression */ |
| 115961 | Vdbe *v; /* statement being coded */ |
| 115962 | |
| 115963 | assert( pExpr!=0 ); |
| 115964 | op = pExpr->op; |
| 115965 | assert( op==TK_AND || op==TK_OR ); |
| 115966 | assert( TK_AND==OP_And ); testcase( op==TK_AND ); |
| 115967 | assert( TK_OR==OP_Or ); testcase( op==TK_OR ); |
| 115968 | assert( pParse->pVdbe!=0 ); |
| 115969 | v = pParse->pVdbe; |
| 115970 | pAlt = sqlite3ExprSimplifiedAndOr(pExpr); |
| 115971 | if( pAlt!=pExpr ){ |
| 115972 | r1 = sqlite3ExprCodeTarget(pParse, pAlt, target); |
| 115973 | sqlite3VdbeAddOp3(v, OP_BitAnd, r1, r1, target); |
| 115974 | return target; |
| 115975 | } |
| 115976 | skipOp = op==TK_AND ? OP_IfNot : OP_If; |
| 115977 | if( exprEvalRhsFirst(pExpr) ){ |
| 115978 | /* Compute the right operand first. Skip the computation of the left |
| 115979 | ** operand if the right operand fully determines the result */ |
| 115980 | r2 = regSS = sqlite3ExprCodeTarget(pParse, pExpr->pRight, target); |
| 115981 | addrSkip = sqlite3VdbeAddOp1(v, skipOp, r2); |
| 115982 | VdbeComment((v, "skip left operand")); |
| 115983 | VdbeCoverage(v); |
| 115984 | r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pTmpReg); |
| 115985 | }else{ |
| 115986 | /* Compute the left operand first */ |
| 115987 | r1 = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); |
| 115988 | if( ExprHasProperty(pExpr->pRight, EP_Subquery) ){ |
| 115989 | /* Skip over the computation of the right operand if the right |
| 115990 | ** operand is a subquery and the left operand completely determines |
| 115991 | ** the result */ |
| 115992 | regSS = r1; |
| 115993 | addrSkip = sqlite3VdbeAddOp1(v, skipOp, r1); |
| 115994 | VdbeComment((v, "skip right operand")); |
| 115995 | VdbeCoverage(v); |
| 115996 | }else{ |
| 115997 | addrSkip = regSS = 0; |
| 115998 | } |
| 115999 | r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pTmpReg); |
| 116000 | } |
| 116001 | sqlite3VdbeAddOp3(v, op, r2, r1, target); |
| 116002 | testcase( (*pTmpReg)==0 ); |
| 116003 | if( addrSkip ){ |
| 116004 | sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); |
| 116005 | sqlite3VdbeJumpHere(v, addrSkip); |
| 116006 | sqlite3VdbeAddOp3(v, OP_Or, regSS, regSS, target); |
| 116007 | VdbeComment((v, "short-circut value")); |
| 116008 | } |
| 116009 | return target; |
| 116010 | } |
| 116011 | |
| 116012 | |
| 116013 | |
| 116014 | /* |
| 116015 | ** Generate code into the current Vdbe to evaluate the given |
| 116016 | ** expression. Attempt to store the results in register "target". |
| 116017 | ** Return the register where results are stored. |
| @@ -115633,10 +116199,16 @@ | |
| 116199 | case TK_STRING: { |
| 116200 | assert( !ExprHasProperty(pExpr, EP_IntValue) ); |
| 116201 | sqlite3VdbeLoadString(v, target, pExpr->u.zToken); |
| 116202 | return target; |
| 116203 | } |
| 116204 | case TK_NULLS: { |
| 116205 | /* Set a range of registers to NULL. pExpr->y.nReg registers starting |
| 116206 | ** with target */ |
| 116207 | sqlite3VdbeAddOp3(v, OP_Null, 0, target, target + pExpr->y.nReg - 1); |
| 116208 | return target; |
| 116209 | } |
| 116210 | default: { |
| 116211 | /* Make NULL the default case so that if a bug causes an illegal |
| 116212 | ** Expr node to be passed into this function, it will be handled |
| 116213 | ** sanely and not crash. But keep the assert() to bring the problem |
| 116214 | ** to the attention of the developers. */ |
| @@ -115724,16 +116296,18 @@ | |
| 116296 | sqlite3VdbeAddOp2(v, OP_Null, 0, inReg); |
| 116297 | } |
| 116298 | } |
| 116299 | testcase( regFree1==0 ); |
| 116300 | testcase( regFree2==0 ); |
| 116301 | } |
| 116302 | break; |
| 116303 | } |
| 116304 | case TK_AND: |
| 116305 | case TK_OR: { |
| 116306 | inReg = exprCodeTargetAndOr(pParse, pExpr, target, ®Free1); |
| 116307 | break; |
| 116308 | } |
| 116309 | case TK_PLUS: |
| 116310 | case TK_STAR: |
| 116311 | case TK_MINUS: |
| 116312 | case TK_REM: |
| 116313 | case TK_BITAND: |
| @@ -115741,12 +116315,10 @@ | |
| 116315 | case TK_SLASH: |
| 116316 | case TK_LSHIFT: |
| 116317 | case TK_RSHIFT: |
| 116318 | case TK_CONCAT: { |
| 116319 | int addrIsNull; |
| 116320 | assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); |
| 116321 | assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); |
| 116322 | assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); |
| 116323 | assert( TK_BITAND==OP_BitAnd ); testcase( op==TK_BITAND ); |
| 116324 | assert( TK_BITOR==OP_BitOr ); testcase( op==TK_BITOR ); |
| @@ -116341,10 +116913,29 @@ | |
| 116913 | } |
| 116914 | pParse->pConstExpr = p; |
| 116915 | } |
| 116916 | return regDest; |
| 116917 | } |
| 116918 | |
| 116919 | /* |
| 116920 | ** Make arrangements to invoke OP_Null on a range of registers |
| 116921 | ** during initialization. |
| 116922 | */ |
| 116923 | SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3ExprNullRegisterRange( |
| 116924 | Parse *pParse, /* Parsing context */ |
| 116925 | int iReg, /* First register to set to NULL */ |
| 116926 | int nReg /* Number of sequential registers to NULL out */ |
| 116927 | ){ |
| 116928 | u8 okConstFactor = pParse->okConstFactor; |
| 116929 | Expr t; |
| 116930 | memset(&t, 0, sizeof(t)); |
| 116931 | t.op = TK_NULLS; |
| 116932 | t.y.nReg = nReg; |
| 116933 | pParse->okConstFactor = 1; |
| 116934 | sqlite3ExprCodeRunJustOnce(pParse, &t, iReg); |
| 116935 | pParse->okConstFactor = okConstFactor; |
| 116936 | } |
| 116937 | |
| 116938 | /* |
| 116939 | ** Generate code to evaluate an expression and store the results |
| 116940 | ** into a register. Return the register number where the results |
| 116941 | ** are stored. |
| @@ -123872,10 +124463,20 @@ | |
| 124463 | if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ |
| 124464 | Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); |
| 124465 | if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ |
| 124466 | pMod = sqlite3PragmaVtabRegister(db, zName); |
| 124467 | } |
| 124468 | #ifndef SQLITE_OMIT_JSON |
| 124469 | if( pMod==0 && sqlite3_strnicmp(zName, "json", 4)==0 ){ |
| 124470 | pMod = sqlite3JsonVtabRegister(db, zName); |
| 124471 | } |
| 124472 | #endif |
| 124473 | #ifdef SQLITE_ENABLE_CARRAY |
| 124474 | if( pMod==0 && sqlite3_stricmp(zName, "carray")==0 ){ |
| 124475 | pMod = sqlite3CarrayRegister(db); |
| 124476 | } |
| 124477 | #endif |
| 124478 | if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ |
| 124479 | testcase( pMod->pEpoTab==0 ); |
| 124480 | return pMod->pEpoTab; |
| 124481 | } |
| 124482 | } |
| @@ -124510,11 +125111,11 @@ | |
| 125111 | */ |
| 125112 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 125113 | int i; |
| 125114 | i16 iCol16; |
| 125115 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 125116 | assert( pIdx->nColumn<=SQLITE_MAX_COLUMN*2 ); |
| 125117 | iCol16 = iCol; |
| 125118 | for(i=0; i<pIdx->nColumn; i++){ |
| 125119 | if( iCol16==pIdx->aiColumn[i] ){ |
| 125120 | return i; |
| 125121 | } |
| @@ -124807,10 +125408,13 @@ | |
| 125408 | sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); |
| 125409 | sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC); |
| 125410 | sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); |
| 125411 | sqlite3VdbeChangeP5(v, OPFLAG_APPEND); |
| 125412 | sqlite3VdbeAddOp0(v, OP_Close); |
| 125413 | }else if( db->init.imposterTable ){ |
| 125414 | pTable->tabFlags |= TF_Imposter; |
| 125415 | if( db->init.imposterTable>=2 ) pTable->tabFlags |= TF_Readonly; |
| 125416 | } |
| 125417 | |
| 125418 | /* Normal (non-error) return. */ |
| 125419 | return; |
| 125420 | |
| @@ -129102,18 +129706,23 @@ | |
| 129706 | pKey->aSortFlags[i] = pIdx->aSortOrder[i]; |
| 129707 | assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) ); |
| 129708 | } |
| 129709 | if( pParse->nErr ){ |
| 129710 | assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); |
| 129711 | if( pIdx->bNoQuery==0 |
| 129712 | && sqlite3HashFind(&pIdx->pSchema->idxHash, pIdx->zName) |
| 129713 | ){ |
| 129714 | /* Deactivate the index because it contains an unknown collating |
| 129715 | ** sequence. The only way to reactive the index is to reload the |
| 129716 | ** schema. Adding the missing collating sequence later does not |
| 129717 | ** reactive the index. The application had the chance to register |
| 129718 | ** the missing index using the collation-needed callback. For |
| 129719 | ** simplicity, SQLite will not give the application a second chance. |
| 129720 | ** |
| 129721 | ** Except, do not do this if the index is not in the schema hash |
| 129722 | ** table. In this case the index is currently being constructed |
| 129723 | ** by a CREATE INDEX statement, and retrying will not help. */ |
| 129724 | pIdx->bNoQuery = 1; |
| 129725 | pParse->rc = SQLITE_ERROR_RETRY; |
| 129726 | } |
| 129727 | sqlite3KeyInfoUnref(pKey); |
| 129728 | pKey = 0; |
| @@ -131977,11 +132586,11 @@ | |
| 132586 | ** a decoding of those digits into *pVal. Or return false if any |
| 132587 | ** one of the first N characters in z[] is not a hexadecimal digit. |
| 132588 | */ |
| 132589 | static int isNHex(const char *z, int N, u32 *pVal){ |
| 132590 | int i; |
| 132591 | u32 v = 0; |
| 132592 | for(i=0; i<N; i++){ |
| 132593 | if( !sqlite3Isxdigit(z[i]) ) return 0; |
| 132594 | v = (v<<4) + sqlite3HexToInt(z[i]); |
| 132595 | } |
| 132596 | *pVal = v; |
| @@ -132490,10 +133099,11 @@ | |
| 133099 | int nSep, |
| 133100 | const char *zSep |
| 133101 | ){ |
| 133102 | i64 j, n = 0; |
| 133103 | int i; |
| 133104 | int bNotNull = 0; /* True after at least NOT NULL argument seen */ |
| 133105 | char *z; |
| 133106 | for(i=0; i<argc; i++){ |
| 133107 | n += sqlite3_value_bytes(argv[i]); |
| 133108 | } |
| 133109 | n += (argc-1)*(i64)nSep; |
| @@ -132506,16 +133116,17 @@ | |
| 133116 | for(i=0; i<argc; i++){ |
| 133117 | if( sqlite3_value_type(argv[i])!=SQLITE_NULL ){ |
| 133118 | int k = sqlite3_value_bytes(argv[i]); |
| 133119 | const char *v = (const char*)sqlite3_value_text(argv[i]); |
| 133120 | if( v!=0 ){ |
| 133121 | if( bNotNull && nSep>0 ){ |
| 133122 | memcpy(&z[j], zSep, nSep); |
| 133123 | j += nSep; |
| 133124 | } |
| 133125 | memcpy(&z[j], v, k); |
| 133126 | j += k; |
| 133127 | bNotNull = 1; |
| 133128 | } |
| 133129 | } |
| 133130 | } |
| 133131 | z[j] = 0; |
| 133132 | assert( j<=n ); |
| @@ -133456,10 +134067,456 @@ | |
| 134067 | type0 = sqlite3_value_numeric_type(argv[0]); |
| 134068 | if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; |
| 134069 | x = sqlite3_value_double(argv[0]); |
| 134070 | sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); |
| 134071 | } |
| 134072 | |
| 134073 | #if defined(SQLITE_ENABLE_PERCENTILE) |
| 134074 | /*********************************************************************** |
| 134075 | ** This section implements the percentile(Y,P) SQL function and similar. |
| 134076 | ** Requirements: |
| 134077 | ** |
| 134078 | ** (1) The percentile(Y,P) function is an aggregate function taking |
| 134079 | ** exactly two arguments. |
| 134080 | ** |
| 134081 | ** (2) If the P argument to percentile(Y,P) is not the same for every |
| 134082 | ** row in the aggregate then an error is thrown. The word "same" |
| 134083 | ** in the previous sentence means that the value differ by less |
| 134084 | ** than 0.001. |
| 134085 | ** |
| 134086 | ** (3) If the P argument to percentile(Y,P) evaluates to anything other |
| 134087 | ** than a number in the range of 0.0 to 100.0 inclusive then an |
| 134088 | ** error is thrown. |
| 134089 | ** |
| 134090 | ** (4) If any Y argument to percentile(Y,P) evaluates to a value that |
| 134091 | ** is not NULL and is not numeric then an error is thrown. |
| 134092 | ** |
| 134093 | ** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus |
| 134094 | ** infinity then an error is thrown. (SQLite always interprets NaN |
| 134095 | ** values as NULL.) |
| 134096 | ** |
| 134097 | ** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, |
| 134098 | ** including CASE WHEN expressions. |
| 134099 | ** |
| 134100 | ** (7) The percentile(Y,P) aggregate is able to handle inputs of at least |
| 134101 | ** one million (1,000,000) rows. |
| 134102 | ** |
| 134103 | ** (8) If there are no non-NULL values for Y, then percentile(Y,P) |
| 134104 | ** returns NULL. |
| 134105 | ** |
| 134106 | ** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) |
| 134107 | ** returns the one Y value. |
| 134108 | ** |
| 134109 | ** (10) If there N non-NULL values of Y where N is two or more and |
| 134110 | ** the Y values are ordered from least to greatest and a graph is |
| 134111 | ** drawn from 0 to N-1 such that the height of the graph at J is |
| 134112 | ** the J-th Y value and such that straight lines are drawn between |
| 134113 | ** adjacent Y values, then the percentile(Y,P) function returns |
| 134114 | ** the height of the graph at P*(N-1)/100. |
| 134115 | ** |
| 134116 | ** (11) The percentile(Y,P) function always returns either a floating |
| 134117 | ** point number or NULL. |
| 134118 | ** |
| 134119 | ** (12) The percentile(Y,P) is implemented as a single C99 source-code |
| 134120 | ** file that compiles into a shared-library or DLL that can be loaded |
| 134121 | ** into SQLite using the sqlite3_load_extension() interface. |
| 134122 | ** |
| 134123 | ** (13) A separate median(Y) function is the equivalent percentile(Y,50). |
| 134124 | ** |
| 134125 | ** (14) A separate percentile_cont(Y,P) function is equivalent to |
| 134126 | ** percentile(Y,P/100.0). In other words, the fraction value in |
| 134127 | ** the second argument is in the range of 0 to 1 instead of 0 to 100. |
| 134128 | ** |
| 134129 | ** (15) A separate percentile_disc(Y,P) function is like |
| 134130 | ** percentile_cont(Y,P) except that instead of returning the weighted |
| 134131 | ** average of the nearest two input values, it returns the next lower |
| 134132 | ** value. So the percentile_disc(Y,P) will always return a value |
| 134133 | ** that was one of the inputs. |
| 134134 | ** |
| 134135 | ** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and |
| 134136 | ** percentile_disc(Y,P) can be used as window functions. |
| 134137 | ** |
| 134138 | ** Differences from standard SQL: |
| 134139 | ** |
| 134140 | ** * The percentile_cont(X,P) function is equivalent to the following in |
| 134141 | ** standard SQL: |
| 134142 | ** |
| 134143 | ** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) |
| 134144 | ** |
| 134145 | ** The SQLite syntax is much more compact. The standard SQL syntax |
| 134146 | ** is also supported if SQLite is compiled with the |
| 134147 | ** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. |
| 134148 | ** |
| 134149 | ** * No median(X) function exists in the SQL standard. App developers |
| 134150 | ** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". |
| 134151 | ** |
| 134152 | ** * No percentile(Y,P) function exists in the SQL standard. Instead of |
| 134153 | ** percential(Y,P), developers must write this: |
| 134154 | ** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that |
| 134155 | ** the fraction parameter to percentile() goes from 0 to 100 whereas |
| 134156 | ** the fraction parameter in SQL standard percentile_cont() goes from |
| 134157 | ** 0 to 1. |
| 134158 | ** |
| 134159 | ** Implementation notes as of 2024-08-31: |
| 134160 | ** |
| 134161 | ** * The regular aggregate-function versions of these routines work |
| 134162 | ** by accumulating all values in an array of doubles, then sorting |
| 134163 | ** that array using quicksort before computing the answer. Thus |
| 134164 | ** the runtime is O(NlogN) where N is the number of rows of input. |
| 134165 | ** |
| 134166 | ** * For the window-function versions of these routines, the array of |
| 134167 | ** inputs is sorted as soon as the first value is computed. Thereafter, |
| 134168 | ** the array is kept in sorted order using an insert-sort. This |
| 134169 | ** results in O(N*K) performance where K is the size of the window. |
| 134170 | ** One can imagine alternative implementations that give O(N*logN*logK) |
| 134171 | ** performance, but they require more complex logic and data structures. |
| 134172 | ** The developers have elected to keep the asymptotically slower |
| 134173 | ** algorithm for now, for simplicity, under the theory that window |
| 134174 | ** functions are seldom used and when they are, the window size K is |
| 134175 | ** often small. The developers might revisit that decision later, |
| 134176 | ** should the need arise. |
| 134177 | */ |
| 134178 | |
| 134179 | /* The following object is the group context for a single percentile() |
| 134180 | ** aggregate. Remember all input Y values until the very end. |
| 134181 | ** Those values are accumulated in the Percentile.a[] array. |
| 134182 | */ |
| 134183 | typedef struct Percentile Percentile; |
| 134184 | struct Percentile { |
| 134185 | unsigned nAlloc; /* Number of slots allocated for a[] */ |
| 134186 | unsigned nUsed; /* Number of slots actually used in a[] */ |
| 134187 | char bSorted; /* True if a[] is already in sorted order */ |
| 134188 | char bKeepSorted; /* True if advantageous to keep a[] sorted */ |
| 134189 | char bPctValid; /* True if rPct is valid */ |
| 134190 | double rPct; /* Fraction. 0.0 to 1.0 */ |
| 134191 | double *a; /* Array of Y values */ |
| 134192 | }; |
| 134193 | |
| 134194 | /* |
| 134195 | ** Return TRUE if the input floating-point number is an infinity. |
| 134196 | */ |
| 134197 | static int percentIsInfinity(double r){ |
| 134198 | sqlite3_uint64 u; |
| 134199 | assert( sizeof(u)==sizeof(r) ); |
| 134200 | memcpy(&u, &r, sizeof(u)); |
| 134201 | return ((u>>52)&0x7ff)==0x7ff; |
| 134202 | } |
| 134203 | |
| 134204 | /* |
| 134205 | ** Return TRUE if two doubles differ by 0.001 or less. |
| 134206 | */ |
| 134207 | static int percentSameValue(double a, double b){ |
| 134208 | a -= b; |
| 134209 | return a>=-0.001 && a<=0.001; |
| 134210 | } |
| 134211 | |
| 134212 | /* |
| 134213 | ** Search p (which must have p->bSorted) looking for an entry with |
| 134214 | ** value y. Return the index of that entry. |
| 134215 | ** |
| 134216 | ** If bExact is true, return -1 if the entry is not found. |
| 134217 | ** |
| 134218 | ** If bExact is false, return the index at which a new entry with |
| 134219 | ** value y should be insert in order to keep the values in sorted |
| 134220 | ** order. The smallest return value in this case will be 0, and |
| 134221 | ** the largest return value will be p->nUsed. |
| 134222 | */ |
| 134223 | static int percentBinarySearch(Percentile *p, double y, int bExact){ |
| 134224 | int iFirst = 0; /* First element of search range */ |
| 134225 | int iLast = p->nUsed - 1; /* Last element of search range */ |
| 134226 | while( iLast>=iFirst ){ |
| 134227 | int iMid = (iFirst+iLast)/2; |
| 134228 | double x = p->a[iMid]; |
| 134229 | if( x<y ){ |
| 134230 | iFirst = iMid + 1; |
| 134231 | }else if( x>y ){ |
| 134232 | iLast = iMid - 1; |
| 134233 | }else{ |
| 134234 | return iMid; |
| 134235 | } |
| 134236 | } |
| 134237 | if( bExact ) return -1; |
| 134238 | return iFirst; |
| 134239 | } |
| 134240 | |
| 134241 | /* |
| 134242 | ** Generate an error for a percentile function. |
| 134243 | ** |
| 134244 | ** The error format string must have exactly one occurrence of "%%s()" |
| 134245 | ** (with two '%' characters). That substring will be replaced by the name |
| 134246 | ** of the function. |
| 134247 | */ |
| 134248 | static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ |
| 134249 | char *zMsg1; |
| 134250 | char *zMsg2; |
| 134251 | va_list ap; |
| 134252 | |
| 134253 | va_start(ap, zFormat); |
| 134254 | zMsg1 = sqlite3_vmprintf(zFormat, ap); |
| 134255 | va_end(ap); |
| 134256 | zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, sqlite3VdbeFuncName(pCtx)) : 0; |
| 134257 | sqlite3_result_error(pCtx, zMsg2, -1); |
| 134258 | sqlite3_free(zMsg1); |
| 134259 | sqlite3_free(zMsg2); |
| 134260 | } |
| 134261 | |
| 134262 | /* |
| 134263 | ** The "step" function for percentile(Y,P) is called once for each |
| 134264 | ** input row. |
| 134265 | */ |
| 134266 | static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ |
| 134267 | Percentile *p; |
| 134268 | double rPct; |
| 134269 | int eType; |
| 134270 | double y; |
| 134271 | assert( argc==2 || argc==1 ); |
| 134272 | |
| 134273 | if( argc==1 ){ |
| 134274 | /* Requirement 13: median(Y) is the same as percentile(Y,50). */ |
| 134275 | rPct = 0.5; |
| 134276 | }else{ |
| 134277 | /* P must be a number between 0 and 100 for percentile() or between |
| 134278 | ** 0.0 and 1.0 for percentile_cont() and percentile_disc(). |
| 134279 | ** |
| 134280 | ** The user-data is an integer which is 10 times the upper bound. |
| 134281 | */ |
| 134282 | double mxFrac = (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&2)? 100.0 : 1.0; |
| 134283 | eType = sqlite3_value_numeric_type(argv[1]); |
| 134284 | rPct = sqlite3_value_double(argv[1])/mxFrac; |
| 134285 | if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) |
| 134286 | || rPct<0.0 || rPct>1.0 |
| 134287 | ){ |
| 134288 | percentError(pCtx, "the fraction argument to %%s()" |
| 134289 | " is not between 0.0 and %.1f", |
| 134290 | (double)mxFrac); |
| 134291 | return; |
| 134292 | } |
| 134293 | } |
| 134294 | |
| 134295 | /* Allocate the session context. */ |
| 134296 | p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); |
| 134297 | if( p==0 ) return; |
| 134298 | |
| 134299 | /* Remember the P value. Throw an error if the P value is different |
| 134300 | ** from any prior row, per Requirement (2). */ |
| 134301 | if( !p->bPctValid ){ |
| 134302 | p->rPct = rPct; |
| 134303 | p->bPctValid = 1; |
| 134304 | }else if( !percentSameValue(p->rPct,rPct) ){ |
| 134305 | percentError(pCtx, "the fraction argument to %%s()" |
| 134306 | " is not the same for all input rows"); |
| 134307 | return; |
| 134308 | } |
| 134309 | |
| 134310 | /* Ignore rows for which Y is NULL */ |
| 134311 | eType = sqlite3_value_type(argv[0]); |
| 134312 | if( eType==SQLITE_NULL ) return; |
| 134313 | |
| 134314 | /* If not NULL, then Y must be numeric. Otherwise throw an error. |
| 134315 | ** Requirement 4 */ |
| 134316 | if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ |
| 134317 | percentError(pCtx, "input to %%s() is not numeric"); |
| 134318 | return; |
| 134319 | } |
| 134320 | |
| 134321 | /* Throw an error if the Y value is infinity or NaN */ |
| 134322 | y = sqlite3_value_double(argv[0]); |
| 134323 | if( percentIsInfinity(y) ){ |
| 134324 | percentError(pCtx, "Inf input to %%s()"); |
| 134325 | return; |
| 134326 | } |
| 134327 | |
| 134328 | /* Allocate and store the Y */ |
| 134329 | if( p->nUsed>=p->nAlloc ){ |
| 134330 | unsigned n = p->nAlloc*2 + 250; |
| 134331 | double *a = sqlite3_realloc64(p->a, sizeof(double)*n); |
| 134332 | if( a==0 ){ |
| 134333 | sqlite3_free(p->a); |
| 134334 | memset(p, 0, sizeof(*p)); |
| 134335 | sqlite3_result_error_nomem(pCtx); |
| 134336 | return; |
| 134337 | } |
| 134338 | p->nAlloc = n; |
| 134339 | p->a = a; |
| 134340 | } |
| 134341 | if( p->nUsed==0 ){ |
| 134342 | p->a[p->nUsed++] = y; |
| 134343 | p->bSorted = 1; |
| 134344 | }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ |
| 134345 | p->a[p->nUsed++] = y; |
| 134346 | }else if( p->bKeepSorted ){ |
| 134347 | int i; |
| 134348 | i = percentBinarySearch(p, y, 0); |
| 134349 | if( i<(int)p->nUsed ){ |
| 134350 | memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); |
| 134351 | } |
| 134352 | p->a[i] = y; |
| 134353 | p->nUsed++; |
| 134354 | }else{ |
| 134355 | p->a[p->nUsed++] = y; |
| 134356 | p->bSorted = 0; |
| 134357 | } |
| 134358 | } |
| 134359 | |
| 134360 | /* |
| 134361 | ** Interchange two doubles. |
| 134362 | */ |
| 134363 | #define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} |
| 134364 | |
| 134365 | /* |
| 134366 | ** Sort an array of doubles. |
| 134367 | ** |
| 134368 | ** Algorithm: quicksort |
| 134369 | ** |
| 134370 | ** This is implemented separately rather than using the qsort() routine |
| 134371 | ** from the standard library because: |
| 134372 | ** |
| 134373 | ** (1) To avoid a dependency on qsort() |
| 134374 | ** (2) To avoid the function call to the comparison routine for each |
| 134375 | ** comparison. |
| 134376 | */ |
| 134377 | static void percentSort(double *a, unsigned int n){ |
| 134378 | int iLt; /* Entries before a[iLt] are less than rPivot */ |
| 134379 | int iGt; /* Entries at or after a[iGt] are greater than rPivot */ |
| 134380 | int i; /* Loop counter */ |
| 134381 | double rPivot; /* The pivot value */ |
| 134382 | |
| 134383 | assert( n>=2 ); |
| 134384 | if( a[0]>a[n-1] ){ |
| 134385 | SWAP_DOUBLE(a[0],a[n-1]) |
| 134386 | } |
| 134387 | if( n==2 ) return; |
| 134388 | iGt = n-1; |
| 134389 | i = n/2; |
| 134390 | if( a[0]>a[i] ){ |
| 134391 | SWAP_DOUBLE(a[0],a[i]) |
| 134392 | }else if( a[i]>a[iGt] ){ |
| 134393 | SWAP_DOUBLE(a[i],a[iGt]) |
| 134394 | } |
| 134395 | if( n==3 ) return; |
| 134396 | rPivot = a[i]; |
| 134397 | iLt = i = 1; |
| 134398 | do{ |
| 134399 | if( a[i]<rPivot ){ |
| 134400 | if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt]) |
| 134401 | iLt++; |
| 134402 | i++; |
| 134403 | }else if( a[i]>rPivot ){ |
| 134404 | do{ |
| 134405 | iGt--; |
| 134406 | }while( iGt>i && a[iGt]>rPivot ); |
| 134407 | SWAP_DOUBLE(a[i],a[iGt]) |
| 134408 | }else{ |
| 134409 | i++; |
| 134410 | } |
| 134411 | }while( i<iGt ); |
| 134412 | if( iLt>=2 ) percentSort(a, iLt); |
| 134413 | if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); |
| 134414 | |
| 134415 | /* Uncomment for testing */ |
| 134416 | #if 0 |
| 134417 | for(i=0; i<n-1; i++){ |
| 134418 | assert( a[i]<=a[i+1] ); |
| 134419 | } |
| 134420 | #endif |
| 134421 | } |
| 134422 | |
| 134423 | |
| 134424 | /* |
| 134425 | ** The "inverse" function for percentile(Y,P) is called to remove a |
| 134426 | ** row that was previously inserted by "step". |
| 134427 | */ |
| 134428 | static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ |
| 134429 | Percentile *p; |
| 134430 | int eType; |
| 134431 | double y; |
| 134432 | int i; |
| 134433 | assert( argc==2 || argc==1 ); |
| 134434 | |
| 134435 | /* Allocate the session context. */ |
| 134436 | p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); |
| 134437 | assert( p!=0 ); |
| 134438 | |
| 134439 | /* Ignore rows for which Y is NULL */ |
| 134440 | eType = sqlite3_value_type(argv[0]); |
| 134441 | if( eType==SQLITE_NULL ) return; |
| 134442 | |
| 134443 | /* If not NULL, then Y must be numeric. Otherwise throw an error. |
| 134444 | ** Requirement 4 */ |
| 134445 | if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ |
| 134446 | return; |
| 134447 | } |
| 134448 | |
| 134449 | /* Ignore the Y value if it is infinity or NaN */ |
| 134450 | y = sqlite3_value_double(argv[0]); |
| 134451 | if( percentIsInfinity(y) ){ |
| 134452 | return; |
| 134453 | } |
| 134454 | if( p->bSorted==0 ){ |
| 134455 | assert( p->nUsed>1 ); |
| 134456 | percentSort(p->a, p->nUsed); |
| 134457 | p->bSorted = 1; |
| 134458 | } |
| 134459 | p->bKeepSorted = 1; |
| 134460 | |
| 134461 | /* Find and remove the row */ |
| 134462 | i = percentBinarySearch(p, y, 1); |
| 134463 | if( i>=0 ){ |
| 134464 | p->nUsed--; |
| 134465 | if( i<(int)p->nUsed ){ |
| 134466 | memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); |
| 134467 | } |
| 134468 | } |
| 134469 | } |
| 134470 | |
| 134471 | /* |
| 134472 | ** Compute the final output of percentile(). Clean up all allocated |
| 134473 | ** memory if and only if bIsFinal is true. |
| 134474 | */ |
| 134475 | static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ |
| 134476 | Percentile *p; |
| 134477 | int settings = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&1; /* Discrete? */ |
| 134478 | unsigned i1, i2; |
| 134479 | double v1, v2; |
| 134480 | double ix, vx; |
| 134481 | p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); |
| 134482 | if( p==0 ) return; |
| 134483 | if( p->a==0 ) return; |
| 134484 | if( p->nUsed ){ |
| 134485 | if( p->bSorted==0 ){ |
| 134486 | assert( p->nUsed>1 ); |
| 134487 | percentSort(p->a, p->nUsed); |
| 134488 | p->bSorted = 1; |
| 134489 | } |
| 134490 | ix = p->rPct*(p->nUsed-1); |
| 134491 | i1 = (unsigned)ix; |
| 134492 | if( settings & 1 ){ |
| 134493 | vx = p->a[i1]; |
| 134494 | }else{ |
| 134495 | i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; |
| 134496 | v1 = p->a[i1]; |
| 134497 | v2 = p->a[i2]; |
| 134498 | vx = v1 + (v2-v1)*(ix-i1); |
| 134499 | } |
| 134500 | sqlite3_result_double(pCtx, vx); |
| 134501 | } |
| 134502 | if( bIsFinal ){ |
| 134503 | sqlite3_free(p->a); |
| 134504 | memset(p, 0, sizeof(*p)); |
| 134505 | }else{ |
| 134506 | p->bKeepSorted = 1; |
| 134507 | } |
| 134508 | } |
| 134509 | static void percentFinal(sqlite3_context *pCtx){ |
| 134510 | percentCompute(pCtx, 1); |
| 134511 | } |
| 134512 | static void percentValue(sqlite3_context *pCtx){ |
| 134513 | percentCompute(pCtx, 0); |
| 134514 | } |
| 134515 | /****** End of percentile family of functions ******/ |
| 134516 | #endif /* SQLITE_ENABLE_PERCENTILE */ |
| 134517 | |
| 134518 | |
| 134519 | #ifdef SQLITE_DEBUG |
| 134520 | /* |
| 134521 | ** Implementation of fpdecode(x,y,z) function. |
| 134522 | ** |
| @@ -133687,10 +134744,25 @@ | |
| 134744 | WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep, |
| 134745 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 134746 | WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep, |
| 134747 | groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), |
| 134748 | |
| 134749 | #ifdef SQLITE_ENABLE_PERCENTILE |
| 134750 | WAGGREGATE(median, 1, 0,0, percentStep, |
| 134751 | percentFinal, percentValue, percentInverse, |
| 134752 | SQLITE_INNOCUOUS|SQLITE_SELFORDER1), |
| 134753 | WAGGREGATE(percentile, 2, 0x2,0, percentStep, |
| 134754 | percentFinal, percentValue, percentInverse, |
| 134755 | SQLITE_INNOCUOUS|SQLITE_SELFORDER1), |
| 134756 | WAGGREGATE(percentile_cont, 2, 0,0, percentStep, |
| 134757 | percentFinal, percentValue, percentInverse, |
| 134758 | SQLITE_INNOCUOUS|SQLITE_SELFORDER1), |
| 134759 | WAGGREGATE(percentile_disc, 2, 0x1,0, percentStep, |
| 134760 | percentFinal, percentValue, percentInverse, |
| 134761 | SQLITE_INNOCUOUS|SQLITE_SELFORDER1), |
| 134762 | #endif /* SQLITE_ENABLE_PERCENTILE */ |
| 134763 | |
| 134764 | LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 134765 | #ifdef SQLITE_CASE_SENSITIVE_LIKE |
| 134766 | LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 134767 | LIKEFUNC(like, 3, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), |
| 134768 | #else |
| @@ -139184,10 +140256,14 @@ | |
| 140256 | /* Version 3.44.0 and later */ |
| 140257 | void *(*get_clientdata)(sqlite3*,const char*); |
| 140258 | int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); |
| 140259 | /* Version 3.50.0 and later */ |
| 140260 | int (*setlk_timeout)(sqlite3*,int,int); |
| 140261 | /* Version 3.51.0 and later */ |
| 140262 | int (*set_errmsg)(sqlite3*,int,const char*); |
| 140263 | int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); |
| 140264 | |
| 140265 | }; |
| 140266 | |
| 140267 | /* |
| 140268 | ** This is the function signature used for all extension entry points. It |
| 140269 | ** is also defined in the file "loadext.c". |
| @@ -139519,10 +140595,13 @@ | |
| 140595 | /* Version 3.44.0 and later */ |
| 140596 | #define sqlite3_get_clientdata sqlite3_api->get_clientdata |
| 140597 | #define sqlite3_set_clientdata sqlite3_api->set_clientdata |
| 140598 | /* Version 3.50.0 and later */ |
| 140599 | #define sqlite3_setlk_timeout sqlite3_api->setlk_timeout |
| 140600 | /* Version 3.51.0 and later */ |
| 140601 | #define sqlite3_set_errmsg sqlite3_api->set_errmsg |
| 140602 | #define sqlite3_db_status64 sqlite3_api->db_status64 |
| 140603 | #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ |
| 140604 | |
| 140605 | #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) |
| 140606 | /* This case when the file really is being compiled as a loadable |
| 140607 | ** extension */ |
| @@ -140042,11 +141121,14 @@ | |
| 141121 | sqlite3_stmt_explain, |
| 141122 | /* Version 3.44.0 and later */ |
| 141123 | sqlite3_get_clientdata, |
| 141124 | sqlite3_set_clientdata, |
| 141125 | /* Version 3.50.0 and later */ |
| 141126 | sqlite3_setlk_timeout, |
| 141127 | /* Version 3.51.0 and later */ |
| 141128 | sqlite3_set_errmsg, |
| 141129 | sqlite3_db_status64 |
| 141130 | }; |
| 141131 | |
| 141132 | /* True if x is the directory separator character |
| 141133 | */ |
| 141134 | #if SQLITE_OS_WIN |
| @@ -141503,10 +142585,26 @@ | |
| 142585 | addr = sqlite3VdbeAddOp3(v, OP_IfPos, 1, sqlite3VdbeCurrentAddr(v)+2, 1); |
| 142586 | VdbeCoverage(v); |
| 142587 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 142588 | return addr; |
| 142589 | } |
| 142590 | |
| 142591 | /* |
| 142592 | ** Should table pTab be skipped when doing an integrity_check? |
| 142593 | ** Return true or false. |
| 142594 | ** |
| 142595 | ** If pObjTab is not null, the return true if pTab matches pObjTab. |
| 142596 | ** |
| 142597 | ** If pObjTab is null, then return true only if pTab is an imposter table. |
| 142598 | */ |
| 142599 | static int tableSkipIntegrityCheck(const Table *pTab, const Table *pObjTab){ |
| 142600 | if( pObjTab ){ |
| 142601 | return pTab!=pObjTab; |
| 142602 | }else{ |
| 142603 | return (pTab->tabFlags & TF_Imposter)!=0; |
| 142604 | } |
| 142605 | } |
| 142606 | |
| 142607 | /* |
| 142608 | ** Process a pragma statement. |
| 142609 | ** |
| 142610 | ** Pragmas are of this form: |
| @@ -142849,11 +143947,11 @@ | |
| 143947 | pTbls = &db->aDb[i].pSchema->tblHash; |
| 143948 | for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 143949 | Table *pTab = sqliteHashData(x); /* Current table */ |
| 143950 | Index *pIdx; /* An index on pTab */ |
| 143951 | int nIdx; /* Number of indexes on pTab */ |
| 143952 | if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; |
| 143953 | if( HasRowid(pTab) ) cnt++; |
| 143954 | for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } |
| 143955 | } |
| 143956 | if( cnt==0 ) continue; |
| 143957 | if( pObjTab ) cnt++; |
| @@ -142862,11 +143960,11 @@ | |
| 143960 | cnt = 0; |
| 143961 | if( pObjTab ) aRoot[++cnt] = 0; |
| 143962 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 143963 | Table *pTab = sqliteHashData(x); |
| 143964 | Index *pIdx; |
| 143965 | if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; |
| 143966 | if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; |
| 143967 | for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ |
| 143968 | aRoot[++cnt] = pIdx->tnum; |
| 143969 | } |
| 143970 | } |
| @@ -142893,11 +143991,11 @@ | |
| 143991 | sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); |
| 143992 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 143993 | int iTab = 0; |
| 143994 | Table *pTab = sqliteHashData(x); |
| 143995 | Index *pIdx; |
| 143996 | if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; |
| 143997 | if( HasRowid(pTab) ){ |
| 143998 | iTab = cnt++; |
| 143999 | }else{ |
| 144000 | iTab = cnt; |
| 144001 | for(pIdx=pTab->pIndex; ALWAYS(pIdx); pIdx=pIdx->pNext){ |
| @@ -142929,11 +144027,11 @@ | |
| 144027 | int r1 = -1; |
| 144028 | int bStrict; /* True for a STRICT table */ |
| 144029 | int r2; /* Previous key for WITHOUT ROWID tables */ |
| 144030 | int mxCol; /* Maximum non-virtual column number */ |
| 144031 | |
| 144032 | if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; |
| 144033 | if( !IsOrdinaryTable(pTab) ) continue; |
| 144034 | if( isQuick || HasRowid(pTab) ){ |
| 144035 | pPk = 0; |
| 144036 | r2 = 0; |
| 144037 | }else{ |
| @@ -143253,11 +144351,11 @@ | |
| 144351 | */ |
| 144352 | for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ |
| 144353 | Table *pTab = sqliteHashData(x); |
| 144354 | sqlite3_vtab *pVTab; |
| 144355 | int a1; |
| 144356 | if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; |
| 144357 | if( IsOrdinaryTable(pTab) ) continue; |
| 144358 | if( !IsVirtual(pTab) ) continue; |
| 144359 | if( pTab->nCol<=0 ){ |
| 144360 | const char *zMod = pTab->u.vtab.azArg[0]; |
| 144361 | if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; |
| @@ -143485,10 +144583,12 @@ | |
| 144583 | eMode = SQLITE_CHECKPOINT_FULL; |
| 144584 | }else if( sqlite3StrICmp(zRight, "restart")==0 ){ |
| 144585 | eMode = SQLITE_CHECKPOINT_RESTART; |
| 144586 | }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ |
| 144587 | eMode = SQLITE_CHECKPOINT_TRUNCATE; |
| 144588 | }else if( sqlite3StrICmp(zRight, "noop")==0 ){ |
| 144589 | eMode = SQLITE_CHECKPOINT_NOOP; |
| 144590 | } |
| 144591 | } |
| 144592 | pParse->nMem = 3; |
| 144593 | sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); |
| 144594 | sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); |
| @@ -145051,13 +146151,15 @@ | |
| 146151 | ** or encounters a permanent error. A schema problem after one schema |
| 146152 | ** reset is considered a permanent error. */ |
| 146153 | rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); |
| 146154 | assert( rc==SQLITE_OK || *ppStmt==0 ); |
| 146155 | if( rc==SQLITE_OK || db->mallocFailed ) break; |
| 146156 | cnt++; |
| 146157 | }while( (rc==SQLITE_ERROR_RETRY && ALWAYS(cnt<=SQLITE_MAX_PREPARE_RETRY)) |
| 146158 | || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt)==1) ); |
| 146159 | sqlite3BtreeLeaveAll(db); |
| 146160 | assert( rc!=SQLITE_ERROR_RETRY ); |
| 146161 | rc = sqlite3ApiExit(db, rc); |
| 146162 | assert( (rc&db->errMask)==rc ); |
| 146163 | db->busyHandler.nBusy = 0; |
| 146164 | sqlite3_mutex_leave(db->mutex); |
| 146165 | assert( rc==SQLITE_OK || (*ppStmt)==0 ); |
| @@ -145727,12 +146829,11 @@ | |
| 146829 | while( p ){ |
| 146830 | ExprSetProperty(p, joinFlag); |
| 146831 | assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); |
| 146832 | ExprSetVVAProperty(p, EP_NoReduce); |
| 146833 | p->w.iJoin = iTable; |
| 146834 | if( ExprUseXList(p) ){ |
| 146835 | if( p->x.pList ){ |
| 146836 | int i; |
| 146837 | for(i=0; i<p->x.pList->nExpr; i++){ |
| 146838 | sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); |
| 146839 | } |
| @@ -145944,10 +147045,11 @@ | |
| 147045 | else if( pRight->u3.pOn ){ |
| 147046 | sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); |
| 147047 | p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); |
| 147048 | pRight->u3.pOn = 0; |
| 147049 | pRight->fg.isOn = 1; |
| 147050 | p->selFlags |= SF_OnToWhere; |
| 147051 | } |
| 147052 | } |
| 147053 | return 0; |
| 147054 | } |
| 147055 | |
| @@ -146830,11 +147932,14 @@ | |
| 147932 | ** Allocate a KeyInfo object sufficient for an index of N key columns and |
| 147933 | ** X extra columns. |
| 147934 | */ |
| 147935 | SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ |
| 147936 | int nExtra = (N+X)*(sizeof(CollSeq*)+1); |
| 147937 | KeyInfo *p; |
| 147938 | assert( X>=0 ); |
| 147939 | if( NEVER(N+X>0xffff) ) return (KeyInfo*)sqlite3OomFault(db); |
| 147940 | p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); |
| 147941 | if( p ){ |
| 147942 | p->aSortFlags = (u8*)&p->aColl[N+X]; |
| 147943 | p->nKeyField = (u16)N; |
| 147944 | p->nAllField = (u16)(N+X); |
| 147945 | p->enc = ENC(db); |
| @@ -149149,11 +150254,11 @@ | |
| 150254 | ** expressions in pEList. |
| 150255 | ** |
| 150256 | ** ## About "isOuterJoin": |
| 150257 | ** |
| 150258 | ** The isOuterJoin column indicates that the replacement will occur into a |
| 150259 | ** position in the parent that is NULL-able due to an OUTER JOIN. Either the |
| 150260 | ** target slot in the parent is the right operand of a LEFT JOIN, or one of |
| 150261 | ** the left operands of a RIGHT JOIN. In either case, we need to potentially |
| 150262 | ** bypass the substituted expression with OP_IfNullRow. |
| 150263 | ** |
| 150264 | ** Suppose the original expression is an integer constant. Even though the table |
| @@ -149987,21 +151092,16 @@ | |
| 151092 | ** elements we are now copying in. |
| 151093 | */ |
| 151094 | pSub = pSub1; |
| 151095 | for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ |
| 151096 | int nSubSrc; |
| 151097 | u8 jointype = pSubitem->fg.jointype; |
| 151098 | assert( pSub!=0 ); |
| 151099 | pSubSrc = pSub->pSrc; /* FROM clause of subquery */ |
| 151100 | nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ |
| 151101 | pSrc = pParent->pSrc; /* FROM clause of the outer query */ |
| 151102 | |
| 151103 | /* The subquery uses a single slot of the FROM clause of the outer |
| 151104 | ** query. If the subquery has more than one element in its FROM clause, |
| 151105 | ** then expand the outer query to make space for it to hold all elements |
| 151106 | ** of the subquery. |
| 151107 | ** |
| @@ -150017,10 +151117,11 @@ | |
| 151117 | */ |
| 151118 | if( nSubSrc>1 ){ |
| 151119 | pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); |
| 151120 | if( pSrc==0 ) break; |
| 151121 | pParent->pSrc = pSrc; |
| 151122 | pSubitem = &pSrc->a[iFrom]; |
| 151123 | } |
| 151124 | |
| 151125 | /* Transfer the FROM clause terms from the subquery into the |
| 151126 | ** outer query. |
| 151127 | */ |
| @@ -150031,15 +151132,14 @@ | |
| 151132 | assert( pItem->fg.isSubquery |
| 151133 | || pItem->fg.fixedSchema |
| 151134 | || pItem->u4.zDatabase==0 ); |
| 151135 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 151136 | *pItem = pSubSrc->a[i]; |
| 151137 | pItem->fg.jointype |= (jointype & JT_LTORJ); |
| 151138 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 151139 | } |
| 151140 | pSubitem->fg.jointype |= jointype; |
| 151141 | |
| 151142 | /* Now begin substituting subquery result set expressions for |
| 151143 | ** references to the iParent in the outer query. |
| 151144 | ** |
| 151145 | ** Example: |
| @@ -152746,10 +153846,11 @@ | |
| 153846 | Select *pSub = pWhere->x.pSelect; |
| 153847 | Expr *pSubWhere = pSub->pWhere; |
| 153848 | if( pSub->pSrc->nSrc==1 |
| 153849 | && (pSub->selFlags & SF_Aggregate)==0 |
| 153850 | && !pSub->pSrc->a[0].fg.isSubquery |
| 153851 | && pSub->pLimit==0 |
| 153852 | ){ |
| 153853 | memset(pWhere, 0, sizeof(*pWhere)); |
| 153854 | pWhere->op = TK_INTEGER; |
| 153855 | pWhere->u.iValue = 1; |
| 153856 | ExprSetProperty(pWhere, EP_IntValue); |
| @@ -152774,10 +153875,119 @@ | |
| 153875 | existsToJoin(pParse, p, pSubWhere); |
| 153876 | } |
| 153877 | } |
| 153878 | } |
| 153879 | } |
| 153880 | |
| 153881 | /* |
| 153882 | ** Type used for Walker callbacks by selectCheckOnClauses(). |
| 153883 | */ |
| 153884 | typedef struct CheckOnCtx CheckOnCtx; |
| 153885 | struct CheckOnCtx { |
| 153886 | SrcList *pSrc; /* SrcList for this context */ |
| 153887 | int iJoin; /* Cursor numbers must be =< than this */ |
| 153888 | CheckOnCtx *pParent; /* Parent context */ |
| 153889 | }; |
| 153890 | |
| 153891 | /* |
| 153892 | ** True if the SrcList passed as the only argument contains at least |
| 153893 | ** one RIGHT or FULL JOIN. False otherwise. |
| 153894 | */ |
| 153895 | #define hasRightJoin(pSrc) (((pSrc)->a[0].fg.jointype & JT_LTORJ)!=0) |
| 153896 | |
| 153897 | /* |
| 153898 | ** The xExpr callback for the search of invalid ON clause terms. |
| 153899 | */ |
| 153900 | static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ |
| 153901 | CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; |
| 153902 | |
| 153903 | /* Check if pExpr is root or near-root of an ON clause constraint that needs |
| 153904 | ** to be checked to ensure that it does not refer to tables in its FROM |
| 153905 | ** clause to the right of itself. i.e. it is either: |
| 153906 | ** |
| 153907 | ** + an ON clause on an OUTER join, or |
| 153908 | ** + an ON clause on an INNER join within a FROM that features at |
| 153909 | ** least one RIGHT or FULL join. |
| 153910 | */ |
| 153911 | if( (ExprHasProperty(pExpr, EP_OuterON)) |
| 153912 | || (ExprHasProperty(pExpr, EP_InnerON) && hasRightJoin(pCtx->pSrc)) |
| 153913 | ){ |
| 153914 | /* If CheckOnCtx.iJoin is already set, then fall through and process |
| 153915 | ** this expression node as normal. Or, if CheckOnCtx.iJoin is still 0, |
| 153916 | ** set it to the cursor number of the RHS of the join to which this |
| 153917 | ** ON expression was attached and then iterate through the entire |
| 153918 | ** expression. */ |
| 153919 | assert( pCtx->iJoin==0 || pCtx->iJoin==pExpr->w.iJoin ); |
| 153920 | if( pCtx->iJoin==0 ){ |
| 153921 | pCtx->iJoin = pExpr->w.iJoin; |
| 153922 | sqlite3WalkExprNN(pWalker, pExpr); |
| 153923 | pCtx->iJoin = 0; |
| 153924 | return WRC_Prune; |
| 153925 | } |
| 153926 | } |
| 153927 | |
| 153928 | if( pExpr->op==TK_COLUMN ){ |
| 153929 | /* A column expression. Find the SrcList (if any) to which it refers. |
| 153930 | ** Then, if CheckOnCtx.iJoin indicates that this expression is part of an |
| 153931 | ** ON clause from that SrcList (i.e. if iJoin is non-zero), check that it |
| 153932 | ** does not refer to a table to the right of CheckOnCtx.iJoin. */ |
| 153933 | do { |
| 153934 | SrcList *pSrc = pCtx->pSrc; |
| 153935 | int iTab = pExpr->iTable; |
| 153936 | if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ |
| 153937 | if( pCtx->iJoin && iTab>pCtx->iJoin ){ |
| 153938 | sqlite3ErrorMsg(pWalker->pParse, |
| 153939 | "ON clause references tables to its right"); |
| 153940 | return WRC_Abort; |
| 153941 | } |
| 153942 | break; |
| 153943 | } |
| 153944 | pCtx = pCtx->pParent; |
| 153945 | }while( pCtx ); |
| 153946 | } |
| 153947 | return WRC_Continue; |
| 153948 | } |
| 153949 | |
| 153950 | /* |
| 153951 | ** The xSelect callback for the search of invalid ON clause terms. |
| 153952 | */ |
| 153953 | static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ |
| 153954 | CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; |
| 153955 | if( pSelect->pSrc==pCtx->pSrc || pSelect->pSrc->nSrc==0 ){ |
| 153956 | return WRC_Continue; |
| 153957 | }else{ |
| 153958 | CheckOnCtx sCtx; |
| 153959 | memset(&sCtx, 0, sizeof(sCtx)); |
| 153960 | sCtx.pSrc = pSelect->pSrc; |
| 153961 | sCtx.pParent = pCtx; |
| 153962 | pWalker->u.pCheckOnCtx = &sCtx; |
| 153963 | sqlite3WalkSelect(pWalker, pSelect); |
| 153964 | pWalker->u.pCheckOnCtx = pCtx; |
| 153965 | pSelect->selFlags &= ~SF_OnToWhere; |
| 153966 | return WRC_Prune; |
| 153967 | } |
| 153968 | } |
| 153969 | |
| 153970 | /* |
| 153971 | ** Check all ON clauses in pSelect to verify that they do not reference |
| 153972 | ** columns to the right. |
| 153973 | */ |
| 153974 | static void selectCheckOnClauses(Parse *pParse, Select *pSelect){ |
| 153975 | Walker w; |
| 153976 | CheckOnCtx sCtx; |
| 153977 | assert( pSelect->selFlags & SF_OnToWhere ); |
| 153978 | assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); |
| 153979 | memset(&w, 0, sizeof(w)); |
| 153980 | w.pParse = pParse; |
| 153981 | w.xExprCallback = selectCheckOnClausesExpr; |
| 153982 | w.xSelectCallback = selectCheckOnClausesSelect; |
| 153983 | w.u.pCheckOnCtx = &sCtx; |
| 153984 | memset(&sCtx, 0, sizeof(sCtx)); |
| 153985 | sCtx.pSrc = pSelect->pSrc; |
| 153986 | sqlite3WalkExprNN(&w, pSelect->pWhere); |
| 153987 | pSelect->selFlags &= ~SF_OnToWhere; |
| 153988 | } |
| 153989 | |
| 153990 | /* |
| 153991 | ** Generate byte-code for the SELECT statement given in the p argument. |
| 153992 | ** |
| 153993 | ** The results are returned according to the SelectDest structure. |
| @@ -152901,10 +154111,22 @@ | |
| 154111 | if( sqlite3TreeTrace & 0x10 ){ |
| 154112 | TREETRACE(0x10,pParse,p, ("after name resolution:\n")); |
| 154113 | sqlite3TreeViewSelect(0, p, 0); |
| 154114 | } |
| 154115 | #endif |
| 154116 | |
| 154117 | /* If the SELECT statement contains ON clauses that were moved into |
| 154118 | ** the WHERE clause, go through and verify that none of the terms |
| 154119 | ** in the ON clauses reference tables to the right of the ON clause. |
| 154120 | ** Do this now, after name resolution, but before query flattening |
| 154121 | */ |
| 154122 | if( p->selFlags & SF_OnToWhere ){ |
| 154123 | selectCheckOnClauses(pParse, p); |
| 154124 | if( pParse->nErr ){ |
| 154125 | goto select_end; |
| 154126 | } |
| 154127 | } |
| 154128 | |
| 154129 | /* If the SF_UFSrcCheck flag is set, then this function is being called |
| 154130 | ** as part of populating the temp table for an UPDATE...FROM statement. |
| 154131 | ** In this case, it is an error if the target object (pSrc->a[0]) name |
| 154132 | ** or alias is duplicated within FROM clause (pSrc->a[1..n]). |
| @@ -153766,10 +154988,11 @@ | |
| 154988 | iBMem = pParse->nMem + 1; |
| 154989 | pParse->nMem += pGroupBy->nExpr; |
| 154990 | sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag); |
| 154991 | VdbeComment((v, "clear abort flag")); |
| 154992 | sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1); |
| 154993 | sqlite3ExprNullRegisterRange(pParse, iAMem, pGroupBy->nExpr); |
| 154994 | |
| 154995 | /* Begin a loop that will extract all source rows in GROUP BY order. |
| 154996 | ** This might involve two separate loops with an OP_Sort in between, or |
| 154997 | ** it might be a single loop that uses an index to extract information |
| 154998 | ** in the right order to begin with. |
| @@ -155466,11 +156689,14 @@ | |
| 156689 | sqlite3 *db = pParse->db; |
| 156690 | ExprList *pNew; |
| 156691 | Returning *pReturning; |
| 156692 | Select sSelect; |
| 156693 | SrcList *pFrom; |
| 156694 | union { |
| 156695 | SrcList sSrc; |
| 156696 | u8 fromSpace[SZ_SRCLIST_1]; |
| 156697 | } uSrc; |
| 156698 | |
| 156699 | assert( v!=0 ); |
| 156700 | if( !pParse->bReturning ){ |
| 156701 | /* This RETURNING trigger must be for a different statement as |
| 156702 | ** this statement lacks a RETURNING clause. */ |
| @@ -155482,12 +156708,12 @@ | |
| 156708 | if( pTrigger != &(pReturning->retTrig) ){ |
| 156709 | /* This RETURNING trigger is for a different statement */ |
| 156710 | return; |
| 156711 | } |
| 156712 | memset(&sSelect, 0, sizeof(sSelect)); |
| 156713 | memset(&uSrc, 0, sizeof(uSrc)); |
| 156714 | pFrom = &uSrc.sSrc; |
| 156715 | sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); |
| 156716 | sSelect.pSrc = pFrom; |
| 156717 | pFrom->nSrc = 1; |
| 156718 | pFrom->a[0].pSTab = pTab; |
| 156719 | pFrom->a[0].zName = pTab->zName; /* tag-20240424-1 */ |
| @@ -163014,11 +164240,14 @@ | |
| 164240 | WhereClause *pWC = &pWInfo->sWC; |
| 164241 | WhereInfo *pSubWInfo; |
| 164242 | WhereLoop *pLoop = pLevel->pWLoop; |
| 164243 | SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; |
| 164244 | SrcList *pFrom; |
| 164245 | union { |
| 164246 | SrcList sSrc; |
| 164247 | u8 fromSpace[SZ_SRCLIST_1]; |
| 164248 | } uSrc; |
| 164249 | Bitmask mAll = 0; |
| 164250 | int k; |
| 164251 | |
| 164252 | ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pSTab->zName)); |
| 164253 | sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, |
| @@ -163058,11 +164287,11 @@ | |
| 164287 | if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; |
| 164288 | pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, |
| 164289 | sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); |
| 164290 | } |
| 164291 | } |
| 164292 | pFrom = &uSrc.sSrc; |
| 164293 | pFrom->nSrc = 1; |
| 164294 | pFrom->nAlloc = 1; |
| 164295 | memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem)); |
| 164296 | pFrom->a[0].fg.jointype = 0; |
| 164297 | assert( pParse->withinRJSubrtn < 100 ); |
| @@ -164275,25 +165504,11 @@ | |
| 165504 | Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); |
| 165505 | if( ExprHasProperty(pExpr, EP_OuterON) ){ |
| 165506 | prereqAll |= x; |
| 165507 | extraRight = x-1; /* ON clause terms may not be used with an index |
| 165508 | ** on left table of a LEFT JOIN. Ticket #3015 */ |
| 165509 | }else if( (prereqAll>>1)>=x ){ |
| 165510 | ExprClearProperty(pExpr, EP_InnerON); |
| 165511 | } |
| 165512 | } |
| 165513 | pTerm->prereqAll = prereqAll; |
| 165514 | pTerm->leftCursor = -1; |
| @@ -169084,10 +170299,11 @@ | |
| 170299 | if( pProbe->bNoQuery ) continue; |
| 170300 | rSize = pProbe->aiRowLogEst[0]; |
| 170301 | pNew->u.btree.nEq = 0; |
| 170302 | pNew->u.btree.nBtm = 0; |
| 170303 | pNew->u.btree.nTop = 0; |
| 170304 | pNew->u.btree.nDistinctCol = 0; |
| 170305 | pNew->nSkip = 0; |
| 170306 | pNew->nLTerm = 0; |
| 170307 | pNew->iSortIdx = 0; |
| 170308 | pNew->rSetup = 0; |
| 170309 | pNew->prereq = mPrereq; |
| @@ -170150,14 +171366,16 @@ | |
| 171366 | if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ |
| 171367 | if( pLoop->u.vtab.isOrdered |
| 171368 | && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) |
| 171369 | ){ |
| 171370 | obSat = obDone; |
| 171371 | }else{ |
| 171372 | /* No further ORDER BY terms may be matched. So this call should |
| 171373 | ** return >=0, not -1. Clear isOrderDistinct to ensure it does so. */ |
| 171374 | isOrderDistinct = 0; |
| 171375 | } |
| 171376 | break; |
| 171377 | } |
| 171378 | iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; |
| 171379 | |
| 171380 | /* Mark off any ORDER BY term X that is a column in the table of |
| 171381 | ** the current loop for which there is term in the WHERE |
| @@ -170897,21 +172115,28 @@ | |
| 172115 | |
| 172116 | /* Check to see if pWLoop should be added to the set of |
| 172117 | ** mxChoice best-so-far paths. |
| 172118 | ** |
| 172119 | ** First look for an existing path among best-so-far paths |
| 172120 | ** that: |
| 172121 | ** (1) covers the same set of loops, and |
| 172122 | ** (2) has a compatible isOrdered value. |
| 172123 | ** |
| 172124 | ** "Compatible isOrdered value" means either |
| 172125 | ** (A) both have isOrdered==-1, or |
| 172126 | ** (B) both have isOrder>=0, or |
| 172127 | ** (C) ordering does not matter because this is the last round |
| 172128 | ** of the solver. |
| 172129 | ** |
| 172130 | ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent |
| 172131 | ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range |
| 172132 | ** of legal values for isOrdered, -1..64. |
| 172133 | */ |
| 172134 | testcase( nTo==0 ); |
| 172135 | for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ |
| 172136 | if( pTo->maskLoop==maskNew |
| 172137 | && ( ((pTo->isOrdered^isOrdered)&0x80)==0 || iLoop==nLoop-1 ) |
| 172138 | ){ |
| 172139 | testcase( jj==nTo-1 ); |
| 172140 | break; |
| 172141 | } |
| 172142 | } |
| @@ -171062,15 +172287,14 @@ | |
| 172287 | sqlite3ErrorMsg(pParse, "no query solution"); |
| 172288 | sqlite3StackFreeNN(pParse->db, pSpace); |
| 172289 | return SQLITE_ERROR; |
| 172290 | } |
| 172291 | |
| 172292 | /* Only one path is available, which is the best path */ |
| 172293 | assert( nFrom==1 ); |
| 172294 | pFrom = aFrom; |
| 172295 | |
| 172296 | assert( pWInfo->nLevel==nLoop ); |
| 172297 | /* Load the lowest cost path into pWInfo */ |
| 172298 | for(iLoop=0; iLoop<nLoop; iLoop++){ |
| 172299 | WhereLevel *pLevel = pWInfo->a + iLoop; |
| 172300 | pLevel->pWLoop = pWLoop = pFrom->aLoop[iLoop]; |
| @@ -171199,11 +172423,14 @@ | |
| 172423 | int once = 0; |
| 172424 | #endif |
| 172425 | for(i=0; i<pWInfo->nLevel; i++){ |
| 172426 | WhereLoop *p = pWInfo->a[i].pWLoop; |
| 172427 | if( p==0 ) break; |
| 172428 | if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ |
| 172429 | /* Treat a vtab scan as similar to a full-table scan */ |
| 172430 | break; |
| 172431 | } |
| 172432 | if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ |
| 172433 | u8 iTab = p->iTab; |
| 172434 | WhereLoop *pLoop; |
| 172435 | for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ |
| 172436 | if( pLoop->iTab!=iTab ) continue; |
| @@ -175312,11 +176539,11 @@ | |
| 176539 | ** RETURN_ROW |
| 176540 | ** |
| 176541 | ** |
| 176542 | ** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING |
| 176543 | ** |
| 176544 | ** ... loop started by sqlite3WhereBegin() ... |
| 176545 | ** if( new partition ){ |
| 176546 | ** Gosub flush |
| 176547 | ** } |
| 176548 | ** Insert new row into eph table. |
| 176549 | ** if( first row of partition ){ |
| @@ -175830,10 +177057,16 @@ | |
| 177057 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 177058 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1); |
| 177059 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); |
| 177060 | }else{ |
| 177061 | assert( pMWin->eEnd==TK_FOLLOWING ); |
| 177062 | /* assert( regStart>=0 ); |
| 177063 | ** regEnd = regEnd - regStart; |
| 177064 | ** regStart = 0; */ |
| 177065 | sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd); |
| 177066 | sqlite3VdbeAddOp2(v, OP_Integer, 0, regStart); |
| 177067 | |
| 177068 | addrStart = sqlite3VdbeCurrentAddr(v); |
| 177069 | addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); |
| 177070 | addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); |
| 177071 | } |
| 177072 | sqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart); |
| @@ -183317,13 +184550,10 @@ | |
| 184550 | #endif |
| 184551 | #ifdef SQLITE_ENABLE_DBSTAT_VTAB |
| 184552 | sqlite3DbstatRegister, |
| 184553 | #endif |
| 184554 | sqlite3TestExtInit, |
| 184555 | #ifdef SQLITE_ENABLE_STMTVTAB |
| 184556 | sqlite3StmtVtabInit, |
| 184557 | #endif |
| 184558 | #ifdef SQLITE_ENABLE_BYTECODE_VTAB |
| 184559 | sqlite3VdbeBytecodeVtabInit, |
| @@ -184775,10 +186005,13 @@ | |
| 186005 | for(i=0; i<2 && zName==0; i++, rc &= 0xff){ |
| 186006 | switch( rc ){ |
| 186007 | case SQLITE_OK: zName = "SQLITE_OK"; break; |
| 186008 | case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; |
| 186009 | case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; |
| 186010 | case SQLITE_ERROR_RETRY: zName = "SQLITE_ERROR_RETRY"; break; |
| 186011 | case SQLITE_ERROR_MISSING_COLLSEQ: |
| 186012 | zName = "SQLITE_ERROR_MISSING_COLLSEQ"; break; |
| 186013 | case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; |
| 186014 | case SQLITE_PERM: zName = "SQLITE_PERM"; break; |
| 186015 | case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; |
| 186016 | case SQLITE_ABORT_ROLLBACK: zName = "SQLITE_ABORT_ROLLBACK"; break; |
| 186017 | case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; |
| @@ -185955,10 +187188,33 @@ | |
| 187188 | } |
| 187189 | } |
| 187190 | sqlite3_mutex_leave(db->mutex); |
| 187191 | return z; |
| 187192 | } |
| 187193 | |
| 187194 | /* |
| 187195 | ** Set the error code and error message associated with the database handle. |
| 187196 | ** |
| 187197 | ** This routine is intended to be called by outside extensions (ex: the |
| 187198 | ** Session extension). Internal logic should invoke sqlite3Error() or |
| 187199 | ** sqlite3ErrorWithMsg() directly. |
| 187200 | */ |
| 187201 | SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ |
| 187202 | int rc = SQLITE_OK; |
| 187203 | if( !sqlite3SafetyCheckSickOrOk(db) ){ |
| 187204 | return SQLITE_MISUSE_BKPT; |
| 187205 | } |
| 187206 | sqlite3_mutex_enter(db->mutex); |
| 187207 | if( zMsg ){ |
| 187208 | sqlite3ErrorWithMsg(db, errcode, "%s", zMsg); |
| 187209 | }else{ |
| 187210 | sqlite3Error(db, errcode); |
| 187211 | } |
| 187212 | rc = sqlite3ApiExit(db, rc); |
| 187213 | sqlite3_mutex_leave(db->mutex); |
| 187214 | return rc; |
| 187215 | } |
| 187216 | |
| 187217 | /* |
| 187218 | ** Return the byte offset of the most recent error |
| 187219 | */ |
| 187220 | SQLITE_API int sqlite3_error_offset(sqlite3 *db){ |
| @@ -187780,17 +189036,19 @@ | |
| 189036 | case SQLITE_TESTCTRL_ISINIT: { |
| 189037 | if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; |
| 189038 | break; |
| 189039 | } |
| 189040 | |
| 189041 | /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, mode, tnum); |
| 189042 | ** |
| 189043 | ** This test control is used to create imposter tables. "db" is a pointer |
| 189044 | ** to the database connection. dbName is the database name (ex: "main" or |
| 189045 | ** "temp") which will receive the imposter. "mode" turns imposter mode on |
| 189046 | ** or off. mode==0 means imposter mode is off. mode==1 means imposter mode |
| 189047 | ** is on. mode==2 means imposter mode is on but results in an imposter |
| 189048 | ** table that is read-only unless writable_schema is on. "tnum" is the |
| 189049 | ** root page of the b-tree to which the imposter table should connect. |
| 189050 | ** |
| 189051 | ** Enable imposter mode only when the schema has already been parsed. Then |
| 189052 | ** run a single CREATE TABLE statement to construct the imposter table in |
| 189053 | ** the parsed schema. Then turn imposter mode back off again. |
| 189054 | ** |
| @@ -189023,21 +190281,24 @@ | |
| 190281 | ** |
| 190282 | */ |
| 190283 | #ifndef _FTSINT_H |
| 190284 | #define _FTSINT_H |
| 190285 | |
| 190286 | /* |
| 190287 | ** Activate assert() only if SQLITE_TEST is enabled. |
| 190288 | */ |
| 190289 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 190290 | # define NDEBUG 1 |
| 190291 | #endif |
| 190292 | |
| 190293 | /* #include <assert.h> */ |
| 190294 | /* #include <stdlib.h> */ |
| 190295 | /* #include <stddef.h> */ |
| 190296 | /* #include <stdio.h> */ |
| 190297 | /* #include <string.h> */ |
| 190298 | /* #include <stdarg.h> */ |
| 190299 | |
| 190300 | /* FTS3/FTS4 require virtual tables */ |
| 190301 | #ifdef SQLITE_OMIT_VIRTUALTABLE |
| 190302 | # undef SQLITE_ENABLE_FTS3 |
| 190303 | # undef SQLITE_ENABLE_FTS4 |
| 190304 | #endif |
| @@ -189476,17 +190737,10 @@ | |
| 190737 | /* |
| 190738 | ** Macro used to suppress compiler warnings for unused parameters. |
| 190739 | */ |
| 190740 | #define UNUSED_PARAMETER(x) (void)(x) |
| 190741 | |
| 190742 | /* |
| 190743 | ** The TESTONLY macro is used to enclose variable declarations or |
| 190744 | ** other bits of code that are needed to support the arguments |
| 190745 | ** within testcase() and assert() macros. |
| 190746 | */ |
| @@ -203757,12 +205011,12 @@ | |
| 205011 | /* |
| 205012 | ** An object of this type contains the state required to create or append |
| 205013 | ** to an appendable b-tree segment. |
| 205014 | */ |
| 205015 | struct IncrmergeWriter { |
| 205016 | i64 nLeafEst; /* Space allocated for leaf blocks */ |
| 205017 | i64 nWork; /* Number of leaf pages flushed */ |
| 205018 | sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ |
| 205019 | int iIdx; /* Index of *output* segment in iAbsLevel+1 */ |
| 205020 | sqlite3_int64 iStart; /* Block number of first allocated block */ |
| 205021 | sqlite3_int64 iEnd; /* Block number of last allocated block */ |
| 205022 | sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */ |
| @@ -204504,21 +205758,21 @@ | |
| 205758 | Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */ |
| 205759 | IncrmergeWriter *pWriter /* Populate this object */ |
| 205760 | ){ |
| 205761 | int rc; /* Return Code */ |
| 205762 | int i; /* Iterator variable */ |
| 205763 | i64 nLeafEst = 0; /* Blocks allocated for leaf nodes */ |
| 205764 | sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ |
| 205765 | sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ |
| 205766 | |
| 205767 | /* Calculate nLeafEst. */ |
| 205768 | rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0); |
| 205769 | if( rc==SQLITE_OK ){ |
| 205770 | sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); |
| 205771 | sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); |
| 205772 | if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ |
| 205773 | nLeafEst = sqlite3_column_int64(pLeafEst, 0); |
| 205774 | } |
| 205775 | rc = sqlite3_reset(pLeafEst); |
| 205776 | } |
| 205777 | if( rc!=SQLITE_OK ) return rc; |
| 205778 | |
| @@ -205897,14 +207151,10 @@ | |
| 207151 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) |
| 207152 | |
| 207153 | /* #include <string.h> */ |
| 207154 | /* #include <assert.h> */ |
| 207155 | |
| 207156 | /* |
| 207157 | ** Characters that may appear in the second argument to matchinfo(). |
| 207158 | */ |
| 207159 | #define FTS3_MATCHINFO_NPHRASE 'p' /* 1 value */ |
| 207160 | #define FTS3_MATCHINFO_NCOL 'c' /* 1 value */ |
| @@ -210754,11 +212004,11 @@ | |
| 212004 | switch( (u8)zIn[1] ){ |
| 212005 | case '\'': |
| 212006 | jsonAppendChar(pOut, '\''); |
| 212007 | break; |
| 212008 | case 'v': |
| 212009 | jsonAppendRawNZ(pOut, "\\u000b", 6); |
| 212010 | break; |
| 212011 | case 'x': |
| 212012 | if( sz2<4 ){ |
| 212013 | pOut->eErr |= JSTRING_MALFORMED; |
| 212014 | sz2 = 2; |
| @@ -211604,23 +212854,31 @@ | |
| 212854 | /* |
| 212855 | ** Return the value of the BLOB node at index i. |
| 212856 | ** |
| 212857 | ** If the value is a primitive, return it as an SQL value. |
| 212858 | ** If the value is an array or object, return it as either |
| 212859 | ** JSON text or the BLOB encoding, depending on the eMode flag |
| 212860 | ** as follows: |
| 212861 | ** |
| 212862 | ** eMode==0 JSONB if the JSON_B flag is set in userdata or |
| 212863 | ** text if the JSON_B flag is omitted from userdata. |
| 212864 | ** |
| 212865 | ** eMode==1 Text |
| 212866 | ** |
| 212867 | ** eMode==2 JSONB |
| 212868 | */ |
| 212869 | static void jsonReturnFromBlob( |
| 212870 | JsonParse *pParse, /* Complete JSON parse tree */ |
| 212871 | u32 i, /* Index of the node */ |
| 212872 | sqlite3_context *pCtx, /* Return value for this function */ |
| 212873 | int eMode /* Format of return: text of JSONB */ |
| 212874 | ){ |
| 212875 | u32 n, sz; |
| 212876 | int rc; |
| 212877 | sqlite3 *db = sqlite3_context_db_handle(pCtx); |
| 212878 | |
| 212879 | assert( eMode>=0 && eMode<=2 ); |
| 212880 | n = jsonbPayloadSize(pParse, i, &sz); |
| 212881 | if( n==0 ){ |
| 212882 | sqlite3_result_error(pCtx, "malformed JSON", -1); |
| 212883 | return; |
| 212884 | } |
| @@ -211657,11 +212915,23 @@ | |
| 212915 | z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); |
| 212916 | if( z==0 ) goto returnfromblob_oom; |
| 212917 | rc = sqlite3DecOrHexToI64(z, &iRes); |
| 212918 | sqlite3DbFree(db, z); |
| 212919 | if( rc==0 ){ |
| 212920 | if( iRes<0 ){ |
| 212921 | /* A hexadecimal literal with 16 significant digits and with the |
| 212922 | ** high-order bit set is a negative integer in SQLite (and hence |
| 212923 | ** iRes comes back as negative) but should be interpreted as a |
| 212924 | ** positive value if it occurs within JSON. The value is too |
| 212925 | ** large to appear as an SQLite integer so it must be converted |
| 212926 | ** into floating point. */ |
| 212927 | double r; |
| 212928 | r = (double)*(sqlite3_uint64*)&iRes; |
| 212929 | sqlite3_result_double(pCtx, bNeg ? -r : r); |
| 212930 | }else{ |
| 212931 | sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); |
| 212932 | } |
| 212933 | }else if( rc==3 && bNeg ){ |
| 212934 | sqlite3_result_int64(pCtx, SMALLEST_INT64); |
| 212935 | }else if( rc==1 ){ |
| 212936 | goto returnfromblob_malformed; |
| 212937 | }else{ |
| @@ -211735,12 +213005,18 @@ | |
| 213005 | sqlite3_result_text(pCtx, zOut, iOut, SQLITE_DYNAMIC); |
| 213006 | break; |
| 213007 | } |
| 213008 | case JSONB_ARRAY: |
| 213009 | case JSONB_OBJECT: { |
| 213010 | if( eMode==0 ){ |
| 213011 | if( (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)) & JSON_BLOB)!=0 ){ |
| 213012 | eMode = 2; |
| 213013 | }else{ |
| 213014 | eMode = 1; |
| 213015 | } |
| 213016 | } |
| 213017 | if( eMode==2 ){ |
| 213018 | sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); |
| 213019 | }else{ |
| 213020 | jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); |
| 213021 | } |
| 213022 | break; |
| @@ -213383,10 +214659,11 @@ | |
| 214659 | u32 i; /* Index in sParse.aBlob[] of current row */ |
| 214660 | u32 iEnd; /* EOF when i equals or exceeds this value */ |
| 214661 | u32 nRoot; /* Size of the root path in bytes */ |
| 214662 | u8 eType; /* Type of the container for element i */ |
| 214663 | u8 bRecursive; /* True for json_tree(). False for json_each() */ |
| 214664 | u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ |
| 214665 | u32 nParent; /* Current nesting depth */ |
| 214666 | u32 nParentAlloc; /* Space allocated for aParent[] */ |
| 214667 | JsonParent *aParent; /* Parent elements of i */ |
| 214668 | sqlite3 *db; /* Database connection */ |
| 214669 | JsonString path; /* Current path */ |
| @@ -213394,10 +214671,12 @@ | |
| 214671 | }; |
| 214672 | typedef struct JsonEachConnection JsonEachConnection; |
| 214673 | struct JsonEachConnection { |
| 214674 | sqlite3_vtab base; /* Base class - must be first */ |
| 214675 | sqlite3 *db; /* Database connection */ |
| 214676 | u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ |
| 214677 | u8 bRecursive; /* True for json_tree(). False for json_each() */ |
| 214678 | }; |
| 214679 | |
| 214680 | |
| 214681 | /* Constructor for the json_each virtual table */ |
| 214682 | static int jsonEachConnect( |
| @@ -213436,10 +214715,12 @@ | |
| 214715 | pNew = (JsonEachConnection*)sqlite3DbMallocZero(db, sizeof(*pNew)); |
| 214716 | *ppVtab = (sqlite3_vtab*)pNew; |
| 214717 | if( pNew==0 ) return SQLITE_NOMEM; |
| 214718 | sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
| 214719 | pNew->db = db; |
| 214720 | pNew->eMode = argv[0][4]=='b' ? 2 : 1; |
| 214721 | pNew->bRecursive = argv[0][4+pNew->eMode]=='t'; |
| 214722 | } |
| 214723 | return rc; |
| 214724 | } |
| 214725 | |
| 214726 | /* destructor for json_each virtual table */ |
| @@ -213447,34 +214728,26 @@ | |
| 214728 | JsonEachConnection *p = (JsonEachConnection*)pVtab; |
| 214729 | sqlite3DbFree(p->db, pVtab); |
| 214730 | return SQLITE_OK; |
| 214731 | } |
| 214732 | |
| 214733 | /* constructor for a JsonEachCursor object for json_each()/json_tree(). */ |
| 214734 | static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
| 214735 | JsonEachConnection *pVtab = (JsonEachConnection*)p; |
| 214736 | JsonEachCursor *pCur; |
| 214737 | |
| 214738 | UNUSED_PARAMETER(p); |
| 214739 | pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); |
| 214740 | if( pCur==0 ) return SQLITE_NOMEM; |
| 214741 | pCur->db = pVtab->db; |
| 214742 | pCur->eMode = pVtab->eMode; |
| 214743 | pCur->bRecursive = pVtab->bRecursive; |
| 214744 | jsonStringZero(&pCur->path); |
| 214745 | *ppCursor = &pCur->base; |
| 214746 | return SQLITE_OK; |
| 214747 | } |
| 214748 | |
| 214749 | /* Reset a JsonEachCursor back to its original state. Free any memory |
| 214750 | ** held. */ |
| 214751 | static void jsonEachCursorReset(JsonEachCursor *p){ |
| 214752 | jsonParseReset(&p->sParse); |
| 214753 | jsonStringReset(&p->path); |
| @@ -213675,11 +214948,11 @@ | |
| 214948 | } |
| 214949 | break; |
| 214950 | } |
| 214951 | case JEACH_VALUE: { |
| 214952 | u32 i = jsonSkipLabel(p); |
| 214953 | jsonReturnFromBlob(&p->sParse, i, ctx, p->eMode); |
| 214954 | if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ |
| 214955 | sqlite3_result_subtype(ctx, JSON_SUBTYPE); |
| 214956 | } |
| 214957 | break; |
| 214958 | } |
| @@ -213919,40 +215192,11 @@ | |
| 215192 | 0, /* xCreate */ |
| 215193 | jsonEachConnect, /* xConnect */ |
| 215194 | jsonEachBestIndex, /* xBestIndex */ |
| 215195 | jsonEachDisconnect, /* xDisconnect */ |
| 215196 | 0, /* xDestroy */ |
| 215197 | jsonEachOpen, /* xOpen - open a cursor */ |
| 215198 | jsonEachClose, /* xClose - close a cursor */ |
| 215199 | jsonEachFilter, /* xFilter - configure scan constraints */ |
| 215200 | jsonEachNext, /* xNext - advance a cursor */ |
| 215201 | jsonEachEof, /* xEof - check for end of scan */ |
| 215202 | jsonEachColumn, /* xColumn - read data */ |
| @@ -214037,26 +215281,25 @@ | |
| 215281 | #endif |
| 215282 | } |
| 215283 | |
| 215284 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) |
| 215285 | /* |
| 215286 | ** Register the JSON table-valued function named zName and return a |
| 215287 | ** pointer to its Module object. Return NULL if something goes wrong. |
| 215288 | */ |
| 215289 | SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3 *db, const char *zName){ |
| 215290 | unsigned int i; |
| 215291 | static const char *azModule[] = { |
| 215292 | "json_each", "json_tree", "jsonb_each", "jsonb_tree" |
| 215293 | }; |
| 215294 | assert( sqlite3HashFind(&db->aModule, zName)==0 ); |
| 215295 | for(i=0; i<sizeof(azModule)/sizeof(azModule[0]); i++){ |
| 215296 | if( sqlite3StrICmp(azModule[i],zName)==0 ){ |
| 215297 | return sqlite3VtabCreateModule(db, azModule[i], &jsonEachModule, 0, 0); |
| 215298 | } |
| 215299 | } |
| 215300 | return 0; |
| 215301 | } |
| 215302 | #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */ |
| 215303 | |
| 215304 | /************** End of json.c ************************************************/ |
| 215305 | /************** Begin file rtree.c *******************************************/ |
| @@ -228289,12 +229532,12 @@ | |
| 229532 | typedef struct DbpageTable DbpageTable; |
| 229533 | typedef struct DbpageCursor DbpageCursor; |
| 229534 | |
| 229535 | struct DbpageCursor { |
| 229536 | sqlite3_vtab_cursor base; /* Base class. Must be first */ |
| 229537 | Pgno pgno; /* Current page number */ |
| 229538 | Pgno mxPgno; /* Last page to visit on this scan */ |
| 229539 | Pager *pPager; /* Pager being read/written */ |
| 229540 | DbPage *pPage1; /* Page 1 of the database */ |
| 229541 | int iDb; /* Index of database to analyze */ |
| 229542 | int szPage; /* Size of each page in bytes */ |
| 229543 | }; |
| @@ -228427,11 +229670,11 @@ | |
| 229670 | if( pCsr==0 ){ |
| 229671 | return SQLITE_NOMEM_BKPT; |
| 229672 | }else{ |
| 229673 | memset(pCsr, 0, sizeof(DbpageCursor)); |
| 229674 | pCsr->base.pVtab = pVTab; |
| 229675 | pCsr->pgno = 0; |
| 229676 | } |
| 229677 | |
| 229678 | *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| 229679 | return SQLITE_OK; |
| 229680 | } |
| @@ -228527,16 +229770,16 @@ | |
| 229770 | ){ |
| 229771 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 229772 | int rc = SQLITE_OK; |
| 229773 | switch( i ){ |
| 229774 | case 0: { /* pgno */ |
| 229775 | sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno); |
| 229776 | break; |
| 229777 | } |
| 229778 | case 1: { /* data */ |
| 229779 | DbPage *pDbPage = 0; |
| 229780 | if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){ |
| 229781 | /* The pending byte page. Assume it is zeroed out. Attempting to |
| 229782 | ** request this page from the page is an SQLITE_CORRUPT error. */ |
| 229783 | sqlite3_result_zeroblob(ctx, pCsr->szPage); |
| 229784 | }else{ |
| 229785 | rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); |
| @@ -228606,14 +229849,14 @@ | |
| 229849 | if( argc==1 ){ |
| 229850 | zErr = "cannot delete"; |
| 229851 | goto update_fail; |
| 229852 | } |
| 229853 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ |
| 229854 | pgno = (Pgno)sqlite3_value_int64(argv[2]); |
| 229855 | isInsert = 1; |
| 229856 | }else{ |
| 229857 | pgno = (Pgno)sqlite3_value_int64(argv[0]); |
| 229858 | if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ |
| 229859 | zErr = "cannot insert"; |
| 229860 | goto update_fail; |
| 229861 | } |
| 229862 | isInsert = 0; |
| @@ -228744,10 +229987,539 @@ | |
| 229987 | #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| 229988 | SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } |
| 229989 | #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ |
| 229990 | |
| 229991 | /************** End of dbpage.c **********************************************/ |
| 229992 | /************** Begin file carray.c ******************************************/ |
| 229993 | /* |
| 229994 | ** 2016-06-29 |
| 229995 | ** |
| 229996 | ** The author disclaims copyright to this source code. In place of |
| 229997 | ** a legal notice, here is a blessing: |
| 229998 | ** |
| 229999 | ** May you do good and not evil. |
| 230000 | ** May you find forgiveness for yourself and forgive others. |
| 230001 | ** May you share freely, never taking more than you give. |
| 230002 | ** |
| 230003 | ************************************************************************* |
| 230004 | ** |
| 230005 | ** This file implements a table-valued-function that |
| 230006 | ** returns the values in a C-language array. |
| 230007 | ** Examples: |
| 230008 | ** |
| 230009 | ** SELECT * FROM carray($ptr,5) |
| 230010 | ** |
| 230011 | ** The query above returns 5 integers contained in a C-language array |
| 230012 | ** at the address $ptr. $ptr is a pointer to the array of integers. |
| 230013 | ** The pointer value must be assigned to $ptr using the |
| 230014 | ** sqlite3_bind_pointer() interface with a pointer type of "carray". |
| 230015 | ** For example: |
| 230016 | ** |
| 230017 | ** static int aX[] = { 53, 9, 17, 2231, 4, 99 }; |
| 230018 | ** int i = sqlite3_bind_parameter_index(pStmt, "$ptr"); |
| 230019 | ** sqlite3_bind_pointer(pStmt, i, aX, "carray", 0); |
| 230020 | ** |
| 230021 | ** There is an optional third parameter to determine the datatype of |
| 230022 | ** the C-language array. Allowed values of the third parameter are |
| 230023 | ** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example: |
| 230024 | ** |
| 230025 | ** SELECT * FROM carray($ptr,10,'char*'); |
| 230026 | ** |
| 230027 | ** The default value of the third parameter is 'int32'. |
| 230028 | ** |
| 230029 | ** HOW IT WORKS |
| 230030 | ** |
| 230031 | ** The carray "function" is really a virtual table with the |
| 230032 | ** following schema: |
| 230033 | ** |
| 230034 | ** CREATE TABLE carray( |
| 230035 | ** value, |
| 230036 | ** pointer HIDDEN, |
| 230037 | ** count HIDDEN, |
| 230038 | ** ctype TEXT HIDDEN |
| 230039 | ** ); |
| 230040 | ** |
| 230041 | ** If the hidden columns "pointer" and "count" are unconstrained, then |
| 230042 | ** the virtual table has no rows. Otherwise, the virtual table interprets |
| 230043 | ** the integer value of "pointer" as a pointer to the array and "count" |
| 230044 | ** as the number of elements in the array. The virtual table steps through |
| 230045 | ** the array, element by element. |
| 230046 | */ |
| 230047 | #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) |
| 230048 | /* #include "sqliteInt.h" */ |
| 230049 | #if defined(_WIN32) || defined(__RTP__) || defined(_WRS_KERNEL) |
| 230050 | struct iovec { |
| 230051 | void *iov_base; |
| 230052 | size_t iov_len; |
| 230053 | }; |
| 230054 | #else |
| 230055 | # include <sys/uio.h> |
| 230056 | #endif |
| 230057 | |
| 230058 | /* |
| 230059 | ** Names of allowed datatypes |
| 230060 | */ |
| 230061 | static const char *azType[] = { "int32", "int64", "double", "char*", |
| 230062 | "struct iovec" }; |
| 230063 | |
| 230064 | /* |
| 230065 | ** Structure used to hold the sqlite3_carray_bind() information |
| 230066 | */ |
| 230067 | typedef struct carray_bind carray_bind; |
| 230068 | struct carray_bind { |
| 230069 | void *aData; /* The data */ |
| 230070 | int nData; /* Number of elements */ |
| 230071 | int mFlags; /* Control flags */ |
| 230072 | void (*xDel)(void*); /* Destructor for aData */ |
| 230073 | }; |
| 230074 | |
| 230075 | |
| 230076 | /* carray_cursor is a subclass of sqlite3_vtab_cursor which will |
| 230077 | ** serve as the underlying representation of a cursor that scans |
| 230078 | ** over rows of the result |
| 230079 | */ |
| 230080 | typedef struct carray_cursor carray_cursor; |
| 230081 | struct carray_cursor { |
| 230082 | sqlite3_vtab_cursor base; /* Base class - must be first */ |
| 230083 | sqlite3_int64 iRowid; /* The rowid */ |
| 230084 | void *pPtr; /* Pointer to the array of values */ |
| 230085 | sqlite3_int64 iCnt; /* Number of integers in the array */ |
| 230086 | unsigned char eType; /* One of the CARRAY_type values */ |
| 230087 | }; |
| 230088 | |
| 230089 | /* |
| 230090 | ** The carrayConnect() method is invoked to create a new |
| 230091 | ** carray_vtab that describes the carray virtual table. |
| 230092 | ** |
| 230093 | ** Think of this routine as the constructor for carray_vtab objects. |
| 230094 | ** |
| 230095 | ** All this routine needs to do is: |
| 230096 | ** |
| 230097 | ** (1) Allocate the carray_vtab object and initialize all fields. |
| 230098 | ** |
| 230099 | ** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the |
| 230100 | ** result set of queries against carray will look like. |
| 230101 | */ |
| 230102 | static int carrayConnect( |
| 230103 | sqlite3 *db, |
| 230104 | void *pAux, |
| 230105 | int argc, const char *const*argv, |
| 230106 | sqlite3_vtab **ppVtab, |
| 230107 | char **pzErr |
| 230108 | ){ |
| 230109 | sqlite3_vtab *pNew; |
| 230110 | int rc; |
| 230111 | |
| 230112 | /* Column numbers */ |
| 230113 | #define CARRAY_COLUMN_VALUE 0 |
| 230114 | #define CARRAY_COLUMN_POINTER 1 |
| 230115 | #define CARRAY_COLUMN_COUNT 2 |
| 230116 | #define CARRAY_COLUMN_CTYPE 3 |
| 230117 | |
| 230118 | rc = sqlite3_declare_vtab(db, |
| 230119 | "CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)"); |
| 230120 | if( rc==SQLITE_OK ){ |
| 230121 | pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); |
| 230122 | if( pNew==0 ) return SQLITE_NOMEM; |
| 230123 | memset(pNew, 0, sizeof(*pNew)); |
| 230124 | } |
| 230125 | return rc; |
| 230126 | } |
| 230127 | |
| 230128 | /* |
| 230129 | ** This method is the destructor for carray_cursor objects. |
| 230130 | */ |
| 230131 | static int carrayDisconnect(sqlite3_vtab *pVtab){ |
| 230132 | sqlite3_free(pVtab); |
| 230133 | return SQLITE_OK; |
| 230134 | } |
| 230135 | |
| 230136 | /* |
| 230137 | ** Constructor for a new carray_cursor object. |
| 230138 | */ |
| 230139 | static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
| 230140 | carray_cursor *pCur; |
| 230141 | pCur = sqlite3_malloc( sizeof(*pCur) ); |
| 230142 | if( pCur==0 ) return SQLITE_NOMEM; |
| 230143 | memset(pCur, 0, sizeof(*pCur)); |
| 230144 | *ppCursor = &pCur->base; |
| 230145 | return SQLITE_OK; |
| 230146 | } |
| 230147 | |
| 230148 | /* |
| 230149 | ** Destructor for a carray_cursor. |
| 230150 | */ |
| 230151 | static int carrayClose(sqlite3_vtab_cursor *cur){ |
| 230152 | sqlite3_free(cur); |
| 230153 | return SQLITE_OK; |
| 230154 | } |
| 230155 | |
| 230156 | |
| 230157 | /* |
| 230158 | ** Advance a carray_cursor to its next row of output. |
| 230159 | */ |
| 230160 | static int carrayNext(sqlite3_vtab_cursor *cur){ |
| 230161 | carray_cursor *pCur = (carray_cursor*)cur; |
| 230162 | pCur->iRowid++; |
| 230163 | return SQLITE_OK; |
| 230164 | } |
| 230165 | |
| 230166 | /* |
| 230167 | ** Return values of columns for the row at which the carray_cursor |
| 230168 | ** is currently pointing. |
| 230169 | */ |
| 230170 | static int carrayColumn( |
| 230171 | sqlite3_vtab_cursor *cur, /* The cursor */ |
| 230172 | sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ |
| 230173 | int i /* Which column to return */ |
| 230174 | ){ |
| 230175 | carray_cursor *pCur = (carray_cursor*)cur; |
| 230176 | sqlite3_int64 x = 0; |
| 230177 | switch( i ){ |
| 230178 | case CARRAY_COLUMN_POINTER: return SQLITE_OK; |
| 230179 | case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break; |
| 230180 | case CARRAY_COLUMN_CTYPE: { |
| 230181 | sqlite3_result_text(ctx, azType[pCur->eType], -1, SQLITE_STATIC); |
| 230182 | return SQLITE_OK; |
| 230183 | } |
| 230184 | default: { |
| 230185 | switch( pCur->eType ){ |
| 230186 | case CARRAY_INT32: { |
| 230187 | int *p = (int*)pCur->pPtr; |
| 230188 | sqlite3_result_int(ctx, p[pCur->iRowid-1]); |
| 230189 | return SQLITE_OK; |
| 230190 | } |
| 230191 | case CARRAY_INT64: { |
| 230192 | sqlite3_int64 *p = (sqlite3_int64*)pCur->pPtr; |
| 230193 | sqlite3_result_int64(ctx, p[pCur->iRowid-1]); |
| 230194 | return SQLITE_OK; |
| 230195 | } |
| 230196 | case CARRAY_DOUBLE: { |
| 230197 | double *p = (double*)pCur->pPtr; |
| 230198 | sqlite3_result_double(ctx, p[pCur->iRowid-1]); |
| 230199 | return SQLITE_OK; |
| 230200 | } |
| 230201 | case CARRAY_TEXT: { |
| 230202 | const char **p = (const char**)pCur->pPtr; |
| 230203 | sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT); |
| 230204 | return SQLITE_OK; |
| 230205 | } |
| 230206 | default: { |
| 230207 | const struct iovec *p = (struct iovec*)pCur->pPtr; |
| 230208 | assert( pCur->eType==CARRAY_BLOB ); |
| 230209 | sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base, |
| 230210 | (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT); |
| 230211 | return SQLITE_OK; |
| 230212 | } |
| 230213 | } |
| 230214 | } |
| 230215 | } |
| 230216 | sqlite3_result_int64(ctx, x); |
| 230217 | return SQLITE_OK; |
| 230218 | } |
| 230219 | |
| 230220 | /* |
| 230221 | ** Return the rowid for the current row. In this implementation, the |
| 230222 | ** rowid is the same as the output value. |
| 230223 | */ |
| 230224 | static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 230225 | carray_cursor *pCur = (carray_cursor*)cur; |
| 230226 | *pRowid = pCur->iRowid; |
| 230227 | return SQLITE_OK; |
| 230228 | } |
| 230229 | |
| 230230 | /* |
| 230231 | ** Return TRUE if the cursor has been moved off of the last |
| 230232 | ** row of output. |
| 230233 | */ |
| 230234 | static int carrayEof(sqlite3_vtab_cursor *cur){ |
| 230235 | carray_cursor *pCur = (carray_cursor*)cur; |
| 230236 | return pCur->iRowid>pCur->iCnt; |
| 230237 | } |
| 230238 | |
| 230239 | /* |
| 230240 | ** This method is called to "rewind" the carray_cursor object back |
| 230241 | ** to the first row of output. |
| 230242 | */ |
| 230243 | static int carrayFilter( |
| 230244 | sqlite3_vtab_cursor *pVtabCursor, |
| 230245 | int idxNum, const char *idxStr, |
| 230246 | int argc, sqlite3_value **argv |
| 230247 | ){ |
| 230248 | carray_cursor *pCur = (carray_cursor *)pVtabCursor; |
| 230249 | pCur->pPtr = 0; |
| 230250 | pCur->iCnt = 0; |
| 230251 | switch( idxNum ){ |
| 230252 | case 1: { |
| 230253 | carray_bind *pBind = sqlite3_value_pointer(argv[0], "carray-bind"); |
| 230254 | if( pBind==0 ) break; |
| 230255 | pCur->pPtr = pBind->aData; |
| 230256 | pCur->iCnt = pBind->nData; |
| 230257 | pCur->eType = pBind->mFlags & 0x07; |
| 230258 | break; |
| 230259 | } |
| 230260 | case 2: |
| 230261 | case 3: { |
| 230262 | pCur->pPtr = sqlite3_value_pointer(argv[0], "carray"); |
| 230263 | pCur->iCnt = pCur->pPtr ? sqlite3_value_int64(argv[1]) : 0; |
| 230264 | if( idxNum<3 ){ |
| 230265 | pCur->eType = CARRAY_INT32; |
| 230266 | }else{ |
| 230267 | unsigned char i; |
| 230268 | const char *zType = (const char*)sqlite3_value_text(argv[2]); |
| 230269 | for(i=0; i<sizeof(azType)/sizeof(azType[0]); i++){ |
| 230270 | if( sqlite3_stricmp(zType, azType[i])==0 ) break; |
| 230271 | } |
| 230272 | if( i>=sizeof(azType)/sizeof(azType[0]) ){ |
| 230273 | pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf( |
| 230274 | "unknown datatype: %Q", zType); |
| 230275 | return SQLITE_ERROR; |
| 230276 | }else{ |
| 230277 | pCur->eType = i; |
| 230278 | } |
| 230279 | } |
| 230280 | break; |
| 230281 | } |
| 230282 | } |
| 230283 | pCur->iRowid = 1; |
| 230284 | return SQLITE_OK; |
| 230285 | } |
| 230286 | |
| 230287 | /* |
| 230288 | ** SQLite will invoke this method one or more times while planning a query |
| 230289 | ** that uses the carray virtual table. This routine needs to create |
| 230290 | ** a query plan for each invocation and compute an estimated cost for that |
| 230291 | ** plan. |
| 230292 | ** |
| 230293 | ** In this implementation idxNum is used to represent the |
| 230294 | ** query plan. idxStr is unused. |
| 230295 | ** |
| 230296 | ** idxNum is: |
| 230297 | ** |
| 230298 | ** 1 If only the pointer= constraint exists. In this case, the |
| 230299 | ** parameter must be bound using sqlite3_carray_bind(). |
| 230300 | ** |
| 230301 | ** 2 if the pointer= and count= constraints exist. |
| 230302 | ** |
| 230303 | ** 3 if the ctype= constraint also exists. |
| 230304 | ** |
| 230305 | ** idxNum is 0 otherwise and carray becomes an empty table. |
| 230306 | */ |
| 230307 | static int carrayBestIndex( |
| 230308 | sqlite3_vtab *tab, |
| 230309 | sqlite3_index_info *pIdxInfo |
| 230310 | ){ |
| 230311 | int i; /* Loop over constraints */ |
| 230312 | int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */ |
| 230313 | int cntIdx = -1; /* Index of the count= constraint, or -1 if none */ |
| 230314 | int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */ |
| 230315 | unsigned seen = 0; /* Bitmask of == constrainted columns */ |
| 230316 | |
| 230317 | const struct sqlite3_index_constraint *pConstraint; |
| 230318 | pConstraint = pIdxInfo->aConstraint; |
| 230319 | for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ |
| 230320 | if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; |
| 230321 | if( pConstraint->iColumn>=0 ) seen |= 1 << pConstraint->iColumn; |
| 230322 | if( pConstraint->usable==0 ) continue; |
| 230323 | switch( pConstraint->iColumn ){ |
| 230324 | case CARRAY_COLUMN_POINTER: |
| 230325 | ptrIdx = i; |
| 230326 | break; |
| 230327 | case CARRAY_COLUMN_COUNT: |
| 230328 | cntIdx = i; |
| 230329 | break; |
| 230330 | case CARRAY_COLUMN_CTYPE: |
| 230331 | ctypeIdx = i; |
| 230332 | break; |
| 230333 | } |
| 230334 | } |
| 230335 | if( ptrIdx>=0 ){ |
| 230336 | pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1; |
| 230337 | pIdxInfo->aConstraintUsage[ptrIdx].omit = 1; |
| 230338 | pIdxInfo->estimatedCost = (double)1; |
| 230339 | pIdxInfo->estimatedRows = 100; |
| 230340 | pIdxInfo->idxNum = 1; |
| 230341 | if( cntIdx>=0 ){ |
| 230342 | pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2; |
| 230343 | pIdxInfo->aConstraintUsage[cntIdx].omit = 1; |
| 230344 | pIdxInfo->idxNum = 2; |
| 230345 | if( ctypeIdx>=0 ){ |
| 230346 | pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3; |
| 230347 | pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1; |
| 230348 | pIdxInfo->idxNum = 3; |
| 230349 | }else if( seen & (1<<CARRAY_COLUMN_CTYPE) ){ |
| 230350 | /* In a three-argument carray(), we need to know the value of all |
| 230351 | ** three arguments */ |
| 230352 | return SQLITE_CONSTRAINT; |
| 230353 | } |
| 230354 | }else if( seen & (1<<CARRAY_COLUMN_COUNT) ){ |
| 230355 | /* In a two-argument carray(), we need to know the value of both |
| 230356 | ** arguments */ |
| 230357 | return SQLITE_CONSTRAINT; |
| 230358 | } |
| 230359 | }else{ |
| 230360 | pIdxInfo->estimatedCost = (double)2147483647; |
| 230361 | pIdxInfo->estimatedRows = 2147483647; |
| 230362 | pIdxInfo->idxNum = 0; |
| 230363 | } |
| 230364 | return SQLITE_OK; |
| 230365 | } |
| 230366 | |
| 230367 | /* |
| 230368 | ** This following structure defines all the methods for the |
| 230369 | ** carray virtual table. |
| 230370 | */ |
| 230371 | static sqlite3_module carrayModule = { |
| 230372 | 0, /* iVersion */ |
| 230373 | 0, /* xCreate */ |
| 230374 | carrayConnect, /* xConnect */ |
| 230375 | carrayBestIndex, /* xBestIndex */ |
| 230376 | carrayDisconnect, /* xDisconnect */ |
| 230377 | 0, /* xDestroy */ |
| 230378 | carrayOpen, /* xOpen - open a cursor */ |
| 230379 | carrayClose, /* xClose - close a cursor */ |
| 230380 | carrayFilter, /* xFilter - configure scan constraints */ |
| 230381 | carrayNext, /* xNext - advance a cursor */ |
| 230382 | carrayEof, /* xEof - check for end of scan */ |
| 230383 | carrayColumn, /* xColumn - read data */ |
| 230384 | carrayRowid, /* xRowid - read data */ |
| 230385 | 0, /* xUpdate */ |
| 230386 | 0, /* xBegin */ |
| 230387 | 0, /* xSync */ |
| 230388 | 0, /* xCommit */ |
| 230389 | 0, /* xRollback */ |
| 230390 | 0, /* xFindMethod */ |
| 230391 | 0, /* xRename */ |
| 230392 | 0, /* xSavepoint */ |
| 230393 | 0, /* xRelease */ |
| 230394 | 0, /* xRollbackTo */ |
| 230395 | 0, /* xShadow */ |
| 230396 | 0 /* xIntegrity */ |
| 230397 | }; |
| 230398 | |
| 230399 | /* |
| 230400 | ** Destructor for the carray_bind object |
| 230401 | */ |
| 230402 | static void carrayBindDel(void *pPtr){ |
| 230403 | carray_bind *p = (carray_bind*)pPtr; |
| 230404 | if( p->xDel!=SQLITE_STATIC ){ |
| 230405 | p->xDel(p->aData); |
| 230406 | } |
| 230407 | sqlite3_free(p); |
| 230408 | } |
| 230409 | |
| 230410 | /* |
| 230411 | ** Invoke this interface in order to bind to the single-argument |
| 230412 | ** version of CARRAY(). |
| 230413 | */ |
| 230414 | SQLITE_API int sqlite3_carray_bind( |
| 230415 | sqlite3_stmt *pStmt, |
| 230416 | int idx, |
| 230417 | void *aData, |
| 230418 | int nData, |
| 230419 | int mFlags, |
| 230420 | void (*xDestroy)(void*) |
| 230421 | ){ |
| 230422 | carray_bind *pNew = 0; |
| 230423 | int i; |
| 230424 | int rc = SQLITE_OK; |
| 230425 | |
| 230426 | /* Ensure that the mFlags value is acceptable. */ |
| 230427 | assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 ); |
| 230428 | assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 ); |
| 230429 | if( mFlags<CARRAY_INT32 || mFlags>CARRAY_BLOB ){ |
| 230430 | rc = SQLITE_ERROR; |
| 230431 | goto carray_bind_error; |
| 230432 | } |
| 230433 | |
| 230434 | pNew = sqlite3_malloc64(sizeof(*pNew)); |
| 230435 | if( pNew==0 ){ |
| 230436 | rc = SQLITE_NOMEM; |
| 230437 | goto carray_bind_error; |
| 230438 | } |
| 230439 | |
| 230440 | pNew->nData = nData; |
| 230441 | pNew->mFlags = mFlags; |
| 230442 | if( xDestroy==SQLITE_TRANSIENT ){ |
| 230443 | sqlite3_int64 sz = nData; |
| 230444 | switch( mFlags ){ |
| 230445 | case CARRAY_INT32: sz *= 4; break; |
| 230446 | case CARRAY_INT64: sz *= 8; break; |
| 230447 | case CARRAY_DOUBLE: sz *= 8; break; |
| 230448 | case CARRAY_TEXT: sz *= sizeof(char*); break; |
| 230449 | default: sz *= sizeof(struct iovec); break; |
| 230450 | } |
| 230451 | if( mFlags==CARRAY_TEXT ){ |
| 230452 | for(i=0; i<nData; i++){ |
| 230453 | const char *z = ((char**)aData)[i]; |
| 230454 | if( z ) sz += strlen(z) + 1; |
| 230455 | } |
| 230456 | }else if( mFlags==CARRAY_BLOB ){ |
| 230457 | for(i=0; i<nData; i++){ |
| 230458 | sz += ((struct iovec*)aData)[i].iov_len; |
| 230459 | } |
| 230460 | } |
| 230461 | |
| 230462 | pNew->aData = sqlite3_malloc64( sz ); |
| 230463 | if( pNew->aData==0 ){ |
| 230464 | rc = SQLITE_NOMEM; |
| 230465 | goto carray_bind_error; |
| 230466 | } |
| 230467 | |
| 230468 | if( mFlags==CARRAY_TEXT ){ |
| 230469 | char **az = (char**)pNew->aData; |
| 230470 | char *z = (char*)&az[nData]; |
| 230471 | for(i=0; i<nData; i++){ |
| 230472 | const char *zData = ((char**)aData)[i]; |
| 230473 | sqlite3_int64 n; |
| 230474 | if( zData==0 ){ |
| 230475 | az[i] = 0; |
| 230476 | continue; |
| 230477 | } |
| 230478 | az[i] = z; |
| 230479 | n = strlen(zData); |
| 230480 | memcpy(z, zData, n+1); |
| 230481 | z += n+1; |
| 230482 | } |
| 230483 | }else if( mFlags==CARRAY_BLOB ){ |
| 230484 | struct iovec *p = (struct iovec*)pNew->aData; |
| 230485 | unsigned char *z = (unsigned char*)&p[nData]; |
| 230486 | for(i=0; i<nData; i++){ |
| 230487 | size_t n = ((struct iovec*)aData)[i].iov_len; |
| 230488 | p[i].iov_len = n; |
| 230489 | p[i].iov_base = z; |
| 230490 | z += n; |
| 230491 | memcpy(p[i].iov_base, ((struct iovec*)aData)[i].iov_base, n); |
| 230492 | } |
| 230493 | }else{ |
| 230494 | memcpy(pNew->aData, aData, sz); |
| 230495 | } |
| 230496 | pNew->xDel = sqlite3_free; |
| 230497 | }else{ |
| 230498 | pNew->aData = aData; |
| 230499 | pNew->xDel = xDestroy; |
| 230500 | } |
| 230501 | return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); |
| 230502 | |
| 230503 | carray_bind_error: |
| 230504 | if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ |
| 230505 | xDestroy(aData); |
| 230506 | } |
| 230507 | sqlite3_free(pNew); |
| 230508 | return rc; |
| 230509 | } |
| 230510 | |
| 230511 | /* |
| 230512 | ** Invoke this routine to register the carray() function. |
| 230513 | */ |
| 230514 | SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3 *db){ |
| 230515 | return sqlite3VtabCreateModule(db, "carray", &carrayModule, 0, 0); |
| 230516 | } |
| 230517 | |
| 230518 | #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) */ |
| 230519 | |
| 230520 | /************** End of carray.c **********************************************/ |
| 230521 | /************** Begin file sqlite3session.c **********************************/ |
| 230522 | |
| 230523 | #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) |
| 230524 | /* #include "sqlite3session.h" */ |
| 230525 | /* #include <assert.h> */ |
| @@ -231561,10 +233333,23 @@ | |
| 233333 | assert( (a - p->aRecord)==p->nRecord ); |
| 233334 | } |
| 233335 | |
| 233336 | return rc; |
| 233337 | } |
| 233338 | |
| 233339 | static int sessionPrepare( |
| 233340 | sqlite3 *db, |
| 233341 | sqlite3_stmt **pp, |
| 233342 | char **pzErrmsg, |
| 233343 | const char *zSql |
| 233344 | ){ |
| 233345 | int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0); |
| 233346 | if( pzErrmsg && rc!=SQLITE_OK ){ |
| 233347 | *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); |
| 233348 | } |
| 233349 | return rc; |
| 233350 | } |
| 233351 | |
| 233352 | /* |
| 233353 | ** Formulate and prepare a SELECT statement to retrieve a row from table |
| 233354 | ** zTab in database zDb based on its primary key. i.e. |
| 233355 | ** |
| @@ -231583,16 +233368,16 @@ | |
| 233368 | const char *zTab, /* Table name */ |
| 233369 | int bRowid, |
| 233370 | int nCol, /* Number of columns in table */ |
| 233371 | const char **azCol, /* Names of table columns */ |
| 233372 | u8 *abPK, /* PRIMARY KEY array */ |
| 233373 | sqlite3_stmt **ppStmt, /* OUT: Prepared SELECT statement */ |
| 233374 | char **pzErrmsg /* OUT: Error message */ |
| 233375 | ){ |
| 233376 | int rc = SQLITE_OK; |
| 233377 | char *zSql = 0; |
| 233378 | const char *zSep = ""; |
| 233379 | int i; |
| 233380 | |
| 233381 | SessionBuffer cols = {0, 0, 0}; |
| 233382 | SessionBuffer nooptest = {0, 0, 0}; |
| 233383 | SessionBuffer pkfield = {0, 0, 0}; |
| @@ -231668,11 +233453,11 @@ | |
| 233453 | nSql = buf.nBuf; |
| 233454 | } |
| 233455 | #endif |
| 233456 | |
| 233457 | if( rc==SQLITE_OK ){ |
| 233458 | rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql); |
| 233459 | } |
| 233460 | sqlite3_free(zSql); |
| 233461 | sqlite3_free(nooptest.aBuf); |
| 233462 | sqlite3_free(pkfield.aBuf); |
| 233463 | sqlite3_free(pkvar.aBuf); |
| @@ -231832,11 +233617,11 @@ | |
| 233617 | sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); |
| 233618 | |
| 233619 | /* Build and compile a statement to execute: */ |
| 233620 | if( rc==SQLITE_OK ){ |
| 233621 | rc = sessionSelectStmt(db, 0, pSession->zDb, |
| 233622 | zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0 |
| 233623 | ); |
| 233624 | } |
| 233625 | |
| 233626 | nNoop = buf.nBuf; |
| 233627 | for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){ |
| @@ -233041,10 +234826,11 @@ | |
| 234826 | SessionBuffer rebase; /* Rebase information (if any) here */ |
| 234827 | u8 bRebaseStarted; /* If table header is already in rebase */ |
| 234828 | u8 bRebase; /* True to collect rebase information */ |
| 234829 | u8 bIgnoreNoop; /* True to ignore no-op conflicts */ |
| 234830 | int bRowid; |
| 234831 | char *zErr; /* Error message, if any */ |
| 234832 | }; |
| 234833 | |
| 234834 | /* Number of prepared UPDATE statements to cache. */ |
| 234835 | #define SESSION_UPDATE_CACHE_SZ 12 |
| 234836 | |
| @@ -233266,11 +235052,11 @@ | |
| 235052 | } |
| 235053 | sessionAppendStr(&buf, ")", &rc); |
| 235054 | } |
| 235055 | |
| 235056 | if( rc==SQLITE_OK ){ |
| 235057 | rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf); |
| 235058 | } |
| 235059 | sqlite3_free(buf.aBuf); |
| 235060 | |
| 235061 | return rc; |
| 235062 | } |
| @@ -233293,11 +235079,11 @@ | |
| 235079 | const char *zTab, /* Table name */ |
| 235080 | SessionApplyCtx *p /* Session changeset-apply context */ |
| 235081 | ){ |
| 235082 | /* TODO */ |
| 235083 | return sessionSelectStmt(db, p->bIgnoreNoop, |
| 235084 | "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr |
| 235085 | ); |
| 235086 | } |
| 235087 | |
| 235088 | /* |
| 235089 | ** Formulate and prepare an INSERT statement to add a record to table zTab. |
| @@ -233330,37 +235116,33 @@ | |
| 235116 | sessionAppendStr(&buf, ", ?", &rc); |
| 235117 | } |
| 235118 | sessionAppendStr(&buf, ")", &rc); |
| 235119 | |
| 235120 | if( rc==SQLITE_OK ){ |
| 235121 | rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf); |
| 235122 | } |
| 235123 | sqlite3_free(buf.aBuf); |
| 235124 | return rc; |
| 235125 | } |
| 235126 | |
| 235127 | /* |
| 235128 | ** Prepare statements for applying changes to the sqlite_stat1 table. |
| 235129 | ** These are similar to those created by sessionSelectRow(), |
| 235130 | ** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for |
| 235131 | ** other tables. |
| 235132 | */ |
| 235133 | static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ |
| 235134 | int rc = sessionSelectRow(db, "sqlite_stat1", p); |
| 235135 | if( rc==SQLITE_OK ){ |
| 235136 | rc = sessionPrepare(db, &p->pInsert, 0, |
| 235137 | "INSERT INTO main.sqlite_stat1 VALUES(?1, " |
| 235138 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " |
| 235139 | "?3)" |
| 235140 | ); |
| 235141 | } |
| 235142 | if( rc==SQLITE_OK ){ |
| 235143 | rc = sessionPrepare(db, &p->pDelete, 0, |
| 235144 | "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " |
| 235145 | "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " |
| 235146 | "AND (?4 OR stat IS ?3)" |
| 235147 | ); |
| 235148 | } |
| @@ -233580,11 +235362,11 @@ | |
| 235362 | sqlite3_changeset_iter *pIter, /* Changeset iterator */ |
| 235363 | int(*xConflict)(void *, int, sqlite3_changeset_iter*), |
| 235364 | void *pCtx, /* First argument for conflict handler */ |
| 235365 | int *pbReplace /* OUT: Set to true if PK row is found */ |
| 235366 | ){ |
| 235367 | int res = SQLITE_CHANGESET_OMIT;/* Value returned by conflict handler */ |
| 235368 | int rc; |
| 235369 | int nCol; |
| 235370 | int op; |
| 235371 | const char *zDummy; |
| 235372 | |
| @@ -233601,15 +235383,13 @@ | |
| 235383 | rc = SQLITE_OK; |
| 235384 | } |
| 235385 | |
| 235386 | if( rc==SQLITE_ROW ){ |
| 235387 | /* There exists another row with the new.* primary key. */ |
| 235388 | if( 0==p->bIgnoreNoop |
| 235389 | || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) |
| 235390 | ){ |
| 235391 | pIter->pConflict = p->pSelect; |
| 235392 | res = xConflict(pCtx, eType, pIter); |
| 235393 | pIter->pConflict = 0; |
| 235394 | } |
| 235395 | rc = sqlite3_reset(p->pSelect); |
| @@ -233619,11 +235399,13 @@ | |
| 235399 | ** to the SessionApplyCtx.constraints buffer. */ |
| 235400 | u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; |
| 235401 | int nBlob = pIter->in.iNext - pIter->in.iCurrent; |
| 235402 | sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); |
| 235403 | return SQLITE_OK; |
| 235404 | }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE |
| 235405 | || eType==SQLITE_CHANGESET_CONFLICT |
| 235406 | ){ |
| 235407 | /* No other row with the new.* primary key. */ |
| 235408 | res = xConflict(pCtx, eType+1, pIter); |
| 235409 | if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; |
| 235410 | } |
| 235411 | } |
| @@ -233717,11 +235499,11 @@ | |
| 235499 | } |
| 235500 | if( rc!=SQLITE_OK ) return rc; |
| 235501 | |
| 235502 | sqlite3_step(p->pDelete); |
| 235503 | rc = sqlite3_reset(p->pDelete); |
| 235504 | if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ |
| 235505 | rc = sessionConflictHandler( |
| 235506 | SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry |
| 235507 | ); |
| 235508 | }else if( (rc&0xff)==SQLITE_CONSTRAINT ){ |
| 235509 | rc = sessionConflictHandler( |
| @@ -234122,10 +235904,11 @@ | |
| 235904 | } |
| 235905 | } |
| 235906 | |
| 235907 | assert( sApply.bRebase || sApply.rebase.nBuf==0 ); |
| 235908 | if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){ |
| 235909 | assert( ppRebase!=0 && pnRebase!=0 ); |
| 235910 | *ppRebase = (void*)sApply.rebase.aBuf; |
| 235911 | *pnRebase = sApply.rebase.nBuf; |
| 235912 | sApply.rebase.aBuf = 0; |
| 235913 | } |
| 235914 | sessionUpdateFree(&sApply); |
| @@ -234139,10 +235922,15 @@ | |
| 235922 | if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){ |
| 235923 | assert( db->flags & SQLITE_FkNoAction ); |
| 235924 | db->flags &= ~((u64)SQLITE_FkNoAction); |
| 235925 | db->aDb[0].pSchema->schema_cookie -= 32; |
| 235926 | } |
| 235927 | |
| 235928 | assert( rc!=SQLITE_OK || sApply.zErr==0 ); |
| 235929 | sqlite3_set_errmsg(db, rc, sApply.zErr); |
| 235930 | sqlite3_free(sApply.zErr); |
| 235931 | |
| 235932 | sqlite3_mutex_leave(sqlite3_db_mutex(db)); |
| 235933 | return rc; |
| 235934 | } |
| 235935 | |
| 235936 | /* |
| @@ -236348,25 +238136,18 @@ | |
| 238136 | ** Constants for the largest and smallest possible 64-bit signed integers. |
| 238137 | */ |
| 238138 | # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) |
| 238139 | # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) |
| 238140 | |
| 238141 | /* |
| 238142 | ** This macro is used in a single assert() within fts5 to check that an |
| 238143 | ** allocation is aligned to an 8-byte boundary. But it is a complicated |
| 238144 | ** macro to get right for multiple platforms without generating warnings. |
| 238145 | ** So instead of reproducing the entire definition from sqliteInt.h, we |
| 238146 | ** just do without this assert() for the rare non-amalgamation builds. |
| 238147 | */ |
| 238148 | #define EIGHT_BYTE_ALIGNMENT(x) 1 |
| 238149 | |
| 238150 | /* |
| 238151 | ** Macros needed to provide flexible arrays in a portable way |
| 238152 | */ |
| 238153 | #ifndef offsetof |
| @@ -237110,11 +238891,11 @@ | |
| 238891 | ** ){ |
| 238892 | ** // The document with rowid iRowid matches the expression! |
| 238893 | ** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); |
| 238894 | ** } |
| 238895 | */ |
| 238896 | static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc); |
| 238897 | static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); |
| 238898 | static int sqlite3Fts5ExprEof(Fts5Expr*); |
| 238899 | static i64 sqlite3Fts5ExprRowid(Fts5Expr*); |
| 238900 | |
| 238901 | static void sqlite3Fts5ExprFree(Fts5Expr*); |
| @@ -242679,11 +244460,17 @@ | |
| 244460 | ** equal to iFirst. |
| 244461 | ** |
| 244462 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It |
| 244463 | ** is not considered an error if the query does not match any documents. |
| 244464 | */ |
| 244465 | static int sqlite3Fts5ExprFirst( |
| 244466 | Fts5Expr *p, |
| 244467 | Fts5Index *pIdx, |
| 244468 | i64 iFirst, |
| 244469 | i64 iLast, |
| 244470 | int bDesc |
| 244471 | ){ |
| 244472 | Fts5ExprNode *pRoot = p->pRoot; |
| 244473 | int rc; /* Return code */ |
| 244474 | |
| 244475 | p->pIndex = pIdx; |
| 244476 | p->bDesc = bDesc; |
| @@ -242700,10 +244487,13 @@ | |
| 244487 | |
| 244488 | /* If the iterator is not at a real match, skip forward until it is. */ |
| 244489 | while( pRoot->bNomatch && rc==SQLITE_OK ){ |
| 244490 | assert( pRoot->bEof==0 ); |
| 244491 | rc = fts5ExprNodeNext(p, pRoot, 0, 0); |
| 244492 | } |
| 244493 | if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ |
| 244494 | pRoot->bEof = 1; |
| 244495 | } |
| 244496 | return rc; |
| 244497 | } |
| 244498 | |
| 244499 | /* |
| @@ -245876,13 +247666,13 @@ | |
| 247666 | ** backing store corruption. */ |
| 247667 | if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid); |
| 247668 | |
| 247669 | if( rc==SQLITE_OK ){ |
| 247670 | u8 *aOut = 0; /* Read blob data into this buffer */ |
| 247671 | i64 nByte = sqlite3_blob_bytes(p->pReader); |
| 247672 | i64 szData = (sizeof(Fts5Data) + 7) & ~7; |
| 247673 | i64 nAlloc = szData + nByte + FTS5_DATA_PADDING; |
| 247674 | pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); |
| 247675 | if( pRet ){ |
| 247676 | pRet->nn = nByte; |
| 247677 | aOut = pRet->p = (u8*)pRet + szData; |
| 247678 | }else{ |
| @@ -251821,15 +253611,18 @@ | |
| 253611 | ** function populates it with the initial structure objects for each index, |
| 253612 | ** and the initial version of the "averages" record (a zero-byte blob). |
| 253613 | */ |
| 253614 | static int sqlite3Fts5IndexReinit(Fts5Index *p){ |
| 253615 | Fts5Structure *pTmp; |
| 253616 | union { |
| 253617 | Fts5Structure sFts; |
| 253618 | u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; |
| 253619 | } uFts; |
| 253620 | fts5StructureInvalidate(p); |
| 253621 | fts5IndexDiscardData(p); |
| 253622 | pTmp = &uFts.sFts; |
| 253623 | memset(uFts.tmpSpace, 0, sizeof(uFts.tmpSpace)); |
| 253624 | if( p->pConfig->bContentlessDelete ){ |
| 253625 | pTmp->nOriginCntr = 1; |
| 253626 | } |
| 253627 | fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); |
| 253628 | fts5StructureWrite(p, pTmp); |
| @@ -255045,10 +256838,21 @@ | |
| 256838 | { |
| 256839 | pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; |
| 256840 | } |
| 256841 | #endif |
| 256842 | } |
| 256843 | |
| 256844 | static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ |
| 256845 | #if SQLITE_VERSION_NUMBER>=3008002 |
| 256846 | #ifndef SQLITE_CORE |
| 256847 | if( sqlite3_libversion_number()>=3008002 ) |
| 256848 | #endif |
| 256849 | { |
| 256850 | pIdxInfo->estimatedRows = nRow; |
| 256851 | } |
| 256852 | #endif |
| 256853 | } |
| 256854 | |
| 256855 | static int fts5UsePatternMatch( |
| 256856 | Fts5Config *pConfig, |
| 256857 | struct sqlite3_index_constraint *p |
| 256858 | ){ |
| @@ -255181,11 +256985,11 @@ | |
| 256985 | bSeenRank = 1; |
| 256986 | }else{ |
| 256987 | nSeenMatch++; |
| 256988 | idxStr[iIdxStr++] = 'M'; |
| 256989 | sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); |
| 256990 | iIdxStr += (int)strlen(&idxStr[iIdxStr]); |
| 256991 | assert( idxStr[iIdxStr]=='\0' ); |
| 256992 | } |
| 256993 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 256994 | pInfo->aConstraintUsage[i].omit = 1; |
| 256995 | } |
| @@ -255200,10 +257004,11 @@ | |
| 257004 | nSeenMatch++; |
| 257005 | }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ |
| 257006 | idxStr[iIdxStr++] = '='; |
| 257007 | bSeenEq = 1; |
| 257008 | pInfo->aConstraintUsage[i].argvIndex = ++iCons; |
| 257009 | pInfo->aConstraintUsage[i].omit = 1; |
| 257010 | } |
| 257011 | } |
| 257012 | } |
| 257013 | |
| 257014 | if( bSeenEq==0 ){ |
| @@ -255247,21 +257052,25 @@ | |
| 257052 | } |
| 257053 | } |
| 257054 | |
| 257055 | /* Calculate the estimated cost based on the flags set in idxFlags. */ |
| 257056 | if( bSeenEq ){ |
| 257057 | pInfo->estimatedCost = nSeenMatch ? 1000.0 : 25.0; |
| 257058 | fts5SetUniqueFlag(pInfo); |
| 257059 | fts5SetEstimatedRows(pInfo, 1); |
| 257060 | }else{ |
| 257061 | if( bSeenLt && bSeenGt ){ |
| 257062 | pInfo->estimatedCost = nSeenMatch ? 5000.0 : 750000.0; |
| 257063 | }else if( bSeenLt || bSeenGt ){ |
| 257064 | pInfo->estimatedCost = nSeenMatch ? 7500.0 : 2250000.0; |
| 257065 | }else{ |
| 257066 | pInfo->estimatedCost = nSeenMatch ? 10000.0 : 3000000.0; |
| 257067 | } |
| 257068 | for(i=1; i<nSeenMatch; i++){ |
| 257069 | pInfo->estimatedCost *= 0.4; |
| 257070 | } |
| 257071 | fts5SetEstimatedRows(pInfo, (i64)(pInfo->estimatedCost / 4.0)); |
| 257072 | } |
| 257073 | |
| 257074 | pInfo->idxNum = idxFlags; |
| 257075 | return SQLITE_OK; |
| 257076 | } |
| @@ -255456,11 +257265,13 @@ | |
| 257265 | if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){ |
| 257266 | Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); |
| 257267 | int bDesc = pCsr->bDesc; |
| 257268 | i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); |
| 257269 | |
| 257270 | rc = sqlite3Fts5ExprFirst( |
| 257271 | pCsr->pExpr, pTab->p.pIndex, iRowid, pCsr->iLastRowid, bDesc |
| 257272 | ); |
| 257273 | if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ |
| 257274 | *pbSkip = 1; |
| 257275 | } |
| 257276 | |
| 257277 | CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK); |
| @@ -255628,11 +257439,13 @@ | |
| 257439 | } |
| 257440 | |
| 257441 | static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ |
| 257442 | int rc; |
| 257443 | Fts5Expr *pExpr = pCsr->pExpr; |
| 257444 | rc = sqlite3Fts5ExprFirst( |
| 257445 | pExpr, pTab->p.pIndex, pCsr->iFirstRowid, pCsr->iLastRowid, bDesc |
| 257446 | ); |
| 257447 | if( sqlite3Fts5ExprEof(pExpr) ){ |
| 257448 | CsrFlagSet(pCsr, FTS5CSR_EOF); |
| 257449 | } |
| 257450 | fts5CsrNewrow(pCsr); |
| 257451 | return rc; |
| @@ -258113,11 +259926,11 @@ | |
| 259926 | int nArg, /* Number of args */ |
| 259927 | sqlite3_value **apUnused /* Function arguments */ |
| 259928 | ){ |
| 259929 | assert( nArg==0 ); |
| 259930 | UNUSED_PARAM2(nArg, apUnused); |
| 259931 | sqlite3_result_text(pCtx, "fts5: 2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70", -1, SQLITE_TRANSIENT); |
| 259932 | } |
| 259933 | |
| 259934 | /* |
| 259935 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 259936 | ** |
| @@ -258136,13 +259949,13 @@ | |
| 259949 | sqlite3_context *pCtx, /* Function call context */ |
| 259950 | int nArg, /* Number of args */ |
| 259951 | sqlite3_value **apArg /* Function arguments */ |
| 259952 | ){ |
| 259953 | const char *zLocale = 0; |
| 259954 | i64 nLocale = 0; |
| 259955 | const char *zText = 0; |
| 259956 | i64 nText = 0; |
| 259957 | |
| 259958 | assert( nArg==2 ); |
| 259959 | UNUSED_PARAM(nArg); |
| 259960 | |
| 259961 | zLocale = (const char*)sqlite3_value_text(apArg[0]); |
| @@ -258155,14 +259968,14 @@ | |
| 259968 | sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT); |
| 259969 | }else{ |
| 259970 | Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); |
| 259971 | u8 *pBlob = 0; |
| 259972 | u8 *pCsr = 0; |
| 259973 | i64 nBlob = 0; |
| 259974 | |
| 259975 | nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; |
| 259976 | pBlob = (u8*)sqlite3_malloc64(nBlob); |
| 259977 | if( pBlob==0 ){ |
| 259978 | sqlite3_result_error_nomem(pCtx); |
| 259979 | return; |
| 259980 | } |
| 259981 | |
| 259982 |
+178
-42
| --- extsrc/sqlite3.h | ||
| +++ extsrc/sqlite3.h | ||
| @@ -146,11 +146,14 @@ | ||
| 146 | 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | 148 | */ |
| 149 | 149 | #define SQLITE_VERSION "3.51.0" |
| 150 | 150 | #define SQLITE_VERSION_NUMBER 3051000 |
| 151 | -#define SQLITE_SOURCE_ID "2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839" | |
| 151 | +#define SQLITE_SOURCE_ID "2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70" | |
| 152 | +#define SQLITE_SCM_BRANCH "trunk" | |
| 153 | +#define SQLITE_SCM_TAGS "" | |
| 154 | +#define SQLITE_SCM_DATETIME "2025-10-15T10:52:45.276Z" | |
| 152 | 155 | |
| 153 | 156 | /* |
| 154 | 157 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | 158 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | 159 | ** |
| @@ -495,10 +498,13 @@ | ||
| 495 | 498 | ** [sqlite3_extended_errcode()]. |
| 496 | 499 | */ |
| 497 | 500 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 498 | 501 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 499 | 502 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 503 | +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) | |
| 504 | +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) | |
| 505 | +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) | |
| 500 | 506 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 501 | 507 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 502 | 508 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 503 | 509 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 504 | 510 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -529,10 +535,12 @@ | ||
| 529 | 535 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 530 | 536 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 531 | 537 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 532 | 538 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 533 | 539 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 540 | +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) | |
| 541 | +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) | |
| 534 | 542 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 535 | 543 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 536 | 544 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 537 | 545 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 538 | 546 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2333,21 +2341,24 @@ | ||
| 2333 | 2341 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2334 | 2342 | ** databases.)^ </dd> |
| 2335 | 2343 | ** |
| 2336 | 2344 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2337 | 2345 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2338 | -** <dd> ^This option is used to enable or disable the | |
| 2339 | -** [fts3_tokenizer()] function which is part of the | |
| 2340 | -** [FTS3] full-text search engine extension. | |
| 2341 | -** There must be two additional arguments. | |
| 2342 | -** The first argument is an integer which is 0 to disable fts3_tokenizer() or | |
| 2343 | -** positive to enable fts3_tokenizer() or negative to leave the setting | |
| 2344 | -** unchanged. | |
| 2345 | -** The second parameter is a pointer to an integer into which | |
| 2346 | -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled | |
| 2347 | -** following this call. The second parameter may be a NULL pointer, in | |
| 2348 | -** which case the new setting is not reported back. </dd> | |
| 2346 | +** <dd> ^This option is used to enable or disable using the | |
| 2347 | +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine | |
| 2348 | +** extension - without using bound parameters as the parameters. Doing so | |
| 2349 | +** is disabled by default. There must be two additional arguments. The first | |
| 2350 | +** argument is an integer. If it is passed 0, then using fts3_tokenizer() | |
| 2351 | +** without bound parameters is disabled. If it is passed a positive value, | |
| 2352 | +** then calling fts3_tokenizer without bound parameters is enabled. If it | |
| 2353 | +** is passed a negative value, this setting is not modified - this can be | |
| 2354 | +** used to query for the current setting. The second parameter is a pointer | |
| 2355 | +** to an integer into which is written 0 or 1 to indicate the current value | |
| 2356 | +** of this setting (after it is modified, if applicable). The second | |
| 2357 | +** parameter may be a NULL pointer, in which case the value of the setting | |
| 2358 | +** is not reported back. Refer to [FTS3] documentation for further details. | |
| 2359 | +** </dd> | |
| 2349 | 2360 | ** |
| 2350 | 2361 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2351 | 2362 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2352 | 2363 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2353 | 2364 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4193,10 +4204,38 @@ | ||
| 4193 | 4204 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4194 | 4205 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4195 | 4206 | SQLITE_API const char *sqlite3_errstr(int); |
| 4196 | 4207 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4197 | 4208 | |
| 4209 | +/* | |
| 4210 | +** CAPI3REF: Set Error Codes And Message | |
| 4211 | +** METHOD: sqlite3 | |
| 4212 | +** | |
| 4213 | +** Set the error code of the database handle passed as the first argument | |
| 4214 | +** to errcode, and the error message to a copy of nul-terminated string | |
| 4215 | +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to | |
| 4216 | +** the default message associated with the supplied error code. Subsequent | |
| 4217 | +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will | |
| 4218 | +** return the values set by this routine in place of what was previously | |
| 4219 | +** set by SQLite itself. | |
| 4220 | +** | |
| 4221 | +** This function returns SQLITE_OK if the error code and error message are | |
| 4222 | +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if | |
| 4223 | +** the database handle is NULL or invalid. | |
| 4224 | +** | |
| 4225 | +** The error code and message set by this routine remains in effect until | |
| 4226 | +** they are changed, either by another call to this routine or until they are | |
| 4227 | +** changed to by SQLite itself to reflect the result of some subsquent | |
| 4228 | +** API call. | |
| 4229 | +** | |
| 4230 | +** This function is intended for use by SQLite extensions or wrappers. The | |
| 4231 | +** idea is that an extension or wrapper can use this routine to set error | |
| 4232 | +** messages and error codes and thus behave more like a core SQLite | |
| 4233 | +** feature from the point of view of an application. | |
| 4234 | +*/ | |
| 4235 | +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); | |
| 4236 | + | |
| 4198 | 4237 | /* |
| 4199 | 4238 | ** CAPI3REF: Prepared Statement Object |
| 4200 | 4239 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4201 | 4240 | ** |
| 4202 | 4241 | ** An instance of this object represents a single SQL statement that |
| @@ -6203,10 +6242,11 @@ | ||
| 6203 | 6242 | ** to be attached to [database connection] D using name N. Subsequent |
| 6204 | 6243 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6205 | 6244 | ** or a NULL pointer if there were no prior calls to |
| 6206 | 6245 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6207 | 6246 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6247 | +** It returns 0 on success and SQLITE_NOMEM on allocation failure. | |
| 6208 | 6248 | ** |
| 6209 | 6249 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6210 | 6250 | ** argument P on the first of the following occurrences: |
| 6211 | 6251 | ** <ul> |
| 6212 | 6252 | ** <li> An out-of-memory error occurs during the call to |
| @@ -8878,14 +8918,23 @@ | ||
| 8878 | 8918 | ** the resetFlg is true, then the highest instantaneous value is |
| 8879 | 8919 | ** reset back down to the current value. |
| 8880 | 8920 | ** |
| 8881 | 8921 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 8882 | 8922 | ** non-zero [error code] on failure. |
| 8923 | +** | |
| 8924 | +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same | |
| 8925 | +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H | |
| 8926 | +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead | |
| 8927 | +** of pointers to 32-bit integers, which allows larger status values | |
| 8928 | +** to be returned. If a status value exceeds 2,147,483,647 then | |
| 8929 | +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() | |
| 8930 | +** will return the full value. | |
| 8883 | 8931 | ** |
| 8884 | 8932 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 8885 | 8933 | */ |
| 8886 | 8934 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 8935 | +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); | |
| 8887 | 8936 | |
| 8888 | 8937 | /* |
| 8889 | 8938 | ** CAPI3REF: Status Parameters for database connections |
| 8890 | 8939 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 8891 | 8940 | ** |
| @@ -8978,10 +9027,14 @@ | ||
| 8978 | 9027 | ** database file in rollback mode databases. Any pages written as part of |
| 8979 | 9028 | ** transaction rollback or database recovery operations are not included. |
| 8980 | 9029 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 8981 | 9030 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 8982 | 9031 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 9032 | +** <p> | |
| 9033 | +** ^(There is overlap between the quantities measured by this parameter | |
| 9034 | +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. | |
| 9035 | +** Resetting one will reduce the other.)^ | |
| 8983 | 9036 | ** </dd> |
| 8984 | 9037 | ** |
| 8985 | 9038 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 8986 | 9039 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 8987 | 9040 | ** been written to disk in the middle of a transaction due to the page |
| @@ -8993,10 +9046,22 @@ | ||
| 8993 | 9046 | ** |
| 8994 | 9047 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 8995 | 9048 | ** <dd>This parameter returns zero for the current value if and only if |
| 8996 | 9049 | ** all foreign key constraints (deferred or immediate) have been |
| 8997 | 9050 | ** resolved.)^ ^The highwater mark is always 0. |
| 9051 | +** | |
| 9052 | +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> | |
| 9053 | +** <dd>^(This parameter returns the number of bytes written to temporary | |
| 9054 | +** files on disk that could have been kept in memory had sufficient memory | |
| 9055 | +** been available. This value includes writes to intermediate tables that | |
| 9056 | +** are part of complex queries, external sorts that spill to disk, and | |
| 9057 | +** writes to TEMP tables.)^ | |
| 9058 | +** ^The highwater mark is always 0. | |
| 9059 | +** <p> | |
| 9060 | +** ^(There is overlap between the quantities measured by this parameter | |
| 9061 | +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. | |
| 9062 | +** Resetting one will reduce the other.)^ | |
| 8998 | 9063 | ** </dd> |
| 8999 | 9064 | ** </dl> |
| 9000 | 9065 | */ |
| 9001 | 9066 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9002 | 9067 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9009,11 +9074,12 @@ | ||
| 9009 | 9074 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9010 | 9075 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9011 | 9076 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9012 | 9077 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9013 | 9078 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9014 | -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ | |
| 9079 | +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 | |
| 9080 | +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ | |
| 9015 | 9081 | |
| 9016 | 9082 | |
| 9017 | 9083 | /* |
| 9018 | 9084 | ** CAPI3REF: Prepared Statement Status |
| 9019 | 9085 | ** METHOD: sqlite3_stmt |
| @@ -9774,25 +9840,38 @@ | ||
| 9774 | 9840 | ** ^The third parameter is the name of the database that was written to - |
| 9775 | 9841 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 9776 | 9842 | ** is the number of pages currently in the write-ahead log file, |
| 9777 | 9843 | ** including those that were just committed. |
| 9778 | 9844 | ** |
| 9779 | -** The callback function should normally return [SQLITE_OK]. ^If an error | |
| 9845 | +** ^The callback function should normally return [SQLITE_OK]. ^If an error | |
| 9780 | 9846 | ** code is returned, that error will propagate back up through the |
| 9781 | 9847 | ** SQLite code base to cause the statement that provoked the callback |
| 9782 | 9848 | ** to report an error, though the commit will have still occurred. If the |
| 9783 | 9849 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 9784 | 9850 | ** that does not correspond to any valid SQLite error code, the results |
| 9785 | 9851 | ** are undefined. |
| 9786 | 9852 | ** |
| 9787 | -** A single database handle may have at most a single write-ahead log callback | |
| 9788 | -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any | |
| 9789 | -** previously registered write-ahead log callback. ^The return value is | |
| 9790 | -** a copy of the third parameter from the previous call, if any, or 0. | |
| 9791 | -** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the | |
| 9792 | -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will | |
| 9793 | -** overwrite any prior [sqlite3_wal_hook()] settings. | |
| 9853 | +** ^A single database handle may have at most a single write-ahead log | |
| 9854 | +** callback registered at one time. ^Calling [sqlite3_wal_hook()] | |
| 9855 | +** replaces the default behavior or previously registered write-ahead | |
| 9856 | +** log callback. | |
| 9857 | +** | |
| 9858 | +** ^The return value is a copy of the third parameter from the | |
| 9859 | +** previous call, if any, or 0. | |
| 9860 | +** | |
| 9861 | +** ^The [sqlite3_wal_autocheckpoint()] interface and the | |
| 9862 | +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and | |
| 9863 | +** will overwrite any prior [sqlite3_wal_hook()] settings. | |
| 9864 | +** | |
| 9865 | +** ^If a write-ahead log callback is set using this function then | |
| 9866 | +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] | |
| 9867 | +** should be invoked periodically to keep the write-ahead log file | |
| 9868 | +** from growing without bound. | |
| 9869 | +** | |
| 9870 | +** ^Passing a NULL pointer for the callback disables automatic | |
| 9871 | +** checkpointing entirely. To re-enable the default behavior, call | |
| 9872 | +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. | |
| 9794 | 9873 | */ |
| 9795 | 9874 | SQLITE_API void *sqlite3_wal_hook( |
| 9796 | 9875 | sqlite3*, |
| 9797 | 9876 | int(*)(void *,sqlite3*,const char*,int), |
| 9798 | 9877 | void* |
| @@ -9805,11 +9884,11 @@ | ||
| 9805 | 9884 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 9806 | 9885 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 9807 | 9886 | ** to automatically [checkpoint] |
| 9808 | 9887 | ** after committing a transaction if there are N or |
| 9809 | 9888 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 9810 | -** a negative value as the nFrame parameter disables automatic | |
| 9889 | +** a negative value as the N parameter disables automatic | |
| 9811 | 9890 | ** checkpoints entirely. |
| 9812 | 9891 | ** |
| 9813 | 9892 | ** ^The callback registered by this function replaces any existing callback |
| 9814 | 9893 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 9815 | 9894 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -9821,13 +9900,14 @@ | ||
| 9821 | 9900 | ** ^Checkpoints initiated by this mechanism are |
| 9822 | 9901 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 9823 | 9902 | ** |
| 9824 | 9903 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 9825 | 9904 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 9826 | -** pages. The use of this interface | |
| 9827 | -** is only necessary if the default setting is found to be suboptimal | |
| 9828 | -** for a particular application. | |
| 9905 | +** pages. | |
| 9906 | +** | |
| 9907 | +** ^The use of this interface is only necessary if the default setting | |
| 9908 | +** is found to be suboptimal for a particular application. | |
| 9829 | 9909 | */ |
| 9830 | 9910 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 9831 | 9911 | |
| 9832 | 9912 | /* |
| 9833 | 9913 | ** CAPI3REF: Checkpoint a database |
| @@ -9888,10 +9968,15 @@ | ||
| 9888 | 9968 | ** |
| 9889 | 9969 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 9890 | 9970 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 9891 | 9971 | ** addition that it also truncates the log file to zero bytes just prior |
| 9892 | 9972 | ** to a successful return. |
| 9973 | +** | |
| 9974 | +** <dt>SQLITE_CHECKPOINT_NOOP<dd> | |
| 9975 | +** ^This mode always checkpoints zero frames. The only reason to invoke | |
| 9976 | +** a NOOP checkpoint is to access the values returned by | |
| 9977 | +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. | |
| 9893 | 9978 | ** </dl> |
| 9894 | 9979 | ** |
| 9895 | 9980 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 9896 | 9981 | ** the log file or to -1 if the checkpoint could not run because |
| 9897 | 9982 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -9958,10 +10043,11 @@ | ||
| 9958 | 10043 | ** These constants define all valid values for the "checkpoint mode" passed |
| 9959 | 10044 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 9960 | 10045 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 9961 | 10046 | ** meaning of each of these checkpoint modes. |
| 9962 | 10047 | */ |
| 10048 | +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ | |
| 9963 | 10049 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 9964 | 10050 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 9965 | 10051 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 9966 | 10052 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 9967 | 10053 | |
| @@ -10785,11 +10871,11 @@ | ||
| 10785 | 10871 | ** to avoid a memory leak. |
| 10786 | 10872 | ** |
| 10787 | 10873 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 10788 | 10874 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10789 | 10875 | */ |
| 10790 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( | |
| 10876 | +SQLITE_API int sqlite3_snapshot_get( | |
| 10791 | 10877 | sqlite3 *db, |
| 10792 | 10878 | const char *zSchema, |
| 10793 | 10879 | sqlite3_snapshot **ppSnapshot |
| 10794 | 10880 | ); |
| 10795 | 10881 | |
| @@ -10834,11 +10920,11 @@ | ||
| 10834 | 10920 | ** database connection in order to make it ready to use snapshots.) |
| 10835 | 10921 | ** |
| 10836 | 10922 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 10837 | 10923 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10838 | 10924 | */ |
| 10839 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( | |
| 10925 | +SQLITE_API int sqlite3_snapshot_open( | |
| 10840 | 10926 | sqlite3 *db, |
| 10841 | 10927 | const char *zSchema, |
| 10842 | 10928 | sqlite3_snapshot *pSnapshot |
| 10843 | 10929 | ); |
| 10844 | 10930 | |
| @@ -10851,11 +10937,11 @@ | ||
| 10851 | 10937 | ** using this routine to avoid a memory leak. |
| 10852 | 10938 | ** |
| 10853 | 10939 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 10854 | 10940 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10855 | 10941 | */ |
| 10856 | -SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); | |
| 10942 | +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); | |
| 10857 | 10943 | |
| 10858 | 10944 | /* |
| 10859 | 10945 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 10860 | 10946 | ** METHOD: sqlite3_snapshot |
| 10861 | 10947 | ** |
| @@ -10878,11 +10964,11 @@ | ||
| 10878 | 10964 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 10879 | 10965 | ** |
| 10880 | 10966 | ** This interface is only available if SQLite is compiled with the |
| 10881 | 10967 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10882 | 10968 | */ |
| 10883 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( | |
| 10969 | +SQLITE_API int sqlite3_snapshot_cmp( | |
| 10884 | 10970 | sqlite3_snapshot *p1, |
| 10885 | 10971 | sqlite3_snapshot *p2 |
| 10886 | 10972 | ); |
| 10887 | 10973 | |
| 10888 | 10974 | /* |
| @@ -10906,11 +10992,11 @@ | ||
| 10906 | 10992 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 10907 | 10993 | ** |
| 10908 | 10994 | ** This interface is only available if SQLite is compiled with the |
| 10909 | 10995 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10910 | 10996 | */ |
| 10911 | -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); | |
| 10997 | +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); | |
| 10912 | 10998 | |
| 10913 | 10999 | /* |
| 10914 | 11000 | ** CAPI3REF: Serialize a database |
| 10915 | 11001 | ** |
| 10916 | 11002 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -10980,16 +11066,17 @@ | ||
| 10980 | 11066 | /* |
| 10981 | 11067 | ** CAPI3REF: Deserialize a database |
| 10982 | 11068 | ** |
| 10983 | 11069 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 10984 | 11070 | ** [database connection] D to disconnect from database S and then |
| 10985 | -** reopen S as an in-memory database based on the serialization contained | |
| 10986 | -** in P. The serialized database P is N bytes in size. M is the size of | |
| 10987 | -** the buffer P, which might be larger than N. If M is larger than N, and | |
| 10988 | -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is | |
| 10989 | -** permitted to add content to the in-memory database as long as the total | |
| 10990 | -** size does not exceed M bytes. | |
| 11071 | +** reopen S as an in-memory database based on the serialization | |
| 11072 | +** contained in P. If S is a NULL pointer, the main database is | |
| 11073 | +** used. The serialized database P is N bytes in size. M is the size | |
| 11074 | +** of the buffer P, which might be larger than N. If M is larger than | |
| 11075 | +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then | |
| 11076 | +** SQLite is permitted to add content to the in-memory database as | |
| 11077 | +** long as the total size does not exceed M bytes. | |
| 10991 | 11078 | ** |
| 10992 | 11079 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 10993 | 11080 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 10994 | 11081 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 10995 | 11082 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11052,10 +11139,56 @@ | ||
| 11052 | 11139 | */ |
| 11053 | 11140 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11054 | 11141 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11055 | 11142 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11056 | 11143 | |
| 11144 | +/* | |
| 11145 | +** CAPI3REF: Bind array values to the CARRAY table-valued function | |
| 11146 | +** | |
| 11147 | +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to | |
| 11148 | +** one of the first argument of the [carray() table-valued function]. The | |
| 11149 | +** S parameter is a pointer to the [prepared statement] that uses the carray() | |
| 11150 | +** functions. I is the parameter index to be bound. P is a pointer to the | |
| 11151 | +** array to be bound, and N is the number of eements in the array. The | |
| 11152 | +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], | |
| 11153 | +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to | |
| 11154 | +** indicate the datatype of the array being bound. The X argument is not a | |
| 11155 | +** NULL pointer, then SQLite will invoke the function X on the P parameter | |
| 11156 | +** after it has finished using P. | |
| 11157 | +*/ | |
| 11158 | +SQLITE_API SQLITE_API int sqlite3_carray_bind( | |
| 11159 | + sqlite3_stmt *pStmt, /* Statement to be bound */ | |
| 11160 | + int i, /* Parameter index */ | |
| 11161 | + void *aData, /* Pointer to array data */ | |
| 11162 | + int nData, /* Number of data elements */ | |
| 11163 | + int mFlags, /* CARRAY flags */ | |
| 11164 | + void (*xDel)(void*) /* Destructor for aData */ | |
| 11165 | +); | |
| 11166 | + | |
| 11167 | +/* | |
| 11168 | +** CAPI3REF: Datatypes for the CARRAY table-valued funtion | |
| 11169 | +** | |
| 11170 | +** The fifth argument to the [sqlite3_carray_bind()] interface musts be | |
| 11171 | +** one of the following constants, to specify the datatype of the array | |
| 11172 | +** that is being bound into the [carray table-valued function]. | |
| 11173 | +*/ | |
| 11174 | +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ | |
| 11175 | +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ | |
| 11176 | +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ | |
| 11177 | +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ | |
| 11178 | +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ | |
| 11179 | + | |
| 11180 | +/* | |
| 11181 | +** Versions of the above #defines that omit the initial SQLITE_, for | |
| 11182 | +** legacy compatibility. | |
| 11183 | +*/ | |
| 11184 | +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ | |
| 11185 | +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ | |
| 11186 | +#define CARRAY_DOUBLE 2 /* Data is doubles */ | |
| 11187 | +#define CARRAY_TEXT 3 /* Data is char* */ | |
| 11188 | +#define CARRAY_BLOB 4 /* Data is struct iovec */ | |
| 11189 | + | |
| 11057 | 11190 | /* |
| 11058 | 11191 | ** Undo the hack that converts floating point types to integer for |
| 11059 | 11192 | ** builds on processors without floating point support. |
| 11060 | 11193 | */ |
| 11061 | 11194 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12310,10 +12443,19 @@ | ||
| 12310 | 12443 | ** CAPI3REF: Apply A Changeset To A Database |
| 12311 | 12444 | ** |
| 12312 | 12445 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12313 | 12446 | ** update the "main" database attached to handle db with the changes found in |
| 12314 | 12447 | ** the changeset passed via the second and third arguments. |
| 12448 | +** | |
| 12449 | +** All changes made by these functions are enclosed in a savepoint transaction. | |
| 12450 | +** If any other error (aside from a constraint failure when attempting to | |
| 12451 | +** write to the target database) occurs, then the savepoint transaction is | |
| 12452 | +** rolled back, restoring the target database to its original state, and an | |
| 12453 | +** SQLite error code returned. Additionally, starting with version 3.51.0, | |
| 12454 | +** an error code and error message that may be accessed using the | |
| 12455 | +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database | |
| 12456 | +** handle. | |
| 12315 | 12457 | ** |
| 12316 | 12458 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12317 | 12459 | ** callback". This may be passed NULL, in which case all changes in the |
| 12318 | 12460 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12319 | 12461 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12448,16 +12590,10 @@ | ||
| 12448 | 12590 | ** It is safe to execute SQL statements, including those that write to the |
| 12449 | 12591 | ** table that the callback related to, from within the xConflict callback. |
| 12450 | 12592 | ** This can be used to further customize the application's conflict |
| 12451 | 12593 | ** resolution strategy. |
| 12452 | 12594 | ** |
| 12453 | -** All changes made by these functions are enclosed in a savepoint transaction. | |
| 12454 | -** If any other error (aside from a constraint failure when attempting to | |
| 12455 | -** write to the target database) occurs, then the savepoint transaction is | |
| 12456 | -** rolled back, restoring the target database to its original state, and an | |
| 12457 | -** SQLite error code returned. | |
| 12458 | -** | |
| 12459 | 12595 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12460 | 12596 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12461 | 12597 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12462 | 12598 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12463 | 12599 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| 12464 | 12600 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -146,11 +146,14 @@ | |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.51.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3051000 |
| 151 | #define SQLITE_SOURCE_ID "2025-07-15 19:00:01 9f184f8dfa5ef6d57e10376adc30e0060ceda07d283c23dfdfe3dbdd6608f839" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| @@ -495,10 +498,13 @@ | |
| 495 | ** [sqlite3_extended_errcode()]. |
| 496 | */ |
| 497 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 498 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 499 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 500 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 501 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 502 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 503 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 504 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -529,10 +535,12 @@ | |
| 529 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 530 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 531 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 532 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 533 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 534 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 535 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 536 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 537 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 538 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2333,21 +2341,24 @@ | |
| 2333 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2334 | ** databases.)^ </dd> |
| 2335 | ** |
| 2336 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2337 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2338 | ** <dd> ^This option is used to enable or disable the |
| 2339 | ** [fts3_tokenizer()] function which is part of the |
| 2340 | ** [FTS3] full-text search engine extension. |
| 2341 | ** There must be two additional arguments. |
| 2342 | ** The first argument is an integer which is 0 to disable fts3_tokenizer() or |
| 2343 | ** positive to enable fts3_tokenizer() or negative to leave the setting |
| 2344 | ** unchanged. |
| 2345 | ** The second parameter is a pointer to an integer into which |
| 2346 | ** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled |
| 2347 | ** following this call. The second parameter may be a NULL pointer, in |
| 2348 | ** which case the new setting is not reported back. </dd> |
| 2349 | ** |
| 2350 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2351 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2352 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2353 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4193,10 +4204,38 @@ | |
| 4193 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4194 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4195 | SQLITE_API const char *sqlite3_errstr(int); |
| 4196 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4197 | |
| 4198 | /* |
| 4199 | ** CAPI3REF: Prepared Statement Object |
| 4200 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4201 | ** |
| 4202 | ** An instance of this object represents a single SQL statement that |
| @@ -6203,10 +6242,11 @@ | |
| 6203 | ** to be attached to [database connection] D using name N. Subsequent |
| 6204 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6205 | ** or a NULL pointer if there were no prior calls to |
| 6206 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6207 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6208 | ** |
| 6209 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6210 | ** argument P on the first of the following occurrences: |
| 6211 | ** <ul> |
| 6212 | ** <li> An out-of-memory error occurs during the call to |
| @@ -8878,14 +8918,23 @@ | |
| 8878 | ** the resetFlg is true, then the highest instantaneous value is |
| 8879 | ** reset back down to the current value. |
| 8880 | ** |
| 8881 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 8882 | ** non-zero [error code] on failure. |
| 8883 | ** |
| 8884 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 8885 | */ |
| 8886 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 8887 | |
| 8888 | /* |
| 8889 | ** CAPI3REF: Status Parameters for database connections |
| 8890 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 8891 | ** |
| @@ -8978,10 +9027,14 @@ | |
| 8978 | ** database file in rollback mode databases. Any pages written as part of |
| 8979 | ** transaction rollback or database recovery operations are not included. |
| 8980 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 8981 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 8982 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 8983 | ** </dd> |
| 8984 | ** |
| 8985 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 8986 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 8987 | ** been written to disk in the middle of a transaction due to the page |
| @@ -8993,10 +9046,22 @@ | |
| 8993 | ** |
| 8994 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 8995 | ** <dd>This parameter returns zero for the current value if and only if |
| 8996 | ** all foreign key constraints (deferred or immediate) have been |
| 8997 | ** resolved.)^ ^The highwater mark is always 0. |
| 8998 | ** </dd> |
| 8999 | ** </dl> |
| 9000 | */ |
| 9001 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9002 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9009,11 +9074,12 @@ | |
| 9009 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9010 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9011 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9012 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9013 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9014 | #define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ |
| 9015 | |
| 9016 | |
| 9017 | /* |
| 9018 | ** CAPI3REF: Prepared Statement Status |
| 9019 | ** METHOD: sqlite3_stmt |
| @@ -9774,25 +9840,38 @@ | |
| 9774 | ** ^The third parameter is the name of the database that was written to - |
| 9775 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 9776 | ** is the number of pages currently in the write-ahead log file, |
| 9777 | ** including those that were just committed. |
| 9778 | ** |
| 9779 | ** The callback function should normally return [SQLITE_OK]. ^If an error |
| 9780 | ** code is returned, that error will propagate back up through the |
| 9781 | ** SQLite code base to cause the statement that provoked the callback |
| 9782 | ** to report an error, though the commit will have still occurred. If the |
| 9783 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 9784 | ** that does not correspond to any valid SQLite error code, the results |
| 9785 | ** are undefined. |
| 9786 | ** |
| 9787 | ** A single database handle may have at most a single write-ahead log callback |
| 9788 | ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any |
| 9789 | ** previously registered write-ahead log callback. ^The return value is |
| 9790 | ** a copy of the third parameter from the previous call, if any, or 0. |
| 9791 | ** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the |
| 9792 | ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will |
| 9793 | ** overwrite any prior [sqlite3_wal_hook()] settings. |
| 9794 | */ |
| 9795 | SQLITE_API void *sqlite3_wal_hook( |
| 9796 | sqlite3*, |
| 9797 | int(*)(void *,sqlite3*,const char*,int), |
| 9798 | void* |
| @@ -9805,11 +9884,11 @@ | |
| 9805 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 9806 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 9807 | ** to automatically [checkpoint] |
| 9808 | ** after committing a transaction if there are N or |
| 9809 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 9810 | ** a negative value as the nFrame parameter disables automatic |
| 9811 | ** checkpoints entirely. |
| 9812 | ** |
| 9813 | ** ^The callback registered by this function replaces any existing callback |
| 9814 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 9815 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -9821,13 +9900,14 @@ | |
| 9821 | ** ^Checkpoints initiated by this mechanism are |
| 9822 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 9823 | ** |
| 9824 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 9825 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 9826 | ** pages. The use of this interface |
| 9827 | ** is only necessary if the default setting is found to be suboptimal |
| 9828 | ** for a particular application. |
| 9829 | */ |
| 9830 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 9831 | |
| 9832 | /* |
| 9833 | ** CAPI3REF: Checkpoint a database |
| @@ -9888,10 +9968,15 @@ | |
| 9888 | ** |
| 9889 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 9890 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 9891 | ** addition that it also truncates the log file to zero bytes just prior |
| 9892 | ** to a successful return. |
| 9893 | ** </dl> |
| 9894 | ** |
| 9895 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 9896 | ** the log file or to -1 if the checkpoint could not run because |
| 9897 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -9958,10 +10043,11 @@ | |
| 9958 | ** These constants define all valid values for the "checkpoint mode" passed |
| 9959 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 9960 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 9961 | ** meaning of each of these checkpoint modes. |
| 9962 | */ |
| 9963 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 9964 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 9965 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 9966 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 9967 | |
| @@ -10785,11 +10871,11 @@ | |
| 10785 | ** to avoid a memory leak. |
| 10786 | ** |
| 10787 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 10788 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10789 | */ |
| 10790 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( |
| 10791 | sqlite3 *db, |
| 10792 | const char *zSchema, |
| 10793 | sqlite3_snapshot **ppSnapshot |
| 10794 | ); |
| 10795 | |
| @@ -10834,11 +10920,11 @@ | |
| 10834 | ** database connection in order to make it ready to use snapshots.) |
| 10835 | ** |
| 10836 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 10837 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10838 | */ |
| 10839 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( |
| 10840 | sqlite3 *db, |
| 10841 | const char *zSchema, |
| 10842 | sqlite3_snapshot *pSnapshot |
| 10843 | ); |
| 10844 | |
| @@ -10851,11 +10937,11 @@ | |
| 10851 | ** using this routine to avoid a memory leak. |
| 10852 | ** |
| 10853 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 10854 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10855 | */ |
| 10856 | SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); |
| 10857 | |
| 10858 | /* |
| 10859 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 10860 | ** METHOD: sqlite3_snapshot |
| 10861 | ** |
| @@ -10878,11 +10964,11 @@ | |
| 10878 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 10879 | ** |
| 10880 | ** This interface is only available if SQLite is compiled with the |
| 10881 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10882 | */ |
| 10883 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( |
| 10884 | sqlite3_snapshot *p1, |
| 10885 | sqlite3_snapshot *p2 |
| 10886 | ); |
| 10887 | |
| 10888 | /* |
| @@ -10906,11 +10992,11 @@ | |
| 10906 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 10907 | ** |
| 10908 | ** This interface is only available if SQLite is compiled with the |
| 10909 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10910 | */ |
| 10911 | SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); |
| 10912 | |
| 10913 | /* |
| 10914 | ** CAPI3REF: Serialize a database |
| 10915 | ** |
| 10916 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -10980,16 +11066,17 @@ | |
| 10980 | /* |
| 10981 | ** CAPI3REF: Deserialize a database |
| 10982 | ** |
| 10983 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 10984 | ** [database connection] D to disconnect from database S and then |
| 10985 | ** reopen S as an in-memory database based on the serialization contained |
| 10986 | ** in P. The serialized database P is N bytes in size. M is the size of |
| 10987 | ** the buffer P, which might be larger than N. If M is larger than N, and |
| 10988 | ** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is |
| 10989 | ** permitted to add content to the in-memory database as long as the total |
| 10990 | ** size does not exceed M bytes. |
| 10991 | ** |
| 10992 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 10993 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 10994 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 10995 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11052,10 +11139,56 @@ | |
| 11052 | */ |
| 11053 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11054 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11055 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11056 | |
| 11057 | /* |
| 11058 | ** Undo the hack that converts floating point types to integer for |
| 11059 | ** builds on processors without floating point support. |
| 11060 | */ |
| 11061 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12310,10 +12443,19 @@ | |
| 12310 | ** CAPI3REF: Apply A Changeset To A Database |
| 12311 | ** |
| 12312 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12313 | ** update the "main" database attached to handle db with the changes found in |
| 12314 | ** the changeset passed via the second and third arguments. |
| 12315 | ** |
| 12316 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12317 | ** callback". This may be passed NULL, in which case all changes in the |
| 12318 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12319 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12448,16 +12590,10 @@ | |
| 12448 | ** It is safe to execute SQL statements, including those that write to the |
| 12449 | ** table that the callback related to, from within the xConflict callback. |
| 12450 | ** This can be used to further customize the application's conflict |
| 12451 | ** resolution strategy. |
| 12452 | ** |
| 12453 | ** All changes made by these functions are enclosed in a savepoint transaction. |
| 12454 | ** If any other error (aside from a constraint failure when attempting to |
| 12455 | ** write to the target database) occurs, then the savepoint transaction is |
| 12456 | ** rolled back, restoring the target database to its original state, and an |
| 12457 | ** SQLite error code returned. |
| 12458 | ** |
| 12459 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12460 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12461 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12462 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12463 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| 12464 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -146,11 +146,14 @@ | |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.51.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3051000 |
| 151 | #define SQLITE_SOURCE_ID "2025-10-15 10:52:45 5cbccab499bc3983aac1f57355552db607dee6c7ef4eb00d794dbee89c18db70" |
| 152 | #define SQLITE_SCM_BRANCH "trunk" |
| 153 | #define SQLITE_SCM_TAGS "" |
| 154 | #define SQLITE_SCM_DATETIME "2025-10-15T10:52:45.276Z" |
| 155 | |
| 156 | /* |
| 157 | ** CAPI3REF: Run-Time Library Version Numbers |
| 158 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 159 | ** |
| @@ -495,10 +498,13 @@ | |
| 498 | ** [sqlite3_extended_errcode()]. |
| 499 | */ |
| 500 | #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) |
| 501 | #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) |
| 502 | #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) |
| 503 | #define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) |
| 504 | #define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) |
| 505 | #define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) |
| 506 | #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) |
| 507 | #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) |
| 508 | #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) |
| 509 | #define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) |
| 510 | #define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) |
| @@ -529,10 +535,12 @@ | |
| 535 | #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) |
| 536 | #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) |
| 537 | #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) |
| 538 | #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) |
| 539 | #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) |
| 540 | #define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) |
| 541 | #define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) |
| 542 | #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) |
| 543 | #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) |
| 544 | #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) |
| 545 | #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) |
| 546 | #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) |
| @@ -2333,21 +2341,24 @@ | |
| 2341 | ** views in the main database schema or in the schemas of ATTACH-ed |
| 2342 | ** databases.)^ </dd> |
| 2343 | ** |
| 2344 | ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] |
| 2345 | ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> |
| 2346 | ** <dd> ^This option is used to enable or disable using the |
| 2347 | ** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine |
| 2348 | ** extension - without using bound parameters as the parameters. Doing so |
| 2349 | ** is disabled by default. There must be two additional arguments. The first |
| 2350 | ** argument is an integer. If it is passed 0, then using fts3_tokenizer() |
| 2351 | ** without bound parameters is disabled. If it is passed a positive value, |
| 2352 | ** then calling fts3_tokenizer without bound parameters is enabled. If it |
| 2353 | ** is passed a negative value, this setting is not modified - this can be |
| 2354 | ** used to query for the current setting. The second parameter is a pointer |
| 2355 | ** to an integer into which is written 0 or 1 to indicate the current value |
| 2356 | ** of this setting (after it is modified, if applicable). The second |
| 2357 | ** parameter may be a NULL pointer, in which case the value of the setting |
| 2358 | ** is not reported back. Refer to [FTS3] documentation for further details. |
| 2359 | ** </dd> |
| 2360 | ** |
| 2361 | ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] |
| 2362 | ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> |
| 2363 | ** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()] |
| 2364 | ** interface independently of the [load_extension()] SQL function. |
| @@ -4193,10 +4204,38 @@ | |
| 4204 | SQLITE_API const char *sqlite3_errmsg(sqlite3*); |
| 4205 | SQLITE_API const void *sqlite3_errmsg16(sqlite3*); |
| 4206 | SQLITE_API const char *sqlite3_errstr(int); |
| 4207 | SQLITE_API int sqlite3_error_offset(sqlite3 *db); |
| 4208 | |
| 4209 | /* |
| 4210 | ** CAPI3REF: Set Error Codes And Message |
| 4211 | ** METHOD: sqlite3 |
| 4212 | ** |
| 4213 | ** Set the error code of the database handle passed as the first argument |
| 4214 | ** to errcode, and the error message to a copy of nul-terminated string |
| 4215 | ** zErrMsg. If zErrMsg is passed NULL, then the error message is set to |
| 4216 | ** the default message associated with the supplied error code. Subsequent |
| 4217 | ** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will |
| 4218 | ** return the values set by this routine in place of what was previously |
| 4219 | ** set by SQLite itself. |
| 4220 | ** |
| 4221 | ** This function returns SQLITE_OK if the error code and error message are |
| 4222 | ** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if |
| 4223 | ** the database handle is NULL or invalid. |
| 4224 | ** |
| 4225 | ** The error code and message set by this routine remains in effect until |
| 4226 | ** they are changed, either by another call to this routine or until they are |
| 4227 | ** changed to by SQLite itself to reflect the result of some subsquent |
| 4228 | ** API call. |
| 4229 | ** |
| 4230 | ** This function is intended for use by SQLite extensions or wrappers. The |
| 4231 | ** idea is that an extension or wrapper can use this routine to set error |
| 4232 | ** messages and error codes and thus behave more like a core SQLite |
| 4233 | ** feature from the point of view of an application. |
| 4234 | */ |
| 4235 | SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); |
| 4236 | |
| 4237 | /* |
| 4238 | ** CAPI3REF: Prepared Statement Object |
| 4239 | ** KEYWORDS: {prepared statement} {prepared statements} |
| 4240 | ** |
| 4241 | ** An instance of this object represents a single SQL statement that |
| @@ -6203,10 +6242,11 @@ | |
| 6242 | ** to be attached to [database connection] D using name N. Subsequent |
| 6243 | ** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P |
| 6244 | ** or a NULL pointer if there were no prior calls to |
| 6245 | ** sqlite3_set_clientdata() with the same values of D and N. |
| 6246 | ** Names are compared using strcmp() and are thus case sensitive. |
| 6247 | ** It returns 0 on success and SQLITE_NOMEM on allocation failure. |
| 6248 | ** |
| 6249 | ** If P and X are both non-NULL, then the destructor X is invoked with |
| 6250 | ** argument P on the first of the following occurrences: |
| 6251 | ** <ul> |
| 6252 | ** <li> An out-of-memory error occurs during the call to |
| @@ -8878,14 +8918,23 @@ | |
| 8918 | ** the resetFlg is true, then the highest instantaneous value is |
| 8919 | ** reset back down to the current value. |
| 8920 | ** |
| 8921 | ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a |
| 8922 | ** non-zero [error code] on failure. |
| 8923 | ** |
| 8924 | ** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same |
| 8925 | ** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H |
| 8926 | ** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead |
| 8927 | ** of pointers to 32-bit integers, which allows larger status values |
| 8928 | ** to be returned. If a status value exceeds 2,147,483,647 then |
| 8929 | ** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() |
| 8930 | ** will return the full value. |
| 8931 | ** |
| 8932 | ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. |
| 8933 | */ |
| 8934 | SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); |
| 8935 | SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); |
| 8936 | |
| 8937 | /* |
| 8938 | ** CAPI3REF: Status Parameters for database connections |
| 8939 | ** KEYWORDS: {SQLITE_DBSTATUS options} |
| 8940 | ** |
| @@ -8978,10 +9027,14 @@ | |
| 9027 | ** database file in rollback mode databases. Any pages written as part of |
| 9028 | ** transaction rollback or database recovery operations are not included. |
| 9029 | ** If an IO or other error occurs while writing a page to disk, the effect |
| 9030 | ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The |
| 9031 | ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. |
| 9032 | ** <p> |
| 9033 | ** ^(There is overlap between the quantities measured by this parameter |
| 9034 | ** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. |
| 9035 | ** Resetting one will reduce the other.)^ |
| 9036 | ** </dd> |
| 9037 | ** |
| 9038 | ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> |
| 9039 | ** <dd>This parameter returns the number of dirty cache entries that have |
| 9040 | ** been written to disk in the middle of a transaction due to the page |
| @@ -8993,10 +9046,22 @@ | |
| 9046 | ** |
| 9047 | ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> |
| 9048 | ** <dd>This parameter returns zero for the current value if and only if |
| 9049 | ** all foreign key constraints (deferred or immediate) have been |
| 9050 | ** resolved.)^ ^The highwater mark is always 0. |
| 9051 | ** |
| 9052 | ** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> |
| 9053 | ** <dd>^(This parameter returns the number of bytes written to temporary |
| 9054 | ** files on disk that could have been kept in memory had sufficient memory |
| 9055 | ** been available. This value includes writes to intermediate tables that |
| 9056 | ** are part of complex queries, external sorts that spill to disk, and |
| 9057 | ** writes to TEMP tables.)^ |
| 9058 | ** ^The highwater mark is always 0. |
| 9059 | ** <p> |
| 9060 | ** ^(There is overlap between the quantities measured by this parameter |
| 9061 | ** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. |
| 9062 | ** Resetting one will reduce the other.)^ |
| 9063 | ** </dd> |
| 9064 | ** </dl> |
| 9065 | */ |
| 9066 | #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 |
| 9067 | #define SQLITE_DBSTATUS_CACHE_USED 1 |
| @@ -9009,11 +9074,12 @@ | |
| 9074 | #define SQLITE_DBSTATUS_CACHE_MISS 8 |
| 9075 | #define SQLITE_DBSTATUS_CACHE_WRITE 9 |
| 9076 | #define SQLITE_DBSTATUS_DEFERRED_FKS 10 |
| 9077 | #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 |
| 9078 | #define SQLITE_DBSTATUS_CACHE_SPILL 12 |
| 9079 | #define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 |
| 9080 | #define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ |
| 9081 | |
| 9082 | |
| 9083 | /* |
| 9084 | ** CAPI3REF: Prepared Statement Status |
| 9085 | ** METHOD: sqlite3_stmt |
| @@ -9774,25 +9840,38 @@ | |
| 9840 | ** ^The third parameter is the name of the database that was written to - |
| 9841 | ** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter |
| 9842 | ** is the number of pages currently in the write-ahead log file, |
| 9843 | ** including those that were just committed. |
| 9844 | ** |
| 9845 | ** ^The callback function should normally return [SQLITE_OK]. ^If an error |
| 9846 | ** code is returned, that error will propagate back up through the |
| 9847 | ** SQLite code base to cause the statement that provoked the callback |
| 9848 | ** to report an error, though the commit will have still occurred. If the |
| 9849 | ** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value |
| 9850 | ** that does not correspond to any valid SQLite error code, the results |
| 9851 | ** are undefined. |
| 9852 | ** |
| 9853 | ** ^A single database handle may have at most a single write-ahead log |
| 9854 | ** callback registered at one time. ^Calling [sqlite3_wal_hook()] |
| 9855 | ** replaces the default behavior or previously registered write-ahead |
| 9856 | ** log callback. |
| 9857 | ** |
| 9858 | ** ^The return value is a copy of the third parameter from the |
| 9859 | ** previous call, if any, or 0. |
| 9860 | ** |
| 9861 | ** ^The [sqlite3_wal_autocheckpoint()] interface and the |
| 9862 | ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and |
| 9863 | ** will overwrite any prior [sqlite3_wal_hook()] settings. |
| 9864 | ** |
| 9865 | ** ^If a write-ahead log callback is set using this function then |
| 9866 | ** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] |
| 9867 | ** should be invoked periodically to keep the write-ahead log file |
| 9868 | ** from growing without bound. |
| 9869 | ** |
| 9870 | ** ^Passing a NULL pointer for the callback disables automatic |
| 9871 | ** checkpointing entirely. To re-enable the default behavior, call |
| 9872 | ** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. |
| 9873 | */ |
| 9874 | SQLITE_API void *sqlite3_wal_hook( |
| 9875 | sqlite3*, |
| 9876 | int(*)(void *,sqlite3*,const char*,int), |
| 9877 | void* |
| @@ -9805,11 +9884,11 @@ | |
| 9884 | ** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around |
| 9885 | ** [sqlite3_wal_hook()] that causes any database on [database connection] D |
| 9886 | ** to automatically [checkpoint] |
| 9887 | ** after committing a transaction if there are N or |
| 9888 | ** more frames in the [write-ahead log] file. ^Passing zero or |
| 9889 | ** a negative value as the N parameter disables automatic |
| 9890 | ** checkpoints entirely. |
| 9891 | ** |
| 9892 | ** ^The callback registered by this function replaces any existing callback |
| 9893 | ** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback |
| 9894 | ** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism |
| @@ -9821,13 +9900,14 @@ | |
| 9900 | ** ^Checkpoints initiated by this mechanism are |
| 9901 | ** [sqlite3_wal_checkpoint_v2|PASSIVE]. |
| 9902 | ** |
| 9903 | ** ^Every new [database connection] defaults to having the auto-checkpoint |
| 9904 | ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] |
| 9905 | ** pages. |
| 9906 | ** |
| 9907 | ** ^The use of this interface is only necessary if the default setting |
| 9908 | ** is found to be suboptimal for a particular application. |
| 9909 | */ |
| 9910 | SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); |
| 9911 | |
| 9912 | /* |
| 9913 | ** CAPI3REF: Checkpoint a database |
| @@ -9888,10 +9968,15 @@ | |
| 9968 | ** |
| 9969 | ** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd> |
| 9970 | ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the |
| 9971 | ** addition that it also truncates the log file to zero bytes just prior |
| 9972 | ** to a successful return. |
| 9973 | ** |
| 9974 | ** <dt>SQLITE_CHECKPOINT_NOOP<dd> |
| 9975 | ** ^This mode always checkpoints zero frames. The only reason to invoke |
| 9976 | ** a NOOP checkpoint is to access the values returned by |
| 9977 | ** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. |
| 9978 | ** </dl> |
| 9979 | ** |
| 9980 | ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in |
| 9981 | ** the log file or to -1 if the checkpoint could not run because |
| 9982 | ** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not |
| @@ -9958,10 +10043,11 @@ | |
| 10043 | ** These constants define all valid values for the "checkpoint mode" passed |
| 10044 | ** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. |
| 10045 | ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the |
| 10046 | ** meaning of each of these checkpoint modes. |
| 10047 | */ |
| 10048 | #define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ |
| 10049 | #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ |
| 10050 | #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ |
| 10051 | #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ |
| 10052 | #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ |
| 10053 | |
| @@ -10785,11 +10871,11 @@ | |
| 10871 | ** to avoid a memory leak. |
| 10872 | ** |
| 10873 | ** The [sqlite3_snapshot_get()] interface is only available when the |
| 10874 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10875 | */ |
| 10876 | SQLITE_API int sqlite3_snapshot_get( |
| 10877 | sqlite3 *db, |
| 10878 | const char *zSchema, |
| 10879 | sqlite3_snapshot **ppSnapshot |
| 10880 | ); |
| 10881 | |
| @@ -10834,11 +10920,11 @@ | |
| 10920 | ** database connection in order to make it ready to use snapshots.) |
| 10921 | ** |
| 10922 | ** The [sqlite3_snapshot_open()] interface is only available when the |
| 10923 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10924 | */ |
| 10925 | SQLITE_API int sqlite3_snapshot_open( |
| 10926 | sqlite3 *db, |
| 10927 | const char *zSchema, |
| 10928 | sqlite3_snapshot *pSnapshot |
| 10929 | ); |
| 10930 | |
| @@ -10851,11 +10937,11 @@ | |
| 10937 | ** using this routine to avoid a memory leak. |
| 10938 | ** |
| 10939 | ** The [sqlite3_snapshot_free()] interface is only available when the |
| 10940 | ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. |
| 10941 | */ |
| 10942 | SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); |
| 10943 | |
| 10944 | /* |
| 10945 | ** CAPI3REF: Compare the ages of two snapshot handles. |
| 10946 | ** METHOD: sqlite3_snapshot |
| 10947 | ** |
| @@ -10878,11 +10964,11 @@ | |
| 10964 | ** snapshot, and a positive value if P1 is a newer snapshot than P2. |
| 10965 | ** |
| 10966 | ** This interface is only available if SQLite is compiled with the |
| 10967 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10968 | */ |
| 10969 | SQLITE_API int sqlite3_snapshot_cmp( |
| 10970 | sqlite3_snapshot *p1, |
| 10971 | sqlite3_snapshot *p2 |
| 10972 | ); |
| 10973 | |
| 10974 | /* |
| @@ -10906,11 +10992,11 @@ | |
| 10992 | ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. |
| 10993 | ** |
| 10994 | ** This interface is only available if SQLite is compiled with the |
| 10995 | ** [SQLITE_ENABLE_SNAPSHOT] option. |
| 10996 | */ |
| 10997 | SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); |
| 10998 | |
| 10999 | /* |
| 11000 | ** CAPI3REF: Serialize a database |
| 11001 | ** |
| 11002 | ** The sqlite3_serialize(D,S,P,F) interface returns a pointer to |
| @@ -10980,16 +11066,17 @@ | |
| 11066 | /* |
| 11067 | ** CAPI3REF: Deserialize a database |
| 11068 | ** |
| 11069 | ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the |
| 11070 | ** [database connection] D to disconnect from database S and then |
| 11071 | ** reopen S as an in-memory database based on the serialization |
| 11072 | ** contained in P. If S is a NULL pointer, the main database is |
| 11073 | ** used. The serialized database P is N bytes in size. M is the size |
| 11074 | ** of the buffer P, which might be larger than N. If M is larger than |
| 11075 | ** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then |
| 11076 | ** SQLite is permitted to add content to the in-memory database as |
| 11077 | ** long as the total size does not exceed M bytes. |
| 11078 | ** |
| 11079 | ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will |
| 11080 | ** invoke sqlite3_free() on the serialization buffer when the database |
| 11081 | ** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then |
| 11082 | ** SQLite will try to increase the buffer size using sqlite3_realloc64() |
| @@ -11052,10 +11139,56 @@ | |
| 11139 | */ |
| 11140 | #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ |
| 11141 | #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ |
| 11142 | #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ |
| 11143 | |
| 11144 | /* |
| 11145 | ** CAPI3REF: Bind array values to the CARRAY table-valued function |
| 11146 | ** |
| 11147 | ** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to |
| 11148 | ** one of the first argument of the [carray() table-valued function]. The |
| 11149 | ** S parameter is a pointer to the [prepared statement] that uses the carray() |
| 11150 | ** functions. I is the parameter index to be bound. P is a pointer to the |
| 11151 | ** array to be bound, and N is the number of eements in the array. The |
| 11152 | ** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], |
| 11153 | ** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to |
| 11154 | ** indicate the datatype of the array being bound. The X argument is not a |
| 11155 | ** NULL pointer, then SQLite will invoke the function X on the P parameter |
| 11156 | ** after it has finished using P. |
| 11157 | */ |
| 11158 | SQLITE_API SQLITE_API int sqlite3_carray_bind( |
| 11159 | sqlite3_stmt *pStmt, /* Statement to be bound */ |
| 11160 | int i, /* Parameter index */ |
| 11161 | void *aData, /* Pointer to array data */ |
| 11162 | int nData, /* Number of data elements */ |
| 11163 | int mFlags, /* CARRAY flags */ |
| 11164 | void (*xDel)(void*) /* Destructor for aData */ |
| 11165 | ); |
| 11166 | |
| 11167 | /* |
| 11168 | ** CAPI3REF: Datatypes for the CARRAY table-valued funtion |
| 11169 | ** |
| 11170 | ** The fifth argument to the [sqlite3_carray_bind()] interface musts be |
| 11171 | ** one of the following constants, to specify the datatype of the array |
| 11172 | ** that is being bound into the [carray table-valued function]. |
| 11173 | */ |
| 11174 | #define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ |
| 11175 | #define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ |
| 11176 | #define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ |
| 11177 | #define SQLITE_CARRAY_TEXT 3 /* Data is char* */ |
| 11178 | #define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ |
| 11179 | |
| 11180 | /* |
| 11181 | ** Versions of the above #defines that omit the initial SQLITE_, for |
| 11182 | ** legacy compatibility. |
| 11183 | */ |
| 11184 | #define CARRAY_INT32 0 /* Data is 32-bit signed integers */ |
| 11185 | #define CARRAY_INT64 1 /* Data is 64-bit signed integers */ |
| 11186 | #define CARRAY_DOUBLE 2 /* Data is doubles */ |
| 11187 | #define CARRAY_TEXT 3 /* Data is char* */ |
| 11188 | #define CARRAY_BLOB 4 /* Data is struct iovec */ |
| 11189 | |
| 11190 | /* |
| 11191 | ** Undo the hack that converts floating point types to integer for |
| 11192 | ** builds on processors without floating point support. |
| 11193 | */ |
| 11194 | #ifdef SQLITE_OMIT_FLOATING_POINT |
| @@ -12310,10 +12443,19 @@ | |
| 12443 | ** CAPI3REF: Apply A Changeset To A Database |
| 12444 | ** |
| 12445 | ** Apply a changeset or patchset to a database. These functions attempt to |
| 12446 | ** update the "main" database attached to handle db with the changes found in |
| 12447 | ** the changeset passed via the second and third arguments. |
| 12448 | ** |
| 12449 | ** All changes made by these functions are enclosed in a savepoint transaction. |
| 12450 | ** If any other error (aside from a constraint failure when attempting to |
| 12451 | ** write to the target database) occurs, then the savepoint transaction is |
| 12452 | ** rolled back, restoring the target database to its original state, and an |
| 12453 | ** SQLite error code returned. Additionally, starting with version 3.51.0, |
| 12454 | ** an error code and error message that may be accessed using the |
| 12455 | ** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database |
| 12456 | ** handle. |
| 12457 | ** |
| 12458 | ** The fourth argument (xFilter) passed to these functions is the "filter |
| 12459 | ** callback". This may be passed NULL, in which case all changes in the |
| 12460 | ** changeset are applied to the database. For sqlite3changeset_apply() and |
| 12461 | ** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once |
| @@ -12448,16 +12590,10 @@ | |
| 12590 | ** It is safe to execute SQL statements, including those that write to the |
| 12591 | ** table that the callback related to, from within the xConflict callback. |
| 12592 | ** This can be used to further customize the application's conflict |
| 12593 | ** resolution strategy. |
| 12594 | ** |
| 12595 | ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and |
| 12596 | ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() |
| 12597 | ** may set (*ppRebase) to point to a "rebase" that may be used with the |
| 12598 | ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) |
| 12599 | ** is set to the size of the buffer in bytes. It is the responsibility of the |
| 12600 |
+2
-2
| --- src/add.c | ||
| +++ src/add.c | ||
| @@ -1006,12 +1006,12 @@ | ||
| 1006 | 1006 | |
| 1007 | 1007 | /* |
| 1008 | 1008 | ** COMMAND: mv |
| 1009 | 1009 | ** COMMAND: rename* |
| 1010 | 1010 | ** |
| 1011 | -** Usage: %fossil mv|rename OLDNAME NEWNAME | |
| 1012 | -** or: %fossil mv|rename OLDNAME... DIR | |
| 1011 | +** Usage: %fossil mv|rename ?OPTIONS? OLDNAME NEWNAME | |
| 1012 | +** or: %fossil mv|rename ?OPTIONS? OLDNAME... DIR | |
| 1013 | 1013 | ** |
| 1014 | 1014 | ** Move or rename one or more files or directories within the repository tree. |
| 1015 | 1015 | ** You can either rename a file or directory or move it to another subdirectory. |
| 1016 | 1016 | ** |
| 1017 | 1017 | ** The 'mv' command does NOT normally rename or move the files on disk. |
| 1018 | 1018 |
| --- src/add.c | |
| +++ src/add.c | |
| @@ -1006,12 +1006,12 @@ | |
| 1006 | |
| 1007 | /* |
| 1008 | ** COMMAND: mv |
| 1009 | ** COMMAND: rename* |
| 1010 | ** |
| 1011 | ** Usage: %fossil mv|rename OLDNAME NEWNAME |
| 1012 | ** or: %fossil mv|rename OLDNAME... DIR |
| 1013 | ** |
| 1014 | ** Move or rename one or more files or directories within the repository tree. |
| 1015 | ** You can either rename a file or directory or move it to another subdirectory. |
| 1016 | ** |
| 1017 | ** The 'mv' command does NOT normally rename or move the files on disk. |
| 1018 |
| --- src/add.c | |
| +++ src/add.c | |
| @@ -1006,12 +1006,12 @@ | |
| 1006 | |
| 1007 | /* |
| 1008 | ** COMMAND: mv |
| 1009 | ** COMMAND: rename* |
| 1010 | ** |
| 1011 | ** Usage: %fossil mv|rename ?OPTIONS? OLDNAME NEWNAME |
| 1012 | ** or: %fossil mv|rename ?OPTIONS? OLDNAME... DIR |
| 1013 | ** |
| 1014 | ** Move or rename one or more files or directories within the repository tree. |
| 1015 | ** You can either rename a file or directory or move it to another subdirectory. |
| 1016 | ** |
| 1017 | ** The 'mv' command does NOT normally rename or move the files on disk. |
| 1018 |
+80
-25
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -843,10 +843,11 @@ | ||
| 843 | 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | 844 | style_set_current_feature("branch"); |
| 845 | 845 | style_header("Branches"); |
| 846 | 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | + | |
| 848 | 849 | login_anonymous_available(); |
| 849 | 850 | |
| 850 | 851 | brlist_create_temp_table(); |
| 851 | 852 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 852 | 853 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1019,40 +1020,47 @@ | ||
| 1019 | 1020 | /* |
| 1020 | 1021 | ** This routine is called while for each check-in that is rendered by |
| 1021 | 1022 | ** the timeline of a "brlist" page. Add some additional hyperlinks |
| 1022 | 1023 | ** to the end of the line. |
| 1023 | 1024 | */ |
| 1024 | -static void brtimeline_extra(int rid){ | |
| 1025 | - Stmt q; | |
| 1025 | +static void brtimeline_extra( | |
| 1026 | + Stmt *pQuery, /* Current row of the timeline query */ | |
| 1027 | + int tmFlags, /* Flags to www_print_timeline() */ | |
| 1028 | + const char *zThisUser, /* Suppress links to this user */ | |
| 1029 | + const char *zThisTag /* Suppress links to this tag */ | |
| 1030 | +){ | |
| 1031 | + int rid; | |
| 1032 | + int tmFlagsNew; | |
| 1033 | + char *zBrName; | |
| 1034 | + | |
| 1035 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 1036 | + tmFlagsNew = (tmFlags & ~TIMELINE_VIEWS) | TIMELINE_MODERN; | |
| 1037 | + cgi_printf("("); | |
| 1038 | + }else{ | |
| 1039 | + tmFlagsNew = tmFlags; | |
| 1040 | + } | |
| 1041 | + timeline_extra(pQuery,tmFlagsNew,zThisUser,zThisTag); | |
| 1042 | + | |
| 1026 | 1043 | if( !g.perm.Hyperlink ) return; |
| 1027 | - db_prepare(&q, | |
| 1028 | - "SELECT substr(tagname,5) FROM tagxref, tag" | |
| 1029 | - " WHERE tagxref.rid=%d" | |
| 1030 | - " AND tagxref.tagid=tag.tagid" | |
| 1031 | - " AND tagxref.tagtype>0" | |
| 1032 | - " AND tag.tagname GLOB 'sym-*'", | |
| 1033 | - rid | |
| 1034 | - ); | |
| 1035 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 1036 | - const char *zTagName = db_column_text(&q, 0); | |
| 1037 | - @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> | |
| 1038 | - } | |
| 1039 | - db_finalize(&q); | |
| 1044 | + rid = db_column_int(pQuery,0); | |
| 1045 | + zBrName = branch_of_rid(rid); | |
| 1046 | + @ branch: <span class='timelineHash'>\ | |
| 1047 | + @ %z(href("%R/timeline?r=%T",zBrName))%h(zBrName)</a></span> | |
| 1048 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 1049 | + cgi_printf(")"); | |
| 1050 | + } | |
| 1040 | 1051 | } |
| 1041 | 1052 | |
| 1042 | 1053 | /* |
| 1043 | 1054 | ** WEBPAGE: brtimeline |
| 1044 | 1055 | ** |
| 1045 | -** Show a timeline of all branches | |
| 1056 | +** List the first check of every branch, starting with the most recent | |
| 1057 | +** and going backwards in time. | |
| 1046 | 1058 | ** |
| 1047 | 1059 | ** Query parameters: |
| 1048 | 1060 | ** |
| 1049 | -** ng No graph | |
| 1050 | -** nohidden Hide check-ins with "hidden" tag | |
| 1051 | -** onlyhidden Show only check-ins with "hidden" tag | |
| 1052 | -** brbg Background color by branch name | |
| 1053 | -** ubg Background color by user name | |
| 1061 | +** ubg Color the graph by user, not by branch. | |
| 1054 | 1062 | */ |
| 1055 | 1063 | void brtimeline_page(void){ |
| 1056 | 1064 | Blob sql = empty_blob; |
| 1057 | 1065 | Stmt q; |
| 1058 | 1066 | int tmFlags; /* Timeline display flags */ |
| @@ -1059,14 +1067,15 @@ | ||
| 1059 | 1067 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1060 | 1068 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1061 | 1069 | |
| 1062 | 1070 | login_check_credentials(); |
| 1063 | 1071 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1072 | + if( robot_restrict("timelineX") ) return; | |
| 1064 | 1073 | |
| 1065 | 1074 | style_set_current_feature("branch"); |
| 1066 | 1075 | style_header("Branches"); |
| 1067 | - style_submenu_element("List", "brlist"); | |
| 1076 | + style_submenu_element("Branch List", "brlist"); | |
| 1068 | 1077 | login_anonymous_available(); |
| 1069 | 1078 | timeline_ss_submenu(); |
| 1070 | 1079 | cgi_check_for_malice(); |
| 1071 | 1080 | @ <h2>The initial check-in for each branch:</h2> |
| 1072 | 1081 | blob_append(&sql, timeline_query_for_www(), -1); |
| @@ -1083,12 +1092,58 @@ | ||
| 1083 | 1092 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1084 | 1093 | blob_reset(&sql); |
| 1085 | 1094 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1086 | 1095 | ** many descenders to (off-screen) parents. */ |
| 1087 | 1096 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1088 | - if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH; | |
| 1089 | - if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR; | |
| 1090 | - if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR; | |
| 1097 | + if( PB("ubg")!=0 ){ | |
| 1098 | + tmFlags |= TIMELINE_UCOLOR; | |
| 1099 | + }else{ | |
| 1100 | + tmFlags |= TIMELINE_BRCOLOR; | |
| 1101 | + } | |
| 1091 | 1102 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra); |
| 1092 | 1103 | db_finalize(&q); |
| 1093 | 1104 | style_finish_page(); |
| 1094 | 1105 | } |
| 1106 | + | |
| 1107 | +/* | |
| 1108 | +** Generate a multichoice submenu for the few recent active branches. zName is | |
| 1109 | +** the query parameter used to select the current checkin. zCI is optional and | |
| 1110 | +** represent the currently selected checkin, so if it is a checkin hash | |
| 1111 | +** instead of a branch, it can be part of the multichoice menu. | |
| 1112 | +*/ | |
| 1113 | +void generate_branch_submenu_multichoice( | |
| 1114 | + const char* zName, /* Query parameter name */ | |
| 1115 | + const char* zCI /* Current checkin */ | |
| 1116 | +){ | |
| 1117 | + Stmt q; | |
| 1118 | + const int brFlags = BRL_ORDERBY_MTIME | BRL_OPEN_ONLY; | |
| 1119 | + static const char *zBranchMenuList[32*2]; /* 2 per entries */ | |
| 1120 | + const int nLimit = count(zBranchMenuList)/2; | |
| 1121 | + int i = 0; | |
| 1122 | + | |
| 1123 | + if( zName == 0 ) zName = "ci"; | |
| 1124 | + | |
| 1125 | + branch_prepare_list_query(&q, brFlags, 0, nLimit, 0); | |
| 1126 | + zBranchMenuList[i++] = ""; | |
| 1127 | + zBranchMenuList[i++] = "All Checkins"; | |
| 1128 | + | |
| 1129 | + if( zCI ){ | |
| 1130 | + zCI = fossil_strdup(zCI); | |
| 1131 | + zBranchMenuList[i++] = zCI; | |
| 1132 | + zBranchMenuList[i++] = zCI; | |
| 1133 | + } | |
| 1134 | + /* If current checkin is not "tip", add it to the list */ | |
| 1135 | + if( zCI==0 || strcmp(zCI, "tip") ){ | |
| 1136 | + zBranchMenuList[i++] = "tip"; | |
| 1137 | + zBranchMenuList[i++] = "tip"; | |
| 1138 | + } | |
| 1139 | + while( i/2 < nLimit && db_step(&q)==SQLITE_ROW ){ | |
| 1140 | + const char* zBr = fossil_strdup(db_column_text(&q, 0)); | |
| 1141 | + /* zCI is already in the list, don't add it twice */ | |
| 1142 | + if( zCI==0 || strcmp(zBr, zCI) ){ | |
| 1143 | + zBranchMenuList[i++] = zBr; | |
| 1144 | + zBranchMenuList[i++] = zBr; | |
| 1145 | + } | |
| 1146 | + } | |
| 1147 | + db_finalize(&q); | |
| 1148 | + style_submenu_multichoice(zName, i/2, zBranchMenuList, 0); | |
| 1149 | +} | |
| 1095 | 1150 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -843,10 +843,11 @@ | |
| 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | style_set_current_feature("branch"); |
| 845 | style_header("Branches"); |
| 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | login_anonymous_available(); |
| 849 | |
| 850 | brlist_create_temp_table(); |
| 851 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 852 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1019,40 +1020,47 @@ | |
| 1019 | /* |
| 1020 | ** This routine is called while for each check-in that is rendered by |
| 1021 | ** the timeline of a "brlist" page. Add some additional hyperlinks |
| 1022 | ** to the end of the line. |
| 1023 | */ |
| 1024 | static void brtimeline_extra(int rid){ |
| 1025 | Stmt q; |
| 1026 | if( !g.perm.Hyperlink ) return; |
| 1027 | db_prepare(&q, |
| 1028 | "SELECT substr(tagname,5) FROM tagxref, tag" |
| 1029 | " WHERE tagxref.rid=%d" |
| 1030 | " AND tagxref.tagid=tag.tagid" |
| 1031 | " AND tagxref.tagtype>0" |
| 1032 | " AND tag.tagname GLOB 'sym-*'", |
| 1033 | rid |
| 1034 | ); |
| 1035 | while( db_step(&q)==SQLITE_ROW ){ |
| 1036 | const char *zTagName = db_column_text(&q, 0); |
| 1037 | @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> |
| 1038 | } |
| 1039 | db_finalize(&q); |
| 1040 | } |
| 1041 | |
| 1042 | /* |
| 1043 | ** WEBPAGE: brtimeline |
| 1044 | ** |
| 1045 | ** Show a timeline of all branches |
| 1046 | ** |
| 1047 | ** Query parameters: |
| 1048 | ** |
| 1049 | ** ng No graph |
| 1050 | ** nohidden Hide check-ins with "hidden" tag |
| 1051 | ** onlyhidden Show only check-ins with "hidden" tag |
| 1052 | ** brbg Background color by branch name |
| 1053 | ** ubg Background color by user name |
| 1054 | */ |
| 1055 | void brtimeline_page(void){ |
| 1056 | Blob sql = empty_blob; |
| 1057 | Stmt q; |
| 1058 | int tmFlags; /* Timeline display flags */ |
| @@ -1059,14 +1067,15 @@ | |
| 1059 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1060 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1061 | |
| 1062 | login_check_credentials(); |
| 1063 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1064 | |
| 1065 | style_set_current_feature("branch"); |
| 1066 | style_header("Branches"); |
| 1067 | style_submenu_element("List", "brlist"); |
| 1068 | login_anonymous_available(); |
| 1069 | timeline_ss_submenu(); |
| 1070 | cgi_check_for_malice(); |
| 1071 | @ <h2>The initial check-in for each branch:</h2> |
| 1072 | blob_append(&sql, timeline_query_for_www(), -1); |
| @@ -1083,12 +1092,58 @@ | |
| 1083 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1084 | blob_reset(&sql); |
| 1085 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1086 | ** many descenders to (off-screen) parents. */ |
| 1087 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1088 | if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH; |
| 1089 | if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR; |
| 1090 | if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR; |
| 1091 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra); |
| 1092 | db_finalize(&q); |
| 1093 | style_finish_page(); |
| 1094 | } |
| 1095 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -843,10 +843,11 @@ | |
| 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | style_set_current_feature("branch"); |
| 845 | style_header("Branches"); |
| 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | |
| 849 | login_anonymous_available(); |
| 850 | |
| 851 | brlist_create_temp_table(); |
| 852 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 853 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1019,40 +1020,47 @@ | |
| 1020 | /* |
| 1021 | ** This routine is called while for each check-in that is rendered by |
| 1022 | ** the timeline of a "brlist" page. Add some additional hyperlinks |
| 1023 | ** to the end of the line. |
| 1024 | */ |
| 1025 | static void brtimeline_extra( |
| 1026 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1027 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1028 | const char *zThisUser, /* Suppress links to this user */ |
| 1029 | const char *zThisTag /* Suppress links to this tag */ |
| 1030 | ){ |
| 1031 | int rid; |
| 1032 | int tmFlagsNew; |
| 1033 | char *zBrName; |
| 1034 | |
| 1035 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 1036 | tmFlagsNew = (tmFlags & ~TIMELINE_VIEWS) | TIMELINE_MODERN; |
| 1037 | cgi_printf("("); |
| 1038 | }else{ |
| 1039 | tmFlagsNew = tmFlags; |
| 1040 | } |
| 1041 | timeline_extra(pQuery,tmFlagsNew,zThisUser,zThisTag); |
| 1042 | |
| 1043 | if( !g.perm.Hyperlink ) return; |
| 1044 | rid = db_column_int(pQuery,0); |
| 1045 | zBrName = branch_of_rid(rid); |
| 1046 | @ branch: <span class='timelineHash'>\ |
| 1047 | @ %z(href("%R/timeline?r=%T",zBrName))%h(zBrName)</a></span> |
| 1048 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 1049 | cgi_printf(")"); |
| 1050 | } |
| 1051 | } |
| 1052 | |
| 1053 | /* |
| 1054 | ** WEBPAGE: brtimeline |
| 1055 | ** |
| 1056 | ** List the first check of every branch, starting with the most recent |
| 1057 | ** and going backwards in time. |
| 1058 | ** |
| 1059 | ** Query parameters: |
| 1060 | ** |
| 1061 | ** ubg Color the graph by user, not by branch. |
| 1062 | */ |
| 1063 | void brtimeline_page(void){ |
| 1064 | Blob sql = empty_blob; |
| 1065 | Stmt q; |
| 1066 | int tmFlags; /* Timeline display flags */ |
| @@ -1059,14 +1067,15 @@ | |
| 1067 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1068 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1069 | |
| 1070 | login_check_credentials(); |
| 1071 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1072 | if( robot_restrict("timelineX") ) return; |
| 1073 | |
| 1074 | style_set_current_feature("branch"); |
| 1075 | style_header("Branches"); |
| 1076 | style_submenu_element("Branch List", "brlist"); |
| 1077 | login_anonymous_available(); |
| 1078 | timeline_ss_submenu(); |
| 1079 | cgi_check_for_malice(); |
| 1080 | @ <h2>The initial check-in for each branch:</h2> |
| 1081 | blob_append(&sql, timeline_query_for_www(), -1); |
| @@ -1083,12 +1092,58 @@ | |
| 1092 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1093 | blob_reset(&sql); |
| 1094 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1095 | ** many descenders to (off-screen) parents. */ |
| 1096 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1097 | if( PB("ubg")!=0 ){ |
| 1098 | tmFlags |= TIMELINE_UCOLOR; |
| 1099 | }else{ |
| 1100 | tmFlags |= TIMELINE_BRCOLOR; |
| 1101 | } |
| 1102 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra); |
| 1103 | db_finalize(&q); |
| 1104 | style_finish_page(); |
| 1105 | } |
| 1106 | |
| 1107 | /* |
| 1108 | ** Generate a multichoice submenu for the few recent active branches. zName is |
| 1109 | ** the query parameter used to select the current checkin. zCI is optional and |
| 1110 | ** represent the currently selected checkin, so if it is a checkin hash |
| 1111 | ** instead of a branch, it can be part of the multichoice menu. |
| 1112 | */ |
| 1113 | void generate_branch_submenu_multichoice( |
| 1114 | const char* zName, /* Query parameter name */ |
| 1115 | const char* zCI /* Current checkin */ |
| 1116 | ){ |
| 1117 | Stmt q; |
| 1118 | const int brFlags = BRL_ORDERBY_MTIME | BRL_OPEN_ONLY; |
| 1119 | static const char *zBranchMenuList[32*2]; /* 2 per entries */ |
| 1120 | const int nLimit = count(zBranchMenuList)/2; |
| 1121 | int i = 0; |
| 1122 | |
| 1123 | if( zName == 0 ) zName = "ci"; |
| 1124 | |
| 1125 | branch_prepare_list_query(&q, brFlags, 0, nLimit, 0); |
| 1126 | zBranchMenuList[i++] = ""; |
| 1127 | zBranchMenuList[i++] = "All Checkins"; |
| 1128 | |
| 1129 | if( zCI ){ |
| 1130 | zCI = fossil_strdup(zCI); |
| 1131 | zBranchMenuList[i++] = zCI; |
| 1132 | zBranchMenuList[i++] = zCI; |
| 1133 | } |
| 1134 | /* If current checkin is not "tip", add it to the list */ |
| 1135 | if( zCI==0 || strcmp(zCI, "tip") ){ |
| 1136 | zBranchMenuList[i++] = "tip"; |
| 1137 | zBranchMenuList[i++] = "tip"; |
| 1138 | } |
| 1139 | while( i/2 < nLimit && db_step(&q)==SQLITE_ROW ){ |
| 1140 | const char* zBr = fossil_strdup(db_column_text(&q, 0)); |
| 1141 | /* zCI is already in the list, don't add it twice */ |
| 1142 | if( zCI==0 || strcmp(zBr, zCI) ){ |
| 1143 | zBranchMenuList[i++] = zBr; |
| 1144 | zBranchMenuList[i++] = zBr; |
| 1145 | } |
| 1146 | } |
| 1147 | db_finalize(&q); |
| 1148 | style_submenu_multichoice(zName, i/2, zBranchMenuList, 0); |
| 1149 | } |
| 1150 |
+24
-31
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -171,12 +171,10 @@ | ||
| 171 | 171 | const char *zCI = P("ci"); |
| 172 | 172 | int rid = 0; |
| 173 | 173 | char *zUuid = 0; |
| 174 | 174 | Manifest *pM = 0; |
| 175 | 175 | const char *zSubdirLink; |
| 176 | - int linkTrunk = 1; | |
| 177 | - int linkTip = 1; | |
| 178 | 176 | HQuery sURI; |
| 179 | 177 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 180 | 178 | int isBranchCI = 0; /* True if ci= refers to a branch name */ |
| 181 | 179 | char *zHeader = 0; |
| 182 | 180 | const char *zRegexp; /* The re= query parameter */ |
| @@ -198,13 +196,10 @@ | ||
| 198 | 196 | */ |
| 199 | 197 | if( bDocDir && zCI==0 ) zCI = "trunk"; |
| 200 | 198 | if( zCI ){ |
| 201 | 199 | pM = manifest_get_by_name(zCI, &rid); |
| 202 | 200 | if( pM ){ |
| 203 | - int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); | |
| 204 | - linkTrunk = trunkRid && rid != trunkRid; | |
| 205 | - linkTip = rid != symbolic_name_to_rid("tip", "ci"); | |
| 206 | 201 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | 202 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | 203 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | 204 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | 205 | Th_StoreUnsafe("current_checkin", zCI); |
| @@ -234,10 +229,13 @@ | ||
| 234 | 229 | }else{ |
| 235 | 230 | zMatch = ""; |
| 236 | 231 | } |
| 237 | 232 | style_header("%s", zHeader); |
| 238 | 233 | fossil_free(zHeader); |
| 234 | + if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){ | |
| 235 | + style_submenu_element("Download","%R/rchvdwnld/%!S",zUuid); | |
| 236 | + } | |
| 239 | 237 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 240 | 238 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 241 | 239 | pathelementFunc, 0, 0); |
| 242 | 240 | url_initialize(&sURI, "dir"); |
| 243 | 241 | cgi_check_for_malice(); |
| @@ -283,25 +281,22 @@ | ||
| 283 | 281 | } |
| 284 | 282 | }else{ |
| 285 | 283 | @ in any check-in</h2> |
| 286 | 284 | zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); |
| 287 | 285 | } |
| 288 | - if( linkTrunk && !bDocDir ){ | |
| 289 | - style_submenu_element("Trunk", "%s", | |
| 290 | - url_render(&sURI, "ci", "trunk", 0, 0)); | |
| 291 | - } | |
| 292 | - if( linkTip && !bDocDir ){ | |
| 293 | - style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); | |
| 294 | - } | |
| 295 | 286 | if( zD && !bDocDir ){ |
| 296 | 287 | style_submenu_element("History","%R/timeline?chng=%T/*", zD); |
| 297 | 288 | } |
| 298 | 289 | if( !bDocDir ){ |
| 299 | - style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); | |
| 300 | 290 | style_submenu_element("Tree-View", "%s", |
| 301 | 291 | url_render(&sURI, "type", "tree", 0, 0)); |
| 302 | 292 | } |
| 293 | + | |
| 294 | + if( !bDocDir ){ | |
| 295 | + /* Generate the Branch list submenu */ | |
| 296 | + generate_branch_submenu_multichoice("ci", zCI); | |
| 297 | + } | |
| 303 | 298 | |
| 304 | 299 | /* Compute the temporary table "localfiles" containing the names |
| 305 | 300 | ** of all files and subdirectories in the zD[] directory. |
| 306 | 301 | ** |
| 307 | 302 | ** Subdirectory names begin with "/". This causes them to sort |
| @@ -705,12 +700,10 @@ | ||
| 705 | 700 | Manifest *pM = 0; |
| 706 | 701 | double rNow = 0; |
| 707 | 702 | char *zNow = 0; |
| 708 | 703 | int useMtime = atoi(PD("mtime","0")); |
| 709 | 704 | int sortOrder = atoi(PD("sort",useMtime?"1":"0")); |
| 710 | - int linkTrunk = 1; /* include link to "trunk" */ | |
| 711 | - int linkTip = 1; /* include link to "tip" */ | |
| 712 | 705 | const char *zRE; /* the value for the re=REGEXP query parameter */ |
| 713 | 706 | const char *zObjType; /* "files" by default or "folders" for "nofiles" */ |
| 714 | 707 | char *zREx = ""; /* Extra parameters for path hyperlinks */ |
| 715 | 708 | ReCompiled *pRE = 0; /* Compiled regular expression */ |
| 716 | 709 | FileTreeNode *p; /* One line of the tree */ |
| @@ -747,11 +740,11 @@ | ||
| 747 | 740 | } |
| 748 | 741 | |
| 749 | 742 | /* If a regular expression is specified, compile it */ |
| 750 | 743 | zRE = P("re"); |
| 751 | 744 | if( zRE ){ |
| 752 | - re_compile(&pRE, zRE, 0); | |
| 745 | + fossil_re_compile(&pRE, zRE, 0); | |
| 753 | 746 | zREx = mprintf("&re=%T", zRE); |
| 754 | 747 | } |
| 755 | 748 | cgi_check_for_malice(); |
| 756 | 749 | |
| 757 | 750 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| @@ -762,13 +755,10 @@ | ||
| 762 | 755 | ** files from all check-ins to be displayed. |
| 763 | 756 | */ |
| 764 | 757 | if( zCI ){ |
| 765 | 758 | pM = manifest_get_by_name(zCI, &rid); |
| 766 | 759 | if( pM ){ |
| 767 | - int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); | |
| 768 | - linkTrunk = trunkRid && rid != trunkRid; | |
| 769 | - linkTip = rid != symbolic_name_to_rid("tip", "ci"); | |
| 770 | 760 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 771 | 761 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | 762 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | 763 | " FROM event WHERE objid=%d", rid); |
| 774 | 764 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| @@ -781,10 +771,13 @@ | ||
| 781 | 771 | if( zCI==0 ){ |
| 782 | 772 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 783 | 773 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 784 | 774 | } |
| 785 | 775 | |
| 776 | + /* Generate the Branch list submenu */ | |
| 777 | + generate_branch_submenu_multichoice("ci", zCI); | |
| 778 | + | |
| 786 | 779 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 787 | 780 | if( zD==0 ){ |
| 788 | 781 | if( zCI ){ |
| 789 | 782 | zHeader = mprintf("Top-level Files of %s", zCI); |
| 790 | 783 | }else{ |
| @@ -818,24 +811,19 @@ | ||
| 818 | 811 | "2", "Sort By Size" |
| 819 | 812 | }; |
| 820 | 813 | style_submenu_multichoice("sort", 3, sort_orders, 0); |
| 821 | 814 | } |
| 822 | 815 | if( zCI ){ |
| 823 | - style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); | |
| 824 | 816 | if( nD==0 && !showDirOnly ){ |
| 825 | 817 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 826 | 818 | } |
| 827 | 819 | } |
| 828 | - if( linkTrunk ){ | |
| 829 | - style_submenu_element("Trunk", "%s", | |
| 830 | - url_render(&sURI, "ci", "trunk", 0, 0)); | |
| 831 | - } | |
| 832 | - if( linkTip ){ | |
| 833 | - style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); | |
| 834 | - } | |
| 835 | 820 | style_submenu_element("Flat-View", "%s", |
| 836 | 821 | url_render(&sURI, "type", "flat", 0, 0)); |
| 822 | + if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){ | |
| 823 | + style_submenu_element("Download","%R/rchvdwnld/%!S", zUuid); | |
| 824 | + } | |
| 837 | 825 | |
| 838 | 826 | /* Compute the file hierarchy. |
| 839 | 827 | */ |
| 840 | 828 | if( zCI ){ |
| 841 | 829 | Stmt q; |
| @@ -1038,11 +1026,12 @@ | ||
| 1038 | 1026 | @ CREATE TABLE IF NOT EXISTS temp.fileage( |
| 1039 | 1027 | @ fnid INTEGER PRIMARY KEY, |
| 1040 | 1028 | @ fid INTEGER, |
| 1041 | 1029 | @ mid INTEGER, |
| 1042 | 1030 | @ mtime DATETIME, |
| 1043 | -@ pathname TEXT | |
| 1031 | +@ pathname TEXT, | |
| 1032 | +@ uuid TEXT | |
| 1044 | 1033 | @ ); |
| 1045 | 1034 | @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; |
| 1046 | 1035 | ; |
| 1047 | 1036 | |
| 1048 | 1037 | static const char zComputeFileAgeRun[] = |
| @@ -1050,12 +1039,13 @@ | ||
| 1050 | 1039 | @ ckin(x) AS (VALUES(:ckin) |
| 1051 | 1040 | @ UNION |
| 1052 | 1041 | @ SELECT plink.pid |
| 1053 | 1042 | @ FROM ckin, plink |
| 1054 | 1043 | @ WHERE plink.cid=ckin.x) |
| 1055 | -@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname) | |
| 1056 | -@ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name | |
| 1044 | +@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname, uuid) | |
| 1045 | +@ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name, | |
| 1046 | +@ foci.uuid | |
| 1057 | 1047 | @ FROM foci, filename, blob, mlink, event |
| 1058 | 1048 | @ WHERE foci.checkinID=:ckin |
| 1059 | 1049 | @ AND foci.filename GLOB :glob |
| 1060 | 1050 | @ AND filename.name=foci.filename |
| 1061 | 1051 | @ AND blob.uuid=foci.uuid |
| @@ -1162,11 +1152,10 @@ | ||
| 1162 | 1152 | int showId = PB("showid"); |
| 1163 | 1153 | Stmt q1, q2; |
| 1164 | 1154 | double baseTime; |
| 1165 | 1155 | login_check_credentials(); |
| 1166 | 1156 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1167 | - if( exclude_spiders(0) ) return; | |
| 1168 | 1157 | zName = P("name"); |
| 1169 | 1158 | if( zName==0 ) zName = "tip"; |
| 1170 | 1159 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1171 | 1160 | if( rid==0 ){ |
| 1172 | 1161 | fossil_fatal("not a valid check-in: %s", zName); |
| @@ -1175,10 +1164,14 @@ | ||
| 1175 | 1164 | isBranchCI = branch_includes_uuid(zName,zUuid); |
| 1176 | 1165 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1177 | 1166 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1178 | 1167 | " WHERE objid=%d", rid); |
| 1179 | 1168 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1169 | + | |
| 1170 | + /* Generate the Branch list submenu */ | |
| 1171 | + generate_branch_submenu_multichoice("name", zName); | |
| 1172 | + | |
| 1180 | 1173 | style_header("File Ages"); |
| 1181 | 1174 | zGlob = P("glob"); |
| 1182 | 1175 | cgi_check_for_malice(); |
| 1183 | 1176 | compute_fileage(rid,zGlob); |
| 1184 | 1177 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1185 | 1178 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -171,12 +171,10 @@ | |
| 171 | const char *zCI = P("ci"); |
| 172 | int rid = 0; |
| 173 | char *zUuid = 0; |
| 174 | Manifest *pM = 0; |
| 175 | const char *zSubdirLink; |
| 176 | int linkTrunk = 1; |
| 177 | int linkTip = 1; |
| 178 | HQuery sURI; |
| 179 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 180 | int isBranchCI = 0; /* True if ci= refers to a branch name */ |
| 181 | char *zHeader = 0; |
| 182 | const char *zRegexp; /* The re= query parameter */ |
| @@ -198,13 +196,10 @@ | |
| 198 | */ |
| 199 | if( bDocDir && zCI==0 ) zCI = "trunk"; |
| 200 | if( zCI ){ |
| 201 | pM = manifest_get_by_name(zCI, &rid); |
| 202 | if( pM ){ |
| 203 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 204 | linkTrunk = trunkRid && rid != trunkRid; |
| 205 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 206 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | Th_StoreUnsafe("current_checkin", zCI); |
| @@ -234,10 +229,13 @@ | |
| 234 | }else{ |
| 235 | zMatch = ""; |
| 236 | } |
| 237 | style_header("%s", zHeader); |
| 238 | fossil_free(zHeader); |
| 239 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 240 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 241 | pathelementFunc, 0, 0); |
| 242 | url_initialize(&sURI, "dir"); |
| 243 | cgi_check_for_malice(); |
| @@ -283,25 +281,22 @@ | |
| 283 | } |
| 284 | }else{ |
| 285 | @ in any check-in</h2> |
| 286 | zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); |
| 287 | } |
| 288 | if( linkTrunk && !bDocDir ){ |
| 289 | style_submenu_element("Trunk", "%s", |
| 290 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| 291 | } |
| 292 | if( linkTip && !bDocDir ){ |
| 293 | style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); |
| 294 | } |
| 295 | if( zD && !bDocDir ){ |
| 296 | style_submenu_element("History","%R/timeline?chng=%T/*", zD); |
| 297 | } |
| 298 | if( !bDocDir ){ |
| 299 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 300 | style_submenu_element("Tree-View", "%s", |
| 301 | url_render(&sURI, "type", "tree", 0, 0)); |
| 302 | } |
| 303 | |
| 304 | /* Compute the temporary table "localfiles" containing the names |
| 305 | ** of all files and subdirectories in the zD[] directory. |
| 306 | ** |
| 307 | ** Subdirectory names begin with "/". This causes them to sort |
| @@ -705,12 +700,10 @@ | |
| 705 | Manifest *pM = 0; |
| 706 | double rNow = 0; |
| 707 | char *zNow = 0; |
| 708 | int useMtime = atoi(PD("mtime","0")); |
| 709 | int sortOrder = atoi(PD("sort",useMtime?"1":"0")); |
| 710 | int linkTrunk = 1; /* include link to "trunk" */ |
| 711 | int linkTip = 1; /* include link to "tip" */ |
| 712 | const char *zRE; /* the value for the re=REGEXP query parameter */ |
| 713 | const char *zObjType; /* "files" by default or "folders" for "nofiles" */ |
| 714 | char *zREx = ""; /* Extra parameters for path hyperlinks */ |
| 715 | ReCompiled *pRE = 0; /* Compiled regular expression */ |
| 716 | FileTreeNode *p; /* One line of the tree */ |
| @@ -747,11 +740,11 @@ | |
| 747 | } |
| 748 | |
| 749 | /* If a regular expression is specified, compile it */ |
| 750 | zRE = P("re"); |
| 751 | if( zRE ){ |
| 752 | re_compile(&pRE, zRE, 0); |
| 753 | zREx = mprintf("&re=%T", zRE); |
| 754 | } |
| 755 | cgi_check_for_malice(); |
| 756 | |
| 757 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| @@ -762,13 +755,10 @@ | |
| 762 | ** files from all check-ins to be displayed. |
| 763 | */ |
| 764 | if( zCI ){ |
| 765 | pM = manifest_get_by_name(zCI, &rid); |
| 766 | if( pM ){ |
| 767 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 768 | linkTrunk = trunkRid && rid != trunkRid; |
| 769 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 770 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 771 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | " FROM event WHERE objid=%d", rid); |
| 774 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| @@ -781,10 +771,13 @@ | |
| 781 | if( zCI==0 ){ |
| 782 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 783 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 784 | } |
| 785 | |
| 786 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 787 | if( zD==0 ){ |
| 788 | if( zCI ){ |
| 789 | zHeader = mprintf("Top-level Files of %s", zCI); |
| 790 | }else{ |
| @@ -818,24 +811,19 @@ | |
| 818 | "2", "Sort By Size" |
| 819 | }; |
| 820 | style_submenu_multichoice("sort", 3, sort_orders, 0); |
| 821 | } |
| 822 | if( zCI ){ |
| 823 | style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); |
| 824 | if( nD==0 && !showDirOnly ){ |
| 825 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 826 | } |
| 827 | } |
| 828 | if( linkTrunk ){ |
| 829 | style_submenu_element("Trunk", "%s", |
| 830 | url_render(&sURI, "ci", "trunk", 0, 0)); |
| 831 | } |
| 832 | if( linkTip ){ |
| 833 | style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); |
| 834 | } |
| 835 | style_submenu_element("Flat-View", "%s", |
| 836 | url_render(&sURI, "type", "flat", 0, 0)); |
| 837 | |
| 838 | /* Compute the file hierarchy. |
| 839 | */ |
| 840 | if( zCI ){ |
| 841 | Stmt q; |
| @@ -1038,11 +1026,12 @@ | |
| 1038 | @ CREATE TABLE IF NOT EXISTS temp.fileage( |
| 1039 | @ fnid INTEGER PRIMARY KEY, |
| 1040 | @ fid INTEGER, |
| 1041 | @ mid INTEGER, |
| 1042 | @ mtime DATETIME, |
| 1043 | @ pathname TEXT |
| 1044 | @ ); |
| 1045 | @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; |
| 1046 | ; |
| 1047 | |
| 1048 | static const char zComputeFileAgeRun[] = |
| @@ -1050,12 +1039,13 @@ | |
| 1050 | @ ckin(x) AS (VALUES(:ckin) |
| 1051 | @ UNION |
| 1052 | @ SELECT plink.pid |
| 1053 | @ FROM ckin, plink |
| 1054 | @ WHERE plink.cid=ckin.x) |
| 1055 | @ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname) |
| 1056 | @ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name |
| 1057 | @ FROM foci, filename, blob, mlink, event |
| 1058 | @ WHERE foci.checkinID=:ckin |
| 1059 | @ AND foci.filename GLOB :glob |
| 1060 | @ AND filename.name=foci.filename |
| 1061 | @ AND blob.uuid=foci.uuid |
| @@ -1162,11 +1152,10 @@ | |
| 1162 | int showId = PB("showid"); |
| 1163 | Stmt q1, q2; |
| 1164 | double baseTime; |
| 1165 | login_check_credentials(); |
| 1166 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1167 | if( exclude_spiders(0) ) return; |
| 1168 | zName = P("name"); |
| 1169 | if( zName==0 ) zName = "tip"; |
| 1170 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1171 | if( rid==0 ){ |
| 1172 | fossil_fatal("not a valid check-in: %s", zName); |
| @@ -1175,10 +1164,14 @@ | |
| 1175 | isBranchCI = branch_includes_uuid(zName,zUuid); |
| 1176 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1177 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1178 | " WHERE objid=%d", rid); |
| 1179 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1180 | style_header("File Ages"); |
| 1181 | zGlob = P("glob"); |
| 1182 | cgi_check_for_malice(); |
| 1183 | compute_fileage(rid,zGlob); |
| 1184 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1185 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -171,12 +171,10 @@ | |
| 171 | const char *zCI = P("ci"); |
| 172 | int rid = 0; |
| 173 | char *zUuid = 0; |
| 174 | Manifest *pM = 0; |
| 175 | const char *zSubdirLink; |
| 176 | HQuery sURI; |
| 177 | int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ |
| 178 | int isBranchCI = 0; /* True if ci= refers to a branch name */ |
| 179 | char *zHeader = 0; |
| 180 | const char *zRegexp; /* The re= query parameter */ |
| @@ -198,13 +196,10 @@ | |
| 196 | */ |
| 197 | if( bDocDir && zCI==0 ) zCI = "trunk"; |
| 198 | if( zCI ){ |
| 199 | pM = manifest_get_by_name(zCI, &rid); |
| 200 | if( pM ){ |
| 201 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 202 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 203 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 204 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 205 | Th_StoreUnsafe("current_checkin", zCI); |
| @@ -234,10 +229,13 @@ | |
| 229 | }else{ |
| 230 | zMatch = ""; |
| 231 | } |
| 232 | style_header("%s", zHeader); |
| 233 | fossil_free(zHeader); |
| 234 | if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){ |
| 235 | style_submenu_element("Download","%R/rchvdwnld/%!S",zUuid); |
| 236 | } |
| 237 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 238 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 239 | pathelementFunc, 0, 0); |
| 240 | url_initialize(&sURI, "dir"); |
| 241 | cgi_check_for_malice(); |
| @@ -283,25 +281,22 @@ | |
| 281 | } |
| 282 | }else{ |
| 283 | @ in any check-in</h2> |
| 284 | zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); |
| 285 | } |
| 286 | if( zD && !bDocDir ){ |
| 287 | style_submenu_element("History","%R/timeline?chng=%T/*", zD); |
| 288 | } |
| 289 | if( !bDocDir ){ |
| 290 | style_submenu_element("Tree-View", "%s", |
| 291 | url_render(&sURI, "type", "tree", 0, 0)); |
| 292 | } |
| 293 | |
| 294 | if( !bDocDir ){ |
| 295 | /* Generate the Branch list submenu */ |
| 296 | generate_branch_submenu_multichoice("ci", zCI); |
| 297 | } |
| 298 | |
| 299 | /* Compute the temporary table "localfiles" containing the names |
| 300 | ** of all files and subdirectories in the zD[] directory. |
| 301 | ** |
| 302 | ** Subdirectory names begin with "/". This causes them to sort |
| @@ -705,12 +700,10 @@ | |
| 700 | Manifest *pM = 0; |
| 701 | double rNow = 0; |
| 702 | char *zNow = 0; |
| 703 | int useMtime = atoi(PD("mtime","0")); |
| 704 | int sortOrder = atoi(PD("sort",useMtime?"1":"0")); |
| 705 | const char *zRE; /* the value for the re=REGEXP query parameter */ |
| 706 | const char *zObjType; /* "files" by default or "folders" for "nofiles" */ |
| 707 | char *zREx = ""; /* Extra parameters for path hyperlinks */ |
| 708 | ReCompiled *pRE = 0; /* Compiled regular expression */ |
| 709 | FileTreeNode *p; /* One line of the tree */ |
| @@ -747,11 +740,11 @@ | |
| 740 | } |
| 741 | |
| 742 | /* If a regular expression is specified, compile it */ |
| 743 | zRE = P("re"); |
| 744 | if( zRE ){ |
| 745 | fossil_re_compile(&pRE, zRE, 0); |
| 746 | zREx = mprintf("&re=%T", zRE); |
| 747 | } |
| 748 | cgi_check_for_malice(); |
| 749 | |
| 750 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| @@ -762,13 +755,10 @@ | |
| 755 | ** files from all check-ins to be displayed. |
| 756 | */ |
| 757 | if( zCI ){ |
| 758 | pM = manifest_get_by_name(zCI, &rid); |
| 759 | if( pM ){ |
| 760 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 761 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 762 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 763 | " FROM event WHERE objid=%d", rid); |
| 764 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| @@ -781,10 +771,13 @@ | |
| 771 | if( zCI==0 ){ |
| 772 | rNow = db_double(0.0, "SELECT max(mtime) FROM event"); |
| 773 | zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); |
| 774 | } |
| 775 | |
| 776 | /* Generate the Branch list submenu */ |
| 777 | generate_branch_submenu_multichoice("ci", zCI); |
| 778 | |
| 779 | assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); |
| 780 | if( zD==0 ){ |
| 781 | if( zCI ){ |
| 782 | zHeader = mprintf("Top-level Files of %s", zCI); |
| 783 | }else{ |
| @@ -818,24 +811,19 @@ | |
| 811 | "2", "Sort By Size" |
| 812 | }; |
| 813 | style_submenu_multichoice("sort", 3, sort_orders, 0); |
| 814 | } |
| 815 | if( zCI ){ |
| 816 | if( nD==0 && !showDirOnly ){ |
| 817 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 818 | } |
| 819 | } |
| 820 | style_submenu_element("Flat-View", "%s", |
| 821 | url_render(&sURI, "type", "flat", 0, 0)); |
| 822 | if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){ |
| 823 | style_submenu_element("Download","%R/rchvdwnld/%!S", zUuid); |
| 824 | } |
| 825 | |
| 826 | /* Compute the file hierarchy. |
| 827 | */ |
| 828 | if( zCI ){ |
| 829 | Stmt q; |
| @@ -1038,11 +1026,12 @@ | |
| 1026 | @ CREATE TABLE IF NOT EXISTS temp.fileage( |
| 1027 | @ fnid INTEGER PRIMARY KEY, |
| 1028 | @ fid INTEGER, |
| 1029 | @ mid INTEGER, |
| 1030 | @ mtime DATETIME, |
| 1031 | @ pathname TEXT, |
| 1032 | @ uuid TEXT |
| 1033 | @ ); |
| 1034 | @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; |
| 1035 | ; |
| 1036 | |
| 1037 | static const char zComputeFileAgeRun[] = |
| @@ -1050,12 +1039,13 @@ | |
| 1039 | @ ckin(x) AS (VALUES(:ckin) |
| 1040 | @ UNION |
| 1041 | @ SELECT plink.pid |
| 1042 | @ FROM ckin, plink |
| 1043 | @ WHERE plink.cid=ckin.x) |
| 1044 | @ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname, uuid) |
| 1045 | @ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name, |
| 1046 | @ foci.uuid |
| 1047 | @ FROM foci, filename, blob, mlink, event |
| 1048 | @ WHERE foci.checkinID=:ckin |
| 1049 | @ AND foci.filename GLOB :glob |
| 1050 | @ AND filename.name=foci.filename |
| 1051 | @ AND blob.uuid=foci.uuid |
| @@ -1162,11 +1152,10 @@ | |
| 1152 | int showId = PB("showid"); |
| 1153 | Stmt q1, q2; |
| 1154 | double baseTime; |
| 1155 | login_check_credentials(); |
| 1156 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1157 | zName = P("name"); |
| 1158 | if( zName==0 ) zName = "tip"; |
| 1159 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1160 | if( rid==0 ){ |
| 1161 | fossil_fatal("not a valid check-in: %s", zName); |
| @@ -1175,10 +1164,14 @@ | |
| 1164 | isBranchCI = branch_includes_uuid(zName,zUuid); |
| 1165 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1166 | zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" |
| 1167 | " WHERE objid=%d", rid); |
| 1168 | style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); |
| 1169 | |
| 1170 | /* Generate the Branch list submenu */ |
| 1171 | generate_branch_submenu_multichoice("name", zName); |
| 1172 | |
| 1173 | style_header("File Ages"); |
| 1174 | zGlob = P("glob"); |
| 1175 | cgi_check_for_malice(); |
| 1176 | compute_fileage(rid,zGlob); |
| 1177 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1178 |
+12
-8
| --- src/captcha.c | ||
| +++ src/captcha.c | ||
| @@ -744,11 +744,11 @@ | ||
| 744 | 744 | (void)exclude_spiders(1); |
| 745 | 745 | @ <hr><p>The captcha is shown above. Add a name=HEX query parameter |
| 746 | 746 | @ to see how HEX would be rendered in the current captcha font. |
| 747 | 747 | @ <h2>Debug/Testing Values:</h2> |
| 748 | 748 | @ <ul> |
| 749 | - @ <li> g.isHuman = %d(g.isHuman) | |
| 749 | + @ <li> g.isRobot = %d(g.isRobot) | |
| 750 | 750 | @ <li> g.zLogin = %h(g.zLogin) |
| 751 | 751 | @ <li> login_cookie_welformed() = %d(login_cookie_wellformed()) |
| 752 | 752 | @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)). |
| 753 | 753 | @ </ul> |
| 754 | 754 | style_finish_page(); |
| @@ -759,10 +759,18 @@ | ||
| 759 | 759 | @ %s(captcha_render(zPw)) |
| 760 | 760 | @ </pre> |
| 761 | 761 | style_finish_page(); |
| 762 | 762 | } |
| 763 | 763 | } |
| 764 | + | |
| 765 | +/* | |
| 766 | +** WEBPAGE: honeypot | |
| 767 | +** This page is a honeypot for spiders and bots. | |
| 768 | +*/ | |
| 769 | +void honeypot_page(void){ | |
| 770 | + (void)exclude_spiders(0); | |
| 771 | +} | |
| 764 | 772 | |
| 765 | 773 | /* |
| 766 | 774 | ** Check to see if the current request is coming from an agent that |
| 767 | 775 | ** self-identifies as a spider. |
| 768 | 776 | ** |
| @@ -776,27 +784,23 @@ | ||
| 776 | 784 | ** If the bTest argument is non-zero, then show the captcha regardless of |
| 777 | 785 | ** how the agent identifies. This is used for testing only. |
| 778 | 786 | */ |
| 779 | 787 | int exclude_spiders(int bTest){ |
| 780 | 788 | if( !bTest ){ |
| 781 | - if( g.isHuman ) return 0; /* This user has already proven human */ | |
| 782 | 789 | if( g.zLogin!=0 ) return 0; /* Logged in. Consider them human */ |
| 783 | 790 | if( login_cookie_wellformed() ){ |
| 784 | 791 | /* Logged into another member of the login group */ |
| 785 | 792 | return 0; |
| 786 | 793 | } |
| 787 | 794 | } |
| 788 | 795 | |
| 789 | 796 | /* This appears to be a spider. Offer the captcha */ |
| 790 | 797 | style_set_current_feature("captcha"); |
| 791 | - style_header("I think you are a robot"); | |
| 798 | + style_header("Captcha"); | |
| 792 | 799 | style_submenu_enable(0); |
| 793 | 800 | @ <form method='POST' action='%R/ityaar'> |
| 794 | - @ <p>You seem like a robot. | |
| 795 | - @ | |
| 796 | - @ <p>If you are human, you can prove that by solving the captcha below, | |
| 797 | - @ after which you will be allowed to proceed. | |
| 801 | + @ <h2>Prove that you are human: | |
| 798 | 802 | if( bTest ){ |
| 799 | 803 | @ <input type="hidden" name="istest" value="1"> |
| 800 | 804 | } |
| 801 | 805 | captcha_generate(3); |
| 802 | 806 | @ </form> |
| @@ -830,11 +834,11 @@ | ||
| 830 | 834 | } |
| 831 | 835 | cgi_append_header("X-Robot: 0\r\n"); |
| 832 | 836 | } |
| 833 | 837 | login_redirect_to_g(); |
| 834 | 838 | }else{ |
| 835 | - g.isHuman = 0; | |
| 839 | + g.isRobot = 1; | |
| 836 | 840 | (void)exclude_spiders(bTest); |
| 837 | 841 | if( bTest ){ |
| 838 | 842 | @ <hr><p>Wrong code. Try again |
| 839 | 843 | style_finish_page(); |
| 840 | 844 | } |
| 841 | 845 |
| --- src/captcha.c | |
| +++ src/captcha.c | |
| @@ -744,11 +744,11 @@ | |
| 744 | (void)exclude_spiders(1); |
| 745 | @ <hr><p>The captcha is shown above. Add a name=HEX query parameter |
| 746 | @ to see how HEX would be rendered in the current captcha font. |
| 747 | @ <h2>Debug/Testing Values:</h2> |
| 748 | @ <ul> |
| 749 | @ <li> g.isHuman = %d(g.isHuman) |
| 750 | @ <li> g.zLogin = %h(g.zLogin) |
| 751 | @ <li> login_cookie_welformed() = %d(login_cookie_wellformed()) |
| 752 | @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)). |
| 753 | @ </ul> |
| 754 | style_finish_page(); |
| @@ -759,10 +759,18 @@ | |
| 759 | @ %s(captcha_render(zPw)) |
| 760 | @ </pre> |
| 761 | style_finish_page(); |
| 762 | } |
| 763 | } |
| 764 | |
| 765 | /* |
| 766 | ** Check to see if the current request is coming from an agent that |
| 767 | ** self-identifies as a spider. |
| 768 | ** |
| @@ -776,27 +784,23 @@ | |
| 776 | ** If the bTest argument is non-zero, then show the captcha regardless of |
| 777 | ** how the agent identifies. This is used for testing only. |
| 778 | */ |
| 779 | int exclude_spiders(int bTest){ |
| 780 | if( !bTest ){ |
| 781 | if( g.isHuman ) return 0; /* This user has already proven human */ |
| 782 | if( g.zLogin!=0 ) return 0; /* Logged in. Consider them human */ |
| 783 | if( login_cookie_wellformed() ){ |
| 784 | /* Logged into another member of the login group */ |
| 785 | return 0; |
| 786 | } |
| 787 | } |
| 788 | |
| 789 | /* This appears to be a spider. Offer the captcha */ |
| 790 | style_set_current_feature("captcha"); |
| 791 | style_header("I think you are a robot"); |
| 792 | style_submenu_enable(0); |
| 793 | @ <form method='POST' action='%R/ityaar'> |
| 794 | @ <p>You seem like a robot. |
| 795 | @ |
| 796 | @ <p>If you are human, you can prove that by solving the captcha below, |
| 797 | @ after which you will be allowed to proceed. |
| 798 | if( bTest ){ |
| 799 | @ <input type="hidden" name="istest" value="1"> |
| 800 | } |
| 801 | captcha_generate(3); |
| 802 | @ </form> |
| @@ -830,11 +834,11 @@ | |
| 830 | } |
| 831 | cgi_append_header("X-Robot: 0\r\n"); |
| 832 | } |
| 833 | login_redirect_to_g(); |
| 834 | }else{ |
| 835 | g.isHuman = 0; |
| 836 | (void)exclude_spiders(bTest); |
| 837 | if( bTest ){ |
| 838 | @ <hr><p>Wrong code. Try again |
| 839 | style_finish_page(); |
| 840 | } |
| 841 |
| --- src/captcha.c | |
| +++ src/captcha.c | |
| @@ -744,11 +744,11 @@ | |
| 744 | (void)exclude_spiders(1); |
| 745 | @ <hr><p>The captcha is shown above. Add a name=HEX query parameter |
| 746 | @ to see how HEX would be rendered in the current captcha font. |
| 747 | @ <h2>Debug/Testing Values:</h2> |
| 748 | @ <ul> |
| 749 | @ <li> g.isRobot = %d(g.isRobot) |
| 750 | @ <li> g.zLogin = %h(g.zLogin) |
| 751 | @ <li> login_cookie_welformed() = %d(login_cookie_wellformed()) |
| 752 | @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)). |
| 753 | @ </ul> |
| 754 | style_finish_page(); |
| @@ -759,10 +759,18 @@ | |
| 759 | @ %s(captcha_render(zPw)) |
| 760 | @ </pre> |
| 761 | style_finish_page(); |
| 762 | } |
| 763 | } |
| 764 | |
| 765 | /* |
| 766 | ** WEBPAGE: honeypot |
| 767 | ** This page is a honeypot for spiders and bots. |
| 768 | */ |
| 769 | void honeypot_page(void){ |
| 770 | (void)exclude_spiders(0); |
| 771 | } |
| 772 | |
| 773 | /* |
| 774 | ** Check to see if the current request is coming from an agent that |
| 775 | ** self-identifies as a spider. |
| 776 | ** |
| @@ -776,27 +784,23 @@ | |
| 784 | ** If the bTest argument is non-zero, then show the captcha regardless of |
| 785 | ** how the agent identifies. This is used for testing only. |
| 786 | */ |
| 787 | int exclude_spiders(int bTest){ |
| 788 | if( !bTest ){ |
| 789 | if( g.zLogin!=0 ) return 0; /* Logged in. Consider them human */ |
| 790 | if( login_cookie_wellformed() ){ |
| 791 | /* Logged into another member of the login group */ |
| 792 | return 0; |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | /* This appears to be a spider. Offer the captcha */ |
| 797 | style_set_current_feature("captcha"); |
| 798 | style_header("Captcha"); |
| 799 | style_submenu_enable(0); |
| 800 | @ <form method='POST' action='%R/ityaar'> |
| 801 | @ <h2>Prove that you are human: |
| 802 | if( bTest ){ |
| 803 | @ <input type="hidden" name="istest" value="1"> |
| 804 | } |
| 805 | captcha_generate(3); |
| 806 | @ </form> |
| @@ -830,11 +834,11 @@ | |
| 834 | } |
| 835 | cgi_append_header("X-Robot: 0\r\n"); |
| 836 | } |
| 837 | login_redirect_to_g(); |
| 838 | }else{ |
| 839 | g.isRobot = 1; |
| 840 | (void)exclude_spiders(bTest); |
| 841 | if( bTest ){ |
| 842 | @ <hr><p>Wrong code. Try again |
| 843 | style_finish_page(); |
| 844 | } |
| 845 |
+82
-36
| --- src/cgi.c | ||
| +++ src/cgi.c | ||
| @@ -241,11 +241,11 @@ | ||
| 241 | 241 | } |
| 242 | 242 | |
| 243 | 243 | /* |
| 244 | 244 | ** Additional information used to form the HTTP reply |
| 245 | 245 | */ |
| 246 | -static const char *zContentType = "text/html"; /* Content type of the reply */ | |
| 246 | +static const char *zReplyMimeType = "text/html"; /* Content type of the reply */ | |
| 247 | 247 | static const char *zReplyStatus = "OK"; /* Reply status description */ |
| 248 | 248 | static int iReplyStatus = 200; /* Reply status code */ |
| 249 | 249 | static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ |
| 250 | 250 | static int rangeStart = 0; /* Start of Range: */ |
| 251 | 251 | static int rangeEnd = 0; /* End of Range: plus 1 */ |
| @@ -256,11 +256,13 @@ | ||
| 256 | 256 | ** The reply content type defaults to "text/html". It only needs to be |
| 257 | 257 | ** changed (by calling this routine) in the exceptional case where some |
| 258 | 258 | ** other content type is being returned. |
| 259 | 259 | */ |
| 260 | 260 | void cgi_set_content_type(const char *zType){ |
| 261 | - zContentType = fossil_strdup(zType); | |
| 261 | + int i; | |
| 262 | + for(i=0; zType[i]>='+' && zType[i]<='z'; i++){} | |
| 263 | + zReplyMimeType = fossil_strndup(zType, i); | |
| 262 | 264 | } |
| 263 | 265 | |
| 264 | 266 | /* |
| 265 | 267 | ** Erase any existing reply content. Replace is with a pNewContent. |
| 266 | 268 | ** |
| @@ -336,14 +338,14 @@ | ||
| 336 | 338 | if( g.fNoHttpCompress ) return 0; |
| 337 | 339 | if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; |
| 338 | 340 | /* Maintenance note: this oddball structure is intended to make |
| 339 | 341 | ** adding new mimetypes to this list less of a performance hit than |
| 340 | 342 | ** doing a strcmp/glob over a growing set of compressible types. */ |
| 341 | - switch(zContentType ? *zContentType : 0){ | |
| 343 | + switch(zReplyMimeType ? *zReplyMimeType : 0){ | |
| 342 | 344 | case (int)'a': |
| 343 | - if(0==fossil_strncmp("application/",zContentType,12)){ | |
| 344 | - const char * z = &zContentType[12]; | |
| 345 | + if(0==fossil_strncmp("application/",zReplyMimeType,12)){ | |
| 346 | + const char * z = &zReplyMimeType[12]; | |
| 345 | 347 | switch(*z){ |
| 346 | 348 | case (int)'j': |
| 347 | 349 | return fossil_strcmp("javascript", z)==0 |
| 348 | 350 | || fossil_strcmp("json", z)==0; |
| 349 | 351 | case (int)'w': return fossil_strcmp("wasm", z)==0; |
| @@ -354,14 +356,14 @@ | ||
| 354 | 356 | return sqlite3_strglob("*xml", z)==0; |
| 355 | 357 | } |
| 356 | 358 | } |
| 357 | 359 | break; |
| 358 | 360 | case (int)'i': |
| 359 | - return fossil_strcmp(zContentType, "image/svg+xml")==0 | |
| 360 | - || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0; | |
| 361 | + return fossil_strcmp(zReplyMimeType, "image/svg+xml")==0 | |
| 362 | + || fossil_strcmp(zReplyMimeType, "image/vnd.microsoft.icon")==0; | |
| 361 | 363 | case (int)'t': |
| 362 | - return fossil_strncmp(zContentType, "text/", 5)==0; | |
| 364 | + return fossil_strncmp(zReplyMimeType, "text/", 5)==0; | |
| 363 | 365 | } |
| 364 | 366 | return 0; |
| 365 | 367 | } |
| 366 | 368 | |
| 367 | 369 | |
| @@ -402,10 +404,12 @@ | ||
| 402 | 404 | } |
| 403 | 405 | #ifdef FOSSIL_ENABLE_SSL |
| 404 | 406 | return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1); |
| 405 | 407 | #else |
| 406 | 408 | fossil_fatal("SSL not available"); |
| 409 | + /* NOT REACHED */ | |
| 410 | + return 0; | |
| 407 | 411 | #endif |
| 408 | 412 | } |
| 409 | 413 | |
| 410 | 414 | /* Works like feof(): |
| 411 | 415 | ** |
| @@ -449,22 +453,22 @@ | ||
| 449 | 453 | } |
| 450 | 454 | |
| 451 | 455 | /* |
| 452 | 456 | ** Given a Content-Type value, returns a string suitable for appending |
| 453 | 457 | ** to the Content-Type header for adding (or not) the "; charset=..." |
| 454 | -** part. It returns an empty string for most types or if zContentType | |
| 458 | +** part. It returns an empty string for most types or if zReplyMimeType | |
| 455 | 459 | ** is NULL. |
| 456 | 460 | ** |
| 457 | 461 | ** See forum post f60dece061c364d1 for the discussions which lead to |
| 458 | 462 | ** this. Previously we always appended the charset, but WASM loaders |
| 459 | 463 | ** are pedantic and refuse to load any responses which have a |
| 460 | 464 | ** charset. Also, adding a charset is not strictly appropriate for |
| 461 | 465 | ** most types (and not required for many others which may ostensibly |
| 462 | 466 | ** benefit from one, as detailed in that forum post). |
| 463 | 467 | */ |
| 464 | -static const char * content_type_charset(const char *zContentType){ | |
| 465 | - if(0==fossil_strncmp(zContentType,"text/",5)){ | |
| 468 | +static const char * content_type_charset(const char *zReplyMimeType){ | |
| 469 | + if(0==fossil_strncmp(zReplyMimeType,"text/",5)){ | |
| 466 | 470 | return "; charset=utf-8"; |
| 467 | 471 | } |
| 468 | 472 | return ""; |
| 469 | 473 | } |
| 470 | 474 | |
| @@ -500,11 +504,11 @@ | ||
| 500 | 504 | assert( rangeEnd==0 ); |
| 501 | 505 | blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); |
| 502 | 506 | } |
| 503 | 507 | if( etag_tag()[0]!=0 |
| 504 | 508 | && iReplyStatus==200 |
| 505 | - && strcmp(zContentType,"text/html")!=0 | |
| 509 | + && strcmp(zReplyMimeType,"text/html")!=0 | |
| 506 | 510 | ){ |
| 507 | 511 | /* Do not cache HTML replies as those will have been generated and |
| 508 | 512 | ** will likely, therefore, contains a nonce and we want that nonce to |
| 509 | 513 | ** be different every time. */ |
| 510 | 514 | blob_appendf(&hdr, "ETag: \"%s\"\r\n", etag_tag()); |
| @@ -542,13 +546,13 @@ | ||
| 542 | 546 | ** These headers are probably best added by the web server hosting fossil as |
| 543 | 547 | ** a CGI script. |
| 544 | 548 | */ |
| 545 | 549 | |
| 546 | 550 | if( iReplyStatus!=304 ) { |
| 547 | - blob_appendf(&hdr, "Content-Type: %s%s\r\n", zContentType, | |
| 548 | - content_type_charset(zContentType)); | |
| 549 | - if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ | |
| 551 | + blob_appendf(&hdr, "Content-Type: %s%s\r\n", zReplyMimeType, | |
| 552 | + content_type_charset(zReplyMimeType)); | |
| 553 | + if( fossil_strcmp(zReplyMimeType,"application/x-fossil")==0 ){ | |
| 550 | 554 | cgi_combine_header_and_body(); |
| 551 | 555 | blob_compress(&cgiContent[0], &cgiContent[0]); |
| 552 | 556 | } |
| 553 | 557 | |
| 554 | 558 | if( is_gzippable() && iReplyStatus!=206 ){ |
| @@ -944,10 +948,20 @@ | ||
| 944 | 948 | ** portion is fixed but a copy is be made of zValue. |
| 945 | 949 | */ |
| 946 | 950 | void cgi_setenv(const char *zName, const char *zValue){ |
| 947 | 951 | cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0); |
| 948 | 952 | } |
| 953 | + | |
| 954 | +/* | |
| 955 | +** Returns true if NUL-terminated z contains any non-NUL | |
| 956 | +** control characters (<0x20, 32d). | |
| 957 | +*/ | |
| 958 | +static int contains_ctrl(const char *z){ | |
| 959 | + assert(z); | |
| 960 | + for( ; *z>=0x20; ++z ){} | |
| 961 | + return 0!=*z; | |
| 962 | +} | |
| 949 | 963 | |
| 950 | 964 | /* |
| 951 | 965 | ** Add a list of query parameters or cookies to the parameter set. |
| 952 | 966 | ** |
| 953 | 967 | ** Each parameter is of the form NAME=VALUE. Both the NAME and the |
| @@ -974,12 +988,16 @@ | ||
| 974 | 988 | ** before the NAME is ignored. |
| 975 | 989 | ** |
| 976 | 990 | ** The input string "z" is modified but no copies is made. "z" |
| 977 | 991 | ** should not be deallocated or changed again after this routine |
| 978 | 992 | ** returns or it will corrupt the parameter table. |
| 993 | +** | |
| 994 | +** If bPermitCtrl is false and the decoded value of any entry in z | |
| 995 | +** contains control characters (<0x20, 32d) then that key/value pair | |
| 996 | +** are skipped. | |
| 979 | 997 | */ |
| 980 | -static void add_param_list(char *z, int terminator){ | |
| 998 | +static void add_param_list(char *z, int terminator, int bPermitCtrl){ | |
| 981 | 999 | int isQP = terminator=='&'; |
| 982 | 1000 | while( *z ){ |
| 983 | 1001 | char *zName; |
| 984 | 1002 | char *zValue; |
| 985 | 1003 | while( fossil_isspace(*z) ){ z++; } |
| @@ -998,11 +1016,14 @@ | ||
| 998 | 1016 | }else{ |
| 999 | 1017 | if( *z ){ *z++ = 0; } |
| 1000 | 1018 | zValue = ""; |
| 1001 | 1019 | } |
| 1002 | 1020 | if( zName[0] && fossil_no_strange_characters(zName+1) ){ |
| 1003 | - if( fossil_islower(zName[0]) ){ | |
| 1021 | + if( 0==bPermitCtrl && contains_ctrl(zValue) ){ | |
| 1022 | + continue /* Reject it. An argument could be made | |
| 1023 | + ** for break instead of continue. */; | |
| 1024 | + }else if( fossil_islower(zName[0]) ){ | |
| 1004 | 1025 | cgi_set_parameter_nocopy(zName, zValue, isQP); |
| 1005 | 1026 | }else if( fossil_isupper(zName[0]) ){ |
| 1006 | 1027 | cgi_set_parameter_nocopy_tolower(zName, zValue, isQP); |
| 1007 | 1028 | } |
| 1008 | 1029 | } |
| @@ -1297,11 +1318,11 @@ | ||
| 1297 | 1318 | int rc = 0; |
| 1298 | 1319 | char * z = (char*)P("QUERY_STRING"); |
| 1299 | 1320 | if( z ){ |
| 1300 | 1321 | rc = 0x01; |
| 1301 | 1322 | z = fossil_strdup(z); |
| 1302 | - add_param_list(z, '&'); | |
| 1323 | + add_param_list(z, '&', 0); | |
| 1303 | 1324 | z = (char*)P("skin"); |
| 1304 | 1325 | if( z ){ |
| 1305 | 1326 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1306 | 1327 | rc |= 0x02; |
| 1307 | 1328 | if( !zErr && P("once")==0 ){ |
| @@ -1457,11 +1478,11 @@ | ||
| 1457 | 1478 | } |
| 1458 | 1479 | #endif |
| 1459 | 1480 | z = (char*)P("HTTP_COOKIE"); |
| 1460 | 1481 | if( z ){ |
| 1461 | 1482 | z = fossil_strdup(z); |
| 1462 | - add_param_list(z, ';'); | |
| 1483 | + add_param_list(z, ';', 0); | |
| 1463 | 1484 | z = (char*)cookie_value("skin",0); |
| 1464 | 1485 | if(z){ |
| 1465 | 1486 | skin_use_alternative(z, 2, SKIN_FROM_COOKIE); |
| 1466 | 1487 | } |
| 1467 | 1488 | } |
| @@ -1520,11 +1541,11 @@ | ||
| 1520 | 1541 | || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0 |
| 1521 | 1542 | ){ |
| 1522 | 1543 | char *z = blob_str(&g.cgiIn); |
| 1523 | 1544 | cgi_trace(z); |
| 1524 | 1545 | if( g.zContentType[0]=='a' ){ |
| 1525 | - add_param_list(z, '&'); | |
| 1546 | + add_param_list(z, '&', 1); | |
| 1526 | 1547 | }else{ |
| 1527 | 1548 | process_multipart_form_data(z, len); |
| 1528 | 1549 | } |
| 1529 | 1550 | blob_init(&g.cgiIn, 0, 0); |
| 1530 | 1551 | } |
| @@ -1612,10 +1633,25 @@ | ||
| 1612 | 1633 | } |
| 1613 | 1634 | } |
| 1614 | 1635 | CGIDEBUG(("no-match [%s]\n", zName)); |
| 1615 | 1636 | return zDefault; |
| 1616 | 1637 | } |
| 1638 | + | |
| 1639 | +/* | |
| 1640 | +** Return TRUE if the specific parameter exists and is a query parameter. | |
| 1641 | +** Return FALSE if the parameter is a cookie or environment variable. | |
| 1642 | +*/ | |
| 1643 | +int cgi_is_qp(const char *zName){ | |
| 1644 | + int i; | |
| 1645 | + if( zName==0 || fossil_isupper(zName[0]) ) return 0; | |
| 1646 | + for(i=0; i<nUsedQP; i++){ | |
| 1647 | + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ | |
| 1648 | + return aParamQP[i].isQP; | |
| 1649 | + } | |
| 1650 | + } | |
| 1651 | + return 0; | |
| 1652 | +} | |
| 1617 | 1653 | |
| 1618 | 1654 | /* |
| 1619 | 1655 | ** Renders the "begone, spider" page and exits. |
| 1620 | 1656 | */ |
| 1621 | 1657 | static void cgi_begone_spider(const char *zName){ |
| @@ -2006,11 +2042,11 @@ | ||
| 2006 | 2042 | char *z; |
| 2007 | 2043 | va_start(ap, zMsg); |
| 2008 | 2044 | z = vmprintf(zMsg, ap); |
| 2009 | 2045 | va_end(ap); |
| 2010 | 2046 | cgi_set_status(400, "Bad Request"); |
| 2011 | - zContentType = "text/plain"; | |
| 2047 | + zReplyMimeType = "text/plain"; | |
| 2012 | 2048 | if( g.zReqType==0 ) g.zReqType = "WWW"; |
| 2013 | 2049 | if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){ |
| 2014 | 2050 | const char *zServer = PD("SERVER_SOFTWARE",""); |
| 2015 | 2051 | cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z); |
| 2016 | 2052 | }else{ |
| @@ -2254,12 +2290,13 @@ | ||
| 2254 | 2290 | */ |
| 2255 | 2291 | void cgi_handle_ssh_http_request(const char *zIpAddr){ |
| 2256 | 2292 | static int nCycles = 0; |
| 2257 | 2293 | static char *zCmd = 0; |
| 2258 | 2294 | char *z, *zToken; |
| 2259 | - const char *zType = 0; | |
| 2260 | - int i, content_length = 0; | |
| 2295 | + char *zMethod; | |
| 2296 | + int i; | |
| 2297 | + size_t n; | |
| 2261 | 2298 | char zLine[2000]; /* A single line of input. */ |
| 2262 | 2299 | |
| 2263 | 2300 | assert( !g.httpUseSSL ); |
| 2264 | 2301 | #ifdef FOSSIL_ENABLE_JSON |
| 2265 | 2302 | if( nCycles==0 ){ json_bootstrap_early(); } |
| @@ -2304,10 +2341,11 @@ | ||
| 2304 | 2341 | if( zToken==0 ){ |
| 2305 | 2342 | malformed_request("malformed HTTP header"); |
| 2306 | 2343 | } |
| 2307 | 2344 | } |
| 2308 | 2345 | |
| 2346 | + zMethod = fossil_strdup(zToken); | |
| 2309 | 2347 | if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 |
| 2310 | 2348 | && fossil_strcmp(zToken,"HEAD")!=0 ){ |
| 2311 | 2349 | malformed_request("unsupported HTTP method"); |
| 2312 | 2350 | } |
| 2313 | 2351 | |
| @@ -2317,10 +2355,20 @@ | ||
| 2317 | 2355 | } |
| 2318 | 2356 | |
| 2319 | 2357 | zToken = extract_token(z, &z); |
| 2320 | 2358 | if( zToken==0 ){ |
| 2321 | 2359 | malformed_request("malformed URL in HTTP header"); |
| 2360 | + } | |
| 2361 | + n = strlen(g.zRepositoryName); | |
| 2362 | + if( fossil_strncmp(g.zRepositoryName, zToken, n)==0 | |
| 2363 | + && (zToken[n]=='/' || zToken[n]==0) | |
| 2364 | + && fossil_strcmp(zMethod,"GET")==0 | |
| 2365 | + ){ | |
| 2366 | + zToken += n; | |
| 2367 | + if( zToken && strlen(zToken)==0 ){ | |
| 2368 | + malformed_request("malformed URL in HTTP header"); | |
| 2369 | + } | |
| 2322 | 2370 | } |
| 2323 | 2371 | if( nCycles==0 ){ |
| 2324 | 2372 | cgi_setenv("REQUEST_URI", zToken); |
| 2325 | 2373 | cgi_setenv("SCRIPT_NAME", ""); |
| 2326 | 2374 | } |
| @@ -2327,12 +2375,14 @@ | ||
| 2327 | 2375 | |
| 2328 | 2376 | for(i=0; zToken[i] && zToken[i]!='?'; i++){} |
| 2329 | 2377 | if( zToken[i] ) zToken[i++] = 0; |
| 2330 | 2378 | if( nCycles==0 ){ |
| 2331 | 2379 | cgi_setenv("PATH_INFO", zToken); |
| 2380 | + cgi_setenv("QUERY_STRING",&zToken[i]); | |
| 2332 | 2381 | }else{ |
| 2333 | 2382 | cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); |
| 2383 | + cgi_replace_parameter("QUERY_STRING",fossil_strdup(&zToken[i])); | |
| 2334 | 2384 | } |
| 2335 | 2385 | |
| 2336 | 2386 | /* Get all the optional fields that follow the first line. |
| 2337 | 2387 | */ |
| 2338 | 2388 | while( fgets(zLine,sizeof(zLine),g.httpIn) ){ |
| @@ -2348,13 +2398,19 @@ | ||
| 2348 | 2398 | zVal[i] = 0; |
| 2349 | 2399 | for(i=0; zFieldName[i]; i++){ |
| 2350 | 2400 | zFieldName[i] = fossil_tolower(zFieldName[i]); |
| 2351 | 2401 | } |
| 2352 | 2402 | if( fossil_strcmp(zFieldName,"content-length:")==0 ){ |
| 2353 | - content_length = atoi(zVal); | |
| 2403 | + if( nCycles==0 ){ | |
| 2404 | + cgi_setenv("CONTENT_LENGTH", zVal); | |
| 2405 | + }else{ | |
| 2406 | + cgi_replace_parameter("CONTENT_LENGTH", zVal); | |
| 2407 | + } | |
| 2354 | 2408 | }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ |
| 2355 | - g.zContentType = zType = fossil_strdup(zVal); | |
| 2409 | + if( nCycles==0 ){ | |
| 2410 | + cgi_setenv("CONTENT_TYPE", zVal); | |
| 2411 | + } | |
| 2356 | 2412 | }else if( fossil_strcmp(zFieldName,"host:")==0 ){ |
| 2357 | 2413 | if( nCycles==0 ){ |
| 2358 | 2414 | cgi_setenv("HTTP_HOST", zVal); |
| 2359 | 2415 | } |
| 2360 | 2416 | }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
| @@ -2381,21 +2437,11 @@ | ||
| 2381 | 2437 | } |
| 2382 | 2438 | |
| 2383 | 2439 | cgi_reset_content(); |
| 2384 | 2440 | cgi_destination(CGI_BODY); |
| 2385 | 2441 | |
| 2386 | - if( content_length>0 && zType ){ | |
| 2387 | - blob_zero(&g.cgiIn); | |
| 2388 | - if( fossil_strcmp(zType, "application/x-fossil")==0 ){ | |
| 2389 | - blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); | |
| 2390 | - blob_uncompress(&g.cgiIn, &g.cgiIn); | |
| 2391 | - }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ | |
| 2392 | - blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); | |
| 2393 | - }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ | |
| 2394 | - blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); | |
| 2395 | - } | |
| 2396 | - } | |
| 2442 | + cgi_init(); | |
| 2397 | 2443 | cgi_trace(0); |
| 2398 | 2444 | nCycles++; |
| 2399 | 2445 | } |
| 2400 | 2446 | |
| 2401 | 2447 | /* |
| 2402 | 2448 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -241,11 +241,11 @@ | |
| 241 | } |
| 242 | |
| 243 | /* |
| 244 | ** Additional information used to form the HTTP reply |
| 245 | */ |
| 246 | static const char *zContentType = "text/html"; /* Content type of the reply */ |
| 247 | static const char *zReplyStatus = "OK"; /* Reply status description */ |
| 248 | static int iReplyStatus = 200; /* Reply status code */ |
| 249 | static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ |
| 250 | static int rangeStart = 0; /* Start of Range: */ |
| 251 | static int rangeEnd = 0; /* End of Range: plus 1 */ |
| @@ -256,11 +256,13 @@ | |
| 256 | ** The reply content type defaults to "text/html". It only needs to be |
| 257 | ** changed (by calling this routine) in the exceptional case where some |
| 258 | ** other content type is being returned. |
| 259 | */ |
| 260 | void cgi_set_content_type(const char *zType){ |
| 261 | zContentType = fossil_strdup(zType); |
| 262 | } |
| 263 | |
| 264 | /* |
| 265 | ** Erase any existing reply content. Replace is with a pNewContent. |
| 266 | ** |
| @@ -336,14 +338,14 @@ | |
| 336 | if( g.fNoHttpCompress ) return 0; |
| 337 | if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; |
| 338 | /* Maintenance note: this oddball structure is intended to make |
| 339 | ** adding new mimetypes to this list less of a performance hit than |
| 340 | ** doing a strcmp/glob over a growing set of compressible types. */ |
| 341 | switch(zContentType ? *zContentType : 0){ |
| 342 | case (int)'a': |
| 343 | if(0==fossil_strncmp("application/",zContentType,12)){ |
| 344 | const char * z = &zContentType[12]; |
| 345 | switch(*z){ |
| 346 | case (int)'j': |
| 347 | return fossil_strcmp("javascript", z)==0 |
| 348 | || fossil_strcmp("json", z)==0; |
| 349 | case (int)'w': return fossil_strcmp("wasm", z)==0; |
| @@ -354,14 +356,14 @@ | |
| 354 | return sqlite3_strglob("*xml", z)==0; |
| 355 | } |
| 356 | } |
| 357 | break; |
| 358 | case (int)'i': |
| 359 | return fossil_strcmp(zContentType, "image/svg+xml")==0 |
| 360 | || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0; |
| 361 | case (int)'t': |
| 362 | return fossil_strncmp(zContentType, "text/", 5)==0; |
| 363 | } |
| 364 | return 0; |
| 365 | } |
| 366 | |
| 367 | |
| @@ -402,10 +404,12 @@ | |
| 402 | } |
| 403 | #ifdef FOSSIL_ENABLE_SSL |
| 404 | return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1); |
| 405 | #else |
| 406 | fossil_fatal("SSL not available"); |
| 407 | #endif |
| 408 | } |
| 409 | |
| 410 | /* Works like feof(): |
| 411 | ** |
| @@ -449,22 +453,22 @@ | |
| 449 | } |
| 450 | |
| 451 | /* |
| 452 | ** Given a Content-Type value, returns a string suitable for appending |
| 453 | ** to the Content-Type header for adding (or not) the "; charset=..." |
| 454 | ** part. It returns an empty string for most types or if zContentType |
| 455 | ** is NULL. |
| 456 | ** |
| 457 | ** See forum post f60dece061c364d1 for the discussions which lead to |
| 458 | ** this. Previously we always appended the charset, but WASM loaders |
| 459 | ** are pedantic and refuse to load any responses which have a |
| 460 | ** charset. Also, adding a charset is not strictly appropriate for |
| 461 | ** most types (and not required for many others which may ostensibly |
| 462 | ** benefit from one, as detailed in that forum post). |
| 463 | */ |
| 464 | static const char * content_type_charset(const char *zContentType){ |
| 465 | if(0==fossil_strncmp(zContentType,"text/",5)){ |
| 466 | return "; charset=utf-8"; |
| 467 | } |
| 468 | return ""; |
| 469 | } |
| 470 | |
| @@ -500,11 +504,11 @@ | |
| 500 | assert( rangeEnd==0 ); |
| 501 | blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); |
| 502 | } |
| 503 | if( etag_tag()[0]!=0 |
| 504 | && iReplyStatus==200 |
| 505 | && strcmp(zContentType,"text/html")!=0 |
| 506 | ){ |
| 507 | /* Do not cache HTML replies as those will have been generated and |
| 508 | ** will likely, therefore, contains a nonce and we want that nonce to |
| 509 | ** be different every time. */ |
| 510 | blob_appendf(&hdr, "ETag: \"%s\"\r\n", etag_tag()); |
| @@ -542,13 +546,13 @@ | |
| 542 | ** These headers are probably best added by the web server hosting fossil as |
| 543 | ** a CGI script. |
| 544 | */ |
| 545 | |
| 546 | if( iReplyStatus!=304 ) { |
| 547 | blob_appendf(&hdr, "Content-Type: %s%s\r\n", zContentType, |
| 548 | content_type_charset(zContentType)); |
| 549 | if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ |
| 550 | cgi_combine_header_and_body(); |
| 551 | blob_compress(&cgiContent[0], &cgiContent[0]); |
| 552 | } |
| 553 | |
| 554 | if( is_gzippable() && iReplyStatus!=206 ){ |
| @@ -944,10 +948,20 @@ | |
| 944 | ** portion is fixed but a copy is be made of zValue. |
| 945 | */ |
| 946 | void cgi_setenv(const char *zName, const char *zValue){ |
| 947 | cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0); |
| 948 | } |
| 949 | |
| 950 | /* |
| 951 | ** Add a list of query parameters or cookies to the parameter set. |
| 952 | ** |
| 953 | ** Each parameter is of the form NAME=VALUE. Both the NAME and the |
| @@ -974,12 +988,16 @@ | |
| 974 | ** before the NAME is ignored. |
| 975 | ** |
| 976 | ** The input string "z" is modified but no copies is made. "z" |
| 977 | ** should not be deallocated or changed again after this routine |
| 978 | ** returns or it will corrupt the parameter table. |
| 979 | */ |
| 980 | static void add_param_list(char *z, int terminator){ |
| 981 | int isQP = terminator=='&'; |
| 982 | while( *z ){ |
| 983 | char *zName; |
| 984 | char *zValue; |
| 985 | while( fossil_isspace(*z) ){ z++; } |
| @@ -998,11 +1016,14 @@ | |
| 998 | }else{ |
| 999 | if( *z ){ *z++ = 0; } |
| 1000 | zValue = ""; |
| 1001 | } |
| 1002 | if( zName[0] && fossil_no_strange_characters(zName+1) ){ |
| 1003 | if( fossil_islower(zName[0]) ){ |
| 1004 | cgi_set_parameter_nocopy(zName, zValue, isQP); |
| 1005 | }else if( fossil_isupper(zName[0]) ){ |
| 1006 | cgi_set_parameter_nocopy_tolower(zName, zValue, isQP); |
| 1007 | } |
| 1008 | } |
| @@ -1297,11 +1318,11 @@ | |
| 1297 | int rc = 0; |
| 1298 | char * z = (char*)P("QUERY_STRING"); |
| 1299 | if( z ){ |
| 1300 | rc = 0x01; |
| 1301 | z = fossil_strdup(z); |
| 1302 | add_param_list(z, '&'); |
| 1303 | z = (char*)P("skin"); |
| 1304 | if( z ){ |
| 1305 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1306 | rc |= 0x02; |
| 1307 | if( !zErr && P("once")==0 ){ |
| @@ -1457,11 +1478,11 @@ | |
| 1457 | } |
| 1458 | #endif |
| 1459 | z = (char*)P("HTTP_COOKIE"); |
| 1460 | if( z ){ |
| 1461 | z = fossil_strdup(z); |
| 1462 | add_param_list(z, ';'); |
| 1463 | z = (char*)cookie_value("skin",0); |
| 1464 | if(z){ |
| 1465 | skin_use_alternative(z, 2, SKIN_FROM_COOKIE); |
| 1466 | } |
| 1467 | } |
| @@ -1520,11 +1541,11 @@ | |
| 1520 | || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0 |
| 1521 | ){ |
| 1522 | char *z = blob_str(&g.cgiIn); |
| 1523 | cgi_trace(z); |
| 1524 | if( g.zContentType[0]=='a' ){ |
| 1525 | add_param_list(z, '&'); |
| 1526 | }else{ |
| 1527 | process_multipart_form_data(z, len); |
| 1528 | } |
| 1529 | blob_init(&g.cgiIn, 0, 0); |
| 1530 | } |
| @@ -1612,10 +1633,25 @@ | |
| 1612 | } |
| 1613 | } |
| 1614 | CGIDEBUG(("no-match [%s]\n", zName)); |
| 1615 | return zDefault; |
| 1616 | } |
| 1617 | |
| 1618 | /* |
| 1619 | ** Renders the "begone, spider" page and exits. |
| 1620 | */ |
| 1621 | static void cgi_begone_spider(const char *zName){ |
| @@ -2006,11 +2042,11 @@ | |
| 2006 | char *z; |
| 2007 | va_start(ap, zMsg); |
| 2008 | z = vmprintf(zMsg, ap); |
| 2009 | va_end(ap); |
| 2010 | cgi_set_status(400, "Bad Request"); |
| 2011 | zContentType = "text/plain"; |
| 2012 | if( g.zReqType==0 ) g.zReqType = "WWW"; |
| 2013 | if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){ |
| 2014 | const char *zServer = PD("SERVER_SOFTWARE",""); |
| 2015 | cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z); |
| 2016 | }else{ |
| @@ -2254,12 +2290,13 @@ | |
| 2254 | */ |
| 2255 | void cgi_handle_ssh_http_request(const char *zIpAddr){ |
| 2256 | static int nCycles = 0; |
| 2257 | static char *zCmd = 0; |
| 2258 | char *z, *zToken; |
| 2259 | const char *zType = 0; |
| 2260 | int i, content_length = 0; |
| 2261 | char zLine[2000]; /* A single line of input. */ |
| 2262 | |
| 2263 | assert( !g.httpUseSSL ); |
| 2264 | #ifdef FOSSIL_ENABLE_JSON |
| 2265 | if( nCycles==0 ){ json_bootstrap_early(); } |
| @@ -2304,10 +2341,11 @@ | |
| 2304 | if( zToken==0 ){ |
| 2305 | malformed_request("malformed HTTP header"); |
| 2306 | } |
| 2307 | } |
| 2308 | |
| 2309 | if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 |
| 2310 | && fossil_strcmp(zToken,"HEAD")!=0 ){ |
| 2311 | malformed_request("unsupported HTTP method"); |
| 2312 | } |
| 2313 | |
| @@ -2317,10 +2355,20 @@ | |
| 2317 | } |
| 2318 | |
| 2319 | zToken = extract_token(z, &z); |
| 2320 | if( zToken==0 ){ |
| 2321 | malformed_request("malformed URL in HTTP header"); |
| 2322 | } |
| 2323 | if( nCycles==0 ){ |
| 2324 | cgi_setenv("REQUEST_URI", zToken); |
| 2325 | cgi_setenv("SCRIPT_NAME", ""); |
| 2326 | } |
| @@ -2327,12 +2375,14 @@ | |
| 2327 | |
| 2328 | for(i=0; zToken[i] && zToken[i]!='?'; i++){} |
| 2329 | if( zToken[i] ) zToken[i++] = 0; |
| 2330 | if( nCycles==0 ){ |
| 2331 | cgi_setenv("PATH_INFO", zToken); |
| 2332 | }else{ |
| 2333 | cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); |
| 2334 | } |
| 2335 | |
| 2336 | /* Get all the optional fields that follow the first line. |
| 2337 | */ |
| 2338 | while( fgets(zLine,sizeof(zLine),g.httpIn) ){ |
| @@ -2348,13 +2398,19 @@ | |
| 2348 | zVal[i] = 0; |
| 2349 | for(i=0; zFieldName[i]; i++){ |
| 2350 | zFieldName[i] = fossil_tolower(zFieldName[i]); |
| 2351 | } |
| 2352 | if( fossil_strcmp(zFieldName,"content-length:")==0 ){ |
| 2353 | content_length = atoi(zVal); |
| 2354 | }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ |
| 2355 | g.zContentType = zType = fossil_strdup(zVal); |
| 2356 | }else if( fossil_strcmp(zFieldName,"host:")==0 ){ |
| 2357 | if( nCycles==0 ){ |
| 2358 | cgi_setenv("HTTP_HOST", zVal); |
| 2359 | } |
| 2360 | }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
| @@ -2381,21 +2437,11 @@ | |
| 2381 | } |
| 2382 | |
| 2383 | cgi_reset_content(); |
| 2384 | cgi_destination(CGI_BODY); |
| 2385 | |
| 2386 | if( content_length>0 && zType ){ |
| 2387 | blob_zero(&g.cgiIn); |
| 2388 | if( fossil_strcmp(zType, "application/x-fossil")==0 ){ |
| 2389 | blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); |
| 2390 | blob_uncompress(&g.cgiIn, &g.cgiIn); |
| 2391 | }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ |
| 2392 | blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); |
| 2393 | }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ |
| 2394 | blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); |
| 2395 | } |
| 2396 | } |
| 2397 | cgi_trace(0); |
| 2398 | nCycles++; |
| 2399 | } |
| 2400 | |
| 2401 | /* |
| 2402 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -241,11 +241,11 @@ | |
| 241 | } |
| 242 | |
| 243 | /* |
| 244 | ** Additional information used to form the HTTP reply |
| 245 | */ |
| 246 | static const char *zReplyMimeType = "text/html"; /* Content type of the reply */ |
| 247 | static const char *zReplyStatus = "OK"; /* Reply status description */ |
| 248 | static int iReplyStatus = 200; /* Reply status code */ |
| 249 | static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ |
| 250 | static int rangeStart = 0; /* Start of Range: */ |
| 251 | static int rangeEnd = 0; /* End of Range: plus 1 */ |
| @@ -256,11 +256,13 @@ | |
| 256 | ** The reply content type defaults to "text/html". It only needs to be |
| 257 | ** changed (by calling this routine) in the exceptional case where some |
| 258 | ** other content type is being returned. |
| 259 | */ |
| 260 | void cgi_set_content_type(const char *zType){ |
| 261 | int i; |
| 262 | for(i=0; zType[i]>='+' && zType[i]<='z'; i++){} |
| 263 | zReplyMimeType = fossil_strndup(zType, i); |
| 264 | } |
| 265 | |
| 266 | /* |
| 267 | ** Erase any existing reply content. Replace is with a pNewContent. |
| 268 | ** |
| @@ -336,14 +338,14 @@ | |
| 338 | if( g.fNoHttpCompress ) return 0; |
| 339 | if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; |
| 340 | /* Maintenance note: this oddball structure is intended to make |
| 341 | ** adding new mimetypes to this list less of a performance hit than |
| 342 | ** doing a strcmp/glob over a growing set of compressible types. */ |
| 343 | switch(zReplyMimeType ? *zReplyMimeType : 0){ |
| 344 | case (int)'a': |
| 345 | if(0==fossil_strncmp("application/",zReplyMimeType,12)){ |
| 346 | const char * z = &zReplyMimeType[12]; |
| 347 | switch(*z){ |
| 348 | case (int)'j': |
| 349 | return fossil_strcmp("javascript", z)==0 |
| 350 | || fossil_strcmp("json", z)==0; |
| 351 | case (int)'w': return fossil_strcmp("wasm", z)==0; |
| @@ -354,14 +356,14 @@ | |
| 356 | return sqlite3_strglob("*xml", z)==0; |
| 357 | } |
| 358 | } |
| 359 | break; |
| 360 | case (int)'i': |
| 361 | return fossil_strcmp(zReplyMimeType, "image/svg+xml")==0 |
| 362 | || fossil_strcmp(zReplyMimeType, "image/vnd.microsoft.icon")==0; |
| 363 | case (int)'t': |
| 364 | return fossil_strncmp(zReplyMimeType, "text/", 5)==0; |
| 365 | } |
| 366 | return 0; |
| 367 | } |
| 368 | |
| 369 | |
| @@ -402,10 +404,12 @@ | |
| 404 | } |
| 405 | #ifdef FOSSIL_ENABLE_SSL |
| 406 | return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1); |
| 407 | #else |
| 408 | fossil_fatal("SSL not available"); |
| 409 | /* NOT REACHED */ |
| 410 | return 0; |
| 411 | #endif |
| 412 | } |
| 413 | |
| 414 | /* Works like feof(): |
| 415 | ** |
| @@ -449,22 +453,22 @@ | |
| 453 | } |
| 454 | |
| 455 | /* |
| 456 | ** Given a Content-Type value, returns a string suitable for appending |
| 457 | ** to the Content-Type header for adding (or not) the "; charset=..." |
| 458 | ** part. It returns an empty string for most types or if zReplyMimeType |
| 459 | ** is NULL. |
| 460 | ** |
| 461 | ** See forum post f60dece061c364d1 for the discussions which lead to |
| 462 | ** this. Previously we always appended the charset, but WASM loaders |
| 463 | ** are pedantic and refuse to load any responses which have a |
| 464 | ** charset. Also, adding a charset is not strictly appropriate for |
| 465 | ** most types (and not required for many others which may ostensibly |
| 466 | ** benefit from one, as detailed in that forum post). |
| 467 | */ |
| 468 | static const char * content_type_charset(const char *zReplyMimeType){ |
| 469 | if(0==fossil_strncmp(zReplyMimeType,"text/",5)){ |
| 470 | return "; charset=utf-8"; |
| 471 | } |
| 472 | return ""; |
| 473 | } |
| 474 | |
| @@ -500,11 +504,11 @@ | |
| 504 | assert( rangeEnd==0 ); |
| 505 | blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); |
| 506 | } |
| 507 | if( etag_tag()[0]!=0 |
| 508 | && iReplyStatus==200 |
| 509 | && strcmp(zReplyMimeType,"text/html")!=0 |
| 510 | ){ |
| 511 | /* Do not cache HTML replies as those will have been generated and |
| 512 | ** will likely, therefore, contains a nonce and we want that nonce to |
| 513 | ** be different every time. */ |
| 514 | blob_appendf(&hdr, "ETag: \"%s\"\r\n", etag_tag()); |
| @@ -542,13 +546,13 @@ | |
| 546 | ** These headers are probably best added by the web server hosting fossil as |
| 547 | ** a CGI script. |
| 548 | */ |
| 549 | |
| 550 | if( iReplyStatus!=304 ) { |
| 551 | blob_appendf(&hdr, "Content-Type: %s%s\r\n", zReplyMimeType, |
| 552 | content_type_charset(zReplyMimeType)); |
| 553 | if( fossil_strcmp(zReplyMimeType,"application/x-fossil")==0 ){ |
| 554 | cgi_combine_header_and_body(); |
| 555 | blob_compress(&cgiContent[0], &cgiContent[0]); |
| 556 | } |
| 557 | |
| 558 | if( is_gzippable() && iReplyStatus!=206 ){ |
| @@ -944,10 +948,20 @@ | |
| 948 | ** portion is fixed but a copy is be made of zValue. |
| 949 | */ |
| 950 | void cgi_setenv(const char *zName, const char *zValue){ |
| 951 | cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0); |
| 952 | } |
| 953 | |
| 954 | /* |
| 955 | ** Returns true if NUL-terminated z contains any non-NUL |
| 956 | ** control characters (<0x20, 32d). |
| 957 | */ |
| 958 | static int contains_ctrl(const char *z){ |
| 959 | assert(z); |
| 960 | for( ; *z>=0x20; ++z ){} |
| 961 | return 0!=*z; |
| 962 | } |
| 963 | |
| 964 | /* |
| 965 | ** Add a list of query parameters or cookies to the parameter set. |
| 966 | ** |
| 967 | ** Each parameter is of the form NAME=VALUE. Both the NAME and the |
| @@ -974,12 +988,16 @@ | |
| 988 | ** before the NAME is ignored. |
| 989 | ** |
| 990 | ** The input string "z" is modified but no copies is made. "z" |
| 991 | ** should not be deallocated or changed again after this routine |
| 992 | ** returns or it will corrupt the parameter table. |
| 993 | ** |
| 994 | ** If bPermitCtrl is false and the decoded value of any entry in z |
| 995 | ** contains control characters (<0x20, 32d) then that key/value pair |
| 996 | ** are skipped. |
| 997 | */ |
| 998 | static void add_param_list(char *z, int terminator, int bPermitCtrl){ |
| 999 | int isQP = terminator=='&'; |
| 1000 | while( *z ){ |
| 1001 | char *zName; |
| 1002 | char *zValue; |
| 1003 | while( fossil_isspace(*z) ){ z++; } |
| @@ -998,11 +1016,14 @@ | |
| 1016 | }else{ |
| 1017 | if( *z ){ *z++ = 0; } |
| 1018 | zValue = ""; |
| 1019 | } |
| 1020 | if( zName[0] && fossil_no_strange_characters(zName+1) ){ |
| 1021 | if( 0==bPermitCtrl && contains_ctrl(zValue) ){ |
| 1022 | continue /* Reject it. An argument could be made |
| 1023 | ** for break instead of continue. */; |
| 1024 | }else if( fossil_islower(zName[0]) ){ |
| 1025 | cgi_set_parameter_nocopy(zName, zValue, isQP); |
| 1026 | }else if( fossil_isupper(zName[0]) ){ |
| 1027 | cgi_set_parameter_nocopy_tolower(zName, zValue, isQP); |
| 1028 | } |
| 1029 | } |
| @@ -1297,11 +1318,11 @@ | |
| 1318 | int rc = 0; |
| 1319 | char * z = (char*)P("QUERY_STRING"); |
| 1320 | if( z ){ |
| 1321 | rc = 0x01; |
| 1322 | z = fossil_strdup(z); |
| 1323 | add_param_list(z, '&', 0); |
| 1324 | z = (char*)P("skin"); |
| 1325 | if( z ){ |
| 1326 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1327 | rc |= 0x02; |
| 1328 | if( !zErr && P("once")==0 ){ |
| @@ -1457,11 +1478,11 @@ | |
| 1478 | } |
| 1479 | #endif |
| 1480 | z = (char*)P("HTTP_COOKIE"); |
| 1481 | if( z ){ |
| 1482 | z = fossil_strdup(z); |
| 1483 | add_param_list(z, ';', 0); |
| 1484 | z = (char*)cookie_value("skin",0); |
| 1485 | if(z){ |
| 1486 | skin_use_alternative(z, 2, SKIN_FROM_COOKIE); |
| 1487 | } |
| 1488 | } |
| @@ -1520,11 +1541,11 @@ | |
| 1541 | || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0 |
| 1542 | ){ |
| 1543 | char *z = blob_str(&g.cgiIn); |
| 1544 | cgi_trace(z); |
| 1545 | if( g.zContentType[0]=='a' ){ |
| 1546 | add_param_list(z, '&', 1); |
| 1547 | }else{ |
| 1548 | process_multipart_form_data(z, len); |
| 1549 | } |
| 1550 | blob_init(&g.cgiIn, 0, 0); |
| 1551 | } |
| @@ -1612,10 +1633,25 @@ | |
| 1633 | } |
| 1634 | } |
| 1635 | CGIDEBUG(("no-match [%s]\n", zName)); |
| 1636 | return zDefault; |
| 1637 | } |
| 1638 | |
| 1639 | /* |
| 1640 | ** Return TRUE if the specific parameter exists and is a query parameter. |
| 1641 | ** Return FALSE if the parameter is a cookie or environment variable. |
| 1642 | */ |
| 1643 | int cgi_is_qp(const char *zName){ |
| 1644 | int i; |
| 1645 | if( zName==0 || fossil_isupper(zName[0]) ) return 0; |
| 1646 | for(i=0; i<nUsedQP; i++){ |
| 1647 | if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
| 1648 | return aParamQP[i].isQP; |
| 1649 | } |
| 1650 | } |
| 1651 | return 0; |
| 1652 | } |
| 1653 | |
| 1654 | /* |
| 1655 | ** Renders the "begone, spider" page and exits. |
| 1656 | */ |
| 1657 | static void cgi_begone_spider(const char *zName){ |
| @@ -2006,11 +2042,11 @@ | |
| 2042 | char *z; |
| 2043 | va_start(ap, zMsg); |
| 2044 | z = vmprintf(zMsg, ap); |
| 2045 | va_end(ap); |
| 2046 | cgi_set_status(400, "Bad Request"); |
| 2047 | zReplyMimeType = "text/plain"; |
| 2048 | if( g.zReqType==0 ) g.zReqType = "WWW"; |
| 2049 | if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){ |
| 2050 | const char *zServer = PD("SERVER_SOFTWARE",""); |
| 2051 | cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z); |
| 2052 | }else{ |
| @@ -2254,12 +2290,13 @@ | |
| 2290 | */ |
| 2291 | void cgi_handle_ssh_http_request(const char *zIpAddr){ |
| 2292 | static int nCycles = 0; |
| 2293 | static char *zCmd = 0; |
| 2294 | char *z, *zToken; |
| 2295 | char *zMethod; |
| 2296 | int i; |
| 2297 | size_t n; |
| 2298 | char zLine[2000]; /* A single line of input. */ |
| 2299 | |
| 2300 | assert( !g.httpUseSSL ); |
| 2301 | #ifdef FOSSIL_ENABLE_JSON |
| 2302 | if( nCycles==0 ){ json_bootstrap_early(); } |
| @@ -2304,10 +2341,11 @@ | |
| 2341 | if( zToken==0 ){ |
| 2342 | malformed_request("malformed HTTP header"); |
| 2343 | } |
| 2344 | } |
| 2345 | |
| 2346 | zMethod = fossil_strdup(zToken); |
| 2347 | if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 |
| 2348 | && fossil_strcmp(zToken,"HEAD")!=0 ){ |
| 2349 | malformed_request("unsupported HTTP method"); |
| 2350 | } |
| 2351 | |
| @@ -2317,10 +2355,20 @@ | |
| 2355 | } |
| 2356 | |
| 2357 | zToken = extract_token(z, &z); |
| 2358 | if( zToken==0 ){ |
| 2359 | malformed_request("malformed URL in HTTP header"); |
| 2360 | } |
| 2361 | n = strlen(g.zRepositoryName); |
| 2362 | if( fossil_strncmp(g.zRepositoryName, zToken, n)==0 |
| 2363 | && (zToken[n]=='/' || zToken[n]==0) |
| 2364 | && fossil_strcmp(zMethod,"GET")==0 |
| 2365 | ){ |
| 2366 | zToken += n; |
| 2367 | if( zToken && strlen(zToken)==0 ){ |
| 2368 | malformed_request("malformed URL in HTTP header"); |
| 2369 | } |
| 2370 | } |
| 2371 | if( nCycles==0 ){ |
| 2372 | cgi_setenv("REQUEST_URI", zToken); |
| 2373 | cgi_setenv("SCRIPT_NAME", ""); |
| 2374 | } |
| @@ -2327,12 +2375,14 @@ | |
| 2375 | |
| 2376 | for(i=0; zToken[i] && zToken[i]!='?'; i++){} |
| 2377 | if( zToken[i] ) zToken[i++] = 0; |
| 2378 | if( nCycles==0 ){ |
| 2379 | cgi_setenv("PATH_INFO", zToken); |
| 2380 | cgi_setenv("QUERY_STRING",&zToken[i]); |
| 2381 | }else{ |
| 2382 | cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); |
| 2383 | cgi_replace_parameter("QUERY_STRING",fossil_strdup(&zToken[i])); |
| 2384 | } |
| 2385 | |
| 2386 | /* Get all the optional fields that follow the first line. |
| 2387 | */ |
| 2388 | while( fgets(zLine,sizeof(zLine),g.httpIn) ){ |
| @@ -2348,13 +2398,19 @@ | |
| 2398 | zVal[i] = 0; |
| 2399 | for(i=0; zFieldName[i]; i++){ |
| 2400 | zFieldName[i] = fossil_tolower(zFieldName[i]); |
| 2401 | } |
| 2402 | if( fossil_strcmp(zFieldName,"content-length:")==0 ){ |
| 2403 | if( nCycles==0 ){ |
| 2404 | cgi_setenv("CONTENT_LENGTH", zVal); |
| 2405 | }else{ |
| 2406 | cgi_replace_parameter("CONTENT_LENGTH", zVal); |
| 2407 | } |
| 2408 | }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ |
| 2409 | if( nCycles==0 ){ |
| 2410 | cgi_setenv("CONTENT_TYPE", zVal); |
| 2411 | } |
| 2412 | }else if( fossil_strcmp(zFieldName,"host:")==0 ){ |
| 2413 | if( nCycles==0 ){ |
| 2414 | cgi_setenv("HTTP_HOST", zVal); |
| 2415 | } |
| 2416 | }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
| @@ -2381,21 +2437,11 @@ | |
| 2437 | } |
| 2438 | |
| 2439 | cgi_reset_content(); |
| 2440 | cgi_destination(CGI_BODY); |
| 2441 | |
| 2442 | cgi_init(); |
| 2443 | cgi_trace(0); |
| 2444 | nCycles++; |
| 2445 | } |
| 2446 | |
| 2447 | /* |
| 2448 |
+18
| --- src/chat.c | ||
| +++ src/chat.c | ||
| @@ -389,10 +389,25 @@ | ||
| 389 | 389 | }else{ |
| 390 | 390 | CX("}"); |
| 391 | 391 | } |
| 392 | 392 | fossil_free(zTime); |
| 393 | 393 | } |
| 394 | + | |
| 395 | +/* | |
| 396 | +** Like chat_emit_permissions_error() but emits a single | |
| 397 | +** /chat-message-format JSON object about a CSRF violation. | |
| 398 | +*/ | |
| 399 | +static void chat_emit_csrf_error(void){ | |
| 400 | + char * zTime = cgi_iso8601_datestamp(); | |
| 401 | + cgi_set_content_type("application/json"); | |
| 402 | + CX("{"); | |
| 403 | + CX("\"isError\": true, \"xfrom\": null,"); | |
| 404 | + CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime); | |
| 405 | + CX("\"xmsg\": \"CSRF validation failure.\""); | |
| 406 | + CX("}"); | |
| 407 | + fossil_free(zTime); | |
| 408 | +} | |
| 394 | 409 | |
| 395 | 410 | /* |
| 396 | 411 | ** WEBPAGE: chat-send hidden loadavg-exempt |
| 397 | 412 | ** |
| 398 | 413 | ** This page receives (via XHR) a new chat-message and/or a new file |
| @@ -421,10 +436,13 @@ | ||
| 421 | 436 | const char *zMsg; |
| 422 | 437 | const char *zUserName; |
| 423 | 438 | login_check_credentials(); |
| 424 | 439 | if( 0==g.perm.Chat ) { |
| 425 | 440 | chat_emit_permissions_error(0); |
| 441 | + return; | |
| 442 | + }else if( 0==cgi_csrf_safe(1) ){ | |
| 443 | + chat_emit_csrf_error(); | |
| 426 | 444 | return; |
| 427 | 445 | } |
| 428 | 446 | zUserName = (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; |
| 429 | 447 | nByte = atoi(PD("file:bytes","0")); |
| 430 | 448 | zMsg = PD("msg",""); |
| 431 | 449 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -389,10 +389,25 @@ | |
| 389 | }else{ |
| 390 | CX("}"); |
| 391 | } |
| 392 | fossil_free(zTime); |
| 393 | } |
| 394 | |
| 395 | /* |
| 396 | ** WEBPAGE: chat-send hidden loadavg-exempt |
| 397 | ** |
| 398 | ** This page receives (via XHR) a new chat-message and/or a new file |
| @@ -421,10 +436,13 @@ | |
| 421 | const char *zMsg; |
| 422 | const char *zUserName; |
| 423 | login_check_credentials(); |
| 424 | if( 0==g.perm.Chat ) { |
| 425 | chat_emit_permissions_error(0); |
| 426 | return; |
| 427 | } |
| 428 | zUserName = (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; |
| 429 | nByte = atoi(PD("file:bytes","0")); |
| 430 | zMsg = PD("msg",""); |
| 431 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -389,10 +389,25 @@ | |
| 389 | }else{ |
| 390 | CX("}"); |
| 391 | } |
| 392 | fossil_free(zTime); |
| 393 | } |
| 394 | |
| 395 | /* |
| 396 | ** Like chat_emit_permissions_error() but emits a single |
| 397 | ** /chat-message-format JSON object about a CSRF violation. |
| 398 | */ |
| 399 | static void chat_emit_csrf_error(void){ |
| 400 | char * zTime = cgi_iso8601_datestamp(); |
| 401 | cgi_set_content_type("application/json"); |
| 402 | CX("{"); |
| 403 | CX("\"isError\": true, \"xfrom\": null,"); |
| 404 | CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime); |
| 405 | CX("\"xmsg\": \"CSRF validation failure.\""); |
| 406 | CX("}"); |
| 407 | fossil_free(zTime); |
| 408 | } |
| 409 | |
| 410 | /* |
| 411 | ** WEBPAGE: chat-send hidden loadavg-exempt |
| 412 | ** |
| 413 | ** This page receives (via XHR) a new chat-message and/or a new file |
| @@ -421,10 +436,13 @@ | |
| 436 | const char *zMsg; |
| 437 | const char *zUserName; |
| 438 | login_check_credentials(); |
| 439 | if( 0==g.perm.Chat ) { |
| 440 | chat_emit_permissions_error(0); |
| 441 | return; |
| 442 | }else if( 0==cgi_csrf_safe(1) ){ |
| 443 | chat_emit_csrf_error(); |
| 444 | return; |
| 445 | } |
| 446 | zUserName = (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; |
| 447 | nByte = atoi(PD("file:bytes","0")); |
| 448 | zMsg = PD("msg",""); |
| 449 |
+16
-6
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -718,10 +718,11 @@ | ||
| 718 | 718 | */ |
| 719 | 719 | static void ls_cmd_rev( |
| 720 | 720 | const char *zRev, /* Revision string given */ |
| 721 | 721 | int verboseFlag, /* Verbose flag given */ |
| 722 | 722 | int showAge, /* Age flag given */ |
| 723 | + int showHash, /* Show hash flag given */ | |
| 723 | 724 | int timeOrder, /* Order by time flag given */ |
| 724 | 725 | int treeFmt /* Show output in the tree format */ |
| 725 | 726 | ){ |
| 726 | 727 | Stmt q; |
| 727 | 728 | char *zOrderBy = "pathname COLLATE nocase"; |
| @@ -763,11 +764,11 @@ | ||
| 763 | 764 | } |
| 764 | 765 | |
| 765 | 766 | compute_fileage(rid,0); |
| 766 | 767 | db_prepare(&q, |
| 767 | 768 | "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" |
| 768 | - " blob.size\n" | |
| 769 | + " blob.size, fileage.uuid\n" | |
| 769 | 770 | " FROM fileage, blob\n" |
| 770 | 771 | " WHERE blob.rid=fileage.fid %s\n" |
| 771 | 772 | " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ |
| 772 | 773 | ); |
| 773 | 774 | blob_reset(&where); |
| @@ -778,11 +779,16 @@ | ||
| 778 | 779 | const char *zFile = db_column_text(&q,1); |
| 779 | 780 | int size = db_column_int(&q,2); |
| 780 | 781 | if( treeFmt ){ |
| 781 | 782 | blob_appendf(&out, "%s\n", zFile); |
| 782 | 783 | }else if( verboseFlag ){ |
| 783 | - fossil_print("%s %7d %s\n", zTime, size, zFile); | |
| 784 | + if( showHash ){ | |
| 785 | + const char *zUuid = db_column_text(&q,3); | |
| 786 | + fossil_print("%s %7d [%S] %s\n", zTime, size, zUuid, zFile); | |
| 787 | + }else{ | |
| 788 | + fossil_print("%s %7d %s\n", zTime, size, zFile); | |
| 789 | + } | |
| 784 | 790 | }else if( showAge ){ |
| 785 | 791 | fossil_print("%s %s\n", zTime, zFile); |
| 786 | 792 | }else{ |
| 787 | 793 | fossil_print("%s\n", zFile); |
| 788 | 794 | } |
| @@ -811,18 +817,20 @@ | ||
| 811 | 817 | ** The --age option displays file commit times. Like -r, --age has the |
| 812 | 818 | ** side effect of making -t sort by commit time, not modification time. |
| 813 | 819 | ** |
| 814 | 820 | ** The -v option provides extra information about each file. Without -r, |
| 815 | 821 | ** -v displays the change status, in the manner of the changes command. |
| 816 | -** With -r, -v shows the commit time and size of the checked-in files. | |
| 822 | +** With -r, -v shows the commit time and size of the checked-in files; in | |
| 823 | +** this combination, it additionally shows file hashes with -h. | |
| 817 | 824 | ** |
| 818 | 825 | ** The -t option changes the sort order. Without -t, files are sorted by |
| 819 | 826 | ** path and name (case insensitive sort if -r). If neither --age nor -r |
| 820 | 827 | ** are used, -t sorts by modification time, otherwise by commit time. |
| 821 | 828 | ** |
| 822 | 829 | ** Options: |
| 823 | 830 | ** --age Show when each file was committed |
| 831 | +** -h With -v and -r, show file hashes | |
| 824 | 832 | ** --hash With -v, verify file status using hashing |
| 825 | 833 | ** rather than relying on file sizes and mtimes |
| 826 | 834 | ** -r VERSION The specific check-in to list |
| 827 | 835 | ** -R|--repository REPO Extract info from repository REPO |
| 828 | 836 | ** -t Sort output in time order |
| @@ -840,10 +848,11 @@ | ||
| 840 | 848 | int timeOrder; |
| 841 | 849 | char *zOrderBy = "pathname"; |
| 842 | 850 | Blob where; |
| 843 | 851 | int i; |
| 844 | 852 | int useHash = 0; |
| 853 | + int showHash = 0; | |
| 845 | 854 | const char *zName; |
| 846 | 855 | const char *zRev; |
| 847 | 856 | |
| 848 | 857 | verboseFlag = find_option("verbose","v", 0)!=0; |
| 849 | 858 | if( !verboseFlag ){ |
| @@ -852,20 +861,21 @@ | ||
| 852 | 861 | showAge = find_option("age",0,0)!=0; |
| 853 | 862 | zRev = find_option("r","r",1); |
| 854 | 863 | timeOrder = find_option("t","t",0)!=0; |
| 855 | 864 | if( verboseFlag ){ |
| 856 | 865 | useHash = find_option("hash",0,0)!=0; |
| 866 | + showHash = find_option("h","h",0)!=0; | |
| 857 | 867 | } |
| 858 | 868 | treeFmt = find_option("tree",0,0)!=0; |
| 859 | 869 | if( treeFmt ){ |
| 860 | 870 | if( zRev==0 ) zRev = "current"; |
| 861 | 871 | } |
| 862 | 872 | |
| 863 | 873 | if( zRev!=0 ){ |
| 864 | 874 | db_find_and_open_repository(0, 0); |
| 865 | 875 | verify_all_options(); |
| 866 | - ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder,treeFmt); | |
| 876 | + ls_cmd_rev(zRev,verboseFlag,showAge,showHash,timeOrder,treeFmt); | |
| 867 | 877 | return; |
| 868 | 878 | }else if( find_option("R",0,1)!=0 ){ |
| 869 | 879 | fossil_fatal("the -r is required in addition to -R"); |
| 870 | 880 | } |
| 871 | 881 | |
| @@ -984,11 +994,11 @@ | ||
| 984 | 994 | |
| 985 | 995 | zRev = find_option("r","r",1); |
| 986 | 996 | if( zRev==0 ) zRev = "current"; |
| 987 | 997 | db_find_and_open_repository(0, 0); |
| 988 | 998 | verify_all_options(); |
| 989 | - ls_cmd_rev(zRev,0,0,0,1); | |
| 999 | + ls_cmd_rev(zRev,0,0,0,0,1); | |
| 990 | 1000 | } |
| 991 | 1001 | |
| 992 | 1002 | /* |
| 993 | 1003 | ** COMMAND: extras |
| 994 | 1004 | ** |
| @@ -1502,11 +1512,11 @@ | ||
| 1502 | 1512 | "# * All other text will be displayed as written\n", -1); |
| 1503 | 1513 | }else{ |
| 1504 | 1514 | blob_append(&prompt, |
| 1505 | 1515 | "# * Hyperlinks: [target] or [target|display-text]\n" |
| 1506 | 1516 | "# * Blank lines cause a paragraph break\n" |
| 1507 | - "# * Other text rendered as if it where HTML\n", -1 | |
| 1517 | + "# * Other text rendered as if it were HTML\n", -1 | |
| 1508 | 1518 | ); |
| 1509 | 1519 | } |
| 1510 | 1520 | blob_append(&prompt, "#\n", 2); |
| 1511 | 1521 | |
| 1512 | 1522 | if( dryRunFlag ){ |
| 1513 | 1523 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -718,10 +718,11 @@ | |
| 718 | */ |
| 719 | static void ls_cmd_rev( |
| 720 | const char *zRev, /* Revision string given */ |
| 721 | int verboseFlag, /* Verbose flag given */ |
| 722 | int showAge, /* Age flag given */ |
| 723 | int timeOrder, /* Order by time flag given */ |
| 724 | int treeFmt /* Show output in the tree format */ |
| 725 | ){ |
| 726 | Stmt q; |
| 727 | char *zOrderBy = "pathname COLLATE nocase"; |
| @@ -763,11 +764,11 @@ | |
| 763 | } |
| 764 | |
| 765 | compute_fileage(rid,0); |
| 766 | db_prepare(&q, |
| 767 | "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" |
| 768 | " blob.size\n" |
| 769 | " FROM fileage, blob\n" |
| 770 | " WHERE blob.rid=fileage.fid %s\n" |
| 771 | " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ |
| 772 | ); |
| 773 | blob_reset(&where); |
| @@ -778,11 +779,16 @@ | |
| 778 | const char *zFile = db_column_text(&q,1); |
| 779 | int size = db_column_int(&q,2); |
| 780 | if( treeFmt ){ |
| 781 | blob_appendf(&out, "%s\n", zFile); |
| 782 | }else if( verboseFlag ){ |
| 783 | fossil_print("%s %7d %s\n", zTime, size, zFile); |
| 784 | }else if( showAge ){ |
| 785 | fossil_print("%s %s\n", zTime, zFile); |
| 786 | }else{ |
| 787 | fossil_print("%s\n", zFile); |
| 788 | } |
| @@ -811,18 +817,20 @@ | |
| 811 | ** The --age option displays file commit times. Like -r, --age has the |
| 812 | ** side effect of making -t sort by commit time, not modification time. |
| 813 | ** |
| 814 | ** The -v option provides extra information about each file. Without -r, |
| 815 | ** -v displays the change status, in the manner of the changes command. |
| 816 | ** With -r, -v shows the commit time and size of the checked-in files. |
| 817 | ** |
| 818 | ** The -t option changes the sort order. Without -t, files are sorted by |
| 819 | ** path and name (case insensitive sort if -r). If neither --age nor -r |
| 820 | ** are used, -t sorts by modification time, otherwise by commit time. |
| 821 | ** |
| 822 | ** Options: |
| 823 | ** --age Show when each file was committed |
| 824 | ** --hash With -v, verify file status using hashing |
| 825 | ** rather than relying on file sizes and mtimes |
| 826 | ** -r VERSION The specific check-in to list |
| 827 | ** -R|--repository REPO Extract info from repository REPO |
| 828 | ** -t Sort output in time order |
| @@ -840,10 +848,11 @@ | |
| 840 | int timeOrder; |
| 841 | char *zOrderBy = "pathname"; |
| 842 | Blob where; |
| 843 | int i; |
| 844 | int useHash = 0; |
| 845 | const char *zName; |
| 846 | const char *zRev; |
| 847 | |
| 848 | verboseFlag = find_option("verbose","v", 0)!=0; |
| 849 | if( !verboseFlag ){ |
| @@ -852,20 +861,21 @@ | |
| 852 | showAge = find_option("age",0,0)!=0; |
| 853 | zRev = find_option("r","r",1); |
| 854 | timeOrder = find_option("t","t",0)!=0; |
| 855 | if( verboseFlag ){ |
| 856 | useHash = find_option("hash",0,0)!=0; |
| 857 | } |
| 858 | treeFmt = find_option("tree",0,0)!=0; |
| 859 | if( treeFmt ){ |
| 860 | if( zRev==0 ) zRev = "current"; |
| 861 | } |
| 862 | |
| 863 | if( zRev!=0 ){ |
| 864 | db_find_and_open_repository(0, 0); |
| 865 | verify_all_options(); |
| 866 | ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder,treeFmt); |
| 867 | return; |
| 868 | }else if( find_option("R",0,1)!=0 ){ |
| 869 | fossil_fatal("the -r is required in addition to -R"); |
| 870 | } |
| 871 | |
| @@ -984,11 +994,11 @@ | |
| 984 | |
| 985 | zRev = find_option("r","r",1); |
| 986 | if( zRev==0 ) zRev = "current"; |
| 987 | db_find_and_open_repository(0, 0); |
| 988 | verify_all_options(); |
| 989 | ls_cmd_rev(zRev,0,0,0,1); |
| 990 | } |
| 991 | |
| 992 | /* |
| 993 | ** COMMAND: extras |
| 994 | ** |
| @@ -1502,11 +1512,11 @@ | |
| 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 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -718,10 +718,11 @@ | |
| 718 | */ |
| 719 | static void ls_cmd_rev( |
| 720 | const char *zRev, /* Revision string given */ |
| 721 | int verboseFlag, /* Verbose flag given */ |
| 722 | int showAge, /* Age flag given */ |
| 723 | int showHash, /* Show hash flag given */ |
| 724 | int timeOrder, /* Order by time flag given */ |
| 725 | int treeFmt /* Show output in the tree format */ |
| 726 | ){ |
| 727 | Stmt q; |
| 728 | char *zOrderBy = "pathname COLLATE nocase"; |
| @@ -763,11 +764,11 @@ | |
| 764 | } |
| 765 | |
| 766 | compute_fileage(rid,0); |
| 767 | db_prepare(&q, |
| 768 | "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" |
| 769 | " blob.size, fileage.uuid\n" |
| 770 | " FROM fileage, blob\n" |
| 771 | " WHERE blob.rid=fileage.fid %s\n" |
| 772 | " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ |
| 773 | ); |
| 774 | blob_reset(&where); |
| @@ -778,11 +779,16 @@ | |
| 779 | const char *zFile = db_column_text(&q,1); |
| 780 | int size = db_column_int(&q,2); |
| 781 | if( treeFmt ){ |
| 782 | blob_appendf(&out, "%s\n", zFile); |
| 783 | }else if( verboseFlag ){ |
| 784 | if( showHash ){ |
| 785 | const char *zUuid = db_column_text(&q,3); |
| 786 | fossil_print("%s %7d [%S] %s\n", zTime, size, zUuid, zFile); |
| 787 | }else{ |
| 788 | fossil_print("%s %7d %s\n", zTime, size, zFile); |
| 789 | } |
| 790 | }else if( showAge ){ |
| 791 | fossil_print("%s %s\n", zTime, zFile); |
| 792 | }else{ |
| 793 | fossil_print("%s\n", zFile); |
| 794 | } |
| @@ -811,18 +817,20 @@ | |
| 817 | ** The --age option displays file commit times. Like -r, --age has the |
| 818 | ** side effect of making -t sort by commit time, not modification time. |
| 819 | ** |
| 820 | ** The -v option provides extra information about each file. Without -r, |
| 821 | ** -v displays the change status, in the manner of the changes command. |
| 822 | ** With -r, -v shows the commit time and size of the checked-in files; in |
| 823 | ** this combination, it additionally shows file hashes with -h. |
| 824 | ** |
| 825 | ** The -t option changes the sort order. Without -t, files are sorted by |
| 826 | ** path and name (case insensitive sort if -r). If neither --age nor -r |
| 827 | ** are used, -t sorts by modification time, otherwise by commit time. |
| 828 | ** |
| 829 | ** Options: |
| 830 | ** --age Show when each file was committed |
| 831 | ** -h With -v and -r, show file hashes |
| 832 | ** --hash With -v, verify file status using hashing |
| 833 | ** rather than relying on file sizes and mtimes |
| 834 | ** -r VERSION The specific check-in to list |
| 835 | ** -R|--repository REPO Extract info from repository REPO |
| 836 | ** -t Sort output in time order |
| @@ -840,10 +848,11 @@ | |
| 848 | int timeOrder; |
| 849 | char *zOrderBy = "pathname"; |
| 850 | Blob where; |
| 851 | int i; |
| 852 | int useHash = 0; |
| 853 | int showHash = 0; |
| 854 | const char *zName; |
| 855 | const char *zRev; |
| 856 | |
| 857 | verboseFlag = find_option("verbose","v", 0)!=0; |
| 858 | if( !verboseFlag ){ |
| @@ -852,20 +861,21 @@ | |
| 861 | showAge = find_option("age",0,0)!=0; |
| 862 | zRev = find_option("r","r",1); |
| 863 | timeOrder = find_option("t","t",0)!=0; |
| 864 | if( verboseFlag ){ |
| 865 | useHash = find_option("hash",0,0)!=0; |
| 866 | showHash = find_option("h","h",0)!=0; |
| 867 | } |
| 868 | treeFmt = find_option("tree",0,0)!=0; |
| 869 | if( treeFmt ){ |
| 870 | if( zRev==0 ) zRev = "current"; |
| 871 | } |
| 872 | |
| 873 | if( zRev!=0 ){ |
| 874 | db_find_and_open_repository(0, 0); |
| 875 | verify_all_options(); |
| 876 | ls_cmd_rev(zRev,verboseFlag,showAge,showHash,timeOrder,treeFmt); |
| 877 | return; |
| 878 | }else if( find_option("R",0,1)!=0 ){ |
| 879 | fossil_fatal("the -r is required in addition to -R"); |
| 880 | } |
| 881 | |
| @@ -984,11 +994,11 @@ | |
| 994 | |
| 995 | zRev = find_option("r","r",1); |
| 996 | if( zRev==0 ) zRev = "current"; |
| 997 | db_find_and_open_repository(0, 0); |
| 998 | verify_all_options(); |
| 999 | ls_cmd_rev(zRev,0,0,0,0,1); |
| 1000 | } |
| 1001 | |
| 1002 | /* |
| 1003 | ** COMMAND: extras |
| 1004 | ** |
| @@ -1502,11 +1512,11 @@ | |
| 1512 | "# * All other text will be displayed as written\n", -1); |
| 1513 | }else{ |
| 1514 | blob_append(&prompt, |
| 1515 | "# * Hyperlinks: [target] or [target|display-text]\n" |
| 1516 | "# * Blank lines cause a paragraph break\n" |
| 1517 | "# * Other text rendered as if it were HTML\n", -1 |
| 1518 | ); |
| 1519 | } |
| 1520 | blob_append(&prompt, "#\n", 2); |
| 1521 | |
| 1522 | if( dryRunFlag ){ |
| 1523 |
+225
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -19,10 +19,11 @@ | ||
| 19 | 19 | ** from the local repository. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "checkout.h" |
| 23 | 23 | #include <assert.h> |
| 24 | +#include <zlib.h> | |
| 24 | 25 | |
| 25 | 26 | /* |
| 26 | 27 | ** Check to see if there is an existing check-out that has been |
| 27 | 28 | ** modified. Return values: |
| 28 | 29 | ** |
| @@ -429,5 +430,229 @@ | ||
| 429 | 430 | } |
| 430 | 431 | unlink_local_database(1); |
| 431 | 432 | db_close(1); |
| 432 | 433 | unlink_local_database(0); |
| 433 | 434 | } |
| 435 | + | |
| 436 | + | |
| 437 | +/* | |
| 438 | +** COMMAND: get | |
| 439 | +** | |
| 440 | +** Usage: %fossil get URL ?VERSION? ?OPTIONS? | |
| 441 | +** | |
| 442 | +** Download a single check-in from a remote repository named URL and | |
| 443 | +** unpack all of the files locally. The check-in is identified by VERSION. | |
| 444 | +** | |
| 445 | +** URL can be a traditional URL like one of: | |
| 446 | +** | |
| 447 | +** * https://domain.com/project | |
| 448 | +** * ssh://my-server/project.fossil | |
| 449 | +** * file:/home/user/Fossils/project.fossil | |
| 450 | +** | |
| 451 | +** Or URL can be just the name of a local repository without the "file:" | |
| 452 | +** prefix. | |
| 453 | +** | |
| 454 | +** This command works by downloading an SQL archive of the requested | |
| 455 | +** check-in and then extracting all the files from the archive. | |
| 456 | +** | |
| 457 | +** Options: | |
| 458 | +** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ." | |
| 459 | +** to extract into the local directory. | |
| 460 | +** | |
| 461 | +** -f|--force Overwrite existing files | |
| 462 | +** | |
| 463 | +** --list List all the files that would have been checked | |
| 464 | +** out but do not actually write anything to the | |
| 465 | +** filesystem. | |
| 466 | +** | |
| 467 | +** --sqlar ARCHIVE Store the check-out in an SQL-archive rather | |
| 468 | +** than unpacking them into separate files. | |
| 469 | +** | |
| 470 | +** -v|--verbose Show all files as they are extracted | |
| 471 | +*/ | |
| 472 | +void get_cmd(void){ | |
| 473 | + int forceFlag = find_option("force","f",0)!=0; | |
| 474 | + int bVerbose = find_option("verbose","v",0)!=0; | |
| 475 | + int bQuiet = find_option("quiet","q",0)!=0; | |
| 476 | + int bDebug = find_option("debug",0,0)!=0; | |
| 477 | + int bList = find_option("list",0,0)!=0; | |
| 478 | + const char *zSqlArchive = find_option("sqlar",0,1); | |
| 479 | + const char *z; | |
| 480 | + char *zDest = 0; /* Where to store results */ | |
| 481 | + char *zSql; /* SQL used to query the results */ | |
| 482 | + const char *zUrl; /* Url to get */ | |
| 483 | + const char *zVers; /* Version name to get */ | |
| 484 | + unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; | |
| 485 | + Blob in, out; /* I/O for the HTTP request */ | |
| 486 | + Blob file; /* A file to extract */ | |
| 487 | + sqlite3 *db; /* Database containing downloaded sqlar */ | |
| 488 | + sqlite3_stmt *pStmt; /* Statement for querying the database */ | |
| 489 | + int rc; /* Result of subroutine calls */ | |
| 490 | + int nFile = 0; /* Number of files written */ | |
| 491 | + int nDir = 0; /* Number of directories written */ | |
| 492 | + i64 nByte = 0; /* Number of bytes written */ | |
| 493 | + | |
| 494 | + z = find_option("dest",0,1); | |
| 495 | + if( z ) zDest = fossil_strdup(z); | |
| 496 | + verify_all_options(); | |
| 497 | + if( g.argc<3 || g.argc>4 ){ | |
| 498 | + usage("URL ?VERSION? ?OPTIONS?"); | |
| 499 | + } | |
| 500 | + zUrl = g.argv[2]; | |
| 501 | + zVers = g.argc==4 ? g.argv[3] : "trunk"; | |
| 502 | + | |
| 503 | + /* Parse the URL of the repository */ | |
| 504 | + url_parse(zUrl, 0); | |
| 505 | + | |
| 506 | + /* Construct an appropriate name for the destination directory */ | |
| 507 | + if( zDest==0 ){ | |
| 508 | + int i; | |
| 509 | + const char *zTail; | |
| 510 | + const char *zDot; | |
| 511 | + int n; | |
| 512 | + if( g.url.isFile ){ | |
| 513 | + zTail = file_tail(g.url.name); | |
| 514 | + }else{ | |
| 515 | + zTail = file_tail(g.url.path); | |
| 516 | + } | |
| 517 | + zDot = strchr(zTail,'.'); | |
| 518 | + if( zDot==0 ) zDot = zTail+strlen(zTail); | |
| 519 | + n = (int)(zDot - zTail); | |
| 520 | + zDest = mprintf("%.*s-%s", n, zTail, zVers); | |
| 521 | + for(i=0; zDest[i]; i++){ | |
| 522 | + char c = zDest[i]; | |
| 523 | + if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){ | |
| 524 | + zDest[i] = '-'; | |
| 525 | + } | |
| 526 | + } | |
| 527 | + } | |
| 528 | + if( bDebug ){ | |
| 529 | + fossil_print("dest = %s\n", zDest); | |
| 530 | + } | |
| 531 | + | |
| 532 | + /* Error checking */ | |
| 533 | + if( zDest!=file_tail(zDest) ){ | |
| 534 | + fossil_fatal("--dest must be a simple directory name, not a path"); | |
| 535 | + } | |
| 536 | + if( zVers!=file_tail(zVers) ){ | |
| 537 | + fossil_fatal("The \"fossil get\" command does not currently work with" | |
| 538 | + " version names that contain \"/\". This will be fixed in" | |
| 539 | + " a future release."); | |
| 540 | + } | |
| 541 | + /* To relax the restrictions above, change the subpath URL formula below | |
| 542 | + ** to use query parameters. Ex: /sqlar?r=%t&name=%t */ | |
| 543 | + | |
| 544 | + if( !forceFlag ){ | |
| 545 | + if( zSqlArchive ){ | |
| 546 | + if( file_isdir(zSqlArchive, ExtFILE)>0 ){ | |
| 547 | + fossil_fatal("file already exists: \"%s\"", zSqlArchive); | |
| 548 | + } | |
| 549 | + }else if( file_isdir(zDest, ExtFILE)>0 ){ | |
| 550 | + if( fossil_strcmp(zDest,".")==0 ){ | |
| 551 | + if( file_directory_size(zDest,0,1) ){ | |
| 552 | + fossil_fatal("current directory is not empty"); | |
| 553 | + } | |
| 554 | + }else{ | |
| 555 | + fossil_fatal("\"%s\" already exists", zDest); | |
| 556 | + } | |
| 557 | + } | |
| 558 | + } | |
| 559 | + | |
| 560 | + /* Construct a subpath on the URL if necessary */ | |
| 561 | + if( g.url.isFile ){ | |
| 562 | + g.url.subpath = mprintf("/sqlar/%t/%t.sqlar", zVers, zDest); | |
| 563 | + }else{ | |
| 564 | + g.url.subpath = mprintf("%s/sqlar/%t/%t.sqlar", g.url.path, zVers, zDest); | |
| 565 | + } | |
| 566 | + | |
| 567 | + if( bDebug ){ | |
| 568 | + urlparse_print(0); | |
| 569 | + } | |
| 570 | + | |
| 571 | + /* Fetch the ZIP archive for the requested check-in */ | |
| 572 | + blob_init(&in, 0, 0); | |
| 573 | + blob_init(&out, 0, 0); | |
| 574 | + if( bDebug ) mHttpFlags |= HTTP_VERBOSE; | |
| 575 | + if( bQuiet ) mHttpFlags |= HTTP_QUIET; | |
| 576 | + rc = http_exchange(&in, &out, mHttpFlags, 4, 0); | |
| 577 | + if( rc | |
| 578 | + || out.nUsed<512 | |
| 579 | + || (out.nUsed%512)!=0 | |
| 580 | + || memcmp(out.aData,"SQLite format 3",16)!=0 | |
| 581 | + ){ | |
| 582 | + fossil_fatal("Server did not return the requested check-in."); | |
| 583 | + } | |
| 584 | + | |
| 585 | + if( zSqlArchive ){ | |
| 586 | + blob_write_to_file(&out, zSqlArchive); | |
| 587 | + if( bVerbose ) fossil_print("%s\n", zSqlArchive); | |
| 588 | + return; | |
| 589 | + } | |
| 590 | + | |
| 591 | + rc = sqlite3_open(":memory:", &db); | |
| 592 | + if( rc==SQLITE_OK ){ | |
| 593 | + int sz = blob_size(&out); | |
| 594 | + rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz, | |
| 595 | + SQLITE_DESERIALIZE_READONLY); | |
| 596 | + } | |
| 597 | + if( rc!=SQLITE_OK ){ | |
| 598 | + fossil_fatal("Cannot create an in-memory database: %s", | |
| 599 | + sqlite3_errmsg(db)); | |
| 600 | + } | |
| 601 | + zSql = mprintf("SELECT name, mode, sz, data FROM sqlar" | |
| 602 | + " WHERE name GLOB '%q*'", zDest); | |
| 603 | + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); | |
| 604 | + fossil_free(zSql); | |
| 605 | + if( rc!=0 ){ | |
| 606 | + fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db)); | |
| 607 | + } | |
| 608 | + blob_init(&file, 0, 0); | |
| 609 | + while( sqlite3_step(pStmt)==SQLITE_ROW ){ | |
| 610 | + const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0); | |
| 611 | + int mode = sqlite3_column_int(pStmt, 1); | |
| 612 | + int sz = sqlite3_column_int(pStmt, 2); | |
| 613 | + if( bList ){ | |
| 614 | + fossil_print("%s\n", zFilename); | |
| 615 | + }else if( mode & 0x4000 ){ | |
| 616 | + /* A directory name */ | |
| 617 | + nDir++; | |
| 618 | + file_mkdir(zFilename, ExtFILE, 1); | |
| 619 | + }else{ | |
| 620 | + /* A file */ | |
| 621 | + unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3); | |
| 622 | + unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3); | |
| 623 | + unsigned long int nOut2 = (unsigned long int)sz; | |
| 624 | + nFile++; | |
| 625 | + nByte += sz; | |
| 626 | + blob_resize(&file, sz); | |
| 627 | + if( nIn<sz ){ | |
| 628 | + rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2, | |
| 629 | + inBuf, nIn); | |
| 630 | + if( rc!=Z_OK ){ | |
| 631 | + fossil_fatal("Failed to uncompress file %s", zFilename); | |
| 632 | + } | |
| 633 | + }else{ | |
| 634 | + memcpy(blob_buffer(&file), inBuf, sz); | |
| 635 | + } | |
| 636 | + blob_write_to_file(&file, zFilename); | |
| 637 | + if( mode & 0x40 ){ | |
| 638 | + file_setexe(zFilename, 1); | |
| 639 | + } | |
| 640 | + blob_zero(&file); | |
| 641 | + if( bVerbose ){ | |
| 642 | + fossil_print("%s\n", zFilename); | |
| 643 | + } | |
| 644 | + } | |
| 645 | + } | |
| 646 | + sqlite3_finalize(pStmt); | |
| 647 | + sqlite3_close(db); | |
| 648 | + blob_zero(&out); | |
| 649 | + if( !bVerbose && !bQuiet && nFile>0 && zDest ){ | |
| 650 | + fossil_print("%d files (%,lld bytes) written into %s", | |
| 651 | + nFile, nByte, zDest); | |
| 652 | + if( nDir>1 ){ | |
| 653 | + fossil_print(" and %d subdirectories\n", nDir-1); | |
| 654 | + }else{ | |
| 655 | + fossil_print("\n"); | |
| 656 | + } | |
| 657 | + } | |
| 658 | +} | |
| 434 | 659 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -19,10 +19,11 @@ | |
| 19 | ** from the local repository. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "checkout.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | /* |
| 26 | ** Check to see if there is an existing check-out that has been |
| 27 | ** modified. Return values: |
| 28 | ** |
| @@ -429,5 +430,229 @@ | |
| 429 | } |
| 430 | unlink_local_database(1); |
| 431 | db_close(1); |
| 432 | unlink_local_database(0); |
| 433 | } |
| 434 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -19,10 +19,11 @@ | |
| 19 | ** from the local repository. |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "checkout.h" |
| 23 | #include <assert.h> |
| 24 | #include <zlib.h> |
| 25 | |
| 26 | /* |
| 27 | ** Check to see if there is an existing check-out that has been |
| 28 | ** modified. Return values: |
| 29 | ** |
| @@ -429,5 +430,229 @@ | |
| 430 | } |
| 431 | unlink_local_database(1); |
| 432 | db_close(1); |
| 433 | unlink_local_database(0); |
| 434 | } |
| 435 | |
| 436 | |
| 437 | /* |
| 438 | ** COMMAND: get |
| 439 | ** |
| 440 | ** Usage: %fossil get URL ?VERSION? ?OPTIONS? |
| 441 | ** |
| 442 | ** Download a single check-in from a remote repository named URL and |
| 443 | ** unpack all of the files locally. The check-in is identified by VERSION. |
| 444 | ** |
| 445 | ** URL can be a traditional URL like one of: |
| 446 | ** |
| 447 | ** * https://domain.com/project |
| 448 | ** * ssh://my-server/project.fossil |
| 449 | ** * file:/home/user/Fossils/project.fossil |
| 450 | ** |
| 451 | ** Or URL can be just the name of a local repository without the "file:" |
| 452 | ** prefix. |
| 453 | ** |
| 454 | ** This command works by downloading an SQL archive of the requested |
| 455 | ** check-in and then extracting all the files from the archive. |
| 456 | ** |
| 457 | ** Options: |
| 458 | ** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ." |
| 459 | ** to extract into the local directory. |
| 460 | ** |
| 461 | ** -f|--force Overwrite existing files |
| 462 | ** |
| 463 | ** --list List all the files that would have been checked |
| 464 | ** out but do not actually write anything to the |
| 465 | ** filesystem. |
| 466 | ** |
| 467 | ** --sqlar ARCHIVE Store the check-out in an SQL-archive rather |
| 468 | ** than unpacking them into separate files. |
| 469 | ** |
| 470 | ** -v|--verbose Show all files as they are extracted |
| 471 | */ |
| 472 | void get_cmd(void){ |
| 473 | int forceFlag = find_option("force","f",0)!=0; |
| 474 | int bVerbose = find_option("verbose","v",0)!=0; |
| 475 | int bQuiet = find_option("quiet","q",0)!=0; |
| 476 | int bDebug = find_option("debug",0,0)!=0; |
| 477 | int bList = find_option("list",0,0)!=0; |
| 478 | const char *zSqlArchive = find_option("sqlar",0,1); |
| 479 | const char *z; |
| 480 | char *zDest = 0; /* Where to store results */ |
| 481 | char *zSql; /* SQL used to query the results */ |
| 482 | const char *zUrl; /* Url to get */ |
| 483 | const char *zVers; /* Version name to get */ |
| 484 | unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 485 | Blob in, out; /* I/O for the HTTP request */ |
| 486 | Blob file; /* A file to extract */ |
| 487 | sqlite3 *db; /* Database containing downloaded sqlar */ |
| 488 | sqlite3_stmt *pStmt; /* Statement for querying the database */ |
| 489 | int rc; /* Result of subroutine calls */ |
| 490 | int nFile = 0; /* Number of files written */ |
| 491 | int nDir = 0; /* Number of directories written */ |
| 492 | i64 nByte = 0; /* Number of bytes written */ |
| 493 | |
| 494 | z = find_option("dest",0,1); |
| 495 | if( z ) zDest = fossil_strdup(z); |
| 496 | verify_all_options(); |
| 497 | if( g.argc<3 || g.argc>4 ){ |
| 498 | usage("URL ?VERSION? ?OPTIONS?"); |
| 499 | } |
| 500 | zUrl = g.argv[2]; |
| 501 | zVers = g.argc==4 ? g.argv[3] : "trunk"; |
| 502 | |
| 503 | /* Parse the URL of the repository */ |
| 504 | url_parse(zUrl, 0); |
| 505 | |
| 506 | /* Construct an appropriate name for the destination directory */ |
| 507 | if( zDest==0 ){ |
| 508 | int i; |
| 509 | const char *zTail; |
| 510 | const char *zDot; |
| 511 | int n; |
| 512 | if( g.url.isFile ){ |
| 513 | zTail = file_tail(g.url.name); |
| 514 | }else{ |
| 515 | zTail = file_tail(g.url.path); |
| 516 | } |
| 517 | zDot = strchr(zTail,'.'); |
| 518 | if( zDot==0 ) zDot = zTail+strlen(zTail); |
| 519 | n = (int)(zDot - zTail); |
| 520 | zDest = mprintf("%.*s-%s", n, zTail, zVers); |
| 521 | for(i=0; zDest[i]; i++){ |
| 522 | char c = zDest[i]; |
| 523 | if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){ |
| 524 | zDest[i] = '-'; |
| 525 | } |
| 526 | } |
| 527 | } |
| 528 | if( bDebug ){ |
| 529 | fossil_print("dest = %s\n", zDest); |
| 530 | } |
| 531 | |
| 532 | /* Error checking */ |
| 533 | if( zDest!=file_tail(zDest) ){ |
| 534 | fossil_fatal("--dest must be a simple directory name, not a path"); |
| 535 | } |
| 536 | if( zVers!=file_tail(zVers) ){ |
| 537 | fossil_fatal("The \"fossil get\" command does not currently work with" |
| 538 | " version names that contain \"/\". This will be fixed in" |
| 539 | " a future release."); |
| 540 | } |
| 541 | /* To relax the restrictions above, change the subpath URL formula below |
| 542 | ** to use query parameters. Ex: /sqlar?r=%t&name=%t */ |
| 543 | |
| 544 | if( !forceFlag ){ |
| 545 | if( zSqlArchive ){ |
| 546 | if( file_isdir(zSqlArchive, ExtFILE)>0 ){ |
| 547 | fossil_fatal("file already exists: \"%s\"", zSqlArchive); |
| 548 | } |
| 549 | }else if( file_isdir(zDest, ExtFILE)>0 ){ |
| 550 | if( fossil_strcmp(zDest,".")==0 ){ |
| 551 | if( file_directory_size(zDest,0,1) ){ |
| 552 | fossil_fatal("current directory is not empty"); |
| 553 | } |
| 554 | }else{ |
| 555 | fossil_fatal("\"%s\" already exists", zDest); |
| 556 | } |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | /* Construct a subpath on the URL if necessary */ |
| 561 | if( g.url.isFile ){ |
| 562 | g.url.subpath = mprintf("/sqlar/%t/%t.sqlar", zVers, zDest); |
| 563 | }else{ |
| 564 | g.url.subpath = mprintf("%s/sqlar/%t/%t.sqlar", g.url.path, zVers, zDest); |
| 565 | } |
| 566 | |
| 567 | if( bDebug ){ |
| 568 | urlparse_print(0); |
| 569 | } |
| 570 | |
| 571 | /* Fetch the ZIP archive for the requested check-in */ |
| 572 | blob_init(&in, 0, 0); |
| 573 | blob_init(&out, 0, 0); |
| 574 | if( bDebug ) mHttpFlags |= HTTP_VERBOSE; |
| 575 | if( bQuiet ) mHttpFlags |= HTTP_QUIET; |
| 576 | rc = http_exchange(&in, &out, mHttpFlags, 4, 0); |
| 577 | if( rc |
| 578 | || out.nUsed<512 |
| 579 | || (out.nUsed%512)!=0 |
| 580 | || memcmp(out.aData,"SQLite format 3",16)!=0 |
| 581 | ){ |
| 582 | fossil_fatal("Server did not return the requested check-in."); |
| 583 | } |
| 584 | |
| 585 | if( zSqlArchive ){ |
| 586 | blob_write_to_file(&out, zSqlArchive); |
| 587 | if( bVerbose ) fossil_print("%s\n", zSqlArchive); |
| 588 | return; |
| 589 | } |
| 590 | |
| 591 | rc = sqlite3_open(":memory:", &db); |
| 592 | if( rc==SQLITE_OK ){ |
| 593 | int sz = blob_size(&out); |
| 594 | rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz, |
| 595 | SQLITE_DESERIALIZE_READONLY); |
| 596 | } |
| 597 | if( rc!=SQLITE_OK ){ |
| 598 | fossil_fatal("Cannot create an in-memory database: %s", |
| 599 | sqlite3_errmsg(db)); |
| 600 | } |
| 601 | zSql = mprintf("SELECT name, mode, sz, data FROM sqlar" |
| 602 | " WHERE name GLOB '%q*'", zDest); |
| 603 | rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| 604 | fossil_free(zSql); |
| 605 | if( rc!=0 ){ |
| 606 | fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db)); |
| 607 | } |
| 608 | blob_init(&file, 0, 0); |
| 609 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 610 | const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0); |
| 611 | int mode = sqlite3_column_int(pStmt, 1); |
| 612 | int sz = sqlite3_column_int(pStmt, 2); |
| 613 | if( bList ){ |
| 614 | fossil_print("%s\n", zFilename); |
| 615 | }else if( mode & 0x4000 ){ |
| 616 | /* A directory name */ |
| 617 | nDir++; |
| 618 | file_mkdir(zFilename, ExtFILE, 1); |
| 619 | }else{ |
| 620 | /* A file */ |
| 621 | unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3); |
| 622 | unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3); |
| 623 | unsigned long int nOut2 = (unsigned long int)sz; |
| 624 | nFile++; |
| 625 | nByte += sz; |
| 626 | blob_resize(&file, sz); |
| 627 | if( nIn<sz ){ |
| 628 | rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2, |
| 629 | inBuf, nIn); |
| 630 | if( rc!=Z_OK ){ |
| 631 | fossil_fatal("Failed to uncompress file %s", zFilename); |
| 632 | } |
| 633 | }else{ |
| 634 | memcpy(blob_buffer(&file), inBuf, sz); |
| 635 | } |
| 636 | blob_write_to_file(&file, zFilename); |
| 637 | if( mode & 0x40 ){ |
| 638 | file_setexe(zFilename, 1); |
| 639 | } |
| 640 | blob_zero(&file); |
| 641 | if( bVerbose ){ |
| 642 | fossil_print("%s\n", zFilename); |
| 643 | } |
| 644 | } |
| 645 | } |
| 646 | sqlite3_finalize(pStmt); |
| 647 | sqlite3_close(db); |
| 648 | blob_zero(&out); |
| 649 | if( !bVerbose && !bQuiet && nFile>0 && zDest ){ |
| 650 | fossil_print("%d files (%,lld bytes) written into %s", |
| 651 | nFile, nByte, zDest); |
| 652 | if( nDir>1 ){ |
| 653 | fossil_print(" and %d subdirectories\n", nDir-1); |
| 654 | }else{ |
| 655 | fossil_print("\n"); |
| 656 | } |
| 657 | } |
| 658 | } |
| 659 |
+1
-1
| --- src/clearsign.c | ||
| +++ src/clearsign.c | ||
| @@ -65,11 +65,11 @@ | ||
| 65 | 65 | blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n"); |
| 66 | 66 | blob_appendf(pOut, "%s", blob_str(&tmpBlob)); |
| 67 | 67 | blob_zero(&tmpBlob); |
| 68 | 68 | blob_read_from_file(&tmpBlob, zIn, ExtFILE); |
| 69 | 69 | /* Add signature - already armored by SSH */ |
| 70 | - blob_appendf(pOut, "%s", blob_str(&tmpBlob)); | |
| 70 | + blob_appendb(pOut, &tmpBlob); | |
| 71 | 71 | }else{ |
| 72 | 72 | /* Assume that the external command creates non-detached signatures */ |
| 73 | 73 | blob_read_from_file(pOut, zIn, ExtFILE); |
| 74 | 74 | } |
| 75 | 75 | }else{ |
| 76 | 76 |
| --- src/clearsign.c | |
| +++ src/clearsign.c | |
| @@ -65,11 +65,11 @@ | |
| 65 | blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n"); |
| 66 | blob_appendf(pOut, "%s", blob_str(&tmpBlob)); |
| 67 | blob_zero(&tmpBlob); |
| 68 | blob_read_from_file(&tmpBlob, zIn, ExtFILE); |
| 69 | /* Add signature - already armored by SSH */ |
| 70 | blob_appendf(pOut, "%s", blob_str(&tmpBlob)); |
| 71 | }else{ |
| 72 | /* Assume that the external command creates non-detached signatures */ |
| 73 | blob_read_from_file(pOut, zIn, ExtFILE); |
| 74 | } |
| 75 | }else{ |
| 76 |
| --- src/clearsign.c | |
| +++ src/clearsign.c | |
| @@ -65,11 +65,11 @@ | |
| 65 | blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n"); |
| 66 | blob_appendf(pOut, "%s", blob_str(&tmpBlob)); |
| 67 | blob_zero(&tmpBlob); |
| 68 | blob_read_from_file(&tmpBlob, zIn, ExtFILE); |
| 69 | /* Add signature - already armored by SSH */ |
| 70 | blob_appendb(pOut, &tmpBlob); |
| 71 | }else{ |
| 72 | /* Assume that the external command creates non-detached signatures */ |
| 73 | blob_read_from_file(pOut, zIn, ExtFILE); |
| 74 | } |
| 75 | }else{ |
| 76 |
+7
-27
| --- src/clone.c | ||
| +++ src/clone.c | ||
| @@ -405,40 +405,18 @@ | ||
| 405 | 405 | db_protect_pop(); |
| 406 | 406 | } |
| 407 | 407 | } |
| 408 | 408 | |
| 409 | 409 | /* |
| 410 | -** WEBPAGE: download | |
| 410 | +** WEBPAGE: howtoclone | |
| 411 | 411 | ** |
| 412 | -** Provide a simple page that enables newbies to download the latest tarball or | |
| 413 | -** ZIP archive, and provides instructions on how to clone. | |
| 412 | +** Provide instructions on how to clone this repository. | |
| 414 | 413 | */ |
| 415 | -void download_page(void){ | |
| 414 | +void howtoclone_page(void){ | |
| 416 | 415 | login_check_credentials(); |
| 417 | 416 | cgi_check_for_malice(); |
| 418 | - style_header("Download Page"); | |
| 419 | - if( !g.perm.Zip ){ | |
| 420 | - @ <p>Bummer. You do not have permission to download. | |
| 421 | - if( g.zLogin==0 || g.zLogin[0]==0 ){ | |
| 422 | - @ Maybe it would work better if you | |
| 423 | - @ %z(href("%R/login"))logged in</a>. | |
| 424 | - }else{ | |
| 425 | - @ Contact the site administrator and ask them to give | |
| 426 | - @ you "Download Zip" privileges. | |
| 427 | - } | |
| 428 | - }else{ | |
| 429 | - const char *zDLTag = db_get("download-tag","trunk"); | |
| 430 | - const char *zNm = db_get("short-project-name","download"); | |
| 431 | - char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); | |
| 432 | - @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> | |
| 433 | - zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); | |
| 434 | - @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> | |
| 435 | - if( g.zLogin!=0 ){ | |
| 436 | - zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); | |
| 437 | - @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> | |
| 438 | - } | |
| 439 | - } | |
| 417 | + style_header("How To Clone This Repository"); | |
| 440 | 418 | if( !g.perm.Clone ){ |
| 441 | 419 | @ <p>You are not authorized to clone this repository. |
| 442 | 420 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 443 | 421 | @ Maybe you would be able to clone if you |
| 444 | 422 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | ||
| 446 | 424 | @ Contact the site administrator and ask them to give |
| 447 | 425 | @ you "Clone" privileges in order to clone. |
| 448 | 426 | } |
| 449 | 427 | }else{ |
| 450 | 428 | const char *zNm = db_get("short-project-name","clone"); |
| 451 | - @ <p>Clone the repository using this command: | |
| 429 | + @ <p>Clone this repository by running a command like the following: | |
| 452 | 430 | @ <blockquote><pre> |
| 453 | 431 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 454 | 432 | @ </pre></blockquote> |
| 433 | + @ <p>Do a web search for "fossil clone" or similar to find additional | |
| 434 | + @ information about using a cloned Fossil repository. | |
| 455 | 435 | } |
| 456 | 436 | style_finish_page(); |
| 457 | 437 | } |
| 458 | 438 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -405,40 +405,18 @@ | |
| 405 | db_protect_pop(); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /* |
| 410 | ** WEBPAGE: download |
| 411 | ** |
| 412 | ** Provide a simple page that enables newbies to download the latest tarball or |
| 413 | ** ZIP archive, and provides instructions on how to clone. |
| 414 | */ |
| 415 | void download_page(void){ |
| 416 | login_check_credentials(); |
| 417 | cgi_check_for_malice(); |
| 418 | style_header("Download Page"); |
| 419 | if( !g.perm.Zip ){ |
| 420 | @ <p>Bummer. You do not have permission to download. |
| 421 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 422 | @ Maybe it would work better if you |
| 423 | @ %z(href("%R/login"))logged in</a>. |
| 424 | }else{ |
| 425 | @ Contact the site administrator and ask them to give |
| 426 | @ you "Download Zip" privileges. |
| 427 | } |
| 428 | }else{ |
| 429 | const char *zDLTag = db_get("download-tag","trunk"); |
| 430 | const char *zNm = db_get("short-project-name","download"); |
| 431 | char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); |
| 432 | @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> |
| 433 | zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); |
| 434 | @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> |
| 435 | if( g.zLogin!=0 ){ |
| 436 | zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); |
| 437 | @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> |
| 438 | } |
| 439 | } |
| 440 | if( !g.perm.Clone ){ |
| 441 | @ <p>You are not authorized to clone this repository. |
| 442 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 443 | @ Maybe you would be able to clone if you |
| 444 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | |
| 446 | @ Contact the site administrator and ask them to give |
| 447 | @ you "Clone" privileges in order to clone. |
| 448 | } |
| 449 | }else{ |
| 450 | const char *zNm = db_get("short-project-name","clone"); |
| 451 | @ <p>Clone the repository using this command: |
| 452 | @ <blockquote><pre> |
| 453 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 454 | @ </pre></blockquote> |
| 455 | } |
| 456 | style_finish_page(); |
| 457 | } |
| 458 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -405,40 +405,18 @@ | |
| 405 | db_protect_pop(); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /* |
| 410 | ** WEBPAGE: howtoclone |
| 411 | ** |
| 412 | ** Provide instructions on how to clone this repository. |
| 413 | */ |
| 414 | void howtoclone_page(void){ |
| 415 | login_check_credentials(); |
| 416 | cgi_check_for_malice(); |
| 417 | style_header("How To Clone This Repository"); |
| 418 | if( !g.perm.Clone ){ |
| 419 | @ <p>You are not authorized to clone this repository. |
| 420 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 421 | @ Maybe you would be able to clone if you |
| 422 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | |
| 424 | @ Contact the site administrator and ask them to give |
| 425 | @ you "Clone" privileges in order to clone. |
| 426 | } |
| 427 | }else{ |
| 428 | const char *zNm = db_get("short-project-name","clone"); |
| 429 | @ <p>Clone this repository by running a command like the following: |
| 430 | @ <blockquote><pre> |
| 431 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 432 | @ </pre></blockquote> |
| 433 | @ <p>Do a web search for "fossil clone" or similar to find additional |
| 434 | @ information about using a cloned Fossil repository. |
| 435 | } |
| 436 | style_finish_page(); |
| 437 | } |
| 438 |
+1
-1
| --- src/comformat.c | ||
| +++ src/comformat.c | ||
| @@ -303,11 +303,11 @@ | ||
| 303 | 303 | case 4: |
| 304 | 304 | *pUtf32 = |
| 305 | 305 | ( (z[0] & 0x0f)<<18 ) | |
| 306 | 306 | ( (z[1] & 0x3f)<<12 ) | |
| 307 | 307 | ( (z[2] & 0x3f)<< 6 ) | |
| 308 | - ( (z[4] & 0x3f)<< 0 ) ; | |
| 308 | + ( (z[3] & 0x3f)<< 0 ) ; | |
| 309 | 309 | break; |
| 310 | 310 | case 3: |
| 311 | 311 | *pUtf32 = |
| 312 | 312 | ( (z[0] & 0x0f)<<12 ) | |
| 313 | 313 | ( (z[1] & 0x3f)<< 6 ) | |
| 314 | 314 |
| --- src/comformat.c | |
| +++ src/comformat.c | |
| @@ -303,11 +303,11 @@ | |
| 303 | case 4: |
| 304 | *pUtf32 = |
| 305 | ( (z[0] & 0x0f)<<18 ) | |
| 306 | ( (z[1] & 0x3f)<<12 ) | |
| 307 | ( (z[2] & 0x3f)<< 6 ) | |
| 308 | ( (z[4] & 0x3f)<< 0 ) ; |
| 309 | break; |
| 310 | case 3: |
| 311 | *pUtf32 = |
| 312 | ( (z[0] & 0x0f)<<12 ) | |
| 313 | ( (z[1] & 0x3f)<< 6 ) | |
| 314 |
| --- src/comformat.c | |
| +++ src/comformat.c | |
| @@ -303,11 +303,11 @@ | |
| 303 | case 4: |
| 304 | *pUtf32 = |
| 305 | ( (z[0] & 0x0f)<<18 ) | |
| 306 | ( (z[1] & 0x3f)<<12 ) | |
| 307 | ( (z[2] & 0x3f)<< 6 ) | |
| 308 | ( (z[3] & 0x3f)<< 0 ) ; |
| 309 | break; |
| 310 | case 3: |
| 311 | *pUtf32 = |
| 312 | ( (z[0] & 0x0f)<<12 ) | |
| 313 | ( (z[1] & 0x3f)<< 6 ) | |
| 314 |
+2
-1
| --- src/content.c | ||
| +++ src/content.c | ||
| @@ -600,10 +600,11 @@ | ||
| 600 | 600 | ); |
| 601 | 601 | db_bind_blob(&s1, ":data", &cmpr); |
| 602 | 602 | db_exec(&s1); |
| 603 | 603 | rid = db_last_insert_rowid(); |
| 604 | 604 | if( !pBlob ){ |
| 605 | + assert(!"cannot happen: pBlob is always non-NULL"); | |
| 605 | 606 | db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); |
| 606 | 607 | } |
| 607 | 608 | } |
| 608 | 609 | if( g.markPrivate || isPrivate ){ |
| 609 | 610 | db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid); |
| @@ -863,11 +864,11 @@ | ||
| 863 | 864 | int srcid = aSrc[i]; |
| 864 | 865 | if( srcid==rid ) continue; |
| 865 | 866 | if( content_is_private(srcid) && !content_is_private(rid) ) continue; |
| 866 | 867 | |
| 867 | 868 | /* Compute all ancestors of srcid and make sure rid is not one of them. |
| 868 | - ** If rid is an ancestor of srcid, then making rid a decendent of srcid | |
| 869 | + ** If rid is an ancestor of srcid, then making rid a descendent of srcid | |
| 869 | 870 | ** would create a delta loop. */ |
| 870 | 871 | s = srcid; |
| 871 | 872 | while( (s = delta_source_rid(s))>0 ){ |
| 872 | 873 | if( s==rid ){ |
| 873 | 874 | content_undelta(srcid); |
| 874 | 875 |
| --- src/content.c | |
| +++ src/content.c | |
| @@ -600,10 +600,11 @@ | |
| 600 | ); |
| 601 | db_bind_blob(&s1, ":data", &cmpr); |
| 602 | db_exec(&s1); |
| 603 | rid = db_last_insert_rowid(); |
| 604 | if( !pBlob ){ |
| 605 | db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); |
| 606 | } |
| 607 | } |
| 608 | if( g.markPrivate || isPrivate ){ |
| 609 | db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid); |
| @@ -863,11 +864,11 @@ | |
| 863 | int srcid = aSrc[i]; |
| 864 | if( srcid==rid ) continue; |
| 865 | if( content_is_private(srcid) && !content_is_private(rid) ) continue; |
| 866 | |
| 867 | /* Compute all ancestors of srcid and make sure rid is not one of them. |
| 868 | ** If rid is an ancestor of srcid, then making rid a decendent of srcid |
| 869 | ** would create a delta loop. */ |
| 870 | s = srcid; |
| 871 | while( (s = delta_source_rid(s))>0 ){ |
| 872 | if( s==rid ){ |
| 873 | content_undelta(srcid); |
| 874 |
| --- src/content.c | |
| +++ src/content.c | |
| @@ -600,10 +600,11 @@ | |
| 600 | ); |
| 601 | db_bind_blob(&s1, ":data", &cmpr); |
| 602 | db_exec(&s1); |
| 603 | rid = db_last_insert_rowid(); |
| 604 | if( !pBlob ){ |
| 605 | assert(!"cannot happen: pBlob is always non-NULL"); |
| 606 | db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); |
| 607 | } |
| 608 | } |
| 609 | if( g.markPrivate || isPrivate ){ |
| 610 | db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid); |
| @@ -863,11 +864,11 @@ | |
| 864 | int srcid = aSrc[i]; |
| 865 | if( srcid==rid ) continue; |
| 866 | if( content_is_private(srcid) && !content_is_private(rid) ) continue; |
| 867 | |
| 868 | /* Compute all ancestors of srcid and make sure rid is not one of them. |
| 869 | ** If rid is an ancestor of srcid, then making rid a descendent of srcid |
| 870 | ** would create a delta loop. */ |
| 871 | s = srcid; |
| 872 | while( (s = delta_source_rid(s))>0 ){ |
| 873 | if( s==rid ){ |
| 874 | content_undelta(srcid); |
| 875 |
+7
-1
| --- src/cookies.c | ||
| +++ src/cookies.c | ||
| @@ -256,11 +256,13 @@ | ||
| 256 | 256 | if( isQP ) continue; |
| 257 | 257 | if( fossil_isupper(zName[0]) ) continue; |
| 258 | 258 | if( bFDSonly && strcmp(zName, "fossil_display_settings")!=0 ) continue; |
| 259 | 259 | zDel = mprintf("del%s",zName); |
| 260 | 260 | if( P(zDel)!=0 ){ |
| 261 | - cgi_set_cookie(zName, "", 0, -1); | |
| 261 | + const char *zPath = fossil_strcmp(ROBOT_COOKIE,zName)==0 | |
| 262 | + ? "/" : 0; | |
| 263 | + cgi_set_cookie(zName, "", zPath, -1); | |
| 262 | 264 | cgi_redirect(g.zPath); |
| 263 | 265 | } |
| 264 | 266 | nCookie++; |
| 265 | 267 | @ <li><p><b>%h(zName)</b>: %h(zValue) |
| 266 | 268 | @ <input type="submit" name="%h(zDel)" value="Delete"> |
| @@ -282,10 +284,14 @@ | ||
| 282 | 284 | && hex_prefix_length(&zName[7])==16 |
| 283 | 285 | && hex_prefix_length(zValue)>24 |
| 284 | 286 | ){ |
| 285 | 287 | @ <p>This appears to be a login cookie for another Fossil repository |
| 286 | 288 | @ in the same website. |
| 289 | + }else | |
| 290 | + if( fossil_strcmp(zName, ROBOT_COOKIE)==0 ){ | |
| 291 | + @ <p>This cookie shows that your web-browser has been tested is | |
| 292 | + @ believed to be operated by a human, not a robot. | |
| 287 | 293 | } |
| 288 | 294 | else { |
| 289 | 295 | @ <p>This cookie was not generated by Fossil. It might be something |
| 290 | 296 | @ from another program on the same website. |
| 291 | 297 | } |
| 292 | 298 |
| --- src/cookies.c | |
| +++ src/cookies.c | |
| @@ -256,11 +256,13 @@ | |
| 256 | if( isQP ) continue; |
| 257 | if( fossil_isupper(zName[0]) ) continue; |
| 258 | if( bFDSonly && strcmp(zName, "fossil_display_settings")!=0 ) continue; |
| 259 | zDel = mprintf("del%s",zName); |
| 260 | if( P(zDel)!=0 ){ |
| 261 | cgi_set_cookie(zName, "", 0, -1); |
| 262 | cgi_redirect(g.zPath); |
| 263 | } |
| 264 | nCookie++; |
| 265 | @ <li><p><b>%h(zName)</b>: %h(zValue) |
| 266 | @ <input type="submit" name="%h(zDel)" value="Delete"> |
| @@ -282,10 +284,14 @@ | |
| 282 | && hex_prefix_length(&zName[7])==16 |
| 283 | && hex_prefix_length(zValue)>24 |
| 284 | ){ |
| 285 | @ <p>This appears to be a login cookie for another Fossil repository |
| 286 | @ in the same website. |
| 287 | } |
| 288 | else { |
| 289 | @ <p>This cookie was not generated by Fossil. It might be something |
| 290 | @ from another program on the same website. |
| 291 | } |
| 292 |
| --- src/cookies.c | |
| +++ src/cookies.c | |
| @@ -256,11 +256,13 @@ | |
| 256 | if( isQP ) continue; |
| 257 | if( fossil_isupper(zName[0]) ) continue; |
| 258 | if( bFDSonly && strcmp(zName, "fossil_display_settings")!=0 ) continue; |
| 259 | zDel = mprintf("del%s",zName); |
| 260 | if( P(zDel)!=0 ){ |
| 261 | const char *zPath = fossil_strcmp(ROBOT_COOKIE,zName)==0 |
| 262 | ? "/" : 0; |
| 263 | cgi_set_cookie(zName, "", zPath, -1); |
| 264 | cgi_redirect(g.zPath); |
| 265 | } |
| 266 | nCookie++; |
| 267 | @ <li><p><b>%h(zName)</b>: %h(zValue) |
| 268 | @ <input type="submit" name="%h(zDel)" value="Delete"> |
| @@ -282,10 +284,14 @@ | |
| 284 | && hex_prefix_length(&zName[7])==16 |
| 285 | && hex_prefix_length(zValue)>24 |
| 286 | ){ |
| 287 | @ <p>This appears to be a login cookie for another Fossil repository |
| 288 | @ in the same website. |
| 289 | }else |
| 290 | if( fossil_strcmp(zName, ROBOT_COOKIE)==0 ){ |
| 291 | @ <p>This cookie shows that your web-browser has been tested is |
| 292 | @ believed to be operated by a human, not a robot. |
| 293 | } |
| 294 | else { |
| 295 | @ <p>This cookie was not generated by Fossil. It might be something |
| 296 | @ from another program on the same website. |
| 297 | } |
| 298 |
+19
-22
| --- src/copybtn.js | ||
| +++ src/copybtn.js | ||
| @@ -1,32 +1,34 @@ | ||
| 1 | 1 | /* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts |
| 2 | 2 | ** thereof) of the target elements to the clipboard. |
| 3 | 3 | ** |
| 4 | -** Newly created buttons are <span> elements with an SVG background icon, | |
| 5 | -** defined by the "copy-button" class in the default CSS style sheet, and are | |
| 6 | -** assigned the element ID "copy-<idTarget>". | |
| 7 | -** | |
| 8 | -** To simplify customization, the only properties modified for HTML-defined | |
| 9 | -** buttons are the "onclick" handler, and the "transition" and "opacity" styles | |
| 10 | -** (used for animation). | |
| 4 | +** Newly created buttons are <button> elements plus a nested <span> element with | |
| 5 | +** an SVG background icon, defined by the "copy-button" class in the default CSS | |
| 6 | +** style sheet, and are assigned the element ID "copy-<idTarget>". | |
| 11 | 7 | ** |
| 12 | 8 | ** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(), |
| 13 | 9 | ** needs to be called to attach the "onclick" handler (done automatically from |
| 14 | -** a handler attached to the "DOMContentLoaded" event). | |
| 10 | +** a handler attached to the "DOMContentLoaded" event). These functions create | |
| 11 | +** the nested <span> element if the <button> element has no child nodes. Using | |
| 12 | +** static HTML for the <span> element ensures the buttons are visible if there | |
| 13 | +** are script errors, which may be useful for Fossil JS hackers (as good parts | |
| 14 | +** of the Fossil web UI come down on JS errors, anyway). | |
| 15 | 15 | ** |
| 16 | 16 | ** The initialization functions do not overwrite the "data-copytarget" and |
| 17 | 17 | ** "data-copylength" attributes with empty or null values for <idTarget> and |
| 18 | 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | 19 | ** previous copy length limit. |
| 20 | 20 | ** |
| 21 | 21 | ** HTML snippet for statically created buttons: |
| 22 | 22 | ** |
| 23 | -** <span class="copy-button" id="copy-<idTarget>" | |
| 24 | -** data-copytarget="<idTarget>" data-copylength="<cchLength>"></span> | |
| 23 | +** <button class="copy-button" id="copy-<idTarget>" | |
| 24 | +** data-copytarget="<idTarget>" data-copylength="<cchLength>"> | |
| 25 | +** <span></span> | |
| 26 | +** </button> | |
| 25 | 27 | */ |
| 26 | 28 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 27 | - var elButton = document.createElement("span"); | |
| 29 | + var elButton = document.createElement("button"); | |
| 28 | 30 | elButton.className = "copy-button"; |
| 29 | 31 | if( bFlipped ) elButton.className += " copy-button-flipped"; |
| 30 | 32 | elButton.id = "copy-" + idTarget; |
| 31 | 33 | initCopyButton(elButton,idTarget,cchLength); |
| 32 | 34 | return elButton; |
| @@ -36,15 +38,18 @@ | ||
| 36 | 38 | var elButton = document.getElementById(idButton); |
| 37 | 39 | if( elButton ) initCopyButton(elButton,idTarget,cchLength); |
| 38 | 40 | return elButton; |
| 39 | 41 | } |
| 40 | 42 | function initCopyButton(elButton,idTarget,cchLength){ |
| 41 | - elButton.style.transition = ""; | |
| 42 | - elButton.style.opacity = 1; | |
| 43 | 43 | if( idTarget ) elButton.setAttribute("data-copytarget",idTarget); |
| 44 | 44 | if( cchLength ) elButton.setAttribute("data-copylength",cchLength); |
| 45 | 45 | elButton.onclick = clickCopyButton; |
| 46 | + /* Make sure the <button> contains a single nested <span>. */ | |
| 47 | + if( elButton.childElementCount!=1 || elButton.firstChild.tagName!="SPAN" ){ | |
| 48 | + while( elButton.firstChild ) elButton.removeChild(elButton.lastChild); | |
| 49 | + elButton.appendChild(document.createElement("span")); | |
| 50 | + } | |
| 46 | 51 | return elButton; |
| 47 | 52 | } |
| 48 | 53 | setTimeout(function(){ |
| 49 | 54 | var elButtons = document.getElementsByClassName("copy-button"); |
| 50 | 55 | for ( var i=0; i<elButtons.length; i++ ){ |
| @@ -53,14 +58,11 @@ | ||
| 53 | 58 | },1); |
| 54 | 59 | /* The onclick handler for the "Copy Button". */ |
| 55 | 60 | function clickCopyButton(e){ |
| 56 | 61 | e.preventDefault(); /* Mandatory for <a> and <button>. */ |
| 57 | 62 | e.stopPropagation(); |
| 58 | - if( this.getAttribute("data-copylocked") ) return; | |
| 59 | - this.setAttribute("data-copylocked","1"); | |
| 60 | - this.style.transition = "opacity 400ms ease-in-out"; | |
| 61 | - this.style.opacity = 0; | |
| 63 | + if( this.disabled ) return; /* This check is probably redundant. */ | |
| 62 | 64 | var idTarget = this.getAttribute("data-copytarget"); |
| 63 | 65 | var elTarget = document.getElementById(idTarget); |
| 64 | 66 | if( elTarget ){ |
| 65 | 67 | var text = elTarget.innerText.replace(/^\s+|\s+$/g,""); |
| 66 | 68 | var cchLength = parseInt(this.getAttribute("data-copylength")); |
| @@ -67,15 +69,10 @@ | ||
| 67 | 69 | if( !isNaN(cchLength) && cchLength>0 ){ |
| 68 | 70 | text = text.slice(0,cchLength); /* Assume single-byte chars. */ |
| 69 | 71 | } |
| 70 | 72 | copyTextToClipboard(text); |
| 71 | 73 | } |
| 72 | - setTimeout(function(){ | |
| 73 | - this.style.transition = ""; | |
| 74 | - this.style.opacity = 1; | |
| 75 | - this.removeAttribute("data-copylocked"); | |
| 76 | - }.bind(this),400); | |
| 77 | 74 | } |
| 78 | 75 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 79 | 76 | function copyTextToClipboard(text){ |
| 80 | 77 | if( window.clipboardData && window.clipboardData.setData ){ |
| 81 | 78 | window.clipboardData.setData("Text",text); |
| 82 | 79 |
| --- src/copybtn.js | |
| +++ src/copybtn.js | |
| @@ -1,32 +1,34 @@ | |
| 1 | /* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts |
| 2 | ** thereof) of the target elements to the clipboard. |
| 3 | ** |
| 4 | ** Newly created buttons are <span> elements with an SVG background icon, |
| 5 | ** defined by the "copy-button" class in the default CSS style sheet, and are |
| 6 | ** assigned the element ID "copy-<idTarget>". |
| 7 | ** |
| 8 | ** To simplify customization, the only properties modified for HTML-defined |
| 9 | ** buttons are the "onclick" handler, and the "transition" and "opacity" styles |
| 10 | ** (used for animation). |
| 11 | ** |
| 12 | ** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(), |
| 13 | ** needs to be called to attach the "onclick" handler (done automatically from |
| 14 | ** a handler attached to the "DOMContentLoaded" event). |
| 15 | ** |
| 16 | ** The initialization functions do not overwrite the "data-copytarget" and |
| 17 | ** "data-copylength" attributes with empty or null values for <idTarget> and |
| 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | ** previous copy length limit. |
| 20 | ** |
| 21 | ** HTML snippet for statically created buttons: |
| 22 | ** |
| 23 | ** <span class="copy-button" id="copy-<idTarget>" |
| 24 | ** data-copytarget="<idTarget>" data-copylength="<cchLength>"></span> |
| 25 | */ |
| 26 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 27 | var elButton = document.createElement("span"); |
| 28 | elButton.className = "copy-button"; |
| 29 | if( bFlipped ) elButton.className += " copy-button-flipped"; |
| 30 | elButton.id = "copy-" + idTarget; |
| 31 | initCopyButton(elButton,idTarget,cchLength); |
| 32 | return elButton; |
| @@ -36,15 +38,18 @@ | |
| 36 | var elButton = document.getElementById(idButton); |
| 37 | if( elButton ) initCopyButton(elButton,idTarget,cchLength); |
| 38 | return elButton; |
| 39 | } |
| 40 | function initCopyButton(elButton,idTarget,cchLength){ |
| 41 | elButton.style.transition = ""; |
| 42 | elButton.style.opacity = 1; |
| 43 | if( idTarget ) elButton.setAttribute("data-copytarget",idTarget); |
| 44 | if( cchLength ) elButton.setAttribute("data-copylength",cchLength); |
| 45 | elButton.onclick = clickCopyButton; |
| 46 | return elButton; |
| 47 | } |
| 48 | setTimeout(function(){ |
| 49 | var elButtons = document.getElementsByClassName("copy-button"); |
| 50 | for ( var i=0; i<elButtons.length; i++ ){ |
| @@ -53,14 +58,11 @@ | |
| 53 | },1); |
| 54 | /* The onclick handler for the "Copy Button". */ |
| 55 | function clickCopyButton(e){ |
| 56 | e.preventDefault(); /* Mandatory for <a> and <button>. */ |
| 57 | e.stopPropagation(); |
| 58 | if( this.getAttribute("data-copylocked") ) return; |
| 59 | this.setAttribute("data-copylocked","1"); |
| 60 | this.style.transition = "opacity 400ms ease-in-out"; |
| 61 | this.style.opacity = 0; |
| 62 | var idTarget = this.getAttribute("data-copytarget"); |
| 63 | var elTarget = document.getElementById(idTarget); |
| 64 | if( elTarget ){ |
| 65 | var text = elTarget.innerText.replace(/^\s+|\s+$/g,""); |
| 66 | var cchLength = parseInt(this.getAttribute("data-copylength")); |
| @@ -67,15 +69,10 @@ | |
| 67 | if( !isNaN(cchLength) && cchLength>0 ){ |
| 68 | text = text.slice(0,cchLength); /* Assume single-byte chars. */ |
| 69 | } |
| 70 | copyTextToClipboard(text); |
| 71 | } |
| 72 | setTimeout(function(){ |
| 73 | this.style.transition = ""; |
| 74 | this.style.opacity = 1; |
| 75 | this.removeAttribute("data-copylocked"); |
| 76 | }.bind(this),400); |
| 77 | } |
| 78 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 79 | function copyTextToClipboard(text){ |
| 80 | if( window.clipboardData && window.clipboardData.setData ){ |
| 81 | window.clipboardData.setData("Text",text); |
| 82 |
| --- src/copybtn.js | |
| +++ src/copybtn.js | |
| @@ -1,32 +1,34 @@ | |
| 1 | /* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts |
| 2 | ** thereof) of the target elements to the clipboard. |
| 3 | ** |
| 4 | ** Newly created buttons are <button> elements plus a nested <span> element with |
| 5 | ** an SVG background icon, defined by the "copy-button" class in the default CSS |
| 6 | ** style sheet, and are assigned the element ID "copy-<idTarget>". |
| 7 | ** |
| 8 | ** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(), |
| 9 | ** needs to be called to attach the "onclick" handler (done automatically from |
| 10 | ** a handler attached to the "DOMContentLoaded" event). These functions create |
| 11 | ** the nested <span> element if the <button> element has no child nodes. Using |
| 12 | ** static HTML for the <span> element ensures the buttons are visible if there |
| 13 | ** are script errors, which may be useful for Fossil JS hackers (as good parts |
| 14 | ** of the Fossil web UI come down on JS errors, anyway). |
| 15 | ** |
| 16 | ** The initialization functions do not overwrite the "data-copytarget" and |
| 17 | ** "data-copylength" attributes with empty or null values for <idTarget> and |
| 18 | ** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the |
| 19 | ** previous copy length limit. |
| 20 | ** |
| 21 | ** HTML snippet for statically created buttons: |
| 22 | ** |
| 23 | ** <button class="copy-button" id="copy-<idTarget>" |
| 24 | ** data-copytarget="<idTarget>" data-copylength="<cchLength>"> |
| 25 | ** <span></span> |
| 26 | ** </button> |
| 27 | */ |
| 28 | function makeCopyButton(idTarget,bFlipped,cchLength){ |
| 29 | var elButton = document.createElement("button"); |
| 30 | elButton.className = "copy-button"; |
| 31 | if( bFlipped ) elButton.className += " copy-button-flipped"; |
| 32 | elButton.id = "copy-" + idTarget; |
| 33 | initCopyButton(elButton,idTarget,cchLength); |
| 34 | return elButton; |
| @@ -36,15 +38,18 @@ | |
| 38 | var elButton = document.getElementById(idButton); |
| 39 | if( elButton ) initCopyButton(elButton,idTarget,cchLength); |
| 40 | return elButton; |
| 41 | } |
| 42 | function initCopyButton(elButton,idTarget,cchLength){ |
| 43 | if( idTarget ) elButton.setAttribute("data-copytarget",idTarget); |
| 44 | if( cchLength ) elButton.setAttribute("data-copylength",cchLength); |
| 45 | elButton.onclick = clickCopyButton; |
| 46 | /* Make sure the <button> contains a single nested <span>. */ |
| 47 | if( elButton.childElementCount!=1 || elButton.firstChild.tagName!="SPAN" ){ |
| 48 | while( elButton.firstChild ) elButton.removeChild(elButton.lastChild); |
| 49 | elButton.appendChild(document.createElement("span")); |
| 50 | } |
| 51 | return elButton; |
| 52 | } |
| 53 | setTimeout(function(){ |
| 54 | var elButtons = document.getElementsByClassName("copy-button"); |
| 55 | for ( var i=0; i<elButtons.length; i++ ){ |
| @@ -53,14 +58,11 @@ | |
| 58 | },1); |
| 59 | /* The onclick handler for the "Copy Button". */ |
| 60 | function clickCopyButton(e){ |
| 61 | e.preventDefault(); /* Mandatory for <a> and <button>. */ |
| 62 | e.stopPropagation(); |
| 63 | if( this.disabled ) return; /* This check is probably redundant. */ |
| 64 | var idTarget = this.getAttribute("data-copytarget"); |
| 65 | var elTarget = document.getElementById(idTarget); |
| 66 | if( elTarget ){ |
| 67 | var text = elTarget.innerText.replace(/^\s+|\s+$/g,""); |
| 68 | var cchLength = parseInt(this.getAttribute("data-copylength")); |
| @@ -67,15 +69,10 @@ | |
| 69 | if( !isNaN(cchLength) && cchLength>0 ){ |
| 70 | text = text.slice(0,cchLength); /* Assume single-byte chars. */ |
| 71 | } |
| 72 | copyTextToClipboard(text); |
| 73 | } |
| 74 | } |
| 75 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 76 | function copyTextToClipboard(text){ |
| 77 | if( window.clipboardData && window.clipboardData.setData ){ |
| 78 | window.clipboardData.setData("Text",text); |
| 79 |
M
src/db.c
+26
-4
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -125,10 +125,20 @@ | ||
| 125 | 125 | #endif /* FOSSIL_ENABLE_JSON */ |
| 126 | 126 | if( g.xferPanic && g.cgiOutput==1 ){ |
| 127 | 127 | cgi_reset_content(); |
| 128 | 128 | @ error Database\serror:\s%F(z) |
| 129 | 129 | cgi_reply(); |
| 130 | + } | |
| 131 | + if( strstr(z,"attempt to write a readonly database") ){ | |
| 132 | + static const char *azDbNames[] = { "repository", "localdb", "configdb" }; | |
| 133 | + int i; | |
| 134 | + for(i=0; i<3; i++){ | |
| 135 | + if( sqlite3_db_readonly(g.db, azDbNames[i])==1 ){ | |
| 136 | + z = mprintf("\"%s\" is readonly.\n%s", | |
| 137 | + sqlite3_db_filename(g.db,azDbNames[i]), z); | |
| 138 | + } | |
| 139 | + } | |
| 130 | 140 | } |
| 131 | 141 | fossil_fatal("Database error: %s", z); |
| 132 | 142 | } |
| 133 | 143 | |
| 134 | 144 | /* |
| @@ -1218,12 +1228,12 @@ | ||
| 1218 | 1228 | } |
| 1219 | 1229 | |
| 1220 | 1230 | /* |
| 1221 | 1231 | ** Execute a query. Return the first column of the first row |
| 1222 | 1232 | ** of the result set as a string. Space to hold the string is |
| 1223 | -** obtained from malloc(). If the result set is empty, return | |
| 1224 | -** zDefault instead. | |
| 1233 | +** obtained from fossil_strdup() and should be freed using fossil_free(). | |
| 1234 | +** If the result set is empty, return a copy of zDefault instead. | |
| 1225 | 1235 | */ |
| 1226 | 1236 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1227 | 1237 | va_list ap; |
| 1228 | 1238 | Stmt s; |
| 1229 | 1239 | char *z; |
| @@ -3589,16 +3599,16 @@ | ||
| 3589 | 3599 | |
| 3590 | 3600 | /* |
| 3591 | 3601 | ** Return true if the string zVal represents "true" (or "false"). |
| 3592 | 3602 | */ |
| 3593 | 3603 | int is_truth(const char *zVal){ |
| 3594 | - static const char *const azOn[] = { "on", "yes", "true", "1" }; | |
| 3604 | + static const char *const azOn[] = { "on", "yes", "true" }; | |
| 3595 | 3605 | int i; |
| 3596 | 3606 | for(i=0; i<count(azOn); i++){ |
| 3597 | 3607 | if( fossil_stricmp(zVal,azOn[i])==0 ) return 1; |
| 3598 | 3608 | } |
| 3599 | - return 0; | |
| 3609 | + return atoi(zVal); | |
| 3600 | 3610 | } |
| 3601 | 3611 | int is_false(const char *zVal){ |
| 3602 | 3612 | static const char *const azOff[] = { "off", "no", "false", "0" }; |
| 3603 | 3613 | int i; |
| 3604 | 3614 | for(i=0; i<count(azOff); i++){ |
| @@ -4250,10 +4260,13 @@ | ||
| 4250 | 4260 | ** them). |
| 4251 | 4261 | ** --verbose If passed a URI then this flag is passed on to the clone |
| 4252 | 4262 | ** operation, otherwise it has no effect |
| 4253 | 4263 | ** --workdir DIR Use DIR as the working directory instead of ".". The DIR |
| 4254 | 4264 | ** directory is created if it does not exist. |
| 4265 | +** --reopen REPOFILE Changes the repository file used by the current checkout | |
| 4266 | +** to REPOFILE. Use this after moving a checkout's | |
| 4267 | +** repository. This may lose stash and bisect history. | |
| 4255 | 4268 | ** |
| 4256 | 4269 | ** See also: [[close]], [[clone]] |
| 4257 | 4270 | */ |
| 4258 | 4271 | void cmd_open(void){ |
| 4259 | 4272 | int emptyFlag; |
| @@ -4264,14 +4277,23 @@ | ||
| 4264 | 4277 | int bForce = 0; /* --force. Open even if non-empty dir */ |
| 4265 | 4278 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 4266 | 4279 | const char *zWorkDir; /* --workdir value */ |
| 4267 | 4280 | const char *zRepo = 0; /* Name of the repository file */ |
| 4268 | 4281 | const char *zRepoDir = 0; /* --repodir value */ |
| 4282 | + const char *zReopen = 0; /* --reopen REPOFILE */ | |
| 4269 | 4283 | char *zPwd; /* Initial working directory */ |
| 4270 | 4284 | int isUri = 0; /* True if REPOSITORY is a URI */ |
| 4271 | 4285 | int nLocal; /* Number of preexisting files in cwd */ |
| 4272 | 4286 | int bVerbose = 0; /* --verbose option for clone */ |
| 4287 | + | |
| 4288 | + zReopen = find_option("reopen",0,1); | |
| 4289 | + if( 0!=zReopen ){ | |
| 4290 | + g.argc = 3; | |
| 4291 | + g.argv[2] = (char*)zReopen; | |
| 4292 | + move_repo_cmd(); | |
| 4293 | + return; | |
| 4294 | + } | |
| 4273 | 4295 | |
| 4274 | 4296 | url_proxy_options(); |
| 4275 | 4297 | emptyFlag = find_option("empty",0,0)!=0; |
| 4276 | 4298 | keepFlag = find_option("keep","k",0)!=0; |
| 4277 | 4299 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 4278 | 4300 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -125,10 +125,20 @@ | |
| 125 | #endif /* FOSSIL_ENABLE_JSON */ |
| 126 | if( g.xferPanic && g.cgiOutput==1 ){ |
| 127 | cgi_reset_content(); |
| 128 | @ error Database\serror:\s%F(z) |
| 129 | cgi_reply(); |
| 130 | } |
| 131 | fossil_fatal("Database error: %s", z); |
| 132 | } |
| 133 | |
| 134 | /* |
| @@ -1218,12 +1228,12 @@ | |
| 1218 | } |
| 1219 | |
| 1220 | /* |
| 1221 | ** Execute a query. Return the first column of the first row |
| 1222 | ** of the result set as a string. Space to hold the string is |
| 1223 | ** obtained from malloc(). If the result set is empty, return |
| 1224 | ** zDefault instead. |
| 1225 | */ |
| 1226 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1227 | va_list ap; |
| 1228 | Stmt s; |
| 1229 | char *z; |
| @@ -3589,16 +3599,16 @@ | |
| 3589 | |
| 3590 | /* |
| 3591 | ** Return true if the string zVal represents "true" (or "false"). |
| 3592 | */ |
| 3593 | int is_truth(const char *zVal){ |
| 3594 | static const char *const azOn[] = { "on", "yes", "true", "1" }; |
| 3595 | int i; |
| 3596 | for(i=0; i<count(azOn); i++){ |
| 3597 | if( fossil_stricmp(zVal,azOn[i])==0 ) return 1; |
| 3598 | } |
| 3599 | return 0; |
| 3600 | } |
| 3601 | int is_false(const char *zVal){ |
| 3602 | static const char *const azOff[] = { "off", "no", "false", "0" }; |
| 3603 | int i; |
| 3604 | for(i=0; i<count(azOff); i++){ |
| @@ -4250,10 +4260,13 @@ | |
| 4250 | ** them). |
| 4251 | ** --verbose If passed a URI then this flag is passed on to the clone |
| 4252 | ** operation, otherwise it has no effect |
| 4253 | ** --workdir DIR Use DIR as the working directory instead of ".". The DIR |
| 4254 | ** directory is created if it does not exist. |
| 4255 | ** |
| 4256 | ** See also: [[close]], [[clone]] |
| 4257 | */ |
| 4258 | void cmd_open(void){ |
| 4259 | int emptyFlag; |
| @@ -4264,14 +4277,23 @@ | |
| 4264 | int bForce = 0; /* --force. Open even if non-empty dir */ |
| 4265 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 4266 | const char *zWorkDir; /* --workdir value */ |
| 4267 | const char *zRepo = 0; /* Name of the repository file */ |
| 4268 | const char *zRepoDir = 0; /* --repodir value */ |
| 4269 | char *zPwd; /* Initial working directory */ |
| 4270 | int isUri = 0; /* True if REPOSITORY is a URI */ |
| 4271 | int nLocal; /* Number of preexisting files in cwd */ |
| 4272 | int bVerbose = 0; /* --verbose option for clone */ |
| 4273 | |
| 4274 | url_proxy_options(); |
| 4275 | emptyFlag = find_option("empty",0,0)!=0; |
| 4276 | keepFlag = find_option("keep","k",0)!=0; |
| 4277 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 4278 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -125,10 +125,20 @@ | |
| 125 | #endif /* FOSSIL_ENABLE_JSON */ |
| 126 | if( g.xferPanic && g.cgiOutput==1 ){ |
| 127 | cgi_reset_content(); |
| 128 | @ error Database\serror:\s%F(z) |
| 129 | cgi_reply(); |
| 130 | } |
| 131 | if( strstr(z,"attempt to write a readonly database") ){ |
| 132 | static const char *azDbNames[] = { "repository", "localdb", "configdb" }; |
| 133 | int i; |
| 134 | for(i=0; i<3; i++){ |
| 135 | if( sqlite3_db_readonly(g.db, azDbNames[i])==1 ){ |
| 136 | z = mprintf("\"%s\" is readonly.\n%s", |
| 137 | sqlite3_db_filename(g.db,azDbNames[i]), z); |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | fossil_fatal("Database error: %s", z); |
| 142 | } |
| 143 | |
| 144 | /* |
| @@ -1218,12 +1228,12 @@ | |
| 1228 | } |
| 1229 | |
| 1230 | /* |
| 1231 | ** Execute a query. Return the first column of the first row |
| 1232 | ** of the result set as a string. Space to hold the string is |
| 1233 | ** obtained from fossil_strdup() and should be freed using fossil_free(). |
| 1234 | ** If the result set is empty, return a copy of zDefault instead. |
| 1235 | */ |
| 1236 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1237 | va_list ap; |
| 1238 | Stmt s; |
| 1239 | char *z; |
| @@ -3589,16 +3599,16 @@ | |
| 3599 | |
| 3600 | /* |
| 3601 | ** Return true if the string zVal represents "true" (or "false"). |
| 3602 | */ |
| 3603 | int is_truth(const char *zVal){ |
| 3604 | static const char *const azOn[] = { "on", "yes", "true" }; |
| 3605 | int i; |
| 3606 | for(i=0; i<count(azOn); i++){ |
| 3607 | if( fossil_stricmp(zVal,azOn[i])==0 ) return 1; |
| 3608 | } |
| 3609 | return atoi(zVal); |
| 3610 | } |
| 3611 | int is_false(const char *zVal){ |
| 3612 | static const char *const azOff[] = { "off", "no", "false", "0" }; |
| 3613 | int i; |
| 3614 | for(i=0; i<count(azOff); i++){ |
| @@ -4250,10 +4260,13 @@ | |
| 4260 | ** them). |
| 4261 | ** --verbose If passed a URI then this flag is passed on to the clone |
| 4262 | ** operation, otherwise it has no effect |
| 4263 | ** --workdir DIR Use DIR as the working directory instead of ".". The DIR |
| 4264 | ** directory is created if it does not exist. |
| 4265 | ** --reopen REPOFILE Changes the repository file used by the current checkout |
| 4266 | ** to REPOFILE. Use this after moving a checkout's |
| 4267 | ** repository. This may lose stash and bisect history. |
| 4268 | ** |
| 4269 | ** See also: [[close]], [[clone]] |
| 4270 | */ |
| 4271 | void cmd_open(void){ |
| 4272 | int emptyFlag; |
| @@ -4264,14 +4277,23 @@ | |
| 4277 | int bForce = 0; /* --force. Open even if non-empty dir */ |
| 4278 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 4279 | const char *zWorkDir; /* --workdir value */ |
| 4280 | const char *zRepo = 0; /* Name of the repository file */ |
| 4281 | const char *zRepoDir = 0; /* --repodir value */ |
| 4282 | const char *zReopen = 0; /* --reopen REPOFILE */ |
| 4283 | char *zPwd; /* Initial working directory */ |
| 4284 | int isUri = 0; /* True if REPOSITORY is a URI */ |
| 4285 | int nLocal; /* Number of preexisting files in cwd */ |
| 4286 | int bVerbose = 0; /* --verbose option for clone */ |
| 4287 | |
| 4288 | zReopen = find_option("reopen",0,1); |
| 4289 | if( 0!=zReopen ){ |
| 4290 | g.argc = 3; |
| 4291 | g.argv[2] = (char*)zReopen; |
| 4292 | move_repo_cmd(); |
| 4293 | return; |
| 4294 | } |
| 4295 | |
| 4296 | url_proxy_options(); |
| 4297 | emptyFlag = find_option("empty",0,0)!=0; |
| 4298 | keepFlag = find_option("keep","k",0)!=0; |
| 4299 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 4300 |
+46
-8
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -1,10 +1,13 @@ | ||
| 1 | 1 | /* This CSS file holds the default implementations for all of fossil's |
| 2 | 2 | CSS classes. When /style.css is requested, the rules in this file |
| 3 | 3 | are emitted first, followed by (1) page-specific CSS (if any) and |
| 4 | 4 | (2) skin-specific CSS. |
| 5 | 5 | */ |
| 6 | +body { | |
| 7 | + z-index: 0 /* Used by robot.c:robot_proofofwork() and href.js */; | |
| 8 | +} | |
| 6 | 9 | div.sidebox { |
| 7 | 10 | float: right; |
| 8 | 11 | background-color: white; |
| 9 | 12 | border-width: medium; |
| 10 | 13 | border-style: double; |
| @@ -54,10 +57,13 @@ | ||
| 54 | 57 | border-width: 0; |
| 55 | 58 | } |
| 56 | 59 | span.timelineLeaf { |
| 57 | 60 | font-weight: bold; |
| 58 | 61 | } |
| 62 | +span.timelineHash { | |
| 63 | + font-weight: bold; | |
| 64 | +} | |
| 59 | 65 | span.timelineHistDsp { |
| 60 | 66 | font-weight: bold; |
| 61 | 67 | } |
| 62 | 68 | td.timelineTime { |
| 63 | 69 | vertical-align: top; |
| @@ -557,10 +563,11 @@ | ||
| 557 | 563 | table.diff { |
| 558 | 564 | width: 100%; |
| 559 | 565 | border-spacing: 0; |
| 560 | 566 | border-radius: 5px; |
| 561 | 567 | border: 1px solid black; |
| 568 | + overflow: hidden; /* Prevent background from overlapping rounded borders. */ | |
| 562 | 569 | font-size: 80%; |
| 563 | 570 | } |
| 564 | 571 | table.diff td.diffln{ |
| 565 | 572 | padding: 0; |
| 566 | 573 | } |
| @@ -748,10 +755,22 @@ | ||
| 748 | 755 | border-bottom: 3px solid gold; |
| 749 | 756 | } |
| 750 | 757 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 751 | 758 | border-left: 1px solid gold; |
| 752 | 759 | } |
| 760 | +body.tkt .tktCommentArea { | |
| 761 | + display: flex; | |
| 762 | + flex-direction: column; | |
| 763 | +} | |
| 764 | +body.tkt .newest-first-controls { | |
| 765 | + display: flex; | |
| 766 | + flex-direction: row; | |
| 767 | + flex-wrap: nowrap; | |
| 768 | +} | |
| 769 | +body.tkt .tktCommentArea.reverse { | |
| 770 | + flex-direction: column-reverse; | |
| 771 | +} | |
| 753 | 772 | body.cpage-ckout .file-change-line, |
| 754 | 773 | body.cpage-info .file-change-line, |
| 755 | 774 | body.cpage-vinfo .file-change-line, |
| 756 | 775 | body.cpage-ci .file-change-line, |
| 757 | 776 | body.cpage-vdiff .file-change-line { |
| @@ -1136,19 +1155,40 @@ | ||
| 1136 | 1155 | white-space: nowrap; |
| 1137 | 1156 | } |
| 1138 | 1157 | label[for] { |
| 1139 | 1158 | cursor: pointer; |
| 1140 | 1159 | } |
| 1141 | -.copy-button { | |
| 1142 | - display: inline-block; | |
| 1160 | +button.copy-button, | |
| 1161 | +button.copy-button:hover, | |
| 1162 | +button.copy-button:focus, | |
| 1163 | +button.copy-button:active { | |
| 1143 | 1164 | width: 14px; |
| 1144 | 1165 | height: 14px; |
| 1145 | 1166 | /*Note: .24em is slightly smaller than the average width of a normal space.*/ |
| 1146 | 1167 | margin: -2px .24em 0 0; |
| 1147 | 1168 | padding: 0; |
| 1148 | 1169 | border: 0; |
| 1170 | + outline: 0; | |
| 1171 | + background: none; | |
| 1172 | + font-size: inherit; /* Required for horizontal spacing. */ | |
| 1149 | 1173 | vertical-align: middle; |
| 1174 | + user-select: none; | |
| 1175 | + cursor: pointer; | |
| 1176 | +} | |
| 1177 | +button.copy-button-flipped, | |
| 1178 | +button.copy-button-flipped:hover, | |
| 1179 | +button.copy-button-flipped:focus, | |
| 1180 | +button.copy-button-flipped:active { | |
| 1181 | + margin: -2px 0 0 .24em; | |
| 1182 | +} | |
| 1183 | +button.copy-button span { | |
| 1184 | + display: block; | |
| 1185 | + width: 100%; | |
| 1186 | + height: 100%; | |
| 1187 | + margin: 0; | |
| 1188 | + padding: 0; | |
| 1189 | + border: 0; | |
| 1150 | 1190 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \ |
| 1151 | 1191 | viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \ |
| 1152 | 1192 | d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \ |
| 1153 | 1193 | d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \ |
| 1154 | 1194 | d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \ |
| @@ -1159,19 +1199,17 @@ | ||
| 1159 | 1199 | d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E"); |
| 1160 | 1200 | background-repeat: no-repeat; |
| 1161 | 1201 | background-position: center; |
| 1162 | 1202 | cursor: pointer; |
| 1163 | 1203 | } |
| 1164 | -.copy-button.disabled { | |
| 1204 | +button.copy-button:enabled:active span { | |
| 1205 | + background-size: 90%; | |
| 1206 | +} | |
| 1207 | +button.copy-button:disabled span { | |
| 1165 | 1208 | filter: grayscale(1); |
| 1166 | 1209 | opacity: 0.4; |
| 1167 | 1210 | } |
| 1168 | -.copy-button-flipped { | |
| 1169 | -/*Note: .16em is suitable for element grouping.*/ | |
| 1170 | - margin-left: .16em; | |
| 1171 | - margin-right: 0; | |
| 1172 | -} | |
| 1173 | 1211 | .nobr { |
| 1174 | 1212 | white-space: nowrap; |
| 1175 | 1213 | } |
| 1176 | 1214 | .accordion { |
| 1177 | 1215 | cursor: pointer; |
| 1178 | 1216 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1,10 +1,13 @@ | |
| 1 | /* This CSS file holds the default implementations for all of fossil's |
| 2 | CSS classes. When /style.css is requested, the rules in this file |
| 3 | are emitted first, followed by (1) page-specific CSS (if any) and |
| 4 | (2) skin-specific CSS. |
| 5 | */ |
| 6 | div.sidebox { |
| 7 | float: right; |
| 8 | background-color: white; |
| 9 | border-width: medium; |
| 10 | border-style: double; |
| @@ -54,10 +57,13 @@ | |
| 54 | border-width: 0; |
| 55 | } |
| 56 | span.timelineLeaf { |
| 57 | font-weight: bold; |
| 58 | } |
| 59 | span.timelineHistDsp { |
| 60 | font-weight: bold; |
| 61 | } |
| 62 | td.timelineTime { |
| 63 | vertical-align: top; |
| @@ -557,10 +563,11 @@ | |
| 557 | table.diff { |
| 558 | width: 100%; |
| 559 | border-spacing: 0; |
| 560 | border-radius: 5px; |
| 561 | border: 1px solid black; |
| 562 | font-size: 80%; |
| 563 | } |
| 564 | table.diff td.diffln{ |
| 565 | padding: 0; |
| 566 | } |
| @@ -748,10 +755,22 @@ | |
| 748 | border-bottom: 3px solid gold; |
| 749 | } |
| 750 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 751 | border-left: 1px solid gold; |
| 752 | } |
| 753 | body.cpage-ckout .file-change-line, |
| 754 | body.cpage-info .file-change-line, |
| 755 | body.cpage-vinfo .file-change-line, |
| 756 | body.cpage-ci .file-change-line, |
| 757 | body.cpage-vdiff .file-change-line { |
| @@ -1136,19 +1155,40 @@ | |
| 1136 | white-space: nowrap; |
| 1137 | } |
| 1138 | label[for] { |
| 1139 | cursor: pointer; |
| 1140 | } |
| 1141 | .copy-button { |
| 1142 | display: inline-block; |
| 1143 | width: 14px; |
| 1144 | height: 14px; |
| 1145 | /*Note: .24em is slightly smaller than the average width of a normal space.*/ |
| 1146 | margin: -2px .24em 0 0; |
| 1147 | padding: 0; |
| 1148 | border: 0; |
| 1149 | vertical-align: middle; |
| 1150 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \ |
| 1151 | viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \ |
| 1152 | d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \ |
| 1153 | d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \ |
| 1154 | d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \ |
| @@ -1159,19 +1199,17 @@ | |
| 1159 | d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E"); |
| 1160 | background-repeat: no-repeat; |
| 1161 | background-position: center; |
| 1162 | cursor: pointer; |
| 1163 | } |
| 1164 | .copy-button.disabled { |
| 1165 | filter: grayscale(1); |
| 1166 | opacity: 0.4; |
| 1167 | } |
| 1168 | .copy-button-flipped { |
| 1169 | /*Note: .16em is suitable for element grouping.*/ |
| 1170 | margin-left: .16em; |
| 1171 | margin-right: 0; |
| 1172 | } |
| 1173 | .nobr { |
| 1174 | white-space: nowrap; |
| 1175 | } |
| 1176 | .accordion { |
| 1177 | cursor: pointer; |
| 1178 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -1,10 +1,13 @@ | |
| 1 | /* This CSS file holds the default implementations for all of fossil's |
| 2 | CSS classes. When /style.css is requested, the rules in this file |
| 3 | are emitted first, followed by (1) page-specific CSS (if any) and |
| 4 | (2) skin-specific CSS. |
| 5 | */ |
| 6 | body { |
| 7 | z-index: 0 /* Used by robot.c:robot_proofofwork() and href.js */; |
| 8 | } |
| 9 | div.sidebox { |
| 10 | float: right; |
| 11 | background-color: white; |
| 12 | border-width: medium; |
| 13 | border-style: double; |
| @@ -54,10 +57,13 @@ | |
| 57 | border-width: 0; |
| 58 | } |
| 59 | span.timelineLeaf { |
| 60 | font-weight: bold; |
| 61 | } |
| 62 | span.timelineHash { |
| 63 | font-weight: bold; |
| 64 | } |
| 65 | span.timelineHistDsp { |
| 66 | font-weight: bold; |
| 67 | } |
| 68 | td.timelineTime { |
| 69 | vertical-align: top; |
| @@ -557,10 +563,11 @@ | |
| 563 | table.diff { |
| 564 | width: 100%; |
| 565 | border-spacing: 0; |
| 566 | border-radius: 5px; |
| 567 | border: 1px solid black; |
| 568 | overflow: hidden; /* Prevent background from overlapping rounded borders. */ |
| 569 | font-size: 80%; |
| 570 | } |
| 571 | table.diff td.diffln{ |
| 572 | padding: 0; |
| 573 | } |
| @@ -748,10 +755,22 @@ | |
| 755 | border-bottom: 3px solid gold; |
| 756 | } |
| 757 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 758 | border-left: 1px solid gold; |
| 759 | } |
| 760 | body.tkt .tktCommentArea { |
| 761 | display: flex; |
| 762 | flex-direction: column; |
| 763 | } |
| 764 | body.tkt .newest-first-controls { |
| 765 | display: flex; |
| 766 | flex-direction: row; |
| 767 | flex-wrap: nowrap; |
| 768 | } |
| 769 | body.tkt .tktCommentArea.reverse { |
| 770 | flex-direction: column-reverse; |
| 771 | } |
| 772 | body.cpage-ckout .file-change-line, |
| 773 | body.cpage-info .file-change-line, |
| 774 | body.cpage-vinfo .file-change-line, |
| 775 | body.cpage-ci .file-change-line, |
| 776 | body.cpage-vdiff .file-change-line { |
| @@ -1136,19 +1155,40 @@ | |
| 1155 | white-space: nowrap; |
| 1156 | } |
| 1157 | label[for] { |
| 1158 | cursor: pointer; |
| 1159 | } |
| 1160 | button.copy-button, |
| 1161 | button.copy-button:hover, |
| 1162 | button.copy-button:focus, |
| 1163 | button.copy-button:active { |
| 1164 | width: 14px; |
| 1165 | height: 14px; |
| 1166 | /*Note: .24em is slightly smaller than the average width of a normal space.*/ |
| 1167 | margin: -2px .24em 0 0; |
| 1168 | padding: 0; |
| 1169 | border: 0; |
| 1170 | outline: 0; |
| 1171 | background: none; |
| 1172 | font-size: inherit; /* Required for horizontal spacing. */ |
| 1173 | vertical-align: middle; |
| 1174 | user-select: none; |
| 1175 | cursor: pointer; |
| 1176 | } |
| 1177 | button.copy-button-flipped, |
| 1178 | button.copy-button-flipped:hover, |
| 1179 | button.copy-button-flipped:focus, |
| 1180 | button.copy-button-flipped:active { |
| 1181 | margin: -2px 0 0 .24em; |
| 1182 | } |
| 1183 | button.copy-button span { |
| 1184 | display: block; |
| 1185 | width: 100%; |
| 1186 | height: 100%; |
| 1187 | margin: 0; |
| 1188 | padding: 0; |
| 1189 | border: 0; |
| 1190 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \ |
| 1191 | viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \ |
| 1192 | d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \ |
| 1193 | d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \ |
| 1194 | d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \ |
| @@ -1159,19 +1199,17 @@ | |
| 1199 | d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E"); |
| 1200 | background-repeat: no-repeat; |
| 1201 | background-position: center; |
| 1202 | cursor: pointer; |
| 1203 | } |
| 1204 | button.copy-button:enabled:active span { |
| 1205 | background-size: 90%; |
| 1206 | } |
| 1207 | button.copy-button:disabled span { |
| 1208 | filter: grayscale(1); |
| 1209 | opacity: 0.4; |
| 1210 | } |
| 1211 | .nobr { |
| 1212 | white-space: nowrap; |
| 1213 | } |
| 1214 | .accordion { |
| 1215 | cursor: pointer; |
| 1216 |
+18
-3
| --- src/deltafunc.c | ||
| +++ src/deltafunc.c | ||
| @@ -251,10 +251,11 @@ | ||
| 251 | 251 | if( rc==SQLITE_OK ){ |
| 252 | 252 | pNew = sqlite3_malloc64( sizeof(*pNew) ); |
| 253 | 253 | *ppVtab = (sqlite3_vtab*)pNew; |
| 254 | 254 | if( pNew==0 ) return SQLITE_NOMEM; |
| 255 | 255 | memset(pNew, 0, sizeof(*pNew)); |
| 256 | + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); | |
| 256 | 257 | } |
| 257 | 258 | return rc; |
| 258 | 259 | } |
| 259 | 260 | |
| 260 | 261 | /* |
| @@ -296,15 +297,25 @@ | ||
| 296 | 297 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 297 | 298 | const char *z; |
| 298 | 299 | int i = 0; |
| 299 | 300 | |
| 300 | 301 | pCur->iCursor = pCur->iNext; |
| 302 | + if( pCur->iCursor >= pCur->nDelta ){ | |
| 303 | + pCur->eOp = DELTAPARSE_OP_ERROR; | |
| 304 | + pCur->iNext = pCur->nDelta; | |
| 305 | + return SQLITE_OK; | |
| 306 | + } | |
| 301 | 307 | z = pCur->aDelta + pCur->iCursor; |
| 302 | 308 | pCur->a1 = deltaGetInt(&z, &i); |
| 303 | 309 | switch( z[0] ){ |
| 304 | 310 | case '@': { |
| 305 | 311 | z++; |
| 312 | + if( pCur->iNext>=pCur->nDelta ){ | |
| 313 | + pCur->eOp = DELTAPARSE_OP_ERROR; | |
| 314 | + pCur->iNext = pCur->nDelta; | |
| 315 | + break; | |
| 316 | + } | |
| 306 | 317 | pCur->a2 = deltaGetInt(&z, &i); |
| 307 | 318 | pCur->eOp = DELTAPARSE_OP_COPY; |
| 308 | 319 | pCur->iNext = (int)(&z[1] - pCur->aDelta); |
| 309 | 320 | break; |
| 310 | 321 | } |
| @@ -354,12 +365,16 @@ | ||
| 354 | 365 | } |
| 355 | 366 | case DELTAPARSEVTAB_A2: { |
| 356 | 367 | if( pCur->eOp==DELTAPARSE_OP_COPY ){ |
| 357 | 368 | sqlite3_result_int(ctx, pCur->a2); |
| 358 | 369 | }else if( pCur->eOp==DELTAPARSE_OP_INSERT ){ |
| 359 | - sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1, | |
| 360 | - SQLITE_TRANSIENT); | |
| 370 | + if( pCur->a2 + pCur->a1 > pCur->nDelta ){ | |
| 371 | + sqlite3_result_zeroblob(ctx, pCur->a1); | |
| 372 | + }else{ | |
| 373 | + sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1, | |
| 374 | + SQLITE_TRANSIENT); | |
| 375 | + } | |
| 361 | 376 | } |
| 362 | 377 | break; |
| 363 | 378 | } |
| 364 | 379 | case DELTAPARSEVTAB_DELTA: { |
| 365 | 380 | sqlite3_result_blob(ctx, pCur->aDelta, pCur->nDelta, SQLITE_TRANSIENT); |
| @@ -383,11 +398,11 @@ | ||
| 383 | 398 | ** Return TRUE if the cursor has been moved off of the last |
| 384 | 399 | ** row of output. |
| 385 | 400 | */ |
| 386 | 401 | static int deltaparsevtabEof(sqlite3_vtab_cursor *cur){ |
| 387 | 402 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 388 | - return pCur->eOp==DELTAPARSE_OP_EOF; | |
| 403 | + return pCur->eOp==DELTAPARSE_OP_EOF || pCur->iCursor>=pCur->nDelta; | |
| 389 | 404 | } |
| 390 | 405 | |
| 391 | 406 | /* |
| 392 | 407 | ** This method is called to "rewind" the deltaparsevtab_cursor object back |
| 393 | 408 | ** to the first row of output. This method is always called at least |
| 394 | 409 |
| --- src/deltafunc.c | |
| +++ src/deltafunc.c | |
| @@ -251,10 +251,11 @@ | |
| 251 | if( rc==SQLITE_OK ){ |
| 252 | pNew = sqlite3_malloc64( sizeof(*pNew) ); |
| 253 | *ppVtab = (sqlite3_vtab*)pNew; |
| 254 | if( pNew==0 ) return SQLITE_NOMEM; |
| 255 | memset(pNew, 0, sizeof(*pNew)); |
| 256 | } |
| 257 | return rc; |
| 258 | } |
| 259 | |
| 260 | /* |
| @@ -296,15 +297,25 @@ | |
| 296 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 297 | const char *z; |
| 298 | int i = 0; |
| 299 | |
| 300 | pCur->iCursor = pCur->iNext; |
| 301 | z = pCur->aDelta + pCur->iCursor; |
| 302 | pCur->a1 = deltaGetInt(&z, &i); |
| 303 | switch( z[0] ){ |
| 304 | case '@': { |
| 305 | z++; |
| 306 | pCur->a2 = deltaGetInt(&z, &i); |
| 307 | pCur->eOp = DELTAPARSE_OP_COPY; |
| 308 | pCur->iNext = (int)(&z[1] - pCur->aDelta); |
| 309 | break; |
| 310 | } |
| @@ -354,12 +365,16 @@ | |
| 354 | } |
| 355 | case DELTAPARSEVTAB_A2: { |
| 356 | if( pCur->eOp==DELTAPARSE_OP_COPY ){ |
| 357 | sqlite3_result_int(ctx, pCur->a2); |
| 358 | }else if( pCur->eOp==DELTAPARSE_OP_INSERT ){ |
| 359 | sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1, |
| 360 | SQLITE_TRANSIENT); |
| 361 | } |
| 362 | break; |
| 363 | } |
| 364 | case DELTAPARSEVTAB_DELTA: { |
| 365 | sqlite3_result_blob(ctx, pCur->aDelta, pCur->nDelta, SQLITE_TRANSIENT); |
| @@ -383,11 +398,11 @@ | |
| 383 | ** Return TRUE if the cursor has been moved off of the last |
| 384 | ** row of output. |
| 385 | */ |
| 386 | static int deltaparsevtabEof(sqlite3_vtab_cursor *cur){ |
| 387 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 388 | return pCur->eOp==DELTAPARSE_OP_EOF; |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | ** This method is called to "rewind" the deltaparsevtab_cursor object back |
| 393 | ** to the first row of output. This method is always called at least |
| 394 |
| --- src/deltafunc.c | |
| +++ src/deltafunc.c | |
| @@ -251,10 +251,11 @@ | |
| 251 | if( rc==SQLITE_OK ){ |
| 252 | pNew = sqlite3_malloc64( sizeof(*pNew) ); |
| 253 | *ppVtab = (sqlite3_vtab*)pNew; |
| 254 | if( pNew==0 ) return SQLITE_NOMEM; |
| 255 | memset(pNew, 0, sizeof(*pNew)); |
| 256 | sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
| 257 | } |
| 258 | return rc; |
| 259 | } |
| 260 | |
| 261 | /* |
| @@ -296,15 +297,25 @@ | |
| 297 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 298 | const char *z; |
| 299 | int i = 0; |
| 300 | |
| 301 | pCur->iCursor = pCur->iNext; |
| 302 | if( pCur->iCursor >= pCur->nDelta ){ |
| 303 | pCur->eOp = DELTAPARSE_OP_ERROR; |
| 304 | pCur->iNext = pCur->nDelta; |
| 305 | return SQLITE_OK; |
| 306 | } |
| 307 | z = pCur->aDelta + pCur->iCursor; |
| 308 | pCur->a1 = deltaGetInt(&z, &i); |
| 309 | switch( z[0] ){ |
| 310 | case '@': { |
| 311 | z++; |
| 312 | if( pCur->iNext>=pCur->nDelta ){ |
| 313 | pCur->eOp = DELTAPARSE_OP_ERROR; |
| 314 | pCur->iNext = pCur->nDelta; |
| 315 | break; |
| 316 | } |
| 317 | pCur->a2 = deltaGetInt(&z, &i); |
| 318 | pCur->eOp = DELTAPARSE_OP_COPY; |
| 319 | pCur->iNext = (int)(&z[1] - pCur->aDelta); |
| 320 | break; |
| 321 | } |
| @@ -354,12 +365,16 @@ | |
| 365 | } |
| 366 | case DELTAPARSEVTAB_A2: { |
| 367 | if( pCur->eOp==DELTAPARSE_OP_COPY ){ |
| 368 | sqlite3_result_int(ctx, pCur->a2); |
| 369 | }else if( pCur->eOp==DELTAPARSE_OP_INSERT ){ |
| 370 | if( pCur->a2 + pCur->a1 > pCur->nDelta ){ |
| 371 | sqlite3_result_zeroblob(ctx, pCur->a1); |
| 372 | }else{ |
| 373 | sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1, |
| 374 | SQLITE_TRANSIENT); |
| 375 | } |
| 376 | } |
| 377 | break; |
| 378 | } |
| 379 | case DELTAPARSEVTAB_DELTA: { |
| 380 | sqlite3_result_blob(ctx, pCur->aDelta, pCur->nDelta, SQLITE_TRANSIENT); |
| @@ -383,11 +398,11 @@ | |
| 398 | ** Return TRUE if the cursor has been moved off of the last |
| 399 | ** row of output. |
| 400 | */ |
| 401 | static int deltaparsevtabEof(sqlite3_vtab_cursor *cur){ |
| 402 | deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur; |
| 403 | return pCur->eOp==DELTAPARSE_OP_EOF || pCur->iCursor>=pCur->nDelta; |
| 404 | } |
| 405 | |
| 406 | /* |
| 407 | ** This method is called to "rewind" the deltaparsevtab_cursor object back |
| 408 | ** to the first row of output. This method is always called at least |
| 409 |
+19
-13
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -3085,10 +3085,11 @@ | ||
| 3085 | 3085 | Blob *pOut, /* Write diff here if not NULL */ |
| 3086 | 3086 | DiffConfig *pCfg /* Configuration options */ |
| 3087 | 3087 | ){ |
| 3088 | 3088 | int ignoreWs; /* Ignore whitespace */ |
| 3089 | 3089 | DContext c; |
| 3090 | + int nDel = 0, nIns = 0; | |
| 3090 | 3091 | |
| 3091 | 3092 | if( pCfg->diffFlags & DIFF_INVERT ){ |
| 3092 | 3093 | Blob *pTemp = pA_Blob; |
| 3093 | 3094 | pA_Blob = pB_Blob; |
| 3094 | 3095 | pB_Blob = pTemp; |
| @@ -3163,22 +3164,27 @@ | ||
| 3163 | 3164 | c.aEdit[i+1] = sum; |
| 3164 | 3165 | for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n; |
| 3165 | 3166 | c.aEdit[i+2] = sum; |
| 3166 | 3167 | } |
| 3167 | 3168 | } |
| 3169 | + | |
| 3170 | + if( pCfg->diffFlags & DIFF_NUMSTAT ){ | |
| 3171 | + int i; | |
| 3172 | + for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ | |
| 3173 | + nDel += c.aEdit[i+1]; | |
| 3174 | + nIns += c.aEdit[i+2]; | |
| 3175 | + } | |
| 3176 | + g.diffCnt[1] += nIns; | |
| 3177 | + g.diffCnt[2] += nDel; | |
| 3178 | + if( nIns+nDel ){ | |
| 3179 | + g.diffCnt[0]++; | |
| 3180 | + } | |
| 3181 | + } | |
| 3168 | 3182 | |
| 3169 | 3183 | if( pOut ){ |
| 3170 | - if( pCfg->diffFlags & DIFF_NUMSTAT ){ | |
| 3171 | - int nDel = 0, nIns = 0, i; | |
| 3172 | - for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ | |
| 3173 | - nDel += c.aEdit[i+1]; | |
| 3174 | - nIns += c.aEdit[i+2]; | |
| 3175 | - } | |
| 3176 | - g.diffCnt[1] += nIns; | |
| 3177 | - g.diffCnt[2] += nDel; | |
| 3184 | + if( pCfg->diffFlags & DIFF_NUMSTAT && !(pCfg->diffFlags & DIFF_HTML)){ | |
| 3178 | 3185 | if( nIns+nDel ){ |
| 3179 | - g.diffCnt[0]++; | |
| 3180 | 3186 | if( !(pCfg->diffFlags & DIFF_BRIEF) ){ |
| 3181 | 3187 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3182 | 3188 | } |
| 3183 | 3189 | } |
| 3184 | 3190 | }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){ |
| @@ -3380,11 +3386,11 @@ | ||
| 3380 | 3386 | find_option("i",0,0); |
| 3381 | 3387 | find_option("v",0,0); |
| 3382 | 3388 | diff_options(&DCfg, 0, 0); |
| 3383 | 3389 | zRe = find_option("regexp","e",1); |
| 3384 | 3390 | if( zRe ){ |
| 3385 | - const char *zErr = re_compile(&DCfg.pRe, zRe, 0); | |
| 3391 | + const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0); | |
| 3386 | 3392 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3387 | 3393 | } |
| 3388 | 3394 | verify_all_options(); |
| 3389 | 3395 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 3390 | 3396 | blob_zero(&out); |
| @@ -3423,11 +3429,11 @@ | ||
| 3423 | 3429 | find_option("i",0,0); |
| 3424 | 3430 | find_option("v",0,0); |
| 3425 | 3431 | diff_options(&DCfg, 0, 0); |
| 3426 | 3432 | zRe = find_option("regexp","e",1); |
| 3427 | 3433 | if( zRe ){ |
| 3428 | - const char *zErr = re_compile(&DCfg.pRe, zRe, 0); | |
| 3434 | + const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0); | |
| 3429 | 3435 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3430 | 3436 | } |
| 3431 | 3437 | db_find_and_open_repository(0, 0); |
| 3432 | 3438 | verify_all_options(); |
| 3433 | 3439 | if( g.argc!=4 ) usage("HASH1 HASH2"); |
| @@ -3781,12 +3787,12 @@ | ||
| 3781 | 3787 | unsigned clr1, clr2, clr; |
| 3782 | 3788 | int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */ |
| 3783 | 3789 | |
| 3784 | 3790 | /* Gather query parameters */ |
| 3785 | 3791 | login_check_credentials(); |
| 3786 | - if( !g.perm.Read || g.zLogin==0 ){ login_needed(g.anon.Read); return; } | |
| 3787 | - if( exclude_spiders(0) ) return; | |
| 3792 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 3793 | + if( robot_restrict("annotate") ) return; | |
| 3788 | 3794 | fossil_nice_default(); |
| 3789 | 3795 | zFilename = P("filename"); |
| 3790 | 3796 | zRevision = PD("checkin",0); |
| 3791 | 3797 | zOrigin = P("origin"); |
| 3792 | 3798 | zLimit = P("limit"); |
| 3793 | 3799 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -3085,10 +3085,11 @@ | |
| 3085 | Blob *pOut, /* Write diff here if not NULL */ |
| 3086 | DiffConfig *pCfg /* Configuration options */ |
| 3087 | ){ |
| 3088 | int ignoreWs; /* Ignore whitespace */ |
| 3089 | DContext c; |
| 3090 | |
| 3091 | if( pCfg->diffFlags & DIFF_INVERT ){ |
| 3092 | Blob *pTemp = pA_Blob; |
| 3093 | pA_Blob = pB_Blob; |
| 3094 | pB_Blob = pTemp; |
| @@ -3163,22 +3164,27 @@ | |
| 3163 | c.aEdit[i+1] = sum; |
| 3164 | for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n; |
| 3165 | c.aEdit[i+2] = sum; |
| 3166 | } |
| 3167 | } |
| 3168 | |
| 3169 | if( pOut ){ |
| 3170 | if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
| 3171 | int nDel = 0, nIns = 0, i; |
| 3172 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| 3173 | nDel += c.aEdit[i+1]; |
| 3174 | nIns += c.aEdit[i+2]; |
| 3175 | } |
| 3176 | g.diffCnt[1] += nIns; |
| 3177 | g.diffCnt[2] += nDel; |
| 3178 | if( nIns+nDel ){ |
| 3179 | g.diffCnt[0]++; |
| 3180 | if( !(pCfg->diffFlags & DIFF_BRIEF) ){ |
| 3181 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3182 | } |
| 3183 | } |
| 3184 | }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){ |
| @@ -3380,11 +3386,11 @@ | |
| 3380 | find_option("i",0,0); |
| 3381 | find_option("v",0,0); |
| 3382 | diff_options(&DCfg, 0, 0); |
| 3383 | zRe = find_option("regexp","e",1); |
| 3384 | if( zRe ){ |
| 3385 | const char *zErr = re_compile(&DCfg.pRe, zRe, 0); |
| 3386 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3387 | } |
| 3388 | verify_all_options(); |
| 3389 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 3390 | blob_zero(&out); |
| @@ -3423,11 +3429,11 @@ | |
| 3423 | find_option("i",0,0); |
| 3424 | find_option("v",0,0); |
| 3425 | diff_options(&DCfg, 0, 0); |
| 3426 | zRe = find_option("regexp","e",1); |
| 3427 | if( zRe ){ |
| 3428 | const char *zErr = re_compile(&DCfg.pRe, zRe, 0); |
| 3429 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3430 | } |
| 3431 | db_find_and_open_repository(0, 0); |
| 3432 | verify_all_options(); |
| 3433 | if( g.argc!=4 ) usage("HASH1 HASH2"); |
| @@ -3781,12 +3787,12 @@ | |
| 3781 | unsigned clr1, clr2, clr; |
| 3782 | int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */ |
| 3783 | |
| 3784 | /* Gather query parameters */ |
| 3785 | login_check_credentials(); |
| 3786 | if( !g.perm.Read || g.zLogin==0 ){ login_needed(g.anon.Read); return; } |
| 3787 | if( exclude_spiders(0) ) return; |
| 3788 | fossil_nice_default(); |
| 3789 | zFilename = P("filename"); |
| 3790 | zRevision = PD("checkin",0); |
| 3791 | zOrigin = P("origin"); |
| 3792 | zLimit = P("limit"); |
| 3793 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -3085,10 +3085,11 @@ | |
| 3085 | Blob *pOut, /* Write diff here if not NULL */ |
| 3086 | DiffConfig *pCfg /* Configuration options */ |
| 3087 | ){ |
| 3088 | int ignoreWs; /* Ignore whitespace */ |
| 3089 | DContext c; |
| 3090 | int nDel = 0, nIns = 0; |
| 3091 | |
| 3092 | if( pCfg->diffFlags & DIFF_INVERT ){ |
| 3093 | Blob *pTemp = pA_Blob; |
| 3094 | pA_Blob = pB_Blob; |
| 3095 | pB_Blob = pTemp; |
| @@ -3163,22 +3164,27 @@ | |
| 3164 | c.aEdit[i+1] = sum; |
| 3165 | for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n; |
| 3166 | c.aEdit[i+2] = sum; |
| 3167 | } |
| 3168 | } |
| 3169 | |
| 3170 | if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
| 3171 | int i; |
| 3172 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| 3173 | nDel += c.aEdit[i+1]; |
| 3174 | nIns += c.aEdit[i+2]; |
| 3175 | } |
| 3176 | g.diffCnt[1] += nIns; |
| 3177 | g.diffCnt[2] += nDel; |
| 3178 | if( nIns+nDel ){ |
| 3179 | g.diffCnt[0]++; |
| 3180 | } |
| 3181 | } |
| 3182 | |
| 3183 | if( pOut ){ |
| 3184 | if( pCfg->diffFlags & DIFF_NUMSTAT && !(pCfg->diffFlags & DIFF_HTML)){ |
| 3185 | if( nIns+nDel ){ |
| 3186 | if( !(pCfg->diffFlags & DIFF_BRIEF) ){ |
| 3187 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3188 | } |
| 3189 | } |
| 3190 | }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){ |
| @@ -3380,11 +3386,11 @@ | |
| 3386 | find_option("i",0,0); |
| 3387 | find_option("v",0,0); |
| 3388 | diff_options(&DCfg, 0, 0); |
| 3389 | zRe = find_option("regexp","e",1); |
| 3390 | if( zRe ){ |
| 3391 | const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0); |
| 3392 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3393 | } |
| 3394 | verify_all_options(); |
| 3395 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 3396 | blob_zero(&out); |
| @@ -3423,11 +3429,11 @@ | |
| 3429 | find_option("i",0,0); |
| 3430 | find_option("v",0,0); |
| 3431 | diff_options(&DCfg, 0, 0); |
| 3432 | zRe = find_option("regexp","e",1); |
| 3433 | if( zRe ){ |
| 3434 | const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0); |
| 3435 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 3436 | } |
| 3437 | db_find_and_open_repository(0, 0); |
| 3438 | verify_all_options(); |
| 3439 | if( g.argc!=4 ) usage("HASH1 HASH2"); |
| @@ -3781,12 +3787,12 @@ | |
| 3787 | unsigned clr1, clr2, clr; |
| 3788 | int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */ |
| 3789 | |
| 3790 | /* Gather query parameters */ |
| 3791 | login_check_credentials(); |
| 3792 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 3793 | if( robot_restrict("annotate") ) return; |
| 3794 | fossil_nice_default(); |
| 3795 | zFilename = P("filename"); |
| 3796 | zRevision = PD("checkin",0); |
| 3797 | zOrigin = P("origin"); |
| 3798 | zLimit = P("limit"); |
| 3799 |
+1
-1
| --- src/diff.tcl | ||
| +++ src/diff.tcl | ||
| @@ -281,12 +281,12 @@ | ||
| 281 | 281 | if {$type ne "txt"} { |
| 282 | 282 | $c config -width $widths($type) |
| 283 | 283 | } |
| 284 | 284 | $c config -state disabled |
| 285 | 285 | } |
| 286 | + .wfiles.lb config -height $nDiffs | |
| 286 | 287 | if {$nDiffs <= [.wfiles.lb cget -height]} { |
| 287 | - .wfiles.lb config -height $nDiffs | |
| 288 | 288 | grid remove .wfiles.sb |
| 289 | 289 | } |
| 290 | 290 | |
| 291 | 291 | return $nDiffs |
| 292 | 292 | } |
| 293 | 293 |
| --- src/diff.tcl | |
| +++ src/diff.tcl | |
| @@ -281,12 +281,12 @@ | |
| 281 | if {$type ne "txt"} { |
| 282 | $c config -width $widths($type) |
| 283 | } |
| 284 | $c config -state disabled |
| 285 | } |
| 286 | if {$nDiffs <= [.wfiles.lb cget -height]} { |
| 287 | .wfiles.lb config -height $nDiffs |
| 288 | grid remove .wfiles.sb |
| 289 | } |
| 290 | |
| 291 | return $nDiffs |
| 292 | } |
| 293 |
| --- src/diff.tcl | |
| +++ src/diff.tcl | |
| @@ -281,12 +281,12 @@ | |
| 281 | if {$type ne "txt"} { |
| 282 | $c config -width $widths($type) |
| 283 | } |
| 284 | $c config -state disabled |
| 285 | } |
| 286 | .wfiles.lb config -height $nDiffs |
| 287 | if {$nDiffs <= [.wfiles.lb cget -height]} { |
| 288 | grid remove .wfiles.sb |
| 289 | } |
| 290 | |
| 291 | return $nDiffs |
| 292 | } |
| 293 |
+1
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -1522,10 +1522,11 @@ | ||
| 1522 | 1522 | DiffConfig DCfg; |
| 1523 | 1523 | cgi_check_for_malice(); |
| 1524 | 1524 | login_check_credentials(); |
| 1525 | 1525 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1526 | 1526 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 1527 | + if( robot_restrict("diff") ) return; | |
| 1527 | 1528 | |
| 1528 | 1529 | fossil_nice_default(); |
| 1529 | 1530 | cgi_set_content_type("text/plain"); |
| 1530 | 1531 | diff_config_init(&DCfg, DIFF_VERBOSE); |
| 1531 | 1532 | diff_two_versions(zFrom, zTo, &DCfg, 0); |
| 1532 | 1533 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -1522,10 +1522,11 @@ | |
| 1522 | DiffConfig DCfg; |
| 1523 | cgi_check_for_malice(); |
| 1524 | login_check_credentials(); |
| 1525 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1526 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 1527 | |
| 1528 | fossil_nice_default(); |
| 1529 | cgi_set_content_type("text/plain"); |
| 1530 | diff_config_init(&DCfg, DIFF_VERBOSE); |
| 1531 | diff_two_versions(zFrom, zTo, &DCfg, 0); |
| 1532 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -1522,10 +1522,11 @@ | |
| 1522 | DiffConfig DCfg; |
| 1523 | cgi_check_for_malice(); |
| 1524 | login_check_credentials(); |
| 1525 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1526 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 1527 | if( robot_restrict("diff") ) return; |
| 1528 | |
| 1529 | fossil_nice_default(); |
| 1530 | cgi_set_content_type("text/plain"); |
| 1531 | diff_config_init(&DCfg, DIFF_VERBOSE); |
| 1532 | diff_two_versions(zFrom, zTo, &DCfg, 0); |
| 1533 |
+29
-15
| --- src/dispatch.c | ||
| +++ src/dispatch.c | ||
| @@ -59,11 +59,11 @@ | ||
| 59 | 59 | #define CMDFLAG_ABBREVSUBCMD 0x8000 /* Help text abbreviates subcommands */ |
| 60 | 60 | /**************************************************************************/ |
| 61 | 61 | |
| 62 | 62 | /* Values for the 2nd parameter to dispatch_name_search() */ |
| 63 | 63 | #define CMDFLAG_ANY 0x0038 /* Match anything */ |
| 64 | -#define CMDFLAG_PREFIX 0x0200 /* Prefix match is ok */ | |
| 64 | +#define CMDFLAG_PREFIX 0x0200 /* Prefix match is OK */ | |
| 65 | 65 | |
| 66 | 66 | #endif /* INTERFACE */ |
| 67 | 67 | |
| 68 | 68 | /* |
| 69 | 69 | ** The page_index.h file contains the definition for aCommand[] - an array |
| @@ -275,11 +275,11 @@ | ||
| 275 | 275 | char c = z[i]; |
| 276 | 276 | if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){ |
| 277 | 277 | if( i ) blob_append(pOut, z, i); |
| 278 | 278 | z += i+2; |
| 279 | 279 | n -= i+2; |
| 280 | - blob_appendf(pOut, "<a href='%R/help?cmd=%.*s'>%.*s</a>", | |
| 280 | + blob_appendf(pOut, "<a href='%R/help/%.*s'>%.*s</a>", | |
| 281 | 281 | j-3, z, j-3, z); |
| 282 | 282 | z += j-1; |
| 283 | 283 | n -= j-1; |
| 284 | 284 | i = 0; |
| 285 | 285 | }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){ |
| @@ -835,18 +835,27 @@ | ||
| 835 | 835 | return 0; |
| 836 | 836 | } |
| 837 | 837 | |
| 838 | 838 | /* |
| 839 | 839 | ** WEBPAGE: help |
| 840 | -** URL: /help?name=CMD | |
| 840 | +** URL: /help/CMD or /help/www/PAGE | |
| 841 | 841 | ** |
| 842 | -** Show the built-in help text for CMD. CMD can be a command-line interface | |
| 843 | -** command or a page name from the web interface or a setting. | |
| 842 | +** Show the built-in help text for CMD or PAGE. CMD can be a command-line | |
| 843 | +** interface command or a setting name. PAGE is the name of a | |
| 844 | +** web interface. /help//PAGE also works if the double-/ makes it through | |
| 845 | +** the main web server. | |
| 846 | +** | |
| 844 | 847 | ** Query parameters: |
| 845 | 848 | ** |
| 846 | 849 | ** name=CMD Show help for CMD where CMD is a command name or |
| 847 | -** webpage name or setting name. | |
| 850 | +** or setting name. If CMD beings with "/" it is | |
| 851 | +** interpreted as a PAGE name. | |
| 852 | +** | |
| 853 | +** name=www/PAGE Show help for web page PAGE. | |
| 854 | +** | |
| 855 | +** name=/PAGE The initial "www/" on web-page help can be abbreviated as | |
| 856 | +** just "/" | |
| 848 | 857 | ** |
| 849 | 858 | ** plaintext Show the help within <pre>...</pre>, as if it were |
| 850 | 859 | ** displayed using the "fossil help" command. |
| 851 | 860 | ** |
| 852 | 861 | ** raw Show the raw help text without any formatting. |
| @@ -863,10 +872,15 @@ | ||
| 863 | 872 | |
| 864 | 873 | style_set_current_feature("tkt"); |
| 865 | 874 | style_submenu_element("Topic-List", "%R/help"); |
| 866 | 875 | if( search_restrict(SRCH_HELP)!=0 ){ |
| 867 | 876 | style_submenu_element("Search","%R/search?y=h"); |
| 877 | + } | |
| 878 | + if( strncmp(zCmd,"www/",4)==0 && zCmd[4]!=0 ){ | |
| 879 | + /* Use https://domain/fossil/help/www/timeline or similar with the "www" | |
| 880 | + ** intermediate tag to view web-page documentation. */ | |
| 881 | + zCmd += 3; | |
| 868 | 882 | } |
| 869 | 883 | rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 870 | 884 | if( pCmd ){ |
| 871 | 885 | style_header("Help: %s", pCmd->zName); |
| 872 | 886 | }else{ |
| @@ -935,11 +949,11 @@ | ||
| 935 | 949 | const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":""; |
| 936 | 950 | if( '/'==*z || strncmp(z,"test",4)==0 ) continue; |
| 937 | 951 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue; |
| 938 | 952 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 939 | 953 | else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue; |
| 940 | - @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a> | |
| 954 | + @ <li><a href="%R/help/%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a> | |
| 941 | 955 | /* Output aliases */ |
| 942 | 956 | if( occHelp[aCommand[i].iHelp] > 1 ){ |
| 943 | 957 | int j; |
| 944 | 958 | int aliases[MX_HELP_DUP], nAliases=0; |
| 945 | 959 | for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ |
| @@ -952,11 +966,11 @@ | ||
| 952 | 966 | } |
| 953 | 967 | if( nAliases>0 ){ |
| 954 | 968 | int k; |
| 955 | 969 | @(\ |
| 956 | 970 | for(k=0; k<nAliases; k++){ |
| 957 | - @<a href="%R/help?cmd=%s(aCommand[aliases[k]].zName)">\ | |
| 971 | + @<a href="%R/help/%s(aCommand[aliases[k]].zName)">\ | |
| 958 | 972 | @%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\ |
| 959 | 973 | } |
| 960 | 974 | @)\ |
| 961 | 975 | } |
| 962 | 976 | } |
| @@ -972,11 +986,11 @@ | ||
| 972 | 986 | for(i=0; i<MX_COMMAND; i++){ |
| 973 | 987 | const char *z = aCommand[i].zName; |
| 974 | 988 | if( '/'!=*z ) continue; |
| 975 | 989 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 976 | 990 | if( aCommand[i].zHelp[0] ){ |
| 977 | - @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li> | |
| 991 | + @ <li><a href="%R/help/www%s(z)">%s(z+1)</a></li> | |
| 978 | 992 | }else{ |
| 979 | 993 | @ <li>%s(z+1)</li> |
| 980 | 994 | } |
| 981 | 995 | } |
| 982 | 996 | @ </ul></div> |
| @@ -988,11 +1002,11 @@ | ||
| 988 | 1002 | for(i=0; i<MX_COMMAND; i++){ |
| 989 | 1003 | const char *z = aCommand[i].zName; |
| 990 | 1004 | if( strncmp(z,"test",4)!=0 ) continue; |
| 991 | 1005 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 992 | 1006 | if( aCommand[i].zHelp[0] ){ |
| 993 | - @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> | |
| 1007 | + @ <li><a href="%R/help/%s(z)">%s(z)</a></li> | |
| 994 | 1008 | }else{ |
| 995 | 1009 | @ <li>%s(z)</li> |
| 996 | 1010 | } |
| 997 | 1011 | } |
| 998 | 1012 | @ </ul></div> |
| @@ -1004,11 +1018,11 @@ | ||
| 1004 | 1018 | for(i=0; i<MX_COMMAND; i++){ |
| 1005 | 1019 | const char *z = aCommand[i].zName; |
| 1006 | 1020 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue; |
| 1007 | 1021 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 1008 | 1022 | if( aCommand[i].zHelp[0] ){ |
| 1009 | - @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> | |
| 1023 | + @ <li><a href="%R/help/%s(z)">%s(z)</a></li> | |
| 1010 | 1024 | }else{ |
| 1011 | 1025 | @ <li>%s(z)</li> |
| 1012 | 1026 | } |
| 1013 | 1027 | } |
| 1014 | 1028 | @ </ul></div> |
| @@ -1155,11 +1169,11 @@ | ||
| 1155 | 1169 | }else{ |
| 1156 | 1170 | zPattern = mprintf("> ?fossil [-a-z]+ .*\\b%s\\b", zQSub); |
| 1157 | 1171 | } |
| 1158 | 1172 | fossil_free(zQTop); |
| 1159 | 1173 | fossil_free(zQSub); |
| 1160 | - re_compile(&pRe, zPattern, 0); | |
| 1174 | + fossil_re_compile(&pRe, zPattern, 0); | |
| 1161 | 1175 | fossil_free(zPattern); |
| 1162 | 1176 | blob_init(&in, z, -1); |
| 1163 | 1177 | while( blob_line(&in, &line) ){ |
| 1164 | 1178 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){ |
| 1165 | 1179 | int atStart = 1; |
| @@ -1252,13 +1266,13 @@ | ||
| 1252 | 1266 | ReCompiled *pRe = 0; |
| 1253 | 1267 | Blob in, line; |
| 1254 | 1268 | int n = 0; |
| 1255 | 1269 | |
| 1256 | 1270 | if( bAbbrevSubcmd ){ |
| 1257 | - re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0); | |
| 1271 | + fossil_re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0); | |
| 1258 | 1272 | }else{ |
| 1259 | - re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0); | |
| 1273 | + fossil_re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0); | |
| 1260 | 1274 | } |
| 1261 | 1275 | blob_init(&in, z, -1); |
| 1262 | 1276 | while( blob_line(&in, &line) ){ |
| 1263 | 1277 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){ |
| 1264 | 1278 | simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic); |
| @@ -1285,11 +1299,11 @@ | ||
| 1285 | 1299 | int n = 0; |
| 1286 | 1300 | int bSubsectionSeen = 0; |
| 1287 | 1301 | |
| 1288 | 1302 | blob_init(&txt, z, -1); |
| 1289 | 1303 | blob_init(&subsection, 0, 0); |
| 1290 | - re_compile(&pRe, "^ +-.* ", 0); | |
| 1304 | + fossil_re_compile(&pRe, "^ +-.* ", 0); | |
| 1291 | 1305 | while( blob_line(&txt, &line) ){ |
| 1292 | 1306 | int len = blob_size(&line); |
| 1293 | 1307 | unsigned char *zLine = (unsigned char *)blob_buffer(&line); |
| 1294 | 1308 | if( re_match(pRe, zLine, len) ){ |
| 1295 | 1309 | if( blob_size(&subsection) ){ |
| 1296 | 1310 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -59,11 +59,11 @@ | |
| 59 | #define CMDFLAG_ABBREVSUBCMD 0x8000 /* Help text abbreviates subcommands */ |
| 60 | /**************************************************************************/ |
| 61 | |
| 62 | /* Values for the 2nd parameter to dispatch_name_search() */ |
| 63 | #define CMDFLAG_ANY 0x0038 /* Match anything */ |
| 64 | #define CMDFLAG_PREFIX 0x0200 /* Prefix match is ok */ |
| 65 | |
| 66 | #endif /* INTERFACE */ |
| 67 | |
| 68 | /* |
| 69 | ** The page_index.h file contains the definition for aCommand[] - an array |
| @@ -275,11 +275,11 @@ | |
| 275 | char c = z[i]; |
| 276 | if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){ |
| 277 | if( i ) blob_append(pOut, z, i); |
| 278 | z += i+2; |
| 279 | n -= i+2; |
| 280 | blob_appendf(pOut, "<a href='%R/help?cmd=%.*s'>%.*s</a>", |
| 281 | j-3, z, j-3, z); |
| 282 | z += j-1; |
| 283 | n -= j-1; |
| 284 | i = 0; |
| 285 | }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){ |
| @@ -835,18 +835,27 @@ | |
| 835 | return 0; |
| 836 | } |
| 837 | |
| 838 | /* |
| 839 | ** WEBPAGE: help |
| 840 | ** URL: /help?name=CMD |
| 841 | ** |
| 842 | ** Show the built-in help text for CMD. CMD can be a command-line interface |
| 843 | ** command or a page name from the web interface or a setting. |
| 844 | ** Query parameters: |
| 845 | ** |
| 846 | ** name=CMD Show help for CMD where CMD is a command name or |
| 847 | ** webpage name or setting name. |
| 848 | ** |
| 849 | ** plaintext Show the help within <pre>...</pre>, as if it were |
| 850 | ** displayed using the "fossil help" command. |
| 851 | ** |
| 852 | ** raw Show the raw help text without any formatting. |
| @@ -863,10 +872,15 @@ | |
| 863 | |
| 864 | style_set_current_feature("tkt"); |
| 865 | style_submenu_element("Topic-List", "%R/help"); |
| 866 | if( search_restrict(SRCH_HELP)!=0 ){ |
| 867 | style_submenu_element("Search","%R/search?y=h"); |
| 868 | } |
| 869 | rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 870 | if( pCmd ){ |
| 871 | style_header("Help: %s", pCmd->zName); |
| 872 | }else{ |
| @@ -935,11 +949,11 @@ | |
| 935 | const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":""; |
| 936 | if( '/'==*z || strncmp(z,"test",4)==0 ) continue; |
| 937 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue; |
| 938 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 939 | else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue; |
| 940 | @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a> |
| 941 | /* Output aliases */ |
| 942 | if( occHelp[aCommand[i].iHelp] > 1 ){ |
| 943 | int j; |
| 944 | int aliases[MX_HELP_DUP], nAliases=0; |
| 945 | for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ |
| @@ -952,11 +966,11 @@ | |
| 952 | } |
| 953 | if( nAliases>0 ){ |
| 954 | int k; |
| 955 | @(\ |
| 956 | for(k=0; k<nAliases; k++){ |
| 957 | @<a href="%R/help?cmd=%s(aCommand[aliases[k]].zName)">\ |
| 958 | @%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\ |
| 959 | } |
| 960 | @)\ |
| 961 | } |
| 962 | } |
| @@ -972,11 +986,11 @@ | |
| 972 | for(i=0; i<MX_COMMAND; i++){ |
| 973 | const char *z = aCommand[i].zName; |
| 974 | if( '/'!=*z ) continue; |
| 975 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 976 | if( aCommand[i].zHelp[0] ){ |
| 977 | @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li> |
| 978 | }else{ |
| 979 | @ <li>%s(z+1)</li> |
| 980 | } |
| 981 | } |
| 982 | @ </ul></div> |
| @@ -988,11 +1002,11 @@ | |
| 988 | for(i=0; i<MX_COMMAND; i++){ |
| 989 | const char *z = aCommand[i].zName; |
| 990 | if( strncmp(z,"test",4)!=0 ) continue; |
| 991 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 992 | if( aCommand[i].zHelp[0] ){ |
| 993 | @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> |
| 994 | }else{ |
| 995 | @ <li>%s(z)</li> |
| 996 | } |
| 997 | } |
| 998 | @ </ul></div> |
| @@ -1004,11 +1018,11 @@ | |
| 1004 | for(i=0; i<MX_COMMAND; i++){ |
| 1005 | const char *z = aCommand[i].zName; |
| 1006 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue; |
| 1007 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 1008 | if( aCommand[i].zHelp[0] ){ |
| 1009 | @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> |
| 1010 | }else{ |
| 1011 | @ <li>%s(z)</li> |
| 1012 | } |
| 1013 | } |
| 1014 | @ </ul></div> |
| @@ -1155,11 +1169,11 @@ | |
| 1155 | }else{ |
| 1156 | zPattern = mprintf("> ?fossil [-a-z]+ .*\\b%s\\b", zQSub); |
| 1157 | } |
| 1158 | fossil_free(zQTop); |
| 1159 | fossil_free(zQSub); |
| 1160 | re_compile(&pRe, zPattern, 0); |
| 1161 | fossil_free(zPattern); |
| 1162 | blob_init(&in, z, -1); |
| 1163 | while( blob_line(&in, &line) ){ |
| 1164 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){ |
| 1165 | int atStart = 1; |
| @@ -1252,13 +1266,13 @@ | |
| 1252 | ReCompiled *pRe = 0; |
| 1253 | Blob in, line; |
| 1254 | int n = 0; |
| 1255 | |
| 1256 | if( bAbbrevSubcmd ){ |
| 1257 | re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0); |
| 1258 | }else{ |
| 1259 | re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0); |
| 1260 | } |
| 1261 | blob_init(&in, z, -1); |
| 1262 | while( blob_line(&in, &line) ){ |
| 1263 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){ |
| 1264 | simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic); |
| @@ -1285,11 +1299,11 @@ | |
| 1285 | int n = 0; |
| 1286 | int bSubsectionSeen = 0; |
| 1287 | |
| 1288 | blob_init(&txt, z, -1); |
| 1289 | blob_init(&subsection, 0, 0); |
| 1290 | re_compile(&pRe, "^ +-.* ", 0); |
| 1291 | while( blob_line(&txt, &line) ){ |
| 1292 | int len = blob_size(&line); |
| 1293 | unsigned char *zLine = (unsigned char *)blob_buffer(&line); |
| 1294 | if( re_match(pRe, zLine, len) ){ |
| 1295 | if( blob_size(&subsection) ){ |
| 1296 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -59,11 +59,11 @@ | |
| 59 | #define CMDFLAG_ABBREVSUBCMD 0x8000 /* Help text abbreviates subcommands */ |
| 60 | /**************************************************************************/ |
| 61 | |
| 62 | /* Values for the 2nd parameter to dispatch_name_search() */ |
| 63 | #define CMDFLAG_ANY 0x0038 /* Match anything */ |
| 64 | #define CMDFLAG_PREFIX 0x0200 /* Prefix match is OK */ |
| 65 | |
| 66 | #endif /* INTERFACE */ |
| 67 | |
| 68 | /* |
| 69 | ** The page_index.h file contains the definition for aCommand[] - an array |
| @@ -275,11 +275,11 @@ | |
| 275 | char c = z[i]; |
| 276 | if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){ |
| 277 | if( i ) blob_append(pOut, z, i); |
| 278 | z += i+2; |
| 279 | n -= i+2; |
| 280 | blob_appendf(pOut, "<a href='%R/help/%.*s'>%.*s</a>", |
| 281 | j-3, z, j-3, z); |
| 282 | z += j-1; |
| 283 | n -= j-1; |
| 284 | i = 0; |
| 285 | }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){ |
| @@ -835,18 +835,27 @@ | |
| 835 | return 0; |
| 836 | } |
| 837 | |
| 838 | /* |
| 839 | ** WEBPAGE: help |
| 840 | ** URL: /help/CMD or /help/www/PAGE |
| 841 | ** |
| 842 | ** Show the built-in help text for CMD or PAGE. CMD can be a command-line |
| 843 | ** interface command or a setting name. PAGE is the name of a |
| 844 | ** web interface. /help//PAGE also works if the double-/ makes it through |
| 845 | ** the main web server. |
| 846 | ** |
| 847 | ** Query parameters: |
| 848 | ** |
| 849 | ** name=CMD Show help for CMD where CMD is a command name or |
| 850 | ** or setting name. If CMD beings with "/" it is |
| 851 | ** interpreted as a PAGE name. |
| 852 | ** |
| 853 | ** name=www/PAGE Show help for web page PAGE. |
| 854 | ** |
| 855 | ** name=/PAGE The initial "www/" on web-page help can be abbreviated as |
| 856 | ** just "/" |
| 857 | ** |
| 858 | ** plaintext Show the help within <pre>...</pre>, as if it were |
| 859 | ** displayed using the "fossil help" command. |
| 860 | ** |
| 861 | ** raw Show the raw help text without any formatting. |
| @@ -863,10 +872,15 @@ | |
| 872 | |
| 873 | style_set_current_feature("tkt"); |
| 874 | style_submenu_element("Topic-List", "%R/help"); |
| 875 | if( search_restrict(SRCH_HELP)!=0 ){ |
| 876 | style_submenu_element("Search","%R/search?y=h"); |
| 877 | } |
| 878 | if( strncmp(zCmd,"www/",4)==0 && zCmd[4]!=0 ){ |
| 879 | /* Use https://domain/fossil/help/www/timeline or similar with the "www" |
| 880 | ** intermediate tag to view web-page documentation. */ |
| 881 | zCmd += 3; |
| 882 | } |
| 883 | rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 884 | if( pCmd ){ |
| 885 | style_header("Help: %s", pCmd->zName); |
| 886 | }else{ |
| @@ -935,11 +949,11 @@ | |
| 949 | const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":""; |
| 950 | if( '/'==*z || strncmp(z,"test",4)==0 ) continue; |
| 951 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue; |
| 952 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 953 | else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue; |
| 954 | @ <li><a href="%R/help/%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a> |
| 955 | /* Output aliases */ |
| 956 | if( occHelp[aCommand[i].iHelp] > 1 ){ |
| 957 | int j; |
| 958 | int aliases[MX_HELP_DUP], nAliases=0; |
| 959 | for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ |
| @@ -952,11 +966,11 @@ | |
| 966 | } |
| 967 | if( nAliases>0 ){ |
| 968 | int k; |
| 969 | @(\ |
| 970 | for(k=0; k<nAliases; k++){ |
| 971 | @<a href="%R/help/%s(aCommand[aliases[k]].zName)">\ |
| 972 | @%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\ |
| 973 | } |
| 974 | @)\ |
| 975 | } |
| 976 | } |
| @@ -972,11 +986,11 @@ | |
| 986 | for(i=0; i<MX_COMMAND; i++){ |
| 987 | const char *z = aCommand[i].zName; |
| 988 | if( '/'!=*z ) continue; |
| 989 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 990 | if( aCommand[i].zHelp[0] ){ |
| 991 | @ <li><a href="%R/help/www%s(z)">%s(z+1)</a></li> |
| 992 | }else{ |
| 993 | @ <li>%s(z+1)</li> |
| 994 | } |
| 995 | } |
| 996 | @ </ul></div> |
| @@ -988,11 +1002,11 @@ | |
| 1002 | for(i=0; i<MX_COMMAND; i++){ |
| 1003 | const char *z = aCommand[i].zName; |
| 1004 | if( strncmp(z,"test",4)!=0 ) continue; |
| 1005 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 1006 | if( aCommand[i].zHelp[0] ){ |
| 1007 | @ <li><a href="%R/help/%s(z)">%s(z)</a></li> |
| 1008 | }else{ |
| 1009 | @ <li>%s(z)</li> |
| 1010 | } |
| 1011 | } |
| 1012 | @ </ul></div> |
| @@ -1004,11 +1018,11 @@ | |
| 1018 | for(i=0; i<MX_COMMAND; i++){ |
| 1019 | const char *z = aCommand[i].zName; |
| 1020 | if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue; |
| 1021 | else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; |
| 1022 | if( aCommand[i].zHelp[0] ){ |
| 1023 | @ <li><a href="%R/help/%s(z)">%s(z)</a></li> |
| 1024 | }else{ |
| 1025 | @ <li>%s(z)</li> |
| 1026 | } |
| 1027 | } |
| 1028 | @ </ul></div> |
| @@ -1155,11 +1169,11 @@ | |
| 1169 | }else{ |
| 1170 | zPattern = mprintf("> ?fossil [-a-z]+ .*\\b%s\\b", zQSub); |
| 1171 | } |
| 1172 | fossil_free(zQTop); |
| 1173 | fossil_free(zQSub); |
| 1174 | fossil_re_compile(&pRe, zPattern, 0); |
| 1175 | fossil_free(zPattern); |
| 1176 | blob_init(&in, z, -1); |
| 1177 | while( blob_line(&in, &line) ){ |
| 1178 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){ |
| 1179 | int atStart = 1; |
| @@ -1252,13 +1266,13 @@ | |
| 1266 | ReCompiled *pRe = 0; |
| 1267 | Blob in, line; |
| 1268 | int n = 0; |
| 1269 | |
| 1270 | if( bAbbrevSubcmd ){ |
| 1271 | fossil_re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0); |
| 1272 | }else{ |
| 1273 | fossil_re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0); |
| 1274 | } |
| 1275 | blob_init(&in, z, -1); |
| 1276 | while( blob_line(&in, &line) ){ |
| 1277 | if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){ |
| 1278 | simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic); |
| @@ -1285,11 +1299,11 @@ | |
| 1299 | int n = 0; |
| 1300 | int bSubsectionSeen = 0; |
| 1301 | |
| 1302 | blob_init(&txt, z, -1); |
| 1303 | blob_init(&subsection, 0, 0); |
| 1304 | fossil_re_compile(&pRe, "^ +-.* ", 0); |
| 1305 | while( blob_line(&txt, &line) ){ |
| 1306 | int len = blob_size(&line); |
| 1307 | unsigned char *zLine = (unsigned char *)blob_buffer(&line); |
| 1308 | if( re_match(pRe, zLine, len) ){ |
| 1309 | if( blob_size(&subsection) ){ |
| 1310 |
+3
-3
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -520,20 +520,20 @@ | ||
| 520 | 520 | char *zCustomList = 0; /* value of the mimetypes setting */ |
| 521 | 521 | int nCustomEntries = 0; /* number of entries in the mimetypes |
| 522 | 522 | ** setting */ |
| 523 | 523 | mimetype_verify(); |
| 524 | 524 | style_header("Mimetype List"); |
| 525 | - @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename | |
| 525 | + @ <p>The Fossil <a href="%R/help/www/doc">/doc</a> page uses filename | |
| 526 | 526 | @ suffixes and the following tables to guess at the appropriate mimetype |
| 527 | 527 | @ for each document. Mimetypes may be customized and overridden using |
| 528 | - @ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p> | |
| 528 | + @ <a href="%R/help/mimetypes">the mimetypes config setting</a>.</p> | |
| 529 | 529 | zCustomList = db_get("mimetypes",0); |
| 530 | 530 | if( zCustomList!=0 ){ |
| 531 | 531 | Blob list, entry, key, val; |
| 532 | 532 | @ <h1>Repository-specific mimetypes</h1> |
| 533 | 533 | @ <p>The following extension-to-mimetype mappings are defined via |
| 534 | - @ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p> | |
| 534 | + @ the <a href="%R/help/mimetypes">mimetypes setting</a>.</p> | |
| 535 | 535 | @ <table class='sortable mimetypetable' border=1 cellpadding=0 \ |
| 536 | 536 | @ data-column-types='tt' data-init-sort='0'> |
| 537 | 537 | @ <thead> |
| 538 | 538 | @ <tr><th>Suffix<th>Mimetype |
| 539 | 539 | @ </thead> |
| 540 | 540 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -520,20 +520,20 @@ | |
| 520 | char *zCustomList = 0; /* value of the mimetypes setting */ |
| 521 | int nCustomEntries = 0; /* number of entries in the mimetypes |
| 522 | ** setting */ |
| 523 | mimetype_verify(); |
| 524 | style_header("Mimetype List"); |
| 525 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 526 | @ suffixes and the following tables to guess at the appropriate mimetype |
| 527 | @ for each document. Mimetypes may be customized and overridden using |
| 528 | @ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p> |
| 529 | zCustomList = db_get("mimetypes",0); |
| 530 | if( zCustomList!=0 ){ |
| 531 | Blob list, entry, key, val; |
| 532 | @ <h1>Repository-specific mimetypes</h1> |
| 533 | @ <p>The following extension-to-mimetype mappings are defined via |
| 534 | @ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p> |
| 535 | @ <table class='sortable mimetypetable' border=1 cellpadding=0 \ |
| 536 | @ data-column-types='tt' data-init-sort='0'> |
| 537 | @ <thead> |
| 538 | @ <tr><th>Suffix<th>Mimetype |
| 539 | @ </thead> |
| 540 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -520,20 +520,20 @@ | |
| 520 | char *zCustomList = 0; /* value of the mimetypes setting */ |
| 521 | int nCustomEntries = 0; /* number of entries in the mimetypes |
| 522 | ** setting */ |
| 523 | mimetype_verify(); |
| 524 | style_header("Mimetype List"); |
| 525 | @ <p>The Fossil <a href="%R/help/www/doc">/doc</a> page uses filename |
| 526 | @ suffixes and the following tables to guess at the appropriate mimetype |
| 527 | @ for each document. Mimetypes may be customized and overridden using |
| 528 | @ <a href="%R/help/mimetypes">the mimetypes config setting</a>.</p> |
| 529 | zCustomList = db_get("mimetypes",0); |
| 530 | if( zCustomList!=0 ){ |
| 531 | Blob list, entry, key, val; |
| 532 | @ <h1>Repository-specific mimetypes</h1> |
| 533 | @ <p>The following extension-to-mimetype mappings are defined via |
| 534 | @ the <a href="%R/help/mimetypes">mimetypes setting</a>.</p> |
| 535 | @ <table class='sortable mimetypetable' border=1 cellpadding=0 \ |
| 536 | @ data-column-types='tt' data-init-sort='0'> |
| 537 | @ <thead> |
| 538 | @ <tr><th>Suffix<th>Mimetype |
| 539 | @ </thead> |
| 540 |
+2
-1
| --- src/extcgi.c | ||
| +++ src/extcgi.c | ||
| @@ -229,11 +229,12 @@ | ||
| 229 | 229 | zFailReason = "path does not match any file or script"; |
| 230 | 230 | goto ext_not_found; |
| 231 | 231 | } |
| 232 | 232 | assert( nScript>=nRoot+1 ); |
| 233 | 233 | style_set_current_page("ext/%s", &zScript[nRoot+1]); |
| 234 | - zMime = mimetype_from_name(zScript); | |
| 234 | + zMime = P("mimetype"); | |
| 235 | + if( zMime==0 ) zMime = mimetype_from_name(zScript); | |
| 235 | 236 | if( zMime==0 ) zMime = "application/octet-stream"; |
| 236 | 237 | if( !file_isexe(zScript, ExtFILE) ){ |
| 237 | 238 | /* File is not executable. Must be a regular file. In that case, |
| 238 | 239 | ** disallow extra path elements */ |
| 239 | 240 | if( zPath[nScript]!=0 ){ |
| 240 | 241 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -229,11 +229,12 @@ | |
| 229 | zFailReason = "path does not match any file or script"; |
| 230 | goto ext_not_found; |
| 231 | } |
| 232 | assert( nScript>=nRoot+1 ); |
| 233 | style_set_current_page("ext/%s", &zScript[nRoot+1]); |
| 234 | zMime = mimetype_from_name(zScript); |
| 235 | if( zMime==0 ) zMime = "application/octet-stream"; |
| 236 | if( !file_isexe(zScript, ExtFILE) ){ |
| 237 | /* File is not executable. Must be a regular file. In that case, |
| 238 | ** disallow extra path elements */ |
| 239 | if( zPath[nScript]!=0 ){ |
| 240 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -229,11 +229,12 @@ | |
| 229 | zFailReason = "path does not match any file or script"; |
| 230 | goto ext_not_found; |
| 231 | } |
| 232 | assert( nScript>=nRoot+1 ); |
| 233 | style_set_current_page("ext/%s", &zScript[nRoot+1]); |
| 234 | zMime = P("mimetype"); |
| 235 | if( zMime==0 ) zMime = mimetype_from_name(zScript); |
| 236 | if( zMime==0 ) zMime = "application/octet-stream"; |
| 237 | if( !file_isexe(zScript, ExtFILE) ){ |
| 238 | /* File is not executable. Must be a regular file. In that case, |
| 239 | ** disallow extra path elements */ |
| 240 | if( zPath[nScript]!=0 ){ |
| 241 |
+18
-8
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -394,10 +394,12 @@ | ||
| 394 | 394 | tmFlags = timeline_ss_submenu(); |
| 395 | 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | 396 | zStyle = "Columnar"; |
| 397 | 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | 398 | zStyle = "Compact"; |
| 399 | + }else if( tmFlags & TIMELINE_SIMPLE ){ | |
| 400 | + zStyle = "Simple"; | |
| 399 | 401 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 400 | 402 | zStyle = "Verbose"; |
| 401 | 403 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 402 | 404 | zStyle = "Classic"; |
| 403 | 405 | }else{ |
| @@ -731,14 +733,14 @@ | ||
| 731 | 733 | } |
| 732 | 734 | if( tmFlags & TIMELINE_COMPACT ){ |
| 733 | 735 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 734 | 736 | } |
| 735 | 737 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 736 | - if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); | |
| 738 | + if( tmFlags & TIMELINE_INLINE ) cgi_printf("("); | |
| 737 | 739 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 738 | 740 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 739 | - @ [%S(zUuid)]</a> | |
| 741 | + @ %S(zUuid)</a> | |
| 740 | 742 | if( fShowId ){ |
| 741 | 743 | int srcId = delta_source_rid(frid); |
| 742 | 744 | if( srcId>0 ){ |
| 743 | 745 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 744 | 746 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | ||
| 745 | 747 | }else{ |
| 746 | 748 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 747 | 749 | } |
| 748 | 750 | } |
| 749 | 751 | } |
| 752 | + if( tmFlags & TIMELINE_SIMPLE ){ | |
| 753 | + @ <span class='timelineEllipsis' data-id='%d(frid)' \ | |
| 754 | + @ id='ellipsis-%d(frid)'>...</span> | |
| 755 | + @ <span class='clutter' id='detail-%d(frid)'> | |
| 756 | + } | |
| 750 | 757 | @ check-in: \ |
| 751 | 758 | hyperlink_to_version(zCkin); |
| 752 | 759 | if( fShowId ){ |
| 753 | 760 | @ (%d(fmid)) |
| 754 | 761 | } |
| 755 | 762 | @ user: \ |
| 756 | 763 | hyperlink_to_user(zUser, zDate, ","); |
| 757 | 764 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 758 | - if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){ | |
| 759 | - @ size: %d(szFile)) | |
| 765 | + if( tmFlags & TIMELINE_INLINE ){ | |
| 766 | + @ size: %d(szFile) | |
| 760 | 767 | }else{ |
| 761 | 768 | @ size: %d(szFile) |
| 762 | 769 | } |
| 763 | 770 | if( g.perm.Hyperlink && zUuid ){ |
| 764 | 771 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | ||
| 793 | 800 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 794 | 801 | @ %z(zAncLink)[ancestry]</a> |
| 795 | 802 | } |
| 796 | 803 | tag_private_status(frid); |
| 797 | 804 | /* End timelineDetail */ |
| 798 | - if( tmFlags & TIMELINE_COMPACT ){ | |
| 799 | - @ </span></span> | |
| 805 | + if( tmFlags & (TIMELINE_COMPACT|TIMELINE_SIMPLE) ){ | |
| 806 | + @ </span>)</span> | |
| 800 | 807 | }else{ |
| 801 | - @ </span> | |
| 808 | + @ )</span> | |
| 802 | 809 | } |
| 803 | 810 | @ </td></tr> |
| 804 | 811 | } |
| 805 | 812 | db_finalize(&q); |
| 806 | 813 | db_finalize(&qparent); |
| @@ -813,11 +820,14 @@ | ||
| 813 | 820 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| 814 | 821 | @ <td></td><td></td><td></td></tr> |
| 815 | 822 | } |
| 816 | 823 | } |
| 817 | 824 | @ </table> |
| 818 | - timeline_output_graph_javascript(pGraph, TIMELINE_FILEDIFF, iTableId); | |
| 825 | + { | |
| 826 | + int tmFlags = TIMELINE_GRAPH | TIMELINE_FILEDIFF; | |
| 827 | + timeline_output_graph_javascript(pGraph, tmFlags, iTableId); | |
| 828 | + } | |
| 819 | 829 | style_finish_page(); |
| 820 | 830 | } |
| 821 | 831 | |
| 822 | 832 | /* |
| 823 | 833 | ** WEBPAGE: mlink |
| 824 | 834 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -394,10 +394,12 @@ | |
| 394 | tmFlags = timeline_ss_submenu(); |
| 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | zStyle = "Columnar"; |
| 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | zStyle = "Compact"; |
| 399 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 400 | zStyle = "Verbose"; |
| 401 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 402 | zStyle = "Classic"; |
| 403 | }else{ |
| @@ -731,14 +733,14 @@ | |
| 731 | } |
| 732 | if( tmFlags & TIMELINE_COMPACT ){ |
| 733 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 734 | } |
| 735 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 736 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); |
| 737 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 738 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 739 | @ [%S(zUuid)]</a> |
| 740 | if( fShowId ){ |
| 741 | int srcId = delta_source_rid(frid); |
| 742 | if( srcId>0 ){ |
| 743 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 744 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | |
| 745 | }else{ |
| 746 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 747 | } |
| 748 | } |
| 749 | } |
| 750 | @ check-in: \ |
| 751 | hyperlink_to_version(zCkin); |
| 752 | if( fShowId ){ |
| 753 | @ (%d(fmid)) |
| 754 | } |
| 755 | @ user: \ |
| 756 | hyperlink_to_user(zUser, zDate, ","); |
| 757 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 758 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){ |
| 759 | @ size: %d(szFile)) |
| 760 | }else{ |
| 761 | @ size: %d(szFile) |
| 762 | } |
| 763 | if( g.perm.Hyperlink && zUuid ){ |
| 764 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | |
| 793 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 794 | @ %z(zAncLink)[ancestry]</a> |
| 795 | } |
| 796 | tag_private_status(frid); |
| 797 | /* End timelineDetail */ |
| 798 | if( tmFlags & TIMELINE_COMPACT ){ |
| 799 | @ </span></span> |
| 800 | }else{ |
| 801 | @ </span> |
| 802 | } |
| 803 | @ </td></tr> |
| 804 | } |
| 805 | db_finalize(&q); |
| 806 | db_finalize(&qparent); |
| @@ -813,11 +820,14 @@ | |
| 813 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| 814 | @ <td></td><td></td><td></td></tr> |
| 815 | } |
| 816 | } |
| 817 | @ </table> |
| 818 | timeline_output_graph_javascript(pGraph, TIMELINE_FILEDIFF, iTableId); |
| 819 | style_finish_page(); |
| 820 | } |
| 821 | |
| 822 | /* |
| 823 | ** WEBPAGE: mlink |
| 824 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -394,10 +394,12 @@ | |
| 394 | tmFlags = timeline_ss_submenu(); |
| 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | zStyle = "Columnar"; |
| 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | zStyle = "Compact"; |
| 399 | }else if( tmFlags & TIMELINE_SIMPLE ){ |
| 400 | zStyle = "Simple"; |
| 401 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 402 | zStyle = "Verbose"; |
| 403 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 404 | zStyle = "Classic"; |
| 405 | }else{ |
| @@ -731,14 +733,14 @@ | |
| 733 | } |
| 734 | if( tmFlags & TIMELINE_COMPACT ){ |
| 735 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 736 | } |
| 737 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 738 | if( tmFlags & TIMELINE_INLINE ) cgi_printf("("); |
| 739 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 740 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 741 | @ %S(zUuid)</a> |
| 742 | if( fShowId ){ |
| 743 | int srcId = delta_source_rid(frid); |
| 744 | if( srcId>0 ){ |
| 745 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 746 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | |
| 747 | }else{ |
| 748 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 749 | } |
| 750 | } |
| 751 | } |
| 752 | if( tmFlags & TIMELINE_SIMPLE ){ |
| 753 | @ <span class='timelineEllipsis' data-id='%d(frid)' \ |
| 754 | @ id='ellipsis-%d(frid)'>...</span> |
| 755 | @ <span class='clutter' id='detail-%d(frid)'> |
| 756 | } |
| 757 | @ check-in: \ |
| 758 | hyperlink_to_version(zCkin); |
| 759 | if( fShowId ){ |
| 760 | @ (%d(fmid)) |
| 761 | } |
| 762 | @ user: \ |
| 763 | hyperlink_to_user(zUser, zDate, ","); |
| 764 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 765 | if( tmFlags & TIMELINE_INLINE ){ |
| 766 | @ size: %d(szFile) |
| 767 | }else{ |
| 768 | @ size: %d(szFile) |
| 769 | } |
| 770 | if( g.perm.Hyperlink && zUuid ){ |
| 771 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | |
| 800 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 801 | @ %z(zAncLink)[ancestry]</a> |
| 802 | } |
| 803 | tag_private_status(frid); |
| 804 | /* End timelineDetail */ |
| 805 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_SIMPLE) ){ |
| 806 | @ </span>)</span> |
| 807 | }else{ |
| 808 | @ )</span> |
| 809 | } |
| 810 | @ </td></tr> |
| 811 | } |
| 812 | db_finalize(&q); |
| 813 | db_finalize(&qparent); |
| @@ -813,11 +820,14 @@ | |
| 820 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| 821 | @ <td></td><td></td><td></td></tr> |
| 822 | } |
| 823 | } |
| 824 | @ </table> |
| 825 | { |
| 826 | int tmFlags = TIMELINE_GRAPH | TIMELINE_FILEDIFF; |
| 827 | timeline_output_graph_javascript(pGraph, tmFlags, iTableId); |
| 828 | } |
| 829 | style_finish_page(); |
| 830 | } |
| 831 | |
| 832 | /* |
| 833 | ** WEBPAGE: mlink |
| 834 |
+2
-2
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -1912,18 +1912,18 @@ | ||
| 1912 | 1912 | zQP[1] = zQP[0]; |
| 1913 | 1913 | zQP[2] = 0; |
| 1914 | 1914 | if( pSetting->width==0 ){ |
| 1915 | 1915 | /* Boolean setting */ |
| 1916 | 1916 | @ <tr><td align="right"> |
| 1917 | - @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1917 | + @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1918 | 1918 | @ </td><td> |
| 1919 | 1919 | onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0); |
| 1920 | 1920 | @ </td></tr> |
| 1921 | 1921 | }else{ |
| 1922 | 1922 | /* Text value setting */ |
| 1923 | 1923 | @ <tr><td align="right"> |
| 1924 | - @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1924 | + @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1925 | 1925 | @ </td><td> |
| 1926 | 1926 | entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/, |
| 1927 | 1927 | pSetting->def, 0); |
| 1928 | 1928 | @ </td></tr> |
| 1929 | 1929 | } |
| 1930 | 1930 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -1912,18 +1912,18 @@ | |
| 1912 | zQP[1] = zQP[0]; |
| 1913 | zQP[2] = 0; |
| 1914 | if( pSetting->width==0 ){ |
| 1915 | /* Boolean setting */ |
| 1916 | @ <tr><td align="right"> |
| 1917 | @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1918 | @ </td><td> |
| 1919 | onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0); |
| 1920 | @ </td></tr> |
| 1921 | }else{ |
| 1922 | /* Text value setting */ |
| 1923 | @ <tr><td align="right"> |
| 1924 | @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1925 | @ </td><td> |
| 1926 | entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/, |
| 1927 | pSetting->def, 0); |
| 1928 | @ </td></tr> |
| 1929 | } |
| 1930 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -1912,18 +1912,18 @@ | |
| 1912 | zQP[1] = zQP[0]; |
| 1913 | zQP[2] = 0; |
| 1914 | if( pSetting->width==0 ){ |
| 1915 | /* Boolean setting */ |
| 1916 | @ <tr><td align="right"> |
| 1917 | @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1918 | @ </td><td> |
| 1919 | onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0); |
| 1920 | @ </td></tr> |
| 1921 | }else{ |
| 1922 | /* Text value setting */ |
| 1923 | @ <tr><td align="right"> |
| 1924 | @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1925 | @ </td><td> |
| 1926 | entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/, |
| 1927 | pSetting->def, 0); |
| 1928 | @ </td></tr> |
| 1929 | } |
| 1930 |
+9
-11
| --- src/fossil.copybutton.js | ||
| +++ src/fossil.copybutton.js | ||
| @@ -42,13 +42,11 @@ | ||
| 42 | 42 | |
| 43 | 43 | .oncopy: an optional callback function which is added as an event |
| 44 | 44 | listener for the 'text-copied' event (see below). There is |
| 45 | 45 | functionally no difference from setting this option or adding a |
| 46 | 46 | 'text-copied' event listener to the element, and this option is |
| 47 | - considered to be a convenience form of that. For the sake of | |
| 48 | - framework-level consistency, the default value is a callback | |
| 49 | - which passes the copy button to fossil.dom.flashOnce(). | |
| 47 | + considered to be a convenience form of that. | |
| 50 | 48 | |
| 51 | 49 | Note that this function's own defaultOptions object holds default |
| 52 | 50 | values for some options. Any changes made to that object affect |
| 53 | 51 | any future calls to this function. |
| 54 | 52 | |
| @@ -62,25 +60,21 @@ | ||
| 62 | 60 | member is an object with a "text" property holding the copied |
| 63 | 61 | text. Other properties may be added in the future. The event is |
| 64 | 62 | not fired if copying to the clipboard fails (e.g. is not |
| 65 | 63 | available in the current environment). |
| 66 | 64 | |
| 67 | - As a special case, the copy button's click handler is suppressed | |
| 68 | - (becomes a no-op) for as long as the element has the CSS class | |
| 69 | - "disabled". This allows elements which cannot be disabled via | |
| 70 | - HTML attributes, e.g. a SPAN, to act as a copy button while still | |
| 71 | - providing a way to disable them. | |
| 65 | + The copy button's click handler is suppressed (becomes a no-op) | |
| 66 | + for as long as the element has the "disabled" attribute. | |
| 72 | 67 | |
| 73 | 68 | Returns the copy-initialized element. |
| 74 | 69 | |
| 75 | 70 | Example: |
| 76 | 71 | |
| 77 | 72 | const button = fossil.copyButton('#my-copy-button', { |
| 78 | 73 | copyFromId: 'some-other-element-id' |
| 79 | 74 | }); |
| 80 | 75 | button.addEventListener('text-copied',function(ev){ |
| 81 | - fossil.dom.flashOnce(ev.target); | |
| 82 | 76 | console.debug("Copied text:",ev.detail.text); |
| 83 | 77 | }); |
| 84 | 78 | */ |
| 85 | 79 | F.copyButton = function f(e, opt){ |
| 86 | 80 | if('string'===typeof e){ |
| @@ -103,11 +97,11 @@ | ||
| 103 | 97 | e.addEventListener( |
| 104 | 98 | 'click', |
| 105 | 99 | function(ev){ |
| 106 | 100 | ev.preventDefault(); |
| 107 | 101 | ev.stopPropagation(); |
| 108 | - if(e.classList.contains('disabled')) return; | |
| 102 | + if(e.disabled) return; /* This check is probably redundant. */ | |
| 109 | 103 | const txt = extract.call(opt); |
| 110 | 104 | if(txt && D.copyTextToClipboard(txt)){ |
| 111 | 105 | e.dispatchEvent(new CustomEvent('text-copied',{ |
| 112 | 106 | detail: {text: txt} |
| 113 | 107 | })); |
| @@ -116,15 +110,19 @@ | ||
| 116 | 110 | false |
| 117 | 111 | ); |
| 118 | 112 | if('function' === typeof opt.oncopy){ |
| 119 | 113 | e.addEventListener('text-copied', opt.oncopy, false); |
| 120 | 114 | } |
| 115 | + /* Make sure the <button> contains a single nested <span>. */ | |
| 116 | + if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){ | |
| 117 | + D.append(D.clearElement(e), D.span()); | |
| 118 | + } | |
| 121 | 119 | return e; |
| 122 | 120 | }; |
| 123 | 121 | |
| 124 | 122 | F.copyButton.defaultOptions = { |
| 125 | 123 | cssClass: 'copy-button', |
| 126 | - oncopy: D.flashOnce.eventHandler, | |
| 124 | + oncopy: undefined, | |
| 127 | 125 | style: {/*properties copied as-is into element.style*/} |
| 128 | 126 | }; |
| 129 | 127 | |
| 130 | 128 | })(window.fossil); |
| 131 | 129 |
| --- src/fossil.copybutton.js | |
| +++ src/fossil.copybutton.js | |
| @@ -42,13 +42,11 @@ | |
| 42 | |
| 43 | .oncopy: an optional callback function which is added as an event |
| 44 | listener for the 'text-copied' event (see below). There is |
| 45 | functionally no difference from setting this option or adding a |
| 46 | 'text-copied' event listener to the element, and this option is |
| 47 | considered to be a convenience form of that. For the sake of |
| 48 | framework-level consistency, the default value is a callback |
| 49 | which passes the copy button to fossil.dom.flashOnce(). |
| 50 | |
| 51 | Note that this function's own defaultOptions object holds default |
| 52 | values for some options. Any changes made to that object affect |
| 53 | any future calls to this function. |
| 54 | |
| @@ -62,25 +60,21 @@ | |
| 62 | member is an object with a "text" property holding the copied |
| 63 | text. Other properties may be added in the future. The event is |
| 64 | not fired if copying to the clipboard fails (e.g. is not |
| 65 | available in the current environment). |
| 66 | |
| 67 | As a special case, the copy button's click handler is suppressed |
| 68 | (becomes a no-op) for as long as the element has the CSS class |
| 69 | "disabled". This allows elements which cannot be disabled via |
| 70 | HTML attributes, e.g. a SPAN, to act as a copy button while still |
| 71 | providing a way to disable them. |
| 72 | |
| 73 | Returns the copy-initialized element. |
| 74 | |
| 75 | Example: |
| 76 | |
| 77 | const button = fossil.copyButton('#my-copy-button', { |
| 78 | copyFromId: 'some-other-element-id' |
| 79 | }); |
| 80 | button.addEventListener('text-copied',function(ev){ |
| 81 | fossil.dom.flashOnce(ev.target); |
| 82 | console.debug("Copied text:",ev.detail.text); |
| 83 | }); |
| 84 | */ |
| 85 | F.copyButton = function f(e, opt){ |
| 86 | if('string'===typeof e){ |
| @@ -103,11 +97,11 @@ | |
| 103 | e.addEventListener( |
| 104 | 'click', |
| 105 | function(ev){ |
| 106 | ev.preventDefault(); |
| 107 | ev.stopPropagation(); |
| 108 | if(e.classList.contains('disabled')) return; |
| 109 | const txt = extract.call(opt); |
| 110 | if(txt && D.copyTextToClipboard(txt)){ |
| 111 | e.dispatchEvent(new CustomEvent('text-copied',{ |
| 112 | detail: {text: txt} |
| 113 | })); |
| @@ -116,15 +110,19 @@ | |
| 116 | false |
| 117 | ); |
| 118 | if('function' === typeof opt.oncopy){ |
| 119 | e.addEventListener('text-copied', opt.oncopy, false); |
| 120 | } |
| 121 | return e; |
| 122 | }; |
| 123 | |
| 124 | F.copyButton.defaultOptions = { |
| 125 | cssClass: 'copy-button', |
| 126 | oncopy: D.flashOnce.eventHandler, |
| 127 | style: {/*properties copied as-is into element.style*/} |
| 128 | }; |
| 129 | |
| 130 | })(window.fossil); |
| 131 |
| --- src/fossil.copybutton.js | |
| +++ src/fossil.copybutton.js | |
| @@ -42,13 +42,11 @@ | |
| 42 | |
| 43 | .oncopy: an optional callback function which is added as an event |
| 44 | listener for the 'text-copied' event (see below). There is |
| 45 | functionally no difference from setting this option or adding a |
| 46 | 'text-copied' event listener to the element, and this option is |
| 47 | considered to be a convenience form of that. |
| 48 | |
| 49 | Note that this function's own defaultOptions object holds default |
| 50 | values for some options. Any changes made to that object affect |
| 51 | any future calls to this function. |
| 52 | |
| @@ -62,25 +60,21 @@ | |
| 60 | member is an object with a "text" property holding the copied |
| 61 | text. Other properties may be added in the future. The event is |
| 62 | not fired if copying to the clipboard fails (e.g. is not |
| 63 | available in the current environment). |
| 64 | |
| 65 | The copy button's click handler is suppressed (becomes a no-op) |
| 66 | for as long as the element has the "disabled" attribute. |
| 67 | |
| 68 | Returns the copy-initialized element. |
| 69 | |
| 70 | Example: |
| 71 | |
| 72 | const button = fossil.copyButton('#my-copy-button', { |
| 73 | copyFromId: 'some-other-element-id' |
| 74 | }); |
| 75 | button.addEventListener('text-copied',function(ev){ |
| 76 | console.debug("Copied text:",ev.detail.text); |
| 77 | }); |
| 78 | */ |
| 79 | F.copyButton = function f(e, opt){ |
| 80 | if('string'===typeof e){ |
| @@ -103,11 +97,11 @@ | |
| 97 | e.addEventListener( |
| 98 | 'click', |
| 99 | function(ev){ |
| 100 | ev.preventDefault(); |
| 101 | ev.stopPropagation(); |
| 102 | if(e.disabled) return; /* This check is probably redundant. */ |
| 103 | const txt = extract.call(opt); |
| 104 | if(txt && D.copyTextToClipboard(txt)){ |
| 105 | e.dispatchEvent(new CustomEvent('text-copied',{ |
| 106 | detail: {text: txt} |
| 107 | })); |
| @@ -116,15 +110,19 @@ | |
| 110 | false |
| 111 | ); |
| 112 | if('function' === typeof opt.oncopy){ |
| 113 | e.addEventListener('text-copied', opt.oncopy, false); |
| 114 | } |
| 115 | /* Make sure the <button> contains a single nested <span>. */ |
| 116 | if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){ |
| 117 | D.append(D.clearElement(e), D.span()); |
| 118 | } |
| 119 | return e; |
| 120 | }; |
| 121 | |
| 122 | F.copyButton.defaultOptions = { |
| 123 | cssClass: 'copy-button', |
| 124 | oncopy: undefined, |
| 125 | style: {/*properties copied as-is into element.style*/} |
| 126 | }; |
| 127 | |
| 128 | })(window.fossil); |
| 129 |
+6
-5
| --- src/fossil.dom.js | ||
| +++ src/fossil.dom.js | ||
| @@ -248,11 +248,11 @@ | ||
| 248 | 248 | const e = this.create(childType); |
| 249 | 249 | if(parent) parent.appendChild(e); |
| 250 | 250 | return e; |
| 251 | 251 | }; |
| 252 | 252 | }; |
| 253 | - | |
| 253 | + | |
| 254 | 254 | dom.table = dom.createElemFactory('table'); |
| 255 | 255 | dom.thead = dom.createElemFactoryWithOptionalParent('thead'); |
| 256 | 256 | dom.tbody = dom.createElemFactoryWithOptionalParent('tbody'); |
| 257 | 257 | dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot'); |
| 258 | 258 | dom.tr = dom.createElemFactoryWithOptionalParent('tr'); |
| @@ -381,12 +381,12 @@ | ||
| 381 | 381 | const domAddRemoveClass = function f(action,e){ |
| 382 | 382 | if(!f.rxSPlus){ |
| 383 | 383 | f.rxSPlus = /\s+/; |
| 384 | 384 | f.applyAction = function(e,a,v){ |
| 385 | 385 | if(!e || !v |
| 386 | - /*silently skip empty strings/flasy | |
| 387 | - values, for user convenience*/) return; | |
| 386 | + /*silently skip empty strings/falsy | |
| 387 | + values, for usage convenience*/) return; | |
| 388 | 388 | else if(e.forEach){ |
| 389 | 389 | e.forEach((E)=>E.classList[a](v)); |
| 390 | 390 | }else{ |
| 391 | 391 | e.classList[a](v); |
| 392 | 392 | } |
| @@ -584,10 +584,11 @@ | ||
| 584 | 584 | } |
| 585 | 585 | } |
| 586 | 586 | return e; |
| 587 | 587 | }; |
| 588 | 588 | |
| 589 | + /* Impl for dom.enable() and dom.disable(). */ | |
| 589 | 590 | const enableDisable = function f(enable){ |
| 590 | 591 | var i = 1, n = arguments.length; |
| 591 | 592 | for( ; i < n; ++i ){ |
| 592 | 593 | let e = arguments[i]; |
| 593 | 594 | if(e.forEach){ |
| @@ -843,12 +844,12 @@ | ||
| 843 | 844 | /** |
| 844 | 845 | Parses a string as HTML. |
| 845 | 846 | |
| 846 | 847 | Usages: |
| 847 | 848 | |
| 848 | - Array (htmlString) | |
| 849 | - DOMElement (DOMElement target, htmlString) | |
| 849 | + Array parseHtml(htmlString) | |
| 850 | + DOMElement parseHtml(DOMElement target, htmlString) | |
| 850 | 851 | |
| 851 | 852 | The first form parses the string as HTML and returns an Array of |
| 852 | 853 | all elements parsed from it. If string is falsy then it returns |
| 853 | 854 | an empty array. |
| 854 | 855 | |
| 855 | 856 |
| --- src/fossil.dom.js | |
| +++ src/fossil.dom.js | |
| @@ -248,11 +248,11 @@ | |
| 248 | const e = this.create(childType); |
| 249 | if(parent) parent.appendChild(e); |
| 250 | return e; |
| 251 | }; |
| 252 | }; |
| 253 | |
| 254 | dom.table = dom.createElemFactory('table'); |
| 255 | dom.thead = dom.createElemFactoryWithOptionalParent('thead'); |
| 256 | dom.tbody = dom.createElemFactoryWithOptionalParent('tbody'); |
| 257 | dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot'); |
| 258 | dom.tr = dom.createElemFactoryWithOptionalParent('tr'); |
| @@ -381,12 +381,12 @@ | |
| 381 | const domAddRemoveClass = function f(action,e){ |
| 382 | if(!f.rxSPlus){ |
| 383 | f.rxSPlus = /\s+/; |
| 384 | f.applyAction = function(e,a,v){ |
| 385 | if(!e || !v |
| 386 | /*silently skip empty strings/flasy |
| 387 | values, for user convenience*/) return; |
| 388 | else if(e.forEach){ |
| 389 | e.forEach((E)=>E.classList[a](v)); |
| 390 | }else{ |
| 391 | e.classList[a](v); |
| 392 | } |
| @@ -584,10 +584,11 @@ | |
| 584 | } |
| 585 | } |
| 586 | return e; |
| 587 | }; |
| 588 | |
| 589 | const enableDisable = function f(enable){ |
| 590 | var i = 1, n = arguments.length; |
| 591 | for( ; i < n; ++i ){ |
| 592 | let e = arguments[i]; |
| 593 | if(e.forEach){ |
| @@ -843,12 +844,12 @@ | |
| 843 | /** |
| 844 | Parses a string as HTML. |
| 845 | |
| 846 | Usages: |
| 847 | |
| 848 | Array (htmlString) |
| 849 | DOMElement (DOMElement target, htmlString) |
| 850 | |
| 851 | The first form parses the string as HTML and returns an Array of |
| 852 | all elements parsed from it. If string is falsy then it returns |
| 853 | an empty array. |
| 854 | |
| 855 |
| --- src/fossil.dom.js | |
| +++ src/fossil.dom.js | |
| @@ -248,11 +248,11 @@ | |
| 248 | const e = this.create(childType); |
| 249 | if(parent) parent.appendChild(e); |
| 250 | return e; |
| 251 | }; |
| 252 | }; |
| 253 | |
| 254 | dom.table = dom.createElemFactory('table'); |
| 255 | dom.thead = dom.createElemFactoryWithOptionalParent('thead'); |
| 256 | dom.tbody = dom.createElemFactoryWithOptionalParent('tbody'); |
| 257 | dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot'); |
| 258 | dom.tr = dom.createElemFactoryWithOptionalParent('tr'); |
| @@ -381,12 +381,12 @@ | |
| 381 | const domAddRemoveClass = function f(action,e){ |
| 382 | if(!f.rxSPlus){ |
| 383 | f.rxSPlus = /\s+/; |
| 384 | f.applyAction = function(e,a,v){ |
| 385 | if(!e || !v |
| 386 | /*silently skip empty strings/falsy |
| 387 | values, for usage convenience*/) return; |
| 388 | else if(e.forEach){ |
| 389 | e.forEach((E)=>E.classList[a](v)); |
| 390 | }else{ |
| 391 | e.classList[a](v); |
| 392 | } |
| @@ -584,10 +584,11 @@ | |
| 584 | } |
| 585 | } |
| 586 | return e; |
| 587 | }; |
| 588 | |
| 589 | /* Impl for dom.enable() and dom.disable(). */ |
| 590 | const enableDisable = function f(enable){ |
| 591 | var i = 1, n = arguments.length; |
| 592 | for( ; i < n; ++i ){ |
| 593 | let e = arguments[i]; |
| 594 | if(e.forEach){ |
| @@ -843,12 +844,12 @@ | |
| 844 | /** |
| 845 | Parses a string as HTML. |
| 846 | |
| 847 | Usages: |
| 848 | |
| 849 | Array parseHtml(htmlString) |
| 850 | DOMElement parseHtml(DOMElement target, htmlString) |
| 851 | |
| 852 | The first form parses the string as HTML and returns an Array of |
| 853 | all elements parsed from it. If string is falsy then it returns |
| 854 | an empty array. |
| 855 | |
| 856 |
+4
-8
| --- src/fossil.numbered-lines.js | ||
| +++ src/fossil.numbered-lines.js | ||
| @@ -23,13 +23,10 @@ | ||
| 23 | 23 | .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */ |
| 24 | 24 | .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */ |
| 25 | 25 | .replace('?&','?'); |
| 26 | 26 | const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 }; |
| 27 | 27 | const lineTip = new F.PopupWidget({ |
| 28 | - style: { | |
| 29 | - cursor: 'pointer' | |
| 30 | - }, | |
| 31 | 28 | refresh: function(){ |
| 32 | 29 | const link = this.state.link; |
| 33 | 30 | D.clearElement(link); |
| 34 | 31 | if(lineState.start){ |
| 35 | 32 | const ls = [lineState.start]; |
| @@ -48,23 +45,22 @@ | ||
| 48 | 45 | D.append(link, "No lines selected."); |
| 49 | 46 | } |
| 50 | 47 | }, |
| 51 | 48 | init: function(){ |
| 52 | 49 | const e = this.e; |
| 53 | - const btnCopy = D.span(), | |
| 54 | - link = D.span(); | |
| 50 | + const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button'); | |
| 51 | + link = D.label('linenum-copy-button'); | |
| 55 | 52 | this.state = {link}; |
| 56 | 53 | F.copyButton(btnCopy,{ |
| 57 | 54 | copyFromElement: link, |
| 58 | 55 | extractText: ()=>link.dataset.url, |
| 59 | 56 | oncopy: (ev)=>{ |
| 60 | - D.flashOnce(ev.target, undefined, ()=>lineTip.hide()); | |
| 57 | + setTimeout(()=>lineTip.hide(), 400); | |
| 61 | 58 | // arguably too snazzy: F.toast.message("Copied link to clipboard."); |
| 62 | 59 | } |
| 63 | 60 | }); |
| 64 | - this.e.addEventListener('click', ()=>btnCopy.click(), false); | |
| 65 | - D.append(this.e, btnCopy, link) | |
| 61 | + D.append(this.e, btnCopy, link); | |
| 66 | 62 | } |
| 67 | 63 | }); |
| 68 | 64 | |
| 69 | 65 | tbl.addEventListener('click', ()=>lineTip.hide(), true); |
| 70 | 66 | |
| 71 | 67 |
| --- src/fossil.numbered-lines.js | |
| +++ src/fossil.numbered-lines.js | |
| @@ -23,13 +23,10 @@ | |
| 23 | .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */ |
| 24 | .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */ |
| 25 | .replace('?&','?'); |
| 26 | const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 }; |
| 27 | const lineTip = new F.PopupWidget({ |
| 28 | style: { |
| 29 | cursor: 'pointer' |
| 30 | }, |
| 31 | refresh: function(){ |
| 32 | const link = this.state.link; |
| 33 | D.clearElement(link); |
| 34 | if(lineState.start){ |
| 35 | const ls = [lineState.start]; |
| @@ -48,23 +45,22 @@ | |
| 48 | D.append(link, "No lines selected."); |
| 49 | } |
| 50 | }, |
| 51 | init: function(){ |
| 52 | const e = this.e; |
| 53 | const btnCopy = D.span(), |
| 54 | link = D.span(); |
| 55 | this.state = {link}; |
| 56 | F.copyButton(btnCopy,{ |
| 57 | copyFromElement: link, |
| 58 | extractText: ()=>link.dataset.url, |
| 59 | oncopy: (ev)=>{ |
| 60 | D.flashOnce(ev.target, undefined, ()=>lineTip.hide()); |
| 61 | // arguably too snazzy: F.toast.message("Copied link to clipboard."); |
| 62 | } |
| 63 | }); |
| 64 | this.e.addEventListener('click', ()=>btnCopy.click(), false); |
| 65 | D.append(this.e, btnCopy, link) |
| 66 | } |
| 67 | }); |
| 68 | |
| 69 | tbl.addEventListener('click', ()=>lineTip.hide(), true); |
| 70 | |
| 71 |
| --- src/fossil.numbered-lines.js | |
| +++ src/fossil.numbered-lines.js | |
| @@ -23,13 +23,10 @@ | |
| 23 | .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */ |
| 24 | .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */ |
| 25 | .replace('?&','?'); |
| 26 | const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 }; |
| 27 | const lineTip = new F.PopupWidget({ |
| 28 | refresh: function(){ |
| 29 | const link = this.state.link; |
| 30 | D.clearElement(link); |
| 31 | if(lineState.start){ |
| 32 | const ls = [lineState.start]; |
| @@ -48,23 +45,22 @@ | |
| 45 | D.append(link, "No lines selected."); |
| 46 | } |
| 47 | }, |
| 48 | init: function(){ |
| 49 | const e = this.e; |
| 50 | const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button'); |
| 51 | link = D.label('linenum-copy-button'); |
| 52 | this.state = {link}; |
| 53 | F.copyButton(btnCopy,{ |
| 54 | copyFromElement: link, |
| 55 | extractText: ()=>link.dataset.url, |
| 56 | oncopy: (ev)=>{ |
| 57 | setTimeout(()=>lineTip.hide(), 400); |
| 58 | // arguably too snazzy: F.toast.message("Copied link to clipboard."); |
| 59 | } |
| 60 | }); |
| 61 | D.append(this.e, btnCopy, link); |
| 62 | } |
| 63 | }); |
| 64 | |
| 65 | tbl.addEventListener('click', ()=>lineTip.hide(), true); |
| 66 | |
| 67 |
+2
-7
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -901,14 +901,13 @@ | ||
| 901 | 901 | const cpId = 'copy-to-clipboard-'+id; |
| 902 | 902 | /* ^^^ copy button element ID, needed for LABEL element |
| 903 | 903 | pairing. Recall that we destroy all child elements of |
| 904 | 904 | `content` each time we hit this block, so we can reuse |
| 905 | 905 | that element ID on subsequent toggles. */ |
| 906 | - const btnCp = D.attr(D.addClass(D.span(),'copy-button'), 'id', cpId); | |
| 906 | + const btnCp = D.attr(D.addClass(D.button(),'copy-button'), 'id', cpId); | |
| 907 | 907 | F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw}); |
| 908 | 908 | const lblCp = D.label(cpId, "Copy unformatted text"); |
| 909 | - lblCp.addEventListener('click',()=>btnCp.click(), false); | |
| 910 | 909 | D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp)); |
| 911 | 910 | } |
| 912 | 911 | delete e.$isToggling; |
| 913 | 912 | D.append(content, child); |
| 914 | 913 | return; |
| @@ -1161,11 +1160,11 @@ | ||
| 1161 | 1160 | if(body && !body.style.fontSize){ |
| 1162 | 1161 | /** _Attempt_ to force the iframe to inherit the message's text size |
| 1163 | 1162 | if the body has no explicit size set. On desktop systems |
| 1164 | 1163 | the size is apparently being inherited in that case, but on mobile |
| 1165 | 1164 | not. */ |
| 1166 | - body.style.fontSize = window.getComputedStyle(msgObj.e.content); | |
| 1165 | + body.style.fontSize = window.getComputedStyle(msgObj.e.content).fontSize; | |
| 1167 | 1166 | } |
| 1168 | 1167 | if('' === iframe.style.maxHeight){ |
| 1169 | 1168 | /* Resize iframe height to fit the content. Workaround: if we |
| 1170 | 1169 | adjust the iframe height while it's hidden then its height |
| 1171 | 1170 | is 0, so we must briefly unhide it. */ |
| @@ -1736,14 +1735,10 @@ | ||
| 1736 | 1735 | (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false) |
| 1737 | 1736 | ); |
| 1738 | 1737 | return bxs; |
| 1739 | 1738 | })()/*drag/drop/paste*/; |
| 1740 | 1739 | |
| 1741 | - const tzOffsetToString = function(off){ | |
| 1742 | - const hours = Math.round(off/60), min = Math.round(off % 30); | |
| 1743 | - return ''+(hours + (min ? '.5' : '')); | |
| 1744 | - }; | |
| 1745 | 1740 | const localTime8601 = function(d){ |
| 1746 | 1741 | return [ |
| 1747 | 1742 | d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()), |
| 1748 | 1743 | 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()) |
| 1749 | 1744 | ].join(''); |
| 1750 | 1745 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -901,14 +901,13 @@ | |
| 901 | const cpId = 'copy-to-clipboard-'+id; |
| 902 | /* ^^^ copy button element ID, needed for LABEL element |
| 903 | pairing. Recall that we destroy all child elements of |
| 904 | `content` each time we hit this block, so we can reuse |
| 905 | that element ID on subsequent toggles. */ |
| 906 | const btnCp = D.attr(D.addClass(D.span(),'copy-button'), 'id', cpId); |
| 907 | F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw}); |
| 908 | const lblCp = D.label(cpId, "Copy unformatted text"); |
| 909 | lblCp.addEventListener('click',()=>btnCp.click(), false); |
| 910 | D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp)); |
| 911 | } |
| 912 | delete e.$isToggling; |
| 913 | D.append(content, child); |
| 914 | return; |
| @@ -1161,11 +1160,11 @@ | |
| 1161 | if(body && !body.style.fontSize){ |
| 1162 | /** _Attempt_ to force the iframe to inherit the message's text size |
| 1163 | if the body has no explicit size set. On desktop systems |
| 1164 | the size is apparently being inherited in that case, but on mobile |
| 1165 | not. */ |
| 1166 | body.style.fontSize = window.getComputedStyle(msgObj.e.content); |
| 1167 | } |
| 1168 | if('' === iframe.style.maxHeight){ |
| 1169 | /* Resize iframe height to fit the content. Workaround: if we |
| 1170 | adjust the iframe height while it's hidden then its height |
| 1171 | is 0, so we must briefly unhide it. */ |
| @@ -1736,14 +1735,10 @@ | |
| 1736 | (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false) |
| 1737 | ); |
| 1738 | return bxs; |
| 1739 | })()/*drag/drop/paste*/; |
| 1740 | |
| 1741 | const tzOffsetToString = function(off){ |
| 1742 | const hours = Math.round(off/60), min = Math.round(off % 30); |
| 1743 | return ''+(hours + (min ? '.5' : '')); |
| 1744 | }; |
| 1745 | const localTime8601 = function(d){ |
| 1746 | return [ |
| 1747 | d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()), |
| 1748 | 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()) |
| 1749 | ].join(''); |
| 1750 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -901,14 +901,13 @@ | |
| 901 | const cpId = 'copy-to-clipboard-'+id; |
| 902 | /* ^^^ copy button element ID, needed for LABEL element |
| 903 | pairing. Recall that we destroy all child elements of |
| 904 | `content` each time we hit this block, so we can reuse |
| 905 | that element ID on subsequent toggles. */ |
| 906 | const btnCp = D.attr(D.addClass(D.button(),'copy-button'), 'id', cpId); |
| 907 | F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw}); |
| 908 | const lblCp = D.label(cpId, "Copy unformatted text"); |
| 909 | D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp)); |
| 910 | } |
| 911 | delete e.$isToggling; |
| 912 | D.append(content, child); |
| 913 | return; |
| @@ -1161,11 +1160,11 @@ | |
| 1160 | if(body && !body.style.fontSize){ |
| 1161 | /** _Attempt_ to force the iframe to inherit the message's text size |
| 1162 | if the body has no explicit size set. On desktop systems |
| 1163 | the size is apparently being inherited in that case, but on mobile |
| 1164 | not. */ |
| 1165 | body.style.fontSize = window.getComputedStyle(msgObj.e.content).fontSize; |
| 1166 | } |
| 1167 | if('' === iframe.style.maxHeight){ |
| 1168 | /* Resize iframe height to fit the content. Workaround: if we |
| 1169 | adjust the iframe height while it's hidden then its height |
| 1170 | is 0, so we must briefly unhide it. */ |
| @@ -1736,14 +1735,10 @@ | |
| 1735 | (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false) |
| 1736 | ); |
| 1737 | return bxs; |
| 1738 | })()/*drag/drop/paste*/; |
| 1739 | |
| 1740 | const localTime8601 = function(d){ |
| 1741 | return [ |
| 1742 | d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()), |
| 1743 | 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()) |
| 1744 | ].join(''); |
| 1745 |
+3
-4
| --- src/fossil.page.pikchrshow.js | ||
| +++ src/fossil.page.pikchrshow.js | ||
| @@ -47,11 +47,11 @@ | ||
| 47 | 47 | document.body.classList.add('pikchrshow'); |
| 48 | 48 | P.e = { /* various DOM elements we work with... */ |
| 49 | 49 | previewTarget: E('#pikchrshow-output'), |
| 50 | 50 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| 51 | 51 | previewCopyButton: D.attr( |
| 52 | - D.addClass(D.span(),'copy-button'), | |
| 52 | + D.addClass(D.button(),'copy-button'), | |
| 53 | 53 | 'id','preview-copy-button' |
| 54 | 54 | ), |
| 55 | 55 | previewModeLabel: D.label('preview-copy-button'), |
| 56 | 56 | btnSubmit: E('#pikchr-submit-preview'), |
| 57 | 57 | btnStash: E('#pikchr-stash'), |
| @@ -119,11 +119,10 @@ | ||
| 119 | 119 | }, false); |
| 120 | 120 | |
| 121 | 121 | //////////////////////////////////////////////////////////// |
| 122 | 122 | // Setup clipboard-copy of markup/SVG... |
| 123 | 123 | F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText}); |
| 124 | - P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false); | |
| 125 | 124 | |
| 126 | 125 | //////////////////////////////////////////////////////////// |
| 127 | 126 | // Set up dark mode simulator... |
| 128 | 127 | P.e.cbDarkMode.addEventListener('change', function(ev){ |
| 129 | 128 | if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode'); |
| @@ -348,11 +347,11 @@ | ||
| 348 | 347 | D.addClass(preTgt, 'error'); |
| 349 | 348 | this.e.previewModeLabel.innerText = "Error"; |
| 350 | 349 | return; |
| 351 | 350 | } |
| 352 | 351 | D.removeClass(preTgt, 'error'); |
| 353 | - D.removeClass(this.e.previewCopyButton, 'disabled'); | |
| 352 | + this.e.previewCopyButton.disabled = false; | |
| 354 | 353 | D.removeClass(this.e.markupAlignWrapper, 'hidden'); |
| 355 | 354 | D.enable(this.e.previewModeToggle, this.e.markupAlignRadios); |
| 356 | 355 | let label, svg; |
| 357 | 356 | switch(this.previewMode){ |
| 358 | 357 | case 0: |
| @@ -427,11 +426,11 @@ | ||
| 427 | 426 | P.renderPreview(); |
| 428 | 427 | }; |
| 429 | 428 | } |
| 430 | 429 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 431 | 430 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 432 | - D.addClass(this.e.previewCopyButton, 'disabled'); | |
| 431 | + this.e.previewCopyButton.disabled = true; | |
| 433 | 432 | const content = this.e.taContent.value.trim(); |
| 434 | 433 | this.response.raw = this.response.rawSvg = undefined; |
| 435 | 434 | this.response.inputText = content; |
| 436 | 435 | const sampleScript = fp.$_sampleScript; |
| 437 | 436 | delete fp.$_sampleScript; |
| 438 | 437 |
| --- src/fossil.page.pikchrshow.js | |
| +++ src/fossil.page.pikchrshow.js | |
| @@ -47,11 +47,11 @@ | |
| 47 | document.body.classList.add('pikchrshow'); |
| 48 | P.e = { /* various DOM elements we work with... */ |
| 49 | previewTarget: E('#pikchrshow-output'), |
| 50 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| 51 | previewCopyButton: D.attr( |
| 52 | D.addClass(D.span(),'copy-button'), |
| 53 | 'id','preview-copy-button' |
| 54 | ), |
| 55 | previewModeLabel: D.label('preview-copy-button'), |
| 56 | btnSubmit: E('#pikchr-submit-preview'), |
| 57 | btnStash: E('#pikchr-stash'), |
| @@ -119,11 +119,10 @@ | |
| 119 | }, false); |
| 120 | |
| 121 | //////////////////////////////////////////////////////////// |
| 122 | // Setup clipboard-copy of markup/SVG... |
| 123 | F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText}); |
| 124 | P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false); |
| 125 | |
| 126 | //////////////////////////////////////////////////////////// |
| 127 | // Set up dark mode simulator... |
| 128 | P.e.cbDarkMode.addEventListener('change', function(ev){ |
| 129 | if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode'); |
| @@ -348,11 +347,11 @@ | |
| 348 | D.addClass(preTgt, 'error'); |
| 349 | this.e.previewModeLabel.innerText = "Error"; |
| 350 | return; |
| 351 | } |
| 352 | D.removeClass(preTgt, 'error'); |
| 353 | D.removeClass(this.e.previewCopyButton, 'disabled'); |
| 354 | D.removeClass(this.e.markupAlignWrapper, 'hidden'); |
| 355 | D.enable(this.e.previewModeToggle, this.e.markupAlignRadios); |
| 356 | let label, svg; |
| 357 | switch(this.previewMode){ |
| 358 | case 0: |
| @@ -427,11 +426,11 @@ | |
| 427 | P.renderPreview(); |
| 428 | }; |
| 429 | } |
| 430 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 431 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 432 | D.addClass(this.e.previewCopyButton, 'disabled'); |
| 433 | const content = this.e.taContent.value.trim(); |
| 434 | this.response.raw = this.response.rawSvg = undefined; |
| 435 | this.response.inputText = content; |
| 436 | const sampleScript = fp.$_sampleScript; |
| 437 | delete fp.$_sampleScript; |
| 438 |
| --- src/fossil.page.pikchrshow.js | |
| +++ src/fossil.page.pikchrshow.js | |
| @@ -47,11 +47,11 @@ | |
| 47 | document.body.classList.add('pikchrshow'); |
| 48 | P.e = { /* various DOM elements we work with... */ |
| 49 | previewTarget: E('#pikchrshow-output'), |
| 50 | previewLegend: E('#pikchrshow-output-wrapper > legend'), |
| 51 | previewCopyButton: D.attr( |
| 52 | D.addClass(D.button(),'copy-button'), |
| 53 | 'id','preview-copy-button' |
| 54 | ), |
| 55 | previewModeLabel: D.label('preview-copy-button'), |
| 56 | btnSubmit: E('#pikchr-submit-preview'), |
| 57 | btnStash: E('#pikchr-stash'), |
| @@ -119,11 +119,10 @@ | |
| 119 | }, false); |
| 120 | |
| 121 | //////////////////////////////////////////////////////////// |
| 122 | // Setup clipboard-copy of markup/SVG... |
| 123 | F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText}); |
| 124 | |
| 125 | //////////////////////////////////////////////////////////// |
| 126 | // Set up dark mode simulator... |
| 127 | P.e.cbDarkMode.addEventListener('change', function(ev){ |
| 128 | if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode'); |
| @@ -348,11 +347,11 @@ | |
| 347 | D.addClass(preTgt, 'error'); |
| 348 | this.e.previewModeLabel.innerText = "Error"; |
| 349 | return; |
| 350 | } |
| 351 | D.removeClass(preTgt, 'error'); |
| 352 | this.e.previewCopyButton.disabled = false; |
| 353 | D.removeClass(this.e.markupAlignWrapper, 'hidden'); |
| 354 | D.enable(this.e.previewModeToggle, this.e.markupAlignRadios); |
| 355 | let label, svg; |
| 356 | switch(this.previewMode){ |
| 357 | case 0: |
| @@ -427,11 +426,11 @@ | |
| 426 | P.renderPreview(); |
| 427 | }; |
| 428 | } |
| 429 | D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); |
| 430 | D.addClass(this.e.markupAlignWrapper, 'hidden'); |
| 431 | this.e.previewCopyButton.disabled = true; |
| 432 | const content = this.e.taContent.value.trim(); |
| 433 | this.response.raw = this.response.rawSvg = undefined; |
| 434 | this.response.inputText = content; |
| 435 | const sampleScript = fp.$_sampleScript; |
| 436 | delete fp.$_sampleScript; |
| 437 |
| --- src/fossil.page.pikchrshowasm.js | ||
| +++ src/fossil.page.pikchrshowasm.js | ||
| @@ -312,11 +312,10 @@ | ||
| 312 | 312 | if(this.e.pikOut.dataset.pikchr){ |
| 313 | 313 | this.render(this.e.pikOut.dataset.pikchr); |
| 314 | 314 | } |
| 315 | 315 | }.bind(PS)); |
| 316 | 316 | F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText}); |
| 317 | - PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false); | |
| 318 | 317 | |
| 319 | 318 | PS.addMsgHandler('working',function f(ev){ |
| 320 | 319 | switch(ev.data){ |
| 321 | 320 | case 'start': /* See notes in preStartWork(). */; return; |
| 322 | 321 | case 'end': |
| 323 | 322 | |
| 324 | 323 | ADDED src/fossil.page.ticket.js |
| --- src/fossil.page.pikchrshowasm.js | |
| +++ src/fossil.page.pikchrshowasm.js | |
| @@ -312,11 +312,10 @@ | |
| 312 | if(this.e.pikOut.dataset.pikchr){ |
| 313 | this.render(this.e.pikOut.dataset.pikchr); |
| 314 | } |
| 315 | }.bind(PS)); |
| 316 | F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText}); |
| 317 | PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false); |
| 318 | |
| 319 | PS.addMsgHandler('working',function f(ev){ |
| 320 | switch(ev.data){ |
| 321 | case 'start': /* See notes in preStartWork(). */; return; |
| 322 | case 'end': |
| 323 | |
| 324 | DDED src/fossil.page.ticket.js |
| --- src/fossil.page.pikchrshowasm.js | |
| +++ src/fossil.page.pikchrshowasm.js | |
| @@ -312,11 +312,10 @@ | |
| 312 | if(this.e.pikOut.dataset.pikchr){ |
| 313 | this.render(this.e.pikOut.dataset.pikchr); |
| 314 | } |
| 315 | }.bind(PS)); |
| 316 | F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText}); |
| 317 | |
| 318 | PS.addMsgHandler('working',function f(ev){ |
| 319 | switch(ev.data){ |
| 320 | case 'start': /* See notes in preStartWork(). */; return; |
| 321 | case 'end': |
| 322 | |
| 323 | DDED src/fossil.page.ticket.js |
| --- a/src/fossil.page.ticket.js | ||
| +++ b/src/fossil.page.ticket.js | ||
| @@ -0,0 +1,32 @@ | ||
| 1 | +/* | |
| 2 | + * This script adds a checkbox to reverse the sorting on any body.tkt | |
| 3 | + * pages which contain a .tktCommentArea element. | |
| 4 | + */ | |
| 5 | +window.addEventListener( 'load', function() { | |
| 6 | + const tgt = document.querySelectorAll('.tktCommentArea'); | |
| 7 | + if( !tgt ) return; | |
| 8 | + const F = globalThis.fossil, D = F.dom; | |
| 9 | + let i = 0; | |
| 10 | + for(const e of tgt) { | |
| 11 | + ++i; | |
| 12 | + const childs = e.querySelectorAll('.tktCommentEntry'); | |
| 13 | + if( !childs || 1===childs.length ) continue; | |
| 14 | + const cbReverseKey = 'tktCommentArea:reverse'; | |
| 15 | + const cbReverse = D.checkbox(); | |
| 16 | + const cbId = cbReverseKey+':'+i; | |
| 17 | + cbReverse.setAttribute('id',cbId); | |
| 18 | + const widget = D.append( | |
| 19 | + D.div(), | |
| 20 | + cbReverse, | |
| 21 | + D.label(cbReverse, " Show newest first? ") | |
| 22 | + ); | |
| 23 | + widget.classList.add('newest-first-controls'); | |
| 24 | + e.parentElement.insertBefore(widget,e); | |
| 25 | + const cbReverseIt = ()=>{ | |
| 26 | + e.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); | |
| 27 | + F.storage.set(cbReverseKey, cbReverse.checked ? 1 : 0); | |
| 28 | + }; | |
| 29 | + cbReverse.addEventListener('change', cbReverseIt, true); | |
| 30 | + cbReverse.checked = !!(+F.storage.get(cbReverseKey, 0)); | |
| 31 | + }; | |
| 32 | +}); // window.addEventListener( 'load' ... |
| --- a/src/fossil.page.ticket.js | |
| +++ b/src/fossil.page.ticket.js | |
| @@ -0,0 +1,32 @@ | |
| --- a/src/fossil.page.ticket.js | |
| +++ b/src/fossil.page.ticket.js | |
| @@ -0,0 +1,32 @@ | |
| 1 | /* |
| 2 | * This script adds a checkbox to reverse the sorting on any body.tkt |
| 3 | * pages which contain a .tktCommentArea element. |
| 4 | */ |
| 5 | window.addEventListener( 'load', function() { |
| 6 | const tgt = document.querySelectorAll('.tktCommentArea'); |
| 7 | if( !tgt ) return; |
| 8 | const F = globalThis.fossil, D = F.dom; |
| 9 | let i = 0; |
| 10 | for(const e of tgt) { |
| 11 | ++i; |
| 12 | const childs = e.querySelectorAll('.tktCommentEntry'); |
| 13 | if( !childs || 1===childs.length ) continue; |
| 14 | const cbReverseKey = 'tktCommentArea:reverse'; |
| 15 | const cbReverse = D.checkbox(); |
| 16 | const cbId = cbReverseKey+':'+i; |
| 17 | cbReverse.setAttribute('id',cbId); |
| 18 | const widget = D.append( |
| 19 | D.div(), |
| 20 | cbReverse, |
| 21 | D.label(cbReverse, " Show newest first? ") |
| 22 | ); |
| 23 | widget.classList.add('newest-first-controls'); |
| 24 | e.parentElement.insertBefore(widget,e); |
| 25 | const cbReverseIt = ()=>{ |
| 26 | e.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); |
| 27 | F.storage.set(cbReverseKey, cbReverse.checked ? 1 : 0); |
| 28 | }; |
| 29 | cbReverse.addEventListener('change', cbReverseIt, true); |
| 30 | cbReverse.checked = !!(+F.storage.get(cbReverseKey, 0)); |
| 31 | }; |
| 32 | }); // window.addEventListener( 'load' ... |
+2
-2
| --- src/fossil.page.wikiedit.js | ||
| +++ src/fossil.page.wikiedit.js | ||
| @@ -1234,13 +1234,13 @@ | ||
| 1234 | 1234 | encodeURIComponent(a.filename) |
| 1235 | 1235 | ].join(''), |
| 1236 | 1236 | "raw/"+a.src |
| 1237 | 1237 | ].forEach(function(url){ |
| 1238 | 1238 | const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url); |
| 1239 | - const urlCopy = D.span(); | |
| 1239 | + const urlCopy = D.button(); | |
| 1240 | 1240 | const li = D.li(ul); |
| 1241 | - D.append(li, urlCopy, " ", imgUrl); | |
| 1241 | + D.append(li, urlCopy, imgUrl); | |
| 1242 | 1242 | F.copyButton(urlCopy, {copyFromElement: imgUrl}); |
| 1243 | 1243 | }); |
| 1244 | 1244 | }); |
| 1245 | 1245 | return this; |
| 1246 | 1246 | }; |
| 1247 | 1247 |
| --- src/fossil.page.wikiedit.js | |
| +++ src/fossil.page.wikiedit.js | |
| @@ -1234,13 +1234,13 @@ | |
| 1234 | encodeURIComponent(a.filename) |
| 1235 | ].join(''), |
| 1236 | "raw/"+a.src |
| 1237 | ].forEach(function(url){ |
| 1238 | const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url); |
| 1239 | const urlCopy = D.span(); |
| 1240 | const li = D.li(ul); |
| 1241 | D.append(li, urlCopy, " ", imgUrl); |
| 1242 | F.copyButton(urlCopy, {copyFromElement: imgUrl}); |
| 1243 | }); |
| 1244 | }); |
| 1245 | return this; |
| 1246 | }; |
| 1247 |
| --- src/fossil.page.wikiedit.js | |
| +++ src/fossil.page.wikiedit.js | |
| @@ -1234,13 +1234,13 @@ | |
| 1234 | encodeURIComponent(a.filename) |
| 1235 | ].join(''), |
| 1236 | "raw/"+a.src |
| 1237 | ].forEach(function(url){ |
| 1238 | const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url); |
| 1239 | const urlCopy = D.button(); |
| 1240 | const li = D.li(ul); |
| 1241 | D.append(li, urlCopy, imgUrl); |
| 1242 | F.copyButton(urlCopy, {copyFromElement: imgUrl}); |
| 1243 | }); |
| 1244 | }); |
| 1245 | return this; |
| 1246 | }; |
| 1247 |
+4
-4
| --- src/graph.js | ||
| +++ src/graph.js | ||
| @@ -726,15 +726,15 @@ | ||
| 726 | 726 | function toggleDetail(){ |
| 727 | 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | 728 | var x = document.getElementById("detail-"+id); |
| 729 | 729 | if( x.style.display=="inline" ){ |
| 730 | 730 | x.style.display="none"; |
| 731 | - changeDisplayById("ellipsis-"+id,"inline"); | |
| 731 | + document.getElementById("ellipsis-"+id).textContent = "..."; | |
| 732 | 732 | changeDisplayById("links-"+id,"none"); |
| 733 | 733 | }else{ |
| 734 | 734 | x.style.display="inline"; |
| 735 | - changeDisplayById("ellipsis-"+id,"none"); | |
| 735 | + document.getElementById("ellipsis-"+id).textContent = "←"; | |
| 736 | 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | 737 | } |
| 738 | 738 | checkHeight(); |
| 739 | 739 | } |
| 740 | 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | ||
| 764 | 764 | } |
| 765 | 765 | if( tx.scrollToSelect ){ |
| 766 | 766 | scrollToSelected(); |
| 767 | 767 | } |
| 768 | 768 | |
| 769 | - /* Set the onclick= attributes for elements of the "Compact" display | |
| 770 | - ** mode so that clicking turns the details on and off. | |
| 769 | + /* Set the onclick= attributes for elements of the "Compact" and | |
| 770 | + ** "Simple" views so that clicking turns the details on and off. | |
| 771 | 771 | */ |
| 772 | 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | 773 | var i; |
| 774 | 774 | for(i=0; i<lx.length; i++){ |
| 775 | 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 | 776 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -726,15 +726,15 @@ | |
| 726 | function toggleDetail(){ |
| 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | var x = document.getElementById("detail-"+id); |
| 729 | if( x.style.display=="inline" ){ |
| 730 | x.style.display="none"; |
| 731 | changeDisplayById("ellipsis-"+id,"inline"); |
| 732 | changeDisplayById("links-"+id,"none"); |
| 733 | }else{ |
| 734 | x.style.display="inline"; |
| 735 | changeDisplayById("ellipsis-"+id,"none"); |
| 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | } |
| 738 | checkHeight(); |
| 739 | } |
| 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | |
| 764 | } |
| 765 | if( tx.scrollToSelect ){ |
| 766 | scrollToSelected(); |
| 767 | } |
| 768 | |
| 769 | /* Set the onclick= attributes for elements of the "Compact" display |
| 770 | ** mode so that clicking turns the details on and off. |
| 771 | */ |
| 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | var i; |
| 774 | for(i=0; i<lx.length; i++){ |
| 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -726,15 +726,15 @@ | |
| 726 | function toggleDetail(){ |
| 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | var x = document.getElementById("detail-"+id); |
| 729 | if( x.style.display=="inline" ){ |
| 730 | x.style.display="none"; |
| 731 | document.getElementById("ellipsis-"+id).textContent = "..."; |
| 732 | changeDisplayById("links-"+id,"none"); |
| 733 | }else{ |
| 734 | x.style.display="inline"; |
| 735 | document.getElementById("ellipsis-"+id).textContent = "←"; |
| 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | } |
| 738 | checkHeight(); |
| 739 | } |
| 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | |
| 764 | } |
| 765 | if( tx.scrollToSelect ){ |
| 766 | scrollToSelected(); |
| 767 | } |
| 768 | |
| 769 | /* Set the onclick= attributes for elements of the "Compact" and |
| 770 | ** "Simple" views so that clicking turns the details on and off. |
| 771 | */ |
| 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | var i; |
| 774 | for(i=0; i<lx.length; i++){ |
| 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 |
+16
-12
| --- src/href.js | ||
| +++ src/href.js | ||
| @@ -1,12 +1,12 @@ | ||
| 1 | 1 | /* As an anti-robot defense, <a> elements are initially coded with the |
| 2 | 2 | ** href= set to the honeypot, and <form> elements are initialized with |
| 3 | 3 | ** action= set to the login page. The real values for href= and action= |
| 4 | 4 | ** are held in data-href= and data-action=. The following code moves |
| 5 | 5 | ** data-href= into href= and data-action= into action= for all |
| 6 | -** <a> and <form> elements, after delay and maybe also after mouse | |
| 7 | -** movement is seen. | |
| 6 | +** <a> and <form> elements, after CSS has been loaded, and after a delay, | |
| 7 | +** and maybe also after mouse movement is seen. | |
| 8 | 8 | ** |
| 9 | 9 | ** Before sourcing this script, create a separate <script> element |
| 10 | 10 | ** (with type='application/json' to avoid Content Security Policy issues) |
| 11 | 11 | ** containing: |
| 12 | 12 | ** |
| @@ -18,20 +18,23 @@ | ||
| 18 | 18 | ** until the first mousedown event that occurs after the timer expires. |
| 19 | 19 | */ |
| 20 | 20 | var antiRobot = 0; |
| 21 | 21 | function antiRobotGo(){ |
| 22 | 22 | if( antiRobot!=3 ) return; |
| 23 | - antiRobot = 7; | |
| 24 | - var anchors = document.getElementsByTagName("a"); | |
| 25 | - for(var i=0; i<anchors.length; i++){ | |
| 26 | - var j = anchors[i]; | |
| 27 | - if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href"); | |
| 28 | - } | |
| 29 | - var forms = document.getElementsByTagName("form"); | |
| 30 | - for(var i=0; i<forms.length; i++){ | |
| 31 | - var j = forms[i]; | |
| 32 | - if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action"); | |
| 23 | + var z = window.getComputedStyle(document.body).zIndex; | |
| 24 | + if( z==='0' || z===0 ){ | |
| 25 | + antiRobot = 7; | |
| 26 | + var anchors = document.getElementsByTagName("a"); | |
| 27 | + for(var i=0; i<anchors.length; i++){ | |
| 28 | + var j = anchors[i]; | |
| 29 | + if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href"); | |
| 30 | + } | |
| 31 | + var forms = document.getElementsByTagName("form"); | |
| 32 | + for(var i=0; i<forms.length; i++){ | |
| 33 | + var j = forms[i]; | |
| 34 | + if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action"); | |
| 35 | + } | |
| 33 | 36 | } |
| 34 | 37 | } |
| 35 | 38 | function antiRobotDefense(){ |
| 36 | 39 | var x = document.getElementById("href-data"); |
| 37 | 40 | var jx = x.textContent || x.innerText; |
| @@ -56,8 +59,9 @@ | ||
| 56 | 59 | antiRobotGo(); |
| 57 | 60 | }, g.delay) |
| 58 | 61 | }else{ |
| 59 | 62 | antiRobot |= 1; |
| 60 | 63 | } |
| 64 | + window.addEventListener('load',antiRobotGo); | |
| 61 | 65 | antiRobotGo(); |
| 62 | 66 | } |
| 63 | 67 | antiRobotDefense(); |
| 64 | 68 |
| --- src/href.js | |
| +++ src/href.js | |
| @@ -1,12 +1,12 @@ | |
| 1 | /* As an anti-robot defense, <a> elements are initially coded with the |
| 2 | ** href= set to the honeypot, and <form> elements are initialized with |
| 3 | ** action= set to the login page. The real values for href= and action= |
| 4 | ** are held in data-href= and data-action=. The following code moves |
| 5 | ** data-href= into href= and data-action= into action= for all |
| 6 | ** <a> and <form> elements, after delay and maybe also after mouse |
| 7 | ** movement is seen. |
| 8 | ** |
| 9 | ** Before sourcing this script, create a separate <script> element |
| 10 | ** (with type='application/json' to avoid Content Security Policy issues) |
| 11 | ** containing: |
| 12 | ** |
| @@ -18,20 +18,23 @@ | |
| 18 | ** until the first mousedown event that occurs after the timer expires. |
| 19 | */ |
| 20 | var antiRobot = 0; |
| 21 | function antiRobotGo(){ |
| 22 | if( antiRobot!=3 ) return; |
| 23 | antiRobot = 7; |
| 24 | var anchors = document.getElementsByTagName("a"); |
| 25 | for(var i=0; i<anchors.length; i++){ |
| 26 | var j = anchors[i]; |
| 27 | if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href"); |
| 28 | } |
| 29 | var forms = document.getElementsByTagName("form"); |
| 30 | for(var i=0; i<forms.length; i++){ |
| 31 | var j = forms[i]; |
| 32 | if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action"); |
| 33 | } |
| 34 | } |
| 35 | function antiRobotDefense(){ |
| 36 | var x = document.getElementById("href-data"); |
| 37 | var jx = x.textContent || x.innerText; |
| @@ -56,8 +59,9 @@ | |
| 56 | antiRobotGo(); |
| 57 | }, g.delay) |
| 58 | }else{ |
| 59 | antiRobot |= 1; |
| 60 | } |
| 61 | antiRobotGo(); |
| 62 | } |
| 63 | antiRobotDefense(); |
| 64 |
| --- src/href.js | |
| +++ src/href.js | |
| @@ -1,12 +1,12 @@ | |
| 1 | /* As an anti-robot defense, <a> elements are initially coded with the |
| 2 | ** href= set to the honeypot, and <form> elements are initialized with |
| 3 | ** action= set to the login page. The real values for href= and action= |
| 4 | ** are held in data-href= and data-action=. The following code moves |
| 5 | ** data-href= into href= and data-action= into action= for all |
| 6 | ** <a> and <form> elements, after CSS has been loaded, and after a delay, |
| 7 | ** and maybe also after mouse movement is seen. |
| 8 | ** |
| 9 | ** Before sourcing this script, create a separate <script> element |
| 10 | ** (with type='application/json' to avoid Content Security Policy issues) |
| 11 | ** containing: |
| 12 | ** |
| @@ -18,20 +18,23 @@ | |
| 18 | ** until the first mousedown event that occurs after the timer expires. |
| 19 | */ |
| 20 | var antiRobot = 0; |
| 21 | function antiRobotGo(){ |
| 22 | if( antiRobot!=3 ) return; |
| 23 | var z = window.getComputedStyle(document.body).zIndex; |
| 24 | if( z==='0' || z===0 ){ |
| 25 | antiRobot = 7; |
| 26 | var anchors = document.getElementsByTagName("a"); |
| 27 | for(var i=0; i<anchors.length; i++){ |
| 28 | var j = anchors[i]; |
| 29 | if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href"); |
| 30 | } |
| 31 | var forms = document.getElementsByTagName("form"); |
| 32 | for(var i=0; i<forms.length; i++){ |
| 33 | var j = forms[i]; |
| 34 | if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action"); |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | function antiRobotDefense(){ |
| 39 | var x = document.getElementById("href-data"); |
| 40 | var jx = x.textContent || x.innerText; |
| @@ -56,8 +59,9 @@ | |
| 59 | antiRobotGo(); |
| 60 | }, g.delay) |
| 61 | }else{ |
| 62 | antiRobot |= 1; |
| 63 | } |
| 64 | window.addEventListener('load',antiRobotGo); |
| 65 | antiRobotGo(); |
| 66 | } |
| 67 | antiRobotDefense(); |
| 68 |
+27
-10
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -141,15 +141,22 @@ | ||
| 141 | 141 | Blob *pHdr, /* construct the header here */ |
| 142 | 142 | Blob *pLogin, /* Login card header value or NULL */ |
| 143 | 143 | const char *zAltMimetype /* Alternative mimetype */ |
| 144 | 144 | ){ |
| 145 | 145 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 146 | + const char *zPath; | |
| 146 | 147 | |
| 147 | 148 | blob_zero(pHdr); |
| 149 | + if( g.url.subpath ){ | |
| 150 | + zPath = g.url.subpath; | |
| 151 | + }else if( g.url.path==0 || g.url.path[0]==0 ){ | |
| 152 | + zPath = "/"; | |
| 153 | + }else{ | |
| 154 | + zPath = g.url.path; | |
| 155 | + } | |
| 148 | 156 | blob_appendf(pHdr, "%s %s HTTP/1.0\r\n", |
| 149 | - nPayload>0 ? "POST" : "GET", | |
| 150 | - (g.url.path && g.url.path[0]) ? g.url.path : "/"); | |
| 157 | + nPayload>0 ? "POST" : "GET", zPath); | |
| 151 | 158 | if( g.url.proxyAuth ){ |
| 152 | 159 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 153 | 160 | } |
| 154 | 161 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 155 | 162 | const char *zCredentials = g.zHttpAuth; |
| @@ -459,10 +466,11 @@ | ||
| 459 | 466 | /* Activate the PATH= auxiliary argument to the ssh command if that |
| 460 | 467 | ** is called for. |
| 461 | 468 | */ |
| 462 | 469 | if( g.url.isSsh |
| 463 | 470 | && (g.url.flags & URL_SSH_RETRY)==0 |
| 471 | + && g.db!=0 | |
| 464 | 472 | && ssh_needs_path_argument(g.url.hostname, -1) |
| 465 | 473 | ){ |
| 466 | 474 | g.url.flags |= URL_SSH_PATH; |
| 467 | 475 | } |
| 468 | 476 | |
| @@ -678,19 +686,21 @@ | ||
| 678 | 686 | && (g.url.flags & URL_SSH_EXE)==0 /* Does not have ?fossil=.... */ |
| 679 | 687 | && (g.url.flags & URL_SSH_RETRY)==0 /* Not retried already */ |
| 680 | 688 | ){ |
| 681 | 689 | /* Retry after flipping the SSH_PATH setting */ |
| 682 | 690 | transport_close(&g.url); |
| 683 | - fossil_print( | |
| 684 | - "First attempt to run fossil on %s using SSH failed.\n" | |
| 685 | - "Retrying %s the PATH= argument.\n", | |
| 686 | - g.url.hostname, | |
| 687 | - (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with" | |
| 688 | - ); | |
| 691 | + if( (mHttpFlags & HTTP_QUIET)==0 ){ | |
| 692 | + fossil_print( | |
| 693 | + "First attempt to run fossil on %s using SSH failed.\n" | |
| 694 | + "Retrying %s the PATH= argument.\n", | |
| 695 | + g.url.hostname, | |
| 696 | + (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with" | |
| 697 | + ); | |
| 698 | + } | |
| 689 | 699 | g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY; |
| 690 | 700 | rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype); |
| 691 | - if( rc==0 ){ | |
| 701 | + if( rc==0 && g.db!=0 ){ | |
| 692 | 702 | (void)ssh_needs_path_argument(g.url.hostname, |
| 693 | 703 | (g.url.flags & URL_SSH_PATH)!=0); |
| 694 | 704 | } |
| 695 | 705 | return rc; |
| 696 | 706 | }else{ |
| @@ -808,17 +818,19 @@ | ||
| 808 | 818 | ** Options: |
| 809 | 819 | ** --compress Use ZLIB compression on the payload |
| 810 | 820 | ** --mimetype TYPE Mimetype of the payload |
| 811 | 821 | ** --no-cert-verify Disable TLS cert verification |
| 812 | 822 | ** --out FILE Store the reply in FILE |
| 823 | +** --subpath PATH HTTP request path for ssh: and file: URLs | |
| 813 | 824 | ** -v Verbose output |
| 814 | 825 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 815 | 826 | */ |
| 816 | 827 | void test_httpmsg_command(void){ |
| 817 | 828 | const char *zMimetype; |
| 818 | 829 | const char *zInFile; |
| 819 | 830 | const char *zOutFile; |
| 831 | + const char *zSubpath; | |
| 820 | 832 | Blob in, out; |
| 821 | 833 | unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 822 | 834 | |
| 823 | 835 | zMimetype = find_option("mimetype",0,1); |
| 824 | 836 | zOutFile = find_option("out","o",1); |
| @@ -832,10 +844,11 @@ | ||
| 832 | 844 | if( find_option("xfer",0,0)!=0 ){ |
| 833 | 845 | mHttpFlags |= HTTP_USE_LOGIN; |
| 834 | 846 | mHttpFlags &= ~HTTP_GENERIC; |
| 835 | 847 | } |
| 836 | 848 | if( find_option("ipv4",0,0) ) g.fIPv4 = 1; |
| 849 | + zSubpath = find_option("subpath",0,1); | |
| 837 | 850 | verify_all_options(); |
| 838 | 851 | if( g.argc<3 || g.argc>5 ){ |
| 839 | 852 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 840 | 853 | } |
| 841 | 854 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| @@ -846,11 +859,15 @@ | ||
| 846 | 859 | } |
| 847 | 860 | zOutFile = g.argv[4]; |
| 848 | 861 | } |
| 849 | 862 | url_parse(g.argv[2], 0); |
| 850 | 863 | if( g.url.protocol[0]!='h' ){ |
| 851 | - fossil_fatal("the %s command supports only http: and https:", g.argv[1]); | |
| 864 | + if( zSubpath==0 ){ | |
| 865 | + fossil_fatal("the --subpath option is required for %s://",g.url.protocol); | |
| 866 | + }else{ | |
| 867 | + g.url.subpath = fossil_strdup(zSubpath); | |
| 868 | + } | |
| 852 | 869 | } |
| 853 | 870 | if( zInFile ){ |
| 854 | 871 | blob_read_from_file(&in, zInFile, ExtFILE); |
| 855 | 872 | if( zMimetype==0 && (mHttpFlags & HTTP_GENERIC)!=0 ){ |
| 856 | 873 | if( fossil_strcmp(zInFile,"-")==0 ){ |
| 857 | 874 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -141,15 +141,22 @@ | |
| 141 | Blob *pHdr, /* construct the header here */ |
| 142 | Blob *pLogin, /* Login card header value or NULL */ |
| 143 | const char *zAltMimetype /* Alternative mimetype */ |
| 144 | ){ |
| 145 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 146 | |
| 147 | blob_zero(pHdr); |
| 148 | blob_appendf(pHdr, "%s %s HTTP/1.0\r\n", |
| 149 | nPayload>0 ? "POST" : "GET", |
| 150 | (g.url.path && g.url.path[0]) ? g.url.path : "/"); |
| 151 | if( g.url.proxyAuth ){ |
| 152 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 153 | } |
| 154 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 155 | const char *zCredentials = g.zHttpAuth; |
| @@ -459,10 +466,11 @@ | |
| 459 | /* Activate the PATH= auxiliary argument to the ssh command if that |
| 460 | ** is called for. |
| 461 | */ |
| 462 | if( g.url.isSsh |
| 463 | && (g.url.flags & URL_SSH_RETRY)==0 |
| 464 | && ssh_needs_path_argument(g.url.hostname, -1) |
| 465 | ){ |
| 466 | g.url.flags |= URL_SSH_PATH; |
| 467 | } |
| 468 | |
| @@ -678,19 +686,21 @@ | |
| 678 | && (g.url.flags & URL_SSH_EXE)==0 /* Does not have ?fossil=.... */ |
| 679 | && (g.url.flags & URL_SSH_RETRY)==0 /* Not retried already */ |
| 680 | ){ |
| 681 | /* Retry after flipping the SSH_PATH setting */ |
| 682 | transport_close(&g.url); |
| 683 | fossil_print( |
| 684 | "First attempt to run fossil on %s using SSH failed.\n" |
| 685 | "Retrying %s the PATH= argument.\n", |
| 686 | g.url.hostname, |
| 687 | (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with" |
| 688 | ); |
| 689 | g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY; |
| 690 | rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype); |
| 691 | if( rc==0 ){ |
| 692 | (void)ssh_needs_path_argument(g.url.hostname, |
| 693 | (g.url.flags & URL_SSH_PATH)!=0); |
| 694 | } |
| 695 | return rc; |
| 696 | }else{ |
| @@ -808,17 +818,19 @@ | |
| 808 | ** Options: |
| 809 | ** --compress Use ZLIB compression on the payload |
| 810 | ** --mimetype TYPE Mimetype of the payload |
| 811 | ** --no-cert-verify Disable TLS cert verification |
| 812 | ** --out FILE Store the reply in FILE |
| 813 | ** -v Verbose output |
| 814 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 815 | */ |
| 816 | void test_httpmsg_command(void){ |
| 817 | const char *zMimetype; |
| 818 | const char *zInFile; |
| 819 | const char *zOutFile; |
| 820 | Blob in, out; |
| 821 | unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 822 | |
| 823 | zMimetype = find_option("mimetype",0,1); |
| 824 | zOutFile = find_option("out","o",1); |
| @@ -832,10 +844,11 @@ | |
| 832 | if( find_option("xfer",0,0)!=0 ){ |
| 833 | mHttpFlags |= HTTP_USE_LOGIN; |
| 834 | mHttpFlags &= ~HTTP_GENERIC; |
| 835 | } |
| 836 | if( find_option("ipv4",0,0) ) g.fIPv4 = 1; |
| 837 | verify_all_options(); |
| 838 | if( g.argc<3 || g.argc>5 ){ |
| 839 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 840 | } |
| 841 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| @@ -846,11 +859,15 @@ | |
| 846 | } |
| 847 | zOutFile = g.argv[4]; |
| 848 | } |
| 849 | url_parse(g.argv[2], 0); |
| 850 | if( g.url.protocol[0]!='h' ){ |
| 851 | fossil_fatal("the %s command supports only http: and https:", g.argv[1]); |
| 852 | } |
| 853 | if( zInFile ){ |
| 854 | blob_read_from_file(&in, zInFile, ExtFILE); |
| 855 | if( zMimetype==0 && (mHttpFlags & HTTP_GENERIC)!=0 ){ |
| 856 | if( fossil_strcmp(zInFile,"-")==0 ){ |
| 857 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -141,15 +141,22 @@ | |
| 141 | Blob *pHdr, /* construct the header here */ |
| 142 | Blob *pLogin, /* Login card header value or NULL */ |
| 143 | const char *zAltMimetype /* Alternative mimetype */ |
| 144 | ){ |
| 145 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 146 | const char *zPath; |
| 147 | |
| 148 | blob_zero(pHdr); |
| 149 | if( g.url.subpath ){ |
| 150 | zPath = g.url.subpath; |
| 151 | }else if( g.url.path==0 || g.url.path[0]==0 ){ |
| 152 | zPath = "/"; |
| 153 | }else{ |
| 154 | zPath = g.url.path; |
| 155 | } |
| 156 | blob_appendf(pHdr, "%s %s HTTP/1.0\r\n", |
| 157 | nPayload>0 ? "POST" : "GET", zPath); |
| 158 | if( g.url.proxyAuth ){ |
| 159 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 160 | } |
| 161 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 162 | const char *zCredentials = g.zHttpAuth; |
| @@ -459,10 +466,11 @@ | |
| 466 | /* Activate the PATH= auxiliary argument to the ssh command if that |
| 467 | ** is called for. |
| 468 | */ |
| 469 | if( g.url.isSsh |
| 470 | && (g.url.flags & URL_SSH_RETRY)==0 |
| 471 | && g.db!=0 |
| 472 | && ssh_needs_path_argument(g.url.hostname, -1) |
| 473 | ){ |
| 474 | g.url.flags |= URL_SSH_PATH; |
| 475 | } |
| 476 | |
| @@ -678,19 +686,21 @@ | |
| 686 | && (g.url.flags & URL_SSH_EXE)==0 /* Does not have ?fossil=.... */ |
| 687 | && (g.url.flags & URL_SSH_RETRY)==0 /* Not retried already */ |
| 688 | ){ |
| 689 | /* Retry after flipping the SSH_PATH setting */ |
| 690 | transport_close(&g.url); |
| 691 | if( (mHttpFlags & HTTP_QUIET)==0 ){ |
| 692 | fossil_print( |
| 693 | "First attempt to run fossil on %s using SSH failed.\n" |
| 694 | "Retrying %s the PATH= argument.\n", |
| 695 | g.url.hostname, |
| 696 | (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with" |
| 697 | ); |
| 698 | } |
| 699 | g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY; |
| 700 | rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype); |
| 701 | if( rc==0 && g.db!=0 ){ |
| 702 | (void)ssh_needs_path_argument(g.url.hostname, |
| 703 | (g.url.flags & URL_SSH_PATH)!=0); |
| 704 | } |
| 705 | return rc; |
| 706 | }else{ |
| @@ -808,17 +818,19 @@ | |
| 818 | ** Options: |
| 819 | ** --compress Use ZLIB compression on the payload |
| 820 | ** --mimetype TYPE Mimetype of the payload |
| 821 | ** --no-cert-verify Disable TLS cert verification |
| 822 | ** --out FILE Store the reply in FILE |
| 823 | ** --subpath PATH HTTP request path for ssh: and file: URLs |
| 824 | ** -v Verbose output |
| 825 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 826 | */ |
| 827 | void test_httpmsg_command(void){ |
| 828 | const char *zMimetype; |
| 829 | const char *zInFile; |
| 830 | const char *zOutFile; |
| 831 | const char *zSubpath; |
| 832 | Blob in, out; |
| 833 | unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS; |
| 834 | |
| 835 | zMimetype = find_option("mimetype",0,1); |
| 836 | zOutFile = find_option("out","o",1); |
| @@ -832,10 +844,11 @@ | |
| 844 | if( find_option("xfer",0,0)!=0 ){ |
| 845 | mHttpFlags |= HTTP_USE_LOGIN; |
| 846 | mHttpFlags &= ~HTTP_GENERIC; |
| 847 | } |
| 848 | if( find_option("ipv4",0,0) ) g.fIPv4 = 1; |
| 849 | zSubpath = find_option("subpath",0,1); |
| 850 | verify_all_options(); |
| 851 | if( g.argc<3 || g.argc>5 ){ |
| 852 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 853 | } |
| 854 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| @@ -846,11 +859,15 @@ | |
| 859 | } |
| 860 | zOutFile = g.argv[4]; |
| 861 | } |
| 862 | url_parse(g.argv[2], 0); |
| 863 | if( g.url.protocol[0]!='h' ){ |
| 864 | if( zSubpath==0 ){ |
| 865 | fossil_fatal("the --subpath option is required for %s://",g.url.protocol); |
| 866 | }else{ |
| 867 | g.url.subpath = fossil_strdup(zSubpath); |
| 868 | } |
| 869 | } |
| 870 | if( zInFile ){ |
| 871 | blob_read_from_file(&in, zInFile, ExtFILE); |
| 872 | if( zMimetype==0 && (mHttpFlags & HTTP_GENERIC)!=0 ){ |
| 873 | if( fossil_strcmp(zInFile,"-")==0 ){ |
| 874 |
+73
-41
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -401,34 +401,36 @@ | ||
| 401 | 401 | @ </span></div> |
| 402 | 402 | if( pCfg ){ |
| 403 | 403 | append_diff(zOld, zNew, pCfg); |
| 404 | 404 | } |
| 405 | 405 | }else{ |
| 406 | + const char *zCkin2 = | |
| 407 | + mprintf(validate16(zCkin, -1) ? "%!S" : "%T"/*works-like:"%s"*/, zCkin); | |
| 406 | 408 | if( zOld && zNew ){ |
| 407 | 409 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 408 | 410 | if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 409 | 411 | @ Renamed and modified |
| 410 | - @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ | |
| 412 | + @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\ | |
| 411 | 413 | @ %h(zOldName)</a> |
| 412 | 414 | @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 413 | - @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 415 | + @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ | |
| 414 | 416 | @ %h(zName)</a> |
| 415 | 417 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 416 | 418 | }else{ |
| 417 | - @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 419 | + @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ | |
| 418 | 420 | @ %h(zName)</a> |
| 419 | 421 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 420 | 422 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 421 | 423 | } |
| 422 | 424 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 423 | 425 | @ Name change |
| 424 | - @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ | |
| 426 | + @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\ | |
| 425 | 427 | @ %h(zOldName)</a> |
| 426 | - @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 428 | + @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ | |
| 427 | 429 | @ %h(zName)</a>. |
| 428 | 430 | }else{ |
| 429 | - @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 431 | + @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ | |
| 430 | 432 | @ %h(zName)</a> became |
| 431 | 433 | if( mperm==PERM_EXE ){ |
| 432 | 434 | @ executable with contents |
| 433 | 435 | }else if( mperm==PERM_LNK ){ |
| 434 | 436 | @ a symlink with target |
| @@ -436,14 +438,14 @@ | ||
| 436 | 438 | @ a regular file with contents |
| 437 | 439 | } |
| 438 | 440 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 439 | 441 | } |
| 440 | 442 | }else if( zOld ){ |
| 441 | - @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zOld,zCkin))\ | |
| 443 | + @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zOld,zCkin2))\ | |
| 442 | 444 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. |
| 443 | 445 | }else{ |
| 444 | - @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ | |
| 446 | + @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ | |
| 445 | 447 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 446 | 448 | } |
| 447 | 449 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 448 | 450 | if( pCfg ){ |
| 449 | 451 | @ </span></div> |
| @@ -643,10 +645,12 @@ | ||
| 643 | 645 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 644 | 646 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 645 | 647 | }else{ |
| 646 | 648 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 647 | 649 | } |
| 650 | + @ <div class="section" id="changes_section">Changes</div> | |
| 651 | + DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ | |
| 648 | 652 | @ <div class="sectionmenu info-changes-menu"> |
| 649 | 653 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 650 | 654 | if( diffType!=1 ){ |
| 651 | 655 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 652 | 656 | } |
| @@ -718,10 +722,16 @@ | ||
| 718 | 722 | blob_reset(&old); |
| 719 | 723 | blob_reset(&new); |
| 720 | 724 | } |
| 721 | 725 | } |
| 722 | 726 | db_finalize(&q); |
| 727 | + @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ | |
| 728 | + @ document.getElementById('changes_section').textContent = 'Changes ' + | |
| 729 | + @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + | |
| 730 | + @ '+%d(g.diffCnt[1]) ' + | |
| 731 | + @ '−%d(g.diffCnt[2]))' | |
| 732 | + @ </script> | |
| 723 | 733 | append_diff_javascript(diffType); |
| 724 | 734 | } |
| 725 | 735 | |
| 726 | 736 | /* |
| 727 | 737 | ** Render a web-page diff of the changes in the working check-out to |
| @@ -741,10 +751,12 @@ | ||
| 741 | 751 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 742 | 752 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 743 | 753 | }else{ |
| 744 | 754 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 745 | 755 | } |
| 756 | + @ <div class="section" id="changes_section">Changes</div> | |
| 757 | + DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ | |
| 746 | 758 | @ <div class="sectionmenu info-changes-menu"> |
| 747 | 759 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 748 | 760 | if( diffType!=1 ){ |
| 749 | 761 | @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ |
| 750 | 762 | @ Unified Diff</a> |
| @@ -803,10 +815,16 @@ | ||
| 803 | 815 | } |
| 804 | 816 | fossil_free(zLhs); |
| 805 | 817 | fossil_free(zRhs); |
| 806 | 818 | } |
| 807 | 819 | db_finalize(&q); |
| 820 | + @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ | |
| 821 | + @ document.getElementById('changes_section').textContent = 'Changes ' + | |
| 822 | + @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + | |
| 823 | + @ '+%d(g.diffCnt[1]) ' + | |
| 824 | + @ '−%d(g.diffCnt[2]))' | |
| 825 | + @ </script> | |
| 808 | 826 | append_diff_javascript(diffType); |
| 809 | 827 | } |
| 810 | 828 | |
| 811 | 829 | /* |
| 812 | 830 | ** WEBPAGE: ckout |
| @@ -918,11 +936,11 @@ | ||
| 918 | 936 | @ No such object: %h(zName) |
| 919 | 937 | style_finish_page(); |
| 920 | 938 | return; |
| 921 | 939 | } |
| 922 | 940 | zRe = P("regex"); |
| 923 | - if( zRe ) re_compile(&pRe, zRe, 0); | |
| 941 | + if( zRe ) fossil_re_compile(&pRe, zRe, 0); | |
| 924 | 942 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 925 | 943 | zParent = db_text(0, |
| 926 | 944 | "SELECT uuid FROM plink, blob" |
| 927 | 945 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 928 | 946 | rid |
| @@ -975,34 +993,23 @@ | ||
| 975 | 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 976 | 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 977 | 995 | |
| 978 | 996 | /* The Download: line */ |
| 979 | 997 | if( g.perm.Zip ){ |
| 980 | - char *zPJ = db_get("short-project-name", 0); | |
| 981 | - char *zUrl; | |
| 982 | - Blob projName; | |
| 983 | - int jj; | |
| 984 | - if( zPJ==0 ) zPJ = db_get("project-name", "unnamed"); | |
| 985 | - blob_zero(&projName); | |
| 986 | - blob_append(&projName, zPJ, -1); | |
| 987 | - blob_trim(&projName); | |
| 988 | - zPJ = blob_str(&projName); | |
| 989 | - for(jj=0; zPJ[jj]; jj++){ | |
| 990 | - if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ | |
| 991 | - zPJ[jj] = '_'; | |
| 992 | - } | |
| 993 | - } | |
| 994 | - zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); | |
| 995 | 998 | @ <tr><th>Downloads:</th><td> |
| 996 | - @ %z(href("%s",zUrl))Tarball</a> | |
| 997 | - @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> | |
| 998 | - if( g.zLogin!=0 ){ | |
| 999 | - @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ | |
| 1000 | - @ SQL archive</a></td></tr> | |
| 1001 | - } | |
| 1002 | - fossil_free(zUrl); | |
| 1003 | - blob_reset(&projName); | |
| 999 | + if( robot_would_be_restricted("download") ){ | |
| 1000 | + @ See separate %z(href("%R/rchvdwnld/%!S",zUuid))download page</a> | |
| 1001 | + }else{ | |
| 1002 | + char *zBase = archive_base_name(rid); | |
| 1003 | + @ %z(href("%R/tarball/%s.tar.gz",zBase))Tarball</a> | |
| 1004 | + @ | %z(href("%R/zip/%s.zip",zBase))ZIP archive</a> | |
| 1005 | + if( g.zLogin!=0 ){ | |
| 1006 | + @ | %z(href("%R/sqlar/%s.sqlar",zBase))\ | |
| 1007 | + @ SQL archive</a></td></tr> | |
| 1008 | + } | |
| 1009 | + fossil_free(zBase); | |
| 1010 | + } | |
| 1004 | 1011 | } |
| 1005 | 1012 | |
| 1006 | 1013 | @ <tr><th>Timelines:</th><td> |
| 1007 | 1014 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1008 | 1015 | if( zParent ){ |
| @@ -1167,15 +1174,16 @@ | ||
| 1167 | 1174 | } |
| 1168 | 1175 | render_backlink_graph(zUuid, |
| 1169 | 1176 | "<div class=\"section accordion\">References</div>\n"); |
| 1170 | 1177 | @ <div class="section accordion">Context</div><div class="accordion_panel"> |
| 1171 | 1178 | render_checkin_context(rid, 0, 0, 0); |
| 1172 | - @ </div><div class="section accordion">Changes</div> | |
| 1179 | + @ </div><div class="section accordion" id="changes_section">Changes</div> | |
| 1173 | 1180 | @ <div class="accordion_panel"> |
| 1174 | 1181 | @ <div class="sectionmenu info-changes-menu"> |
| 1175 | 1182 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 1176 | 1183 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 1184 | + DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ | |
| 1177 | 1185 | DCfg.pRe = pRe; |
| 1178 | 1186 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 1179 | 1187 | if( diffType!=1 ){ |
| 1180 | 1188 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 1181 | 1189 | @ Unified Diff</a> |
| @@ -1227,10 +1235,18 @@ | ||
| 1227 | 1235 | append_file_change_line(zUuid, zName, zOld, zNew, zOldName, |
| 1228 | 1236 | pCfg,mperm); |
| 1229 | 1237 | } |
| 1230 | 1238 | db_finalize(&q3); |
| 1231 | 1239 | @ </div> |
| 1240 | + if( diffType!=0 ){ | |
| 1241 | + @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ | |
| 1242 | + @ document.getElementById('changes_section').textContent = 'Changes ' + | |
| 1243 | + @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + | |
| 1244 | + @ '+%d(g.diffCnt[1]) ' + | |
| 1245 | + @ '−%d(g.diffCnt[2]))' | |
| 1246 | + @ </script> | |
| 1247 | + } | |
| 1232 | 1248 | append_diff_javascript(diffType); |
| 1233 | 1249 | style_finish_page(); |
| 1234 | 1250 | } |
| 1235 | 1251 | |
| 1236 | 1252 | /* |
| @@ -1414,17 +1430,18 @@ | ||
| 1414 | 1430 | Blob qpGlob; /* glob= query parameter for generated links */ |
| 1415 | 1431 | int bInvert = PB("inv"); |
| 1416 | 1432 | |
| 1417 | 1433 | login_check_credentials(); |
| 1418 | 1434 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1435 | + if( robot_restrict("diff") ) return; | |
| 1419 | 1436 | login_anonymous_available(); |
| 1420 | 1437 | fossil_nice_default(); |
| 1421 | 1438 | blob_init(&qp, 0, 0); |
| 1422 | 1439 | blob_init(&qpGlob, 0, 0); |
| 1423 | 1440 | diffType = preferred_diff_type(); |
| 1424 | 1441 | zRe = P("regex"); |
| 1425 | - if( zRe ) re_compile(&pRe, zRe, 0); | |
| 1442 | + if( zRe ) fossil_re_compile(&pRe, zRe, 0); | |
| 1426 | 1443 | zBranch = P("branch"); |
| 1427 | 1444 | if( zBranch && zBranch[0]==0 ) zBranch = 0; |
| 1428 | 1445 | if( zBranch ){ |
| 1429 | 1446 | blob_appendf(&qp, "branch=%T", zBranch); |
| 1430 | 1447 | zMergeOrigin = mprintf("merge-in:%s", zBranch); |
| @@ -1918,18 +1935,31 @@ | ||
| 1918 | 1935 | ** * The "preferred-diff-type" setting |
| 1919 | 1936 | ** * 1 for mobile and 2 for desktop, based on the UserAgent |
| 1920 | 1937 | */ |
| 1921 | 1938 | int preferred_diff_type(void){ |
| 1922 | 1939 | int dflt; |
| 1940 | + int res; | |
| 1941 | + int isBot; | |
| 1923 | 1942 | static char zDflt[2] |
| 1924 | 1943 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1925 | - dflt = db_get_int("preferred-diff-type",-99); | |
| 1926 | - if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; | |
| 1944 | + if( robot_would_be_restricted("diff") ){ | |
| 1945 | + dflt = 0; | |
| 1946 | + isBot = 1; | |
| 1947 | + }else{ | |
| 1948 | + dflt = db_get_int("preferred-diff-type",-99); | |
| 1949 | + if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; | |
| 1950 | + isBot = 0; | |
| 1951 | + } | |
| 1927 | 1952 | zDflt[0] = dflt + '0'; |
| 1928 | 1953 | zDflt[1] = 0; |
| 1929 | 1954 | cookie_link_parameter("diff","diff", zDflt); |
| 1930 | - return atoi(PD_NoBot("diff",zDflt)); | |
| 1955 | + res = atoi(PD_NoBot("diff",zDflt)); | |
| 1956 | + if( isBot && res>0 && robot_restrict("diff") ){ | |
| 1957 | + cgi_reply(); | |
| 1958 | + fossil_exit(0); | |
| 1959 | + } | |
| 1960 | + return res; | |
| 1931 | 1961 | } |
| 1932 | 1962 | |
| 1933 | 1963 | |
| 1934 | 1964 | /* |
| 1935 | 1965 | ** WEBPAGE: fdiff |
| @@ -1967,10 +1997,11 @@ | ||
| 1967 | 1997 | int verbose = PB("verbose"); |
| 1968 | 1998 | DiffConfig DCfg; |
| 1969 | 1999 | |
| 1970 | 2000 | login_check_credentials(); |
| 1971 | 2001 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2002 | + if( robot_restrict("diff") ) return; | |
| 1972 | 2003 | diff_config_init(&DCfg, 0); |
| 1973 | 2004 | diffType = preferred_diff_type(); |
| 1974 | 2005 | if( P("from") && P("to") ){ |
| 1975 | 2006 | v1 = artifact_from_ci_and_filename("from"); |
| 1976 | 2007 | v2 = artifact_from_ci_and_filename("to"); |
| @@ -2009,11 +2040,11 @@ | ||
| 2009 | 2040 | } |
| 2010 | 2041 | db_finalize(&q); |
| 2011 | 2042 | } |
| 2012 | 2043 | zRe = P("regex"); |
| 2013 | 2044 | cgi_check_for_malice(); |
| 2014 | - if( zRe ) re_compile(&pRe, zRe, 0); | |
| 2045 | + if( zRe ) fossil_re_compile(&pRe, zRe, 0); | |
| 2015 | 2046 | if( verbose ) objdescFlags |= OBJDESC_DETAIL; |
| 2016 | 2047 | if( isPatch ){ |
| 2017 | 2048 | Blob c1, c2, *pOut; |
| 2018 | 2049 | DiffConfig DCfg; |
| 2019 | 2050 | pOut = cgi_output_blob(); |
| @@ -2407,15 +2438,15 @@ | ||
| 2407 | 2438 | object_description(rid, objdescFlags, 0, &downloadName); |
| 2408 | 2439 | style_submenu_element("Download", "%R/raw/%s?at=%T", |
| 2409 | 2440 | zUuid, file_tail(blob_str(&downloadName))); |
| 2410 | 2441 | @ <hr> |
| 2411 | 2442 | content_get(rid, &content); |
| 2412 | - if( !g.isHuman ){ | |
| 2443 | + if( blob_size(&content)>100000 ){ | |
| 2413 | 2444 | /* Prevent robots from running hexdump on megabyte-sized source files |
| 2414 | 2445 | ** and there by eating up lots of CPU time and bandwidth. There is |
| 2415 | 2446 | ** no good reason for a robot to need a hexdump. */ |
| 2416 | - @ <p>A hex dump of this file is not available. | |
| 2447 | + @ <p>A hex dump of this file is not available because it is too large. | |
| 2417 | 2448 | @ Please download the raw binary file and generate a hex dump yourself.</p> |
| 2418 | 2449 | }else{ |
| 2419 | 2450 | @ <blockquote><pre> |
| 2420 | 2451 | hexdump(&content); |
| 2421 | 2452 | @ </pre></blockquote> |
| @@ -2915,11 +2946,12 @@ | ||
| 2915 | 2946 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2916 | 2947 | } |
| 2917 | 2948 | db_finalize(&q); |
| 2918 | 2949 | } |
| 2919 | 2950 | if( !docOnly ){ |
| 2920 | - style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName)); | |
| 2951 | + style_submenu_element("Download", "%R/raw/%s?at=%T", | |
| 2952 | + zUuid, file_tail(blob_str(&downloadName))); | |
| 2921 | 2953 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2922 | 2954 | style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); |
| 2923 | 2955 | } |
| 2924 | 2956 | } |
| 2925 | 2957 | if( zMime ){ |
| 2926 | 2958 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -401,34 +401,36 @@ | |
| 401 | @ </span></div> |
| 402 | if( pCfg ){ |
| 403 | append_diff(zOld, zNew, pCfg); |
| 404 | } |
| 405 | }else{ |
| 406 | if( zOld && zNew ){ |
| 407 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 408 | if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 409 | @ Renamed and modified |
| 410 | @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ |
| 411 | @ %h(zOldName)</a> |
| 412 | @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 413 | @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 414 | @ %h(zName)</a> |
| 415 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 416 | }else{ |
| 417 | @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 418 | @ %h(zName)</a> |
| 419 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 420 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 421 | } |
| 422 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 423 | @ Name change |
| 424 | @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\ |
| 425 | @ %h(zOldName)</a> |
| 426 | @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 427 | @ %h(zName)</a>. |
| 428 | }else{ |
| 429 | @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 430 | @ %h(zName)</a> became |
| 431 | if( mperm==PERM_EXE ){ |
| 432 | @ executable with contents |
| 433 | }else if( mperm==PERM_LNK ){ |
| 434 | @ a symlink with target |
| @@ -436,14 +438,14 @@ | |
| 436 | @ a regular file with contents |
| 437 | } |
| 438 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 439 | } |
| 440 | }else if( zOld ){ |
| 441 | @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zOld,zCkin))\ |
| 442 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. |
| 443 | }else{ |
| 444 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 445 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 446 | } |
| 447 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 448 | if( pCfg ){ |
| 449 | @ </span></div> |
| @@ -643,10 +645,12 @@ | |
| 643 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 644 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 645 | }else{ |
| 646 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 647 | } |
| 648 | @ <div class="sectionmenu info-changes-menu"> |
| 649 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 650 | if( diffType!=1 ){ |
| 651 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 652 | } |
| @@ -718,10 +722,16 @@ | |
| 718 | blob_reset(&old); |
| 719 | blob_reset(&new); |
| 720 | } |
| 721 | } |
| 722 | db_finalize(&q); |
| 723 | append_diff_javascript(diffType); |
| 724 | } |
| 725 | |
| 726 | /* |
| 727 | ** Render a web-page diff of the changes in the working check-out to |
| @@ -741,10 +751,12 @@ | |
| 741 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 742 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 743 | }else{ |
| 744 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 745 | } |
| 746 | @ <div class="sectionmenu info-changes-menu"> |
| 747 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 748 | if( diffType!=1 ){ |
| 749 | @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ |
| 750 | @ Unified Diff</a> |
| @@ -803,10 +815,16 @@ | |
| 803 | } |
| 804 | fossil_free(zLhs); |
| 805 | fossil_free(zRhs); |
| 806 | } |
| 807 | db_finalize(&q); |
| 808 | append_diff_javascript(diffType); |
| 809 | } |
| 810 | |
| 811 | /* |
| 812 | ** WEBPAGE: ckout |
| @@ -918,11 +936,11 @@ | |
| 918 | @ No such object: %h(zName) |
| 919 | style_finish_page(); |
| 920 | return; |
| 921 | } |
| 922 | zRe = P("regex"); |
| 923 | if( zRe ) re_compile(&pRe, zRe, 0); |
| 924 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 925 | zParent = db_text(0, |
| 926 | "SELECT uuid FROM plink, blob" |
| 927 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 928 | rid |
| @@ -975,34 +993,23 @@ | |
| 975 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 976 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 977 | |
| 978 | /* The Download: line */ |
| 979 | if( g.perm.Zip ){ |
| 980 | char *zPJ = db_get("short-project-name", 0); |
| 981 | char *zUrl; |
| 982 | Blob projName; |
| 983 | int jj; |
| 984 | if( zPJ==0 ) zPJ = db_get("project-name", "unnamed"); |
| 985 | blob_zero(&projName); |
| 986 | blob_append(&projName, zPJ, -1); |
| 987 | blob_trim(&projName); |
| 988 | zPJ = blob_str(&projName); |
| 989 | for(jj=0; zPJ[jj]; jj++){ |
| 990 | if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ |
| 991 | zPJ[jj] = '_'; |
| 992 | } |
| 993 | } |
| 994 | zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); |
| 995 | @ <tr><th>Downloads:</th><td> |
| 996 | @ %z(href("%s",zUrl))Tarball</a> |
| 997 | @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> |
| 998 | if( g.zLogin!=0 ){ |
| 999 | @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ |
| 1000 | @ SQL archive</a></td></tr> |
| 1001 | } |
| 1002 | fossil_free(zUrl); |
| 1003 | blob_reset(&projName); |
| 1004 | } |
| 1005 | |
| 1006 | @ <tr><th>Timelines:</th><td> |
| 1007 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1008 | if( zParent ){ |
| @@ -1167,15 +1174,16 @@ | |
| 1167 | } |
| 1168 | render_backlink_graph(zUuid, |
| 1169 | "<div class=\"section accordion\">References</div>\n"); |
| 1170 | @ <div class="section accordion">Context</div><div class="accordion_panel"> |
| 1171 | render_checkin_context(rid, 0, 0, 0); |
| 1172 | @ </div><div class="section accordion">Changes</div> |
| 1173 | @ <div class="accordion_panel"> |
| 1174 | @ <div class="sectionmenu info-changes-menu"> |
| 1175 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 1176 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 1177 | DCfg.pRe = pRe; |
| 1178 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 1179 | if( diffType!=1 ){ |
| 1180 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 1181 | @ Unified Diff</a> |
| @@ -1227,10 +1235,18 @@ | |
| 1227 | append_file_change_line(zUuid, zName, zOld, zNew, zOldName, |
| 1228 | pCfg,mperm); |
| 1229 | } |
| 1230 | db_finalize(&q3); |
| 1231 | @ </div> |
| 1232 | append_diff_javascript(diffType); |
| 1233 | style_finish_page(); |
| 1234 | } |
| 1235 | |
| 1236 | /* |
| @@ -1414,17 +1430,18 @@ | |
| 1414 | Blob qpGlob; /* glob= query parameter for generated links */ |
| 1415 | int bInvert = PB("inv"); |
| 1416 | |
| 1417 | login_check_credentials(); |
| 1418 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1419 | login_anonymous_available(); |
| 1420 | fossil_nice_default(); |
| 1421 | blob_init(&qp, 0, 0); |
| 1422 | blob_init(&qpGlob, 0, 0); |
| 1423 | diffType = preferred_diff_type(); |
| 1424 | zRe = P("regex"); |
| 1425 | if( zRe ) re_compile(&pRe, zRe, 0); |
| 1426 | zBranch = P("branch"); |
| 1427 | if( zBranch && zBranch[0]==0 ) zBranch = 0; |
| 1428 | if( zBranch ){ |
| 1429 | blob_appendf(&qp, "branch=%T", zBranch); |
| 1430 | zMergeOrigin = mprintf("merge-in:%s", zBranch); |
| @@ -1918,18 +1935,31 @@ | |
| 1918 | ** * The "preferred-diff-type" setting |
| 1919 | ** * 1 for mobile and 2 for desktop, based on the UserAgent |
| 1920 | */ |
| 1921 | int preferred_diff_type(void){ |
| 1922 | int dflt; |
| 1923 | static char zDflt[2] |
| 1924 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1925 | dflt = db_get_int("preferred-diff-type",-99); |
| 1926 | if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; |
| 1927 | zDflt[0] = dflt + '0'; |
| 1928 | zDflt[1] = 0; |
| 1929 | cookie_link_parameter("diff","diff", zDflt); |
| 1930 | return atoi(PD_NoBot("diff",zDflt)); |
| 1931 | } |
| 1932 | |
| 1933 | |
| 1934 | /* |
| 1935 | ** WEBPAGE: fdiff |
| @@ -1967,10 +1997,11 @@ | |
| 1967 | int verbose = PB("verbose"); |
| 1968 | DiffConfig DCfg; |
| 1969 | |
| 1970 | login_check_credentials(); |
| 1971 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1972 | diff_config_init(&DCfg, 0); |
| 1973 | diffType = preferred_diff_type(); |
| 1974 | if( P("from") && P("to") ){ |
| 1975 | v1 = artifact_from_ci_and_filename("from"); |
| 1976 | v2 = artifact_from_ci_and_filename("to"); |
| @@ -2009,11 +2040,11 @@ | |
| 2009 | } |
| 2010 | db_finalize(&q); |
| 2011 | } |
| 2012 | zRe = P("regex"); |
| 2013 | cgi_check_for_malice(); |
| 2014 | if( zRe ) re_compile(&pRe, zRe, 0); |
| 2015 | if( verbose ) objdescFlags |= OBJDESC_DETAIL; |
| 2016 | if( isPatch ){ |
| 2017 | Blob c1, c2, *pOut; |
| 2018 | DiffConfig DCfg; |
| 2019 | pOut = cgi_output_blob(); |
| @@ -2407,15 +2438,15 @@ | |
| 2407 | object_description(rid, objdescFlags, 0, &downloadName); |
| 2408 | style_submenu_element("Download", "%R/raw/%s?at=%T", |
| 2409 | zUuid, file_tail(blob_str(&downloadName))); |
| 2410 | @ <hr> |
| 2411 | content_get(rid, &content); |
| 2412 | if( !g.isHuman ){ |
| 2413 | /* Prevent robots from running hexdump on megabyte-sized source files |
| 2414 | ** and there by eating up lots of CPU time and bandwidth. There is |
| 2415 | ** no good reason for a robot to need a hexdump. */ |
| 2416 | @ <p>A hex dump of this file is not available. |
| 2417 | @ Please download the raw binary file and generate a hex dump yourself.</p> |
| 2418 | }else{ |
| 2419 | @ <blockquote><pre> |
| 2420 | hexdump(&content); |
| 2421 | @ </pre></blockquote> |
| @@ -2915,11 +2946,12 @@ | |
| 2915 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2916 | } |
| 2917 | db_finalize(&q); |
| 2918 | } |
| 2919 | if( !docOnly ){ |
| 2920 | style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName)); |
| 2921 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2922 | style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); |
| 2923 | } |
| 2924 | } |
| 2925 | if( zMime ){ |
| 2926 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -401,34 +401,36 @@ | |
| 401 | @ </span></div> |
| 402 | if( pCfg ){ |
| 403 | append_diff(zOld, zNew, pCfg); |
| 404 | } |
| 405 | }else{ |
| 406 | const char *zCkin2 = |
| 407 | mprintf(validate16(zCkin, -1) ? "%!S" : "%T"/*works-like:"%s"*/, zCkin); |
| 408 | if( zOld && zNew ){ |
| 409 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 410 | if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 411 | @ Renamed and modified |
| 412 | @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\ |
| 413 | @ %h(zOldName)</a> |
| 414 | @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 415 | @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ |
| 416 | @ %h(zName)</a> |
| 417 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 418 | }else{ |
| 419 | @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ |
| 420 | @ %h(zName)</a> |
| 421 | @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a> |
| 422 | @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 423 | } |
| 424 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 425 | @ Name change |
| 426 | @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\ |
| 427 | @ %h(zOldName)</a> |
| 428 | @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ |
| 429 | @ %h(zName)</a>. |
| 430 | }else{ |
| 431 | @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ |
| 432 | @ %h(zName)</a> became |
| 433 | if( mperm==PERM_EXE ){ |
| 434 | @ executable with contents |
| 435 | }else if( mperm==PERM_LNK ){ |
| 436 | @ a symlink with target |
| @@ -436,14 +438,14 @@ | |
| 438 | @ a regular file with contents |
| 439 | } |
| 440 | @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 441 | } |
| 442 | }else if( zOld ){ |
| 443 | @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zOld,zCkin2))\ |
| 444 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>. |
| 445 | }else{ |
| 446 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\ |
| 447 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 448 | } |
| 449 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 450 | if( pCfg ){ |
| 451 | @ </span></div> |
| @@ -643,10 +645,12 @@ | |
| 645 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 646 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 647 | }else{ |
| 648 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 649 | } |
| 650 | @ <div class="section" id="changes_section">Changes</div> |
| 651 | DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ |
| 652 | @ <div class="sectionmenu info-changes-menu"> |
| 653 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 654 | if( diffType!=1 ){ |
| 655 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 656 | } |
| @@ -718,10 +722,16 @@ | |
| 722 | blob_reset(&old); |
| 723 | blob_reset(&new); |
| 724 | } |
| 725 | } |
| 726 | db_finalize(&q); |
| 727 | @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ |
| 728 | @ document.getElementById('changes_section').textContent = 'Changes ' + |
| 729 | @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + |
| 730 | @ '+%d(g.diffCnt[1]) ' + |
| 731 | @ '−%d(g.diffCnt[2]))' |
| 732 | @ </script> |
| 733 | append_diff_javascript(diffType); |
| 734 | } |
| 735 | |
| 736 | /* |
| 737 | ** Render a web-page diff of the changes in the working check-out to |
| @@ -741,10 +751,12 @@ | |
| 751 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 752 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 753 | }else{ |
| 754 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 755 | } |
| 756 | @ <div class="section" id="changes_section">Changes</div> |
| 757 | DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ |
| 758 | @ <div class="sectionmenu info-changes-menu"> |
| 759 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 760 | if( diffType!=1 ){ |
| 761 | @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ |
| 762 | @ Unified Diff</a> |
| @@ -803,10 +815,16 @@ | |
| 815 | } |
| 816 | fossil_free(zLhs); |
| 817 | fossil_free(zRhs); |
| 818 | } |
| 819 | db_finalize(&q); |
| 820 | @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ |
| 821 | @ document.getElementById('changes_section').textContent = 'Changes ' + |
| 822 | @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + |
| 823 | @ '+%d(g.diffCnt[1]) ' + |
| 824 | @ '−%d(g.diffCnt[2]))' |
| 825 | @ </script> |
| 826 | append_diff_javascript(diffType); |
| 827 | } |
| 828 | |
| 829 | /* |
| 830 | ** WEBPAGE: ckout |
| @@ -918,11 +936,11 @@ | |
| 936 | @ No such object: %h(zName) |
| 937 | style_finish_page(); |
| 938 | return; |
| 939 | } |
| 940 | zRe = P("regex"); |
| 941 | if( zRe ) fossil_re_compile(&pRe, zRe, 0); |
| 942 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 943 | zParent = db_text(0, |
| 944 | "SELECT uuid FROM plink, blob" |
| 945 | " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim", |
| 946 | rid |
| @@ -975,34 +993,23 @@ | |
| 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 995 | |
| 996 | /* The Download: line */ |
| 997 | if( g.perm.Zip ){ |
| 998 | @ <tr><th>Downloads:</th><td> |
| 999 | if( robot_would_be_restricted("download") ){ |
| 1000 | @ See separate %z(href("%R/rchvdwnld/%!S",zUuid))download page</a> |
| 1001 | }else{ |
| 1002 | char *zBase = archive_base_name(rid); |
| 1003 | @ %z(href("%R/tarball/%s.tar.gz",zBase))Tarball</a> |
| 1004 | @ | %z(href("%R/zip/%s.zip",zBase))ZIP archive</a> |
| 1005 | if( g.zLogin!=0 ){ |
| 1006 | @ | %z(href("%R/sqlar/%s.sqlar",zBase))\ |
| 1007 | @ SQL archive</a></td></tr> |
| 1008 | } |
| 1009 | fossil_free(zBase); |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | @ <tr><th>Timelines:</th><td> |
| 1014 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1015 | if( zParent ){ |
| @@ -1167,15 +1174,16 @@ | |
| 1174 | } |
| 1175 | render_backlink_graph(zUuid, |
| 1176 | "<div class=\"section accordion\">References</div>\n"); |
| 1177 | @ <div class="section accordion">Context</div><div class="accordion_panel"> |
| 1178 | render_checkin_context(rid, 0, 0, 0); |
| 1179 | @ </div><div class="section accordion" id="changes_section">Changes</div> |
| 1180 | @ <div class="accordion_panel"> |
| 1181 | @ <div class="sectionmenu info-changes-menu"> |
| 1182 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 1183 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 1184 | DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */ |
| 1185 | DCfg.pRe = pRe; |
| 1186 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 1187 | if( diffType!=1 ){ |
| 1188 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 1189 | @ Unified Diff</a> |
| @@ -1227,10 +1235,18 @@ | |
| 1235 | append_file_change_line(zUuid, zName, zOld, zNew, zOldName, |
| 1236 | pCfg,mperm); |
| 1237 | } |
| 1238 | db_finalize(&q3); |
| 1239 | @ </div> |
| 1240 | if( diffType!=0 ){ |
| 1241 | @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */ |
| 1242 | @ document.getElementById('changes_section').textContent = 'Changes ' + |
| 1243 | @ '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' + |
| 1244 | @ '+%d(g.diffCnt[1]) ' + |
| 1245 | @ '−%d(g.diffCnt[2]))' |
| 1246 | @ </script> |
| 1247 | } |
| 1248 | append_diff_javascript(diffType); |
| 1249 | style_finish_page(); |
| 1250 | } |
| 1251 | |
| 1252 | /* |
| @@ -1414,17 +1430,18 @@ | |
| 1430 | Blob qpGlob; /* glob= query parameter for generated links */ |
| 1431 | int bInvert = PB("inv"); |
| 1432 | |
| 1433 | login_check_credentials(); |
| 1434 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1435 | if( robot_restrict("diff") ) return; |
| 1436 | login_anonymous_available(); |
| 1437 | fossil_nice_default(); |
| 1438 | blob_init(&qp, 0, 0); |
| 1439 | blob_init(&qpGlob, 0, 0); |
| 1440 | diffType = preferred_diff_type(); |
| 1441 | zRe = P("regex"); |
| 1442 | if( zRe ) fossil_re_compile(&pRe, zRe, 0); |
| 1443 | zBranch = P("branch"); |
| 1444 | if( zBranch && zBranch[0]==0 ) zBranch = 0; |
| 1445 | if( zBranch ){ |
| 1446 | blob_appendf(&qp, "branch=%T", zBranch); |
| 1447 | zMergeOrigin = mprintf("merge-in:%s", zBranch); |
| @@ -1918,18 +1935,31 @@ | |
| 1935 | ** * The "preferred-diff-type" setting |
| 1936 | ** * 1 for mobile and 2 for desktop, based on the UserAgent |
| 1937 | */ |
| 1938 | int preferred_diff_type(void){ |
| 1939 | int dflt; |
| 1940 | int res; |
| 1941 | int isBot; |
| 1942 | static char zDflt[2] |
| 1943 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1944 | if( robot_would_be_restricted("diff") ){ |
| 1945 | dflt = 0; |
| 1946 | isBot = 1; |
| 1947 | }else{ |
| 1948 | dflt = db_get_int("preferred-diff-type",-99); |
| 1949 | if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; |
| 1950 | isBot = 0; |
| 1951 | } |
| 1952 | zDflt[0] = dflt + '0'; |
| 1953 | zDflt[1] = 0; |
| 1954 | cookie_link_parameter("diff","diff", zDflt); |
| 1955 | res = atoi(PD_NoBot("diff",zDflt)); |
| 1956 | if( isBot && res>0 && robot_restrict("diff") ){ |
| 1957 | cgi_reply(); |
| 1958 | fossil_exit(0); |
| 1959 | } |
| 1960 | return res; |
| 1961 | } |
| 1962 | |
| 1963 | |
| 1964 | /* |
| 1965 | ** WEBPAGE: fdiff |
| @@ -1967,10 +1997,11 @@ | |
| 1997 | int verbose = PB("verbose"); |
| 1998 | DiffConfig DCfg; |
| 1999 | |
| 2000 | login_check_credentials(); |
| 2001 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2002 | if( robot_restrict("diff") ) return; |
| 2003 | diff_config_init(&DCfg, 0); |
| 2004 | diffType = preferred_diff_type(); |
| 2005 | if( P("from") && P("to") ){ |
| 2006 | v1 = artifact_from_ci_and_filename("from"); |
| 2007 | v2 = artifact_from_ci_and_filename("to"); |
| @@ -2009,11 +2040,11 @@ | |
| 2040 | } |
| 2041 | db_finalize(&q); |
| 2042 | } |
| 2043 | zRe = P("regex"); |
| 2044 | cgi_check_for_malice(); |
| 2045 | if( zRe ) fossil_re_compile(&pRe, zRe, 0); |
| 2046 | if( verbose ) objdescFlags |= OBJDESC_DETAIL; |
| 2047 | if( isPatch ){ |
| 2048 | Blob c1, c2, *pOut; |
| 2049 | DiffConfig DCfg; |
| 2050 | pOut = cgi_output_blob(); |
| @@ -2407,15 +2438,15 @@ | |
| 2438 | object_description(rid, objdescFlags, 0, &downloadName); |
| 2439 | style_submenu_element("Download", "%R/raw/%s?at=%T", |
| 2440 | zUuid, file_tail(blob_str(&downloadName))); |
| 2441 | @ <hr> |
| 2442 | content_get(rid, &content); |
| 2443 | if( blob_size(&content)>100000 ){ |
| 2444 | /* Prevent robots from running hexdump on megabyte-sized source files |
| 2445 | ** and there by eating up lots of CPU time and bandwidth. There is |
| 2446 | ** no good reason for a robot to need a hexdump. */ |
| 2447 | @ <p>A hex dump of this file is not available because it is too large. |
| 2448 | @ Please download the raw binary file and generate a hex dump yourself.</p> |
| 2449 | }else{ |
| 2450 | @ <blockquote><pre> |
| 2451 | hexdump(&content); |
| 2452 | @ </pre></blockquote> |
| @@ -2915,11 +2946,12 @@ | |
| 2946 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2947 | } |
| 2948 | db_finalize(&q); |
| 2949 | } |
| 2950 | if( !docOnly ){ |
| 2951 | style_submenu_element("Download", "%R/raw/%s?at=%T", |
| 2952 | zUuid, file_tail(blob_str(&downloadName))); |
| 2953 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2954 | style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); |
| 2955 | } |
| 2956 | } |
| 2957 | if( zMime ){ |
| 2958 |
+1
-1
| --- src/json.c | ||
| +++ src/json.c | ||
| @@ -78,11 +78,11 @@ | ||
| 78 | 78 | ** whether or not we're really running in json mode we have to try |
| 79 | 79 | ** a bit harder. Problem reported here: |
| 80 | 80 | ** https://fossil-scm.org/forum/forumpost/e4953666d6 |
| 81 | 81 | */ |
| 82 | 82 | ReCompiled * pReg = 0; |
| 83 | - const char * zErr = re_compile(&pReg, "^/[^/]+/json(/.*)?", 0); | |
| 83 | + const char * zErr = fossil_re_compile(&pReg, "^/[^/]+/json(/.*)?", 0); | |
| 84 | 84 | assert(zErr==0 && "Regex compilation failed?"); |
| 85 | 85 | if(zErr==0 && |
| 86 | 86 | re_match(pReg, (const unsigned char *)zPathInfo, -1)){ |
| 87 | 87 | rc = 2; |
| 88 | 88 | } |
| 89 | 89 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -78,11 +78,11 @@ | |
| 78 | ** whether or not we're really running in json mode we have to try |
| 79 | ** a bit harder. Problem reported here: |
| 80 | ** https://fossil-scm.org/forum/forumpost/e4953666d6 |
| 81 | */ |
| 82 | ReCompiled * pReg = 0; |
| 83 | const char * zErr = re_compile(&pReg, "^/[^/]+/json(/.*)?", 0); |
| 84 | assert(zErr==0 && "Regex compilation failed?"); |
| 85 | if(zErr==0 && |
| 86 | re_match(pReg, (const unsigned char *)zPathInfo, -1)){ |
| 87 | rc = 2; |
| 88 | } |
| 89 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -78,11 +78,11 @@ | |
| 78 | ** whether or not we're really running in json mode we have to try |
| 79 | ** a bit harder. Problem reported here: |
| 80 | ** https://fossil-scm.org/forum/forumpost/e4953666d6 |
| 81 | */ |
| 82 | ReCompiled * pReg = 0; |
| 83 | const char * zErr = fossil_re_compile(&pReg, "^/[^/]+/json(/.*)?", 0); |
| 84 | assert(zErr==0 && "Regex compilation failed?"); |
| 85 | if(zErr==0 && |
| 86 | re_match(pReg, (const unsigned char *)zPathInfo, -1)){ |
| 87 | rc = 2; |
| 88 | } |
| 89 |
+131
-142
| --- src/login.c | ||
| +++ src/login.c | ||
| @@ -160,10 +160,11 @@ | ||
| 160 | 160 | |
| 161 | 161 | if( zUsername==0 ) return 0; |
| 162 | 162 | else if( zPassword==0 ) return 0; |
| 163 | 163 | else if( zCS==0 ) return 0; |
| 164 | 164 | else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; |
| 165 | + else if( anon_cookie_lifespan()==0 ) return 0; | |
| 165 | 166 | while( 1/*exit-by-break*/ ){ |
| 166 | 167 | zPw = captcha_decode((unsigned int)atoi(zCS), n); |
| 167 | 168 | if( zPw==0 ) return 0; |
| 168 | 169 | if( fossil_stricmp(zPw, zPassword)==0 ) break; |
| 169 | 170 | n++; |
| @@ -338,33 +339,62 @@ | ||
| 338 | 339 | *zDest = zCookie; |
| 339 | 340 | }else{ |
| 340 | 341 | free(zCookie); |
| 341 | 342 | } |
| 342 | 343 | } |
| 344 | + | |
| 345 | +/* | |
| 346 | +** SETTING: anon-cookie-lifespan width=10 default=480 | |
| 347 | +** The number of minutes for which an anonymous login cookie is | |
| 348 | +** valid. Anonymous logins are prohibited if this value is zero. | |
| 349 | +*/ | |
| 350 | + | |
| 351 | + | |
| 352 | +/* | |
| 353 | +** The default lifetime of an anoymous cookie, in minutes. | |
| 354 | +*/ | |
| 355 | +#define ANONYMOUS_COOKIE_LIFESPAN (8*60) | |
| 356 | + | |
| 357 | +/* | |
| 358 | +** Return the lifetime of an anonymous cookie, in minutes. | |
| 359 | +*/ | |
| 360 | +int anon_cookie_lifespan(void){ | |
| 361 | + static int lifespan = -1; | |
| 362 | + if( lifespan<0 ){ | |
| 363 | + lifespan = db_get_int("anon-cookie-lifespan", ANONYMOUS_COOKIE_LIFESPAN); | |
| 364 | + if( lifespan<0 ) lifespan = 0; | |
| 365 | + } | |
| 366 | + return lifespan; | |
| 367 | +} | |
| 343 | 368 | |
| 344 | 369 | /* Sets a cookie for an anonymous user login, which looks like this: |
| 345 | 370 | ** |
| 346 | 371 | ** HASH/TIME/anonymous |
| 347 | 372 | ** |
| 348 | -** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret. | |
| 373 | +** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET | |
| 374 | +** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value. | |
| 349 | 375 | ** |
| 350 | 376 | ** If zCookieDest is not NULL then the generated cookie is assigned to |
| 351 | 377 | ** *zCookieDest and the caller must eventually free() it. |
| 352 | 378 | ** |
| 353 | 379 | ** If bSessionCookie is true, the cookie will be a session cookie. |
| 380 | +** | |
| 381 | +** Search for tag-20250817a to find the code that recognizes this cookie. | |
| 354 | 382 | */ |
| 355 | 383 | void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){ |
| 356 | 384 | char *zNow; /* Current time (julian day number) */ |
| 357 | 385 | char *zCookie; /* The login cookie */ |
| 386 | + const char *zUserAgent; /* The user agent */ | |
| 358 | 387 | const char *zCookieName; /* Name of the login cookie */ |
| 359 | 388 | Blob b; /* Blob used during cookie construction */ |
| 360 | - int expires = bSessionCookie ? 0 : 6*3600; | |
| 389 | + int expires = bSessionCookie ? 0 : anon_cookie_lifespan(); | |
| 361 | 390 | zCookieName = login_cookie_name(); |
| 362 | 391 | zNow = db_text("0", "SELECT julianday('now')"); |
| 363 | 392 | assert( zCookieName && zNow ); |
| 364 | 393 | blob_init(&b, zNow, -1); |
| 365 | - blob_appendf(&b, "/%z", captcha_secret(0)); | |
| 394 | + zUserAgent = PD("HTTP_USER_AGENT","nil"); | |
| 395 | + blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0)); | |
| 366 | 396 | sha1sum_blob(&b, &b); |
| 367 | 397 | zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); |
| 368 | 398 | blob_reset(&b); |
| 369 | 399 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); |
| 370 | 400 | if( zCookieDest ){ |
| @@ -581,17 +611,35 @@ | ||
| 581 | 611 | /* If the "Reset Password" button in the form was pressed, render |
| 582 | 612 | ** the Request Password Reset page in place of this one. */ |
| 583 | 613 | login_reqpwreset_page(); |
| 584 | 614 | return; |
| 585 | 615 | } |
| 586 | - login_check_credentials(); | |
| 616 | + | |
| 617 | + /* If the "anon" query parameter is 1 or 2, that means rework the web-page | |
| 618 | + ** to make it a more user-friendly captcha. Extraneous text and boxes | |
| 619 | + ** are omitted. The user has just the captcha image and an entry box | |
| 620 | + ** and a "Verify" button. Underneath is the same login page for user | |
| 621 | + ** "anonymous", just displayed in an easier to digest format for one-time | |
| 622 | + ** visitors. | |
| 623 | + ** | |
| 624 | + ** anon=1 is advisory and only has effect if there is not some other login | |
| 625 | + ** cookie. anon=2 means always show the captcha. | |
| 626 | + */ | |
| 627 | + anonFlag = anon_cookie_lifespan()>0 ? atoi(PD("anon","0")) : 0; | |
| 628 | + if( anonFlag==2 ){ | |
| 629 | + g.zLogin = 0; | |
| 630 | + }else{ | |
| 631 | + login_check_credentials(); | |
| 632 | + if( g.zLogin!=0 ) anonFlag = 0; | |
| 633 | + } | |
| 634 | + | |
| 587 | 635 | fossil_redirect_to_https_if_needed(1); |
| 588 | 636 | sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
| 589 | 637 | constant_time_cmp_function, 0, 0); |
| 590 | 638 | zUsername = P("u"); |
| 591 | 639 | zPasswd = P("p"); |
| 592 | - anonFlag = g.zLogin==0 && PB("anon"); | |
| 640 | + | |
| 593 | 641 | /* Handle log-out requests */ |
| 594 | 642 | if( P("out") && cgi_csrf_safe(2) ){ |
| 595 | 643 | login_clear_login_data(); |
| 596 | 644 | login_redirect_to_g(); |
| 597 | 645 | return; |
| @@ -717,10 +765,11 @@ | ||
| 717 | 765 | login_redirect_to_g(); |
| 718 | 766 | } |
| 719 | 767 | } |
| 720 | 768 | style_set_current_feature("login"); |
| 721 | 769 | style_header("Login/Logout"); |
| 770 | + if( anonFlag==2 ) g.zLogin = 0; | |
| 722 | 771 | style_adunit_config(ADUNIT_OFF); |
| 723 | 772 | @ %s(zErrMsg) |
| 724 | 773 | if( zGoto && !noAnon ){ |
| 725 | 774 | char *zAbbrev = fossil_strdup(zGoto); |
| 726 | 775 | int i; |
| @@ -728,12 +777,12 @@ | ||
| 728 | 777 | zAbbrev[i] = 0; |
| 729 | 778 | if( g.zLogin ){ |
| 730 | 779 | @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b> |
| 731 | 780 | @ to access <b>%h(zAbbrev)</b>. |
| 732 | 781 | }else if( anonFlag ){ |
| 733 | - @ <p>Login as <b>anonymous</b> or any named user | |
| 734 | - @ to access page <b>%h(zAbbrev)</b>. | |
| 782 | + @ <p><b>Verify that you are human by typing in the 8-character text | |
| 783 | + @ password shown below.</b></p> | |
| 735 | 784 | }else{ |
| 736 | 785 | @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>. |
| 737 | 786 | } |
| 738 | 787 | fossil_free(zAbbrev); |
| 739 | 788 | } |
| @@ -748,26 +797,27 @@ | ||
| 748 | 797 | if( zGoto ){ |
| 749 | 798 | @ <input type="hidden" name="g" value="%h(zGoto)"> |
| 750 | 799 | } |
| 751 | 800 | if( anonFlag ){ |
| 752 | 801 | @ <input type="hidden" name="anon" value="1"> |
| 802 | + @ <input type="hidden" name="u" value="anonymous"> | |
| 753 | 803 | } |
| 754 | 804 | if( g.zLogin ){ |
| 755 | 805 | @ <p>Currently logged in as <b>%h(g.zLogin)</b>. |
| 756 | 806 | @ <input type="submit" name="out" value="Logout" autofocus></p> |
| 757 | 807 | @ </form> |
| 758 | 808 | }else{ |
| 759 | 809 | unsigned int uSeed = captcha_seed(); |
| 760 | - if( g.zLogin==0 && (anonFlag || zGoto==0) ){ | |
| 810 | + if( g.zLogin==0 && (anonFlag || zGoto==0) && anon_cookie_lifespan()>0 ){ | |
| 761 | 811 | zAnonPw = db_text(0, "SELECT pw FROM user" |
| 762 | 812 | " WHERE login='anonymous'" |
| 763 | 813 | " AND cap!=''"); |
| 764 | 814 | }else{ |
| 765 | 815 | zAnonPw = 0; |
| 766 | 816 | } |
| 767 | 817 | @ <table class="login_out"> |
| 768 | - if( P("HTTPS")==0 ){ | |
| 818 | + if( P("HTTPS")==0 && !anonFlag ){ | |
| 769 | 819 | @ <tr><td class="form_label">Warning:</td> |
| 770 | 820 | @ <td><span class='securityWarning'> |
| 771 | 821 | @ Login information, including the password, |
| 772 | 822 | @ will be sent in the clear over an unencrypted connection. |
| 773 | 823 | if( !g.sslNotAvailable ){ |
| @@ -774,41 +824,55 @@ | ||
| 774 | 824 | @ Consider logging in at |
| 775 | 825 | @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead. |
| 776 | 826 | } |
| 777 | 827 | @ </span></td></tr> |
| 778 | 828 | } |
| 779 | - @ <tr> | |
| 780 | - @ <td class="form_label" id="userlabel1">User ID:</td> | |
| 781 | - @ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \ | |
| 782 | - @ size="30" value="%s(anonFlag?"anonymous":"")" autofocus></td> | |
| 783 | - @ </tr> | |
| 829 | + if( !anonFlag ){ | |
| 830 | + @ <tr> | |
| 831 | + @ <td class="form_label" id="userlabel1">User ID:</td> | |
| 832 | + @ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \ | |
| 833 | + @ size="30" value="" autofocus></td> | |
| 834 | + @ </tr> | |
| 835 | + } | |
| 784 | 836 | @ <tr> |
| 785 | 837 | @ <td class="form_label" id="pswdlabel">Password:</td> |
| 786 | 838 | @ <td><input aria-labelledby="pswdlabel" type="password" id="p" \ |
| 787 | - @ name="p" value="" size="30">\ | |
| 788 | - if( zAnonPw && !noAnon ){ | |
| 839 | + @ name="p" value="" size="30"%s(anonFlag ? " autofocus" : "")> | |
| 840 | + if( anonFlag ){ | |
| 841 | + @ </td></tr> | |
| 842 | + @ <tr> | |
| 843 | + @ <td></td><td>\ | |
| 844 | + captcha_speakit_button(uSeed, "Read the password out loud"); | |
| 845 | + }else if( zAnonPw && !noAnon ){ | |
| 789 | 846 | captcha_speakit_button(uSeed, "Speak password for \"anonymous\""); |
| 790 | 847 | } |
| 791 | 848 | @ </td> |
| 792 | 849 | @ </tr> |
| 793 | - @ <tr> | |
| 794 | - @ <td></td> | |
| 795 | - @ <td><input type="checkbox" name="remember" value="1" \ | |
| 796 | - @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")> | |
| 797 | - @ <label for="remember-me">Remember me?</label></td> | |
| 798 | - @ </tr> | |
| 799 | - @ <tr> | |
| 800 | - @ <td></td> | |
| 801 | - @ <td><input type="submit" name="in" value="Login"> | |
| 802 | - @ </tr> | |
| 803 | - if( !noAnon && login_self_register_available(0) ){ | |
| 850 | + if( !anonFlag ){ | |
| 851 | + @ <tr> | |
| 852 | + @ <td></td> | |
| 853 | + @ <td><input type="checkbox" name="remember" value="1" \ | |
| 854 | + @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")> | |
| 855 | + @ <label for="remember-me">Remember me?</label></td> | |
| 856 | + @ </tr> | |
| 857 | + @ <tr> | |
| 858 | + @ <td></td> | |
| 859 | + @ <td><input type="submit" name="in" value="Login"> | |
| 860 | + @ </tr> | |
| 861 | + }else{ | |
| 862 | + @ <tr> | |
| 863 | + @ <td></td> | |
| 864 | + @ <td><input type="submit" name="in" value="Verify that I am human"> | |
| 865 | + @ </tr> | |
| 866 | + } | |
| 867 | + if( !anonFlag && !noAnon && login_self_register_available(0) ){ | |
| 804 | 868 | @ <tr> |
| 805 | 869 | @ <td></td> |
| 806 | 870 | @ <td><input type="submit" name="self" value="Create A New Account"> |
| 807 | 871 | @ </tr> |
| 808 | 872 | } |
| 809 | - if( login_self_password_reset_available() ){ | |
| 873 | + if( !anonFlag && login_self_password_reset_available() ){ | |
| 810 | 874 | @ <tr> |
| 811 | 875 | @ <td></td> |
| 812 | 876 | @ <td><input type="submit" name="pwreset" value="Reset My Password"> |
| 813 | 877 | @ </tr> |
| 814 | 878 | } |
| @@ -817,27 +881,29 @@ | ||
| 817 | 881 | const char *zDecoded = captcha_decode(uSeed, 0); |
| 818 | 882 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| 819 | 883 | char *zCaptcha = captcha_render(zDecoded); |
| 820 | 884 | |
| 821 | 885 | @ <p><input type="hidden" name="cs" value="%u(uSeed)"> |
| 822 | - @ Visitors may enter <b>anonymous</b> as the user-ID with | |
| 823 | - @ the 8-character hexadecimal password shown below:</p> | |
| 886 | + if( !anonFlag ){ | |
| 887 | + @ Visitors may enter <b>anonymous</b> as the user-ID with | |
| 888 | + @ the 8-character hexadecimal password shown below:</p> | |
| 889 | + } | |
| 824 | 890 | @ <div class="captcha"><table class="captcha"><tr><td>\ |
| 825 | 891 | @ <pre class="captcha"> |
| 826 | 892 | @ %h(zCaptcha) |
| 827 | 893 | @ </pre></td></tr></table> |
| 828 | - if( bAutoCaptcha ) { | |
| 894 | + if( bAutoCaptcha && !anonFlag ) { | |
| 829 | 895 | @ <input type="button" value="Fill out captcha" id='autofillButton' \ |
| 830 | 896 | @ data-af='%s(zDecoded)'> |
| 831 | 897 | builtin_request_js("login.js"); |
| 832 | 898 | } |
| 833 | 899 | @ </div> |
| 834 | 900 | free(zCaptcha); |
| 835 | 901 | } |
| 836 | 902 | @ </form> |
| 837 | 903 | } |
| 838 | - if( login_is_individual() ){ | |
| 904 | + if( login_is_individual() && !anonFlag ){ | |
| 839 | 905 | if( g.perm.EmailAlert && alert_enabled() ){ |
| 840 | 906 | @ <hr> |
| 841 | 907 | @ <p>Configure <a href="%R/alerts">Email Alerts</a> |
| 842 | 908 | @ for user <b>%h(g.zLogin)</b></p> |
| 843 | 909 | } |
| @@ -845,15 +911,18 @@ | ||
| 845 | 911 | @ <hr><p> |
| 846 | 912 | @ <a href="%R/timeline?ss=v&y=f&vfx&u=%t(g.zLogin)">Forum |
| 847 | 913 | @ post timeline</a> for user <b>%h(g.zLogin)</b></p> |
| 848 | 914 | } |
| 849 | 915 | } |
| 850 | - @ <hr><p> | |
| 851 | - @ Select your preferred <a href="%R/skins">site skin</a>. | |
| 852 | - @ </p> | |
| 853 | - @ <hr><p> | |
| 854 | - @ Manage your <a href="%R/cookies">cookies</a>.</p> | |
| 916 | + if( !anonFlag ){ | |
| 917 | + @ <hr><p> | |
| 918 | + @ Select your preferred <a href="%R/skins">site skin</a>. | |
| 919 | + @ </p> | |
| 920 | + @ <hr><p> | |
| 921 | + @ Manage your <a href="%R/cookies">cookies</a> or your | |
| 922 | + @ <a href="%R/tokens">access tokens</a>.</p> | |
| 923 | + } | |
| 855 | 924 | if( login_is_individual() ){ |
| 856 | 925 | if( g.perm.Password ){ |
| 857 | 926 | char *zRPW = fossil_random_password(12); |
| 858 | 927 | @ <hr> |
| 859 | 928 | @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> |
| @@ -1262,98 +1331,10 @@ | ||
| 1262 | 1331 | } |
| 1263 | 1332 | fossil_free(zDecode); |
| 1264 | 1333 | return uid; |
| 1265 | 1334 | } |
| 1266 | 1335 | |
| 1267 | -/* | |
| 1268 | -** SETTING: robot-restrict width=40 block-text | |
| 1269 | -** The VALUE of this setting is a list of GLOB patterns that match | |
| 1270 | -** pages for which complex HTTP requests from robots should be disallowed. | |
| 1271 | -** The recommended value for this setting is: | |
| 1272 | -** | |
| 1273 | -** timeline,vdiff,fdiff,annotate,blame | |
| 1274 | -** | |
| 1275 | -*/ | |
| 1276 | - | |
| 1277 | -/* | |
| 1278 | -** Check to see if the current HTTP request is a complex request that | |
| 1279 | -** is coming from a robot and if access should restricted for such robots. | |
| 1280 | -** For the purposes of this module, a "complex request" is an HTTP | |
| 1281 | -** request with one or more query parameters other than "name". | |
| 1282 | -** | |
| 1283 | -** If this routine determines that robots should be restricted, then | |
| 1284 | -** this routine publishes a redirect to the honeypot and exits without | |
| 1285 | -** returning to the caller. | |
| 1286 | -** | |
| 1287 | -** This routine believes that this is a complex request is coming from | |
| 1288 | -** a robot if all of the following are true: | |
| 1289 | -** | |
| 1290 | -** * The user is "nobody". | |
| 1291 | -** * Either the REFERER field of the HTTP header is missing or empty, | |
| 1292 | -** or the USERAGENT field of the HTTP header suggests that | |
| 1293 | -** the request as coming from a robot. | |
| 1294 | -** * There are one or more query parameters other than "name". | |
| 1295 | -** | |
| 1296 | -** Robot restrictions are governed by settings. | |
| 1297 | -** | |
| 1298 | -** robot-restrict The value is a list of GLOB patterns for pages | |
| 1299 | -** that should restrict robot access. No restrictions | |
| 1300 | -** are applied if this setting is undefined or is | |
| 1301 | -** an empty string. | |
| 1302 | -*/ | |
| 1303 | -void login_restrict_robot_access(void){ | |
| 1304 | - const char *zGlob; | |
| 1305 | - int isMatch = 1; | |
| 1306 | - int nQP; /* Number of query parameters other than name= */ | |
| 1307 | - if( g.zLogin!=0 ) return; | |
| 1308 | - zGlob = db_get("robot-restrict",0); | |
| 1309 | - if( zGlob==0 || zGlob[0]==0 ) return; | |
| 1310 | - if( g.isHuman ){ | |
| 1311 | - const char *zReferer; | |
| 1312 | - const char *zAccept; | |
| 1313 | - const char *zBr; | |
| 1314 | - zReferer = P("HTTP_REFERER"); | |
| 1315 | - if( zReferer && zReferer[0]!=0 ) return; | |
| 1316 | - | |
| 1317 | - /* Robots typically do not accept the brotli encoding, at least not | |
| 1318 | - ** at the time of this writing (2025-04-01), but standard web-browser | |
| 1319 | - ** all generally do accept brotli. So if brotli is accepted, | |
| 1320 | - ** assume we are not talking to a robot. We might want to revisit this | |
| 1321 | - ** heuristic in the future... | |
| 1322 | - */ | |
| 1323 | - if( (zAccept = P("HTTP_ACCEPT_ENCODING"))!=0 | |
| 1324 | - && (zBr = strstr(zAccept,"br"))!=0 | |
| 1325 | - && !fossil_isalnum(zBr[2]) | |
| 1326 | - && (zBr==zAccept || !fossil_isalnum(zBr[-1])) | |
| 1327 | - ){ | |
| 1328 | - return; | |
| 1329 | - } | |
| 1330 | - } | |
| 1331 | - nQP = cgi_qp_count(); | |
| 1332 | - if( nQP<1 ) return; | |
| 1333 | - isMatch = glob_multi_match(zGlob, g.zPath); | |
| 1334 | - if( !isMatch ) return; | |
| 1335 | - | |
| 1336 | - /* Check for exceptions to the restriction on the number of query | |
| 1337 | - ** parameters. */ | |
| 1338 | - zGlob = db_get("robot-restrict-qp",0); | |
| 1339 | - if( zGlob && zGlob[0] ){ | |
| 1340 | - char *zPath = mprintf("%s/%d", g.zPath, nQP); | |
| 1341 | - isMatch = glob_multi_match(zGlob, zPath); | |
| 1342 | - fossil_free(zPath); | |
| 1343 | - if( isMatch ) return; | |
| 1344 | - } | |
| 1345 | - | |
| 1346 | - /* If we reach this point, it means we have a situation where we | |
| 1347 | - ** want to restrict the activity of a robot. | |
| 1348 | - */ | |
| 1349 | - g.isHuman = 0; | |
| 1350 | - (void)exclude_spiders(0); | |
| 1351 | - cgi_reply(); | |
| 1352 | - fossil_exit(0); | |
| 1353 | -} | |
| 1354 | - | |
| 1355 | 1336 | /* |
| 1356 | 1337 | ** When this routine is called, we know that the request does not |
| 1357 | 1338 | ** have a login on the present repository. This routine checks to |
| 1358 | 1339 | ** see if their login cookie might be for another member of the |
| 1359 | 1340 | ** login-group. |
| @@ -1388,11 +1369,11 @@ | ||
| 1388 | 1369 | ** |
| 1389 | 1370 | ** g.userUid Database USER.UID value. Might be -1 for "nobody" |
| 1390 | 1371 | ** g.zLogin Database USER.LOGIN value. NULL for user "nobody" |
| 1391 | 1372 | ** g.perm Permissions granted to this user |
| 1392 | 1373 | ** g.anon Permissions that would be available to anonymous |
| 1393 | -** g.isHuman True if the user is human, not a spider or robot | |
| 1374 | +** g.isRobot True if the client is known to be a spider or robot | |
| 1394 | 1375 | ** g.perm Populated based on user account's capabilities |
| 1395 | 1376 | ** |
| 1396 | 1377 | */ |
| 1397 | 1378 | void login_check_credentials(void){ |
| 1398 | 1379 | int uid = 0; /* User id */ |
| @@ -1429,11 +1410,11 @@ | ||
| 1429 | 1410 | uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); |
| 1430 | 1411 | } |
| 1431 | 1412 | g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); |
| 1432 | 1413 | zCap = "sxy"; |
| 1433 | 1414 | g.noPswd = 1; |
| 1434 | - g.isHuman = 1; | |
| 1415 | + g.isRobot = 0; | |
| 1435 | 1416 | zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)" |
| 1436 | 1417 | " FROM user WHERE uid=%d", uid); |
| 1437 | 1418 | login_create_csrf_secret(zSeed); |
| 1438 | 1419 | fossil_free(zSeed); |
| 1439 | 1420 | } |
| @@ -1457,33 +1438,38 @@ | ||
| 1457 | 1438 | } |
| 1458 | 1439 | } |
| 1459 | 1440 | } |
| 1460 | 1441 | if( zUser==0 ){ |
| 1461 | 1442 | /* Invalid cookie */ |
| 1462 | - }else if( fossil_strcmp(zUser, "anonymous")==0 ){ | |
| 1463 | - /* Cookies of the form "HASH/TIME/anonymous". The TIME must not be | |
| 1464 | - ** too old and the sha1 hash of TIME/SECRET must match HASH. | |
| 1465 | - ** SECRET is the "captcha-secret" value in the repository. | |
| 1443 | + }else if( fossil_strcmp(zUser, "anonymous")==0 | |
| 1444 | + && anon_cookie_lifespan()>0 ){ | |
| 1445 | + /* Cookies of the form "HASH/TIME/anonymous". The TIME must | |
| 1446 | + ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and | |
| 1447 | + ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT | |
| 1448 | + ** is the HTTP_USER_AGENT of the client and SECRET is the | |
| 1449 | + ** "captcha-secret" value in the repository. See tag-20250817a | |
| 1450 | + ** for the code the creates this cookie. | |
| 1466 | 1451 | */ |
| 1467 | 1452 | double rTime = atof(zArg); |
| 1453 | + const char *zUserAgent = PD("HTTP_USER_AGENT","nil"); | |
| 1468 | 1454 | Blob b; |
| 1469 | 1455 | char *zSecret; |
| 1470 | 1456 | int n = 0; |
| 1471 | 1457 | |
| 1472 | 1458 | do{ |
| 1473 | 1459 | blob_zero(&b); |
| 1474 | 1460 | zSecret = captcha_secret(n++); |
| 1475 | 1461 | if( zSecret==0 ) break; |
| 1476 | - blob_appendf(&b, "%s/%s", zArg, zSecret); | |
| 1462 | + blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret); | |
| 1477 | 1463 | sha1sum_blob(&b, &b); |
| 1478 | 1464 | if( fossil_strcmp(zHash, blob_str(&b))==0 ){ |
| 1479 | 1465 | uid = db_int(0, |
| 1480 | 1466 | "SELECT uid FROM user WHERE login='anonymous'" |
| 1481 | 1467 | " AND octet_length(cap)>0" |
| 1482 | 1468 | " AND octet_length(pw)>0" |
| 1483 | - " AND %.17g+0.25>julianday('now')", | |
| 1484 | - rTime | |
| 1469 | + " AND %.17g>julianday('now')", | |
| 1470 | + rTime+anon_cookie_lifespan()/1440.0 | |
| 1485 | 1471 | ); |
| 1486 | 1472 | } |
| 1487 | 1473 | }while( uid==0 ); |
| 1488 | 1474 | blob_reset(&b); |
| 1489 | 1475 | }else{ |
| @@ -1559,12 +1545,15 @@ | ||
| 1559 | 1545 | login_create_csrf_secret("none"); |
| 1560 | 1546 | } |
| 1561 | 1547 | |
| 1562 | 1548 | login_set_uid(uid, zCap); |
| 1563 | 1549 | |
| 1564 | - /* Maybe restrict access to robots */ | |
| 1565 | - login_restrict_robot_access(); | |
| 1550 | + /* Maybe restrict access by robots */ | |
| 1551 | + if( g.zLogin==0 && robot_restrict(g.zPath) ){ | |
| 1552 | + cgi_reply(); | |
| 1553 | + fossil_exit(0); | |
| 1554 | + } | |
| 1566 | 1555 | } |
| 1567 | 1556 | |
| 1568 | 1557 | /* |
| 1569 | 1558 | ** Set the current logged in user to be uid. zCap is precomputed |
| 1570 | 1559 | ** (override) capabilities. If zCap==0, then look up the capabilities |
| @@ -1599,15 +1588,15 @@ | ||
| 1599 | 1588 | g.userUid = uid; |
| 1600 | 1589 | if( fossil_strcmp(g.zLogin,"nobody")==0 ){ |
| 1601 | 1590 | g.zLogin = 0; |
| 1602 | 1591 | } |
| 1603 | 1592 | if( PB("isrobot") ){ |
| 1604 | - g.isHuman = 0; | |
| 1593 | + g.isRobot = 1; | |
| 1605 | 1594 | }else if( g.zLogin==0 ){ |
| 1606 | - g.isHuman = isHuman(P("HTTP_USER_AGENT")); | |
| 1595 | + g.isRobot = !isHuman(P("HTTP_USER_AGENT")); | |
| 1607 | 1596 | }else{ |
| 1608 | - g.isHuman = 1; | |
| 1597 | + g.isRobot = 0; | |
| 1609 | 1598 | } |
| 1610 | 1599 | |
| 1611 | 1600 | /* Set the capabilities */ |
| 1612 | 1601 | login_replace_capabilities(zCap, 0); |
| 1613 | 1602 | |
| @@ -1617,11 +1606,11 @@ | ||
| 1617 | 1606 | ** enabled for this repository and make appropriate adjustments to the |
| 1618 | 1607 | ** permission flags if it is. This should be done before the permissions |
| 1619 | 1608 | ** are (potentially) copied to the anonymous permission set; otherwise, |
| 1620 | 1609 | ** those will be out-of-sync. |
| 1621 | 1610 | */ |
| 1622 | - if( zCap[0] && !g.perm.Hyperlink && g.isHuman ){ | |
| 1611 | + if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){ | |
| 1623 | 1612 | int autoLink = db_get_int("auto-hyperlink",1); |
| 1624 | 1613 | if( autoLink==1 ){ |
| 1625 | 1614 | g.jsHref = 1; |
| 1626 | 1615 | g.perm.Hyperlink = 1; |
| 1627 | 1616 | }else if( autoLink==2 ){ |
| @@ -1927,11 +1916,11 @@ | ||
| 1927 | 1916 | blob_appendf(&redir, "%R/login?g=%T", zPathInfo); |
| 1928 | 1917 | } |
| 1929 | 1918 | if( zQS && zQS[0] ){ |
| 1930 | 1919 | blob_appendf(&redir, "%%3f%T", zQS); |
| 1931 | 1920 | } |
| 1932 | - if( anonOk ) blob_append(&redir, "&anon", 5); | |
| 1921 | + if( anonOk ) blob_append(&redir, "&anon=1", 7); | |
| 1933 | 1922 | cgi_redirect(blob_str(&redir)); |
| 1934 | 1923 | /* NOTREACHED */ |
| 1935 | 1924 | assert(0); |
| 1936 | 1925 | } |
| 1937 | 1926 | } |
| @@ -1941,11 +1930,11 @@ | ||
| 1941 | 1930 | ** the anonymous user has Hyperlink permission, then paint a mesage |
| 1942 | 1931 | ** to inform the user that much more information is available by |
| 1943 | 1932 | ** logging in as anonymous. |
| 1944 | 1933 | */ |
| 1945 | 1934 | void login_anonymous_available(void){ |
| 1946 | - if( !g.perm.Hyperlink && g.anon.Hyperlink ){ | |
| 1935 | + if( !g.perm.Hyperlink && g.anon.Hyperlink && anon_cookie_lifespan()>0 ){ | |
| 1947 | 1936 | const char *zUrl = PD("PATH_INFO", ""); |
| 1948 | 1937 | @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br> |
| 1949 | 1938 | @ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> |
| 1950 | 1939 | @ to enable hyperlinks.</p> |
| 1951 | 1940 | } |
| 1952 | 1941 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -160,10 +160,11 @@ | |
| 160 | |
| 161 | if( zUsername==0 ) return 0; |
| 162 | else if( zPassword==0 ) return 0; |
| 163 | else if( zCS==0 ) return 0; |
| 164 | else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; |
| 165 | while( 1/*exit-by-break*/ ){ |
| 166 | zPw = captcha_decode((unsigned int)atoi(zCS), n); |
| 167 | if( zPw==0 ) return 0; |
| 168 | if( fossil_stricmp(zPw, zPassword)==0 ) break; |
| 169 | n++; |
| @@ -338,33 +339,62 @@ | |
| 338 | *zDest = zCookie; |
| 339 | }else{ |
| 340 | free(zCookie); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | /* Sets a cookie for an anonymous user login, which looks like this: |
| 345 | ** |
| 346 | ** HASH/TIME/anonymous |
| 347 | ** |
| 348 | ** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret. |
| 349 | ** |
| 350 | ** If zCookieDest is not NULL then the generated cookie is assigned to |
| 351 | ** *zCookieDest and the caller must eventually free() it. |
| 352 | ** |
| 353 | ** If bSessionCookie is true, the cookie will be a session cookie. |
| 354 | */ |
| 355 | void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){ |
| 356 | char *zNow; /* Current time (julian day number) */ |
| 357 | char *zCookie; /* The login cookie */ |
| 358 | const char *zCookieName; /* Name of the login cookie */ |
| 359 | Blob b; /* Blob used during cookie construction */ |
| 360 | int expires = bSessionCookie ? 0 : 6*3600; |
| 361 | zCookieName = login_cookie_name(); |
| 362 | zNow = db_text("0", "SELECT julianday('now')"); |
| 363 | assert( zCookieName && zNow ); |
| 364 | blob_init(&b, zNow, -1); |
| 365 | blob_appendf(&b, "/%z", captcha_secret(0)); |
| 366 | sha1sum_blob(&b, &b); |
| 367 | zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); |
| 368 | blob_reset(&b); |
| 369 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); |
| 370 | if( zCookieDest ){ |
| @@ -581,17 +611,35 @@ | |
| 581 | /* If the "Reset Password" button in the form was pressed, render |
| 582 | ** the Request Password Reset page in place of this one. */ |
| 583 | login_reqpwreset_page(); |
| 584 | return; |
| 585 | } |
| 586 | login_check_credentials(); |
| 587 | fossil_redirect_to_https_if_needed(1); |
| 588 | sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
| 589 | constant_time_cmp_function, 0, 0); |
| 590 | zUsername = P("u"); |
| 591 | zPasswd = P("p"); |
| 592 | anonFlag = g.zLogin==0 && PB("anon"); |
| 593 | /* Handle log-out requests */ |
| 594 | if( P("out") && cgi_csrf_safe(2) ){ |
| 595 | login_clear_login_data(); |
| 596 | login_redirect_to_g(); |
| 597 | return; |
| @@ -717,10 +765,11 @@ | |
| 717 | login_redirect_to_g(); |
| 718 | } |
| 719 | } |
| 720 | style_set_current_feature("login"); |
| 721 | style_header("Login/Logout"); |
| 722 | style_adunit_config(ADUNIT_OFF); |
| 723 | @ %s(zErrMsg) |
| 724 | if( zGoto && !noAnon ){ |
| 725 | char *zAbbrev = fossil_strdup(zGoto); |
| 726 | int i; |
| @@ -728,12 +777,12 @@ | |
| 728 | zAbbrev[i] = 0; |
| 729 | if( g.zLogin ){ |
| 730 | @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b> |
| 731 | @ to access <b>%h(zAbbrev)</b>. |
| 732 | }else if( anonFlag ){ |
| 733 | @ <p>Login as <b>anonymous</b> or any named user |
| 734 | @ to access page <b>%h(zAbbrev)</b>. |
| 735 | }else{ |
| 736 | @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>. |
| 737 | } |
| 738 | fossil_free(zAbbrev); |
| 739 | } |
| @@ -748,26 +797,27 @@ | |
| 748 | if( zGoto ){ |
| 749 | @ <input type="hidden" name="g" value="%h(zGoto)"> |
| 750 | } |
| 751 | if( anonFlag ){ |
| 752 | @ <input type="hidden" name="anon" value="1"> |
| 753 | } |
| 754 | if( g.zLogin ){ |
| 755 | @ <p>Currently logged in as <b>%h(g.zLogin)</b>. |
| 756 | @ <input type="submit" name="out" value="Logout" autofocus></p> |
| 757 | @ </form> |
| 758 | }else{ |
| 759 | unsigned int uSeed = captcha_seed(); |
| 760 | if( g.zLogin==0 && (anonFlag || zGoto==0) ){ |
| 761 | zAnonPw = db_text(0, "SELECT pw FROM user" |
| 762 | " WHERE login='anonymous'" |
| 763 | " AND cap!=''"); |
| 764 | }else{ |
| 765 | zAnonPw = 0; |
| 766 | } |
| 767 | @ <table class="login_out"> |
| 768 | if( P("HTTPS")==0 ){ |
| 769 | @ <tr><td class="form_label">Warning:</td> |
| 770 | @ <td><span class='securityWarning'> |
| 771 | @ Login information, including the password, |
| 772 | @ will be sent in the clear over an unencrypted connection. |
| 773 | if( !g.sslNotAvailable ){ |
| @@ -774,41 +824,55 @@ | |
| 774 | @ Consider logging in at |
| 775 | @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead. |
| 776 | } |
| 777 | @ </span></td></tr> |
| 778 | } |
| 779 | @ <tr> |
| 780 | @ <td class="form_label" id="userlabel1">User ID:</td> |
| 781 | @ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \ |
| 782 | @ size="30" value="%s(anonFlag?"anonymous":"")" autofocus></td> |
| 783 | @ </tr> |
| 784 | @ <tr> |
| 785 | @ <td class="form_label" id="pswdlabel">Password:</td> |
| 786 | @ <td><input aria-labelledby="pswdlabel" type="password" id="p" \ |
| 787 | @ name="p" value="" size="30">\ |
| 788 | if( zAnonPw && !noAnon ){ |
| 789 | captcha_speakit_button(uSeed, "Speak password for \"anonymous\""); |
| 790 | } |
| 791 | @ </td> |
| 792 | @ </tr> |
| 793 | @ <tr> |
| 794 | @ <td></td> |
| 795 | @ <td><input type="checkbox" name="remember" value="1" \ |
| 796 | @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")> |
| 797 | @ <label for="remember-me">Remember me?</label></td> |
| 798 | @ </tr> |
| 799 | @ <tr> |
| 800 | @ <td></td> |
| 801 | @ <td><input type="submit" name="in" value="Login"> |
| 802 | @ </tr> |
| 803 | if( !noAnon && login_self_register_available(0) ){ |
| 804 | @ <tr> |
| 805 | @ <td></td> |
| 806 | @ <td><input type="submit" name="self" value="Create A New Account"> |
| 807 | @ </tr> |
| 808 | } |
| 809 | if( login_self_password_reset_available() ){ |
| 810 | @ <tr> |
| 811 | @ <td></td> |
| 812 | @ <td><input type="submit" name="pwreset" value="Reset My Password"> |
| 813 | @ </tr> |
| 814 | } |
| @@ -817,27 +881,29 @@ | |
| 817 | const char *zDecoded = captcha_decode(uSeed, 0); |
| 818 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| 819 | char *zCaptcha = captcha_render(zDecoded); |
| 820 | |
| 821 | @ <p><input type="hidden" name="cs" value="%u(uSeed)"> |
| 822 | @ Visitors may enter <b>anonymous</b> as the user-ID with |
| 823 | @ the 8-character hexadecimal password shown below:</p> |
| 824 | @ <div class="captcha"><table class="captcha"><tr><td>\ |
| 825 | @ <pre class="captcha"> |
| 826 | @ %h(zCaptcha) |
| 827 | @ </pre></td></tr></table> |
| 828 | if( bAutoCaptcha ) { |
| 829 | @ <input type="button" value="Fill out captcha" id='autofillButton' \ |
| 830 | @ data-af='%s(zDecoded)'> |
| 831 | builtin_request_js("login.js"); |
| 832 | } |
| 833 | @ </div> |
| 834 | free(zCaptcha); |
| 835 | } |
| 836 | @ </form> |
| 837 | } |
| 838 | if( login_is_individual() ){ |
| 839 | if( g.perm.EmailAlert && alert_enabled() ){ |
| 840 | @ <hr> |
| 841 | @ <p>Configure <a href="%R/alerts">Email Alerts</a> |
| 842 | @ for user <b>%h(g.zLogin)</b></p> |
| 843 | } |
| @@ -845,15 +911,18 @@ | |
| 845 | @ <hr><p> |
| 846 | @ <a href="%R/timeline?ss=v&y=f&vfx&u=%t(g.zLogin)">Forum |
| 847 | @ post timeline</a> for user <b>%h(g.zLogin)</b></p> |
| 848 | } |
| 849 | } |
| 850 | @ <hr><p> |
| 851 | @ Select your preferred <a href="%R/skins">site skin</a>. |
| 852 | @ </p> |
| 853 | @ <hr><p> |
| 854 | @ Manage your <a href="%R/cookies">cookies</a>.</p> |
| 855 | if( login_is_individual() ){ |
| 856 | if( g.perm.Password ){ |
| 857 | char *zRPW = fossil_random_password(12); |
| 858 | @ <hr> |
| 859 | @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> |
| @@ -1262,98 +1331,10 @@ | |
| 1262 | } |
| 1263 | fossil_free(zDecode); |
| 1264 | return uid; |
| 1265 | } |
| 1266 | |
| 1267 | /* |
| 1268 | ** SETTING: robot-restrict width=40 block-text |
| 1269 | ** The VALUE of this setting is a list of GLOB patterns that match |
| 1270 | ** pages for which complex HTTP requests from robots should be disallowed. |
| 1271 | ** The recommended value for this setting is: |
| 1272 | ** |
| 1273 | ** timeline,vdiff,fdiff,annotate,blame |
| 1274 | ** |
| 1275 | */ |
| 1276 | |
| 1277 | /* |
| 1278 | ** Check to see if the current HTTP request is a complex request that |
| 1279 | ** is coming from a robot and if access should restricted for such robots. |
| 1280 | ** For the purposes of this module, a "complex request" is an HTTP |
| 1281 | ** request with one or more query parameters other than "name". |
| 1282 | ** |
| 1283 | ** If this routine determines that robots should be restricted, then |
| 1284 | ** this routine publishes a redirect to the honeypot and exits without |
| 1285 | ** returning to the caller. |
| 1286 | ** |
| 1287 | ** This routine believes that this is a complex request is coming from |
| 1288 | ** a robot if all of the following are true: |
| 1289 | ** |
| 1290 | ** * The user is "nobody". |
| 1291 | ** * Either the REFERER field of the HTTP header is missing or empty, |
| 1292 | ** or the USERAGENT field of the HTTP header suggests that |
| 1293 | ** the request as coming from a robot. |
| 1294 | ** * There are one or more query parameters other than "name". |
| 1295 | ** |
| 1296 | ** Robot restrictions are governed by settings. |
| 1297 | ** |
| 1298 | ** robot-restrict The value is a list of GLOB patterns for pages |
| 1299 | ** that should restrict robot access. No restrictions |
| 1300 | ** are applied if this setting is undefined or is |
| 1301 | ** an empty string. |
| 1302 | */ |
| 1303 | void login_restrict_robot_access(void){ |
| 1304 | const char *zGlob; |
| 1305 | int isMatch = 1; |
| 1306 | int nQP; /* Number of query parameters other than name= */ |
| 1307 | if( g.zLogin!=0 ) return; |
| 1308 | zGlob = db_get("robot-restrict",0); |
| 1309 | if( zGlob==0 || zGlob[0]==0 ) return; |
| 1310 | if( g.isHuman ){ |
| 1311 | const char *zReferer; |
| 1312 | const char *zAccept; |
| 1313 | const char *zBr; |
| 1314 | zReferer = P("HTTP_REFERER"); |
| 1315 | if( zReferer && zReferer[0]!=0 ) return; |
| 1316 | |
| 1317 | /* Robots typically do not accept the brotli encoding, at least not |
| 1318 | ** at the time of this writing (2025-04-01), but standard web-browser |
| 1319 | ** all generally do accept brotli. So if brotli is accepted, |
| 1320 | ** assume we are not talking to a robot. We might want to revisit this |
| 1321 | ** heuristic in the future... |
| 1322 | */ |
| 1323 | if( (zAccept = P("HTTP_ACCEPT_ENCODING"))!=0 |
| 1324 | && (zBr = strstr(zAccept,"br"))!=0 |
| 1325 | && !fossil_isalnum(zBr[2]) |
| 1326 | && (zBr==zAccept || !fossil_isalnum(zBr[-1])) |
| 1327 | ){ |
| 1328 | return; |
| 1329 | } |
| 1330 | } |
| 1331 | nQP = cgi_qp_count(); |
| 1332 | if( nQP<1 ) return; |
| 1333 | isMatch = glob_multi_match(zGlob, g.zPath); |
| 1334 | if( !isMatch ) return; |
| 1335 | |
| 1336 | /* Check for exceptions to the restriction on the number of query |
| 1337 | ** parameters. */ |
| 1338 | zGlob = db_get("robot-restrict-qp",0); |
| 1339 | if( zGlob && zGlob[0] ){ |
| 1340 | char *zPath = mprintf("%s/%d", g.zPath, nQP); |
| 1341 | isMatch = glob_multi_match(zGlob, zPath); |
| 1342 | fossil_free(zPath); |
| 1343 | if( isMatch ) return; |
| 1344 | } |
| 1345 | |
| 1346 | /* If we reach this point, it means we have a situation where we |
| 1347 | ** want to restrict the activity of a robot. |
| 1348 | */ |
| 1349 | g.isHuman = 0; |
| 1350 | (void)exclude_spiders(0); |
| 1351 | cgi_reply(); |
| 1352 | fossil_exit(0); |
| 1353 | } |
| 1354 | |
| 1355 | /* |
| 1356 | ** When this routine is called, we know that the request does not |
| 1357 | ** have a login on the present repository. This routine checks to |
| 1358 | ** see if their login cookie might be for another member of the |
| 1359 | ** login-group. |
| @@ -1388,11 +1369,11 @@ | |
| 1388 | ** |
| 1389 | ** g.userUid Database USER.UID value. Might be -1 for "nobody" |
| 1390 | ** g.zLogin Database USER.LOGIN value. NULL for user "nobody" |
| 1391 | ** g.perm Permissions granted to this user |
| 1392 | ** g.anon Permissions that would be available to anonymous |
| 1393 | ** g.isHuman True if the user is human, not a spider or robot |
| 1394 | ** g.perm Populated based on user account's capabilities |
| 1395 | ** |
| 1396 | */ |
| 1397 | void login_check_credentials(void){ |
| 1398 | int uid = 0; /* User id */ |
| @@ -1429,11 +1410,11 @@ | |
| 1429 | uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); |
| 1430 | } |
| 1431 | g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); |
| 1432 | zCap = "sxy"; |
| 1433 | g.noPswd = 1; |
| 1434 | g.isHuman = 1; |
| 1435 | zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)" |
| 1436 | " FROM user WHERE uid=%d", uid); |
| 1437 | login_create_csrf_secret(zSeed); |
| 1438 | fossil_free(zSeed); |
| 1439 | } |
| @@ -1457,33 +1438,38 @@ | |
| 1457 | } |
| 1458 | } |
| 1459 | } |
| 1460 | if( zUser==0 ){ |
| 1461 | /* Invalid cookie */ |
| 1462 | }else if( fossil_strcmp(zUser, "anonymous")==0 ){ |
| 1463 | /* Cookies of the form "HASH/TIME/anonymous". The TIME must not be |
| 1464 | ** too old and the sha1 hash of TIME/SECRET must match HASH. |
| 1465 | ** SECRET is the "captcha-secret" value in the repository. |
| 1466 | */ |
| 1467 | double rTime = atof(zArg); |
| 1468 | Blob b; |
| 1469 | char *zSecret; |
| 1470 | int n = 0; |
| 1471 | |
| 1472 | do{ |
| 1473 | blob_zero(&b); |
| 1474 | zSecret = captcha_secret(n++); |
| 1475 | if( zSecret==0 ) break; |
| 1476 | blob_appendf(&b, "%s/%s", zArg, zSecret); |
| 1477 | sha1sum_blob(&b, &b); |
| 1478 | if( fossil_strcmp(zHash, blob_str(&b))==0 ){ |
| 1479 | uid = db_int(0, |
| 1480 | "SELECT uid FROM user WHERE login='anonymous'" |
| 1481 | " AND octet_length(cap)>0" |
| 1482 | " AND octet_length(pw)>0" |
| 1483 | " AND %.17g+0.25>julianday('now')", |
| 1484 | rTime |
| 1485 | ); |
| 1486 | } |
| 1487 | }while( uid==0 ); |
| 1488 | blob_reset(&b); |
| 1489 | }else{ |
| @@ -1559,12 +1545,15 @@ | |
| 1559 | login_create_csrf_secret("none"); |
| 1560 | } |
| 1561 | |
| 1562 | login_set_uid(uid, zCap); |
| 1563 | |
| 1564 | /* Maybe restrict access to robots */ |
| 1565 | login_restrict_robot_access(); |
| 1566 | } |
| 1567 | |
| 1568 | /* |
| 1569 | ** Set the current logged in user to be uid. zCap is precomputed |
| 1570 | ** (override) capabilities. If zCap==0, then look up the capabilities |
| @@ -1599,15 +1588,15 @@ | |
| 1599 | g.userUid = uid; |
| 1600 | if( fossil_strcmp(g.zLogin,"nobody")==0 ){ |
| 1601 | g.zLogin = 0; |
| 1602 | } |
| 1603 | if( PB("isrobot") ){ |
| 1604 | g.isHuman = 0; |
| 1605 | }else if( g.zLogin==0 ){ |
| 1606 | g.isHuman = isHuman(P("HTTP_USER_AGENT")); |
| 1607 | }else{ |
| 1608 | g.isHuman = 1; |
| 1609 | } |
| 1610 | |
| 1611 | /* Set the capabilities */ |
| 1612 | login_replace_capabilities(zCap, 0); |
| 1613 | |
| @@ -1617,11 +1606,11 @@ | |
| 1617 | ** enabled for this repository and make appropriate adjustments to the |
| 1618 | ** permission flags if it is. This should be done before the permissions |
| 1619 | ** are (potentially) copied to the anonymous permission set; otherwise, |
| 1620 | ** those will be out-of-sync. |
| 1621 | */ |
| 1622 | if( zCap[0] && !g.perm.Hyperlink && g.isHuman ){ |
| 1623 | int autoLink = db_get_int("auto-hyperlink",1); |
| 1624 | if( autoLink==1 ){ |
| 1625 | g.jsHref = 1; |
| 1626 | g.perm.Hyperlink = 1; |
| 1627 | }else if( autoLink==2 ){ |
| @@ -1927,11 +1916,11 @@ | |
| 1927 | blob_appendf(&redir, "%R/login?g=%T", zPathInfo); |
| 1928 | } |
| 1929 | if( zQS && zQS[0] ){ |
| 1930 | blob_appendf(&redir, "%%3f%T", zQS); |
| 1931 | } |
| 1932 | if( anonOk ) blob_append(&redir, "&anon", 5); |
| 1933 | cgi_redirect(blob_str(&redir)); |
| 1934 | /* NOTREACHED */ |
| 1935 | assert(0); |
| 1936 | } |
| 1937 | } |
| @@ -1941,11 +1930,11 @@ | |
| 1941 | ** the anonymous user has Hyperlink permission, then paint a mesage |
| 1942 | ** to inform the user that much more information is available by |
| 1943 | ** logging in as anonymous. |
| 1944 | */ |
| 1945 | void login_anonymous_available(void){ |
| 1946 | if( !g.perm.Hyperlink && g.anon.Hyperlink ){ |
| 1947 | const char *zUrl = PD("PATH_INFO", ""); |
| 1948 | @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br> |
| 1949 | @ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> |
| 1950 | @ to enable hyperlinks.</p> |
| 1951 | } |
| 1952 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -160,10 +160,11 @@ | |
| 160 | |
| 161 | if( zUsername==0 ) return 0; |
| 162 | else if( zPassword==0 ) return 0; |
| 163 | else if( zCS==0 ) return 0; |
| 164 | else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; |
| 165 | else if( anon_cookie_lifespan()==0 ) return 0; |
| 166 | while( 1/*exit-by-break*/ ){ |
| 167 | zPw = captcha_decode((unsigned int)atoi(zCS), n); |
| 168 | if( zPw==0 ) return 0; |
| 169 | if( fossil_stricmp(zPw, zPassword)==0 ) break; |
| 170 | n++; |
| @@ -338,33 +339,62 @@ | |
| 339 | *zDest = zCookie; |
| 340 | }else{ |
| 341 | free(zCookie); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | /* |
| 346 | ** SETTING: anon-cookie-lifespan width=10 default=480 |
| 347 | ** The number of minutes for which an anonymous login cookie is |
| 348 | ** valid. Anonymous logins are prohibited if this value is zero. |
| 349 | */ |
| 350 | |
| 351 | |
| 352 | /* |
| 353 | ** The default lifetime of an anoymous cookie, in minutes. |
| 354 | */ |
| 355 | #define ANONYMOUS_COOKIE_LIFESPAN (8*60) |
| 356 | |
| 357 | /* |
| 358 | ** Return the lifetime of an anonymous cookie, in minutes. |
| 359 | */ |
| 360 | int anon_cookie_lifespan(void){ |
| 361 | static int lifespan = -1; |
| 362 | if( lifespan<0 ){ |
| 363 | lifespan = db_get_int("anon-cookie-lifespan", ANONYMOUS_COOKIE_LIFESPAN); |
| 364 | if( lifespan<0 ) lifespan = 0; |
| 365 | } |
| 366 | return lifespan; |
| 367 | } |
| 368 | |
| 369 | /* Sets a cookie for an anonymous user login, which looks like this: |
| 370 | ** |
| 371 | ** HASH/TIME/anonymous |
| 372 | ** |
| 373 | ** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET |
| 374 | ** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value. |
| 375 | ** |
| 376 | ** If zCookieDest is not NULL then the generated cookie is assigned to |
| 377 | ** *zCookieDest and the caller must eventually free() it. |
| 378 | ** |
| 379 | ** If bSessionCookie is true, the cookie will be a session cookie. |
| 380 | ** |
| 381 | ** Search for tag-20250817a to find the code that recognizes this cookie. |
| 382 | */ |
| 383 | void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){ |
| 384 | char *zNow; /* Current time (julian day number) */ |
| 385 | char *zCookie; /* The login cookie */ |
| 386 | const char *zUserAgent; /* The user agent */ |
| 387 | const char *zCookieName; /* Name of the login cookie */ |
| 388 | Blob b; /* Blob used during cookie construction */ |
| 389 | int expires = bSessionCookie ? 0 : anon_cookie_lifespan(); |
| 390 | zCookieName = login_cookie_name(); |
| 391 | zNow = db_text("0", "SELECT julianday('now')"); |
| 392 | assert( zCookieName && zNow ); |
| 393 | blob_init(&b, zNow, -1); |
| 394 | zUserAgent = PD("HTTP_USER_AGENT","nil"); |
| 395 | blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0)); |
| 396 | sha1sum_blob(&b, &b); |
| 397 | zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); |
| 398 | blob_reset(&b); |
| 399 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); |
| 400 | if( zCookieDest ){ |
| @@ -581,17 +611,35 @@ | |
| 611 | /* If the "Reset Password" button in the form was pressed, render |
| 612 | ** the Request Password Reset page in place of this one. */ |
| 613 | login_reqpwreset_page(); |
| 614 | return; |
| 615 | } |
| 616 | |
| 617 | /* If the "anon" query parameter is 1 or 2, that means rework the web-page |
| 618 | ** to make it a more user-friendly captcha. Extraneous text and boxes |
| 619 | ** are omitted. The user has just the captcha image and an entry box |
| 620 | ** and a "Verify" button. Underneath is the same login page for user |
| 621 | ** "anonymous", just displayed in an easier to digest format for one-time |
| 622 | ** visitors. |
| 623 | ** |
| 624 | ** anon=1 is advisory and only has effect if there is not some other login |
| 625 | ** cookie. anon=2 means always show the captcha. |
| 626 | */ |
| 627 | anonFlag = anon_cookie_lifespan()>0 ? atoi(PD("anon","0")) : 0; |
| 628 | if( anonFlag==2 ){ |
| 629 | g.zLogin = 0; |
| 630 | }else{ |
| 631 | login_check_credentials(); |
| 632 | if( g.zLogin!=0 ) anonFlag = 0; |
| 633 | } |
| 634 | |
| 635 | fossil_redirect_to_https_if_needed(1); |
| 636 | sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
| 637 | constant_time_cmp_function, 0, 0); |
| 638 | zUsername = P("u"); |
| 639 | zPasswd = P("p"); |
| 640 | |
| 641 | /* Handle log-out requests */ |
| 642 | if( P("out") && cgi_csrf_safe(2) ){ |
| 643 | login_clear_login_data(); |
| 644 | login_redirect_to_g(); |
| 645 | return; |
| @@ -717,10 +765,11 @@ | |
| 765 | login_redirect_to_g(); |
| 766 | } |
| 767 | } |
| 768 | style_set_current_feature("login"); |
| 769 | style_header("Login/Logout"); |
| 770 | if( anonFlag==2 ) g.zLogin = 0; |
| 771 | style_adunit_config(ADUNIT_OFF); |
| 772 | @ %s(zErrMsg) |
| 773 | if( zGoto && !noAnon ){ |
| 774 | char *zAbbrev = fossil_strdup(zGoto); |
| 775 | int i; |
| @@ -728,12 +777,12 @@ | |
| 777 | zAbbrev[i] = 0; |
| 778 | if( g.zLogin ){ |
| 779 | @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b> |
| 780 | @ to access <b>%h(zAbbrev)</b>. |
| 781 | }else if( anonFlag ){ |
| 782 | @ <p><b>Verify that you are human by typing in the 8-character text |
| 783 | @ password shown below.</b></p> |
| 784 | }else{ |
| 785 | @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>. |
| 786 | } |
| 787 | fossil_free(zAbbrev); |
| 788 | } |
| @@ -748,26 +797,27 @@ | |
| 797 | if( zGoto ){ |
| 798 | @ <input type="hidden" name="g" value="%h(zGoto)"> |
| 799 | } |
| 800 | if( anonFlag ){ |
| 801 | @ <input type="hidden" name="anon" value="1"> |
| 802 | @ <input type="hidden" name="u" value="anonymous"> |
| 803 | } |
| 804 | if( g.zLogin ){ |
| 805 | @ <p>Currently logged in as <b>%h(g.zLogin)</b>. |
| 806 | @ <input type="submit" name="out" value="Logout" autofocus></p> |
| 807 | @ </form> |
| 808 | }else{ |
| 809 | unsigned int uSeed = captcha_seed(); |
| 810 | if( g.zLogin==0 && (anonFlag || zGoto==0) && anon_cookie_lifespan()>0 ){ |
| 811 | zAnonPw = db_text(0, "SELECT pw FROM user" |
| 812 | " WHERE login='anonymous'" |
| 813 | " AND cap!=''"); |
| 814 | }else{ |
| 815 | zAnonPw = 0; |
| 816 | } |
| 817 | @ <table class="login_out"> |
| 818 | if( P("HTTPS")==0 && !anonFlag ){ |
| 819 | @ <tr><td class="form_label">Warning:</td> |
| 820 | @ <td><span class='securityWarning'> |
| 821 | @ Login information, including the password, |
| 822 | @ will be sent in the clear over an unencrypted connection. |
| 823 | if( !g.sslNotAvailable ){ |
| @@ -774,41 +824,55 @@ | |
| 824 | @ Consider logging in at |
| 825 | @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead. |
| 826 | } |
| 827 | @ </span></td></tr> |
| 828 | } |
| 829 | if( !anonFlag ){ |
| 830 | @ <tr> |
| 831 | @ <td class="form_label" id="userlabel1">User ID:</td> |
| 832 | @ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \ |
| 833 | @ size="30" value="" autofocus></td> |
| 834 | @ </tr> |
| 835 | } |
| 836 | @ <tr> |
| 837 | @ <td class="form_label" id="pswdlabel">Password:</td> |
| 838 | @ <td><input aria-labelledby="pswdlabel" type="password" id="p" \ |
| 839 | @ name="p" value="" size="30"%s(anonFlag ? " autofocus" : "")> |
| 840 | if( anonFlag ){ |
| 841 | @ </td></tr> |
| 842 | @ <tr> |
| 843 | @ <td></td><td>\ |
| 844 | captcha_speakit_button(uSeed, "Read the password out loud"); |
| 845 | }else if( zAnonPw && !noAnon ){ |
| 846 | captcha_speakit_button(uSeed, "Speak password for \"anonymous\""); |
| 847 | } |
| 848 | @ </td> |
| 849 | @ </tr> |
| 850 | if( !anonFlag ){ |
| 851 | @ <tr> |
| 852 | @ <td></td> |
| 853 | @ <td><input type="checkbox" name="remember" value="1" \ |
| 854 | @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")> |
| 855 | @ <label for="remember-me">Remember me?</label></td> |
| 856 | @ </tr> |
| 857 | @ <tr> |
| 858 | @ <td></td> |
| 859 | @ <td><input type="submit" name="in" value="Login"> |
| 860 | @ </tr> |
| 861 | }else{ |
| 862 | @ <tr> |
| 863 | @ <td></td> |
| 864 | @ <td><input type="submit" name="in" value="Verify that I am human"> |
| 865 | @ </tr> |
| 866 | } |
| 867 | if( !anonFlag && !noAnon && login_self_register_available(0) ){ |
| 868 | @ <tr> |
| 869 | @ <td></td> |
| 870 | @ <td><input type="submit" name="self" value="Create A New Account"> |
| 871 | @ </tr> |
| 872 | } |
| 873 | if( !anonFlag && login_self_password_reset_available() ){ |
| 874 | @ <tr> |
| 875 | @ <td></td> |
| 876 | @ <td><input type="submit" name="pwreset" value="Reset My Password"> |
| 877 | @ </tr> |
| 878 | } |
| @@ -817,27 +881,29 @@ | |
| 881 | const char *zDecoded = captcha_decode(uSeed, 0); |
| 882 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| 883 | char *zCaptcha = captcha_render(zDecoded); |
| 884 | |
| 885 | @ <p><input type="hidden" name="cs" value="%u(uSeed)"> |
| 886 | if( !anonFlag ){ |
| 887 | @ Visitors may enter <b>anonymous</b> as the user-ID with |
| 888 | @ the 8-character hexadecimal password shown below:</p> |
| 889 | } |
| 890 | @ <div class="captcha"><table class="captcha"><tr><td>\ |
| 891 | @ <pre class="captcha"> |
| 892 | @ %h(zCaptcha) |
| 893 | @ </pre></td></tr></table> |
| 894 | if( bAutoCaptcha && !anonFlag ) { |
| 895 | @ <input type="button" value="Fill out captcha" id='autofillButton' \ |
| 896 | @ data-af='%s(zDecoded)'> |
| 897 | builtin_request_js("login.js"); |
| 898 | } |
| 899 | @ </div> |
| 900 | free(zCaptcha); |
| 901 | } |
| 902 | @ </form> |
| 903 | } |
| 904 | if( login_is_individual() && !anonFlag ){ |
| 905 | if( g.perm.EmailAlert && alert_enabled() ){ |
| 906 | @ <hr> |
| 907 | @ <p>Configure <a href="%R/alerts">Email Alerts</a> |
| 908 | @ for user <b>%h(g.zLogin)</b></p> |
| 909 | } |
| @@ -845,15 +911,18 @@ | |
| 911 | @ <hr><p> |
| 912 | @ <a href="%R/timeline?ss=v&y=f&vfx&u=%t(g.zLogin)">Forum |
| 913 | @ post timeline</a> for user <b>%h(g.zLogin)</b></p> |
| 914 | } |
| 915 | } |
| 916 | if( !anonFlag ){ |
| 917 | @ <hr><p> |
| 918 | @ Select your preferred <a href="%R/skins">site skin</a>. |
| 919 | @ </p> |
| 920 | @ <hr><p> |
| 921 | @ Manage your <a href="%R/cookies">cookies</a> or your |
| 922 | @ <a href="%R/tokens">access tokens</a>.</p> |
| 923 | } |
| 924 | if( login_is_individual() ){ |
| 925 | if( g.perm.Password ){ |
| 926 | char *zRPW = fossil_random_password(12); |
| 927 | @ <hr> |
| 928 | @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> |
| @@ -1262,98 +1331,10 @@ | |
| 1331 | } |
| 1332 | fossil_free(zDecode); |
| 1333 | return uid; |
| 1334 | } |
| 1335 | |
| 1336 | /* |
| 1337 | ** When this routine is called, we know that the request does not |
| 1338 | ** have a login on the present repository. This routine checks to |
| 1339 | ** see if their login cookie might be for another member of the |
| 1340 | ** login-group. |
| @@ -1388,11 +1369,11 @@ | |
| 1369 | ** |
| 1370 | ** g.userUid Database USER.UID value. Might be -1 for "nobody" |
| 1371 | ** g.zLogin Database USER.LOGIN value. NULL for user "nobody" |
| 1372 | ** g.perm Permissions granted to this user |
| 1373 | ** g.anon Permissions that would be available to anonymous |
| 1374 | ** g.isRobot True if the client is known to be a spider or robot |
| 1375 | ** g.perm Populated based on user account's capabilities |
| 1376 | ** |
| 1377 | */ |
| 1378 | void login_check_credentials(void){ |
| 1379 | int uid = 0; /* User id */ |
| @@ -1429,11 +1410,11 @@ | |
| 1410 | uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); |
| 1411 | } |
| 1412 | g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); |
| 1413 | zCap = "sxy"; |
| 1414 | g.noPswd = 1; |
| 1415 | g.isRobot = 0; |
| 1416 | zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)" |
| 1417 | " FROM user WHERE uid=%d", uid); |
| 1418 | login_create_csrf_secret(zSeed); |
| 1419 | fossil_free(zSeed); |
| 1420 | } |
| @@ -1457,33 +1438,38 @@ | |
| 1438 | } |
| 1439 | } |
| 1440 | } |
| 1441 | if( zUser==0 ){ |
| 1442 | /* Invalid cookie */ |
| 1443 | }else if( fossil_strcmp(zUser, "anonymous")==0 |
| 1444 | && anon_cookie_lifespan()>0 ){ |
| 1445 | /* Cookies of the form "HASH/TIME/anonymous". The TIME must |
| 1446 | ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and |
| 1447 | ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT |
| 1448 | ** is the HTTP_USER_AGENT of the client and SECRET is the |
| 1449 | ** "captcha-secret" value in the repository. See tag-20250817a |
| 1450 | ** for the code the creates this cookie. |
| 1451 | */ |
| 1452 | double rTime = atof(zArg); |
| 1453 | const char *zUserAgent = PD("HTTP_USER_AGENT","nil"); |
| 1454 | Blob b; |
| 1455 | char *zSecret; |
| 1456 | int n = 0; |
| 1457 | |
| 1458 | do{ |
| 1459 | blob_zero(&b); |
| 1460 | zSecret = captcha_secret(n++); |
| 1461 | if( zSecret==0 ) break; |
| 1462 | blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret); |
| 1463 | sha1sum_blob(&b, &b); |
| 1464 | if( fossil_strcmp(zHash, blob_str(&b))==0 ){ |
| 1465 | uid = db_int(0, |
| 1466 | "SELECT uid FROM user WHERE login='anonymous'" |
| 1467 | " AND octet_length(cap)>0" |
| 1468 | " AND octet_length(pw)>0" |
| 1469 | " AND %.17g>julianday('now')", |
| 1470 | rTime+anon_cookie_lifespan()/1440.0 |
| 1471 | ); |
| 1472 | } |
| 1473 | }while( uid==0 ); |
| 1474 | blob_reset(&b); |
| 1475 | }else{ |
| @@ -1559,12 +1545,15 @@ | |
| 1545 | login_create_csrf_secret("none"); |
| 1546 | } |
| 1547 | |
| 1548 | login_set_uid(uid, zCap); |
| 1549 | |
| 1550 | /* Maybe restrict access by robots */ |
| 1551 | if( g.zLogin==0 && robot_restrict(g.zPath) ){ |
| 1552 | cgi_reply(); |
| 1553 | fossil_exit(0); |
| 1554 | } |
| 1555 | } |
| 1556 | |
| 1557 | /* |
| 1558 | ** Set the current logged in user to be uid. zCap is precomputed |
| 1559 | ** (override) capabilities. If zCap==0, then look up the capabilities |
| @@ -1599,15 +1588,15 @@ | |
| 1588 | g.userUid = uid; |
| 1589 | if( fossil_strcmp(g.zLogin,"nobody")==0 ){ |
| 1590 | g.zLogin = 0; |
| 1591 | } |
| 1592 | if( PB("isrobot") ){ |
| 1593 | g.isRobot = 1; |
| 1594 | }else if( g.zLogin==0 ){ |
| 1595 | g.isRobot = !isHuman(P("HTTP_USER_AGENT")); |
| 1596 | }else{ |
| 1597 | g.isRobot = 0; |
| 1598 | } |
| 1599 | |
| 1600 | /* Set the capabilities */ |
| 1601 | login_replace_capabilities(zCap, 0); |
| 1602 | |
| @@ -1617,11 +1606,11 @@ | |
| 1606 | ** enabled for this repository and make appropriate adjustments to the |
| 1607 | ** permission flags if it is. This should be done before the permissions |
| 1608 | ** are (potentially) copied to the anonymous permission set; otherwise, |
| 1609 | ** those will be out-of-sync. |
| 1610 | */ |
| 1611 | if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){ |
| 1612 | int autoLink = db_get_int("auto-hyperlink",1); |
| 1613 | if( autoLink==1 ){ |
| 1614 | g.jsHref = 1; |
| 1615 | g.perm.Hyperlink = 1; |
| 1616 | }else if( autoLink==2 ){ |
| @@ -1927,11 +1916,11 @@ | |
| 1916 | blob_appendf(&redir, "%R/login?g=%T", zPathInfo); |
| 1917 | } |
| 1918 | if( zQS && zQS[0] ){ |
| 1919 | blob_appendf(&redir, "%%3f%T", zQS); |
| 1920 | } |
| 1921 | if( anonOk ) blob_append(&redir, "&anon=1", 7); |
| 1922 | cgi_redirect(blob_str(&redir)); |
| 1923 | /* NOTREACHED */ |
| 1924 | assert(0); |
| 1925 | } |
| 1926 | } |
| @@ -1941,11 +1930,11 @@ | |
| 1930 | ** the anonymous user has Hyperlink permission, then paint a mesage |
| 1931 | ** to inform the user that much more information is available by |
| 1932 | ** logging in as anonymous. |
| 1933 | */ |
| 1934 | void login_anonymous_available(void){ |
| 1935 | if( !g.perm.Hyperlink && g.anon.Hyperlink && anon_cookie_lifespan()>0 ){ |
| 1936 | const char *zUrl = PD("PATH_INFO", ""); |
| 1937 | @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br> |
| 1938 | @ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> |
| 1939 | @ to enable hyperlinks.</p> |
| 1940 | } |
| 1941 |
+7
-2
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -233,11 +233,12 @@ | ||
| 233 | 233 | * applicable when using SEE on Windows or Linux. */ |
| 234 | 234 | #endif |
| 235 | 235 | int useLocalauth; /* No login required if from 127.0.0.1 */ |
| 236 | 236 | int noPswd; /* Logged in without password (on 127.0.0.1) */ |
| 237 | 237 | int userUid; /* Integer user id */ |
| 238 | - int isHuman; /* True if access by a human, not a spider or bot */ | |
| 238 | + int isRobot; /* True if the client is definitely a robot. False | |
| 239 | + ** negatives are common for this flag */ | |
| 239 | 240 | int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be |
| 240 | 241 | ** accessed through get_comment_format(). */ |
| 241 | 242 | const char *zSockName; /* Name of the unix-domain socket file */ |
| 242 | 243 | const char *zSockMode; /* File permissions for unix-domain socket */ |
| 243 | 244 | const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */ |
| @@ -2439,11 +2440,11 @@ | ||
| 2439 | 2440 | ** can be multiple "redirect:" lines that are |
| 2440 | 2441 | ** processed in order. If the REPO is "*", then |
| 2441 | 2442 | ** an unconditional redirect to URL is taken. |
| 2442 | 2443 | ** When "*" is used a 301 permanent redirect is |
| 2443 | 2444 | ** issued and the tail and query string from the |
| 2444 | -** original query are appeneded onto URL. | |
| 2445 | +** original query are appended onto URL. | |
| 2445 | 2446 | ** |
| 2446 | 2447 | ** jsmode: VALUE Specifies the delivery mode for JavaScript |
| 2447 | 2448 | ** files. See the help text for the --jsmode |
| 2448 | 2449 | ** flag of the http command. |
| 2449 | 2450 | ** |
| @@ -3100,10 +3101,11 @@ | ||
| 3100 | 3101 | ** breaking legacy. |
| 3101 | 3102 | ** |
| 3102 | 3103 | ** Options: |
| 3103 | 3104 | ** --csrf-safe N Set cgi_csrf_safe() to to return N |
| 3104 | 3105 | ** --nobody Pretend to be user "nobody" |
| 3106 | +** --ssh-sim Pretend to be over an SSH connection | |
| 3105 | 3107 | ** --test Do not do special "sync" processing when operating |
| 3106 | 3108 | ** over an SSH link |
| 3107 | 3109 | ** --th-trace Trace TH1 execution (for debugging purposes) |
| 3108 | 3110 | ** --usercap CAP User capability string (Default: "sxy") |
| 3109 | 3111 | */ |
| @@ -3111,10 +3113,13 @@ | ||
| 3111 | 3113 | const char *zIpAddr; /* IP address of remote client */ |
| 3112 | 3114 | const char *zUserCap; |
| 3113 | 3115 | int bTest = 0; |
| 3114 | 3116 | const char *zCsrfSafe = find_option("csrf-safe",0,1); |
| 3115 | 3117 | |
| 3118 | + if( find_option("ssh-sim",0,0)!=0 ){ | |
| 3119 | + putenv("SSH_CONNECTION=127.0.0.1 12345 127.0.0.2 23456"); | |
| 3120 | + } | |
| 3116 | 3121 | Th_InitTraceLog(); |
| 3117 | 3122 | if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe); |
| 3118 | 3123 | zUserCap = find_option("usercap",0,1); |
| 3119 | 3124 | if( !find_option("nobody",0,0) ){ |
| 3120 | 3125 | if( zUserCap==0 ){ |
| 3121 | 3126 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -233,11 +233,12 @@ | |
| 233 | * applicable when using SEE on Windows or Linux. */ |
| 234 | #endif |
| 235 | int useLocalauth; /* No login required if from 127.0.0.1 */ |
| 236 | int noPswd; /* Logged in without password (on 127.0.0.1) */ |
| 237 | int userUid; /* Integer user id */ |
| 238 | int isHuman; /* True if access by a human, not a spider or bot */ |
| 239 | int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be |
| 240 | ** accessed through get_comment_format(). */ |
| 241 | const char *zSockName; /* Name of the unix-domain socket file */ |
| 242 | const char *zSockMode; /* File permissions for unix-domain socket */ |
| 243 | const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */ |
| @@ -2439,11 +2440,11 @@ | |
| 2439 | ** can be multiple "redirect:" lines that are |
| 2440 | ** processed in order. If the REPO is "*", then |
| 2441 | ** an unconditional redirect to URL is taken. |
| 2442 | ** When "*" is used a 301 permanent redirect is |
| 2443 | ** issued and the tail and query string from the |
| 2444 | ** original query are appeneded onto URL. |
| 2445 | ** |
| 2446 | ** jsmode: VALUE Specifies the delivery mode for JavaScript |
| 2447 | ** files. See the help text for the --jsmode |
| 2448 | ** flag of the http command. |
| 2449 | ** |
| @@ -3100,10 +3101,11 @@ | |
| 3100 | ** breaking legacy. |
| 3101 | ** |
| 3102 | ** Options: |
| 3103 | ** --csrf-safe N Set cgi_csrf_safe() to to return N |
| 3104 | ** --nobody Pretend to be user "nobody" |
| 3105 | ** --test Do not do special "sync" processing when operating |
| 3106 | ** over an SSH link |
| 3107 | ** --th-trace Trace TH1 execution (for debugging purposes) |
| 3108 | ** --usercap CAP User capability string (Default: "sxy") |
| 3109 | */ |
| @@ -3111,10 +3113,13 @@ | |
| 3111 | const char *zIpAddr; /* IP address of remote client */ |
| 3112 | const char *zUserCap; |
| 3113 | int bTest = 0; |
| 3114 | const char *zCsrfSafe = find_option("csrf-safe",0,1); |
| 3115 | |
| 3116 | Th_InitTraceLog(); |
| 3117 | if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe); |
| 3118 | zUserCap = find_option("usercap",0,1); |
| 3119 | if( !find_option("nobody",0,0) ){ |
| 3120 | if( zUserCap==0 ){ |
| 3121 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -233,11 +233,12 @@ | |
| 233 | * applicable when using SEE on Windows or Linux. */ |
| 234 | #endif |
| 235 | int useLocalauth; /* No login required if from 127.0.0.1 */ |
| 236 | int noPswd; /* Logged in without password (on 127.0.0.1) */ |
| 237 | int userUid; /* Integer user id */ |
| 238 | int isRobot; /* True if the client is definitely a robot. False |
| 239 | ** negatives are common for this flag */ |
| 240 | int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be |
| 241 | ** accessed through get_comment_format(). */ |
| 242 | const char *zSockName; /* Name of the unix-domain socket file */ |
| 243 | const char *zSockMode; /* File permissions for unix-domain socket */ |
| 244 | const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */ |
| @@ -2439,11 +2440,11 @@ | |
| 2440 | ** can be multiple "redirect:" lines that are |
| 2441 | ** processed in order. If the REPO is "*", then |
| 2442 | ** an unconditional redirect to URL is taken. |
| 2443 | ** When "*" is used a 301 permanent redirect is |
| 2444 | ** issued and the tail and query string from the |
| 2445 | ** original query are appended onto URL. |
| 2446 | ** |
| 2447 | ** jsmode: VALUE Specifies the delivery mode for JavaScript |
| 2448 | ** files. See the help text for the --jsmode |
| 2449 | ** flag of the http command. |
| 2450 | ** |
| @@ -3100,10 +3101,11 @@ | |
| 3101 | ** breaking legacy. |
| 3102 | ** |
| 3103 | ** Options: |
| 3104 | ** --csrf-safe N Set cgi_csrf_safe() to to return N |
| 3105 | ** --nobody Pretend to be user "nobody" |
| 3106 | ** --ssh-sim Pretend to be over an SSH connection |
| 3107 | ** --test Do not do special "sync" processing when operating |
| 3108 | ** over an SSH link |
| 3109 | ** --th-trace Trace TH1 execution (for debugging purposes) |
| 3110 | ** --usercap CAP User capability string (Default: "sxy") |
| 3111 | */ |
| @@ -3111,10 +3113,13 @@ | |
| 3113 | const char *zIpAddr; /* IP address of remote client */ |
| 3114 | const char *zUserCap; |
| 3115 | int bTest = 0; |
| 3116 | const char *zCsrfSafe = find_option("csrf-safe",0,1); |
| 3117 | |
| 3118 | if( find_option("ssh-sim",0,0)!=0 ){ |
| 3119 | putenv("SSH_CONNECTION=127.0.0.1 12345 127.0.0.2 23456"); |
| 3120 | } |
| 3121 | Th_InitTraceLog(); |
| 3122 | if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe); |
| 3123 | zUserCap = find_option("usercap",0,1); |
| 3124 | if( !find_option("nobody",0,0) ){ |
| 3125 | if( zUserCap==0 ){ |
| 3126 |
+13
| --- src/main.mk | ||
| +++ src/main.mk | ||
| @@ -119,10 +119,11 @@ | ||
| 119 | 119 | $(SRCDIR)/purge.c \ |
| 120 | 120 | $(SRCDIR)/rebuild.c \ |
| 121 | 121 | $(SRCDIR)/regexp.c \ |
| 122 | 122 | $(SRCDIR)/repolist.c \ |
| 123 | 123 | $(SRCDIR)/report.c \ |
| 124 | + $(SRCDIR)/robot.c \ | |
| 124 | 125 | $(SRCDIR)/rss.c \ |
| 125 | 126 | $(SRCDIR)/schema.c \ |
| 126 | 127 | $(SRCDIR)/search.c \ |
| 127 | 128 | $(SRCDIR)/security_audit.c \ |
| 128 | 129 | $(SRCDIR)/setup.c \ |
| @@ -236,10 +237,11 @@ | ||
| 236 | 237 | $(SRCDIR)/fossil.page.chat.js \ |
| 237 | 238 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 238 | 239 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 239 | 240 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 240 | 241 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 242 | + $(SRCDIR)/fossil.page.ticket.js \ | |
| 241 | 243 | $(SRCDIR)/fossil.page.whistory.js \ |
| 242 | 244 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 243 | 245 | $(SRCDIR)/fossil.pikchr.js \ |
| 244 | 246 | $(SRCDIR)/fossil.popupwidget.js \ |
| 245 | 247 | $(SRCDIR)/fossil.storage.js \ |
| @@ -385,10 +387,11 @@ | ||
| 385 | 387 | $(OBJDIR)/purge_.c \ |
| 386 | 388 | $(OBJDIR)/rebuild_.c \ |
| 387 | 389 | $(OBJDIR)/regexp_.c \ |
| 388 | 390 | $(OBJDIR)/repolist_.c \ |
| 389 | 391 | $(OBJDIR)/report_.c \ |
| 392 | + $(OBJDIR)/robot_.c \ | |
| 390 | 393 | $(OBJDIR)/rss_.c \ |
| 391 | 394 | $(OBJDIR)/schema_.c \ |
| 392 | 395 | $(OBJDIR)/search_.c \ |
| 393 | 396 | $(OBJDIR)/security_audit_.c \ |
| 394 | 397 | $(OBJDIR)/setup_.c \ |
| @@ -535,10 +538,11 @@ | ||
| 535 | 538 | $(OBJDIR)/purge.o \ |
| 536 | 539 | $(OBJDIR)/rebuild.o \ |
| 537 | 540 | $(OBJDIR)/regexp.o \ |
| 538 | 541 | $(OBJDIR)/repolist.o \ |
| 539 | 542 | $(OBJDIR)/report.o \ |
| 543 | + $(OBJDIR)/robot.o \ | |
| 540 | 544 | $(OBJDIR)/rss.o \ |
| 541 | 545 | $(OBJDIR)/schema.o \ |
| 542 | 546 | $(OBJDIR)/search.o \ |
| 543 | 547 | $(OBJDIR)/security_audit.o \ |
| 544 | 548 | $(OBJDIR)/setup.o \ |
| @@ -878,10 +882,11 @@ | ||
| 878 | 882 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 879 | 883 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 880 | 884 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 881 | 885 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 882 | 886 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 887 | + $(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \ | |
| 883 | 888 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 884 | 889 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 885 | 890 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 886 | 891 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 887 | 892 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -1768,10 +1773,18 @@ | ||
| 1768 | 1773 | |
| 1769 | 1774 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 1770 | 1775 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 1771 | 1776 | |
| 1772 | 1777 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 1778 | + | |
| 1779 | +$(OBJDIR)/robot_.c: $(SRCDIR)/robot.c $(OBJDIR)/translate | |
| 1780 | + $(OBJDIR)/translate $(SRCDIR)/robot.c >$@ | |
| 1781 | + | |
| 1782 | +$(OBJDIR)/robot.o: $(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h | |
| 1783 | + $(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c | |
| 1784 | + | |
| 1785 | +$(OBJDIR)/robot.h: $(OBJDIR)/headers | |
| 1773 | 1786 | |
| 1774 | 1787 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate |
| 1775 | 1788 | $(OBJDIR)/translate $(SRCDIR)/rss.c >$@ |
| 1776 | 1789 | |
| 1777 | 1790 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 1778 | 1791 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -119,10 +119,11 @@ | |
| 119 | $(SRCDIR)/purge.c \ |
| 120 | $(SRCDIR)/rebuild.c \ |
| 121 | $(SRCDIR)/regexp.c \ |
| 122 | $(SRCDIR)/repolist.c \ |
| 123 | $(SRCDIR)/report.c \ |
| 124 | $(SRCDIR)/rss.c \ |
| 125 | $(SRCDIR)/schema.c \ |
| 126 | $(SRCDIR)/search.c \ |
| 127 | $(SRCDIR)/security_audit.c \ |
| 128 | $(SRCDIR)/setup.c \ |
| @@ -236,10 +237,11 @@ | |
| 236 | $(SRCDIR)/fossil.page.chat.js \ |
| 237 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 238 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 239 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 240 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 241 | $(SRCDIR)/fossil.page.whistory.js \ |
| 242 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 243 | $(SRCDIR)/fossil.pikchr.js \ |
| 244 | $(SRCDIR)/fossil.popupwidget.js \ |
| 245 | $(SRCDIR)/fossil.storage.js \ |
| @@ -385,10 +387,11 @@ | |
| 385 | $(OBJDIR)/purge_.c \ |
| 386 | $(OBJDIR)/rebuild_.c \ |
| 387 | $(OBJDIR)/regexp_.c \ |
| 388 | $(OBJDIR)/repolist_.c \ |
| 389 | $(OBJDIR)/report_.c \ |
| 390 | $(OBJDIR)/rss_.c \ |
| 391 | $(OBJDIR)/schema_.c \ |
| 392 | $(OBJDIR)/search_.c \ |
| 393 | $(OBJDIR)/security_audit_.c \ |
| 394 | $(OBJDIR)/setup_.c \ |
| @@ -535,10 +538,11 @@ | |
| 535 | $(OBJDIR)/purge.o \ |
| 536 | $(OBJDIR)/rebuild.o \ |
| 537 | $(OBJDIR)/regexp.o \ |
| 538 | $(OBJDIR)/repolist.o \ |
| 539 | $(OBJDIR)/report.o \ |
| 540 | $(OBJDIR)/rss.o \ |
| 541 | $(OBJDIR)/schema.o \ |
| 542 | $(OBJDIR)/search.o \ |
| 543 | $(OBJDIR)/security_audit.o \ |
| 544 | $(OBJDIR)/setup.o \ |
| @@ -878,10 +882,11 @@ | |
| 878 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 879 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 880 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 881 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 882 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 883 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 884 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 885 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 886 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 887 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -1768,10 +1773,18 @@ | |
| 1768 | |
| 1769 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 1770 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 1771 | |
| 1772 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 1773 | |
| 1774 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate |
| 1775 | $(OBJDIR)/translate $(SRCDIR)/rss.c >$@ |
| 1776 | |
| 1777 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 1778 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -119,10 +119,11 @@ | |
| 119 | $(SRCDIR)/purge.c \ |
| 120 | $(SRCDIR)/rebuild.c \ |
| 121 | $(SRCDIR)/regexp.c \ |
| 122 | $(SRCDIR)/repolist.c \ |
| 123 | $(SRCDIR)/report.c \ |
| 124 | $(SRCDIR)/robot.c \ |
| 125 | $(SRCDIR)/rss.c \ |
| 126 | $(SRCDIR)/schema.c \ |
| 127 | $(SRCDIR)/search.c \ |
| 128 | $(SRCDIR)/security_audit.c \ |
| 129 | $(SRCDIR)/setup.c \ |
| @@ -236,10 +237,11 @@ | |
| 237 | $(SRCDIR)/fossil.page.chat.js \ |
| 238 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 239 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 240 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 241 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 242 | $(SRCDIR)/fossil.page.ticket.js \ |
| 243 | $(SRCDIR)/fossil.page.whistory.js \ |
| 244 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 245 | $(SRCDIR)/fossil.pikchr.js \ |
| 246 | $(SRCDIR)/fossil.popupwidget.js \ |
| 247 | $(SRCDIR)/fossil.storage.js \ |
| @@ -385,10 +387,11 @@ | |
| 387 | $(OBJDIR)/purge_.c \ |
| 388 | $(OBJDIR)/rebuild_.c \ |
| 389 | $(OBJDIR)/regexp_.c \ |
| 390 | $(OBJDIR)/repolist_.c \ |
| 391 | $(OBJDIR)/report_.c \ |
| 392 | $(OBJDIR)/robot_.c \ |
| 393 | $(OBJDIR)/rss_.c \ |
| 394 | $(OBJDIR)/schema_.c \ |
| 395 | $(OBJDIR)/search_.c \ |
| 396 | $(OBJDIR)/security_audit_.c \ |
| 397 | $(OBJDIR)/setup_.c \ |
| @@ -535,10 +538,11 @@ | |
| 538 | $(OBJDIR)/purge.o \ |
| 539 | $(OBJDIR)/rebuild.o \ |
| 540 | $(OBJDIR)/regexp.o \ |
| 541 | $(OBJDIR)/repolist.o \ |
| 542 | $(OBJDIR)/report.o \ |
| 543 | $(OBJDIR)/robot.o \ |
| 544 | $(OBJDIR)/rss.o \ |
| 545 | $(OBJDIR)/schema.o \ |
| 546 | $(OBJDIR)/search.o \ |
| 547 | $(OBJDIR)/security_audit.o \ |
| 548 | $(OBJDIR)/setup.o \ |
| @@ -878,10 +882,11 @@ | |
| 882 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 883 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 884 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 885 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 886 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 887 | $(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \ |
| 888 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 889 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 890 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 891 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 892 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -1768,10 +1773,18 @@ | |
| 1773 | |
| 1774 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 1775 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 1776 | |
| 1777 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 1778 | |
| 1779 | $(OBJDIR)/robot_.c: $(SRCDIR)/robot.c $(OBJDIR)/translate |
| 1780 | $(OBJDIR)/translate $(SRCDIR)/robot.c >$@ |
| 1781 | |
| 1782 | $(OBJDIR)/robot.o: $(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h |
| 1783 | $(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c |
| 1784 | |
| 1785 | $(OBJDIR)/robot.h: $(OBJDIR)/headers |
| 1786 | |
| 1787 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate |
| 1788 | $(OBJDIR)/translate $(SRCDIR)/rss.c >$@ |
| 1789 | |
| 1790 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 1791 |
+1
-1
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -2734,11 +2734,11 @@ | ||
| 2734 | 2734 | } |
| 2735 | 2735 | zName = p->aTag[i].zName; |
| 2736 | 2736 | zValue = p->aTag[i].zValue; |
| 2737 | 2737 | if( strcmp(zName, "*branch")==0 ){ |
| 2738 | 2738 | blob_appendf(&comment, |
| 2739 | - " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].", | |
| 2739 | + " Move to branch [/timeline?r=%t&nd&dp=%!S&unhide | %h].", | |
| 2740 | 2740 | zValue, zTagUuid, zValue); |
| 2741 | 2741 | branchMove = 1; |
| 2742 | 2742 | continue; |
| 2743 | 2743 | }else if( strcmp(zName, "*bgcolor")==0 ){ |
| 2744 | 2744 | blob_appendf(&comment, |
| 2745 | 2745 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2734,11 +2734,11 @@ | |
| 2734 | } |
| 2735 | zName = p->aTag[i].zName; |
| 2736 | zValue = p->aTag[i].zValue; |
| 2737 | if( strcmp(zName, "*branch")==0 ){ |
| 2738 | blob_appendf(&comment, |
| 2739 | " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].", |
| 2740 | zValue, zTagUuid, zValue); |
| 2741 | branchMove = 1; |
| 2742 | continue; |
| 2743 | }else if( strcmp(zName, "*bgcolor")==0 ){ |
| 2744 | blob_appendf(&comment, |
| 2745 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2734,11 +2734,11 @@ | |
| 2734 | } |
| 2735 | zName = p->aTag[i].zName; |
| 2736 | zValue = p->aTag[i].zValue; |
| 2737 | if( strcmp(zName, "*branch")==0 ){ |
| 2738 | blob_appendf(&comment, |
| 2739 | " Move to branch [/timeline?r=%t&nd&dp=%!S&unhide | %h].", |
| 2740 | zValue, zTagUuid, zValue); |
| 2741 | branchMove = 1; |
| 2742 | continue; |
| 2743 | }else if( strcmp(zName, "*bgcolor")==0 ){ |
| 2744 | blob_appendf(&comment, |
| 2745 |
+1
-1
| --- src/markdown.md | ||
| +++ src/markdown.md | ||
| @@ -188,11 +188,11 @@ | ||
| 188 | 188 | |
| 189 | 189 | ## Miscellaneous ## |
| 190 | 190 | |
| 191 | 191 | > * In-line images are made using **\!\[alt-text\]\(image-URL\)**. |
| 192 | 192 | > * Use HTML for advanced formatting such as forms, noting that certain |
| 193 | -> tags are [disallowed in some contexts](/help?cmd=safe-html). | |
| 193 | +> tags are [disallowed in some contexts](/help/safe-html). | |
| 194 | 194 | > * **\<!--** HTML-style comments **-->** are supported. |
| 195 | 195 | > * Escape special characters (ex: **\[** **\(** **\|** **\***) |
| 196 | 196 | > using backslash (ex: **\\\[** **\\\(** **\\\|** **\\\***). |
| 197 | 197 | > * A line consisting of **---**, **\*\*\***, or **\_\_\_** is a horizontal |
| 198 | 198 | > rule. Spaces and extra **-**/**\***/**_** are allowed. |
| 199 | 199 |
| --- src/markdown.md | |
| +++ src/markdown.md | |
| @@ -188,11 +188,11 @@ | |
| 188 | |
| 189 | ## Miscellaneous ## |
| 190 | |
| 191 | > * In-line images are made using **\!\[alt-text\]\(image-URL\)**. |
| 192 | > * Use HTML for advanced formatting such as forms, noting that certain |
| 193 | > tags are [disallowed in some contexts](/help?cmd=safe-html). |
| 194 | > * **\<!--** HTML-style comments **-->** are supported. |
| 195 | > * Escape special characters (ex: **\[** **\(** **\|** **\***) |
| 196 | > using backslash (ex: **\\\[** **\\\(** **\\\|** **\\\***). |
| 197 | > * A line consisting of **---**, **\*\*\***, or **\_\_\_** is a horizontal |
| 198 | > rule. Spaces and extra **-**/**\***/**_** are allowed. |
| 199 |
| --- src/markdown.md | |
| +++ src/markdown.md | |
| @@ -188,11 +188,11 @@ | |
| 188 | |
| 189 | ## Miscellaneous ## |
| 190 | |
| 191 | > * In-line images are made using **\!\[alt-text\]\(image-URL\)**. |
| 192 | > * Use HTML for advanced formatting such as forms, noting that certain |
| 193 | > tags are [disallowed in some contexts](/help/safe-html). |
| 194 | > * **\<!--** HTML-style comments **-->** are supported. |
| 195 | > * Escape special characters (ex: **\[** **\(** **\|** **\***) |
| 196 | > using backslash (ex: **\\\[** **\\\(** **\\\|** **\\\***). |
| 197 | > * A line consisting of **---**, **\*\*\***, or **\_\_\_** is a horizontal |
| 198 | > rule. Spaces and extra **-**/**\***/**_** are allowed. |
| 199 |
+2
-2
| --- src/match.c | ||
| +++ src/match.c | ||
| @@ -142,11 +142,11 @@ | ||
| 142 | 142 | if( zPat[0] ) zPat++; |
| 143 | 143 | |
| 144 | 144 | /* Check for regular expression syntax errors. */ |
| 145 | 145 | if( style==MS_REGEXP ){ |
| 146 | 146 | ReCompiled *regexp; |
| 147 | - const char *zFail = re_compile(®exp, zOne, 0); | |
| 147 | + const char *zFail = fossil_re_compile(®exp, zOne, 0); | |
| 148 | 148 | if( zFail ){ |
| 149 | 149 | re_free(regexp); |
| 150 | 150 | continue; |
| 151 | 151 | } |
| 152 | 152 | p->nPattern++; |
| @@ -375,11 +375,11 @@ | ||
| 375 | 375 | |
| 376 | 376 | /* Check for regular expression syntax errors. */ |
| 377 | 377 | if( matchStyle==MS_REGEXP ){ |
| 378 | 378 | ReCompiled *regexp; |
| 379 | 379 | char *zTagDup = fossil_strndup(zTag, i); |
| 380 | - zFail = re_compile(®exp, zTagDup, 0); | |
| 380 | + zFail = fossil_re_compile(®exp, zTagDup, 0); | |
| 381 | 381 | re_free(regexp); |
| 382 | 382 | fossil_free(zTagDup); |
| 383 | 383 | } |
| 384 | 384 | |
| 385 | 385 | /* Process success and error results. */ |
| 386 | 386 |
| --- src/match.c | |
| +++ src/match.c | |
| @@ -142,11 +142,11 @@ | |
| 142 | if( zPat[0] ) zPat++; |
| 143 | |
| 144 | /* Check for regular expression syntax errors. */ |
| 145 | if( style==MS_REGEXP ){ |
| 146 | ReCompiled *regexp; |
| 147 | const char *zFail = re_compile(®exp, zOne, 0); |
| 148 | if( zFail ){ |
| 149 | re_free(regexp); |
| 150 | continue; |
| 151 | } |
| 152 | p->nPattern++; |
| @@ -375,11 +375,11 @@ | |
| 375 | |
| 376 | /* Check for regular expression syntax errors. */ |
| 377 | if( matchStyle==MS_REGEXP ){ |
| 378 | ReCompiled *regexp; |
| 379 | char *zTagDup = fossil_strndup(zTag, i); |
| 380 | zFail = re_compile(®exp, zTagDup, 0); |
| 381 | re_free(regexp); |
| 382 | fossil_free(zTagDup); |
| 383 | } |
| 384 | |
| 385 | /* Process success and error results. */ |
| 386 |
| --- src/match.c | |
| +++ src/match.c | |
| @@ -142,11 +142,11 @@ | |
| 142 | if( zPat[0] ) zPat++; |
| 143 | |
| 144 | /* Check for regular expression syntax errors. */ |
| 145 | if( style==MS_REGEXP ){ |
| 146 | ReCompiled *regexp; |
| 147 | const char *zFail = fossil_re_compile(®exp, zOne, 0); |
| 148 | if( zFail ){ |
| 149 | re_free(regexp); |
| 150 | continue; |
| 151 | } |
| 152 | p->nPattern++; |
| @@ -375,11 +375,11 @@ | |
| 375 | |
| 376 | /* Check for regular expression syntax errors. */ |
| 377 | if( matchStyle==MS_REGEXP ){ |
| 378 | ReCompiled *regexp; |
| 379 | char *zTagDup = fossil_strndup(zTag, i); |
| 380 | zFail = fossil_re_compile(®exp, zTagDup, 0); |
| 381 | re_free(regexp); |
| 382 | fossil_free(zTagDup); |
| 383 | } |
| 384 | |
| 385 | /* Process success and error results. */ |
| 386 |
+2
-2
| --- src/pikchrshow.c | ||
| +++ src/pikchrshow.c | ||
| @@ -462,12 +462,12 @@ | ||
| 462 | 462 | } CX("</fieldset><!-- .zone-wrapper.input -->"); |
| 463 | 463 | CX("<fieldset class='zone-wrapper output'>"); { |
| 464 | 464 | CX("<legend><div class='button-bar'>"); |
| 465 | 465 | CX("<button id='btn-render-mode'>Render Mode</button> "); |
| 466 | 466 | CX("<span style='white-space:nowrap'>" |
| 467 | - "<span id='preview-copy-button' " | |
| 468 | - "title='Tap to copy to clipboard.'></span>" | |
| 467 | + "<button id='preview-copy-button' " | |
| 468 | + "title='Tap to copy to clipboard.'><span></span></button>" | |
| 469 | 469 | "<label for='preview-copy-button' " |
| 470 | 470 | "title='Tap to copy to clipboard.'></label>" |
| 471 | 471 | "</span>"); |
| 472 | 472 | CX("</div></legend>"); |
| 473 | 473 | CX("<div id='pikchr-output-wrapper'>"); |
| 474 | 474 |
| --- src/pikchrshow.c | |
| +++ src/pikchrshow.c | |
| @@ -462,12 +462,12 @@ | |
| 462 | } CX("</fieldset><!-- .zone-wrapper.input -->"); |
| 463 | CX("<fieldset class='zone-wrapper output'>"); { |
| 464 | CX("<legend><div class='button-bar'>"); |
| 465 | CX("<button id='btn-render-mode'>Render Mode</button> "); |
| 466 | CX("<span style='white-space:nowrap'>" |
| 467 | "<span id='preview-copy-button' " |
| 468 | "title='Tap to copy to clipboard.'></span>" |
| 469 | "<label for='preview-copy-button' " |
| 470 | "title='Tap to copy to clipboard.'></label>" |
| 471 | "</span>"); |
| 472 | CX("</div></legend>"); |
| 473 | CX("<div id='pikchr-output-wrapper'>"); |
| 474 |
| --- src/pikchrshow.c | |
| +++ src/pikchrshow.c | |
| @@ -462,12 +462,12 @@ | |
| 462 | } CX("</fieldset><!-- .zone-wrapper.input -->"); |
| 463 | CX("<fieldset class='zone-wrapper output'>"); { |
| 464 | CX("<legend><div class='button-bar'>"); |
| 465 | CX("<button id='btn-render-mode'>Render Mode</button> "); |
| 466 | CX("<span style='white-space:nowrap'>" |
| 467 | "<button id='preview-copy-button' " |
| 468 | "title='Tap to copy to clipboard.'><span></span></button>" |
| 469 | "<label for='preview-copy-button' " |
| 470 | "title='Tap to copy to clipboard.'></label>" |
| 471 | "</span>"); |
| 472 | CX("</div></legend>"); |
| 473 | CX("<div id='pikchr-output-wrapper'>"); |
| 474 |
+1
-1
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -252,11 +252,11 @@ | ||
| 252 | 252 | */ |
| 253 | 253 | /* |
| 254 | 254 | ** SETTING: timeline-hard-newlines boolean default=off |
| 255 | 255 | ** |
| 256 | 256 | ** If enabled, the timeline honors newline characters in check-in comments. |
| 257 | -** In other words, newlines are coverted into <br> for HTML display. | |
| 257 | +** In other words, newlines are converted into <br> for HTML display. | |
| 258 | 258 | ** The default behavior, when this setting is off, is that newlines are |
| 259 | 259 | ** treated like any other whitespace character. |
| 260 | 260 | */ |
| 261 | 261 | |
| 262 | 262 | /* |
| 263 | 263 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -252,11 +252,11 @@ | |
| 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. |
| 258 | ** The default behavior, when this setting is off, is that newlines are |
| 259 | ** treated like any other whitespace character. |
| 260 | */ |
| 261 | |
| 262 | /* |
| 263 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -252,11 +252,11 @@ | |
| 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 converted into <br> for HTML display. |
| 258 | ** The default behavior, when this setting is off, is that newlines are |
| 259 | ** treated like any other whitespace character. |
| 260 | */ |
| 261 | |
| 262 | /* |
| 263 |
+243
-92
| --- src/regexp.c | ||
| +++ src/regexp.c | ||
| @@ -53,16 +53,19 @@ | ||
| 53 | 53 | ** expression and M is the size of the input string. The matcher never |
| 54 | 54 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 55 | 55 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 56 | 56 | ** regular expression in the O(N*M) performance bound is computed after |
| 57 | 57 | ** this expansion. |
| 58 | +** | |
| 59 | +** To help prevent DoS attacks, the maximum size of the NFA is restricted. | |
| 58 | 60 | */ |
| 59 | 61 | #include "config.h" |
| 60 | 62 | #include "regexp.h" |
| 61 | 63 | |
| 62 | 64 | /* The end-of-input character */ |
| 63 | 65 | #define RE_EOF 0 /* End of input */ |
| 66 | +#define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ | |
| 64 | 67 | |
| 65 | 68 | /* The NFA is implemented as sequence of opcodes taken from the following |
| 66 | 69 | ** set. Each opcode has a single integer argument. |
| 67 | 70 | */ |
| 68 | 71 | #define RE_OP_MATCH 1 /* Match the one character in the argument */ |
| @@ -80,10 +83,11 @@ | ||
| 80 | 83 | #define RE_OP_DIGIT 13 /* digit: [0-9] */ |
| 81 | 84 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 82 | 85 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 83 | 86 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 84 | 87 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 88 | +#define RE_OP_ATSTART 18 /* Currently at the start of the string */ | |
| 85 | 89 | |
| 86 | 90 | /* Each opcode is a "state" in the NFA */ |
| 87 | 91 | typedef unsigned short ReStateNumber; |
| 88 | 92 | |
| 89 | 93 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -113,13 +117,14 @@ | ||
| 113 | 117 | const char *zErr; /* Error message to return */ |
| 114 | 118 | char *aOp; /* Operators for the virtual machine */ |
| 115 | 119 | int *aArg; /* Arguments to each operator */ |
| 116 | 120 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 117 | 121 | unsigned char zInit[12]; /* Initial text to match */ |
| 118 | - int nInit; /* Number of characters in zInit */ | |
| 122 | + int nInit; /* Number of bytes in zInit */ | |
| 119 | 123 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 120 | 124 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 125 | + unsigned mxAlloc; /* Complexity limit */ | |
| 121 | 126 | }; |
| 122 | 127 | #endif |
| 123 | 128 | |
| 124 | 129 | /* Add a state to the given state set if it is not already there */ |
| 125 | 130 | static void re_add_state(ReStateSet *pSet, int newState){ |
| @@ -144,11 +149,11 @@ | ||
| 144 | 149 | }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80 |
| 145 | 150 | && (p->z[p->i+1]&0xc0)==0x80 ){ |
| 146 | 151 | c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); |
| 147 | 152 | p->i += 2; |
| 148 | 153 | if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; |
| 149 | - }else if( (c&0xf8)==0xf0 && p->i+3<p->mx && (p->z[p->i]&0xc0)==0x80 | |
| 154 | + }else if( (c&0xf8)==0xf0 && p->i+2<p->mx && (p->z[p->i]&0xc0)==0x80 | |
| 150 | 155 | && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ |
| 151 | 156 | c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) |
| 152 | 157 | | (p->z[p->i+2]&0x3f); |
| 153 | 158 | p->i += 3; |
| 154 | 159 | if( c<=0xffff || c>0x10ffff ) c = 0xfffd; |
| @@ -185,11 +190,11 @@ | ||
| 185 | 190 | ReStateSet aStateSet[2], *pThis, *pNext; |
| 186 | 191 | ReStateNumber aSpace[100]; |
| 187 | 192 | ReStateNumber *pToFree; |
| 188 | 193 | unsigned int i = 0; |
| 189 | 194 | unsigned int iSwap = 0; |
| 190 | - int c = RE_EOF+1; | |
| 195 | + int c = RE_START; | |
| 191 | 196 | int cPrev = 0; |
| 192 | 197 | int rc = 0; |
| 193 | 198 | ReInput in; |
| 194 | 199 | |
| 195 | 200 | in.z = zIn; |
| @@ -204,10 +209,11 @@ | ||
| 204 | 209 | strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) |
| 205 | 210 | ){ |
| 206 | 211 | in.i++; |
| 207 | 212 | } |
| 208 | 213 | if( in.i+pRe->nInit>in.mx ) return 0; |
| 214 | + c = RE_START-1; | |
| 209 | 215 | } |
| 210 | 216 | |
| 211 | 217 | if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ |
| 212 | 218 | pToFree = 0; |
| 213 | 219 | aStateSet[0].aState = aSpace; |
| @@ -231,10 +237,14 @@ | ||
| 231 | 237 | int x = pThis->aState[i]; |
| 232 | 238 | switch( pRe->aOp[x] ){ |
| 233 | 239 | case RE_OP_MATCH: { |
| 234 | 240 | if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); |
| 235 | 241 | break; |
| 242 | + } | |
| 243 | + case RE_OP_ATSTART: { | |
| 244 | + if( cPrev==RE_START ) re_add_state(pThis, x+1); | |
| 245 | + break; | |
| 236 | 246 | } |
| 237 | 247 | case RE_OP_ANY: { |
| 238 | 248 | if( c!=0 ) re_add_state(pNext, x+1); |
| 239 | 249 | break; |
| 240 | 250 | } |
| @@ -284,13 +294,13 @@ | ||
| 284 | 294 | rc = 1; |
| 285 | 295 | goto re_match_end; |
| 286 | 296 | } |
| 287 | 297 | case RE_OP_CC_EXC: { |
| 288 | 298 | if( c==0 ) break; |
| 289 | - /* fall-through */ | |
| 299 | + /* fall-through */ goto re_op_cc_inc; | |
| 290 | 300 | } |
| 291 | - case RE_OP_CC_INC: { | |
| 301 | + case RE_OP_CC_INC: re_op_cc_inc: { | |
| 292 | 302 | int j = 1; |
| 293 | 303 | int n = pRe->aArg[x]; |
| 294 | 304 | int hit = 0; |
| 295 | 305 | for(j=1; j>0 && j<n; j++){ |
| 296 | 306 | if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){ |
| @@ -313,11 +323,13 @@ | ||
| 313 | 323 | } |
| 314 | 324 | } |
| 315 | 325 | } |
| 316 | 326 | } |
| 317 | 327 | for(i=0; i<pNext->nState; i++){ |
| 318 | - if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } | |
| 328 | + int x = pNext->aState[i]; | |
| 329 | + while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; | |
| 330 | + if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } | |
| 319 | 331 | } |
| 320 | 332 | re_match_end: |
| 321 | 333 | fossil_free(pToFree); |
| 322 | 334 | return rc; |
| 323 | 335 | } |
| @@ -325,15 +337,16 @@ | ||
| 325 | 337 | /* Resize the opcode and argument arrays for an RE under construction. |
| 326 | 338 | */ |
| 327 | 339 | static int re_resize(ReCompiled *p, int N){ |
| 328 | 340 | char *aOp; |
| 329 | 341 | int *aArg; |
| 342 | + if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; } | |
| 330 | 343 | aOp = fossil_realloc(p->aOp, N*sizeof(p->aOp[0])); |
| 331 | - if( aOp==0 ) return 1; | |
| 344 | + if( aOp==0 ){ p->zErr = "out of memory"; return 1; } | |
| 332 | 345 | p->aOp = aOp; |
| 333 | 346 | aArg = fossil_realloc(p->aArg, N*sizeof(p->aArg[0])); |
| 334 | - if( aArg==0 ) return 1; | |
| 347 | + if( aArg==0 ){ p->zErr = "out of memory"; return 1; } | |
| 335 | 348 | p->aArg = aArg; |
| 336 | 349 | p->nAlloc = N; |
| 337 | 350 | return 0; |
| 338 | 351 | } |
| 339 | 352 | |
| @@ -468,11 +481,10 @@ | ||
| 468 | 481 | const char *zErr; |
| 469 | 482 | while( (c = p->xNextChar(&p->sIn))!=0 ){ |
| 470 | 483 | iStart = p->nState; |
| 471 | 484 | switch( c ){ |
| 472 | 485 | case '|': |
| 473 | - case '$': | |
| 474 | 486 | case ')': { |
| 475 | 487 | p->sIn.i--; |
| 476 | 488 | return 0; |
| 477 | 489 | } |
| 478 | 490 | case '(': { |
| @@ -504,44 +516,61 @@ | ||
| 504 | 516 | } |
| 505 | 517 | case '?': { |
| 506 | 518 | if( iPrev<0 ) return "'?' without operand"; |
| 507 | 519 | re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); |
| 508 | 520 | break; |
| 521 | + } | |
| 522 | + case '$': { | |
| 523 | + re_append(p, RE_OP_MATCH, RE_EOF); | |
| 524 | + break; | |
| 525 | + } | |
| 526 | + case '^': { | |
| 527 | + re_append(p, RE_OP_ATSTART, 0); | |
| 528 | + break; | |
| 509 | 529 | } |
| 510 | 530 | case '{': { |
| 511 | - int m = 0, n = 0; | |
| 512 | - int sz, j; | |
| 531 | + unsigned int m = 0, n = 0; | |
| 532 | + unsigned int sz, j; | |
| 513 | 533 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 514 | - while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } | |
| 534 | + while( (c=rePeek(p))>='0' && c<='9' ){ | |
| 535 | + m = m*10 + c - '0'; | |
| 536 | + if( m*2>p->mxAlloc ) return "REGEXP pattern too big"; | |
| 537 | + p->sIn.i++; | |
| 538 | + } | |
| 515 | 539 | n = m; |
| 516 | 540 | if( c==',' ){ |
| 517 | 541 | p->sIn.i++; |
| 518 | 542 | n = 0; |
| 519 | - while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } | |
| 543 | + while( (c=rePeek(p))>='0' && c<='9' ){ | |
| 544 | + n = n*10 + c-'0'; | |
| 545 | + if( n*2>p->mxAlloc ) return "REGEXP pattern too big"; | |
| 546 | + p->sIn.i++; | |
| 547 | + } | |
| 520 | 548 | } |
| 521 | 549 | if( c!='}' ) return "unmatched '{'"; |
| 522 | - if( n>0 && n<m ) return "n less than m in '{m,n}'"; | |
| 550 | + if( n<m ) return "n less than m in '{m,n}'"; | |
| 523 | 551 | p->sIn.i++; |
| 524 | 552 | sz = p->nState - iPrev; |
| 525 | 553 | if( m==0 ){ |
| 526 | 554 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 527 | 555 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| 556 | + iPrev++; | |
| 528 | 557 | n--; |
| 529 | 558 | }else{ |
| 530 | 559 | for(j=1; j<m; j++) re_copy(p, iPrev, sz); |
| 531 | 560 | } |
| 532 | 561 | for(j=m; j<n; j++){ |
| 533 | 562 | re_append(p, RE_OP_FORK, sz+1); |
| 534 | 563 | re_copy(p, iPrev, sz); |
| 535 | 564 | } |
| 536 | 565 | if( n==0 && m>0 ){ |
| 537 | - re_append(p, RE_OP_FORK, -sz); | |
| 566 | + re_append(p, RE_OP_FORK, -(int)sz); | |
| 538 | 567 | } |
| 539 | 568 | break; |
| 540 | 569 | } |
| 541 | 570 | case '[': { |
| 542 | - int iFirst = p->nState; | |
| 571 | + unsigned int iFirst = p->nState; | |
| 543 | 572 | if( rePeek(p)=='^' ){ |
| 544 | 573 | re_append(p, RE_OP_CC_EXC, 0); |
| 545 | 574 | p->sIn.i++; |
| 546 | 575 | }else{ |
| 547 | 576 | re_append(p, RE_OP_CC_INC, 0); |
| @@ -561,11 +590,11 @@ | ||
| 561 | 590 | re_append(p, RE_OP_CC_VALUE, c); |
| 562 | 591 | } |
| 563 | 592 | if( rePeek(p)==']' ){ p->sIn.i++; break; } |
| 564 | 593 | } |
| 565 | 594 | if( c==0 ) return "unclosed '['"; |
| 566 | - p->aArg[iFirst] = p->nState - iFirst; | |
| 595 | + if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; | |
| 567 | 596 | break; |
| 568 | 597 | } |
| 569 | 598 | case '\\': { |
| 570 | 599 | int specialOp = 0; |
| 571 | 600 | switch( rePeek(p) ){ |
| @@ -612,11 +641,16 @@ | ||
| 612 | 641 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 613 | 642 | ** expression suitable for us by re_match() and return a pointer to the |
| 614 | 643 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 615 | 644 | ** error message if something goes wrong. |
| 616 | 645 | */ |
| 617 | -const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ | |
| 646 | +static const char *re_compile( | |
| 647 | + ReCompiled **ppRe, /* OUT: write compiled NFA here */ | |
| 648 | + const char *zIn, /* Input regular expression */ | |
| 649 | + int mxRe, /* Complexity limit */ | |
| 650 | + int noCase /* True for caseless comparisons */ | |
| 651 | +){ | |
| 618 | 652 | ReCompiled *pRe; |
| 619 | 653 | const char *zErr; |
| 620 | 654 | int i, j; |
| 621 | 655 | |
| 622 | 656 | *ppRe = 0; |
| @@ -624,13 +658,15 @@ | ||
| 624 | 658 | if( pRe==0 ){ |
| 625 | 659 | return "out of memory"; |
| 626 | 660 | } |
| 627 | 661 | memset(pRe, 0, sizeof(*pRe)); |
| 628 | 662 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 663 | + pRe->mxAlloc = mxRe; | |
| 629 | 664 | if( re_resize(pRe, 30) ){ |
| 665 | + zErr = pRe->zErr; | |
| 630 | 666 | re_free(pRe); |
| 631 | - return "out of memory"; | |
| 667 | + return zErr; | |
| 632 | 668 | } |
| 633 | 669 | if( zIn[0]=='^' ){ |
| 634 | 670 | zIn++; |
| 635 | 671 | }else{ |
| 636 | 672 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -641,15 +677,11 @@ | ||
| 641 | 677 | zErr = re_subcompile_re(pRe); |
| 642 | 678 | if( zErr ){ |
| 643 | 679 | re_free(pRe); |
| 644 | 680 | return zErr; |
| 645 | 681 | } |
| 646 | - if( rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){ | |
| 647 | - re_append(pRe, RE_OP_MATCH, RE_EOF); | |
| 648 | - re_append(pRe, RE_OP_ACCEPT, 0); | |
| 649 | - *ppRe = pRe; | |
| 650 | - }else if( pRe->sIn.i>=pRe->sIn.mx ){ | |
| 682 | + if( pRe->sIn.i>=pRe->sIn.mx ){ | |
| 651 | 683 | re_append(pRe, RE_OP_ACCEPT, 0); |
| 652 | 684 | *ppRe = pRe; |
| 653 | 685 | }else{ |
| 654 | 686 | re_free(pRe); |
| 655 | 687 | return "unrecognized character"; |
| @@ -658,23 +690,23 @@ | ||
| 658 | 690 | /* The following is a performance optimization. If the regex begins with |
| 659 | 691 | ** ".*" (if the input regex lacks an initial "^") and afterwards there are |
| 660 | 692 | ** one or more matching characters, enter those matching characters into |
| 661 | 693 | ** zInit[]. The re_match() routine can then search ahead in the input |
| 662 | 694 | ** string looking for the initial match without having to run the whole |
| 663 | - ** regex engine over the string. Do not worry able trying to match | |
| 695 | + ** regex engine over the string. Do not worry about trying to match | |
| 664 | 696 | ** unicode characters beyond plane 0 - those are very rare and this is |
| 665 | 697 | ** just an optimization. */ |
| 666 | 698 | if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ |
| 667 | 699 | for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ |
| 668 | 700 | unsigned x = pRe->aArg[i]; |
| 669 | - if( x<=127 ){ | |
| 701 | + if( x<=0x7f ){ | |
| 670 | 702 | pRe->zInit[j++] = (unsigned char)x; |
| 671 | - }else if( x<=0xfff ){ | |
| 703 | + }else if( x<=0x7ff ){ | |
| 672 | 704 | pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); |
| 673 | 705 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 674 | 706 | }else if( x<=0xffff ){ |
| 675 | - pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12)); | |
| 707 | + pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); | |
| 676 | 708 | pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); |
| 677 | 709 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 678 | 710 | }else{ |
| 679 | 711 | break; |
| 680 | 712 | } |
| @@ -682,10 +714,79 @@ | ||
| 682 | 714 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 683 | 715 | pRe->nInit = j; |
| 684 | 716 | } |
| 685 | 717 | return pRe->zErr; |
| 686 | 718 | } |
| 719 | + | |
| 720 | +/* | |
| 721 | +** Implementation of the regexp() SQL function. This function implements | |
| 722 | +** the build-in REGEXP operator. The first argument to the function is the | |
| 723 | +** pattern and the second argument is the string. So, the SQL statements: | |
| 724 | +** | |
| 725 | +** A REGEXP B | |
| 726 | +** | |
| 727 | +** is implemented as regexp(B,A). | |
| 728 | +*/ | |
| 729 | +static void re_sql_func( | |
| 730 | + sqlite3_context *context, | |
| 731 | + int argc, | |
| 732 | + sqlite3_value **argv | |
| 733 | +){ | |
| 734 | + ReCompiled *pRe; /* Compiled regular expression */ | |
| 735 | + const char *zPattern; /* The regular expression */ | |
| 736 | + const unsigned char *zStr;/* String being searched */ | |
| 737 | + const char *zErr; /* Compile error message */ | |
| 738 | + int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ | |
| 739 | + | |
| 740 | + (void)argc; /* Unused */ | |
| 741 | + pRe = sqlite3_get_auxdata(context, 0); | |
| 742 | + if( pRe==0 ){ | |
| 743 | + zPattern = (const char*)sqlite3_value_text(argv[0]); | |
| 744 | + if( zPattern==0 ) return; | |
| 745 | + zErr = fossil_re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); | |
| 746 | + if( zErr ){ | |
| 747 | + re_free(pRe); | |
| 748 | + /* The original SQLite function from which this code was copied raises | |
| 749 | + ** an error if the REGEXP contained a syntax error. This variant | |
| 750 | + ** silently fails to match, as that works better for Fossil. | |
| 751 | + ** sqlite3_result_error(context, zErr, -1); */ | |
| 752 | + sqlite3_result_int(context, 0); | |
| 753 | + return; | |
| 754 | + } | |
| 755 | + if( pRe==0 ){ | |
| 756 | + sqlite3_result_error_nomem(context); | |
| 757 | + return; | |
| 758 | + } | |
| 759 | + setAux = 1; | |
| 760 | + } | |
| 761 | + zStr = (const unsigned char*)sqlite3_value_text(argv[1]); | |
| 762 | + if( zStr!=0 ){ | |
| 763 | + sqlite3_result_int(context, re_match(pRe, zStr, -1)); | |
| 764 | + } | |
| 765 | + if( setAux ){ | |
| 766 | + sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); | |
| 767 | + } | |
| 768 | +} | |
| 769 | + | |
| 770 | +/* | |
| 771 | +** Invoke this routine to register the regexp() function with the | |
| 772 | +** SQLite database connection. | |
| 773 | +*/ | |
| 774 | +int re_add_sql_func(sqlite3 *db){ | |
| 775 | + int rc; | |
| 776 | + rc = sqlite3_create_function(db, "regexp", 2, | |
| 777 | + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, | |
| 778 | + 0, re_sql_func, 0, 0); | |
| 779 | + if( rc==SQLITE_OK ){ | |
| 780 | + /* The regexpi(PATTERN,STRING) function is a case-insensitive version | |
| 781 | + ** of regexp(PATTERN,STRING). */ | |
| 782 | + rc = sqlite3_create_function(db, "regexpi", 2, | |
| 783 | + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, | |
| 784 | + (void*)db, re_sql_func, 0, 0); | |
| 785 | + } | |
| 786 | + return rc; | |
| 787 | +} | |
| 687 | 788 | |
| 688 | 789 | /* |
| 689 | 790 | ** The input zIn is a string that we want to match exactly as part of |
| 690 | 791 | ** a regular expression. Return a new string (in space obtained from |
| 691 | 792 | ** fossil_malloc() or the equivalent) that escapes all regexp syntax |
| @@ -723,71 +824,27 @@ | ||
| 723 | 824 | blob_materialize(&out); |
| 724 | 825 | return out.aData; |
| 725 | 826 | } |
| 726 | 827 | |
| 727 | 828 | /* |
| 728 | -** Implementation of the regexp() SQL function. This function implements | |
| 729 | -** the build-in REGEXP operator. The first argument to the function is the | |
| 730 | -** pattern and the second argument is the string. So, the SQL statements: | |
| 829 | +** SETTING: regexp-limit width=8 default=1000 | |
| 731 | 830 | ** |
| 732 | -** A REGEXP B | |
| 733 | -** | |
| 734 | -** is implemented as regexp(B,A). | |
| 831 | +** Limit the size of the bytecode used to implement a regular expression | |
| 832 | +** to this many steps. It is important to limit this to avoid possible | |
| 833 | +** DoS attacks. | |
| 834 | +*/ | |
| 835 | + | |
| 836 | +/* | |
| 837 | +** Compile an RE using re_maxlen(). | |
| 735 | 838 | */ |
| 736 | -static void re_sql_func( | |
| 737 | - sqlite3_context *context, | |
| 738 | - int argc, | |
| 739 | - sqlite3_value **argv | |
| 839 | +const char *fossil_re_compile( | |
| 840 | + ReCompiled **ppRe, /* OUT: write compiled NFA here */ | |
| 841 | + const char *zIn, /* Input regular expression */ | |
| 842 | + int noCase /* True for caseless comparisons */ | |
| 740 | 843 | ){ |
| 741 | - ReCompiled *pRe; /* Compiled regular expression */ | |
| 742 | - const char *zPattern; /* The regular expression */ | |
| 743 | - const unsigned char *zStr;/* String being searched */ | |
| 744 | - const char *zErr; /* Compile error message */ | |
| 745 | - int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ | |
| 746 | - | |
| 747 | - (void)argc; /* Unused */ | |
| 748 | - pRe = sqlite3_get_auxdata(context, 0); | |
| 749 | - if( pRe==0 ){ | |
| 750 | - zPattern = (const char*)sqlite3_value_text(argv[0]); | |
| 751 | - if( zPattern==0 ) return; | |
| 752 | - zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); | |
| 753 | - if( zErr ){ | |
| 754 | - re_free(pRe); | |
| 755 | - sqlite3_result_int(context, 0); | |
| 756 | - /* sqlite3_result_error(context, zErr, -1); */ | |
| 757 | - return; | |
| 758 | - } | |
| 759 | - if( pRe==0 ){ | |
| 760 | - sqlite3_result_error_nomem(context); | |
| 761 | - return; | |
| 762 | - } | |
| 763 | - setAux = 1; | |
| 764 | - } | |
| 765 | - zStr = (const unsigned char*)sqlite3_value_text(argv[1]); | |
| 766 | - if( zStr!=0 ){ | |
| 767 | - sqlite3_result_int(context, re_match(pRe, zStr, -1)); | |
| 768 | - } | |
| 769 | - if( setAux ){ | |
| 770 | - sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); | |
| 771 | - } | |
| 772 | -} | |
| 773 | - | |
| 774 | -/* | |
| 775 | -** Invoke this routine to register the regexp() function with the | |
| 776 | -** SQLite database connection. | |
| 777 | -*/ | |
| 778 | -int re_add_sql_func(sqlite3 *db){ | |
| 779 | - int rc; | |
| 780 | - rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, | |
| 781 | - 0, re_sql_func, 0, 0); | |
| 782 | - if( rc==SQLITE_OK ){ | |
| 783 | - /* The regexpi(PATTERN,STRING) function is a case-insensitive version | |
| 784 | - ** of regexp(PATTERN,STRING). */ | |
| 785 | - rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, | |
| 786 | - (void*)db, re_sql_func, 0, 0); | |
| 787 | - } | |
| 788 | - return rc; | |
| 844 | + int mxLen = g.db ? db_get_int("regexp-limit",1000) : 1000; | |
| 845 | + return re_compile(ppRe, zIn, mxLen, noCase); | |
| 789 | 846 | } |
| 790 | 847 | |
| 791 | 848 | /* |
| 792 | 849 | ** Run a "grep" over a single file read from disk. |
| 793 | 850 | */ |
| @@ -850,25 +907,38 @@ | ||
| 850 | 907 | ** Run a regular expression match over the named disk files, or against |
| 851 | 908 | ** standard input if no disk files are named on the command-line. |
| 852 | 909 | ** |
| 853 | 910 | ** Options: |
| 854 | 911 | ** -i|--ignore-case Ignore case |
| 912 | +** --robot-exception Use the robot-exception setting as the REGEXP | |
| 855 | 913 | */ |
| 856 | 914 | void re_test_grep(void){ |
| 857 | 915 | ReCompiled *pRe; |
| 858 | 916 | const char *zErr; |
| 917 | + int iFileList = 3; | |
| 859 | 918 | int ignoreCase = find_option("ignore-case","i",0)!=0; |
| 860 | - if( g.argc<3 ){ | |
| 861 | - usage("REGEXP [FILE...]"); | |
| 919 | + int bRobot = find_option("robot-exception",0,0)!=0; | |
| 920 | + if( bRobot ){ | |
| 921 | + const char *zRe; | |
| 922 | + db_find_and_open_repository(0,0); | |
| 923 | + verify_all_options(); | |
| 924 | + zRe = db_get("robot-exception","^$"); | |
| 925 | + zErr = fossil_re_compile(&pRe, zRe, ignoreCase); | |
| 926 | + iFileList = 2; | |
| 927 | + }else{ | |
| 928 | + verify_all_options(); | |
| 929 | + if( g.argc<3 ){ | |
| 930 | + usage("REGEXP [FILE...]"); | |
| 931 | + } | |
| 932 | + zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase); | |
| 862 | 933 | } |
| 863 | - zErr = re_compile(&pRe, g.argv[2], ignoreCase); | |
| 864 | 934 | if( zErr ) fossil_fatal("%s", zErr); |
| 865 | - if( g.argc==3 ){ | |
| 935 | + if( g.argc==iFileList ){ | |
| 866 | 936 | grep_file(pRe, "-", stdin); |
| 867 | 937 | }else{ |
| 868 | 938 | int i; |
| 869 | - for(i=3; i<g.argc; i++){ | |
| 939 | + for(i=iFileList; i<g.argc; i++){ | |
| 870 | 940 | FILE *in = fossil_fopen(g.argv[i], "rb"); |
| 871 | 941 | if( in==0 ){ |
| 872 | 942 | fossil_warning("cannot open \"%s\"", g.argv[i]); |
| 873 | 943 | }else{ |
| 874 | 944 | grep_file(pRe, g.argv[i], in); |
| @@ -939,11 +1009,11 @@ | ||
| 939 | 1009 | db_find_and_open_repository(0, 0); |
| 940 | 1010 | verify_all_options(); |
| 941 | 1011 | if( g.argc<4 ){ |
| 942 | 1012 | usage("REGEXP FILENAME ..."); |
| 943 | 1013 | } |
| 944 | - zErr = re_compile(&pRe, g.argv[2], ignoreCase); | |
| 1014 | + zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase); | |
| 945 | 1015 | if( zErr ) fossil_fatal("%s", zErr); |
| 946 | 1016 | |
| 947 | 1017 | add_content_sql_commands(g.db); |
| 948 | 1018 | db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);"); |
| 949 | 1019 | for(ii=3; ii<g.argc; ii++){ |
| @@ -1016,5 +1086,86 @@ | ||
| 1016 | 1086 | }else{ |
| 1017 | 1087 | fossil_print("%d\n", nMatch); |
| 1018 | 1088 | } |
| 1019 | 1089 | } |
| 1020 | 1090 | } |
| 1091 | + | |
| 1092 | +/* | |
| 1093 | +** WEBPAGE: re_rules | |
| 1094 | +** | |
| 1095 | +** Show a summary of the regular expression matching rules for Fossil. | |
| 1096 | +*/ | |
| 1097 | +void re_rules_page(void){ | |
| 1098 | + style_set_current_feature("wiki"); | |
| 1099 | + style_header("Regular Expression Syntax"); | |
| 1100 | + @ <p>Syntax rules for regular expression matching in Fossil:</p> | |
| 1101 | + @ | |
| 1102 | + @ <table border="0" cellpadding="0" cellspacing="0"> | |
| 1103 | + @ <tr><th>   <th>Pattern | |
| 1104 | + @ <th>   <th align="left">Match | |
| 1105 | + @ <tr><td><td><i>X</i><b>*</b> | |
| 1106 | + @ <td><td>Zero or more occurrences of <i>X</i> | |
| 1107 | + @ <tr><td><td><i>X</i><b>+</b> | |
| 1108 | + @ <td><td>One or more occurrences of <i>X</i> | |
| 1109 | + @ <tr><td><td><i>X</i><b>?</b> | |
| 1110 | + @ <td><td>Zero or one occurrences of <i>X</i> | |
| 1111 | + @ <tr><td><td><i>X</i><b>{</b><i>P</i><b>,</b><i>Q</i><b>}</b> | |
| 1112 | + @ <td><td>Between P and Q occurrences of <i>X</i> | |
| 1113 | + @ <tr><td><td><b>(</b><i>X</i><b>)</b> | |
| 1114 | + @ <td><td><i>X</i> | |
| 1115 | + @ <tr><td><td><i>X</i><b>|</b><i>Y</i> | |
| 1116 | + @ <td><td><i>X</i> or <i>Y</i> | |
| 1117 | + @ <tr><td><td><b>^</b><i>X</i> | |
| 1118 | + @ <td><td><i>X</i> at the beginning of the string | |
| 1119 | + @ <tr><td><td><i>X</i><b>$</b> | |
| 1120 | + @ <td><td><i>X</i> at the end of the string | |
| 1121 | + @ <tr><td><td><b>.</b> | |
| 1122 | + @ <td><td>Any single character | |
| 1123 | + @ <tr><td><td><b>\</b><i>C</i> | |
| 1124 | + @ <td><td>Character <i>C</i> if <i>C</i> is one of: <b>\{}()[]|*+?</b> | |
| 1125 | + @ <tr><td><td><b>\</b><i>C</i> | |
| 1126 | + @ <td><td>C-language escapes if <i>C</i> is one of: <b>afnrtv</b> | |
| 1127 | + @ <tr><td><td><b>\u</b><i>HHHH</i> | |
| 1128 | + @ <td><td>Unicode character U+HHHH where <i>HHHH</i> is four hex digits | |
| 1129 | + @ <tr><td><td><b>\</b><i>HH</i> | |
| 1130 | + @ <td><td>Unicode character U+00HH where <i>HH</i> is two hex digits | |
| 1131 | + @ <tr><td><td><b>[</b><i>abc</i><b>]</b> | |
| 1132 | + @ <td><td>Any single character from <i>abc</i> | |
| 1133 | + @ <tr><td><td><b>[^</b><i>abc</i><b>]</b> | |
| 1134 | + @ <td><td>Any single character not in <i>abc</i> | |
| 1135 | + @ <tr><td><td><b>[</b><i>a-z</i><b>]</b> | |
| 1136 | + @ <td><td>Any single character between <i>a</i> and <i>z</i>, inclusive | |
| 1137 | + @ <tr><td><td><b>[^</b><i>a-z</i><b>]</b> | |
| 1138 | + @ <td><td>Any single character not between <i>a</i> and <i>z</i> | |
| 1139 | + @ <tr><td><td><b>\b</b> | |
| 1140 | + @ <td><td>Word boundary | |
| 1141 | + @ <tr><td><td><b>\w</b> | |
| 1142 | + @ <td><td>A word character: a-zA-Z0-9 or _ | |
| 1143 | + @ <tr><td><td><b>\W</b> | |
| 1144 | + @ <td><td>A non-word character | |
| 1145 | + @ <tr><td><td><b>\d</b> | |
| 1146 | + @ <td><td>A digit. 0-9 | |
| 1147 | + @ <tr><td><td><b>\D</b> | |
| 1148 | + @ <td><td>A non-digit character | |
| 1149 | + @ <tr><td><td><b>\s</b> | |
| 1150 | + @ <td><td>A whitespace character | |
| 1151 | + @ <tr><td><td><b>\S</b> | |
| 1152 | + @ <td><td>A non-whitespace character | |
| 1153 | + @ </table> | |
| 1154 | + @ | |
| 1155 | + @ <p>In the "Pattern" column of the table above:</p> | |
| 1156 | + @ <ul> | |
| 1157 | + @ <li> "<i>X</i>" and "<i>Y</i>" mean any subpattern | |
| 1158 | + @ <li> "<i>P</i>" and "<i>Q</i>" mean integers | |
| 1159 | + @ <li> "<i>C</i>" means a single character | |
| 1160 | + @ <li> "<i>H</i>" means a hexadecimal digit | |
| 1161 | + @ <li> "<i>abc</i>" means any sequences of one or more characters | |
| 1162 | + @ <li> "<i>a-z</i>" means any single character, a single "<b>-</b>" | |
| 1163 | + @ character, and then one additional character. | |
| 1164 | + @ <li> All other symbols in the patterns are literal text | |
| 1165 | + @ </ul> | |
| 1166 | + @ | |
| 1167 | + @ <p>The "<i>X</i><b>|</b><i>Y</i>" pattern has lower precedence | |
| 1168 | + @ than the others. Use "<b>(</b>...<b>)</b>" for grouping, as | |
| 1169 | + @ necessary. | |
| 1170 | + style_finish_page(); | |
| 1171 | +} | |
| 1021 | 1172 | |
| 1022 | 1173 | ADDED src/robot.c |
| --- src/regexp.c | |
| +++ src/regexp.c | |
| @@ -53,16 +53,19 @@ | |
| 53 | ** expression and M is the size of the input string. The matcher never |
| 54 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 55 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 56 | ** regular expression in the O(N*M) performance bound is computed after |
| 57 | ** this expansion. |
| 58 | */ |
| 59 | #include "config.h" |
| 60 | #include "regexp.h" |
| 61 | |
| 62 | /* The end-of-input character */ |
| 63 | #define RE_EOF 0 /* End of input */ |
| 64 | |
| 65 | /* The NFA is implemented as sequence of opcodes taken from the following |
| 66 | ** set. Each opcode has a single integer argument. |
| 67 | */ |
| 68 | #define RE_OP_MATCH 1 /* Match the one character in the argument */ |
| @@ -80,10 +83,11 @@ | |
| 80 | #define RE_OP_DIGIT 13 /* digit: [0-9] */ |
| 81 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 82 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 83 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 84 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 85 | |
| 86 | /* Each opcode is a "state" in the NFA */ |
| 87 | typedef unsigned short ReStateNumber; |
| 88 | |
| 89 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -113,13 +117,14 @@ | |
| 113 | const char *zErr; /* Error message to return */ |
| 114 | char *aOp; /* Operators for the virtual machine */ |
| 115 | int *aArg; /* Arguments to each operator */ |
| 116 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 117 | unsigned char zInit[12]; /* Initial text to match */ |
| 118 | int nInit; /* Number of characters in zInit */ |
| 119 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 120 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 121 | }; |
| 122 | #endif |
| 123 | |
| 124 | /* Add a state to the given state set if it is not already there */ |
| 125 | static void re_add_state(ReStateSet *pSet, int newState){ |
| @@ -144,11 +149,11 @@ | |
| 144 | }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80 |
| 145 | && (p->z[p->i+1]&0xc0)==0x80 ){ |
| 146 | c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); |
| 147 | p->i += 2; |
| 148 | if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; |
| 149 | }else if( (c&0xf8)==0xf0 && p->i+3<p->mx && (p->z[p->i]&0xc0)==0x80 |
| 150 | && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ |
| 151 | c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) |
| 152 | | (p->z[p->i+2]&0x3f); |
| 153 | p->i += 3; |
| 154 | if( c<=0xffff || c>0x10ffff ) c = 0xfffd; |
| @@ -185,11 +190,11 @@ | |
| 185 | ReStateSet aStateSet[2], *pThis, *pNext; |
| 186 | ReStateNumber aSpace[100]; |
| 187 | ReStateNumber *pToFree; |
| 188 | unsigned int i = 0; |
| 189 | unsigned int iSwap = 0; |
| 190 | int c = RE_EOF+1; |
| 191 | int cPrev = 0; |
| 192 | int rc = 0; |
| 193 | ReInput in; |
| 194 | |
| 195 | in.z = zIn; |
| @@ -204,10 +209,11 @@ | |
| 204 | strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) |
| 205 | ){ |
| 206 | in.i++; |
| 207 | } |
| 208 | if( in.i+pRe->nInit>in.mx ) return 0; |
| 209 | } |
| 210 | |
| 211 | if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ |
| 212 | pToFree = 0; |
| 213 | aStateSet[0].aState = aSpace; |
| @@ -231,10 +237,14 @@ | |
| 231 | int x = pThis->aState[i]; |
| 232 | switch( pRe->aOp[x] ){ |
| 233 | case RE_OP_MATCH: { |
| 234 | if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); |
| 235 | break; |
| 236 | } |
| 237 | case RE_OP_ANY: { |
| 238 | if( c!=0 ) re_add_state(pNext, x+1); |
| 239 | break; |
| 240 | } |
| @@ -284,13 +294,13 @@ | |
| 284 | rc = 1; |
| 285 | goto re_match_end; |
| 286 | } |
| 287 | case RE_OP_CC_EXC: { |
| 288 | if( c==0 ) break; |
| 289 | /* fall-through */ |
| 290 | } |
| 291 | case RE_OP_CC_INC: { |
| 292 | int j = 1; |
| 293 | int n = pRe->aArg[x]; |
| 294 | int hit = 0; |
| 295 | for(j=1; j>0 && j<n; j++){ |
| 296 | if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){ |
| @@ -313,11 +323,13 @@ | |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | } |
| 317 | for(i=0; i<pNext->nState; i++){ |
| 318 | if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } |
| 319 | } |
| 320 | re_match_end: |
| 321 | fossil_free(pToFree); |
| 322 | return rc; |
| 323 | } |
| @@ -325,15 +337,16 @@ | |
| 325 | /* Resize the opcode and argument arrays for an RE under construction. |
| 326 | */ |
| 327 | static int re_resize(ReCompiled *p, int N){ |
| 328 | char *aOp; |
| 329 | int *aArg; |
| 330 | aOp = fossil_realloc(p->aOp, N*sizeof(p->aOp[0])); |
| 331 | if( aOp==0 ) return 1; |
| 332 | p->aOp = aOp; |
| 333 | aArg = fossil_realloc(p->aArg, N*sizeof(p->aArg[0])); |
| 334 | if( aArg==0 ) return 1; |
| 335 | p->aArg = aArg; |
| 336 | p->nAlloc = N; |
| 337 | return 0; |
| 338 | } |
| 339 | |
| @@ -468,11 +481,10 @@ | |
| 468 | const char *zErr; |
| 469 | while( (c = p->xNextChar(&p->sIn))!=0 ){ |
| 470 | iStart = p->nState; |
| 471 | switch( c ){ |
| 472 | case '|': |
| 473 | case '$': |
| 474 | case ')': { |
| 475 | p->sIn.i--; |
| 476 | return 0; |
| 477 | } |
| 478 | case '(': { |
| @@ -504,44 +516,61 @@ | |
| 504 | } |
| 505 | case '?': { |
| 506 | if( iPrev<0 ) return "'?' without operand"; |
| 507 | re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); |
| 508 | break; |
| 509 | } |
| 510 | case '{': { |
| 511 | int m = 0, n = 0; |
| 512 | int sz, j; |
| 513 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 514 | while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } |
| 515 | n = m; |
| 516 | if( c==',' ){ |
| 517 | p->sIn.i++; |
| 518 | n = 0; |
| 519 | while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } |
| 520 | } |
| 521 | if( c!='}' ) return "unmatched '{'"; |
| 522 | if( n>0 && n<m ) return "n less than m in '{m,n}'"; |
| 523 | p->sIn.i++; |
| 524 | sz = p->nState - iPrev; |
| 525 | if( m==0 ){ |
| 526 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 527 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| 528 | n--; |
| 529 | }else{ |
| 530 | for(j=1; j<m; j++) re_copy(p, iPrev, sz); |
| 531 | } |
| 532 | for(j=m; j<n; j++){ |
| 533 | re_append(p, RE_OP_FORK, sz+1); |
| 534 | re_copy(p, iPrev, sz); |
| 535 | } |
| 536 | if( n==0 && m>0 ){ |
| 537 | re_append(p, RE_OP_FORK, -sz); |
| 538 | } |
| 539 | break; |
| 540 | } |
| 541 | case '[': { |
| 542 | int iFirst = p->nState; |
| 543 | if( rePeek(p)=='^' ){ |
| 544 | re_append(p, RE_OP_CC_EXC, 0); |
| 545 | p->sIn.i++; |
| 546 | }else{ |
| 547 | re_append(p, RE_OP_CC_INC, 0); |
| @@ -561,11 +590,11 @@ | |
| 561 | re_append(p, RE_OP_CC_VALUE, c); |
| 562 | } |
| 563 | if( rePeek(p)==']' ){ p->sIn.i++; break; } |
| 564 | } |
| 565 | if( c==0 ) return "unclosed '['"; |
| 566 | p->aArg[iFirst] = p->nState - iFirst; |
| 567 | break; |
| 568 | } |
| 569 | case '\\': { |
| 570 | int specialOp = 0; |
| 571 | switch( rePeek(p) ){ |
| @@ -612,11 +641,16 @@ | |
| 612 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 613 | ** expression suitable for us by re_match() and return a pointer to the |
| 614 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 615 | ** error message if something goes wrong. |
| 616 | */ |
| 617 | const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ |
| 618 | ReCompiled *pRe; |
| 619 | const char *zErr; |
| 620 | int i, j; |
| 621 | |
| 622 | *ppRe = 0; |
| @@ -624,13 +658,15 @@ | |
| 624 | if( pRe==0 ){ |
| 625 | return "out of memory"; |
| 626 | } |
| 627 | memset(pRe, 0, sizeof(*pRe)); |
| 628 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 629 | if( re_resize(pRe, 30) ){ |
| 630 | re_free(pRe); |
| 631 | return "out of memory"; |
| 632 | } |
| 633 | if( zIn[0]=='^' ){ |
| 634 | zIn++; |
| 635 | }else{ |
| 636 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -641,15 +677,11 @@ | |
| 641 | zErr = re_subcompile_re(pRe); |
| 642 | if( zErr ){ |
| 643 | re_free(pRe); |
| 644 | return zErr; |
| 645 | } |
| 646 | if( rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){ |
| 647 | re_append(pRe, RE_OP_MATCH, RE_EOF); |
| 648 | re_append(pRe, RE_OP_ACCEPT, 0); |
| 649 | *ppRe = pRe; |
| 650 | }else if( pRe->sIn.i>=pRe->sIn.mx ){ |
| 651 | re_append(pRe, RE_OP_ACCEPT, 0); |
| 652 | *ppRe = pRe; |
| 653 | }else{ |
| 654 | re_free(pRe); |
| 655 | return "unrecognized character"; |
| @@ -658,23 +690,23 @@ | |
| 658 | /* The following is a performance optimization. If the regex begins with |
| 659 | ** ".*" (if the input regex lacks an initial "^") and afterwards there are |
| 660 | ** one or more matching characters, enter those matching characters into |
| 661 | ** zInit[]. The re_match() routine can then search ahead in the input |
| 662 | ** string looking for the initial match without having to run the whole |
| 663 | ** regex engine over the string. Do not worry able trying to match |
| 664 | ** unicode characters beyond plane 0 - those are very rare and this is |
| 665 | ** just an optimization. */ |
| 666 | if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ |
| 667 | for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ |
| 668 | unsigned x = pRe->aArg[i]; |
| 669 | if( x<=127 ){ |
| 670 | pRe->zInit[j++] = (unsigned char)x; |
| 671 | }else if( x<=0xfff ){ |
| 672 | pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); |
| 673 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 674 | }else if( x<=0xffff ){ |
| 675 | pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12)); |
| 676 | pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); |
| 677 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 678 | }else{ |
| 679 | break; |
| 680 | } |
| @@ -682,10 +714,79 @@ | |
| 682 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 683 | pRe->nInit = j; |
| 684 | } |
| 685 | return pRe->zErr; |
| 686 | } |
| 687 | |
| 688 | /* |
| 689 | ** The input zIn is a string that we want to match exactly as part of |
| 690 | ** a regular expression. Return a new string (in space obtained from |
| 691 | ** fossil_malloc() or the equivalent) that escapes all regexp syntax |
| @@ -723,71 +824,27 @@ | |
| 723 | blob_materialize(&out); |
| 724 | return out.aData; |
| 725 | } |
| 726 | |
| 727 | /* |
| 728 | ** Implementation of the regexp() SQL function. This function implements |
| 729 | ** the build-in REGEXP operator. The first argument to the function is the |
| 730 | ** pattern and the second argument is the string. So, the SQL statements: |
| 731 | ** |
| 732 | ** A REGEXP B |
| 733 | ** |
| 734 | ** is implemented as regexp(B,A). |
| 735 | */ |
| 736 | static void re_sql_func( |
| 737 | sqlite3_context *context, |
| 738 | int argc, |
| 739 | sqlite3_value **argv |
| 740 | ){ |
| 741 | ReCompiled *pRe; /* Compiled regular expression */ |
| 742 | const char *zPattern; /* The regular expression */ |
| 743 | const unsigned char *zStr;/* String being searched */ |
| 744 | const char *zErr; /* Compile error message */ |
| 745 | int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ |
| 746 | |
| 747 | (void)argc; /* Unused */ |
| 748 | pRe = sqlite3_get_auxdata(context, 0); |
| 749 | if( pRe==0 ){ |
| 750 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 751 | if( zPattern==0 ) return; |
| 752 | zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); |
| 753 | if( zErr ){ |
| 754 | re_free(pRe); |
| 755 | sqlite3_result_int(context, 0); |
| 756 | /* sqlite3_result_error(context, zErr, -1); */ |
| 757 | return; |
| 758 | } |
| 759 | if( pRe==0 ){ |
| 760 | sqlite3_result_error_nomem(context); |
| 761 | return; |
| 762 | } |
| 763 | setAux = 1; |
| 764 | } |
| 765 | zStr = (const unsigned char*)sqlite3_value_text(argv[1]); |
| 766 | if( zStr!=0 ){ |
| 767 | sqlite3_result_int(context, re_match(pRe, zStr, -1)); |
| 768 | } |
| 769 | if( setAux ){ |
| 770 | sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | /* |
| 775 | ** Invoke this routine to register the regexp() function with the |
| 776 | ** SQLite database connection. |
| 777 | */ |
| 778 | int re_add_sql_func(sqlite3 *db){ |
| 779 | int rc; |
| 780 | rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, |
| 781 | 0, re_sql_func, 0, 0); |
| 782 | if( rc==SQLITE_OK ){ |
| 783 | /* The regexpi(PATTERN,STRING) function is a case-insensitive version |
| 784 | ** of regexp(PATTERN,STRING). */ |
| 785 | rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, |
| 786 | (void*)db, re_sql_func, 0, 0); |
| 787 | } |
| 788 | return rc; |
| 789 | } |
| 790 | |
| 791 | /* |
| 792 | ** Run a "grep" over a single file read from disk. |
| 793 | */ |
| @@ -850,25 +907,38 @@ | |
| 850 | ** Run a regular expression match over the named disk files, or against |
| 851 | ** standard input if no disk files are named on the command-line. |
| 852 | ** |
| 853 | ** Options: |
| 854 | ** -i|--ignore-case Ignore case |
| 855 | */ |
| 856 | void re_test_grep(void){ |
| 857 | ReCompiled *pRe; |
| 858 | const char *zErr; |
| 859 | int ignoreCase = find_option("ignore-case","i",0)!=0; |
| 860 | if( g.argc<3 ){ |
| 861 | usage("REGEXP [FILE...]"); |
| 862 | } |
| 863 | zErr = re_compile(&pRe, g.argv[2], ignoreCase); |
| 864 | if( zErr ) fossil_fatal("%s", zErr); |
| 865 | if( g.argc==3 ){ |
| 866 | grep_file(pRe, "-", stdin); |
| 867 | }else{ |
| 868 | int i; |
| 869 | for(i=3; i<g.argc; i++){ |
| 870 | FILE *in = fossil_fopen(g.argv[i], "rb"); |
| 871 | if( in==0 ){ |
| 872 | fossil_warning("cannot open \"%s\"", g.argv[i]); |
| 873 | }else{ |
| 874 | grep_file(pRe, g.argv[i], in); |
| @@ -939,11 +1009,11 @@ | |
| 939 | db_find_and_open_repository(0, 0); |
| 940 | verify_all_options(); |
| 941 | if( g.argc<4 ){ |
| 942 | usage("REGEXP FILENAME ..."); |
| 943 | } |
| 944 | zErr = re_compile(&pRe, g.argv[2], ignoreCase); |
| 945 | if( zErr ) fossil_fatal("%s", zErr); |
| 946 | |
| 947 | add_content_sql_commands(g.db); |
| 948 | db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);"); |
| 949 | for(ii=3; ii<g.argc; ii++){ |
| @@ -1016,5 +1086,86 @@ | |
| 1016 | }else{ |
| 1017 | fossil_print("%d\n", nMatch); |
| 1018 | } |
| 1019 | } |
| 1020 | } |
| 1021 | |
| 1022 | DDED src/robot.c |
| --- src/regexp.c | |
| +++ src/regexp.c | |
| @@ -53,16 +53,19 @@ | |
| 53 | ** expression and M is the size of the input string. The matcher never |
| 54 | ** exhibits exponential behavior. Note that the X{p,q} operator expands |
| 55 | ** to p copies of X following by q-p copies of X? and that the size of the |
| 56 | ** regular expression in the O(N*M) performance bound is computed after |
| 57 | ** this expansion. |
| 58 | ** |
| 59 | ** To help prevent DoS attacks, the maximum size of the NFA is restricted. |
| 60 | */ |
| 61 | #include "config.h" |
| 62 | #include "regexp.h" |
| 63 | |
| 64 | /* The end-of-input character */ |
| 65 | #define RE_EOF 0 /* End of input */ |
| 66 | #define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ |
| 67 | |
| 68 | /* The NFA is implemented as sequence of opcodes taken from the following |
| 69 | ** set. Each opcode has a single integer argument. |
| 70 | */ |
| 71 | #define RE_OP_MATCH 1 /* Match the one character in the argument */ |
| @@ -80,10 +83,11 @@ | |
| 83 | #define RE_OP_DIGIT 13 /* digit: [0-9] */ |
| 84 | #define RE_OP_NOTDIGIT 14 /* Not a digit */ |
| 85 | #define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ |
| 86 | #define RE_OP_NOTSPACE 16 /* Not a digit */ |
| 87 | #define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ |
| 88 | #define RE_OP_ATSTART 18 /* Currently at the start of the string */ |
| 89 | |
| 90 | /* Each opcode is a "state" in the NFA */ |
| 91 | typedef unsigned short ReStateNumber; |
| 92 | |
| 93 | /* Because this is an NFA and not a DFA, multiple states can be active at |
| @@ -113,13 +117,14 @@ | |
| 117 | const char *zErr; /* Error message to return */ |
| 118 | char *aOp; /* Operators for the virtual machine */ |
| 119 | int *aArg; /* Arguments to each operator */ |
| 120 | unsigned (*xNextChar)(ReInput*); /* Next character function */ |
| 121 | unsigned char zInit[12]; /* Initial text to match */ |
| 122 | int nInit; /* Number of bytes in zInit */ |
| 123 | unsigned nState; /* Number of entries in aOp[] and aArg[] */ |
| 124 | unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ |
| 125 | unsigned mxAlloc; /* Complexity limit */ |
| 126 | }; |
| 127 | #endif |
| 128 | |
| 129 | /* Add a state to the given state set if it is not already there */ |
| 130 | static void re_add_state(ReStateSet *pSet, int newState){ |
| @@ -144,11 +149,11 @@ | |
| 149 | }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80 |
| 150 | && (p->z[p->i+1]&0xc0)==0x80 ){ |
| 151 | c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); |
| 152 | p->i += 2; |
| 153 | if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; |
| 154 | }else if( (c&0xf8)==0xf0 && p->i+2<p->mx && (p->z[p->i]&0xc0)==0x80 |
| 155 | && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ |
| 156 | c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) |
| 157 | | (p->z[p->i+2]&0x3f); |
| 158 | p->i += 3; |
| 159 | if( c<=0xffff || c>0x10ffff ) c = 0xfffd; |
| @@ -185,11 +190,11 @@ | |
| 190 | ReStateSet aStateSet[2], *pThis, *pNext; |
| 191 | ReStateNumber aSpace[100]; |
| 192 | ReStateNumber *pToFree; |
| 193 | unsigned int i = 0; |
| 194 | unsigned int iSwap = 0; |
| 195 | int c = RE_START; |
| 196 | int cPrev = 0; |
| 197 | int rc = 0; |
| 198 | ReInput in; |
| 199 | |
| 200 | in.z = zIn; |
| @@ -204,10 +209,11 @@ | |
| 209 | strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) |
| 210 | ){ |
| 211 | in.i++; |
| 212 | } |
| 213 | if( in.i+pRe->nInit>in.mx ) return 0; |
| 214 | c = RE_START-1; |
| 215 | } |
| 216 | |
| 217 | if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ |
| 218 | pToFree = 0; |
| 219 | aStateSet[0].aState = aSpace; |
| @@ -231,10 +237,14 @@ | |
| 237 | int x = pThis->aState[i]; |
| 238 | switch( pRe->aOp[x] ){ |
| 239 | case RE_OP_MATCH: { |
| 240 | if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); |
| 241 | break; |
| 242 | } |
| 243 | case RE_OP_ATSTART: { |
| 244 | if( cPrev==RE_START ) re_add_state(pThis, x+1); |
| 245 | break; |
| 246 | } |
| 247 | case RE_OP_ANY: { |
| 248 | if( c!=0 ) re_add_state(pNext, x+1); |
| 249 | break; |
| 250 | } |
| @@ -284,13 +294,13 @@ | |
| 294 | rc = 1; |
| 295 | goto re_match_end; |
| 296 | } |
| 297 | case RE_OP_CC_EXC: { |
| 298 | if( c==0 ) break; |
| 299 | /* fall-through */ goto re_op_cc_inc; |
| 300 | } |
| 301 | case RE_OP_CC_INC: re_op_cc_inc: { |
| 302 | int j = 1; |
| 303 | int n = pRe->aArg[x]; |
| 304 | int hit = 0; |
| 305 | for(j=1; j>0 && j<n; j++){ |
| 306 | if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){ |
| @@ -313,11 +323,13 @@ | |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | } |
| 327 | for(i=0; i<pNext->nState; i++){ |
| 328 | int x = pNext->aState[i]; |
| 329 | while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; |
| 330 | if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } |
| 331 | } |
| 332 | re_match_end: |
| 333 | fossil_free(pToFree); |
| 334 | return rc; |
| 335 | } |
| @@ -325,15 +337,16 @@ | |
| 337 | /* Resize the opcode and argument arrays for an RE under construction. |
| 338 | */ |
| 339 | static int re_resize(ReCompiled *p, int N){ |
| 340 | char *aOp; |
| 341 | int *aArg; |
| 342 | if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; } |
| 343 | aOp = fossil_realloc(p->aOp, N*sizeof(p->aOp[0])); |
| 344 | if( aOp==0 ){ p->zErr = "out of memory"; return 1; } |
| 345 | p->aOp = aOp; |
| 346 | aArg = fossil_realloc(p->aArg, N*sizeof(p->aArg[0])); |
| 347 | if( aArg==0 ){ p->zErr = "out of memory"; return 1; } |
| 348 | p->aArg = aArg; |
| 349 | p->nAlloc = N; |
| 350 | return 0; |
| 351 | } |
| 352 | |
| @@ -468,11 +481,10 @@ | |
| 481 | const char *zErr; |
| 482 | while( (c = p->xNextChar(&p->sIn))!=0 ){ |
| 483 | iStart = p->nState; |
| 484 | switch( c ){ |
| 485 | case '|': |
| 486 | case ')': { |
| 487 | p->sIn.i--; |
| 488 | return 0; |
| 489 | } |
| 490 | case '(': { |
| @@ -504,44 +516,61 @@ | |
| 516 | } |
| 517 | case '?': { |
| 518 | if( iPrev<0 ) return "'?' without operand"; |
| 519 | re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); |
| 520 | break; |
| 521 | } |
| 522 | case '$': { |
| 523 | re_append(p, RE_OP_MATCH, RE_EOF); |
| 524 | break; |
| 525 | } |
| 526 | case '^': { |
| 527 | re_append(p, RE_OP_ATSTART, 0); |
| 528 | break; |
| 529 | } |
| 530 | case '{': { |
| 531 | unsigned int m = 0, n = 0; |
| 532 | unsigned int sz, j; |
| 533 | if( iPrev<0 ) return "'{m,n}' without operand"; |
| 534 | while( (c=rePeek(p))>='0' && c<='9' ){ |
| 535 | m = m*10 + c - '0'; |
| 536 | if( m*2>p->mxAlloc ) return "REGEXP pattern too big"; |
| 537 | p->sIn.i++; |
| 538 | } |
| 539 | n = m; |
| 540 | if( c==',' ){ |
| 541 | p->sIn.i++; |
| 542 | n = 0; |
| 543 | while( (c=rePeek(p))>='0' && c<='9' ){ |
| 544 | n = n*10 + c-'0'; |
| 545 | if( n*2>p->mxAlloc ) return "REGEXP pattern too big"; |
| 546 | p->sIn.i++; |
| 547 | } |
| 548 | } |
| 549 | if( c!='}' ) return "unmatched '{'"; |
| 550 | if( n<m ) return "n less than m in '{m,n}'"; |
| 551 | p->sIn.i++; |
| 552 | sz = p->nState - iPrev; |
| 553 | if( m==0 ){ |
| 554 | if( n==0 ) return "both m and n are zero in '{m,n}'"; |
| 555 | re_insert(p, iPrev, RE_OP_FORK, sz+1); |
| 556 | iPrev++; |
| 557 | n--; |
| 558 | }else{ |
| 559 | for(j=1; j<m; j++) re_copy(p, iPrev, sz); |
| 560 | } |
| 561 | for(j=m; j<n; j++){ |
| 562 | re_append(p, RE_OP_FORK, sz+1); |
| 563 | re_copy(p, iPrev, sz); |
| 564 | } |
| 565 | if( n==0 && m>0 ){ |
| 566 | re_append(p, RE_OP_FORK, -(int)sz); |
| 567 | } |
| 568 | break; |
| 569 | } |
| 570 | case '[': { |
| 571 | unsigned int iFirst = p->nState; |
| 572 | if( rePeek(p)=='^' ){ |
| 573 | re_append(p, RE_OP_CC_EXC, 0); |
| 574 | p->sIn.i++; |
| 575 | }else{ |
| 576 | re_append(p, RE_OP_CC_INC, 0); |
| @@ -561,11 +590,11 @@ | |
| 590 | re_append(p, RE_OP_CC_VALUE, c); |
| 591 | } |
| 592 | if( rePeek(p)==']' ){ p->sIn.i++; break; } |
| 593 | } |
| 594 | if( c==0 ) return "unclosed '['"; |
| 595 | if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; |
| 596 | break; |
| 597 | } |
| 598 | case '\\': { |
| 599 | int specialOp = 0; |
| 600 | switch( rePeek(p) ){ |
| @@ -612,11 +641,16 @@ | |
| 641 | ** Compile a textual regular expression in zIn[] into a compiled regular |
| 642 | ** expression suitable for us by re_match() and return a pointer to the |
| 643 | ** compiled regular expression in *ppRe. Return NULL on success or an |
| 644 | ** error message if something goes wrong. |
| 645 | */ |
| 646 | static const char *re_compile( |
| 647 | ReCompiled **ppRe, /* OUT: write compiled NFA here */ |
| 648 | const char *zIn, /* Input regular expression */ |
| 649 | int mxRe, /* Complexity limit */ |
| 650 | int noCase /* True for caseless comparisons */ |
| 651 | ){ |
| 652 | ReCompiled *pRe; |
| 653 | const char *zErr; |
| 654 | int i, j; |
| 655 | |
| 656 | *ppRe = 0; |
| @@ -624,13 +658,15 @@ | |
| 658 | if( pRe==0 ){ |
| 659 | return "out of memory"; |
| 660 | } |
| 661 | memset(pRe, 0, sizeof(*pRe)); |
| 662 | pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; |
| 663 | pRe->mxAlloc = mxRe; |
| 664 | if( re_resize(pRe, 30) ){ |
| 665 | zErr = pRe->zErr; |
| 666 | re_free(pRe); |
| 667 | return zErr; |
| 668 | } |
| 669 | if( zIn[0]=='^' ){ |
| 670 | zIn++; |
| 671 | }else{ |
| 672 | re_append(pRe, RE_OP_ANYSTAR, 0); |
| @@ -641,15 +677,11 @@ | |
| 677 | zErr = re_subcompile_re(pRe); |
| 678 | if( zErr ){ |
| 679 | re_free(pRe); |
| 680 | return zErr; |
| 681 | } |
| 682 | if( pRe->sIn.i>=pRe->sIn.mx ){ |
| 683 | re_append(pRe, RE_OP_ACCEPT, 0); |
| 684 | *ppRe = pRe; |
| 685 | }else{ |
| 686 | re_free(pRe); |
| 687 | return "unrecognized character"; |
| @@ -658,23 +690,23 @@ | |
| 690 | /* The following is a performance optimization. If the regex begins with |
| 691 | ** ".*" (if the input regex lacks an initial "^") and afterwards there are |
| 692 | ** one or more matching characters, enter those matching characters into |
| 693 | ** zInit[]. The re_match() routine can then search ahead in the input |
| 694 | ** string looking for the initial match without having to run the whole |
| 695 | ** regex engine over the string. Do not worry about trying to match |
| 696 | ** unicode characters beyond plane 0 - those are very rare and this is |
| 697 | ** just an optimization. */ |
| 698 | if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ |
| 699 | for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ |
| 700 | unsigned x = pRe->aArg[i]; |
| 701 | if( x<=0x7f ){ |
| 702 | pRe->zInit[j++] = (unsigned char)x; |
| 703 | }else if( x<=0x7ff ){ |
| 704 | pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); |
| 705 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 706 | }else if( x<=0xffff ){ |
| 707 | pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); |
| 708 | pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); |
| 709 | pRe->zInit[j++] = 0x80 | (x&0x3f); |
| 710 | }else{ |
| 711 | break; |
| 712 | } |
| @@ -682,10 +714,79 @@ | |
| 714 | if( j>0 && pRe->zInit[j-1]==0 ) j--; |
| 715 | pRe->nInit = j; |
| 716 | } |
| 717 | return pRe->zErr; |
| 718 | } |
| 719 | |
| 720 | /* |
| 721 | ** Implementation of the regexp() SQL function. This function implements |
| 722 | ** the build-in REGEXP operator. The first argument to the function is the |
| 723 | ** pattern and the second argument is the string. So, the SQL statements: |
| 724 | ** |
| 725 | ** A REGEXP B |
| 726 | ** |
| 727 | ** is implemented as regexp(B,A). |
| 728 | */ |
| 729 | static void re_sql_func( |
| 730 | sqlite3_context *context, |
| 731 | int argc, |
| 732 | sqlite3_value **argv |
| 733 | ){ |
| 734 | ReCompiled *pRe; /* Compiled regular expression */ |
| 735 | const char *zPattern; /* The regular expression */ |
| 736 | const unsigned char *zStr;/* String being searched */ |
| 737 | const char *zErr; /* Compile error message */ |
| 738 | int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ |
| 739 | |
| 740 | (void)argc; /* Unused */ |
| 741 | pRe = sqlite3_get_auxdata(context, 0); |
| 742 | if( pRe==0 ){ |
| 743 | zPattern = (const char*)sqlite3_value_text(argv[0]); |
| 744 | if( zPattern==0 ) return; |
| 745 | zErr = fossil_re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); |
| 746 | if( zErr ){ |
| 747 | re_free(pRe); |
| 748 | /* The original SQLite function from which this code was copied raises |
| 749 | ** an error if the REGEXP contained a syntax error. This variant |
| 750 | ** silently fails to match, as that works better for Fossil. |
| 751 | ** sqlite3_result_error(context, zErr, -1); */ |
| 752 | sqlite3_result_int(context, 0); |
| 753 | return; |
| 754 | } |
| 755 | if( pRe==0 ){ |
| 756 | sqlite3_result_error_nomem(context); |
| 757 | return; |
| 758 | } |
| 759 | setAux = 1; |
| 760 | } |
| 761 | zStr = (const unsigned char*)sqlite3_value_text(argv[1]); |
| 762 | if( zStr!=0 ){ |
| 763 | sqlite3_result_int(context, re_match(pRe, zStr, -1)); |
| 764 | } |
| 765 | if( setAux ){ |
| 766 | sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | /* |
| 771 | ** Invoke this routine to register the regexp() function with the |
| 772 | ** SQLite database connection. |
| 773 | */ |
| 774 | int re_add_sql_func(sqlite3 *db){ |
| 775 | int rc; |
| 776 | rc = sqlite3_create_function(db, "regexp", 2, |
| 777 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, |
| 778 | 0, re_sql_func, 0, 0); |
| 779 | if( rc==SQLITE_OK ){ |
| 780 | /* The regexpi(PATTERN,STRING) function is a case-insensitive version |
| 781 | ** of regexp(PATTERN,STRING). */ |
| 782 | rc = sqlite3_create_function(db, "regexpi", 2, |
| 783 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, |
| 784 | (void*)db, re_sql_func, 0, 0); |
| 785 | } |
| 786 | return rc; |
| 787 | } |
| 788 | |
| 789 | /* |
| 790 | ** The input zIn is a string that we want to match exactly as part of |
| 791 | ** a regular expression. Return a new string (in space obtained from |
| 792 | ** fossil_malloc() or the equivalent) that escapes all regexp syntax |
| @@ -723,71 +824,27 @@ | |
| 824 | blob_materialize(&out); |
| 825 | return out.aData; |
| 826 | } |
| 827 | |
| 828 | /* |
| 829 | ** SETTING: regexp-limit width=8 default=1000 |
| 830 | ** |
| 831 | ** Limit the size of the bytecode used to implement a regular expression |
| 832 | ** to this many steps. It is important to limit this to avoid possible |
| 833 | ** DoS attacks. |
| 834 | */ |
| 835 | |
| 836 | /* |
| 837 | ** Compile an RE using re_maxlen(). |
| 838 | */ |
| 839 | const char *fossil_re_compile( |
| 840 | ReCompiled **ppRe, /* OUT: write compiled NFA here */ |
| 841 | const char *zIn, /* Input regular expression */ |
| 842 | int noCase /* True for caseless comparisons */ |
| 843 | ){ |
| 844 | int mxLen = g.db ? db_get_int("regexp-limit",1000) : 1000; |
| 845 | return re_compile(ppRe, zIn, mxLen, noCase); |
| 846 | } |
| 847 | |
| 848 | /* |
| 849 | ** Run a "grep" over a single file read from disk. |
| 850 | */ |
| @@ -850,25 +907,38 @@ | |
| 907 | ** Run a regular expression match over the named disk files, or against |
| 908 | ** standard input if no disk files are named on the command-line. |
| 909 | ** |
| 910 | ** Options: |
| 911 | ** -i|--ignore-case Ignore case |
| 912 | ** --robot-exception Use the robot-exception setting as the REGEXP |
| 913 | */ |
| 914 | void re_test_grep(void){ |
| 915 | ReCompiled *pRe; |
| 916 | const char *zErr; |
| 917 | int iFileList = 3; |
| 918 | int ignoreCase = find_option("ignore-case","i",0)!=0; |
| 919 | int bRobot = find_option("robot-exception",0,0)!=0; |
| 920 | if( bRobot ){ |
| 921 | const char *zRe; |
| 922 | db_find_and_open_repository(0,0); |
| 923 | verify_all_options(); |
| 924 | zRe = db_get("robot-exception","^$"); |
| 925 | zErr = fossil_re_compile(&pRe, zRe, ignoreCase); |
| 926 | iFileList = 2; |
| 927 | }else{ |
| 928 | verify_all_options(); |
| 929 | if( g.argc<3 ){ |
| 930 | usage("REGEXP [FILE...]"); |
| 931 | } |
| 932 | zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase); |
| 933 | } |
| 934 | if( zErr ) fossil_fatal("%s", zErr); |
| 935 | if( g.argc==iFileList ){ |
| 936 | grep_file(pRe, "-", stdin); |
| 937 | }else{ |
| 938 | int i; |
| 939 | for(i=iFileList; i<g.argc; i++){ |
| 940 | FILE *in = fossil_fopen(g.argv[i], "rb"); |
| 941 | if( in==0 ){ |
| 942 | fossil_warning("cannot open \"%s\"", g.argv[i]); |
| 943 | }else{ |
| 944 | grep_file(pRe, g.argv[i], in); |
| @@ -939,11 +1009,11 @@ | |
| 1009 | db_find_and_open_repository(0, 0); |
| 1010 | verify_all_options(); |
| 1011 | if( g.argc<4 ){ |
| 1012 | usage("REGEXP FILENAME ..."); |
| 1013 | } |
| 1014 | zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase); |
| 1015 | if( zErr ) fossil_fatal("%s", zErr); |
| 1016 | |
| 1017 | add_content_sql_commands(g.db); |
| 1018 | db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);"); |
| 1019 | for(ii=3; ii<g.argc; ii++){ |
| @@ -1016,5 +1086,86 @@ | |
| 1086 | }else{ |
| 1087 | fossil_print("%d\n", nMatch); |
| 1088 | } |
| 1089 | } |
| 1090 | } |
| 1091 | |
| 1092 | /* |
| 1093 | ** WEBPAGE: re_rules |
| 1094 | ** |
| 1095 | ** Show a summary of the regular expression matching rules for Fossil. |
| 1096 | */ |
| 1097 | void re_rules_page(void){ |
| 1098 | style_set_current_feature("wiki"); |
| 1099 | style_header("Regular Expression Syntax"); |
| 1100 | @ <p>Syntax rules for regular expression matching in Fossil:</p> |
| 1101 | @ |
| 1102 | @ <table border="0" cellpadding="0" cellspacing="0"> |
| 1103 | @ <tr><th>   <th>Pattern |
| 1104 | @ <th>   <th align="left">Match |
| 1105 | @ <tr><td><td><i>X</i><b>*</b> |
| 1106 | @ <td><td>Zero or more occurrences of <i>X</i> |
| 1107 | @ <tr><td><td><i>X</i><b>+</b> |
| 1108 | @ <td><td>One or more occurrences of <i>X</i> |
| 1109 | @ <tr><td><td><i>X</i><b>?</b> |
| 1110 | @ <td><td>Zero or one occurrences of <i>X</i> |
| 1111 | @ <tr><td><td><i>X</i><b>{</b><i>P</i><b>,</b><i>Q</i><b>}</b> |
| 1112 | @ <td><td>Between P and Q occurrences of <i>X</i> |
| 1113 | @ <tr><td><td><b>(</b><i>X</i><b>)</b> |
| 1114 | @ <td><td><i>X</i> |
| 1115 | @ <tr><td><td><i>X</i><b>|</b><i>Y</i> |
| 1116 | @ <td><td><i>X</i> or <i>Y</i> |
| 1117 | @ <tr><td><td><b>^</b><i>X</i> |
| 1118 | @ <td><td><i>X</i> at the beginning of the string |
| 1119 | @ <tr><td><td><i>X</i><b>$</b> |
| 1120 | @ <td><td><i>X</i> at the end of the string |
| 1121 | @ <tr><td><td><b>.</b> |
| 1122 | @ <td><td>Any single character |
| 1123 | @ <tr><td><td><b>\</b><i>C</i> |
| 1124 | @ <td><td>Character <i>C</i> if <i>C</i> is one of: <b>\{}()[]|*+?</b> |
| 1125 | @ <tr><td><td><b>\</b><i>C</i> |
| 1126 | @ <td><td>C-language escapes if <i>C</i> is one of: <b>afnrtv</b> |
| 1127 | @ <tr><td><td><b>\u</b><i>HHHH</i> |
| 1128 | @ <td><td>Unicode character U+HHHH where <i>HHHH</i> is four hex digits |
| 1129 | @ <tr><td><td><b>\</b><i>HH</i> |
| 1130 | @ <td><td>Unicode character U+00HH where <i>HH</i> is two hex digits |
| 1131 | @ <tr><td><td><b>[</b><i>abc</i><b>]</b> |
| 1132 | @ <td><td>Any single character from <i>abc</i> |
| 1133 | @ <tr><td><td><b>[^</b><i>abc</i><b>]</b> |
| 1134 | @ <td><td>Any single character not in <i>abc</i> |
| 1135 | @ <tr><td><td><b>[</b><i>a-z</i><b>]</b> |
| 1136 | @ <td><td>Any single character between <i>a</i> and <i>z</i>, inclusive |
| 1137 | @ <tr><td><td><b>[^</b><i>a-z</i><b>]</b> |
| 1138 | @ <td><td>Any single character not between <i>a</i> and <i>z</i> |
| 1139 | @ <tr><td><td><b>\b</b> |
| 1140 | @ <td><td>Word boundary |
| 1141 | @ <tr><td><td><b>\w</b> |
| 1142 | @ <td><td>A word character: a-zA-Z0-9 or _ |
| 1143 | @ <tr><td><td><b>\W</b> |
| 1144 | @ <td><td>A non-word character |
| 1145 | @ <tr><td><td><b>\d</b> |
| 1146 | @ <td><td>A digit. 0-9 |
| 1147 | @ <tr><td><td><b>\D</b> |
| 1148 | @ <td><td>A non-digit character |
| 1149 | @ <tr><td><td><b>\s</b> |
| 1150 | @ <td><td>A whitespace character |
| 1151 | @ <tr><td><td><b>\S</b> |
| 1152 | @ <td><td>A non-whitespace character |
| 1153 | @ </table> |
| 1154 | @ |
| 1155 | @ <p>In the "Pattern" column of the table above:</p> |
| 1156 | @ <ul> |
| 1157 | @ <li> "<i>X</i>" and "<i>Y</i>" mean any subpattern |
| 1158 | @ <li> "<i>P</i>" and "<i>Q</i>" mean integers |
| 1159 | @ <li> "<i>C</i>" means a single character |
| 1160 | @ <li> "<i>H</i>" means a hexadecimal digit |
| 1161 | @ <li> "<i>abc</i>" means any sequences of one or more characters |
| 1162 | @ <li> "<i>a-z</i>" means any single character, a single "<b>-</b>" |
| 1163 | @ character, and then one additional character. |
| 1164 | @ <li> All other symbols in the patterns are literal text |
| 1165 | @ </ul> |
| 1166 | @ |
| 1167 | @ <p>The "<i>X</i><b>|</b><i>Y</i>" pattern has lower precedence |
| 1168 | @ than the others. Use "<b>(</b>...<b>)</b>" for grouping, as |
| 1169 | @ necessary. |
| 1170 | style_finish_page(); |
| 1171 | } |
| 1172 | |
| 1173 | DDED src/robot.c |
+410
| --- a/src/robot.c | ||
| +++ b/src/robot.c | ||
| @@ -0,0 +1,410 @@ | ||
| 1 | +t to treat as human. Allow it through and also set the robot cookie. | |
| 2 | + */ | |
| 3 | + z = P("token"); | |
| 4 | + if( z!=0 ){ | |
| 5 | + if( db_exists("SELECT 1 FROM config" | |
| 6 | + " WHERE name='token-%q'" | |
| 7 | + " AND json_valid(value,6)" | |
| 8 | + " AND value->>'user' IS NOT NULL", z) | |
| 9 | + ){ | |
| 10 | + char *zVal; | |
| 11 | + robot_pow_hash(); | |
| 12 | + zVal = mprintf("%u", robot.h1); | |
| 13 | + cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900); | |
| 14 | + fossil_free(zVal); | |
| 15 | + robot.resultCache = KNOWN_NOT_ROBOT; | |
| 16 | + return 0; /* There is a valid token= query parameter */ | |
| 17 | + } | |
| 18 | + cgi_tag_query_parameter("token"); | |
| 19 | + } | |
| 20 | + | |
| 21 | + /* We have no proof that the request is coming from an interactive | |
| 22 | + ** human session, so assume the request comes from a robot. | |
| 23 | + */ | |
| 24 | + robot.resultCache = MIGHT_BE_ROBOT; | |
| 25 | + return 1; | |
| 26 | +} | |
| 27 | + | |
| 28 | +/* | |
| 29 | +** Rewrite the current page with content that attempts | |
| 30 | +** to prove that the client is not a robot. | |
| 31 | +*/ | |
| 32 | +static void ask_for_proof_that_client_is_not_robot(void){ | |
| 33 | + unsigned p1, p2, p3, p4, p5, k2, k3; | |
| 34 | + int k; | |
| 35 | + | |
| 36 | + /* Ask the client to present proof-of-work */ | |
| 37 | + cgi_reset_content(); | |
| 38 | + cgi_set_content_type("text/html"); | |
| 39 | + style_header("Browser Verification"); | |
| 40 | + @ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1> | |
| 41 | + @ <form method="GET" id="x6"><p> | |
| 42 | + @ <span id="x3" style="visibility:hidden;">\ | |
| 43 | + @ Press <input type="submit" id="x5" value="Ok" focus> to continue</span> | |
| 44 | + @ <span id="x7" style="visibility:hidden;">You appear to be a robot.</span>\ | |
| 45 | + @ </p> | |
| 46 | + if( g.zExtra && g.zExtra[0] ) cgi_tag_query_parameter("name"); | |
| 47 | + cgi_query_parameters_to_hidden(); | |
| 48 | + @ <input id="x4" type="hidden" name="proof" value="0"> | |
| 49 | + @ </form> | |
| 50 | + @ <script nonce='%s(style_nonce())'> | |
| 51 | + @ function aaa(x){return document.getElementById(x);The "diff" tagge name. The "diff" tag | |
| 52 | +* | |
| 53 | +** /vpatch. Thefdiff, and /vpatch. The | |
| 54 | +** | |
| 55 | +** also coversaise. "zip" also covers | |
| 56 | +** /tarba | |
| 57 | +** then itaracter appended then it | |
| 58 | +** only | |
| 59 | +** particularly difficult to compute. In all other case, the tag should | |
| 60 | +*** | |
| 61 | +** Usually the tag should exaUseful "X" t | |
| 62 | +** "zipX". See to disable all robot restrictions. | |
| 63 | +*/ | |
| 64 | +/* | |
| 65 | +** SETTING: robot-exception width=40 block-text | |
| 66 | +** | |
| 67 | +** The value of this setting should be a regular expression. | |
| 68 | +** If it matches the REQUEST_URI without the SCRIPT_NAME prefix | |
| 69 | +** matches this regular expression, then the request is an exception | |
| 70 | +** to anti-robot defenses and should be allowed through. For | |
| 71 | +** example, to allow robots to download tarballs or ZIP archives | |
| 72 | +** for named versions and releases, you could use an expression like | |
| 73 | +** this: | |
| 74 | +** | |
| 75 | +** ^/tarball/(version-[0-9.]+|release)/ | |
| 76 | +** | |
| 77 | +** This setting can hold multiple regular expressions, one | |
| 78 | +** regular expression per line. The input URL is exempted from | |
| 79 | +** anti-robot defenses if any of the multiple regular expressions | |
| 80 | +** matches. | |
| 81 | +*/ | |
| 82 | +/* | |
| 83 | +** SETTING: robot-zip-leaf boolean | |
| 84 | +** | |
| 85 | +** If this setting is true, the robots are allowed to download tarballs, | |
| 86 | +** ZIP-archives, and SQL-archives even though "zipX" is found in | |
| 87 | +** the [[robot-restrict]] setting as long as the specific check-in being | |
| 88 | +** downloaded is a leaf check-in. | |
| 89 | +*/ | |
| 90 | +/* | |
| 91 | +** SETTING: robot-zip-tag width=40 block-text | |
| 92 | +** | |
| 93 | +** If this setting is a list of GLOB patterns matching tags, | |
| 94 | +** then robots are allowed to download tarballs, ZIP-archives, and | |
| 95 | +** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as | |
| 96 | +** the specific check-in being downloaded has a tags that matches | |
| 97 | +** the GLOB list of this setting. Recommended value: | |
| 98 | +** "release,robot-access". | |
| 99 | +*/ | |
| 100 | + | |
| 101 | +/* | |
| 102 | +** Return the default restriction GLOB | |
| 103 | +*/ | |
| 104 | +const char *robot_restrict_default(void){ | |
| 105 | + /* NOTE: The default value is also mentioned in the online help screen of | |
| 106 | + ** the "robot-restrict" setting, and in the www/antibot.wiki document. */ | |
| 107 | + return "timelineX,diff,annotate,fileage,file,finfo,reports," | |
| 108 | + "tree,hexdump,download"; | |
| 109 | +} | |
| 110 | + | |
| 111 | +/* | |
| 112 | +** Return true if zTag matches one of the tags in the rT_URI without the SCRIPT_NAME prefix | |
| 113 | +** matches this regular expression, then the request is an exception | |
| 114 | +** to anti-robot defenses and should be allowed through. For | |
| 115 | +** example, to allow robots to download tarballs or ZIP archives | |
| 116 | +** for named versions and releases, you could]+|release)/ | |
| 117 | +** | |
| 118 | +** This setting can hold multiple regular expressions, one | |
| 119 | +** regular expression per line. The input URL is exempted from | |
| 120 | +** anti-robot defenses if any of the multiple regular expressions | |
| 121 | +** matches. | |
| 122 | +*/ | |
| 123 | +/* | |
| 124 | +** SETTING: robot-zip-leaf boolean | |
| 125 | +** | |
| 126 | +** If this setting is true, the robots are allowed to download tarballs, | |
| 127 | +** ZIP-archives, and SQL-archives even though "zipX" is found in | |
| 128 | +** the [[robot-restrict]] setting as long as the specific check-in being | |
| 129 | +** downloaded is a leaf check-in. | |
| 130 | +*/ | |
| 131 | +/* | |
| 132 | +** SETTING: robot-zip-tag width=40 block-text | |
| 133 | +** | |
| 134 | +** If this setting is a list of GLOB patterns matching tags, | |
| 135 | +** then robots are allowed to download tarballs, ZIP-archives, and | |
| 136 | +** SQL-archives even though "zipX" appears in [[ro downloaded has a tags that matches | |
| 137 | +** the GLOB list of this setting. Recommended value: | |
| 138 | +** "release,robot-access". | |
| 139 | +*/ | |
| 140 | + | |
| 141 | +/* | |
| 142 | +** Return the default restriction GLOB | |
| 143 | +*/ | |
| 144 | +const char *robot_restrict_default(void){ | |
| 145 | + /* NOTE: The default value is also mentioned in the online help screen of | |
| 146 | + ** the "robot-restrict" setting, and in the www/antibot.wiki document. */ | |
| 147 | + return "timelineX,diff,annotate,fileage,file,finfo,reports," | |
| 148 | + "tree,hexdump,download"; | |
| 149 | +} | |
| 150 | + | |
| 151 | +/* | |
| 152 | +** Return true if zTag matches one of the tags in the robot-restrict | |
| 153 | +** setting. | |
| 154 | +** | |
| 155 | +** A zTag of "*" matches anything. | |
| 156 | +*/ | |
| 157 | +static int robot_restrict_has_tag(const char *zTag){ | |
| 158 | + static const char *zGlob = 0; | |
| 159 | + if( zGlob==0 ){ | |
| 160 | + zGlob = db_get("robot-restrict",robot_restrict_default()); | |
| 161 | + if( zGlob==0 ) zGlob = ""; | |
| 162 | + } | |
| 163 | + if( zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){ | |
| 164 | + return 0; | |
| 165 | + } | |
| 166 | + if( zTag==0 || (zTag[0]=='*' && zTag[1]==0) ){ | |
| 167 | + return 1; | |
| 168 | + } | |
| 169 | + return glob_multi_match(zGlob,zTag); | |
| 170 | +} | |
| 171 | + | |
| 172 | +/* | |
| 173 | +** Check the request URI to see if it matches one of the URI | |
| 174 | +** exceptions listed in the robot-exception setting. Return true | |
| 175 | +** if it does. Return false if it does not. | |
| 176 | +** | |
| 177 | +** For the purposes of this routine, the "request URI" means | |
| 178 | +** the REQUEST_URI value with the SCRIPT_NAME prefix removed and | |
| 179 | +** with QUERY_STRING appended with a "?" separator if QUERY_STRING | |
| 180 | +** is not empty. | |
| 181 | +** | |
| 182 | +** If the robot-exception setting does not exist or is an empty | |
| 183 | +** string, then return false. | |
| 184 | +*/ | |
| 185 | +int robot_exception(void){ | |
| 186 | + const char *zRE = db_get("robot-exception",0); | |
| 187 | + const char *zQS; /* QUERY_STRING */ | |
| 188 | + const char *zURI; /* REQUEST_URI */ | |
| 189 | + const char *zSN; /* SCRIPT_NAME */ | |
| 190 | + const char *zNL; /* Next newline character */ | |
| 191 | + char *zRequest; /* REQUEST_URL w/o SCRIPT_NAME prefix + QUERY_STRING */ | |
| 192 | + int nRequest; /* Length of zRequest in bytes */ | |
| 193 | + size_t nURI, nSN; /* Length of zURI and zSN */ | |
| 194 | + int bMatch = 0; /* True if there is a match */ | |
| 195 | + | |
| 196 | + if( zRE==0 ) return 0; | |
| 197 | + if( zRE[0]==0 ) return 0; | |
| 198 | + zURI = PD("REQUEST_URI",""); | |
| 199 | + nURI = strlen(zURI); | |
| 200 | + zSN = PD("SCRIPT_NAME",""); | |
| 201 | + nSN = strlen(zSN); | |
| 202 | + if( nSN<=nURI ) zURI += nSN; | |
| 203 | + zQS = P("QUERY_STRING"); | |
| 204 | + if( zQS && zQS[0] ){ | |
| 205 | + zRequest = mprintf("%s?%s", zURI, zQS); | |
| 206 | + }else{ | |
| 207 | + zRequest = fossil_strdup(zURI); | |
| 208 | + } | |
| 209 | + nRequest = (int)strlen(zRequest); | |
| 210 | + while( zRE[0] && bMatch==0 ){ | |
| 211 | + char *z; | |
| 212 | + const char *zErr; | |
| 213 | + size_t n; | |
| 214 | + ReCompiled *pRe; | |
| 215 | + zNL = strchr(zRE,'\n'); | |
| 216 | + if( zNL ){ | |
| 217 | + n = (size_t)(zNL - zRE)+1; | |
| 218 | + while( zNL>zRE && fossil_isspace(zNL[0]) ) zNL--; | |
| 219 | + if( zNL==zRE ){ | |
| 220 | + zRE += n; | |
| 221 | + continue; | |
| 222 | + } | |
| 223 | + }else{ | |
| 224 | + n = strlen(zRE); | |
| 225 | + } | |
| 226 | + z = mprintf("%.*s", (int)(zNL - zRE)+1, zRE); | |
| 227 | + zRE += n; | |
| 228 | + zErr = fossil_re_compile(&pRe, z, 0); | |
| 229 | + if( zErr ){ | |
| 230 | + fossil_warning("robot-exception error \"%s\" in expression \"%s\"\n", | |
| 231 | + zErr, z); | |
| 232 | + fossil_free(z); | |
| 233 | + continue; | |
| 234 | + } | |
| 235 | + fossil_free(z); | |
| 236 | + bMatch = re_match(pRe, (const unsigned char*)zRequest, nRequest); | |
| 237 | + re_free(pRe); | |
| 238 | + } | |
| 239 | + fossil_free(zRequest); | |
| 240 | + return bMatch; | |
| 241 | +} | |
| 242 | + | |
| 243 | +/* | |
| 244 | +** Return true if one or more of the conditions below are true. | |
| 245 | +** Return false if all of the following are false: | |
| 246 | +** | |
| 247 | +** * The zTag is on the robot-restrict list | |
| 248 | +** | |
| 249 | +** * The client that submitted the HTTP request might be | |
| 250 | +** a robot | |
| 251 | +** | |
| 252 | +** * The Request URI does not match any of the exceptions | |
| 253 | +** in the robot-exception setting. | |
| 254 | +** | |
| 255 | +** In other words, return true if a call to robot_restrict() would | |
| 256 | +** return true and false if a call to robot_restrict() would return | |
| 257 | +** false. | |
| 258 | +** | |
| 259 | +** The difference between this routine an robot_restrict() is that | |
| 260 | +** this routine does not generate a proof-of-work captcha. This | |
| 261 | +** routine does not change the HTTP reply in any way. It simply | |
| 262 | +** returns true or false. | |
| 263 | +*/ | |
| 264 | +int robot_would_be_restricted(const char *zTag){ | |
| 265 | + if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0; | |
| 266 | + if( !robot_restrict_has_tag(zTag) ) return 0; | |
| 267 | + if( !client_might_be_a_robot() ) return 0; | |
| 268 | + if( robot_exception() ){ | |
| 269 | + robot.resultCache = KNOWN_NOT_ROBOT; | |
| 270 | + return 0; | |
| 271 | + } | |
| 272 | + return 1; | |
| 273 | +} | |
| 274 | + | |
| 275 | +/* | |
| 276 | +** Check to see if the page named in the argument is on the | |
| 277 | +** robot-restrict list. If it is on the list and if the user | |
| 278 | +** is might be a robot, then bring up a captcha to test to make | |
| 279 | +** sure that client is not a robot. | |
| 280 | +** | |
| 281 | +** This routine returns true if a captcha was rendered and if subsequent | |
| 282 | +** page generation should be aborted. It returns false if the page | |
| 283 | +** should not be restricted and should be rendered normally. | |
| 284 | +*/ | |
| 285 | +int robot_restrict(const char *zTag){ | |
| 286 | + if( robot_would_be_restricted(zTag) ){ | |
| 287 | + /* Generate the proof-of-work captcha */ | |
| 288 | + ask_for_proof_that_client_is_not_robot(); | |
| 289 | + return 1; | |
| 290 | + }else{ | |
| 291 | + return 0; | |
| 292 | + } | |
| 293 | +} | |
| 294 | + | |
| 295 | +/* | |
| 296 | +** Check to see if a robot is allowed to download a tarball, ZIP archive, | |
| 297 | +** or SQL Archive for a particular check-in identified by the "rid" | |
| 298 | +** argument. Return true to block the download. Return false to | |
| 299 | +** continue. Prior to returning true, a captcha is presented to the user. | |
| 300 | +** No output is generated when returning false. | |
| 301 | +** | |
| 302 | +** The rules: | |
| 303 | +** | |
| 304 | +** (1) If "zipX" is missing from the robot-restrict setting, then robots | |
| 305 | +** are allowed to download any archive. None of the remaining rules | |
| 306 | +** below are consulted unless "zipX" is on the robot-restrict setting. | |
| 307 | +** | |
| 308 | +** (2) If the robot-zip-leaf setting is true, then robots are allowed | |
| 309 | +** to download archives for any leaf check-in. This allows URL like | |
| 310 | +** /tarball/trunk/archive.tar.gz to work since branch labels like "trunk" | |
| 311 | +** always resolve to a leaf. | |
| 312 | +** | |
| 313 | +** (3) If the robot-zip-tag setting is a comma-separated tags, then any | |
| 314 | +** check-in that contains one of the tags on that list is allowed to | |
| 315 | +** be downloaded. This allows check-ins with tags like "release" or | |
| 316 | +** "robot-access" to be downloaded by robots. | |
| 317 | +*/ | |
| 318 | +int robot_restrict_zip(int rid){ | |
| 319 | + const char *zTag; | |
| 320 | + if( !robot_restrict_has_tag("zipX") || !client_might_be_a_robot() ){ | |
| 321 | + return 0; /* Rule (1) */ | |
| 322 | + } | |
| 323 | + | |
| 324 | + if( db_get_boolean("robot-zip-leaf",0) && is_a_leaf(rid) ){ | |
| 325 | + return 0; /* Rule (2) */ | |
| 326 | + } | |
| 327 | + | |
| 328 | + zTag = db_get("robot-zip-tag",0); | |
| 329 | + if( zTag && zTag[0] && fossil_strcmp(zTag,"off")!=0 ){ | |
| 330 | + int ok = 0; | |
| 331 | + Stmt q; | |
| 332 | + db_prepare(&q, | |
| 333 | + "SELECT substr(tagname,5) FROM tagxref, tag" | |
| 334 | + " WHERE tagxref.rid=%d" | |
| 335 | + " AND tag.tagid=tagxref.tagid" | |
| 336 | + " AND tagxref.tagtype=1" | |
| 337 | + " AND tag.tagname GLOB 'sym-*'", | |
| 338 | + rid | |
| 339 | + ); | |
| 340 | + while( !ok && db_step(&q)==SQLITE_ROW ){ | |
| 341 | + if( glob_multi_match(zTag, db_column_text(&q,0)) ) ok = 1; | |
| 342 | + } | |
| 343 | + db_finalize(&q); | |
| 344 | + if( ok ) return 0; /* Rule (3) */ | |
| 345 | + } | |
| 346 | + | |
| 347 | + /* Generate the proof-of-work captcha */ | |
| 348 | + ask_for_proof_that_client_is_not_robot(); | |
| 349 | + return 1; | |
| 350 | +} | |
| 351 | + | |
| 352 | +/* | |
| 353 | +** WEBPAGE: test-robotck | |
| 354 | +** | |
| 355 | +** Run the robot_restrict() function using the value of the "name=" | |
| 356 | +** query parameter as an argument. Used for testing the robot_restrict() | |
| 357 | +** logic. | |
| 358 | +** | |
| 359 | +** Whenever this page is successfully rendered (when it doesn't go to | |
| 360 | +** the captcha) it deletes the proof-of-work cookie. So reloading the | |
| 361 | +** page will reset the cookie and restart the verification. | |
| 362 | +** | |
| 363 | +** If the zip=CHECKIN query parameter is provided, then also invoke | |
| 364 | +** robot_restrict_archive() on the RID of CHECKIN. | |
| 365 | +*/ | |
| 366 | +void robot_restrict_test_page(void){ | |
| 367 | + const char *zName = P("name"); | |
| 368 | + const char *zZip = P("zip"); | |
| 369 | + const char *zP1 = P("proof"); | |
| 370 | + const char *zP2 = P(ROBOT_COOKIE); | |
| 371 | + const char *z; | |
| 372 | + int rid = 0; | |
| 373 | + if( zName==0 || zName[0]==0 ) zName = g.zPath; | |
| 374 | + login_check_credentials(); | |
| 375 | + if( g.zLogin==0 ){ login_needed(1); return; } | |
| 376 | + g.zLogin = 0; | |
| 377 | + if( robot_restrict(zName) ) return; | |
| 378 | + if( zZip && zZip[0] ){ | |
| 379 | + rid = symbolic_name_to_rid(zZip, "ci"); | |
| 380 | + if( rid && robot_restrict_zip(rid) ) return; | |
| 381 | + } | |
| 382 | + style_set_current_feature("test"); | |
| 383 | + style_header("robot_restrict() test"); | |
| 384 | + @ <h1>Captcha passed</h1> | |
| 385 | + @ | |
| 386 | + @ <p> | |
| 387 | + if( zP1 && zP1[0] ){ | |
| 388 | + @ proof=%h(zP1)<br> | |
| 389 | + } | |
| 390 | + if( zP2 && zP2[0] ){ | |
| 391 | + @ %h(ROBOT_COOKIE)=%h(zP2)<br> | |
| 392 | + cgi_set_cookie(ROBOT_COOKIE,"",0,-1); | |
| 393 | + } | |
| 394 | + if( zZip && zZip[0] ){ | |
| 395 | + @ zip=%h(zZip)<br> | |
| 396 | + @ rid=%d(rid)<br> | |
| 397 | + } | |
| 398 | + if( g.perm.Admin ){ | |
| 399 | + z = db_get("robot-restrict",robot_restrict_default()); | |
| 400 | + if( z && z[0] ){ | |
| 401 | + @ robot-restrict=%h(z)</br> | |
| 402 | + } | |
| 403 | + @ robot.h1=%u(robot.h1)<br> | |
| 404 | + @ robot.h2=%u(robot.h2)<br> | |
| 405 | + switch( robot.resultCache ){ | |
| 406 | + case MIGHT_BE_ROBOT: { | |
| 407 | + @ robot.resultCache=MIGHT_BE_ROBOT<br> | |
| 408 | + break; | |
| 409 | + } | |
| 410 | + ca |
| --- a/src/robot.c | |
| +++ b/src/robot.c | |
| @@ -0,0 +1,410 @@ | |
| --- a/src/robot.c | |
| +++ b/src/robot.c | |
| @@ -0,0 +1,410 @@ | |
| 1 | t to treat as human. Allow it through and also set the robot cookie. |
| 2 | */ |
| 3 | z = P("token"); |
| 4 | if( z!=0 ){ |
| 5 | if( db_exists("SELECT 1 FROM config" |
| 6 | " WHERE name='token-%q'" |
| 7 | " AND json_valid(value,6)" |
| 8 | " AND value->>'user' IS NOT NULL", z) |
| 9 | ){ |
| 10 | char *zVal; |
| 11 | robot_pow_hash(); |
| 12 | zVal = mprintf("%u", robot.h1); |
| 13 | cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900); |
| 14 | fossil_free(zVal); |
| 15 | robot.resultCache = KNOWN_NOT_ROBOT; |
| 16 | return 0; /* There is a valid token= query parameter */ |
| 17 | } |
| 18 | cgi_tag_query_parameter("token"); |
| 19 | } |
| 20 | |
| 21 | /* We have no proof that the request is coming from an interactive |
| 22 | ** human session, so assume the request comes from a robot. |
| 23 | */ |
| 24 | robot.resultCache = MIGHT_BE_ROBOT; |
| 25 | return 1; |
| 26 | } |
| 27 | |
| 28 | /* |
| 29 | ** Rewrite the current page with content that attempts |
| 30 | ** to prove that the client is not a robot. |
| 31 | */ |
| 32 | static void ask_for_proof_that_client_is_not_robot(void){ |
| 33 | unsigned p1, p2, p3, p4, p5, k2, k3; |
| 34 | int k; |
| 35 | |
| 36 | /* Ask the client to present proof-of-work */ |
| 37 | cgi_reset_content(); |
| 38 | cgi_set_content_type("text/html"); |
| 39 | style_header("Browser Verification"); |
| 40 | @ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1> |
| 41 | @ <form method="GET" id="x6"><p> |
| 42 | @ <span id="x3" style="visibility:hidden;">\ |
| 43 | @ Press <input type="submit" id="x5" value="Ok" focus> to continue</span> |
| 44 | @ <span id="x7" style="visibility:hidden;">You appear to be a robot.</span>\ |
| 45 | @ </p> |
| 46 | if( g.zExtra && g.zExtra[0] ) cgi_tag_query_parameter("name"); |
| 47 | cgi_query_parameters_to_hidden(); |
| 48 | @ <input id="x4" type="hidden" name="proof" value="0"> |
| 49 | @ </form> |
| 50 | @ <script nonce='%s(style_nonce())'> |
| 51 | @ function aaa(x){return document.getElementById(x);The "diff" tagge name. The "diff" tag |
| 52 | * |
| 53 | ** /vpatch. Thefdiff, and /vpatch. The |
| 54 | ** |
| 55 | ** also coversaise. "zip" also covers |
| 56 | ** /tarba |
| 57 | ** then itaracter appended then it |
| 58 | ** only |
| 59 | ** particularly difficult to compute. In all other case, the tag should |
| 60 | *** |
| 61 | ** Usually the tag should exaUseful "X" t |
| 62 | ** "zipX". See to disable all robot restrictions. |
| 63 | */ |
| 64 | /* |
| 65 | ** SETTING: robot-exception width=40 block-text |
| 66 | ** |
| 67 | ** The value of this setting should be a regular expression. |
| 68 | ** If it matches the REQUEST_URI without the SCRIPT_NAME prefix |
| 69 | ** matches this regular expression, then the request is an exception |
| 70 | ** to anti-robot defenses and should be allowed through. For |
| 71 | ** example, to allow robots to download tarballs or ZIP archives |
| 72 | ** for named versions and releases, you could use an expression like |
| 73 | ** this: |
| 74 | ** |
| 75 | ** ^/tarball/(version-[0-9.]+|release)/ |
| 76 | ** |
| 77 | ** This setting can hold multiple regular expressions, one |
| 78 | ** regular expression per line. The input URL is exempted from |
| 79 | ** anti-robot defenses if any of the multiple regular expressions |
| 80 | ** matches. |
| 81 | */ |
| 82 | /* |
| 83 | ** SETTING: robot-zip-leaf boolean |
| 84 | ** |
| 85 | ** If this setting is true, the robots are allowed to download tarballs, |
| 86 | ** ZIP-archives, and SQL-archives even though "zipX" is found in |
| 87 | ** the [[robot-restrict]] setting as long as the specific check-in being |
| 88 | ** downloaded is a leaf check-in. |
| 89 | */ |
| 90 | /* |
| 91 | ** SETTING: robot-zip-tag width=40 block-text |
| 92 | ** |
| 93 | ** If this setting is a list of GLOB patterns matching tags, |
| 94 | ** then robots are allowed to download tarballs, ZIP-archives, and |
| 95 | ** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as |
| 96 | ** the specific check-in being downloaded has a tags that matches |
| 97 | ** the GLOB list of this setting. Recommended value: |
| 98 | ** "release,robot-access". |
| 99 | */ |
| 100 | |
| 101 | /* |
| 102 | ** Return the default restriction GLOB |
| 103 | */ |
| 104 | const char *robot_restrict_default(void){ |
| 105 | /* NOTE: The default value is also mentioned in the online help screen of |
| 106 | ** the "robot-restrict" setting, and in the www/antibot.wiki document. */ |
| 107 | return "timelineX,diff,annotate,fileage,file,finfo,reports," |
| 108 | "tree,hexdump,download"; |
| 109 | } |
| 110 | |
| 111 | /* |
| 112 | ** Return true if zTag matches one of the tags in the rT_URI without the SCRIPT_NAME prefix |
| 113 | ** matches this regular expression, then the request is an exception |
| 114 | ** to anti-robot defenses and should be allowed through. For |
| 115 | ** example, to allow robots to download tarballs or ZIP archives |
| 116 | ** for named versions and releases, you could]+|release)/ |
| 117 | ** |
| 118 | ** This setting can hold multiple regular expressions, one |
| 119 | ** regular expression per line. The input URL is exempted from |
| 120 | ** anti-robot defenses if any of the multiple regular expressions |
| 121 | ** matches. |
| 122 | */ |
| 123 | /* |
| 124 | ** SETTING: robot-zip-leaf boolean |
| 125 | ** |
| 126 | ** If this setting is true, the robots are allowed to download tarballs, |
| 127 | ** ZIP-archives, and SQL-archives even though "zipX" is found in |
| 128 | ** the [[robot-restrict]] setting as long as the specific check-in being |
| 129 | ** downloaded is a leaf check-in. |
| 130 | */ |
| 131 | /* |
| 132 | ** SETTING: robot-zip-tag width=40 block-text |
| 133 | ** |
| 134 | ** If this setting is a list of GLOB patterns matching tags, |
| 135 | ** then robots are allowed to download tarballs, ZIP-archives, and |
| 136 | ** SQL-archives even though "zipX" appears in [[ro downloaded has a tags that matches |
| 137 | ** the GLOB list of this setting. Recommended value: |
| 138 | ** "release,robot-access". |
| 139 | */ |
| 140 | |
| 141 | /* |
| 142 | ** Return the default restriction GLOB |
| 143 | */ |
| 144 | const char *robot_restrict_default(void){ |
| 145 | /* NOTE: The default value is also mentioned in the online help screen of |
| 146 | ** the "robot-restrict" setting, and in the www/antibot.wiki document. */ |
| 147 | return "timelineX,diff,annotate,fileage,file,finfo,reports," |
| 148 | "tree,hexdump,download"; |
| 149 | } |
| 150 | |
| 151 | /* |
| 152 | ** Return true if zTag matches one of the tags in the robot-restrict |
| 153 | ** setting. |
| 154 | ** |
| 155 | ** A zTag of "*" matches anything. |
| 156 | */ |
| 157 | static int robot_restrict_has_tag(const char *zTag){ |
| 158 | static const char *zGlob = 0; |
| 159 | if( zGlob==0 ){ |
| 160 | zGlob = db_get("robot-restrict",robot_restrict_default()); |
| 161 | if( zGlob==0 ) zGlob = ""; |
| 162 | } |
| 163 | if( zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){ |
| 164 | return 0; |
| 165 | } |
| 166 | if( zTag==0 || (zTag[0]=='*' && zTag[1]==0) ){ |
| 167 | return 1; |
| 168 | } |
| 169 | return glob_multi_match(zGlob,zTag); |
| 170 | } |
| 171 | |
| 172 | /* |
| 173 | ** Check the request URI to see if it matches one of the URI |
| 174 | ** exceptions listed in the robot-exception setting. Return true |
| 175 | ** if it does. Return false if it does not. |
| 176 | ** |
| 177 | ** For the purposes of this routine, the "request URI" means |
| 178 | ** the REQUEST_URI value with the SCRIPT_NAME prefix removed and |
| 179 | ** with QUERY_STRING appended with a "?" separator if QUERY_STRING |
| 180 | ** is not empty. |
| 181 | ** |
| 182 | ** If the robot-exception setting does not exist or is an empty |
| 183 | ** string, then return false. |
| 184 | */ |
| 185 | int robot_exception(void){ |
| 186 | const char *zRE = db_get("robot-exception",0); |
| 187 | const char *zQS; /* QUERY_STRING */ |
| 188 | const char *zURI; /* REQUEST_URI */ |
| 189 | const char *zSN; /* SCRIPT_NAME */ |
| 190 | const char *zNL; /* Next newline character */ |
| 191 | char *zRequest; /* REQUEST_URL w/o SCRIPT_NAME prefix + QUERY_STRING */ |
| 192 | int nRequest; /* Length of zRequest in bytes */ |
| 193 | size_t nURI, nSN; /* Length of zURI and zSN */ |
| 194 | int bMatch = 0; /* True if there is a match */ |
| 195 | |
| 196 | if( zRE==0 ) return 0; |
| 197 | if( zRE[0]==0 ) return 0; |
| 198 | zURI = PD("REQUEST_URI",""); |
| 199 | nURI = strlen(zURI); |
| 200 | zSN = PD("SCRIPT_NAME",""); |
| 201 | nSN = strlen(zSN); |
| 202 | if( nSN<=nURI ) zURI += nSN; |
| 203 | zQS = P("QUERY_STRING"); |
| 204 | if( zQS && zQS[0] ){ |
| 205 | zRequest = mprintf("%s?%s", zURI, zQS); |
| 206 | }else{ |
| 207 | zRequest = fossil_strdup(zURI); |
| 208 | } |
| 209 | nRequest = (int)strlen(zRequest); |
| 210 | while( zRE[0] && bMatch==0 ){ |
| 211 | char *z; |
| 212 | const char *zErr; |
| 213 | size_t n; |
| 214 | ReCompiled *pRe; |
| 215 | zNL = strchr(zRE,'\n'); |
| 216 | if( zNL ){ |
| 217 | n = (size_t)(zNL - zRE)+1; |
| 218 | while( zNL>zRE && fossil_isspace(zNL[0]) ) zNL--; |
| 219 | if( zNL==zRE ){ |
| 220 | zRE += n; |
| 221 | continue; |
| 222 | } |
| 223 | }else{ |
| 224 | n = strlen(zRE); |
| 225 | } |
| 226 | z = mprintf("%.*s", (int)(zNL - zRE)+1, zRE); |
| 227 | zRE += n; |
| 228 | zErr = fossil_re_compile(&pRe, z, 0); |
| 229 | if( zErr ){ |
| 230 | fossil_warning("robot-exception error \"%s\" in expression \"%s\"\n", |
| 231 | zErr, z); |
| 232 | fossil_free(z); |
| 233 | continue; |
| 234 | } |
| 235 | fossil_free(z); |
| 236 | bMatch = re_match(pRe, (const unsigned char*)zRequest, nRequest); |
| 237 | re_free(pRe); |
| 238 | } |
| 239 | fossil_free(zRequest); |
| 240 | return bMatch; |
| 241 | } |
| 242 | |
| 243 | /* |
| 244 | ** Return true if one or more of the conditions below are true. |
| 245 | ** Return false if all of the following are false: |
| 246 | ** |
| 247 | ** * The zTag is on the robot-restrict list |
| 248 | ** |
| 249 | ** * The client that submitted the HTTP request might be |
| 250 | ** a robot |
| 251 | ** |
| 252 | ** * The Request URI does not match any of the exceptions |
| 253 | ** in the robot-exception setting. |
| 254 | ** |
| 255 | ** In other words, return true if a call to robot_restrict() would |
| 256 | ** return true and false if a call to robot_restrict() would return |
| 257 | ** false. |
| 258 | ** |
| 259 | ** The difference between this routine an robot_restrict() is that |
| 260 | ** this routine does not generate a proof-of-work captcha. This |
| 261 | ** routine does not change the HTTP reply in any way. It simply |
| 262 | ** returns true or false. |
| 263 | */ |
| 264 | int robot_would_be_restricted(const char *zTag){ |
| 265 | if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0; |
| 266 | if( !robot_restrict_has_tag(zTag) ) return 0; |
| 267 | if( !client_might_be_a_robot() ) return 0; |
| 268 | if( robot_exception() ){ |
| 269 | robot.resultCache = KNOWN_NOT_ROBOT; |
| 270 | return 0; |
| 271 | } |
| 272 | return 1; |
| 273 | } |
| 274 | |
| 275 | /* |
| 276 | ** Check to see if the page named in the argument is on the |
| 277 | ** robot-restrict list. If it is on the list and if the user |
| 278 | ** is might be a robot, then bring up a captcha to test to make |
| 279 | ** sure that client is not a robot. |
| 280 | ** |
| 281 | ** This routine returns true if a captcha was rendered and if subsequent |
| 282 | ** page generation should be aborted. It returns false if the page |
| 283 | ** should not be restricted and should be rendered normally. |
| 284 | */ |
| 285 | int robot_restrict(const char *zTag){ |
| 286 | if( robot_would_be_restricted(zTag) ){ |
| 287 | /* Generate the proof-of-work captcha */ |
| 288 | ask_for_proof_that_client_is_not_robot(); |
| 289 | return 1; |
| 290 | }else{ |
| 291 | return 0; |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /* |
| 296 | ** Check to see if a robot is allowed to download a tarball, ZIP archive, |
| 297 | ** or SQL Archive for a particular check-in identified by the "rid" |
| 298 | ** argument. Return true to block the download. Return false to |
| 299 | ** continue. Prior to returning true, a captcha is presented to the user. |
| 300 | ** No output is generated when returning false. |
| 301 | ** |
| 302 | ** The rules: |
| 303 | ** |
| 304 | ** (1) If "zipX" is missing from the robot-restrict setting, then robots |
| 305 | ** are allowed to download any archive. None of the remaining rules |
| 306 | ** below are consulted unless "zipX" is on the robot-restrict setting. |
| 307 | ** |
| 308 | ** (2) If the robot-zip-leaf setting is true, then robots are allowed |
| 309 | ** to download archives for any leaf check-in. This allows URL like |
| 310 | ** /tarball/trunk/archive.tar.gz to work since branch labels like "trunk" |
| 311 | ** always resolve to a leaf. |
| 312 | ** |
| 313 | ** (3) If the robot-zip-tag setting is a comma-separated tags, then any |
| 314 | ** check-in that contains one of the tags on that list is allowed to |
| 315 | ** be downloaded. This allows check-ins with tags like "release" or |
| 316 | ** "robot-access" to be downloaded by robots. |
| 317 | */ |
| 318 | int robot_restrict_zip(int rid){ |
| 319 | const char *zTag; |
| 320 | if( !robot_restrict_has_tag("zipX") || !client_might_be_a_robot() ){ |
| 321 | return 0; /* Rule (1) */ |
| 322 | } |
| 323 | |
| 324 | if( db_get_boolean("robot-zip-leaf",0) && is_a_leaf(rid) ){ |
| 325 | return 0; /* Rule (2) */ |
| 326 | } |
| 327 | |
| 328 | zTag = db_get("robot-zip-tag",0); |
| 329 | if( zTag && zTag[0] && fossil_strcmp(zTag,"off")!=0 ){ |
| 330 | int ok = 0; |
| 331 | Stmt q; |
| 332 | db_prepare(&q, |
| 333 | "SELECT substr(tagname,5) FROM tagxref, tag" |
| 334 | " WHERE tagxref.rid=%d" |
| 335 | " AND tag.tagid=tagxref.tagid" |
| 336 | " AND tagxref.tagtype=1" |
| 337 | " AND tag.tagname GLOB 'sym-*'", |
| 338 | rid |
| 339 | ); |
| 340 | while( !ok && db_step(&q)==SQLITE_ROW ){ |
| 341 | if( glob_multi_match(zTag, db_column_text(&q,0)) ) ok = 1; |
| 342 | } |
| 343 | db_finalize(&q); |
| 344 | if( ok ) return 0; /* Rule (3) */ |
| 345 | } |
| 346 | |
| 347 | /* Generate the proof-of-work captcha */ |
| 348 | ask_for_proof_that_client_is_not_robot(); |
| 349 | return 1; |
| 350 | } |
| 351 | |
| 352 | /* |
| 353 | ** WEBPAGE: test-robotck |
| 354 | ** |
| 355 | ** Run the robot_restrict() function using the value of the "name=" |
| 356 | ** query parameter as an argument. Used for testing the robot_restrict() |
| 357 | ** logic. |
| 358 | ** |
| 359 | ** Whenever this page is successfully rendered (when it doesn't go to |
| 360 | ** the captcha) it deletes the proof-of-work cookie. So reloading the |
| 361 | ** page will reset the cookie and restart the verification. |
| 362 | ** |
| 363 | ** If the zip=CHECKIN query parameter is provided, then also invoke |
| 364 | ** robot_restrict_archive() on the RID of CHECKIN. |
| 365 | */ |
| 366 | void robot_restrict_test_page(void){ |
| 367 | const char *zName = P("name"); |
| 368 | const char *zZip = P("zip"); |
| 369 | const char *zP1 = P("proof"); |
| 370 | const char *zP2 = P(ROBOT_COOKIE); |
| 371 | const char *z; |
| 372 | int rid = 0; |
| 373 | if( zName==0 || zName[0]==0 ) zName = g.zPath; |
| 374 | login_check_credentials(); |
| 375 | if( g.zLogin==0 ){ login_needed(1); return; } |
| 376 | g.zLogin = 0; |
| 377 | if( robot_restrict(zName) ) return; |
| 378 | if( zZip && zZip[0] ){ |
| 379 | rid = symbolic_name_to_rid(zZip, "ci"); |
| 380 | if( rid && robot_restrict_zip(rid) ) return; |
| 381 | } |
| 382 | style_set_current_feature("test"); |
| 383 | style_header("robot_restrict() test"); |
| 384 | @ <h1>Captcha passed</h1> |
| 385 | @ |
| 386 | @ <p> |
| 387 | if( zP1 && zP1[0] ){ |
| 388 | @ proof=%h(zP1)<br> |
| 389 | } |
| 390 | if( zP2 && zP2[0] ){ |
| 391 | @ %h(ROBOT_COOKIE)=%h(zP2)<br> |
| 392 | cgi_set_cookie(ROBOT_COOKIE,"",0,-1); |
| 393 | } |
| 394 | if( zZip && zZip[0] ){ |
| 395 | @ zip=%h(zZip)<br> |
| 396 | @ rid=%d(rid)<br> |
| 397 | } |
| 398 | if( g.perm.Admin ){ |
| 399 | z = db_get("robot-restrict",robot_restrict_default()); |
| 400 | if( z && z[0] ){ |
| 401 | @ robot-restrict=%h(z)</br> |
| 402 | } |
| 403 | @ robot.h1=%u(robot.h1)<br> |
| 404 | @ robot.h2=%u(robot.h2)<br> |
| 405 | switch( robot.resultCache ){ |
| 406 | case MIGHT_BE_ROBOT: { |
| 407 | @ robot.resultCache=MIGHT_BE_ROBOT<br> |
| 408 | break; |
| 409 | } |
| 410 | ca |
+6
-6
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -130,14 +130,14 @@ | ||
| 130 | 130 | search_end(p); |
| 131 | 131 | }else{ |
| 132 | 132 | p = fossil_malloc(sizeof(*p)); |
| 133 | 133 | memset(p, 0, sizeof(*p)); |
| 134 | 134 | } |
| 135 | - p->zPattern = z = fossil_strdup(zPattern); | |
| 136 | - p->zMarkBegin = fossil_strdup(zMarkBegin); | |
| 137 | - p->zMarkEnd = fossil_strdup(zMarkEnd); | |
| 138 | - p->zMarkGap = fossil_strdup(zMarkGap); | |
| 135 | + p->zPattern = z = mprintf("%s",zPattern); | |
| 136 | + p->zMarkBegin = mprintf("%s",zMarkBegin); | |
| 137 | + p->zMarkEnd = mprintf("%s",zMarkEnd); | |
| 138 | + p->zMarkGap = mprintf("%s",zMarkGap); | |
| 139 | 139 | p->fSrchFlg = fSrchFlg; |
| 140 | 140 | blob_init(&p->snip, 0, 0); |
| 141 | 141 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 142 | 142 | while( *z && !ISALNUM(*z) ){ z++; } |
| 143 | 143 | if( *z==0 ) break; |
| @@ -983,11 +983,11 @@ | ||
| 983 | 983 | zPrefix = "Built-in help for the"; |
| 984 | 984 | } |
| 985 | 985 | db_multi_exec( |
| 986 | 986 | "INSERT INTO x(label,url,score,id,snip)" |
| 987 | 987 | " SELECT format('%q \"%%s\" %%s',name,type)," |
| 988 | - " '/help?cmd='||name," | |
| 988 | + " '/help/'||name," | |
| 989 | 989 | " search_score()," |
| 990 | 990 | " 'h'||rowid," |
| 991 | 991 | " search_snippet()" |
| 992 | 992 | " FROM helptext" |
| 993 | 993 | " WHERE search_match(format('the \"%%s\" %%s',name,type)," |
| @@ -1076,11 +1076,11 @@ | ||
| 1076 | 1076 | ** causing errors in FTS5 searches with inputs which contain AND, OR, |
| 1077 | 1077 | ** and symbols like #. The caller is responsible for passing the |
| 1078 | 1078 | ** result to fossil_free(). |
| 1079 | 1079 | */ |
| 1080 | 1080 | char *search_simplify_pattern(const char * zPattern){ |
| 1081 | - char *zPat = fossil_strdup(zPattern); | |
| 1081 | + char *zPat = mprintf("%s",zPattern); | |
| 1082 | 1082 | int i; |
| 1083 | 1083 | for(i=0; zPat[i]; i++){ |
| 1084 | 1084 | if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; |
| 1085 | 1085 | if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); |
| 1086 | 1086 | } |
| 1087 | 1087 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -130,14 +130,14 @@ | |
| 130 | search_end(p); |
| 131 | }else{ |
| 132 | p = fossil_malloc(sizeof(*p)); |
| 133 | memset(p, 0, sizeof(*p)); |
| 134 | } |
| 135 | p->zPattern = z = fossil_strdup(zPattern); |
| 136 | p->zMarkBegin = fossil_strdup(zMarkBegin); |
| 137 | p->zMarkEnd = fossil_strdup(zMarkEnd); |
| 138 | p->zMarkGap = fossil_strdup(zMarkGap); |
| 139 | p->fSrchFlg = fSrchFlg; |
| 140 | blob_init(&p->snip, 0, 0); |
| 141 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 142 | while( *z && !ISALNUM(*z) ){ z++; } |
| 143 | if( *z==0 ) break; |
| @@ -983,11 +983,11 @@ | |
| 983 | zPrefix = "Built-in help for the"; |
| 984 | } |
| 985 | db_multi_exec( |
| 986 | "INSERT INTO x(label,url,score,id,snip)" |
| 987 | " SELECT format('%q \"%%s\" %%s',name,type)," |
| 988 | " '/help?cmd='||name," |
| 989 | " search_score()," |
| 990 | " 'h'||rowid," |
| 991 | " search_snippet()" |
| 992 | " FROM helptext" |
| 993 | " WHERE search_match(format('the \"%%s\" %%s',name,type)," |
| @@ -1076,11 +1076,11 @@ | |
| 1076 | ** causing errors in FTS5 searches with inputs which contain AND, OR, |
| 1077 | ** and symbols like #. The caller is responsible for passing the |
| 1078 | ** result to fossil_free(). |
| 1079 | */ |
| 1080 | char *search_simplify_pattern(const char * zPattern){ |
| 1081 | char *zPat = fossil_strdup(zPattern); |
| 1082 | int i; |
| 1083 | for(i=0; zPat[i]; i++){ |
| 1084 | if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; |
| 1085 | if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); |
| 1086 | } |
| 1087 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -130,14 +130,14 @@ | |
| 130 | search_end(p); |
| 131 | }else{ |
| 132 | p = fossil_malloc(sizeof(*p)); |
| 133 | memset(p, 0, sizeof(*p)); |
| 134 | } |
| 135 | p->zPattern = z = mprintf("%s",zPattern); |
| 136 | p->zMarkBegin = mprintf("%s",zMarkBegin); |
| 137 | p->zMarkEnd = mprintf("%s",zMarkEnd); |
| 138 | p->zMarkGap = mprintf("%s",zMarkGap); |
| 139 | p->fSrchFlg = fSrchFlg; |
| 140 | blob_init(&p->snip, 0, 0); |
| 141 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 142 | while( *z && !ISALNUM(*z) ){ z++; } |
| 143 | if( *z==0 ) break; |
| @@ -983,11 +983,11 @@ | |
| 983 | zPrefix = "Built-in help for the"; |
| 984 | } |
| 985 | db_multi_exec( |
| 986 | "INSERT INTO x(label,url,score,id,snip)" |
| 987 | " SELECT format('%q \"%%s\" %%s',name,type)," |
| 988 | " '/help/'||name," |
| 989 | " search_score()," |
| 990 | " 'h'||rowid," |
| 991 | " search_snippet()" |
| 992 | " FROM helptext" |
| 993 | " WHERE search_match(format('the \"%%s\" %%s',name,type)," |
| @@ -1076,11 +1076,11 @@ | |
| 1076 | ** causing errors in FTS5 searches with inputs which contain AND, OR, |
| 1077 | ** and symbols like #. The caller is responsible for passing the |
| 1078 | ** result to fossil_free(). |
| 1079 | */ |
| 1080 | char *search_simplify_pattern(const char * zPattern){ |
| 1081 | char *zPat = mprintf("%s",zPattern); |
| 1082 | int i; |
| 1083 | for(i=0; zPat[i]; i++){ |
| 1084 | if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; |
| 1085 | if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); |
| 1086 | } |
| 1087 |
+4
-2
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -369,11 +369,11 @@ | ||
| 369 | 369 | zVulnReport = db_get("vuln-report","log"); |
| 370 | 370 | if( fossil_strcmp(zVulnReport,"block")!=0 |
| 371 | 371 | && fossil_strcmp(zVulnReport,"fatal")!=0 |
| 372 | 372 | ){ |
| 373 | 373 | @ <li><p><b>WARNING:</b> |
| 374 | - @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a> | |
| 374 | + @ The <a href="%R/help/vuln-report">vuln-report setting</a> | |
| 375 | 375 | @ has a value of "%h(zVulnReport)". This disables defenses against |
| 376 | 376 | @ XSS or SQL-injection vulnerabilities caused by coding errors in |
| 377 | 377 | @ custom TH1 scripts. For the best security, change |
| 378 | 378 | @ the value of the vuln-report setting to "block" or "fatal". |
| 379 | 379 | } |
| @@ -954,11 +954,13 @@ | ||
| 954 | 954 | if( prevWasTime ){ |
| 955 | 955 | if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){ |
| 956 | 956 | bOutput = (eType & 0x01)!=0; |
| 957 | 957 | nHack++; |
| 958 | 958 | }else |
| 959 | - if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){ | |
| 959 | + if( (strncmp(z,"panic: ", 7)==0 && strncmp(z+7,"Timeout",7)!=0) | |
| 960 | + || strstr(z," assertion fault ")!=0 | |
| 961 | + ){ | |
| 960 | 962 | bOutput = (eType & 0x02)!=0; |
| 961 | 963 | nPanic++; |
| 962 | 964 | }else |
| 963 | 965 | if( strncmp(z,"SMTP:", 5)==0 ){ |
| 964 | 966 | bOutput = (eType & 0x20)!=0; |
| 965 | 967 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -369,11 +369,11 @@ | |
| 369 | zVulnReport = db_get("vuln-report","log"); |
| 370 | if( fossil_strcmp(zVulnReport,"block")!=0 |
| 371 | && fossil_strcmp(zVulnReport,"fatal")!=0 |
| 372 | ){ |
| 373 | @ <li><p><b>WARNING:</b> |
| 374 | @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a> |
| 375 | @ has a value of "%h(zVulnReport)". This disables defenses against |
| 376 | @ XSS or SQL-injection vulnerabilities caused by coding errors in |
| 377 | @ custom TH1 scripts. For the best security, change |
| 378 | @ the value of the vuln-report setting to "block" or "fatal". |
| 379 | } |
| @@ -954,11 +954,13 @@ | |
| 954 | if( prevWasTime ){ |
| 955 | if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){ |
| 956 | bOutput = (eType & 0x01)!=0; |
| 957 | nHack++; |
| 958 | }else |
| 959 | if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){ |
| 960 | bOutput = (eType & 0x02)!=0; |
| 961 | nPanic++; |
| 962 | }else |
| 963 | if( strncmp(z,"SMTP:", 5)==0 ){ |
| 964 | bOutput = (eType & 0x20)!=0; |
| 965 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -369,11 +369,11 @@ | |
| 369 | zVulnReport = db_get("vuln-report","log"); |
| 370 | if( fossil_strcmp(zVulnReport,"block")!=0 |
| 371 | && fossil_strcmp(zVulnReport,"fatal")!=0 |
| 372 | ){ |
| 373 | @ <li><p><b>WARNING:</b> |
| 374 | @ The <a href="%R/help/vuln-report">vuln-report setting</a> |
| 375 | @ has a value of "%h(zVulnReport)". This disables defenses against |
| 376 | @ XSS or SQL-injection vulnerabilities caused by coding errors in |
| 377 | @ custom TH1 scripts. For the best security, change |
| 378 | @ the value of the vuln-report setting to "block" or "fatal". |
| 379 | } |
| @@ -954,11 +954,13 @@ | |
| 954 | if( prevWasTime ){ |
| 955 | if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){ |
| 956 | bOutput = (eType & 0x01)!=0; |
| 957 | nHack++; |
| 958 | }else |
| 959 | if( (strncmp(z,"panic: ", 7)==0 && strncmp(z+7,"Timeout",7)!=0) |
| 960 | || strstr(z," assertion fault ")!=0 |
| 961 | ){ |
| 962 | bOutput = (eType & 0x02)!=0; |
| 963 | nPanic++; |
| 964 | }else |
| 965 | if( strncmp(z,"SMTP:", 5)==0 ){ |
| 966 | bOutput = (eType & 0x20)!=0; |
| 967 |
+156
-85
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -118,10 +118,12 @@ | ||
| 118 | 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | 120 | } |
| 121 | 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | 122 | "Timeline display preferences"); |
| 123 | + setup_menu_entry("Tarballs and ZIPs", "setup_download", | |
| 124 | + "Preferences for auto-generated tarballs and ZIP files"); | |
| 123 | 125 | if( setup_user ){ |
| 124 | 126 | setup_menu_entry("Login-Group", "setup_login_group", |
| 125 | 127 | "Manage single sign-on between this repository and others" |
| 126 | 128 | " on the same server"); |
| 127 | 129 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | ||
| 140 | 142 | setup_menu_entry("URL Aliases", "waliassetup", |
| 141 | 143 | "Configure URL aliases"); |
| 142 | 144 | if( setup_user ){ |
| 143 | 145 | setup_menu_entry("Notification", "setup_notification", |
| 144 | 146 | "Automatic notifications of changes via outbound email"); |
| 147 | +#if 0 /* Disabled for now. Does this even work? */ | |
| 145 | 148 | setup_menu_entry("Transfers", "xfersetup", |
| 146 | 149 | "Configure the transfer system for this repository"); |
| 150 | +#endif | |
| 147 | 151 | } |
| 148 | 152 | setup_menu_entry("Skins", "setup_skin_admin", |
| 149 | 153 | "Select and/or modify the web interface \"skins\""); |
| 150 | 154 | setup_menu_entry("Moderation", "setup_modreq", |
| 151 | 155 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -421,56 +425,38 @@ | ||
| 421 | 425 | }; |
| 422 | 426 | multiple_choice_attribute( |
| 423 | 427 | "Enable hyperlinks base on User-Agent and/or Javascript", |
| 424 | 428 | "auto-hyperlink", "autohyperlink", "1", |
| 425 | 429 | count(azDefenseOpts)/2, azDefenseOpts); |
| 426 | - @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users, | |
| 427 | - @ including user "nobody", as long as the User-Agent string in the | |
| 428 | - @ HTTP header indicates that the request is coming from an actual human | |
| 429 | - @ being. If this setting is "UserAgent only" (2) then the | |
| 430 | - @ UserAgent string is the only factor considered. If the value of this | |
| 431 | - @ setting is "UserAgent And Javascript" (1) then Javascript is added that | |
| 432 | - @ runs after the page loads and fills in the href= values of <a> | |
| 433 | - @ elements. In either case, <a> tags are only generated if the | |
| 434 | - @ UserAgent string indicates that the request is coming from a human and | |
| 435 | - @ not a robot. | |
| 436 | - @ | |
| 437 | - @ <p>This setting is designed to give easy access to humans while | |
| 438 | - @ keeping out robots. | |
| 439 | - @ You do not normally want a robot to walk your entire repository because | |
| 440 | - @ if it does, your server will end up computing diffs and annotations for | |
| 441 | - @ every historical version of every file and creating ZIPs and tarballs of | |
| 442 | - @ every historical check-in, which can use a lot of CPU and bandwidth | |
| 443 | - @ even for relatively small projects.</p> | |
| 444 | - @ | |
| 445 | - @ <p>The "UserAgent and Javascript" value for this setting provides | |
| 446 | - @ superior protection from robots. However, that setting also prevents | |
| 447 | - @ the visited/unvisited colors on hyperlinks from displaying correctly | |
| 448 | - @ on Safari-derived browsers. (Chrome and Firefox work fine.) Since | |
| 449 | - @ Safari is the underlying rendering engine on all iPhones and iPads, | |
| 450 | - @ this means that hyperlink visited/unvisited colors will not operate | |
| 451 | - @ on those platforms when "UserAgent and Javascript" is selected.</p> | |
| 452 | - @ | |
| 453 | - @ <p>Additional parameters that control the behavior of Javascript:</p> | |
| 454 | - @ <blockquote> | |
| 430 | + @ <br> | |
| 455 | 431 | entry_attribute("Delay in milliseconds before enabling hyperlinks", 5, |
| 456 | 432 | "auto-hyperlink-delay", "ah-delay", "50", 0); |
| 457 | 433 | @ <br> |
| 458 | 434 | onoff_attribute("Also require a mouse event before enabling hyperlinks", |
| 459 | 435 | "auto-hyperlink-mouseover", "ahmo", 0, 0); |
| 460 | - @ </blockquote> | |
| 436 | + @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users, | |
| 437 | + @ including user "nobody" if the request appears to be from a human. | |
| 438 | + @ Disabling hyperlinks helps prevent robots from walking your site and | |
| 439 | + @ soaking up all your CPU and bandwidth. | |
| 440 | + @ If this setting is "UserAgent only" (2) then the | |
| 441 | + @ UserAgent string is the only factor considered. If the value of this | |
| 442 | + @ setting is "UserAgent And Javascript" (1) then Javascript is added that | |
| 443 | + @ runs after the page loads and fills in the href= values of <a> | |
| 444 | + @ elements. In either case, <a> tags are not generated if the | |
| 445 | + @ UserAgent string indicates that the client is a robot. | |
| 446 | + @ (Property: "auto-hyperlink")</p> | |
| 447 | + @ | |
| 461 | 448 | @ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds |
| 462 | 449 | @ and "require a mouse event" should be turned on. These values only come |
| 463 | 450 | @ into play when the main auto-hyperlink settings is 2 ("UserAgent and |
| 464 | - @ Javascript").</p> | |
| 451 | + @ Javascript"). | |
| 452 | + @ (Properties: "auto-hyperlink-delay" and "auto-hyperlink-mouseover")</p> | |
| 465 | 453 | @ |
| 466 | 454 | @ <p>To see if Javascript-base hyperlink enabling mechanism is working, |
| 467 | - @ visit the <a href="%R/test-env">/test-env</a> page (from a separate | |
| 468 | - @ web browser that is not logged in, even as "anonymous") and verify | |
| 455 | + @ visit the <a href="%R/test-env">/test-env</a> page from a separate | |
| 456 | + @ web browser that is not logged in, even as "anonymous" and verify | |
| 469 | 457 | @ that the "g.jsHref" value is "1".</p> |
| 470 | - @ <p>(Properties: "auto-hyperlink", "auto-hyperlink-delay", and | |
| 471 | - @ "auto-hyperlink-mouseover"")</p> | |
| 472 | 458 | } |
| 473 | 459 | |
| 474 | 460 | /* |
| 475 | 461 | ** WEBPAGE: setup_robot |
| 476 | 462 | ** |
| @@ -488,19 +474,74 @@ | ||
| 488 | 474 | @ <p>A Fossil website can have billions of pages in its tree, even for a |
| 489 | 475 | @ modest project. Many of those pages (examples: diffs and tarballs) |
| 490 | 476 | @ might be expensive to compute. A robot that tries to walk the entire |
| 491 | 477 | @ website can present a crippling CPU and bandwidth load. |
| 492 | 478 | @ |
| 493 | - @ <p>The settings on this page are intended to help site administrators | |
| 494 | - @ defend the site against robots. | |
| 479 | + @ <p>The settings on this page are intended to help administrators | |
| 480 | + @ defend against abusive robots. | |
| 495 | 481 | @ |
| 496 | 482 | @ <form action="%R/setup_robot" method="post"><div> |
| 497 | 483 | login_insert_csrf_secret(); |
| 498 | 484 | @ <input type="submit" name="submit" value="Apply Changes"></p> |
| 499 | 485 | @ <hr> |
| 486 | + @ <p><b>Do not allow robots access to these pages.</b><br> | |
| 487 | + @ If the page name matches the GLOB pattern of this setting, and the | |
| 488 | + @ users is "nobody", and the client has not previously passed a captcha | |
| 489 | + @ test to show that it is not a robot, then the page is not displayed. | |
| 490 | + @ A captcha test is is rendered instead. | |
| 491 | + @ The default value for this setting is: | |
| 492 | + @ <p> | |
| 493 | + @    <tt>%h(robot_restrict_default())</tt> | |
| 494 | + @ <p> | |
| 495 | + @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and | |
| 496 | + @ /vpatch. The "annotate" tag covers /annotate and also /blame and | |
| 497 | + @ /praise. The "zip" covers itself and also /tarball and /sqlar. | |
| 498 | + @ If a tag has an "X" character appended (ex: "timelineX") then it only | |
| 499 | + @ applies if query parameters are such that the page is expensive | |
| 500 | + @ and/or unusual. In all other case, the tag should exactly match | |
| 501 | + @ the page name. | |
| 502 | + @ | |
| 503 | + @ To disable robot restrictions, change this setting to "off". | |
| 504 | + @ (Property: robot-restrict) | |
| 505 | + @ <br> | |
| 506 | + textarea_attribute("", 2, 80, | |
| 507 | + "robot-restrict", "rbrestrict", robot_restrict_default(), 0); | |
| 508 | + | |
| 509 | + @ <p><b>Exception #1</b><br> | |
| 510 | + @ If "zipX" appears in the robot-restrict list above, then tarballs, | |
| 511 | + @ ZIP-archives, and SQL-archives may be downloaded by robots if | |
| 512 | + @ the check-in is a leaf (robot-zip-leaf):<br> | |
| 513 | + onoff_attribute("Allow tarballs for leaf check-ins", | |
| 514 | + "robot-zip-leaf", "rzleaf", 0, 0); | |
| 515 | + | |
| 516 | + @ <p><b>Exception #2</b><br> | |
| 517 | + @ If "zipX" appears in the robot-restrict list above, then tarballs, | |
| 518 | + @ ZIP-archives, and SQL-archives may be downloaded by robots if | |
| 519 | + @ the check-in has one or more tags that match the following | |
| 520 | + @ list of GLOB patterns: (robot-zip-tag)<br> | |
| 521 | + textarea_attribute("", 2, 80, | |
| 522 | + "robot-zip-tag", "rztag", "", 0); | |
| 523 | + | |
| 524 | + @ <p><b>Exception #3</b><br> | |
| 525 | + @ If the request URI matches any of the following | |
| 526 | + @ <a href="%R/re_rules">regular expressions</a> (one per line), then the | |
| 527 | + @ request is exempt from anti-robot defenses. | |
| 528 | + @ The regular expression is matched against the REQUEST_URI with the | |
| 529 | + @ SCRIPT_NAME prefix removed, and with QUERY_STRING appended following | |
| 530 | + @ a "?" if QUERY_STRING exists. (Property: robot-exception)<br> | |
| 531 | + textarea_attribute("", 3, 80, | |
| 532 | + "robot-exception", "rbexcept", "", 0); | |
| 533 | + @ <hr> | |
| 500 | 534 | addAutoHyperlinkSettings(); |
| 501 | 535 | |
| 536 | + @ <hr> | |
| 537 | + entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", | |
| 538 | + "anoncookls", "840", 0); | |
| 539 | + @ <p>The number of minutes for which an anonymous login cookie is valid. | |
| 540 | + @ Set to zero to disable anonymous login. | |
| 541 | + @ (property: anon-cookie-lifespan) | |
| 542 | + | |
| 502 | 543 | @ <hr> |
| 503 | 544 | entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", |
| 504 | 545 | "0.0", 0); |
| 505 | 546 | @ <p>Some expensive operations (such as computing tarballs, zip archives, |
| 506 | 547 | @ or annotation/blame pages) are prohibited if the load average on the host |
| @@ -508,37 +549,11 @@ | ||
| 508 | 549 | @ computations here. Set this to 0.0 to disable the load average limit. |
| 509 | 550 | @ This limit is only enforced on Unix servers. On Linux systems, |
| 510 | 551 | @ access to the /proc virtual filesystem is required, which means this limit |
| 511 | 552 | @ might not work inside a chroot() jail. |
| 512 | 553 | @ (Property: "max-loadavg")</p> |
| 513 | - | |
| 514 | - @ <hr> | |
| 515 | - @ <p><b>Do not allow robots to make complex requests | |
| 516 | - @ against the following pages.</b> | |
| 517 | - @ <p> A "complex request" is an HTTP request that has one or more query | |
| 518 | - @ parameters. Some robots will spend hours juggling around query parameters | |
| 519 | - @ or even forging fake query parameters in an effort to discover new | |
| 520 | - @ behavior or to find an SQL injection opportunity or similar. This can | |
| 521 | - @ waste hours of CPU time and gigabytes of bandwidth on the server. A | |
| 522 | - @ suggested value for this setting is: | |
| 523 | - @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>". | |
| 524 | - @ (Property: robot-restrict) | |
| 525 | - @ <br> | |
| 526 | - textarea_attribute("", 2, 80, | |
| 527 | - "robot-restrict", "rbrestrict", "", 0); | |
| 528 | - @ <br> The following comma-separated GLOB pattern allows for exceptions | |
| 529 | - @ in the maximum number of query parameters before a request is considered | |
| 530 | - @ complex. If this GLOB pattern exists and is non-empty and if it | |
| 531 | - @ matches against the pagename followed by "/" and the number of query | |
| 532 | - @ parameters, then the request is allowed through. For example, the | |
| 533 | - @ suggested pattern of "timeline/[012]" allows the /timeline page to | |
| 534 | - @ pass with up to 2 query parameters besides "name". | |
| 535 | - @ (Property: robot-restrict-qp) | |
| 536 | - @ <br> | |
| 537 | - textarea_attribute("", 2, 80, | |
| 538 | - "robot-restrict-qp", "rbrestrictqp", "", 0); | |
| 539 | - | |
| 554 | + @ | |
| 540 | 555 | @ <hr> |
| 541 | 556 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 542 | 557 | @ </div></form> |
| 543 | 558 | db_end_transaction(0); |
| 544 | 559 | style_finish_page(); |
| @@ -774,10 +789,17 @@ | ||
| 774 | 789 | @ "anonymous" that will automatically fill in the CAPTCHA password. |
| 775 | 790 | @ This is less secure than forcing the user to do it manually, but is |
| 776 | 791 | @ probably secure enough and it is certainly more convenient for |
| 777 | 792 | @ anonymous users. (Property: "auto-captcha")</p> |
| 778 | 793 | |
| 794 | + @ <hr> | |
| 795 | + entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", | |
| 796 | + "anoncookls", "840", 0); | |
| 797 | + @ <p>The number of minutes for which an anonymous login cookie is valid. | |
| 798 | + @ Set to zero to disable anonymous logins. | |
| 799 | + @ (property: anon-cookie-lifespan) | |
| 800 | + | |
| 779 | 801 | @ <hr> |
| 780 | 802 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 781 | 803 | @ </div></form> |
| 782 | 804 | db_end_transaction(0); |
| 783 | 805 | style_finish_page(); |
| @@ -968,10 +990,15 @@ | ||
| 968 | 990 | "1", "HH:MM:SS", |
| 969 | 991 | "2", "YYYY-MM-DD HH:MM", |
| 970 | 992 | "3", "YYMMDD HH:MM", |
| 971 | 993 | "4", "(off)" |
| 972 | 994 | }; |
| 995 | + static const char *const azLeafMark[] = { | |
| 996 | + "0", "No", | |
| 997 | + "1", "Yes", | |
| 998 | + "2", "Yes - with emphasis", | |
| 999 | + }; | |
| 973 | 1000 | login_check_credentials(); |
| 974 | 1001 | if( !g.perm.Admin ){ |
| 975 | 1002 | login_needed(0); |
| 976 | 1003 | return; |
| 977 | 1004 | } |
| @@ -1056,10 +1083,16 @@ | ||
| 1056 | 1083 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1057 | 1084 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1058 | 1085 | @ the complete date and time is shown on every timeline entry using the |
| 1059 | 1086 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1060 | 1087 | |
| 1088 | + @ <hr> | |
| 1089 | + multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves", | |
| 1090 | + "tml", "1", count(azLeafMark)/2, azLeafMark); | |
| 1091 | + @ <p>Should timeline entries for leaf check-ins be identified in the | |
| 1092 | + @ detail section. (Property: "timeline-mark-leaves")</p> | |
| 1093 | + | |
| 1061 | 1094 | @ <hr> |
| 1062 | 1095 | entry_attribute("Max timeline comment length", 6, |
| 1063 | 1096 | "timeline-max-comment", "tmc", "0", 0); |
| 1064 | 1097 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1065 | 1098 | @ "0" there is no length limit. |
| @@ -1158,11 +1191,11 @@ | ||
| 1158 | 1191 | continue; |
| 1159 | 1192 | } |
| 1160 | 1193 | onoff_attribute("", pSet->name, |
| 1161 | 1194 | pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, |
| 1162 | 1195 | is_truth(pSet->def), hasVersionableValue); |
| 1163 | - @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a> | |
| 1196 | + @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a> | |
| 1164 | 1197 | if( pSet->versionable ){ |
| 1165 | 1198 | @ (v)<br> |
| 1166 | 1199 | } else { |
| 1167 | 1200 | @ <br> |
| 1168 | 1201 | } |
| @@ -1177,11 +1210,11 @@ | ||
| 1177 | 1210 | (db_get_versioned(pSet->name, NULL, NULL)!=0); |
| 1178 | 1211 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1179 | 1212 | continue; |
| 1180 | 1213 | } |
| 1181 | 1214 | @ <tr><td> |
| 1182 | - @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a> | |
| 1215 | + @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a> | |
| 1183 | 1216 | if( pSet->versionable ){ |
| 1184 | 1217 | @ (v) |
| 1185 | 1218 | } else { |
| 1186 | 1219 | @ |
| 1187 | 1220 | } |
| @@ -1198,11 +1231,11 @@ | ||
| 1198 | 1231 | if( pSet->width>0 && pSet->forceTextArea ){ |
| 1199 | 1232 | int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0; |
| 1200 | 1233 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1201 | 1234 | continue; |
| 1202 | 1235 | } |
| 1203 | - @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a> | |
| 1236 | + @ <a href='%R/help/%s(pSet->name)'>%s(pSet->name)</a> | |
| 1204 | 1237 | if( pSet->versionable ){ |
| 1205 | 1238 | @ (v)<br> |
| 1206 | 1239 | } else { |
| 1207 | 1240 | @ <br> |
| 1208 | 1241 | } |
| @@ -1309,28 +1342,10 @@ | ||
| 1309 | 1342 | @ Omit the trailing "/". |
| 1310 | 1343 | @ If this repo will not be set up as a persistent server and will not |
| 1311 | 1344 | @ be sending email alerts, then leave this entry blank. |
| 1312 | 1345 | @ Suggested value: "%h(g.zBaseURL)" |
| 1313 | 1346 | @ (Property: "email-url")</p> |
| 1314 | - @ <hr> | |
| 1315 | - entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", | |
| 1316 | - "spn", "", 0); | |
| 1317 | - @ <p>This is used as a prefix on the names of generated tarballs and | |
| 1318 | - @ ZIP archive. For best results, keep this prefix brief and avoid special | |
| 1319 | - @ characters such as "/" and "\". | |
| 1320 | - @ If no tarball prefix is specified, then the full Project Name above is used. | |
| 1321 | - @ (Property: "short-project-name") | |
| 1322 | - @ </p> | |
| 1323 | - @ <hr> | |
| 1324 | - entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0); | |
| 1325 | - @ <p>The <a href='%R/download'>/download</a> page is designed to provide | |
| 1326 | - @ a convenient place for newbies | |
| 1327 | - @ to download a ZIP archive or a tarball of the project. By default, | |
| 1328 | - @ the latest trunk check-in is downloaded. Change this tag to something | |
| 1329 | - @ else (ex: release) to alter the behavior of the /download page. | |
| 1330 | - @ (Property: "download-tag") | |
| 1331 | - @ </p> | |
| 1332 | 1347 | @ <hr> |
| 1333 | 1348 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1334 | 1349 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1335 | 1350 | @ option is selected and when no pathname is |
| 1336 | 1351 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1408,10 +1423,66 @@ | ||
| 1408 | 1423 | @ <p>The default value is blank, meaning no added entries. |
| 1409 | 1424 | @ (Property: sitemap-extra) |
| 1410 | 1425 | @ <p> |
| 1411 | 1426 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1412 | 1427 | "sitemap-extra", "smextra", "", 0); |
| 1428 | + @ <hr> | |
| 1429 | + @ <p><input type="submit" name="submit" value="Apply Changes"></p> | |
| 1430 | + @ </div></form> | |
| 1431 | + db_end_transaction(0); | |
| 1432 | + style_finish_page(); | |
| 1433 | +} | |
| 1434 | + | |
| 1435 | +/* | |
| 1436 | +** WEBPAGE: setup_download | |
| 1437 | +** | |
| 1438 | +** The "Admin/Download" page. Requires Setup privilege. | |
| 1439 | +*/ | |
| 1440 | +void setup_download(void){ | |
| 1441 | + login_check_credentials(); | |
| 1442 | + if( !g.perm.Setup ){ | |
| 1443 | + login_needed(0); | |
| 1444 | + return; | |
| 1445 | + } | |
| 1446 | + | |
| 1447 | + style_set_current_feature("setup"); | |
| 1448 | + style_header("Tarball and ZIP Downloads"); | |
| 1449 | + db_begin_transaction(); | |
| 1450 | + @ <form action="%R/setup_download" method="post"><div> | |
| 1451 | + login_insert_csrf_secret(); | |
| 1452 | + @ <input type="submit" name="submit" value="Apply Changes"></p> | |
| 1453 | + @ <hr> | |
| 1454 | + entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name", | |
| 1455 | + "spn", "", 0); | |
| 1456 | + @ <p>This is used as a prefix for the names of generated tarballs and | |
| 1457 | + @ ZIP archive. Keep this prefix brief and use only lower-case ASCII | |
| 1458 | + @ characters, digits, "_", "-" in the name. If this setting is blank, | |
| 1459 | + @ then the full <a href='%R/help/project-name'>project-name</a> setting | |
| 1460 | + @ is used instead. | |
| 1461 | + @ (Property: "short-project-name") | |
| 1462 | + @ </p> | |
| 1463 | + @ <hr> | |
| 1464 | + @ <p><b>Configuration for the <a href="%R/download">/download</a> page.</b> | |
| 1465 | + @ <p>The value is a TCL list divided into groups of four tokens: | |
| 1466 | + @ <ol> | |
| 1467 | + @ <li> Maximum number of matches (COUNT). | |
| 1468 | + @ <li> Tag to match using glob (TAG). | |
| 1469 | + @ <li> Maximum age of check-ins to match (MAX_AGE). | |
| 1470 | + @ <li> Comment to apply to matches (COMMENT). | |
| 1471 | + @ </ol> | |
| 1472 | + @ Each 4-tuple will match zero or more check-ins. The /download page | |
| 1473 | + @ displays the union of matches from all 4-tuples. | |
| 1474 | + @ See the <a href="%R/help/suggested-downloads">suggested-downloads</a> | |
| 1475 | + @ setting documentation for further detail. | |
| 1476 | + @ <p> | |
| 1477 | + @ The /download page is omitted from the <a href="%R/sitemap">/sitemap</a> | |
| 1478 | + @ if the first token is "0" or "off" or "no". The default value | |
| 1479 | + @ for this setting is "off". | |
| 1480 | + @ (Property: <a href="%R/help/suggested-downloads">suggested-downloads</a>) | |
| 1481 | + @ <p> | |
| 1482 | + textarea_attribute("", 4, 80, | |
| 1483 | + "suggested-downloads", "sgtrlst", "off", 0); | |
| 1413 | 1484 | @ <hr> |
| 1414 | 1485 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1415 | 1486 | @ </div></form> |
| 1416 | 1487 | db_end_transaction(0); |
| 1417 | 1488 | style_finish_page(); |
| 1418 | 1489 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -118,10 +118,12 @@ | |
| 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | } |
| 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | "Timeline display preferences"); |
| 123 | if( setup_user ){ |
| 124 | setup_menu_entry("Login-Group", "setup_login_group", |
| 125 | "Manage single sign-on between this repository and others" |
| 126 | " on the same server"); |
| 127 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | |
| 140 | setup_menu_entry("URL Aliases", "waliassetup", |
| 141 | "Configure URL aliases"); |
| 142 | if( setup_user ){ |
| 143 | setup_menu_entry("Notification", "setup_notification", |
| 144 | "Automatic notifications of changes via outbound email"); |
| 145 | setup_menu_entry("Transfers", "xfersetup", |
| 146 | "Configure the transfer system for this repository"); |
| 147 | } |
| 148 | setup_menu_entry("Skins", "setup_skin_admin", |
| 149 | "Select and/or modify the web interface \"skins\""); |
| 150 | setup_menu_entry("Moderation", "setup_modreq", |
| 151 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -421,56 +425,38 @@ | |
| 421 | }; |
| 422 | multiple_choice_attribute( |
| 423 | "Enable hyperlinks base on User-Agent and/or Javascript", |
| 424 | "auto-hyperlink", "autohyperlink", "1", |
| 425 | count(azDefenseOpts)/2, azDefenseOpts); |
| 426 | @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users, |
| 427 | @ including user "nobody", as long as the User-Agent string in the |
| 428 | @ HTTP header indicates that the request is coming from an actual human |
| 429 | @ being. If this setting is "UserAgent only" (2) then the |
| 430 | @ UserAgent string is the only factor considered. If the value of this |
| 431 | @ setting is "UserAgent And Javascript" (1) then Javascript is added that |
| 432 | @ runs after the page loads and fills in the href= values of <a> |
| 433 | @ elements. In either case, <a> tags are only generated if the |
| 434 | @ UserAgent string indicates that the request is coming from a human and |
| 435 | @ not a robot. |
| 436 | @ |
| 437 | @ <p>This setting is designed to give easy access to humans while |
| 438 | @ keeping out robots. |
| 439 | @ You do not normally want a robot to walk your entire repository because |
| 440 | @ if it does, your server will end up computing diffs and annotations for |
| 441 | @ every historical version of every file and creating ZIPs and tarballs of |
| 442 | @ every historical check-in, which can use a lot of CPU and bandwidth |
| 443 | @ even for relatively small projects.</p> |
| 444 | @ |
| 445 | @ <p>The "UserAgent and Javascript" value for this setting provides |
| 446 | @ superior protection from robots. However, that setting also prevents |
| 447 | @ the visited/unvisited colors on hyperlinks from displaying correctly |
| 448 | @ on Safari-derived browsers. (Chrome and Firefox work fine.) Since |
| 449 | @ Safari is the underlying rendering engine on all iPhones and iPads, |
| 450 | @ this means that hyperlink visited/unvisited colors will not operate |
| 451 | @ on those platforms when "UserAgent and Javascript" is selected.</p> |
| 452 | @ |
| 453 | @ <p>Additional parameters that control the behavior of Javascript:</p> |
| 454 | @ <blockquote> |
| 455 | entry_attribute("Delay in milliseconds before enabling hyperlinks", 5, |
| 456 | "auto-hyperlink-delay", "ah-delay", "50", 0); |
| 457 | @ <br> |
| 458 | onoff_attribute("Also require a mouse event before enabling hyperlinks", |
| 459 | "auto-hyperlink-mouseover", "ahmo", 0, 0); |
| 460 | @ </blockquote> |
| 461 | @ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds |
| 462 | @ and "require a mouse event" should be turned on. These values only come |
| 463 | @ into play when the main auto-hyperlink settings is 2 ("UserAgent and |
| 464 | @ Javascript").</p> |
| 465 | @ |
| 466 | @ <p>To see if Javascript-base hyperlink enabling mechanism is working, |
| 467 | @ visit the <a href="%R/test-env">/test-env</a> page (from a separate |
| 468 | @ web browser that is not logged in, even as "anonymous") and verify |
| 469 | @ that the "g.jsHref" value is "1".</p> |
| 470 | @ <p>(Properties: "auto-hyperlink", "auto-hyperlink-delay", and |
| 471 | @ "auto-hyperlink-mouseover"")</p> |
| 472 | } |
| 473 | |
| 474 | /* |
| 475 | ** WEBPAGE: setup_robot |
| 476 | ** |
| @@ -488,19 +474,74 @@ | |
| 488 | @ <p>A Fossil website can have billions of pages in its tree, even for a |
| 489 | @ modest project. Many of those pages (examples: diffs and tarballs) |
| 490 | @ might be expensive to compute. A robot that tries to walk the entire |
| 491 | @ website can present a crippling CPU and bandwidth load. |
| 492 | @ |
| 493 | @ <p>The settings on this page are intended to help site administrators |
| 494 | @ defend the site against robots. |
| 495 | @ |
| 496 | @ <form action="%R/setup_robot" method="post"><div> |
| 497 | login_insert_csrf_secret(); |
| 498 | @ <input type="submit" name="submit" value="Apply Changes"></p> |
| 499 | @ <hr> |
| 500 | addAutoHyperlinkSettings(); |
| 501 | |
| 502 | @ <hr> |
| 503 | entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", |
| 504 | "0.0", 0); |
| 505 | @ <p>Some expensive operations (such as computing tarballs, zip archives, |
| 506 | @ or annotation/blame pages) are prohibited if the load average on the host |
| @@ -508,37 +549,11 @@ | |
| 508 | @ computations here. Set this to 0.0 to disable the load average limit. |
| 509 | @ This limit is only enforced on Unix servers. On Linux systems, |
| 510 | @ access to the /proc virtual filesystem is required, which means this limit |
| 511 | @ might not work inside a chroot() jail. |
| 512 | @ (Property: "max-loadavg")</p> |
| 513 | |
| 514 | @ <hr> |
| 515 | @ <p><b>Do not allow robots to make complex requests |
| 516 | @ against the following pages.</b> |
| 517 | @ <p> A "complex request" is an HTTP request that has one or more query |
| 518 | @ parameters. Some robots will spend hours juggling around query parameters |
| 519 | @ or even forging fake query parameters in an effort to discover new |
| 520 | @ behavior or to find an SQL injection opportunity or similar. This can |
| 521 | @ waste hours of CPU time and gigabytes of bandwidth on the server. A |
| 522 | @ suggested value for this setting is: |
| 523 | @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>". |
| 524 | @ (Property: robot-restrict) |
| 525 | @ <br> |
| 526 | textarea_attribute("", 2, 80, |
| 527 | "robot-restrict", "rbrestrict", "", 0); |
| 528 | @ <br> The following comma-separated GLOB pattern allows for exceptions |
| 529 | @ in the maximum number of query parameters before a request is considered |
| 530 | @ complex. If this GLOB pattern exists and is non-empty and if it |
| 531 | @ matches against the pagename followed by "/" and the number of query |
| 532 | @ parameters, then the request is allowed through. For example, the |
| 533 | @ suggested pattern of "timeline/[012]" allows the /timeline page to |
| 534 | @ pass with up to 2 query parameters besides "name". |
| 535 | @ (Property: robot-restrict-qp) |
| 536 | @ <br> |
| 537 | textarea_attribute("", 2, 80, |
| 538 | "robot-restrict-qp", "rbrestrictqp", "", 0); |
| 539 | |
| 540 | @ <hr> |
| 541 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 542 | @ </div></form> |
| 543 | db_end_transaction(0); |
| 544 | style_finish_page(); |
| @@ -774,10 +789,17 @@ | |
| 774 | @ "anonymous" that will automatically fill in the CAPTCHA password. |
| 775 | @ This is less secure than forcing the user to do it manually, but is |
| 776 | @ probably secure enough and it is certainly more convenient for |
| 777 | @ anonymous users. (Property: "auto-captcha")</p> |
| 778 | |
| 779 | @ <hr> |
| 780 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 781 | @ </div></form> |
| 782 | db_end_transaction(0); |
| 783 | style_finish_page(); |
| @@ -968,10 +990,15 @@ | |
| 968 | "1", "HH:MM:SS", |
| 969 | "2", "YYYY-MM-DD HH:MM", |
| 970 | "3", "YYMMDD HH:MM", |
| 971 | "4", "(off)" |
| 972 | }; |
| 973 | login_check_credentials(); |
| 974 | if( !g.perm.Admin ){ |
| 975 | login_needed(0); |
| 976 | return; |
| 977 | } |
| @@ -1056,10 +1083,16 @@ | |
| 1056 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1057 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1058 | @ the complete date and time is shown on every timeline entry using the |
| 1059 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1060 | |
| 1061 | @ <hr> |
| 1062 | entry_attribute("Max timeline comment length", 6, |
| 1063 | "timeline-max-comment", "tmc", "0", 0); |
| 1064 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1065 | @ "0" there is no length limit. |
| @@ -1158,11 +1191,11 @@ | |
| 1158 | continue; |
| 1159 | } |
| 1160 | onoff_attribute("", pSet->name, |
| 1161 | pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, |
| 1162 | is_truth(pSet->def), hasVersionableValue); |
| 1163 | @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a> |
| 1164 | if( pSet->versionable ){ |
| 1165 | @ (v)<br> |
| 1166 | } else { |
| 1167 | @ <br> |
| 1168 | } |
| @@ -1177,11 +1210,11 @@ | |
| 1177 | (db_get_versioned(pSet->name, NULL, NULL)!=0); |
| 1178 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1179 | continue; |
| 1180 | } |
| 1181 | @ <tr><td> |
| 1182 | @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a> |
| 1183 | if( pSet->versionable ){ |
| 1184 | @ (v) |
| 1185 | } else { |
| 1186 | @ |
| 1187 | } |
| @@ -1198,11 +1231,11 @@ | |
| 1198 | if( pSet->width>0 && pSet->forceTextArea ){ |
| 1199 | int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0; |
| 1200 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1201 | continue; |
| 1202 | } |
| 1203 | @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a> |
| 1204 | if( pSet->versionable ){ |
| 1205 | @ (v)<br> |
| 1206 | } else { |
| 1207 | @ <br> |
| 1208 | } |
| @@ -1309,28 +1342,10 @@ | |
| 1309 | @ Omit the trailing "/". |
| 1310 | @ If this repo will not be set up as a persistent server and will not |
| 1311 | @ be sending email alerts, then leave this entry blank. |
| 1312 | @ Suggested value: "%h(g.zBaseURL)" |
| 1313 | @ (Property: "email-url")</p> |
| 1314 | @ <hr> |
| 1315 | entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", |
| 1316 | "spn", "", 0); |
| 1317 | @ <p>This is used as a prefix on the names of generated tarballs and |
| 1318 | @ ZIP archive. For best results, keep this prefix brief and avoid special |
| 1319 | @ characters such as "/" and "\". |
| 1320 | @ If no tarball prefix is specified, then the full Project Name above is used. |
| 1321 | @ (Property: "short-project-name") |
| 1322 | @ </p> |
| 1323 | @ <hr> |
| 1324 | entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0); |
| 1325 | @ <p>The <a href='%R/download'>/download</a> page is designed to provide |
| 1326 | @ a convenient place for newbies |
| 1327 | @ to download a ZIP archive or a tarball of the project. By default, |
| 1328 | @ the latest trunk check-in is downloaded. Change this tag to something |
| 1329 | @ else (ex: release) to alter the behavior of the /download page. |
| 1330 | @ (Property: "download-tag") |
| 1331 | @ </p> |
| 1332 | @ <hr> |
| 1333 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1334 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1335 | @ option is selected and when no pathname is |
| 1336 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1408,10 +1423,66 @@ | |
| 1408 | @ <p>The default value is blank, meaning no added entries. |
| 1409 | @ (Property: sitemap-extra) |
| 1410 | @ <p> |
| 1411 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1412 | "sitemap-extra", "smextra", "", 0); |
| 1413 | @ <hr> |
| 1414 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1415 | @ </div></form> |
| 1416 | db_end_transaction(0); |
| 1417 | style_finish_page(); |
| 1418 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -118,10 +118,12 @@ | |
| 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | } |
| 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | "Timeline display preferences"); |
| 123 | setup_menu_entry("Tarballs and ZIPs", "setup_download", |
| 124 | "Preferences for auto-generated tarballs and ZIP files"); |
| 125 | if( setup_user ){ |
| 126 | setup_menu_entry("Login-Group", "setup_login_group", |
| 127 | "Manage single sign-on between this repository and others" |
| 128 | " on the same server"); |
| 129 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | |
| 142 | setup_menu_entry("URL Aliases", "waliassetup", |
| 143 | "Configure URL aliases"); |
| 144 | if( setup_user ){ |
| 145 | setup_menu_entry("Notification", "setup_notification", |
| 146 | "Automatic notifications of changes via outbound email"); |
| 147 | #if 0 /* Disabled for now. Does this even work? */ |
| 148 | setup_menu_entry("Transfers", "xfersetup", |
| 149 | "Configure the transfer system for this repository"); |
| 150 | #endif |
| 151 | } |
| 152 | setup_menu_entry("Skins", "setup_skin_admin", |
| 153 | "Select and/or modify the web interface \"skins\""); |
| 154 | setup_menu_entry("Moderation", "setup_modreq", |
| 155 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -421,56 +425,38 @@ | |
| 425 | }; |
| 426 | multiple_choice_attribute( |
| 427 | "Enable hyperlinks base on User-Agent and/or Javascript", |
| 428 | "auto-hyperlink", "autohyperlink", "1", |
| 429 | count(azDefenseOpts)/2, azDefenseOpts); |
| 430 | @ <br> |
| 431 | entry_attribute("Delay in milliseconds before enabling hyperlinks", 5, |
| 432 | "auto-hyperlink-delay", "ah-delay", "50", 0); |
| 433 | @ <br> |
| 434 | onoff_attribute("Also require a mouse event before enabling hyperlinks", |
| 435 | "auto-hyperlink-mouseover", "ahmo", 0, 0); |
| 436 | @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users, |
| 437 | @ including user "nobody" if the request appears to be from a human. |
| 438 | @ Disabling hyperlinks helps prevent robots from walking your site and |
| 439 | @ soaking up all your CPU and bandwidth. |
| 440 | @ If this setting is "UserAgent only" (2) then the |
| 441 | @ UserAgent string is the only factor considered. If the value of this |
| 442 | @ setting is "UserAgent And Javascript" (1) then Javascript is added that |
| 443 | @ runs after the page loads and fills in the href= values of <a> |
| 444 | @ elements. In either case, <a> tags are not generated if the |
| 445 | @ UserAgent string indicates that the client is a robot. |
| 446 | @ (Property: "auto-hyperlink")</p> |
| 447 | @ |
| 448 | @ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds |
| 449 | @ and "require a mouse event" should be turned on. These values only come |
| 450 | @ into play when the main auto-hyperlink settings is 2 ("UserAgent and |
| 451 | @ Javascript"). |
| 452 | @ (Properties: "auto-hyperlink-delay" and "auto-hyperlink-mouseover")</p> |
| 453 | @ |
| 454 | @ <p>To see if Javascript-base hyperlink enabling mechanism is working, |
| 455 | @ visit the <a href="%R/test-env">/test-env</a> page from a separate |
| 456 | @ web browser that is not logged in, even as "anonymous" and verify |
| 457 | @ that the "g.jsHref" value is "1".</p> |
| 458 | } |
| 459 | |
| 460 | /* |
| 461 | ** WEBPAGE: setup_robot |
| 462 | ** |
| @@ -488,19 +474,74 @@ | |
| 474 | @ <p>A Fossil website can have billions of pages in its tree, even for a |
| 475 | @ modest project. Many of those pages (examples: diffs and tarballs) |
| 476 | @ might be expensive to compute. A robot that tries to walk the entire |
| 477 | @ website can present a crippling CPU and bandwidth load. |
| 478 | @ |
| 479 | @ <p>The settings on this page are intended to help administrators |
| 480 | @ defend against abusive robots. |
| 481 | @ |
| 482 | @ <form action="%R/setup_robot" method="post"><div> |
| 483 | login_insert_csrf_secret(); |
| 484 | @ <input type="submit" name="submit" value="Apply Changes"></p> |
| 485 | @ <hr> |
| 486 | @ <p><b>Do not allow robots access to these pages.</b><br> |
| 487 | @ If the page name matches the GLOB pattern of this setting, and the |
| 488 | @ users is "nobody", and the client has not previously passed a captcha |
| 489 | @ test to show that it is not a robot, then the page is not displayed. |
| 490 | @ A captcha test is is rendered instead. |
| 491 | @ The default value for this setting is: |
| 492 | @ <p> |
| 493 | @    <tt>%h(robot_restrict_default())</tt> |
| 494 | @ <p> |
| 495 | @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 496 | @ /vpatch. The "annotate" tag covers /annotate and also /blame and |
| 497 | @ /praise. The "zip" covers itself and also /tarball and /sqlar. |
| 498 | @ If a tag has an "X" character appended (ex: "timelineX") then it only |
| 499 | @ applies if query parameters are such that the page is expensive |
| 500 | @ and/or unusual. In all other case, the tag should exactly match |
| 501 | @ the page name. |
| 502 | @ |
| 503 | @ To disable robot restrictions, change this setting to "off". |
| 504 | @ (Property: robot-restrict) |
| 505 | @ <br> |
| 506 | textarea_attribute("", 2, 80, |
| 507 | "robot-restrict", "rbrestrict", robot_restrict_default(), 0); |
| 508 | |
| 509 | @ <p><b>Exception #1</b><br> |
| 510 | @ If "zipX" appears in the robot-restrict list above, then tarballs, |
| 511 | @ ZIP-archives, and SQL-archives may be downloaded by robots if |
| 512 | @ the check-in is a leaf (robot-zip-leaf):<br> |
| 513 | onoff_attribute("Allow tarballs for leaf check-ins", |
| 514 | "robot-zip-leaf", "rzleaf", 0, 0); |
| 515 | |
| 516 | @ <p><b>Exception #2</b><br> |
| 517 | @ If "zipX" appears in the robot-restrict list above, then tarballs, |
| 518 | @ ZIP-archives, and SQL-archives may be downloaded by robots if |
| 519 | @ the check-in has one or more tags that match the following |
| 520 | @ list of GLOB patterns: (robot-zip-tag)<br> |
| 521 | textarea_attribute("", 2, 80, |
| 522 | "robot-zip-tag", "rztag", "", 0); |
| 523 | |
| 524 | @ <p><b>Exception #3</b><br> |
| 525 | @ If the request URI matches any of the following |
| 526 | @ <a href="%R/re_rules">regular expressions</a> (one per line), then the |
| 527 | @ request is exempt from anti-robot defenses. |
| 528 | @ The regular expression is matched against the REQUEST_URI with the |
| 529 | @ SCRIPT_NAME prefix removed, and with QUERY_STRING appended following |
| 530 | @ a "?" if QUERY_STRING exists. (Property: robot-exception)<br> |
| 531 | textarea_attribute("", 3, 80, |
| 532 | "robot-exception", "rbexcept", "", 0); |
| 533 | @ <hr> |
| 534 | addAutoHyperlinkSettings(); |
| 535 | |
| 536 | @ <hr> |
| 537 | entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", |
| 538 | "anoncookls", "840", 0); |
| 539 | @ <p>The number of minutes for which an anonymous login cookie is valid. |
| 540 | @ Set to zero to disable anonymous login. |
| 541 | @ (property: anon-cookie-lifespan) |
| 542 | |
| 543 | @ <hr> |
| 544 | entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", |
| 545 | "0.0", 0); |
| 546 | @ <p>Some expensive operations (such as computing tarballs, zip archives, |
| 547 | @ or annotation/blame pages) are prohibited if the load average on the host |
| @@ -508,37 +549,11 @@ | |
| 549 | @ computations here. Set this to 0.0 to disable the load average limit. |
| 550 | @ This limit is only enforced on Unix servers. On Linux systems, |
| 551 | @ access to the /proc virtual filesystem is required, which means this limit |
| 552 | @ might not work inside a chroot() jail. |
| 553 | @ (Property: "max-loadavg")</p> |
| 554 | @ |
| 555 | @ <hr> |
| 556 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 557 | @ </div></form> |
| 558 | db_end_transaction(0); |
| 559 | style_finish_page(); |
| @@ -774,10 +789,17 @@ | |
| 789 | @ "anonymous" that will automatically fill in the CAPTCHA password. |
| 790 | @ This is less secure than forcing the user to do it manually, but is |
| 791 | @ probably secure enough and it is certainly more convenient for |
| 792 | @ anonymous users. (Property: "auto-captcha")</p> |
| 793 | |
| 794 | @ <hr> |
| 795 | entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", |
| 796 | "anoncookls", "840", 0); |
| 797 | @ <p>The number of minutes for which an anonymous login cookie is valid. |
| 798 | @ Set to zero to disable anonymous logins. |
| 799 | @ (property: anon-cookie-lifespan) |
| 800 | |
| 801 | @ <hr> |
| 802 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 803 | @ </div></form> |
| 804 | db_end_transaction(0); |
| 805 | style_finish_page(); |
| @@ -968,10 +990,15 @@ | |
| 990 | "1", "HH:MM:SS", |
| 991 | "2", "YYYY-MM-DD HH:MM", |
| 992 | "3", "YYMMDD HH:MM", |
| 993 | "4", "(off)" |
| 994 | }; |
| 995 | static const char *const azLeafMark[] = { |
| 996 | "0", "No", |
| 997 | "1", "Yes", |
| 998 | "2", "Yes - with emphasis", |
| 999 | }; |
| 1000 | login_check_credentials(); |
| 1001 | if( !g.perm.Admin ){ |
| 1002 | login_needed(0); |
| 1003 | return; |
| 1004 | } |
| @@ -1056,10 +1083,16 @@ | |
| 1083 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1084 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1085 | @ the complete date and time is shown on every timeline entry using the |
| 1086 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1087 | |
| 1088 | @ <hr> |
| 1089 | multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves", |
| 1090 | "tml", "1", count(azLeafMark)/2, azLeafMark); |
| 1091 | @ <p>Should timeline entries for leaf check-ins be identified in the |
| 1092 | @ detail section. (Property: "timeline-mark-leaves")</p> |
| 1093 | |
| 1094 | @ <hr> |
| 1095 | entry_attribute("Max timeline comment length", 6, |
| 1096 | "timeline-max-comment", "tmc", "0", 0); |
| 1097 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1098 | @ "0" there is no length limit. |
| @@ -1158,11 +1191,11 @@ | |
| 1191 | continue; |
| 1192 | } |
| 1193 | onoff_attribute("", pSet->name, |
| 1194 | pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, |
| 1195 | is_truth(pSet->def), hasVersionableValue); |
| 1196 | @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a> |
| 1197 | if( pSet->versionable ){ |
| 1198 | @ (v)<br> |
| 1199 | } else { |
| 1200 | @ <br> |
| 1201 | } |
| @@ -1177,11 +1210,11 @@ | |
| 1210 | (db_get_versioned(pSet->name, NULL, NULL)!=0); |
| 1211 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1212 | continue; |
| 1213 | } |
| 1214 | @ <tr><td> |
| 1215 | @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a> |
| 1216 | if( pSet->versionable ){ |
| 1217 | @ (v) |
| 1218 | } else { |
| 1219 | @ |
| 1220 | } |
| @@ -1198,11 +1231,11 @@ | |
| 1231 | if( pSet->width>0 && pSet->forceTextArea ){ |
| 1232 | int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0; |
| 1233 | if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ |
| 1234 | continue; |
| 1235 | } |
| 1236 | @ <a href='%R/help/%s(pSet->name)'>%s(pSet->name)</a> |
| 1237 | if( pSet->versionable ){ |
| 1238 | @ (v)<br> |
| 1239 | } else { |
| 1240 | @ <br> |
| 1241 | } |
| @@ -1309,28 +1342,10 @@ | |
| 1342 | @ Omit the trailing "/". |
| 1343 | @ If this repo will not be set up as a persistent server and will not |
| 1344 | @ be sending email alerts, then leave this entry blank. |
| 1345 | @ Suggested value: "%h(g.zBaseURL)" |
| 1346 | @ (Property: "email-url")</p> |
| 1347 | @ <hr> |
| 1348 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1349 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1350 | @ option is selected and when no pathname is |
| 1351 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1408,10 +1423,66 @@ | |
| 1423 | @ <p>The default value is blank, meaning no added entries. |
| 1424 | @ (Property: sitemap-extra) |
| 1425 | @ <p> |
| 1426 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1427 | "sitemap-extra", "smextra", "", 0); |
| 1428 | @ <hr> |
| 1429 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1430 | @ </div></form> |
| 1431 | db_end_transaction(0); |
| 1432 | style_finish_page(); |
| 1433 | } |
| 1434 | |
| 1435 | /* |
| 1436 | ** WEBPAGE: setup_download |
| 1437 | ** |
| 1438 | ** The "Admin/Download" page. Requires Setup privilege. |
| 1439 | */ |
| 1440 | void setup_download(void){ |
| 1441 | login_check_credentials(); |
| 1442 | if( !g.perm.Setup ){ |
| 1443 | login_needed(0); |
| 1444 | return; |
| 1445 | } |
| 1446 | |
| 1447 | style_set_current_feature("setup"); |
| 1448 | style_header("Tarball and ZIP Downloads"); |
| 1449 | db_begin_transaction(); |
| 1450 | @ <form action="%R/setup_download" method="post"><div> |
| 1451 | login_insert_csrf_secret(); |
| 1452 | @ <input type="submit" name="submit" value="Apply Changes"></p> |
| 1453 | @ <hr> |
| 1454 | entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name", |
| 1455 | "spn", "", 0); |
| 1456 | @ <p>This is used as a prefix for the names of generated tarballs and |
| 1457 | @ ZIP archive. Keep this prefix brief and use only lower-case ASCII |
| 1458 | @ characters, digits, "_", "-" in the name. If this setting is blank, |
| 1459 | @ then the full <a href='%R/help/project-name'>project-name</a> setting |
| 1460 | @ is used instead. |
| 1461 | @ (Property: "short-project-name") |
| 1462 | @ </p> |
| 1463 | @ <hr> |
| 1464 | @ <p><b>Configuration for the <a href="%R/download">/download</a> page.</b> |
| 1465 | @ <p>The value is a TCL list divided into groups of four tokens: |
| 1466 | @ <ol> |
| 1467 | @ <li> Maximum number of matches (COUNT). |
| 1468 | @ <li> Tag to match using glob (TAG). |
| 1469 | @ <li> Maximum age of check-ins to match (MAX_AGE). |
| 1470 | @ <li> Comment to apply to matches (COMMENT). |
| 1471 | @ </ol> |
| 1472 | @ Each 4-tuple will match zero or more check-ins. The /download page |
| 1473 | @ displays the union of matches from all 4-tuples. |
| 1474 | @ See the <a href="%R/help/suggested-downloads">suggested-downloads</a> |
| 1475 | @ setting documentation for further detail. |
| 1476 | @ <p> |
| 1477 | @ The /download page is omitted from the <a href="%R/sitemap">/sitemap</a> |
| 1478 | @ if the first token is "0" or "off" or "no". The default value |
| 1479 | @ for this setting is "off". |
| 1480 | @ (Property: <a href="%R/help/suggested-downloads">suggested-downloads</a>) |
| 1481 | @ <p> |
| 1482 | textarea_attribute("", 4, 80, |
| 1483 | "suggested-downloads", "sgtrlst", "off", 0); |
| 1484 | @ <hr> |
| 1485 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1486 | @ </div></form> |
| 1487 | db_end_transaction(0); |
| 1488 | style_finish_page(); |
| 1489 |
+3
| --- src/sitemap.c | ||
| +++ src/sitemap.c | ||
| @@ -132,10 +132,13 @@ | ||
| 132 | 132 | @ <li>%z(href("%R/uvlist"))Unversioned Files</a> |
| 133 | 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | 135 | } |
| 136 | 136 | @ </ul> |
| 137 | + } | |
| 138 | + if( g.perm.Zip && db_get_boolean("suggested-downloads",0)!=0 ){ | |
| 139 | + @ <li>%z(href("%R/download"))Tarballs and ZIPs</a> | |
| 137 | 140 | } |
| 138 | 141 | if( g.perm.Read ){ |
| 139 | 142 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 140 | 143 | @ <ul> |
| 141 | 144 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 142 | 145 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -132,10 +132,13 @@ | |
| 132 | @ <li>%z(href("%R/uvlist"))Unversioned Files</a> |
| 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | } |
| 136 | @ </ul> |
| 137 | } |
| 138 | if( g.perm.Read ){ |
| 139 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 140 | @ <ul> |
| 141 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 142 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -132,10 +132,13 @@ | |
| 132 | @ <li>%z(href("%R/uvlist"))Unversioned Files</a> |
| 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | } |
| 136 | @ </ul> |
| 137 | } |
| 138 | if( g.perm.Zip && db_get_boolean("suggested-downloads",0)!=0 ){ |
| 139 | @ <li>%z(href("%R/download"))Tarballs and ZIPs</a> |
| 140 | } |
| 141 | if( g.perm.Read ){ |
| 142 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 143 | @ <ul> |
| 144 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 145 |
+2
-2
| --- src/skins.c | ||
| +++ src/skins.c | ||
| @@ -1345,11 +1345,11 @@ | ||
| 1345 | 1345 | } |
| 1346 | 1346 | |
| 1347 | 1347 | /* |
| 1348 | 1348 | ** WEBPAGE: skins |
| 1349 | 1349 | ** |
| 1350 | -** Show a list of all of the built-in skins, plus the respository skin, | |
| 1350 | +** Show a list of all of the built-in skins, plus the repository skin, | |
| 1351 | 1351 | ** and provide the user with an opportunity to change to any of them. |
| 1352 | 1352 | */ |
| 1353 | 1353 | void skins_page(void){ |
| 1354 | 1354 | int i; |
| 1355 | 1355 | char *zBase = fossil_strdup(g.zTop); |
| @@ -1373,11 +1373,11 @@ | ||
| 1373 | 1373 | @ you are using a draft skin, |
| 1374 | 1374 | }else{ |
| 1375 | 1375 | @ this fossil instance was started with a hard-coded skin |
| 1376 | 1376 | @ value |
| 1377 | 1377 | } |
| 1378 | - @ which supercedes any option selected below. A skin selected | |
| 1378 | + @ which supersedes any option selected below. A skin selected | |
| 1379 | 1379 | @ below will be recorded in your |
| 1380 | 1380 | @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie |
| 1381 | 1381 | @ but will not be used so long as the site has a |
| 1382 | 1382 | @ higher-priority skin in place. |
| 1383 | 1383 | @ </p> |
| 1384 | 1384 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -1345,11 +1345,11 @@ | |
| 1345 | } |
| 1346 | |
| 1347 | /* |
| 1348 | ** WEBPAGE: skins |
| 1349 | ** |
| 1350 | ** Show a list of all of the built-in skins, plus the respository skin, |
| 1351 | ** and provide the user with an opportunity to change to any of them. |
| 1352 | */ |
| 1353 | void skins_page(void){ |
| 1354 | int i; |
| 1355 | char *zBase = fossil_strdup(g.zTop); |
| @@ -1373,11 +1373,11 @@ | |
| 1373 | @ you are using a draft skin, |
| 1374 | }else{ |
| 1375 | @ this fossil instance was started with a hard-coded skin |
| 1376 | @ value |
| 1377 | } |
| 1378 | @ which supercedes any option selected below. A skin selected |
| 1379 | @ below will be recorded in your |
| 1380 | @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie |
| 1381 | @ but will not be used so long as the site has a |
| 1382 | @ higher-priority skin in place. |
| 1383 | @ </p> |
| 1384 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -1345,11 +1345,11 @@ | |
| 1345 | } |
| 1346 | |
| 1347 | /* |
| 1348 | ** WEBPAGE: skins |
| 1349 | ** |
| 1350 | ** Show a list of all of the built-in skins, plus the repository skin, |
| 1351 | ** and provide the user with an opportunity to change to any of them. |
| 1352 | */ |
| 1353 | void skins_page(void){ |
| 1354 | int i; |
| 1355 | char *zBase = fossil_strdup(g.zTop); |
| @@ -1373,11 +1373,11 @@ | |
| 1373 | @ you are using a draft skin, |
| 1374 | }else{ |
| 1375 | @ this fossil instance was started with a hard-coded skin |
| 1376 | @ value |
| 1377 | } |
| 1378 | @ which supersedes any option selected below. A skin selected |
| 1379 | @ below will be recorded in your |
| 1380 | @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie |
| 1381 | @ but will not be used so long as the site has a |
| 1382 | @ higher-priority skin in place. |
| 1383 | @ </p> |
| 1384 |
+15
-5
| --- src/stash.c | ||
| +++ src/stash.c | ||
| @@ -550,22 +550,26 @@ | ||
| 550 | 550 | ** |
| 551 | 551 | ** Update to the baseline check-out for STASHID then apply the |
| 552 | 552 | ** changes of STASHID. Keep STASHID so that it can be reused |
| 553 | 553 | ** This command is undoable. |
| 554 | 554 | ** |
| 555 | -** > fossil stash drop|rm ?STASHID? ?-a|--all? | |
| 555 | +** > fossil stash drop|rm ?STASHIDs...? ?-a|--all? | |
| 556 | 556 | ** |
| 557 | -** Forget everything about STASHID. Forget the whole stash if the | |
| 558 | -** -a|--all flag is used. Individual drops are undoable but -a|--all | |
| 559 | -** is not. | |
| 557 | +** Forget everything about the given STASHIDs. Forget the whole | |
| 558 | +** stash if the -a|--all flag is used. Individual drops are | |
| 559 | +** undoable but -a|--all is not. | |
| 560 | 560 | ** |
| 561 | 561 | ** > fossil stash diff ?STASHID? ?DIFF-OPTIONS? |
| 562 | 562 | ** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS? |
| 563 | 563 | ** |
| 564 | 564 | ** Show diffs of the current working directory and what that |
| 565 | 565 | ** directory would be if STASHID were applied. With gdiff, |
| 566 | 566 | ** gdiff-command is used instead of internal diff logic. |
| 567 | +** | |
| 568 | +** > fossil stash rename STASHID NEW-NAME | |
| 569 | +** | |
| 570 | +** Change the description of the given STASHID entry to NEW-NAME. | |
| 567 | 571 | */ |
| 568 | 572 | void stash_cmd(void){ |
| 569 | 573 | const char *zCmd; |
| 570 | 574 | int nCmd; |
| 571 | 575 | int stashid = 0; |
| @@ -771,11 +775,17 @@ | ||
| 771 | 775 | } |
| 772 | 776 | diff_options(&DCfg, zCmd[0]=='g', 0); |
| 773 | 777 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 774 | 778 | stash_diff(stashid, fBaseline, &DCfg); |
| 775 | 779 | }else |
| 776 | - if( strncmp(zCmd, "help", nCmd)==0 ){ | |
| 780 | + if( strncmp(zCmd, "rename", nCmd)==0 ){ | |
| 781 | + if( g.argc!=5 ) usage("rename STASHID NAME"); | |
| 782 | + stashid = stash_get_id(g.argv[3]); | |
| 783 | + db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d", | |
| 784 | + g.argv[4], stashid); | |
| 785 | + } | |
| 786 | + else if( strncmp(zCmd, "help", nCmd)==0 ){ | |
| 777 | 787 | g.argv[1] = "help"; |
| 778 | 788 | g.argv[2] = "stash"; |
| 779 | 789 | g.argc = 3; |
| 780 | 790 | help_cmd(); |
| 781 | 791 | }else |
| 782 | 792 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -550,22 +550,26 @@ | |
| 550 | ** |
| 551 | ** Update to the baseline check-out for STASHID then apply the |
| 552 | ** changes of STASHID. Keep STASHID so that it can be reused |
| 553 | ** This command is undoable. |
| 554 | ** |
| 555 | ** > fossil stash drop|rm ?STASHID? ?-a|--all? |
| 556 | ** |
| 557 | ** Forget everything about STASHID. Forget the whole stash if the |
| 558 | ** -a|--all flag is used. Individual drops are undoable but -a|--all |
| 559 | ** is not. |
| 560 | ** |
| 561 | ** > fossil stash diff ?STASHID? ?DIFF-OPTIONS? |
| 562 | ** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS? |
| 563 | ** |
| 564 | ** Show diffs of the current working directory and what that |
| 565 | ** directory would be if STASHID were applied. With gdiff, |
| 566 | ** gdiff-command is used instead of internal diff logic. |
| 567 | */ |
| 568 | void stash_cmd(void){ |
| 569 | const char *zCmd; |
| 570 | int nCmd; |
| 571 | int stashid = 0; |
| @@ -771,11 +775,17 @@ | |
| 771 | } |
| 772 | diff_options(&DCfg, zCmd[0]=='g', 0); |
| 773 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 774 | stash_diff(stashid, fBaseline, &DCfg); |
| 775 | }else |
| 776 | if( strncmp(zCmd, "help", nCmd)==0 ){ |
| 777 | g.argv[1] = "help"; |
| 778 | g.argv[2] = "stash"; |
| 779 | g.argc = 3; |
| 780 | help_cmd(); |
| 781 | }else |
| 782 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -550,22 +550,26 @@ | |
| 550 | ** |
| 551 | ** Update to the baseline check-out for STASHID then apply the |
| 552 | ** changes of STASHID. Keep STASHID so that it can be reused |
| 553 | ** This command is undoable. |
| 554 | ** |
| 555 | ** > fossil stash drop|rm ?STASHIDs...? ?-a|--all? |
| 556 | ** |
| 557 | ** Forget everything about the given STASHIDs. Forget the whole |
| 558 | ** stash if the -a|--all flag is used. Individual drops are |
| 559 | ** undoable but -a|--all is not. |
| 560 | ** |
| 561 | ** > fossil stash diff ?STASHID? ?DIFF-OPTIONS? |
| 562 | ** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS? |
| 563 | ** |
| 564 | ** Show diffs of the current working directory and what that |
| 565 | ** directory would be if STASHID were applied. With gdiff, |
| 566 | ** gdiff-command is used instead of internal diff logic. |
| 567 | ** |
| 568 | ** > fossil stash rename STASHID NEW-NAME |
| 569 | ** |
| 570 | ** Change the description of the given STASHID entry to NEW-NAME. |
| 571 | */ |
| 572 | void stash_cmd(void){ |
| 573 | const char *zCmd; |
| 574 | int nCmd; |
| 575 | int stashid = 0; |
| @@ -771,11 +775,17 @@ | |
| 775 | } |
| 776 | diff_options(&DCfg, zCmd[0]=='g', 0); |
| 777 | stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0); |
| 778 | stash_diff(stashid, fBaseline, &DCfg); |
| 779 | }else |
| 780 | if( strncmp(zCmd, "rename", nCmd)==0 ){ |
| 781 | if( g.argc!=5 ) usage("rename STASHID NAME"); |
| 782 | stashid = stash_get_id(g.argv[3]); |
| 783 | db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d", |
| 784 | g.argv[4], stashid); |
| 785 | } |
| 786 | else if( strncmp(zCmd, "help", nCmd)==0 ){ |
| 787 | g.argv[1] = "help"; |
| 788 | g.argv[2] = "stash"; |
| 789 | g.argc = 3; |
| 790 | help_cmd(); |
| 791 | }else |
| 792 |
+21
-62
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -478,11 +478,11 @@ | ||
| 478 | 478 | /* |
| 479 | 479 | ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js |
| 480 | 480 | ** Javascript module, and generates HTML elements with the following IDs: |
| 481 | 481 | ** |
| 482 | 482 | ** TARGETID: The <span> wrapper around TEXT. |
| 483 | -** copy-TARGETID: The <span> for the copy button. | |
| 483 | +** copy-TARGETID: The <button> for the copy button. | |
| 484 | 484 | ** |
| 485 | 485 | ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT. |
| 486 | 486 | ** |
| 487 | 487 | ** The COPYLENGTH argument defines the length of the substring of TEXT copied to |
| 488 | 488 | ** clipboard: |
| @@ -510,18 +510,20 @@ | ||
| 510 | 510 | if( cchLength==1 ) cchLength = hash_digits(0); |
| 511 | 511 | else if( cchLength==2 ) cchLength = hash_digits(1); |
| 512 | 512 | if( !bFlipped ){ |
| 513 | 513 | const char *zBtnFmt = |
| 514 | 514 | "<span class=\"nobr\">" |
| 515 | - "<span " | |
| 516 | - "class=\"copy-button\" " | |
| 517 | - "id=\"copy-%h\" " | |
| 518 | - "data-copytarget=\"%h\" " | |
| 519 | - "data-copylength=\"%d\">" | |
| 520 | - "</span>" | |
| 515 | + "<button " | |
| 516 | + "class=\"copy-button\" " | |
| 517 | + "id=\"copy-%h\" " | |
| 518 | + "data-copytarget=\"%h\" " | |
| 519 | + "data-copylength=\"%d\">" | |
| 520 | + "<span>" | |
| 521 | + "</span>" | |
| 522 | + "</button>" | |
| 521 | 523 | "<span id=\"%h\">" |
| 522 | - "%s" | |
| 524 | + "%s" | |
| 523 | 525 | "</span>" |
| 524 | 526 | "</span>"; |
| 525 | 527 | if( bOutputCGI ){ |
| 526 | 528 | cgi_printf( |
| 527 | 529 | zBtnFmt/*works-like:"%h%h%d%h%s"*/, |
| @@ -533,18 +535,20 @@ | ||
| 533 | 535 | } |
| 534 | 536 | }else{ |
| 535 | 537 | const char *zBtnFmt = |
| 536 | 538 | "<span class=\"nobr\">" |
| 537 | 539 | "<span id=\"%h\">" |
| 538 | - "%s" | |
| 539 | - "</span>" | |
| 540 | - "<span " | |
| 541 | - "class=\"copy-button copy-button-flipped\" " | |
| 542 | - "id=\"copy-%h\" " | |
| 543 | - "data-copytarget=\"%h\" " | |
| 544 | - "data-copylength=\"%d\">" | |
| 545 | - "</span>" | |
| 540 | + "%s" | |
| 541 | + "</span>" | |
| 542 | + "<button " | |
| 543 | + "class=\"copy-button copy-button-flipped\" " | |
| 544 | + "id=\"copy-%h\" " | |
| 545 | + "data-copytarget=\"%h\" " | |
| 546 | + "data-copylength=\"%d\">" | |
| 547 | + "<span>" | |
| 548 | + "</span>" | |
| 549 | + "</button>" | |
| 546 | 550 | "</span>"; |
| 547 | 551 | if( bOutputCGI ){ |
| 548 | 552 | cgi_printf( |
| 549 | 553 | zBtnFmt/*works-like:"%h%s%h%h%d"*/, |
| 550 | 554 | zTargetId,zText,zTargetId,zTargetId,cchLength); |
| @@ -1386,55 +1390,10 @@ | ||
| 1386 | 1390 | */ |
| 1387 | 1391 | void page_test_env(void){ |
| 1388 | 1392 | webpage_error(""); |
| 1389 | 1393 | } |
| 1390 | 1394 | |
| 1391 | -/* | |
| 1392 | -** WEBPAGE: honeypot | |
| 1393 | -** This page is a honeypot for spiders and bots. | |
| 1394 | -*/ | |
| 1395 | -void honeypot_page(void){ | |
| 1396 | - unsigned int uSeed = captcha_seed(); | |
| 1397 | - const char *zDecoded = captcha_decode(uSeed, 0); | |
| 1398 | - int bAutoCaptcha = db_get_boolean("auto-captcha", 0); | |
| 1399 | - char *zCaptcha = captcha_render(zDecoded); | |
| 1400 | - style_header("I think you are a robot"); | |
| 1401 | - @ <p>You seem like a robot.</p> | |
| 1402 | - @ | |
| 1403 | - @ <p>Is that incorrect? Are you really human? | |
| 1404 | - @ If so, please prove it by transcribing the captcha text | |
| 1405 | - @ into the entry box below and pressing "Submit". | |
| 1406 | - @ <form action="%R/login" method="post"> | |
| 1407 | - @ <input type="hidden" id="u" name="u" value="anonymous"> | |
| 1408 | - @ <p> | |
| 1409 | - @ Captcha: <input type="text" id="p" name="p" value=""> | |
| 1410 | - @ <input type="submit" name="in" value="Submit"> | |
| 1411 | - @ | |
| 1412 | - @ <p>Alternatively, you can <a href="%R/login">log in</a> using an | |
| 1413 | - @ existing userid. | |
| 1414 | - @ | |
| 1415 | - @ <p><input type="hidden" name="cs" value="%u(uSeed)"> | |
| 1416 | - @ <div class="captcha"><table class="captcha"><tr><td>\ | |
| 1417 | - @ <pre class="captcha"> | |
| 1418 | - @ %h(zCaptcha) | |
| 1419 | - @ </pre></td></tr></table> | |
| 1420 | - if( bAutoCaptcha ) { | |
| 1421 | - @ <input type="button" value="Fill out captcha" id='autofillButton' \ | |
| 1422 | - @ data-af='%s(zDecoded)'> | |
| 1423 | - builtin_request_js("login.js"); | |
| 1424 | - } | |
| 1425 | - @ </div> | |
| 1426 | - free(zCaptcha); | |
| 1427 | - @ | |
| 1428 | - @ <p>We regret this inconvenience. However, robots have become so | |
| 1429 | - @ prolific and so aggressive that they will soak up too much CPU time | |
| 1430 | - @ and network bandwidth on our servers if allowed to run unchecked. | |
| 1431 | - @ Your cooperation in demonstrating that you are human is | |
| 1432 | - @ appreciated. | |
| 1433 | - style_finish_page(); | |
| 1434 | -} | |
| 1435 | - | |
| 1436 | 1395 | /* |
| 1437 | 1396 | ** Webpages that encounter an error due to missing or incorrect |
| 1438 | 1397 | ** query parameters can jump to this routine to render an error |
| 1439 | 1398 | ** message screen. |
| 1440 | 1399 | ** |
| @@ -1483,11 +1442,11 @@ | ||
| 1483 | 1442 | @ g.zHttpsURL = %h(g.zHttpsURL)<br> |
| 1484 | 1443 | @ g.zTop = %h(g.zTop)<br> |
| 1485 | 1444 | @ g.zPath = %h(g.zPath)<br> |
| 1486 | 1445 | @ g.userUid = %d(g.userUid)<br> |
| 1487 | 1446 | @ g.zLogin = %h(g.zLogin)<br> |
| 1488 | - @ g.isHuman = %d(g.isHuman)<br> | |
| 1447 | + @ g.isRobot = %d(g.isRobot)<br> | |
| 1489 | 1448 | @ g.jsHref = %d(g.jsHref)<br> |
| 1490 | 1449 | if( g.zLocalRoot ){ |
| 1491 | 1450 | @ g.zLocalRoot = %h(g.zLocalRoot)<br> |
| 1492 | 1451 | }else{ |
| 1493 | 1452 | @ g.zLocalRoot = <i>none</i><br> |
| 1494 | 1453 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -478,11 +478,11 @@ | |
| 478 | /* |
| 479 | ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js |
| 480 | ** Javascript module, and generates HTML elements with the following IDs: |
| 481 | ** |
| 482 | ** TARGETID: The <span> wrapper around TEXT. |
| 483 | ** copy-TARGETID: The <span> for the copy button. |
| 484 | ** |
| 485 | ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT. |
| 486 | ** |
| 487 | ** The COPYLENGTH argument defines the length of the substring of TEXT copied to |
| 488 | ** clipboard: |
| @@ -510,18 +510,20 @@ | |
| 510 | if( cchLength==1 ) cchLength = hash_digits(0); |
| 511 | else if( cchLength==2 ) cchLength = hash_digits(1); |
| 512 | if( !bFlipped ){ |
| 513 | const char *zBtnFmt = |
| 514 | "<span class=\"nobr\">" |
| 515 | "<span " |
| 516 | "class=\"copy-button\" " |
| 517 | "id=\"copy-%h\" " |
| 518 | "data-copytarget=\"%h\" " |
| 519 | "data-copylength=\"%d\">" |
| 520 | "</span>" |
| 521 | "<span id=\"%h\">" |
| 522 | "%s" |
| 523 | "</span>" |
| 524 | "</span>"; |
| 525 | if( bOutputCGI ){ |
| 526 | cgi_printf( |
| 527 | zBtnFmt/*works-like:"%h%h%d%h%s"*/, |
| @@ -533,18 +535,20 @@ | |
| 533 | } |
| 534 | }else{ |
| 535 | const char *zBtnFmt = |
| 536 | "<span class=\"nobr\">" |
| 537 | "<span id=\"%h\">" |
| 538 | "%s" |
| 539 | "</span>" |
| 540 | "<span " |
| 541 | "class=\"copy-button copy-button-flipped\" " |
| 542 | "id=\"copy-%h\" " |
| 543 | "data-copytarget=\"%h\" " |
| 544 | "data-copylength=\"%d\">" |
| 545 | "</span>" |
| 546 | "</span>"; |
| 547 | if( bOutputCGI ){ |
| 548 | cgi_printf( |
| 549 | zBtnFmt/*works-like:"%h%s%h%h%d"*/, |
| 550 | zTargetId,zText,zTargetId,zTargetId,cchLength); |
| @@ -1386,55 +1390,10 @@ | |
| 1386 | */ |
| 1387 | void page_test_env(void){ |
| 1388 | webpage_error(""); |
| 1389 | } |
| 1390 | |
| 1391 | /* |
| 1392 | ** WEBPAGE: honeypot |
| 1393 | ** This page is a honeypot for spiders and bots. |
| 1394 | */ |
| 1395 | void honeypot_page(void){ |
| 1396 | unsigned int uSeed = captcha_seed(); |
| 1397 | const char *zDecoded = captcha_decode(uSeed, 0); |
| 1398 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| 1399 | char *zCaptcha = captcha_render(zDecoded); |
| 1400 | style_header("I think you are a robot"); |
| 1401 | @ <p>You seem like a robot.</p> |
| 1402 | @ |
| 1403 | @ <p>Is that incorrect? Are you really human? |
| 1404 | @ If so, please prove it by transcribing the captcha text |
| 1405 | @ into the entry box below and pressing "Submit". |
| 1406 | @ <form action="%R/login" method="post"> |
| 1407 | @ <input type="hidden" id="u" name="u" value="anonymous"> |
| 1408 | @ <p> |
| 1409 | @ Captcha: <input type="text" id="p" name="p" value=""> |
| 1410 | @ <input type="submit" name="in" value="Submit"> |
| 1411 | @ |
| 1412 | @ <p>Alternatively, you can <a href="%R/login">log in</a> using an |
| 1413 | @ existing userid. |
| 1414 | @ |
| 1415 | @ <p><input type="hidden" name="cs" value="%u(uSeed)"> |
| 1416 | @ <div class="captcha"><table class="captcha"><tr><td>\ |
| 1417 | @ <pre class="captcha"> |
| 1418 | @ %h(zCaptcha) |
| 1419 | @ </pre></td></tr></table> |
| 1420 | if( bAutoCaptcha ) { |
| 1421 | @ <input type="button" value="Fill out captcha" id='autofillButton' \ |
| 1422 | @ data-af='%s(zDecoded)'> |
| 1423 | builtin_request_js("login.js"); |
| 1424 | } |
| 1425 | @ </div> |
| 1426 | free(zCaptcha); |
| 1427 | @ |
| 1428 | @ <p>We regret this inconvenience. However, robots have become so |
| 1429 | @ prolific and so aggressive that they will soak up too much CPU time |
| 1430 | @ and network bandwidth on our servers if allowed to run unchecked. |
| 1431 | @ Your cooperation in demonstrating that you are human is |
| 1432 | @ appreciated. |
| 1433 | style_finish_page(); |
| 1434 | } |
| 1435 | |
| 1436 | /* |
| 1437 | ** Webpages that encounter an error due to missing or incorrect |
| 1438 | ** query parameters can jump to this routine to render an error |
| 1439 | ** message screen. |
| 1440 | ** |
| @@ -1483,11 +1442,11 @@ | |
| 1483 | @ g.zHttpsURL = %h(g.zHttpsURL)<br> |
| 1484 | @ g.zTop = %h(g.zTop)<br> |
| 1485 | @ g.zPath = %h(g.zPath)<br> |
| 1486 | @ g.userUid = %d(g.userUid)<br> |
| 1487 | @ g.zLogin = %h(g.zLogin)<br> |
| 1488 | @ g.isHuman = %d(g.isHuman)<br> |
| 1489 | @ g.jsHref = %d(g.jsHref)<br> |
| 1490 | if( g.zLocalRoot ){ |
| 1491 | @ g.zLocalRoot = %h(g.zLocalRoot)<br> |
| 1492 | }else{ |
| 1493 | @ g.zLocalRoot = <i>none</i><br> |
| 1494 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -478,11 +478,11 @@ | |
| 478 | /* |
| 479 | ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js |
| 480 | ** Javascript module, and generates HTML elements with the following IDs: |
| 481 | ** |
| 482 | ** TARGETID: The <span> wrapper around TEXT. |
| 483 | ** copy-TARGETID: The <button> for the copy button. |
| 484 | ** |
| 485 | ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT. |
| 486 | ** |
| 487 | ** The COPYLENGTH argument defines the length of the substring of TEXT copied to |
| 488 | ** clipboard: |
| @@ -510,18 +510,20 @@ | |
| 510 | if( cchLength==1 ) cchLength = hash_digits(0); |
| 511 | else if( cchLength==2 ) cchLength = hash_digits(1); |
| 512 | if( !bFlipped ){ |
| 513 | const char *zBtnFmt = |
| 514 | "<span class=\"nobr\">" |
| 515 | "<button " |
| 516 | "class=\"copy-button\" " |
| 517 | "id=\"copy-%h\" " |
| 518 | "data-copytarget=\"%h\" " |
| 519 | "data-copylength=\"%d\">" |
| 520 | "<span>" |
| 521 | "</span>" |
| 522 | "</button>" |
| 523 | "<span id=\"%h\">" |
| 524 | "%s" |
| 525 | "</span>" |
| 526 | "</span>"; |
| 527 | if( bOutputCGI ){ |
| 528 | cgi_printf( |
| 529 | zBtnFmt/*works-like:"%h%h%d%h%s"*/, |
| @@ -533,18 +535,20 @@ | |
| 535 | } |
| 536 | }else{ |
| 537 | const char *zBtnFmt = |
| 538 | "<span class=\"nobr\">" |
| 539 | "<span id=\"%h\">" |
| 540 | "%s" |
| 541 | "</span>" |
| 542 | "<button " |
| 543 | "class=\"copy-button copy-button-flipped\" " |
| 544 | "id=\"copy-%h\" " |
| 545 | "data-copytarget=\"%h\" " |
| 546 | "data-copylength=\"%d\">" |
| 547 | "<span>" |
| 548 | "</span>" |
| 549 | "</button>" |
| 550 | "</span>"; |
| 551 | if( bOutputCGI ){ |
| 552 | cgi_printf( |
| 553 | zBtnFmt/*works-like:"%h%s%h%h%d"*/, |
| 554 | zTargetId,zText,zTargetId,zTargetId,cchLength); |
| @@ -1386,55 +1390,10 @@ | |
| 1390 | */ |
| 1391 | void page_test_env(void){ |
| 1392 | webpage_error(""); |
| 1393 | } |
| 1394 | |
| 1395 | /* |
| 1396 | ** Webpages that encounter an error due to missing or incorrect |
| 1397 | ** query parameters can jump to this routine to render an error |
| 1398 | ** message screen. |
| 1399 | ** |
| @@ -1483,11 +1442,11 @@ | |
| 1442 | @ g.zHttpsURL = %h(g.zHttpsURL)<br> |
| 1443 | @ g.zTop = %h(g.zTop)<br> |
| 1444 | @ g.zPath = %h(g.zPath)<br> |
| 1445 | @ g.userUid = %d(g.userUid)<br> |
| 1446 | @ g.zLogin = %h(g.zLogin)<br> |
| 1447 | @ g.isRobot = %d(g.isRobot)<br> |
| 1448 | @ g.jsHref = %d(g.jsHref)<br> |
| 1449 | if( g.zLocalRoot ){ |
| 1450 | @ g.zLocalRoot = %h(g.zLocalRoot)<br> |
| 1451 | }else{ |
| 1452 | @ g.zLocalRoot = <i>none</i><br> |
| 1453 |
+1
-1
| --- src/tag.c | ||
| +++ src/tag.c | ||
| @@ -218,11 +218,11 @@ | ||
| 218 | 218 | } |
| 219 | 219 | } |
| 220 | 220 | if( zCol ){ |
| 221 | 221 | db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d", |
| 222 | 222 | zCol, zValue, rid); |
| 223 | - if( tagid==TAG_COMMENT ){ | |
| 223 | + if( tagid==TAG_COMMENT && zValue!=0 ){ | |
| 224 | 224 | char *zCopy = fossil_strdup(zValue); |
| 225 | 225 | backlink_extract(zCopy, MT_NONE, rid, BKLNK_COMMENT, mtime, 1); |
| 226 | 226 | free(zCopy); |
| 227 | 227 | } |
| 228 | 228 | } |
| 229 | 229 |
| --- src/tag.c | |
| +++ src/tag.c | |
| @@ -218,11 +218,11 @@ | |
| 218 | } |
| 219 | } |
| 220 | if( zCol ){ |
| 221 | db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d", |
| 222 | zCol, zValue, rid); |
| 223 | if( tagid==TAG_COMMENT ){ |
| 224 | char *zCopy = fossil_strdup(zValue); |
| 225 | backlink_extract(zCopy, MT_NONE, rid, BKLNK_COMMENT, mtime, 1); |
| 226 | free(zCopy); |
| 227 | } |
| 228 | } |
| 229 |
| --- src/tag.c | |
| +++ src/tag.c | |
| @@ -218,11 +218,11 @@ | |
| 218 | } |
| 219 | } |
| 220 | if( zCol ){ |
| 221 | db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d", |
| 222 | zCol, zValue, rid); |
| 223 | if( tagid==TAG_COMMENT && zValue!=0 ){ |
| 224 | char *zCopy = fossil_strdup(zValue); |
| 225 | backlink_extract(zCopy, MT_NONE, rid, BKLNK_COMMENT, mtime, 1); |
| 226 | free(zCopy); |
| 227 | } |
| 228 | } |
| 229 |
+531
-34
| --- src/tar.c | ||
| +++ src/tar.c | ||
| @@ -31,10 +31,65 @@ | ||
| 31 | 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | 33 | Blob pax; /* PAX data */ |
| 34 | 34 | } tball; |
| 35 | 35 | |
| 36 | +/* | |
| 37 | +** Convert a string so that it contains only lower-case ASCII, digits, | |
| 38 | +** "_" and "-". Changes are made in-place. | |
| 39 | +*/ | |
| 40 | +static void sanitize_name(char *zName){ | |
| 41 | + int i; | |
| 42 | + char c; | |
| 43 | + for(i=0; (c = zName[i])!=0; i++){ | |
| 44 | + if( fossil_isupper(c) ){ | |
| 45 | + zName[i] = fossil_tolower(c); | |
| 46 | + }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){ | |
| 47 | + if( c<=0x7f ){ | |
| 48 | + zName[i] = '_'; | |
| 49 | + }else{ | |
| 50 | + /* 123456789 123456789 123456 */ | |
| 51 | + zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26]; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +/* | |
| 58 | +** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR) | |
| 59 | +** based on the rid of the check-in contained in that file. | |
| 60 | +** | |
| 61 | +** PROJECTNAME-DATETIME-HASHPREFIX | |
| 62 | +** | |
| 63 | +** So that the name will be safe to use as a URL or a filename on any system, | |
| 64 | +** the name is only allowed to contain lower-case ASCII alphabetics, | |
| 65 | +** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All | |
| 66 | +** other bytes are mapped into a lower-case alphabetic. | |
| 67 | +** | |
| 68 | +** The value returned is obtained from mprintf() or fossil_strdup() and should | |
| 69 | +** be released by the caller using fossil_free(). | |
| 70 | +*/ | |
| 71 | +char *archive_base_name(int rid){ | |
| 72 | + char *zPrefix; | |
| 73 | + char *zName; | |
| 74 | + zPrefix = db_get("short-project-name",0); | |
| 75 | + if( zPrefix==0 || zPrefix[0]==0 ){ | |
| 76 | + zPrefix = db_get("project-name","unnamed"); | |
| 77 | + } | |
| 78 | + zName = db_text(0, | |
| 79 | + "SELECT %Q||" | |
| 80 | + " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||" | |
| 81 | + " substr(blob.uuid,1,10)" | |
| 82 | + " FROM blob, event LEFT JOIN config" | |
| 83 | + " WHERE blob.rid=%d" | |
| 84 | + " AND event.objid=%d" | |
| 85 | + " AND config.name='project-name'", | |
| 86 | + zPrefix, rid, rid); | |
| 87 | + fossil_free(zPrefix); | |
| 88 | + sanitize_name(zName); | |
| 89 | + return zName; | |
| 90 | +} | |
| 36 | 91 | |
| 37 | 92 | /* |
| 38 | 93 | ** field lengths of 'ustar' name and prefix fields. |
| 39 | 94 | */ |
| 40 | 95 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | ||
| 653 | 708 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 654 | 709 | zOut = 0; |
| 655 | 710 | } |
| 656 | 711 | |
| 657 | 712 | if( zName==0 ){ |
| 658 | - zName = db_text("default-name", | |
| 659 | - "SELECT replace(%Q,' ','_') " | |
| 660 | - " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " | |
| 661 | - " || substr(blob.uuid, 1, 10)" | |
| 662 | - " FROM event, blob" | |
| 663 | - " WHERE event.objid=%d" | |
| 664 | - " AND blob.rid=%d", | |
| 665 | - db_get("project-name", "unnamed"), rid, rid | |
| 666 | - ); | |
| 713 | + zName = archive_base_name(rid); | |
| 667 | 714 | } |
| 668 | 715 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 669 | 716 | zName, pInclude, pExclude, listFlag); |
| 670 | 717 | glob_free(pInclude); |
| 671 | 718 | glob_free(pExclude); |
| @@ -675,45 +722,171 @@ | ||
| 675 | 722 | blob_reset(&tarball); |
| 676 | 723 | } |
| 677 | 724 | } |
| 678 | 725 | |
| 679 | 726 | /* |
| 680 | -** Check to see if the input string is of the form: | |
| 681 | -** | |
| 682 | -** check-in-name/filename.ext | |
| 683 | -** | |
| 684 | -** In other words, check to see if the input contains a single '/' | |
| 685 | -** character that separates a valid check-in name from a filename. | |
| 686 | -** | |
| 687 | -** If the condition is true, return the check-in name and set the | |
| 688 | -** input string to be the filename. | |
| 689 | -** | |
| 690 | -** If the condition is false, return NULL | |
| 727 | +** This is a helper routine for tar_uuid_from_name(). It handles | |
| 728 | +** the case where *pzName contains no "/" character. Check for | |
| 729 | +** format (3). Return the hash if the name matches format (3), | |
| 730 | +** or return NULL if it does not. | |
| 731 | +*/ | |
| 732 | +static char *format_three_parser(const char *zName){ | |
| 733 | + int iDot = 0; /* Index in zName[] of the first '.' */ | |
| 734 | + int iDash1 = 0; /* Index in zName[] of the '-' before the timestamp */ | |
| 735 | + int iDash2 = 0; /* Index in zName[] of the '-' between timestamp and hash */ | |
| 736 | + int nHash; /* Size of the hash */ | |
| 737 | + char *zHash; /* A copy of the hash value */ | |
| 738 | + char *zDate; /* Copy of the timestamp */ | |
| 739 | + char *zUuid; /* Final result */ | |
| 740 | + int i; /* Loop query */ | |
| 741 | + Stmt q; /* Query to verify that hash and timestamp agree */ | |
| 742 | + | |
| 743 | + for(i=0; zName[i]; i++){ | |
| 744 | + char c = zName[i]; | |
| 745 | + if( c=='.' ){ iDot = i; break; } | |
| 746 | + if( c=='-' ){ iDash1 = iDash2; iDash2 = i; } | |
| 747 | + if( !fossil_isalnum(c) && c!='_' && c!='-' ){ break; } | |
| 748 | + } | |
| 749 | + if( iDot==0 ) return 0; | |
| 750 | + if( iDash1==0 ) return 0; | |
| 751 | + nHash = iDot - iDash2 - 1; | |
| 752 | + if( nHash<8 ) return 0; /* HASH value too short */ | |
| 753 | + if( (iDash2 - iDash1)!=15 ) return 0; /* Wrong timestamp size */ | |
| 754 | + zHash = fossil_strndup(&zName[iDash2+1], nHash); | |
| 755 | + zDate = fossil_strndup(&zName[iDash1+1], 14); | |
| 756 | + db_prepare(&q, | |
| 757 | + "SELECT blob.uuid" | |
| 758 | + " FROM blob JOIN event ON event.objid=blob.rid" | |
| 759 | + " WHERE blob.uuid GLOB '%q*'" | |
| 760 | + " AND strftime('%%Y%%m%%d%%H%%M%%S',event.mtime)='%q'", | |
| 761 | + zHash, zDate | |
| 762 | + ); | |
| 763 | + fossil_free(zHash); | |
| 764 | + fossil_free(zDate); | |
| 765 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 766 | + zUuid = fossil_strdup(db_column_text(&q,0)); | |
| 767 | + }else{ | |
| 768 | + zUuid = 0; | |
| 769 | + } | |
| 770 | + db_finalize(&q); | |
| 771 | + return zUuid; | |
| 772 | +} | |
| 773 | + | |
| 774 | +/* | |
| 775 | +** Check to see if the input string is of one of the following | |
| 776 | +** two the forms: | |
| 777 | +** | |
| 778 | +** check-in-name/filename.ext (1) | |
| 779 | +** tag-name/check-in-name/filename.ext (2) | |
| 780 | +** project-datetime-hash.ext (3) | |
| 781 | +** | |
| 782 | +** In other words, check to see if the input string contains either | |
| 783 | +** a check-in name or a tag-name and a check-in name separated by | |
| 784 | +** a slash. There must be between 0 or 2 "/" characters. In the | |
| 785 | +** second form, tag-name must be an individual tag (not a branch-tag) | |
| 786 | +** that is found on the check-in identified by the check-in-name. | |
| 787 | +** | |
| 788 | +** If the condition is true, then: | |
| 789 | +** | |
| 790 | +** * Make *pzName point to the filename suffix only | |
| 791 | +** * return a copy of the check-in name in memory from mprintf(). | |
| 792 | +** | |
| 793 | +** If the condition is false, leave *pzName unchanged and return either | |
| 794 | +** NULL or an empty string. Normally NULL is returned, however an | |
| 795 | +** empty string is returned for format (2) if check-in-name does not | |
| 796 | +** match tag-name. | |
| 797 | +** | |
| 798 | +** Format (2) is specifically designed to allow URLs like this: | |
| 799 | +** | |
| 800 | +** /tarball/release/UUID/PROJECT.tar.gz | |
| 801 | +** | |
| 802 | +** Such URLs will pass through most anti-robot filters because of the | |
| 803 | +** "/tarball/release" prefix will match the suggested "robot-exception" | |
| 804 | +** pattern and can still refer to an historic release rather than just | |
| 805 | +** the most recent release. | |
| 806 | +** | |
| 807 | +** Format (3) is designed to allow URLs like this: | |
| 808 | +** | |
| 809 | +** /tarball/fossil-20251018193920-d6c9aee97df.tar.gz | |
| 810 | +** | |
| 811 | +** In other words, filename itself contains sufficient information to | |
| 812 | +** uniquely identify the check-in, including a timestamp of the form | |
| 813 | +** YYYYMMDDHHMMSS and a prefix of the check-in hash. The timestamp | |
| 814 | +** and hash must immediately preceed the first "." in the name. | |
| 691 | 815 | */ |
| 692 | 816 | char *tar_uuid_from_name(char **pzName){ |
| 693 | - char *zName = *pzName; | |
| 694 | - int i, n; | |
| 695 | - for(i=n=0; zName[i]; i++){ | |
| 817 | + char *zName = *pzName; /* Original input */ | |
| 818 | + int n1 = 0; /* Bytes in first prefix (tag-name) */ | |
| 819 | + int n2 = 0; /* Bytes in second prefix (check-in-name) */ | |
| 820 | + int n = 0; /* max(n1,n2) */ | |
| 821 | + int i; /* Loop counter */ | |
| 822 | + for(i=n1=n2=0; zName[i]; i++){ | |
| 696 | 823 | if( zName[i]=='/' ){ |
| 697 | - if( n==0 ) n = i; | |
| 698 | - else return 0; | |
| 699 | - } | |
| 700 | - } | |
| 701 | - if( n==0 ) return 0; | |
| 702 | - if( zName[n+1]==0 ) return 0; | |
| 703 | - zName[n] = 0; | |
| 704 | - *pzName = fossil_strdup(&zName[n+1]); | |
| 705 | - return zName; | |
| 824 | + if( n1==0 ){ | |
| 825 | + n = n1 = i; | |
| 826 | + }else if( n2==0 ){ | |
| 827 | + n = n2 = i; | |
| 828 | + }else{ | |
| 829 | + return 0; /* More than two "/" characters seen */ | |
| 830 | + } | |
| 831 | + } | |
| 832 | + } | |
| 833 | + if( n1==0 ){ | |
| 834 | + /* Check for format (3) */ | |
| 835 | + return format_three_parser(*pzName); | |
| 836 | + } | |
| 837 | + if( zName[n+1]==0 ){ | |
| 838 | + return 0; /* No filename suffix */ | |
| 839 | + } | |
| 840 | + if( n2==0 ){ | |
| 841 | + /* Format (1): check-in name only. The check-in-name is not verified */ | |
| 842 | + zName[n1] = 0; | |
| 843 | + *pzName = fossil_strdup(&zName[n1+1]); | |
| 844 | + return zName; | |
| 845 | + }else if( n2>n1+1 ){ | |
| 846 | + /* Format (2): tag-name/check-in-name. Verify that check-in-name is real | |
| 847 | + ** and that the check-in has the tag named by tag-name. | |
| 848 | + */ | |
| 849 | + char *zCkin = mprintf("%.*s", n2-n1-1, &zName[n1+1]); | |
| 850 | + char *zTag; | |
| 851 | + int rid = symbolic_name_to_rid(zCkin,"ci"); | |
| 852 | + int hasTag; | |
| 853 | + if( rid<=0 ){ | |
| 854 | + fossil_free(zCkin); | |
| 855 | + return fossil_strdup(""); | |
| 856 | + } | |
| 857 | + zTag = mprintf("%.*s", n1, zName); | |
| 858 | + hasTag = db_exists( | |
| 859 | + "SELECT 1 FROM tagxref, tag" | |
| 860 | + " WHERE tagxref.rid=%d" | |
| 861 | + " AND tag.tagid=tagxref.tagid" | |
| 862 | + " AND tagxref.tagtype=1" | |
| 863 | + " AND tag.tagname='sym-%q'", | |
| 864 | + rid, zTag | |
| 865 | + ); | |
| 866 | + fossil_free(zTag); | |
| 867 | + if( !hasTag ){ | |
| 868 | + fossil_free(zCkin); | |
| 869 | + return fossil_strdup(""); | |
| 870 | + } | |
| 871 | + *pzName = fossil_strdup(&zName[n2+1]); | |
| 872 | + return zCkin; | |
| 873 | + }else{ | |
| 874 | + return 0; | |
| 875 | + } | |
| 706 | 876 | } |
| 707 | 877 | |
| 708 | 878 | /* |
| 709 | 879 | ** WEBPAGE: tarball |
| 710 | -** URL: /tarball/[VERSION/]NAME.tar.gz | |
| 880 | +** URL: /tarball/NAME.tar.gz | |
| 881 | +** or: /tarball/VERSION/NAME.tar.gz | |
| 882 | +** or: /tarball/TAG/VERSION/NAME.tar.gz | |
| 711 | 883 | ** |
| 712 | 884 | ** Generate a compressed tarball for the check-in specified by VERSION. |
| 713 | 885 | ** The tarball is called NAME.tar.gz and has a top-level directory called |
| 714 | -** NAME. | |
| 886 | +** NAME. If TAG is provided, then VERSION must hold TAG or else an error | |
| 887 | +** is returned. | |
| 715 | 888 | ** |
| 716 | 889 | ** The optional VERSION element defaults to "trunk" per the r= rules below. |
| 717 | 890 | ** All of the following URLs are equivalent: |
| 718 | 891 | ** |
| 719 | 892 | ** /tarball/release/xyz.tar.gz |
| @@ -745,10 +918,25 @@ | ||
| 745 | 918 | ** |
| 746 | 919 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 747 | 920 | ** comma-separated list of GLOB patterns, where each |
| 748 | 921 | ** pattern can optionally be quoted using ".." or '..'. |
| 749 | 922 | ** Any file matching both ex= and in= is excluded. |
| 923 | +** | |
| 924 | +** Robot Defenses: | |
| 925 | +** | |
| 926 | +** * If "zip" appears in the robot-restrict setting, then robots are | |
| 927 | +** not allowed to access this page. Suspected robots will be | |
| 928 | +** presented with a captcha. | |
| 929 | +** | |
| 930 | +** * If "zipX" appears in the robot-restrict setting, then robots are | |
| 931 | +** restricted in the same way as with "zip", but with exceptions. | |
| 932 | +** If the check-in for which an archive is requested is a leaf check-in | |
| 933 | +** and if the robot-zip-leaf setting is true, then the request is | |
| 934 | +** allowed. Or if the check-in has a tag that matches any of the | |
| 935 | +** GLOB patterns on the list in the robot-zip-tag setting, then the | |
| 936 | +** request is allowed. Otherwise, the usual robot defenses are | |
| 937 | +** activated. | |
| 750 | 938 | */ |
| 751 | 939 | void tarball_page(void){ |
| 752 | 940 | int rid; |
| 753 | 941 | char *zName, *zRid, *zKey; |
| 754 | 942 | int nName, nRid; |
| @@ -760,10 +948,11 @@ | ||
| 760 | 948 | Blob tarball; /* Tarball accumulated here */ |
| 761 | 949 | const char *z; |
| 762 | 950 | |
| 763 | 951 | login_check_credentials(); |
| 764 | 952 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 953 | + if( robot_restrict("zip") ) return; | |
| 765 | 954 | fossil_nice_default(); |
| 766 | 955 | zName = fossil_strdup(PD("name","")); |
| 767 | 956 | z = P("r"); |
| 768 | 957 | if( z==0 ) z = P("uuid"); |
| 769 | 958 | if( z==0 ) z = tar_uuid_from_name(&zName); |
| @@ -796,10 +985,11 @@ | ||
| 796 | 985 | if( rid==0 ){ |
| 797 | 986 | cgi_set_status(404, "Not Found"); |
| 798 | 987 | @ Not found |
| 799 | 988 | return; |
| 800 | 989 | } |
| 990 | + if( robot_restrict_zip(rid) ) return; | |
| 801 | 991 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 802 | 992 | |
| 803 | 993 | /* Compute a unique key for the cache entry based on query parameters */ |
| 804 | 994 | blob_init(&cacheKey, 0, 0); |
| 805 | 995 | blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid)); |
| @@ -847,5 +1037,312 @@ | ||
| 847 | 1037 | g.zOpenRevision = 0; |
| 848 | 1038 | blob_reset(&cacheKey); |
| 849 | 1039 | cgi_set_content(&tarball); |
| 850 | 1040 | cgi_set_content_type("application/x-compressed"); |
| 851 | 1041 | } |
| 1042 | + | |
| 1043 | +/* | |
| 1044 | +** This routine is called for each check-in on the /download page to | |
| 1045 | +** construct the "extra" information after the description. | |
| 1046 | +*/ | |
| 1047 | +void download_extra( | |
| 1048 | + Stmt *pQuery, /* Current row of the timeline query */ | |
| 1049 | + int tmFlags, /* Flags to www_print_timeline() */ | |
| 1050 | + const char *zThisUser, /* Suppress links to this user */ | |
| 1051 | + const char *zThisTag /* Suppress links to this tag */ | |
| 1052 | +){ | |
| 1053 | + const char *zType = db_column_text(pQuery, 7); | |
| 1054 | + assert( zType!=0 ); | |
| 1055 | + if( zType[0]!='c' ){ | |
| 1056 | + timeline_extra(pQuery, tmFlags, zThisUser, zThisTag); | |
| 1057 | + }else{ | |
| 1058 | + int rid = db_column_int(pQuery, 0); | |
| 1059 | + const char *zUuid = db_column_text(pQuery, 1); | |
| 1060 | + char *zBrName = branch_of_rid(rid); | |
| 1061 | + char *zNm; | |
| 1062 | + | |
| 1063 | + if( tmFlags & TIMELINE_COLUMNAR ){ | |
| 1064 | + @ <nobr>check-in: \ | |
| 1065 | + @ %z(href("%R/info/%!S",zUuid))<span class='timelineHash'>\ | |
| 1066 | + @ %S(zUuid)</span></a></nobr><br> | |
| 1067 | + if( fossil_strcmp(zBrName,"trunk")!=0 ){ | |
| 1068 | + @ <nobr>branch: \ | |
| 1069 | + @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a></nobr><br>\ | |
| 1070 | + } | |
| 1071 | + }else{ | |
| 1072 | + if( (tmFlags & TIMELINE_CLASSIC)==0 ){ | |
| 1073 | + @ check-in: %z(href("%R/info/%!S",zUuid))\ | |
| 1074 | + @ <span class='timelineHash'>%S(zUuid)</span></a> | |
| 1075 | + } | |
| 1076 | + if( (tmFlags & TIMELINE_GRAPH)==0 && fossil_strcmp(zBrName,"trunk")!=0 ){ | |
| 1077 | + @ branch: \ | |
| 1078 | + @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a> | |
| 1079 | + } | |
| 1080 | + } | |
| 1081 | + zNm = archive_base_name(rid); | |
| 1082 | + @ %z(href("%R/tarball/%s.tar.gz",zNm))\ | |
| 1083 | + @ <button>Tarball</button></a> | |
| 1084 | + @ %z(href("%R/zip/%s.zip",zNm))\ | |
| 1085 | + @ <button>ZIP Archive</button></a> | |
| 1086 | + fossil_free(zBrName); | |
| 1087 | + fossil_free(zNm); | |
| 1088 | + } | |
| 1089 | +} | |
| 1090 | + | |
| 1091 | +/* | |
| 1092 | +** SETTING: suggested-downloads width=70 block-text | |
| 1093 | +** | |
| 1094 | +** This setting controls the suggested tarball/ZIP downloads on the | |
| 1095 | +** [[/download]] page. The value is a TCL list. Each set of four items | |
| 1096 | +** defines a set of check-ins to be added to the suggestion list. | |
| 1097 | +** The items in each group are: | |
| 1098 | +** | |
| 1099 | +** | COUNT TAG MAX_AGE COMMENT | |
| 1100 | +** | |
| 1101 | +** COUNT is the number of check-ins to match, starting with the most | |
| 1102 | +** recent and working bacwards in time. Check-ins match if they contain | |
| 1103 | +** the tag TAG. If MAX_AGE is not an empty string, then it specifies | |
| 1104 | +** the maximum age of any matching check-in. COMMENT is an optional | |
| 1105 | +** comment for each match. | |
| 1106 | +** | |
| 1107 | +** The special value of "OPEN-LEAF" for TAG matches any check-in that | |
| 1108 | +** is an open leaf. | |
| 1109 | +** | |
| 1110 | +** MAX_AGE is of the form "{AMT UNITS}" where AMT is a floating point | |
| 1111 | +** value and UNITS is one of "seconds", "hours", "days", "weeks", "months", | |
| 1112 | +** or "years". If MAX_AGE is an empty string then there is no age limit. | |
| 1113 | +** | |
| 1114 | +** If COMMENT is not an empty string, then it is an additional comment | |
| 1115 | +** added to the output description of the suggested download. The idea of | |
| 1116 | +** COMMENT is to explain to the reader why a check-in is a suggested | |
| 1117 | +** download. | |
| 1118 | +** | |
| 1119 | +** Example: | |
| 1120 | +** | |
| 1121 | +** | 1 trunk {} {Latest Trunk Check-in} | |
| 1122 | +** | 5 OPEN-LEAF {1 month} {Active Branch} | |
| 1123 | +** | 999 release {1 year} {Official Release} | |
| 1124 | +** | |
| 1125 | +** The value causes the /download page to show the union of the most | |
| 1126 | +** recent trunk check-in of any age, the five most recent | |
| 1127 | +** open leaves within the past month, and essentially | |
| 1128 | +** all releases within the past year. If the same check-in matches more | |
| 1129 | +** than one rule, the COMMENT of the first match is used. | |
| 1130 | +*/ | |
| 1131 | + | |
| 1132 | +/* | |
| 1133 | +** WEBPAGE: /download | |
| 1134 | +** | |
| 1135 | +** Show a special no-graph timeline of recent important check-ins with | |
| 1136 | +** an opportunity to pull tarballs and ZIPs. | |
| 1137 | +*/ | |
| 1138 | +void download_page(void){ | |
| 1139 | + Stmt q; /* The actual timeline query */ | |
| 1140 | + const char *zTarlistCfg; /* Configuration string */ | |
| 1141 | + char **azItem; /* Decomposed elements of zTarlistCfg */ | |
| 1142 | + int *anItem; /* Bytes in each term of azItem[] */ | |
| 1143 | + int nItem; /* Number of terms in azItem[] */ | |
| 1144 | + int i; /* Loop counter */ | |
| 1145 | + int tmFlags; /* Timeline display flags */ | |
| 1146 | + int n; /* Number of suggested downloads */ | |
| 1147 | + double rNow; /* Current time. Julian day number */ | |
| 1148 | + int bPlainTextCom; /* Use plain-text comments */ | |
| 1149 | + | |
| 1150 | + login_check_credentials(); | |
| 1151 | + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } | |
| 1152 | + | |
| 1153 | + style_set_current_feature("timeline"); | |
| 1154 | + style_header("Suggested Downloads"); | |
| 1155 | + | |
| 1156 | + zTarlistCfg = db_get("suggested-downloads","off"); | |
| 1157 | + db_multi_exec( | |
| 1158 | + "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY, com TEXT);" | |
| 1159 | + ); | |
| 1160 | + rNow = db_double(0.0,"SELECT julianday()"); | |
| 1161 | + if( !g.interp ) Th_FossilInit(0); | |
| 1162 | + Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg), | |
| 1163 | + &azItem, &anItem, &nItem); | |
| 1164 | + bPlainTextCom = db_get_boolean("timeline-plaintext",0); | |
| 1165 | + for(i=0; i<nItem-3; i+=4){ | |
| 1166 | + int cnt; /* The number of instances of zLabel to use */ | |
| 1167 | + char *zLabel; /* The label to match */ | |
| 1168 | + double rStart; /* Starting time, julian day number */ | |
| 1169 | + char *zComment = 0; /* Comment to apply */ | |
| 1170 | + if( anItem[i]==1 && azItem[i][0]=='*' ){ | |
| 1171 | + cnt = -1; | |
| 1172 | + }else if( anItem[i]<1 ){ | |
| 1173 | + cnt = 0; | |
| 1174 | + }else{ | |
| 1175 | + cnt = atoi(azItem[i]); | |
| 1176 | + } | |
| 1177 | + if( cnt==0 ) continue; | |
| 1178 | + zLabel = fossil_strndup(azItem[i+1],anItem[i+1]); | |
| 1179 | + if( anItem[i+2]==0 ){ | |
| 1180 | + rStart = 0.0; | |
| 1181 | + }else{ | |
| 1182 | + char *zMax = fossil_strndup(azItem[i+2], anItem[i+2]); | |
| 1183 | + double r = atof(zMax); | |
| 1184 | + if( strstr(zMax,"sec") ){ | |
| 1185 | + rStart = rNow - r/86400.0; | |
| 1186 | + }else | |
| 1187 | + if( strstr(zMax,"hou") ){ | |
| 1188 | + rStart = rNow - r/24.0; | |
| 1189 | + }else | |
| 1190 | + if( strstr(zMax,"da") ){ | |
| 1191 | + rStart = rNow - r; | |
| 1192 | + }else | |
| 1193 | + if( strstr(zMax,"wee") ){ | |
| 1194 | + rStart = rNow - r*7.0; | |
| 1195 | + }else | |
| 1196 | + if( strstr(zMax,"mon") ){ | |
| 1197 | + rStart = rNow - r*30.44; | |
| 1198 | + }else | |
| 1199 | + if( strstr(zMax,"yea") ){ | |
| 1200 | + rStart = rNow - r*365.24; | |
| 1201 | + }else | |
| 1202 | + { /* Default to seconds */ | |
| 1203 | + rStart = rNow - r/86400.0; | |
| 1204 | + } | |
| 1205 | + } | |
| 1206 | + if( anItem[i+3]==0 ){ | |
| 1207 | + zComment = fossil_strdup(""); | |
| 1208 | + }else if( bPlainTextCom ){ | |
| 1209 | + zComment = mprintf("** %.*s ** ", anItem[i+3], azItem[i+3]); | |
| 1210 | + }else{ | |
| 1211 | + zComment = mprintf("<b>%.*s</b>\n<p>", anItem[i+3], azItem[i+3]); | |
| 1212 | + } | |
| 1213 | + if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){ | |
| 1214 | + db_multi_exec( | |
| 1215 | + "INSERT OR IGNORE INTO tarlist(rid,com)" | |
| 1216 | + " SELECT leaf.rid, %Q FROM leaf, event" | |
| 1217 | + " WHERE event.objid=leaf.rid" | |
| 1218 | + " AND event.mtime>=%.6f" | |
| 1219 | + " AND NOT EXISTS(SELECT 1 FROM tagxref" | |
| 1220 | + " WHERE tagxref.rid=leaf.rid" | |
| 1221 | + " AND tagid=%d AND tagtype>0)" | |
| 1222 | + " ORDER BY event.mtime DESC LIMIT %d", | |
| 1223 | + zComment, rStart, TAG_CLOSED, cnt | |
| 1224 | + ); | |
| 1225 | + }else{ | |
| 1226 | + db_multi_exec( | |
| 1227 | + "WITH taglist(tid) AS" | |
| 1228 | + " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')" | |
| 1229 | + "INSERT OR IGNORE INTO tarlist(rid,com)" | |
| 1230 | + " SELECT event.objid, %Q FROM event CROSS JOIN tagxref" | |
| 1231 | + " WHERE event.type='ci'" | |
| 1232 | + " AND event.mtime>=%.6f" | |
| 1233 | + " AND tagxref.tagid IN taglist" | |
| 1234 | + " AND tagtype>0" | |
| 1235 | + " AND tagxref.rid=event.objid" | |
| 1236 | + " ORDER BY event.mtime DESC LIMIT %d", | |
| 1237 | + zLabel, zComment, rStart, cnt | |
| 1238 | + ); | |
| 1239 | + } | |
| 1240 | + fossil_free(zLabel); | |
| 1241 | + fossil_free(zComment); | |
| 1242 | + } | |
| 1243 | + Th_Free(g.interp, azItem); | |
| 1244 | + | |
| 1245 | + n = db_int(0, "SELECT count(*) FROM tarlist"); | |
| 1246 | + if( n==0 ){ | |
| 1247 | + @ <h2>No tarball/ZIP suggestions are available at this time</h2> | |
| 1248 | + }else{ | |
| 1249 | + @ <h2>%d(n) Tarball/ZIP Download Suggestion%s(n>1?"s":""):</h2> | |
| 1250 | + db_prepare(&q, | |
| 1251 | + "WITH matches AS (%s AND blob.rid IN (SELECT rid FROM tarlist))\n" | |
| 1252 | + "SELECT blobRid, uuid, timestamp," | |
| 1253 | + " com||comment," | |
| 1254 | + " user, leaf, bgColor, eventType, tags, tagid, brief, mtime" | |
| 1255 | + " FROM matches JOIN tarlist ON tarlist.rid=blobRid" | |
| 1256 | + " ORDER BY matches.mtime DESC", | |
| 1257 | + timeline_query_for_www() | |
| 1258 | + ); | |
| 1259 | + | |
| 1260 | + tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR | |
| 1261 | + | TIMELINE_BRCOLOR; | |
| 1262 | + www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, download_extra); | |
| 1263 | + db_finalize(&q); | |
| 1264 | + } | |
| 1265 | + if( g.perm.Clone ){ | |
| 1266 | + char *zNm = fossil_strdup(db_get("project-name","clone")); | |
| 1267 | + sanitize_name(zNm); | |
| 1268 | + @ <hr> | |
| 1269 | + @ <h2>You Can Clone This Repository</h2> | |
| 1270 | + @ | |
| 1271 | + @ <p>Clone this repository by running a command similar to the following: | |
| 1272 | + @ <blockquote><pre> | |
| 1273 | + @ fossil clone %s(g.zBaseURL) %h(zNm).fossil | |
| 1274 | + @ </pre></blockquote> | |
| 1275 | + @ <p>A clone gives you local access to all historical content. | |
| 1276 | + @ Cloning is a bandwidth- and CPU-efficient alternative to extracting | |
| 1277 | + @ multiple tarballs and ZIPs. | |
| 1278 | + @ Do a web search for "fossil clone" or similar to find additional | |
| 1279 | + @ information about using a cloned Fossil repository. Or ask your | |
| 1280 | + @ favorite AI how to extract content from a Fossil clone. | |
| 1281 | + fossil_free(zNm); | |
| 1282 | + } | |
| 1283 | + | |
| 1284 | + style_finish_page(); | |
| 1285 | +} | |
| 1286 | + | |
| 1287 | +/* | |
| 1288 | +** WEBPAGE: rchvdwnld | |
| 1289 | +** | |
| 1290 | +** Short for "archive download". This page should have a single name= | |
| 1291 | +** query parameter that is a check-in hash. It present a menu of possible | |
| 1292 | +** download options for that check-in, including tarball, ZIP, or SQLAR. | |
| 1293 | +** | |
| 1294 | +** This is a utility page. The /dir and /tree pages sometimes have a | |
| 1295 | +** "Download" option in their submenu which redirects here. Those pages | |
| 1296 | +** used to have separate "Tarball" and "ZIP" submenu entries, but as | |
| 1297 | +** submenu entries appear in alphabetical order, that caused the two | |
| 1298 | +** submenu entries to be separated from one another, which is distracting. | |
| 1299 | +*/ | |
| 1300 | +void rchvdwnld_page(void){ | |
| 1301 | + const char *zUuid; | |
| 1302 | + char *zBase; | |
| 1303 | + int nUuid; | |
| 1304 | + int rid; | |
| 1305 | + login_check_credentials(); | |
| 1306 | + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } | |
| 1307 | + if( robot_restrict("zip") || robot_restrict("download") ) return; | |
| 1308 | + | |
| 1309 | + zUuid = P("name"); | |
| 1310 | + if( zUuid==0 | |
| 1311 | + || (nUuid = (int)strlen(zUuid))<6 | |
| 1312 | + || !validate16(zUuid,-1) | |
| 1313 | + || (rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid))==0 | |
| 1314 | + || !db_exists("SELECT 1 from event WHERE type='ci' AND objid=%d",rid) | |
| 1315 | + ){ | |
| 1316 | + fossil_redirect_home(); | |
| 1317 | + } | |
| 1318 | + zUuid = db_text(zUuid, "SELECT uuid FROM blob WHERE rid=%d", rid); | |
| 1319 | + style_header("Downloads For Check-in %!S", zUuid); | |
| 1320 | + zBase = archive_base_name(rid); | |
| 1321 | + @ <div class="section accordion">Downloads for check-in \ | |
| 1322 | + @ %z(href("%R/info/%!S",zUuid))%S(zUuid)</a></div> | |
| 1323 | + @ <div class="accordion_panel"> | |
| 1324 | + @ <table class="label-value"> | |
| 1325 | + @ <tr> | |
| 1326 | + @ <th>Tarball:</th> | |
| 1327 | + @ <td>%z(href("%R/tarball/%s.tar.gz",zBase))\ | |
| 1328 | + @ %s(g.zBaseURL)/tarball/%s(zBase).tar.gz</a></td> | |
| 1329 | + @ </tr> | |
| 1330 | + @ | |
| 1331 | + @ <tr> | |
| 1332 | + @ <th>ZIP:</th> | |
| 1333 | + @ <td>%z(href("%R/zip/%s.zip",zBase))\ | |
| 1334 | + @ %s(g.zBaseURL)/zip/%s(zBase).zip</a></td> | |
| 1335 | + @ </tr> | |
| 1336 | + @ | |
| 1337 | + @ <tr> | |
| 1338 | + @ <th>SQLAR:</th> | |
| 1339 | + @ <td>%z(href("%R/sqlar/%s.sqlar",zBase))\ | |
| 1340 | + @ %s(g.zBaseURL)/sqlar/%s(zBase).sqlar</a></td> | |
| 1341 | + @ </tr> | |
| 1342 | + @ </table></div> | |
| 1343 | + fossil_free(zBase); | |
| 1344 | + @ <div class="section accordion">Context</div><div class="accordion_panel"> | |
| 1345 | + render_checkin_context(rid, 0, 0, 0); | |
| 1346 | + @ </div> | |
| 1347 | + style_finish_page(); | |
| 1348 | +} | |
| 852 | 1349 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -31,10 +31,65 @@ | |
| 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | Blob pax; /* PAX data */ |
| 34 | } tball; |
| 35 | |
| 36 | |
| 37 | /* |
| 38 | ** field lengths of 'ustar' name and prefix fields. |
| 39 | */ |
| 40 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | |
| 653 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 654 | zOut = 0; |
| 655 | } |
| 656 | |
| 657 | if( zName==0 ){ |
| 658 | zName = db_text("default-name", |
| 659 | "SELECT replace(%Q,' ','_') " |
| 660 | " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " |
| 661 | " || substr(blob.uuid, 1, 10)" |
| 662 | " FROM event, blob" |
| 663 | " WHERE event.objid=%d" |
| 664 | " AND blob.rid=%d", |
| 665 | db_get("project-name", "unnamed"), rid, rid |
| 666 | ); |
| 667 | } |
| 668 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 669 | zName, pInclude, pExclude, listFlag); |
| 670 | glob_free(pInclude); |
| 671 | glob_free(pExclude); |
| @@ -675,45 +722,171 @@ | |
| 675 | blob_reset(&tarball); |
| 676 | } |
| 677 | } |
| 678 | |
| 679 | /* |
| 680 | ** Check to see if the input string is of the form: |
| 681 | ** |
| 682 | ** check-in-name/filename.ext |
| 683 | ** |
| 684 | ** In other words, check to see if the input contains a single '/' |
| 685 | ** character that separates a valid check-in name from a filename. |
| 686 | ** |
| 687 | ** If the condition is true, return the check-in name and set the |
| 688 | ** input string to be the filename. |
| 689 | ** |
| 690 | ** If the condition is false, return NULL |
| 691 | */ |
| 692 | char *tar_uuid_from_name(char **pzName){ |
| 693 | char *zName = *pzName; |
| 694 | int i, n; |
| 695 | for(i=n=0; zName[i]; i++){ |
| 696 | if( zName[i]=='/' ){ |
| 697 | if( n==0 ) n = i; |
| 698 | else return 0; |
| 699 | } |
| 700 | } |
| 701 | if( n==0 ) return 0; |
| 702 | if( zName[n+1]==0 ) return 0; |
| 703 | zName[n] = 0; |
| 704 | *pzName = fossil_strdup(&zName[n+1]); |
| 705 | return zName; |
| 706 | } |
| 707 | |
| 708 | /* |
| 709 | ** WEBPAGE: tarball |
| 710 | ** URL: /tarball/[VERSION/]NAME.tar.gz |
| 711 | ** |
| 712 | ** Generate a compressed tarball for the check-in specified by VERSION. |
| 713 | ** The tarball is called NAME.tar.gz and has a top-level directory called |
| 714 | ** NAME. |
| 715 | ** |
| 716 | ** The optional VERSION element defaults to "trunk" per the r= rules below. |
| 717 | ** All of the following URLs are equivalent: |
| 718 | ** |
| 719 | ** /tarball/release/xyz.tar.gz |
| @@ -745,10 +918,25 @@ | |
| 745 | ** |
| 746 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 747 | ** comma-separated list of GLOB patterns, where each |
| 748 | ** pattern can optionally be quoted using ".." or '..'. |
| 749 | ** Any file matching both ex= and in= is excluded. |
| 750 | */ |
| 751 | void tarball_page(void){ |
| 752 | int rid; |
| 753 | char *zName, *zRid, *zKey; |
| 754 | int nName, nRid; |
| @@ -760,10 +948,11 @@ | |
| 760 | Blob tarball; /* Tarball accumulated here */ |
| 761 | const char *z; |
| 762 | |
| 763 | login_check_credentials(); |
| 764 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 765 | fossil_nice_default(); |
| 766 | zName = fossil_strdup(PD("name","")); |
| 767 | z = P("r"); |
| 768 | if( z==0 ) z = P("uuid"); |
| 769 | if( z==0 ) z = tar_uuid_from_name(&zName); |
| @@ -796,10 +985,11 @@ | |
| 796 | if( rid==0 ){ |
| 797 | cgi_set_status(404, "Not Found"); |
| 798 | @ Not found |
| 799 | return; |
| 800 | } |
| 801 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 802 | |
| 803 | /* Compute a unique key for the cache entry based on query parameters */ |
| 804 | blob_init(&cacheKey, 0, 0); |
| 805 | blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid)); |
| @@ -847,5 +1037,312 @@ | |
| 847 | g.zOpenRevision = 0; |
| 848 | blob_reset(&cacheKey); |
| 849 | cgi_set_content(&tarball); |
| 850 | cgi_set_content_type("application/x-compressed"); |
| 851 | } |
| 852 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -31,10 +31,65 @@ | |
| 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | Blob pax; /* PAX data */ |
| 34 | } tball; |
| 35 | |
| 36 | /* |
| 37 | ** Convert a string so that it contains only lower-case ASCII, digits, |
| 38 | ** "_" and "-". Changes are made in-place. |
| 39 | */ |
| 40 | static void sanitize_name(char *zName){ |
| 41 | int i; |
| 42 | char c; |
| 43 | for(i=0; (c = zName[i])!=0; i++){ |
| 44 | if( fossil_isupper(c) ){ |
| 45 | zName[i] = fossil_tolower(c); |
| 46 | }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){ |
| 47 | if( c<=0x7f ){ |
| 48 | zName[i] = '_'; |
| 49 | }else{ |
| 50 | /* 123456789 123456789 123456 */ |
| 51 | zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26]; |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /* |
| 58 | ** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR) |
| 59 | ** based on the rid of the check-in contained in that file. |
| 60 | ** |
| 61 | ** PROJECTNAME-DATETIME-HASHPREFIX |
| 62 | ** |
| 63 | ** So that the name will be safe to use as a URL or a filename on any system, |
| 64 | ** the name is only allowed to contain lower-case ASCII alphabetics, |
| 65 | ** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All |
| 66 | ** other bytes are mapped into a lower-case alphabetic. |
| 67 | ** |
| 68 | ** The value returned is obtained from mprintf() or fossil_strdup() and should |
| 69 | ** be released by the caller using fossil_free(). |
| 70 | */ |
| 71 | char *archive_base_name(int rid){ |
| 72 | char *zPrefix; |
| 73 | char *zName; |
| 74 | zPrefix = db_get("short-project-name",0); |
| 75 | if( zPrefix==0 || zPrefix[0]==0 ){ |
| 76 | zPrefix = db_get("project-name","unnamed"); |
| 77 | } |
| 78 | zName = db_text(0, |
| 79 | "SELECT %Q||" |
| 80 | " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||" |
| 81 | " substr(blob.uuid,1,10)" |
| 82 | " FROM blob, event LEFT JOIN config" |
| 83 | " WHERE blob.rid=%d" |
| 84 | " AND event.objid=%d" |
| 85 | " AND config.name='project-name'", |
| 86 | zPrefix, rid, rid); |
| 87 | fossil_free(zPrefix); |
| 88 | sanitize_name(zName); |
| 89 | return zName; |
| 90 | } |
| 91 | |
| 92 | /* |
| 93 | ** field lengths of 'ustar' name and prefix fields. |
| 94 | */ |
| 95 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | |
| 708 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 709 | zOut = 0; |
| 710 | } |
| 711 | |
| 712 | if( zName==0 ){ |
| 713 | zName = archive_base_name(rid); |
| 714 | } |
| 715 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 716 | zName, pInclude, pExclude, listFlag); |
| 717 | glob_free(pInclude); |
| 718 | glob_free(pExclude); |
| @@ -675,45 +722,171 @@ | |
| 722 | blob_reset(&tarball); |
| 723 | } |
| 724 | } |
| 725 | |
| 726 | /* |
| 727 | ** This is a helper routine for tar_uuid_from_name(). It handles |
| 728 | ** the case where *pzName contains no "/" character. Check for |
| 729 | ** format (3). Return the hash if the name matches format (3), |
| 730 | ** or return NULL if it does not. |
| 731 | */ |
| 732 | static char *format_three_parser(const char *zName){ |
| 733 | int iDot = 0; /* Index in zName[] of the first '.' */ |
| 734 | int iDash1 = 0; /* Index in zName[] of the '-' before the timestamp */ |
| 735 | int iDash2 = 0; /* Index in zName[] of the '-' between timestamp and hash */ |
| 736 | int nHash; /* Size of the hash */ |
| 737 | char *zHash; /* A copy of the hash value */ |
| 738 | char *zDate; /* Copy of the timestamp */ |
| 739 | char *zUuid; /* Final result */ |
| 740 | int i; /* Loop query */ |
| 741 | Stmt q; /* Query to verify that hash and timestamp agree */ |
| 742 | |
| 743 | for(i=0; zName[i]; i++){ |
| 744 | char c = zName[i]; |
| 745 | if( c=='.' ){ iDot = i; break; } |
| 746 | if( c=='-' ){ iDash1 = iDash2; iDash2 = i; } |
| 747 | if( !fossil_isalnum(c) && c!='_' && c!='-' ){ break; } |
| 748 | } |
| 749 | if( iDot==0 ) return 0; |
| 750 | if( iDash1==0 ) return 0; |
| 751 | nHash = iDot - iDash2 - 1; |
| 752 | if( nHash<8 ) return 0; /* HASH value too short */ |
| 753 | if( (iDash2 - iDash1)!=15 ) return 0; /* Wrong timestamp size */ |
| 754 | zHash = fossil_strndup(&zName[iDash2+1], nHash); |
| 755 | zDate = fossil_strndup(&zName[iDash1+1], 14); |
| 756 | db_prepare(&q, |
| 757 | "SELECT blob.uuid" |
| 758 | " FROM blob JOIN event ON event.objid=blob.rid" |
| 759 | " WHERE blob.uuid GLOB '%q*'" |
| 760 | " AND strftime('%%Y%%m%%d%%H%%M%%S',event.mtime)='%q'", |
| 761 | zHash, zDate |
| 762 | ); |
| 763 | fossil_free(zHash); |
| 764 | fossil_free(zDate); |
| 765 | if( db_step(&q)==SQLITE_ROW ){ |
| 766 | zUuid = fossil_strdup(db_column_text(&q,0)); |
| 767 | }else{ |
| 768 | zUuid = 0; |
| 769 | } |
| 770 | db_finalize(&q); |
| 771 | return zUuid; |
| 772 | } |
| 773 | |
| 774 | /* |
| 775 | ** Check to see if the input string is of one of the following |
| 776 | ** two the forms: |
| 777 | ** |
| 778 | ** check-in-name/filename.ext (1) |
| 779 | ** tag-name/check-in-name/filename.ext (2) |
| 780 | ** project-datetime-hash.ext (3) |
| 781 | ** |
| 782 | ** In other words, check to see if the input string contains either |
| 783 | ** a check-in name or a tag-name and a check-in name separated by |
| 784 | ** a slash. There must be between 0 or 2 "/" characters. In the |
| 785 | ** second form, tag-name must be an individual tag (not a branch-tag) |
| 786 | ** that is found on the check-in identified by the check-in-name. |
| 787 | ** |
| 788 | ** If the condition is true, then: |
| 789 | ** |
| 790 | ** * Make *pzName point to the filename suffix only |
| 791 | ** * return a copy of the check-in name in memory from mprintf(). |
| 792 | ** |
| 793 | ** If the condition is false, leave *pzName unchanged and return either |
| 794 | ** NULL or an empty string. Normally NULL is returned, however an |
| 795 | ** empty string is returned for format (2) if check-in-name does not |
| 796 | ** match tag-name. |
| 797 | ** |
| 798 | ** Format (2) is specifically designed to allow URLs like this: |
| 799 | ** |
| 800 | ** /tarball/release/UUID/PROJECT.tar.gz |
| 801 | ** |
| 802 | ** Such URLs will pass through most anti-robot filters because of the |
| 803 | ** "/tarball/release" prefix will match the suggested "robot-exception" |
| 804 | ** pattern and can still refer to an historic release rather than just |
| 805 | ** the most recent release. |
| 806 | ** |
| 807 | ** Format (3) is designed to allow URLs like this: |
| 808 | ** |
| 809 | ** /tarball/fossil-20251018193920-d6c9aee97df.tar.gz |
| 810 | ** |
| 811 | ** In other words, filename itself contains sufficient information to |
| 812 | ** uniquely identify the check-in, including a timestamp of the form |
| 813 | ** YYYYMMDDHHMMSS and a prefix of the check-in hash. The timestamp |
| 814 | ** and hash must immediately preceed the first "." in the name. |
| 815 | */ |
| 816 | char *tar_uuid_from_name(char **pzName){ |
| 817 | char *zName = *pzName; /* Original input */ |
| 818 | int n1 = 0; /* Bytes in first prefix (tag-name) */ |
| 819 | int n2 = 0; /* Bytes in second prefix (check-in-name) */ |
| 820 | int n = 0; /* max(n1,n2) */ |
| 821 | int i; /* Loop counter */ |
| 822 | for(i=n1=n2=0; zName[i]; i++){ |
| 823 | if( zName[i]=='/' ){ |
| 824 | if( n1==0 ){ |
| 825 | n = n1 = i; |
| 826 | }else if( n2==0 ){ |
| 827 | n = n2 = i; |
| 828 | }else{ |
| 829 | return 0; /* More than two "/" characters seen */ |
| 830 | } |
| 831 | } |
| 832 | } |
| 833 | if( n1==0 ){ |
| 834 | /* Check for format (3) */ |
| 835 | return format_three_parser(*pzName); |
| 836 | } |
| 837 | if( zName[n+1]==0 ){ |
| 838 | return 0; /* No filename suffix */ |
| 839 | } |
| 840 | if( n2==0 ){ |
| 841 | /* Format (1): check-in name only. The check-in-name is not verified */ |
| 842 | zName[n1] = 0; |
| 843 | *pzName = fossil_strdup(&zName[n1+1]); |
| 844 | return zName; |
| 845 | }else if( n2>n1+1 ){ |
| 846 | /* Format (2): tag-name/check-in-name. Verify that check-in-name is real |
| 847 | ** and that the check-in has the tag named by tag-name. |
| 848 | */ |
| 849 | char *zCkin = mprintf("%.*s", n2-n1-1, &zName[n1+1]); |
| 850 | char *zTag; |
| 851 | int rid = symbolic_name_to_rid(zCkin,"ci"); |
| 852 | int hasTag; |
| 853 | if( rid<=0 ){ |
| 854 | fossil_free(zCkin); |
| 855 | return fossil_strdup(""); |
| 856 | } |
| 857 | zTag = mprintf("%.*s", n1, zName); |
| 858 | hasTag = db_exists( |
| 859 | "SELECT 1 FROM tagxref, tag" |
| 860 | " WHERE tagxref.rid=%d" |
| 861 | " AND tag.tagid=tagxref.tagid" |
| 862 | " AND tagxref.tagtype=1" |
| 863 | " AND tag.tagname='sym-%q'", |
| 864 | rid, zTag |
| 865 | ); |
| 866 | fossil_free(zTag); |
| 867 | if( !hasTag ){ |
| 868 | fossil_free(zCkin); |
| 869 | return fossil_strdup(""); |
| 870 | } |
| 871 | *pzName = fossil_strdup(&zName[n2+1]); |
| 872 | return zCkin; |
| 873 | }else{ |
| 874 | return 0; |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | /* |
| 879 | ** WEBPAGE: tarball |
| 880 | ** URL: /tarball/NAME.tar.gz |
| 881 | ** or: /tarball/VERSION/NAME.tar.gz |
| 882 | ** or: /tarball/TAG/VERSION/NAME.tar.gz |
| 883 | ** |
| 884 | ** Generate a compressed tarball for the check-in specified by VERSION. |
| 885 | ** The tarball is called NAME.tar.gz and has a top-level directory called |
| 886 | ** NAME. If TAG is provided, then VERSION must hold TAG or else an error |
| 887 | ** is returned. |
| 888 | ** |
| 889 | ** The optional VERSION element defaults to "trunk" per the r= rules below. |
| 890 | ** All of the following URLs are equivalent: |
| 891 | ** |
| 892 | ** /tarball/release/xyz.tar.gz |
| @@ -745,10 +918,25 @@ | |
| 918 | ** |
| 919 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 920 | ** comma-separated list of GLOB patterns, where each |
| 921 | ** pattern can optionally be quoted using ".." or '..'. |
| 922 | ** Any file matching both ex= and in= is excluded. |
| 923 | ** |
| 924 | ** Robot Defenses: |
| 925 | ** |
| 926 | ** * If "zip" appears in the robot-restrict setting, then robots are |
| 927 | ** not allowed to access this page. Suspected robots will be |
| 928 | ** presented with a captcha. |
| 929 | ** |
| 930 | ** * If "zipX" appears in the robot-restrict setting, then robots are |
| 931 | ** restricted in the same way as with "zip", but with exceptions. |
| 932 | ** If the check-in for which an archive is requested is a leaf check-in |
| 933 | ** and if the robot-zip-leaf setting is true, then the request is |
| 934 | ** allowed. Or if the check-in has a tag that matches any of the |
| 935 | ** GLOB patterns on the list in the robot-zip-tag setting, then the |
| 936 | ** request is allowed. Otherwise, the usual robot defenses are |
| 937 | ** activated. |
| 938 | */ |
| 939 | void tarball_page(void){ |
| 940 | int rid; |
| 941 | char *zName, *zRid, *zKey; |
| 942 | int nName, nRid; |
| @@ -760,10 +948,11 @@ | |
| 948 | Blob tarball; /* Tarball accumulated here */ |
| 949 | const char *z; |
| 950 | |
| 951 | login_check_credentials(); |
| 952 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 953 | if( robot_restrict("zip") ) return; |
| 954 | fossil_nice_default(); |
| 955 | zName = fossil_strdup(PD("name","")); |
| 956 | z = P("r"); |
| 957 | if( z==0 ) z = P("uuid"); |
| 958 | if( z==0 ) z = tar_uuid_from_name(&zName); |
| @@ -796,10 +985,11 @@ | |
| 985 | if( rid==0 ){ |
| 986 | cgi_set_status(404, "Not Found"); |
| 987 | @ Not found |
| 988 | return; |
| 989 | } |
| 990 | if( robot_restrict_zip(rid) ) return; |
| 991 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 992 | |
| 993 | /* Compute a unique key for the cache entry based on query parameters */ |
| 994 | blob_init(&cacheKey, 0, 0); |
| 995 | blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid)); |
| @@ -847,5 +1037,312 @@ | |
| 1037 | g.zOpenRevision = 0; |
| 1038 | blob_reset(&cacheKey); |
| 1039 | cgi_set_content(&tarball); |
| 1040 | cgi_set_content_type("application/x-compressed"); |
| 1041 | } |
| 1042 | |
| 1043 | /* |
| 1044 | ** This routine is called for each check-in on the /download page to |
| 1045 | ** construct the "extra" information after the description. |
| 1046 | */ |
| 1047 | void download_extra( |
| 1048 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1049 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1050 | const char *zThisUser, /* Suppress links to this user */ |
| 1051 | const char *zThisTag /* Suppress links to this tag */ |
| 1052 | ){ |
| 1053 | const char *zType = db_column_text(pQuery, 7); |
| 1054 | assert( zType!=0 ); |
| 1055 | if( zType[0]!='c' ){ |
| 1056 | timeline_extra(pQuery, tmFlags, zThisUser, zThisTag); |
| 1057 | }else{ |
| 1058 | int rid = db_column_int(pQuery, 0); |
| 1059 | const char *zUuid = db_column_text(pQuery, 1); |
| 1060 | char *zBrName = branch_of_rid(rid); |
| 1061 | char *zNm; |
| 1062 | |
| 1063 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 1064 | @ <nobr>check-in: \ |
| 1065 | @ %z(href("%R/info/%!S",zUuid))<span class='timelineHash'>\ |
| 1066 | @ %S(zUuid)</span></a></nobr><br> |
| 1067 | if( fossil_strcmp(zBrName,"trunk")!=0 ){ |
| 1068 | @ <nobr>branch: \ |
| 1069 | @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a></nobr><br>\ |
| 1070 | } |
| 1071 | }else{ |
| 1072 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 1073 | @ check-in: %z(href("%R/info/%!S",zUuid))\ |
| 1074 | @ <span class='timelineHash'>%S(zUuid)</span></a> |
| 1075 | } |
| 1076 | if( (tmFlags & TIMELINE_GRAPH)==0 && fossil_strcmp(zBrName,"trunk")!=0 ){ |
| 1077 | @ branch: \ |
| 1078 | @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a> |
| 1079 | } |
| 1080 | } |
| 1081 | zNm = archive_base_name(rid); |
| 1082 | @ %z(href("%R/tarball/%s.tar.gz",zNm))\ |
| 1083 | @ <button>Tarball</button></a> |
| 1084 | @ %z(href("%R/zip/%s.zip",zNm))\ |
| 1085 | @ <button>ZIP Archive</button></a> |
| 1086 | fossil_free(zBrName); |
| 1087 | fossil_free(zNm); |
| 1088 | } |
| 1089 | } |
| 1090 | |
| 1091 | /* |
| 1092 | ** SETTING: suggested-downloads width=70 block-text |
| 1093 | ** |
| 1094 | ** This setting controls the suggested tarball/ZIP downloads on the |
| 1095 | ** [[/download]] page. The value is a TCL list. Each set of four items |
| 1096 | ** defines a set of check-ins to be added to the suggestion list. |
| 1097 | ** The items in each group are: |
| 1098 | ** |
| 1099 | ** | COUNT TAG MAX_AGE COMMENT |
| 1100 | ** |
| 1101 | ** COUNT is the number of check-ins to match, starting with the most |
| 1102 | ** recent and working bacwards in time. Check-ins match if they contain |
| 1103 | ** the tag TAG. If MAX_AGE is not an empty string, then it specifies |
| 1104 | ** the maximum age of any matching check-in. COMMENT is an optional |
| 1105 | ** comment for each match. |
| 1106 | ** |
| 1107 | ** The special value of "OPEN-LEAF" for TAG matches any check-in that |
| 1108 | ** is an open leaf. |
| 1109 | ** |
| 1110 | ** MAX_AGE is of the form "{AMT UNITS}" where AMT is a floating point |
| 1111 | ** value and UNITS is one of "seconds", "hours", "days", "weeks", "months", |
| 1112 | ** or "years". If MAX_AGE is an empty string then there is no age limit. |
| 1113 | ** |
| 1114 | ** If COMMENT is not an empty string, then it is an additional comment |
| 1115 | ** added to the output description of the suggested download. The idea of |
| 1116 | ** COMMENT is to explain to the reader why a check-in is a suggested |
| 1117 | ** download. |
| 1118 | ** |
| 1119 | ** Example: |
| 1120 | ** |
| 1121 | ** | 1 trunk {} {Latest Trunk Check-in} |
| 1122 | ** | 5 OPEN-LEAF {1 month} {Active Branch} |
| 1123 | ** | 999 release {1 year} {Official Release} |
| 1124 | ** |
| 1125 | ** The value causes the /download page to show the union of the most |
| 1126 | ** recent trunk check-in of any age, the five most recent |
| 1127 | ** open leaves within the past month, and essentially |
| 1128 | ** all releases within the past year. If the same check-in matches more |
| 1129 | ** than one rule, the COMMENT of the first match is used. |
| 1130 | */ |
| 1131 | |
| 1132 | /* |
| 1133 | ** WEBPAGE: /download |
| 1134 | ** |
| 1135 | ** Show a special no-graph timeline of recent important check-ins with |
| 1136 | ** an opportunity to pull tarballs and ZIPs. |
| 1137 | */ |
| 1138 | void download_page(void){ |
| 1139 | Stmt q; /* The actual timeline query */ |
| 1140 | const char *zTarlistCfg; /* Configuration string */ |
| 1141 | char **azItem; /* Decomposed elements of zTarlistCfg */ |
| 1142 | int *anItem; /* Bytes in each term of azItem[] */ |
| 1143 | int nItem; /* Number of terms in azItem[] */ |
| 1144 | int i; /* Loop counter */ |
| 1145 | int tmFlags; /* Timeline display flags */ |
| 1146 | int n; /* Number of suggested downloads */ |
| 1147 | double rNow; /* Current time. Julian day number */ |
| 1148 | int bPlainTextCom; /* Use plain-text comments */ |
| 1149 | |
| 1150 | login_check_credentials(); |
| 1151 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1152 | |
| 1153 | style_set_current_feature("timeline"); |
| 1154 | style_header("Suggested Downloads"); |
| 1155 | |
| 1156 | zTarlistCfg = db_get("suggested-downloads","off"); |
| 1157 | db_multi_exec( |
| 1158 | "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY, com TEXT);" |
| 1159 | ); |
| 1160 | rNow = db_double(0.0,"SELECT julianday()"); |
| 1161 | if( !g.interp ) Th_FossilInit(0); |
| 1162 | Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg), |
| 1163 | &azItem, &anItem, &nItem); |
| 1164 | bPlainTextCom = db_get_boolean("timeline-plaintext",0); |
| 1165 | for(i=0; i<nItem-3; i+=4){ |
| 1166 | int cnt; /* The number of instances of zLabel to use */ |
| 1167 | char *zLabel; /* The label to match */ |
| 1168 | double rStart; /* Starting time, julian day number */ |
| 1169 | char *zComment = 0; /* Comment to apply */ |
| 1170 | if( anItem[i]==1 && azItem[i][0]=='*' ){ |
| 1171 | cnt = -1; |
| 1172 | }else if( anItem[i]<1 ){ |
| 1173 | cnt = 0; |
| 1174 | }else{ |
| 1175 | cnt = atoi(azItem[i]); |
| 1176 | } |
| 1177 | if( cnt==0 ) continue; |
| 1178 | zLabel = fossil_strndup(azItem[i+1],anItem[i+1]); |
| 1179 | if( anItem[i+2]==0 ){ |
| 1180 | rStart = 0.0; |
| 1181 | }else{ |
| 1182 | char *zMax = fossil_strndup(azItem[i+2], anItem[i+2]); |
| 1183 | double r = atof(zMax); |
| 1184 | if( strstr(zMax,"sec") ){ |
| 1185 | rStart = rNow - r/86400.0; |
| 1186 | }else |
| 1187 | if( strstr(zMax,"hou") ){ |
| 1188 | rStart = rNow - r/24.0; |
| 1189 | }else |
| 1190 | if( strstr(zMax,"da") ){ |
| 1191 | rStart = rNow - r; |
| 1192 | }else |
| 1193 | if( strstr(zMax,"wee") ){ |
| 1194 | rStart = rNow - r*7.0; |
| 1195 | }else |
| 1196 | if( strstr(zMax,"mon") ){ |
| 1197 | rStart = rNow - r*30.44; |
| 1198 | }else |
| 1199 | if( strstr(zMax,"yea") ){ |
| 1200 | rStart = rNow - r*365.24; |
| 1201 | }else |
| 1202 | { /* Default to seconds */ |
| 1203 | rStart = rNow - r/86400.0; |
| 1204 | } |
| 1205 | } |
| 1206 | if( anItem[i+3]==0 ){ |
| 1207 | zComment = fossil_strdup(""); |
| 1208 | }else if( bPlainTextCom ){ |
| 1209 | zComment = mprintf("** %.*s ** ", anItem[i+3], azItem[i+3]); |
| 1210 | }else{ |
| 1211 | zComment = mprintf("<b>%.*s</b>\n<p>", anItem[i+3], azItem[i+3]); |
| 1212 | } |
| 1213 | if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){ |
| 1214 | db_multi_exec( |
| 1215 | "INSERT OR IGNORE INTO tarlist(rid,com)" |
| 1216 | " SELECT leaf.rid, %Q FROM leaf, event" |
| 1217 | " WHERE event.objid=leaf.rid" |
| 1218 | " AND event.mtime>=%.6f" |
| 1219 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 1220 | " WHERE tagxref.rid=leaf.rid" |
| 1221 | " AND tagid=%d AND tagtype>0)" |
| 1222 | " ORDER BY event.mtime DESC LIMIT %d", |
| 1223 | zComment, rStart, TAG_CLOSED, cnt |
| 1224 | ); |
| 1225 | }else{ |
| 1226 | db_multi_exec( |
| 1227 | "WITH taglist(tid) AS" |
| 1228 | " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')" |
| 1229 | "INSERT OR IGNORE INTO tarlist(rid,com)" |
| 1230 | " SELECT event.objid, %Q FROM event CROSS JOIN tagxref" |
| 1231 | " WHERE event.type='ci'" |
| 1232 | " AND event.mtime>=%.6f" |
| 1233 | " AND tagxref.tagid IN taglist" |
| 1234 | " AND tagtype>0" |
| 1235 | " AND tagxref.rid=event.objid" |
| 1236 | " ORDER BY event.mtime DESC LIMIT %d", |
| 1237 | zLabel, zComment, rStart, cnt |
| 1238 | ); |
| 1239 | } |
| 1240 | fossil_free(zLabel); |
| 1241 | fossil_free(zComment); |
| 1242 | } |
| 1243 | Th_Free(g.interp, azItem); |
| 1244 | |
| 1245 | n = db_int(0, "SELECT count(*) FROM tarlist"); |
| 1246 | if( n==0 ){ |
| 1247 | @ <h2>No tarball/ZIP suggestions are available at this time</h2> |
| 1248 | }else{ |
| 1249 | @ <h2>%d(n) Tarball/ZIP Download Suggestion%s(n>1?"s":""):</h2> |
| 1250 | db_prepare(&q, |
| 1251 | "WITH matches AS (%s AND blob.rid IN (SELECT rid FROM tarlist))\n" |
| 1252 | "SELECT blobRid, uuid, timestamp," |
| 1253 | " com||comment," |
| 1254 | " user, leaf, bgColor, eventType, tags, tagid, brief, mtime" |
| 1255 | " FROM matches JOIN tarlist ON tarlist.rid=blobRid" |
| 1256 | " ORDER BY matches.mtime DESC", |
| 1257 | timeline_query_for_www() |
| 1258 | ); |
| 1259 | |
| 1260 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR |
| 1261 | | TIMELINE_BRCOLOR; |
| 1262 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, download_extra); |
| 1263 | db_finalize(&q); |
| 1264 | } |
| 1265 | if( g.perm.Clone ){ |
| 1266 | char *zNm = fossil_strdup(db_get("project-name","clone")); |
| 1267 | sanitize_name(zNm); |
| 1268 | @ <hr> |
| 1269 | @ <h2>You Can Clone This Repository</h2> |
| 1270 | @ |
| 1271 | @ <p>Clone this repository by running a command similar to the following: |
| 1272 | @ <blockquote><pre> |
| 1273 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 1274 | @ </pre></blockquote> |
| 1275 | @ <p>A clone gives you local access to all historical content. |
| 1276 | @ Cloning is a bandwidth- and CPU-efficient alternative to extracting |
| 1277 | @ multiple tarballs and ZIPs. |
| 1278 | @ Do a web search for "fossil clone" or similar to find additional |
| 1279 | @ information about using a cloned Fossil repository. Or ask your |
| 1280 | @ favorite AI how to extract content from a Fossil clone. |
| 1281 | fossil_free(zNm); |
| 1282 | } |
| 1283 | |
| 1284 | style_finish_page(); |
| 1285 | } |
| 1286 | |
| 1287 | /* |
| 1288 | ** WEBPAGE: rchvdwnld |
| 1289 | ** |
| 1290 | ** Short for "archive download". This page should have a single name= |
| 1291 | ** query parameter that is a check-in hash. It present a menu of possible |
| 1292 | ** download options for that check-in, including tarball, ZIP, or SQLAR. |
| 1293 | ** |
| 1294 | ** This is a utility page. The /dir and /tree pages sometimes have a |
| 1295 | ** "Download" option in their submenu which redirects here. Those pages |
| 1296 | ** used to have separate "Tarball" and "ZIP" submenu entries, but as |
| 1297 | ** submenu entries appear in alphabetical order, that caused the two |
| 1298 | ** submenu entries to be separated from one another, which is distracting. |
| 1299 | */ |
| 1300 | void rchvdwnld_page(void){ |
| 1301 | const char *zUuid; |
| 1302 | char *zBase; |
| 1303 | int nUuid; |
| 1304 | int rid; |
| 1305 | login_check_credentials(); |
| 1306 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1307 | if( robot_restrict("zip") || robot_restrict("download") ) return; |
| 1308 | |
| 1309 | zUuid = P("name"); |
| 1310 | if( zUuid==0 |
| 1311 | || (nUuid = (int)strlen(zUuid))<6 |
| 1312 | || !validate16(zUuid,-1) |
| 1313 | || (rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid))==0 |
| 1314 | || !db_exists("SELECT 1 from event WHERE type='ci' AND objid=%d",rid) |
| 1315 | ){ |
| 1316 | fossil_redirect_home(); |
| 1317 | } |
| 1318 | zUuid = db_text(zUuid, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1319 | style_header("Downloads For Check-in %!S", zUuid); |
| 1320 | zBase = archive_base_name(rid); |
| 1321 | @ <div class="section accordion">Downloads for check-in \ |
| 1322 | @ %z(href("%R/info/%!S",zUuid))%S(zUuid)</a></div> |
| 1323 | @ <div class="accordion_panel"> |
| 1324 | @ <table class="label-value"> |
| 1325 | @ <tr> |
| 1326 | @ <th>Tarball:</th> |
| 1327 | @ <td>%z(href("%R/tarball/%s.tar.gz",zBase))\ |
| 1328 | @ %s(g.zBaseURL)/tarball/%s(zBase).tar.gz</a></td> |
| 1329 | @ </tr> |
| 1330 | @ |
| 1331 | @ <tr> |
| 1332 | @ <th>ZIP:</th> |
| 1333 | @ <td>%z(href("%R/zip/%s.zip",zBase))\ |
| 1334 | @ %s(g.zBaseURL)/zip/%s(zBase).zip</a></td> |
| 1335 | @ </tr> |
| 1336 | @ |
| 1337 | @ <tr> |
| 1338 | @ <th>SQLAR:</th> |
| 1339 | @ <td>%z(href("%R/sqlar/%s.sqlar",zBase))\ |
| 1340 | @ %s(g.zBaseURL)/sqlar/%s(zBase).sqlar</a></td> |
| 1341 | @ </tr> |
| 1342 | @ </table></div> |
| 1343 | fossil_free(zBase); |
| 1344 | @ <div class="section accordion">Context</div><div class="accordion_panel"> |
| 1345 | render_checkin_context(rid, 0, 0, 0); |
| 1346 | @ </div> |
| 1347 | style_finish_page(); |
| 1348 | } |
| 1349 |
+1
-1
| --- src/th_lang.c | ||
| +++ src/th_lang.c | ||
| @@ -956,11 +956,11 @@ | ||
| 956 | 956 | ** |
| 957 | 957 | */ |
| 958 | 958 | static int string_match_command( |
| 959 | 959 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 960 | 960 | ){ |
| 961 | - extern char *fossil_strndup(const char*,int); | |
| 961 | + extern char *fossil_strndup(const char*,ssize_t); | |
| 962 | 962 | extern void fossil_free(void*); |
| 963 | 963 | char *zPat, *zStr; |
| 964 | 964 | int rc; |
| 965 | 965 | if( argc!=4 ){ |
| 966 | 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 967 | 967 |
| --- src/th_lang.c | |
| +++ src/th_lang.c | |
| @@ -956,11 +956,11 @@ | |
| 956 | ** |
| 957 | */ |
| 958 | static int string_match_command( |
| 959 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 960 | ){ |
| 961 | extern char *fossil_strndup(const char*,int); |
| 962 | extern void fossil_free(void*); |
| 963 | char *zPat, *zStr; |
| 964 | int rc; |
| 965 | if( argc!=4 ){ |
| 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 967 |
| --- src/th_lang.c | |
| +++ src/th_lang.c | |
| @@ -956,11 +956,11 @@ | |
| 956 | ** |
| 957 | */ |
| 958 | static int string_match_command( |
| 959 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 960 | ){ |
| 961 | extern char *fossil_strndup(const char*,ssize_t); |
| 962 | extern void fossil_free(void*); |
| 963 | char *zPat, *zStr; |
| 964 | int rc; |
| 965 | if( argc!=4 ){ |
| 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 967 |
+3
-3
| --- src/th_main.c | ||
| +++ src/th_main.c | ||
| @@ -2145,11 +2145,11 @@ | ||
| 2145 | 2145 | } |
| 2146 | 2146 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2147 | 2147 | if( nArg+2!=argc ){ |
| 2148 | 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2149 | 2149 | } |
| 2150 | - zErr = re_compile(&pRe, argv[nArg], noCase); | |
| 2150 | + zErr = fossil_re_compile(&pRe, argv[nArg], noCase); | |
| 2151 | 2151 | if( !zErr ){ |
| 2152 | 2152 | Th_SetResultInt(interp, re_match(pRe, |
| 2153 | 2153 | (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); |
| 2154 | 2154 | rc = TH_OK; |
| 2155 | 2155 | }else{ |
| @@ -2202,11 +2202,11 @@ | ||
| 2202 | 2202 | Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0); |
| 2203 | 2203 | return TH_ERROR; |
| 2204 | 2204 | } |
| 2205 | 2205 | zRegexp = db_get("th1-uri-regexp", 0); |
| 2206 | 2206 | if( zRegexp && zRegexp[0] ){ |
| 2207 | - const char *zErr = re_compile(&pRe, zRegexp, 0); | |
| 2207 | + const char *zErr = fossil_re_compile(&pRe, zRegexp, 0); | |
| 2208 | 2208 | if( zErr ){ |
| 2209 | 2209 | Th_SetResult(interp, zErr, -1); |
| 2210 | 2210 | return TH_ERROR; |
| 2211 | 2211 | } |
| 2212 | 2212 | } |
| @@ -2965,11 +2965,11 @@ | ||
| 2965 | 2965 | if( rc!=TH_OK ) break; |
| 2966 | 2966 | z += i; |
| 2967 | 2967 | if( z[0] ){ z += 6; } |
| 2968 | 2968 | i = 0; |
| 2969 | 2969 | }else{ |
| 2970 | - i++; | |
| 2970 | + i += strcspn(&z[i+1], "<$") + 1; | |
| 2971 | 2971 | } |
| 2972 | 2972 | } |
| 2973 | 2973 | if( rc==TH_ERROR ){ |
| 2974 | 2974 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2975 | 2975 | sendError(pOut,zResult, n, 1); |
| 2976 | 2976 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -2145,11 +2145,11 @@ | |
| 2145 | } |
| 2146 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2147 | if( nArg+2!=argc ){ |
| 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2149 | } |
| 2150 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2151 | if( !zErr ){ |
| 2152 | Th_SetResultInt(interp, re_match(pRe, |
| 2153 | (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); |
| 2154 | rc = TH_OK; |
| 2155 | }else{ |
| @@ -2202,11 +2202,11 @@ | |
| 2202 | Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0); |
| 2203 | return TH_ERROR; |
| 2204 | } |
| 2205 | zRegexp = db_get("th1-uri-regexp", 0); |
| 2206 | if( zRegexp && zRegexp[0] ){ |
| 2207 | const char *zErr = re_compile(&pRe, zRegexp, 0); |
| 2208 | if( zErr ){ |
| 2209 | Th_SetResult(interp, zErr, -1); |
| 2210 | return TH_ERROR; |
| 2211 | } |
| 2212 | } |
| @@ -2965,11 +2965,11 @@ | |
| 2965 | if( rc!=TH_OK ) break; |
| 2966 | z += i; |
| 2967 | if( z[0] ){ z += 6; } |
| 2968 | i = 0; |
| 2969 | }else{ |
| 2970 | i++; |
| 2971 | } |
| 2972 | } |
| 2973 | if( rc==TH_ERROR ){ |
| 2974 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2975 | sendError(pOut,zResult, n, 1); |
| 2976 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -2145,11 +2145,11 @@ | |
| 2145 | } |
| 2146 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2147 | if( nArg+2!=argc ){ |
| 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2149 | } |
| 2150 | zErr = fossil_re_compile(&pRe, argv[nArg], noCase); |
| 2151 | if( !zErr ){ |
| 2152 | Th_SetResultInt(interp, re_match(pRe, |
| 2153 | (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); |
| 2154 | rc = TH_OK; |
| 2155 | }else{ |
| @@ -2202,11 +2202,11 @@ | |
| 2202 | Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0); |
| 2203 | return TH_ERROR; |
| 2204 | } |
| 2205 | zRegexp = db_get("th1-uri-regexp", 0); |
| 2206 | if( zRegexp && zRegexp[0] ){ |
| 2207 | const char *zErr = fossil_re_compile(&pRe, zRegexp, 0); |
| 2208 | if( zErr ){ |
| 2209 | Th_SetResult(interp, zErr, -1); |
| 2210 | return TH_ERROR; |
| 2211 | } |
| 2212 | } |
| @@ -2965,11 +2965,11 @@ | |
| 2965 | if( rc!=TH_OK ) break; |
| 2966 | z += i; |
| 2967 | if( z[0] ){ z += 6; } |
| 2968 | i = 0; |
| 2969 | }else{ |
| 2970 | i += strcspn(&z[i+1], "<$") + 1; |
| 2971 | } |
| 2972 | } |
| 2973 | if( rc==TH_ERROR ){ |
| 2974 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2975 | sendError(pOut,zResult, n, 1); |
| 2976 |
+284
-164
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -115,11 +115,13 @@ | ||
| 115 | 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | -#define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */ | |
| 120 | +#define TIMELINE_SIMPLE 0x0020000 /* Use the "simple" view style */ | |
| 121 | +#define TIMELINE_INLINE 0x0033000 /* Mask for views with in-line display */ | |
| 122 | +#define TIMELINE_VIEWS 0x003f000 /* Mask for all of the view styles */ | |
| 121 | 123 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 122 | 124 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 123 | 125 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 124 | 126 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 125 | 127 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -170,10 +172,182 @@ | ||
| 170 | 172 | manifest_destroy(pPost); |
| 171 | 173 | } |
| 172 | 174 | } |
| 173 | 175 | |
| 174 | 176 | |
| 177 | +/* | |
| 178 | +** This routine generates the default "extra" text after the description | |
| 179 | +** in a timeline. | |
| 180 | +** | |
| 181 | +** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" | |
| 182 | +** | |
| 183 | +** This routine is used if no xExtra argument is supplied to | |
| 184 | +** www_print_timeline(). | |
| 185 | +*/ | |
| 186 | +void timeline_extra( | |
| 187 | + Stmt *pQuery, /* Current row of the timeline query */ | |
| 188 | + int tmFlags, /* Flags to www_print_timeline() */ | |
| 189 | + const char *zThisUser, /* Suppress links to this user */ | |
| 190 | + const char *zThisTag /* Suppress links to this tag */ | |
| 191 | +){ | |
| 192 | + int rid = db_column_int(pQuery, 0); | |
| 193 | + const char *zUuid = db_column_text(pQuery, 1); | |
| 194 | + const char *zDate = db_column_text(pQuery, 2); | |
| 195 | + const char *zType = db_column_text(pQuery, 7); | |
| 196 | + const char *zUser = db_column_text(pQuery, 4); | |
| 197 | + const char *zTagList = db_column_text(pQuery, 8); | |
| 198 | + int tagid = db_column_int(pQuery, 9); | |
| 199 | + const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; | |
| 200 | + | |
| 201 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 202 | + cgi_printf("("); | |
| 203 | + } | |
| 204 | + | |
| 205 | + if( (tmFlags & TIMELINE_CLASSIC)==0 ){ | |
| 206 | + if( zType[0]=='c' ){ | |
| 207 | + const char *zPrefix = 0; | |
| 208 | + static int markLeaves = -1; | |
| 209 | + if( markLeaves<0 ){ | |
| 210 | + markLeaves = db_get_int("timeline-mark-leaves",1); | |
| 211 | + if( markLeaves<0 ) markLeaves = 1; | |
| 212 | + } | |
| 213 | + if( strcmp(zUuid, MANIFEST_UUID)==0 ){ | |
| 214 | + /* This will only ever happen when Fossil is drawing a timeline for | |
| 215 | + ** its own self-host repository. If the timeline shows the specific | |
| 216 | + ** check-in corresponding to the current executable, then tag that | |
| 217 | + ** check-in with "self" */ | |
| 218 | + zPrefix = "self "; | |
| 219 | + }else if( markLeaves && db_column_int(pQuery,5) ){ | |
| 220 | + if( markLeaves==1 ){ | |
| 221 | + zPrefix = has_closed_tag(rid) ? "closed " : "leaf "; | |
| 222 | + }else{ | |
| 223 | + zPrefix = has_closed_tag(rid) ? | |
| 224 | + "<span class='timelineLeaf'>Closed-Leaf</span>\n" : | |
| 225 | + "<span class='timelineLeaf'>Leaf</span>\n"; | |
| 226 | + } | |
| 227 | + } | |
| 228 | + cgi_printf("%scheck-in: %z<span class='timelineHash'>" | |
| 229 | + "%S</span></a> ", | |
| 230 | + zPrefix, href("%R/info/%!S",zUuid),zUuid); | |
| 231 | + }else if( zType[0]=='e' && tagid ){ | |
| 232 | + cgi_printf("technote: "); | |
| 233 | + hyperlink_to_event_tagid(tagid<0?-tagid:tagid); | |
| 234 | + }else{ | |
| 235 | + cgi_printf("artifact: %z%S</a> ", | |
| 236 | + href("%R/info/%!S",zUuid),zUuid); | |
| 237 | + } | |
| 238 | + }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' | |
| 239 | + || zType[0]=='n' || zType[0]=='f'){ | |
| 240 | + cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); | |
| 241 | + } | |
| 242 | + | |
| 243 | + if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ | |
| 244 | + @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ | |
| 245 | + @ data-id='%d(rid)'>...</span> | |
| 246 | + @ <span class='clutter' id='detail-%d(rid)'> | |
| 247 | + } | |
| 248 | + | |
| 249 | + if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ | |
| 250 | + char *zLink; | |
| 251 | + if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ | |
| 252 | + zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); | |
| 253 | + }else{ | |
| 254 | + zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate); | |
| 255 | + } | |
| 256 | + cgi_printf("user: %z%h</a>", href("%z",zLink), zDispUser); | |
| 257 | + }else{ | |
| 258 | + cgi_printf("user: %h", zDispUser); | |
| 259 | + } | |
| 260 | + | |
| 261 | + /* Generate the "tags: TAGLIST" at the end of the comment, together | |
| 262 | + ** with hyperlinks to the tag list. | |
| 263 | + */ | |
| 264 | + if( zTagList && zTagList[0]==0 ) zTagList = 0; | |
| 265 | + if( zTagList ){ | |
| 266 | + if( g.perm.Hyperlink ){ | |
| 267 | + int i; | |
| 268 | + const char *z = zTagList; | |
| 269 | + Blob links; | |
| 270 | + blob_zero(&links); | |
| 271 | + while( z && z[0] ){ | |
| 272 | + for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} | |
| 273 | + if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ | |
| 274 | + blob_appendf(&links, | |
| 275 | + "%z%#h</a>%.2s", | |
| 276 | + href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i] | |
| 277 | + ); | |
| 278 | + }else{ | |
| 279 | + blob_appendf(&links, "%#h", i+2, z); | |
| 280 | + } | |
| 281 | + if( z[i]==0 ) break; | |
| 282 | + z += i+2; | |
| 283 | + } | |
| 284 | + cgi_printf(" tags: %s", blob_str(&links)); | |
| 285 | + blob_reset(&links); | |
| 286 | + }else{ | |
| 287 | + cgi_printf(" tags: %h", zTagList); | |
| 288 | + } | |
| 289 | + } | |
| 290 | + | |
| 291 | + if( tmFlags & TIMELINE_SHOWRID ){ | |
| 292 | + int srcId = delta_source_rid(rid); | |
| 293 | + if( srcId ){ | |
| 294 | + cgi_printf(" id: %z%d←%d</a>", | |
| 295 | + href("%R/deltachain/%d",rid), rid, srcId); | |
| 296 | + }else{ | |
| 297 | + cgi_printf(" id: %z%d</a>", | |
| 298 | + href("%R/deltachain/%d",rid), rid); | |
| 299 | + } | |
| 300 | + } | |
| 301 | + tag_private_status(rid); | |
| 302 | + | |
| 303 | + if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ | |
| 304 | + cgi_printf("</span>"); /* End of the declutter span */ | |
| 305 | + } | |
| 306 | + | |
| 307 | + /* End timelineDetail */ | |
| 308 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 309 | + cgi_printf(")"); | |
| 310 | + } | |
| 311 | +} | |
| 312 | + | |
| 313 | + | |
| 314 | +/* | |
| 315 | +** SETTING: timeline-truncate-at-blank boolean default=off | |
| 316 | +** | |
| 317 | +** If enabled, check-in comments displayed on the timeline are truncated | |
| 318 | +** at the first blank line of the comment text. The comment text after | |
| 319 | +** the first blank line is only seen in the /info or similar pages that | |
| 320 | +** show details about the check-in. | |
| 321 | +*/ | |
| 322 | +/* | |
| 323 | +** SETTING: timeline-tslink-info boolean default=off | |
| 324 | +** | |
| 325 | +** The hyperlink on the timestamp associated with each timeline entry, | |
| 326 | +** on the far left-hand side of the screen, normally targets another | |
| 327 | +** /timeline page that shows the entry in context. However, if this | |
| 328 | +** option is turned on, that hyperlink targets the /info page showing | |
| 329 | +** the details of the entry. | |
| 330 | +*/ | |
| 331 | +/* | |
| 332 | +** SETTING: timeline-mark-leaves width=5 default=1 | |
| 333 | +** | |
| 334 | +** Determine whether or not leaf check-ins are marked as such in the | |
| 335 | +** details section of the timeline. The value is an integer between 0 | |
| 336 | +** and 2: | |
| 337 | +** | |
| 338 | +** 0 Do not show any special marking for leaf check-ins. | |
| 339 | +** | |
| 340 | +** 1 Show just "leaf" or "closed" | |
| 341 | +** | |
| 342 | +** 2 Show "Leaf" or "Closed-Leaf" with emphasis | |
| 343 | +** | |
| 344 | +** The default is currently 1. Prior to 2025-10-19, the default was 2. | |
| 345 | +** This setting has no effect on the "Classic" view, which always behaves | |
| 346 | +** as if the setting were 2. | |
| 347 | +*/ | |
| 348 | + | |
| 175 | 349 | /* |
| 176 | 350 | ** Output a timeline in the web format given a query. The query |
| 177 | 351 | ** should return these columns: |
| 178 | 352 | ** |
| 179 | 353 | ** 0. rid |
| @@ -194,11 +368,11 @@ | ||
| 194 | 368 | const char *zThisUser, /* Suppress links to this user */ |
| 195 | 369 | const char *zThisTag, /* Suppress links to this tag */ |
| 196 | 370 | Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */ |
| 197 | 371 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 198 | 372 | int secondRid, /* Secondary highlight (or zero) */ |
| 199 | - void (*xExtra)(int) /* Routine to call on each line of display */ | |
| 373 | + void (*xExtra)(Stmt*,int,const char*,const char*) /* generate "extra" text */ | |
| 200 | 374 | ){ |
| 201 | 375 | int mxWikiLen; |
| 202 | 376 | Blob comment; |
| 203 | 377 | int prevTagid = 0; |
| 204 | 378 | int suppressCnt = 0; |
| @@ -214,55 +388,41 @@ | ||
| 214 | 388 | const char *zStyle; /* Sub-name for classes for the style */ |
| 215 | 389 | const char *zDateFmt; |
| 216 | 390 | int iTableId = timeline_tableid(); |
| 217 | 391 | int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info |
| 218 | 392 | ** page rather than the /timeline page */ |
| 393 | + char *zMainBranch = db_get("main-branch","trunk"); | |
| 394 | + | |
| 219 | 395 | |
| 220 | 396 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 221 | 397 | vid = db_lget_int("checkout", 0); |
| 222 | 398 | } |
| 399 | + if( xExtra==0 ) xExtra = timeline_extra; | |
| 223 | 400 | zPrevDate[0] = 0; |
| 224 | 401 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 225 | 402 | 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 | 403 | 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 | 404 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| 245 | 405 | if( (tmFlags & TIMELINE_VIEWS)==0 ){ |
| 246 | 406 | tmFlags |= timeline_ss_cookie(); |
| 247 | 407 | } |
| 248 | 408 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 249 | 409 | zStyle = "Columnar"; |
| 250 | 410 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 251 | 411 | zStyle = "Compact"; |
| 412 | + }else if( tmFlags & TIMELINE_SIMPLE ){ | |
| 413 | + zStyle = "Simple"; | |
| 252 | 414 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 253 | 415 | zStyle = "Verbose"; |
| 254 | 416 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 255 | 417 | zStyle = "Classic"; |
| 256 | 418 | }else{ |
| 257 | 419 | zStyle = "Modern"; |
| 258 | 420 | } |
| 259 | 421 | zDateFmt = P("datefmt"); |
| 260 | 422 | if( zDateFmt ) dateFormat = atoi(zDateFmt); |
| 261 | - if( tmFlags & TIMELINE_GRAPH ){ | |
| 262 | - pGraph = graph_init(); | |
| 263 | - } | |
| 423 | + pGraph = graph_init(); | |
| 264 | 424 | if( (tmFlags & TIMELINE_CHPICK)!=0 |
| 265 | 425 | && !db_table_exists("repository","cherrypick") |
| 266 | 426 | ){ |
| 267 | 427 | tmFlags &= ~TIMELINE_CHPICK; |
| 268 | 428 | } |
| @@ -275,13 +435,11 @@ | ||
| 275 | 435 | int isLeaf = db_column_int(pQuery, 5); |
| 276 | 436 | const char *zBgClr = db_column_text(pQuery, 6); |
| 277 | 437 | const char *zDate = db_column_text(pQuery, 2); |
| 278 | 438 | const char *zType = db_column_text(pQuery, 7); |
| 279 | 439 | const char *zUser = db_column_text(pQuery, 4); |
| 280 | - const char *zTagList = db_column_text(pQuery, 8); | |
| 281 | 440 | int tagid = db_column_int(pQuery, 9); |
| 282 | - const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; | |
| 283 | 441 | char *zBr = 0; /* Branch */ |
| 284 | 442 | int commentColumn = 3; /* Column containing comment text */ |
| 285 | 443 | int modPending; /* Pending moderation */ |
| 286 | 444 | char *zDateLink; /* URL for the link on the timestamp */ |
| 287 | 445 | int drawDetailEllipsis; /* True to show ellipsis in place of detail */ |
| @@ -434,11 +592,11 @@ | ||
| 434 | 592 | zBr = branch_of_rid(rid); |
| 435 | 593 | if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){ |
| 436 | 594 | /* If no background color is specified, use a color based on the |
| 437 | 595 | ** branch name */ |
| 438 | 596 | if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 439 | - }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){ | |
| 597 | + }else if( zBr==0 || strcmp(zBr,zMainBranch)==0 ){ | |
| 440 | 598 | zBgClr = 0; |
| 441 | 599 | }else{ |
| 442 | 600 | zBgClr = hash_color(zBr); |
| 443 | 601 | } |
| 444 | 602 | } |
| @@ -446,32 +604,34 @@ | ||
| 446 | 604 | if( zType[0]=='c' && pGraph ){ |
| 447 | 605 | int nParent = 0; |
| 448 | 606 | int nCherrypick = 0; |
| 449 | 607 | GraphRowId aParent[GR_MAX_RAIL]; |
| 450 | 608 | static Stmt qparent; |
| 451 | - db_static_prepare(&qparent, | |
| 452 | - "SELECT pid FROM plink" | |
| 453 | - " WHERE cid=:rid AND pid NOT IN phantom" | |
| 454 | - " ORDER BY isprim DESC /*sort*/" | |
| 455 | - ); | |
| 456 | - db_bind_int(&qparent, ":rid", rid); | |
| 457 | - while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){ | |
| 458 | - aParent[nParent++] = db_column_int(&qparent, 0); | |
| 459 | - } | |
| 460 | - db_reset(&qparent); | |
| 461 | - if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){ | |
| 462 | - static Stmt qcherrypick; | |
| 463 | - db_static_prepare(&qcherrypick, | |
| 464 | - "SELECT parentid FROM cherrypick" | |
| 465 | - " WHERE childid=:rid AND parentid NOT IN phantom" | |
| 466 | - ); | |
| 467 | - db_bind_int(&qcherrypick, ":rid", rid); | |
| 468 | - while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){ | |
| 469 | - aParent[nParent++] = db_column_int(&qcherrypick, 0); | |
| 470 | - nCherrypick++; | |
| 471 | - } | |
| 472 | - db_reset(&qcherrypick); | |
| 609 | + if( tmFlags & TIMELINE_GRAPH ){ | |
| 610 | + db_static_prepare(&qparent, | |
| 611 | + "SELECT pid FROM plink" | |
| 612 | + " WHERE cid=:rid AND pid NOT IN phantom" | |
| 613 | + " ORDER BY isprim DESC /*sort*/" | |
| 614 | + ); | |
| 615 | + db_bind_int(&qparent, ":rid", rid); | |
| 616 | + while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){ | |
| 617 | + aParent[nParent++] = db_column_int(&qparent, 0); | |
| 618 | + } | |
| 619 | + db_reset(&qparent); | |
| 620 | + if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){ | |
| 621 | + static Stmt qcherrypick; | |
| 622 | + db_static_prepare(&qcherrypick, | |
| 623 | + "SELECT parentid FROM cherrypick" | |
| 624 | + " WHERE childid=:rid AND parentid NOT IN phantom" | |
| 625 | + ); | |
| 626 | + db_bind_int(&qcherrypick, ":rid", rid); | |
| 627 | + while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){ | |
| 628 | + aParent[nParent++] = db_column_int(&qcherrypick, 0); | |
| 629 | + nCherrypick++; | |
| 630 | + } | |
| 631 | + db_reset(&qcherrypick); | |
| 632 | + } | |
| 473 | 633 | } |
| 474 | 634 | gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent, |
| 475 | 635 | zBr, zBgClr, zUuid, |
| 476 | 636 | isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0); |
| 477 | 637 | @ <div id="m%d(gidx)" class="tl-nodemark"></div> |
| @@ -598,23 +758,15 @@ | ||
| 598 | 758 | drawDetailEllipsis = 0; |
| 599 | 759 | }else{ |
| 600 | 760 | cgi_printf("%W",blob_str(&comment)); |
| 601 | 761 | } |
| 602 | 762 | } |
| 603 | - | |
| 604 | - if( zType[0]=='c' && strcmp(zUuid, MANIFEST_UUID)==0 ){ | |
| 605 | - /* This will only ever happen when Fossil is drawing a timeline for | |
| 606 | - ** its own self-host repository. If the timeline shows the specific | |
| 607 | - ** check-in corresponding to the current executable, then tag that | |
| 608 | - ** check-in with "This is me!". */ | |
| 609 | - @ <b>← This is me!</b> | |
| 610 | - } | |
| 611 | 763 | |
| 612 | 764 | @ </span> |
| 613 | 765 | blob_reset(&comment); |
| 614 | 766 | |
| 615 | - /* Generate extra information and hyperlinks to follow the comment. | |
| 767 | + /* Generate extra information and hyperlinks that follow the comment. | |
| 616 | 768 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 617 | 769 | */ |
| 618 | 770 | if( drawDetailEllipsis ){ |
| 619 | 771 | @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ |
| 620 | 772 | @ data-id='%d(rid)'>...</span> |
| @@ -628,101 +780,17 @@ | ||
| 628 | 780 | } |
| 629 | 781 | if( tmFlags & TIMELINE_COMPACT ){ |
| 630 | 782 | cgi_printf("<span class='clutter' id='detail-%d'>",rid); |
| 631 | 783 | } |
| 632 | 784 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 633 | - if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ | |
| 634 | - cgi_printf("("); | |
| 635 | - } | |
| 636 | - | |
| 637 | - if( (tmFlags & TIMELINE_CLASSIC)==0 ){ | |
| 638 | - if( zType[0]=='c' ){ | |
| 639 | - if( isLeaf ){ | |
| 640 | - if( has_closed_tag(rid) ){ | |
| 641 | - @ <span class='timelineLeaf'>Closed-Leaf</span> | |
| 642 | - }else{ | |
| 643 | - @ <span class='timelineLeaf'>Leaf</span> | |
| 644 | - } | |
| 645 | - } | |
| 646 | - cgi_printf("check-in: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); | |
| 647 | - }else if( zType[0]=='e' && tagid ){ | |
| 648 | - cgi_printf("technote: "); | |
| 649 | - hyperlink_to_event_tagid(tagid<0?-tagid:tagid); | |
| 650 | - }else{ | |
| 651 | - cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); | |
| 652 | - } | |
| 653 | - }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' | |
| 654 | - || zType[0]=='n' || zType[0]=='f'){ | |
| 655 | - cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); | |
| 656 | - } | |
| 657 | - | |
| 658 | - if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ | |
| 659 | - char *zLink; | |
| 660 | - if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ | |
| 661 | - zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); | |
| 662 | - }else{ | |
| 663 | - zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate); | |
| 664 | - } | |
| 665 | - cgi_printf("user: %z%h</a>", href("%z",zLink), zDispUser); | |
| 666 | - }else{ | |
| 667 | - cgi_printf("user: %h", zDispUser); | |
| 668 | - } | |
| 669 | - | |
| 670 | - /* Generate the "tags: TAGLIST" at the end of the comment, together | |
| 671 | - ** with hyperlinks to the tag list. | |
| 672 | - */ | |
| 673 | - if( zTagList && zTagList[0]==0 ) zTagList = 0; | |
| 674 | - if( zTagList ){ | |
| 675 | - if( g.perm.Hyperlink ){ | |
| 676 | - int i; | |
| 677 | - const char *z = zTagList; | |
| 678 | - Blob links; | |
| 679 | - blob_zero(&links); | |
| 680 | - while( z && z[0] ){ | |
| 681 | - for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} | |
| 682 | - if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ | |
| 683 | - blob_appendf(&links, | |
| 684 | - "%z%#h</a>%.2s", | |
| 685 | - href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i] | |
| 686 | - ); | |
| 687 | - }else{ | |
| 688 | - blob_appendf(&links, "%#h", i+2, z); | |
| 689 | - } | |
| 690 | - if( z[i]==0 ) break; | |
| 691 | - z += i+2; | |
| 692 | - } | |
| 693 | - cgi_printf(" tags: %s", blob_str(&links)); | |
| 694 | - blob_reset(&links); | |
| 695 | - }else{ | |
| 696 | - cgi_printf(" tags: %h", zTagList); | |
| 697 | - } | |
| 698 | - } | |
| 699 | - | |
| 700 | - if( tmFlags & TIMELINE_SHOWRID ){ | |
| 701 | - int srcId = delta_source_rid(rid); | |
| 702 | - if( srcId ){ | |
| 703 | - cgi_printf(" id: %z%d←%d</a>", | |
| 704 | - href("%R/deltachain/%d",rid), rid, srcId); | |
| 705 | - }else{ | |
| 706 | - cgi_printf(" id: %z%d</a>", | |
| 707 | - href("%R/deltachain/%d",rid), rid); | |
| 708 | - } | |
| 709 | - } | |
| 710 | - tag_private_status(rid); | |
| 711 | - if( xExtra ){ | |
| 712 | - xExtra(rid); | |
| 713 | - } | |
| 714 | - /* End timelineDetail */ | |
| 715 | - if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ | |
| 716 | - cgi_printf(")"); | |
| 717 | - } | |
| 785 | + xExtra(pQuery, tmFlags, zThisUser, zThisTag); | |
| 718 | 786 | if( tmFlags & TIMELINE_COMPACT ){ |
| 719 | 787 | @ </span></span> |
| 720 | 788 | }else{ |
| 721 | 789 | @ </span> |
| 722 | 790 | } |
| 723 | - | |
| 791 | + | |
| 724 | 792 | /* Generate the file-change list if requested */ |
| 725 | 793 | if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0 |
| 726 | 794 | && zType[0]=='c' && g.perm.Hyperlink |
| 727 | 795 | ){ |
| 728 | 796 | int inUl = 0; |
| @@ -908,11 +976,13 @@ | ||
| 908 | 976 | int omitDescenders; /* True to omit descenders */ |
| 909 | 977 | int scrollToSelect; /* True to scroll to the selection */ |
| 910 | 978 | int dwellTimeout; /* Milliseconds to wait for tooltips to show */ |
| 911 | 979 | int closeTimeout; /* Milliseconds to wait for tooltips to close */ |
| 912 | 980 | u8 *aiMap; /* The rail map */ |
| 981 | + u8 bNoGraph; /* True to show a minimal graph */ | |
| 913 | 982 | |
| 983 | + bNoGraph = (tmFlags & TIMELINE_GRAPH)==0; | |
| 914 | 984 | iRailPitch = atoi(PD("railpitch","0")); |
| 915 | 985 | showArrowheads = skin_detail_boolean("timeline-arrowheads"); |
| 916 | 986 | circleNodes = skin_detail_boolean("timeline-circle-nodes"); |
| 917 | 987 | colorGraph = skin_detail_boolean("timeline-color-graph-lines"); |
| 918 | 988 | iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0; |
| @@ -930,11 +1000,11 @@ | ||
| 930 | 1000 | @ "nomo": %d(PB("nomo")), |
| 931 | 1001 | @ "iTopRow": %d(iTopRow), |
| 932 | 1002 | @ "omitDescenders": %d(omitDescenders), |
| 933 | 1003 | @ "fileDiff": %d(fileDiff), |
| 934 | 1004 | @ "scrollToSelect": %d(scrollToSelect), |
| 935 | - @ "nrail": %d(pGraph->mxRail+1), | |
| 1005 | + @ "nrail": %d(bNoGraph?1:pGraph->mxRail+1), | |
| 936 | 1006 | @ "baseUrl": "%R", |
| 937 | 1007 | @ "dwellTimeout": %d(dwellTimeout), |
| 938 | 1008 | @ "closeTimeout": %d(closeTimeout), |
| 939 | 1009 | @ "hashDigits": %d(hash_digits(1)), |
| 940 | 1010 | @ "bottomRowId": "btm-%d(iTableId)", |
| @@ -992,12 +1062,16 @@ | ||
| 992 | 1062 | aiMap = pGraph->aiRailMap; |
| 993 | 1063 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 994 | 1064 | int k = 0; |
| 995 | 1065 | cgi_printf("{\"id\":%d,", pRow->idx); |
| 996 | 1066 | cgi_printf("\"bg\":\"%s\",", pRow->zBgClr); |
| 997 | - cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1); | |
| 998 | - if( pRow->bDescender ){ | |
| 1067 | + if( bNoGraph ){ | |
| 1068 | + cgi_printf("\"r\":0,"); /* Chng to ":-1" to omit node circles */ | |
| 1069 | + }else{ | |
| 1070 | + cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1); | |
| 1071 | + } | |
| 1072 | + if( pRow->bDescender && !bNoGraph ){ | |
| 999 | 1073 | cgi_printf("\"d\":%d,", pRow->bDescender); |
| 1000 | 1074 | } |
| 1001 | 1075 | if( pRow->mergeOut>=0 ){ |
| 1002 | 1076 | cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]); |
| 1003 | 1077 | if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx; |
| @@ -1004,11 +1078,13 @@ | ||
| 1004 | 1078 | cgi_printf("\"mu\":%d,", pRow->mergeUpto); |
| 1005 | 1079 | if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){ |
| 1006 | 1080 | cgi_printf("\"cu\":%d,", pRow->cherrypickUpto); |
| 1007 | 1081 | } |
| 1008 | 1082 | } |
| 1009 | - if( pRow->isStepParent ){ | |
| 1083 | + if( bNoGraph ){ | |
| 1084 | + cgi_printf("\"u\":-1,"); | |
| 1085 | + }else if( pRow->isStepParent ){ | |
| 1010 | 1086 | cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1011 | 1087 | }else{ |
| 1012 | 1088 | cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1013 | 1089 | } |
| 1014 | 1090 | k = 0; |
| @@ -1206,10 +1282,26 @@ | ||
| 1206 | 1282 | if( i>2 ){ |
| 1207 | 1283 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1208 | 1284 | } |
| 1209 | 1285 | } |
| 1210 | 1286 | |
| 1287 | +/* | |
| 1288 | +** SETTING: timeline-default-style width=5 default=m | |
| 1289 | +** | |
| 1290 | +** This setting determines the default "view style" for timelines. | |
| 1291 | +** The setting should be a single character, one of the following: | |
| 1292 | +** | |
| 1293 | +** c Compact | |
| 1294 | +** j Columnar | |
| 1295 | +** m Modern | |
| 1296 | +** s Simple | |
| 1297 | +** v Verbose | |
| 1298 | +** x Classic | |
| 1299 | +** | |
| 1300 | +** The default value is m (Modern). | |
| 1301 | +*/ | |
| 1302 | + | |
| 1211 | 1303 | /* |
| 1212 | 1304 | ** Return the default value for the "ss" cookie or query parameter. |
| 1213 | 1305 | ** The "ss" cookie determines the graph style. See the |
| 1214 | 1306 | ** timeline_view_styles[] global constant for a list of choices. |
| 1215 | 1307 | */ |
| @@ -1230,10 +1322,11 @@ | ||
| 1230 | 1322 | switch( v[0] ){ |
| 1231 | 1323 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1232 | 1324 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1233 | 1325 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1234 | 1326 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1327 | + case 's': tmFlags = TIMELINE_SIMPLE; break; | |
| 1235 | 1328 | default: tmFlags = TIMELINE_MODERN; break; |
| 1236 | 1329 | } |
| 1237 | 1330 | return tmFlags; |
| 1238 | 1331 | } |
| 1239 | 1332 | |
| @@ -1242,15 +1335,16 @@ | ||
| 1242 | 1335 | */ |
| 1243 | 1336 | const char *const timeline_view_styles[] = { |
| 1244 | 1337 | "m", "Modern View", |
| 1245 | 1338 | "j", "Columnar View", |
| 1246 | 1339 | "c", "Compact View", |
| 1340 | + "s", "Simple View", | |
| 1247 | 1341 | "v", "Verbose View", |
| 1248 | 1342 | "x", "Classic View", |
| 1249 | 1343 | }; |
| 1250 | 1344 | #if INTERFACE |
| 1251 | -# define N_TIMELINE_VIEW_STYLE 5 | |
| 1345 | +# define N_TIMELINE_VIEW_STYLE 6 | |
| 1252 | 1346 | #endif |
| 1253 | 1347 | |
| 1254 | 1348 | /* |
| 1255 | 1349 | ** Add the select/option box to the timeline submenu that is used to |
| 1256 | 1350 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1363,11 +1457,11 @@ | ||
| 1363 | 1457 | ** the input is a valid date space and false if not. |
| 1364 | 1458 | */ |
| 1365 | 1459 | static int timeline_is_datespan(const char *zDay){ |
| 1366 | 1460 | size_t n = strlen(zDay); |
| 1367 | 1461 | int i, d, m; |
| 1368 | - | |
| 1462 | + | |
| 1369 | 1463 | if( n<17 || n>18 ) return 0; |
| 1370 | 1464 | if( n==18 ){ |
| 1371 | 1465 | if( zDay[17]!='Z' && zDay[17]!='z' ) return 0; |
| 1372 | 1466 | n--; |
| 1373 | 1467 | } |
| @@ -1389,17 +1483,17 @@ | ||
| 1389 | 1483 | |
| 1390 | 1484 | /* |
| 1391 | 1485 | ** Find the first check-in encountered with a particular tag |
| 1392 | 1486 | ** when moving either forwards are backwards in time from a |
| 1393 | 1487 | ** particular starting point (iFrom). Return the rid of that |
| 1394 | -** first check-in. If there are no check-ins in the decendent | |
| 1488 | +** first check-in. If there are no check-ins in the descendent | |
| 1395 | 1489 | ** or ancestor set of check-in iFrom that match the tag, then |
| 1396 | 1490 | ** return 0. |
| 1397 | 1491 | */ |
| 1398 | 1492 | static int timeline_endpoint( |
| 1399 | 1493 | int iFrom, /* Starting point */ |
| 1400 | - const char *zEnd, /* Tag we are searching for */ | |
| 1494 | + const char *zEnd, /* Tag we are searching for */ | |
| 1401 | 1495 | int bForward /* 1: forwards in time (descendants) 0: backwards */ |
| 1402 | 1496 | ){ |
| 1403 | 1497 | int tagId; |
| 1404 | 1498 | int endId = 0; |
| 1405 | 1499 | Stmt q; |
| @@ -1590,11 +1684,11 @@ | ||
| 1590 | 1684 | ** dp2=CKIN2 Same as 'd2=CKIN2&p2=CKIN2' |
| 1591 | 1685 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1592 | 1686 | ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN |
| 1593 | 1687 | ** p=CX ... from CX back to time of CHECKIN |
| 1594 | 1688 | ** from=CX ... path from CX back to CHECKIN |
| 1595 | -** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN | |
| 1689 | +** ft=CHECKIN "Forward To": Show descendents forward to CHECKIN | |
| 1596 | 1690 | ** d=CX ... from CX up to the time of CHECKIN |
| 1597 | 1691 | ** from=CX ... path from CX up to CHECKIN |
| 1598 | 1692 | ** t=TAG Show only check-ins with the given TAG |
| 1599 | 1693 | ** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related" |
| 1600 | 1694 | ** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List" |
| @@ -1607,11 +1701,11 @@ | ||
| 1607 | 1701 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1608 | 1702 | ** "like", or "regexp". |
| 1609 | 1703 | ** u=USER Only show items associated with USER |
| 1610 | 1704 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1611 | 1705 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1612 | -* x: "Classic". | |
| 1706 | +** x: "Classic". | |
| 1613 | 1707 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1614 | 1708 | ** ng No Graph. |
| 1615 | 1709 | ** ncp Omit cherrypick merges |
| 1616 | 1710 | ** nd Do not highlight the focus check-in |
| 1617 | 1711 | ** nsm Omit the submenu |
| @@ -1830,10 +1924,13 @@ | ||
| 1830 | 1924 | || (bisectLocal && !g.perm.Setup) |
| 1831 | 1925 | ){ |
| 1832 | 1926 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1833 | 1927 | return; |
| 1834 | 1928 | } |
| 1929 | + if( zBefore || zCirca ){ | |
| 1930 | + if( robot_restrict("timelineX") ) return; | |
| 1931 | + } | |
| 1835 | 1932 | if( !bisectLocal ){ |
| 1836 | 1933 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1837 | 1934 | } |
| 1838 | 1935 | cookie_read_parameter("y","y"); |
| 1839 | 1936 | zType = P("y"); |
| @@ -1969,10 +2066,11 @@ | ||
| 1969 | 2066 | } |
| 1970 | 2067 | if( showSql ) db_append_dml_to_blob(&allSql); |
| 1971 | 2068 | if( zUses!=0 ){ |
| 1972 | 2069 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 1973 | 2070 | if( ufid ){ |
| 2071 | + if( robot_restrict("timelineX") ) return; | |
| 1974 | 2072 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1975 | 2073 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 1976 | 2074 | compute_uses_file("usesfile", ufid, 0); |
| 1977 | 2075 | zType = "ci"; |
| 1978 | 2076 | disableY = 1; |
| @@ -2042,11 +2140,11 @@ | ||
| 2042 | 2140 | zBisect = 0; |
| 2043 | 2141 | } |
| 2044 | 2142 | |
| 2045 | 2143 | style_header("Timeline"); |
| 2046 | 2144 | if( advancedMenu ){ |
| 2047 | - style_submenu_element("Help", "%R/help?cmd=/timeline"); | |
| 2145 | + style_submenu_element("Help", "%R/help/www/timeline"); | |
| 2048 | 2146 | } |
| 2049 | 2147 | login_anonymous_available(); |
| 2050 | 2148 | timeline_temp_table(); |
| 2051 | 2149 | blob_zero(&sql); |
| 2052 | 2150 | blob_zero(&desc); |
| @@ -2283,11 +2381,11 @@ | ||
| 2283 | 2381 | if( zError==0 ){ |
| 2284 | 2382 | zError = "Cannot use the ft= query parameter when both p= and d= " |
| 2285 | 2383 | "are used and have distinct values."; |
| 2286 | 2384 | } |
| 2287 | 2385 | zFwdTo = 0; |
| 2288 | - } | |
| 2386 | + } | |
| 2289 | 2387 | if( zFwdTo ){ |
| 2290 | 2388 | double rStartDate = mtime_of_rid(d_rid, 0.0); |
| 2291 | 2389 | ridFwdTo = first_checkin_with_tag_after_date(zFwdTo, rStartDate); |
| 2292 | 2390 | if( ridFwdTo==0 ){ |
| 2293 | 2391 | ridFwdTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2342,11 +2440,11 @@ | ||
| 2342 | 2440 | if( zError==0 ){ |
| 2343 | 2441 | zError = "Cannot use the bt= query parameter when both p= and d= " |
| 2344 | 2442 | "are used and have distinct values."; |
| 2345 | 2443 | } |
| 2346 | 2444 | zBackTo = 0; |
| 2347 | - } | |
| 2445 | + } | |
| 2348 | 2446 | if( zBackTo ){ |
| 2349 | 2447 | double rDateLimit = mtime_of_rid(p_rid, 0.0); |
| 2350 | 2448 | ridBackTo = last_checkin_with_tag_before_date(zBackTo, rDateLimit); |
| 2351 | 2449 | if( ridBackTo==0 ){ |
| 2352 | 2450 | ridBackTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2368,11 +2466,11 @@ | ||
| 2368 | 2466 | }else{ |
| 2369 | 2467 | removeFileGlobFromOk(zChng); |
| 2370 | 2468 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2371 | 2469 | if( np>0 || nd==0 ){ |
| 2372 | 2470 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 2373 | - blob_appendf(&desc, "%d ancestor%s", | |
| 2471 | + blob_appendf(&desc, "%d ancestor%s", | |
| 2374 | 2472 | np>=0 ? np : 0, (1==np)?"":"s"); |
| 2375 | 2473 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2376 | 2474 | } |
| 2377 | 2475 | if( useDividers && !selectedRid ) selectedRid = p_rid; |
| 2378 | 2476 | } |
| @@ -2402,11 +2500,11 @@ | ||
| 2402 | 2500 | href("%R/info?name=%h",zBaseName), zBaseName, |
| 2403 | 2501 | href("%R/info?name=%h",zBackTo), zBackTo); |
| 2404 | 2502 | }else{ |
| 2405 | 2503 | blob_appendf(&desc, " back to %z%h</a>%s", |
| 2406 | 2504 | href("%R/info?name=%h",zBackTo), zBackTo, |
| 2407 | - bBackAdded ? " (not a direct anscestor)" : ""); | |
| 2505 | + bBackAdded ? " (not a direct ancestor)" : ""); | |
| 2408 | 2506 | if( ridFwdTo && zFwdTo ){ |
| 2409 | 2507 | blob_appendf(&desc, " and up to %z%h</a>%s", |
| 2410 | 2508 | href("%R/info?name=%h",zFwdTo), zFwdTo, |
| 2411 | 2509 | bFwdAdded ? " (not a direct descendant)" : ""); |
| 2412 | 2510 | } |
| @@ -2651,11 +2749,11 @@ | ||
| 2651 | 2749 | blob_append_sql(&cond, |
| 2652 | 2750 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2653 | 2751 | " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", |
| 2654 | 2752 | zStart, zTZMod, zEnd, zTZMod); |
| 2655 | 2753 | nEntry = -1; |
| 2656 | - | |
| 2754 | + | |
| 2657 | 2755 | if( fossil_ui_localtime() && bZulu ){ |
| 2658 | 2756 | zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd); |
| 2659 | 2757 | }else{ |
| 2660 | 2758 | zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd); |
| 2661 | 2759 | } |
| @@ -3107,10 +3205,11 @@ | ||
| 3107 | 3205 | if( r>0.0 && !selectedRid ) selectedRid = timeline_add_divider(r); |
| 3108 | 3206 | } |
| 3109 | 3207 | blob_zero(&sql); |
| 3110 | 3208 | if( PB("oldestfirst") ){ |
| 3111 | 3209 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby ASC /*scan*/"); |
| 3210 | + tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK); | |
| 3112 | 3211 | }else{ |
| 3113 | 3212 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 3114 | 3213 | } |
| 3115 | 3214 | if( fossil_islower(desc.aData[0]) ){ |
| 3116 | 3215 | desc.aData[0] = fossil_toupper(desc.aData[0]); |
| @@ -3139,10 +3238,20 @@ | ||
| 3139 | 3238 | |
| 3140 | 3239 | /* Report any errors. */ |
| 3141 | 3240 | if( zError ){ |
| 3142 | 3241 | @ <p class="generalError">%h(zError)</p> |
| 3143 | 3242 | } |
| 3243 | + | |
| 3244 | + /* Swap zNewer and zOlder buttons if we display oldestfirst */ | |
| 3245 | + if( PB("oldestfirst") ){ | |
| 3246 | + char *zSwap = zNewerButton; | |
| 3247 | + char *zSwapLabel = zNewerButtonLabel; | |
| 3248 | + zNewerButton = zOlderButton; | |
| 3249 | + zNewerButtonLabel = zOlderButtonLabel; | |
| 3250 | + zOlderButton = zSwap; | |
| 3251 | + zOlderButtonLabel = zSwapLabel; | |
| 3252 | + } | |
| 3144 | 3253 | |
| 3145 | 3254 | if( zNewerButton ){ |
| 3146 | 3255 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3147 | 3256 | @ ↑</a> |
| 3148 | 3257 | } |
| @@ -3620,19 +3729,21 @@ | ||
| 3620 | 3729 | ** N is negative, output the first -N lines. If N is |
| 3621 | 3730 | ** zero, no limit. Default is -20 meaning 20 lines. |
| 3622 | 3731 | ** --offset P Skip P changes |
| 3623 | 3732 | ** -p|--path PATH Output items affecting PATH only. |
| 3624 | 3733 | ** PATH can be a file or a sub directory. |
| 3734 | +** -r|--reverse Show items in chronological order. | |
| 3625 | 3735 | ** -R REPO_FILE Specifies the repository db to use. Default is |
| 3626 | 3736 | ** the current check-out's repository. |
| 3627 | 3737 | ** --sql Show the SQL used to generate the timeline |
| 3628 | 3738 | ** -t|--type TYPE Output items from the given types only, such as: |
| 3629 | 3739 | ** ci = file commits only |
| 3630 | 3740 | ** e = technical notes only |
| 3631 | 3741 | ** f = forum posts only |
| 3632 | 3742 | ** t = tickets only |
| 3633 | 3743 | ** w = wiki commits only |
| 3744 | +** -u|--for-user USER Only show items associated with USER | |
| 3634 | 3745 | ** -v|--verbose Output the list of files changed by each commit |
| 3635 | 3746 | ** and the type of each change (edited, deleted, |
| 3636 | 3747 | ** etc.) after the check-in comment. |
| 3637 | 3748 | ** -W|--width N Width of lines (default is to auto-detect). N must be |
| 3638 | 3749 | ** either greater than 20 or it must be zero 0 to |
| @@ -3644,17 +3755,19 @@ | ||
| 3644 | 3755 | int n, k, width; |
| 3645 | 3756 | const char *zLimit; |
| 3646 | 3757 | const char *zWidth; |
| 3647 | 3758 | const char *zOffset; |
| 3648 | 3759 | const char *zType; |
| 3760 | + const char *zUser; | |
| 3649 | 3761 | char *zOrigin; |
| 3650 | 3762 | char *zDate; |
| 3651 | 3763 | Blob sql; |
| 3652 | 3764 | int objid = 0; |
| 3653 | 3765 | Blob uuid; |
| 3654 | 3766 | int mode = TIMELINE_MODE_NONE; |
| 3655 | - int verboseFlag = 0 ; | |
| 3767 | + int verboseFlag = 0; | |
| 3768 | + int reverseFlag = 0; | |
| 3656 | 3769 | int iOffset; |
| 3657 | 3770 | const char *zFilePattern = 0; |
| 3658 | 3771 | const char *zFormat = 0; |
| 3659 | 3772 | const char *zBr = 0; |
| 3660 | 3773 | Blob treeName; |
| @@ -3666,10 +3779,11 @@ | ||
| 3666 | 3779 | } |
| 3667 | 3780 | db_find_and_open_repository(0, 0); |
| 3668 | 3781 | zLimit = find_option("limit","n",1); |
| 3669 | 3782 | zWidth = find_option("width","W",1); |
| 3670 | 3783 | zType = find_option("type","t",1); |
| 3784 | + zUser = find_option("for-user","u",1); | |
| 3671 | 3785 | zFilePattern = find_option("path","p",1); |
| 3672 | 3786 | zFormat = find_option("format","F",1); |
| 3673 | 3787 | zBr = find_option("branch","b",1); |
| 3674 | 3788 | if( find_option("current-branch","c",0)!=0 ){ |
| 3675 | 3789 | if( !g.localOpen ){ |
| @@ -3707,10 +3821,11 @@ | ||
| 3707 | 3821 | }else{ |
| 3708 | 3822 | width = -1; |
| 3709 | 3823 | } |
| 3710 | 3824 | zOffset = find_option("offset",0,1); |
| 3711 | 3825 | iOffset = zOffset ? atoi(zOffset) : 0; |
| 3826 | + reverseFlag = find_option("reverse","r",0)!=0; | |
| 3712 | 3827 | |
| 3713 | 3828 | /* We should be done with options.. */ |
| 3714 | 3829 | verify_all_options(); |
| 3715 | 3830 | |
| 3716 | 3831 | if( g.argc>=4 ){ |
| @@ -3727,11 +3842,11 @@ | ||
| 3727 | 3842 | mode = TIMELINE_MODE_PARENTS; |
| 3728 | 3843 | }else if( strncmp(g.argv[2],"parents",k)==0 ){ |
| 3729 | 3844 | mode = TIMELINE_MODE_PARENTS; |
| 3730 | 3845 | }else if(!zType && !zLimit){ |
| 3731 | 3846 | usage("?WHEN? ?CHECKIN|DATETIME? ?-n|--limit #? ?-t|--type TYPE? " |
| 3732 | - "?-W|--width WIDTH? ?-p|--path PATH?"); | |
| 3847 | + "?-W|--width WIDTH? ?-p|--path PATH? ?-r|--reverse?"); | |
| 3733 | 3848 | } |
| 3734 | 3849 | if( '-' != *g.argv[3] ){ |
| 3735 | 3850 | zOrigin = g.argv[3]; |
| 3736 | 3851 | }else{ |
| 3737 | 3852 | zOrigin = "now"; |
| @@ -3797,10 +3912,13 @@ | ||
| 3797 | 3912 | mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/ |
| 3798 | 3913 | ); |
| 3799 | 3914 | if( zType && (zType[0]!='a') ){ |
| 3800 | 3915 | blob_append_sql(&sql, "\n AND event.type=%Q ", zType); |
| 3801 | 3916 | } |
| 3917 | + if( zUser && (zUser[0]!='\0') ){ | |
| 3918 | + blob_append_sql(&sql, "\n AND user0=%Q ", zUser); | |
| 3919 | + } | |
| 3802 | 3920 | |
| 3803 | 3921 | /* When zFilePattern is specified, compute complete ancestry; |
| 3804 | 3922 | * limit later at print_timeline() */ |
| 3805 | 3923 | if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){ |
| 3806 | 3924 | db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| @@ -3850,23 +3968,25 @@ | ||
| 3850 | 3968 | " WHERE tx.value='%q'\n" |
| 3851 | 3969 | ")\n" /* No merge closures */ |
| 3852 | 3970 | " AND (tagxref.value IS NULL OR tagxref.value='%q')", |
| 3853 | 3971 | zBr, zBr, zBr, TAG_BRANCH, zBr, zBr); |
| 3854 | 3972 | } |
| 3855 | - | |
| 3973 | + | |
| 3856 | 3974 | if( mode==TIMELINE_MODE_AFTER ){ |
| 3857 | 3975 | int lim = n; |
| 3858 | 3976 | if( n == 0 ){ |
| 3859 | 3977 | lim = -1; /* 0 means no limit */ |
| 3860 | 3978 | }else if( n < 0 ){ |
| 3861 | 3979 | lim = -n; |
| 3862 | 3980 | } |
| 3863 | 3981 | /* Complete the above outer select. */ |
| 3864 | - blob_append_sql(&sql, | |
| 3865 | - "\nORDER BY event.mtime LIMIT %d) t ORDER BY t.mDateTime DESC;", lim); | |
| 3982 | + blob_append_sql(&sql, | |
| 3983 | + "\nORDER BY event.mtime LIMIT %d) t ORDER BY t.mDateTime %s", | |
| 3984 | + lim, reverseFlag ? "" : "DESC"); | |
| 3866 | 3985 | }else{ |
| 3867 | - blob_append_sql(&sql, "\nORDER BY event.mtime DESC"); | |
| 3986 | + blob_append_sql(&sql, | |
| 3987 | + "\nORDER BY event.mtime %s", reverseFlag ? "" : "DESC"); | |
| 3868 | 3988 | } |
| 3869 | 3989 | if( iOffset>0 ){ |
| 3870 | 3990 | /* Don't handle LIMIT here, otherwise print_timeline() |
| 3871 | 3991 | * will not determine the end-marker correctly! */ |
| 3872 | 3992 | blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset); |
| 3873 | 3993 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -115,11 +115,13 @@ | |
| 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | #define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */ |
| 121 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 122 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 123 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 124 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 125 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -170,10 +172,182 @@ | |
| 170 | manifest_destroy(pPost); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | |
| 175 | /* |
| 176 | ** Output a timeline in the web format given a query. The query |
| 177 | ** should return these columns: |
| 178 | ** |
| 179 | ** 0. rid |
| @@ -194,11 +368,11 @@ | |
| 194 | const char *zThisUser, /* Suppress links to this user */ |
| 195 | const char *zThisTag, /* Suppress links to this tag */ |
| 196 | Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */ |
| 197 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 198 | int secondRid, /* Secondary highlight (or zero) */ |
| 199 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 200 | ){ |
| 201 | int mxWikiLen; |
| 202 | Blob comment; |
| 203 | int prevTagid = 0; |
| 204 | int suppressCnt = 0; |
| @@ -214,55 +388,41 @@ | |
| 214 | const char *zStyle; /* Sub-name for classes for the style */ |
| 215 | const char *zDateFmt; |
| 216 | int iTableId = timeline_tableid(); |
| 217 | int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info |
| 218 | ** page rather than the /timeline page */ |
| 219 | |
| 220 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 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 ){ |
| 249 | zStyle = "Columnar"; |
| 250 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 251 | zStyle = "Compact"; |
| 252 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 253 | zStyle = "Verbose"; |
| 254 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 255 | zStyle = "Classic"; |
| 256 | }else{ |
| 257 | zStyle = "Modern"; |
| 258 | } |
| 259 | zDateFmt = P("datefmt"); |
| 260 | if( zDateFmt ) dateFormat = atoi(zDateFmt); |
| 261 | if( tmFlags & TIMELINE_GRAPH ){ |
| 262 | pGraph = graph_init(); |
| 263 | } |
| 264 | if( (tmFlags & TIMELINE_CHPICK)!=0 |
| 265 | && !db_table_exists("repository","cherrypick") |
| 266 | ){ |
| 267 | tmFlags &= ~TIMELINE_CHPICK; |
| 268 | } |
| @@ -275,13 +435,11 @@ | |
| 275 | int isLeaf = db_column_int(pQuery, 5); |
| 276 | const char *zBgClr = db_column_text(pQuery, 6); |
| 277 | const char *zDate = db_column_text(pQuery, 2); |
| 278 | const char *zType = db_column_text(pQuery, 7); |
| 279 | const char *zUser = db_column_text(pQuery, 4); |
| 280 | const char *zTagList = db_column_text(pQuery, 8); |
| 281 | int tagid = db_column_int(pQuery, 9); |
| 282 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 283 | char *zBr = 0; /* Branch */ |
| 284 | int commentColumn = 3; /* Column containing comment text */ |
| 285 | int modPending; /* Pending moderation */ |
| 286 | char *zDateLink; /* URL for the link on the timestamp */ |
| 287 | int drawDetailEllipsis; /* True to show ellipsis in place of detail */ |
| @@ -434,11 +592,11 @@ | |
| 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); |
| 443 | } |
| 444 | } |
| @@ -446,32 +604,34 @@ | |
| 446 | if( zType[0]=='c' && pGraph ){ |
| 447 | int nParent = 0; |
| 448 | int nCherrypick = 0; |
| 449 | GraphRowId aParent[GR_MAX_RAIL]; |
| 450 | static Stmt qparent; |
| 451 | db_static_prepare(&qparent, |
| 452 | "SELECT pid FROM plink" |
| 453 | " WHERE cid=:rid AND pid NOT IN phantom" |
| 454 | " ORDER BY isprim DESC /*sort*/" |
| 455 | ); |
| 456 | db_bind_int(&qparent, ":rid", rid); |
| 457 | while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){ |
| 458 | aParent[nParent++] = db_column_int(&qparent, 0); |
| 459 | } |
| 460 | db_reset(&qparent); |
| 461 | if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){ |
| 462 | static Stmt qcherrypick; |
| 463 | db_static_prepare(&qcherrypick, |
| 464 | "SELECT parentid FROM cherrypick" |
| 465 | " WHERE childid=:rid AND parentid NOT IN phantom" |
| 466 | ); |
| 467 | db_bind_int(&qcherrypick, ":rid", rid); |
| 468 | while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){ |
| 469 | aParent[nParent++] = db_column_int(&qcherrypick, 0); |
| 470 | nCherrypick++; |
| 471 | } |
| 472 | db_reset(&qcherrypick); |
| 473 | } |
| 474 | gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent, |
| 475 | zBr, zBgClr, zUuid, |
| 476 | isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0); |
| 477 | @ <div id="m%d(gidx)" class="tl-nodemark"></div> |
| @@ -598,23 +758,15 @@ | |
| 598 | drawDetailEllipsis = 0; |
| 599 | }else{ |
| 600 | cgi_printf("%W",blob_str(&comment)); |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | if( zType[0]=='c' && strcmp(zUuid, MANIFEST_UUID)==0 ){ |
| 605 | /* This will only ever happen when Fossil is drawing a timeline for |
| 606 | ** its own self-host repository. If the timeline shows the specific |
| 607 | ** check-in corresponding to the current executable, then tag that |
| 608 | ** check-in with "This is me!". */ |
| 609 | @ <b>← This is me!</b> |
| 610 | } |
| 611 | |
| 612 | @ </span> |
| 613 | blob_reset(&comment); |
| 614 | |
| 615 | /* Generate extra information and hyperlinks to follow the comment. |
| 616 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 617 | */ |
| 618 | if( drawDetailEllipsis ){ |
| 619 | @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ |
| 620 | @ data-id='%d(rid)'>...</span> |
| @@ -628,101 +780,17 @@ | |
| 628 | } |
| 629 | if( tmFlags & TIMELINE_COMPACT ){ |
| 630 | cgi_printf("<span class='clutter' id='detail-%d'>",rid); |
| 631 | } |
| 632 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 633 | if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ |
| 634 | cgi_printf("("); |
| 635 | } |
| 636 | |
| 637 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 638 | if( zType[0]=='c' ){ |
| 639 | if( isLeaf ){ |
| 640 | if( has_closed_tag(rid) ){ |
| 641 | @ <span class='timelineLeaf'>Closed-Leaf</span> |
| 642 | }else{ |
| 643 | @ <span class='timelineLeaf'>Leaf</span> |
| 644 | } |
| 645 | } |
| 646 | cgi_printf("check-in: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 647 | }else if( zType[0]=='e' && tagid ){ |
| 648 | cgi_printf("technote: "); |
| 649 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 650 | }else{ |
| 651 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 652 | } |
| 653 | }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' |
| 654 | || zType[0]=='n' || zType[0]=='f'){ |
| 655 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 656 | } |
| 657 | |
| 658 | if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ |
| 659 | char *zLink; |
| 660 | if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ |
| 661 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); |
| 662 | }else{ |
| 663 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate); |
| 664 | } |
| 665 | cgi_printf("user: %z%h</a>", href("%z",zLink), zDispUser); |
| 666 | }else{ |
| 667 | cgi_printf("user: %h", zDispUser); |
| 668 | } |
| 669 | |
| 670 | /* Generate the "tags: TAGLIST" at the end of the comment, together |
| 671 | ** with hyperlinks to the tag list. |
| 672 | */ |
| 673 | if( zTagList && zTagList[0]==0 ) zTagList = 0; |
| 674 | if( zTagList ){ |
| 675 | if( g.perm.Hyperlink ){ |
| 676 | int i; |
| 677 | const char *z = zTagList; |
| 678 | Blob links; |
| 679 | blob_zero(&links); |
| 680 | while( z && z[0] ){ |
| 681 | for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} |
| 682 | if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ |
| 683 | blob_appendf(&links, |
| 684 | "%z%#h</a>%.2s", |
| 685 | href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i] |
| 686 | ); |
| 687 | }else{ |
| 688 | blob_appendf(&links, "%#h", i+2, z); |
| 689 | } |
| 690 | if( z[i]==0 ) break; |
| 691 | z += i+2; |
| 692 | } |
| 693 | cgi_printf(" tags: %s", blob_str(&links)); |
| 694 | blob_reset(&links); |
| 695 | }else{ |
| 696 | cgi_printf(" tags: %h", zTagList); |
| 697 | } |
| 698 | } |
| 699 | |
| 700 | if( tmFlags & TIMELINE_SHOWRID ){ |
| 701 | int srcId = delta_source_rid(rid); |
| 702 | if( srcId ){ |
| 703 | cgi_printf(" id: %z%d←%d</a>", |
| 704 | href("%R/deltachain/%d",rid), rid, srcId); |
| 705 | }else{ |
| 706 | cgi_printf(" id: %z%d</a>", |
| 707 | href("%R/deltachain/%d",rid), rid); |
| 708 | } |
| 709 | } |
| 710 | tag_private_status(rid); |
| 711 | if( xExtra ){ |
| 712 | xExtra(rid); |
| 713 | } |
| 714 | /* End timelineDetail */ |
| 715 | if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ |
| 716 | cgi_printf(")"); |
| 717 | } |
| 718 | if( tmFlags & TIMELINE_COMPACT ){ |
| 719 | @ </span></span> |
| 720 | }else{ |
| 721 | @ </span> |
| 722 | } |
| 723 | |
| 724 | /* Generate the file-change list if requested */ |
| 725 | if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0 |
| 726 | && zType[0]=='c' && g.perm.Hyperlink |
| 727 | ){ |
| 728 | int inUl = 0; |
| @@ -908,11 +976,13 @@ | |
| 908 | int omitDescenders; /* True to omit descenders */ |
| 909 | int scrollToSelect; /* True to scroll to the selection */ |
| 910 | int dwellTimeout; /* Milliseconds to wait for tooltips to show */ |
| 911 | int closeTimeout; /* Milliseconds to wait for tooltips to close */ |
| 912 | u8 *aiMap; /* The rail map */ |
| 913 | |
| 914 | iRailPitch = atoi(PD("railpitch","0")); |
| 915 | showArrowheads = skin_detail_boolean("timeline-arrowheads"); |
| 916 | circleNodes = skin_detail_boolean("timeline-circle-nodes"); |
| 917 | colorGraph = skin_detail_boolean("timeline-color-graph-lines"); |
| 918 | iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0; |
| @@ -930,11 +1000,11 @@ | |
| 930 | @ "nomo": %d(PB("nomo")), |
| 931 | @ "iTopRow": %d(iTopRow), |
| 932 | @ "omitDescenders": %d(omitDescenders), |
| 933 | @ "fileDiff": %d(fileDiff), |
| 934 | @ "scrollToSelect": %d(scrollToSelect), |
| 935 | @ "nrail": %d(pGraph->mxRail+1), |
| 936 | @ "baseUrl": "%R", |
| 937 | @ "dwellTimeout": %d(dwellTimeout), |
| 938 | @ "closeTimeout": %d(closeTimeout), |
| 939 | @ "hashDigits": %d(hash_digits(1)), |
| 940 | @ "bottomRowId": "btm-%d(iTableId)", |
| @@ -992,12 +1062,16 @@ | |
| 992 | aiMap = pGraph->aiRailMap; |
| 993 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 994 | int k = 0; |
| 995 | cgi_printf("{\"id\":%d,", pRow->idx); |
| 996 | cgi_printf("\"bg\":\"%s\",", pRow->zBgClr); |
| 997 | cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1); |
| 998 | if( pRow->bDescender ){ |
| 999 | cgi_printf("\"d\":%d,", pRow->bDescender); |
| 1000 | } |
| 1001 | if( pRow->mergeOut>=0 ){ |
| 1002 | cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]); |
| 1003 | if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx; |
| @@ -1004,11 +1078,13 @@ | |
| 1004 | cgi_printf("\"mu\":%d,", pRow->mergeUpto); |
| 1005 | if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){ |
| 1006 | cgi_printf("\"cu\":%d,", pRow->cherrypickUpto); |
| 1007 | } |
| 1008 | } |
| 1009 | if( pRow->isStepParent ){ |
| 1010 | cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1011 | }else{ |
| 1012 | cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1013 | } |
| 1014 | k = 0; |
| @@ -1206,10 +1282,26 @@ | |
| 1206 | if( i>2 ){ |
| 1207 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1208 | } |
| 1209 | } |
| 1210 | |
| 1211 | /* |
| 1212 | ** Return the default value for the "ss" cookie or query parameter. |
| 1213 | ** The "ss" cookie determines the graph style. See the |
| 1214 | ** timeline_view_styles[] global constant for a list of choices. |
| 1215 | */ |
| @@ -1230,10 +1322,11 @@ | |
| 1230 | switch( v[0] ){ |
| 1231 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1232 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1233 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1234 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1235 | default: tmFlags = TIMELINE_MODERN; break; |
| 1236 | } |
| 1237 | return tmFlags; |
| 1238 | } |
| 1239 | |
| @@ -1242,15 +1335,16 @@ | |
| 1242 | */ |
| 1243 | const char *const timeline_view_styles[] = { |
| 1244 | "m", "Modern View", |
| 1245 | "j", "Columnar View", |
| 1246 | "c", "Compact View", |
| 1247 | "v", "Verbose View", |
| 1248 | "x", "Classic View", |
| 1249 | }; |
| 1250 | #if INTERFACE |
| 1251 | # define N_TIMELINE_VIEW_STYLE 5 |
| 1252 | #endif |
| 1253 | |
| 1254 | /* |
| 1255 | ** Add the select/option box to the timeline submenu that is used to |
| 1256 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1363,11 +1457,11 @@ | |
| 1363 | ** the input is a valid date space and false if not. |
| 1364 | */ |
| 1365 | static int timeline_is_datespan(const char *zDay){ |
| 1366 | size_t n = strlen(zDay); |
| 1367 | int i, d, m; |
| 1368 | |
| 1369 | if( n<17 || n>18 ) return 0; |
| 1370 | if( n==18 ){ |
| 1371 | if( zDay[17]!='Z' && zDay[17]!='z' ) return 0; |
| 1372 | n--; |
| 1373 | } |
| @@ -1389,17 +1483,17 @@ | |
| 1389 | |
| 1390 | /* |
| 1391 | ** Find the first check-in encountered with a particular tag |
| 1392 | ** when moving either forwards are backwards in time from a |
| 1393 | ** particular starting point (iFrom). Return the rid of that |
| 1394 | ** first check-in. If there are no check-ins in the decendent |
| 1395 | ** or ancestor set of check-in iFrom that match the tag, then |
| 1396 | ** return 0. |
| 1397 | */ |
| 1398 | static int timeline_endpoint( |
| 1399 | int iFrom, /* Starting point */ |
| 1400 | const char *zEnd, /* Tag we are searching for */ |
| 1401 | int bForward /* 1: forwards in time (descendants) 0: backwards */ |
| 1402 | ){ |
| 1403 | int tagId; |
| 1404 | int endId = 0; |
| 1405 | Stmt q; |
| @@ -1590,11 +1684,11 @@ | |
| 1590 | ** dp2=CKIN2 Same as 'd2=CKIN2&p2=CKIN2' |
| 1591 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1592 | ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN |
| 1593 | ** p=CX ... from CX back to time of CHECKIN |
| 1594 | ** from=CX ... path from CX back to CHECKIN |
| 1595 | ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN |
| 1596 | ** d=CX ... from CX up to the time of CHECKIN |
| 1597 | ** from=CX ... path from CX up to CHECKIN |
| 1598 | ** t=TAG Show only check-ins with the given TAG |
| 1599 | ** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related" |
| 1600 | ** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List" |
| @@ -1607,11 +1701,11 @@ | |
| 1607 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1608 | ** "like", or "regexp". |
| 1609 | ** u=USER Only show items associated with USER |
| 1610 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1611 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1612 | * x: "Classic". |
| 1613 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1614 | ** ng No Graph. |
| 1615 | ** ncp Omit cherrypick merges |
| 1616 | ** nd Do not highlight the focus check-in |
| 1617 | ** nsm Omit the submenu |
| @@ -1830,10 +1924,13 @@ | |
| 1830 | || (bisectLocal && !g.perm.Setup) |
| 1831 | ){ |
| 1832 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1833 | return; |
| 1834 | } |
| 1835 | if( !bisectLocal ){ |
| 1836 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1837 | } |
| 1838 | cookie_read_parameter("y","y"); |
| 1839 | zType = P("y"); |
| @@ -1969,10 +2066,11 @@ | |
| 1969 | } |
| 1970 | if( showSql ) db_append_dml_to_blob(&allSql); |
| 1971 | if( zUses!=0 ){ |
| 1972 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 1973 | if( ufid ){ |
| 1974 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1975 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 1976 | compute_uses_file("usesfile", ufid, 0); |
| 1977 | zType = "ci"; |
| 1978 | disableY = 1; |
| @@ -2042,11 +2140,11 @@ | |
| 2042 | zBisect = 0; |
| 2043 | } |
| 2044 | |
| 2045 | style_header("Timeline"); |
| 2046 | if( advancedMenu ){ |
| 2047 | style_submenu_element("Help", "%R/help?cmd=/timeline"); |
| 2048 | } |
| 2049 | login_anonymous_available(); |
| 2050 | timeline_temp_table(); |
| 2051 | blob_zero(&sql); |
| 2052 | blob_zero(&desc); |
| @@ -2283,11 +2381,11 @@ | |
| 2283 | if( zError==0 ){ |
| 2284 | zError = "Cannot use the ft= query parameter when both p= and d= " |
| 2285 | "are used and have distinct values."; |
| 2286 | } |
| 2287 | zFwdTo = 0; |
| 2288 | } |
| 2289 | if( zFwdTo ){ |
| 2290 | double rStartDate = mtime_of_rid(d_rid, 0.0); |
| 2291 | ridFwdTo = first_checkin_with_tag_after_date(zFwdTo, rStartDate); |
| 2292 | if( ridFwdTo==0 ){ |
| 2293 | ridFwdTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2342,11 +2440,11 @@ | |
| 2342 | if( zError==0 ){ |
| 2343 | zError = "Cannot use the bt= query parameter when both p= and d= " |
| 2344 | "are used and have distinct values."; |
| 2345 | } |
| 2346 | zBackTo = 0; |
| 2347 | } |
| 2348 | if( zBackTo ){ |
| 2349 | double rDateLimit = mtime_of_rid(p_rid, 0.0); |
| 2350 | ridBackTo = last_checkin_with_tag_before_date(zBackTo, rDateLimit); |
| 2351 | if( ridBackTo==0 ){ |
| 2352 | ridBackTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2368,11 +2466,11 @@ | |
| 2368 | }else{ |
| 2369 | removeFileGlobFromOk(zChng); |
| 2370 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2371 | if( np>0 || nd==0 ){ |
| 2372 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 2373 | blob_appendf(&desc, "%d ancestor%s", |
| 2374 | np>=0 ? np : 0, (1==np)?"":"s"); |
| 2375 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2376 | } |
| 2377 | if( useDividers && !selectedRid ) selectedRid = p_rid; |
| 2378 | } |
| @@ -2402,11 +2500,11 @@ | |
| 2402 | href("%R/info?name=%h",zBaseName), zBaseName, |
| 2403 | href("%R/info?name=%h",zBackTo), zBackTo); |
| 2404 | }else{ |
| 2405 | blob_appendf(&desc, " back to %z%h</a>%s", |
| 2406 | href("%R/info?name=%h",zBackTo), zBackTo, |
| 2407 | bBackAdded ? " (not a direct anscestor)" : ""); |
| 2408 | if( ridFwdTo && zFwdTo ){ |
| 2409 | blob_appendf(&desc, " and up to %z%h</a>%s", |
| 2410 | href("%R/info?name=%h",zFwdTo), zFwdTo, |
| 2411 | bFwdAdded ? " (not a direct descendant)" : ""); |
| 2412 | } |
| @@ -2651,11 +2749,11 @@ | |
| 2651 | blob_append_sql(&cond, |
| 2652 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2653 | " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", |
| 2654 | zStart, zTZMod, zEnd, zTZMod); |
| 2655 | nEntry = -1; |
| 2656 | |
| 2657 | if( fossil_ui_localtime() && bZulu ){ |
| 2658 | zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd); |
| 2659 | }else{ |
| 2660 | zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd); |
| 2661 | } |
| @@ -3107,10 +3205,11 @@ | |
| 3107 | if( r>0.0 && !selectedRid ) selectedRid = timeline_add_divider(r); |
| 3108 | } |
| 3109 | blob_zero(&sql); |
| 3110 | if( PB("oldestfirst") ){ |
| 3111 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby ASC /*scan*/"); |
| 3112 | }else{ |
| 3113 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 3114 | } |
| 3115 | if( fossil_islower(desc.aData[0]) ){ |
| 3116 | desc.aData[0] = fossil_toupper(desc.aData[0]); |
| @@ -3139,10 +3238,20 @@ | |
| 3139 | |
| 3140 | /* Report any errors. */ |
| 3141 | if( zError ){ |
| 3142 | @ <p class="generalError">%h(zError)</p> |
| 3143 | } |
| 3144 | |
| 3145 | if( zNewerButton ){ |
| 3146 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3147 | @ ↑</a> |
| 3148 | } |
| @@ -3620,19 +3729,21 @@ | |
| 3620 | ** N is negative, output the first -N lines. If N is |
| 3621 | ** zero, no limit. Default is -20 meaning 20 lines. |
| 3622 | ** --offset P Skip P changes |
| 3623 | ** -p|--path PATH Output items affecting PATH only. |
| 3624 | ** PATH can be a file or a sub directory. |
| 3625 | ** -R REPO_FILE Specifies the repository db to use. Default is |
| 3626 | ** the current check-out's repository. |
| 3627 | ** --sql Show the SQL used to generate the timeline |
| 3628 | ** -t|--type TYPE Output items from the given types only, such as: |
| 3629 | ** ci = file commits only |
| 3630 | ** e = technical notes only |
| 3631 | ** f = forum posts only |
| 3632 | ** t = tickets only |
| 3633 | ** w = wiki commits only |
| 3634 | ** -v|--verbose Output the list of files changed by each commit |
| 3635 | ** and the type of each change (edited, deleted, |
| 3636 | ** etc.) after the check-in comment. |
| 3637 | ** -W|--width N Width of lines (default is to auto-detect). N must be |
| 3638 | ** either greater than 20 or it must be zero 0 to |
| @@ -3644,17 +3755,19 @@ | |
| 3644 | int n, k, width; |
| 3645 | const char *zLimit; |
| 3646 | const char *zWidth; |
| 3647 | const char *zOffset; |
| 3648 | const char *zType; |
| 3649 | char *zOrigin; |
| 3650 | char *zDate; |
| 3651 | Blob sql; |
| 3652 | int objid = 0; |
| 3653 | Blob uuid; |
| 3654 | int mode = TIMELINE_MODE_NONE; |
| 3655 | int verboseFlag = 0 ; |
| 3656 | int iOffset; |
| 3657 | const char *zFilePattern = 0; |
| 3658 | const char *zFormat = 0; |
| 3659 | const char *zBr = 0; |
| 3660 | Blob treeName; |
| @@ -3666,10 +3779,11 @@ | |
| 3666 | } |
| 3667 | db_find_and_open_repository(0, 0); |
| 3668 | zLimit = find_option("limit","n",1); |
| 3669 | zWidth = find_option("width","W",1); |
| 3670 | zType = find_option("type","t",1); |
| 3671 | zFilePattern = find_option("path","p",1); |
| 3672 | zFormat = find_option("format","F",1); |
| 3673 | zBr = find_option("branch","b",1); |
| 3674 | if( find_option("current-branch","c",0)!=0 ){ |
| 3675 | if( !g.localOpen ){ |
| @@ -3707,10 +3821,11 @@ | |
| 3707 | }else{ |
| 3708 | width = -1; |
| 3709 | } |
| 3710 | zOffset = find_option("offset",0,1); |
| 3711 | iOffset = zOffset ? atoi(zOffset) : 0; |
| 3712 | |
| 3713 | /* We should be done with options.. */ |
| 3714 | verify_all_options(); |
| 3715 | |
| 3716 | if( g.argc>=4 ){ |
| @@ -3727,11 +3842,11 @@ | |
| 3727 | mode = TIMELINE_MODE_PARENTS; |
| 3728 | }else if( strncmp(g.argv[2],"parents",k)==0 ){ |
| 3729 | mode = TIMELINE_MODE_PARENTS; |
| 3730 | }else if(!zType && !zLimit){ |
| 3731 | usage("?WHEN? ?CHECKIN|DATETIME? ?-n|--limit #? ?-t|--type TYPE? " |
| 3732 | "?-W|--width WIDTH? ?-p|--path PATH?"); |
| 3733 | } |
| 3734 | if( '-' != *g.argv[3] ){ |
| 3735 | zOrigin = g.argv[3]; |
| 3736 | }else{ |
| 3737 | zOrigin = "now"; |
| @@ -3797,10 +3912,13 @@ | |
| 3797 | mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/ |
| 3798 | ); |
| 3799 | if( zType && (zType[0]!='a') ){ |
| 3800 | blob_append_sql(&sql, "\n AND event.type=%Q ", zType); |
| 3801 | } |
| 3802 | |
| 3803 | /* When zFilePattern is specified, compute complete ancestry; |
| 3804 | * limit later at print_timeline() */ |
| 3805 | if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){ |
| 3806 | db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| @@ -3850,23 +3968,25 @@ | |
| 3850 | " WHERE tx.value='%q'\n" |
| 3851 | ")\n" /* No merge closures */ |
| 3852 | " AND (tagxref.value IS NULL OR tagxref.value='%q')", |
| 3853 | zBr, zBr, zBr, TAG_BRANCH, zBr, zBr); |
| 3854 | } |
| 3855 | |
| 3856 | if( mode==TIMELINE_MODE_AFTER ){ |
| 3857 | int lim = n; |
| 3858 | if( n == 0 ){ |
| 3859 | lim = -1; /* 0 means no limit */ |
| 3860 | }else if( n < 0 ){ |
| 3861 | lim = -n; |
| 3862 | } |
| 3863 | /* Complete the above outer select. */ |
| 3864 | blob_append_sql(&sql, |
| 3865 | "\nORDER BY event.mtime LIMIT %d) t ORDER BY t.mDateTime DESC;", lim); |
| 3866 | }else{ |
| 3867 | blob_append_sql(&sql, "\nORDER BY event.mtime DESC"); |
| 3868 | } |
| 3869 | if( iOffset>0 ){ |
| 3870 | /* Don't handle LIMIT here, otherwise print_timeline() |
| 3871 | * will not determine the end-marker correctly! */ |
| 3872 | blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset); |
| 3873 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -115,11 +115,13 @@ | |
| 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | #define TIMELINE_SIMPLE 0x0020000 /* Use the "simple" view style */ |
| 121 | #define TIMELINE_INLINE 0x0033000 /* Mask for views with in-line display */ |
| 122 | #define TIMELINE_VIEWS 0x003f000 /* Mask for all of the view styles */ |
| 123 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 124 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 125 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 126 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 127 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -170,10 +172,182 @@ | |
| 172 | manifest_destroy(pPost); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | |
| 177 | /* |
| 178 | ** This routine generates the default "extra" text after the description |
| 179 | ** in a timeline. |
| 180 | ** |
| 181 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 182 | ** |
| 183 | ** This routine is used if no xExtra argument is supplied to |
| 184 | ** www_print_timeline(). |
| 185 | */ |
| 186 | void timeline_extra( |
| 187 | Stmt *pQuery, /* Current row of the timeline query */ |
| 188 | int tmFlags, /* Flags to www_print_timeline() */ |
| 189 | const char *zThisUser, /* Suppress links to this user */ |
| 190 | const char *zThisTag /* Suppress links to this tag */ |
| 191 | ){ |
| 192 | int rid = db_column_int(pQuery, 0); |
| 193 | const char *zUuid = db_column_text(pQuery, 1); |
| 194 | const char *zDate = db_column_text(pQuery, 2); |
| 195 | const char *zType = db_column_text(pQuery, 7); |
| 196 | const char *zUser = db_column_text(pQuery, 4); |
| 197 | const char *zTagList = db_column_text(pQuery, 8); |
| 198 | int tagid = db_column_int(pQuery, 9); |
| 199 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 200 | |
| 201 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 202 | cgi_printf("("); |
| 203 | } |
| 204 | |
| 205 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 206 | if( zType[0]=='c' ){ |
| 207 | const char *zPrefix = 0; |
| 208 | static int markLeaves = -1; |
| 209 | if( markLeaves<0 ){ |
| 210 | markLeaves = db_get_int("timeline-mark-leaves",1); |
| 211 | if( markLeaves<0 ) markLeaves = 1; |
| 212 | } |
| 213 | if( strcmp(zUuid, MANIFEST_UUID)==0 ){ |
| 214 | /* This will only ever happen when Fossil is drawing a timeline for |
| 215 | ** its own self-host repository. If the timeline shows the specific |
| 216 | ** check-in corresponding to the current executable, then tag that |
| 217 | ** check-in with "self" */ |
| 218 | zPrefix = "self "; |
| 219 | }else if( markLeaves && db_column_int(pQuery,5) ){ |
| 220 | if( markLeaves==1 ){ |
| 221 | zPrefix = has_closed_tag(rid) ? "closed " : "leaf "; |
| 222 | }else{ |
| 223 | zPrefix = has_closed_tag(rid) ? |
| 224 | "<span class='timelineLeaf'>Closed-Leaf</span>\n" : |
| 225 | "<span class='timelineLeaf'>Leaf</span>\n"; |
| 226 | } |
| 227 | } |
| 228 | cgi_printf("%scheck-in: %z<span class='timelineHash'>" |
| 229 | "%S</span></a> ", |
| 230 | zPrefix, href("%R/info/%!S",zUuid),zUuid); |
| 231 | }else if( zType[0]=='e' && tagid ){ |
| 232 | cgi_printf("technote: "); |
| 233 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 234 | }else{ |
| 235 | cgi_printf("artifact: %z%S</a> ", |
| 236 | href("%R/info/%!S",zUuid),zUuid); |
| 237 | } |
| 238 | }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' |
| 239 | || zType[0]=='n' || zType[0]=='f'){ |
| 240 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 241 | } |
| 242 | |
| 243 | if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ |
| 244 | @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ |
| 245 | @ data-id='%d(rid)'>...</span> |
| 246 | @ <span class='clutter' id='detail-%d(rid)'> |
| 247 | } |
| 248 | |
| 249 | if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ |
| 250 | char *zLink; |
| 251 | if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ |
| 252 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); |
| 253 | }else{ |
| 254 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate); |
| 255 | } |
| 256 | cgi_printf("user: %z%h</a>", href("%z",zLink), zDispUser); |
| 257 | }else{ |
| 258 | cgi_printf("user: %h", zDispUser); |
| 259 | } |
| 260 | |
| 261 | /* Generate the "tags: TAGLIST" at the end of the comment, together |
| 262 | ** with hyperlinks to the tag list. |
| 263 | */ |
| 264 | if( zTagList && zTagList[0]==0 ) zTagList = 0; |
| 265 | if( zTagList ){ |
| 266 | if( g.perm.Hyperlink ){ |
| 267 | int i; |
| 268 | const char *z = zTagList; |
| 269 | Blob links; |
| 270 | blob_zero(&links); |
| 271 | while( z && z[0] ){ |
| 272 | for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} |
| 273 | if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ |
| 274 | blob_appendf(&links, |
| 275 | "%z%#h</a>%.2s", |
| 276 | href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i] |
| 277 | ); |
| 278 | }else{ |
| 279 | blob_appendf(&links, "%#h", i+2, z); |
| 280 | } |
| 281 | if( z[i]==0 ) break; |
| 282 | z += i+2; |
| 283 | } |
| 284 | cgi_printf(" tags: %s", blob_str(&links)); |
| 285 | blob_reset(&links); |
| 286 | }else{ |
| 287 | cgi_printf(" tags: %h", zTagList); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | if( tmFlags & TIMELINE_SHOWRID ){ |
| 292 | int srcId = delta_source_rid(rid); |
| 293 | if( srcId ){ |
| 294 | cgi_printf(" id: %z%d←%d</a>", |
| 295 | href("%R/deltachain/%d",rid), rid, srcId); |
| 296 | }else{ |
| 297 | cgi_printf(" id: %z%d</a>", |
| 298 | href("%R/deltachain/%d",rid), rid); |
| 299 | } |
| 300 | } |
| 301 | tag_private_status(rid); |
| 302 | |
| 303 | if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ |
| 304 | cgi_printf("</span>"); /* End of the declutter span */ |
| 305 | } |
| 306 | |
| 307 | /* End timelineDetail */ |
| 308 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 309 | cgi_printf(")"); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | |
| 314 | /* |
| 315 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| 316 | ** |
| 317 | ** If enabled, check-in comments displayed on the timeline are truncated |
| 318 | ** at the first blank line of the comment text. The comment text after |
| 319 | ** the first blank line is only seen in the /info or similar pages that |
| 320 | ** show details about the check-in. |
| 321 | */ |
| 322 | /* |
| 323 | ** SETTING: timeline-tslink-info boolean default=off |
| 324 | ** |
| 325 | ** The hyperlink on the timestamp associated with each timeline entry, |
| 326 | ** on the far left-hand side of the screen, normally targets another |
| 327 | ** /timeline page that shows the entry in context. However, if this |
| 328 | ** option is turned on, that hyperlink targets the /info page showing |
| 329 | ** the details of the entry. |
| 330 | */ |
| 331 | /* |
| 332 | ** SETTING: timeline-mark-leaves width=5 default=1 |
| 333 | ** |
| 334 | ** Determine whether or not leaf check-ins are marked as such in the |
| 335 | ** details section of the timeline. The value is an integer between 0 |
| 336 | ** and 2: |
| 337 | ** |
| 338 | ** 0 Do not show any special marking for leaf check-ins. |
| 339 | ** |
| 340 | ** 1 Show just "leaf" or "closed" |
| 341 | ** |
| 342 | ** 2 Show "Leaf" or "Closed-Leaf" with emphasis |
| 343 | ** |
| 344 | ** The default is currently 1. Prior to 2025-10-19, the default was 2. |
| 345 | ** This setting has no effect on the "Classic" view, which always behaves |
| 346 | ** as if the setting were 2. |
| 347 | */ |
| 348 | |
| 349 | /* |
| 350 | ** Output a timeline in the web format given a query. The query |
| 351 | ** should return these columns: |
| 352 | ** |
| 353 | ** 0. rid |
| @@ -194,11 +368,11 @@ | |
| 368 | const char *zThisUser, /* Suppress links to this user */ |
| 369 | const char *zThisTag, /* Suppress links to this tag */ |
| 370 | Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */ |
| 371 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 372 | int secondRid, /* Secondary highlight (or zero) */ |
| 373 | void (*xExtra)(Stmt*,int,const char*,const char*) /* generate "extra" text */ |
| 374 | ){ |
| 375 | int mxWikiLen; |
| 376 | Blob comment; |
| 377 | int prevTagid = 0; |
| 378 | int suppressCnt = 0; |
| @@ -214,55 +388,41 @@ | |
| 388 | const char *zStyle; /* Sub-name for classes for the style */ |
| 389 | const char *zDateFmt; |
| 390 | int iTableId = timeline_tableid(); |
| 391 | int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info |
| 392 | ** page rather than the /timeline page */ |
| 393 | char *zMainBranch = db_get("main-branch","trunk"); |
| 394 | |
| 395 | |
| 396 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 397 | vid = db_lget_int("checkout", 0); |
| 398 | } |
| 399 | if( xExtra==0 ) xExtra = timeline_extra; |
| 400 | zPrevDate[0] = 0; |
| 401 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 402 | dateFormat = db_get_int("timeline-date-format", 0); |
| 403 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 404 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| 405 | if( (tmFlags & TIMELINE_VIEWS)==0 ){ |
| 406 | tmFlags |= timeline_ss_cookie(); |
| 407 | } |
| 408 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 409 | zStyle = "Columnar"; |
| 410 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 411 | zStyle = "Compact"; |
| 412 | }else if( tmFlags & TIMELINE_SIMPLE ){ |
| 413 | zStyle = "Simple"; |
| 414 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 415 | zStyle = "Verbose"; |
| 416 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 417 | zStyle = "Classic"; |
| 418 | }else{ |
| 419 | zStyle = "Modern"; |
| 420 | } |
| 421 | zDateFmt = P("datefmt"); |
| 422 | if( zDateFmt ) dateFormat = atoi(zDateFmt); |
| 423 | pGraph = graph_init(); |
| 424 | if( (tmFlags & TIMELINE_CHPICK)!=0 |
| 425 | && !db_table_exists("repository","cherrypick") |
| 426 | ){ |
| 427 | tmFlags &= ~TIMELINE_CHPICK; |
| 428 | } |
| @@ -275,13 +435,11 @@ | |
| 435 | int isLeaf = db_column_int(pQuery, 5); |
| 436 | const char *zBgClr = db_column_text(pQuery, 6); |
| 437 | const char *zDate = db_column_text(pQuery, 2); |
| 438 | const char *zType = db_column_text(pQuery, 7); |
| 439 | const char *zUser = db_column_text(pQuery, 4); |
| 440 | int tagid = db_column_int(pQuery, 9); |
| 441 | char *zBr = 0; /* Branch */ |
| 442 | int commentColumn = 3; /* Column containing comment text */ |
| 443 | int modPending; /* Pending moderation */ |
| 444 | char *zDateLink; /* URL for the link on the timestamp */ |
| 445 | int drawDetailEllipsis; /* True to show ellipsis in place of detail */ |
| @@ -434,11 +592,11 @@ | |
| 592 | zBr = branch_of_rid(rid); |
| 593 | if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){ |
| 594 | /* If no background color is specified, use a color based on the |
| 595 | ** branch name */ |
| 596 | if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 597 | }else if( zBr==0 || strcmp(zBr,zMainBranch)==0 ){ |
| 598 | zBgClr = 0; |
| 599 | }else{ |
| 600 | zBgClr = hash_color(zBr); |
| 601 | } |
| 602 | } |
| @@ -446,32 +604,34 @@ | |
| 604 | if( zType[0]=='c' && pGraph ){ |
| 605 | int nParent = 0; |
| 606 | int nCherrypick = 0; |
| 607 | GraphRowId aParent[GR_MAX_RAIL]; |
| 608 | static Stmt qparent; |
| 609 | if( tmFlags & TIMELINE_GRAPH ){ |
| 610 | db_static_prepare(&qparent, |
| 611 | "SELECT pid FROM plink" |
| 612 | " WHERE cid=:rid AND pid NOT IN phantom" |
| 613 | " ORDER BY isprim DESC /*sort*/" |
| 614 | ); |
| 615 | db_bind_int(&qparent, ":rid", rid); |
| 616 | while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){ |
| 617 | aParent[nParent++] = db_column_int(&qparent, 0); |
| 618 | } |
| 619 | db_reset(&qparent); |
| 620 | if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){ |
| 621 | static Stmt qcherrypick; |
| 622 | db_static_prepare(&qcherrypick, |
| 623 | "SELECT parentid FROM cherrypick" |
| 624 | " WHERE childid=:rid AND parentid NOT IN phantom" |
| 625 | ); |
| 626 | db_bind_int(&qcherrypick, ":rid", rid); |
| 627 | while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){ |
| 628 | aParent[nParent++] = db_column_int(&qcherrypick, 0); |
| 629 | nCherrypick++; |
| 630 | } |
| 631 | db_reset(&qcherrypick); |
| 632 | } |
| 633 | } |
| 634 | gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent, |
| 635 | zBr, zBgClr, zUuid, |
| 636 | isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0); |
| 637 | @ <div id="m%d(gidx)" class="tl-nodemark"></div> |
| @@ -598,23 +758,15 @@ | |
| 758 | drawDetailEllipsis = 0; |
| 759 | }else{ |
| 760 | cgi_printf("%W",blob_str(&comment)); |
| 761 | } |
| 762 | } |
| 763 | |
| 764 | @ </span> |
| 765 | blob_reset(&comment); |
| 766 | |
| 767 | /* Generate extra information and hyperlinks that follow the comment. |
| 768 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 769 | */ |
| 770 | if( drawDetailEllipsis ){ |
| 771 | @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ |
| 772 | @ data-id='%d(rid)'>...</span> |
| @@ -628,101 +780,17 @@ | |
| 780 | } |
| 781 | if( tmFlags & TIMELINE_COMPACT ){ |
| 782 | cgi_printf("<span class='clutter' id='detail-%d'>",rid); |
| 783 | } |
| 784 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 785 | xExtra(pQuery, tmFlags, zThisUser, zThisTag); |
| 786 | if( tmFlags & TIMELINE_COMPACT ){ |
| 787 | @ </span></span> |
| 788 | }else{ |
| 789 | @ </span> |
| 790 | } |
| 791 | |
| 792 | /* Generate the file-change list if requested */ |
| 793 | if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0 |
| 794 | && zType[0]=='c' && g.perm.Hyperlink |
| 795 | ){ |
| 796 | int inUl = 0; |
| @@ -908,11 +976,13 @@ | |
| 976 | int omitDescenders; /* True to omit descenders */ |
| 977 | int scrollToSelect; /* True to scroll to the selection */ |
| 978 | int dwellTimeout; /* Milliseconds to wait for tooltips to show */ |
| 979 | int closeTimeout; /* Milliseconds to wait for tooltips to close */ |
| 980 | u8 *aiMap; /* The rail map */ |
| 981 | u8 bNoGraph; /* True to show a minimal graph */ |
| 982 | |
| 983 | bNoGraph = (tmFlags & TIMELINE_GRAPH)==0; |
| 984 | iRailPitch = atoi(PD("railpitch","0")); |
| 985 | showArrowheads = skin_detail_boolean("timeline-arrowheads"); |
| 986 | circleNodes = skin_detail_boolean("timeline-circle-nodes"); |
| 987 | colorGraph = skin_detail_boolean("timeline-color-graph-lines"); |
| 988 | iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0; |
| @@ -930,11 +1000,11 @@ | |
| 1000 | @ "nomo": %d(PB("nomo")), |
| 1001 | @ "iTopRow": %d(iTopRow), |
| 1002 | @ "omitDescenders": %d(omitDescenders), |
| 1003 | @ "fileDiff": %d(fileDiff), |
| 1004 | @ "scrollToSelect": %d(scrollToSelect), |
| 1005 | @ "nrail": %d(bNoGraph?1:pGraph->mxRail+1), |
| 1006 | @ "baseUrl": "%R", |
| 1007 | @ "dwellTimeout": %d(dwellTimeout), |
| 1008 | @ "closeTimeout": %d(closeTimeout), |
| 1009 | @ "hashDigits": %d(hash_digits(1)), |
| 1010 | @ "bottomRowId": "btm-%d(iTableId)", |
| @@ -992,12 +1062,16 @@ | |
| 1062 | aiMap = pGraph->aiRailMap; |
| 1063 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 1064 | int k = 0; |
| 1065 | cgi_printf("{\"id\":%d,", pRow->idx); |
| 1066 | cgi_printf("\"bg\":\"%s\",", pRow->zBgClr); |
| 1067 | if( bNoGraph ){ |
| 1068 | cgi_printf("\"r\":0,"); /* Chng to ":-1" to omit node circles */ |
| 1069 | }else{ |
| 1070 | cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1); |
| 1071 | } |
| 1072 | if( pRow->bDescender && !bNoGraph ){ |
| 1073 | cgi_printf("\"d\":%d,", pRow->bDescender); |
| 1074 | } |
| 1075 | if( pRow->mergeOut>=0 ){ |
| 1076 | cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]); |
| 1077 | if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx; |
| @@ -1004,11 +1078,13 @@ | |
| 1078 | cgi_printf("\"mu\":%d,", pRow->mergeUpto); |
| 1079 | if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){ |
| 1080 | cgi_printf("\"cu\":%d,", pRow->cherrypickUpto); |
| 1081 | } |
| 1082 | } |
| 1083 | if( bNoGraph ){ |
| 1084 | cgi_printf("\"u\":-1,"); |
| 1085 | }else if( pRow->isStepParent ){ |
| 1086 | cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1087 | }else{ |
| 1088 | cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]); |
| 1089 | } |
| 1090 | k = 0; |
| @@ -1206,10 +1282,26 @@ | |
| 1282 | if( i>2 ){ |
| 1283 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1284 | } |
| 1285 | } |
| 1286 | |
| 1287 | /* |
| 1288 | ** SETTING: timeline-default-style width=5 default=m |
| 1289 | ** |
| 1290 | ** This setting determines the default "view style" for timelines. |
| 1291 | ** The setting should be a single character, one of the following: |
| 1292 | ** |
| 1293 | ** c Compact |
| 1294 | ** j Columnar |
| 1295 | ** m Modern |
| 1296 | ** s Simple |
| 1297 | ** v Verbose |
| 1298 | ** x Classic |
| 1299 | ** |
| 1300 | ** The default value is m (Modern). |
| 1301 | */ |
| 1302 | |
| 1303 | /* |
| 1304 | ** Return the default value for the "ss" cookie or query parameter. |
| 1305 | ** The "ss" cookie determines the graph style. See the |
| 1306 | ** timeline_view_styles[] global constant for a list of choices. |
| 1307 | */ |
| @@ -1230,10 +1322,11 @@ | |
| 1322 | switch( v[0] ){ |
| 1323 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1324 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1325 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1326 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1327 | case 's': tmFlags = TIMELINE_SIMPLE; break; |
| 1328 | default: tmFlags = TIMELINE_MODERN; break; |
| 1329 | } |
| 1330 | return tmFlags; |
| 1331 | } |
| 1332 | |
| @@ -1242,15 +1335,16 @@ | |
| 1335 | */ |
| 1336 | const char *const timeline_view_styles[] = { |
| 1337 | "m", "Modern View", |
| 1338 | "j", "Columnar View", |
| 1339 | "c", "Compact View", |
| 1340 | "s", "Simple View", |
| 1341 | "v", "Verbose View", |
| 1342 | "x", "Classic View", |
| 1343 | }; |
| 1344 | #if INTERFACE |
| 1345 | # define N_TIMELINE_VIEW_STYLE 6 |
| 1346 | #endif |
| 1347 | |
| 1348 | /* |
| 1349 | ** Add the select/option box to the timeline submenu that is used to |
| 1350 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1363,11 +1457,11 @@ | |
| 1457 | ** the input is a valid date space and false if not. |
| 1458 | */ |
| 1459 | static int timeline_is_datespan(const char *zDay){ |
| 1460 | size_t n = strlen(zDay); |
| 1461 | int i, d, m; |
| 1462 | |
| 1463 | if( n<17 || n>18 ) return 0; |
| 1464 | if( n==18 ){ |
| 1465 | if( zDay[17]!='Z' && zDay[17]!='z' ) return 0; |
| 1466 | n--; |
| 1467 | } |
| @@ -1389,17 +1483,17 @@ | |
| 1483 | |
| 1484 | /* |
| 1485 | ** Find the first check-in encountered with a particular tag |
| 1486 | ** when moving either forwards are backwards in time from a |
| 1487 | ** particular starting point (iFrom). Return the rid of that |
| 1488 | ** first check-in. If there are no check-ins in the descendent |
| 1489 | ** or ancestor set of check-in iFrom that match the tag, then |
| 1490 | ** return 0. |
| 1491 | */ |
| 1492 | static int timeline_endpoint( |
| 1493 | int iFrom, /* Starting point */ |
| 1494 | const char *zEnd, /* Tag we are searching for */ |
| 1495 | int bForward /* 1: forwards in time (descendants) 0: backwards */ |
| 1496 | ){ |
| 1497 | int tagId; |
| 1498 | int endId = 0; |
| 1499 | Stmt q; |
| @@ -1590,11 +1684,11 @@ | |
| 1684 | ** dp2=CKIN2 Same as 'd2=CKIN2&p2=CKIN2' |
| 1685 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1686 | ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN |
| 1687 | ** p=CX ... from CX back to time of CHECKIN |
| 1688 | ** from=CX ... path from CX back to CHECKIN |
| 1689 | ** ft=CHECKIN "Forward To": Show descendents forward to CHECKIN |
| 1690 | ** d=CX ... from CX up to the time of CHECKIN |
| 1691 | ** from=CX ... path from CX up to CHECKIN |
| 1692 | ** t=TAG Show only check-ins with the given TAG |
| 1693 | ** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related" |
| 1694 | ** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List" |
| @@ -1607,11 +1701,11 @@ | |
| 1701 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1702 | ** "like", or "regexp". |
| 1703 | ** u=USER Only show items associated with USER |
| 1704 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1705 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1706 | ** x: "Classic". |
| 1707 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1708 | ** ng No Graph. |
| 1709 | ** ncp Omit cherrypick merges |
| 1710 | ** nd Do not highlight the focus check-in |
| 1711 | ** nsm Omit the submenu |
| @@ -1830,10 +1924,13 @@ | |
| 1924 | || (bisectLocal && !g.perm.Setup) |
| 1925 | ){ |
| 1926 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1927 | return; |
| 1928 | } |
| 1929 | if( zBefore || zCirca ){ |
| 1930 | if( robot_restrict("timelineX") ) return; |
| 1931 | } |
| 1932 | if( !bisectLocal ){ |
| 1933 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1934 | } |
| 1935 | cookie_read_parameter("y","y"); |
| 1936 | zType = P("y"); |
| @@ -1969,10 +2066,11 @@ | |
| 2066 | } |
| 2067 | if( showSql ) db_append_dml_to_blob(&allSql); |
| 2068 | if( zUses!=0 ){ |
| 2069 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 2070 | if( ufid ){ |
| 2071 | if( robot_restrict("timelineX") ) return; |
| 2072 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 2073 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 2074 | compute_uses_file("usesfile", ufid, 0); |
| 2075 | zType = "ci"; |
| 2076 | disableY = 1; |
| @@ -2042,11 +2140,11 @@ | |
| 2140 | zBisect = 0; |
| 2141 | } |
| 2142 | |
| 2143 | style_header("Timeline"); |
| 2144 | if( advancedMenu ){ |
| 2145 | style_submenu_element("Help", "%R/help/www/timeline"); |
| 2146 | } |
| 2147 | login_anonymous_available(); |
| 2148 | timeline_temp_table(); |
| 2149 | blob_zero(&sql); |
| 2150 | blob_zero(&desc); |
| @@ -2283,11 +2381,11 @@ | |
| 2381 | if( zError==0 ){ |
| 2382 | zError = "Cannot use the ft= query parameter when both p= and d= " |
| 2383 | "are used and have distinct values."; |
| 2384 | } |
| 2385 | zFwdTo = 0; |
| 2386 | } |
| 2387 | if( zFwdTo ){ |
| 2388 | double rStartDate = mtime_of_rid(d_rid, 0.0); |
| 2389 | ridFwdTo = first_checkin_with_tag_after_date(zFwdTo, rStartDate); |
| 2390 | if( ridFwdTo==0 ){ |
| 2391 | ridFwdTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2342,11 +2440,11 @@ | |
| 2440 | if( zError==0 ){ |
| 2441 | zError = "Cannot use the bt= query parameter when both p= and d= " |
| 2442 | "are used and have distinct values."; |
| 2443 | } |
| 2444 | zBackTo = 0; |
| 2445 | } |
| 2446 | if( zBackTo ){ |
| 2447 | double rDateLimit = mtime_of_rid(p_rid, 0.0); |
| 2448 | ridBackTo = last_checkin_with_tag_before_date(zBackTo, rDateLimit); |
| 2449 | if( ridBackTo==0 ){ |
| 2450 | ridBackTo = name_to_typed_rid(zBackTo,"ci"); |
| @@ -2368,11 +2466,11 @@ | |
| 2466 | }else{ |
| 2467 | removeFileGlobFromOk(zChng); |
| 2468 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2469 | if( np>0 || nd==0 ){ |
| 2470 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 2471 | blob_appendf(&desc, "%d ancestor%s", |
| 2472 | np>=0 ? np : 0, (1==np)?"":"s"); |
| 2473 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2474 | } |
| 2475 | if( useDividers && !selectedRid ) selectedRid = p_rid; |
| 2476 | } |
| @@ -2402,11 +2500,11 @@ | |
| 2500 | href("%R/info?name=%h",zBaseName), zBaseName, |
| 2501 | href("%R/info?name=%h",zBackTo), zBackTo); |
| 2502 | }else{ |
| 2503 | blob_appendf(&desc, " back to %z%h</a>%s", |
| 2504 | href("%R/info?name=%h",zBackTo), zBackTo, |
| 2505 | bBackAdded ? " (not a direct ancestor)" : ""); |
| 2506 | if( ridFwdTo && zFwdTo ){ |
| 2507 | blob_appendf(&desc, " and up to %z%h</a>%s", |
| 2508 | href("%R/info?name=%h",zFwdTo), zFwdTo, |
| 2509 | bFwdAdded ? " (not a direct descendant)" : ""); |
| 2510 | } |
| @@ -2651,11 +2749,11 @@ | |
| 2749 | blob_append_sql(&cond, |
| 2750 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2751 | " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", |
| 2752 | zStart, zTZMod, zEnd, zTZMod); |
| 2753 | nEntry = -1; |
| 2754 | |
| 2755 | if( fossil_ui_localtime() && bZulu ){ |
| 2756 | zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd); |
| 2757 | }else{ |
| 2758 | zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd); |
| 2759 | } |
| @@ -3107,10 +3205,11 @@ | |
| 3205 | if( r>0.0 && !selectedRid ) selectedRid = timeline_add_divider(r); |
| 3206 | } |
| 3207 | blob_zero(&sql); |
| 3208 | if( PB("oldestfirst") ){ |
| 3209 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby ASC /*scan*/"); |
| 3210 | tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK); |
| 3211 | }else{ |
| 3212 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 3213 | } |
| 3214 | if( fossil_islower(desc.aData[0]) ){ |
| 3215 | desc.aData[0] = fossil_toupper(desc.aData[0]); |
| @@ -3139,10 +3238,20 @@ | |
| 3238 | |
| 3239 | /* Report any errors. */ |
| 3240 | if( zError ){ |
| 3241 | @ <p class="generalError">%h(zError)</p> |
| 3242 | } |
| 3243 | |
| 3244 | /* Swap zNewer and zOlder buttons if we display oldestfirst */ |
| 3245 | if( PB("oldestfirst") ){ |
| 3246 | char *zSwap = zNewerButton; |
| 3247 | char *zSwapLabel = zNewerButtonLabel; |
| 3248 | zNewerButton = zOlderButton; |
| 3249 | zNewerButtonLabel = zOlderButtonLabel; |
| 3250 | zOlderButton = zSwap; |
| 3251 | zOlderButtonLabel = zSwapLabel; |
| 3252 | } |
| 3253 | |
| 3254 | if( zNewerButton ){ |
| 3255 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3256 | @ ↑</a> |
| 3257 | } |
| @@ -3620,19 +3729,21 @@ | |
| 3729 | ** N is negative, output the first -N lines. If N is |
| 3730 | ** zero, no limit. Default is -20 meaning 20 lines. |
| 3731 | ** --offset P Skip P changes |
| 3732 | ** -p|--path PATH Output items affecting PATH only. |
| 3733 | ** PATH can be a file or a sub directory. |
| 3734 | ** -r|--reverse Show items in chronological order. |
| 3735 | ** -R REPO_FILE Specifies the repository db to use. Default is |
| 3736 | ** the current check-out's repository. |
| 3737 | ** --sql Show the SQL used to generate the timeline |
| 3738 | ** -t|--type TYPE Output items from the given types only, such as: |
| 3739 | ** ci = file commits only |
| 3740 | ** e = technical notes only |
| 3741 | ** f = forum posts only |
| 3742 | ** t = tickets only |
| 3743 | ** w = wiki commits only |
| 3744 | ** -u|--for-user USER Only show items associated with USER |
| 3745 | ** -v|--verbose Output the list of files changed by each commit |
| 3746 | ** and the type of each change (edited, deleted, |
| 3747 | ** etc.) after the check-in comment. |
| 3748 | ** -W|--width N Width of lines (default is to auto-detect). N must be |
| 3749 | ** either greater than 20 or it must be zero 0 to |
| @@ -3644,17 +3755,19 @@ | |
| 3755 | int n, k, width; |
| 3756 | const char *zLimit; |
| 3757 | const char *zWidth; |
| 3758 | const char *zOffset; |
| 3759 | const char *zType; |
| 3760 | const char *zUser; |
| 3761 | char *zOrigin; |
| 3762 | char *zDate; |
| 3763 | Blob sql; |
| 3764 | int objid = 0; |
| 3765 | Blob uuid; |
| 3766 | int mode = TIMELINE_MODE_NONE; |
| 3767 | int verboseFlag = 0; |
| 3768 | int reverseFlag = 0; |
| 3769 | int iOffset; |
| 3770 | const char *zFilePattern = 0; |
| 3771 | const char *zFormat = 0; |
| 3772 | const char *zBr = 0; |
| 3773 | Blob treeName; |
| @@ -3666,10 +3779,11 @@ | |
| 3779 | } |
| 3780 | db_find_and_open_repository(0, 0); |
| 3781 | zLimit = find_option("limit","n",1); |
| 3782 | zWidth = find_option("width","W",1); |
| 3783 | zType = find_option("type","t",1); |
| 3784 | zUser = find_option("for-user","u",1); |
| 3785 | zFilePattern = find_option("path","p",1); |
| 3786 | zFormat = find_option("format","F",1); |
| 3787 | zBr = find_option("branch","b",1); |
| 3788 | if( find_option("current-branch","c",0)!=0 ){ |
| 3789 | if( !g.localOpen ){ |
| @@ -3707,10 +3821,11 @@ | |
| 3821 | }else{ |
| 3822 | width = -1; |
| 3823 | } |
| 3824 | zOffset = find_option("offset",0,1); |
| 3825 | iOffset = zOffset ? atoi(zOffset) : 0; |
| 3826 | reverseFlag = find_option("reverse","r",0)!=0; |
| 3827 | |
| 3828 | /* We should be done with options.. */ |
| 3829 | verify_all_options(); |
| 3830 | |
| 3831 | if( g.argc>=4 ){ |
| @@ -3727,11 +3842,11 @@ | |
| 3842 | mode = TIMELINE_MODE_PARENTS; |
| 3843 | }else if( strncmp(g.argv[2],"parents",k)==0 ){ |
| 3844 | mode = TIMELINE_MODE_PARENTS; |
| 3845 | }else if(!zType && !zLimit){ |
| 3846 | usage("?WHEN? ?CHECKIN|DATETIME? ?-n|--limit #? ?-t|--type TYPE? " |
| 3847 | "?-W|--width WIDTH? ?-p|--path PATH? ?-r|--reverse?"); |
| 3848 | } |
| 3849 | if( '-' != *g.argv[3] ){ |
| 3850 | zOrigin = g.argv[3]; |
| 3851 | }else{ |
| 3852 | zOrigin = "now"; |
| @@ -3797,10 +3912,13 @@ | |
| 3912 | mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/ |
| 3913 | ); |
| 3914 | if( zType && (zType[0]!='a') ){ |
| 3915 | blob_append_sql(&sql, "\n AND event.type=%Q ", zType); |
| 3916 | } |
| 3917 | if( zUser && (zUser[0]!='\0') ){ |
| 3918 | blob_append_sql(&sql, "\n AND user0=%Q ", zUser); |
| 3919 | } |
| 3920 | |
| 3921 | /* When zFilePattern is specified, compute complete ancestry; |
| 3922 | * limit later at print_timeline() */ |
| 3923 | if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){ |
| 3924 | db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| @@ -3850,23 +3968,25 @@ | |
| 3968 | " WHERE tx.value='%q'\n" |
| 3969 | ")\n" /* No merge closures */ |
| 3970 | " AND (tagxref.value IS NULL OR tagxref.value='%q')", |
| 3971 | zBr, zBr, zBr, TAG_BRANCH, zBr, zBr); |
| 3972 | } |
| 3973 | |
| 3974 | if( mode==TIMELINE_MODE_AFTER ){ |
| 3975 | int lim = n; |
| 3976 | if( n == 0 ){ |
| 3977 | lim = -1; /* 0 means no limit */ |
| 3978 | }else if( n < 0 ){ |
| 3979 | lim = -n; |
| 3980 | } |
| 3981 | /* Complete the above outer select. */ |
| 3982 | blob_append_sql(&sql, |
| 3983 | "\nORDER BY event.mtime LIMIT %d) t ORDER BY t.mDateTime %s", |
| 3984 | lim, reverseFlag ? "" : "DESC"); |
| 3985 | }else{ |
| 3986 | blob_append_sql(&sql, |
| 3987 | "\nORDER BY event.mtime %s", reverseFlag ? "" : "DESC"); |
| 3988 | } |
| 3989 | if( iOffset>0 ){ |
| 3990 | /* Don't handle LIMIT here, otherwise print_timeline() |
| 3991 | * will not determine the end-marker correctly! */ |
| 3992 | blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset); |
| 3993 |
+4
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -791,10 +791,14 @@ | ||
| 791 | 791 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br>\n", -1); |
| 792 | 792 | |
| 793 | 793 | if( zFullName ){ |
| 794 | 794 | attachment_list(zFullName, "<h2>Attachments:</h2>", 1); |
| 795 | 795 | } |
| 796 | + | |
| 797 | + builtin_fossil_js_bundle_or("dom", "storage", NULL); | |
| 798 | + builtin_request_js("fossil.page.ticket.js"); | |
| 799 | + builtin_fulfill_js_requests(); | |
| 796 | 800 | |
| 797 | 801 | style_finish_page(); |
| 798 | 802 | } |
| 799 | 803 | |
| 800 | 804 | /* |
| 801 | 805 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -791,10 +791,14 @@ | |
| 791 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br>\n", -1); |
| 792 | |
| 793 | if( zFullName ){ |
| 794 | attachment_list(zFullName, "<h2>Attachments:</h2>", 1); |
| 795 | } |
| 796 | |
| 797 | style_finish_page(); |
| 798 | } |
| 799 | |
| 800 | /* |
| 801 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -791,10 +791,14 @@ | |
| 791 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br>\n", -1); |
| 792 | |
| 793 | if( zFullName ){ |
| 794 | attachment_list(zFullName, "<h2>Attachments:</h2>", 1); |
| 795 | } |
| 796 | |
| 797 | builtin_fossil_js_bundle_or("dom", "storage", NULL); |
| 798 | builtin_request_js("fossil.page.ticket.js"); |
| 799 | builtin_fulfill_js_requests(); |
| 800 | |
| 801 | style_finish_page(); |
| 802 | } |
| 803 | |
| 804 | /* |
| 805 |
+12
-8
| --- src/tktsetup.c | ||
| +++ src/tktsetup.c | ||
| @@ -603,23 +603,24 @@ | ||
| 603 | 603 | @ } |
| 604 | 604 | @ } |
| 605 | 605 | @ } |
| 606 | 606 | @ set seenRow 0 |
| 607 | 607 | @ set alwaysPlaintext [info exists plaintext] |
| 608 | -@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, | |
| 608 | +@ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin, | |
| 609 | 609 | @ mimetype as xmimetype, icomment AS xcomment, |
| 610 | 610 | @ username AS xusername |
| 611 | 611 | @ FROM ticketchng |
| 612 | 612 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 613 | 613 | @ if {$seenRow} { |
| 614 | 614 | @ html "<hr>\n" |
| 615 | 615 | @ } else { |
| 616 | 616 | @ html "<tr><td class='tktDspLabel' style='text-align:left'>\n" |
| 617 | 617 | @ html "User Comments:</td></tr>\n" |
| 618 | -@ html "<tr><td colspan='5' class='tktDspValue'>\n" | |
| 618 | +@ html "<tr><td colspan='5' class='tktDspValue'><div class='tktCommentArea'>\n" | |
| 619 | 619 | @ set seenRow 1 |
| 620 | 620 | @ } |
| 621 | +@ html "<div class='tktCommentEntry'>" | |
| 621 | 622 | @ html "<span class='tktDspCommenter'>" |
| 622 | 623 | @ puts $xlogin |
| 623 | 624 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 624 | 625 | @ puts " (claiming to be $xusername)" |
| 625 | 626 | @ } |
| @@ -637,12 +638,13 @@ | ||
| 637 | 638 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 638 | 639 | @ } else { |
| 639 | 640 | @ set r [randhex] |
| 640 | 641 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 641 | 642 | @ } |
| 643 | +@ html "</div>"; # .tktCommentEntry | |
| 642 | 644 | @ } |
| 643 | -@ if {$seenRow} {html "</td></tr>\n"} | |
| 645 | +@ if {$seenRow} {html "</div></td></tr>\n"} | |
| 644 | 646 | @ </th1> |
| 645 | 647 | @ </table> |
| 646 | 648 | ; |
| 647 | 649 | |
| 648 | 650 | |
| @@ -789,11 +791,11 @@ | ||
| 789 | 791 | @ </tr> |
| 790 | 792 | @ |
| 791 | 793 | @ <th1> |
| 792 | 794 | @ set seenRow 0 |
| 793 | 795 | @ set alwaysPlaintext [info exists plaintext] |
| 794 | -@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, | |
| 796 | +@ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin, | |
| 795 | 797 | @ mimetype as xmimetype, icomment AS xcomment, |
| 796 | 798 | @ username AS xusername |
| 797 | 799 | @ FROM ticketchng |
| 798 | 800 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 799 | 801 | @ if {$seenRow} { |
| @@ -800,13 +802,14 @@ | ||
| 800 | 802 | @ html "<hr>\n" |
| 801 | 803 | @ } else { |
| 802 | 804 | @ html "<tr><td colspan='2'><hr></td></tr>\n" |
| 803 | 805 | @ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n" |
| 804 | 806 | @ html "Previous User Comments:</td></tr>\n" |
| 805 | -@ html "<tr><td colspan='2' class='tktDspValue'>\n" | |
| 807 | +@ html "<tr><td colspan='2' class='tktDspValue'><div class='tktCommentArea'>\n" | |
| 806 | 808 | @ set seenRow 1 |
| 807 | 809 | @ } |
| 810 | +@ html "<div class='tktCommentEntry'>" | |
| 808 | 811 | @ html "<span class='tktDspCommenter'>" |
| 809 | 812 | @ puts $xlogin |
| 810 | 813 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 811 | 814 | @ puts " (claiming to be $xusername)" |
| 812 | 815 | @ } |
| @@ -824,12 +827,13 @@ | ||
| 824 | 827 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 825 | 828 | @ } else { |
| 826 | 829 | @ set r [randhex] |
| 827 | 830 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 828 | 831 | @ } |
| 832 | +@ html "</div>"; # .tktCommentEntry | |
| 829 | 833 | @ } |
| 830 | -@ if {$seenRow} {html "</td></tr>\n"} | |
| 834 | +@ if {$seenRow} {html "</div></td></tr>\n"} | |
| 831 | 835 | @ </th1> |
| 832 | 836 | @ |
| 833 | 837 | @ </table> |
| 834 | 838 | ; |
| 835 | 839 | |
| @@ -926,12 +930,12 @@ | ||
| 926 | 930 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 927 | 931 | @ WHEN status='Tested' THEN '#bde5d6' |
| 928 | 932 | @ WHEN status='Deferred' THEN '#cacae5' |
| 929 | 933 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 930 | 934 | @ substr(tkt_uuid,1,10) AS '#', |
| 931 | -@ datetime(tkt_ctime) AS 'created', | |
| 932 | -@ datetime(tkt_mtime) AS 'modified', | |
| 935 | +@ datetime(tkt_ctime,toLocal()) AS 'created', | |
| 936 | +@ datetime(tkt_mtime,toLocal()) AS 'modified', | |
| 933 | 937 | @ type, |
| 934 | 938 | @ status, |
| 935 | 939 | @ subsystem, |
| 936 | 940 | @ title, |
| 937 | 941 | @ comment AS '_comments' |
| 938 | 942 |
| --- src/tktsetup.c | |
| +++ src/tktsetup.c | |
| @@ -603,23 +603,24 @@ | |
| 603 | @ } |
| 604 | @ } |
| 605 | @ } |
| 606 | @ set seenRow 0 |
| 607 | @ set alwaysPlaintext [info exists plaintext] |
| 608 | @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, |
| 609 | @ mimetype as xmimetype, icomment AS xcomment, |
| 610 | @ username AS xusername |
| 611 | @ FROM ticketchng |
| 612 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 613 | @ if {$seenRow} { |
| 614 | @ html "<hr>\n" |
| 615 | @ } else { |
| 616 | @ html "<tr><td class='tktDspLabel' style='text-align:left'>\n" |
| 617 | @ html "User Comments:</td></tr>\n" |
| 618 | @ html "<tr><td colspan='5' class='tktDspValue'>\n" |
| 619 | @ set seenRow 1 |
| 620 | @ } |
| 621 | @ html "<span class='tktDspCommenter'>" |
| 622 | @ puts $xlogin |
| 623 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 624 | @ puts " (claiming to be $xusername)" |
| 625 | @ } |
| @@ -637,12 +638,13 @@ | |
| 637 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 638 | @ } else { |
| 639 | @ set r [randhex] |
| 640 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 641 | @ } |
| 642 | @ } |
| 643 | @ if {$seenRow} {html "</td></tr>\n"} |
| 644 | @ </th1> |
| 645 | @ </table> |
| 646 | ; |
| 647 | |
| 648 | |
| @@ -789,11 +791,11 @@ | |
| 789 | @ </tr> |
| 790 | @ |
| 791 | @ <th1> |
| 792 | @ set seenRow 0 |
| 793 | @ set alwaysPlaintext [info exists plaintext] |
| 794 | @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, |
| 795 | @ mimetype as xmimetype, icomment AS xcomment, |
| 796 | @ username AS xusername |
| 797 | @ FROM ticketchng |
| 798 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 799 | @ if {$seenRow} { |
| @@ -800,13 +802,14 @@ | |
| 800 | @ html "<hr>\n" |
| 801 | @ } else { |
| 802 | @ html "<tr><td colspan='2'><hr></td></tr>\n" |
| 803 | @ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n" |
| 804 | @ html "Previous User Comments:</td></tr>\n" |
| 805 | @ html "<tr><td colspan='2' class='tktDspValue'>\n" |
| 806 | @ set seenRow 1 |
| 807 | @ } |
| 808 | @ html "<span class='tktDspCommenter'>" |
| 809 | @ puts $xlogin |
| 810 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 811 | @ puts " (claiming to be $xusername)" |
| 812 | @ } |
| @@ -824,12 +827,13 @@ | |
| 824 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 825 | @ } else { |
| 826 | @ set r [randhex] |
| 827 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 828 | @ } |
| 829 | @ } |
| 830 | @ if {$seenRow} {html "</td></tr>\n"} |
| 831 | @ </th1> |
| 832 | @ |
| 833 | @ </table> |
| 834 | ; |
| 835 | |
| @@ -926,12 +930,12 @@ | |
| 926 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 927 | @ WHEN status='Tested' THEN '#bde5d6' |
| 928 | @ WHEN status='Deferred' THEN '#cacae5' |
| 929 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 930 | @ substr(tkt_uuid,1,10) AS '#', |
| 931 | @ datetime(tkt_ctime) AS 'created', |
| 932 | @ datetime(tkt_mtime) AS 'modified', |
| 933 | @ type, |
| 934 | @ status, |
| 935 | @ subsystem, |
| 936 | @ title, |
| 937 | @ comment AS '_comments' |
| 938 |
| --- src/tktsetup.c | |
| +++ src/tktsetup.c | |
| @@ -603,23 +603,24 @@ | |
| 603 | @ } |
| 604 | @ } |
| 605 | @ } |
| 606 | @ set seenRow 0 |
| 607 | @ set alwaysPlaintext [info exists plaintext] |
| 608 | @ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin, |
| 609 | @ mimetype as xmimetype, icomment AS xcomment, |
| 610 | @ username AS xusername |
| 611 | @ FROM ticketchng |
| 612 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 613 | @ if {$seenRow} { |
| 614 | @ html "<hr>\n" |
| 615 | @ } else { |
| 616 | @ html "<tr><td class='tktDspLabel' style='text-align:left'>\n" |
| 617 | @ html "User Comments:</td></tr>\n" |
| 618 | @ html "<tr><td colspan='5' class='tktDspValue'><div class='tktCommentArea'>\n" |
| 619 | @ set seenRow 1 |
| 620 | @ } |
| 621 | @ html "<div class='tktCommentEntry'>" |
| 622 | @ html "<span class='tktDspCommenter'>" |
| 623 | @ puts $xlogin |
| 624 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 625 | @ puts " (claiming to be $xusername)" |
| 626 | @ } |
| @@ -637,12 +638,13 @@ | |
| 638 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 639 | @ } else { |
| 640 | @ set r [randhex] |
| 641 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 642 | @ } |
| 643 | @ html "</div>"; # .tktCommentEntry |
| 644 | @ } |
| 645 | @ if {$seenRow} {html "</div></td></tr>\n"} |
| 646 | @ </th1> |
| 647 | @ </table> |
| 648 | ; |
| 649 | |
| 650 | |
| @@ -789,11 +791,11 @@ | |
| 791 | @ </tr> |
| 792 | @ |
| 793 | @ <th1> |
| 794 | @ set seenRow 0 |
| 795 | @ set alwaysPlaintext [info exists plaintext] |
| 796 | @ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin, |
| 797 | @ mimetype as xmimetype, icomment AS xcomment, |
| 798 | @ username AS xusername |
| 799 | @ FROM ticketchng |
| 800 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 801 | @ if {$seenRow} { |
| @@ -800,13 +802,14 @@ | |
| 802 | @ html "<hr>\n" |
| 803 | @ } else { |
| 804 | @ html "<tr><td colspan='2'><hr></td></tr>\n" |
| 805 | @ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n" |
| 806 | @ html "Previous User Comments:</td></tr>\n" |
| 807 | @ html "<tr><td colspan='2' class='tktDspValue'><div class='tktCommentArea'>\n" |
| 808 | @ set seenRow 1 |
| 809 | @ } |
| 810 | @ html "<div class='tktCommentEntry'>" |
| 811 | @ html "<span class='tktDspCommenter'>" |
| 812 | @ puts $xlogin |
| 813 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 814 | @ puts " (claiming to be $xusername)" |
| 815 | @ } |
| @@ -824,12 +827,13 @@ | |
| 827 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 828 | @ } else { |
| 829 | @ set r [randhex] |
| 830 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 831 | @ } |
| 832 | @ html "</div>"; # .tktCommentEntry |
| 833 | @ } |
| 834 | @ if {$seenRow} {html "</div></td></tr>\n"} |
| 835 | @ </th1> |
| 836 | @ |
| 837 | @ </table> |
| 838 | ; |
| 839 | |
| @@ -926,12 +930,12 @@ | |
| 930 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 931 | @ WHEN status='Tested' THEN '#bde5d6' |
| 932 | @ WHEN status='Deferred' THEN '#cacae5' |
| 933 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 934 | @ substr(tkt_uuid,1,10) AS '#', |
| 935 | @ datetime(tkt_ctime,toLocal()) AS 'created', |
| 936 | @ datetime(tkt_mtime,toLocal()) AS 'modified', |
| 937 | @ type, |
| 938 | @ status, |
| 939 | @ subsystem, |
| 940 | @ title, |
| 941 | @ comment AS '_comments' |
| 942 |
+9
| --- src/unversioned.c | ||
| +++ src/unversioned.c | ||
| @@ -320,10 +320,13 @@ | ||
| 320 | 320 | const char *zError = 0; |
| 321 | 321 | const char *zIn; |
| 322 | 322 | const char *zAs; |
| 323 | 323 | Blob file; |
| 324 | 324 | int i; |
| 325 | + i64 mxSize = sqlite3_limit(g.db,SQLITE_LIMIT_LENGTH,-1) - 850; | |
| 326 | + /* Extra space for other fields ------^^^ */ | |
| 327 | + /* of the UNVESIONED table row. */ | |
| 325 | 328 | |
| 326 | 329 | zAs = find_option("as",0,1); |
| 327 | 330 | verify_all_options(); |
| 328 | 331 | if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE"); |
| 329 | 332 | db_begin_transaction(); |
| @@ -336,13 +339,19 @@ | ||
| 336 | 339 | zError = "be absolute"; |
| 337 | 340 | }else if ( !file_is_simple_pathname(zIn,1) ){ |
| 338 | 341 | zError = "contain complex paths"; |
| 339 | 342 | }else if( contains_whitespace(zIn) ){ |
| 340 | 343 | zError = "contain whitespace"; |
| 344 | + }else if( strlen(zIn)>500 ){ | |
| 345 | + zError = "be more than 500 bytes long"; | |
| 341 | 346 | } |
| 342 | 347 | if( zError ){ |
| 343 | 348 | fossil_fatal("unversioned filenames may not %s: %Q", zError, zIn); |
| 349 | + } | |
| 350 | + if( file_size(g.argv[i], ExtFILE)>mxSize ){ | |
| 351 | + fossil_fatal("file \"%s\" is too big; max size: %,lld bytes", | |
| 352 | + g.argv[i], mxSize); | |
| 344 | 353 | } |
| 345 | 354 | blob_init(&file,0,0); |
| 346 | 355 | blob_read_from_file(&file, g.argv[i], ExtFILE); |
| 347 | 356 | unversioned_write(zIn, &file, mtime); |
| 348 | 357 | blob_reset(&file); |
| 349 | 358 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -320,10 +320,13 @@ | |
| 320 | const char *zError = 0; |
| 321 | const char *zIn; |
| 322 | const char *zAs; |
| 323 | Blob file; |
| 324 | int i; |
| 325 | |
| 326 | zAs = find_option("as",0,1); |
| 327 | verify_all_options(); |
| 328 | if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE"); |
| 329 | db_begin_transaction(); |
| @@ -336,13 +339,19 @@ | |
| 336 | zError = "be absolute"; |
| 337 | }else if ( !file_is_simple_pathname(zIn,1) ){ |
| 338 | zError = "contain complex paths"; |
| 339 | }else if( contains_whitespace(zIn) ){ |
| 340 | zError = "contain whitespace"; |
| 341 | } |
| 342 | if( zError ){ |
| 343 | fossil_fatal("unversioned filenames may not %s: %Q", zError, zIn); |
| 344 | } |
| 345 | blob_init(&file,0,0); |
| 346 | blob_read_from_file(&file, g.argv[i], ExtFILE); |
| 347 | unversioned_write(zIn, &file, mtime); |
| 348 | blob_reset(&file); |
| 349 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -320,10 +320,13 @@ | |
| 320 | const char *zError = 0; |
| 321 | const char *zIn; |
| 322 | const char *zAs; |
| 323 | Blob file; |
| 324 | int i; |
| 325 | i64 mxSize = sqlite3_limit(g.db,SQLITE_LIMIT_LENGTH,-1) - 850; |
| 326 | /* Extra space for other fields ------^^^ */ |
| 327 | /* of the UNVESIONED table row. */ |
| 328 | |
| 329 | zAs = find_option("as",0,1); |
| 330 | verify_all_options(); |
| 331 | if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE"); |
| 332 | db_begin_transaction(); |
| @@ -336,13 +339,19 @@ | |
| 339 | zError = "be absolute"; |
| 340 | }else if ( !file_is_simple_pathname(zIn,1) ){ |
| 341 | zError = "contain complex paths"; |
| 342 | }else if( contains_whitespace(zIn) ){ |
| 343 | zError = "contain whitespace"; |
| 344 | }else if( strlen(zIn)>500 ){ |
| 345 | zError = "be more than 500 bytes long"; |
| 346 | } |
| 347 | if( zError ){ |
| 348 | fossil_fatal("unversioned filenames may not %s: %Q", zError, zIn); |
| 349 | } |
| 350 | if( file_size(g.argv[i], ExtFILE)>mxSize ){ |
| 351 | fossil_fatal("file \"%s\" is too big; max size: %,lld bytes", |
| 352 | g.argv[i], mxSize); |
| 353 | } |
| 354 | blob_init(&file,0,0); |
| 355 | blob_read_from_file(&file, g.argv[i], ExtFILE); |
| 356 | unversioned_write(zIn, &file, mtime); |
| 357 | blob_reset(&file); |
| 358 |
+3
| --- src/url.c | ||
| +++ src/url.c | ||
| @@ -58,10 +58,11 @@ | ||
| 58 | 58 | char *user; /* User id for http: */ |
| 59 | 59 | char *passwd; /* Password for http: */ |
| 60 | 60 | char *canonical; /* Canonical representation of the URL */ |
| 61 | 61 | char *proxyAuth; /* Proxy-Authorizer: string */ |
| 62 | 62 | char *fossil; /* The fossil query parameter on ssh: */ |
| 63 | + char *subpath; /* Secondary HTTP request path for ssh: and file: */ | |
| 63 | 64 | char *pwConfig; /* CONFIG table entry that gave us the password */ |
| 64 | 65 | unsigned flags; /* Boolean flags controlling URL processing */ |
| 65 | 66 | int useProxy; /* Used to remember that a proxy is in use */ |
| 66 | 67 | int proxyOrigPort; /* Tunneled port number for https through proxy */ |
| 67 | 68 | char *proxyUrlPath; /* Remember path when proxy is use */ |
| @@ -406,10 +407,11 @@ | ||
| 406 | 407 | fossil_free(p->name); |
| 407 | 408 | fossil_free(p->path); |
| 408 | 409 | fossil_free(p->user); |
| 409 | 410 | fossil_free(p->passwd); |
| 410 | 411 | fossil_free(p->fossil); |
| 412 | + fossil_free(p->subpath); | |
| 411 | 413 | fossil_free(p->pwConfig); |
| 412 | 414 | memset(p, 0, sizeof(*p)); |
| 413 | 415 | } |
| 414 | 416 | |
| 415 | 417 | /* |
| @@ -483,10 +485,11 @@ | ||
| 483 | 485 | fossil_print("g.url.passwd = ************\n"); |
| 484 | 486 | } |
| 485 | 487 | fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig); |
| 486 | 488 | fossil_print("g.url.canonical = %s\n", g.url.canonical); |
| 487 | 489 | fossil_print("g.url.fossil = %s\n", g.url.fossil); |
| 490 | + fossil_print("g.url.subpath = %s\n", g.url.subpath); | |
| 488 | 491 | fossil_print("g.url.flags = 0x%04x\n", g.url.flags); |
| 489 | 492 | fossil_print("url_full(g.url) = %z\n", url_full(&g.url)); |
| 490 | 493 | } |
| 491 | 494 | |
| 492 | 495 | /* |
| 493 | 496 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -58,10 +58,11 @@ | |
| 58 | char *user; /* User id for http: */ |
| 59 | char *passwd; /* Password for http: */ |
| 60 | char *canonical; /* Canonical representation of the URL */ |
| 61 | char *proxyAuth; /* Proxy-Authorizer: string */ |
| 62 | char *fossil; /* The fossil query parameter on ssh: */ |
| 63 | char *pwConfig; /* CONFIG table entry that gave us the password */ |
| 64 | unsigned flags; /* Boolean flags controlling URL processing */ |
| 65 | int useProxy; /* Used to remember that a proxy is in use */ |
| 66 | int proxyOrigPort; /* Tunneled port number for https through proxy */ |
| 67 | char *proxyUrlPath; /* Remember path when proxy is use */ |
| @@ -406,10 +407,11 @@ | |
| 406 | fossil_free(p->name); |
| 407 | fossil_free(p->path); |
| 408 | fossil_free(p->user); |
| 409 | fossil_free(p->passwd); |
| 410 | fossil_free(p->fossil); |
| 411 | fossil_free(p->pwConfig); |
| 412 | memset(p, 0, sizeof(*p)); |
| 413 | } |
| 414 | |
| 415 | /* |
| @@ -483,10 +485,11 @@ | |
| 483 | fossil_print("g.url.passwd = ************\n"); |
| 484 | } |
| 485 | fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig); |
| 486 | fossil_print("g.url.canonical = %s\n", g.url.canonical); |
| 487 | fossil_print("g.url.fossil = %s\n", g.url.fossil); |
| 488 | fossil_print("g.url.flags = 0x%04x\n", g.url.flags); |
| 489 | fossil_print("url_full(g.url) = %z\n", url_full(&g.url)); |
| 490 | } |
| 491 | |
| 492 | /* |
| 493 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -58,10 +58,11 @@ | |
| 58 | char *user; /* User id for http: */ |
| 59 | char *passwd; /* Password for http: */ |
| 60 | char *canonical; /* Canonical representation of the URL */ |
| 61 | char *proxyAuth; /* Proxy-Authorizer: string */ |
| 62 | char *fossil; /* The fossil query parameter on ssh: */ |
| 63 | char *subpath; /* Secondary HTTP request path for ssh: and file: */ |
| 64 | char *pwConfig; /* CONFIG table entry that gave us the password */ |
| 65 | unsigned flags; /* Boolean flags controlling URL processing */ |
| 66 | int useProxy; /* Used to remember that a proxy is in use */ |
| 67 | int proxyOrigPort; /* Tunneled port number for https through proxy */ |
| 68 | char *proxyUrlPath; /* Remember path when proxy is use */ |
| @@ -406,10 +407,11 @@ | |
| 407 | fossil_free(p->name); |
| 408 | fossil_free(p->path); |
| 409 | fossil_free(p->user); |
| 410 | fossil_free(p->passwd); |
| 411 | fossil_free(p->fossil); |
| 412 | fossil_free(p->subpath); |
| 413 | fossil_free(p->pwConfig); |
| 414 | memset(p, 0, sizeof(*p)); |
| 415 | } |
| 416 | |
| 417 | /* |
| @@ -483,10 +485,11 @@ | |
| 485 | fossil_print("g.url.passwd = ************\n"); |
| 486 | } |
| 487 | fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig); |
| 488 | fossil_print("g.url.canonical = %s\n", g.url.canonical); |
| 489 | fossil_print("g.url.fossil = %s\n", g.url.fossil); |
| 490 | fossil_print("g.url.subpath = %s\n", g.url.subpath); |
| 491 | fossil_print("g.url.flags = 0x%04x\n", g.url.flags); |
| 492 | fossil_print("url_full(g.url) = %z\n", url_full(&g.url)); |
| 493 | } |
| 494 | |
| 495 | /* |
| 496 |
+4
-3
| --- src/user.c | ||
| +++ src/user.c | ||
| @@ -109,10 +109,12 @@ | ||
| 109 | 109 | } |
| 110 | 110 | void freepass(){ |
| 111 | 111 | if( !zPwdBuffer ) return; |
| 112 | 112 | assert( nPwdBuffer>0 ); |
| 113 | 113 | fossil_secure_free_page(zPwdBuffer, nPwdBuffer); |
| 114 | + zPwdBuffer = 0; | |
| 115 | + nPwdBuffer = 0; | |
| 114 | 116 | } |
| 115 | 117 | #endif |
| 116 | 118 | |
| 117 | 119 | /* |
| 118 | 120 | ** Scramble substitution matrix: |
| @@ -286,13 +288,12 @@ | ||
| 286 | 288 | char *zPrompt = mprintf("\rpassword for %s: ", zUser); |
| 287 | 289 | char *zPw; |
| 288 | 290 | Blob x; |
| 289 | 291 | fossil_force_newline(); |
| 290 | 292 | prompt_for_password(zPrompt, &x, 0); |
| 291 | - free(zPrompt); | |
| 292 | - zPw = mprintf("%b", &x); | |
| 293 | - blob_reset(&x); | |
| 293 | + fossil_free(zPrompt); | |
| 294 | + zPw = blob_str(&x)/*transfer ownership*/; | |
| 294 | 295 | return zPw; |
| 295 | 296 | } |
| 296 | 297 | |
| 297 | 298 | /* |
| 298 | 299 | ** Prompt the user to enter a single line of text. |
| 299 | 300 |
| --- src/user.c | |
| +++ src/user.c | |
| @@ -109,10 +109,12 @@ | |
| 109 | } |
| 110 | void freepass(){ |
| 111 | if( !zPwdBuffer ) return; |
| 112 | assert( nPwdBuffer>0 ); |
| 113 | fossil_secure_free_page(zPwdBuffer, nPwdBuffer); |
| 114 | } |
| 115 | #endif |
| 116 | |
| 117 | /* |
| 118 | ** Scramble substitution matrix: |
| @@ -286,13 +288,12 @@ | |
| 286 | char *zPrompt = mprintf("\rpassword for %s: ", zUser); |
| 287 | char *zPw; |
| 288 | Blob x; |
| 289 | fossil_force_newline(); |
| 290 | prompt_for_password(zPrompt, &x, 0); |
| 291 | free(zPrompt); |
| 292 | zPw = mprintf("%b", &x); |
| 293 | blob_reset(&x); |
| 294 | return zPw; |
| 295 | } |
| 296 | |
| 297 | /* |
| 298 | ** Prompt the user to enter a single line of text. |
| 299 |
| --- src/user.c | |
| +++ src/user.c | |
| @@ -109,10 +109,12 @@ | |
| 109 | } |
| 110 | void freepass(){ |
| 111 | if( !zPwdBuffer ) return; |
| 112 | assert( nPwdBuffer>0 ); |
| 113 | fossil_secure_free_page(zPwdBuffer, nPwdBuffer); |
| 114 | zPwdBuffer = 0; |
| 115 | nPwdBuffer = 0; |
| 116 | } |
| 117 | #endif |
| 118 | |
| 119 | /* |
| 120 | ** Scramble substitution matrix: |
| @@ -286,13 +288,12 @@ | |
| 288 | char *zPrompt = mprintf("\rpassword for %s: ", zUser); |
| 289 | char *zPw; |
| 290 | Blob x; |
| 291 | fossil_force_newline(); |
| 292 | prompt_for_password(zPrompt, &x, 0); |
| 293 | fossil_free(zPrompt); |
| 294 | zPw = blob_str(&x)/*transfer ownership*/; |
| 295 | return zPw; |
| 296 | } |
| 297 | |
| 298 | /* |
| 299 | ** Prompt the user to enter a single line of text. |
| 300 |
+42
| --- src/util.c | ||
| +++ src/util.c | ||
| @@ -899,10 +899,52 @@ | ||
| 899 | 899 | }else{ |
| 900 | 900 | fossil_print("%s\n", zPassword); |
| 901 | 901 | } |
| 902 | 902 | fossil_free(zPassword); |
| 903 | 903 | } |
| 904 | + | |
| 905 | +/* | |
| 906 | +** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4). | |
| 907 | +** | |
| 908 | +** Format: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx | |
| 909 | +** where M=4 and N=8, 9, a, or b (this leaves 122 random bits) | |
| 910 | +*/ | |
| 911 | +char* fossil_generate_uuid() { | |
| 912 | + static const char zDigits[] = "0123456789abcdef"; | |
| 913 | + unsigned char aBlob[16]; | |
| 914 | + unsigned char zStr[37]; | |
| 915 | + unsigned char *p = zStr; | |
| 916 | + int i, k; | |
| 917 | + | |
| 918 | + sqlite3_randomness(16, aBlob); | |
| 919 | + aBlob[6] = (aBlob[6]&0x0f) + 0x40; /* Version byte: 0100 xxxx */ | |
| 920 | + aBlob[8] = (aBlob[8]&0x3f) + 0x80; /* Variant byte: 1000 xxxx */ | |
| 921 | + | |
| 922 | + for(i=0, k=0x550; i<16; i++, k=k>>1){ | |
| 923 | + if( k&1 ){ | |
| 924 | + *p++ = '-'; /* Add a dash after byte 4, 6, 8, and 12 */ | |
| 925 | + } | |
| 926 | + *p++ = zDigits[aBlob[i]>>4]; | |
| 927 | + *p++ = zDigits[aBlob[i]&0xf]; | |
| 928 | + } | |
| 929 | + *p = 0; | |
| 930 | + | |
| 931 | + return fossil_strdup((char*)zStr); | |
| 932 | +} | |
| 933 | + | |
| 934 | +/* | |
| 935 | +** COMMAND: test-generate-uuid | |
| 936 | +** | |
| 937 | +** Usage: %fossil test-generate-uuid | |
| 938 | +** | |
| 939 | +** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4): | |
| 940 | +** | |
| 941 | +** xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx - where M=4 and N=8, 9, a, or b | |
| 942 | +*/ | |
| 943 | +void test_generate_uuid(void){ | |
| 944 | + fossil_print("%s\n", fossil_generate_uuid()); | |
| 945 | +} | |
| 904 | 946 | |
| 905 | 947 | /* |
| 906 | 948 | ** Return the number of decimal digits in a nonnegative integer. This is useful |
| 907 | 949 | ** when formatting text. |
| 908 | 950 | */ |
| 909 | 951 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -899,10 +899,52 @@ | |
| 899 | }else{ |
| 900 | fossil_print("%s\n", zPassword); |
| 901 | } |
| 902 | fossil_free(zPassword); |
| 903 | } |
| 904 | |
| 905 | /* |
| 906 | ** Return the number of decimal digits in a nonnegative integer. This is useful |
| 907 | ** when formatting text. |
| 908 | */ |
| 909 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -899,10 +899,52 @@ | |
| 899 | }else{ |
| 900 | fossil_print("%s\n", zPassword); |
| 901 | } |
| 902 | fossil_free(zPassword); |
| 903 | } |
| 904 | |
| 905 | /* |
| 906 | ** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4). |
| 907 | ** |
| 908 | ** Format: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx |
| 909 | ** where M=4 and N=8, 9, a, or b (this leaves 122 random bits) |
| 910 | */ |
| 911 | char* fossil_generate_uuid() { |
| 912 | static const char zDigits[] = "0123456789abcdef"; |
| 913 | unsigned char aBlob[16]; |
| 914 | unsigned char zStr[37]; |
| 915 | unsigned char *p = zStr; |
| 916 | int i, k; |
| 917 | |
| 918 | sqlite3_randomness(16, aBlob); |
| 919 | aBlob[6] = (aBlob[6]&0x0f) + 0x40; /* Version byte: 0100 xxxx */ |
| 920 | aBlob[8] = (aBlob[8]&0x3f) + 0x80; /* Variant byte: 1000 xxxx */ |
| 921 | |
| 922 | for(i=0, k=0x550; i<16; i++, k=k>>1){ |
| 923 | if( k&1 ){ |
| 924 | *p++ = '-'; /* Add a dash after byte 4, 6, 8, and 12 */ |
| 925 | } |
| 926 | *p++ = zDigits[aBlob[i]>>4]; |
| 927 | *p++ = zDigits[aBlob[i]&0xf]; |
| 928 | } |
| 929 | *p = 0; |
| 930 | |
| 931 | return fossil_strdup((char*)zStr); |
| 932 | } |
| 933 | |
| 934 | /* |
| 935 | ** COMMAND: test-generate-uuid |
| 936 | ** |
| 937 | ** Usage: %fossil test-generate-uuid |
| 938 | ** |
| 939 | ** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4): |
| 940 | ** |
| 941 | ** xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx - where M=4 and N=8, 9, a, or b |
| 942 | */ |
| 943 | void test_generate_uuid(void){ |
| 944 | fossil_print("%s\n", fossil_generate_uuid()); |
| 945 | } |
| 946 | |
| 947 | /* |
| 948 | ** Return the number of decimal digits in a nonnegative integer. This is useful |
| 949 | ** when formatting text. |
| 950 | */ |
| 951 |
-1
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -1333,11 +1333,10 @@ | ||
| 1333 | 1333 | pzUuidList = &zUuidList; |
| 1334 | 1334 | pnUuidList = &nUuidList; |
| 1335 | 1335 | } |
| 1336 | 1336 | if( g.syncInfo.zLoginCard ){ |
| 1337 | 1337 | /* Login card received via HTTP Cookie header */ |
| 1338 | - assert( g.syncInfo.fLoginCardMode && "Set via HTTP cookie" ); | |
| 1339 | 1338 | blob_zero(&xfer.line); |
| 1340 | 1339 | blob_append(&xfer.line, g.syncInfo.zLoginCard, -1); |
| 1341 | 1340 | xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, |
| 1342 | 1341 | count(xfer.aToken)); |
| 1343 | 1342 | fossil_free( g.syncInfo.zLoginCard ); |
| 1344 | 1343 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1333,11 +1333,10 @@ | |
| 1333 | pzUuidList = &zUuidList; |
| 1334 | pnUuidList = &nUuidList; |
| 1335 | } |
| 1336 | if( g.syncInfo.zLoginCard ){ |
| 1337 | /* Login card received via HTTP Cookie header */ |
| 1338 | assert( g.syncInfo.fLoginCardMode && "Set via HTTP cookie" ); |
| 1339 | blob_zero(&xfer.line); |
| 1340 | blob_append(&xfer.line, g.syncInfo.zLoginCard, -1); |
| 1341 | xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, |
| 1342 | count(xfer.aToken)); |
| 1343 | fossil_free( g.syncInfo.zLoginCard ); |
| 1344 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1333,11 +1333,10 @@ | |
| 1333 | pzUuidList = &zUuidList; |
| 1334 | pnUuidList = &nUuidList; |
| 1335 | } |
| 1336 | if( g.syncInfo.zLoginCard ){ |
| 1337 | /* Login card received via HTTP Cookie header */ |
| 1338 | blob_zero(&xfer.line); |
| 1339 | blob_append(&xfer.line, g.syncInfo.zLoginCard, -1); |
| 1340 | xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, |
| 1341 | count(xfer.aToken)); |
| 1342 | fossil_free( g.syncInfo.zLoginCard ); |
| 1343 |
+24
-18
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -406,16 +406,16 @@ | ||
| 406 | 406 | sqlite3_exec(p->db, |
| 407 | 407 | "PRAGMA page_size=512;" |
| 408 | 408 | "PRAGMA journal_mode = off;" |
| 409 | 409 | "PRAGMA cache_spill = off;" |
| 410 | 410 | "BEGIN;" |
| 411 | - "CREATE TABLE sqlar(" | |
| 412 | - "name TEXT PRIMARY KEY, -- name of the file\n" | |
| 413 | - "mode INT, -- access permissions\n" | |
| 414 | - "mtime INT, -- last modification time\n" | |
| 415 | - "sz INT, -- original file size\n" | |
| 416 | - "data BLOB -- compressed content\n" | |
| 411 | + "CREATE TABLE sqlar(\n" | |
| 412 | + " name TEXT PRIMARY KEY, -- name of the file\n" | |
| 413 | + " mode INT, -- access permissions\n" | |
| 414 | + " mtime INT, -- last modification time\n" | |
| 415 | + " sz INT, -- original file size\n" | |
| 416 | + " data BLOB -- compressed content\n" | |
| 417 | 417 | ");", 0, 0, 0 |
| 418 | 418 | ); |
| 419 | 419 | sqlite3_prepare(p->db, |
| 420 | 420 | "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1, |
| 421 | 421 | &p->pInsert, 0 |
| @@ -864,19 +864,11 @@ | ||
| 864 | 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | 865 | zOut = 0; |
| 866 | 866 | } |
| 867 | 867 | |
| 868 | 868 | if( zName==0 ){ |
| 869 | - zName = db_text("default-name", | |
| 870 | - "SELECT replace(%Q,' ','_') " | |
| 871 | - " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " | |
| 872 | - " || substr(blob.uuid, 1, 10)" | |
| 873 | - " FROM event, blob" | |
| 874 | - " WHERE event.objid=%d" | |
| 875 | - " AND blob.rid=%d", | |
| 876 | - db_get("project-name", "unnamed"), rid, rid | |
| 877 | - ); | |
| 869 | + zName = archive_base_name(rid); | |
| 878 | 870 | } |
| 879 | 871 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 880 | 872 | zName, pInclude, pExclude, listFlag); |
| 881 | 873 | glob_free(pInclude); |
| 882 | 874 | glob_free(pExclude); |
| @@ -995,10 +987,25 @@ | ||
| 995 | 987 | ** |
| 996 | 988 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 997 | 989 | ** comma-separated list of GLOB patterns, where each |
| 998 | 990 | ** pattern can optionally be quoted using ".." or '..'. |
| 999 | 991 | ** Any file matching both ex= and in= is excluded. |
| 992 | +** | |
| 993 | +** Robot Defenses: | |
| 994 | +** | |
| 995 | +** * If "zip" appears in the robot-restrict setting, then robots are | |
| 996 | +** not allowed to access this page. Suspected robots will be | |
| 997 | +** presented with a captcha. | |
| 998 | +** | |
| 999 | +** * If "zipX" appears in the robot-restrict setting, then robots are | |
| 1000 | +** restricted in the same way as with "zip", but with exceptions. | |
| 1001 | +** If the check-in for which an archive is requested is a leaf check-in | |
| 1002 | +** and if the robot-zip-leaf setting is true, then the request is | |
| 1003 | +** allowed. Or if the check-in has a tag that matches any of the | |
| 1004 | +** GLOB patterns on the list in the robot-zip-tag setting, then the | |
| 1005 | +** request is allowed. Otherwise, the usual robot defenses are | |
| 1006 | +** activated. | |
| 1000 | 1007 | */ |
| 1001 | 1008 | void baseline_zip_page(void){ |
| 1002 | 1009 | int rid; |
| 1003 | 1010 | const char *z; |
| 1004 | 1011 | char *zName, *zRid, *zKey; |
| @@ -1012,16 +1019,14 @@ | ||
| 1012 | 1019 | int eType = ARCHIVE_ZIP; /* Type of archive to generate */ |
| 1013 | 1020 | char *zType; /* Human-readable archive type */ |
| 1014 | 1021 | |
| 1015 | 1022 | login_check_credentials(); |
| 1016 | 1023 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1024 | + if( robot_restrict("zip") ) return; | |
| 1017 | 1025 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 1018 | 1026 | eType = ARCHIVE_SQLAR; |
| 1019 | 1027 | zType = "SQL"; |
| 1020 | - /* For some reason, SQL-archives are like catnip for robots. So | |
| 1021 | - ** don't allow them to be downloaded by user "nobody" */ | |
| 1022 | - if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; } | |
| 1023 | 1028 | }else{ |
| 1024 | 1029 | eType = ARCHIVE_ZIP; |
| 1025 | 1030 | zType = "ZIP"; |
| 1026 | 1031 | } |
| 1027 | 1032 | fossil_nice_default(); |
| @@ -1068,10 +1073,11 @@ | ||
| 1068 | 1073 | if( rid<=0 ){ |
| 1069 | 1074 | cgi_set_status(404, "Not Found"); |
| 1070 | 1075 | @ Not found |
| 1071 | 1076 | return; |
| 1072 | 1077 | } |
| 1078 | + if( robot_restrict_zip(rid) ) return; | |
| 1073 | 1079 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 1074 | 1080 | |
| 1075 | 1081 | /* Compute a unique key for the cache entry based on query parameters */ |
| 1076 | 1082 | blob_init(&cacheKey, 0, 0); |
| 1077 | 1083 | blob_appendf(&cacheKey, "/%s/%z", g.zPath, rid_to_uuid(rid)); |
| 1078 | 1084 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -406,16 +406,16 @@ | |
| 406 | sqlite3_exec(p->db, |
| 407 | "PRAGMA page_size=512;" |
| 408 | "PRAGMA journal_mode = off;" |
| 409 | "PRAGMA cache_spill = off;" |
| 410 | "BEGIN;" |
| 411 | "CREATE TABLE sqlar(" |
| 412 | "name TEXT PRIMARY KEY, -- name of the file\n" |
| 413 | "mode INT, -- access permissions\n" |
| 414 | "mtime INT, -- last modification time\n" |
| 415 | "sz INT, -- original file size\n" |
| 416 | "data BLOB -- compressed content\n" |
| 417 | ");", 0, 0, 0 |
| 418 | ); |
| 419 | sqlite3_prepare(p->db, |
| 420 | "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1, |
| 421 | &p->pInsert, 0 |
| @@ -864,19 +864,11 @@ | |
| 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | zOut = 0; |
| 866 | } |
| 867 | |
| 868 | if( zName==0 ){ |
| 869 | zName = db_text("default-name", |
| 870 | "SELECT replace(%Q,' ','_') " |
| 871 | " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " |
| 872 | " || substr(blob.uuid, 1, 10)" |
| 873 | " FROM event, blob" |
| 874 | " WHERE event.objid=%d" |
| 875 | " AND blob.rid=%d", |
| 876 | db_get("project-name", "unnamed"), rid, rid |
| 877 | ); |
| 878 | } |
| 879 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 880 | zName, pInclude, pExclude, listFlag); |
| 881 | glob_free(pInclude); |
| 882 | glob_free(pExclude); |
| @@ -995,10 +987,25 @@ | |
| 995 | ** |
| 996 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 997 | ** comma-separated list of GLOB patterns, where each |
| 998 | ** pattern can optionally be quoted using ".." or '..'. |
| 999 | ** Any file matching both ex= and in= is excluded. |
| 1000 | */ |
| 1001 | void baseline_zip_page(void){ |
| 1002 | int rid; |
| 1003 | const char *z; |
| 1004 | char *zName, *zRid, *zKey; |
| @@ -1012,16 +1019,14 @@ | |
| 1012 | int eType = ARCHIVE_ZIP; /* Type of archive to generate */ |
| 1013 | char *zType; /* Human-readable archive type */ |
| 1014 | |
| 1015 | login_check_credentials(); |
| 1016 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1017 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 1018 | eType = ARCHIVE_SQLAR; |
| 1019 | zType = "SQL"; |
| 1020 | /* For some reason, SQL-archives are like catnip for robots. So |
| 1021 | ** don't allow them to be downloaded by user "nobody" */ |
| 1022 | if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; } |
| 1023 | }else{ |
| 1024 | eType = ARCHIVE_ZIP; |
| 1025 | zType = "ZIP"; |
| 1026 | } |
| 1027 | fossil_nice_default(); |
| @@ -1068,10 +1073,11 @@ | |
| 1068 | if( rid<=0 ){ |
| 1069 | cgi_set_status(404, "Not Found"); |
| 1070 | @ Not found |
| 1071 | return; |
| 1072 | } |
| 1073 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 1074 | |
| 1075 | /* Compute a unique key for the cache entry based on query parameters */ |
| 1076 | blob_init(&cacheKey, 0, 0); |
| 1077 | blob_appendf(&cacheKey, "/%s/%z", g.zPath, rid_to_uuid(rid)); |
| 1078 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -406,16 +406,16 @@ | |
| 406 | sqlite3_exec(p->db, |
| 407 | "PRAGMA page_size=512;" |
| 408 | "PRAGMA journal_mode = off;" |
| 409 | "PRAGMA cache_spill = off;" |
| 410 | "BEGIN;" |
| 411 | "CREATE TABLE sqlar(\n" |
| 412 | " name TEXT PRIMARY KEY, -- name of the file\n" |
| 413 | " mode INT, -- access permissions\n" |
| 414 | " mtime INT, -- last modification time\n" |
| 415 | " sz INT, -- original file size\n" |
| 416 | " data BLOB -- compressed content\n" |
| 417 | ");", 0, 0, 0 |
| 418 | ); |
| 419 | sqlite3_prepare(p->db, |
| 420 | "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1, |
| 421 | &p->pInsert, 0 |
| @@ -864,19 +864,11 @@ | |
| 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | zOut = 0; |
| 866 | } |
| 867 | |
| 868 | if( zName==0 ){ |
| 869 | zName = archive_base_name(rid); |
| 870 | } |
| 871 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 872 | zName, pInclude, pExclude, listFlag); |
| 873 | glob_free(pInclude); |
| 874 | glob_free(pExclude); |
| @@ -995,10 +987,25 @@ | |
| 987 | ** |
| 988 | ** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
| 989 | ** comma-separated list of GLOB patterns, where each |
| 990 | ** pattern can optionally be quoted using ".." or '..'. |
| 991 | ** Any file matching both ex= and in= is excluded. |
| 992 | ** |
| 993 | ** Robot Defenses: |
| 994 | ** |
| 995 | ** * If "zip" appears in the robot-restrict setting, then robots are |
| 996 | ** not allowed to access this page. Suspected robots will be |
| 997 | ** presented with a captcha. |
| 998 | ** |
| 999 | ** * If "zipX" appears in the robot-restrict setting, then robots are |
| 1000 | ** restricted in the same way as with "zip", but with exceptions. |
| 1001 | ** If the check-in for which an archive is requested is a leaf check-in |
| 1002 | ** and if the robot-zip-leaf setting is true, then the request is |
| 1003 | ** allowed. Or if the check-in has a tag that matches any of the |
| 1004 | ** GLOB patterns on the list in the robot-zip-tag setting, then the |
| 1005 | ** request is allowed. Otherwise, the usual robot defenses are |
| 1006 | ** activated. |
| 1007 | */ |
| 1008 | void baseline_zip_page(void){ |
| 1009 | int rid; |
| 1010 | const char *z; |
| 1011 | char *zName, *zRid, *zKey; |
| @@ -1012,16 +1019,14 @@ | |
| 1019 | int eType = ARCHIVE_ZIP; /* Type of archive to generate */ |
| 1020 | char *zType; /* Human-readable archive type */ |
| 1021 | |
| 1022 | login_check_credentials(); |
| 1023 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1024 | if( robot_restrict("zip") ) return; |
| 1025 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 1026 | eType = ARCHIVE_SQLAR; |
| 1027 | zType = "SQL"; |
| 1028 | }else{ |
| 1029 | eType = ARCHIVE_ZIP; |
| 1030 | zType = "ZIP"; |
| 1031 | } |
| 1032 | fossil_nice_default(); |
| @@ -1068,10 +1073,11 @@ | |
| 1073 | if( rid<=0 ){ |
| 1074 | cgi_set_status(404, "Not Found"); |
| 1075 | @ Not found |
| 1076 | return; |
| 1077 | } |
| 1078 | if( robot_restrict_zip(rid) ) return; |
| 1079 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 1080 | |
| 1081 | /* Compute a unique key for the cache entry based on query parameters */ |
| 1082 | blob_init(&cacheKey, 0, 0); |
| 1083 | blob_appendf(&cacheKey, "/%s/%z", g.zPath, rid_to_uuid(rid)); |
| 1084 |
-139
| --- test/commit-warning.test | ||
| +++ test/commit-warning.test | ||
| @@ -160,146 +160,7 @@ | ||
| 160 | 160 | 1}]]} |
| 161 | 161 | |
| 162 | 162 | |
| 163 | 163 | ############################################################################### |
| 164 | 164 | |
| 165 | -# TODO: Change to a collection of test-case crafted files | |
| 166 | -# rather than depend on this list of files that will | |
| 167 | -# be fragile as development progresses. | |
| 168 | -# | |
| 169 | -# Unless the real goal of this test is to document a collection | |
| 170 | -# of source files that MUST NEVER BE TEXT. | |
| 171 | -# | |
| 172 | -test_block_in_checkout pre-commit-warnings-fossil-1 { | |
| 173 | - fossil test-commit-warning --no-settings | |
| 174 | -} { | |
| 175 | - test pre-commit-warnings-fossil-1 {[normalize_result] eq \ | |
| 176 | - [subst -nocommands -novariables [string trim { | |
| 177 | -1\tcompat/zlib/contrib/blast/test.pk\tbinary data | |
| 178 | -1\tcompat/zlib/contrib/dotzlib/DotZLib.build\tCR/LF line endings | |
| 179 | -1\tcompat/zlib/contrib/dotzlib/DotZLib.chm\tbinary data | |
| 180 | -1\tcompat/zlib/contrib/dotzlib/DotZLib.sln\tCR/LF line endings | |
| 181 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs\tCR/LF line endings | |
| 182 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs\tinvalid UTF-8 | |
| 183 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs\tinvalid UTF-8 | |
| 184 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs\tinvalid UTF-8 | |
| 185 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/Deflater.cs\tinvalid UTF-8 | |
| 186 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs\tinvalid UTF-8 | |
| 187 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj\tCR/LF line endings | |
| 188 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs\tinvalid UTF-8 | |
| 189 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/Inflater.cs\tinvalid UTF-8 | |
| 190 | -1\tcompat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs\tCR/LF line endings | |
| 191 | -1\tcompat/zlib/contrib/dotzlib/LICENSE_1_0.txt\tCR/LF line endings | |
| 192 | -1\tcompat/zlib/contrib/dotzlib/readme.txt\tCR/LF line endings | |
| 193 | -1\tcompat/zlib/contrib/gcc_gvmat64/gvmat64.S\tCR/LF line endings | |
| 194 | -1\tcompat/zlib/contrib/puff/zeros.raw\tbinary data | |
| 195 | -1\tcompat/zlib/contrib/testzlib/testzlib.c\tCR/LF line endings | |
| 196 | -1\tcompat/zlib/contrib/testzlib/testzlib.txt\tCR/LF line endings | |
| 197 | -1\tcompat/zlib/contrib/vstudio/readme.txt\tCR/LF line endings | |
| 198 | -1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj\tCR/LF line endings | |
| 199 | -1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters\tCR/LF line endings | |
| 200 | -1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj\tCR/LF line endings | |
| 201 | -1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters\tCR/LF line endings | |
| 202 | -1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj\tCR/LF line endings | |
| 203 | -1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters\tCR/LF line endings | |
| 204 | -1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj\tCR/LF line endings | |
| 205 | -1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters\tCR/LF line endings | |
| 206 | -1\tcompat/zlib/contrib/vstudio/vc10/zlib.rc\tCR/LF line endings | |
| 207 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj\tCR/LF line endings | |
| 208 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters\tCR/LF line endings | |
| 209 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.def\tCR/LF line endings | |
| 210 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.sln\tCR/LF line endings | |
| 211 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj\tCR/LF line endings | |
| 212 | -1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters\tCR/LF line endings | |
| 213 | -1\tcompat/zlib/contrib/vstudio/vc11/miniunz.vcxproj\tCR/LF line endings | |
| 214 | -1\tcompat/zlib/contrib/vstudio/vc11/minizip.vcxproj\tCR/LF line endings | |
| 215 | -1\tcompat/zlib/contrib/vstudio/vc11/testzlib.vcxproj\tCR/LF line endings | |
| 216 | -1\tcompat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj\tCR/LF line endings | |
| 217 | -1\tcompat/zlib/contrib/vstudio/vc11/zlib.rc\tCR/LF line endings | |
| 218 | -1\tcompat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj\tCR/LF line endings | |
| 219 | -1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.def\tCR/LF line endings | |
| 220 | -1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.sln\tCR/LF line endings | |
| 221 | -1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj\tCR/LF line endings | |
| 222 | -1\tcompat/zlib/contrib/vstudio/vc12/zlibvc.def\tCR/LF line endings | |
| 223 | -1\tcompat/zlib/contrib/vstudio/vc14/zlibvc.def\tCR/LF line endings | |
| 224 | -1\tcompat/zlib/contrib/vstudio/vc9/miniunz.vcproj\tCR/LF line endings | |
| 225 | -1\tcompat/zlib/contrib/vstudio/vc9/minizip.vcproj\tCR/LF line endings | |
| 226 | -1\tcompat/zlib/contrib/vstudio/vc9/testzlib.vcproj\tCR/LF line endings | |
| 227 | -1\tcompat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj\tCR/LF line endings | |
| 228 | -1\tcompat/zlib/contrib/vstudio/vc9/zlib.rc\tCR/LF line endings | |
| 229 | -1\tcompat/zlib/contrib/vstudio/vc9/zlibstat.vcproj\tCR/LF line endings | |
| 230 | -1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.def\tCR/LF line endings | |
| 231 | -1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.sln\tCR/LF line endings | |
| 232 | -1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.vcproj\tCR/LF line endings | |
| 233 | -1\tcompat/zlib/win32/zlib.def\tCR/LF line endings | |
| 234 | -1\tcompat/zlib/zlib.3.pdf\tbinary data | |
| 235 | -1\tcompat/zlib/zlib.map\tCR/LF line endings | |
| 236 | -1\textsrc/pikchr.wasm\tbinary data | |
| 237 | -1\tskins/blitz/arrow_project.png\tbinary data | |
| 238 | -1\tskins/blitz/dir.png\tbinary data | |
| 239 | -1\tskins/blitz/file.png\tbinary data | |
| 240 | -1\tskins/blitz/fossil_100.png\tbinary data | |
| 241 | -1\tskins/blitz/fossil_80_reversed_darkcyan.png\tbinary data | |
| 242 | -1\tskins/blitz/fossil_80_reversed_darkcyan_text.png\tbinary data | |
| 243 | -1\tskins/blitz/rss_20.png\tbinary data | |
| 244 | -1\tsrc/alerts/bflat2.wav\tbinary data | |
| 245 | -1\tsrc/alerts/bflat3.wav\tbinary data | |
| 246 | -1\tsrc/alerts/bloop.wav\tbinary data | |
| 247 | -1\tsrc/alerts/plunk.wav\tbinary data | |
| 248 | -1\tsrc/sounds/0.wav\tbinary data | |
| 249 | -1\tsrc/sounds/1.wav\tbinary data | |
| 250 | -1\tsrc/sounds/2.wav\tbinary data | |
| 251 | -1\tsrc/sounds/3.wav\tbinary data | |
| 252 | -1\tsrc/sounds/4.wav\tbinary data | |
| 253 | -1\tsrc/sounds/5.wav\tbinary data | |
| 254 | -1\tsrc/sounds/6.wav\tbinary data | |
| 255 | -1\tsrc/sounds/7.wav\tbinary data | |
| 256 | -1\tsrc/sounds/8.wav\tbinary data | |
| 257 | -1\tsrc/sounds/9.wav\tbinary data | |
| 258 | -1\tsrc/sounds/a.wav\tbinary data | |
| 259 | -1\tsrc/sounds/b.wav\tbinary data | |
| 260 | -1\tsrc/sounds/c.wav\tbinary data | |
| 261 | -1\tsrc/sounds/d.wav\tbinary data | |
| 262 | -1\tsrc/sounds/e.wav\tbinary data | |
| 263 | -1\tsrc/sounds/f.wav\tbinary data | |
| 264 | -1\ttest/th1-docs-input.txt\tCR/LF line endings | |
| 265 | -1\ttest/th1-hooks-input.txt\tCR/LF line endings | |
| 266 | -1\ttest/utf16be.txt\tUnicode | |
| 267 | -1\ttest/utf16le.txt\tUnicode | |
| 268 | -1\twin/buildmsvc.bat\tCR/LF line endings | |
| 269 | -1\twin/fossil.ico\tbinary data | |
| 270 | -1\twin/fossil.rc\tinvalid UTF-8 | |
| 271 | -1\twww/apple-touch-icon.png\tbinary data | |
| 272 | -1\twww/background.jpg\tbinary data | |
| 273 | -1\twww/build-icons/linux.gif\tbinary data | |
| 274 | -1\twww/build-icons/linux64.gif\tbinary data | |
| 275 | -1\twww/build-icons/mac.gif\tbinary data | |
| 276 | -1\twww/build-icons/openbsd.gif\tbinary data | |
| 277 | -1\twww/build-icons/src.gif\tbinary data | |
| 278 | -1\twww/build-icons/win32.gif\tbinary data | |
| 279 | -1\twww/copyright-release.pdf\tbinary data | |
| 280 | -1\twww/encode1.gif\tbinary data | |
| 281 | -1\twww/encode2.gif\tbinary data | |
| 282 | -1\twww/encode3.gif\tbinary data | |
| 283 | -1\twww/encode4.gif\tbinary data | |
| 284 | -1\twww/encode5.gif\tbinary data | |
| 285 | -1\twww/encode6.gif\tbinary data | |
| 286 | -1\twww/encode7.gif\tbinary data | |
| 287 | -1\twww/encode8.gif\tbinary data | |
| 288 | -1\twww/encode9.gif\tbinary data | |
| 289 | -1\twww/fossil.gif\tbinary data | |
| 290 | -1\twww/fossil2.gif\tbinary data | |
| 291 | -1\twww/fossil3.gif\tbinary data | |
| 292 | -1\twww/fossil_logo_small.gif\tbinary data | |
| 293 | -1\twww/fossil_logo_small2.gif\tbinary data | |
| 294 | -1\twww/fossil_logo_small3.gif\tbinary data | |
| 295 | -1\twww/server/windows/cgi-bin-perm.png\tbinary data | |
| 296 | -1\twww/server/windows/cgi-exec-perm.png\tbinary data | |
| 297 | -1\twww/server/windows/cgi-install-iis.png\tbinary data | |
| 298 | -1\twww/server/windows/cgi-script-map.png\tbinary data | |
| 299 | -1\twww/xkcd-git.gif\tbinary data | |
| 300 | -1}]]} | |
| 301 | -} | |
| 302 | - | |
| 303 | -############################################################################### | |
| 304 | 165 | |
| 305 | 166 | test_cleanup |
| 306 | 167 |
| --- test/commit-warning.test | |
| +++ test/commit-warning.test | |
| @@ -160,146 +160,7 @@ | |
| 160 | 1}]]} |
| 161 | |
| 162 | |
| 163 | ############################################################################### |
| 164 | |
| 165 | # TODO: Change to a collection of test-case crafted files |
| 166 | # rather than depend on this list of files that will |
| 167 | # be fragile as development progresses. |
| 168 | # |
| 169 | # Unless the real goal of this test is to document a collection |
| 170 | # of source files that MUST NEVER BE TEXT. |
| 171 | # |
| 172 | test_block_in_checkout pre-commit-warnings-fossil-1 { |
| 173 | fossil test-commit-warning --no-settings |
| 174 | } { |
| 175 | test pre-commit-warnings-fossil-1 {[normalize_result] eq \ |
| 176 | [subst -nocommands -novariables [string trim { |
| 177 | 1\tcompat/zlib/contrib/blast/test.pk\tbinary data |
| 178 | 1\tcompat/zlib/contrib/dotzlib/DotZLib.build\tCR/LF line endings |
| 179 | 1\tcompat/zlib/contrib/dotzlib/DotZLib.chm\tbinary data |
| 180 | 1\tcompat/zlib/contrib/dotzlib/DotZLib.sln\tCR/LF line endings |
| 181 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs\tCR/LF line endings |
| 182 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs\tinvalid UTF-8 |
| 183 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs\tinvalid UTF-8 |
| 184 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs\tinvalid UTF-8 |
| 185 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/Deflater.cs\tinvalid UTF-8 |
| 186 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs\tinvalid UTF-8 |
| 187 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj\tCR/LF line endings |
| 188 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs\tinvalid UTF-8 |
| 189 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/Inflater.cs\tinvalid UTF-8 |
| 190 | 1\tcompat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs\tCR/LF line endings |
| 191 | 1\tcompat/zlib/contrib/dotzlib/LICENSE_1_0.txt\tCR/LF line endings |
| 192 | 1\tcompat/zlib/contrib/dotzlib/readme.txt\tCR/LF line endings |
| 193 | 1\tcompat/zlib/contrib/gcc_gvmat64/gvmat64.S\tCR/LF line endings |
| 194 | 1\tcompat/zlib/contrib/puff/zeros.raw\tbinary data |
| 195 | 1\tcompat/zlib/contrib/testzlib/testzlib.c\tCR/LF line endings |
| 196 | 1\tcompat/zlib/contrib/testzlib/testzlib.txt\tCR/LF line endings |
| 197 | 1\tcompat/zlib/contrib/vstudio/readme.txt\tCR/LF line endings |
| 198 | 1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj\tCR/LF line endings |
| 199 | 1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters\tCR/LF line endings |
| 200 | 1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj\tCR/LF line endings |
| 201 | 1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters\tCR/LF line endings |
| 202 | 1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj\tCR/LF line endings |
| 203 | 1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters\tCR/LF line endings |
| 204 | 1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj\tCR/LF line endings |
| 205 | 1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters\tCR/LF line endings |
| 206 | 1\tcompat/zlib/contrib/vstudio/vc10/zlib.rc\tCR/LF line endings |
| 207 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj\tCR/LF line endings |
| 208 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters\tCR/LF line endings |
| 209 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.def\tCR/LF line endings |
| 210 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.sln\tCR/LF line endings |
| 211 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj\tCR/LF line endings |
| 212 | 1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters\tCR/LF line endings |
| 213 | 1\tcompat/zlib/contrib/vstudio/vc11/miniunz.vcxproj\tCR/LF line endings |
| 214 | 1\tcompat/zlib/contrib/vstudio/vc11/minizip.vcxproj\tCR/LF line endings |
| 215 | 1\tcompat/zlib/contrib/vstudio/vc11/testzlib.vcxproj\tCR/LF line endings |
| 216 | 1\tcompat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj\tCR/LF line endings |
| 217 | 1\tcompat/zlib/contrib/vstudio/vc11/zlib.rc\tCR/LF line endings |
| 218 | 1\tcompat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj\tCR/LF line endings |
| 219 | 1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.def\tCR/LF line endings |
| 220 | 1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.sln\tCR/LF line endings |
| 221 | 1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj\tCR/LF line endings |
| 222 | 1\tcompat/zlib/contrib/vstudio/vc12/zlibvc.def\tCR/LF line endings |
| 223 | 1\tcompat/zlib/contrib/vstudio/vc14/zlibvc.def\tCR/LF line endings |
| 224 | 1\tcompat/zlib/contrib/vstudio/vc9/miniunz.vcproj\tCR/LF line endings |
| 225 | 1\tcompat/zlib/contrib/vstudio/vc9/minizip.vcproj\tCR/LF line endings |
| 226 | 1\tcompat/zlib/contrib/vstudio/vc9/testzlib.vcproj\tCR/LF line endings |
| 227 | 1\tcompat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj\tCR/LF line endings |
| 228 | 1\tcompat/zlib/contrib/vstudio/vc9/zlib.rc\tCR/LF line endings |
| 229 | 1\tcompat/zlib/contrib/vstudio/vc9/zlibstat.vcproj\tCR/LF line endings |
| 230 | 1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.def\tCR/LF line endings |
| 231 | 1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.sln\tCR/LF line endings |
| 232 | 1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.vcproj\tCR/LF line endings |
| 233 | 1\tcompat/zlib/win32/zlib.def\tCR/LF line endings |
| 234 | 1\tcompat/zlib/zlib.3.pdf\tbinary data |
| 235 | 1\tcompat/zlib/zlib.map\tCR/LF line endings |
| 236 | 1\textsrc/pikchr.wasm\tbinary data |
| 237 | 1\tskins/blitz/arrow_project.png\tbinary data |
| 238 | 1\tskins/blitz/dir.png\tbinary data |
| 239 | 1\tskins/blitz/file.png\tbinary data |
| 240 | 1\tskins/blitz/fossil_100.png\tbinary data |
| 241 | 1\tskins/blitz/fossil_80_reversed_darkcyan.png\tbinary data |
| 242 | 1\tskins/blitz/fossil_80_reversed_darkcyan_text.png\tbinary data |
| 243 | 1\tskins/blitz/rss_20.png\tbinary data |
| 244 | 1\tsrc/alerts/bflat2.wav\tbinary data |
| 245 | 1\tsrc/alerts/bflat3.wav\tbinary data |
| 246 | 1\tsrc/alerts/bloop.wav\tbinary data |
| 247 | 1\tsrc/alerts/plunk.wav\tbinary data |
| 248 | 1\tsrc/sounds/0.wav\tbinary data |
| 249 | 1\tsrc/sounds/1.wav\tbinary data |
| 250 | 1\tsrc/sounds/2.wav\tbinary data |
| 251 | 1\tsrc/sounds/3.wav\tbinary data |
| 252 | 1\tsrc/sounds/4.wav\tbinary data |
| 253 | 1\tsrc/sounds/5.wav\tbinary data |
| 254 | 1\tsrc/sounds/6.wav\tbinary data |
| 255 | 1\tsrc/sounds/7.wav\tbinary data |
| 256 | 1\tsrc/sounds/8.wav\tbinary data |
| 257 | 1\tsrc/sounds/9.wav\tbinary data |
| 258 | 1\tsrc/sounds/a.wav\tbinary data |
| 259 | 1\tsrc/sounds/b.wav\tbinary data |
| 260 | 1\tsrc/sounds/c.wav\tbinary data |
| 261 | 1\tsrc/sounds/d.wav\tbinary data |
| 262 | 1\tsrc/sounds/e.wav\tbinary data |
| 263 | 1\tsrc/sounds/f.wav\tbinary data |
| 264 | 1\ttest/th1-docs-input.txt\tCR/LF line endings |
| 265 | 1\ttest/th1-hooks-input.txt\tCR/LF line endings |
| 266 | 1\ttest/utf16be.txt\tUnicode |
| 267 | 1\ttest/utf16le.txt\tUnicode |
| 268 | 1\twin/buildmsvc.bat\tCR/LF line endings |
| 269 | 1\twin/fossil.ico\tbinary data |
| 270 | 1\twin/fossil.rc\tinvalid UTF-8 |
| 271 | 1\twww/apple-touch-icon.png\tbinary data |
| 272 | 1\twww/background.jpg\tbinary data |
| 273 | 1\twww/build-icons/linux.gif\tbinary data |
| 274 | 1\twww/build-icons/linux64.gif\tbinary data |
| 275 | 1\twww/build-icons/mac.gif\tbinary data |
| 276 | 1\twww/build-icons/openbsd.gif\tbinary data |
| 277 | 1\twww/build-icons/src.gif\tbinary data |
| 278 | 1\twww/build-icons/win32.gif\tbinary data |
| 279 | 1\twww/copyright-release.pdf\tbinary data |
| 280 | 1\twww/encode1.gif\tbinary data |
| 281 | 1\twww/encode2.gif\tbinary data |
| 282 | 1\twww/encode3.gif\tbinary data |
| 283 | 1\twww/encode4.gif\tbinary data |
| 284 | 1\twww/encode5.gif\tbinary data |
| 285 | 1\twww/encode6.gif\tbinary data |
| 286 | 1\twww/encode7.gif\tbinary data |
| 287 | 1\twww/encode8.gif\tbinary data |
| 288 | 1\twww/encode9.gif\tbinary data |
| 289 | 1\twww/fossil.gif\tbinary data |
| 290 | 1\twww/fossil2.gif\tbinary data |
| 291 | 1\twww/fossil3.gif\tbinary data |
| 292 | 1\twww/fossil_logo_small.gif\tbinary data |
| 293 | 1\twww/fossil_logo_small2.gif\tbinary data |
| 294 | 1\twww/fossil_logo_small3.gif\tbinary data |
| 295 | 1\twww/server/windows/cgi-bin-perm.png\tbinary data |
| 296 | 1\twww/server/windows/cgi-exec-perm.png\tbinary data |
| 297 | 1\twww/server/windows/cgi-install-iis.png\tbinary data |
| 298 | 1\twww/server/windows/cgi-script-map.png\tbinary data |
| 299 | 1\twww/xkcd-git.gif\tbinary data |
| 300 | 1}]]} |
| 301 | } |
| 302 | |
| 303 | ############################################################################### |
| 304 | |
| 305 | test_cleanup |
| 306 |
| --- test/commit-warning.test | |
| +++ test/commit-warning.test | |
| @@ -160,146 +160,7 @@ | |
| 160 | 1}]]} |
| 161 | |
| 162 | |
| 163 | ############################################################################### |
| 164 | |
| 165 | |
| 166 | test_cleanup |
| 167 |
+1
-1
| --- test/json.test | ||
| +++ test/json.test | ||
| @@ -176,11 +176,11 @@ | ||
| 176 | 176 | test_dict_keys $testname [dict get $::JR payload] $okfields $badfields |
| 177 | 177 | } |
| 178 | 178 | |
| 179 | 179 | #### VERSION AKA HAI |
| 180 | 180 | |
| 181 | -# The JSON API generally assumes we have a respository, so let it have one. | |
| 181 | +# The JSON API generally assumes we have a repository, so let it have one. | |
| 182 | 182 | |
| 183 | 183 | # Set FOSSIL_USER to ensure consistent results in "json user list" |
| 184 | 184 | set _fossil_user "" |
| 185 | 185 | if [info exists env(FOSSIL_USER)] { |
| 186 | 186 | set _fossil_user $env(FOSSIL_USER) |
| 187 | 187 |
| --- test/json.test | |
| +++ test/json.test | |
| @@ -176,11 +176,11 @@ | |
| 176 | test_dict_keys $testname [dict get $::JR payload] $okfields $badfields |
| 177 | } |
| 178 | |
| 179 | #### VERSION AKA HAI |
| 180 | |
| 181 | # The JSON API generally assumes we have a respository, so let it have one. |
| 182 | |
| 183 | # Set FOSSIL_USER to ensure consistent results in "json user list" |
| 184 | set _fossil_user "" |
| 185 | if [info exists env(FOSSIL_USER)] { |
| 186 | set _fossil_user $env(FOSSIL_USER) |
| 187 |
| --- test/json.test | |
| +++ test/json.test | |
| @@ -176,11 +176,11 @@ | |
| 176 | test_dict_keys $testname [dict get $::JR payload] $okfields $badfields |
| 177 | } |
| 178 | |
| 179 | #### VERSION AKA HAI |
| 180 | |
| 181 | # The JSON API generally assumes we have a repository, so let it have one. |
| 182 | |
| 183 | # Set FOSSIL_USER to ensure consistent results in "json user list" |
| 184 | set _fossil_user "" |
| 185 | if [info exists env(FOSSIL_USER)] { |
| 186 | set _fossil_user $env(FOSSIL_USER) |
| 187 |
+1
-1
| --- test/set-manifest.test | ||
| +++ test/set-manifest.test | ||
| @@ -38,11 +38,11 @@ | ||
| 38 | 38 | if {[catch {package require sha1}] != 0} { |
| 39 | 39 | puts "The \"sha1\" package is not available." |
| 40 | 40 | test_cleanup_then_return |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | -# We need a respository, so let it have one. | |
| 43 | +# We need a repository, so let it have one. | |
| 44 | 44 | test_setup |
| 45 | 45 | |
| 46 | 46 | #### Verify classic behavior of the manifest setting |
| 47 | 47 | |
| 48 | 48 | # Setting is off by default, and there are no extra files. |
| 49 | 49 | |
| 50 | 50 | DELETED test/settings.test |
| 51 | 51 | ADDED test/settings.test.off |
| --- test/set-manifest.test | |
| +++ test/set-manifest.test | |
| @@ -38,11 +38,11 @@ | |
| 38 | if {[catch {package require sha1}] != 0} { |
| 39 | puts "The \"sha1\" package is not available." |
| 40 | test_cleanup_then_return |
| 41 | } |
| 42 | |
| 43 | # We need a respository, so let it have one. |
| 44 | test_setup |
| 45 | |
| 46 | #### Verify classic behavior of the manifest setting |
| 47 | |
| 48 | # Setting is off by default, and there are no extra files. |
| 49 | |
| 50 | ELETED test/settings.test |
| 51 | DDED test/settings.test.off |
| --- test/set-manifest.test | |
| +++ test/set-manifest.test | |
| @@ -38,11 +38,11 @@ | |
| 38 | if {[catch {package require sha1}] != 0} { |
| 39 | puts "The \"sha1\" package is not available." |
| 40 | test_cleanup_then_return |
| 41 | } |
| 42 | |
| 43 | # We need a repository, so let it have one. |
| 44 | test_setup |
| 45 | |
| 46 | #### Verify classic behavior of the manifest setting |
| 47 | |
| 48 | # Setting is off by default, and there are no extra files. |
| 49 | |
| 50 | ELETED test/settings.test |
| 51 | DDED test/settings.test.off |
D
test/settings.test
-138
| --- a/test/settings.test | ||
| +++ b/test/settings.test | ||
| @@ -1,138 +0,0 @@ | ||
| 1 | -# | |
| 2 | -# Copyright (c) 2016 D. Richard Hipp | |
| 3 | -# | |
| 4 | -# This program is free software; you can redistribute it and/or | |
| 5 | -# modify it under the terms of the Simplified BSD License (also | |
| 6 | -# known as the "2-Clause License" or "FreeBSD License".) | |
| 7 | -# | |
| 8 | -# This program is distributed in the hope that it will be useful, | |
| 9 | -# but without any warranty; without even the implied warranty of | |
| 10 | -# merchantability or fitness for a particular purpose. | |
| 11 | -# | |
| 12 | -# Author contact information: | |
| 13 | -# [email protected] | |
| 14 | -# http://www.hwaci.com/drh/ | |
| 15 | -# | |
| 16 | -############################################################################ | |
| 17 | -# | |
| 18 | -# The "settings" and "unset" commands. | |
| 19 | -# | |
| 20 | - | |
| 21 | -set path [file dirname [info script]]; test_setup | |
| 22 | - | |
| 23 | -############################################################################### | |
| 24 | -# | |
| 25 | -# Complete syntax as tested: | |
| 26 | -# | |
| 27 | -# fossil settings ?PROPERTY? ?VALUE? ?OPTIONS? | |
| 28 | -# fossil unset PROPERTY ?OPTIONS? | |
| 29 | -# | |
| 30 | -# Where the only supported options are "--global" and "--exact". | |
| 31 | -# | |
| 32 | -############################################################################### | |
| 33 | -# | |
| 34 | -# NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list | |
| 35 | -# of settings to test and needs to be manually updated when new settings | |
| 36 | -# are added. | |
| 37 | -# | |
| 38 | -############################################################################### | |
| 39 | -# | |
| 40 | -# NOTE: The [extract_setting_names] procedure extracts the list of setting | |
| 41 | -# names from the line-ending normalized output of the "fossil settings" | |
| 42 | -# command. It assumes that a setting name must begin with a lowercase | |
| 43 | -# letter. It also assumes that any output lines that start with a | |
| 44 | -# lowercase letter contain a setting name starting at that same point. | |
| 45 | -# | |
| 46 | -proc extract_setting_names { data } { | |
| 47 | - set names [list] | |
| 48 | - | |
| 49 | - foreach {dummy name} [regexp \ | |
| 50 | - -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] { | |
| 51 | - lappend names $name | |
| 52 | - } | |
| 53 | - | |
| 54 | - return $names | |
| 55 | -} | |
| 56 | - | |
| 57 | -############################################################################### | |
| 58 | - | |
| 59 | -set all_settings [get_all_settings] | |
| 60 | - | |
| 61 | -fossil settings | |
| 62 | -set local_settings [extract_setting_names [normalize_result_no_trim]] | |
| 63 | - | |
| 64 | -fossil settings --global | |
| 65 | -set global_settings [extract_setting_names [normalize_result_no_trim]] | |
| 66 | - | |
| 67 | -foreach name $all_settings { | |
| 68 | - test settings-have-local-$name { | |
| 69 | - [lsearch -exact $local_settings $name] != -1 | |
| 70 | - } | |
| 71 | - | |
| 72 | - test settings-have-global-$name { | |
| 73 | - [lsearch -exact $global_settings $name] != -1 | |
| 74 | - } | |
| 75 | -} | |
| 76 | - | |
| 77 | -foreach name $local_settings { | |
| 78 | - test settings-valid-local-$name { | |
| 79 | - [lsearch -exact $all_settings $name] != -1 | |
| 80 | - } | |
| 81 | -} | |
| 82 | - | |
| 83 | -foreach name $global_settings { | |
| 84 | - test settings-valid-global-$name { | |
| 85 | - [lsearch -exact $all_settings $name] != -1 | |
| 86 | - } | |
| 87 | -} | |
| 88 | - | |
| 89 | -############################################################################### | |
| 90 | - | |
| 91 | -set pattern(1) {^%name%$} | |
| 92 | -set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$} | |
| 93 | - | |
| 94 | -foreach name $all_settings { | |
| 95 | - fossil settings $name --exact | |
| 96 | - set data [normalize_result] | |
| 97 | - | |
| 98 | - test settings-query-local-$name { | |
| 99 | - [regexp -- [string map [list %name% $name] $pattern(1)] $data] || | |
| 100 | - [regexp -- [string map [list %name% $name] $pattern(2)] $data] | |
| 101 | - } | |
| 102 | - | |
| 103 | - if {$name eq "manifest"} { | |
| 104 | - fossil settings $name --exact --global -expectError | |
| 105 | - } else { | |
| 106 | - fossil settings $name --exact --global | |
| 107 | - } | |
| 108 | - set data [normalize_result] | |
| 109 | - | |
| 110 | - if {$name eq "manifest"} { | |
| 111 | - test settings-query-global-$name { | |
| 112 | - $data eq "cannot set 'manifest' globally" | |
| 113 | - } | |
| 114 | - } else { | |
| 115 | - test settings-query-global-$name { | |
| 116 | - [regexp -- [string map [list %name% $name] $pattern(1)] $data] || | |
| 117 | - [regexp -- [string map [list %name% $name] $pattern(2)] $data] | |
| 118 | - } | |
| 119 | - } | |
| 120 | -} | |
| 121 | - | |
| 122 | -############################################################################### | |
| 123 | - | |
| 124 | -fossil settings bad-setting -expectError | |
| 125 | - | |
| 126 | -test settings-query-bad-local { | |
| 127 | - [normalize_result] eq "no such setting: bad-setting" | |
| 128 | -} | |
| 129 | - | |
| 130 | -fossil settings bad-setting --global -expectError | |
| 131 | - | |
| 132 | -test settings-query-bad-global { | |
| 133 | - [normalize_result] eq "no such setting: bad-setting" | |
| 134 | -} | |
| 135 | - | |
| 136 | -############################################################################### | |
| 137 | - | |
| 138 | -test_cleanup |
| --- a/test/settings.test | |
| +++ b/test/settings.test | |
| @@ -1,138 +0,0 @@ | |
| 1 | # |
| 2 | # Copyright (c) 2016 D. Richard Hipp |
| 3 | # |
| 4 | # This program is free software; you can redistribute it and/or |
| 5 | # modify it under the terms of the Simplified BSD License (also |
| 6 | # known as the "2-Clause License" or "FreeBSD License".) |
| 7 | # |
| 8 | # This program is distributed in the hope that it will be useful, |
| 9 | # but without any warranty; without even the implied warranty of |
| 10 | # merchantability or fitness for a particular purpose. |
| 11 | # |
| 12 | # Author contact information: |
| 13 | # [email protected] |
| 14 | # http://www.hwaci.com/drh/ |
| 15 | # |
| 16 | ############################################################################ |
| 17 | # |
| 18 | # The "settings" and "unset" commands. |
| 19 | # |
| 20 | |
| 21 | set path [file dirname [info script]]; test_setup |
| 22 | |
| 23 | ############################################################################### |
| 24 | # |
| 25 | # Complete syntax as tested: |
| 26 | # |
| 27 | # fossil settings ?PROPERTY? ?VALUE? ?OPTIONS? |
| 28 | # fossil unset PROPERTY ?OPTIONS? |
| 29 | # |
| 30 | # Where the only supported options are "--global" and "--exact". |
| 31 | # |
| 32 | ############################################################################### |
| 33 | # |
| 34 | # NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list |
| 35 | # of settings to test and needs to be manually updated when new settings |
| 36 | # are added. |
| 37 | # |
| 38 | ############################################################################### |
| 39 | # |
| 40 | # NOTE: The [extract_setting_names] procedure extracts the list of setting |
| 41 | # names from the line-ending normalized output of the "fossil settings" |
| 42 | # command. It assumes that a setting name must begin with a lowercase |
| 43 | # letter. It also assumes that any output lines that start with a |
| 44 | # lowercase letter contain a setting name starting at that same point. |
| 45 | # |
| 46 | proc extract_setting_names { data } { |
| 47 | set names [list] |
| 48 | |
| 49 | foreach {dummy name} [regexp \ |
| 50 | -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] { |
| 51 | lappend names $name |
| 52 | } |
| 53 | |
| 54 | return $names |
| 55 | } |
| 56 | |
| 57 | ############################################################################### |
| 58 | |
| 59 | set all_settings [get_all_settings] |
| 60 | |
| 61 | fossil settings |
| 62 | set local_settings [extract_setting_names [normalize_result_no_trim]] |
| 63 | |
| 64 | fossil settings --global |
| 65 | set global_settings [extract_setting_names [normalize_result_no_trim]] |
| 66 | |
| 67 | foreach name $all_settings { |
| 68 | test settings-have-local-$name { |
| 69 | [lsearch -exact $local_settings $name] != -1 |
| 70 | } |
| 71 | |
| 72 | test settings-have-global-$name { |
| 73 | [lsearch -exact $global_settings $name] != -1 |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | foreach name $local_settings { |
| 78 | test settings-valid-local-$name { |
| 79 | [lsearch -exact $all_settings $name] != -1 |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | foreach name $global_settings { |
| 84 | test settings-valid-global-$name { |
| 85 | [lsearch -exact $all_settings $name] != -1 |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | ############################################################################### |
| 90 | |
| 91 | set pattern(1) {^%name%$} |
| 92 | set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$} |
| 93 | |
| 94 | foreach name $all_settings { |
| 95 | fossil settings $name --exact |
| 96 | set data [normalize_result] |
| 97 | |
| 98 | test settings-query-local-$name { |
| 99 | [regexp -- [string map [list %name% $name] $pattern(1)] $data] || |
| 100 | [regexp -- [string map [list %name% $name] $pattern(2)] $data] |
| 101 | } |
| 102 | |
| 103 | if {$name eq "manifest"} { |
| 104 | fossil settings $name --exact --global -expectError |
| 105 | } else { |
| 106 | fossil settings $name --exact --global |
| 107 | } |
| 108 | set data [normalize_result] |
| 109 | |
| 110 | if {$name eq "manifest"} { |
| 111 | test settings-query-global-$name { |
| 112 | $data eq "cannot set 'manifest' globally" |
| 113 | } |
| 114 | } else { |
| 115 | test settings-query-global-$name { |
| 116 | [regexp -- [string map [list %name% $name] $pattern(1)] $data] || |
| 117 | [regexp -- [string map [list %name% $name] $pattern(2)] $data] |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | ############################################################################### |
| 123 | |
| 124 | fossil settings bad-setting -expectError |
| 125 | |
| 126 | test settings-query-bad-local { |
| 127 | [normalize_result] eq "no such setting: bad-setting" |
| 128 | } |
| 129 | |
| 130 | fossil settings bad-setting --global -expectError |
| 131 | |
| 132 | test settings-query-bad-global { |
| 133 | [normalize_result] eq "no such setting: bad-setting" |
| 134 | } |
| 135 | |
| 136 | ############################################################################### |
| 137 | |
| 138 | test_cleanup |
| --- a/test/settings.test | |
| +++ b/test/settings.test | |
| @@ -1,138 +0,0 @@ | |
No diff available
+138
| --- a/test/settings.test.off | ||
| +++ b/test/settings.test.off | ||
| @@ -0,0 +1,138 @@ | ||
| 1 | +# | |
| 2 | +# Copyright (c) 2016 D. Richard Hipp | |
| 3 | +# | |
| 4 | +# This program is free software; you can redistribute it and/or | |
| 5 | +# modify it under the terms of the Simplified BSD License (also | |
| 6 | +# known as the "2-Clause License" or "FreeBSD License".) | |
| 7 | +# | |
| 8 | +# This program is distributed in the hope that it will be useful, | |
| 9 | +# but without any warranty; without even the implied warranty of | |
| 10 | +# merchantability or fitness for a particular purpose. | |
| 11 | +# | |
| 12 | +# Author contact information: | |
| 13 | +# [email protected] | |
| 14 | +# http://www.hwaci.com/drh/ | |
| 15 | +# | |
| 16 | +############################################################################ | |
| 17 | +# | |
| 18 | +# The "settings" and "unset" commands. | |
| 19 | +# | |
| 20 | + | |
| 21 | +set path [file dirname [info script]]; test_setup | |
| 22 | + | |
| 23 | +############################################################################### | |
| 24 | +# | |
| 25 | +# Complete syntax as tested: | |
| 26 | +# | |
| 27 | +# fossil settings ?PROPERTY? ?VALUE? ?OPTIONS? | |
| 28 | +# fossil unset PROPERTY ?OPTIONS? | |
| 29 | +# | |
| 30 | +# Where the only supported options are "--global" and "--exact". | |
| 31 | +# | |
| 32 | +############################################################################### | |
| 33 | +# | |
| 34 | +# NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list | |
| 35 | +# of settings to test and needs to be manually updated when new settings | |
| 36 | +# are added. | |
| 37 | +# | |
| 38 | +############################################################################### | |
| 39 | +# | |
| 40 | +# NOTE: The [extract_setting_names] procedure extracts the list of setting | |
| 41 | +# names from the line-ending normalized output of the "fossil settings" | |
| 42 | +# command. It assumes that a setting name must begin with a lowercase | |
| 43 | +# letter. It also assumes that any output lines that start with a | |
| 44 | +# lowercase letter contain a setting name starting at that same point. | |
| 45 | +# | |
| 46 | +proc extract_setting_names { data } { | |
| 47 | + set names [list] | |
| 48 | + | |
| 49 | + foreach {dummy name} [regexp \ | |
| 50 | + -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] { | |
| 51 | + lappend names $name | |
| 52 | + } | |
| 53 | + | |
| 54 | + return $names | |
| 55 | +} | |
| 56 | + | |
| 57 | +############################################################################### | |
| 58 | + | |
| 59 | +set all_settings [get_all_settings] | |
| 60 | + | |
| 61 | +fossil settings | |
| 62 | +set local_settings [extract_setting_names [normalize_result_no_trim]] | |
| 63 | + | |
| 64 | +fossil settings --global | |
| 65 | +set global_settings [extract_setting_names [normalize_result_no_trim]] | |
| 66 | + | |
| 67 | +foreach name $all_settings { | |
| 68 | + test settings-have-local-$name { | |
| 69 | + [lsearch -exact $local_settings $name] != -1 | |
| 70 | + } | |
| 71 | + | |
| 72 | + test settings-have-global-$name { | |
| 73 | + [lsearch -exact $global_settings $name] != -1 | |
| 74 | + } | |
| 75 | +} | |
| 76 | + | |
| 77 | +foreach name $local_settings { | |
| 78 | + test settings-valid-local-$name { | |
| 79 | + [lsearch -exact $all_settings $name] != -1 | |
| 80 | + } | |
| 81 | +} | |
| 82 | + | |
| 83 | +foreach name $global_settings { | |
| 84 | + test settings-valid-global-$name { | |
| 85 | + [lsearch -exact $all_settings $name] != -1 | |
| 86 | + } | |
| 87 | +} | |
| 88 | + | |
| 89 | +############################################################################### | |
| 90 | + | |
| 91 | +set pattern(1) {^%name%$} | |
| 92 | +set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$} | |
| 93 | + | |
| 94 | +foreach name $all_settings { | |
| 95 | + fossil settings $name --exact | |
| 96 | + set data [normalize_result] | |
| 97 | + | |
| 98 | + test settings-query-local-$name { | |
| 99 | + [regexp -- [string map [list %name% $name] $pattern(1)] $data] || | |
| 100 | + [regexp -- [string map [list %name% $name] $pattern(2)] $data] | |
| 101 | + } | |
| 102 | + | |
| 103 | + if {$name eq "manifest"} { | |
| 104 | + fossil settings $name --exact --global -expectError | |
| 105 | + } else { | |
| 106 | + fossil settings $name --exact --global | |
| 107 | + } | |
| 108 | + set data [normalize_result] | |
| 109 | + | |
| 110 | + if {$name eq "manifest"} { | |
| 111 | + test settings-query-global-$name { | |
| 112 | + $data eq "cannot set 'manifest' globally" | |
| 113 | + } | |
| 114 | + } else { | |
| 115 | + test settings-query-global-$name { | |
| 116 | + [regexp -- [string map [list %name% $name] $pattern(1)] $data] || | |
| 117 | + [regexp -- [string map [list %name% $name] $pattern(2)] $data] | |
| 118 | + } | |
| 119 | + } | |
| 120 | +} | |
| 121 | + | |
| 122 | +############################################################################### | |
| 123 | + | |
| 124 | +fossil settings bad-setting -expectError | |
| 125 | + | |
| 126 | +test settings-query-bad-local { | |
| 127 | + [normalize_result] eq "no such setting: bad-setting" | |
| 128 | +} | |
| 129 | + | |
| 130 | +fossil settings bad-setting --global -expectError | |
| 131 | + | |
| 132 | +test settings-query-bad-global { | |
| 133 | + [normalize_result] eq "no such setting: bad-setting" | |
| 134 | +} | |
| 135 | + | |
| 136 | +############################################################################### | |
| 137 | + | |
| 138 | +test_cleanup |
| --- a/test/settings.test.off | |
| +++ b/test/settings.test.off | |
| @@ -0,0 +1,138 @@ | |
| --- a/test/settings.test.off | |
| +++ b/test/settings.test.off | |
| @@ -0,0 +1,138 @@ | |
| 1 | # |
| 2 | # Copyright (c) 2016 D. Richard Hipp |
| 3 | # |
| 4 | # This program is free software; you can redistribute it and/or |
| 5 | # modify it under the terms of the Simplified BSD License (also |
| 6 | # known as the "2-Clause License" or "FreeBSD License".) |
| 7 | # |
| 8 | # This program is distributed in the hope that it will be useful, |
| 9 | # but without any warranty; without even the implied warranty of |
| 10 | # merchantability or fitness for a particular purpose. |
| 11 | # |
| 12 | # Author contact information: |
| 13 | # [email protected] |
| 14 | # http://www.hwaci.com/drh/ |
| 15 | # |
| 16 | ############################################################################ |
| 17 | # |
| 18 | # The "settings" and "unset" commands. |
| 19 | # |
| 20 | |
| 21 | set path [file dirname [info script]]; test_setup |
| 22 | |
| 23 | ############################################################################### |
| 24 | # |
| 25 | # Complete syntax as tested: |
| 26 | # |
| 27 | # fossil settings ?PROPERTY? ?VALUE? ?OPTIONS? |
| 28 | # fossil unset PROPERTY ?OPTIONS? |
| 29 | # |
| 30 | # Where the only supported options are "--global" and "--exact". |
| 31 | # |
| 32 | ############################################################################### |
| 33 | # |
| 34 | # NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list |
| 35 | # of settings to test and needs to be manually updated when new settings |
| 36 | # are added. |
| 37 | # |
| 38 | ############################################################################### |
| 39 | # |
| 40 | # NOTE: The [extract_setting_names] procedure extracts the list of setting |
| 41 | # names from the line-ending normalized output of the "fossil settings" |
| 42 | # command. It assumes that a setting name must begin with a lowercase |
| 43 | # letter. It also assumes that any output lines that start with a |
| 44 | # lowercase letter contain a setting name starting at that same point. |
| 45 | # |
| 46 | proc extract_setting_names { data } { |
| 47 | set names [list] |
| 48 | |
| 49 | foreach {dummy name} [regexp \ |
| 50 | -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] { |
| 51 | lappend names $name |
| 52 | } |
| 53 | |
| 54 | return $names |
| 55 | } |
| 56 | |
| 57 | ############################################################################### |
| 58 | |
| 59 | set all_settings [get_all_settings] |
| 60 | |
| 61 | fossil settings |
| 62 | set local_settings [extract_setting_names [normalize_result_no_trim]] |
| 63 | |
| 64 | fossil settings --global |
| 65 | set global_settings [extract_setting_names [normalize_result_no_trim]] |
| 66 | |
| 67 | foreach name $all_settings { |
| 68 | test settings-have-local-$name { |
| 69 | [lsearch -exact $local_settings $name] != -1 |
| 70 | } |
| 71 | |
| 72 | test settings-have-global-$name { |
| 73 | [lsearch -exact $global_settings $name] != -1 |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | foreach name $local_settings { |
| 78 | test settings-valid-local-$name { |
| 79 | [lsearch -exact $all_settings $name] != -1 |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | foreach name $global_settings { |
| 84 | test settings-valid-global-$name { |
| 85 | [lsearch -exact $all_settings $name] != -1 |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | ############################################################################### |
| 90 | |
| 91 | set pattern(1) {^%name%$} |
| 92 | set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$} |
| 93 | |
| 94 | foreach name $all_settings { |
| 95 | fossil settings $name --exact |
| 96 | set data [normalize_result] |
| 97 | |
| 98 | test settings-query-local-$name { |
| 99 | [regexp -- [string map [list %name% $name] $pattern(1)] $data] || |
| 100 | [regexp -- [string map [list %name% $name] $pattern(2)] $data] |
| 101 | } |
| 102 | |
| 103 | if {$name eq "manifest"} { |
| 104 | fossil settings $name --exact --global -expectError |
| 105 | } else { |
| 106 | fossil settings $name --exact --global |
| 107 | } |
| 108 | set data [normalize_result] |
| 109 | |
| 110 | if {$name eq "manifest"} { |
| 111 | test settings-query-global-$name { |
| 112 | $data eq "cannot set 'manifest' globally" |
| 113 | } |
| 114 | } else { |
| 115 | test settings-query-global-$name { |
| 116 | [regexp -- [string map [list %name% $name] $pattern(1)] $data] || |
| 117 | [regexp -- [string map [list %name% $name] $pattern(2)] $data] |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | ############################################################################### |
| 123 | |
| 124 | fossil settings bad-setting -expectError |
| 125 | |
| 126 | test settings-query-bad-local { |
| 127 | [normalize_result] eq "no such setting: bad-setting" |
| 128 | } |
| 129 | |
| 130 | fossil settings bad-setting --global -expectError |
| 131 | |
| 132 | test settings-query-bad-global { |
| 133 | [normalize_result] eq "no such setting: bad-setting" |
| 134 | } |
| 135 | |
| 136 | ############################################################################### |
| 137 | |
| 138 | test_cleanup |
+2
-2
| --- tools/makeheaders.c | ||
| +++ tools/makeheaders.c | ||
| @@ -486,18 +486,18 @@ | ||
| 486 | 486 | /* |
| 487 | 487 | ** Compute a hash on a string. The number returned is a non-negative |
| 488 | 488 | ** value between 0 and 2**31 - 1 |
| 489 | 489 | */ |
| 490 | 490 | static int Hash(const char *z, int n){ |
| 491 | - int h = 0; | |
| 491 | + unsigned int h = 0; | |
| 492 | 492 | if( n<=0 ){ |
| 493 | 493 | n = strlen(z); |
| 494 | 494 | } |
| 495 | 495 | while( n-- ){ |
| 496 | 496 | h = h ^ (h<<5) ^ *z++; |
| 497 | 497 | } |
| 498 | - return h & 0x7fffffff; | |
| 498 | + return (int)(h & 0x7fffffff); | |
| 499 | 499 | } |
| 500 | 500 | |
| 501 | 501 | /* |
| 502 | 502 | ** Given an identifier name, try to find a declaration for that |
| 503 | 503 | ** identifier in the hash table. If found, return a pointer to |
| 504 | 504 |
| --- tools/makeheaders.c | |
| +++ tools/makeheaders.c | |
| @@ -486,18 +486,18 @@ | |
| 486 | /* |
| 487 | ** Compute a hash on a string. The number returned is a non-negative |
| 488 | ** value between 0 and 2**31 - 1 |
| 489 | */ |
| 490 | static int Hash(const char *z, int n){ |
| 491 | int h = 0; |
| 492 | if( n<=0 ){ |
| 493 | n = strlen(z); |
| 494 | } |
| 495 | while( n-- ){ |
| 496 | h = h ^ (h<<5) ^ *z++; |
| 497 | } |
| 498 | return h & 0x7fffffff; |
| 499 | } |
| 500 | |
| 501 | /* |
| 502 | ** Given an identifier name, try to find a declaration for that |
| 503 | ** identifier in the hash table. If found, return a pointer to |
| 504 |
| --- tools/makeheaders.c | |
| +++ tools/makeheaders.c | |
| @@ -486,18 +486,18 @@ | |
| 486 | /* |
| 487 | ** Compute a hash on a string. The number returned is a non-negative |
| 488 | ** value between 0 and 2**31 - 1 |
| 489 | */ |
| 490 | static int Hash(const char *z, int n){ |
| 491 | unsigned int h = 0; |
| 492 | if( n<=0 ){ |
| 493 | n = strlen(z); |
| 494 | } |
| 495 | while( n-- ){ |
| 496 | h = h ^ (h<<5) ^ *z++; |
| 497 | } |
| 498 | return (int)(h & 0x7fffffff); |
| 499 | } |
| 500 | |
| 501 | /* |
| 502 | ** Given an identifier name, try to find a declaration for that |
| 503 | ** identifier in the hash table. If found, return a pointer to |
| 504 |
+1
| --- tools/makemake.tcl | ||
| +++ tools/makemake.tcl | ||
| @@ -152,10 +152,11 @@ | ||
| 152 | 152 | purge |
| 153 | 153 | rebuild |
| 154 | 154 | regexp |
| 155 | 155 | repolist |
| 156 | 156 | report |
| 157 | + robot | |
| 157 | 158 | rss |
| 158 | 159 | schema |
| 159 | 160 | search |
| 160 | 161 | security_audit |
| 161 | 162 | setup |
| 162 | 163 |
| --- tools/makemake.tcl | |
| +++ tools/makemake.tcl | |
| @@ -152,10 +152,11 @@ | |
| 152 | purge |
| 153 | rebuild |
| 154 | regexp |
| 155 | repolist |
| 156 | report |
| 157 | rss |
| 158 | schema |
| 159 | search |
| 160 | security_audit |
| 161 | setup |
| 162 |
| --- tools/makemake.tcl | |
| +++ tools/makemake.tcl | |
| @@ -152,10 +152,11 @@ | |
| 152 | purge |
| 153 | rebuild |
| 154 | regexp |
| 155 | repolist |
| 156 | report |
| 157 | robot |
| 158 | rss |
| 159 | schema |
| 160 | search |
| 161 | security_audit |
| 162 | setup |
| 163 |
+10
-4
| --- win/Makefile.dmc | ||
| +++ win/Makefile.dmc | ||
| @@ -32,13 +32,13 @@ | ||
| 32 | 32 | |
| 33 | 33 | SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_SETLK_TIMEOUT -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | 34 | |
| 35 | 35 | PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000 |
| 36 | 36 | |
| 37 | -SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c | |
| 37 | +SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c robot_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c | |
| 38 | 38 | |
| 39 | -OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O | |
| 39 | +OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\robot$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O | |
| 40 | 40 | |
| 41 | 41 | |
| 42 | 42 | RC=$(DMDIR)\bin\rcc |
| 43 | 43 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 44 | 44 | |
| @@ -53,11 +53,11 @@ | ||
| 53 | 53 | |
| 54 | 54 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 55 | 55 | $(RC) $(RCFLAGS) -o$@ $** |
| 56 | 56 | |
| 57 | 57 | $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res |
| 58 | - +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ | |
| 58 | + +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report robot rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ | |
| 59 | 59 | +echo fossil >> $@ |
| 60 | 60 | +echo fossil >> $@ |
| 61 | 61 | +echo $(LIBS) >> $@ |
| 62 | 62 | +echo. >> $@ |
| 63 | 63 | +echo fossil >> $@ |
| @@ -755,10 +755,16 @@ | ||
| 755 | 755 | $(OBJDIR)\report$O : report_.c report.h |
| 756 | 756 | $(TCC) -o$@ -c report_.c |
| 757 | 757 | |
| 758 | 758 | report_.c : $(SRCDIR)\report.c |
| 759 | 759 | +translate$E $** > $@ |
| 760 | + | |
| 761 | +$(OBJDIR)\robot$O : robot_.c robot.h | |
| 762 | + $(TCC) -o$@ -c robot_.c | |
| 763 | + | |
| 764 | +robot_.c : $(SRCDIR)\robot.c | |
| 765 | + +translate$E $** > $@ | |
| 760 | 766 | |
| 761 | 767 | $(OBJDIR)\rss$O : rss_.c rss.h |
| 762 | 768 | $(TCC) -o$@ -c rss_.c |
| 763 | 769 | |
| 764 | 770 | rss_.c : $(SRCDIR)\rss.c |
| @@ -1015,7 +1021,7 @@ | ||
| 1015 | 1021 | |
| 1016 | 1022 | zip_.c : $(SRCDIR)\zip.c |
| 1017 | 1023 | +translate$E $** > $@ |
| 1018 | 1024 | |
| 1019 | 1025 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1020 | - +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h | |
| 1026 | + +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h robot_.c:robot.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h | |
| 1021 | 1027 | @copy /Y nul: headers |
| 1022 | 1028 |
| --- win/Makefile.dmc | |
| +++ win/Makefile.dmc | |
| @@ -32,13 +32,13 @@ | |
| 32 | |
| 33 | SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_SETLK_TIMEOUT -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | |
| 35 | PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000 |
| 36 | |
| 37 | SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c |
| 38 | |
| 39 | OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O |
| 40 | |
| 41 | |
| 42 | RC=$(DMDIR)\bin\rcc |
| 43 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 44 | |
| @@ -53,11 +53,11 @@ | |
| 53 | |
| 54 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 55 | $(RC) $(RCFLAGS) -o$@ $** |
| 56 | |
| 57 | $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res |
| 58 | +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ |
| 59 | +echo fossil >> $@ |
| 60 | +echo fossil >> $@ |
| 61 | +echo $(LIBS) >> $@ |
| 62 | +echo. >> $@ |
| 63 | +echo fossil >> $@ |
| @@ -755,10 +755,16 @@ | |
| 755 | $(OBJDIR)\report$O : report_.c report.h |
| 756 | $(TCC) -o$@ -c report_.c |
| 757 | |
| 758 | report_.c : $(SRCDIR)\report.c |
| 759 | +translate$E $** > $@ |
| 760 | |
| 761 | $(OBJDIR)\rss$O : rss_.c rss.h |
| 762 | $(TCC) -o$@ -c rss_.c |
| 763 | |
| 764 | rss_.c : $(SRCDIR)\rss.c |
| @@ -1015,7 +1021,7 @@ | |
| 1015 | |
| 1016 | zip_.c : $(SRCDIR)\zip.c |
| 1017 | +translate$E $** > $@ |
| 1018 | |
| 1019 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1020 | +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h |
| 1021 | @copy /Y nul: headers |
| 1022 |
| --- win/Makefile.dmc | |
| +++ win/Makefile.dmc | |
| @@ -32,13 +32,13 @@ | |
| 32 | |
| 33 | SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_SETLK_TIMEOUT -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | |
| 35 | PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000 |
| 36 | |
| 37 | SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c robot_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c |
| 38 | |
| 39 | OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\robot$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O |
| 40 | |
| 41 | |
| 42 | RC=$(DMDIR)\bin\rcc |
| 43 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 44 | |
| @@ -53,11 +53,11 @@ | |
| 53 | |
| 54 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 55 | $(RC) $(RCFLAGS) -o$@ $** |
| 56 | |
| 57 | $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res |
| 58 | +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report robot rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ |
| 59 | +echo fossil >> $@ |
| 60 | +echo fossil >> $@ |
| 61 | +echo $(LIBS) >> $@ |
| 62 | +echo. >> $@ |
| 63 | +echo fossil >> $@ |
| @@ -755,10 +755,16 @@ | |
| 755 | $(OBJDIR)\report$O : report_.c report.h |
| 756 | $(TCC) -o$@ -c report_.c |
| 757 | |
| 758 | report_.c : $(SRCDIR)\report.c |
| 759 | +translate$E $** > $@ |
| 760 | |
| 761 | $(OBJDIR)\robot$O : robot_.c robot.h |
| 762 | $(TCC) -o$@ -c robot_.c |
| 763 | |
| 764 | robot_.c : $(SRCDIR)\robot.c |
| 765 | +translate$E $** > $@ |
| 766 | |
| 767 | $(OBJDIR)\rss$O : rss_.c rss.h |
| 768 | $(TCC) -o$@ -c rss_.c |
| 769 | |
| 770 | rss_.c : $(SRCDIR)\rss.c |
| @@ -1015,7 +1021,7 @@ | |
| 1021 | |
| 1022 | zip_.c : $(SRCDIR)\zip.c |
| 1023 | +translate$E $** > $@ |
| 1024 | |
| 1025 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1026 | +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h robot_.c:robot.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h |
| 1027 | @copy /Y nul: headers |
| 1028 |
+13
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -505,10 +505,11 @@ | ||
| 505 | 505 | $(SRCDIR)/purge.c \ |
| 506 | 506 | $(SRCDIR)/rebuild.c \ |
| 507 | 507 | $(SRCDIR)/regexp.c \ |
| 508 | 508 | $(SRCDIR)/repolist.c \ |
| 509 | 509 | $(SRCDIR)/report.c \ |
| 510 | + $(SRCDIR)/robot.c \ | |
| 510 | 511 | $(SRCDIR)/rss.c \ |
| 511 | 512 | $(SRCDIR)/schema.c \ |
| 512 | 513 | $(SRCDIR)/search.c \ |
| 513 | 514 | $(SRCDIR)/security_audit.c \ |
| 514 | 515 | $(SRCDIR)/setup.c \ |
| @@ -622,10 +623,11 @@ | ||
| 622 | 623 | $(SRCDIR)/fossil.page.chat.js \ |
| 623 | 624 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 624 | 625 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 625 | 626 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 626 | 627 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 628 | + $(SRCDIR)/fossil.page.ticket.js \ | |
| 627 | 629 | $(SRCDIR)/fossil.page.whistory.js \ |
| 628 | 630 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 629 | 631 | $(SRCDIR)/fossil.pikchr.js \ |
| 630 | 632 | $(SRCDIR)/fossil.popupwidget.js \ |
| 631 | 633 | $(SRCDIR)/fossil.storage.js \ |
| @@ -771,10 +773,11 @@ | ||
| 771 | 773 | $(OBJDIR)/purge_.c \ |
| 772 | 774 | $(OBJDIR)/rebuild_.c \ |
| 773 | 775 | $(OBJDIR)/regexp_.c \ |
| 774 | 776 | $(OBJDIR)/repolist_.c \ |
| 775 | 777 | $(OBJDIR)/report_.c \ |
| 778 | + $(OBJDIR)/robot_.c \ | |
| 776 | 779 | $(OBJDIR)/rss_.c \ |
| 777 | 780 | $(OBJDIR)/schema_.c \ |
| 778 | 781 | $(OBJDIR)/search_.c \ |
| 779 | 782 | $(OBJDIR)/security_audit_.c \ |
| 780 | 783 | $(OBJDIR)/setup_.c \ |
| @@ -921,10 +924,11 @@ | ||
| 921 | 924 | $(OBJDIR)/purge.o \ |
| 922 | 925 | $(OBJDIR)/rebuild.o \ |
| 923 | 926 | $(OBJDIR)/regexp.o \ |
| 924 | 927 | $(OBJDIR)/repolist.o \ |
| 925 | 928 | $(OBJDIR)/report.o \ |
| 929 | + $(OBJDIR)/robot.o \ | |
| 926 | 930 | $(OBJDIR)/rss.o \ |
| 927 | 931 | $(OBJDIR)/schema.o \ |
| 928 | 932 | $(OBJDIR)/search.o \ |
| 929 | 933 | $(OBJDIR)/security_audit.o \ |
| 930 | 934 | $(OBJDIR)/setup.o \ |
| @@ -1275,10 +1279,11 @@ | ||
| 1275 | 1279 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 1276 | 1280 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 1277 | 1281 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 1278 | 1282 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 1279 | 1283 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 1284 | + $(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \ | |
| 1280 | 1285 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 1281 | 1286 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 1282 | 1287 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 1283 | 1288 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 1284 | 1289 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -2167,10 +2172,18 @@ | ||
| 2167 | 2172 | |
| 2168 | 2173 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 2169 | 2174 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 2170 | 2175 | |
| 2171 | 2176 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 2177 | + | |
| 2178 | +$(OBJDIR)/robot_.c: $(SRCDIR)/robot.c $(TRANSLATE) | |
| 2179 | + $(TRANSLATE) $(SRCDIR)/robot.c >$@ | |
| 2180 | + | |
| 2181 | +$(OBJDIR)/robot.o: $(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h | |
| 2182 | + $(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c | |
| 2183 | + | |
| 2184 | +$(OBJDIR)/robot.h: $(OBJDIR)/headers | |
| 2172 | 2185 | |
| 2173 | 2186 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(TRANSLATE) |
| 2174 | 2187 | $(TRANSLATE) $(SRCDIR)/rss.c >$@ |
| 2175 | 2188 | |
| 2176 | 2189 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 2177 | 2190 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -505,10 +505,11 @@ | |
| 505 | $(SRCDIR)/purge.c \ |
| 506 | $(SRCDIR)/rebuild.c \ |
| 507 | $(SRCDIR)/regexp.c \ |
| 508 | $(SRCDIR)/repolist.c \ |
| 509 | $(SRCDIR)/report.c \ |
| 510 | $(SRCDIR)/rss.c \ |
| 511 | $(SRCDIR)/schema.c \ |
| 512 | $(SRCDIR)/search.c \ |
| 513 | $(SRCDIR)/security_audit.c \ |
| 514 | $(SRCDIR)/setup.c \ |
| @@ -622,10 +623,11 @@ | |
| 622 | $(SRCDIR)/fossil.page.chat.js \ |
| 623 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 624 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 625 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 626 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 627 | $(SRCDIR)/fossil.page.whistory.js \ |
| 628 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 629 | $(SRCDIR)/fossil.pikchr.js \ |
| 630 | $(SRCDIR)/fossil.popupwidget.js \ |
| 631 | $(SRCDIR)/fossil.storage.js \ |
| @@ -771,10 +773,11 @@ | |
| 771 | $(OBJDIR)/purge_.c \ |
| 772 | $(OBJDIR)/rebuild_.c \ |
| 773 | $(OBJDIR)/regexp_.c \ |
| 774 | $(OBJDIR)/repolist_.c \ |
| 775 | $(OBJDIR)/report_.c \ |
| 776 | $(OBJDIR)/rss_.c \ |
| 777 | $(OBJDIR)/schema_.c \ |
| 778 | $(OBJDIR)/search_.c \ |
| 779 | $(OBJDIR)/security_audit_.c \ |
| 780 | $(OBJDIR)/setup_.c \ |
| @@ -921,10 +924,11 @@ | |
| 921 | $(OBJDIR)/purge.o \ |
| 922 | $(OBJDIR)/rebuild.o \ |
| 923 | $(OBJDIR)/regexp.o \ |
| 924 | $(OBJDIR)/repolist.o \ |
| 925 | $(OBJDIR)/report.o \ |
| 926 | $(OBJDIR)/rss.o \ |
| 927 | $(OBJDIR)/schema.o \ |
| 928 | $(OBJDIR)/search.o \ |
| 929 | $(OBJDIR)/security_audit.o \ |
| 930 | $(OBJDIR)/setup.o \ |
| @@ -1275,10 +1279,11 @@ | |
| 1275 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 1276 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 1277 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 1278 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 1279 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 1280 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 1281 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 1282 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 1283 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 1284 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -2167,10 +2172,18 @@ | |
| 2167 | |
| 2168 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 2169 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 2170 | |
| 2171 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 2172 | |
| 2173 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(TRANSLATE) |
| 2174 | $(TRANSLATE) $(SRCDIR)/rss.c >$@ |
| 2175 | |
| 2176 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 2177 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -505,10 +505,11 @@ | |
| 505 | $(SRCDIR)/purge.c \ |
| 506 | $(SRCDIR)/rebuild.c \ |
| 507 | $(SRCDIR)/regexp.c \ |
| 508 | $(SRCDIR)/repolist.c \ |
| 509 | $(SRCDIR)/report.c \ |
| 510 | $(SRCDIR)/robot.c \ |
| 511 | $(SRCDIR)/rss.c \ |
| 512 | $(SRCDIR)/schema.c \ |
| 513 | $(SRCDIR)/search.c \ |
| 514 | $(SRCDIR)/security_audit.c \ |
| 515 | $(SRCDIR)/setup.c \ |
| @@ -622,10 +623,11 @@ | |
| 623 | $(SRCDIR)/fossil.page.chat.js \ |
| 624 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 625 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 626 | $(SRCDIR)/fossil.page.pikchrshow.js \ |
| 627 | $(SRCDIR)/fossil.page.pikchrshowasm.js \ |
| 628 | $(SRCDIR)/fossil.page.ticket.js \ |
| 629 | $(SRCDIR)/fossil.page.whistory.js \ |
| 630 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 631 | $(SRCDIR)/fossil.pikchr.js \ |
| 632 | $(SRCDIR)/fossil.popupwidget.js \ |
| 633 | $(SRCDIR)/fossil.storage.js \ |
| @@ -771,10 +773,11 @@ | |
| 773 | $(OBJDIR)/purge_.c \ |
| 774 | $(OBJDIR)/rebuild_.c \ |
| 775 | $(OBJDIR)/regexp_.c \ |
| 776 | $(OBJDIR)/repolist_.c \ |
| 777 | $(OBJDIR)/report_.c \ |
| 778 | $(OBJDIR)/robot_.c \ |
| 779 | $(OBJDIR)/rss_.c \ |
| 780 | $(OBJDIR)/schema_.c \ |
| 781 | $(OBJDIR)/search_.c \ |
| 782 | $(OBJDIR)/security_audit_.c \ |
| 783 | $(OBJDIR)/setup_.c \ |
| @@ -921,10 +924,11 @@ | |
| 924 | $(OBJDIR)/purge.o \ |
| 925 | $(OBJDIR)/rebuild.o \ |
| 926 | $(OBJDIR)/regexp.o \ |
| 927 | $(OBJDIR)/repolist.o \ |
| 928 | $(OBJDIR)/report.o \ |
| 929 | $(OBJDIR)/robot.o \ |
| 930 | $(OBJDIR)/rss.o \ |
| 931 | $(OBJDIR)/schema.o \ |
| 932 | $(OBJDIR)/search.o \ |
| 933 | $(OBJDIR)/security_audit.o \ |
| 934 | $(OBJDIR)/setup.o \ |
| @@ -1275,10 +1279,11 @@ | |
| 1279 | $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ |
| 1280 | $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ |
| 1281 | $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ |
| 1282 | $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ |
| 1283 | $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ |
| 1284 | $(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \ |
| 1285 | $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ |
| 1286 | $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ |
| 1287 | $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ |
| 1288 | $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ |
| 1289 | $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ |
| @@ -2167,10 +2172,18 @@ | |
| 2172 | |
| 2173 | $(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h |
| 2174 | $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c |
| 2175 | |
| 2176 | $(OBJDIR)/report.h: $(OBJDIR)/headers |
| 2177 | |
| 2178 | $(OBJDIR)/robot_.c: $(SRCDIR)/robot.c $(TRANSLATE) |
| 2179 | $(TRANSLATE) $(SRCDIR)/robot.c >$@ |
| 2180 | |
| 2181 | $(OBJDIR)/robot.o: $(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h |
| 2182 | $(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c |
| 2183 | |
| 2184 | $(OBJDIR)/robot.h: $(OBJDIR)/headers |
| 2185 | |
| 2186 | $(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(TRANSLATE) |
| 2187 | $(TRANSLATE) $(SRCDIR)/rss.c >$@ |
| 2188 | |
| 2189 | $(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h |
| 2190 |
+12
| --- win/Makefile.msc | ||
| +++ win/Makefile.msc | ||
| @@ -467,10 +467,11 @@ | ||
| 467 | 467 | "$(OX)\purge_.c" \ |
| 468 | 468 | "$(OX)\rebuild_.c" \ |
| 469 | 469 | "$(OX)\regexp_.c" \ |
| 470 | 470 | "$(OX)\repolist_.c" \ |
| 471 | 471 | "$(OX)\report_.c" \ |
| 472 | + "$(OX)\robot_.c" \ | |
| 472 | 473 | "$(OX)\rss_.c" \ |
| 473 | 474 | "$(OX)\schema_.c" \ |
| 474 | 475 | "$(OX)\search_.c" \ |
| 475 | 476 | "$(OX)\security_audit_.c" \ |
| 476 | 477 | "$(OX)\setup_.c" \ |
| @@ -584,10 +585,11 @@ | ||
| 584 | 585 | "$(SRCDIR)\fossil.page.chat.js" \ |
| 585 | 586 | "$(SRCDIR)\fossil.page.fileedit.js" \ |
| 586 | 587 | "$(SRCDIR)\fossil.page.forumpost.js" \ |
| 587 | 588 | "$(SRCDIR)\fossil.page.pikchrshow.js" \ |
| 588 | 589 | "$(SRCDIR)\fossil.page.pikchrshowasm.js" \ |
| 590 | + "$(SRCDIR)\fossil.page.ticket.js" \ | |
| 589 | 591 | "$(SRCDIR)\fossil.page.whistory.js" \ |
| 590 | 592 | "$(SRCDIR)\fossil.page.wikiedit.js" \ |
| 591 | 593 | "$(SRCDIR)\fossil.pikchr.js" \ |
| 592 | 594 | "$(SRCDIR)\fossil.popupwidget.js" \ |
| 593 | 595 | "$(SRCDIR)\fossil.storage.js" \ |
| @@ -734,10 +736,11 @@ | ||
| 734 | 736 | "$(OX)\purge$O" \ |
| 735 | 737 | "$(OX)\rebuild$O" \ |
| 736 | 738 | "$(OX)\regexp$O" \ |
| 737 | 739 | "$(OX)\repolist$O" \ |
| 738 | 740 | "$(OX)\report$O" \ |
| 741 | + "$(OX)\robot$O" \ | |
| 739 | 742 | "$(OX)\rss$O" \ |
| 740 | 743 | "$(OX)\schema$O" \ |
| 741 | 744 | "$(OX)\search$O" \ |
| 742 | 745 | "$(OX)\security_audit$O" \ |
| 743 | 746 | "$(OX)\setup$O" \ |
| @@ -984,10 +987,11 @@ | ||
| 984 | 987 | echo "$(OX)\purge.obj" >> $@ |
| 985 | 988 | echo "$(OX)\rebuild.obj" >> $@ |
| 986 | 989 | echo "$(OX)\regexp.obj" >> $@ |
| 987 | 990 | echo "$(OX)\repolist.obj" >> $@ |
| 988 | 991 | echo "$(OX)\report.obj" >> $@ |
| 992 | + echo "$(OX)\robot.obj" >> $@ | |
| 989 | 993 | echo "$(OX)\rss.obj" >> $@ |
| 990 | 994 | echo "$(OX)\schema.obj" >> $@ |
| 991 | 995 | echo "$(OX)\search.obj" >> $@ |
| 992 | 996 | echo "$(OX)\security_audit.obj" >> $@ |
| 993 | 997 | echo "$(OX)\setup.obj" >> $@ |
| @@ -1217,10 +1221,11 @@ | ||
| 1217 | 1221 | echo "$(SRCDIR)\fossil.page.chat.js" >> $@ |
| 1218 | 1222 | echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ |
| 1219 | 1223 | echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ |
| 1220 | 1224 | echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ |
| 1221 | 1225 | echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@ |
| 1226 | + echo "$(SRCDIR)\fossil.page.ticket.js" >> $@ | |
| 1222 | 1227 | echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ |
| 1223 | 1228 | echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ |
| 1224 | 1229 | echo "$(SRCDIR)\fossil.pikchr.js" >> $@ |
| 1225 | 1230 | echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ |
| 1226 | 1231 | echo "$(SRCDIR)\fossil.storage.js" >> $@ |
| @@ -1889,10 +1894,16 @@ | ||
| 1889 | 1894 | "$(OX)\report$O" : "$(OX)\report_.c" "$(OX)\report.h" |
| 1890 | 1895 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\report_.c" |
| 1891 | 1896 | |
| 1892 | 1897 | "$(OX)\report_.c" : "$(SRCDIR)\report.c" |
| 1893 | 1898 | "$(OBJDIR)\translate$E" $** > $@ |
| 1899 | + | |
| 1900 | +"$(OX)\robot$O" : "$(OX)\robot_.c" "$(OX)\robot.h" | |
| 1901 | + $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\robot_.c" | |
| 1902 | + | |
| 1903 | +"$(OX)\robot_.c" : "$(SRCDIR)\robot.c" | |
| 1904 | + "$(OBJDIR)\translate$E" $** > $@ | |
| 1894 | 1905 | |
| 1895 | 1906 | "$(OX)\rss$O" : "$(OX)\rss_.c" "$(OX)\rss.h" |
| 1896 | 1907 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rss_.c" |
| 1897 | 1908 | |
| 1898 | 1909 | "$(OX)\rss_.c" : "$(SRCDIR)\rss.c" |
| @@ -2257,10 +2268,11 @@ | ||
| 2257 | 2268 | "$(OX)\purge_.c":"$(OX)\purge.h" \ |
| 2258 | 2269 | "$(OX)\rebuild_.c":"$(OX)\rebuild.h" \ |
| 2259 | 2270 | "$(OX)\regexp_.c":"$(OX)\regexp.h" \ |
| 2260 | 2271 | "$(OX)\repolist_.c":"$(OX)\repolist.h" \ |
| 2261 | 2272 | "$(OX)\report_.c":"$(OX)\report.h" \ |
| 2273 | + "$(OX)\robot_.c":"$(OX)\robot.h" \ | |
| 2262 | 2274 | "$(OX)\rss_.c":"$(OX)\rss.h" \ |
| 2263 | 2275 | "$(OX)\schema_.c":"$(OX)\schema.h" \ |
| 2264 | 2276 | "$(OX)\search_.c":"$(OX)\search.h" \ |
| 2265 | 2277 | "$(OX)\security_audit_.c":"$(OX)\security_audit.h" \ |
| 2266 | 2278 | "$(OX)\setup_.c":"$(OX)\setup.h" \ |
| 2267 | 2279 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -467,10 +467,11 @@ | |
| 467 | "$(OX)\purge_.c" \ |
| 468 | "$(OX)\rebuild_.c" \ |
| 469 | "$(OX)\regexp_.c" \ |
| 470 | "$(OX)\repolist_.c" \ |
| 471 | "$(OX)\report_.c" \ |
| 472 | "$(OX)\rss_.c" \ |
| 473 | "$(OX)\schema_.c" \ |
| 474 | "$(OX)\search_.c" \ |
| 475 | "$(OX)\security_audit_.c" \ |
| 476 | "$(OX)\setup_.c" \ |
| @@ -584,10 +585,11 @@ | |
| 584 | "$(SRCDIR)\fossil.page.chat.js" \ |
| 585 | "$(SRCDIR)\fossil.page.fileedit.js" \ |
| 586 | "$(SRCDIR)\fossil.page.forumpost.js" \ |
| 587 | "$(SRCDIR)\fossil.page.pikchrshow.js" \ |
| 588 | "$(SRCDIR)\fossil.page.pikchrshowasm.js" \ |
| 589 | "$(SRCDIR)\fossil.page.whistory.js" \ |
| 590 | "$(SRCDIR)\fossil.page.wikiedit.js" \ |
| 591 | "$(SRCDIR)\fossil.pikchr.js" \ |
| 592 | "$(SRCDIR)\fossil.popupwidget.js" \ |
| 593 | "$(SRCDIR)\fossil.storage.js" \ |
| @@ -734,10 +736,11 @@ | |
| 734 | "$(OX)\purge$O" \ |
| 735 | "$(OX)\rebuild$O" \ |
| 736 | "$(OX)\regexp$O" \ |
| 737 | "$(OX)\repolist$O" \ |
| 738 | "$(OX)\report$O" \ |
| 739 | "$(OX)\rss$O" \ |
| 740 | "$(OX)\schema$O" \ |
| 741 | "$(OX)\search$O" \ |
| 742 | "$(OX)\security_audit$O" \ |
| 743 | "$(OX)\setup$O" \ |
| @@ -984,10 +987,11 @@ | |
| 984 | echo "$(OX)\purge.obj" >> $@ |
| 985 | echo "$(OX)\rebuild.obj" >> $@ |
| 986 | echo "$(OX)\regexp.obj" >> $@ |
| 987 | echo "$(OX)\repolist.obj" >> $@ |
| 988 | echo "$(OX)\report.obj" >> $@ |
| 989 | echo "$(OX)\rss.obj" >> $@ |
| 990 | echo "$(OX)\schema.obj" >> $@ |
| 991 | echo "$(OX)\search.obj" >> $@ |
| 992 | echo "$(OX)\security_audit.obj" >> $@ |
| 993 | echo "$(OX)\setup.obj" >> $@ |
| @@ -1217,10 +1221,11 @@ | |
| 1217 | echo "$(SRCDIR)\fossil.page.chat.js" >> $@ |
| 1218 | echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ |
| 1219 | echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ |
| 1220 | echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ |
| 1221 | echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@ |
| 1222 | echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ |
| 1223 | echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ |
| 1224 | echo "$(SRCDIR)\fossil.pikchr.js" >> $@ |
| 1225 | echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ |
| 1226 | echo "$(SRCDIR)\fossil.storage.js" >> $@ |
| @@ -1889,10 +1894,16 @@ | |
| 1889 | "$(OX)\report$O" : "$(OX)\report_.c" "$(OX)\report.h" |
| 1890 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\report_.c" |
| 1891 | |
| 1892 | "$(OX)\report_.c" : "$(SRCDIR)\report.c" |
| 1893 | "$(OBJDIR)\translate$E" $** > $@ |
| 1894 | |
| 1895 | "$(OX)\rss$O" : "$(OX)\rss_.c" "$(OX)\rss.h" |
| 1896 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rss_.c" |
| 1897 | |
| 1898 | "$(OX)\rss_.c" : "$(SRCDIR)\rss.c" |
| @@ -2257,10 +2268,11 @@ | |
| 2257 | "$(OX)\purge_.c":"$(OX)\purge.h" \ |
| 2258 | "$(OX)\rebuild_.c":"$(OX)\rebuild.h" \ |
| 2259 | "$(OX)\regexp_.c":"$(OX)\regexp.h" \ |
| 2260 | "$(OX)\repolist_.c":"$(OX)\repolist.h" \ |
| 2261 | "$(OX)\report_.c":"$(OX)\report.h" \ |
| 2262 | "$(OX)\rss_.c":"$(OX)\rss.h" \ |
| 2263 | "$(OX)\schema_.c":"$(OX)\schema.h" \ |
| 2264 | "$(OX)\search_.c":"$(OX)\search.h" \ |
| 2265 | "$(OX)\security_audit_.c":"$(OX)\security_audit.h" \ |
| 2266 | "$(OX)\setup_.c":"$(OX)\setup.h" \ |
| 2267 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -467,10 +467,11 @@ | |
| 467 | "$(OX)\purge_.c" \ |
| 468 | "$(OX)\rebuild_.c" \ |
| 469 | "$(OX)\regexp_.c" \ |
| 470 | "$(OX)\repolist_.c" \ |
| 471 | "$(OX)\report_.c" \ |
| 472 | "$(OX)\robot_.c" \ |
| 473 | "$(OX)\rss_.c" \ |
| 474 | "$(OX)\schema_.c" \ |
| 475 | "$(OX)\search_.c" \ |
| 476 | "$(OX)\security_audit_.c" \ |
| 477 | "$(OX)\setup_.c" \ |
| @@ -584,10 +585,11 @@ | |
| 585 | "$(SRCDIR)\fossil.page.chat.js" \ |
| 586 | "$(SRCDIR)\fossil.page.fileedit.js" \ |
| 587 | "$(SRCDIR)\fossil.page.forumpost.js" \ |
| 588 | "$(SRCDIR)\fossil.page.pikchrshow.js" \ |
| 589 | "$(SRCDIR)\fossil.page.pikchrshowasm.js" \ |
| 590 | "$(SRCDIR)\fossil.page.ticket.js" \ |
| 591 | "$(SRCDIR)\fossil.page.whistory.js" \ |
| 592 | "$(SRCDIR)\fossil.page.wikiedit.js" \ |
| 593 | "$(SRCDIR)\fossil.pikchr.js" \ |
| 594 | "$(SRCDIR)\fossil.popupwidget.js" \ |
| 595 | "$(SRCDIR)\fossil.storage.js" \ |
| @@ -734,10 +736,11 @@ | |
| 736 | "$(OX)\purge$O" \ |
| 737 | "$(OX)\rebuild$O" \ |
| 738 | "$(OX)\regexp$O" \ |
| 739 | "$(OX)\repolist$O" \ |
| 740 | "$(OX)\report$O" \ |
| 741 | "$(OX)\robot$O" \ |
| 742 | "$(OX)\rss$O" \ |
| 743 | "$(OX)\schema$O" \ |
| 744 | "$(OX)\search$O" \ |
| 745 | "$(OX)\security_audit$O" \ |
| 746 | "$(OX)\setup$O" \ |
| @@ -984,10 +987,11 @@ | |
| 987 | echo "$(OX)\purge.obj" >> $@ |
| 988 | echo "$(OX)\rebuild.obj" >> $@ |
| 989 | echo "$(OX)\regexp.obj" >> $@ |
| 990 | echo "$(OX)\repolist.obj" >> $@ |
| 991 | echo "$(OX)\report.obj" >> $@ |
| 992 | echo "$(OX)\robot.obj" >> $@ |
| 993 | echo "$(OX)\rss.obj" >> $@ |
| 994 | echo "$(OX)\schema.obj" >> $@ |
| 995 | echo "$(OX)\search.obj" >> $@ |
| 996 | echo "$(OX)\security_audit.obj" >> $@ |
| 997 | echo "$(OX)\setup.obj" >> $@ |
| @@ -1217,10 +1221,11 @@ | |
| 1221 | echo "$(SRCDIR)\fossil.page.chat.js" >> $@ |
| 1222 | echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ |
| 1223 | echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ |
| 1224 | echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ |
| 1225 | echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@ |
| 1226 | echo "$(SRCDIR)\fossil.page.ticket.js" >> $@ |
| 1227 | echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ |
| 1228 | echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ |
| 1229 | echo "$(SRCDIR)\fossil.pikchr.js" >> $@ |
| 1230 | echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ |
| 1231 | echo "$(SRCDIR)\fossil.storage.js" >> $@ |
| @@ -1889,10 +1894,16 @@ | |
| 1894 | "$(OX)\report$O" : "$(OX)\report_.c" "$(OX)\report.h" |
| 1895 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\report_.c" |
| 1896 | |
| 1897 | "$(OX)\report_.c" : "$(SRCDIR)\report.c" |
| 1898 | "$(OBJDIR)\translate$E" $** > $@ |
| 1899 | |
| 1900 | "$(OX)\robot$O" : "$(OX)\robot_.c" "$(OX)\robot.h" |
| 1901 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\robot_.c" |
| 1902 | |
| 1903 | "$(OX)\robot_.c" : "$(SRCDIR)\robot.c" |
| 1904 | "$(OBJDIR)\translate$E" $** > $@ |
| 1905 | |
| 1906 | "$(OX)\rss$O" : "$(OX)\rss_.c" "$(OX)\rss.h" |
| 1907 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rss_.c" |
| 1908 | |
| 1909 | "$(OX)\rss_.c" : "$(SRCDIR)\rss.c" |
| @@ -2257,10 +2268,11 @@ | |
| 2268 | "$(OX)\purge_.c":"$(OX)\purge.h" \ |
| 2269 | "$(OX)\rebuild_.c":"$(OX)\rebuild.h" \ |
| 2270 | "$(OX)\regexp_.c":"$(OX)\regexp.h" \ |
| 2271 | "$(OX)\repolist_.c":"$(OX)\repolist.h" \ |
| 2272 | "$(OX)\report_.c":"$(OX)\report.h" \ |
| 2273 | "$(OX)\robot_.c":"$(OX)\robot.h" \ |
| 2274 | "$(OX)\rss_.c":"$(OX)\rss.h" \ |
| 2275 | "$(OX)\schema_.c":"$(OX)\schema.h" \ |
| 2276 | "$(OX)\search_.c":"$(OX)\search.h" \ |
| 2277 | "$(OX)\security_audit_.c":"$(OX)\security_audit.h" \ |
| 2278 | "$(OX)\setup_.c":"$(OX)\setup.h" \ |
| 2279 |
+3
-3
| --- www/aboutcgi.wiki | ||
| +++ www/aboutcgi.wiki | ||
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | |
| 132 | 132 | Usually, the webpage being requested is the first term of the |
| 133 | 133 | PATH_INFO environment variable. (Exceptions to this rule are noted |
| 134 | 134 | in the sequel.) For our example, the first term of PATH_INFO |
| 135 | 135 | is "timeline", which means that Fossil will generate |
| 136 | -the [/help?cmd=/timeline|/timeline] webpage. | |
| 136 | +the [/help/www/timeline|/timeline] webpage. | |
| 137 | 137 | |
| 138 | 138 | With Fossil, terms of PATH_INFO beyond the webpage name are converted into |
| 139 | 139 | the "name" query parameter. Hence, the following two URLs mean |
| 140 | 140 | exactly the same thing to Fossil: |
| 141 | 141 | <ol type='A'> |
| @@ -143,11 +143,11 @@ | ||
| 143 | 143 | <li> [https://fossil-scm.org/home/info?name=c14ecc43] |
| 144 | 144 | </ol> |
| 145 | 145 | |
| 146 | 146 | In both cases, the CGI script is called "/fossil". For case (A), |
| 147 | 147 | the PATH_INFO variable will be "info/c14ecc43" and so the |
| 148 | -"[/help?cmd=/info|/info]" webpage will be generated and the suffix of | |
| 148 | +"[/help/www/info|/info]" webpage will be generated and the suffix of | |
| 149 | 149 | PATH_INFO will be converted into the "name" query parameter, which |
| 150 | 150 | identifies the artifact about which information is requested. |
| 151 | 151 | In case (B), the PATH_INFO is just "info", but the same "name" |
| 152 | 152 | query parameter is set explicitly by the URL itself. |
| 153 | 153 | |
| @@ -282,11 +282,11 @@ | ||
| 282 | 282 | can then reference various properties values. |
| 283 | 283 | Fossil does not care where the value of each property comes from (POST |
| 284 | 284 | content, cookies, or query parameters) only that the property exists |
| 285 | 285 | and has a value.</p></li> |
| 286 | 286 | <li><p> |
| 287 | -The "[/help?cmd=ui|fossil ui]" and "[/help?cmd=server|fossil server]" commands | |
| 287 | +The "[/help/ui|fossil ui]" and "[/help/server|fossil server]" commands | |
| 288 | 288 | are implemented using a simple built-in web server that accepts incoming HTTP |
| 289 | 289 | requests, translates each request into a CGI invocation, then creates a |
| 290 | 290 | separate child Fossil process to handle each request. In other words, CGI |
| 291 | 291 | is used internally to implement "fossil ui/server". |
| 292 | 292 | <br><br> |
| 293 | 293 |
| --- www/aboutcgi.wiki | |
| +++ www/aboutcgi.wiki | |
| @@ -131,11 +131,11 @@ | |
| 131 | |
| 132 | Usually, the webpage being requested is the first term of the |
| 133 | PATH_INFO environment variable. (Exceptions to this rule are noted |
| 134 | in the sequel.) For our example, the first term of PATH_INFO |
| 135 | is "timeline", which means that Fossil will generate |
| 136 | the [/help?cmd=/timeline|/timeline] webpage. |
| 137 | |
| 138 | With Fossil, terms of PATH_INFO beyond the webpage name are converted into |
| 139 | the "name" query parameter. Hence, the following two URLs mean |
| 140 | exactly the same thing to Fossil: |
| 141 | <ol type='A'> |
| @@ -143,11 +143,11 @@ | |
| 143 | <li> [https://fossil-scm.org/home/info?name=c14ecc43] |
| 144 | </ol> |
| 145 | |
| 146 | In both cases, the CGI script is called "/fossil". For case (A), |
| 147 | the PATH_INFO variable will be "info/c14ecc43" and so the |
| 148 | "[/help?cmd=/info|/info]" webpage will be generated and the suffix of |
| 149 | PATH_INFO will be converted into the "name" query parameter, which |
| 150 | identifies the artifact about which information is requested. |
| 151 | In case (B), the PATH_INFO is just "info", but the same "name" |
| 152 | query parameter is set explicitly by the URL itself. |
| 153 | |
| @@ -282,11 +282,11 @@ | |
| 282 | can then reference various properties values. |
| 283 | Fossil does not care where the value of each property comes from (POST |
| 284 | content, cookies, or query parameters) only that the property exists |
| 285 | and has a value.</p></li> |
| 286 | <li><p> |
| 287 | The "[/help?cmd=ui|fossil ui]" and "[/help?cmd=server|fossil server]" commands |
| 288 | are implemented using a simple built-in web server that accepts incoming HTTP |
| 289 | requests, translates each request into a CGI invocation, then creates a |
| 290 | separate child Fossil process to handle each request. In other words, CGI |
| 291 | is used internally to implement "fossil ui/server". |
| 292 | <br><br> |
| 293 |
| --- www/aboutcgi.wiki | |
| +++ www/aboutcgi.wiki | |
| @@ -131,11 +131,11 @@ | |
| 131 | |
| 132 | Usually, the webpage being requested is the first term of the |
| 133 | PATH_INFO environment variable. (Exceptions to this rule are noted |
| 134 | in the sequel.) For our example, the first term of PATH_INFO |
| 135 | is "timeline", which means that Fossil will generate |
| 136 | the [/help/www/timeline|/timeline] webpage. |
| 137 | |
| 138 | With Fossil, terms of PATH_INFO beyond the webpage name are converted into |
| 139 | the "name" query parameter. Hence, the following two URLs mean |
| 140 | exactly the same thing to Fossil: |
| 141 | <ol type='A'> |
| @@ -143,11 +143,11 @@ | |
| 143 | <li> [https://fossil-scm.org/home/info?name=c14ecc43] |
| 144 | </ol> |
| 145 | |
| 146 | In both cases, the CGI script is called "/fossil". For case (A), |
| 147 | the PATH_INFO variable will be "info/c14ecc43" and so the |
| 148 | "[/help/www/info|/info]" webpage will be generated and the suffix of |
| 149 | PATH_INFO will be converted into the "name" query parameter, which |
| 150 | identifies the artifact about which information is requested. |
| 151 | In case (B), the PATH_INFO is just "info", but the same "name" |
| 152 | query parameter is set explicitly by the URL itself. |
| 153 | |
| @@ -282,11 +282,11 @@ | |
| 282 | can then reference various properties values. |
| 283 | Fossil does not care where the value of each property comes from (POST |
| 284 | content, cookies, or query parameters) only that the property exists |
| 285 | and has a value.</p></li> |
| 286 | <li><p> |
| 287 | The "[/help/ui|fossil ui]" and "[/help/server|fossil server]" commands |
| 288 | are implemented using a simple built-in web server that accepts incoming HTTP |
| 289 | requests, translates each request into a CGI invocation, then creates a |
| 290 | separate child Fossil process to handle each request. In other words, CGI |
| 291 | is used internally to implement "fossil ui/server". |
| 292 | <br><br> |
| 293 |
+11
-11
| --- www/aboutdownload.wiki | ||
| +++ www/aboutdownload.wiki | ||
| @@ -4,29 +4,29 @@ | ||
| 4 | 4 | |
| 5 | 5 | The [/uv/download.html|Download] page for the Fossil self-hosting |
| 6 | 6 | repository is implemented using [./unvers.wiki|unversioned files]. |
| 7 | 7 | The "download.html" screen itself, and the various build products |
| 8 | 8 | are all stored as unversioned content. The download.html page |
| 9 | -uses XMLHttpRequest() to retrieve the [/help?cmd=/juvlist|/juvlist] webpage | |
| 9 | +uses XMLHttpRequest() to retrieve the [/help/www/juvlist|/juvlist] webpage | |
| 10 | 10 | for a list of all unversioned files. Javascript in the |
| 11 | 11 | [/uv/download.js?mimetype=text/plain|download.js] file (which is |
| 12 | 12 | sourced by "download.html") then figures out which unversioned files are |
| 13 | 13 | build products and paints appropriate icons on the displayed |
| 14 | 14 | download page. |
| 15 | 15 | |
| 16 | 16 | Except, the "Source Tarball" download products are not stored as |
| 17 | 17 | unversioned files. They are computed on-demand by the |
| 18 | -[/help?cmd=/tarball|/tarball web page]. | |
| 18 | +[/help/www/tarball|/tarball web page]. | |
| 19 | 19 | |
| 20 | 20 | When a new version is generated, the developers use the |
| 21 | -[/help?cmd=uv|fossil uv edit] command to make minor changes | |
| 21 | +[/help/uv|fossil uv edit] command to make minor changes | |
| 22 | 22 | to the "[/uv/download.js?mimetype=text/plain|download.js]" |
| 23 | 23 | file so that it knows about the |
| 24 | 24 | new version number. Then the developers run |
| 25 | -the [/help?cmd=uv|fossil uv add] command for each | |
| 25 | +the [/help/uv|fossil uv add] command for each | |
| 26 | 26 | build product. Finally, the |
| 27 | -[/help?cmd=uv|fossil uv sync] command is run to push all | |
| 27 | +[/help/uv|fossil uv sync] command is run to push all | |
| 28 | 28 | the content up to servers. All |
| 29 | 29 | [./selfhost.wiki|three self-hosting repositories] for Fossil |
| 30 | 30 | are updated automatically. |
| 31 | 31 | |
| 32 | 32 | <h2>2.0 Details</h2> |
| @@ -51,11 +51,11 @@ | ||
| 51 | 51 | "[./embeddeddoc.wiki|embedded documentation]" for further details on |
| 52 | 52 | how this <div class='fossil-doc'> markup works. |
| 53 | 53 | |
| 54 | 54 | With each new release, the "releases" variable in the javascript on |
| 55 | 55 | the [/uv/download.js?mimetype=text/plain|download.js] page is |
| 56 | -edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add | |
| 56 | +edited (using "[/help/uv|fossil uv edit download.js]") to add | |
| 57 | 57 | details of the release. |
| 58 | 58 | |
| 59 | 59 | When the JavaScript in the "download.js" file runs, it requests |
| 60 | 60 | a listing of all unversioned content using the /juvlist URL. |
| 61 | 61 | ([/juvlist|sample /juvlist output]). The content of the download page is |
| @@ -65,25 +65,25 @@ | ||
| 65 | 65 | Build products need to be constructed on different machines. The precompiled |
| 66 | 66 | binary for Linux is compiled on Linux, the precompiled binary for Windows |
| 67 | 67 | is compiled on Windows11, and so forth. After a new release is tagged, |
| 68 | 68 | the release manager goes around to each of the target platforms, checks |
| 69 | 69 | out the release and compiles it, then runs |
| 70 | -[/help?cmd=uv|fossil uv add] for the build product followed by | |
| 71 | -[/help?cmd=uv|fossil uv sync] to push the new build product to the | |
| 70 | +[/help/uv|fossil uv add] for the build product followed by | |
| 71 | +[/help/uv|fossil uv sync] to push the new build product to the | |
| 72 | 72 | [./selfhost.wiki|various servers]. This process is repeated for |
| 73 | 73 | each build product. |
| 74 | 74 | |
| 75 | 75 | When older builds are retired from the download page, the |
| 76 | 76 | [/uv/download.js?mimetype=text/plain|download.js] page is again |
| 77 | 77 | edited to remove the corresponding entry from the "release" variable |
| 78 | 78 | and the edit is synced using |
| 79 | -[/help?cmd=uv|fossil uv sync]. This causes the build products to | |
| 79 | +[/help/uv|fossil uv sync]. This causes the build products to | |
| 80 | 80 | disappear from the download page immediately. But those build products |
| 81 | 81 | are still taking up space in the unversioned content table of the |
| 82 | 82 | server repository. To purge the obsolete build products, one or |
| 83 | -more [/help?cmd=uv|fossil uv rm] commands are run, followed by | |
| 84 | -another [/help?cmd=uv|fossil uv sync]. It is important to purge | |
| 83 | +more [/help/uv|fossil uv rm] commands are run, followed by | |
| 84 | +another [/help/uv|fossil uv sync]. It is important to purge | |
| 85 | 85 | obsolete build products since they take up a lot of space. |
| 86 | 86 | At [/repo-tabsize] you can see that the unversioned table takes up |
| 87 | 87 | a substantial fraction of the repository. |
| 88 | 88 | |
| 89 | 89 | <h2>3.0 Security</h2> |
| 90 | 90 |
| --- www/aboutdownload.wiki | |
| +++ www/aboutdownload.wiki | |
| @@ -4,29 +4,29 @@ | |
| 4 | |
| 5 | The [/uv/download.html|Download] page for the Fossil self-hosting |
| 6 | repository is implemented using [./unvers.wiki|unversioned files]. |
| 7 | The "download.html" screen itself, and the various build products |
| 8 | are all stored as unversioned content. The download.html page |
| 9 | uses XMLHttpRequest() to retrieve the [/help?cmd=/juvlist|/juvlist] webpage |
| 10 | for a list of all unversioned files. Javascript in the |
| 11 | [/uv/download.js?mimetype=text/plain|download.js] file (which is |
| 12 | sourced by "download.html") then figures out which unversioned files are |
| 13 | build products and paints appropriate icons on the displayed |
| 14 | download page. |
| 15 | |
| 16 | Except, the "Source Tarball" download products are not stored as |
| 17 | unversioned files. They are computed on-demand by the |
| 18 | [/help?cmd=/tarball|/tarball web page]. |
| 19 | |
| 20 | When a new version is generated, the developers use the |
| 21 | [/help?cmd=uv|fossil uv edit] command to make minor changes |
| 22 | to the "[/uv/download.js?mimetype=text/plain|download.js]" |
| 23 | file so that it knows about the |
| 24 | new version number. Then the developers run |
| 25 | the [/help?cmd=uv|fossil uv add] command for each |
| 26 | build product. Finally, the |
| 27 | [/help?cmd=uv|fossil uv sync] command is run to push all |
| 28 | the content up to servers. All |
| 29 | [./selfhost.wiki|three self-hosting repositories] for Fossil |
| 30 | are updated automatically. |
| 31 | |
| 32 | <h2>2.0 Details</h2> |
| @@ -51,11 +51,11 @@ | |
| 51 | "[./embeddeddoc.wiki|embedded documentation]" for further details on |
| 52 | how this <div class='fossil-doc'> markup works. |
| 53 | |
| 54 | With each new release, the "releases" variable in the javascript on |
| 55 | the [/uv/download.js?mimetype=text/plain|download.js] page is |
| 56 | edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add |
| 57 | details of the release. |
| 58 | |
| 59 | When the JavaScript in the "download.js" file runs, it requests |
| 60 | a listing of all unversioned content using the /juvlist URL. |
| 61 | ([/juvlist|sample /juvlist output]). The content of the download page is |
| @@ -65,25 +65,25 @@ | |
| 65 | Build products need to be constructed on different machines. The precompiled |
| 66 | binary for Linux is compiled on Linux, the precompiled binary for Windows |
| 67 | is compiled on Windows11, and so forth. After a new release is tagged, |
| 68 | the release manager goes around to each of the target platforms, checks |
| 69 | out the release and compiles it, then runs |
| 70 | [/help?cmd=uv|fossil uv add] for the build product followed by |
| 71 | [/help?cmd=uv|fossil uv sync] to push the new build product to the |
| 72 | [./selfhost.wiki|various servers]. This process is repeated for |
| 73 | each build product. |
| 74 | |
| 75 | When older builds are retired from the download page, the |
| 76 | [/uv/download.js?mimetype=text/plain|download.js] page is again |
| 77 | edited to remove the corresponding entry from the "release" variable |
| 78 | and the edit is synced using |
| 79 | [/help?cmd=uv|fossil uv sync]. This causes the build products to |
| 80 | disappear from the download page immediately. But those build products |
| 81 | are still taking up space in the unversioned content table of the |
| 82 | server repository. To purge the obsolete build products, one or |
| 83 | more [/help?cmd=uv|fossil uv rm] commands are run, followed by |
| 84 | another [/help?cmd=uv|fossil uv sync]. It is important to purge |
| 85 | obsolete build products since they take up a lot of space. |
| 86 | At [/repo-tabsize] you can see that the unversioned table takes up |
| 87 | a substantial fraction of the repository. |
| 88 | |
| 89 | <h2>3.0 Security</h2> |
| 90 |
| --- www/aboutdownload.wiki | |
| +++ www/aboutdownload.wiki | |
| @@ -4,29 +4,29 @@ | |
| 4 | |
| 5 | The [/uv/download.html|Download] page for the Fossil self-hosting |
| 6 | repository is implemented using [./unvers.wiki|unversioned files]. |
| 7 | The "download.html" screen itself, and the various build products |
| 8 | are all stored as unversioned content. The download.html page |
| 9 | uses XMLHttpRequest() to retrieve the [/help/www/juvlist|/juvlist] webpage |
| 10 | for a list of all unversioned files. Javascript in the |
| 11 | [/uv/download.js?mimetype=text/plain|download.js] file (which is |
| 12 | sourced by "download.html") then figures out which unversioned files are |
| 13 | build products and paints appropriate icons on the displayed |
| 14 | download page. |
| 15 | |
| 16 | Except, the "Source Tarball" download products are not stored as |
| 17 | unversioned files. They are computed on-demand by the |
| 18 | [/help/www/tarball|/tarball web page]. |
| 19 | |
| 20 | When a new version is generated, the developers use the |
| 21 | [/help/uv|fossil uv edit] command to make minor changes |
| 22 | to the "[/uv/download.js?mimetype=text/plain|download.js]" |
| 23 | file so that it knows about the |
| 24 | new version number. Then the developers run |
| 25 | the [/help/uv|fossil uv add] command for each |
| 26 | build product. Finally, the |
| 27 | [/help/uv|fossil uv sync] command is run to push all |
| 28 | the content up to servers. All |
| 29 | [./selfhost.wiki|three self-hosting repositories] for Fossil |
| 30 | are updated automatically. |
| 31 | |
| 32 | <h2>2.0 Details</h2> |
| @@ -51,11 +51,11 @@ | |
| 51 | "[./embeddeddoc.wiki|embedded documentation]" for further details on |
| 52 | how this <div class='fossil-doc'> markup works. |
| 53 | |
| 54 | With each new release, the "releases" variable in the javascript on |
| 55 | the [/uv/download.js?mimetype=text/plain|download.js] page is |
| 56 | edited (using "[/help/uv|fossil uv edit download.js]") to add |
| 57 | details of the release. |
| 58 | |
| 59 | When the JavaScript in the "download.js" file runs, it requests |
| 60 | a listing of all unversioned content using the /juvlist URL. |
| 61 | ([/juvlist|sample /juvlist output]). The content of the download page is |
| @@ -65,25 +65,25 @@ | |
| 65 | Build products need to be constructed on different machines. The precompiled |
| 66 | binary for Linux is compiled on Linux, the precompiled binary for Windows |
| 67 | is compiled on Windows11, and so forth. After a new release is tagged, |
| 68 | the release manager goes around to each of the target platforms, checks |
| 69 | out the release and compiles it, then runs |
| 70 | [/help/uv|fossil uv add] for the build product followed by |
| 71 | [/help/uv|fossil uv sync] to push the new build product to the |
| 72 | [./selfhost.wiki|various servers]. This process is repeated for |
| 73 | each build product. |
| 74 | |
| 75 | When older builds are retired from the download page, the |
| 76 | [/uv/download.js?mimetype=text/plain|download.js] page is again |
| 77 | edited to remove the corresponding entry from the "release" variable |
| 78 | and the edit is synced using |
| 79 | [/help/uv|fossil uv sync]. This causes the build products to |
| 80 | disappear from the download page immediately. But those build products |
| 81 | are still taking up space in the unversioned content table of the |
| 82 | server repository. To purge the obsolete build products, one or |
| 83 | more [/help/uv|fossil uv rm] commands are run, followed by |
| 84 | another [/help/uv|fossil uv sync]. It is important to purge |
| 85 | obsolete build products since they take up a lot of space. |
| 86 | At [/repo-tabsize] you can see that the unversioned table takes up |
| 87 | a substantial fraction of the repository. |
| 88 | |
| 89 | <h2>3.0 Security</h2> |
| 90 |
+11
-11
| --- www/alerts.md | ||
| +++ www/alerts.md | ||
| @@ -3,11 +3,11 @@ | ||
| 3 | 3 | ## Overview |
| 4 | 4 | |
| 5 | 5 | Beginning with version 2.7, Fossil can send email messages to |
| 6 | 6 | subscribers to alert them to changes in the repository: |
| 7 | 7 | |
| 8 | - * New [checkins](/help?cmd=ci) | |
| 8 | + * New [checkins](/help/ci) | |
| 9 | 9 | * [Ticket](./tickets.wiki) changes |
| 10 | 10 | * [Wiki](./wikitheory.wiki) page changes |
| 11 | 11 | * New and edited [forum](./forum.wiki) posts |
| 12 | 12 | * Users receiving [new permissions](./caps/index.md) (admins only) |
| 13 | 13 | * Announcements |
| @@ -541,26 +541,26 @@ | ||
| 541 | 541 | control the email alert system, some of which have not been mentioned so |
| 542 | 542 | far: |
| 543 | 543 | |
| 544 | 544 | Commands: |
| 545 | 545 | |
| 546 | - * The [`alerts`](/help?cmd=alerts) command | |
| 547 | - * The [`test-alert`](/help?cmd=test-alert) command | |
| 548 | - * The [`test-add-alerts`](/help?cmd=test-add-alerts) command | |
| 546 | + * The [`alerts`](/help/alerts) command | |
| 547 | + * The [`test-alert`](/help/test-alert) command | |
| 548 | + * The [`test-add-alerts`](/help/test-add-alerts) command | |
| 549 | 549 | |
| 550 | 550 | Web pages available to users and subscribers: |
| 551 | 551 | |
| 552 | - * The [`/subscribe`](/help?cmd=/subscribe) page | |
| 553 | - * The [`/alerts`](/help?cmd=/alerts) page | |
| 554 | - * The [`/unsubscribe`](/help?cmd=/unsubscribe) page | |
| 555 | - * The [`/renew`](/help?cmd=/renew) page | |
| 556 | - * The [`/contact_admin`](/help?cmd=/contact_admin) page | |
| 552 | + * The [`/subscribe`](/help/www/subscribe) page | |
| 553 | + * The [`/alerts`](/help/www/alerts) page | |
| 554 | + * The [`/unsubscribe`](/help/www/unsubscribe) page | |
| 555 | + * The [`/renew`](/help/www/renew) page | |
| 556 | + * The [`/contact_admin`](/help/www/contact_admin) page | |
| 557 | 557 | |
| 558 | 558 | Administrator-only web pages: |
| 559 | 559 | |
| 560 | - * The [`/setup_notification`](/help?cmd=/setup_notification) page | |
| 561 | - * The [`/subscribers`](/help?cmd=/subscribers) page | |
| 560 | + * The [`/setup_notification`](/help/www/setup_notification) page | |
| 561 | + * The [`/subscribers`](/help/www/subscribers) page | |
| 562 | 562 | |
| 563 | 563 | |
| 564 | 564 | <a id="design"></a> |
| 565 | 565 | ## Design of Email Alerts |
| 566 | 566 | |
| 567 | 567 |
| --- www/alerts.md | |
| +++ www/alerts.md | |
| @@ -3,11 +3,11 @@ | |
| 3 | ## Overview |
| 4 | |
| 5 | Beginning with version 2.7, Fossil can send email messages to |
| 6 | subscribers to alert them to changes in the repository: |
| 7 | |
| 8 | * New [checkins](/help?cmd=ci) |
| 9 | * [Ticket](./tickets.wiki) changes |
| 10 | * [Wiki](./wikitheory.wiki) page changes |
| 11 | * New and edited [forum](./forum.wiki) posts |
| 12 | * Users receiving [new permissions](./caps/index.md) (admins only) |
| 13 | * Announcements |
| @@ -541,26 +541,26 @@ | |
| 541 | control the email alert system, some of which have not been mentioned so |
| 542 | far: |
| 543 | |
| 544 | Commands: |
| 545 | |
| 546 | * The [`alerts`](/help?cmd=alerts) command |
| 547 | * The [`test-alert`](/help?cmd=test-alert) command |
| 548 | * The [`test-add-alerts`](/help?cmd=test-add-alerts) command |
| 549 | |
| 550 | Web pages available to users and subscribers: |
| 551 | |
| 552 | * The [`/subscribe`](/help?cmd=/subscribe) page |
| 553 | * The [`/alerts`](/help?cmd=/alerts) page |
| 554 | * The [`/unsubscribe`](/help?cmd=/unsubscribe) page |
| 555 | * The [`/renew`](/help?cmd=/renew) page |
| 556 | * The [`/contact_admin`](/help?cmd=/contact_admin) page |
| 557 | |
| 558 | Administrator-only web pages: |
| 559 | |
| 560 | * The [`/setup_notification`](/help?cmd=/setup_notification) page |
| 561 | * The [`/subscribers`](/help?cmd=/subscribers) page |
| 562 | |
| 563 | |
| 564 | <a id="design"></a> |
| 565 | ## Design of Email Alerts |
| 566 | |
| 567 |
| --- www/alerts.md | |
| +++ www/alerts.md | |
| @@ -3,11 +3,11 @@ | |
| 3 | ## Overview |
| 4 | |
| 5 | Beginning with version 2.7, Fossil can send email messages to |
| 6 | subscribers to alert them to changes in the repository: |
| 7 | |
| 8 | * New [checkins](/help/ci) |
| 9 | * [Ticket](./tickets.wiki) changes |
| 10 | * [Wiki](./wikitheory.wiki) page changes |
| 11 | * New and edited [forum](./forum.wiki) posts |
| 12 | * Users receiving [new permissions](./caps/index.md) (admins only) |
| 13 | * Announcements |
| @@ -541,26 +541,26 @@ | |
| 541 | control the email alert system, some of which have not been mentioned so |
| 542 | far: |
| 543 | |
| 544 | Commands: |
| 545 | |
| 546 | * The [`alerts`](/help/alerts) command |
| 547 | * The [`test-alert`](/help/test-alert) command |
| 548 | * The [`test-add-alerts`](/help/test-add-alerts) command |
| 549 | |
| 550 | Web pages available to users and subscribers: |
| 551 | |
| 552 | * The [`/subscribe`](/help/www/subscribe) page |
| 553 | * The [`/alerts`](/help/www/alerts) page |
| 554 | * The [`/unsubscribe`](/help/www/unsubscribe) page |
| 555 | * The [`/renew`](/help/www/renew) page |
| 556 | * The [`/contact_admin`](/help/www/contact_admin) page |
| 557 | |
| 558 | Administrator-only web pages: |
| 559 | |
| 560 | * The [`/setup_notification`](/help/www/setup_notification) page |
| 561 | * The [`/subscribers`](/help/www/subscribers) page |
| 562 | |
| 563 | |
| 564 | <a id="design"></a> |
| 565 | ## Design of Email Alerts |
| 566 | |
| 567 |
+147
-31
| --- www/antibot.wiki | ||
| +++ www/antibot.wiki | ||
| @@ -1,17 +1,34 @@ | ||
| 1 | 1 | <title>Defense Against Robots</title> |
| 2 | 2 | |
| 3 | -A typical Fossil website can have millions of pages, and many of | |
| 4 | -those pages (for example diffs and annotations and tarballs) can | |
| 5 | -be expensive to compute. | |
| 3 | +A typical Fossil website can have billions and billions of pages, | |
| 4 | +and many of those pages (for example diffs and annotations and tarballs) | |
| 5 | +can be expensive to compute. | |
| 6 | 6 | If a robot walks a Fossil-generated website, |
| 7 | 7 | it can present a crippling bandwidth and CPU load. |
| 8 | +A "robots.txt" file can help, but in practice, most robots these | |
| 9 | +days ignore the robots.txt file, so it won't help much. | |
| 8 | 10 | |
| 9 | 11 | A Fossil website is intended to be used |
| 10 | 12 | interactively by humans, not walked by robots. This article |
| 11 | 13 | describes the techniques used by Fossil to try to welcome human |
| 12 | 14 | users while keeping out robots. |
| 15 | + | |
| 16 | +<h2>Defenses Are Enabled By Default</h2> | |
| 17 | + | |
| 18 | +In the latest implementations of Fossil, most robot defenses are | |
| 19 | +enabled by default. You can probably get by with standing up a | |
| 20 | +public-facing Fossil instance in the default configuration. But | |
| 21 | +you can also customize the defenses to serve your particular needs. | |
| 22 | + | |
| 23 | +<h2>Customizing Anti-Robot Defenses</h2> | |
| 24 | + | |
| 25 | +Admin users can configure robot defenses on the | |
| 26 | +"Robot Defense Settings" page (/setup_robot). | |
| 27 | +That page is accessible (to Admin users) from the default menu bar | |
| 28 | +by click on the "Admin" menu choice, then selecting the | |
| 29 | +"Robot-Defense" link from the list. | |
| 13 | 30 | |
| 14 | 31 | <h2>The Hyperlink User Capability</h2> |
| 15 | 32 | |
| 16 | 33 | Every Fossil web session has a "user". For random passers-by on the internet |
| 17 | 34 | (and for robots) that user is "nobody". The "anonymous" user is also |
| @@ -36,11 +53,11 @@ | ||
| 36 | 53 | |
| 37 | 54 | But requiring a login, even an anonymous login, can be annoying. |
| 38 | 55 | Fossil provides other techniques for blocking robots which |
| 39 | 56 | are less cumbersome to humans. |
| 40 | 57 | |
| 41 | -<h2>Automatic Hyperlinks Based on UserAgent</h2> | |
| 58 | +<h2>Automatic Hyperlinks Based on UserAgent and Javascript</h2> | |
| 42 | 59 | |
| 43 | 60 | Fossil has the ability to selectively enable hyperlinks for users |
| 44 | 61 | that lack the <b>Hyperlink</b> capability based on their UserAgent string in the |
| 45 | 62 | HTTP request header and on the browsers ability to run Javascript. |
| 46 | 63 | |
| @@ -68,21 +85,22 @@ | ||
| 68 | 85 | to "play nicely" on the internet and are quite open |
| 69 | 86 | about the fact that they are a robot. And so the UserAgent string |
| 70 | 87 | provides a good first-guess about whether or not a request originates |
| 71 | 88 | from a human or a robot. |
| 72 | 89 | |
| 73 | -In Fossil, under the Admin/Robot-Defense menu, there is a setting entitled | |
| 74 | -"<b>Enable hyperlinks based on User-Agent and/or Javascript</b>". | |
| 75 | -If this setting is set to "UserAgent only" or "UserAgent and Javascript", | |
| 76 | -and if the UserAgent string looks like a human and not a robot, then | |
| 90 | +The [/help/auto-hyperlink|auto-hyperlink] setting, shown as | |
| 91 | +"<b>Enable hyperlinks based on User-Agent and/or Javascript</b>" on | |
| 92 | +the Robot Defense Settings page, | |
| 93 | +can be set to "UserAgent only" or "UserAgent and Javascript" or "off". | |
| 94 | +If the UserAgent string looks like a human and not a robot, then | |
| 77 | 95 | Fossil will enable hyperlinks even if the <b>Hyperlink</b> capability |
| 78 | 96 | is omitted from the user permissions. This setting gives humans easy |
| 79 | 97 | access to the hyperlinks while preventing robots |
| 80 | 98 | from walking the billions of pages on a typical Fossil site. |
| 81 | 99 | |
| 82 | -If the setting is "UserAgent only", then the hyperlinks are simply | |
| 83 | -enabled and that is all. But if the setting is "UserAgent and Javascript", | |
| 100 | +If the setting is "UserAgent only" (2), then the hyperlinks are simply | |
| 101 | +enabled and that is all. But if the setting is "UserAgent and Javascript" (1), | |
| 84 | 102 | then the hyperlinks are not enabled directly. |
| 85 | 103 | Instead, the HTML code that is generated contains anchor tags ("<a>") |
| 86 | 104 | with "href=" attributes that point to [/honeypot] rather than the correct |
| 87 | 105 | link. JavaScript code is added to the end of the page that goes back and |
| 88 | 106 | fills in the correct "href=" attributes of |
| @@ -92,30 +110,14 @@ | ||
| 92 | 110 | UserAgent string. Most robots do not bother to run JavaScript and |
| 93 | 111 | so to the robot the empty anchor tag will be useless. But all modern |
| 94 | 112 | web browsers implement JavaScript, so hyperlinks will show up |
| 95 | 113 | normally for human users. |
| 96 | 114 | |
| 97 | -<h2>Further Defenses</h2> | |
| 98 | - | |
| 99 | -Recently (as of this writing, in the spring of 2013) the Fossil server | |
| 100 | -on the SQLite website ([http://www.sqlite.org/src/]) has been hit repeatedly | |
| 101 | -by Chinese robots that use forged UserAgent strings to make them look | |
| 102 | -like normal web browsers and which interpret JavaScript. We do not | |
| 103 | -believe these attacks to be nefarious since SQLite is public domain | |
| 104 | -and the attackers could obtain all information they ever wanted to | |
| 105 | -know about SQLite simply by cloning the repository. Instead, we | |
| 106 | -believe these "attacks" are coming from "script kiddies". But regardless | |
| 107 | -of whether or not malice is involved, these attacks do present | |
| 108 | -an unnecessary load on the server which reduces the responsiveness of | |
| 109 | -the SQLite website for well-behaved and socially responsible users. | |
| 110 | -For this reason, additional defenses against | |
| 111 | -robots have been put in place. | |
| 112 | - | |
| 113 | -On the Admin/Robot-Defense page of Fossil, just below the | |
| 114 | -"<b>Enable hyperlinks using User-Agent and/or Javascript</b>" | |
| 115 | -setting, there are now two additional sub-settings that can be optionally | |
| 116 | -enabled to control hyperlinks. | |
| 115 | +If the [/help/auto-hyperlink|"auto-hyperlink"] setting is (2) | |
| 116 | +"<b>Enable hyperlinks using User-Agent and/or Javascript</b>", | |
| 117 | +then there are now two additional sub-settings that control when | |
| 118 | +hyperlinks are enabled. | |
| 117 | 119 | |
| 118 | 120 | The first new sub-setting is a delay (in milliseconds) before setting |
| 119 | 121 | the "href=" attributes on anchor tags. The default value for this |
| 120 | 122 | delay is 10 milliseconds. The idea here is that a robots will try to |
| 121 | 123 | interpret the links on the page immediately, and will not wait for delayed |
| @@ -130,13 +132,127 @@ | ||
| 130 | 132 | |
| 131 | 133 | See also [./loadmgmt.md|Managing Server Load] for a description |
| 132 | 134 | of how expensive pages can be disabled when the server is under heavy |
| 133 | 135 | load. |
| 134 | 136 | |
| 137 | +<h2>Do Not Allow Robot Access To Certain Pages</h2> | |
| 138 | + | |
| 139 | +The [/help/robot-restrict|robot-restrict setting] is a comma-separated | |
| 140 | +list of GLOB patterns for pages for which robot access is prohibited. | |
| 141 | +The default value is: | |
| 142 | + | |
| 143 | +<blockquote><pre> | |
| 144 | +timelineX,diff,annotate,fileage,file,finfo,reports | |
| 145 | +</pre></blockquote> | |
| 146 | + | |
| 147 | +Each entry corresponds to the first path element on the URI for a | |
| 148 | +Fossil-generated page. If Fossil does not know for certain that the | |
| 149 | +HTTP request is coming from a human, then any attempt to access one of | |
| 150 | +these pages brings up a javascript-powered captcha. The user has to | |
| 151 | +click the accept button the captcha once, and that sets a cookie allowing | |
| 152 | +the user to continue surfing without interruption for 15 minutes or so | |
| 153 | +before being presented with another captcha. | |
| 154 | + | |
| 155 | +Some path elements have special meanings: | |
| 156 | + | |
| 157 | + * <b>timelineX →</b> | |
| 158 | + This means a subset of /timeline/ pages that are considered | |
| 159 | + "expensive". The exact definition of which timeline pages are | |
| 160 | + expensive and which are not is still the subject of active | |
| 161 | + experimentation and is likely to change by the time you read this | |
| 162 | + text. The idea is that anybody (including robots) can see a timeline | |
| 163 | + of the most recent changes, but timelines of long-ago change or that | |
| 164 | + contain lists of file changes or other harder-to-compute values are | |
| 165 | + prohibited. | |
| 166 | + | |
| 167 | + * <b>zip →</b> | |
| 168 | + The special "zip" keyword also matches "/tarball/" and "/sqlar/". | |
| 169 | + | |
| 170 | + * <b>zipX →</b> | |
| 171 | + This is like "zip" in that it restricts access to "/zip/", "/tarball"/ | |
| 172 | + and "/sqlar/" but with exceptions:<ol type="a"> | |
| 173 | + <li><p> If the [/help/robot-zip-leaf|robot-zip-leaf] setting is | |
| 174 | + true, then tarballs of leaf check-ins are allowed. This permits | |
| 175 | + URLs that attempt to download the latest check-in on trunk or | |
| 176 | + from a named branch, for example. | |
| 177 | + <li><p> If a check-in has a tag that matches the GLOB list in | |
| 178 | + [/help/robot-zip-tag|robot-zip-tag], then tarballs of that | |
| 179 | + check-in are allowed. This allow check-ins tagged with | |
| 180 | + "release" or "allow-robots" (for example) to be downloaded | |
| 181 | + without restriction. | |
| 182 | + </ol> | |
| 183 | + The "zipX" restriction is not in the default robot-restrict setting. | |
| 184 | + This is something you might want to add, depending on your needs. | |
| 185 | + | |
| 186 | + * <b>diff →</b> | |
| 187 | + This matches /vdiff/ and /fdiff/ and /vpatch/ and any other page that | |
| 188 | + is primarily about showing the difference between two check-ins or two | |
| 189 | + file versioons. | |
| 190 | + | |
| 191 | + * <b>annotate →</b> | |
| 192 | + This also matches /blame/ and /praise/. | |
| 193 | + | |
| 194 | +Other special keywords may be added in the future. | |
| 195 | + | |
| 196 | +The default [/help/robot-restrict|robot-restrict] | |
| 197 | +setting has been shown in practice to do a good job of keeping | |
| 198 | +robots from consuming all available CPU and bandwidth while will | |
| 199 | +still allowing humans access to the full power of the site without | |
| 200 | +having to be logged in. | |
| 201 | + | |
| 202 | +One possible enhancement is to add "zipX" to the | |
| 203 | +[/help/robot-restrict|robot-restrict] setting, | |
| 204 | +and enable [help?cmd=robot-zip-leaf|robot-zip-leaf] | |
| 205 | +and configure [help?cmd=robot-zip-tag|robot-zip-tag]. | |
| 206 | +Do this if you find that robots downloading lots of | |
| 207 | +obscure tarballs is causing load issues on your site. | |
| 208 | + | |
| 209 | +<h2>Anti-robot Exception RegExps</h2> | |
| 210 | + | |
| 211 | +The [/help/robot-exception|robot-exception setting] under the name | |
| 212 | +of <b>Exceptions to anti-robot restrictions</b> is a list of | |
| 213 | +[/re_rules|regular expressions], one per line, that match | |
| 214 | +URIs that will bypass the captcha and allow robots full access. The | |
| 215 | +intent of this setting is to allow automated build scripts | |
| 216 | +to download specific tarballs of project snapshots. | |
| 217 | + | |
| 218 | +The recommended value for this setting allows robots to use URIs of the | |
| 219 | +following form: | |
| 220 | + | |
| 221 | +<blockquote> | |
| 222 | +<b>https://</b><i>DOMAIN</i><b>/tarball/release/</b><i>HASH</i><b>/</b><i>NAME</i><b>.tar.gz</b> | |
| 223 | +</blockquote> | |
| 224 | + | |
| 225 | +The <i>HASH</i> part of this URL can be any valid | |
| 226 | +[./checkin_names.wiki|check-in name]. The link works as long as that | |
| 227 | +check-in is tagged with the "release" symbolic tag. In this way, | |
| 228 | +robots are permitted to download tarballs (and ZIP archives) of official | |
| 229 | +releases, but not every intermediate check-in between releases. Humans | |
| 230 | +who are willing to click the captcha can still download whatever they | |
| 231 | +want, but robots are blocked by the captcha. This prevents aggressive | |
| 232 | +robots from downloading tarballs of every historical check-in of your | |
| 233 | +project, once per day, which many robots these days seem eager to do. | |
| 234 | + | |
| 235 | +For example, on the Fossil project itself, this URL will work, even for | |
| 236 | +robots: | |
| 237 | + | |
| 238 | +<blockquote> | |
| 239 | +https://fossil-scm.org/home/tarball/release/version-2.27/fossil-scm.tar.gz | |
| 240 | +</blockquote> | |
| 241 | + | |
| 242 | +But the next URL will not work for robots because check-in 3bbd18a284c8bd6a | |
| 243 | +is not tagged as a "release": | |
| 244 | + | |
| 245 | +<blockquote> | |
| 246 | +https://fossil-scm.org/home/tarball/release/3bbd18a284c8bd6a/fossil-scm.tar.gz | |
| 247 | +</blockquote> | |
| 248 | + | |
| 249 | +The second URL will work for humans, just not robots. | |
| 250 | + | |
| 135 | 251 | <h2>The Ongoing Struggle</h2> |
| 136 | 252 | |
| 137 | -Fossil currently does a very good job of providing easy access to humans | |
| 253 | +Fossil currently does a good job of providing easy access to humans | |
| 138 | 254 | while keeping out troublesome robots. However, robots |
| 139 | 255 | continue to grow more sophisticated, requiring ever more advanced |
| 140 | 256 | defenses. This "arms race" is unlikely to ever end. The developers of |
| 141 | 257 | Fossil will continue to try improve the robot defenses of Fossil so |
| 142 | 258 | check back from time to time for the latest releases and updates. |
| 143 | 259 |
| --- www/antibot.wiki | |
| +++ www/antibot.wiki | |
| @@ -1,17 +1,34 @@ | |
| 1 | <title>Defense Against Robots</title> |
| 2 | |
| 3 | A typical Fossil website can have millions of pages, and many of |
| 4 | those pages (for example diffs and annotations and tarballs) can |
| 5 | be expensive to compute. |
| 6 | If a robot walks a Fossil-generated website, |
| 7 | it can present a crippling bandwidth and CPU load. |
| 8 | |
| 9 | A Fossil website is intended to be used |
| 10 | interactively by humans, not walked by robots. This article |
| 11 | describes the techniques used by Fossil to try to welcome human |
| 12 | users while keeping out robots. |
| 13 | |
| 14 | <h2>The Hyperlink User Capability</h2> |
| 15 | |
| 16 | Every Fossil web session has a "user". For random passers-by on the internet |
| 17 | (and for robots) that user is "nobody". The "anonymous" user is also |
| @@ -36,11 +53,11 @@ | |
| 36 | |
| 37 | But requiring a login, even an anonymous login, can be annoying. |
| 38 | Fossil provides other techniques for blocking robots which |
| 39 | are less cumbersome to humans. |
| 40 | |
| 41 | <h2>Automatic Hyperlinks Based on UserAgent</h2> |
| 42 | |
| 43 | Fossil has the ability to selectively enable hyperlinks for users |
| 44 | that lack the <b>Hyperlink</b> capability based on their UserAgent string in the |
| 45 | HTTP request header and on the browsers ability to run Javascript. |
| 46 | |
| @@ -68,21 +85,22 @@ | |
| 68 | to "play nicely" on the internet and are quite open |
| 69 | about the fact that they are a robot. And so the UserAgent string |
| 70 | provides a good first-guess about whether or not a request originates |
| 71 | from a human or a robot. |
| 72 | |
| 73 | In Fossil, under the Admin/Robot-Defense menu, there is a setting entitled |
| 74 | "<b>Enable hyperlinks based on User-Agent and/or Javascript</b>". |
| 75 | If this setting is set to "UserAgent only" or "UserAgent and Javascript", |
| 76 | and if the UserAgent string looks like a human and not a robot, then |
| 77 | Fossil will enable hyperlinks even if the <b>Hyperlink</b> capability |
| 78 | is omitted from the user permissions. This setting gives humans easy |
| 79 | access to the hyperlinks while preventing robots |
| 80 | from walking the billions of pages on a typical Fossil site. |
| 81 | |
| 82 | If the setting is "UserAgent only", then the hyperlinks are simply |
| 83 | enabled and that is all. But if the setting is "UserAgent and Javascript", |
| 84 | then the hyperlinks are not enabled directly. |
| 85 | Instead, the HTML code that is generated contains anchor tags ("<a>") |
| 86 | with "href=" attributes that point to [/honeypot] rather than the correct |
| 87 | link. JavaScript code is added to the end of the page that goes back and |
| 88 | fills in the correct "href=" attributes of |
| @@ -92,30 +110,14 @@ | |
| 92 | UserAgent string. Most robots do not bother to run JavaScript and |
| 93 | so to the robot the empty anchor tag will be useless. But all modern |
| 94 | web browsers implement JavaScript, so hyperlinks will show up |
| 95 | normally for human users. |
| 96 | |
| 97 | <h2>Further Defenses</h2> |
| 98 | |
| 99 | Recently (as of this writing, in the spring of 2013) the Fossil server |
| 100 | on the SQLite website ([http://www.sqlite.org/src/]) has been hit repeatedly |
| 101 | by Chinese robots that use forged UserAgent strings to make them look |
| 102 | like normal web browsers and which interpret JavaScript. We do not |
| 103 | believe these attacks to be nefarious since SQLite is public domain |
| 104 | and the attackers could obtain all information they ever wanted to |
| 105 | know about SQLite simply by cloning the repository. Instead, we |
| 106 | believe these "attacks" are coming from "script kiddies". But regardless |
| 107 | of whether or not malice is involved, these attacks do present |
| 108 | an unnecessary load on the server which reduces the responsiveness of |
| 109 | the SQLite website for well-behaved and socially responsible users. |
| 110 | For this reason, additional defenses against |
| 111 | robots have been put in place. |
| 112 | |
| 113 | On the Admin/Robot-Defense page of Fossil, just below the |
| 114 | "<b>Enable hyperlinks using User-Agent and/or Javascript</b>" |
| 115 | setting, there are now two additional sub-settings that can be optionally |
| 116 | enabled to control hyperlinks. |
| 117 | |
| 118 | The first new sub-setting is a delay (in milliseconds) before setting |
| 119 | the "href=" attributes on anchor tags. The default value for this |
| 120 | delay is 10 milliseconds. The idea here is that a robots will try to |
| 121 | interpret the links on the page immediately, and will not wait for delayed |
| @@ -130,13 +132,127 @@ | |
| 130 | |
| 131 | See also [./loadmgmt.md|Managing Server Load] for a description |
| 132 | of how expensive pages can be disabled when the server is under heavy |
| 133 | load. |
| 134 | |
| 135 | <h2>The Ongoing Struggle</h2> |
| 136 | |
| 137 | Fossil currently does a very good job of providing easy access to humans |
| 138 | while keeping out troublesome robots. However, robots |
| 139 | continue to grow more sophisticated, requiring ever more advanced |
| 140 | defenses. This "arms race" is unlikely to ever end. The developers of |
| 141 | Fossil will continue to try improve the robot defenses of Fossil so |
| 142 | check back from time to time for the latest releases and updates. |
| 143 |
| --- www/antibot.wiki | |
| +++ www/antibot.wiki | |
| @@ -1,17 +1,34 @@ | |
| 1 | <title>Defense Against Robots</title> |
| 2 | |
| 3 | A typical Fossil website can have billions and billions of pages, |
| 4 | and many of those pages (for example diffs and annotations and tarballs) |
| 5 | can be expensive to compute. |
| 6 | If a robot walks a Fossil-generated website, |
| 7 | it can present a crippling bandwidth and CPU load. |
| 8 | A "robots.txt" file can help, but in practice, most robots these |
| 9 | days ignore the robots.txt file, so it won't help much. |
| 10 | |
| 11 | A Fossil website is intended to be used |
| 12 | interactively by humans, not walked by robots. This article |
| 13 | describes the techniques used by Fossil to try to welcome human |
| 14 | users while keeping out robots. |
| 15 | |
| 16 | <h2>Defenses Are Enabled By Default</h2> |
| 17 | |
| 18 | In the latest implementations of Fossil, most robot defenses are |
| 19 | enabled by default. You can probably get by with standing up a |
| 20 | public-facing Fossil instance in the default configuration. But |
| 21 | you can also customize the defenses to serve your particular needs. |
| 22 | |
| 23 | <h2>Customizing Anti-Robot Defenses</h2> |
| 24 | |
| 25 | Admin users can configure robot defenses on the |
| 26 | "Robot Defense Settings" page (/setup_robot). |
| 27 | That page is accessible (to Admin users) from the default menu bar |
| 28 | by click on the "Admin" menu choice, then selecting the |
| 29 | "Robot-Defense" link from the list. |
| 30 | |
| 31 | <h2>The Hyperlink User Capability</h2> |
| 32 | |
| 33 | Every Fossil web session has a "user". For random passers-by on the internet |
| 34 | (and for robots) that user is "nobody". The "anonymous" user is also |
| @@ -36,11 +53,11 @@ | |
| 53 | |
| 54 | But requiring a login, even an anonymous login, can be annoying. |
| 55 | Fossil provides other techniques for blocking robots which |
| 56 | are less cumbersome to humans. |
| 57 | |
| 58 | <h2>Automatic Hyperlinks Based on UserAgent and Javascript</h2> |
| 59 | |
| 60 | Fossil has the ability to selectively enable hyperlinks for users |
| 61 | that lack the <b>Hyperlink</b> capability based on their UserAgent string in the |
| 62 | HTTP request header and on the browsers ability to run Javascript. |
| 63 | |
| @@ -68,21 +85,22 @@ | |
| 85 | to "play nicely" on the internet and are quite open |
| 86 | about the fact that they are a robot. And so the UserAgent string |
| 87 | provides a good first-guess about whether or not a request originates |
| 88 | from a human or a robot. |
| 89 | |
| 90 | The [/help/auto-hyperlink|auto-hyperlink] setting, shown as |
| 91 | "<b>Enable hyperlinks based on User-Agent and/or Javascript</b>" on |
| 92 | the Robot Defense Settings page, |
| 93 | can be set to "UserAgent only" or "UserAgent and Javascript" or "off". |
| 94 | If the UserAgent string looks like a human and not a robot, then |
| 95 | Fossil will enable hyperlinks even if the <b>Hyperlink</b> capability |
| 96 | is omitted from the user permissions. This setting gives humans easy |
| 97 | access to the hyperlinks while preventing robots |
| 98 | from walking the billions of pages on a typical Fossil site. |
| 99 | |
| 100 | If the setting is "UserAgent only" (2), then the hyperlinks are simply |
| 101 | enabled and that is all. But if the setting is "UserAgent and Javascript" (1), |
| 102 | then the hyperlinks are not enabled directly. |
| 103 | Instead, the HTML code that is generated contains anchor tags ("<a>") |
| 104 | with "href=" attributes that point to [/honeypot] rather than the correct |
| 105 | link. JavaScript code is added to the end of the page that goes back and |
| 106 | fills in the correct "href=" attributes of |
| @@ -92,30 +110,14 @@ | |
| 110 | UserAgent string. Most robots do not bother to run JavaScript and |
| 111 | so to the robot the empty anchor tag will be useless. But all modern |
| 112 | web browsers implement JavaScript, so hyperlinks will show up |
| 113 | normally for human users. |
| 114 | |
| 115 | If the [/help/auto-hyperlink|"auto-hyperlink"] setting is (2) |
| 116 | "<b>Enable hyperlinks using User-Agent and/or Javascript</b>", |
| 117 | then there are now two additional sub-settings that control when |
| 118 | hyperlinks are enabled. |
| 119 | |
| 120 | The first new sub-setting is a delay (in milliseconds) before setting |
| 121 | the "href=" attributes on anchor tags. The default value for this |
| 122 | delay is 10 milliseconds. The idea here is that a robots will try to |
| 123 | interpret the links on the page immediately, and will not wait for delayed |
| @@ -130,13 +132,127 @@ | |
| 132 | |
| 133 | See also [./loadmgmt.md|Managing Server Load] for a description |
| 134 | of how expensive pages can be disabled when the server is under heavy |
| 135 | load. |
| 136 | |
| 137 | <h2>Do Not Allow Robot Access To Certain Pages</h2> |
| 138 | |
| 139 | The [/help/robot-restrict|robot-restrict setting] is a comma-separated |
| 140 | list of GLOB patterns for pages for which robot access is prohibited. |
| 141 | The default value is: |
| 142 | |
| 143 | <blockquote><pre> |
| 144 | timelineX,diff,annotate,fileage,file,finfo,reports |
| 145 | </pre></blockquote> |
| 146 | |
| 147 | Each entry corresponds to the first path element on the URI for a |
| 148 | Fossil-generated page. If Fossil does not know for certain that the |
| 149 | HTTP request is coming from a human, then any attempt to access one of |
| 150 | these pages brings up a javascript-powered captcha. The user has to |
| 151 | click the accept button the captcha once, and that sets a cookie allowing |
| 152 | the user to continue surfing without interruption for 15 minutes or so |
| 153 | before being presented with another captcha. |
| 154 | |
| 155 | Some path elements have special meanings: |
| 156 | |
| 157 | * <b>timelineX →</b> |
| 158 | This means a subset of /timeline/ pages that are considered |
| 159 | "expensive". The exact definition of which timeline pages are |
| 160 | expensive and which are not is still the subject of active |
| 161 | experimentation and is likely to change by the time you read this |
| 162 | text. The idea is that anybody (including robots) can see a timeline |
| 163 | of the most recent changes, but timelines of long-ago change or that |
| 164 | contain lists of file changes or other harder-to-compute values are |
| 165 | prohibited. |
| 166 | |
| 167 | * <b>zip →</b> |
| 168 | The special "zip" keyword also matches "/tarball/" and "/sqlar/". |
| 169 | |
| 170 | * <b>zipX →</b> |
| 171 | This is like "zip" in that it restricts access to "/zip/", "/tarball"/ |
| 172 | and "/sqlar/" but with exceptions:<ol type="a"> |
| 173 | <li><p> If the [/help/robot-zip-leaf|robot-zip-leaf] setting is |
| 174 | true, then tarballs of leaf check-ins are allowed. This permits |
| 175 | URLs that attempt to download the latest check-in on trunk or |
| 176 | from a named branch, for example. |
| 177 | <li><p> If a check-in has a tag that matches the GLOB list in |
| 178 | [/help/robot-zip-tag|robot-zip-tag], then tarballs of that |
| 179 | check-in are allowed. This allow check-ins tagged with |
| 180 | "release" or "allow-robots" (for example) to be downloaded |
| 181 | without restriction. |
| 182 | </ol> |
| 183 | The "zipX" restriction is not in the default robot-restrict setting. |
| 184 | This is something you might want to add, depending on your needs. |
| 185 | |
| 186 | * <b>diff →</b> |
| 187 | This matches /vdiff/ and /fdiff/ and /vpatch/ and any other page that |
| 188 | is primarily about showing the difference between two check-ins or two |
| 189 | file versioons. |
| 190 | |
| 191 | * <b>annotate →</b> |
| 192 | This also matches /blame/ and /praise/. |
| 193 | |
| 194 | Other special keywords may be added in the future. |
| 195 | |
| 196 | The default [/help/robot-restrict|robot-restrict] |
| 197 | setting has been shown in practice to do a good job of keeping |
| 198 | robots from consuming all available CPU and bandwidth while will |
| 199 | still allowing humans access to the full power of the site without |
| 200 | having to be logged in. |
| 201 | |
| 202 | One possible enhancement is to add "zipX" to the |
| 203 | [/help/robot-restrict|robot-restrict] setting, |
| 204 | and enable [help?cmd=robot-zip-leaf|robot-zip-leaf] |
| 205 | and configure [help?cmd=robot-zip-tag|robot-zip-tag]. |
| 206 | Do this if you find that robots downloading lots of |
| 207 | obscure tarballs is causing load issues on your site. |
| 208 | |
| 209 | <h2>Anti-robot Exception RegExps</h2> |
| 210 | |
| 211 | The [/help/robot-exception|robot-exception setting] under the name |
| 212 | of <b>Exceptions to anti-robot restrictions</b> is a list of |
| 213 | [/re_rules|regular expressions], one per line, that match |
| 214 | URIs that will bypass the captcha and allow robots full access. The |
| 215 | intent of this setting is to allow automated build scripts |
| 216 | to download specific tarballs of project snapshots. |
| 217 | |
| 218 | The recommended value for this setting allows robots to use URIs of the |
| 219 | following form: |
| 220 | |
| 221 | <blockquote> |
| 222 | <b>https://</b><i>DOMAIN</i><b>/tarball/release/</b><i>HASH</i><b>/</b><i>NAME</i><b>.tar.gz</b> |
| 223 | </blockquote> |
| 224 | |
| 225 | The <i>HASH</i> part of this URL can be any valid |
| 226 | [./checkin_names.wiki|check-in name]. The link works as long as that |
| 227 | check-in is tagged with the "release" symbolic tag. In this way, |
| 228 | robots are permitted to download tarballs (and ZIP archives) of official |
| 229 | releases, but not every intermediate check-in between releases. Humans |
| 230 | who are willing to click the captcha can still download whatever they |
| 231 | want, but robots are blocked by the captcha. This prevents aggressive |
| 232 | robots from downloading tarballs of every historical check-in of your |
| 233 | project, once per day, which many robots these days seem eager to do. |
| 234 | |
| 235 | For example, on the Fossil project itself, this URL will work, even for |
| 236 | robots: |
| 237 | |
| 238 | <blockquote> |
| 239 | https://fossil-scm.org/home/tarball/release/version-2.27/fossil-scm.tar.gz |
| 240 | </blockquote> |
| 241 | |
| 242 | But the next URL will not work for robots because check-in 3bbd18a284c8bd6a |
| 243 | is not tagged as a "release": |
| 244 | |
| 245 | <blockquote> |
| 246 | https://fossil-scm.org/home/tarball/release/3bbd18a284c8bd6a/fossil-scm.tar.gz |
| 247 | </blockquote> |
| 248 | |
| 249 | The second URL will work for humans, just not robots. |
| 250 | |
| 251 | <h2>The Ongoing Struggle</h2> |
| 252 | |
| 253 | Fossil currently does a good job of providing easy access to humans |
| 254 | while keeping out troublesome robots. However, robots |
| 255 | continue to grow more sophisticated, requiring ever more advanced |
| 256 | defenses. This "arms race" is unlikely to ever end. The developers of |
| 257 | Fossil will continue to try improve the robot defenses of Fossil so |
| 258 | check back from time to time for the latest releases and updates. |
| 259 |
+5
-5
| --- www/backoffice.md | ||
| +++ www/backoffice.md | ||
| @@ -34,15 +34,15 @@ | ||
| 34 | 34 | process is already assigned to do the work, then a new backoffice process |
| 35 | 35 | is started to do the work. |
| 36 | 36 | |
| 37 | 37 | This happens for every webpage, regardless of how that webpage is launched, |
| 38 | 38 | and regardless of the purpose of the webpage. This also happens on the |
| 39 | -server for "[fossil sync](/help?cmd=sync)" and | |
| 40 | -[fossil clone](/help?cmd=clone)" commands which are implemented as | |
| 39 | +server for "[fossil sync](/help/sync)" and | |
| 40 | +[fossil clone](/help/clone)" commands which are implemented as | |
| 41 | 41 | web requests - albeit requests that the human user never sees. |
| 42 | 42 | Web requests can arrive at the Fossil server via direct TCP/IP (for example |
| 43 | -when Fossil is started using commands like "[fossil server](/help?cmd=server)") | |
| 43 | +when Fossil is started using commands like "[fossil server](/help/server)") | |
| 44 | 44 | or via [CGI](./server/any/cgi.md) or |
| 45 | 45 | [SCGI](./server/any/scgi.md) or via SSH. |
| 46 | 46 | A backoffice process might be started regardless of the origin of the |
| 47 | 47 | request. |
| 48 | 48 | |
| @@ -100,11 +100,11 @@ | ||
| 100 | 100 | [Fossil Forum](https://fossil-scm.org/forum) so that we can perhaps |
| 101 | 101 | fix the problem.) For now, the backoffice must be run manually |
| 102 | 102 | on OpenBSD systems. |
| 103 | 103 | |
| 104 | 104 | To set up fully-manual backoffice, first disable the automatic backoffice |
| 105 | -using the "[backoffice-disable](/help?cmd=backoffice-disable)" setting. | |
| 105 | +using the "[backoffice-disable](/help/backoffice-disable)" setting. | |
| 106 | 106 | |
| 107 | 107 | fossil setting backoffice-disable on |
| 108 | 108 | |
| 109 | 109 | Then arrange to invoke the backoffice separately using a command |
| 110 | 110 | like this: |
| @@ -111,11 +111,11 @@ | ||
| 111 | 111 | |
| 112 | 112 | fossil backoffice --poll 30 _REPOSITORY-LIST_ |
| 113 | 113 | |
| 114 | 114 | Multiple repositories can be named. This one command will handle |
| 115 | 115 | launching the backoffice for all of them. There are additional useful |
| 116 | -command-line options. See the "[fossil backoffice](/help?cmd=backoffice)" | |
| 116 | +command-line options. See the "[fossil backoffice](/help/backoffice)" | |
| 117 | 117 | documentation for details. |
| 118 | 118 | |
| 119 | 119 | The backoffice processes run manually using the "fossil backoffice" |
| 120 | 120 | command do not normally use a lease. That means that if you run the |
| 121 | 121 | "fossil backoffice" command with --poll and you forget to disable |
| 122 | 122 |
| --- www/backoffice.md | |
| +++ www/backoffice.md | |
| @@ -34,15 +34,15 @@ | |
| 34 | process is already assigned to do the work, then a new backoffice process |
| 35 | is started to do the work. |
| 36 | |
| 37 | This happens for every webpage, regardless of how that webpage is launched, |
| 38 | and regardless of the purpose of the webpage. This also happens on the |
| 39 | server for "[fossil sync](/help?cmd=sync)" and |
| 40 | [fossil clone](/help?cmd=clone)" commands which are implemented as |
| 41 | web requests - albeit requests that the human user never sees. |
| 42 | Web requests can arrive at the Fossil server via direct TCP/IP (for example |
| 43 | when Fossil is started using commands like "[fossil server](/help?cmd=server)") |
| 44 | or via [CGI](./server/any/cgi.md) or |
| 45 | [SCGI](./server/any/scgi.md) or via SSH. |
| 46 | A backoffice process might be started regardless of the origin of the |
| 47 | request. |
| 48 | |
| @@ -100,11 +100,11 @@ | |
| 100 | [Fossil Forum](https://fossil-scm.org/forum) so that we can perhaps |
| 101 | fix the problem.) For now, the backoffice must be run manually |
| 102 | on OpenBSD systems. |
| 103 | |
| 104 | To set up fully-manual backoffice, first disable the automatic backoffice |
| 105 | using the "[backoffice-disable](/help?cmd=backoffice-disable)" setting. |
| 106 | |
| 107 | fossil setting backoffice-disable on |
| 108 | |
| 109 | Then arrange to invoke the backoffice separately using a command |
| 110 | like this: |
| @@ -111,11 +111,11 @@ | |
| 111 | |
| 112 | fossil backoffice --poll 30 _REPOSITORY-LIST_ |
| 113 | |
| 114 | Multiple repositories can be named. This one command will handle |
| 115 | launching the backoffice for all of them. There are additional useful |
| 116 | command-line options. See the "[fossil backoffice](/help?cmd=backoffice)" |
| 117 | documentation for details. |
| 118 | |
| 119 | The backoffice processes run manually using the "fossil backoffice" |
| 120 | command do not normally use a lease. That means that if you run the |
| 121 | "fossil backoffice" command with --poll and you forget to disable |
| 122 |
| --- www/backoffice.md | |
| +++ www/backoffice.md | |
| @@ -34,15 +34,15 @@ | |
| 34 | process is already assigned to do the work, then a new backoffice process |
| 35 | is started to do the work. |
| 36 | |
| 37 | This happens for every webpage, regardless of how that webpage is launched, |
| 38 | and regardless of the purpose of the webpage. This also happens on the |
| 39 | server for "[fossil sync](/help/sync)" and |
| 40 | [fossil clone](/help/clone)" commands which are implemented as |
| 41 | web requests - albeit requests that the human user never sees. |
| 42 | Web requests can arrive at the Fossil server via direct TCP/IP (for example |
| 43 | when Fossil is started using commands like "[fossil server](/help/server)") |
| 44 | or via [CGI](./server/any/cgi.md) or |
| 45 | [SCGI](./server/any/scgi.md) or via SSH. |
| 46 | A backoffice process might be started regardless of the origin of the |
| 47 | request. |
| 48 | |
| @@ -100,11 +100,11 @@ | |
| 100 | [Fossil Forum](https://fossil-scm.org/forum) so that we can perhaps |
| 101 | fix the problem.) For now, the backoffice must be run manually |
| 102 | on OpenBSD systems. |
| 103 | |
| 104 | To set up fully-manual backoffice, first disable the automatic backoffice |
| 105 | using the "[backoffice-disable](/help/backoffice-disable)" setting. |
| 106 | |
| 107 | fossil setting backoffice-disable on |
| 108 | |
| 109 | Then arrange to invoke the backoffice separately using a command |
| 110 | like this: |
| @@ -111,11 +111,11 @@ | |
| 111 | |
| 112 | fossil backoffice --poll 30 _REPOSITORY-LIST_ |
| 113 | |
| 114 | Multiple repositories can be named. This one command will handle |
| 115 | launching the backoffice for all of them. There are additional useful |
| 116 | command-line options. See the "[fossil backoffice](/help/backoffice)" |
| 117 | documentation for details. |
| 118 | |
| 119 | The backoffice processes run manually using the "fossil backoffice" |
| 120 | command do not normally use a lease. That means that if you run the |
| 121 | "fossil backoffice" command with --poll and you forget to disable |
| 122 |
+2
-2
| --- www/backup.md | ||
| +++ www/backup.md | ||
| @@ -68,11 +68,11 @@ | ||
| 68 | 68 | #### <a id="other-cfg"></a> Others |
| 69 | 69 | |
| 70 | 70 | A repo’s URL aliases, [interwiki configuration](./interwiki.md), and |
| 71 | 71 | [ticket customizations](./custom_tcket.wiki) also do not normally sync. |
| 72 | 72 | |
| 73 | -[cfg]: /help?cmd=configuration | |
| 73 | +[cfg]: /help/configuration | |
| 74 | 74 | |
| 75 | 75 | |
| 76 | 76 | |
| 77 | 77 | ## <a id="private"></a> Private Branches |
| 78 | 78 | |
| @@ -289,11 +289,11 @@ | ||
| 289 | 289 | find them in a newly-created repo DB. We get around this by passing |
| 290 | 290 | the `--no-repository` flag, which suppresses this behavior. Doing it |
| 291 | 291 | this way saves you from needing to go and build a matching version of |
| 292 | 292 | `sqlite3` just to restore the backup. |
| 293 | 293 | |
| 294 | -[bu]: /help?cmd=backup | |
| 294 | +[bu]: /help/backup | |
| 295 | 295 | [grcp]: https://www.grc.com/passwords.htm |
| 296 | 296 | [hb]: https://brew.sh |
| 297 | 297 | [hbul]: https://docs.brew.sh/FAQ#what-does-keg-only-mean |
| 298 | 298 | [lz4]: https://lz4.github.io/lz4/ |
| 299 | 299 | [pbr]: ./private.wiki |
| 300 | 300 |
| --- www/backup.md | |
| +++ www/backup.md | |
| @@ -68,11 +68,11 @@ | |
| 68 | #### <a id="other-cfg"></a> Others |
| 69 | |
| 70 | A repo’s URL aliases, [interwiki configuration](./interwiki.md), and |
| 71 | [ticket customizations](./custom_tcket.wiki) also do not normally sync. |
| 72 | |
| 73 | [cfg]: /help?cmd=configuration |
| 74 | |
| 75 | |
| 76 | |
| 77 | ## <a id="private"></a> Private Branches |
| 78 | |
| @@ -289,11 +289,11 @@ | |
| 289 | find them in a newly-created repo DB. We get around this by passing |
| 290 | the `--no-repository` flag, which suppresses this behavior. Doing it |
| 291 | this way saves you from needing to go and build a matching version of |
| 292 | `sqlite3` just to restore the backup. |
| 293 | |
| 294 | [bu]: /help?cmd=backup |
| 295 | [grcp]: https://www.grc.com/passwords.htm |
| 296 | [hb]: https://brew.sh |
| 297 | [hbul]: https://docs.brew.sh/FAQ#what-does-keg-only-mean |
| 298 | [lz4]: https://lz4.github.io/lz4/ |
| 299 | [pbr]: ./private.wiki |
| 300 |
| --- www/backup.md | |
| +++ www/backup.md | |
| @@ -68,11 +68,11 @@ | |
| 68 | #### <a id="other-cfg"></a> Others |
| 69 | |
| 70 | A repo’s URL aliases, [interwiki configuration](./interwiki.md), and |
| 71 | [ticket customizations](./custom_tcket.wiki) also do not normally sync. |
| 72 | |
| 73 | [cfg]: /help/configuration |
| 74 | |
| 75 | |
| 76 | |
| 77 | ## <a id="private"></a> Private Branches |
| 78 | |
| @@ -289,11 +289,11 @@ | |
| 289 | find them in a newly-created repo DB. We get around this by passing |
| 290 | the `--no-repository` flag, which suppresses this behavior. Doing it |
| 291 | this way saves you from needing to go and build a matching version of |
| 292 | `sqlite3` just to restore the backup. |
| 293 | |
| 294 | [bu]: /help/backup |
| 295 | [grcp]: https://www.grc.com/passwords.htm |
| 296 | [hb]: https://brew.sh |
| 297 | [hbul]: https://docs.brew.sh/FAQ#what-does-keg-only-mean |
| 298 | [lz4]: https://lz4.github.io/lz4/ |
| 299 | [pbr]: ./private.wiki |
| 300 |
+7
-7
| --- www/blame.wiki | ||
| +++ www/blame.wiki | ||
| @@ -1,15 +1,15 @@ | ||
| 1 | 1 | <title>The Annotate Algorithm</title> |
| 2 | 2 | |
| 3 | 3 | <h2>1.0 Introduction</h2> |
| 4 | 4 | |
| 5 | -The [/help?cmd=annotate|fossil annotate], | |
| 6 | -[/help?cmd=blame|fossil blame], and | |
| 7 | -[/help?cmd=praise|fossil praise] commands, and the | |
| 8 | -[/help?cmd=/annotate|/annotate], | |
| 9 | -[/help?cmd=/blame|/blame], and | |
| 10 | -[/help?cmd=/praise|/praise] web pages are all used to show the most | |
| 5 | +The [/help/annotate|fossil annotate], | |
| 6 | +[/help/blame|fossil blame], and | |
| 7 | +[/help/praise|fossil praise] commands, and the | |
| 8 | +[/help/www/annotate|/annotate], | |
| 9 | +[/help/www/blame|/blame], and | |
| 10 | +[/help/www/praise|/praise] web pages are all used to show the most | |
| 11 | 11 | recent check-in that modified each line of a particular file. |
| 12 | 12 | This article overviews the algorithm used to compute the annotation |
| 13 | 13 | for a file in Fossil. |
| 14 | 14 | |
| 15 | 15 | <h2>2.0 Algorithm</h2> |
| @@ -45,11 +45,11 @@ | ||
| 45 | 45 | |
| 46 | 46 | The time-consuming part of this algorithm is step 6b - computing the |
| 47 | 47 | diff from all historical versions of the file to the version of the file |
| 48 | 48 | under analysis. For a large file that has many historical changes, this |
| 49 | 49 | can take several seconds. For this reason, the default |
| 50 | -[/help?cmd=/annotate|/annotate] webpage only shows those lines that were | |
| 50 | +[/help/www/annotate|/annotate] webpage only shows those lines that were | |
| 51 | 51 | changed by the 20 most recent modifications to the file. This allows |
| 52 | 52 | the loop on step 6 to terminate after only 19 diffs instead of the hundreds |
| 53 | 53 | or thousands of diffs that might be required for a frequently modified file. |
| 54 | 54 | |
| 55 | 55 | As currently implemented (as of 2015-12-12) the annotate algorithm does not |
| 56 | 56 |
| --- www/blame.wiki | |
| +++ www/blame.wiki | |
| @@ -1,15 +1,15 @@ | |
| 1 | <title>The Annotate Algorithm</title> |
| 2 | |
| 3 | <h2>1.0 Introduction</h2> |
| 4 | |
| 5 | The [/help?cmd=annotate|fossil annotate], |
| 6 | [/help?cmd=blame|fossil blame], and |
| 7 | [/help?cmd=praise|fossil praise] commands, and the |
| 8 | [/help?cmd=/annotate|/annotate], |
| 9 | [/help?cmd=/blame|/blame], and |
| 10 | [/help?cmd=/praise|/praise] web pages are all used to show the most |
| 11 | recent check-in that modified each line of a particular file. |
| 12 | This article overviews the algorithm used to compute the annotation |
| 13 | for a file in Fossil. |
| 14 | |
| 15 | <h2>2.0 Algorithm</h2> |
| @@ -45,11 +45,11 @@ | |
| 45 | |
| 46 | The time-consuming part of this algorithm is step 6b - computing the |
| 47 | diff from all historical versions of the file to the version of the file |
| 48 | under analysis. For a large file that has many historical changes, this |
| 49 | can take several seconds. For this reason, the default |
| 50 | [/help?cmd=/annotate|/annotate] webpage only shows those lines that were |
| 51 | changed by the 20 most recent modifications to the file. This allows |
| 52 | the loop on step 6 to terminate after only 19 diffs instead of the hundreds |
| 53 | or thousands of diffs that might be required for a frequently modified file. |
| 54 | |
| 55 | As currently implemented (as of 2015-12-12) the annotate algorithm does not |
| 56 |
| --- www/blame.wiki | |
| +++ www/blame.wiki | |
| @@ -1,15 +1,15 @@ | |
| 1 | <title>The Annotate Algorithm</title> |
| 2 | |
| 3 | <h2>1.0 Introduction</h2> |
| 4 | |
| 5 | The [/help/annotate|fossil annotate], |
| 6 | [/help/blame|fossil blame], and |
| 7 | [/help/praise|fossil praise] commands, and the |
| 8 | [/help/www/annotate|/annotate], |
| 9 | [/help/www/blame|/blame], and |
| 10 | [/help/www/praise|/praise] web pages are all used to show the most |
| 11 | recent check-in that modified each line of a particular file. |
| 12 | This article overviews the algorithm used to compute the annotation |
| 13 | for a file in Fossil. |
| 14 | |
| 15 | <h2>2.0 Algorithm</h2> |
| @@ -45,11 +45,11 @@ | |
| 45 | |
| 46 | The time-consuming part of this algorithm is step 6b - computing the |
| 47 | diff from all historical versions of the file to the version of the file |
| 48 | under analysis. For a large file that has many historical changes, this |
| 49 | can take several seconds. For this reason, the default |
| 50 | [/help/www/annotate|/annotate] webpage only shows those lines that were |
| 51 | changed by the 20 most recent modifications to the file. This allows |
| 52 | the loop on step 6 to terminate after only 19 diffs instead of the hundreds |
| 53 | or thousands of diffs that might be required for a frequently modified file. |
| 54 | |
| 55 | As currently implemented (as of 2015-12-12) the annotate algorithm does not |
| 56 |
+6
-6
| --- www/blockchain.md | ||
| +++ www/blockchain.md | ||
| @@ -208,11 +208,11 @@ | ||
| 208 | 208 | this makes it “not a blockchain” is a subjective matter. |
| 209 | 209 | |
| 210 | 210 | [arh]: ./hooks.md |
| 211 | 211 | [bse]: https://www.researchgate.net/publication/311572122_What_is_Blockchain_a_Gentle_Introduction |
| 212 | 212 | [caps]: ./caps/ |
| 213 | -[cs]: /help?cmd=clearsign | |
| 213 | +[cs]: /help/clearsign | |
| 214 | 214 | [dboc]: https://en.wikipedia.org/wiki/Debasement |
| 215 | 215 | [dsig]: https://en.wikipedia.org/wiki/Digital_signature |
| 216 | 216 | [fb]: ./branching.wiki |
| 217 | 217 | [GPG]: https://gnupg.org/ |
| 218 | 218 | [PGP]: https://www.openpgp.org/ |
| @@ -278,11 +278,11 @@ | ||
| 278 | 278 | [ctcp]: ./cap-theorem.md#cp |
| 279 | 279 | [cap]: https://en.wikipedia.org/wiki/CAP_theorem |
| 280 | 280 | [dlt]: https://en.wikipedia.org/wiki/Distributed_ledger |
| 281 | 281 | [DVCS]: https://en.wikipedia.org/wiki/Distributed_version_control |
| 282 | 282 | [fc]: https://en.wikipedia.org/wiki/Fiat_money |
| 283 | -[purge]: /help?cmd=purge | |
| 283 | +[purge]: /help/purge | |
| 284 | 284 | [shun]: ./shunning.wiki |
| 285 | 285 | |
| 286 | 286 | |
| 287 | 287 | <a id="dpc"></a> |
| 288 | 288 | ## Distributed Partial Consensus |
| @@ -335,11 +335,11 @@ | ||
| 335 | 335 | sending each an HTTP GET `/info` query for the artifact ID, concluding |
| 336 | 336 | that the commit is legitimate once you get enough HTTP 200 status codes back. All of this is |
| 337 | 337 | hypothetical, because Fossil doesn’t do this today. |
| 338 | 338 | |
| 339 | 339 | [AGI]: https://en.wikipedia.org/wiki/Artificial_general_intelligence |
| 340 | -[rcks]: /help?cmd=repo-cksum | |
| 340 | +[rcks]: /help/repo-cksum | |
| 341 | 341 | |
| 342 | 342 | |
| 343 | 343 | |
| 344 | 344 | <a id="anon"></a> |
| 345 | 345 | ## Anonymity |
| @@ -447,14 +447,14 @@ | ||
| 447 | 447 | self-contained, and with a smaller attack surface. |
| 448 | 448 | |
| 449 | 449 | |
| 450 | 450 | [alert]: ./alerts.md |
| 451 | 451 | [capi]: ./caps/ref.html#i |
| 452 | -[mrep]: /help?cmd=remote | |
| 452 | +[mrep]: /help/remote | |
| 453 | 453 | [scost]: https://en.wikipedia.org/wiki/Software_development_effort_estimation |
| 454 | -[scrub]: /help?cmd=scrub | |
| 455 | -[sreg]: /help?cmd=self-register | |
| 454 | +[scrub]: /help/scrub | |
| 455 | +[sreg]: /help/self-register | |
| 456 | 456 | |
| 457 | 457 | |
| 458 | 458 | # Conclusion |
| 459 | 459 | |
| 460 | 460 | This author believes it is technologically indefensible to call Fossil a |
| 461 | 461 |
| --- www/blockchain.md | |
| +++ www/blockchain.md | |
| @@ -208,11 +208,11 @@ | |
| 208 | this makes it “not a blockchain” is a subjective matter. |
| 209 | |
| 210 | [arh]: ./hooks.md |
| 211 | [bse]: https://www.researchgate.net/publication/311572122_What_is_Blockchain_a_Gentle_Introduction |
| 212 | [caps]: ./caps/ |
| 213 | [cs]: /help?cmd=clearsign |
| 214 | [dboc]: https://en.wikipedia.org/wiki/Debasement |
| 215 | [dsig]: https://en.wikipedia.org/wiki/Digital_signature |
| 216 | [fb]: ./branching.wiki |
| 217 | [GPG]: https://gnupg.org/ |
| 218 | [PGP]: https://www.openpgp.org/ |
| @@ -278,11 +278,11 @@ | |
| 278 | [ctcp]: ./cap-theorem.md#cp |
| 279 | [cap]: https://en.wikipedia.org/wiki/CAP_theorem |
| 280 | [dlt]: https://en.wikipedia.org/wiki/Distributed_ledger |
| 281 | [DVCS]: https://en.wikipedia.org/wiki/Distributed_version_control |
| 282 | [fc]: https://en.wikipedia.org/wiki/Fiat_money |
| 283 | [purge]: /help?cmd=purge |
| 284 | [shun]: ./shunning.wiki |
| 285 | |
| 286 | |
| 287 | <a id="dpc"></a> |
| 288 | ## Distributed Partial Consensus |
| @@ -335,11 +335,11 @@ | |
| 335 | sending each an HTTP GET `/info` query for the artifact ID, concluding |
| 336 | that the commit is legitimate once you get enough HTTP 200 status codes back. All of this is |
| 337 | hypothetical, because Fossil doesn’t do this today. |
| 338 | |
| 339 | [AGI]: https://en.wikipedia.org/wiki/Artificial_general_intelligence |
| 340 | [rcks]: /help?cmd=repo-cksum |
| 341 | |
| 342 | |
| 343 | |
| 344 | <a id="anon"></a> |
| 345 | ## Anonymity |
| @@ -447,14 +447,14 @@ | |
| 447 | self-contained, and with a smaller attack surface. |
| 448 | |
| 449 | |
| 450 | [alert]: ./alerts.md |
| 451 | [capi]: ./caps/ref.html#i |
| 452 | [mrep]: /help?cmd=remote |
| 453 | [scost]: https://en.wikipedia.org/wiki/Software_development_effort_estimation |
| 454 | [scrub]: /help?cmd=scrub |
| 455 | [sreg]: /help?cmd=self-register |
| 456 | |
| 457 | |
| 458 | # Conclusion |
| 459 | |
| 460 | This author believes it is technologically indefensible to call Fossil a |
| 461 |
| --- www/blockchain.md | |
| +++ www/blockchain.md | |
| @@ -208,11 +208,11 @@ | |
| 208 | this makes it “not a blockchain” is a subjective matter. |
| 209 | |
| 210 | [arh]: ./hooks.md |
| 211 | [bse]: https://www.researchgate.net/publication/311572122_What_is_Blockchain_a_Gentle_Introduction |
| 212 | [caps]: ./caps/ |
| 213 | [cs]: /help/clearsign |
| 214 | [dboc]: https://en.wikipedia.org/wiki/Debasement |
| 215 | [dsig]: https://en.wikipedia.org/wiki/Digital_signature |
| 216 | [fb]: ./branching.wiki |
| 217 | [GPG]: https://gnupg.org/ |
| 218 | [PGP]: https://www.openpgp.org/ |
| @@ -278,11 +278,11 @@ | |
| 278 | [ctcp]: ./cap-theorem.md#cp |
| 279 | [cap]: https://en.wikipedia.org/wiki/CAP_theorem |
| 280 | [dlt]: https://en.wikipedia.org/wiki/Distributed_ledger |
| 281 | [DVCS]: https://en.wikipedia.org/wiki/Distributed_version_control |
| 282 | [fc]: https://en.wikipedia.org/wiki/Fiat_money |
| 283 | [purge]: /help/purge |
| 284 | [shun]: ./shunning.wiki |
| 285 | |
| 286 | |
| 287 | <a id="dpc"></a> |
| 288 | ## Distributed Partial Consensus |
| @@ -335,11 +335,11 @@ | |
| 335 | sending each an HTTP GET `/info` query for the artifact ID, concluding |
| 336 | that the commit is legitimate once you get enough HTTP 200 status codes back. All of this is |
| 337 | hypothetical, because Fossil doesn’t do this today. |
| 338 | |
| 339 | [AGI]: https://en.wikipedia.org/wiki/Artificial_general_intelligence |
| 340 | [rcks]: /help/repo-cksum |
| 341 | |
| 342 | |
| 343 | |
| 344 | <a id="anon"></a> |
| 345 | ## Anonymity |
| @@ -447,14 +447,14 @@ | |
| 447 | self-contained, and with a smaller attack surface. |
| 448 | |
| 449 | |
| 450 | [alert]: ./alerts.md |
| 451 | [capi]: ./caps/ref.html#i |
| 452 | [mrep]: /help/remote |
| 453 | [scost]: https://en.wikipedia.org/wiki/Software_development_effort_estimation |
| 454 | [scrub]: /help/scrub |
| 455 | [sreg]: /help/self-register |
| 456 | |
| 457 | |
| 458 | # Conclusion |
| 459 | |
| 460 | This author believes it is technologically indefensible to call Fossil a |
| 461 |
+5
-5
| --- www/branching.wiki | ||
| +++ www/branching.wiki | ||
| @@ -75,11 +75,11 @@ | ||
| 75 | 75 | But perhaps Bob is off-network when he does his commit, so he has no way |
| 76 | 76 | of knowing that Alice has already committed her changes. Or, it could |
| 77 | 77 | be that Bob has turned off "autosync" mode in Fossil. Or, maybe Bob |
| 78 | 78 | just doesn't want to merge in Alice's changes before he has saved his |
| 79 | 79 | own, so he forces the commit to occur using the "--allow-fork" option to |
| 80 | -the <b>[/help?cmd=commit | fossil commit]</b> command. For any of these | |
| 80 | +the <b>[/help/commit | fossil commit]</b> command. For any of these | |
| 81 | 81 | reasons, two commits against check-in 2 have occurred, so the DAG now |
| 82 | 82 | has two leaves. |
| 83 | 83 | |
| 84 | 84 | In such a condition, a person working with this repository has a |
| 85 | 85 | dilemma: which version of the project is the "latest" in the sense of |
| @@ -111,11 +111,11 @@ | ||
| 111 | 111 | easier to justify, since presumably the lone developer is never confused |
| 112 | 112 | about why there are two or more leaves on that branch. Further |
| 113 | 113 | justifications for intentional forking are [#forking | given below]. |
| 114 | 114 | |
| 115 | 115 | Let us return to Figure 2. To resolve such situations before they can |
| 116 | -become a real problem, Alice can use the <b>[/help?cmd=merge | fossil | |
| 116 | +become a real problem, Alice can use the <b>[/help/merge | fossil | |
| 117 | 117 | merge]</b> command to merge Bob's changes into her local copy of |
| 118 | 118 | check-in 3. Without arguments, that command merges all leaves on the |
| 119 | 119 | current branch. Alice can then verify that the merge is sensible and if |
| 120 | 120 | so, commit the results as check-in 5. This results in a DAG as shown in |
| 121 | 121 | Figure 3. |
| @@ -349,11 +349,11 @@ | ||
| 349 | 349 | refusing the check-in simply because it would create a fork. |
| 350 | 350 | <br><br> |
| 351 | 351 | If you are writing such a tool — e.g. a shell script to make |
| 352 | 352 | multiple manipulations on a Fossil repo — it's better to make it |
| 353 | 353 | smart enough to detect this condition and cope with it, such as |
| 354 | - by making a call to <b>[/help?cmd=update | fossil update]</b> | |
| 354 | + by making a call to <b>[/help/update | fossil update]</b> | |
| 355 | 355 | and checking for a merge conflict. That said, if the alternative is |
| 356 | 356 | losing information, you may feel justified in creating forks that an |
| 357 | 357 | interactive user must later manually clean up with <b>fossil merge</b> |
| 358 | 358 | commands.</p></li> |
| 359 | 359 | </ol> |
| @@ -600,11 +600,11 @@ | ||
| 600 | 600 | to that branch with a <b>fossil update $BRANCH</b> command. (There is an |
| 601 | 601 | implicit autosync in that command, if the option was enabled at the |
| 602 | 602 | time of the update.)</p></li> |
| 603 | 603 | |
| 604 | 604 | <li><p>The same thing, only in a fresh checkout directory with a |
| 605 | - <b>[/help?cmd=open | fossil open $REPO $BRANCH]</b> command.</p></li> | |
| 605 | + <b>[/help/open | fossil open $REPO $BRANCH]</b> command.</p></li> | |
| 606 | 606 | |
| 607 | 607 | <li><p>Alan makes his check-in 3 while Betty has check-in 1 or 2 as |
| 608 | 608 | the tip in her local clone, but because she's working with an |
| 609 | 609 | autosync'd connection to the same upstream repository as Alan, on |
| 610 | 610 | attempting what will become check-in 4, she gets the "would fork" |
| @@ -751,11 +751,11 @@ | ||
| 751 | 751 | is especially useful with utility branches. There are several of these |
| 752 | 752 | in the SQLite and Fossil repositories: "broken-build," "declined," |
| 753 | 753 | "mistake," etc. As you might guess from these names, such branch names |
| 754 | 754 | are used in renaming the tip of one branch to shunt it off away from the |
| 755 | 755 | mainline of that branch due to some human error. (See |
| 756 | -<b>[/help?cmd=amend | fossil | |
| 756 | +<b>[/help/amend | fossil | |
| 757 | 757 | amend]</b> and the Fossil UI check-in amendment features.) This is a |
| 758 | 758 | workaround for Fossil's [./shunning.wiki|normal inability to forget |
| 759 | 759 | history]: we usually don't want to actually <i>remove</i> history, but |
| 760 | 760 | would like to sometimes set some of it aside under a new label. |
| 761 | 761 | |
| 762 | 762 |
| --- www/branching.wiki | |
| +++ www/branching.wiki | |
| @@ -75,11 +75,11 @@ | |
| 75 | But perhaps Bob is off-network when he does his commit, so he has no way |
| 76 | of knowing that Alice has already committed her changes. Or, it could |
| 77 | be that Bob has turned off "autosync" mode in Fossil. Or, maybe Bob |
| 78 | just doesn't want to merge in Alice's changes before he has saved his |
| 79 | own, so he forces the commit to occur using the "--allow-fork" option to |
| 80 | the <b>[/help?cmd=commit | fossil commit]</b> command. For any of these |
| 81 | reasons, two commits against check-in 2 have occurred, so the DAG now |
| 82 | has two leaves. |
| 83 | |
| 84 | In such a condition, a person working with this repository has a |
| 85 | dilemma: which version of the project is the "latest" in the sense of |
| @@ -111,11 +111,11 @@ | |
| 111 | easier to justify, since presumably the lone developer is never confused |
| 112 | about why there are two or more leaves on that branch. Further |
| 113 | justifications for intentional forking are [#forking | given below]. |
| 114 | |
| 115 | Let us return to Figure 2. To resolve such situations before they can |
| 116 | become a real problem, Alice can use the <b>[/help?cmd=merge | fossil |
| 117 | merge]</b> command to merge Bob's changes into her local copy of |
| 118 | check-in 3. Without arguments, that command merges all leaves on the |
| 119 | current branch. Alice can then verify that the merge is sensible and if |
| 120 | so, commit the results as check-in 5. This results in a DAG as shown in |
| 121 | Figure 3. |
| @@ -349,11 +349,11 @@ | |
| 349 | refusing the check-in simply because it would create a fork. |
| 350 | <br><br> |
| 351 | If you are writing such a tool — e.g. a shell script to make |
| 352 | multiple manipulations on a Fossil repo — it's better to make it |
| 353 | smart enough to detect this condition and cope with it, such as |
| 354 | by making a call to <b>[/help?cmd=update | fossil update]</b> |
| 355 | and checking for a merge conflict. That said, if the alternative is |
| 356 | losing information, you may feel justified in creating forks that an |
| 357 | interactive user must later manually clean up with <b>fossil merge</b> |
| 358 | commands.</p></li> |
| 359 | </ol> |
| @@ -600,11 +600,11 @@ | |
| 600 | to that branch with a <b>fossil update $BRANCH</b> command. (There is an |
| 601 | implicit autosync in that command, if the option was enabled at the |
| 602 | time of the update.)</p></li> |
| 603 | |
| 604 | <li><p>The same thing, only in a fresh checkout directory with a |
| 605 | <b>[/help?cmd=open | fossil open $REPO $BRANCH]</b> command.</p></li> |
| 606 | |
| 607 | <li><p>Alan makes his check-in 3 while Betty has check-in 1 or 2 as |
| 608 | the tip in her local clone, but because she's working with an |
| 609 | autosync'd connection to the same upstream repository as Alan, on |
| 610 | attempting what will become check-in 4, she gets the "would fork" |
| @@ -751,11 +751,11 @@ | |
| 751 | is especially useful with utility branches. There are several of these |
| 752 | in the SQLite and Fossil repositories: "broken-build," "declined," |
| 753 | "mistake," etc. As you might guess from these names, such branch names |
| 754 | are used in renaming the tip of one branch to shunt it off away from the |
| 755 | mainline of that branch due to some human error. (See |
| 756 | <b>[/help?cmd=amend | fossil |
| 757 | amend]</b> and the Fossil UI check-in amendment features.) This is a |
| 758 | workaround for Fossil's [./shunning.wiki|normal inability to forget |
| 759 | history]: we usually don't want to actually <i>remove</i> history, but |
| 760 | would like to sometimes set some of it aside under a new label. |
| 761 | |
| 762 |
| --- www/branching.wiki | |
| +++ www/branching.wiki | |
| @@ -75,11 +75,11 @@ | |
| 75 | But perhaps Bob is off-network when he does his commit, so he has no way |
| 76 | of knowing that Alice has already committed her changes. Or, it could |
| 77 | be that Bob has turned off "autosync" mode in Fossil. Or, maybe Bob |
| 78 | just doesn't want to merge in Alice's changes before he has saved his |
| 79 | own, so he forces the commit to occur using the "--allow-fork" option to |
| 80 | the <b>[/help/commit | fossil commit]</b> command. For any of these |
| 81 | reasons, two commits against check-in 2 have occurred, so the DAG now |
| 82 | has two leaves. |
| 83 | |
| 84 | In such a condition, a person working with this repository has a |
| 85 | dilemma: which version of the project is the "latest" in the sense of |
| @@ -111,11 +111,11 @@ | |
| 111 | easier to justify, since presumably the lone developer is never confused |
| 112 | about why there are two or more leaves on that branch. Further |
| 113 | justifications for intentional forking are [#forking | given below]. |
| 114 | |
| 115 | Let us return to Figure 2. To resolve such situations before they can |
| 116 | become a real problem, Alice can use the <b>[/help/merge | fossil |
| 117 | merge]</b> command to merge Bob's changes into her local copy of |
| 118 | check-in 3. Without arguments, that command merges all leaves on the |
| 119 | current branch. Alice can then verify that the merge is sensible and if |
| 120 | so, commit the results as check-in 5. This results in a DAG as shown in |
| 121 | Figure 3. |
| @@ -349,11 +349,11 @@ | |
| 349 | refusing the check-in simply because it would create a fork. |
| 350 | <br><br> |
| 351 | If you are writing such a tool — e.g. a shell script to make |
| 352 | multiple manipulations on a Fossil repo — it's better to make it |
| 353 | smart enough to detect this condition and cope with it, such as |
| 354 | by making a call to <b>[/help/update | fossil update]</b> |
| 355 | and checking for a merge conflict. That said, if the alternative is |
| 356 | losing information, you may feel justified in creating forks that an |
| 357 | interactive user must later manually clean up with <b>fossil merge</b> |
| 358 | commands.</p></li> |
| 359 | </ol> |
| @@ -600,11 +600,11 @@ | |
| 600 | to that branch with a <b>fossil update $BRANCH</b> command. (There is an |
| 601 | implicit autosync in that command, if the option was enabled at the |
| 602 | time of the update.)</p></li> |
| 603 | |
| 604 | <li><p>The same thing, only in a fresh checkout directory with a |
| 605 | <b>[/help/open | fossil open $REPO $BRANCH]</b> command.</p></li> |
| 606 | |
| 607 | <li><p>Alan makes his check-in 3 while Betty has check-in 1 or 2 as |
| 608 | the tip in her local clone, but because she's working with an |
| 609 | autosync'd connection to the same upstream repository as Alan, on |
| 610 | attempting what will become check-in 4, she gets the "would fork" |
| @@ -751,11 +751,11 @@ | |
| 751 | is especially useful with utility branches. There are several of these |
| 752 | in the SQLite and Fossil repositories: "broken-build," "declined," |
| 753 | "mistake," etc. As you might guess from these names, such branch names |
| 754 | are used in renaming the tip of one branch to shunt it off away from the |
| 755 | mainline of that branch due to some human error. (See |
| 756 | <b>[/help/amend | fossil |
| 757 | amend]</b> and the Fossil UI check-in amendment features.) This is a |
| 758 | workaround for Fossil's [./shunning.wiki|normal inability to forget |
| 759 | history]: we usually don't want to actually <i>remove</i> history, but |
| 760 | would like to sometimes set some of it aside under a new label. |
| 761 | |
| 762 |
+2
-2
| --- www/build.wiki | ||
| +++ www/build.wiki | ||
| @@ -335,11 +335,11 @@ | ||
| 335 | 335 | change at any time. It is only known to work on Linux systems and has |
| 336 | 336 | been seen to work on x86/64 and ARM. |
| 337 | 337 | |
| 338 | 338 | Fossil has builtin support for processing specific features using |
| 339 | 339 | <tt>libfuzzer</tt>. The features which can be tested this way are |
| 340 | -found in the help text for the [/help?cmd=test-fuzz|test-fuzz | |
| 340 | +found in the help text for the [/help/test-fuzz|test-fuzz | |
| 341 | 341 | command]. |
| 342 | 342 | |
| 343 | 343 | Fuzzing requires: |
| 344 | 344 | |
| 345 | 345 | * The clang C compiler. |
| @@ -510,11 +510,11 @@ | ||
| 510 | 510 | "should" (as of this writing) suffice. Any older version may or may not |
| 511 | 511 | work.</div> |
| 512 | 512 | |
| 513 | 513 | After that succeeds, we need to run the normal build so that those |
| 514 | 514 | generated files can be compiled in to the fossil binary, accessible |
| 515 | -via the [/help?cmd=/builtin|/builtin page]: | |
| 515 | +via the [/help/www/builtin|/builtin page]: | |
| 516 | 516 | |
| 517 | 517 | <pre><code>$ make</code></pre> |
| 518 | 518 | |
| 519 | 519 | Before checking in those newly-built files, they need to be tested by |
| 520 | 520 | running the [/pikchrshow] page. If that page loads, the compilation |
| 521 | 521 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -335,11 +335,11 @@ | |
| 335 | change at any time. It is only known to work on Linux systems and has |
| 336 | been seen to work on x86/64 and ARM. |
| 337 | |
| 338 | Fossil has builtin support for processing specific features using |
| 339 | <tt>libfuzzer</tt>. The features which can be tested this way are |
| 340 | found in the help text for the [/help?cmd=test-fuzz|test-fuzz |
| 341 | command]. |
| 342 | |
| 343 | Fuzzing requires: |
| 344 | |
| 345 | * The clang C compiler. |
| @@ -510,11 +510,11 @@ | |
| 510 | "should" (as of this writing) suffice. Any older version may or may not |
| 511 | work.</div> |
| 512 | |
| 513 | After that succeeds, we need to run the normal build so that those |
| 514 | generated files can be compiled in to the fossil binary, accessible |
| 515 | via the [/help?cmd=/builtin|/builtin page]: |
| 516 | |
| 517 | <pre><code>$ make</code></pre> |
| 518 | |
| 519 | Before checking in those newly-built files, they need to be tested by |
| 520 | running the [/pikchrshow] page. If that page loads, the compilation |
| 521 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -335,11 +335,11 @@ | |
| 335 | change at any time. It is only known to work on Linux systems and has |
| 336 | been seen to work on x86/64 and ARM. |
| 337 | |
| 338 | Fossil has builtin support for processing specific features using |
| 339 | <tt>libfuzzer</tt>. The features which can be tested this way are |
| 340 | found in the help text for the [/help/test-fuzz|test-fuzz |
| 341 | command]. |
| 342 | |
| 343 | Fuzzing requires: |
| 344 | |
| 345 | * The clang C compiler. |
| @@ -510,11 +510,11 @@ | |
| 510 | "should" (as of this writing) suffice. Any older version may or may not |
| 511 | work.</div> |
| 512 | |
| 513 | After that succeeds, we need to run the normal build so that those |
| 514 | generated files can be compiled in to the fossil binary, accessible |
| 515 | via the [/help/www/builtin|/builtin page]: |
| 516 | |
| 517 | <pre><code>$ make</code></pre> |
| 518 | |
| 519 | Before checking in those newly-built files, they need to be tested by |
| 520 | running the [/pikchrshow] page. If that page loads, the compilation |
| 521 |
+6
-6
| --- www/caps/admin-v-setup.md | ||
| +++ www/caps/admin-v-setup.md | ||
| @@ -261,13 +261,13 @@ | ||
| 261 | 261 | |
| 262 | 262 | Setup users can do many things that Admin users cannot. They may not |
| 263 | 263 | only use all of the Admin UI features, they may also: |
| 264 | 264 | |
| 265 | 265 | * See record IDs (RIDs) on screens that show them |
| 266 | -* See the MIME type of attachments on [`/ainfo` pages](/help?cmd=/ainfo) | |
| 267 | -* See a remote repo’s HTTP [cache status](/help?cmd=/cachestat) | |
| 268 | - and [pull cache entries](/help?cmd=/cacheget) | |
| 266 | +* See the MIME type of attachments on [`/ainfo` pages](/help/www/ainfo) | |
| 267 | +* See a remote repo’s HTTP [cache status](/help/www/cachestat) | |
| 268 | + and [pull cache entries](/help/www/cacheget) | |
| 269 | 269 | * Edit a Setup user’s account! |
| 270 | 270 | |
| 271 | 271 | The “Admin” feature of Fossil UI is so-named because Admin users can use |
| 272 | 272 | about half of its functions, but only Setup can use these pages: |
| 273 | 273 | |
| @@ -395,11 +395,11 @@ | ||
| 395 | 395 | |
| 396 | 396 | |
| 397 | 397 | ### <a id="y"></a>Write Unversioned |
| 398 | 398 | |
| 399 | 399 | Fossil currently doesn’t distinguish the sub-operations of |
| 400 | -[`fossil uv`](/help?cmd=uv); they’re all covered by [**WrUnver**][capy] | |
| 400 | +[`fossil uv`](/help/uv); they’re all covered by [**WrUnver**][capy] | |
| 401 | 401 | (“y”) capability. Since some of these operations are unconditionally |
| 402 | 402 | destructive due to the nature of unversioned content, and since this |
| 403 | 403 | goes against Fossil’s philosophy of immutable history, nobody gets cap |
| 404 | 404 | “y” on a Fossil repo by default, not even the Setup or Admin users. A |
| 405 | 405 | Setup or Admin user must grant cap “y” to someone — not necessarily |
| @@ -446,16 +446,16 @@ | ||
| 446 | 446 | [capa]: ./ref.html#a |
| 447 | 447 | [caps]: ./ref.html#s |
| 448 | 448 | [capx]: ./ref.html#x |
| 449 | 449 | [capy]: ./ref.html#y |
| 450 | 450 | |
| 451 | -[fcp]: https://fossil-scm.org/home/help?cmd=configuration | |
| 451 | +[fcp]: https://fossil-scm.org/home/help/configuration | |
| 452 | 452 | [fdp]: ../fossil-v-git.wiki#devorg |
| 453 | 453 | [forum]: https://fossil-scm.org/forum/ |
| 454 | -[fui]: /help?cmd=ui | |
| 454 | +[fui]: /help/ui | |
| 455 | 455 | [lg]: ./login-groups.md |
| 456 | 456 | [rs]: https://fossil-scm.org/home/doc/trunk/www/settings.wiki |
| 457 | 457 | [sia]: https://fossil-scm.org/home/artifact?ln=1259-1260&name=0fda31b6683c206a |
| 458 | 458 | [snoy]: https://fossil-scm.org/forum/forumpost/00e1c4ecff |
| 459 | 459 | [th1]: ../th1.md |
| 460 | 460 | [tt]: https://en.wikipedia.org/wiki/Tiger_team#Security |
| 461 | 461 | [webo]: ./#webonly |
| 462 | 462 |
| --- www/caps/admin-v-setup.md | |
| +++ www/caps/admin-v-setup.md | |
| @@ -261,13 +261,13 @@ | |
| 261 | |
| 262 | Setup users can do many things that Admin users cannot. They may not |
| 263 | only use all of the Admin UI features, they may also: |
| 264 | |
| 265 | * See record IDs (RIDs) on screens that show them |
| 266 | * See the MIME type of attachments on [`/ainfo` pages](/help?cmd=/ainfo) |
| 267 | * See a remote repo’s HTTP [cache status](/help?cmd=/cachestat) |
| 268 | and [pull cache entries](/help?cmd=/cacheget) |
| 269 | * Edit a Setup user’s account! |
| 270 | |
| 271 | The “Admin” feature of Fossil UI is so-named because Admin users can use |
| 272 | about half of its functions, but only Setup can use these pages: |
| 273 | |
| @@ -395,11 +395,11 @@ | |
| 395 | |
| 396 | |
| 397 | ### <a id="y"></a>Write Unversioned |
| 398 | |
| 399 | Fossil currently doesn’t distinguish the sub-operations of |
| 400 | [`fossil uv`](/help?cmd=uv); they’re all covered by [**WrUnver**][capy] |
| 401 | (“y”) capability. Since some of these operations are unconditionally |
| 402 | destructive due to the nature of unversioned content, and since this |
| 403 | goes against Fossil’s philosophy of immutable history, nobody gets cap |
| 404 | “y” on a Fossil repo by default, not even the Setup or Admin users. A |
| 405 | Setup or Admin user must grant cap “y” to someone — not necessarily |
| @@ -446,16 +446,16 @@ | |
| 446 | [capa]: ./ref.html#a |
| 447 | [caps]: ./ref.html#s |
| 448 | [capx]: ./ref.html#x |
| 449 | [capy]: ./ref.html#y |
| 450 | |
| 451 | [fcp]: https://fossil-scm.org/home/help?cmd=configuration |
| 452 | [fdp]: ../fossil-v-git.wiki#devorg |
| 453 | [forum]: https://fossil-scm.org/forum/ |
| 454 | [fui]: /help?cmd=ui |
| 455 | [lg]: ./login-groups.md |
| 456 | [rs]: https://fossil-scm.org/home/doc/trunk/www/settings.wiki |
| 457 | [sia]: https://fossil-scm.org/home/artifact?ln=1259-1260&name=0fda31b6683c206a |
| 458 | [snoy]: https://fossil-scm.org/forum/forumpost/00e1c4ecff |
| 459 | [th1]: ../th1.md |
| 460 | [tt]: https://en.wikipedia.org/wiki/Tiger_team#Security |
| 461 | [webo]: ./#webonly |
| 462 |
| --- www/caps/admin-v-setup.md | |
| +++ www/caps/admin-v-setup.md | |
| @@ -261,13 +261,13 @@ | |
| 261 | |
| 262 | Setup users can do many things that Admin users cannot. They may not |
| 263 | only use all of the Admin UI features, they may also: |
| 264 | |
| 265 | * See record IDs (RIDs) on screens that show them |
| 266 | * See the MIME type of attachments on [`/ainfo` pages](/help/www/ainfo) |
| 267 | * See a remote repo’s HTTP [cache status](/help/www/cachestat) |
| 268 | and [pull cache entries](/help/www/cacheget) |
| 269 | * Edit a Setup user’s account! |
| 270 | |
| 271 | The “Admin” feature of Fossil UI is so-named because Admin users can use |
| 272 | about half of its functions, but only Setup can use these pages: |
| 273 | |
| @@ -395,11 +395,11 @@ | |
| 395 | |
| 396 | |
| 397 | ### <a id="y"></a>Write Unversioned |
| 398 | |
| 399 | Fossil currently doesn’t distinguish the sub-operations of |
| 400 | [`fossil uv`](/help/uv); they’re all covered by [**WrUnver**][capy] |
| 401 | (“y”) capability. Since some of these operations are unconditionally |
| 402 | destructive due to the nature of unversioned content, and since this |
| 403 | goes against Fossil’s philosophy of immutable history, nobody gets cap |
| 404 | “y” on a Fossil repo by default, not even the Setup or Admin users. A |
| 405 | Setup or Admin user must grant cap “y” to someone — not necessarily |
| @@ -446,16 +446,16 @@ | |
| 446 | [capa]: ./ref.html#a |
| 447 | [caps]: ./ref.html#s |
| 448 | [capx]: ./ref.html#x |
| 449 | [capy]: ./ref.html#y |
| 450 | |
| 451 | [fcp]: https://fossil-scm.org/home/help/configuration |
| 452 | [fdp]: ../fossil-v-git.wiki#devorg |
| 453 | [forum]: https://fossil-scm.org/forum/ |
| 454 | [fui]: /help/ui |
| 455 | [lg]: ./login-groups.md |
| 456 | [rs]: https://fossil-scm.org/home/doc/trunk/www/settings.wiki |
| 457 | [sia]: https://fossil-scm.org/home/artifact?ln=1259-1260&name=0fda31b6683c206a |
| 458 | [snoy]: https://fossil-scm.org/forum/forumpost/00e1c4ecff |
| 459 | [th1]: ../th1.md |
| 460 | [tt]: https://en.wikipedia.org/wiki/Tiger_team#Security |
| 461 | [webo]: ./#webonly |
| 462 |
+1
-1
| --- www/caps/login-groups.md | ||
| +++ www/caps/login-groups.md | ||
| @@ -117,11 +117,11 @@ | ||
| 117 | 117 | you into both A and B, within the restrictions set out above. |
| 118 | 118 | |
| 119 | 119 | Changes are transitive in the same way, provided you check that “apply |
| 120 | 120 | to all” box on the user edit screen. |
| 121 | 121 | |
| 122 | -[lg]: /help?cmd=login-group | |
| 122 | +[lg]: /help/login-group | |
| 123 | 123 | [sh]: ../server/any/http-over-ssh.md |
| 124 | 124 | [wo]: ./index.md#webonly |
| 125 | 125 | |
| 126 | 126 | ----- |
| 127 | 127 | |
| 128 | 128 |
| --- www/caps/login-groups.md | |
| +++ www/caps/login-groups.md | |
| @@ -117,11 +117,11 @@ | |
| 117 | you into both A and B, within the restrictions set out above. |
| 118 | |
| 119 | Changes are transitive in the same way, provided you check that “apply |
| 120 | to all” box on the user edit screen. |
| 121 | |
| 122 | [lg]: /help?cmd=login-group |
| 123 | [sh]: ../server/any/http-over-ssh.md |
| 124 | [wo]: ./index.md#webonly |
| 125 | |
| 126 | ----- |
| 127 | |
| 128 |
| --- www/caps/login-groups.md | |
| +++ www/caps/login-groups.md | |
| @@ -117,11 +117,11 @@ | |
| 117 | you into both A and B, within the restrictions set out above. |
| 118 | |
| 119 | Changes are transitive in the same way, provided you check that “apply |
| 120 | to all” box on the user edit screen. |
| 121 | |
| 122 | [lg]: /help/login-group |
| 123 | [sh]: ../server/any/http-over-ssh.md |
| 124 | [wo]: ./index.md#webonly |
| 125 | |
| 126 | ----- |
| 127 | |
| 128 |
+3
-3
| --- www/caps/ref.html | ||
| +++ www/caps/ref.html | ||
| @@ -315,13 +315,13 @@ | ||
| 315 | 315 | <tr id="z"> |
| 316 | 316 | <th>z</th> |
| 317 | 317 | <th>Zip</th> |
| 318 | 318 | <td> |
| 319 | 319 | Pull archives of particular repository versions via <a |
| 320 | - href="/help?cmd=/zip"><tt>/zip</tt></a>, <a | |
| 321 | - href="/help?cmd=/tarball"><tt>/tarball</tt></a>, and <a | |
| 322 | - href="/help?cmd=/sqlar"><tt>/sqlar</tt></a> URLs. This is an | |
| 320 | + href="/help/www/zip"><tt>/zip</tt></a>, <a | |
| 321 | + href="/help/www/tarball"><tt>/tarball</tt></a>, and <a | |
| 322 | + href="/help/www/sqlar"><tt>/sqlar</tt></a> URLs. This is an | |
| 323 | 323 | expensive capability to grant, because creating such archives can |
| 324 | 324 | put a large load on <a href="../server/">a Fossil server</a> which |
| 325 | 325 | you may then need to <a href="../loadmgmt.md">manage</a>. |
| 326 | 326 | Mnemonic: <b>z</b>ip file download. |
| 327 | 327 | </td> |
| 328 | 328 |
| --- www/caps/ref.html | |
| +++ www/caps/ref.html | |
| @@ -315,13 +315,13 @@ | |
| 315 | <tr id="z"> |
| 316 | <th>z</th> |
| 317 | <th>Zip</th> |
| 318 | <td> |
| 319 | Pull archives of particular repository versions via <a |
| 320 | href="/help?cmd=/zip"><tt>/zip</tt></a>, <a |
| 321 | href="/help?cmd=/tarball"><tt>/tarball</tt></a>, and <a |
| 322 | href="/help?cmd=/sqlar"><tt>/sqlar</tt></a> URLs. This is an |
| 323 | expensive capability to grant, because creating such archives can |
| 324 | put a large load on <a href="../server/">a Fossil server</a> which |
| 325 | you may then need to <a href="../loadmgmt.md">manage</a>. |
| 326 | Mnemonic: <b>z</b>ip file download. |
| 327 | </td> |
| 328 |
| --- www/caps/ref.html | |
| +++ www/caps/ref.html | |
| @@ -315,13 +315,13 @@ | |
| 315 | <tr id="z"> |
| 316 | <th>z</th> |
| 317 | <th>Zip</th> |
| 318 | <td> |
| 319 | Pull archives of particular repository versions via <a |
| 320 | href="/help/www/zip"><tt>/zip</tt></a>, <a |
| 321 | href="/help/www/tarball"><tt>/tarball</tt></a>, and <a |
| 322 | href="/help/www/sqlar"><tt>/sqlar</tt></a> URLs. This is an |
| 323 | expensive capability to grant, because creating such archives can |
| 324 | put a large load on <a href="../server/">a Fossil server</a> which |
| 325 | you may then need to <a href="../loadmgmt.md">manage</a>. |
| 326 | Mnemonic: <b>z</b>ip file download. |
| 327 | </td> |
| 328 |
+2
-2
| --- www/cgi.wiki | ||
| +++ www/cgi.wiki | ||
| @@ -72,11 +72,11 @@ | ||
| 72 | 72 | and if the PATH_INFO string is empty, then Fossil will show a list |
| 73 | 73 | of available Fossil repositories. |
| 74 | 74 | |
| 75 | 75 | The "skin" of the reply is determined by the first |
| 76 | 76 | repository in the list that has a non-zero |
| 77 | -[/help?cmd=repolist-skin|repolist-skin] setting. | |
| 77 | +[/help/repolist-skin|repolist-skin] setting. | |
| 78 | 78 | |
| 79 | 79 | If no repository has such a non-zero repolist-skin setting, then |
| 80 | 80 | the repository list is generic HTML without any decoration, with |
| 81 | 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | 82 | environment variable. The variable can be defined in the CGI |
| @@ -193,11 +193,11 @@ | ||
| 193 | 193 | repo name is "*", then an unconditional redirect to URL is taken. |
| 194 | 194 | |
| 195 | 195 | |
| 196 | 196 | <h2 id="jsmode">jsmode: <i>VALUE</i></h2> |
| 197 | 197 | |
| 198 | -Specifies the delivery mode for JavaScript files. See "[/help?cmd=http | | |
| 198 | +Specifies the delivery mode for JavaScript files. See "[/help/http | | |
| 199 | 199 | http --jsmode]" for the allowed values and their meanings. |
| 200 | 200 | |
| 201 | 201 | |
| 202 | 202 | <h2 id="mainmenu">mainmenu: <i>FILE</i></h2> |
| 203 | 203 | |
| 204 | 204 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -72,11 +72,11 @@ | |
| 72 | and if the PATH_INFO string is empty, then Fossil will show a list |
| 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 |
| @@ -193,11 +193,11 @@ | |
| 193 | repo name is "*", then an unconditional redirect to URL is taken. |
| 194 | |
| 195 | |
| 196 | <h2 id="jsmode">jsmode: <i>VALUE</i></h2> |
| 197 | |
| 198 | Specifies the delivery mode for JavaScript files. See "[/help?cmd=http | |
| 199 | http --jsmode]" for the allowed values and their meanings. |
| 200 | |
| 201 | |
| 202 | <h2 id="mainmenu">mainmenu: <i>FILE</i></h2> |
| 203 | |
| 204 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -72,11 +72,11 @@ | |
| 72 | and if the PATH_INFO string is empty, then Fossil will show a list |
| 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/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 |
| @@ -193,11 +193,11 @@ | |
| 193 | repo name is "*", then an unconditional redirect to URL is taken. |
| 194 | |
| 195 | |
| 196 | <h2 id="jsmode">jsmode: <i>VALUE</i></h2> |
| 197 | |
| 198 | Specifies the delivery mode for JavaScript files. See "[/help/http | |
| 199 | http --jsmode]" for the allowed values and their meanings. |
| 200 | |
| 201 | |
| 202 | <h2 id="mainmenu">mainmenu: <i>FILE</i></h2> |
| 203 | |
| 204 |
+409
-345
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,44 +1,108 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | -<h2 id='v2_27'>Changes for version 2.27 (pending)</h2><ol> | |
| 4 | - <li> Fix a SQL injection on the [/help?cmd=/file|/file page]. Thanks to | |
| 3 | +<h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol> | |
| 4 | + <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a"> | |
| 5 | + <li> The default configuration now allows robots to download any tarball | |
| 6 | + or similar, to better support of automated build systems. | |
| 7 | + <li> New special tag "zipX" for the [/help/robot-restrict|robot-restrict] | |
| 8 | + setting blocks robot access to tarballs, but with exceptions to support | |
| 9 | + automated build systems. | |
| 10 | + <li> Enhancements to the default value for the | |
| 11 | + [/help/robot-restrict|robot-restrict setting]. | |
| 12 | + </ol> | |
| 13 | + <li> A drop-down menu of recent branches is now possible for the submenu, and | |
| 14 | + is used in the [/dir?ci=trunk|code browser]. | |
| 15 | + <li> Easier access to generated tarballs and ZIPs:<ol type="a"> | |
| 16 | + <li> When in the [/dir?ci=trunk|code browser] at the top-level, | |
| 17 | + a new "Download" submenu option is available to take the | |
| 18 | + user to a page where he can download a tarball or ZIP archive. | |
| 19 | + <li> New [/help/www/download|/download page] is available. When | |
| 20 | + configured using the new | |
| 21 | + [/help/suggested-downloads|suggested-downloads setting], a | |
| 22 | + link to [/download] named "Tarballs and ZIPs" appears in the | |
| 23 | + [/sitemap] and thus on the hamburger menu. | |
| 24 | + <li> The filenames for tarballs and ZIPs are now standardized to | |
| 25 | + include a timestamp and a hash prefix. | |
| 26 | + </ol> | |
| 27 | + <li> Timeline enhancements:<ol type="a"> | |
| 28 | + <li> A new "Simple" view is available. This is compromise between "Verbose" | |
| 29 | + and "Compact" that shows only the check-in hash rather than the full | |
| 30 | + detail section. There is an ellipsis that one can click on to see the | |
| 31 | + full detail text. | |
| 32 | + <li> The artifact hash in the detail section of each timeline entry is now | |
| 33 | + emphasized (controlled by CSS) to make it easier to locate amid all | |
| 34 | + the other text and links. | |
| 35 | + <li> When clicking on the ellipsis in "Compact" or "Simple" views, the ellipsis | |
| 36 | + is replaced by a left arrow ("←") which can be clicked to hide the extra | |
| 37 | + detail again. | |
| 38 | + <li> New setting [/help/timeline-mark-leaves|timeline-mark-leaves] controls | |
| 39 | + whether or not leaf check-ins are marked in the timeline. | |
| 40 | + <li> "No-graph" timelines (using the "ng" query parameter) now show | |
| 41 | + branch colors and bare check-in circles on the left. The check-in | |
| 42 | + circles appear, but no lines connecting them. | |
| 43 | + ([/timeline?ng|example]). | |
| 44 | + </ol> | |
| 45 | + <li> The [/help/timeline|timeline command] is enhanced with the new | |
| 46 | + "<tt>-u|--for-user</tt>" option. | |
| 47 | + <li> The [/help/open|open command]'s new "<tt>--reopen REPOFILE</tt>" flag | |
| 48 | + can be used to fix a checkout after moving its repository file. | |
| 49 | +</ol> | |
| 50 | + | |
| 51 | +<h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol> | |
| 52 | + <li> Close a potential Denial-of-Service attack against any public-facing Fossil | |
| 53 | + server involving exponential behavior in Fossil's regexp implementation. | |
| 54 | + <li> Fix a SQL injection on the [/help/www/file|/file page]. Thanks to | |
| 5 | 55 | additional defenses built into Fossil, as well as good luck, this injection |
| 6 | 56 | is not exploitable for either data exfiltration or privilege escalation. The |
| 7 | 57 | only possible result of invoking the injection is a harmless SQL syntax error. |
| 8 | - (The [https://en.wikipedia.org/wiki/Swiss_cheese_model|holes in the Swiss cheese] | |
| 9 | - did not line up!) | |
| 10 | - <li> Enhance the chng= query parameter on the [/help?cmd=/timeline|timeline page] | |
| 11 | - so that it works with other query parameters like p=, d=, from=, and to=. | |
| 12 | - <li> Always include nodes identify by sel1= and sel2= in the /timeline display. | |
| 13 | - <li> Enable the --editor option on the [/help?cmd=amend|fossil amend] command. | |
| 14 | - <li> Require at least an anonymous login to access the /blame page and similar, | |
| 15 | - to help prevent robots from soaking up excess CPU time on such pages. | |
| 58 | + <li> Strengthen robot defenses to help prevent public-facing servers from being | |
| 59 | + overwhelmed by the latest generation of AI spiders. | |
| 60 | + <ol type="a"> | |
| 61 | + <li> New javascript captcha used to restrict access by user "nobody" to pages | |
| 62 | + listed in the [/help/robot-restrict|robot-restrict setting]. | |
| 63 | + <li> The [/help/robot-exception|robot-exception setting] is available to allow | |
| 64 | + access to pages that match a regular expression. Use this, for example, to | |
| 65 | + allow curl scripts and similar to download release tarballs. | |
| 66 | + <li> Require at least an anonymous login to access the /blame page and similar. | |
| 67 | + </ol> | |
| 68 | + <li> [/help/www/timeline|Timeline] enhancements: | |
| 69 | + <ol type="a"> | |
| 70 | + <li> The chng= query parameter on the [/help/www/timeline|timeline page] | |
| 71 | + so that it works with other query parameters like p=, d=, from=, and to=. | |
| 72 | + <li> Always include nodes identify by sel1= and sel2= in the /timeline display. | |
| 73 | + <li> Improved title when p= and d= are different. | |
| 74 | + </ol> | |
| 75 | + <li> Enable the --editor option on the [/help/amend|fossil amend] command. | |
| 16 | 76 | <li> When walking the filesystem looking for Fossil repositories, avoid descending |
| 17 | 77 | into directories named "/proc". |
| 18 | - <ll> Reduce memory requirements for sending authenticated sync protocol | |
| 78 | + <li> Reduce memory requirements for sending authenticated sync protocol | |
| 19 | 79 | messages. |
| 20 | - </ol> | |
| 80 | + <li> Show numstat-style change statistics in the /info and /ckout pages. | |
| 81 | + <li> Add the [/help/stash | stash rename] subcommand. | |
| 82 | + <li> Add the "-h" option to the "[/help/ls|ls]" command to display | |
| 83 | + file hashes for a specific check-in when in verbose mode. | |
| 84 | + </ol> | |
| 21 | 85 | |
| 22 | 86 | <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol> |
| 23 | - <li>Enhancements to [/help?cmd=diff|fossil diff] and similar: | |
| 87 | + <li>Enhancements to [/help/diff|fossil diff] and similar: | |
| 24 | 88 | <ol type="a"> |
| 25 | 89 | <li> The argument to the --from option can be a directory name, causing |
| 26 | 90 | Fossil to use files under that directory as the baseline for the diff. |
| 27 | - <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] | |
| 91 | + <li> For "gdiff", if no [/help/gdiff-command|gdiff-command setting] | |
| 28 | 92 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 29 | 93 | are available, or a --by diff if not. |
| 30 | 94 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 31 | 95 | diff up to date with the latest changes on disk. |
| 32 | 96 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 33 | 97 | diffs of multiple files. |
| 34 | 98 | </ol> |
| 35 | - <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information | |
| 99 | + <li>Added the [/help/www/ckout|/ckout web page] to provide information | |
| 36 | 100 | about pending changes in a working check-out |
| 37 | - <li>Enhancements to the [/help?cmd=ui|fossil ui] command: | |
| 101 | + <li>Enhancements to the [/help/ui|fossil ui] command: | |
| 38 | 102 | <ol type="a"> |
| 39 | - <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its | |
| 103 | + <li> Defaults to using the new [/help/www/ckout|/ckout page] as its | |
| 40 | 104 | start page. Or, if the new "--from PATH" option is present, the |
| 41 | 105 | default start page becomes "/ckout?exbase=PATH". |
| 42 | 106 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 43 | 107 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 44 | 108 | person editing this change log has |
| @@ -46,25 +110,25 @@ | ||
| 46 | 110 | press "Reload" on the web browser to view edits. |
| 47 | 111 | <li> Accept both IPv4 and IPv6 connections on all platforms, including |
| 48 | 112 | Windows and OpenBSD. This also applies to the "fossil server" |
| 49 | 113 | command. |
| 50 | 114 | </ol> |
| 51 | - <li>Enhancements to [/help?cmd=merge|fossil merge]: | |
| 115 | + <li>Enhancements to [/help/merge|fossil merge]: | |
| 52 | 116 | <ol type="a"> |
| 53 | - <li> Added the [/help?cmd=merge-info|fossil merge-info] command and | |
| 117 | + <li> Added the [/help/merge-info|fossil merge-info] command and | |
| 54 | 118 | especially the --tk option to that command, to provide analysis |
| 55 | 119 | of the most recent merge or update operation. |
| 56 | 120 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 57 | 121 | text that shows Fossil's suggested resolution to the conflict. |
| 58 | 122 | </ol> |
| 59 | - <li>Enhancements to [/help?cmd=commit|fossil commit]: | |
| 123 | + <li>Enhancements to [/help/commit|fossil commit]: | |
| 60 | 124 | <ol type="a"> |
| 61 | 125 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 62 | 126 | in the check-in comment, it will alert the developer and give |
| 63 | 127 | him or her the opportunity to edit the comment before continuing. |
| 64 | 128 | This feature is controllable by the |
| 65 | - [/help?cmd=verify-comments|verify-comments setting]. | |
| 129 | + [/help/verify-comments|verify-comments setting]. | |
| 66 | 130 | <li> The new "--if-changes" option causes the commit to become |
| 67 | 131 | a quiet no-op if there are no pending changes. |
| 68 | 132 | <li> Added the ability to sign check-ins with SSH keys. Artifacts signed |
| 69 | 133 | this way are ignored by all previous fossil versions, as if they |
| 70 | 134 | were plain-text file content instead of Fossil artifacts. |
| @@ -76,13 +140,13 @@ | ||
| 76 | 140 | </ol> |
| 77 | 141 | <li>Deprecate the --comfmtflags and --comment-format global options and |
| 78 | 142 | no longer list them in the built-in help, but keep them working for |
| 79 | 143 | backwards compatibility. |
| 80 | 144 | Alternative TTY comment formatting can still be specified using the |
| 81 | - [/help?cmd=comment-format|comment-format setting], if desired. The | |
| 145 | + [/help/comment-format|comment-format setting], if desired. The | |
| 82 | 146 | default comment format is now called "canonical", not "legacy". |
| 83 | - <li>Enhancements to the [/help?cmd=/timeline|/timeline page]: | |
| 147 | + <li>Enhancements to the [/help/www/timeline|/timeline page]: | |
| 84 | 148 | <ol type="a"> |
| 85 | 149 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 86 | 150 | like "rl=" ("Related List") but adds "mionly" style related |
| 87 | 151 | check-ins instead of the full "rel" style. |
| 88 | 152 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -111,33 +175,33 @@ | ||
| 111 | 175 | in which case the timeline shows those check-ins that are both |
| 112 | 176 | ancestors of p= and descendants of d=. |
| 113 | 177 | <li> The saturation and intensity of user-specified checkin and branch |
| 114 | 178 | background colors are automatically adjusted to keep the colors |
| 115 | 179 | compatible with the current skin, unless the |
| 116 | - [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. | |
| 180 | + [/help/raw-bgcolor|raw-bgcolor setting] is turned on. | |
| 117 | 181 | </ol> |
| 118 | - <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like | |
| 182 | + <li>The [/help/www/docfile|/docfile webpage] was added. It works like | |
| 119 | 183 | /doc but keeps the title of markdown documents with the document rather |
| 120 | 184 | that moving it up to the page title. |
| 121 | - <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis | |
| 185 | + <li>Added the [/help/www/clusterlist|/clusterlist page] for analysis | |
| 122 | 186 | and debugging |
| 123 | 187 | <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 124 | 188 | decoding of the artifact described by NAME. |
| 125 | - <li>Improvements to the [/help?cmd=patch|fossil patch] command: | |
| 189 | + <li>Improvements to the [/help/patch|fossil patch] command: | |
| 126 | 190 | <ol type="a"> |
| 127 | 191 | <li> Fix a bug in "fossil patch create" that causes |
| 128 | - [/help?cmd=revert|fossil revert] operations that happened | |
| 129 | - on individualfiles after a [/help?cmd=merge|fossil merge] | |
| 192 | + [/help/revert|fossil revert] operations that happened | |
| 193 | + on individualfiles after a [/help/merge|fossil merge] | |
| 130 | 194 | to be omitted from the patch. |
| 131 | - <li> Added the [/help?cmd=patch|patch alias] command for managing | |
| 195 | + <li> Added the [/help/patch|patch alias] command for managing | |
| 132 | 196 | aliases for remote checkout names. |
| 133 | 197 | </ol> |
| 134 | - <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command: | |
| 198 | + <li>Enhancements to on-line help and the [/help/help|fossil help] command: | |
| 135 | 199 | <ol type="a"> |
| 136 | 200 | <li> Add the ability to search the help text, either in the UI |
| 137 | - (on the [/help?cmd=/search|/search page]) or from the command-line | |
| 138 | - (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) | |
| 201 | + (on the [/help/www/search|/search page]) or from the command-line | |
| 202 | + (using the "[/help/search|fossil search -h PATTERN]" command.) | |
| 139 | 203 | <li> Accepts an optional SUBCOMMAND argument following the |
| 140 | 204 | COMMAND argument and only shows results for the specified |
| 141 | 205 | subcommand, not the entire command. |
| 142 | 206 | <li> The -u (--usage) option shows only the command-line syntax |
| 143 | 207 | <li> The -o (--options) option shows only the command-line options |
| @@ -153,11 +217,11 @@ | ||
| 153 | 217 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 154 | 218 | <li> Show creation time in report and ticket view. |
| 155 | 219 | <li> Show previous comments in edit ticket as reference. |
| 156 | 220 | </ol> |
| 157 | 221 | <li>Added the "hash" query parameter to the |
| 158 | - [/help?cmd=/whatis|/whatis webpage]. | |
| 222 | + [/help/www/whatis|/whatis webpage]. | |
| 159 | 223 | <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] |
| 160 | 224 | which alerts subscribers when an admin creates a new user or |
| 161 | 225 | when a user's permissions change. |
| 162 | 226 | <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains |
| 163 | 227 | the substring "description", then the project description for each repository |
| @@ -169,44 +233,44 @@ | ||
| 169 | 233 | <ol type="a"> |
| 170 | 234 | <li> TH1 now makes a distinction between |
| 171 | 235 | [/doc/trunk/www/th1.md#taint|tainted and untainted string values]. |
| 172 | 236 | This makes it more difficult to write custom TH1 scripts that |
| 173 | 237 | contain XSS or SQL-injection bugs. The |
| 174 | - [/help?cmd=vuln-report|vuln-report] setting was added to control | |
| 238 | + [/help/vuln-report|vuln-report] setting was added to control | |
| 175 | 239 | what Fossil does when it encounters a potential TH1 |
| 176 | 240 | security problem. |
| 177 | - <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr] | |
| 241 | + <li> The "--th" option was removed from the [/help/pikchr|fossil pikchr] | |
| 178 | 242 | command. |
| 179 | 243 | <li> The "enable_htmlify" TH1 command was removed. |
| 180 | 244 | </ol> |
| 181 | - <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing | |
| 245 | + <li>Make [/help/www/chat|/chat] better-behaved during server outages, reducing | |
| 182 | 246 | the frequency of reconnection attempts over time and providing feedback |
| 183 | 247 | to the user when the connection is down. |
| 184 | - <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged | |
| 248 | + <li>The [/help/www/sqlar|/sqlar] page does not work for users who are not logged | |
| 185 | 249 | in, nor are links to that page displayed to users who are not logged in. Being |
| 186 | 250 | logged in as "anonymous" is sufficient to overcome this restriction, assuming |
| 187 | 251 | that "anonymous" can download tarballs and ZIP archives. |
| 188 | 252 | <li>Many other minor fixes and additions. |
| 189 | 253 | </ol> |
| 190 | 254 | |
| 191 | 255 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 192 | 256 | |
| 193 | - * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories | |
| 257 | + * The "[/help/ui|fossil ui /]" command now works even for repositories | |
| 194 | 258 | that have non-ASCII filenames |
| 195 | - * Add the [/help?cmd=tree|fossil tree] command. | |
| 259 | + * Add the [/help/tree|fossil tree] command. | |
| 196 | 260 | * On case-insensitive filesystems, store files using the filesystem's |
| 197 | 261 | preferred case rather than the case typed in by the user. |
| 198 | 262 | * Change the name "fossil cherry-pick" command to "fossil cherrypick", |
| 199 | 263 | which is more familiar to Git users. Retain the legacy name for |
| 200 | 264 | compatibility. |
| 201 | - * Add new query parameters to the [/help?cmd=/timeline|/timeline page]: | |
| 265 | + * Add new query parameters to the [/help/www/timeline|/timeline page]: | |
| 202 | 266 | d2=, p2=, and dp2=. |
| 203 | - * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values. | |
| 204 | - * Add the -b|--brief option to the [/help?cmd=status|fossil status] command. | |
| 205 | - * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page]. | |
| 206 | - * Add history search to the [/help?cmd=/chat|/chat page]. | |
| 207 | - * Add Unix socket support to the [/help?cmd=server|server command]. | |
| 267 | + * Add options to the [/help/tag|fossil tag] command that will list tag values. | |
| 268 | + * Add the -b|--brief option to the [/help/status|fossil status] command. | |
| 269 | + * Add ability to upload unversioned files via the [/help/www/uvlist|/uvlist page]. | |
| 270 | + * Add history search to the [/help/www/chat|/chat page]. | |
| 271 | + * Add Unix socket support to the [/help/server|server command]. | |
| 208 | 272 | * On Windows, use the root certificates managed by the operating system |
| 209 | 273 | (requires OpenSSL 3.2.0 or greater). |
| 210 | 274 | * Take into account zero-width and double-width unicode characters when |
| 211 | 275 | formatting the command-line timeline. |
| 212 | 276 | * Update the built-in SQLite to version 3.47.0. Precompiled binaries are |
| @@ -243,11 +307,11 @@ | ||
| 243 | 307 | </ul> |
| 244 | 308 | * If an "ssh:" sync fails in a way that suggests that the fossil executable |
| 245 | 309 | could not be found on the remote host, then retry after adding a PATH= |
| 246 | 310 | prefix to the command. This helps "ssh:" to "just work" when the server |
| 247 | 311 | is a Mac. |
| 248 | - * Enhancements to the [/help?cmd=/timeline|/timeline page]: | |
| 312 | + * Enhancements to the [/help/www/timeline|/timeline page]: | |
| 249 | 313 | <ul> |
| 250 | 314 | <li> Add the x= query paramater |
| 251 | 315 | <li> Add the shortcut tl= and rl= query parameters |
| 252 | 316 | <li> Add support for from=,ft= and from=,bt= query parameter combinations |
| 253 | 317 | <li> Automatically highlight the endpoints for from=,to= queries. |
| @@ -257,16 +321,16 @@ | ||
| 257 | 321 | * Moved the /museum/repo.fossil file referenced from the Dockerfile from |
| 258 | 322 | the ENTRYPOINT to the CMD part to allow use of --repolist mode. |
| 259 | 323 | * The [/uvlist] page now shows the hash algorithm used so that |
| 260 | 324 | viewers don't have to guess. The hash is shown in a fixed-width |
| 261 | 325 | font for a more visually pleasing display. |
| 262 | - * If the [/help?cmd=autosync|autosync setting] contains keyword "all", | |
| 326 | + * If the [/help/autosync|autosync setting] contains keyword "all", | |
| 263 | 327 | the automatic sync occurs against all defined remote repositories, not |
| 264 | 328 | just the default. |
| 265 | 329 | * Markdown formatter: improved handling of indented fenced code blocks |
| 266 | 330 | that contain blank lines. |
| 267 | - * When doing a "[/help?cmd=add|fossil add]" on a system with case-insensitive | |
| 331 | + * When doing a "[/help/add|fossil add]" on a system with case-insensitive | |
| 268 | 332 | but case-preserving filenames (Mac and Windows) try to use the filename |
| 269 | 333 | case as it is known to the filesystem, not the case entered by the |
| 270 | 334 | user on the command-line. See |
| 271 | 335 | [forum:/forumpost/30d9c0d131610f53|forum thread 30d9c0d131610f53]. |
| 272 | 336 | * Fix problems with one-click unsubscribe on email notifications. |
| @@ -280,17 +344,17 @@ | ||
| 280 | 344 | <h2 id='v2_23'>Changes for version 2.23 (2023-11-01)</h2> |
| 281 | 345 | |
| 282 | 346 | * Add ability to "close" forum threads, such that unprivileged users |
| 283 | 347 | may no longer respond to them. Only administrators can close |
| 284 | 348 | threads or respond to them by default, and the |
| 285 | - [/help?cmd=forum-close-policy|forum-close-policy setting] can be | |
| 349 | + [/help/forum-close-policy|forum-close-policy setting] can be | |
| 286 | 350 | used to add that capability to moderators. |
| 287 | - * Add the [/help?cmd=all|fossil all whatis] command. | |
| 288 | - * The [/help?cmd=status|fossil status] command and relevant UI pages now | |
| 351 | + * Add the [/help/all|fossil all whatis] command. | |
| 352 | + * The [/help/status|fossil status] command and relevant UI pages now | |
| 289 | 353 | correctly report files which were both renamed <b>and</b> edited as such. |
| 290 | 354 | * Show default value of settings that have a default in |
| 291 | - [/help?cmd=help|fossil help SETTING] output. | |
| 355 | + [/help/help|fossil help SETTING] output. | |
| 292 | 356 | * On timeline graphs, show closed check-ins using an X in the middle of the |
| 293 | 357 | node circle or box. |
| 294 | 358 | * New options for email notification: Get email only for the first |
| 295 | 359 | post in each new thread, and/or posts that are in reply to my posts. |
| 296 | 360 | * Fix a regression bug introduced in version 2.22 that caused FTS5 searches |
| @@ -303,35 +367,35 @@ | ||
| 303 | 367 | <li> Better defense against cross-site request forgery (CSRF) |
| 304 | 368 | attacks. |
| 305 | 369 | <li> Improvements to static analysis of source code (the codecheck1.c |
| 306 | 370 | file in the source tree). |
| 307 | 371 | </ul> |
| 308 | - * Enhance the [/help?cmd=/dir|treeview file listings] | |
| 372 | + * Enhance the [/help/www/dir|treeview file listings] | |
| 309 | 373 | ([/dir?type=tree&ci=trunk|example]) by displaying file sizes |
| 310 | 374 | and adding the option to sort by file size. |
| 311 | - * The [/help?cmd=fts-config|fossil fts-config] command now shows how much | |
| 375 | + * The [/help/fts-config|fossil fts-config] command now shows how much | |
| 312 | 376 | repository space is used by the full-text index. |
| 313 | 377 | * Changing a setting to an empty string is now the same as deleting the |
| 314 | 378 | setting, in most cases. There are a few exceptions, indicated by the |
| 315 | 379 | keep-empty flag on the setting definition. |
| 316 | - * The [/help?cmd=branch|fossil branch list] command can now filter branches | |
| 380 | + * The [/help/branch|fossil branch list] command can now filter branches | |
| 317 | 381 | that have/have not been merged into the current branch. |
| 318 | 382 | * Improvements to interactions with remote repositories over SSH: |
| 319 | 383 | <ul> |
| 320 | 384 | <li> Print the text of the SSH command that is run to do remote interaction, |
| 321 | 385 | for full disclosure to the operator. |
| 322 | - <li> Add a PATH= argument to the [/help?cmd=ui|fossil ui remote:/] and | |
| 323 | - [/help?cmd=patch|fossil patch push/pull remote:...] commands so that | |
| 386 | + <li> Add a PATH= argument to the [/help/ui|fossil ui remote:/] and | |
| 387 | + [/help/patch|fossil patch push/pull remote:...] commands so that | |
| 324 | 388 | they work when the "remote" machine is a Mac and the "fossil" |
| 325 | 389 | executable is in the $HOME/bin directory. |
| 326 | 390 | </ul> |
| 327 | 391 | * Update built-in libraries SQLite, ZLib, Pikchr to their latest versions. |
| 328 | 392 | * Documentation enhancements and typo fixes. |
| 329 | 393 | |
| 330 | 394 | |
| 331 | 395 | <h2 id='v2_22'>Changes for version 2.22 (2023-05-31)</h2> |
| 332 | - * Enhancements to the [/help?cmd=/timeline|/timeline webpage]: <ol type="a"> | |
| 396 | + * Enhancements to the [/help/www/timeline|/timeline webpage]: <ol type="a"> | |
| 333 | 397 | <li> Add the ft=TAG query parameter which in combination with d=Y |
| 334 | 398 | shows all descendants of Y up to TAG |
| 335 | 399 | <li> Enhance the s=PATTERN (search) query parameter so that forum post |
| 336 | 400 | text is also searched when the "vfx" query parameter is used |
| 337 | 401 | <li> Fix the u= (user) query parameter so that it works with a= and b= |
| @@ -355,56 +419,56 @@ | ||
| 355 | 419 | searching in Chinese. |
| 356 | 420 | * Comment lines (starting with a '#') are now supported inside |
| 357 | 421 | [./settings.wiki#versionable|versioned settings]. |
| 358 | 422 | * Default permissions for anonymous users in new repositories are |
| 359 | 423 | changed to "hz". |
| 360 | - * The [/help?cmd=status|fossil status] command now detects when a | |
| 424 | + * The [/help/status|fossil status] command now detects when a | |
| 361 | 425 | file used to be a symlink and has been replaced by a regular file. |
| 362 | 426 | (It previously checked for the inverse case only.) |
| 363 | - * The [/help?cmd=empty-dirs|empty-dirs setting] now reuses the same | |
| 427 | + * The [/help/empty-dirs|empty-dirs setting] now reuses the same | |
| 364 | 428 | parser as the *-glob settings instead of its prior idiosyncratic |
| 365 | 429 | parser, allowing quoted whitespace in patterns. |
| 366 | - * Enhancements to the [/help?cmd=/reports|/reports webpage]: | |
| 430 | + * Enhancements to the [/help/www/reports|/reports webpage]: | |
| 367 | 431 | <ol type="a"> |
| 368 | 432 | <li> The by-week, by-month, and by-year options now show an estimated |
| 369 | 433 | size of the current week, month, or year as a dashed box. |
| 370 | 434 | <li> New sub-categories "Merge Check-ins" and "Non-Merge Check-ins". |
| 371 | 435 | </ol> |
| 372 | 436 | |
| 373 | 437 | <h2 id='v2_21'>Changes for version 2.21 (2023-02-25)</h2> |
| 374 | 438 | * Users can request a password reset. This feature is disabled by default. |
| 375 | - Use the new [/help?cmd=self-pw-reset|self-pw-reset property] to enable it. | |
| 376 | - New web pages [/help?cmd=/resetpw|/resetpw] and | |
| 377 | - [/help?cmd=/reqpwreset|/reqpwreset] added. | |
| 378 | - * Add the [/help?cmd=repack|fossil repack] command (together with | |
| 379 | - [/help?cmd=all|fossil all repack]) as a convenient way to optimize the | |
| 439 | + Use the new [/help/self-pw-reset|self-pw-reset property] to enable it. | |
| 440 | + New web pages [/help/www/resetpw|/resetpw] and | |
| 441 | + [/help/www/reqpwreset|/reqpwreset] added. | |
| 442 | + * Add the [/help/repack|fossil repack] command (together with | |
| 443 | + [/help/all|fossil all repack]) as a convenient way to optimize the | |
| 380 | 444 | size of one or all of the repositories on a system. |
| 381 | 445 | * Add the ability to put text descriptions on ticket report formats. |
| 382 | 446 | * Upgrade the test-find-pivot command to the [/help/merge-base|merge-base command]. |
| 383 | - * The [/help?cmd=/chat|/chat page] can now embed fossil-rendered | |
| 447 | + * The [/help/www/chat|/chat page] can now embed fossil-rendered | |
| 384 | 448 | views of wiki/markdown/pikchr file attachments with the caveat that such |
| 385 | 449 | embedding happens in an iframe and thus does not inherit styles and such |
| 386 | 450 | from the containing browser window. |
| 387 | - * The [/help?cmd=all|fossil all remote] subcommand added to "fossil all". | |
| 451 | + * The [/help/all|fossil all remote] subcommand added to "fossil all". | |
| 388 | 452 | * Passwords for remembered remote repositories are now stored as irreversible |
| 389 | 453 | hashes rather than obscured clear-text, for improved security. |
| 390 | 454 | * Add the "nossl" and "nocompress" options to CGI. |
| 391 | 455 | * Update search infrastructure from FTS4 to FTS5. |
| 392 | - * Add the [/help?cmd=/deltachain|/deltachain] page for debugging purposes. | |
| 456 | + * Add the [/help/www/deltachain|/deltachain] page for debugging purposes. | |
| 393 | 457 | * Writes to the database are disabled by default if the HTTP request |
| 394 | 458 | does not come from the same origin. This enhancement is a defense in depth |
| 395 | 459 | measure only; it does not address any known vulnerabilities. |
| 396 | 460 | * Improvements to automatic detection and mitigation of attacks from |
| 397 | 461 | malicious robots. |
| 398 | 462 | |
| 399 | 463 | <h2 id='v2_20'>Changes for version 2.20 (2022-11-16)</h2> |
| 400 | - * Added the [/help?cmd=chat-timeline-user|chat-timeline-user setting]. If | |
| 464 | + * Added the [/help/chat-timeline-user|chat-timeline-user setting]. If | |
| 401 | 465 | it is not an empty string, then any changes that would appear on the timeline |
| 402 | 466 | are announced in [./chat.md|the chat room]. |
| 403 | 467 | * The /unsubscribe page now requests confirmation. [./alerts.md|Email notifications] |
| 404 | 468 | now contain only an "Unsubscribe" link, and not a link to subscription management. |
| 405 | - * Added the "[/help?cmd=branch|fossil branch lsh]" subcommand to list the | |
| 469 | + * Added the "[/help/branch|fossil branch lsh]" subcommand to list the | |
| 406 | 470 | most recently modified branches. |
| 407 | 471 | * More elements of the /info page are now inside of an accordion. |
| 408 | 472 | * Replace the <tt>--dryrun</tt> flag with <tt>--dry-run</tt> in all |
| 409 | 473 | commands which still used the former name, for consistency. |
| 410 | 474 | * Rebuilt [/file/Dockerfile | the stock Dockerfile] to create a "from scratch" |
| @@ -434,11 +498,11 @@ | ||
| 434 | 498 | accomplished using a Common Table Expression in the underlying SQL. |
| 435 | 499 | * Sort tag listings (command line and webpage) by taking numbers into |
| 436 | 500 | consideration so as to cater for tags that follow semantic versioning. |
| 437 | 501 | * On the wiki listings, omit by default wiki pages that are associated with |
| 438 | 502 | check-ins and branches. |
| 439 | - * Add the new "[/help?cmd=describe|fossil describe]" command. | |
| 503 | + * Add the new "[/help/describe|fossil describe]" command. | |
| 440 | 504 | * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support]. |
| 441 | 505 | See corresponding [../test/markdown-test3.md|test cases], |
| 442 | 506 | [/wiki?name=branch/markdown-footnotes#il|known limitations] and |
| 443 | 507 | [forum:/forumthread/ee1f1597e46ec07a|discussion]. |
| 444 | 508 | * Add the new special name "start:BRANCH" to refer to the first check-in of |
| @@ -450,96 +514,96 @@ | ||
| 450 | 514 | operation. Also require explicit "system" proxy setting to use |
| 451 | 515 | "http_proxy" environment variable. |
| 452 | 516 | * Reimplemented the [/pikchrshow] app to use a WebAssembly build of |
| 453 | 517 | pikchr so that it can render pikchrs on the client instead of requiring |
| 454 | 518 | a server round-trip. |
| 455 | - * Add the [/help?cmd=email-listid|email-listid setting]. If set, it is | |
| 519 | + * Add the [/help/email-listid|email-listid setting]. If set, it is | |
| 456 | 520 | used as the List-ID header for all outbound notification emails. |
| 457 | - * Add the "--branch" option to the "[/help?cmd=timeline|timeline]" command | |
| 521 | + * Add the "--branch" option to the "[/help/timeline|timeline]" command | |
| 458 | 522 | to restrict the displayed items to a specific branch. |
| 459 | - * Add the "--versions" option to "[/help?cmd=diff|fossil diff]" | |
| 523 | + * Add the "--versions" option to "[/help/diff|fossil diff]" | |
| 460 | 524 | to display details about the compared versions into the patch header. |
| 461 | 525 | * Numerous other minor enhancements. |
| 462 | 526 | |
| 463 | 527 | <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2> |
| 464 | 528 | * Added support for [./ssl-server.md|SSL/TLS server mode] for commands |
| 465 | - like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]" | |
| 466 | - * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for | |
| 467 | - [/help?cmd=merge|merge --cherrypick]. | |
| 468 | - * Add new setting "[/help?cmd=large-file-size|large-file-size]". If the size | |
| 529 | + like "[/help/server|fossil server]" and "[/help/http|fossil http]" | |
| 530 | + * The new [/help/cherry-pick|cherry-pick command] is an alias for | |
| 531 | + [/help/merge|merge --cherrypick]. | |
| 532 | + * Add new setting "[/help/large-file-size|large-file-size]". If the size | |
| 469 | 533 | of any file in a commit exceeds this size, a warning is issued. |
| 470 | - * Query parameter "year=YYYY" is now accepted by [/help?cmd=/timeline|/timeline]. | |
| 471 | - * The [/help?cmd=tar|tar] and [/help?cmd=zip|zip commands] no longer | |
| 534 | + * Query parameter "year=YYYY" is now accepted by [/help/www/timeline|/timeline]. | |
| 535 | + * The [/help/tar|tar] and [/help/zip|zip commands] no longer | |
| 472 | 536 | sterilize the manifest file. |
| 473 | 537 | * Further improvement to diff alignment in cases that involve both |
| 474 | 538 | edits and indentation changes. |
| 475 | 539 | * [/doc/trunk/www/chat.md|Chat] improvements:<ul> |
| 476 | - <li> [/help?cmd=/chat|The /chat page] input options have been reworked | |
| 540 | + <li> [/help/www/chat|The /chat page] input options have been reworked | |
| 477 | 541 | again for better cross-browser portability. |
| 478 | - <li> When sending a [/help?cmd=/chat|/chat] message fails, it is no longer | |
| 542 | + <li> When sending a [/help/www/chat|/chat] message fails, it is no longer | |
| 479 | 543 | immediately lost and sending may optionally be retried. |
| 480 | - <li> [/help?cmd=/chat|/chat] can now optionally embed attachments of certain | |
| 544 | + <li> [/help/www/chat|/chat] can now optionally embed attachments of certain | |
| 481 | 545 | types directly into message bodies via an iframe. |
| 482 | - <li> Add the "--as FILENAME" option to the "[/help?cmd=chat|fossil chat send]" | |
| 546 | + <li> Add the "--as FILENAME" option to the "[/help/chat|fossil chat send]" | |
| 483 | 547 | command. |
| 484 | - <li> Added the "[/help?cmd=chat|fossil chat pull]" command, available to | |
| 548 | + <li> Added the "[/help/chat|fossil chat pull]" command, available to | |
| 485 | 549 | administrators only, for backing up the chat conversation. |
| 486 | 550 | </ul> |
| 487 | - * Promote the test-detach command into the [/help?cmd=detach|detach command]. | |
| 488 | - * For "[/help?cmd=pull|fossil pull]" with the --from-parent-project option, | |
| 551 | + * Promote the test-detach command into the [/help/detach|detach command]. | |
| 552 | + * For "[/help/pull|fossil pull]" with the --from-parent-project option, | |
| 489 | 553 | if no URL is specified then use the last URL from the most recent prior |
| 490 | 554 | "fossil pull --from-parent-project". |
| 491 | 555 | * Add options --project-name and --project-desc to the |
| 492 | - "[/help?cmd=init|fossil init]" command. | |
| 493 | - * The [/help?cmd=/ext|/ext page] generates the SERVER_SOFTWARE environment | |
| 556 | + "[/help/init|fossil init]" command. | |
| 557 | + * The [/help/www/ext|/ext page] generates the SERVER_SOFTWARE environment | |
| 494 | 558 | variable for clients. |
| 495 | 559 | * Fix the REQUEST_URI [/doc/trunk/www/aboutcgi.wiki#cgivar|CGI variable] such |
| 496 | 560 | that it includes the query string. This is how most other systems understand |
| 497 | 561 | REQUEST_URI. |
| 498 | - * Added the --transport-command option to [/help?cmd=sync|fossil sync] | |
| 562 | + * Added the --transport-command option to [/help/sync|fossil sync] | |
| 499 | 563 | and similar. |
| 500 | 564 | |
| 501 | 565 | <h2 id='v2_17'>Changes for version 2.17 (2021-10-09)</h2> |
| 502 | 566 | |
| 503 | 567 | * Major improvements to the "diff" subsystem, including: <ul> |
| 504 | - <li> Added new [/help?cmd=diff|formatting options]: --by, -b, --webpage, --json, --tcl. | |
| 568 | + <li> Added new [/help/diff|formatting options]: --by, -b, --webpage, --json, --tcl. | |
| 505 | 569 | <li> Partial-line matching for unified diffs |
| 506 | 570 | <li> Better partial-line matching for side-by-side diffs |
| 507 | 571 | <li> Buttons on web-based diffs to show more context |
| 508 | 572 | <li> Performance improvements |
| 509 | 573 | </ul> |
| 510 | - * The --branchcolor option on [/help?cmd=commit|fossil commit] and | |
| 511 | - [/help?cmd=amend|fossil amend] can now take the value "auto" to | |
| 574 | + * The --branchcolor option on [/help/commit|fossil commit] and | |
| 575 | + [/help/amend|fossil amend] can now take the value "auto" to | |
| 512 | 576 | force Fossil to use its built-in automatic color choosing algorithm. |
| 513 | 577 | * Fossil now [./concepts.wiki#workflow|autosyncs] prior to running |
| 514 | - [/help?cmd=open|fossil open]. | |
| 515 | - * Add the [/help?cmd=ticket-default-report|ticket-default-report setting], | |
| 578 | + [/help/open|fossil open]. | |
| 579 | + * Add the [/help/ticket-default-report|ticket-default-report setting], | |
| 516 | 580 | which if set to the title of a ticket report causes that ticket report |
| 517 | 581 | to be displayed below the search box in the /ticket page. |
| 518 | - * The "nc" query parameter to the [/help?cmd=/timeline|/timeline] page | |
| 582 | + * The "nc" query parameter to the [/help/www/timeline|/timeline] page | |
| 519 | 583 | causes all graph coloring to be omitted. |
| 520 | - * Improvements and bug fixes to the new "[/help?cmd=ui|fossil ui REMOTE]" | |
| 584 | + * Improvements and bug fixes to the new "[/help/ui|fossil ui REMOTE]" | |
| 521 | 585 | feature so that it works better on a wider variety of platforms. |
| 522 | - * In [/help?cmd=/wikiedit|/wikiedit], show the list of attachments for | |
| 586 | + * In [/help/www/wikiedit|/wikiedit], show the list of attachments for | |
| 523 | 587 | the current page and list URLs suitable for pasting them into the page. |
| 524 | - * Add the --no-http-compression option to [/help?cmd=sync|fossil sync] | |
| 588 | + * Add the --no-http-compression option to [/help/sync|fossil sync] | |
| 525 | 589 | and similar. |
| 526 | - * Print total payload bytes on a [/help?cmd=sync|fossil sync] when using | |
| 590 | + * Print total payload bytes on a [/help/sync|fossil sync] when using | |
| 527 | 591 | the --verbose option. |
| 528 | 592 | * Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and |
| 529 | - </tt>unhide</tt> subcommands to [/help?cmd=branch|the branch command]. | |
| 530 | - * The "-p" option to [/help?cmd=branch|fossil branch list] shows only | |
| 593 | + </tt>unhide</tt> subcommands to [/help/branch|the branch command]. | |
| 594 | + * The "-p" option to [/help/branch|fossil branch list] shows only | |
| 531 | 595 | private branches. |
| 532 | 596 | * The [/md_rules|Markdown formatter] now interprets the content of |
| 533 | 597 | block HTML markup (such as <table>) in most cases. Only content |
| 534 | 598 | of <pre> and <script> is passed through verbatim. |
| 535 | - * The [/help?cmd=wiki|wiki list command] no longer lists "deleted" | |
| 599 | + * The [/help/wiki|wiki list command] no longer lists "deleted" | |
| 536 | 600 | pages by default. Use the new <tt>--all</tt> option to include deleted |
| 537 | 601 | pages in the output. |
| 538 | - * The [/help?cmd=all|fossil all git status] command only shows reports for | |
| 602 | + * The [/help/all|fossil all git status] command only shows reports for | |
| 539 | 603 | the subset of repositories that have a configured Git export. |
| 540 | - * The [/help?cmd=/chat|/chat] configuration was reimplemented and | |
| 604 | + * The [/help/www/chat|/chat] configuration was reimplemented and | |
| 541 | 605 | provides new options, including the ability for a repository |
| 542 | 606 | administrator to |
| 543 | 607 | [./chat.md#notifications|extend the selection of notification sounds] |
| 544 | 608 | using unversioned files. |
| 545 | 609 | * Chat now uses fossil's full complement of markdown features, |
| @@ -548,14 +612,14 @@ | ||
| 548 | 612 | markdown-processed when they are sent instead of when they |
| 549 | 613 | are saved. |
| 550 | 614 | * Added a chat message preview mode so messages can be previewed |
| 551 | 615 | before being sent. Similarly, added a per-message ability to view |
| 552 | 616 | the raw un-parsed message text. |
| 553 | - * The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit], | |
| 554 | - [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow] | |
| 617 | + * The hotkey to activate preview mode in [/help/www/wikiedit|/wikiedit], | |
| 618 | + [/help/www/fileedit|/fileedit], and [/help/www/pikchrshow|/pikchrshow] | |
| 555 | 619 | was changed from ctrl-enter to shift-enter in order to align with |
| 556 | - [/help?cmd=/chat|/chat]'s new preview feature and related future | |
| 620 | + [/help/www/chat|/chat]'s new preview feature and related future | |
| 557 | 621 | changes. |
| 558 | 622 | |
| 559 | 623 | <h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2> |
| 560 | 624 | * <b>Security:</b> Fix the client-side TLS so that it verifies that the |
| 561 | 625 | server hostname matches its certificate. |
| @@ -562,11 +626,11 @@ | ||
| 562 | 626 | * The default "ssh" command on Windows is changed to "ssh" instead of the |
| 563 | 627 | legacy "plink", as ssh is now generally available on Windows systems. |
| 564 | 628 | Installations that still need to use the legacy "plink" can make that |
| 565 | 629 | happen by running: '<tt>fossil set ssh-command "plink -ssh" --global</tt>'. |
| 566 | 630 | * Added the [./patchcmd.md|fossil patch] command. |
| 567 | - * The [/help?cmd=ui|fossil ui] command is enhanced in multiple ways:<ol> | |
| 631 | + * The [/help/ui|fossil ui] command is enhanced in multiple ways:<ol> | |
| 568 | 632 | <li> The REPOSITORY argument can be the name of a check-out directory. |
| 569 | 633 | <li> If the REPOSITORY argument is prefixed by "HOST:" or "USER@HOST:" |
| 570 | 634 | then the ui is run on the remote machine and tunnelled back to the local |
| 571 | 635 | machine using ssh. (The latest version of fossil must be installed on |
| 572 | 636 | both the local and the remote for this to work correctly.) |
| @@ -575,25 +639,25 @@ | ||
| 575 | 639 | * The [/brlist|/brlist web page] allows the user to |
| 576 | 640 | select multiple branches to be displayed together in a single |
| 577 | 641 | timeline. |
| 578 | 642 | * The [./forum.wiki|Forum] provides a hyperlink on the author of each |
| 579 | 643 | post that goes to a timeline of recent posts by that same author. |
| 580 | - * Added the "[/help?cmd=bisect|fossil bisect run]" command for improved | |
| 644 | + * Added the "[/help/bisect|fossil bisect run]" command for improved | |
| 581 | 645 | automation of bisects. |
| 582 | - * The [/help?cmd=merge|fossil merge] command now does a better job merging | |
| 646 | + * The [/help/merge|fossil merge] command now does a better job merging | |
| 583 | 647 | branches where files have been renamed between the current branch and the |
| 584 | 648 | branch being merged. |
| 585 | - * The [/help?cmd=open|fossil open] command allows the repository file | |
| 649 | + * The [/help/open|fossil open] command allows the repository file | |
| 586 | 650 | to be inside the working directory without requiring the --force flag. |
| 587 | - * The [/help?cmd=/wikiedit|/wikiedit] and [/help?cmd=/wikinew|/wikinew] | |
| 651 | + * The [/help/www/wikiedit|/wikiedit] and [/help/www/wikinew|/wikinew] | |
| 588 | 652 | pages now default to markdown format. |
| 589 | - * The [/help?cmd=/login|/login] page now links to a user's forum post | |
| 653 | + * The [/help/www/login|/login] page now links to a user's forum post | |
| 590 | 654 | timeline if the repository has forum posts. |
| 591 | 655 | * Tags may now be propagated for forum posts, wiki pages, and technotes. |
| 592 | - The [/help?cmd=tag|tag command] can now manipulate and list such tags. | |
| 656 | + The [/help/tag|tag command] can now manipulate and list such tags. | |
| 593 | 657 | * [./caps/login-groups.md|Login-Groups] are now shown on the repository |
| 594 | - list of the "[/help?cmd=all|fossil all ui]" command. | |
| 658 | + list of the "[/help/all|fossil all ui]" command. | |
| 595 | 659 | * Administrators can configure [./alerts.md|email alerts] to expire |
| 596 | 660 | a specific number of days (ex: 365) after the last user contact with |
| 597 | 661 | the Fossil server. This prevents alert emails being sent to |
| 598 | 662 | abandoned email accounts forever. |
| 599 | 663 | * SQL that defines [/tktsetup_tab|database objects for tickets] now |
| @@ -610,11 +674,11 @@ | ||
| 610 | 674 | * <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to |
| 611 | 675 | the patch is recommended.</b> |
| 612 | 676 | * The [./defcsp.md|default CSP] has been relaxed slightly to allow |
| 613 | 677 | images to be loaded from any URL. All other resources are still |
| 614 | 678 | locked down by default. |
| 615 | - * The built-in skins all use the "[/help?cmd=mainmenu|mainmenu]" | |
| 679 | + * The built-in skins all use the "[/help/mainmenu|mainmenu]" | |
| 616 | 680 | setting to determine the content of the main menu. |
| 617 | 681 | The ability to edit the |
| 618 | 682 | "mainmenu" setting is added on the /Admin/Configuration page. |
| 619 | 683 | * The hamburger menu is now available on most of the built-in skins. |
| 620 | 684 | * Any built-in skin named "X" can be used instead of the standard |
| @@ -624,40 +688,40 @@ | ||
| 624 | 688 | included. The [/skins] page may be used to select a skin. |
| 625 | 689 | * The [/cookies] page now gives the user an opportunity to delete |
| 626 | 690 | individual cookies. And the /cookies page is linked from the |
| 627 | 691 | /sitemap, so that it appears in hamburger menus. |
| 628 | 692 | * The [/sitemap] extensions are now specified by a single new |
| 629 | - "[/help?cmd=sitemap-extra|sitemap-extra setting]", | |
| 693 | + "[/help/sitemap-extra|sitemap-extra setting]", | |
| 630 | 694 | rather than a cluster of various |
| 631 | 695 | "sitemap-*" settings. The older settings are no longer used. |
| 632 | 696 | <b>This change might require minor server configuration |
| 633 | 697 | adjustments on servers that use /sitemap extensions.</b> |
| 634 | 698 | The /Admin/Configuration page provides the ability to edit |
| 635 | 699 | the new "sitemap-extra" setting. |
| 636 | 700 | * Added the "--ckout-alias NAME" option to |
| 637 | - [/help?cmd=ui|fossil ui], [/help?cmd=server|fossil server], and | |
| 638 | - [/help?cmd=http|fossil http]. This option causes Fossil to | |
| 701 | + [/help/ui|fossil ui], [/help/server|fossil server], and | |
| 702 | + [/help/http|fossil http]. This option causes Fossil to | |
| 639 | 703 | understand URIs of the form "/doc/NAME/..." as if they were |
| 640 | - "[/help?cmd=/doc|/doc/ckout/...]", to facilitate testing of | |
| 704 | + "[/help/www/doc|/doc/ckout/...]", to facilitate testing of | |
| 641 | 705 | [./embeddeddoc.wiki|embedded documentation] changes prior to |
| 642 | 706 | check-in. |
| 643 | 707 | * For diff web pages, if the diff type (unified versus side-by-side) |
| 644 | 708 | is not specified by a query parameter, and if the |
| 645 | - "[/help?cmd=preferred-diff-type|preferred-diff-type]" | |
| 709 | + "[/help/preferred-diff-type|preferred-diff-type]" | |
| 646 | 710 | setting is omitted or less than 1, then select the diff type based |
| 647 | 711 | on a guess of whether or not the request is coming from a mobile |
| 648 | 712 | device. Mobile gets unified and desktop gets side-by-side. |
| 649 | 713 | * The various pages which show diffs now have toggles to show/hide |
| 650 | 714 | individual diffs. |
| 651 | - * Add the "[/help?cmd=preferred-diff-type|preferred-diff-type]" | |
| 715 | + * Add the "[/help/preferred-diff-type|preferred-diff-type]" | |
| 652 | 716 | setting to allow an admin to force a default diff type. |
| 653 | 717 | * The "pikchr-background" setting is now available in |
| 654 | 718 | "detail.txt" skin files, for better control of Pikchr |
| 655 | 719 | colors in inverted color schemes. |
| 656 | 720 | * Add the <tt>--list</tt> option to the |
| 657 | - [/help?cmd=tarball|tarball], | |
| 658 | - [/help?cmd=zip|zip], and [/help?cmd=sqlar|sqlar] | |
| 721 | + [/help/tarball|tarball], | |
| 722 | + [/help/zip|zip], and [/help/sqlar|sqlar] | |
| 659 | 723 | commands. |
| 660 | 724 | * The javascript used to implement the hamburger menu on the |
| 661 | 725 | default built-in skin has been made generic so that it is usable |
| 662 | 726 | by a variety of skins, and promoted to an ordinary built-in |
| 663 | 727 | javascript file. |
| @@ -665,24 +729,24 @@ | ||
| 665 | 729 | "[/doc/trunk/www/th1.md#bireqjs|builtin_request_js]", |
| 666 | 730 | "[/doc/trunk/www/th1.md#capexpr|capexpr]", |
| 667 | 731 | "foreach", "lappend", and "string match" |
| 668 | 732 | * The [/help/leaves|leaves command] now shows the branch point |
| 669 | 733 | of each leaf. |
| 670 | - * The [/help?cmd=add|fossil add] command refuses to add files whose | |
| 734 | + * The [/help/add|fossil add] command refuses to add files whose | |
| 671 | 735 | names are reserved by Windows (ex: "aux") unless the --allow-reserved |
| 672 | 736 | option is included. This helps prevent Unix users from accidentally |
| 673 | 737 | creating check-ins that are unreadable by Windows users. |
| 674 | - * Add the "re=" query parameter to the [/help?cmd=/dir|/dir] webpage, | |
| 675 | - for symmetry with the [/help?cmd=/tree|/tree] page. | |
| 738 | + * Add the "re=" query parameter to the [/help/www/dir|/dir] webpage, | |
| 739 | + for symmetry with the [/help/www/tree|/tree] page. | |
| 676 | 740 | * Update the built-in SQLite to version 3.35.0. |
| 677 | 741 | * The ./configure script now has the --print-minimum-sqlite-version option |
| 678 | 742 | that prints the minimum SQLite version required by the current version |
| 679 | 743 | of Fossil. This might be used by integrators who insist on building |
| 680 | 744 | Fossil to link against the system SQLite library rather than the |
| 681 | 745 | built-in copy of SQLite, to verify that their system SQLite library |
| 682 | 746 | is recent enough. |
| 683 | - * Webpage that shows [/help?cmd=/whistory|history of a wiki page] | |
| 747 | + * Webpage that shows [/help/www/whistory|history of a wiki page] | |
| 684 | 748 | gained client-side UI to help with comparison between two arbitrary |
| 685 | 749 | versions of a wiki (by the means of anchoring a "baseline" version) |
| 686 | 750 | and the ability to squeeze several sequential edits made by the same |
| 687 | 751 | user into a single "recycled" row (the latest edit in that sequence). |
| 688 | 752 | |
| @@ -702,40 +766,40 @@ | ||
| 702 | 766 | version 2.14 and then later downgrade or otherwise use an earlier |
| 703 | 767 | version of Fossil, the email notification mechanism may fail |
| 704 | 768 | to send out notifications for some events, due to the missing |
| 705 | 769 | trigger. If you want to |
| 706 | 770 | permanently downgrade an installation, then you should run |
| 707 | - "[/help?cmd=rebuild|fossil rebuild]" after the downgrade | |
| 771 | + "[/help/rebuild|fossil rebuild]" after the downgrade | |
| 708 | 772 | to get email notifications working again. If you are not using |
| 709 | 773 | email notification, then the schema change will not affect you in |
| 710 | 774 | any way. |
| 711 | 775 | * <b>Schema Update Notice #2:</b> |
| 712 | 776 | This release changes how the descriptions of wiki edits are stored |
| 713 | 777 | in the EVENT table, for improved display on timelines. You must |
| 714 | - run "[/help?cmd=rebuild|fossil rebuild]" to take advantage of | |
| 778 | + run "[/help/rebuild|fossil rebuild]" to take advantage of | |
| 715 | 779 | this enhancement. Everything will still work without |
| 716 | 780 | "fossil rebuild", except you will get goofy descriptions of |
| 717 | 781 | wiki updates in the timeline. |
| 718 | 782 | * Add support for [./chat.md|Fossil chat]. |
| 719 | - * The "[/help?cmd=clone|fossil clone]" command is enhanced so that | |
| 783 | + * The "[/help/clone|fossil clone]" command is enhanced so that | |
| 720 | 784 | if the repository filename is omitted, an appropriate name is derived |
| 721 | 785 | from the remote URL and the newly cloned repo is opened. This makes |
| 722 | 786 | the clone command work more like Git, thus making it easier for |
| 723 | 787 | people transitioning from Git. |
| 724 | - * Added the --mainbranch option to the [/help?cmd=git|fossil git export] | |
| 788 | + * Added the --mainbranch option to the [/help/git|fossil git export] | |
| 725 | 789 | command. |
| 726 | 790 | * Added the --format option to the |
| 727 | - "[/help?cmd=timeline|fossil timeline]" command. | |
| 791 | + "[/help/timeline|fossil timeline]" command. | |
| 728 | 792 | * Enhance the --numstat option on the |
| 729 | - "[/help?cmd=diff|fossil diff]" command so that it shows a total | |
| 793 | + "[/help/diff|fossil diff]" command so that it shows a total | |
| 730 | 794 | number of lines added and deleted and total number of files |
| 731 | 795 | modified. |
| 732 | - * Add the "contact" sub-command to [/help?cmd=user|fossil user]. | |
| 733 | - * Added commands "[/help?cmd=all|fossil all git export]" and | |
| 734 | - "[/help?cmd=all|fossil all git status]". | |
| 796 | + * Add the "contact" sub-command to [/help/user|fossil user]. | |
| 797 | + * Added commands "[/help/all|fossil all git export]" and | |
| 798 | + "[/help/all|fossil all git status]". | |
| 735 | 799 | * Added the "df=CHECKIN" query parameter to the |
| 736 | - [/help?cmd=/timeline|/timeline page]. | |
| 800 | + [/help/www/timeline|/timeline page]. | |
| 737 | 801 | * Improvements to the "[/sitemap]" page. Add subpages |
| 738 | 802 | [/sitemap-timeline] and [/sitemap-test]. |
| 739 | 803 | * Better text position in cylinder objects of Pikchr diagrams. |
| 740 | 804 | * New "details.txt" settings available to custom skins to better control |
| 741 | 805 | the rendering of Pikchr diagrams: |
| @@ -757,24 +821,24 @@ | ||
| 757 | 821 | * Added support for [./interwiki.md|interwiki links]. |
| 758 | 822 | * Enable <del> and <ins> markup in wiki. |
| 759 | 823 | * Improvements to the Forum threading display. |
| 760 | 824 | * Added support for embedding [./pikchr.md|pikchr] |
| 761 | 825 | markup in markdown and fossil-wiki content. |
| 762 | - * The new "[/help?cmd=pikchr|pikchr]" command can render | |
| 826 | + * The new "[/help/pikchr|pikchr]" command can render | |
| 763 | 827 | pikchr scripts, optionally pre-processed with |
| 764 | 828 | [/doc/trunk/www/th1.md|TH1] blocks and variables exactly like |
| 765 | 829 | site skins are. |
| 766 | - * The new [/help?cmd=/pikchrshow|pikchrshow] page provides an | |
| 830 | + * The new [/help/www/pikchrshow|pikchrshow] page provides an | |
| 767 | 831 | editor and previewer for pikchr markup. |
| 768 | - * In [/help?cmd=/wikiedit|/wikiedit] and | |
| 769 | - [/help?cmd=/fileedit|/fileedit], Ctrl-Enter can now be used | |
| 832 | + * In [/help/www/wikiedit|/wikiedit] and | |
| 833 | + [/help/www/fileedit|/fileedit], Ctrl-Enter can now be used | |
| 770 | 834 | initiate a preview and to toggle between the editor and preview |
| 771 | 835 | tabs. |
| 772 | 836 | * The <tt>/artifact</tt> and <tt>/file</tt> views, when in |
| 773 | 837 | line-number mode, now support interactive selection of a range |
| 774 | 838 | of lines to hyperlink to. |
| 775 | - * Enhance the [/help?cmd=/finfo|/finfo] webpage so that when query | |
| 839 | + * Enhance the [/help/www/finfo|/finfo] webpage so that when query | |
| 776 | 840 | parameters identify both a filename and a checkin, the resulting |
| 777 | 841 | graph tracks the identified file across renames. |
| 778 | 842 | * The built-in SQLite is updated to an alpha of version 3.34.0, and |
| 779 | 843 | the minimum SQLite version is increased to 3.34.0 because the |
| 780 | 844 | /finfo change in the previous bullet depends on enhancements to |
| @@ -783,18 +847,18 @@ | ||
| 783 | 847 | * Countless other minor refinements and documentation improvements. |
| 784 | 848 | |
| 785 | 849 | <h2 id='v2_12'>Changes for Version 2.12.1 (2020-08-20)</h2> |
| 786 | 850 | |
| 787 | 851 | * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz. |
| 788 | - * Security fix in the "[/help?cmd=git|fossil git export]" command. | |
| 852 | + * Security fix in the "[/help/git|fossil git export]" command. | |
| 789 | 853 | The same fix is also backported to version 2.10.1 and 2.11.1. |
| 790 | 854 | New "safety-net" features were added to prevent similar problems |
| 791 | 855 | in the future. |
| 792 | 856 | * Enhancements to the graph display for cases when there are |
| 793 | 857 | many cherry-pick merges into a single check-in. |
| 794 | 858 | [/timeline?f=2d75e87b760c0a9|Example] |
| 795 | - * Enhance the [/help?cmd=open|fossil open] command with the new | |
| 859 | + * Enhance the [/help/open|fossil open] command with the new | |
| 796 | 860 | --workdir option and the ability to accept a URL as the repository |
| 797 | 861 | name, causing the remote repository to be cloned automatically. |
| 798 | 862 | Do not allow "fossil open" to open in a non-empty working directory |
| 799 | 863 | unless the --keep option or the new --force option is used. |
| 800 | 864 | * Enhance the markdown formatter to more closely follow the |
| @@ -803,65 +867,65 @@ | ||
| 803 | 867 | Underscores in the middle of identifiers (ex: fossil_printf()) |
| 804 | 868 | no longer need to be escaped. |
| 805 | 869 | * The markdown-to-html translator can prevent unsafe HTML |
| 806 | 870 | (for example: <script>) on user-contributed pages like forum and |
| 807 | 871 | tickets and wiki. The admin can adjust this behavior using |
| 808 | - the [/help?cmd=safe-html|safe-html setting] on the Admin/Wiki page. | |
| 872 | + the [/help/safe-html|safe-html setting] on the Admin/Wiki page. | |
| 809 | 873 | The default is to disallow unsafe HTML everywhere. |
| 810 | 874 | [https://fossil-scm.org/forum/forumpost/3714e6568f|Example]. |
| 811 | 875 | * Added the "collapse" and "expand" capability for long forum posts. |
| 812 | 876 | [https://fossil-scm.org/forum/forumpost/9297029862|Example] |
| 813 | - * The "[/help?cmd=remote-url|fossil remote]" command now has options for | |
| 877 | + * The "[/help/remote-url|fossil remote]" command now has options for | |
| 814 | 878 | specifying multiple persistent remotes with symbolic names. Currently |
| 815 | 879 | only one remote can be used at a time, but that might change in the |
| 816 | 880 | future. |
| 817 | 881 | * Add the "Remember me?" checkbox on the login page. Use a session |
| 818 | 882 | cookie for the login if it is not checked. |
| 819 | - * Added the experimental "[/help?cmd=hook|fossil hook]" command for | |
| 883 | + * Added the experimental "[/help/hook|fossil hook]" command for | |
| 820 | 884 | managing "hook scripts" that run before checkin or after a push. |
| 821 | - * Enhance the [/help?cmd=revert|fossil revert] command so that it | |
| 885 | + * Enhance the [/help/revert|fossil revert] command so that it | |
| 822 | 886 | is able to revert all files beneath a directory. |
| 823 | - * Add the [/help?cmd=bisect|fossil bisect skip] command. | |
| 824 | - * Add the [/help?cmd=backup|fossil backup] command. | |
| 825 | - * Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked | |
| 887 | + * Add the [/help/bisect|fossil bisect skip] command. | |
| 888 | + * Add the [/help/backup|fossil backup] command. | |
| 889 | + * Enhance [/help/bisect|fossil bisect ui] so that it shows all unchecked | |
| 826 | 890 | check-ins in between the innermost "good" and "bad" check-ins. |
| 827 | - * Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]", | |
| 828 | - "[/help?cmd=rm|fossil rm]", and | |
| 829 | - "[/help?cmd=addremove|fossil addremove]" commands. | |
| 891 | + * Added the <tt>--reset</tt> flag to the "[/help/add|fossil add]", | |
| 892 | + "[/help/rm|fossil rm]", and | |
| 893 | + "[/help/addremove|fossil addremove]" commands. | |
| 830 | 894 | * Added the "<tt>--min</tt> <i>N</i>" and "<tt>--logfile</tt> <i>FILENAME</i>" |
| 831 | - flags to the [/help?cmd=backoffice|backoffice] command, as well as other | |
| 895 | + flags to the [/help/backoffice|backoffice] command, as well as other | |
| 832 | 896 | enhancements to make the backoffice command a viable replacement for |
| 833 | 897 | automatic backoffice. Other incremental backoffice improvements. |
| 834 | - * Added the [/help?cmd=/fileedit|/fileedit page], which allows | |
| 898 | + * Added the [/help/www/fileedit|/fileedit page], which allows | |
| 835 | 899 | editing of text files online. Requires explicit activation by |
| 836 | 900 | a setup user. |
| 837 | 901 | * Translate built-in help text into HTML for display on web pages. |
| 838 | - [/help?cmd=help|Example]. | |
| 839 | - * On the [/help?cmd=/timeline|/timeline] webpage, the combination | |
| 902 | + [/help/help|Example]. | |
| 903 | + * On the [/help/www/timeline|/timeline] webpage, the combination | |
| 840 | 904 | of query parameters "p=CHECKIN" and "bt=ANCESTOR" draws all |
| 841 | 905 | ancestors of CHECKIN going back to ANCESTOR. For example, |
| 842 | 906 | [/timeline?p=202006271506&bt=version-2.11] shows all ancestors |
| 843 | 907 | of the checkin that occurred on 2020-06-27 15:06 going back to |
| 844 | 908 | the 2.11 release. |
| 845 | 909 | * Update the built-in SQLite so that the |
| 846 | - "[/help?cmd=sql|fossil sql]" command supports new output | |
| 910 | + "[/help/sql|fossil sql]" command supports new output | |
| 847 | 911 | modes ".mode box" and ".mode json". |
| 848 | 912 | * Add the "<tt>obscure()</tt>" SQL function to the |
| 849 | - "[/help?cmd=sql|fossil sql]" command. | |
| 913 | + "[/help/sql|fossil sql]" command. | |
| 850 | 914 | * Added virtual tables "<tt>helptext</tt>" and "<tt>builtin</tt>" to |
| 851 | - the "[/help?cmd=sql|fossil sql]" command, providing access to the | |
| 915 | + the "[/help/sql|fossil sql]" command, providing access to the | |
| 852 | 916 | dispatch table including all help text, and the builtin data files, |
| 853 | 917 | respectively. |
| 854 | 918 | * [./delta_format.wiki|Delta compression] is now applied to forum edits. |
| 855 | - * The [/help?cmd=/wikiedit|wiki editor] has been modernized and is | |
| 919 | + * The [/help/www/wikiedit|wiki editor] has been modernized and is | |
| 856 | 920 | now Ajax-based. The WYSIWYG editing option for Fossil-format wiki |
| 857 | 921 | pages was removed. (Please let us know, via the site's Forum menu, |
| 858 | 922 | if that removal unduly impacts you.) This also changes the semantics |
| 859 | 923 | of the wiki "Sandbox": that pseudo-page may be freely edited but |
| 860 | - no longer saved via the UI (the [/help?cmd=wiki|wiki CLI command] | |
| 924 | + no longer saved via the UI (the [/help/wiki|wiki CLI command] | |
| 861 | 925 | can, though). |
| 862 | - * The [/help?cmd=allow-symlinks|allow-symlinks setting] no longer | |
| 926 | + * The [/help/allow-symlinks|allow-symlinks setting] no longer | |
| 863 | 927 | syncs. It must be activated individually on any clones which require |
| 864 | 928 | symlinks. |
| 865 | 929 | * Countless documentation enhancements. |
| 866 | 930 | |
| 867 | 931 | <h2 id='v2_11'>Changes for Version 2.11 (2020-05-25)</h2> |
| @@ -873,46 +937,46 @@ | ||
| 873 | 937 | can now omit punctation. So, for example, "202004181942" and |
| 874 | 938 | "2020-04-18 19:42" mean the same thing. |
| 875 | 939 | * Enhance backlink processing so that it works with Markdown-formatted |
| 876 | 940 | tickets and so that it works for wiki pages. |
| 877 | 941 | Ticket [a3572c6a5b47cd5a]. |
| 878 | - <ul><li> "[/help?cmd=rebuild|fossil rebuild]" is needed to | |
| 942 | + <ul><li> "[/help/rebuild|fossil rebuild]" is needed to | |
| 879 | 943 | take full advantage of this fix. Fossil will continue |
| 880 | 944 | to work without the rebuild, but the new backlinks will be missing.</ul> |
| 881 | 945 | * The algorithm for finding the |
| 882 | 946 | [./tech_overview.wiki#configloc|location of the configuration database] |
| 883 | 947 | is enhanced to be XDG-compliant. |
| 884 | 948 | * Add a hide/show feature to |
| 885 | 949 | [./wikitheory.wiki#assocwiki|associated wiki] display on |
| 886 | 950 | check-in and branch information pages. |
| 887 | - * Enhance the "[/help?cmd=info|fossil info]" command so that it | |
| 951 | + * Enhance the "[/help/info|fossil info]" command so that it | |
| 888 | 952 | works with no arguments even if not within an open check-out. |
| 889 | 953 | * Many improvements to the forum and especially email notification |
| 890 | 954 | of forum posts, in response to community feedback after switching |
| 891 | 955 | SQLite support from a mailing list over to the forum. |
| 892 | 956 | * Minimum length of a self-registered user ID increased from 3 to 6 |
| 893 | 957 | characters. |
| 894 | 958 | * When the "vfx" query parameter is used on the |
| 895 | - "[/help?cmd=/timeline|/timeline]" page, it causes the complete | |
| 959 | + "[/help/www/timeline|/timeline]" page, it causes the complete | |
| 896 | 960 | text of forum posts to be displayed. |
| 897 | - * Rework the "[/help?cmd=grep|fossil grep]" command to be more useful. | |
| 898 | - * Expose the [/help?cmd=redirect-to-https|redirect-to-https] | |
| 899 | - setting to the [/help?cmd=settings|settings] command. | |
| 961 | + * Rework the "[/help/grep|fossil grep]" command to be more useful. | |
| 962 | + * Expose the [/help/redirect-to-https|redirect-to-https] | |
| 963 | + setting to the [/help/settings|settings] command. | |
| 900 | 964 | * Improve support for CGI on IIS web servers. |
| 901 | 965 | * The [./serverext.wiki|/ext page] can now render index files, |
| 902 | 966 | in the same way as the embedded docs. |
| 903 | 967 | * Most commands now support the Unix-conventional "<tt>--</tt>" |
| 904 | 968 | flag to treat all following arguments as filenames |
| 905 | 969 | instead of flags. |
| 906 | - * Added the [/help?cmd=mimetypes|mimetypes config setting] | |
| 970 | + * Added the [/help/mimetypes|mimetypes config setting] | |
| 907 | 971 | (versionable) to enable mimetype overrides and custom definitions. |
| 908 | 972 | * Add an option on the /Admin/Timeline setup page to set a default |
| 909 | 973 | timeline style other than "Modern". |
| 910 | 974 | * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs |
| 911 | 975 | of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated |
| 912 | 976 | into the check-in hash for the document currently being viewed. |
| 913 | - * Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all | |
| 977 | + * Added the [/help/www/phantoms|/phantoms] webpage that shows all | |
| 914 | 978 | phantom artifacts. |
| 915 | 979 | * Enhancements to phantom processing to try to reduce |
| 916 | 980 | bandwidth-using chatter about phantoms on the sync protocol. |
| 917 | 981 | * Security: Fossil now assumes that the schema of every |
| 918 | 982 | database it opens has been tampered with by an adversary and takes |
| @@ -920,23 +984,23 @@ | ||
| 920 | 984 | * Security: Fossil now puts the Content-Security-Policy in the |
| 921 | 985 | HTTP reply header, in addition to also leaving it in the |
| 922 | 986 | HTML <head> section, so that it is always available, even |
| 923 | 987 | if a custom skin overrides the HTML <head> and omits |
| 924 | 988 | the CSP in the process. |
| 925 | - * Output of the [/help?cmd=diff|fossil diff -y] command automatically | |
| 989 | + * Output of the [/help/diff|fossil diff -y] command automatically | |
| 926 | 990 | adjusts according to the terminal width. |
| 927 | 991 | * The Content-Security-Policy is now set using the |
| 928 | - [/help?cmd=default-csp|default-csp setting]. | |
| 929 | - * Merge conflicts caused via the [/help?cmd=merge|merge] and | |
| 930 | - [/help?cmd=update|update] commands no longer leave temporary | |
| 992 | + [/help/default-csp|default-csp setting]. | |
| 993 | + * Merge conflicts caused via the [/help/merge|merge] and | |
| 994 | + [/help/update|update] commands no longer leave temporary | |
| 931 | 995 | files behind unless the new <tt>--keep-merge-files</tt> flag |
| 932 | 996 | is used. |
| 933 | - * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible | |
| 997 | + * The [/help/www/artifact_stats|/artifact_stats page] is now accessible | |
| 934 | 998 | to all users if the new "artifact_stats_enable" setting is turned |
| 935 | 999 | on. There is a new checkbox under the /Admin/Access menu to turn |
| 936 | 1000 | that capability on and off. |
| 937 | - * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing | |
| 1001 | + * Add the [/help/tls-config|fossil tls-config] command for viewing | |
| 938 | 1002 | the TLS configuration and the list of SSL Cert exceptions. |
| 939 | 1003 | * Captchas all include a button to read the captcha using an audio |
| 940 | 1004 | file, so that they can be completed by the visually impaired. |
| 941 | 1005 | * Stop using the IP address as part of the login cookie. |
| 942 | 1006 | * Bug fix: fix the SSL cert validation logic so that if an exception |
| @@ -957,27 +1021,27 @@ | ||
| 957 | 1021 | <h2 id='v2_10'>Changes for Version 2.10 (2019-10-04)</h2> |
| 958 | 1022 | |
| 959 | 1023 | * (2.10.2): backport security fixes from 2.12.1 |
| 960 | 1024 | * (2.10.1): backport security fix for the "fossil git export" command. |
| 961 | 1025 | * Added support for [./serverext.wiki|CGI-based Server Extensions]. |
| 962 | - * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to | |
| 1026 | + * Added the [/help/repolist-skin|repolist-skin] setting used to | |
| 963 | 1027 | add style to repository list pages. |
| 964 | 1028 | * Enhance the hierarchical display of Forum threads to do less |
| 965 | 1029 | indentation and to provide links back to the previous message |
| 966 | 1030 | in the thread. Provide sequential numbers for all messages in |
| 967 | 1031 | a forum thread. |
| 968 | 1032 | * Add support for fenced code blocks and improved hyperlink |
| 969 | 1033 | processing to the [/md_rules|markdown formatter]. |
| 970 | 1034 | * Add support for hyperlinks to wiki pages in the |
| 971 | 1035 | [/md_rules|markdown formatter]. |
| 972 | - * Enhance the [/help?cmd=/stat|/stat] page so that it gives the | |
| 1036 | + * Enhance the [/help/www/stat|/stat] page so that it gives the | |
| 973 | 1037 | option to show a breakdown of forum posts. |
| 974 | 1038 | * The special check-in name "merge-in:BRANCH" means the source of |
| 975 | 1039 | the most recent merge-in from the parent branch of BRANCH. |
| 976 | 1040 | * Add hyperlinks to branch-diffs on the /info page and from |
| 977 | 1041 | timelines of a branch. |
| 978 | - * Add graphical context on the [/help?cmd=/vdiff|/vdiff] page. | |
| 1042 | + * Add graphical context on the [/help/www/vdiff|/vdiff] page. | |
| 979 | 1043 | * Uppercase query parameters, POST parameters, and cookie names are |
| 980 | 1044 | converted to all lowercase and entered into the parameter set, |
| 981 | 1045 | instead of being discarded. |
| 982 | 1046 | * Change the default [./hashpolicy.wiki|hash policy] to SHA3. |
| 983 | 1047 | * Timeout [./server/any/cgi.md|CGI requests] after 300 seconds, or |
| @@ -990,14 +1054,14 @@ | ||
| 990 | 1054 | * Performance optimizations. |
| 991 | 1055 | * Many documentation improvements. |
| 992 | 1056 | |
| 993 | 1057 | <h2 id='v2_9'>Changes for Version 2.9 (2019-07-13)</h2> |
| 994 | 1058 | |
| 995 | - * Added the [/help?cmd=git|fossil git export] command and instructions | |
| 1059 | + * Added the [/help/git|fossil git export] command and instructions | |
| 996 | 1060 | for [./mirrortogithub.md|creating a GitHub mirror of a Fossil project]. |
| 997 | 1061 | * Improved handling of relative hyperlinks on the |
| 998 | - [/help?cmd=/artifact|/artifact] pages for wiki. For example, | |
| 1062 | + [/help/www/artifact|/artifact] pages for wiki. For example, | |
| 999 | 1063 | hyperlinks and the lizard <img> now work correctly |
| 1000 | 1064 | for both [/artifact/2ff24ab0887cf522] and |
| 1001 | 1065 | [/doc/0d7ac90d575004c2415/www/index.wiki]. |
| 1002 | 1066 | * Enhancements to the timeline graph layout, to show more information |
| 1003 | 1067 | with less clutter. |
| @@ -1006,28 +1070,28 @@ | ||
| 1006 | 1070 | configuration. |
| 1007 | 1071 | * Copy buttons added to various check-in hash and branch name links. |
| 1008 | 1072 | * Double-clicking on a /timeline graph node now jumps to the /info page |
| 1009 | 1073 | for the check-in. So, repurpose the timestamp hyperlink to show all |
| 1010 | 1074 | activity around that check-in in time. |
| 1011 | - * Added the [/help?cmd=touch|fossil touch] command, and the --setmtime | |
| 1012 | - option on the [/help?cmd=open|fossil open] and | |
| 1013 | - [/help?cmd=update|fossil update] commands. | |
| 1075 | + * Added the [/help/touch|fossil touch] command, and the --setmtime | |
| 1076 | + option on the [/help/open|fossil open] and | |
| 1077 | + [/help/update|fossil update] commands. | |
| 1014 | 1078 | * Many documentation enhancements. |
| 1015 | - * For the "[/help?cmd=update|fossil update]" and | |
| 1016 | - "[/help?cmd=checkout|fossil checkout]" commands, if a | |
| 1079 | + * For the "[/help/update|fossil update]" and | |
| 1080 | + "[/help/checkout|fossil checkout]" commands, if a | |
| 1017 | 1081 | managed file is removed because it is no longer part of the target |
| 1018 | 1082 | check-in and the directory containing the file is empty after the |
| 1019 | 1083 | file is removed and the directory is not the current working |
| 1020 | - directory and is not on the [/help?cmd=empty-dirs|empty-dirs] | |
| 1084 | + directory and is not on the [/help/empty-dirs|empty-dirs] | |
| 1021 | 1085 | list, then also remove the directory. |
| 1022 | 1086 | * Update internal Unicode character tables, used in regular expression |
| 1023 | 1087 | handling, from version 11.0 to 12.1. |
| 1024 | - * In "[/help?cmd=regexp|fossil regexp]", "[/help?cmd=grep|fossil grep]" | |
| 1088 | + * In "[/help/regexp|fossil regexp]", "[/help/grep|fossil grep]" | |
| 1025 | 1089 | and the TH1 "regexp" command, the -nocase option now removes multiple |
| 1026 | 1090 | diacritics from the same character (derived from SQLite's |
| 1027 | 1091 | remove_diacritics=2) |
| 1028 | - * Added the [/help?cmd=/secureraw|/secureraw] page that requires the | |
| 1092 | + * Added the [/help/www/secureraw|/secureraw] page that requires the | |
| 1029 | 1093 | complete SHA1 or SHA3 hash, not just a prefix, before it will deliver |
| 1030 | 1094 | content. |
| 1031 | 1095 | * Accept purely numeric ISO8601 date/time strings as long as they |
| 1032 | 1096 | do not conflict with a hash. Example: "20190510134217" instead of |
| 1033 | 1097 | "2019-05-10 13:42:17". This helps keep URLs shorter and less |
| @@ -1040,19 +1104,19 @@ | ||
| 1040 | 1104 | extra path element is not needed. |
| 1041 | 1105 | * If an automatic sync gets a permanent redirect request, then update |
| 1042 | 1106 | the saved remote URL to the new address. |
| 1043 | 1107 | * Temporary filenames (for example used for external "diff" commands) |
| 1044 | 1108 | try to preserve the suffix of the original file. |
| 1045 | - * Added the [/help?cmd=/thisdayinhistory|/thisdayinhistory] web page. | |
| 1046 | - * Enhanced parsing of [/help?cmd=/timeline|/timeline] query parameters | |
| 1109 | + * Added the [/help/www/thisdayinhistory|/thisdayinhistory] web page. | |
| 1110 | + * Enhanced parsing of [/help/www/timeline|/timeline] query parameters | |
| 1047 | 1111 | "ymd=", "ym=", and "yw=". All arguments are option (in which case they |
| 1048 | 1112 | default to the current time) and all accept ISO8601 date/times without |
| 1049 | 1113 | punctuation. |
| 1050 | 1114 | * Automatically disapprove pending moderation requests for a user when |
| 1051 | 1115 | that user is deleted. This helps in dealing with spam-bots. |
| 1052 | 1116 | * Improvements to the "Capability Summary" section in the |
| 1053 | - [/help?cmd=/secaudit0|Security Audit] web-page. | |
| 1117 | + [/help/www/secaudit0|Security Audit] web-page. | |
| 1054 | 1118 | * Use new "ci-lock" and "ci-lock-failed" pragmas in the |
| 1055 | 1119 | [./sync.wiki|sync protocol] to try to prevent accident forks |
| 1056 | 1120 | caused by concurrent commits when operating in auto-sync mode. |
| 1057 | 1121 | * Fix a bug ([https://fossil-scm.org/forum/forumpost/c51b9a1169|details]) |
| 1058 | 1122 | that can cause repository databases to be overwritten with debugging |
| @@ -1153,11 +1217,11 @@ | ||
| 1153 | 1217 | included in the header or footer. |
| 1154 | 1218 | * Add the [./backoffice.md|backoffice]. |
| 1155 | 1219 | * Update internal Unicode character tables, used in regular expression |
| 1156 | 1220 | handling, from version 10.0 to 11.0. |
| 1157 | 1221 | * Improvements to the "Security Audit" administration page |
| 1158 | - * Add the [/help?cmd=branch|fossil branch current] command. | |
| 1222 | + * Add the [/help/branch|fossil branch current] command. | |
| 1159 | 1223 | * Add the [./grep.md|grep] command. |
| 1160 | 1224 | * Update the built-in SQLite to version 3.25.1. |
| 1161 | 1225 | * Some code and interfaces are in place to support sending and |
| 1162 | 1226 | receiving email directly via SMTP, but this feature is not yet |
| 1163 | 1227 | complete or ready for production use. |
| @@ -1176,20 +1240,20 @@ | ||
| 1176 | 1240 | same as "Verbose" in the previous release. The "Verbose" mode is |
| 1177 | 1241 | now like "Compact" except the extra check-in details are shown by |
| 1178 | 1242 | default. |
| 1179 | 1243 | * Add support for ETags:, Last-Modified:, and If-Modified-Since: |
| 1180 | 1244 | cache control mechanisms. |
| 1181 | - * Enhance the [/help?cmd=/tarball|/tarball], | |
| 1182 | - [/help?cmd=/zip|/zip], and | |
| 1183 | - [/help?cmd=/sqlar|/sqlar] pages so that the checkin | |
| 1245 | + * Enhance the [/help/www/tarball|/tarball], | |
| 1246 | + [/help/www/zip|/zip], and | |
| 1247 | + [/help/www/sqlar|/sqlar] pages so that the checkin | |
| 1184 | 1248 | name to be downloaded can be expressed as part of the URI, |
| 1185 | 1249 | and without the need for query parameters. |
| 1186 | - * On the [/help?cmd=/timeline|/timeline] webpage, add the days=N | |
| 1250 | + * On the [/help/www/timeline|/timeline] webpage, add the days=N | |
| 1187 | 1251 | query parameter and enhance the ymd=DATE and yw=DATE query parameters |
| 1188 | 1252 | to accept 'now' as an argument to show the latest day or week. |
| 1189 | 1253 | * In the web page that comes up in response to the |
| 1190 | - [/help?cmd=all|fossil all ui] command, show the last modification | |
| 1254 | + [/help/all|fossil all ui] command, show the last modification | |
| 1191 | 1255 | time for each repository, and allow click-to-sort on the modification |
| 1192 | 1256 | time column. |
| 1193 | 1257 | * In the tarball cache replacement algorithm, give extra weight to |
| 1194 | 1258 | tarballs that have been accessed more than once. |
| 1195 | 1259 | * Additional defenses against web-based attacks. There have not been |
| @@ -1236,35 +1300,35 @@ | ||
| 1236 | 1300 | to define their own URLs on the web interface that are rewritten to |
| 1237 | 1301 | built-in URLs with specific parameters. Create and configure URL Aliases |
| 1238 | 1302 | using the /Setup/URL_Aliases menu option in the web interface. |
| 1239 | 1303 | * Add tech-note search capability. |
| 1240 | 1304 | * Add the -r|--revision and -o|--origin options to the |
| 1241 | - [/help?cmd=annotate|annotate] command. | |
| 1242 | - * Add the origin= query parameter to the [/help?cmd=/annotate|/annotate] | |
| 1305 | + [/help/annotate|annotate] command. | |
| 1306 | + * Add the origin= query parameter to the [/help/www/annotate|/annotate] | |
| 1243 | 1307 | webpage. |
| 1244 | - * The [/help?cmd=annotate|fossil annotate] command and the | |
| 1245 | - [/help?cmd=/annotate|/annotate] web page go backwards in time as far | |
| 1308 | + * The [/help/annotate|fossil annotate] command and the | |
| 1309 | + [/help/www/annotate|/annotate] web page go backwards in time as far | |
| 1246 | 1310 | as can be computed in 30 milliseconds by default, rather than stopping |
| 1247 | 1311 | after 20 steps. The new limit= query parameter or the --limit command-line |
| 1248 | 1312 | option can be used to alter this timeout. |
| 1249 | 1313 | * Provide separate [/help#settings|on-line help screens for each setting]. |
| 1250 | 1314 | * Back out support for the --no-dir-symlinks option |
| 1251 | 1315 | * Remove support from the legacy configuration sync protocol. The only |
| 1252 | 1316 | way now to do a configuration push or pull is to use the new protocol that |
| 1253 | 1317 | was added in 2011. |
| 1254 | - * Add the from= and to= query parameters to [/help?cmd=/fdiff|/fdiff] | |
| 1318 | + * Add the from= and to= query parameters to [/help/www/fdiff|/fdiff] | |
| 1255 | 1319 | in order to get a diff of two files in the same check-in. |
| 1256 | 1320 | * Fix the "ssh://" protocol to prevent an attack whereby the attacker convinces |
| 1257 | 1321 | a victim to run a "clone" with a dodgy URL and thereby gains access to their |
| 1258 | 1322 | system. |
| 1259 | 1323 | * Provide a checkbox that will temporarily disable all ad-units. |
| 1260 | - * Improvements to the [/help?cmd=/stat|/stat] page | |
| 1261 | - * Various new hyperlinks to the [/help?cmd=/bloblist|/bloblist] | |
| 1262 | - and [/help?cmd=/bigbloblist|/bigbloblist] pages. | |
| 1263 | - * Correct the [/help?cmd=/doc|/doc] page to support read-only repositories. | |
| 1264 | - * Correct [/help?cmd=/zip|/zip], [/help?cmd=/tarball|/tarball], | |
| 1265 | - [/help?cmd=zip|zip], and [/help?cmd=tarball|tarball] pages and commands to | |
| 1324 | + * Improvements to the [/help/www/stat|/stat] page | |
| 1325 | + * Various new hyperlinks to the [/help/www/bloblist|/bloblist] | |
| 1326 | + and [/help/www/bigbloblist|/bigbloblist] pages. | |
| 1327 | + * Correct the [/help/www/doc|/doc] page to support read-only repositories. | |
| 1328 | + * Correct [/help/www/zip|/zip], [/help/www/tarball|/tarball], | |
| 1329 | + [/help/zip|zip], and [/help/tarball|tarball] pages and commands to | |
| 1266 | 1330 | honor the versioned manifest setting when outside of an open checkout |
| 1267 | 1331 | directory. |
| 1268 | 1332 | * The admin-log and access-log settings are now on by default for |
| 1269 | 1333 | new repositories. |
| 1270 | 1334 | * Update the built-in SQLite to version 3.21.0. |
| @@ -1272,34 +1336,34 @@ | ||
| 1272 | 1336 | <h2 id='v2_3'>Changes for Version 2.3 (2017-07-21)</h2> |
| 1273 | 1337 | |
| 1274 | 1338 | * Update the built-in SQLite to version 3.20.0 (beta). |
| 1275 | 1339 | * Update internal Unicode character tables, used in regular expression |
| 1276 | 1340 | handling, from version 9.0 to 10.0. |
| 1277 | - * Show the last-sync-URL on the [/help?cmd=/urllist|/urllist] page. | |
| 1341 | + * Show the last-sync-URL on the [/help/www/urllist|/urllist] page. | |
| 1278 | 1342 | * Added the "Event Summary" activity report. |
| 1279 | 1343 | [/reports?type=ci&view=lastchng|example] |
| 1280 | 1344 | * Added the "Security Audit" page, available to administrators only |
| 1281 | 1345 | * Added the Last Login time to the user list page, for administrators only |
| 1282 | - * Added the --numstat option to the [/help?cmd=diff|fossil diff] command | |
| 1346 | + * Added the --numstat option to the [/help/diff|fossil diff] command | |
| 1283 | 1347 | * Limit the size of the heap and stack on unix systems, as a proactive |
| 1284 | 1348 | defense against the |
| 1285 | 1349 | [https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt|Stack Clash] |
| 1286 | 1350 | attack. |
| 1287 | 1351 | * Fix "database locked" warnings caused by "PRAGMA optimize". |
| 1288 | 1352 | * Fix a potential XSS vulnerability on the |
| 1289 | - [/help?cmd=/help|/help] webpage. | |
| 1353 | + [/help/www/help|/help] webpage. | |
| 1290 | 1354 | * Documentation updates |
| 1291 | 1355 | |
| 1292 | 1356 | <h2 id='v2_2'>Changes for Version 2.2 (2017-04-11)</h2> |
| 1293 | 1357 | |
| 1294 | 1358 | * GIT comment tags are now handled by Fossil during import/export. |
| 1295 | 1359 | * Show the content of README files on directory listings. |
| 1296 | 1360 | ([/file/skins|example]) |
| 1297 | 1361 | * Support for Basic Authentication if enabled (default off). |
| 1298 | 1362 | * Show the hash algorithms used on the |
| 1299 | - [/help?cmd=/rcvfromlist|/rcvfromlist] page. | |
| 1300 | - * The [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] pages | |
| 1363 | + [/help/www/rcvfromlist|/rcvfromlist] page. | |
| 1364 | + * The [/help/www/tarball|/tarball] and [/help/www/zip|/zip] pages | |
| 1301 | 1365 | now use the the r= query parameter |
| 1302 | 1366 | to select which check-in to deliver. The uuid= query parameter |
| 1303 | 1367 | is still accepted for backwards compatibility. |
| 1304 | 1368 | * Update the built-in SQLite to version 3.18.0. |
| 1305 | 1369 | * Run "[https://www.sqlite.org/pragma.html#pragma_optimize|PRAGMA optimize]" |
| @@ -1308,12 +1372,12 @@ | ||
| 1308 | 1372 | <h2 id='v2_1'>Changes for Version 2.1 (2017-03-10)</h2> |
| 1309 | 1373 | |
| 1310 | 1374 | * Add support for [./hashpolicy.wiki|hash policies] that control which |
| 1311 | 1375 | of the Hardened-SHA1 or SHA3-256 algorithms is used to name new |
| 1312 | 1376 | artifacts. |
| 1313 | - * Add the "gshow" and "gcat" subcommands to [/help?cmd=stash|fossil stash]. | |
| 1314 | - * Add the [/help?cmd=/juvlist|/juvlist] web page and use it to construct | |
| 1377 | + * Add the "gshow" and "gcat" subcommands to [/help/stash|fossil stash]. | |
| 1378 | + * Add the [/help/www/juvlist|/juvlist] web page and use it to construct | |
| 1315 | 1379 | the [/uv/download.html|Download Page] of the Fossil self-hosting website |
| 1316 | 1380 | using Ajax. |
| 1317 | 1381 | |
| 1318 | 1382 | <h2 id='v2_0'>Changes for Version 2.0 (2017-03-03)</h2> |
| 1319 | 1383 | |
| @@ -1321,23 +1385,23 @@ | ||
| 1321 | 1385 | [https://github.com/cr-marcstevens/sha1collisiondetection|hardened SHA1] |
| 1322 | 1386 | implementation by Marc Stevens and Dan Shumow. |
| 1323 | 1387 | * Add the ability to read and understand |
| 1324 | 1388 | [./fileformat.wiki#names|artifact names] that are based on SHA3-256 |
| 1325 | 1389 | rather than SHA1, but do not actually generate any such names. |
| 1326 | - * Added the [/help?cmd=sha3sum|sha3sum] command. | |
| 1390 | + * Added the [/help/sha3sum|sha3sum] command. | |
| 1327 | 1391 | * Update the built-in SQLite to version 3.17.0. |
| 1328 | 1392 | |
| 1329 | 1393 | <h2 id='v1_37'>Changes for Version 1.37 (2017-01-16)</h2> |
| 1330 | 1394 | |
| 1331 | 1395 | * Add checkbox widgets to various web pages. See [/technote/8d18bf27e9| |
| 1332 | 1396 | this technote] for more information. To get the checkboxes to look as |
| 1333 | 1397 | intended, you must update the CSS in your repository and all clones. |
| 1334 | 1398 | * Add the [/help/all|fossil all ui] command |
| 1335 | - * Add the [/help?cmd=/file|/file] webpage | |
| 1336 | - * Enhance the [/help?cmd=/brlist|/brlist] webpage to make use of branch colors. | |
| 1399 | + * Add the [/help/www/file|/file] webpage | |
| 1400 | + * Enhance the [/help/www/brlist|/brlist] webpage to make use of branch colors. | |
| 1337 | 1401 | * Add support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the |
| 1338 | - [/help?cmd=/timeline|/timeline] webpage, with associated form widgets. | |
| 1402 | + [/help/www/timeline|/timeline] webpage, with associated form widgets. | |
| 1339 | 1403 | * Enhance the [/help/changes|changes] and [/help/status|status] commands |
| 1340 | 1404 | with many new filter options so that specific kinds of changes can be |
| 1341 | 1405 | found without having to pipe through grep or sed. |
| 1342 | 1406 | * Enhanced the [/help/sqlite3|fossil sql] command so that it opens the |
| 1343 | 1407 | [./tech_overview.wiki#localdb|checkout database] and the |
| @@ -1350,11 +1414,11 @@ | ||
| 1350 | 1414 | </ul> |
| 1351 | 1415 | * Rename crnl-glob [/help/settings|setting] to crlf-glob, but keep |
| 1352 | 1416 | crnl-glob as a compatibility alias. |
| 1353 | 1417 | * Added the --command option to the [/help/diff|diff] command. |
| 1354 | 1418 | * Fix a C99-ism that prevents the 1.36 release from building with MSVC. |
| 1355 | - * Fix [/help?cmd=ticket|ticket set] when using the "+" prefix with fields | |
| 1419 | + * Fix [/help/ticket|ticket set] when using the "+" prefix with fields | |
| 1356 | 1420 | from the "ticketchng" table. |
| 1357 | 1421 | * Remove the "fusefs" command from builds that do not have the underlying |
| 1358 | 1422 | support enabled. |
| 1359 | 1423 | * Fixes for incremental git import/export. |
| 1360 | 1424 | * Minor security enhancements to |
| @@ -1364,58 +1428,58 @@ | ||
| 1364 | 1428 | |
| 1365 | 1429 | |
| 1366 | 1430 | <h2 id='v1_36'>Changes for Version 1.36 (2016-10-24)</h2> |
| 1367 | 1431 | |
| 1368 | 1432 | * Add support for [./unvers.wiki|unversioned content], |
| 1369 | - the [/help?cmd=unversioned|fossil unversioned] command and the | |
| 1370 | - [/help?cmd=/uv|/uv] and [/uvlist] web pages. | |
| 1433 | + the [/help/unversioned|fossil unversioned] command and the | |
| 1434 | + [/help/www/uv|/uv] and [/uvlist] web pages. | |
| 1371 | 1435 | * The [/uv/download.html|download page] is moved into |
| 1372 | 1436 | [./unvers.wiki|unversioned content] so that the self-hosting Fossil |
| 1373 | 1437 | websites no longer uses any external content. |
| 1374 | 1438 | * Added the "Search" button to the graphical diff generated by |
| 1375 | - the --tk option on the [/help?cmd=diff|diff] command. | |
| 1439 | + the --tk option on the [/help/diff|diff] command. | |
| 1376 | 1440 | * Added the "--checkin VERSION" option to the |
| 1377 | - [/help?cmd=diff|diff] command. | |
| 1378 | - * Various performance enhancements to the [/help?cmd=diff|diff] command. | |
| 1441 | + [/help/diff|diff] command. | |
| 1442 | + * Various performance enhancements to the [/help/diff|diff] command. | |
| 1379 | 1443 | * Update internal Unicode character tables, used in regular expression |
| 1380 | 1444 | handling, from version 8.0 to 9.0. |
| 1381 | 1445 | * Update the built-in SQLite to version 3.15. Fossil now requires |
| 1382 | 1446 | the SQLITE_DBCONFIG_MAINDBNAME interface of SQLite which is only available |
| 1383 | 1447 | in SQLite version 3.15 and later and so Fossil will not work with |
| 1384 | 1448 | earlier SQLite versions. |
| 1385 | 1449 | * Fix [https://www.mail-archive.com/[email protected]/msg23618.html|multi-line timeline bug] |
| 1386 | - * Enhance the [/help?cmd=purge|fossil purge] command. | |
| 1387 | - * New command [/help?cmd=shell|fossil shell]. | |
| 1450 | + * Enhance the [/help/purge|fossil purge] command. | |
| 1451 | + * New command [/help/shell|fossil shell]. | |
| 1388 | 1452 | * SQL parameters whose names are all lower-case in Ticket Report SQL |
| 1389 | 1453 | queries are filled in using HTTP query parameter values. |
| 1390 | 1454 | * Added support for [./childprojects.wiki|child projects] that are |
| 1391 | 1455 | able to pull from their parent but not push. |
| 1392 | 1456 | * Added the -nocomplain option to the TH1 "query" command. |
| 1393 | 1457 | * Added support for the chng=GLOBLIST query parameter on the |
| 1394 | - [/help?cmd=/timeline|/timeline] webpage. | |
| 1458 | + [/help/www/timeline|/timeline] webpage. | |
| 1395 | 1459 | |
| 1396 | 1460 | <h2 id='v1_35'>Changes for Version 1.35 (2016-06-14)</h2> |
| 1397 | 1461 | |
| 1398 | 1462 | * Enable symlinks by default on all non-Windows platforms. |
| 1399 | 1463 | * Enhance the [/md_rules|Markdown formatting] so that hyperlinks that begin |
| 1400 | 1464 | with "/" are relative to the root of the Fossil repository. |
| 1401 | - * Rework the [/help?cmd=/setup_ulist|/setup_list page] (the User List page) | |
| 1465 | + * Rework the [/help/www/setup_ulist|/setup_list page] (the User List page) | |
| 1402 | 1466 | to display all users in a click-to-sort table. |
| 1403 | 1467 | * Fix backslash-octal escape on filenames while importing from git |
| 1404 | 1468 | * When markdown documents begin with <h1> HTML elements, use that |
| 1405 | 1469 | header at the document title. |
| 1406 | - * Added the [/help?cmd=/bigbloblist|/bigbloblist page]. | |
| 1407 | - * Enhance the [/help?cmd=/finfo|/finfo page] so that when it is showing | |
| 1470 | + * Added the [/help/www/bigbloblist|/bigbloblist page]. | |
| 1471 | + * Enhance the [/help/www/finfo|/finfo page] so that when it is showing | |
| 1408 | 1472 | the ancestors of a particular file version, it only shows direct |
| 1409 | 1473 | ancestors and omits changes on branches, thus making it show the same set |
| 1410 | - of ancestors that are used for [/help?cmd=/blame|/blame]. | |
| 1411 | - * Added the --page option to the [/help?cmd=ui|fossil ui] command | |
| 1412 | - * Added the [/help?cmd=bisect|fossil bisect ui] command | |
| 1413 | - * Enhanced the [/help?cmd=diff|fossil diff] command so that it accepts | |
| 1474 | + of ancestors that are used for [/help/www/blame|/blame]. | |
| 1475 | + * Added the --page option to the [/help/ui|fossil ui] command | |
| 1476 | + * Added the [/help/bisect|fossil bisect ui] command | |
| 1477 | + * Enhanced the [/help/diff|fossil diff] command so that it accepts | |
| 1414 | 1478 | directory names as arguments and computes diffs on all files contained |
| 1415 | 1479 | within those directories. |
| 1416 | - * Fix the [/help?cmd=add|fossil add] command so that it shows "SKIP" for | |
| 1480 | + * Fix the [/help/add|fossil add] command so that it shows "SKIP" for | |
| 1417 | 1481 | files added that were already under management. |
| 1418 | 1482 | * TH1 enhancements: |
| 1419 | 1483 | <ul><li>Add <nowiki>[array exists]</nowiki> command.</li> |
| 1420 | 1484 | <li>Add minimal <nowiki>[array names]</nowiki> command.</li> |
| 1421 | 1485 | <li>Add tcl_platform(engine) and tcl_platform(platform) array |
| @@ -1423,32 +1487,32 @@ | ||
| 1423 | 1487 | </ul> |
| 1424 | 1488 | * Get autosetup working with MinGW. |
| 1425 | 1489 | * Fix autosetup detection of zlib in the source tree. |
| 1426 | 1490 | * Added autosetup detection of OpenSSL when it may be present under the |
| 1427 | 1491 | "compat" subdirectory of the source tree. |
| 1428 | - * Added the [/help?cmd=reparent|fossil reparent] command | |
| 1429 | - * Added --include and --exclude options to [/help?cmd=tarball|fossil tarball] | |
| 1430 | - and [/help?cmd=zip|fossil zip] and the in= and ex= query parameters to the | |
| 1431 | - [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] web pages. | |
| 1492 | + * Added the [/help/reparent|fossil reparent] command | |
| 1493 | + * Added --include and --exclude options to [/help/tarball|fossil tarball] | |
| 1494 | + and [/help/zip|fossil zip] and the in= and ex= query parameters to the | |
| 1495 | + [/help/www/tarball|/tarball] and [/help/www/zip|/zip] web pages. | |
| 1432 | 1496 | * Add support for [./encryptedrepos.wiki|encrypted Fossil repositories]. |
| 1433 | 1497 | * If the FOSSIL_PWREADER environment variable is set, then use the program it |
| 1434 | 1498 | names in place of getpass() to read passwords and passphrases |
| 1435 | 1499 | * Option --baseurl now works on Windows. |
| 1436 | 1500 | * Numerous documentation improvements. |
| 1437 | 1501 | * Update the built-in SQLite to version 3.13.0. |
| 1438 | 1502 | |
| 1439 | 1503 | <h2 id='v1_34'>Changes for Version 1.34 (2015-11-02)</h2> |
| 1440 | 1504 | |
| 1441 | - * Make the [/help?cmd=clean|fossil clean] command undoable for files less | |
| 1505 | + * Make the [/help/clean|fossil clean] command undoable for files less | |
| 1442 | 1506 | than 10MiB. |
| 1443 | 1507 | * Update internal Unicode character tables, used in regular expression |
| 1444 | 1508 | handling, from version 7.0 to 8.0. |
| 1445 | - * Add the new [/help?cmd=amend|amend] command which is used to modify | |
| 1509 | + * Add the new [/help/amend|amend] command which is used to modify | |
| 1446 | 1510 | tags of a "check-in". |
| 1447 | - * Fix bug in [/help?cmd=import|import] command, handling version 3 of | |
| 1511 | + * Fix bug in [/help/import|import] command, handling version 3 of | |
| 1448 | 1512 | the svndump format for subversion. |
| 1449 | - * Add the [/help?cmd=all|all cache] command. | |
| 1513 | + * Add the [/help/all|all cache] command. | |
| 1450 | 1514 | * TH1 enhancements: |
| 1451 | 1515 | <ul><li>Add minimal <nowiki>[lsearch]</nowiki> command. Only exact |
| 1452 | 1516 | case-sensitive matching is supported.</li> |
| 1453 | 1517 | <li>Add the <nowiki>[glob_match]</nowiki>, <nowiki>[markdown]</nowiki>, |
| 1454 | 1518 | <nowiki>[dir]</nowiki>, and <nowiki>[encode64]</nowiki> commands.</li> |
| @@ -1455,106 +1519,106 @@ | ||
| 1455 | 1519 | <li>Add the <nowiki>[tclIsSafe] and [tclMakeSafe]</nowiki> commands to |
| 1456 | 1520 | the Tcl integration subsystem.</li> |
| 1457 | 1521 | <li>Add 'double', 'integer', and 'list' classes to the |
| 1458 | 1522 | <nowiki>[string is]</nowiki> command.</li> |
| 1459 | 1523 | </ul> |
| 1460 | - * Add the --undo option to the [/help?cmd=diff|diff] command. | |
| 1524 | + * Add the --undo option to the [/help/diff|diff] command. | |
| 1461 | 1525 | * Build-in Antirez's "linenoise" command-line editing library for use with |
| 1462 | - the [/help?cmd=sqlite3|fossil sql] command on Unix platforms. | |
| 1463 | - * Add [/help?cmd=stash|stash cat] as an alias for the | |
| 1464 | - [/help?cmd=stash|stash show] command. | |
| 1465 | - * Automatically pull before [/help?cmd=merge|fossil merge] when auto-sync | |
| 1526 | + the [/help/sqlite3|fossil sql] command on Unix platforms. | |
| 1527 | + * Add [/help/stash|stash cat] as an alias for the | |
| 1528 | + [/help/stash|stash show] command. | |
| 1529 | + * Automatically pull before [/help/merge|fossil merge] when auto-sync | |
| 1466 | 1530 | is enabled. |
| 1467 | - * Fix --hard option to [/help?cmd=mv|fossil mv] and [/help?cmd=rm|fossil rm] | |
| 1531 | + * Fix --hard option to [/help/mv|fossil mv] and [/help/rm|fossil rm] | |
| 1468 | 1532 | to enable them to work properly with certain relative paths. |
| 1469 | 1533 | * Change the mimetype for ".n" and ".man" files to text/plain. |
| 1470 | - * Display improvements in the [/help?cmd=bisect|fossil bisect chart] command. | |
| 1534 | + * Display improvements in the [/help/bisect|fossil bisect chart] command. | |
| 1471 | 1535 | * Updated the built-in SQLite to version 3.9.1 and activated JSON1 and FTS5 |
| 1472 | 1536 | support (both currently unused within Fossil). |
| 1473 | 1537 | |
| 1474 | 1538 | <h2 id='v1_33'>Changes for Version 1.33 (2015-05-23)</h2> |
| 1475 | - * Improved fork detection on [/help?cmd=update|fossil update], | |
| 1476 | - [/help?cmd=status|fossil status] and related commands. | |
| 1539 | + * Improved fork detection on [/help/update|fossil update], | |
| 1540 | + [/help/status|fossil status] and related commands. | |
| 1477 | 1541 | * Change the default skin to what used to be called "San Francisco Modern". |
| 1478 | 1542 | * Add the [/repo-tabsize] web page |
| 1479 | - * Add [/help?cmd=import|fossil import --svn], for importing a subversion | |
| 1543 | + * Add [/help/import|fossil import --svn], for importing a subversion | |
| 1480 | 1544 | repository into fossil which was exported using "svnadmin dump". |
| 1481 | - * Add the "--compress-only" option to [/help?cmd=rebuild|fossil rebuild]. | |
| 1545 | + * Add the "--compress-only" option to [/help/rebuild|fossil rebuild]. | |
| 1482 | 1546 | * Use a pie chart on the [/reports?view=byuser] page. |
| 1483 | - * Enhanced [/help?cmd=clean|fossil clean --verily] so that it ignores | |
| 1547 | + * Enhanced [/help/clean|fossil clean --verily] so that it ignores | |
| 1484 | 1548 | keep-glob and ignore-glob settings. Added the -x alias for --verily. |
| 1485 | - * Add the --soft and --hard options to [/help?cmd=rm|fossil rm] and | |
| 1486 | - [/help?cmd=mv|fossil mv]. The default is still --soft, but that is | |
| 1549 | + * Add the --soft and --hard options to [/help/rm|fossil rm] and | |
| 1550 | + [/help/mv|fossil mv]. The default is still --soft, but that is | |
| 1487 | 1551 | now configurable at compile-time or by the mv-rm-files setting. |
| 1488 | 1552 | * Improved ability to [./customgraph.md|customize the timeline graph]. |
| 1489 | 1553 | * Improvements to the [/sitemap] page. |
| 1490 | - * Automatically adjust the [/help?cmd=timeline|CLI timeline] to the terminal | |
| 1554 | + * Automatically adjust the [/help/timeline|CLI timeline] to the terminal | |
| 1491 | 1555 | width on Linux. |
| 1492 | 1556 | * Added <nowiki>[info commands] and [info vars]</nowiki> commands to TH1. |
| 1493 | 1557 | These commands perform the same function as their Tcl counterparts, |
| 1494 | 1558 | except they do not accept a pattern argument. |
| 1495 | 1559 | * Fix some obscure issues with TH1 expression processing. |
| 1496 | 1560 | * Fix titles in search results for documents that are not wiki, markdown, |
| 1497 | 1561 | or HTML. |
| 1498 | 1562 | * Formally translate TH1 to Tcl return codes and vice-versa, where |
| 1499 | 1563 | necessary, in the Tcl integration subsystem. |
| 1500 | - * Add [/help?cmd=leaves|fossil leaves -multiple], for finding multiple | |
| 1564 | + * Add [/help/leaves|fossil leaves -multiple], for finding multiple | |
| 1501 | 1565 | leaves on the same branch. |
| 1502 | 1566 | * Added the "Blitz" skin option. |
| 1503 | 1567 | * Removed the ".fossil-settings/keep-glob" file. It should not have been |
| 1504 | 1568 | checked into the repository. |
| 1505 | 1569 | * Update the built-in SQLite to version 3.8.10.2. |
| 1506 | - * Make [/help?cmd=open|fossil open] honor ".fossil-settings/allow-symlinks". | |
| 1507 | - * Allow [/help?cmd=add|fossil add] to be used on symlinks to nonexistent or | |
| 1508 | - unreadable files in the same way as [/help?cmd=addremove|fossil addremove]. | |
| 1570 | + * Make [/help/open|fossil open] honor ".fossil-settings/allow-symlinks". | |
| 1571 | + * Allow [/help/add|fossil add] to be used on symlinks to nonexistent or | |
| 1572 | + unreadable files in the same way as [/help/addremove|fossil addremove]. | |
| 1509 | 1573 | * Added fork warning to be issued if sync produced a fork |
| 1510 | - * Update the [/help?cmd=/info|info] page to report when a file becomes a | |
| 1574 | + * Update the [/help/www/info|info] page to report when a file becomes a | |
| 1511 | 1575 | symlink. Additionally show the UUID for files whose types have changed |
| 1512 | 1576 | without changing contents or symlink target. |
| 1513 | - * Have [/help?cmd=changes|fossil changes] and | |
| 1514 | - [/help?cmd=status|fossil status] report when executable or symlink status | |
| 1577 | + * Have [/help/changes|fossil changes] and | |
| 1578 | + [/help/status|fossil status] report when executable or symlink status | |
| 1515 | 1579 | changes on otherwise unmodified files. |
| 1516 | - * Permit filtering weekday and file [/help?cmd=/reports|reports] by user. | |
| 1580 | + * Permit filtering weekday and file [/help/www/reports|reports] by user. | |
| 1517 | 1581 | Also ensure the user parameter is preserved when changing types. Add a |
| 1518 | 1582 | field for direct entry of the user name to each applicable report. |
| 1519 | - * Create parent directories of [/help?cmd=settings|empty-dirs] if they don't | |
| 1583 | + * Create parent directories of [/help/settings|empty-dirs] if they don't | |
| 1520 | 1584 | already exist. |
| 1521 | 1585 | * Inhibit timeline links to wiki pages that have been deleted. |
| 1522 | 1586 | |
| 1523 | 1587 | <h2 id='v1_33'>Changes for Version 1.32 (2015-03-14)</h2> |
| 1524 | - * When creating a new repository using [/help?cmd=init|fossil init], ensure | |
| 1588 | + * When creating a new repository using [/help/init|fossil init], ensure | |
| 1525 | 1589 | that the new repository is fully compatible with historical versions of |
| 1526 | 1590 | Fossil by having a valid manifest as RID 1. |
| 1527 | 1591 | * Anti-aliased rendering of arrowheads on timeline graphs. |
| 1528 | 1592 | * Added vi/less-style key bindings to the --tk diff GUI. |
| 1529 | 1593 | * Documentation updates to fix spellings and changes all "checkins" to |
| 1530 | 1594 | "check-ins". |
| 1531 | 1595 | * Add the --repolist option to server commands such as |
| 1532 | - [/help?cmd=server|fossil server] or [/help?cmd=http|fossil http]. | |
| 1596 | + [/help/server|fossil server] or [/help/http|fossil http]. | |
| 1533 | 1597 | * Added the "Xekri" skin. |
| 1534 | 1598 | * Enhance the "ln=" query parameter on artifact displays to accept multiple |
| 1535 | 1599 | ranges, separate by spaces (or "+" when URL-encoded). |
| 1536 | - * Added [/help?cmd=forget|fossil forget] as an alias for | |
| 1537 | - [/help?cmd=rm|fossil rm]. | |
| 1600 | + * Added [/help/forget|fossil forget] as an alias for | |
| 1601 | + [/help/rm|fossil rm]. | |
| 1538 | 1602 | |
| 1539 | 1603 | <h2 id='v1_31'>Changes For Version 1.31 (2015-02-23)</h2> |
| 1540 | 1604 | * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID |
| 1541 | 1605 | columns to the schema, to support better drawing of file change graphs. |
| 1542 | - A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required. | |
| 1606 | + A [/help/rebuild|fossil rebuild] is recommended but is not required. | |
| 1543 | 1607 | so that the new graph drawing logic can work effectively. |
| 1544 | 1608 | * Added [/search|search] over Check-in comments, Documents, Tickets and |
| 1545 | 1609 | Wiki. Disabled by default. The search can be either a full-scan or it |
| 1546 | 1610 | can use an index that is kept up-to-date automatically. The new |
| 1547 | - /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command | |
| 1611 | + /srchsetup web-page and the [/help/fts-config|fts-config] command | |
| 1548 | 1612 | were added to help configure the search capability. Expect further |
| 1549 | 1613 | enhancements to the search capabilities in subsequent releases. |
| 1550 | 1614 | * Added form elements to some submenus (in particular the /timeline) |
| 1551 | 1615 | for easier operation. |
| 1552 | - * Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild]. | |
| 1616 | + * Added the --ifneeded option to [/help/rebuild|fossil rebuild]. | |
| 1553 | 1617 | * Added "override skins" using the "skin:" line of the CGI script or |
| 1554 | - using the --skin LABEL option on the [/help?cmd=server|server], | |
| 1555 | - [/help?cmd=ui|ui], or [/help?cmd=http|http] commands. | |
| 1618 | + using the --skin LABEL option on the [/help/server|server], | |
| 1619 | + [/help/ui|ui], or [/help/http|http] commands. | |
| 1556 | 1620 | * Embedded html documents that begin with |
| 1557 | 1621 | <doc class="fossil-doc"> are displayed with standard |
| 1558 | 1622 | headers and footers added. |
| 1559 | 1623 | * Allow <div style='...'> markup in [/wiki_rules|wiki]. |
| 1560 | 1624 | * Renamed "Events" to "Technical Notes", while updating the technote |
| @@ -1561,24 +1625,24 @@ | ||
| 1561 | 1625 | display and control pages. Add support for technotes as plain text |
| 1562 | 1626 | or as Markdown. |
| 1563 | 1627 | * Added the [/md_rules] pages containing summary instructions on the |
| 1564 | 1628 | Markdown format. |
| 1565 | 1629 | * Added the --repolist and --nojail options to the various server commands |
| 1566 | - (ex: [/help?cmd=server|fossil server]). | |
| 1567 | - * Added the [/help?cmd=all|fossil all add] subcommand to "fossil all". | |
| 1630 | + (ex: [/help/server|fossil server]). | |
| 1631 | + * Added the [/help/all|fossil all add] subcommand to "fossil all". | |
| 1568 | 1632 | * Improvements to the /login page. Some hyperlinks to pages that require |
| 1569 | 1633 | "anonymous" privileges are displayed even if the current user is "nobody" |
| 1570 | 1634 | but automatically redirect to /login. |
| 1571 | - * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file | |
| 1635 | + * The [/help/www/doc|/doc] web-page will now try to deliver the file | |
| 1572 | 1636 | "404.md" from the top-level directory (if such a file exists) in |
| 1573 | 1637 | place of its built-in 404 text. |
| 1574 | 1638 | * Download of Tarballs and ZIP Archives by user "nobody" is now enabled |
| 1575 | 1639 | by default in new repositories. |
| 1576 | 1640 | * Enhancements to the table sorting controls. More display tables |
| 1577 | 1641 | are now sortable. |
| 1578 | - * Add IPv6 support to [/help?cmd=sync|fossil sync] and | |
| 1579 | - [/help?cmd=clone|fossil clone] | |
| 1642 | + * Add IPv6 support to [/help/sync|fossil sync] and | |
| 1643 | + [/help/clone|fossil clone] | |
| 1580 | 1644 | * Add more skins such as "San Francisco Modern" and "Eagle". |
| 1581 | 1645 | * During shutdown, check to see if the check-out database (".fslckout") |
| 1582 | 1646 | contains a lot of free space, and if it does, VACUUM it. |
| 1583 | 1647 | * Added the [/mimetype_list] page. |
| 1584 | 1648 | * Added the [/hash-collisions] page. |
| @@ -1586,14 +1650,14 @@ | ||
| 1586 | 1650 | ticket reports. |
| 1587 | 1651 | * Break out the components (css, footer, and header) for the |
| 1588 | 1652 | various built-in skins into separate files in the source tree. |
| 1589 | 1653 | |
| 1590 | 1654 | <h2 id='v1_30'>Changes For Version 1.30 (2015-01-19)</h2> |
| 1591 | - * Added the [/help?cmd=bundle|fossil bundle] command. | |
| 1592 | - * Added the [/help?cmd=purge|fossil purge] command. | |
| 1593 | - * Added the [/help?cmd=publish|fossil publish] command. | |
| 1594 | - * Added the [/help?cmd=unpublished|fossil unpublished] command. | |
| 1655 | + * Added the [/help/bundle|fossil bundle] command. | |
| 1656 | + * Added the [/help/purge|fossil purge] command. | |
| 1657 | + * Added the [/help/publish|fossil publish] command. | |
| 1658 | + * Added the [/help/unpublished|fossil unpublished] command. | |
| 1595 | 1659 | * Enhance the [/tree] webpage to show the age of each file with the option |
| 1596 | 1660 | to sort by age. |
| 1597 | 1661 | * Enhance the [/brlist] webpage to show additional information about each branch |
| 1598 | 1662 | and to be sortable by clicking on column headers. |
| 1599 | 1663 | * Add support for Docker. Just install docker and type |
| @@ -1603,17 +1667,17 @@ | ||
| 1603 | 1667 | copies of all historical check-ins. This only works on systems that |
| 1604 | 1668 | support FuseFS. |
| 1605 | 1669 | * Add the administrative log that records all configuration. |
| 1606 | 1670 | * Added the [/sitemap] webpage. |
| 1607 | 1671 | * Added the [/bloblist] web page. |
| 1608 | - * Let [/help?cmd=new|fossil new] no longer create an initial empty commit | |
| 1672 | + * Let [/help/new|fossil new] no longer create an initial empty commit | |
| 1609 | 1673 | by default. The first commit after checking out an empty repository will |
| 1610 | 1674 | become the initial commit. |
| 1611 | - * Added the [/help?cmd=all|fossil all dbstat] and | |
| 1612 | - [/help?cmd=all|fossil all info] commands. | |
| 1675 | + * Added the [/help/all|fossil all dbstat] and | |
| 1676 | + [/help/all|fossil all info] commands. | |
| 1613 | 1677 | * Update SQLite to version 3.8.8. |
| 1614 | - * Added the --verily option to the [/help?cmd=clean|fossil clean] command. | |
| 1678 | + * Added the --verily option to the [/help/clean|fossil clean] command. | |
| 1615 | 1679 | * Add the "autosync-tries" setting to control the number of autosync attempts |
| 1616 | 1680 | before returning an error. |
| 1617 | 1681 | * Added a compile-time option (--with-miniz) to build using miniz instead |
| 1618 | 1682 | of zlib. Disabled by default. |
| 1619 | 1683 | * Support customization of commands and webpages, including the ability to |
| @@ -1623,12 +1687,12 @@ | ||
| 1623 | 1687 | [trace], [getParameter], [setParameter], [artifact], and |
| 1624 | 1688 | [globalState]</nowiki> commands to TH1, primarily for use by TH1 hooks. |
| 1625 | 1689 | * Automatically adjust the width of command-line timeline output according to the |
| 1626 | 1690 | detected width of the terminal. |
| 1627 | 1691 | * Prompt the user to optionally fix invalid UTF-8 at check-in. |
| 1628 | - * Added a line-number toggle option to the [/help?cmd=/info|/info] | |
| 1629 | - and [/help?cmd=/artifact|/artifact] pages. | |
| 1692 | + * Added a line-number toggle option to the [/help/www/info|/info] | |
| 1693 | + and [/help/www/artifact|/artifact] pages. | |
| 1630 | 1694 | * Most commands now issue errors rather than silently ignoring unrecognized |
| 1631 | 1695 | command-line options. |
| 1632 | 1696 | * Use full 40-character SHA1 hashes (instead of abbreviations) in most |
| 1633 | 1697 | internal URLs. |
| 1634 | 1698 | * The "ssh:" sync method on Windows now uses "plink.exe" instead of "ssh" as |
| @@ -1642,11 +1706,11 @@ | ||
| 1642 | 1706 | * Fix a rare and long-standing sync protocol bug |
| 1643 | 1707 | that would silently prevent the sync from running to completion. Before |
| 1644 | 1708 | this bug-fix it was sometimes necessary to do "fossil sync --verily" to |
| 1645 | 1709 | get two repositories in sync. |
| 1646 | 1710 | * Add the [/finfo?name=src/foci.c|files_of_checkin] virtual table - useful |
| 1647 | - for ad hoc queries in the [/help?cmd=sqlite3|fossil sql] interface, | |
| 1711 | + for ad hoc queries in the [/help/sqlite3|fossil sql] interface, | |
| 1648 | 1712 | and also used internally. |
| 1649 | 1713 | * Added the "$secureurl" TH1 variable for use in headers and footers. |
| 1650 | 1714 | * (Internal:) Add the ability to include resources as separate files in the |
| 1651 | 1715 | source tree that are converted into constant byte arrays in the compiled |
| 1652 | 1716 | binary. Use this feature to store the Tk script that implements the --tk |
| @@ -1666,143 +1730,143 @@ | ||
| 1666 | 1730 | * The [/reports] page now requires Read ("o") permissions. The "byweek" |
| 1667 | 1731 | report now properly propagates the selected year through the event type |
| 1668 | 1732 | filter links. |
| 1669 | 1733 | * The [/help/info | info command] now shows leaf status of the checkout. |
| 1670 | 1734 | * Add support for tunneling https through a http proxy (Ticket [e854101c4f]). |
| 1671 | - * Add option --empty to the "[/help?cmd=open | fossil open]" command. | |
| 1672 | - * Enhanced [/help?cmd=/fileage|the fileage page] to support a glob parameter. | |
| 1735 | + * Add option --empty to the "[/help/open | fossil open]" command. | |
| 1736 | + * Enhanced [/help/www/fileage|the fileage page] to support a glob parameter. | |
| 1673 | 1737 | * Add -w|--ignore-all-space and -Z|--ignore-trailing-space options to |
| 1674 | - [/help?cmd=annotate|fossil annotate], [/help?cmd=blame|fossil blame], | |
| 1675 | - [/help?cmd=diff|fossil (g)diff], [/help?cmd=stash|fossil stash diff]. | |
| 1676 | - * Add --strip-trailing-cr option to [/help?cmd=diff|fossil (g)diff] and | |
| 1677 | - [/help?cmd=stash|fossil stash diff]. | |
| 1738 | + [/help/annotate|fossil annotate], [/help/blame|fossil blame], | |
| 1739 | + [/help/diff|fossil (g)diff], [/help/stash|fossil stash diff]. | |
| 1740 | + * Add --strip-trailing-cr option to [/help/diff|fossil (g)diff] and | |
| 1741 | + [/help/stash|fossil stash diff]. | |
| 1678 | 1742 | * Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff |
| 1679 | 1743 | and /vdiff UI pages. |
| 1680 | 1744 | * Enhance [/reports?view=byweekday|/reports] with a "byweekday" view. |
| 1681 | - * Enhance the [/help?cmd=cat|fossil cat] command so that it works outside | |
| 1745 | + * Enhance the [/help/cat|fossil cat] command so that it works outside | |
| 1682 | 1746 | of a checkout when using the -R command-line option. |
| 1683 | 1747 | * Use full-length SHA1 hashes, not abbreviations, in most hyperlinks. |
| 1684 | 1748 | * Correctly render the <title> markup on wiki pages in the |
| 1685 | - [/help?cmd=/artifact|/artifact] webpage. | |
| 1686 | - * Enhance the [/help?cmd=whatis|fossil whatis] command to report on attachments | |
| 1687 | - and cluster artifacts. Added the [/help?cmd=test-whatis-all] command for | |
| 1749 | + [/help/www/artifact|/artifact] webpage. | |
| 1750 | + * Enhance the [/help/whatis|fossil whatis] command to report on attachments | |
| 1751 | + and cluster artifacts. Added the [/help/test-whatis-all] command for | |
| 1688 | 1752 | testing purposes. |
| 1689 | - * Add support for HTTP Basic Authentication on [/help?cmd=clone|clone] and | |
| 1690 | - [/help?cmd=sync|sync]. | |
| 1691 | - * Fix the [/help?cmd=stash|stash] so that it remembers added files and re-adds | |
| 1753 | + * Add support for HTTP Basic Authentication on [/help/clone|clone] and | |
| 1754 | + [/help/sync|sync]. | |
| 1755 | + * Fix the [/help/stash|stash] so that it remembers added files and re-adds | |
| 1692 | 1756 | them when the stash is applied. |
| 1693 | 1757 | * Fix the server so that it avoids writing to the database (and thus avoids |
| 1694 | 1758 | database locking issues) on a |
| 1695 | - [/help?cmd=pull|pull] or [/help?cmd=clone|clone]. | |
| 1759 | + [/help/pull|pull] or [/help/clone|clone]. | |
| 1696 | 1760 | * Add support for [./server.wiki#loadmgmt|server load management] using both |
| 1697 | - a cache of expensive pages (the [/help?cmd=cache|fossil cache] command) | |
| 1761 | + a cache of expensive pages (the [/help/cache|fossil cache] command) | |
| 1698 | 1762 | and by rejecting expensive page requests when the server load average is too |
| 1699 | 1763 | high. |
| 1700 | - * Add the [/help?cmd=praise|fossil praise] command as an alias for | |
| 1701 | - [/help?cmd=blame|fossil blame] for subversion compatibility. | |
| 1702 | - * Enhance the [/help?cmd=test-diff|fossil test-diff] command with -y or --tk | |
| 1764 | + * Add the [/help/praise|fossil praise] command as an alias for | |
| 1765 | + [/help/blame|fossil blame] for subversion compatibility. | |
| 1766 | + * Enhance the [/help/test-diff|fossil test-diff] command with -y or --tk | |
| 1703 | 1767 | options so that it shows both filenames above their respective columns in |
| 1704 | 1768 | the side-by-side diff output. |
| 1705 | - * Issue a warning if a [/help?cmd=add|fossil add] command tries to add a file | |
| 1769 | + * Issue a warning if a [/help/add|fossil add] command tries to add a file | |
| 1706 | 1770 | that matches the ignore-glob. |
| 1707 | - * Add option -W|--width to "[/help?cmd=stash|fossil stash ls]" | |
| 1708 | - and "[/help?cmd=leaves|fossil leaves]" commands. | |
| 1771 | + * Add option -W|--width to "[/help/stash|fossil stash ls]" | |
| 1772 | + and "[/help/leaves|fossil leaves]" commands. | |
| 1709 | 1773 | * Enhance support for running as the root user. Now works on Haiku. |
| 1710 | - * Added the <tt>-empty</tt> option to [/help?cmd=new|fossil new], which | |
| 1774 | + * Added the <tt>-empty</tt> option to [/help/new|fossil new], which | |
| 1711 | 1775 | causes it to not create an initial empty commit. The first commit after |
| 1712 | 1776 | checking out a repo created this way will become the initial commit. |
| 1713 | 1777 | * Enhance sync operations by committing each round-trip to minimize number |
| 1714 | 1778 | of retransmits when autosync fails. Include option for |
| 1715 | - [/help?cmd=update| fossil update] and [/help?cmd=merge| fossil merge] to | |
| 1779 | + [/help/update| fossil update] and [/help/merge| fossil merge] to | |
| 1716 | 1780 | continue even if missing content. |
| 1717 | 1781 | * Minor portability fixes for platforms where the char type is unsigned |
| 1718 | 1782 | by default. |
| 1719 | 1783 | |
| 1720 | 1784 | <h2>Changes For Version 1.28 (2014-01-27)</h2> |
| 1721 | - * Enhance [/help?cmd=/reports | /reports] to support event type filtering. | |
| 1785 | + * Enhance [/help/www/reports | /reports] to support event type filtering. | |
| 1722 | 1786 | * When cloning a repository, the user name passed via the URL (if any) |
| 1723 | 1787 | is now used as the default local admin user's name. |
| 1724 | 1788 | * Enhance the SSH transport mechanism so that it runs a single instance of |
| 1725 | 1789 | the "fossil" executable on the remote side, obviating the need for a shell |
| 1726 | 1790 | on the remote side. Some users may need to add the "?fossil=/path/to/fossil" |
| 1727 | 1791 | query parameter to "ssh:" URIs if their fossil binary is not in a standard |
| 1728 | 1792 | place. |
| 1729 | - * Add the "[/help?cmd=blame | fossil blame]" command that works just like | |
| 1793 | + * Add the "[/help/blame | fossil blame]" command that works just like | |
| 1730 | 1794 | "fossil annotate" but uses a different output format that includes the |
| 1731 | 1795 | user who made each change and omits line numbers. |
| 1732 | 1796 | * Add the "Tarball and ZIP-archive Prefix" configuration parameter under |
| 1733 | 1797 | Admin/Configuration. |
| 1734 | 1798 | * Fix CGI processing so that it works on web servers that do not |
| 1735 | 1799 | supply REQUEST_URI. |
| 1736 | 1800 | * Add options --dirsonly, --emptydirs, and --allckouts to the |
| 1737 | - "[/help?cmd=clean | fossil clean]" command. | |
| 1801 | + "[/help/clean | fossil clean]" command. | |
| 1738 | 1802 | * Ten-fold performance improvement in large "fossil blame" or |
| 1739 | 1803 | "fossil annotate" commands. |
| 1740 | - * Add option -W|--width and --offset to "[/help?cmd=timeline | fossil timeline]" | |
| 1741 | - and "[/help?cmd=finfo | fossil finfo]" commands. | |
| 1742 | - * Option -n|--limit of "[/help?cmd=timeline | fossil timeline]" now | |
| 1804 | + * Add option -W|--width and --offset to "[/help/timeline | fossil timeline]" | |
| 1805 | + and "[/help/finfo | fossil finfo]" commands. | |
| 1806 | + * Option -n|--limit of "[/help/timeline | fossil timeline]" now | |
| 1743 | 1807 | specifies the number of entries, just like all other commands which |
| 1744 | 1808 | have the -n|--limit option. The various timeline-related functions |
| 1745 | 1809 | now output "--- ?? limit (??) reached ---" at the end whenever |
| 1746 | 1810 | appropriate. Use "-n 0" if no limit is desired. |
| 1747 | 1811 | * Fix handling of password embedded in Fossil URL. |
| 1748 | - * New <tt>--once</tt> option to [/help?cmd=clone | fossil clone] command | |
| 1812 | + * New <tt>--once</tt> option to [/help/clone | fossil clone] command | |
| 1749 | 1813 | which does not store the URL or password when cloning. |
| 1750 | - * Modify [/help?cmd=ui | fossil ui] to respect "default user" in an open | |
| 1814 | + * Modify [/help/ui | fossil ui] to respect "default user" in an open | |
| 1751 | 1815 | repository. |
| 1752 | 1816 | * Fossil now hides check-ins that have the "hidden" tag in timeline webpages. |
| 1753 | 1817 | * Enhance <tt>/ci_edit</tt> page to add the "hidden" tag to check-ins. |
| 1754 | 1818 | * Advanced possibilities for commit and ticket change notifications over |
| 1755 | 1819 | http using TH1 scripting. |
| 1756 | 1820 | * Add --sha1sum and --integrate options |
| 1757 | - to the "[/help?cmd=commit | fossil commit]" command. | |
| 1821 | + to the "[/help/commit | fossil commit]" command. | |
| 1758 | 1822 | * Add the "clean" and "extra" subcommands to the |
| 1759 | - "[/help?cmd=all | fossil all]" command | |
| 1760 | - * Add the --whatif option to "[/help?cmd=clean|fossil clean]" that works the | |
| 1823 | + "[/help/all | fossil all]" command | |
| 1824 | + * Add the --whatif option to "[/help/clean|fossil clean]" that works the | |
| 1761 | 1825 | same as "--dry-run", |
| 1762 | 1826 | so that the name does not collide with the --dry-run option of "fossil all". |
| 1763 | 1827 | * Provide a configuration option to show dates on the web timeline |
| 1764 | 1828 | as "YYMMMDD HH:MM" |
| 1765 | 1829 | * Add an option to the "stats" webpage that allows an administrator to see |
| 1766 | 1830 | the current repository schema. |
| 1767 | - * Enhancements to the "[/help?cmd=/vdiff|/vdiff]" webpage for more difference | |
| 1831 | + * Enhancements to the "[/help/www/vdiff|/vdiff]" webpage for more difference | |
| 1768 | 1832 | display options. |
| 1769 | 1833 | * Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative |
| 1770 | 1834 | to "/dir" and make it the default way of showing file lists. |
| 1771 | 1835 | * Send gzipped HTTP responses to clients that support it. |
| 1772 | 1836 | |
| 1773 | 1837 | <h2>Changes For Version 1.27 (2013-09-11)</h2> |
| 1774 | - * Enhance the [/help?cmd=changes | fossil changes], | |
| 1775 | - [/help?cmd=clean | fossil clean], [/help?cmd=extras | fossil extras], | |
| 1776 | - [/help?cmd=ls | fossil ls] and [/help?cmd=status | fossil status] commands | |
| 1838 | + * Enhance the [/help/changes | fossil changes], | |
| 1839 | + [/help/clean | fossil clean], [/help/extras | fossil extras], | |
| 1840 | + [/help/ls | fossil ls] and [/help/status | fossil status] commands | |
| 1777 | 1841 | to restrict operation to files and directories named on the command-line. |
| 1778 | - * New --integrate option to [/help?cmd=merge | fossil merge], which | |
| 1842 | + * New --integrate option to [/help/merge | fossil merge], which | |
| 1779 | 1843 | automatically closes the merged branch when committing. |
| 1780 | 1844 | * Renamed <tt>/stats_report</tt> page to [/reports]. Graph width is now |
| 1781 | 1845 | relative, not absolute. |
| 1782 | 1846 | * Added <tt>yw=YYYY-WW</tt> (year-week) filter to timeline to limit the results |
| 1783 | 1847 | to a specific year and calendar week number, e.g. [/timeline?yw=2013-01]. |
| 1784 | 1848 | * Updates to SQLite to prevent opening a repository file using file descriptors |
| 1785 | 1849 | 1 or 2 on Unix. This fixes a bug under which an assertion failure could |
| 1786 | 1850 | overwrite part of a repository database file, corrupting it. |
| 1787 | 1851 | * Added support for unlimited line lengths in side-by-side diffs. |
| 1788 | - * New --close option to [/help?cmd=commit | fossil commit], which | |
| 1852 | + * New --close option to [/help/commit | fossil commit], which | |
| 1789 | 1853 | immediately closes the branch being committed. |
| 1790 | - * Added <tt>chart</tt> option to [/help?cmd=bisect | fossil bisect]. | |
| 1854 | + * Added <tt>chart</tt> option to [/help/bisect | fossil bisect]. | |
| 1791 | 1855 | * Improvements to the "human or bot?" determination. |
| 1792 | 1856 | * Reports errors about missing CGI-standard environment variables for HTTP |
| 1793 | 1857 | servers which do not support them. |
| 1794 | 1858 | * Minor improvements to sync support on Windows. |
| 1795 | - * Added <tt>--scgi</tt> option to [/help?cmd=server | fossil server]. | |
| 1859 | + * Added <tt>--scgi</tt> option to [/help/server | fossil server]. | |
| 1796 | 1860 | * Internal improvements to the sync process. |
| 1797 | 1861 | * The internals of the JSON API are now MIT-licensed, so downstream |
| 1798 | 1862 | users/packagers are no longer affected by the "do no evil" license |
| 1799 | 1863 | clause. |
| 1800 | 1864 | |
| 1801 | 1865 | <h2>Changes For Version 1.26 (2013-06-18)</h2> |
| 1802 | - * The argument to the --port option for the [/help?cmd=ui | fossil ui] and | |
| 1803 | - [/help?cmd=server | fossil server] commands can take an IP address in addition | |
| 1866 | + * The argument to the --port option for the [/help/ui | fossil ui] and | |
| 1867 | + [/help/server | fossil server] commands can take an IP address in addition | |
| 1804 | 1868 | to the port number, causing Fossil to bind to just that one IP address. |
| 1805 | 1869 | * After prompting for a password, also ask if that password should be |
| 1806 | 1870 | remembered. |
| 1807 | 1871 | * Performance improvements to the diff engine. |
| 1808 | 1872 | * Fix the side-by-side diff engine to work better with multi-byte Unicode text. |
| @@ -1809,11 +1873,11 @@ | ||
| 1809 | 1873 | * Color-coding in the web-based annotation (blame) display. Fix the annotation |
| 1810 | 1874 | engine so that it is no longer confused by time-warps. |
| 1811 | 1875 | * The markdown formatter is now available by default and can be used for |
| 1812 | 1876 | tickets, wiki, and embedded documentation. |
| 1813 | 1877 | * Add subcommands "fossil bisect log" and "fossil bisect status" to the |
| 1814 | - [/help?cmd=bisect | fossil bisect] command, as well as other bisect enhancements. | |
| 1878 | + [/help/bisect | fossil bisect] command, as well as other bisect enhancements. | |
| 1815 | 1879 | * Enhanced defenses that prevent spiders from using excessive CPU and bandwidth. |
| 1816 | 1880 | * Consistent use of the -n or --dry-run command line options. |
| 1817 | 1881 | * Win32: Fossil now understands Cygwin paths containing one or more of |
| 1818 | 1882 | the characters <nowiki>"*:<>?|</nowiki>. Those are normally forbidden in |
| 1819 | 1883 | win32. This means that the win32 fossil.exe is better usable in a Cygwin |
| 1820 | 1884 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,44 +1,108 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_27'>Changes for version 2.27 (pending)</h2><ol> |
| 4 | <li> Fix a SQL injection on the [/help?cmd=/file|/file page]. Thanks to |
| 5 | additional defenses built into Fossil, as well as good luck, this injection |
| 6 | is not exploitable for either data exfiltration or privilege escalation. The |
| 7 | only possible result of invoking the injection is a harmless SQL syntax error. |
| 8 | (The [https://en.wikipedia.org/wiki/Swiss_cheese_model|holes in the Swiss cheese] |
| 9 | did not line up!) |
| 10 | <li> Enhance the chng= query parameter on the [/help?cmd=/timeline|timeline page] |
| 11 | so that it works with other query parameters like p=, d=, from=, and to=. |
| 12 | <li> Always include nodes identify by sel1= and sel2= in the /timeline display. |
| 13 | <li> Enable the --editor option on the [/help?cmd=amend|fossil amend] command. |
| 14 | <li> Require at least an anonymous login to access the /blame page and similar, |
| 15 | to help prevent robots from soaking up excess CPU time on such pages. |
| 16 | <li> When walking the filesystem looking for Fossil repositories, avoid descending |
| 17 | into directories named "/proc". |
| 18 | <ll> Reduce memory requirements for sending authenticated sync protocol |
| 19 | messages. |
| 20 | </ol> |
| 21 | |
| 22 | <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol> |
| 23 | <li>Enhancements to [/help?cmd=diff|fossil diff] and similar: |
| 24 | <ol type="a"> |
| 25 | <li> The argument to the --from option can be a directory name, causing |
| 26 | Fossil to use files under that directory as the baseline for the diff. |
| 27 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 28 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 29 | are available, or a --by diff if not. |
| 30 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 31 | diff up to date with the latest changes on disk. |
| 32 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 33 | diffs of multiple files. |
| 34 | </ol> |
| 35 | <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 36 | about pending changes in a working check-out |
| 37 | <li>Enhancements to the [/help?cmd=ui|fossil ui] command: |
| 38 | <ol type="a"> |
| 39 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 40 | start page. Or, if the new "--from PATH" option is present, the |
| 41 | default start page becomes "/ckout?exbase=PATH". |
| 42 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 43 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 44 | person editing this change log has |
| @@ -46,25 +110,25 @@ | |
| 46 | press "Reload" on the web browser to view edits. |
| 47 | <li> Accept both IPv4 and IPv6 connections on all platforms, including |
| 48 | Windows and OpenBSD. This also applies to the "fossil server" |
| 49 | command. |
| 50 | </ol> |
| 51 | <li>Enhancements to [/help?cmd=merge|fossil merge]: |
| 52 | <ol type="a"> |
| 53 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 54 | especially the --tk option to that command, to provide analysis |
| 55 | of the most recent merge or update operation. |
| 56 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 57 | text that shows Fossil's suggested resolution to the conflict. |
| 58 | </ol> |
| 59 | <li>Enhancements to [/help?cmd=commit|fossil commit]: |
| 60 | <ol type="a"> |
| 61 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 62 | in the check-in comment, it will alert the developer and give |
| 63 | him or her the opportunity to edit the comment before continuing. |
| 64 | This feature is controllable by the |
| 65 | [/help?cmd=verify-comments|verify-comments setting]. |
| 66 | <li> The new "--if-changes" option causes the commit to become |
| 67 | a quiet no-op if there are no pending changes. |
| 68 | <li> Added the ability to sign check-ins with SSH keys. Artifacts signed |
| 69 | this way are ignored by all previous fossil versions, as if they |
| 70 | were plain-text file content instead of Fossil artifacts. |
| @@ -76,13 +140,13 @@ | |
| 76 | </ol> |
| 77 | <li>Deprecate the --comfmtflags and --comment-format global options and |
| 78 | no longer list them in the built-in help, but keep them working for |
| 79 | backwards compatibility. |
| 80 | Alternative TTY comment formatting can still be specified using the |
| 81 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 82 | default comment format is now called "canonical", not "legacy". |
| 83 | <li>Enhancements to the [/help?cmd=/timeline|/timeline page]: |
| 84 | <ol type="a"> |
| 85 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 86 | like "rl=" ("Related List") but adds "mionly" style related |
| 87 | check-ins instead of the full "rel" style. |
| 88 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -111,33 +175,33 @@ | |
| 111 | in which case the timeline shows those check-ins that are both |
| 112 | ancestors of p= and descendants of d=. |
| 113 | <li> The saturation and intensity of user-specified checkin and branch |
| 114 | background colors are automatically adjusted to keep the colors |
| 115 | compatible with the current skin, unless the |
| 116 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 117 | </ol> |
| 118 | <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like |
| 119 | /doc but keeps the title of markdown documents with the document rather |
| 120 | that moving it up to the page title. |
| 121 | <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 122 | and debugging |
| 123 | <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 124 | decoding of the artifact described by NAME. |
| 125 | <li>Improvements to the [/help?cmd=patch|fossil patch] command: |
| 126 | <ol type="a"> |
| 127 | <li> Fix a bug in "fossil patch create" that causes |
| 128 | [/help?cmd=revert|fossil revert] operations that happened |
| 129 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 130 | to be omitted from the patch. |
| 131 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 132 | aliases for remote checkout names. |
| 133 | </ol> |
| 134 | <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 135 | <ol type="a"> |
| 136 | <li> Add the ability to search the help text, either in the UI |
| 137 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 138 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 139 | <li> Accepts an optional SUBCOMMAND argument following the |
| 140 | COMMAND argument and only shows results for the specified |
| 141 | subcommand, not the entire command. |
| 142 | <li> The -u (--usage) option shows only the command-line syntax |
| 143 | <li> The -o (--options) option shows only the command-line options |
| @@ -153,11 +217,11 @@ | |
| 153 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 154 | <li> Show creation time in report and ticket view. |
| 155 | <li> Show previous comments in edit ticket as reference. |
| 156 | </ol> |
| 157 | <li>Added the "hash" query parameter to the |
| 158 | [/help?cmd=/whatis|/whatis webpage]. |
| 159 | <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] |
| 160 | which alerts subscribers when an admin creates a new user or |
| 161 | when a user's permissions change. |
| 162 | <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains |
| 163 | the substring "description", then the project description for each repository |
| @@ -169,44 +233,44 @@ | |
| 169 | <ol type="a"> |
| 170 | <li> TH1 now makes a distinction between |
| 171 | [/doc/trunk/www/th1.md#taint|tainted and untainted string values]. |
| 172 | This makes it more difficult to write custom TH1 scripts that |
| 173 | contain XSS or SQL-injection bugs. The |
| 174 | [/help?cmd=vuln-report|vuln-report] setting was added to control |
| 175 | what Fossil does when it encounters a potential TH1 |
| 176 | security problem. |
| 177 | <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr] |
| 178 | command. |
| 179 | <li> The "enable_htmlify" TH1 command was removed. |
| 180 | </ol> |
| 181 | <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing |
| 182 | the frequency of reconnection attempts over time and providing feedback |
| 183 | to the user when the connection is down. |
| 184 | <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged |
| 185 | in, nor are links to that page displayed to users who are not logged in. Being |
| 186 | logged in as "anonymous" is sufficient to overcome this restriction, assuming |
| 187 | that "anonymous" can download tarballs and ZIP archives. |
| 188 | <li>Many other minor fixes and additions. |
| 189 | </ol> |
| 190 | |
| 191 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 192 | |
| 193 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 194 | that have non-ASCII filenames |
| 195 | * Add the [/help?cmd=tree|fossil tree] command. |
| 196 | * On case-insensitive filesystems, store files using the filesystem's |
| 197 | preferred case rather than the case typed in by the user. |
| 198 | * Change the name "fossil cherry-pick" command to "fossil cherrypick", |
| 199 | which is more familiar to Git users. Retain the legacy name for |
| 200 | compatibility. |
| 201 | * Add new query parameters to the [/help?cmd=/timeline|/timeline page]: |
| 202 | d2=, p2=, and dp2=. |
| 203 | * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values. |
| 204 | * Add the -b|--brief option to the [/help?cmd=status|fossil status] command. |
| 205 | * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page]. |
| 206 | * Add history search to the [/help?cmd=/chat|/chat page]. |
| 207 | * Add Unix socket support to the [/help?cmd=server|server command]. |
| 208 | * On Windows, use the root certificates managed by the operating system |
| 209 | (requires OpenSSL 3.2.0 or greater). |
| 210 | * Take into account zero-width and double-width unicode characters when |
| 211 | formatting the command-line timeline. |
| 212 | * Update the built-in SQLite to version 3.47.0. Precompiled binaries are |
| @@ -243,11 +307,11 @@ | |
| 243 | </ul> |
| 244 | * If an "ssh:" sync fails in a way that suggests that the fossil executable |
| 245 | could not be found on the remote host, then retry after adding a PATH= |
| 246 | prefix to the command. This helps "ssh:" to "just work" when the server |
| 247 | is a Mac. |
| 248 | * Enhancements to the [/help?cmd=/timeline|/timeline page]: |
| 249 | <ul> |
| 250 | <li> Add the x= query paramater |
| 251 | <li> Add the shortcut tl= and rl= query parameters |
| 252 | <li> Add support for from=,ft= and from=,bt= query parameter combinations |
| 253 | <li> Automatically highlight the endpoints for from=,to= queries. |
| @@ -257,16 +321,16 @@ | |
| 257 | * Moved the /museum/repo.fossil file referenced from the Dockerfile from |
| 258 | the ENTRYPOINT to the CMD part to allow use of --repolist mode. |
| 259 | * The [/uvlist] page now shows the hash algorithm used so that |
| 260 | viewers don't have to guess. The hash is shown in a fixed-width |
| 261 | font for a more visually pleasing display. |
| 262 | * If the [/help?cmd=autosync|autosync setting] contains keyword "all", |
| 263 | the automatic sync occurs against all defined remote repositories, not |
| 264 | just the default. |
| 265 | * Markdown formatter: improved handling of indented fenced code blocks |
| 266 | that contain blank lines. |
| 267 | * When doing a "[/help?cmd=add|fossil add]" on a system with case-insensitive |
| 268 | but case-preserving filenames (Mac and Windows) try to use the filename |
| 269 | case as it is known to the filesystem, not the case entered by the |
| 270 | user on the command-line. See |
| 271 | [forum:/forumpost/30d9c0d131610f53|forum thread 30d9c0d131610f53]. |
| 272 | * Fix problems with one-click unsubscribe on email notifications. |
| @@ -280,17 +344,17 @@ | |
| 280 | <h2 id='v2_23'>Changes for version 2.23 (2023-11-01)</h2> |
| 281 | |
| 282 | * Add ability to "close" forum threads, such that unprivileged users |
| 283 | may no longer respond to them. Only administrators can close |
| 284 | threads or respond to them by default, and the |
| 285 | [/help?cmd=forum-close-policy|forum-close-policy setting] can be |
| 286 | used to add that capability to moderators. |
| 287 | * Add the [/help?cmd=all|fossil all whatis] command. |
| 288 | * The [/help?cmd=status|fossil status] command and relevant UI pages now |
| 289 | correctly report files which were both renamed <b>and</b> edited as such. |
| 290 | * Show default value of settings that have a default in |
| 291 | [/help?cmd=help|fossil help SETTING] output. |
| 292 | * On timeline graphs, show closed check-ins using an X in the middle of the |
| 293 | node circle or box. |
| 294 | * New options for email notification: Get email only for the first |
| 295 | post in each new thread, and/or posts that are in reply to my posts. |
| 296 | * Fix a regression bug introduced in version 2.22 that caused FTS5 searches |
| @@ -303,35 +367,35 @@ | |
| 303 | <li> Better defense against cross-site request forgery (CSRF) |
| 304 | attacks. |
| 305 | <li> Improvements to static analysis of source code (the codecheck1.c |
| 306 | file in the source tree). |
| 307 | </ul> |
| 308 | * Enhance the [/help?cmd=/dir|treeview file listings] |
| 309 | ([/dir?type=tree&ci=trunk|example]) by displaying file sizes |
| 310 | and adding the option to sort by file size. |
| 311 | * The [/help?cmd=fts-config|fossil fts-config] command now shows how much |
| 312 | repository space is used by the full-text index. |
| 313 | * Changing a setting to an empty string is now the same as deleting the |
| 314 | setting, in most cases. There are a few exceptions, indicated by the |
| 315 | keep-empty flag on the setting definition. |
| 316 | * The [/help?cmd=branch|fossil branch list] command can now filter branches |
| 317 | that have/have not been merged into the current branch. |
| 318 | * Improvements to interactions with remote repositories over SSH: |
| 319 | <ul> |
| 320 | <li> Print the text of the SSH command that is run to do remote interaction, |
| 321 | for full disclosure to the operator. |
| 322 | <li> Add a PATH= argument to the [/help?cmd=ui|fossil ui remote:/] and |
| 323 | [/help?cmd=patch|fossil patch push/pull remote:...] commands so that |
| 324 | they work when the "remote" machine is a Mac and the "fossil" |
| 325 | executable is in the $HOME/bin directory. |
| 326 | </ul> |
| 327 | * Update built-in libraries SQLite, ZLib, Pikchr to their latest versions. |
| 328 | * Documentation enhancements and typo fixes. |
| 329 | |
| 330 | |
| 331 | <h2 id='v2_22'>Changes for version 2.22 (2023-05-31)</h2> |
| 332 | * Enhancements to the [/help?cmd=/timeline|/timeline webpage]: <ol type="a"> |
| 333 | <li> Add the ft=TAG query parameter which in combination with d=Y |
| 334 | shows all descendants of Y up to TAG |
| 335 | <li> Enhance the s=PATTERN (search) query parameter so that forum post |
| 336 | text is also searched when the "vfx" query parameter is used |
| 337 | <li> Fix the u= (user) query parameter so that it works with a= and b= |
| @@ -355,56 +419,56 @@ | |
| 355 | searching in Chinese. |
| 356 | * Comment lines (starting with a '#') are now supported inside |
| 357 | [./settings.wiki#versionable|versioned settings]. |
| 358 | * Default permissions for anonymous users in new repositories are |
| 359 | changed to "hz". |
| 360 | * The [/help?cmd=status|fossil status] command now detects when a |
| 361 | file used to be a symlink and has been replaced by a regular file. |
| 362 | (It previously checked for the inverse case only.) |
| 363 | * The [/help?cmd=empty-dirs|empty-dirs setting] now reuses the same |
| 364 | parser as the *-glob settings instead of its prior idiosyncratic |
| 365 | parser, allowing quoted whitespace in patterns. |
| 366 | * Enhancements to the [/help?cmd=/reports|/reports webpage]: |
| 367 | <ol type="a"> |
| 368 | <li> The by-week, by-month, and by-year options now show an estimated |
| 369 | size of the current week, month, or year as a dashed box. |
| 370 | <li> New sub-categories "Merge Check-ins" and "Non-Merge Check-ins". |
| 371 | </ol> |
| 372 | |
| 373 | <h2 id='v2_21'>Changes for version 2.21 (2023-02-25)</h2> |
| 374 | * Users can request a password reset. This feature is disabled by default. |
| 375 | Use the new [/help?cmd=self-pw-reset|self-pw-reset property] to enable it. |
| 376 | New web pages [/help?cmd=/resetpw|/resetpw] and |
| 377 | [/help?cmd=/reqpwreset|/reqpwreset] added. |
| 378 | * Add the [/help?cmd=repack|fossil repack] command (together with |
| 379 | [/help?cmd=all|fossil all repack]) as a convenient way to optimize the |
| 380 | size of one or all of the repositories on a system. |
| 381 | * Add the ability to put text descriptions on ticket report formats. |
| 382 | * Upgrade the test-find-pivot command to the [/help/merge-base|merge-base command]. |
| 383 | * The [/help?cmd=/chat|/chat page] can now embed fossil-rendered |
| 384 | views of wiki/markdown/pikchr file attachments with the caveat that such |
| 385 | embedding happens in an iframe and thus does not inherit styles and such |
| 386 | from the containing browser window. |
| 387 | * The [/help?cmd=all|fossil all remote] subcommand added to "fossil all". |
| 388 | * Passwords for remembered remote repositories are now stored as irreversible |
| 389 | hashes rather than obscured clear-text, for improved security. |
| 390 | * Add the "nossl" and "nocompress" options to CGI. |
| 391 | * Update search infrastructure from FTS4 to FTS5. |
| 392 | * Add the [/help?cmd=/deltachain|/deltachain] page for debugging purposes. |
| 393 | * Writes to the database are disabled by default if the HTTP request |
| 394 | does not come from the same origin. This enhancement is a defense in depth |
| 395 | measure only; it does not address any known vulnerabilities. |
| 396 | * Improvements to automatic detection and mitigation of attacks from |
| 397 | malicious robots. |
| 398 | |
| 399 | <h2 id='v2_20'>Changes for version 2.20 (2022-11-16)</h2> |
| 400 | * Added the [/help?cmd=chat-timeline-user|chat-timeline-user setting]. If |
| 401 | it is not an empty string, then any changes that would appear on the timeline |
| 402 | are announced in [./chat.md|the chat room]. |
| 403 | * The /unsubscribe page now requests confirmation. [./alerts.md|Email notifications] |
| 404 | now contain only an "Unsubscribe" link, and not a link to subscription management. |
| 405 | * Added the "[/help?cmd=branch|fossil branch lsh]" subcommand to list the |
| 406 | most recently modified branches. |
| 407 | * More elements of the /info page are now inside of an accordion. |
| 408 | * Replace the <tt>--dryrun</tt> flag with <tt>--dry-run</tt> in all |
| 409 | commands which still used the former name, for consistency. |
| 410 | * Rebuilt [/file/Dockerfile | the stock Dockerfile] to create a "from scratch" |
| @@ -434,11 +498,11 @@ | |
| 434 | accomplished using a Common Table Expression in the underlying SQL. |
| 435 | * Sort tag listings (command line and webpage) by taking numbers into |
| 436 | consideration so as to cater for tags that follow semantic versioning. |
| 437 | * On the wiki listings, omit by default wiki pages that are associated with |
| 438 | check-ins and branches. |
| 439 | * Add the new "[/help?cmd=describe|fossil describe]" command. |
| 440 | * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support]. |
| 441 | See corresponding [../test/markdown-test3.md|test cases], |
| 442 | [/wiki?name=branch/markdown-footnotes#il|known limitations] and |
| 443 | [forum:/forumthread/ee1f1597e46ec07a|discussion]. |
| 444 | * Add the new special name "start:BRANCH" to refer to the first check-in of |
| @@ -450,96 +514,96 @@ | |
| 450 | operation. Also require explicit "system" proxy setting to use |
| 451 | "http_proxy" environment variable. |
| 452 | * Reimplemented the [/pikchrshow] app to use a WebAssembly build of |
| 453 | pikchr so that it can render pikchrs on the client instead of requiring |
| 454 | a server round-trip. |
| 455 | * Add the [/help?cmd=email-listid|email-listid setting]. If set, it is |
| 456 | used as the List-ID header for all outbound notification emails. |
| 457 | * Add the "--branch" option to the "[/help?cmd=timeline|timeline]" command |
| 458 | to restrict the displayed items to a specific branch. |
| 459 | * Add the "--versions" option to "[/help?cmd=diff|fossil diff]" |
| 460 | to display details about the compared versions into the patch header. |
| 461 | * Numerous other minor enhancements. |
| 462 | |
| 463 | <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2> |
| 464 | * Added support for [./ssl-server.md|SSL/TLS server mode] for commands |
| 465 | like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]" |
| 466 | * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for |
| 467 | [/help?cmd=merge|merge --cherrypick]. |
| 468 | * Add new setting "[/help?cmd=large-file-size|large-file-size]". If the size |
| 469 | of any file in a commit exceeds this size, a warning is issued. |
| 470 | * Query parameter "year=YYYY" is now accepted by [/help?cmd=/timeline|/timeline]. |
| 471 | * The [/help?cmd=tar|tar] and [/help?cmd=zip|zip commands] no longer |
| 472 | sterilize the manifest file. |
| 473 | * Further improvement to diff alignment in cases that involve both |
| 474 | edits and indentation changes. |
| 475 | * [/doc/trunk/www/chat.md|Chat] improvements:<ul> |
| 476 | <li> [/help?cmd=/chat|The /chat page] input options have been reworked |
| 477 | again for better cross-browser portability. |
| 478 | <li> When sending a [/help?cmd=/chat|/chat] message fails, it is no longer |
| 479 | immediately lost and sending may optionally be retried. |
| 480 | <li> [/help?cmd=/chat|/chat] can now optionally embed attachments of certain |
| 481 | types directly into message bodies via an iframe. |
| 482 | <li> Add the "--as FILENAME" option to the "[/help?cmd=chat|fossil chat send]" |
| 483 | command. |
| 484 | <li> Added the "[/help?cmd=chat|fossil chat pull]" command, available to |
| 485 | administrators only, for backing up the chat conversation. |
| 486 | </ul> |
| 487 | * Promote the test-detach command into the [/help?cmd=detach|detach command]. |
| 488 | * For "[/help?cmd=pull|fossil pull]" with the --from-parent-project option, |
| 489 | if no URL is specified then use the last URL from the most recent prior |
| 490 | "fossil pull --from-parent-project". |
| 491 | * Add options --project-name and --project-desc to the |
| 492 | "[/help?cmd=init|fossil init]" command. |
| 493 | * The [/help?cmd=/ext|/ext page] generates the SERVER_SOFTWARE environment |
| 494 | variable for clients. |
| 495 | * Fix the REQUEST_URI [/doc/trunk/www/aboutcgi.wiki#cgivar|CGI variable] such |
| 496 | that it includes the query string. This is how most other systems understand |
| 497 | REQUEST_URI. |
| 498 | * Added the --transport-command option to [/help?cmd=sync|fossil sync] |
| 499 | and similar. |
| 500 | |
| 501 | <h2 id='v2_17'>Changes for version 2.17 (2021-10-09)</h2> |
| 502 | |
| 503 | * Major improvements to the "diff" subsystem, including: <ul> |
| 504 | <li> Added new [/help?cmd=diff|formatting options]: --by, -b, --webpage, --json, --tcl. |
| 505 | <li> Partial-line matching for unified diffs |
| 506 | <li> Better partial-line matching for side-by-side diffs |
| 507 | <li> Buttons on web-based diffs to show more context |
| 508 | <li> Performance improvements |
| 509 | </ul> |
| 510 | * The --branchcolor option on [/help?cmd=commit|fossil commit] and |
| 511 | [/help?cmd=amend|fossil amend] can now take the value "auto" to |
| 512 | force Fossil to use its built-in automatic color choosing algorithm. |
| 513 | * Fossil now [./concepts.wiki#workflow|autosyncs] prior to running |
| 514 | [/help?cmd=open|fossil open]. |
| 515 | * Add the [/help?cmd=ticket-default-report|ticket-default-report setting], |
| 516 | which if set to the title of a ticket report causes that ticket report |
| 517 | to be displayed below the search box in the /ticket page. |
| 518 | * The "nc" query parameter to the [/help?cmd=/timeline|/timeline] page |
| 519 | causes all graph coloring to be omitted. |
| 520 | * Improvements and bug fixes to the new "[/help?cmd=ui|fossil ui REMOTE]" |
| 521 | feature so that it works better on a wider variety of platforms. |
| 522 | * In [/help?cmd=/wikiedit|/wikiedit], show the list of attachments for |
| 523 | the current page and list URLs suitable for pasting them into the page. |
| 524 | * Add the --no-http-compression option to [/help?cmd=sync|fossil sync] |
| 525 | and similar. |
| 526 | * Print total payload bytes on a [/help?cmd=sync|fossil sync] when using |
| 527 | the --verbose option. |
| 528 | * Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and |
| 529 | </tt>unhide</tt> subcommands to [/help?cmd=branch|the branch command]. |
| 530 | * The "-p" option to [/help?cmd=branch|fossil branch list] shows only |
| 531 | private branches. |
| 532 | * The [/md_rules|Markdown formatter] now interprets the content of |
| 533 | block HTML markup (such as <table>) in most cases. Only content |
| 534 | of <pre> and <script> is passed through verbatim. |
| 535 | * The [/help?cmd=wiki|wiki list command] no longer lists "deleted" |
| 536 | pages by default. Use the new <tt>--all</tt> option to include deleted |
| 537 | pages in the output. |
| 538 | * The [/help?cmd=all|fossil all git status] command only shows reports for |
| 539 | the subset of repositories that have a configured Git export. |
| 540 | * The [/help?cmd=/chat|/chat] configuration was reimplemented and |
| 541 | provides new options, including the ability for a repository |
| 542 | administrator to |
| 543 | [./chat.md#notifications|extend the selection of notification sounds] |
| 544 | using unversioned files. |
| 545 | * Chat now uses fossil's full complement of markdown features, |
| @@ -548,14 +612,14 @@ | |
| 548 | markdown-processed when they are sent instead of when they |
| 549 | are saved. |
| 550 | * Added a chat message preview mode so messages can be previewed |
| 551 | before being sent. Similarly, added a per-message ability to view |
| 552 | the raw un-parsed message text. |
| 553 | * The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit], |
| 554 | [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow] |
| 555 | was changed from ctrl-enter to shift-enter in order to align with |
| 556 | [/help?cmd=/chat|/chat]'s new preview feature and related future |
| 557 | changes. |
| 558 | |
| 559 | <h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2> |
| 560 | * <b>Security:</b> Fix the client-side TLS so that it verifies that the |
| 561 | server hostname matches its certificate. |
| @@ -562,11 +626,11 @@ | |
| 562 | * The default "ssh" command on Windows is changed to "ssh" instead of the |
| 563 | legacy "plink", as ssh is now generally available on Windows systems. |
| 564 | Installations that still need to use the legacy "plink" can make that |
| 565 | happen by running: '<tt>fossil set ssh-command "plink -ssh" --global</tt>'. |
| 566 | * Added the [./patchcmd.md|fossil patch] command. |
| 567 | * The [/help?cmd=ui|fossil ui] command is enhanced in multiple ways:<ol> |
| 568 | <li> The REPOSITORY argument can be the name of a check-out directory. |
| 569 | <li> If the REPOSITORY argument is prefixed by "HOST:" or "USER@HOST:" |
| 570 | then the ui is run on the remote machine and tunnelled back to the local |
| 571 | machine using ssh. (The latest version of fossil must be installed on |
| 572 | both the local and the remote for this to work correctly.) |
| @@ -575,25 +639,25 @@ | |
| 575 | * The [/brlist|/brlist web page] allows the user to |
| 576 | select multiple branches to be displayed together in a single |
| 577 | timeline. |
| 578 | * The [./forum.wiki|Forum] provides a hyperlink on the author of each |
| 579 | post that goes to a timeline of recent posts by that same author. |
| 580 | * Added the "[/help?cmd=bisect|fossil bisect run]" command for improved |
| 581 | automation of bisects. |
| 582 | * The [/help?cmd=merge|fossil merge] command now does a better job merging |
| 583 | branches where files have been renamed between the current branch and the |
| 584 | branch being merged. |
| 585 | * The [/help?cmd=open|fossil open] command allows the repository file |
| 586 | to be inside the working directory without requiring the --force flag. |
| 587 | * The [/help?cmd=/wikiedit|/wikiedit] and [/help?cmd=/wikinew|/wikinew] |
| 588 | pages now default to markdown format. |
| 589 | * The [/help?cmd=/login|/login] page now links to a user's forum post |
| 590 | timeline if the repository has forum posts. |
| 591 | * Tags may now be propagated for forum posts, wiki pages, and technotes. |
| 592 | The [/help?cmd=tag|tag command] can now manipulate and list such tags. |
| 593 | * [./caps/login-groups.md|Login-Groups] are now shown on the repository |
| 594 | list of the "[/help?cmd=all|fossil all ui]" command. |
| 595 | * Administrators can configure [./alerts.md|email alerts] to expire |
| 596 | a specific number of days (ex: 365) after the last user contact with |
| 597 | the Fossil server. This prevents alert emails being sent to |
| 598 | abandoned email accounts forever. |
| 599 | * SQL that defines [/tktsetup_tab|database objects for tickets] now |
| @@ -610,11 +674,11 @@ | |
| 610 | * <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to |
| 611 | the patch is recommended.</b> |
| 612 | * The [./defcsp.md|default CSP] has been relaxed slightly to allow |
| 613 | images to be loaded from any URL. All other resources are still |
| 614 | locked down by default. |
| 615 | * The built-in skins all use the "[/help?cmd=mainmenu|mainmenu]" |
| 616 | setting to determine the content of the main menu. |
| 617 | The ability to edit the |
| 618 | "mainmenu" setting is added on the /Admin/Configuration page. |
| 619 | * The hamburger menu is now available on most of the built-in skins. |
| 620 | * Any built-in skin named "X" can be used instead of the standard |
| @@ -624,40 +688,40 @@ | |
| 624 | included. The [/skins] page may be used to select a skin. |
| 625 | * The [/cookies] page now gives the user an opportunity to delete |
| 626 | individual cookies. And the /cookies page is linked from the |
| 627 | /sitemap, so that it appears in hamburger menus. |
| 628 | * The [/sitemap] extensions are now specified by a single new |
| 629 | "[/help?cmd=sitemap-extra|sitemap-extra setting]", |
| 630 | rather than a cluster of various |
| 631 | "sitemap-*" settings. The older settings are no longer used. |
| 632 | <b>This change might require minor server configuration |
| 633 | adjustments on servers that use /sitemap extensions.</b> |
| 634 | The /Admin/Configuration page provides the ability to edit |
| 635 | the new "sitemap-extra" setting. |
| 636 | * Added the "--ckout-alias NAME" option to |
| 637 | [/help?cmd=ui|fossil ui], [/help?cmd=server|fossil server], and |
| 638 | [/help?cmd=http|fossil http]. This option causes Fossil to |
| 639 | understand URIs of the form "/doc/NAME/..." as if they were |
| 640 | "[/help?cmd=/doc|/doc/ckout/...]", to facilitate testing of |
| 641 | [./embeddeddoc.wiki|embedded documentation] changes prior to |
| 642 | check-in. |
| 643 | * For diff web pages, if the diff type (unified versus side-by-side) |
| 644 | is not specified by a query parameter, and if the |
| 645 | "[/help?cmd=preferred-diff-type|preferred-diff-type]" |
| 646 | setting is omitted or less than 1, then select the diff type based |
| 647 | on a guess of whether or not the request is coming from a mobile |
| 648 | device. Mobile gets unified and desktop gets side-by-side. |
| 649 | * The various pages which show diffs now have toggles to show/hide |
| 650 | individual diffs. |
| 651 | * Add the "[/help?cmd=preferred-diff-type|preferred-diff-type]" |
| 652 | setting to allow an admin to force a default diff type. |
| 653 | * The "pikchr-background" setting is now available in |
| 654 | "detail.txt" skin files, for better control of Pikchr |
| 655 | colors in inverted color schemes. |
| 656 | * Add the <tt>--list</tt> option to the |
| 657 | [/help?cmd=tarball|tarball], |
| 658 | [/help?cmd=zip|zip], and [/help?cmd=sqlar|sqlar] |
| 659 | commands. |
| 660 | * The javascript used to implement the hamburger menu on the |
| 661 | default built-in skin has been made generic so that it is usable |
| 662 | by a variety of skins, and promoted to an ordinary built-in |
| 663 | javascript file. |
| @@ -665,24 +729,24 @@ | |
| 665 | "[/doc/trunk/www/th1.md#bireqjs|builtin_request_js]", |
| 666 | "[/doc/trunk/www/th1.md#capexpr|capexpr]", |
| 667 | "foreach", "lappend", and "string match" |
| 668 | * The [/help/leaves|leaves command] now shows the branch point |
| 669 | of each leaf. |
| 670 | * The [/help?cmd=add|fossil add] command refuses to add files whose |
| 671 | names are reserved by Windows (ex: "aux") unless the --allow-reserved |
| 672 | option is included. This helps prevent Unix users from accidentally |
| 673 | creating check-ins that are unreadable by Windows users. |
| 674 | * Add the "re=" query parameter to the [/help?cmd=/dir|/dir] webpage, |
| 675 | for symmetry with the [/help?cmd=/tree|/tree] page. |
| 676 | * Update the built-in SQLite to version 3.35.0. |
| 677 | * The ./configure script now has the --print-minimum-sqlite-version option |
| 678 | that prints the minimum SQLite version required by the current version |
| 679 | of Fossil. This might be used by integrators who insist on building |
| 680 | Fossil to link against the system SQLite library rather than the |
| 681 | built-in copy of SQLite, to verify that their system SQLite library |
| 682 | is recent enough. |
| 683 | * Webpage that shows [/help?cmd=/whistory|history of a wiki page] |
| 684 | gained client-side UI to help with comparison between two arbitrary |
| 685 | versions of a wiki (by the means of anchoring a "baseline" version) |
| 686 | and the ability to squeeze several sequential edits made by the same |
| 687 | user into a single "recycled" row (the latest edit in that sequence). |
| 688 | |
| @@ -702,40 +766,40 @@ | |
| 702 | version 2.14 and then later downgrade or otherwise use an earlier |
| 703 | version of Fossil, the email notification mechanism may fail |
| 704 | to send out notifications for some events, due to the missing |
| 705 | trigger. If you want to |
| 706 | permanently downgrade an installation, then you should run |
| 707 | "[/help?cmd=rebuild|fossil rebuild]" after the downgrade |
| 708 | to get email notifications working again. If you are not using |
| 709 | email notification, then the schema change will not affect you in |
| 710 | any way. |
| 711 | * <b>Schema Update Notice #2:</b> |
| 712 | This release changes how the descriptions of wiki edits are stored |
| 713 | in the EVENT table, for improved display on timelines. You must |
| 714 | run "[/help?cmd=rebuild|fossil rebuild]" to take advantage of |
| 715 | this enhancement. Everything will still work without |
| 716 | "fossil rebuild", except you will get goofy descriptions of |
| 717 | wiki updates in the timeline. |
| 718 | * Add support for [./chat.md|Fossil chat]. |
| 719 | * The "[/help?cmd=clone|fossil clone]" command is enhanced so that |
| 720 | if the repository filename is omitted, an appropriate name is derived |
| 721 | from the remote URL and the newly cloned repo is opened. This makes |
| 722 | the clone command work more like Git, thus making it easier for |
| 723 | people transitioning from Git. |
| 724 | * Added the --mainbranch option to the [/help?cmd=git|fossil git export] |
| 725 | command. |
| 726 | * Added the --format option to the |
| 727 | "[/help?cmd=timeline|fossil timeline]" command. |
| 728 | * Enhance the --numstat option on the |
| 729 | "[/help?cmd=diff|fossil diff]" command so that it shows a total |
| 730 | number of lines added and deleted and total number of files |
| 731 | modified. |
| 732 | * Add the "contact" sub-command to [/help?cmd=user|fossil user]. |
| 733 | * Added commands "[/help?cmd=all|fossil all git export]" and |
| 734 | "[/help?cmd=all|fossil all git status]". |
| 735 | * Added the "df=CHECKIN" query parameter to the |
| 736 | [/help?cmd=/timeline|/timeline page]. |
| 737 | * Improvements to the "[/sitemap]" page. Add subpages |
| 738 | [/sitemap-timeline] and [/sitemap-test]. |
| 739 | * Better text position in cylinder objects of Pikchr diagrams. |
| 740 | * New "details.txt" settings available to custom skins to better control |
| 741 | the rendering of Pikchr diagrams: |
| @@ -757,24 +821,24 @@ | |
| 757 | * Added support for [./interwiki.md|interwiki links]. |
| 758 | * Enable <del> and <ins> markup in wiki. |
| 759 | * Improvements to the Forum threading display. |
| 760 | * Added support for embedding [./pikchr.md|pikchr] |
| 761 | markup in markdown and fossil-wiki content. |
| 762 | * The new "[/help?cmd=pikchr|pikchr]" command can render |
| 763 | pikchr scripts, optionally pre-processed with |
| 764 | [/doc/trunk/www/th1.md|TH1] blocks and variables exactly like |
| 765 | site skins are. |
| 766 | * The new [/help?cmd=/pikchrshow|pikchrshow] page provides an |
| 767 | editor and previewer for pikchr markup. |
| 768 | * In [/help?cmd=/wikiedit|/wikiedit] and |
| 769 | [/help?cmd=/fileedit|/fileedit], Ctrl-Enter can now be used |
| 770 | initiate a preview and to toggle between the editor and preview |
| 771 | tabs. |
| 772 | * The <tt>/artifact</tt> and <tt>/file</tt> views, when in |
| 773 | line-number mode, now support interactive selection of a range |
| 774 | of lines to hyperlink to. |
| 775 | * Enhance the [/help?cmd=/finfo|/finfo] webpage so that when query |
| 776 | parameters identify both a filename and a checkin, the resulting |
| 777 | graph tracks the identified file across renames. |
| 778 | * The built-in SQLite is updated to an alpha of version 3.34.0, and |
| 779 | the minimum SQLite version is increased to 3.34.0 because the |
| 780 | /finfo change in the previous bullet depends on enhancements to |
| @@ -783,18 +847,18 @@ | |
| 783 | * Countless other minor refinements and documentation improvements. |
| 784 | |
| 785 | <h2 id='v2_12'>Changes for Version 2.12.1 (2020-08-20)</h2> |
| 786 | |
| 787 | * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz. |
| 788 | * Security fix in the "[/help?cmd=git|fossil git export]" command. |
| 789 | The same fix is also backported to version 2.10.1 and 2.11.1. |
| 790 | New "safety-net" features were added to prevent similar problems |
| 791 | in the future. |
| 792 | * Enhancements to the graph display for cases when there are |
| 793 | many cherry-pick merges into a single check-in. |
| 794 | [/timeline?f=2d75e87b760c0a9|Example] |
| 795 | * Enhance the [/help?cmd=open|fossil open] command with the new |
| 796 | --workdir option and the ability to accept a URL as the repository |
| 797 | name, causing the remote repository to be cloned automatically. |
| 798 | Do not allow "fossil open" to open in a non-empty working directory |
| 799 | unless the --keep option or the new --force option is used. |
| 800 | * Enhance the markdown formatter to more closely follow the |
| @@ -803,65 +867,65 @@ | |
| 803 | Underscores in the middle of identifiers (ex: fossil_printf()) |
| 804 | no longer need to be escaped. |
| 805 | * The markdown-to-html translator can prevent unsafe HTML |
| 806 | (for example: <script>) on user-contributed pages like forum and |
| 807 | tickets and wiki. The admin can adjust this behavior using |
| 808 | the [/help?cmd=safe-html|safe-html setting] on the Admin/Wiki page. |
| 809 | The default is to disallow unsafe HTML everywhere. |
| 810 | [https://fossil-scm.org/forum/forumpost/3714e6568f|Example]. |
| 811 | * Added the "collapse" and "expand" capability for long forum posts. |
| 812 | [https://fossil-scm.org/forum/forumpost/9297029862|Example] |
| 813 | * The "[/help?cmd=remote-url|fossil remote]" command now has options for |
| 814 | specifying multiple persistent remotes with symbolic names. Currently |
| 815 | only one remote can be used at a time, but that might change in the |
| 816 | future. |
| 817 | * Add the "Remember me?" checkbox on the login page. Use a session |
| 818 | cookie for the login if it is not checked. |
| 819 | * Added the experimental "[/help?cmd=hook|fossil hook]" command for |
| 820 | managing "hook scripts" that run before checkin or after a push. |
| 821 | * Enhance the [/help?cmd=revert|fossil revert] command so that it |
| 822 | is able to revert all files beneath a directory. |
| 823 | * Add the [/help?cmd=bisect|fossil bisect skip] command. |
| 824 | * Add the [/help?cmd=backup|fossil backup] command. |
| 825 | * Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked |
| 826 | check-ins in between the innermost "good" and "bad" check-ins. |
| 827 | * Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]", |
| 828 | "[/help?cmd=rm|fossil rm]", and |
| 829 | "[/help?cmd=addremove|fossil addremove]" commands. |
| 830 | * Added the "<tt>--min</tt> <i>N</i>" and "<tt>--logfile</tt> <i>FILENAME</i>" |
| 831 | flags to the [/help?cmd=backoffice|backoffice] command, as well as other |
| 832 | enhancements to make the backoffice command a viable replacement for |
| 833 | automatic backoffice. Other incremental backoffice improvements. |
| 834 | * Added the [/help?cmd=/fileedit|/fileedit page], which allows |
| 835 | editing of text files online. Requires explicit activation by |
| 836 | a setup user. |
| 837 | * Translate built-in help text into HTML for display on web pages. |
| 838 | [/help?cmd=help|Example]. |
| 839 | * On the [/help?cmd=/timeline|/timeline] webpage, the combination |
| 840 | of query parameters "p=CHECKIN" and "bt=ANCESTOR" draws all |
| 841 | ancestors of CHECKIN going back to ANCESTOR. For example, |
| 842 | [/timeline?p=202006271506&bt=version-2.11] shows all ancestors |
| 843 | of the checkin that occurred on 2020-06-27 15:06 going back to |
| 844 | the 2.11 release. |
| 845 | * Update the built-in SQLite so that the |
| 846 | "[/help?cmd=sql|fossil sql]" command supports new output |
| 847 | modes ".mode box" and ".mode json". |
| 848 | * Add the "<tt>obscure()</tt>" SQL function to the |
| 849 | "[/help?cmd=sql|fossil sql]" command. |
| 850 | * Added virtual tables "<tt>helptext</tt>" and "<tt>builtin</tt>" to |
| 851 | the "[/help?cmd=sql|fossil sql]" command, providing access to the |
| 852 | dispatch table including all help text, and the builtin data files, |
| 853 | respectively. |
| 854 | * [./delta_format.wiki|Delta compression] is now applied to forum edits. |
| 855 | * The [/help?cmd=/wikiedit|wiki editor] has been modernized and is |
| 856 | now Ajax-based. The WYSIWYG editing option for Fossil-format wiki |
| 857 | pages was removed. (Please let us know, via the site's Forum menu, |
| 858 | if that removal unduly impacts you.) This also changes the semantics |
| 859 | of the wiki "Sandbox": that pseudo-page may be freely edited but |
| 860 | no longer saved via the UI (the [/help?cmd=wiki|wiki CLI command] |
| 861 | can, though). |
| 862 | * The [/help?cmd=allow-symlinks|allow-symlinks setting] no longer |
| 863 | syncs. It must be activated individually on any clones which require |
| 864 | symlinks. |
| 865 | * Countless documentation enhancements. |
| 866 | |
| 867 | <h2 id='v2_11'>Changes for Version 2.11 (2020-05-25)</h2> |
| @@ -873,46 +937,46 @@ | |
| 873 | can now omit punctation. So, for example, "202004181942" and |
| 874 | "2020-04-18 19:42" mean the same thing. |
| 875 | * Enhance backlink processing so that it works with Markdown-formatted |
| 876 | tickets and so that it works for wiki pages. |
| 877 | Ticket [a3572c6a5b47cd5a]. |
| 878 | <ul><li> "[/help?cmd=rebuild|fossil rebuild]" is needed to |
| 879 | take full advantage of this fix. Fossil will continue |
| 880 | to work without the rebuild, but the new backlinks will be missing.</ul> |
| 881 | * The algorithm for finding the |
| 882 | [./tech_overview.wiki#configloc|location of the configuration database] |
| 883 | is enhanced to be XDG-compliant. |
| 884 | * Add a hide/show feature to |
| 885 | [./wikitheory.wiki#assocwiki|associated wiki] display on |
| 886 | check-in and branch information pages. |
| 887 | * Enhance the "[/help?cmd=info|fossil info]" command so that it |
| 888 | works with no arguments even if not within an open check-out. |
| 889 | * Many improvements to the forum and especially email notification |
| 890 | of forum posts, in response to community feedback after switching |
| 891 | SQLite support from a mailing list over to the forum. |
| 892 | * Minimum length of a self-registered user ID increased from 3 to 6 |
| 893 | characters. |
| 894 | * When the "vfx" query parameter is used on the |
| 895 | "[/help?cmd=/timeline|/timeline]" page, it causes the complete |
| 896 | text of forum posts to be displayed. |
| 897 | * Rework the "[/help?cmd=grep|fossil grep]" command to be more useful. |
| 898 | * Expose the [/help?cmd=redirect-to-https|redirect-to-https] |
| 899 | setting to the [/help?cmd=settings|settings] command. |
| 900 | * Improve support for CGI on IIS web servers. |
| 901 | * The [./serverext.wiki|/ext page] can now render index files, |
| 902 | in the same way as the embedded docs. |
| 903 | * Most commands now support the Unix-conventional "<tt>--</tt>" |
| 904 | flag to treat all following arguments as filenames |
| 905 | instead of flags. |
| 906 | * Added the [/help?cmd=mimetypes|mimetypes config setting] |
| 907 | (versionable) to enable mimetype overrides and custom definitions. |
| 908 | * Add an option on the /Admin/Timeline setup page to set a default |
| 909 | timeline style other than "Modern". |
| 910 | * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs |
| 911 | of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated |
| 912 | into the check-in hash for the document currently being viewed. |
| 913 | * Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all |
| 914 | phantom artifacts. |
| 915 | * Enhancements to phantom processing to try to reduce |
| 916 | bandwidth-using chatter about phantoms on the sync protocol. |
| 917 | * Security: Fossil now assumes that the schema of every |
| 918 | database it opens has been tampered with by an adversary and takes |
| @@ -920,23 +984,23 @@ | |
| 920 | * Security: Fossil now puts the Content-Security-Policy in the |
| 921 | HTTP reply header, in addition to also leaving it in the |
| 922 | HTML <head> section, so that it is always available, even |
| 923 | if a custom skin overrides the HTML <head> and omits |
| 924 | the CSP in the process. |
| 925 | * Output of the [/help?cmd=diff|fossil diff -y] command automatically |
| 926 | adjusts according to the terminal width. |
| 927 | * The Content-Security-Policy is now set using the |
| 928 | [/help?cmd=default-csp|default-csp setting]. |
| 929 | * Merge conflicts caused via the [/help?cmd=merge|merge] and |
| 930 | [/help?cmd=update|update] commands no longer leave temporary |
| 931 | files behind unless the new <tt>--keep-merge-files</tt> flag |
| 932 | is used. |
| 933 | * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible |
| 934 | to all users if the new "artifact_stats_enable" setting is turned |
| 935 | on. There is a new checkbox under the /Admin/Access menu to turn |
| 936 | that capability on and off. |
| 937 | * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing |
| 938 | the TLS configuration and the list of SSL Cert exceptions. |
| 939 | * Captchas all include a button to read the captcha using an audio |
| 940 | file, so that they can be completed by the visually impaired. |
| 941 | * Stop using the IP address as part of the login cookie. |
| 942 | * Bug fix: fix the SSL cert validation logic so that if an exception |
| @@ -957,27 +1021,27 @@ | |
| 957 | <h2 id='v2_10'>Changes for Version 2.10 (2019-10-04)</h2> |
| 958 | |
| 959 | * (2.10.2): backport security fixes from 2.12.1 |
| 960 | * (2.10.1): backport security fix for the "fossil git export" command. |
| 961 | * Added support for [./serverext.wiki|CGI-based Server Extensions]. |
| 962 | * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to |
| 963 | add style to repository list pages. |
| 964 | * Enhance the hierarchical display of Forum threads to do less |
| 965 | indentation and to provide links back to the previous message |
| 966 | in the thread. Provide sequential numbers for all messages in |
| 967 | a forum thread. |
| 968 | * Add support for fenced code blocks and improved hyperlink |
| 969 | processing to the [/md_rules|markdown formatter]. |
| 970 | * Add support for hyperlinks to wiki pages in the |
| 971 | [/md_rules|markdown formatter]. |
| 972 | * Enhance the [/help?cmd=/stat|/stat] page so that it gives the |
| 973 | option to show a breakdown of forum posts. |
| 974 | * The special check-in name "merge-in:BRANCH" means the source of |
| 975 | the most recent merge-in from the parent branch of BRANCH. |
| 976 | * Add hyperlinks to branch-diffs on the /info page and from |
| 977 | timelines of a branch. |
| 978 | * Add graphical context on the [/help?cmd=/vdiff|/vdiff] page. |
| 979 | * Uppercase query parameters, POST parameters, and cookie names are |
| 980 | converted to all lowercase and entered into the parameter set, |
| 981 | instead of being discarded. |
| 982 | * Change the default [./hashpolicy.wiki|hash policy] to SHA3. |
| 983 | * Timeout [./server/any/cgi.md|CGI requests] after 300 seconds, or |
| @@ -990,14 +1054,14 @@ | |
| 990 | * Performance optimizations. |
| 991 | * Many documentation improvements. |
| 992 | |
| 993 | <h2 id='v2_9'>Changes for Version 2.9 (2019-07-13)</h2> |
| 994 | |
| 995 | * Added the [/help?cmd=git|fossil git export] command and instructions |
| 996 | for [./mirrortogithub.md|creating a GitHub mirror of a Fossil project]. |
| 997 | * Improved handling of relative hyperlinks on the |
| 998 | [/help?cmd=/artifact|/artifact] pages for wiki. For example, |
| 999 | hyperlinks and the lizard <img> now work correctly |
| 1000 | for both [/artifact/2ff24ab0887cf522] and |
| 1001 | [/doc/0d7ac90d575004c2415/www/index.wiki]. |
| 1002 | * Enhancements to the timeline graph layout, to show more information |
| 1003 | with less clutter. |
| @@ -1006,28 +1070,28 @@ | |
| 1006 | configuration. |
| 1007 | * Copy buttons added to various check-in hash and branch name links. |
| 1008 | * Double-clicking on a /timeline graph node now jumps to the /info page |
| 1009 | for the check-in. So, repurpose the timestamp hyperlink to show all |
| 1010 | activity around that check-in in time. |
| 1011 | * Added the [/help?cmd=touch|fossil touch] command, and the --setmtime |
| 1012 | option on the [/help?cmd=open|fossil open] and |
| 1013 | [/help?cmd=update|fossil update] commands. |
| 1014 | * Many documentation enhancements. |
| 1015 | * For the "[/help?cmd=update|fossil update]" and |
| 1016 | "[/help?cmd=checkout|fossil checkout]" commands, if a |
| 1017 | managed file is removed because it is no longer part of the target |
| 1018 | check-in and the directory containing the file is empty after the |
| 1019 | file is removed and the directory is not the current working |
| 1020 | directory and is not on the [/help?cmd=empty-dirs|empty-dirs] |
| 1021 | list, then also remove the directory. |
| 1022 | * Update internal Unicode character tables, used in regular expression |
| 1023 | handling, from version 11.0 to 12.1. |
| 1024 | * In "[/help?cmd=regexp|fossil regexp]", "[/help?cmd=grep|fossil grep]" |
| 1025 | and the TH1 "regexp" command, the -nocase option now removes multiple |
| 1026 | diacritics from the same character (derived from SQLite's |
| 1027 | remove_diacritics=2) |
| 1028 | * Added the [/help?cmd=/secureraw|/secureraw] page that requires the |
| 1029 | complete SHA1 or SHA3 hash, not just a prefix, before it will deliver |
| 1030 | content. |
| 1031 | * Accept purely numeric ISO8601 date/time strings as long as they |
| 1032 | do not conflict with a hash. Example: "20190510134217" instead of |
| 1033 | "2019-05-10 13:42:17". This helps keep URLs shorter and less |
| @@ -1040,19 +1104,19 @@ | |
| 1040 | extra path element is not needed. |
| 1041 | * If an automatic sync gets a permanent redirect request, then update |
| 1042 | the saved remote URL to the new address. |
| 1043 | * Temporary filenames (for example used for external "diff" commands) |
| 1044 | try to preserve the suffix of the original file. |
| 1045 | * Added the [/help?cmd=/thisdayinhistory|/thisdayinhistory] web page. |
| 1046 | * Enhanced parsing of [/help?cmd=/timeline|/timeline] query parameters |
| 1047 | "ymd=", "ym=", and "yw=". All arguments are option (in which case they |
| 1048 | default to the current time) and all accept ISO8601 date/times without |
| 1049 | punctuation. |
| 1050 | * Automatically disapprove pending moderation requests for a user when |
| 1051 | that user is deleted. This helps in dealing with spam-bots. |
| 1052 | * Improvements to the "Capability Summary" section in the |
| 1053 | [/help?cmd=/secaudit0|Security Audit] web-page. |
| 1054 | * Use new "ci-lock" and "ci-lock-failed" pragmas in the |
| 1055 | [./sync.wiki|sync protocol] to try to prevent accident forks |
| 1056 | caused by concurrent commits when operating in auto-sync mode. |
| 1057 | * Fix a bug ([https://fossil-scm.org/forum/forumpost/c51b9a1169|details]) |
| 1058 | that can cause repository databases to be overwritten with debugging |
| @@ -1153,11 +1217,11 @@ | |
| 1153 | included in the header or footer. |
| 1154 | * Add the [./backoffice.md|backoffice]. |
| 1155 | * Update internal Unicode character tables, used in regular expression |
| 1156 | handling, from version 10.0 to 11.0. |
| 1157 | * Improvements to the "Security Audit" administration page |
| 1158 | * Add the [/help?cmd=branch|fossil branch current] command. |
| 1159 | * Add the [./grep.md|grep] command. |
| 1160 | * Update the built-in SQLite to version 3.25.1. |
| 1161 | * Some code and interfaces are in place to support sending and |
| 1162 | receiving email directly via SMTP, but this feature is not yet |
| 1163 | complete or ready for production use. |
| @@ -1176,20 +1240,20 @@ | |
| 1176 | same as "Verbose" in the previous release. The "Verbose" mode is |
| 1177 | now like "Compact" except the extra check-in details are shown by |
| 1178 | default. |
| 1179 | * Add support for ETags:, Last-Modified:, and If-Modified-Since: |
| 1180 | cache control mechanisms. |
| 1181 | * Enhance the [/help?cmd=/tarball|/tarball], |
| 1182 | [/help?cmd=/zip|/zip], and |
| 1183 | [/help?cmd=/sqlar|/sqlar] pages so that the checkin |
| 1184 | name to be downloaded can be expressed as part of the URI, |
| 1185 | and without the need for query parameters. |
| 1186 | * On the [/help?cmd=/timeline|/timeline] webpage, add the days=N |
| 1187 | query parameter and enhance the ymd=DATE and yw=DATE query parameters |
| 1188 | to accept 'now' as an argument to show the latest day or week. |
| 1189 | * In the web page that comes up in response to the |
| 1190 | [/help?cmd=all|fossil all ui] command, show the last modification |
| 1191 | time for each repository, and allow click-to-sort on the modification |
| 1192 | time column. |
| 1193 | * In the tarball cache replacement algorithm, give extra weight to |
| 1194 | tarballs that have been accessed more than once. |
| 1195 | * Additional defenses against web-based attacks. There have not been |
| @@ -1236,35 +1300,35 @@ | |
| 1236 | to define their own URLs on the web interface that are rewritten to |
| 1237 | built-in URLs with specific parameters. Create and configure URL Aliases |
| 1238 | using the /Setup/URL_Aliases menu option in the web interface. |
| 1239 | * Add tech-note search capability. |
| 1240 | * Add the -r|--revision and -o|--origin options to the |
| 1241 | [/help?cmd=annotate|annotate] command. |
| 1242 | * Add the origin= query parameter to the [/help?cmd=/annotate|/annotate] |
| 1243 | webpage. |
| 1244 | * The [/help?cmd=annotate|fossil annotate] command and the |
| 1245 | [/help?cmd=/annotate|/annotate] web page go backwards in time as far |
| 1246 | as can be computed in 30 milliseconds by default, rather than stopping |
| 1247 | after 20 steps. The new limit= query parameter or the --limit command-line |
| 1248 | option can be used to alter this timeout. |
| 1249 | * Provide separate [/help#settings|on-line help screens for each setting]. |
| 1250 | * Back out support for the --no-dir-symlinks option |
| 1251 | * Remove support from the legacy configuration sync protocol. The only |
| 1252 | way now to do a configuration push or pull is to use the new protocol that |
| 1253 | was added in 2011. |
| 1254 | * Add the from= and to= query parameters to [/help?cmd=/fdiff|/fdiff] |
| 1255 | in order to get a diff of two files in the same check-in. |
| 1256 | * Fix the "ssh://" protocol to prevent an attack whereby the attacker convinces |
| 1257 | a victim to run a "clone" with a dodgy URL and thereby gains access to their |
| 1258 | system. |
| 1259 | * Provide a checkbox that will temporarily disable all ad-units. |
| 1260 | * Improvements to the [/help?cmd=/stat|/stat] page |
| 1261 | * Various new hyperlinks to the [/help?cmd=/bloblist|/bloblist] |
| 1262 | and [/help?cmd=/bigbloblist|/bigbloblist] pages. |
| 1263 | * Correct the [/help?cmd=/doc|/doc] page to support read-only repositories. |
| 1264 | * Correct [/help?cmd=/zip|/zip], [/help?cmd=/tarball|/tarball], |
| 1265 | [/help?cmd=zip|zip], and [/help?cmd=tarball|tarball] pages and commands to |
| 1266 | honor the versioned manifest setting when outside of an open checkout |
| 1267 | directory. |
| 1268 | * The admin-log and access-log settings are now on by default for |
| 1269 | new repositories. |
| 1270 | * Update the built-in SQLite to version 3.21.0. |
| @@ -1272,34 +1336,34 @@ | |
| 1272 | <h2 id='v2_3'>Changes for Version 2.3 (2017-07-21)</h2> |
| 1273 | |
| 1274 | * Update the built-in SQLite to version 3.20.0 (beta). |
| 1275 | * Update internal Unicode character tables, used in regular expression |
| 1276 | handling, from version 9.0 to 10.0. |
| 1277 | * Show the last-sync-URL on the [/help?cmd=/urllist|/urllist] page. |
| 1278 | * Added the "Event Summary" activity report. |
| 1279 | [/reports?type=ci&view=lastchng|example] |
| 1280 | * Added the "Security Audit" page, available to administrators only |
| 1281 | * Added the Last Login time to the user list page, for administrators only |
| 1282 | * Added the --numstat option to the [/help?cmd=diff|fossil diff] command |
| 1283 | * Limit the size of the heap and stack on unix systems, as a proactive |
| 1284 | defense against the |
| 1285 | [https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt|Stack Clash] |
| 1286 | attack. |
| 1287 | * Fix "database locked" warnings caused by "PRAGMA optimize". |
| 1288 | * Fix a potential XSS vulnerability on the |
| 1289 | [/help?cmd=/help|/help] webpage. |
| 1290 | * Documentation updates |
| 1291 | |
| 1292 | <h2 id='v2_2'>Changes for Version 2.2 (2017-04-11)</h2> |
| 1293 | |
| 1294 | * GIT comment tags are now handled by Fossil during import/export. |
| 1295 | * Show the content of README files on directory listings. |
| 1296 | ([/file/skins|example]) |
| 1297 | * Support for Basic Authentication if enabled (default off). |
| 1298 | * Show the hash algorithms used on the |
| 1299 | [/help?cmd=/rcvfromlist|/rcvfromlist] page. |
| 1300 | * The [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] pages |
| 1301 | now use the the r= query parameter |
| 1302 | to select which check-in to deliver. The uuid= query parameter |
| 1303 | is still accepted for backwards compatibility. |
| 1304 | * Update the built-in SQLite to version 3.18.0. |
| 1305 | * Run "[https://www.sqlite.org/pragma.html#pragma_optimize|PRAGMA optimize]" |
| @@ -1308,12 +1372,12 @@ | |
| 1308 | <h2 id='v2_1'>Changes for Version 2.1 (2017-03-10)</h2> |
| 1309 | |
| 1310 | * Add support for [./hashpolicy.wiki|hash policies] that control which |
| 1311 | of the Hardened-SHA1 or SHA3-256 algorithms is used to name new |
| 1312 | artifacts. |
| 1313 | * Add the "gshow" and "gcat" subcommands to [/help?cmd=stash|fossil stash]. |
| 1314 | * Add the [/help?cmd=/juvlist|/juvlist] web page and use it to construct |
| 1315 | the [/uv/download.html|Download Page] of the Fossil self-hosting website |
| 1316 | using Ajax. |
| 1317 | |
| 1318 | <h2 id='v2_0'>Changes for Version 2.0 (2017-03-03)</h2> |
| 1319 | |
| @@ -1321,23 +1385,23 @@ | |
| 1321 | [https://github.com/cr-marcstevens/sha1collisiondetection|hardened SHA1] |
| 1322 | implementation by Marc Stevens and Dan Shumow. |
| 1323 | * Add the ability to read and understand |
| 1324 | [./fileformat.wiki#names|artifact names] that are based on SHA3-256 |
| 1325 | rather than SHA1, but do not actually generate any such names. |
| 1326 | * Added the [/help?cmd=sha3sum|sha3sum] command. |
| 1327 | * Update the built-in SQLite to version 3.17.0. |
| 1328 | |
| 1329 | <h2 id='v1_37'>Changes for Version 1.37 (2017-01-16)</h2> |
| 1330 | |
| 1331 | * Add checkbox widgets to various web pages. See [/technote/8d18bf27e9| |
| 1332 | this technote] for more information. To get the checkboxes to look as |
| 1333 | intended, you must update the CSS in your repository and all clones. |
| 1334 | * Add the [/help/all|fossil all ui] command |
| 1335 | * Add the [/help?cmd=/file|/file] webpage |
| 1336 | * Enhance the [/help?cmd=/brlist|/brlist] webpage to make use of branch colors. |
| 1337 | * Add support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the |
| 1338 | [/help?cmd=/timeline|/timeline] webpage, with associated form widgets. |
| 1339 | * Enhance the [/help/changes|changes] and [/help/status|status] commands |
| 1340 | with many new filter options so that specific kinds of changes can be |
| 1341 | found without having to pipe through grep or sed. |
| 1342 | * Enhanced the [/help/sqlite3|fossil sql] command so that it opens the |
| 1343 | [./tech_overview.wiki#localdb|checkout database] and the |
| @@ -1350,11 +1414,11 @@ | |
| 1350 | </ul> |
| 1351 | * Rename crnl-glob [/help/settings|setting] to crlf-glob, but keep |
| 1352 | crnl-glob as a compatibility alias. |
| 1353 | * Added the --command option to the [/help/diff|diff] command. |
| 1354 | * Fix a C99-ism that prevents the 1.36 release from building with MSVC. |
| 1355 | * Fix [/help?cmd=ticket|ticket set] when using the "+" prefix with fields |
| 1356 | from the "ticketchng" table. |
| 1357 | * Remove the "fusefs" command from builds that do not have the underlying |
| 1358 | support enabled. |
| 1359 | * Fixes for incremental git import/export. |
| 1360 | * Minor security enhancements to |
| @@ -1364,58 +1428,58 @@ | |
| 1364 | |
| 1365 | |
| 1366 | <h2 id='v1_36'>Changes for Version 1.36 (2016-10-24)</h2> |
| 1367 | |
| 1368 | * Add support for [./unvers.wiki|unversioned content], |
| 1369 | the [/help?cmd=unversioned|fossil unversioned] command and the |
| 1370 | [/help?cmd=/uv|/uv] and [/uvlist] web pages. |
| 1371 | * The [/uv/download.html|download page] is moved into |
| 1372 | [./unvers.wiki|unversioned content] so that the self-hosting Fossil |
| 1373 | websites no longer uses any external content. |
| 1374 | * Added the "Search" button to the graphical diff generated by |
| 1375 | the --tk option on the [/help?cmd=diff|diff] command. |
| 1376 | * Added the "--checkin VERSION" option to the |
| 1377 | [/help?cmd=diff|diff] command. |
| 1378 | * Various performance enhancements to the [/help?cmd=diff|diff] command. |
| 1379 | * Update internal Unicode character tables, used in regular expression |
| 1380 | handling, from version 8.0 to 9.0. |
| 1381 | * Update the built-in SQLite to version 3.15. Fossil now requires |
| 1382 | the SQLITE_DBCONFIG_MAINDBNAME interface of SQLite which is only available |
| 1383 | in SQLite version 3.15 and later and so Fossil will not work with |
| 1384 | earlier SQLite versions. |
| 1385 | * Fix [https://www.mail-archive.com/[email protected]/msg23618.html|multi-line timeline bug] |
| 1386 | * Enhance the [/help?cmd=purge|fossil purge] command. |
| 1387 | * New command [/help?cmd=shell|fossil shell]. |
| 1388 | * SQL parameters whose names are all lower-case in Ticket Report SQL |
| 1389 | queries are filled in using HTTP query parameter values. |
| 1390 | * Added support for [./childprojects.wiki|child projects] that are |
| 1391 | able to pull from their parent but not push. |
| 1392 | * Added the -nocomplain option to the TH1 "query" command. |
| 1393 | * Added support for the chng=GLOBLIST query parameter on the |
| 1394 | [/help?cmd=/timeline|/timeline] webpage. |
| 1395 | |
| 1396 | <h2 id='v1_35'>Changes for Version 1.35 (2016-06-14)</h2> |
| 1397 | |
| 1398 | * Enable symlinks by default on all non-Windows platforms. |
| 1399 | * Enhance the [/md_rules|Markdown formatting] so that hyperlinks that begin |
| 1400 | with "/" are relative to the root of the Fossil repository. |
| 1401 | * Rework the [/help?cmd=/setup_ulist|/setup_list page] (the User List page) |
| 1402 | to display all users in a click-to-sort table. |
| 1403 | * Fix backslash-octal escape on filenames while importing from git |
| 1404 | * When markdown documents begin with <h1> HTML elements, use that |
| 1405 | header at the document title. |
| 1406 | * Added the [/help?cmd=/bigbloblist|/bigbloblist page]. |
| 1407 | * Enhance the [/help?cmd=/finfo|/finfo page] so that when it is showing |
| 1408 | the ancestors of a particular file version, it only shows direct |
| 1409 | ancestors and omits changes on branches, thus making it show the same set |
| 1410 | of ancestors that are used for [/help?cmd=/blame|/blame]. |
| 1411 | * Added the --page option to the [/help?cmd=ui|fossil ui] command |
| 1412 | * Added the [/help?cmd=bisect|fossil bisect ui] command |
| 1413 | * Enhanced the [/help?cmd=diff|fossil diff] command so that it accepts |
| 1414 | directory names as arguments and computes diffs on all files contained |
| 1415 | within those directories. |
| 1416 | * Fix the [/help?cmd=add|fossil add] command so that it shows "SKIP" for |
| 1417 | files added that were already under management. |
| 1418 | * TH1 enhancements: |
| 1419 | <ul><li>Add <nowiki>[array exists]</nowiki> command.</li> |
| 1420 | <li>Add minimal <nowiki>[array names]</nowiki> command.</li> |
| 1421 | <li>Add tcl_platform(engine) and tcl_platform(platform) array |
| @@ -1423,32 +1487,32 @@ | |
| 1423 | </ul> |
| 1424 | * Get autosetup working with MinGW. |
| 1425 | * Fix autosetup detection of zlib in the source tree. |
| 1426 | * Added autosetup detection of OpenSSL when it may be present under the |
| 1427 | "compat" subdirectory of the source tree. |
| 1428 | * Added the [/help?cmd=reparent|fossil reparent] command |
| 1429 | * Added --include and --exclude options to [/help?cmd=tarball|fossil tarball] |
| 1430 | and [/help?cmd=zip|fossil zip] and the in= and ex= query parameters to the |
| 1431 | [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] web pages. |
| 1432 | * Add support for [./encryptedrepos.wiki|encrypted Fossil repositories]. |
| 1433 | * If the FOSSIL_PWREADER environment variable is set, then use the program it |
| 1434 | names in place of getpass() to read passwords and passphrases |
| 1435 | * Option --baseurl now works on Windows. |
| 1436 | * Numerous documentation improvements. |
| 1437 | * Update the built-in SQLite to version 3.13.0. |
| 1438 | |
| 1439 | <h2 id='v1_34'>Changes for Version 1.34 (2015-11-02)</h2> |
| 1440 | |
| 1441 | * Make the [/help?cmd=clean|fossil clean] command undoable for files less |
| 1442 | than 10MiB. |
| 1443 | * Update internal Unicode character tables, used in regular expression |
| 1444 | handling, from version 7.0 to 8.0. |
| 1445 | * Add the new [/help?cmd=amend|amend] command which is used to modify |
| 1446 | tags of a "check-in". |
| 1447 | * Fix bug in [/help?cmd=import|import] command, handling version 3 of |
| 1448 | the svndump format for subversion. |
| 1449 | * Add the [/help?cmd=all|all cache] command. |
| 1450 | * TH1 enhancements: |
| 1451 | <ul><li>Add minimal <nowiki>[lsearch]</nowiki> command. Only exact |
| 1452 | case-sensitive matching is supported.</li> |
| 1453 | <li>Add the <nowiki>[glob_match]</nowiki>, <nowiki>[markdown]</nowiki>, |
| 1454 | <nowiki>[dir]</nowiki>, and <nowiki>[encode64]</nowiki> commands.</li> |
| @@ -1455,106 +1519,106 @@ | |
| 1455 | <li>Add the <nowiki>[tclIsSafe] and [tclMakeSafe]</nowiki> commands to |
| 1456 | the Tcl integration subsystem.</li> |
| 1457 | <li>Add 'double', 'integer', and 'list' classes to the |
| 1458 | <nowiki>[string is]</nowiki> command.</li> |
| 1459 | </ul> |
| 1460 | * Add the --undo option to the [/help?cmd=diff|diff] command. |
| 1461 | * Build-in Antirez's "linenoise" command-line editing library for use with |
| 1462 | the [/help?cmd=sqlite3|fossil sql] command on Unix platforms. |
| 1463 | * Add [/help?cmd=stash|stash cat] as an alias for the |
| 1464 | [/help?cmd=stash|stash show] command. |
| 1465 | * Automatically pull before [/help?cmd=merge|fossil merge] when auto-sync |
| 1466 | is enabled. |
| 1467 | * Fix --hard option to [/help?cmd=mv|fossil mv] and [/help?cmd=rm|fossil rm] |
| 1468 | to enable them to work properly with certain relative paths. |
| 1469 | * Change the mimetype for ".n" and ".man" files to text/plain. |
| 1470 | * Display improvements in the [/help?cmd=bisect|fossil bisect chart] command. |
| 1471 | * Updated the built-in SQLite to version 3.9.1 and activated JSON1 and FTS5 |
| 1472 | support (both currently unused within Fossil). |
| 1473 | |
| 1474 | <h2 id='v1_33'>Changes for Version 1.33 (2015-05-23)</h2> |
| 1475 | * Improved fork detection on [/help?cmd=update|fossil update], |
| 1476 | [/help?cmd=status|fossil status] and related commands. |
| 1477 | * Change the default skin to what used to be called "San Francisco Modern". |
| 1478 | * Add the [/repo-tabsize] web page |
| 1479 | * Add [/help?cmd=import|fossil import --svn], for importing a subversion |
| 1480 | repository into fossil which was exported using "svnadmin dump". |
| 1481 | * Add the "--compress-only" option to [/help?cmd=rebuild|fossil rebuild]. |
| 1482 | * Use a pie chart on the [/reports?view=byuser] page. |
| 1483 | * Enhanced [/help?cmd=clean|fossil clean --verily] so that it ignores |
| 1484 | keep-glob and ignore-glob settings. Added the -x alias for --verily. |
| 1485 | * Add the --soft and --hard options to [/help?cmd=rm|fossil rm] and |
| 1486 | [/help?cmd=mv|fossil mv]. The default is still --soft, but that is |
| 1487 | now configurable at compile-time or by the mv-rm-files setting. |
| 1488 | * Improved ability to [./customgraph.md|customize the timeline graph]. |
| 1489 | * Improvements to the [/sitemap] page. |
| 1490 | * Automatically adjust the [/help?cmd=timeline|CLI timeline] to the terminal |
| 1491 | width on Linux. |
| 1492 | * Added <nowiki>[info commands] and [info vars]</nowiki> commands to TH1. |
| 1493 | These commands perform the same function as their Tcl counterparts, |
| 1494 | except they do not accept a pattern argument. |
| 1495 | * Fix some obscure issues with TH1 expression processing. |
| 1496 | * Fix titles in search results for documents that are not wiki, markdown, |
| 1497 | or HTML. |
| 1498 | * Formally translate TH1 to Tcl return codes and vice-versa, where |
| 1499 | necessary, in the Tcl integration subsystem. |
| 1500 | * Add [/help?cmd=leaves|fossil leaves -multiple], for finding multiple |
| 1501 | leaves on the same branch. |
| 1502 | * Added the "Blitz" skin option. |
| 1503 | * Removed the ".fossil-settings/keep-glob" file. It should not have been |
| 1504 | checked into the repository. |
| 1505 | * Update the built-in SQLite to version 3.8.10.2. |
| 1506 | * Make [/help?cmd=open|fossil open] honor ".fossil-settings/allow-symlinks". |
| 1507 | * Allow [/help?cmd=add|fossil add] to be used on symlinks to nonexistent or |
| 1508 | unreadable files in the same way as [/help?cmd=addremove|fossil addremove]. |
| 1509 | * Added fork warning to be issued if sync produced a fork |
| 1510 | * Update the [/help?cmd=/info|info] page to report when a file becomes a |
| 1511 | symlink. Additionally show the UUID for files whose types have changed |
| 1512 | without changing contents or symlink target. |
| 1513 | * Have [/help?cmd=changes|fossil changes] and |
| 1514 | [/help?cmd=status|fossil status] report when executable or symlink status |
| 1515 | changes on otherwise unmodified files. |
| 1516 | * Permit filtering weekday and file [/help?cmd=/reports|reports] by user. |
| 1517 | Also ensure the user parameter is preserved when changing types. Add a |
| 1518 | field for direct entry of the user name to each applicable report. |
| 1519 | * Create parent directories of [/help?cmd=settings|empty-dirs] if they don't |
| 1520 | already exist. |
| 1521 | * Inhibit timeline links to wiki pages that have been deleted. |
| 1522 | |
| 1523 | <h2 id='v1_33'>Changes for Version 1.32 (2015-03-14)</h2> |
| 1524 | * When creating a new repository using [/help?cmd=init|fossil init], ensure |
| 1525 | that the new repository is fully compatible with historical versions of |
| 1526 | Fossil by having a valid manifest as RID 1. |
| 1527 | * Anti-aliased rendering of arrowheads on timeline graphs. |
| 1528 | * Added vi/less-style key bindings to the --tk diff GUI. |
| 1529 | * Documentation updates to fix spellings and changes all "checkins" to |
| 1530 | "check-ins". |
| 1531 | * Add the --repolist option to server commands such as |
| 1532 | [/help?cmd=server|fossil server] or [/help?cmd=http|fossil http]. |
| 1533 | * Added the "Xekri" skin. |
| 1534 | * Enhance the "ln=" query parameter on artifact displays to accept multiple |
| 1535 | ranges, separate by spaces (or "+" when URL-encoded). |
| 1536 | * Added [/help?cmd=forget|fossil forget] as an alias for |
| 1537 | [/help?cmd=rm|fossil rm]. |
| 1538 | |
| 1539 | <h2 id='v1_31'>Changes For Version 1.31 (2015-02-23)</h2> |
| 1540 | * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID |
| 1541 | columns to the schema, to support better drawing of file change graphs. |
| 1542 | A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required. |
| 1543 | so that the new graph drawing logic can work effectively. |
| 1544 | * Added [/search|search] over Check-in comments, Documents, Tickets and |
| 1545 | Wiki. Disabled by default. The search can be either a full-scan or it |
| 1546 | can use an index that is kept up-to-date automatically. The new |
| 1547 | /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command |
| 1548 | were added to help configure the search capability. Expect further |
| 1549 | enhancements to the search capabilities in subsequent releases. |
| 1550 | * Added form elements to some submenus (in particular the /timeline) |
| 1551 | for easier operation. |
| 1552 | * Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild]. |
| 1553 | * Added "override skins" using the "skin:" line of the CGI script or |
| 1554 | using the --skin LABEL option on the [/help?cmd=server|server], |
| 1555 | [/help?cmd=ui|ui], or [/help?cmd=http|http] commands. |
| 1556 | * Embedded html documents that begin with |
| 1557 | <doc class="fossil-doc"> are displayed with standard |
| 1558 | headers and footers added. |
| 1559 | * Allow <div style='...'> markup in [/wiki_rules|wiki]. |
| 1560 | * Renamed "Events" to "Technical Notes", while updating the technote |
| @@ -1561,24 +1625,24 @@ | |
| 1561 | display and control pages. Add support for technotes as plain text |
| 1562 | or as Markdown. |
| 1563 | * Added the [/md_rules] pages containing summary instructions on the |
| 1564 | Markdown format. |
| 1565 | * Added the --repolist and --nojail options to the various server commands |
| 1566 | (ex: [/help?cmd=server|fossil server]). |
| 1567 | * Added the [/help?cmd=all|fossil all add] subcommand to "fossil all". |
| 1568 | * Improvements to the /login page. Some hyperlinks to pages that require |
| 1569 | "anonymous" privileges are displayed even if the current user is "nobody" |
| 1570 | but automatically redirect to /login. |
| 1571 | * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file |
| 1572 | "404.md" from the top-level directory (if such a file exists) in |
| 1573 | place of its built-in 404 text. |
| 1574 | * Download of Tarballs and ZIP Archives by user "nobody" is now enabled |
| 1575 | by default in new repositories. |
| 1576 | * Enhancements to the table sorting controls. More display tables |
| 1577 | are now sortable. |
| 1578 | * Add IPv6 support to [/help?cmd=sync|fossil sync] and |
| 1579 | [/help?cmd=clone|fossil clone] |
| 1580 | * Add more skins such as "San Francisco Modern" and "Eagle". |
| 1581 | * During shutdown, check to see if the check-out database (".fslckout") |
| 1582 | contains a lot of free space, and if it does, VACUUM it. |
| 1583 | * Added the [/mimetype_list] page. |
| 1584 | * Added the [/hash-collisions] page. |
| @@ -1586,14 +1650,14 @@ | |
| 1586 | ticket reports. |
| 1587 | * Break out the components (css, footer, and header) for the |
| 1588 | various built-in skins into separate files in the source tree. |
| 1589 | |
| 1590 | <h2 id='v1_30'>Changes For Version 1.30 (2015-01-19)</h2> |
| 1591 | * Added the [/help?cmd=bundle|fossil bundle] command. |
| 1592 | * Added the [/help?cmd=purge|fossil purge] command. |
| 1593 | * Added the [/help?cmd=publish|fossil publish] command. |
| 1594 | * Added the [/help?cmd=unpublished|fossil unpublished] command. |
| 1595 | * Enhance the [/tree] webpage to show the age of each file with the option |
| 1596 | to sort by age. |
| 1597 | * Enhance the [/brlist] webpage to show additional information about each branch |
| 1598 | and to be sortable by clicking on column headers. |
| 1599 | * Add support for Docker. Just install docker and type |
| @@ -1603,17 +1667,17 @@ | |
| 1603 | copies of all historical check-ins. This only works on systems that |
| 1604 | support FuseFS. |
| 1605 | * Add the administrative log that records all configuration. |
| 1606 | * Added the [/sitemap] webpage. |
| 1607 | * Added the [/bloblist] web page. |
| 1608 | * Let [/help?cmd=new|fossil new] no longer create an initial empty commit |
| 1609 | by default. The first commit after checking out an empty repository will |
| 1610 | become the initial commit. |
| 1611 | * Added the [/help?cmd=all|fossil all dbstat] and |
| 1612 | [/help?cmd=all|fossil all info] commands. |
| 1613 | * Update SQLite to version 3.8.8. |
| 1614 | * Added the --verily option to the [/help?cmd=clean|fossil clean] command. |
| 1615 | * Add the "autosync-tries" setting to control the number of autosync attempts |
| 1616 | before returning an error. |
| 1617 | * Added a compile-time option (--with-miniz) to build using miniz instead |
| 1618 | of zlib. Disabled by default. |
| 1619 | * Support customization of commands and webpages, including the ability to |
| @@ -1623,12 +1687,12 @@ | |
| 1623 | [trace], [getParameter], [setParameter], [artifact], and |
| 1624 | [globalState]</nowiki> commands to TH1, primarily for use by TH1 hooks. |
| 1625 | * Automatically adjust the width of command-line timeline output according to the |
| 1626 | detected width of the terminal. |
| 1627 | * Prompt the user to optionally fix invalid UTF-8 at check-in. |
| 1628 | * Added a line-number toggle option to the [/help?cmd=/info|/info] |
| 1629 | and [/help?cmd=/artifact|/artifact] pages. |
| 1630 | * Most commands now issue errors rather than silently ignoring unrecognized |
| 1631 | command-line options. |
| 1632 | * Use full 40-character SHA1 hashes (instead of abbreviations) in most |
| 1633 | internal URLs. |
| 1634 | * The "ssh:" sync method on Windows now uses "plink.exe" instead of "ssh" as |
| @@ -1642,11 +1706,11 @@ | |
| 1642 | * Fix a rare and long-standing sync protocol bug |
| 1643 | that would silently prevent the sync from running to completion. Before |
| 1644 | this bug-fix it was sometimes necessary to do "fossil sync --verily" to |
| 1645 | get two repositories in sync. |
| 1646 | * Add the [/finfo?name=src/foci.c|files_of_checkin] virtual table - useful |
| 1647 | for ad hoc queries in the [/help?cmd=sqlite3|fossil sql] interface, |
| 1648 | and also used internally. |
| 1649 | * Added the "$secureurl" TH1 variable for use in headers and footers. |
| 1650 | * (Internal:) Add the ability to include resources as separate files in the |
| 1651 | source tree that are converted into constant byte arrays in the compiled |
| 1652 | binary. Use this feature to store the Tk script that implements the --tk |
| @@ -1666,143 +1730,143 @@ | |
| 1666 | * The [/reports] page now requires Read ("o") permissions. The "byweek" |
| 1667 | report now properly propagates the selected year through the event type |
| 1668 | filter links. |
| 1669 | * The [/help/info | info command] now shows leaf status of the checkout. |
| 1670 | * Add support for tunneling https through a http proxy (Ticket [e854101c4f]). |
| 1671 | * Add option --empty to the "[/help?cmd=open | fossil open]" command. |
| 1672 | * Enhanced [/help?cmd=/fileage|the fileage page] to support a glob parameter. |
| 1673 | * Add -w|--ignore-all-space and -Z|--ignore-trailing-space options to |
| 1674 | [/help?cmd=annotate|fossil annotate], [/help?cmd=blame|fossil blame], |
| 1675 | [/help?cmd=diff|fossil (g)diff], [/help?cmd=stash|fossil stash diff]. |
| 1676 | * Add --strip-trailing-cr option to [/help?cmd=diff|fossil (g)diff] and |
| 1677 | [/help?cmd=stash|fossil stash diff]. |
| 1678 | * Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff |
| 1679 | and /vdiff UI pages. |
| 1680 | * Enhance [/reports?view=byweekday|/reports] with a "byweekday" view. |
| 1681 | * Enhance the [/help?cmd=cat|fossil cat] command so that it works outside |
| 1682 | of a checkout when using the -R command-line option. |
| 1683 | * Use full-length SHA1 hashes, not abbreviations, in most hyperlinks. |
| 1684 | * Correctly render the <title> markup on wiki pages in the |
| 1685 | [/help?cmd=/artifact|/artifact] webpage. |
| 1686 | * Enhance the [/help?cmd=whatis|fossil whatis] command to report on attachments |
| 1687 | and cluster artifacts. Added the [/help?cmd=test-whatis-all] command for |
| 1688 | testing purposes. |
| 1689 | * Add support for HTTP Basic Authentication on [/help?cmd=clone|clone] and |
| 1690 | [/help?cmd=sync|sync]. |
| 1691 | * Fix the [/help?cmd=stash|stash] so that it remembers added files and re-adds |
| 1692 | them when the stash is applied. |
| 1693 | * Fix the server so that it avoids writing to the database (and thus avoids |
| 1694 | database locking issues) on a |
| 1695 | [/help?cmd=pull|pull] or [/help?cmd=clone|clone]. |
| 1696 | * Add support for [./server.wiki#loadmgmt|server load management] using both |
| 1697 | a cache of expensive pages (the [/help?cmd=cache|fossil cache] command) |
| 1698 | and by rejecting expensive page requests when the server load average is too |
| 1699 | high. |
| 1700 | * Add the [/help?cmd=praise|fossil praise] command as an alias for |
| 1701 | [/help?cmd=blame|fossil blame] for subversion compatibility. |
| 1702 | * Enhance the [/help?cmd=test-diff|fossil test-diff] command with -y or --tk |
| 1703 | options so that it shows both filenames above their respective columns in |
| 1704 | the side-by-side diff output. |
| 1705 | * Issue a warning if a [/help?cmd=add|fossil add] command tries to add a file |
| 1706 | that matches the ignore-glob. |
| 1707 | * Add option -W|--width to "[/help?cmd=stash|fossil stash ls]" |
| 1708 | and "[/help?cmd=leaves|fossil leaves]" commands. |
| 1709 | * Enhance support for running as the root user. Now works on Haiku. |
| 1710 | * Added the <tt>-empty</tt> option to [/help?cmd=new|fossil new], which |
| 1711 | causes it to not create an initial empty commit. The first commit after |
| 1712 | checking out a repo created this way will become the initial commit. |
| 1713 | * Enhance sync operations by committing each round-trip to minimize number |
| 1714 | of retransmits when autosync fails. Include option for |
| 1715 | [/help?cmd=update| fossil update] and [/help?cmd=merge| fossil merge] to |
| 1716 | continue even if missing content. |
| 1717 | * Minor portability fixes for platforms where the char type is unsigned |
| 1718 | by default. |
| 1719 | |
| 1720 | <h2>Changes For Version 1.28 (2014-01-27)</h2> |
| 1721 | * Enhance [/help?cmd=/reports | /reports] to support event type filtering. |
| 1722 | * When cloning a repository, the user name passed via the URL (if any) |
| 1723 | is now used as the default local admin user's name. |
| 1724 | * Enhance the SSH transport mechanism so that it runs a single instance of |
| 1725 | the "fossil" executable on the remote side, obviating the need for a shell |
| 1726 | on the remote side. Some users may need to add the "?fossil=/path/to/fossil" |
| 1727 | query parameter to "ssh:" URIs if their fossil binary is not in a standard |
| 1728 | place. |
| 1729 | * Add the "[/help?cmd=blame | fossil blame]" command that works just like |
| 1730 | "fossil annotate" but uses a different output format that includes the |
| 1731 | user who made each change and omits line numbers. |
| 1732 | * Add the "Tarball and ZIP-archive Prefix" configuration parameter under |
| 1733 | Admin/Configuration. |
| 1734 | * Fix CGI processing so that it works on web servers that do not |
| 1735 | supply REQUEST_URI. |
| 1736 | * Add options --dirsonly, --emptydirs, and --allckouts to the |
| 1737 | "[/help?cmd=clean | fossil clean]" command. |
| 1738 | * Ten-fold performance improvement in large "fossil blame" or |
| 1739 | "fossil annotate" commands. |
| 1740 | * Add option -W|--width and --offset to "[/help?cmd=timeline | fossil timeline]" |
| 1741 | and "[/help?cmd=finfo | fossil finfo]" commands. |
| 1742 | * Option -n|--limit of "[/help?cmd=timeline | fossil timeline]" now |
| 1743 | specifies the number of entries, just like all other commands which |
| 1744 | have the -n|--limit option. The various timeline-related functions |
| 1745 | now output "--- ?? limit (??) reached ---" at the end whenever |
| 1746 | appropriate. Use "-n 0" if no limit is desired. |
| 1747 | * Fix handling of password embedded in Fossil URL. |
| 1748 | * New <tt>--once</tt> option to [/help?cmd=clone | fossil clone] command |
| 1749 | which does not store the URL or password when cloning. |
| 1750 | * Modify [/help?cmd=ui | fossil ui] to respect "default user" in an open |
| 1751 | repository. |
| 1752 | * Fossil now hides check-ins that have the "hidden" tag in timeline webpages. |
| 1753 | * Enhance <tt>/ci_edit</tt> page to add the "hidden" tag to check-ins. |
| 1754 | * Advanced possibilities for commit and ticket change notifications over |
| 1755 | http using TH1 scripting. |
| 1756 | * Add --sha1sum and --integrate options |
| 1757 | to the "[/help?cmd=commit | fossil commit]" command. |
| 1758 | * Add the "clean" and "extra" subcommands to the |
| 1759 | "[/help?cmd=all | fossil all]" command |
| 1760 | * Add the --whatif option to "[/help?cmd=clean|fossil clean]" that works the |
| 1761 | same as "--dry-run", |
| 1762 | so that the name does not collide with the --dry-run option of "fossil all". |
| 1763 | * Provide a configuration option to show dates on the web timeline |
| 1764 | as "YYMMMDD HH:MM" |
| 1765 | * Add an option to the "stats" webpage that allows an administrator to see |
| 1766 | the current repository schema. |
| 1767 | * Enhancements to the "[/help?cmd=/vdiff|/vdiff]" webpage for more difference |
| 1768 | display options. |
| 1769 | * Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative |
| 1770 | to "/dir" and make it the default way of showing file lists. |
| 1771 | * Send gzipped HTTP responses to clients that support it. |
| 1772 | |
| 1773 | <h2>Changes For Version 1.27 (2013-09-11)</h2> |
| 1774 | * Enhance the [/help?cmd=changes | fossil changes], |
| 1775 | [/help?cmd=clean | fossil clean], [/help?cmd=extras | fossil extras], |
| 1776 | [/help?cmd=ls | fossil ls] and [/help?cmd=status | fossil status] commands |
| 1777 | to restrict operation to files and directories named on the command-line. |
| 1778 | * New --integrate option to [/help?cmd=merge | fossil merge], which |
| 1779 | automatically closes the merged branch when committing. |
| 1780 | * Renamed <tt>/stats_report</tt> page to [/reports]. Graph width is now |
| 1781 | relative, not absolute. |
| 1782 | * Added <tt>yw=YYYY-WW</tt> (year-week) filter to timeline to limit the results |
| 1783 | to a specific year and calendar week number, e.g. [/timeline?yw=2013-01]. |
| 1784 | * Updates to SQLite to prevent opening a repository file using file descriptors |
| 1785 | 1 or 2 on Unix. This fixes a bug under which an assertion failure could |
| 1786 | overwrite part of a repository database file, corrupting it. |
| 1787 | * Added support for unlimited line lengths in side-by-side diffs. |
| 1788 | * New --close option to [/help?cmd=commit | fossil commit], which |
| 1789 | immediately closes the branch being committed. |
| 1790 | * Added <tt>chart</tt> option to [/help?cmd=bisect | fossil bisect]. |
| 1791 | * Improvements to the "human or bot?" determination. |
| 1792 | * Reports errors about missing CGI-standard environment variables for HTTP |
| 1793 | servers which do not support them. |
| 1794 | * Minor improvements to sync support on Windows. |
| 1795 | * Added <tt>--scgi</tt> option to [/help?cmd=server | fossil server]. |
| 1796 | * Internal improvements to the sync process. |
| 1797 | * The internals of the JSON API are now MIT-licensed, so downstream |
| 1798 | users/packagers are no longer affected by the "do no evil" license |
| 1799 | clause. |
| 1800 | |
| 1801 | <h2>Changes For Version 1.26 (2013-06-18)</h2> |
| 1802 | * The argument to the --port option for the [/help?cmd=ui | fossil ui] and |
| 1803 | [/help?cmd=server | fossil server] commands can take an IP address in addition |
| 1804 | to the port number, causing Fossil to bind to just that one IP address. |
| 1805 | * After prompting for a password, also ask if that password should be |
| 1806 | remembered. |
| 1807 | * Performance improvements to the diff engine. |
| 1808 | * Fix the side-by-side diff engine to work better with multi-byte Unicode text. |
| @@ -1809,11 +1873,11 @@ | |
| 1809 | * Color-coding in the web-based annotation (blame) display. Fix the annotation |
| 1810 | engine so that it is no longer confused by time-warps. |
| 1811 | * The markdown formatter is now available by default and can be used for |
| 1812 | tickets, wiki, and embedded documentation. |
| 1813 | * Add subcommands "fossil bisect log" and "fossil bisect status" to the |
| 1814 | [/help?cmd=bisect | fossil bisect] command, as well as other bisect enhancements. |
| 1815 | * Enhanced defenses that prevent spiders from using excessive CPU and bandwidth. |
| 1816 | * Consistent use of the -n or --dry-run command line options. |
| 1817 | * Win32: Fossil now understands Cygwin paths containing one or more of |
| 1818 | the characters <nowiki>"*:<>?|</nowiki>. Those are normally forbidden in |
| 1819 | win32. This means that the win32 fossil.exe is better usable in a Cygwin |
| 1820 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,44 +1,108 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol> |
| 4 | <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a"> |
| 5 | <li> The default configuration now allows robots to download any tarball |
| 6 | or similar, to better support of automated build systems. |
| 7 | <li> New special tag "zipX" for the [/help/robot-restrict|robot-restrict] |
| 8 | setting blocks robot access to tarballs, but with exceptions to support |
| 9 | automated build systems. |
| 10 | <li> Enhancements to the default value for the |
| 11 | [/help/robot-restrict|robot-restrict setting]. |
| 12 | </ol> |
| 13 | <li> A drop-down menu of recent branches is now possible for the submenu, and |
| 14 | is used in the [/dir?ci=trunk|code browser]. |
| 15 | <li> Easier access to generated tarballs and ZIPs:<ol type="a"> |
| 16 | <li> When in the [/dir?ci=trunk|code browser] at the top-level, |
| 17 | a new "Download" submenu option is available to take the |
| 18 | user to a page where he can download a tarball or ZIP archive. |
| 19 | <li> New [/help/www/download|/download page] is available. When |
| 20 | configured using the new |
| 21 | [/help/suggested-downloads|suggested-downloads setting], a |
| 22 | link to [/download] named "Tarballs and ZIPs" appears in the |
| 23 | [/sitemap] and thus on the hamburger menu. |
| 24 | <li> The filenames for tarballs and ZIPs are now standardized to |
| 25 | include a timestamp and a hash prefix. |
| 26 | </ol> |
| 27 | <li> Timeline enhancements:<ol type="a"> |
| 28 | <li> A new "Simple" view is available. This is compromise between "Verbose" |
| 29 | and "Compact" that shows only the check-in hash rather than the full |
| 30 | detail section. There is an ellipsis that one can click on to see the |
| 31 | full detail text. |
| 32 | <li> The artifact hash in the detail section of each timeline entry is now |
| 33 | emphasized (controlled by CSS) to make it easier to locate amid all |
| 34 | the other text and links. |
| 35 | <li> When clicking on the ellipsis in "Compact" or "Simple" views, the ellipsis |
| 36 | is replaced by a left arrow ("←") which can be clicked to hide the extra |
| 37 | detail again. |
| 38 | <li> New setting [/help/timeline-mark-leaves|timeline-mark-leaves] controls |
| 39 | whether or not leaf check-ins are marked in the timeline. |
| 40 | <li> "No-graph" timelines (using the "ng" query parameter) now show |
| 41 | branch colors and bare check-in circles on the left. The check-in |
| 42 | circles appear, but no lines connecting them. |
| 43 | ([/timeline?ng|example]). |
| 44 | </ol> |
| 45 | <li> The [/help/timeline|timeline command] is enhanced with the new |
| 46 | "<tt>-u|--for-user</tt>" option. |
| 47 | <li> The [/help/open|open command]'s new "<tt>--reopen REPOFILE</tt>" flag |
| 48 | can be used to fix a checkout after moving its repository file. |
| 49 | </ol> |
| 50 | |
| 51 | <h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol> |
| 52 | <li> Close a potential Denial-of-Service attack against any public-facing Fossil |
| 53 | server involving exponential behavior in Fossil's regexp implementation. |
| 54 | <li> Fix a SQL injection on the [/help/www/file|/file page]. Thanks to |
| 55 | additional defenses built into Fossil, as well as good luck, this injection |
| 56 | is not exploitable for either data exfiltration or privilege escalation. The |
| 57 | only possible result of invoking the injection is a harmless SQL syntax error. |
| 58 | <li> Strengthen robot defenses to help prevent public-facing servers from being |
| 59 | overwhelmed by the latest generation of AI spiders. |
| 60 | <ol type="a"> |
| 61 | <li> New javascript captcha used to restrict access by user "nobody" to pages |
| 62 | listed in the [/help/robot-restrict|robot-restrict setting]. |
| 63 | <li> The [/help/robot-exception|robot-exception setting] is available to allow |
| 64 | access to pages that match a regular expression. Use this, for example, to |
| 65 | allow curl scripts and similar to download release tarballs. |
| 66 | <li> Require at least an anonymous login to access the /blame page and similar. |
| 67 | </ol> |
| 68 | <li> [/help/www/timeline|Timeline] enhancements: |
| 69 | <ol type="a"> |
| 70 | <li> The chng= query parameter on the [/help/www/timeline|timeline page] |
| 71 | so that it works with other query parameters like p=, d=, from=, and to=. |
| 72 | <li> Always include nodes identify by sel1= and sel2= in the /timeline display. |
| 73 | <li> Improved title when p= and d= are different. |
| 74 | </ol> |
| 75 | <li> Enable the --editor option on the [/help/amend|fossil amend] command. |
| 76 | <li> When walking the filesystem looking for Fossil repositories, avoid descending |
| 77 | into directories named "/proc". |
| 78 | <li> Reduce memory requirements for sending authenticated sync protocol |
| 79 | messages. |
| 80 | <li> Show numstat-style change statistics in the /info and /ckout pages. |
| 81 | <li> Add the [/help/stash | stash rename] subcommand. |
| 82 | <li> Add the "-h" option to the "[/help/ls|ls]" command to display |
| 83 | file hashes for a specific check-in when in verbose mode. |
| 84 | </ol> |
| 85 | |
| 86 | <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol> |
| 87 | <li>Enhancements to [/help/diff|fossil diff] and similar: |
| 88 | <ol type="a"> |
| 89 | <li> The argument to the --from option can be a directory name, causing |
| 90 | Fossil to use files under that directory as the baseline for the diff. |
| 91 | <li> For "gdiff", if no [/help/gdiff-command|gdiff-command setting] |
| 92 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 93 | are available, or a --by diff if not. |
| 94 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 95 | diff up to date with the latest changes on disk. |
| 96 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 97 | diffs of multiple files. |
| 98 | </ol> |
| 99 | <li>Added the [/help/www/ckout|/ckout web page] to provide information |
| 100 | about pending changes in a working check-out |
| 101 | <li>Enhancements to the [/help/ui|fossil ui] command: |
| 102 | <ol type="a"> |
| 103 | <li> Defaults to using the new [/help/www/ckout|/ckout page] as its |
| 104 | start page. Or, if the new "--from PATH" option is present, the |
| 105 | default start page becomes "/ckout?exbase=PATH". |
| 106 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 107 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 108 | person editing this change log has |
| @@ -46,25 +110,25 @@ | |
| 110 | press "Reload" on the web browser to view edits. |
| 111 | <li> Accept both IPv4 and IPv6 connections on all platforms, including |
| 112 | Windows and OpenBSD. This also applies to the "fossil server" |
| 113 | command. |
| 114 | </ol> |
| 115 | <li>Enhancements to [/help/merge|fossil merge]: |
| 116 | <ol type="a"> |
| 117 | <li> Added the [/help/merge-info|fossil merge-info] command and |
| 118 | especially the --tk option to that command, to provide analysis |
| 119 | of the most recent merge or update operation. |
| 120 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 121 | text that shows Fossil's suggested resolution to the conflict. |
| 122 | </ol> |
| 123 | <li>Enhancements to [/help/commit|fossil commit]: |
| 124 | <ol type="a"> |
| 125 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 126 | in the check-in comment, it will alert the developer and give |
| 127 | him or her the opportunity to edit the comment before continuing. |
| 128 | This feature is controllable by the |
| 129 | [/help/verify-comments|verify-comments setting]. |
| 130 | <li> The new "--if-changes" option causes the commit to become |
| 131 | a quiet no-op if there are no pending changes. |
| 132 | <li> Added the ability to sign check-ins with SSH keys. Artifacts signed |
| 133 | this way are ignored by all previous fossil versions, as if they |
| 134 | were plain-text file content instead of Fossil artifacts. |
| @@ -76,13 +140,13 @@ | |
| 140 | </ol> |
| 141 | <li>Deprecate the --comfmtflags and --comment-format global options and |
| 142 | no longer list them in the built-in help, but keep them working for |
| 143 | backwards compatibility. |
| 144 | Alternative TTY comment formatting can still be specified using the |
| 145 | [/help/comment-format|comment-format setting], if desired. The |
| 146 | default comment format is now called "canonical", not "legacy". |
| 147 | <li>Enhancements to the [/help/www/timeline|/timeline page]: |
| 148 | <ol type="a"> |
| 149 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 150 | like "rl=" ("Related List") but adds "mionly" style related |
| 151 | check-ins instead of the full "rel" style. |
| 152 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -111,33 +175,33 @@ | |
| 175 | in which case the timeline shows those check-ins that are both |
| 176 | ancestors of p= and descendants of d=. |
| 177 | <li> The saturation and intensity of user-specified checkin and branch |
| 178 | background colors are automatically adjusted to keep the colors |
| 179 | compatible with the current skin, unless the |
| 180 | [/help/raw-bgcolor|raw-bgcolor setting] is turned on. |
| 181 | </ol> |
| 182 | <li>The [/help/www/docfile|/docfile webpage] was added. It works like |
| 183 | /doc but keeps the title of markdown documents with the document rather |
| 184 | that moving it up to the page title. |
| 185 | <li>Added the [/help/www/clusterlist|/clusterlist page] for analysis |
| 186 | and debugging |
| 187 | <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 188 | decoding of the artifact described by NAME. |
| 189 | <li>Improvements to the [/help/patch|fossil patch] command: |
| 190 | <ol type="a"> |
| 191 | <li> Fix a bug in "fossil patch create" that causes |
| 192 | [/help/revert|fossil revert] operations that happened |
| 193 | on individualfiles after a [/help/merge|fossil merge] |
| 194 | to be omitted from the patch. |
| 195 | <li> Added the [/help/patch|patch alias] command for managing |
| 196 | aliases for remote checkout names. |
| 197 | </ol> |
| 198 | <li>Enhancements to on-line help and the [/help/help|fossil help] command: |
| 199 | <ol type="a"> |
| 200 | <li> Add the ability to search the help text, either in the UI |
| 201 | (on the [/help/www/search|/search page]) or from the command-line |
| 202 | (using the "[/help/search|fossil search -h PATTERN]" command.) |
| 203 | <li> Accepts an optional SUBCOMMAND argument following the |
| 204 | COMMAND argument and only shows results for the specified |
| 205 | subcommand, not the entire command. |
| 206 | <li> The -u (--usage) option shows only the command-line syntax |
| 207 | <li> The -o (--options) option shows only the command-line options |
| @@ -153,11 +217,11 @@ | |
| 217 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 218 | <li> Show creation time in report and ticket view. |
| 219 | <li> Show previous comments in edit ticket as reference. |
| 220 | </ol> |
| 221 | <li>Added the "hash" query parameter to the |
| 222 | [/help/www/whatis|/whatis webpage]. |
| 223 | <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] |
| 224 | which alerts subscribers when an admin creates a new user or |
| 225 | when a user's permissions change. |
| 226 | <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains |
| 227 | the substring "description", then the project description for each repository |
| @@ -169,44 +233,44 @@ | |
| 233 | <ol type="a"> |
| 234 | <li> TH1 now makes a distinction between |
| 235 | [/doc/trunk/www/th1.md#taint|tainted and untainted string values]. |
| 236 | This makes it more difficult to write custom TH1 scripts that |
| 237 | contain XSS or SQL-injection bugs. The |
| 238 | [/help/vuln-report|vuln-report] setting was added to control |
| 239 | what Fossil does when it encounters a potential TH1 |
| 240 | security problem. |
| 241 | <li> The "--th" option was removed from the [/help/pikchr|fossil pikchr] |
| 242 | command. |
| 243 | <li> The "enable_htmlify" TH1 command was removed. |
| 244 | </ol> |
| 245 | <li>Make [/help/www/chat|/chat] better-behaved during server outages, reducing |
| 246 | the frequency of reconnection attempts over time and providing feedback |
| 247 | to the user when the connection is down. |
| 248 | <li>The [/help/www/sqlar|/sqlar] page does not work for users who are not logged |
| 249 | in, nor are links to that page displayed to users who are not logged in. Being |
| 250 | logged in as "anonymous" is sufficient to overcome this restriction, assuming |
| 251 | that "anonymous" can download tarballs and ZIP archives. |
| 252 | <li>Many other minor fixes and additions. |
| 253 | </ol> |
| 254 | |
| 255 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 256 | |
| 257 | * The "[/help/ui|fossil ui /]" command now works even for repositories |
| 258 | that have non-ASCII filenames |
| 259 | * Add the [/help/tree|fossil tree] command. |
| 260 | * On case-insensitive filesystems, store files using the filesystem's |
| 261 | preferred case rather than the case typed in by the user. |
| 262 | * Change the name "fossil cherry-pick" command to "fossil cherrypick", |
| 263 | which is more familiar to Git users. Retain the legacy name for |
| 264 | compatibility. |
| 265 | * Add new query parameters to the [/help/www/timeline|/timeline page]: |
| 266 | d2=, p2=, and dp2=. |
| 267 | * Add options to the [/help/tag|fossil tag] command that will list tag values. |
| 268 | * Add the -b|--brief option to the [/help/status|fossil status] command. |
| 269 | * Add ability to upload unversioned files via the [/help/www/uvlist|/uvlist page]. |
| 270 | * Add history search to the [/help/www/chat|/chat page]. |
| 271 | * Add Unix socket support to the [/help/server|server command]. |
| 272 | * On Windows, use the root certificates managed by the operating system |
| 273 | (requires OpenSSL 3.2.0 or greater). |
| 274 | * Take into account zero-width and double-width unicode characters when |
| 275 | formatting the command-line timeline. |
| 276 | * Update the built-in SQLite to version 3.47.0. Precompiled binaries are |
| @@ -243,11 +307,11 @@ | |
| 307 | </ul> |
| 308 | * If an "ssh:" sync fails in a way that suggests that the fossil executable |
| 309 | could not be found on the remote host, then retry after adding a PATH= |
| 310 | prefix to the command. This helps "ssh:" to "just work" when the server |
| 311 | is a Mac. |
| 312 | * Enhancements to the [/help/www/timeline|/timeline page]: |
| 313 | <ul> |
| 314 | <li> Add the x= query paramater |
| 315 | <li> Add the shortcut tl= and rl= query parameters |
| 316 | <li> Add support for from=,ft= and from=,bt= query parameter combinations |
| 317 | <li> Automatically highlight the endpoints for from=,to= queries. |
| @@ -257,16 +321,16 @@ | |
| 321 | * Moved the /museum/repo.fossil file referenced from the Dockerfile from |
| 322 | the ENTRYPOINT to the CMD part to allow use of --repolist mode. |
| 323 | * The [/uvlist] page now shows the hash algorithm used so that |
| 324 | viewers don't have to guess. The hash is shown in a fixed-width |
| 325 | font for a more visually pleasing display. |
| 326 | * If the [/help/autosync|autosync setting] contains keyword "all", |
| 327 | the automatic sync occurs against all defined remote repositories, not |
| 328 | just the default. |
| 329 | * Markdown formatter: improved handling of indented fenced code blocks |
| 330 | that contain blank lines. |
| 331 | * When doing a "[/help/add|fossil add]" on a system with case-insensitive |
| 332 | but case-preserving filenames (Mac and Windows) try to use the filename |
| 333 | case as it is known to the filesystem, not the case entered by the |
| 334 | user on the command-line. See |
| 335 | [forum:/forumpost/30d9c0d131610f53|forum thread 30d9c0d131610f53]. |
| 336 | * Fix problems with one-click unsubscribe on email notifications. |
| @@ -280,17 +344,17 @@ | |
| 344 | <h2 id='v2_23'>Changes for version 2.23 (2023-11-01)</h2> |
| 345 | |
| 346 | * Add ability to "close" forum threads, such that unprivileged users |
| 347 | may no longer respond to them. Only administrators can close |
| 348 | threads or respond to them by default, and the |
| 349 | [/help/forum-close-policy|forum-close-policy setting] can be |
| 350 | used to add that capability to moderators. |
| 351 | * Add the [/help/all|fossil all whatis] command. |
| 352 | * The [/help/status|fossil status] command and relevant UI pages now |
| 353 | correctly report files which were both renamed <b>and</b> edited as such. |
| 354 | * Show default value of settings that have a default in |
| 355 | [/help/help|fossil help SETTING] output. |
| 356 | * On timeline graphs, show closed check-ins using an X in the middle of the |
| 357 | node circle or box. |
| 358 | * New options for email notification: Get email only for the first |
| 359 | post in each new thread, and/or posts that are in reply to my posts. |
| 360 | * Fix a regression bug introduced in version 2.22 that caused FTS5 searches |
| @@ -303,35 +367,35 @@ | |
| 367 | <li> Better defense against cross-site request forgery (CSRF) |
| 368 | attacks. |
| 369 | <li> Improvements to static analysis of source code (the codecheck1.c |
| 370 | file in the source tree). |
| 371 | </ul> |
| 372 | * Enhance the [/help/www/dir|treeview file listings] |
| 373 | ([/dir?type=tree&ci=trunk|example]) by displaying file sizes |
| 374 | and adding the option to sort by file size. |
| 375 | * The [/help/fts-config|fossil fts-config] command now shows how much |
| 376 | repository space is used by the full-text index. |
| 377 | * Changing a setting to an empty string is now the same as deleting the |
| 378 | setting, in most cases. There are a few exceptions, indicated by the |
| 379 | keep-empty flag on the setting definition. |
| 380 | * The [/help/branch|fossil branch list] command can now filter branches |
| 381 | that have/have not been merged into the current branch. |
| 382 | * Improvements to interactions with remote repositories over SSH: |
| 383 | <ul> |
| 384 | <li> Print the text of the SSH command that is run to do remote interaction, |
| 385 | for full disclosure to the operator. |
| 386 | <li> Add a PATH= argument to the [/help/ui|fossil ui remote:/] and |
| 387 | [/help/patch|fossil patch push/pull remote:...] commands so that |
| 388 | they work when the "remote" machine is a Mac and the "fossil" |
| 389 | executable is in the $HOME/bin directory. |
| 390 | </ul> |
| 391 | * Update built-in libraries SQLite, ZLib, Pikchr to their latest versions. |
| 392 | * Documentation enhancements and typo fixes. |
| 393 | |
| 394 | |
| 395 | <h2 id='v2_22'>Changes for version 2.22 (2023-05-31)</h2> |
| 396 | * Enhancements to the [/help/www/timeline|/timeline webpage]: <ol type="a"> |
| 397 | <li> Add the ft=TAG query parameter which in combination with d=Y |
| 398 | shows all descendants of Y up to TAG |
| 399 | <li> Enhance the s=PATTERN (search) query parameter so that forum post |
| 400 | text is also searched when the "vfx" query parameter is used |
| 401 | <li> Fix the u= (user) query parameter so that it works with a= and b= |
| @@ -355,56 +419,56 @@ | |
| 419 | searching in Chinese. |
| 420 | * Comment lines (starting with a '#') are now supported inside |
| 421 | [./settings.wiki#versionable|versioned settings]. |
| 422 | * Default permissions for anonymous users in new repositories are |
| 423 | changed to "hz". |
| 424 | * The [/help/status|fossil status] command now detects when a |
| 425 | file used to be a symlink and has been replaced by a regular file. |
| 426 | (It previously checked for the inverse case only.) |
| 427 | * The [/help/empty-dirs|empty-dirs setting] now reuses the same |
| 428 | parser as the *-glob settings instead of its prior idiosyncratic |
| 429 | parser, allowing quoted whitespace in patterns. |
| 430 | * Enhancements to the [/help/www/reports|/reports webpage]: |
| 431 | <ol type="a"> |
| 432 | <li> The by-week, by-month, and by-year options now show an estimated |
| 433 | size of the current week, month, or year as a dashed box. |
| 434 | <li> New sub-categories "Merge Check-ins" and "Non-Merge Check-ins". |
| 435 | </ol> |
| 436 | |
| 437 | <h2 id='v2_21'>Changes for version 2.21 (2023-02-25)</h2> |
| 438 | * Users can request a password reset. This feature is disabled by default. |
| 439 | Use the new [/help/self-pw-reset|self-pw-reset property] to enable it. |
| 440 | New web pages [/help/www/resetpw|/resetpw] and |
| 441 | [/help/www/reqpwreset|/reqpwreset] added. |
| 442 | * Add the [/help/repack|fossil repack] command (together with |
| 443 | [/help/all|fossil all repack]) as a convenient way to optimize the |
| 444 | size of one or all of the repositories on a system. |
| 445 | * Add the ability to put text descriptions on ticket report formats. |
| 446 | * Upgrade the test-find-pivot command to the [/help/merge-base|merge-base command]. |
| 447 | * The [/help/www/chat|/chat page] can now embed fossil-rendered |
| 448 | views of wiki/markdown/pikchr file attachments with the caveat that such |
| 449 | embedding happens in an iframe and thus does not inherit styles and such |
| 450 | from the containing browser window. |
| 451 | * The [/help/all|fossil all remote] subcommand added to "fossil all". |
| 452 | * Passwords for remembered remote repositories are now stored as irreversible |
| 453 | hashes rather than obscured clear-text, for improved security. |
| 454 | * Add the "nossl" and "nocompress" options to CGI. |
| 455 | * Update search infrastructure from FTS4 to FTS5. |
| 456 | * Add the [/help/www/deltachain|/deltachain] page for debugging purposes. |
| 457 | * Writes to the database are disabled by default if the HTTP request |
| 458 | does not come from the same origin. This enhancement is a defense in depth |
| 459 | measure only; it does not address any known vulnerabilities. |
| 460 | * Improvements to automatic detection and mitigation of attacks from |
| 461 | malicious robots. |
| 462 | |
| 463 | <h2 id='v2_20'>Changes for version 2.20 (2022-11-16)</h2> |
| 464 | * Added the [/help/chat-timeline-user|chat-timeline-user setting]. If |
| 465 | it is not an empty string, then any changes that would appear on the timeline |
| 466 | are announced in [./chat.md|the chat room]. |
| 467 | * The /unsubscribe page now requests confirmation. [./alerts.md|Email notifications] |
| 468 | now contain only an "Unsubscribe" link, and not a link to subscription management. |
| 469 | * Added the "[/help/branch|fossil branch lsh]" subcommand to list the |
| 470 | most recently modified branches. |
| 471 | * More elements of the /info page are now inside of an accordion. |
| 472 | * Replace the <tt>--dryrun</tt> flag with <tt>--dry-run</tt> in all |
| 473 | commands which still used the former name, for consistency. |
| 474 | * Rebuilt [/file/Dockerfile | the stock Dockerfile] to create a "from scratch" |
| @@ -434,11 +498,11 @@ | |
| 498 | accomplished using a Common Table Expression in the underlying SQL. |
| 499 | * Sort tag listings (command line and webpage) by taking numbers into |
| 500 | consideration so as to cater for tags that follow semantic versioning. |
| 501 | * On the wiki listings, omit by default wiki pages that are associated with |
| 502 | check-ins and branches. |
| 503 | * Add the new "[/help/describe|fossil describe]" command. |
| 504 | * Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support]. |
| 505 | See corresponding [../test/markdown-test3.md|test cases], |
| 506 | [/wiki?name=branch/markdown-footnotes#il|known limitations] and |
| 507 | [forum:/forumthread/ee1f1597e46ec07a|discussion]. |
| 508 | * Add the new special name "start:BRANCH" to refer to the first check-in of |
| @@ -450,96 +514,96 @@ | |
| 514 | operation. Also require explicit "system" proxy setting to use |
| 515 | "http_proxy" environment variable. |
| 516 | * Reimplemented the [/pikchrshow] app to use a WebAssembly build of |
| 517 | pikchr so that it can render pikchrs on the client instead of requiring |
| 518 | a server round-trip. |
| 519 | * Add the [/help/email-listid|email-listid setting]. If set, it is |
| 520 | used as the List-ID header for all outbound notification emails. |
| 521 | * Add the "--branch" option to the "[/help/timeline|timeline]" command |
| 522 | to restrict the displayed items to a specific branch. |
| 523 | * Add the "--versions" option to "[/help/diff|fossil diff]" |
| 524 | to display details about the compared versions into the patch header. |
| 525 | * Numerous other minor enhancements. |
| 526 | |
| 527 | <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2> |
| 528 | * Added support for [./ssl-server.md|SSL/TLS server mode] for commands |
| 529 | like "[/help/server|fossil server]" and "[/help/http|fossil http]" |
| 530 | * The new [/help/cherry-pick|cherry-pick command] is an alias for |
| 531 | [/help/merge|merge --cherrypick]. |
| 532 | * Add new setting "[/help/large-file-size|large-file-size]". If the size |
| 533 | of any file in a commit exceeds this size, a warning is issued. |
| 534 | * Query parameter "year=YYYY" is now accepted by [/help/www/timeline|/timeline]. |
| 535 | * The [/help/tar|tar] and [/help/zip|zip commands] no longer |
| 536 | sterilize the manifest file. |
| 537 | * Further improvement to diff alignment in cases that involve both |
| 538 | edits and indentation changes. |
| 539 | * [/doc/trunk/www/chat.md|Chat] improvements:<ul> |
| 540 | <li> [/help/www/chat|The /chat page] input options have been reworked |
| 541 | again for better cross-browser portability. |
| 542 | <li> When sending a [/help/www/chat|/chat] message fails, it is no longer |
| 543 | immediately lost and sending may optionally be retried. |
| 544 | <li> [/help/www/chat|/chat] can now optionally embed attachments of certain |
| 545 | types directly into message bodies via an iframe. |
| 546 | <li> Add the "--as FILENAME" option to the "[/help/chat|fossil chat send]" |
| 547 | command. |
| 548 | <li> Added the "[/help/chat|fossil chat pull]" command, available to |
| 549 | administrators only, for backing up the chat conversation. |
| 550 | </ul> |
| 551 | * Promote the test-detach command into the [/help/detach|detach command]. |
| 552 | * For "[/help/pull|fossil pull]" with the --from-parent-project option, |
| 553 | if no URL is specified then use the last URL from the most recent prior |
| 554 | "fossil pull --from-parent-project". |
| 555 | * Add options --project-name and --project-desc to the |
| 556 | "[/help/init|fossil init]" command. |
| 557 | * The [/help/www/ext|/ext page] generates the SERVER_SOFTWARE environment |
| 558 | variable for clients. |
| 559 | * Fix the REQUEST_URI [/doc/trunk/www/aboutcgi.wiki#cgivar|CGI variable] such |
| 560 | that it includes the query string. This is how most other systems understand |
| 561 | REQUEST_URI. |
| 562 | * Added the --transport-command option to [/help/sync|fossil sync] |
| 563 | and similar. |
| 564 | |
| 565 | <h2 id='v2_17'>Changes for version 2.17 (2021-10-09)</h2> |
| 566 | |
| 567 | * Major improvements to the "diff" subsystem, including: <ul> |
| 568 | <li> Added new [/help/diff|formatting options]: --by, -b, --webpage, --json, --tcl. |
| 569 | <li> Partial-line matching for unified diffs |
| 570 | <li> Better partial-line matching for side-by-side diffs |
| 571 | <li> Buttons on web-based diffs to show more context |
| 572 | <li> Performance improvements |
| 573 | </ul> |
| 574 | * The --branchcolor option on [/help/commit|fossil commit] and |
| 575 | [/help/amend|fossil amend] can now take the value "auto" to |
| 576 | force Fossil to use its built-in automatic color choosing algorithm. |
| 577 | * Fossil now [./concepts.wiki#workflow|autosyncs] prior to running |
| 578 | [/help/open|fossil open]. |
| 579 | * Add the [/help/ticket-default-report|ticket-default-report setting], |
| 580 | which if set to the title of a ticket report causes that ticket report |
| 581 | to be displayed below the search box in the /ticket page. |
| 582 | * The "nc" query parameter to the [/help/www/timeline|/timeline] page |
| 583 | causes all graph coloring to be omitted. |
| 584 | * Improvements and bug fixes to the new "[/help/ui|fossil ui REMOTE]" |
| 585 | feature so that it works better on a wider variety of platforms. |
| 586 | * In [/help/www/wikiedit|/wikiedit], show the list of attachments for |
| 587 | the current page and list URLs suitable for pasting them into the page. |
| 588 | * Add the --no-http-compression option to [/help/sync|fossil sync] |
| 589 | and similar. |
| 590 | * Print total payload bytes on a [/help/sync|fossil sync] when using |
| 591 | the --verbose option. |
| 592 | * Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and |
| 593 | </tt>unhide</tt> subcommands to [/help/branch|the branch command]. |
| 594 | * The "-p" option to [/help/branch|fossil branch list] shows only |
| 595 | private branches. |
| 596 | * The [/md_rules|Markdown formatter] now interprets the content of |
| 597 | block HTML markup (such as <table>) in most cases. Only content |
| 598 | of <pre> and <script> is passed through verbatim. |
| 599 | * The [/help/wiki|wiki list command] no longer lists "deleted" |
| 600 | pages by default. Use the new <tt>--all</tt> option to include deleted |
| 601 | pages in the output. |
| 602 | * The [/help/all|fossil all git status] command only shows reports for |
| 603 | the subset of repositories that have a configured Git export. |
| 604 | * The [/help/www/chat|/chat] configuration was reimplemented and |
| 605 | provides new options, including the ability for a repository |
| 606 | administrator to |
| 607 | [./chat.md#notifications|extend the selection of notification sounds] |
| 608 | using unversioned files. |
| 609 | * Chat now uses fossil's full complement of markdown features, |
| @@ -548,14 +612,14 @@ | |
| 612 | markdown-processed when they are sent instead of when they |
| 613 | are saved. |
| 614 | * Added a chat message preview mode so messages can be previewed |
| 615 | before being sent. Similarly, added a per-message ability to view |
| 616 | the raw un-parsed message text. |
| 617 | * The hotkey to activate preview mode in [/help/www/wikiedit|/wikiedit], |
| 618 | [/help/www/fileedit|/fileedit], and [/help/www/pikchrshow|/pikchrshow] |
| 619 | was changed from ctrl-enter to shift-enter in order to align with |
| 620 | [/help/www/chat|/chat]'s new preview feature and related future |
| 621 | changes. |
| 622 | |
| 623 | <h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2> |
| 624 | * <b>Security:</b> Fix the client-side TLS so that it verifies that the |
| 625 | server hostname matches its certificate. |
| @@ -562,11 +626,11 @@ | |
| 626 | * The default "ssh" command on Windows is changed to "ssh" instead of the |
| 627 | legacy "plink", as ssh is now generally available on Windows systems. |
| 628 | Installations that still need to use the legacy "plink" can make that |
| 629 | happen by running: '<tt>fossil set ssh-command "plink -ssh" --global</tt>'. |
| 630 | * Added the [./patchcmd.md|fossil patch] command. |
| 631 | * The [/help/ui|fossil ui] command is enhanced in multiple ways:<ol> |
| 632 | <li> The REPOSITORY argument can be the name of a check-out directory. |
| 633 | <li> If the REPOSITORY argument is prefixed by "HOST:" or "USER@HOST:" |
| 634 | then the ui is run on the remote machine and tunnelled back to the local |
| 635 | machine using ssh. (The latest version of fossil must be installed on |
| 636 | both the local and the remote for this to work correctly.) |
| @@ -575,25 +639,25 @@ | |
| 639 | * The [/brlist|/brlist web page] allows the user to |
| 640 | select multiple branches to be displayed together in a single |
| 641 | timeline. |
| 642 | * The [./forum.wiki|Forum] provides a hyperlink on the author of each |
| 643 | post that goes to a timeline of recent posts by that same author. |
| 644 | * Added the "[/help/bisect|fossil bisect run]" command for improved |
| 645 | automation of bisects. |
| 646 | * The [/help/merge|fossil merge] command now does a better job merging |
| 647 | branches where files have been renamed between the current branch and the |
| 648 | branch being merged. |
| 649 | * The [/help/open|fossil open] command allows the repository file |
| 650 | to be inside the working directory without requiring the --force flag. |
| 651 | * The [/help/www/wikiedit|/wikiedit] and [/help/www/wikinew|/wikinew] |
| 652 | pages now default to markdown format. |
| 653 | * The [/help/www/login|/login] page now links to a user's forum post |
| 654 | timeline if the repository has forum posts. |
| 655 | * Tags may now be propagated for forum posts, wiki pages, and technotes. |
| 656 | The [/help/tag|tag command] can now manipulate and list such tags. |
| 657 | * [./caps/login-groups.md|Login-Groups] are now shown on the repository |
| 658 | list of the "[/help/all|fossil all ui]" command. |
| 659 | * Administrators can configure [./alerts.md|email alerts] to expire |
| 660 | a specific number of days (ex: 365) after the last user contact with |
| 661 | the Fossil server. This prevents alert emails being sent to |
| 662 | abandoned email accounts forever. |
| 663 | * SQL that defines [/tktsetup_tab|database objects for tickets] now |
| @@ -610,11 +674,11 @@ | |
| 674 | * <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server. <b>Upgrading to |
| 675 | the patch is recommended.</b> |
| 676 | * The [./defcsp.md|default CSP] has been relaxed slightly to allow |
| 677 | images to be loaded from any URL. All other resources are still |
| 678 | locked down by default. |
| 679 | * The built-in skins all use the "[/help/mainmenu|mainmenu]" |
| 680 | setting to determine the content of the main menu. |
| 681 | The ability to edit the |
| 682 | "mainmenu" setting is added on the /Admin/Configuration page. |
| 683 | * The hamburger menu is now available on most of the built-in skins. |
| 684 | * Any built-in skin named "X" can be used instead of the standard |
| @@ -624,40 +688,40 @@ | |
| 688 | included. The [/skins] page may be used to select a skin. |
| 689 | * The [/cookies] page now gives the user an opportunity to delete |
| 690 | individual cookies. And the /cookies page is linked from the |
| 691 | /sitemap, so that it appears in hamburger menus. |
| 692 | * The [/sitemap] extensions are now specified by a single new |
| 693 | "[/help/sitemap-extra|sitemap-extra setting]", |
| 694 | rather than a cluster of various |
| 695 | "sitemap-*" settings. The older settings are no longer used. |
| 696 | <b>This change might require minor server configuration |
| 697 | adjustments on servers that use /sitemap extensions.</b> |
| 698 | The /Admin/Configuration page provides the ability to edit |
| 699 | the new "sitemap-extra" setting. |
| 700 | * Added the "--ckout-alias NAME" option to |
| 701 | [/help/ui|fossil ui], [/help/server|fossil server], and |
| 702 | [/help/http|fossil http]. This option causes Fossil to |
| 703 | understand URIs of the form "/doc/NAME/..." as if they were |
| 704 | "[/help/www/doc|/doc/ckout/...]", to facilitate testing of |
| 705 | [./embeddeddoc.wiki|embedded documentation] changes prior to |
| 706 | check-in. |
| 707 | * For diff web pages, if the diff type (unified versus side-by-side) |
| 708 | is not specified by a query parameter, and if the |
| 709 | "[/help/preferred-diff-type|preferred-diff-type]" |
| 710 | setting is omitted or less than 1, then select the diff type based |
| 711 | on a guess of whether or not the request is coming from a mobile |
| 712 | device. Mobile gets unified and desktop gets side-by-side. |
| 713 | * The various pages which show diffs now have toggles to show/hide |
| 714 | individual diffs. |
| 715 | * Add the "[/help/preferred-diff-type|preferred-diff-type]" |
| 716 | setting to allow an admin to force a default diff type. |
| 717 | * The "pikchr-background" setting is now available in |
| 718 | "detail.txt" skin files, for better control of Pikchr |
| 719 | colors in inverted color schemes. |
| 720 | * Add the <tt>--list</tt> option to the |
| 721 | [/help/tarball|tarball], |
| 722 | [/help/zip|zip], and [/help/sqlar|sqlar] |
| 723 | commands. |
| 724 | * The javascript used to implement the hamburger menu on the |
| 725 | default built-in skin has been made generic so that it is usable |
| 726 | by a variety of skins, and promoted to an ordinary built-in |
| 727 | javascript file. |
| @@ -665,24 +729,24 @@ | |
| 729 | "[/doc/trunk/www/th1.md#bireqjs|builtin_request_js]", |
| 730 | "[/doc/trunk/www/th1.md#capexpr|capexpr]", |
| 731 | "foreach", "lappend", and "string match" |
| 732 | * The [/help/leaves|leaves command] now shows the branch point |
| 733 | of each leaf. |
| 734 | * The [/help/add|fossil add] command refuses to add files whose |
| 735 | names are reserved by Windows (ex: "aux") unless the --allow-reserved |
| 736 | option is included. This helps prevent Unix users from accidentally |
| 737 | creating check-ins that are unreadable by Windows users. |
| 738 | * Add the "re=" query parameter to the [/help/www/dir|/dir] webpage, |
| 739 | for symmetry with the [/help/www/tree|/tree] page. |
| 740 | * Update the built-in SQLite to version 3.35.0. |
| 741 | * The ./configure script now has the --print-minimum-sqlite-version option |
| 742 | that prints the minimum SQLite version required by the current version |
| 743 | of Fossil. This might be used by integrators who insist on building |
| 744 | Fossil to link against the system SQLite library rather than the |
| 745 | built-in copy of SQLite, to verify that their system SQLite library |
| 746 | is recent enough. |
| 747 | * Webpage that shows [/help/www/whistory|history of a wiki page] |
| 748 | gained client-side UI to help with comparison between two arbitrary |
| 749 | versions of a wiki (by the means of anchoring a "baseline" version) |
| 750 | and the ability to squeeze several sequential edits made by the same |
| 751 | user into a single "recycled" row (the latest edit in that sequence). |
| 752 | |
| @@ -702,40 +766,40 @@ | |
| 766 | version 2.14 and then later downgrade or otherwise use an earlier |
| 767 | version of Fossil, the email notification mechanism may fail |
| 768 | to send out notifications for some events, due to the missing |
| 769 | trigger. If you want to |
| 770 | permanently downgrade an installation, then you should run |
| 771 | "[/help/rebuild|fossil rebuild]" after the downgrade |
| 772 | to get email notifications working again. If you are not using |
| 773 | email notification, then the schema change will not affect you in |
| 774 | any way. |
| 775 | * <b>Schema Update Notice #2:</b> |
| 776 | This release changes how the descriptions of wiki edits are stored |
| 777 | in the EVENT table, for improved display on timelines. You must |
| 778 | run "[/help/rebuild|fossil rebuild]" to take advantage of |
| 779 | this enhancement. Everything will still work without |
| 780 | "fossil rebuild", except you will get goofy descriptions of |
| 781 | wiki updates in the timeline. |
| 782 | * Add support for [./chat.md|Fossil chat]. |
| 783 | * The "[/help/clone|fossil clone]" command is enhanced so that |
| 784 | if the repository filename is omitted, an appropriate name is derived |
| 785 | from the remote URL and the newly cloned repo is opened. This makes |
| 786 | the clone command work more like Git, thus making it easier for |
| 787 | people transitioning from Git. |
| 788 | * Added the --mainbranch option to the [/help/git|fossil git export] |
| 789 | command. |
| 790 | * Added the --format option to the |
| 791 | "[/help/timeline|fossil timeline]" command. |
| 792 | * Enhance the --numstat option on the |
| 793 | "[/help/diff|fossil diff]" command so that it shows a total |
| 794 | number of lines added and deleted and total number of files |
| 795 | modified. |
| 796 | * Add the "contact" sub-command to [/help/user|fossil user]. |
| 797 | * Added commands "[/help/all|fossil all git export]" and |
| 798 | "[/help/all|fossil all git status]". |
| 799 | * Added the "df=CHECKIN" query parameter to the |
| 800 | [/help/www/timeline|/timeline page]. |
| 801 | * Improvements to the "[/sitemap]" page. Add subpages |
| 802 | [/sitemap-timeline] and [/sitemap-test]. |
| 803 | * Better text position in cylinder objects of Pikchr diagrams. |
| 804 | * New "details.txt" settings available to custom skins to better control |
| 805 | the rendering of Pikchr diagrams: |
| @@ -757,24 +821,24 @@ | |
| 821 | * Added support for [./interwiki.md|interwiki links]. |
| 822 | * Enable <del> and <ins> markup in wiki. |
| 823 | * Improvements to the Forum threading display. |
| 824 | * Added support for embedding [./pikchr.md|pikchr] |
| 825 | markup in markdown and fossil-wiki content. |
| 826 | * The new "[/help/pikchr|pikchr]" command can render |
| 827 | pikchr scripts, optionally pre-processed with |
| 828 | [/doc/trunk/www/th1.md|TH1] blocks and variables exactly like |
| 829 | site skins are. |
| 830 | * The new [/help/www/pikchrshow|pikchrshow] page provides an |
| 831 | editor and previewer for pikchr markup. |
| 832 | * In [/help/www/wikiedit|/wikiedit] and |
| 833 | [/help/www/fileedit|/fileedit], Ctrl-Enter can now be used |
| 834 | initiate a preview and to toggle between the editor and preview |
| 835 | tabs. |
| 836 | * The <tt>/artifact</tt> and <tt>/file</tt> views, when in |
| 837 | line-number mode, now support interactive selection of a range |
| 838 | of lines to hyperlink to. |
| 839 | * Enhance the [/help/www/finfo|/finfo] webpage so that when query |
| 840 | parameters identify both a filename and a checkin, the resulting |
| 841 | graph tracks the identified file across renames. |
| 842 | * The built-in SQLite is updated to an alpha of version 3.34.0, and |
| 843 | the minimum SQLite version is increased to 3.34.0 because the |
| 844 | /finfo change in the previous bullet depends on enhancements to |
| @@ -783,18 +847,18 @@ | |
| 847 | * Countless other minor refinements and documentation improvements. |
| 848 | |
| 849 | <h2 id='v2_12'>Changes for Version 2.12.1 (2020-08-20)</h2> |
| 850 | |
| 851 | * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz. |
| 852 | * Security fix in the "[/help/git|fossil git export]" command. |
| 853 | The same fix is also backported to version 2.10.1 and 2.11.1. |
| 854 | New "safety-net" features were added to prevent similar problems |
| 855 | in the future. |
| 856 | * Enhancements to the graph display for cases when there are |
| 857 | many cherry-pick merges into a single check-in. |
| 858 | [/timeline?f=2d75e87b760c0a9|Example] |
| 859 | * Enhance the [/help/open|fossil open] command with the new |
| 860 | --workdir option and the ability to accept a URL as the repository |
| 861 | name, causing the remote repository to be cloned automatically. |
| 862 | Do not allow "fossil open" to open in a non-empty working directory |
| 863 | unless the --keep option or the new --force option is used. |
| 864 | * Enhance the markdown formatter to more closely follow the |
| @@ -803,65 +867,65 @@ | |
| 867 | Underscores in the middle of identifiers (ex: fossil_printf()) |
| 868 | no longer need to be escaped. |
| 869 | * The markdown-to-html translator can prevent unsafe HTML |
| 870 | (for example: <script>) on user-contributed pages like forum and |
| 871 | tickets and wiki. The admin can adjust this behavior using |
| 872 | the [/help/safe-html|safe-html setting] on the Admin/Wiki page. |
| 873 | The default is to disallow unsafe HTML everywhere. |
| 874 | [https://fossil-scm.org/forum/forumpost/3714e6568f|Example]. |
| 875 | * Added the "collapse" and "expand" capability for long forum posts. |
| 876 | [https://fossil-scm.org/forum/forumpost/9297029862|Example] |
| 877 | * The "[/help/remote-url|fossil remote]" command now has options for |
| 878 | specifying multiple persistent remotes with symbolic names. Currently |
| 879 | only one remote can be used at a time, but that might change in the |
| 880 | future. |
| 881 | * Add the "Remember me?" checkbox on the login page. Use a session |
| 882 | cookie for the login if it is not checked. |
| 883 | * Added the experimental "[/help/hook|fossil hook]" command for |
| 884 | managing "hook scripts" that run before checkin or after a push. |
| 885 | * Enhance the [/help/revert|fossil revert] command so that it |
| 886 | is able to revert all files beneath a directory. |
| 887 | * Add the [/help/bisect|fossil bisect skip] command. |
| 888 | * Add the [/help/backup|fossil backup] command. |
| 889 | * Enhance [/help/bisect|fossil bisect ui] so that it shows all unchecked |
| 890 | check-ins in between the innermost "good" and "bad" check-ins. |
| 891 | * Added the <tt>--reset</tt> flag to the "[/help/add|fossil add]", |
| 892 | "[/help/rm|fossil rm]", and |
| 893 | "[/help/addremove|fossil addremove]" commands. |
| 894 | * Added the "<tt>--min</tt> <i>N</i>" and "<tt>--logfile</tt> <i>FILENAME</i>" |
| 895 | flags to the [/help/backoffice|backoffice] command, as well as other |
| 896 | enhancements to make the backoffice command a viable replacement for |
| 897 | automatic backoffice. Other incremental backoffice improvements. |
| 898 | * Added the [/help/www/fileedit|/fileedit page], which allows |
| 899 | editing of text files online. Requires explicit activation by |
| 900 | a setup user. |
| 901 | * Translate built-in help text into HTML for display on web pages. |
| 902 | [/help/help|Example]. |
| 903 | * On the [/help/www/timeline|/timeline] webpage, the combination |
| 904 | of query parameters "p=CHECKIN" and "bt=ANCESTOR" draws all |
| 905 | ancestors of CHECKIN going back to ANCESTOR. For example, |
| 906 | [/timeline?p=202006271506&bt=version-2.11] shows all ancestors |
| 907 | of the checkin that occurred on 2020-06-27 15:06 going back to |
| 908 | the 2.11 release. |
| 909 | * Update the built-in SQLite so that the |
| 910 | "[/help/sql|fossil sql]" command supports new output |
| 911 | modes ".mode box" and ".mode json". |
| 912 | * Add the "<tt>obscure()</tt>" SQL function to the |
| 913 | "[/help/sql|fossil sql]" command. |
| 914 | * Added virtual tables "<tt>helptext</tt>" and "<tt>builtin</tt>" to |
| 915 | the "[/help/sql|fossil sql]" command, providing access to the |
| 916 | dispatch table including all help text, and the builtin data files, |
| 917 | respectively. |
| 918 | * [./delta_format.wiki|Delta compression] is now applied to forum edits. |
| 919 | * The [/help/www/wikiedit|wiki editor] has been modernized and is |
| 920 | now Ajax-based. The WYSIWYG editing option for Fossil-format wiki |
| 921 | pages was removed. (Please let us know, via the site's Forum menu, |
| 922 | if that removal unduly impacts you.) This also changes the semantics |
| 923 | of the wiki "Sandbox": that pseudo-page may be freely edited but |
| 924 | no longer saved via the UI (the [/help/wiki|wiki CLI command] |
| 925 | can, though). |
| 926 | * The [/help/allow-symlinks|allow-symlinks setting] no longer |
| 927 | syncs. It must be activated individually on any clones which require |
| 928 | symlinks. |
| 929 | * Countless documentation enhancements. |
| 930 | |
| 931 | <h2 id='v2_11'>Changes for Version 2.11 (2020-05-25)</h2> |
| @@ -873,46 +937,46 @@ | |
| 937 | can now omit punctation. So, for example, "202004181942" and |
| 938 | "2020-04-18 19:42" mean the same thing. |
| 939 | * Enhance backlink processing so that it works with Markdown-formatted |
| 940 | tickets and so that it works for wiki pages. |
| 941 | Ticket [a3572c6a5b47cd5a]. |
| 942 | <ul><li> "[/help/rebuild|fossil rebuild]" is needed to |
| 943 | take full advantage of this fix. Fossil will continue |
| 944 | to work without the rebuild, but the new backlinks will be missing.</ul> |
| 945 | * The algorithm for finding the |
| 946 | [./tech_overview.wiki#configloc|location of the configuration database] |
| 947 | is enhanced to be XDG-compliant. |
| 948 | * Add a hide/show feature to |
| 949 | [./wikitheory.wiki#assocwiki|associated wiki] display on |
| 950 | check-in and branch information pages. |
| 951 | * Enhance the "[/help/info|fossil info]" command so that it |
| 952 | works with no arguments even if not within an open check-out. |
| 953 | * Many improvements to the forum and especially email notification |
| 954 | of forum posts, in response to community feedback after switching |
| 955 | SQLite support from a mailing list over to the forum. |
| 956 | * Minimum length of a self-registered user ID increased from 3 to 6 |
| 957 | characters. |
| 958 | * When the "vfx" query parameter is used on the |
| 959 | "[/help/www/timeline|/timeline]" page, it causes the complete |
| 960 | text of forum posts to be displayed. |
| 961 | * Rework the "[/help/grep|fossil grep]" command to be more useful. |
| 962 | * Expose the [/help/redirect-to-https|redirect-to-https] |
| 963 | setting to the [/help/settings|settings] command. |
| 964 | * Improve support for CGI on IIS web servers. |
| 965 | * The [./serverext.wiki|/ext page] can now render index files, |
| 966 | in the same way as the embedded docs. |
| 967 | * Most commands now support the Unix-conventional "<tt>--</tt>" |
| 968 | flag to treat all following arguments as filenames |
| 969 | instead of flags. |
| 970 | * Added the [/help/mimetypes|mimetypes config setting] |
| 971 | (versionable) to enable mimetype overrides and custom definitions. |
| 972 | * Add an option on the /Admin/Timeline setup page to set a default |
| 973 | timeline style other than "Modern". |
| 974 | * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs |
| 975 | of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated |
| 976 | into the check-in hash for the document currently being viewed. |
| 977 | * Added the [/help/www/phantoms|/phantoms] webpage that shows all |
| 978 | phantom artifacts. |
| 979 | * Enhancements to phantom processing to try to reduce |
| 980 | bandwidth-using chatter about phantoms on the sync protocol. |
| 981 | * Security: Fossil now assumes that the schema of every |
| 982 | database it opens has been tampered with by an adversary and takes |
| @@ -920,23 +984,23 @@ | |
| 984 | * Security: Fossil now puts the Content-Security-Policy in the |
| 985 | HTTP reply header, in addition to also leaving it in the |
| 986 | HTML <head> section, so that it is always available, even |
| 987 | if a custom skin overrides the HTML <head> and omits |
| 988 | the CSP in the process. |
| 989 | * Output of the [/help/diff|fossil diff -y] command automatically |
| 990 | adjusts according to the terminal width. |
| 991 | * The Content-Security-Policy is now set using the |
| 992 | [/help/default-csp|default-csp setting]. |
| 993 | * Merge conflicts caused via the [/help/merge|merge] and |
| 994 | [/help/update|update] commands no longer leave temporary |
| 995 | files behind unless the new <tt>--keep-merge-files</tt> flag |
| 996 | is used. |
| 997 | * The [/help/www/artifact_stats|/artifact_stats page] is now accessible |
| 998 | to all users if the new "artifact_stats_enable" setting is turned |
| 999 | on. There is a new checkbox under the /Admin/Access menu to turn |
| 1000 | that capability on and off. |
| 1001 | * Add the [/help/tls-config|fossil tls-config] command for viewing |
| 1002 | the TLS configuration and the list of SSL Cert exceptions. |
| 1003 | * Captchas all include a button to read the captcha using an audio |
| 1004 | file, so that they can be completed by the visually impaired. |
| 1005 | * Stop using the IP address as part of the login cookie. |
| 1006 | * Bug fix: fix the SSL cert validation logic so that if an exception |
| @@ -957,27 +1021,27 @@ | |
| 1021 | <h2 id='v2_10'>Changes for Version 2.10 (2019-10-04)</h2> |
| 1022 | |
| 1023 | * (2.10.2): backport security fixes from 2.12.1 |
| 1024 | * (2.10.1): backport security fix for the "fossil git export" command. |
| 1025 | * Added support for [./serverext.wiki|CGI-based Server Extensions]. |
| 1026 | * Added the [/help/repolist-skin|repolist-skin] setting used to |
| 1027 | add style to repository list pages. |
| 1028 | * Enhance the hierarchical display of Forum threads to do less |
| 1029 | indentation and to provide links back to the previous message |
| 1030 | in the thread. Provide sequential numbers for all messages in |
| 1031 | a forum thread. |
| 1032 | * Add support for fenced code blocks and improved hyperlink |
| 1033 | processing to the [/md_rules|markdown formatter]. |
| 1034 | * Add support for hyperlinks to wiki pages in the |
| 1035 | [/md_rules|markdown formatter]. |
| 1036 | * Enhance the [/help/www/stat|/stat] page so that it gives the |
| 1037 | option to show a breakdown of forum posts. |
| 1038 | * The special check-in name "merge-in:BRANCH" means the source of |
| 1039 | the most recent merge-in from the parent branch of BRANCH. |
| 1040 | * Add hyperlinks to branch-diffs on the /info page and from |
| 1041 | timelines of a branch. |
| 1042 | * Add graphical context on the [/help/www/vdiff|/vdiff] page. |
| 1043 | * Uppercase query parameters, POST parameters, and cookie names are |
| 1044 | converted to all lowercase and entered into the parameter set, |
| 1045 | instead of being discarded. |
| 1046 | * Change the default [./hashpolicy.wiki|hash policy] to SHA3. |
| 1047 | * Timeout [./server/any/cgi.md|CGI requests] after 300 seconds, or |
| @@ -990,14 +1054,14 @@ | |
| 1054 | * Performance optimizations. |
| 1055 | * Many documentation improvements. |
| 1056 | |
| 1057 | <h2 id='v2_9'>Changes for Version 2.9 (2019-07-13)</h2> |
| 1058 | |
| 1059 | * Added the [/help/git|fossil git export] command and instructions |
| 1060 | for [./mirrortogithub.md|creating a GitHub mirror of a Fossil project]. |
| 1061 | * Improved handling of relative hyperlinks on the |
| 1062 | [/help/www/artifact|/artifact] pages for wiki. For example, |
| 1063 | hyperlinks and the lizard <img> now work correctly |
| 1064 | for both [/artifact/2ff24ab0887cf522] and |
| 1065 | [/doc/0d7ac90d575004c2415/www/index.wiki]. |
| 1066 | * Enhancements to the timeline graph layout, to show more information |
| 1067 | with less clutter. |
| @@ -1006,28 +1070,28 @@ | |
| 1070 | configuration. |
| 1071 | * Copy buttons added to various check-in hash and branch name links. |
| 1072 | * Double-clicking on a /timeline graph node now jumps to the /info page |
| 1073 | for the check-in. So, repurpose the timestamp hyperlink to show all |
| 1074 | activity around that check-in in time. |
| 1075 | * Added the [/help/touch|fossil touch] command, and the --setmtime |
| 1076 | option on the [/help/open|fossil open] and |
| 1077 | [/help/update|fossil update] commands. |
| 1078 | * Many documentation enhancements. |
| 1079 | * For the "[/help/update|fossil update]" and |
| 1080 | "[/help/checkout|fossil checkout]" commands, if a |
| 1081 | managed file is removed because it is no longer part of the target |
| 1082 | check-in and the directory containing the file is empty after the |
| 1083 | file is removed and the directory is not the current working |
| 1084 | directory and is not on the [/help/empty-dirs|empty-dirs] |
| 1085 | list, then also remove the directory. |
| 1086 | * Update internal Unicode character tables, used in regular expression |
| 1087 | handling, from version 11.0 to 12.1. |
| 1088 | * In "[/help/regexp|fossil regexp]", "[/help/grep|fossil grep]" |
| 1089 | and the TH1 "regexp" command, the -nocase option now removes multiple |
| 1090 | diacritics from the same character (derived from SQLite's |
| 1091 | remove_diacritics=2) |
| 1092 | * Added the [/help/www/secureraw|/secureraw] page that requires the |
| 1093 | complete SHA1 or SHA3 hash, not just a prefix, before it will deliver |
| 1094 | content. |
| 1095 | * Accept purely numeric ISO8601 date/time strings as long as they |
| 1096 | do not conflict with a hash. Example: "20190510134217" instead of |
| 1097 | "2019-05-10 13:42:17". This helps keep URLs shorter and less |
| @@ -1040,19 +1104,19 @@ | |
| 1104 | extra path element is not needed. |
| 1105 | * If an automatic sync gets a permanent redirect request, then update |
| 1106 | the saved remote URL to the new address. |
| 1107 | * Temporary filenames (for example used for external "diff" commands) |
| 1108 | try to preserve the suffix of the original file. |
| 1109 | * Added the [/help/www/thisdayinhistory|/thisdayinhistory] web page. |
| 1110 | * Enhanced parsing of [/help/www/timeline|/timeline] query parameters |
| 1111 | "ymd=", "ym=", and "yw=". All arguments are option (in which case they |
| 1112 | default to the current time) and all accept ISO8601 date/times without |
| 1113 | punctuation. |
| 1114 | * Automatically disapprove pending moderation requests for a user when |
| 1115 | that user is deleted. This helps in dealing with spam-bots. |
| 1116 | * Improvements to the "Capability Summary" section in the |
| 1117 | [/help/www/secaudit0|Security Audit] web-page. |
| 1118 | * Use new "ci-lock" and "ci-lock-failed" pragmas in the |
| 1119 | [./sync.wiki|sync protocol] to try to prevent accident forks |
| 1120 | caused by concurrent commits when operating in auto-sync mode. |
| 1121 | * Fix a bug ([https://fossil-scm.org/forum/forumpost/c51b9a1169|details]) |
| 1122 | that can cause repository databases to be overwritten with debugging |
| @@ -1153,11 +1217,11 @@ | |
| 1217 | included in the header or footer. |
| 1218 | * Add the [./backoffice.md|backoffice]. |
| 1219 | * Update internal Unicode character tables, used in regular expression |
| 1220 | handling, from version 10.0 to 11.0. |
| 1221 | * Improvements to the "Security Audit" administration page |
| 1222 | * Add the [/help/branch|fossil branch current] command. |
| 1223 | * Add the [./grep.md|grep] command. |
| 1224 | * Update the built-in SQLite to version 3.25.1. |
| 1225 | * Some code and interfaces are in place to support sending and |
| 1226 | receiving email directly via SMTP, but this feature is not yet |
| 1227 | complete or ready for production use. |
| @@ -1176,20 +1240,20 @@ | |
| 1240 | same as "Verbose" in the previous release. The "Verbose" mode is |
| 1241 | now like "Compact" except the extra check-in details are shown by |
| 1242 | default. |
| 1243 | * Add support for ETags:, Last-Modified:, and If-Modified-Since: |
| 1244 | cache control mechanisms. |
| 1245 | * Enhance the [/help/www/tarball|/tarball], |
| 1246 | [/help/www/zip|/zip], and |
| 1247 | [/help/www/sqlar|/sqlar] pages so that the checkin |
| 1248 | name to be downloaded can be expressed as part of the URI, |
| 1249 | and without the need for query parameters. |
| 1250 | * On the [/help/www/timeline|/timeline] webpage, add the days=N |
| 1251 | query parameter and enhance the ymd=DATE and yw=DATE query parameters |
| 1252 | to accept 'now' as an argument to show the latest day or week. |
| 1253 | * In the web page that comes up in response to the |
| 1254 | [/help/all|fossil all ui] command, show the last modification |
| 1255 | time for each repository, and allow click-to-sort on the modification |
| 1256 | time column. |
| 1257 | * In the tarball cache replacement algorithm, give extra weight to |
| 1258 | tarballs that have been accessed more than once. |
| 1259 | * Additional defenses against web-based attacks. There have not been |
| @@ -1236,35 +1300,35 @@ | |
| 1300 | to define their own URLs on the web interface that are rewritten to |
| 1301 | built-in URLs with specific parameters. Create and configure URL Aliases |
| 1302 | using the /Setup/URL_Aliases menu option in the web interface. |
| 1303 | * Add tech-note search capability. |
| 1304 | * Add the -r|--revision and -o|--origin options to the |
| 1305 | [/help/annotate|annotate] command. |
| 1306 | * Add the origin= query parameter to the [/help/www/annotate|/annotate] |
| 1307 | webpage. |
| 1308 | * The [/help/annotate|fossil annotate] command and the |
| 1309 | [/help/www/annotate|/annotate] web page go backwards in time as far |
| 1310 | as can be computed in 30 milliseconds by default, rather than stopping |
| 1311 | after 20 steps. The new limit= query parameter or the --limit command-line |
| 1312 | option can be used to alter this timeout. |
| 1313 | * Provide separate [/help#settings|on-line help screens for each setting]. |
| 1314 | * Back out support for the --no-dir-symlinks option |
| 1315 | * Remove support from the legacy configuration sync protocol. The only |
| 1316 | way now to do a configuration push or pull is to use the new protocol that |
| 1317 | was added in 2011. |
| 1318 | * Add the from= and to= query parameters to [/help/www/fdiff|/fdiff] |
| 1319 | in order to get a diff of two files in the same check-in. |
| 1320 | * Fix the "ssh://" protocol to prevent an attack whereby the attacker convinces |
| 1321 | a victim to run a "clone" with a dodgy URL and thereby gains access to their |
| 1322 | system. |
| 1323 | * Provide a checkbox that will temporarily disable all ad-units. |
| 1324 | * Improvements to the [/help/www/stat|/stat] page |
| 1325 | * Various new hyperlinks to the [/help/www/bloblist|/bloblist] |
| 1326 | and [/help/www/bigbloblist|/bigbloblist] pages. |
| 1327 | * Correct the [/help/www/doc|/doc] page to support read-only repositories. |
| 1328 | * Correct [/help/www/zip|/zip], [/help/www/tarball|/tarball], |
| 1329 | [/help/zip|zip], and [/help/tarball|tarball] pages and commands to |
| 1330 | honor the versioned manifest setting when outside of an open checkout |
| 1331 | directory. |
| 1332 | * The admin-log and access-log settings are now on by default for |
| 1333 | new repositories. |
| 1334 | * Update the built-in SQLite to version 3.21.0. |
| @@ -1272,34 +1336,34 @@ | |
| 1336 | <h2 id='v2_3'>Changes for Version 2.3 (2017-07-21)</h2> |
| 1337 | |
| 1338 | * Update the built-in SQLite to version 3.20.0 (beta). |
| 1339 | * Update internal Unicode character tables, used in regular expression |
| 1340 | handling, from version 9.0 to 10.0. |
| 1341 | * Show the last-sync-URL on the [/help/www/urllist|/urllist] page. |
| 1342 | * Added the "Event Summary" activity report. |
| 1343 | [/reports?type=ci&view=lastchng|example] |
| 1344 | * Added the "Security Audit" page, available to administrators only |
| 1345 | * Added the Last Login time to the user list page, for administrators only |
| 1346 | * Added the --numstat option to the [/help/diff|fossil diff] command |
| 1347 | * Limit the size of the heap and stack on unix systems, as a proactive |
| 1348 | defense against the |
| 1349 | [https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt|Stack Clash] |
| 1350 | attack. |
| 1351 | * Fix "database locked" warnings caused by "PRAGMA optimize". |
| 1352 | * Fix a potential XSS vulnerability on the |
| 1353 | [/help/www/help|/help] webpage. |
| 1354 | * Documentation updates |
| 1355 | |
| 1356 | <h2 id='v2_2'>Changes for Version 2.2 (2017-04-11)</h2> |
| 1357 | |
| 1358 | * GIT comment tags are now handled by Fossil during import/export. |
| 1359 | * Show the content of README files on directory listings. |
| 1360 | ([/file/skins|example]) |
| 1361 | * Support for Basic Authentication if enabled (default off). |
| 1362 | * Show the hash algorithms used on the |
| 1363 | [/help/www/rcvfromlist|/rcvfromlist] page. |
| 1364 | * The [/help/www/tarball|/tarball] and [/help/www/zip|/zip] pages |
| 1365 | now use the the r= query parameter |
| 1366 | to select which check-in to deliver. The uuid= query parameter |
| 1367 | is still accepted for backwards compatibility. |
| 1368 | * Update the built-in SQLite to version 3.18.0. |
| 1369 | * Run "[https://www.sqlite.org/pragma.html#pragma_optimize|PRAGMA optimize]" |
| @@ -1308,12 +1372,12 @@ | |
| 1372 | <h2 id='v2_1'>Changes for Version 2.1 (2017-03-10)</h2> |
| 1373 | |
| 1374 | * Add support for [./hashpolicy.wiki|hash policies] that control which |
| 1375 | of the Hardened-SHA1 or SHA3-256 algorithms is used to name new |
| 1376 | artifacts. |
| 1377 | * Add the "gshow" and "gcat" subcommands to [/help/stash|fossil stash]. |
| 1378 | * Add the [/help/www/juvlist|/juvlist] web page and use it to construct |
| 1379 | the [/uv/download.html|Download Page] of the Fossil self-hosting website |
| 1380 | using Ajax. |
| 1381 | |
| 1382 | <h2 id='v2_0'>Changes for Version 2.0 (2017-03-03)</h2> |
| 1383 | |
| @@ -1321,23 +1385,23 @@ | |
| 1385 | [https://github.com/cr-marcstevens/sha1collisiondetection|hardened SHA1] |
| 1386 | implementation by Marc Stevens and Dan Shumow. |
| 1387 | * Add the ability to read and understand |
| 1388 | [./fileformat.wiki#names|artifact names] that are based on SHA3-256 |
| 1389 | rather than SHA1, but do not actually generate any such names. |
| 1390 | * Added the [/help/sha3sum|sha3sum] command. |
| 1391 | * Update the built-in SQLite to version 3.17.0. |
| 1392 | |
| 1393 | <h2 id='v1_37'>Changes for Version 1.37 (2017-01-16)</h2> |
| 1394 | |
| 1395 | * Add checkbox widgets to various web pages. See [/technote/8d18bf27e9| |
| 1396 | this technote] for more information. To get the checkboxes to look as |
| 1397 | intended, you must update the CSS in your repository and all clones. |
| 1398 | * Add the [/help/all|fossil all ui] command |
| 1399 | * Add the [/help/www/file|/file] webpage |
| 1400 | * Enhance the [/help/www/brlist|/brlist] webpage to make use of branch colors. |
| 1401 | * Add support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the |
| 1402 | [/help/www/timeline|/timeline] webpage, with associated form widgets. |
| 1403 | * Enhance the [/help/changes|changes] and [/help/status|status] commands |
| 1404 | with many new filter options so that specific kinds of changes can be |
| 1405 | found without having to pipe through grep or sed. |
| 1406 | * Enhanced the [/help/sqlite3|fossil sql] command so that it opens the |
| 1407 | [./tech_overview.wiki#localdb|checkout database] and the |
| @@ -1350,11 +1414,11 @@ | |
| 1414 | </ul> |
| 1415 | * Rename crnl-glob [/help/settings|setting] to crlf-glob, but keep |
| 1416 | crnl-glob as a compatibility alias. |
| 1417 | * Added the --command option to the [/help/diff|diff] command. |
| 1418 | * Fix a C99-ism that prevents the 1.36 release from building with MSVC. |
| 1419 | * Fix [/help/ticket|ticket set] when using the "+" prefix with fields |
| 1420 | from the "ticketchng" table. |
| 1421 | * Remove the "fusefs" command from builds that do not have the underlying |
| 1422 | support enabled. |
| 1423 | * Fixes for incremental git import/export. |
| 1424 | * Minor security enhancements to |
| @@ -1364,58 +1428,58 @@ | |
| 1428 | |
| 1429 | |
| 1430 | <h2 id='v1_36'>Changes for Version 1.36 (2016-10-24)</h2> |
| 1431 | |
| 1432 | * Add support for [./unvers.wiki|unversioned content], |
| 1433 | the [/help/unversioned|fossil unversioned] command and the |
| 1434 | [/help/www/uv|/uv] and [/uvlist] web pages. |
| 1435 | * The [/uv/download.html|download page] is moved into |
| 1436 | [./unvers.wiki|unversioned content] so that the self-hosting Fossil |
| 1437 | websites no longer uses any external content. |
| 1438 | * Added the "Search" button to the graphical diff generated by |
| 1439 | the --tk option on the [/help/diff|diff] command. |
| 1440 | * Added the "--checkin VERSION" option to the |
| 1441 | [/help/diff|diff] command. |
| 1442 | * Various performance enhancements to the [/help/diff|diff] command. |
| 1443 | * Update internal Unicode character tables, used in regular expression |
| 1444 | handling, from version 8.0 to 9.0. |
| 1445 | * Update the built-in SQLite to version 3.15. Fossil now requires |
| 1446 | the SQLITE_DBCONFIG_MAINDBNAME interface of SQLite which is only available |
| 1447 | in SQLite version 3.15 and later and so Fossil will not work with |
| 1448 | earlier SQLite versions. |
| 1449 | * Fix [https://www.mail-archive.com/[email protected]/msg23618.html|multi-line timeline bug] |
| 1450 | * Enhance the [/help/purge|fossil purge] command. |
| 1451 | * New command [/help/shell|fossil shell]. |
| 1452 | * SQL parameters whose names are all lower-case in Ticket Report SQL |
| 1453 | queries are filled in using HTTP query parameter values. |
| 1454 | * Added support for [./childprojects.wiki|child projects] that are |
| 1455 | able to pull from their parent but not push. |
| 1456 | * Added the -nocomplain option to the TH1 "query" command. |
| 1457 | * Added support for the chng=GLOBLIST query parameter on the |
| 1458 | [/help/www/timeline|/timeline] webpage. |
| 1459 | |
| 1460 | <h2 id='v1_35'>Changes for Version 1.35 (2016-06-14)</h2> |
| 1461 | |
| 1462 | * Enable symlinks by default on all non-Windows platforms. |
| 1463 | * Enhance the [/md_rules|Markdown formatting] so that hyperlinks that begin |
| 1464 | with "/" are relative to the root of the Fossil repository. |
| 1465 | * Rework the [/help/www/setup_ulist|/setup_list page] (the User List page) |
| 1466 | to display all users in a click-to-sort table. |
| 1467 | * Fix backslash-octal escape on filenames while importing from git |
| 1468 | * When markdown documents begin with <h1> HTML elements, use that |
| 1469 | header at the document title. |
| 1470 | * Added the [/help/www/bigbloblist|/bigbloblist page]. |
| 1471 | * Enhance the [/help/www/finfo|/finfo page] so that when it is showing |
| 1472 | the ancestors of a particular file version, it only shows direct |
| 1473 | ancestors and omits changes on branches, thus making it show the same set |
| 1474 | of ancestors that are used for [/help/www/blame|/blame]. |
| 1475 | * Added the --page option to the [/help/ui|fossil ui] command |
| 1476 | * Added the [/help/bisect|fossil bisect ui] command |
| 1477 | * Enhanced the [/help/diff|fossil diff] command so that it accepts |
| 1478 | directory names as arguments and computes diffs on all files contained |
| 1479 | within those directories. |
| 1480 | * Fix the [/help/add|fossil add] command so that it shows "SKIP" for |
| 1481 | files added that were already under management. |
| 1482 | * TH1 enhancements: |
| 1483 | <ul><li>Add <nowiki>[array exists]</nowiki> command.</li> |
| 1484 | <li>Add minimal <nowiki>[array names]</nowiki> command.</li> |
| 1485 | <li>Add tcl_platform(engine) and tcl_platform(platform) array |
| @@ -1423,32 +1487,32 @@ | |
| 1487 | </ul> |
| 1488 | * Get autosetup working with MinGW. |
| 1489 | * Fix autosetup detection of zlib in the source tree. |
| 1490 | * Added autosetup detection of OpenSSL when it may be present under the |
| 1491 | "compat" subdirectory of the source tree. |
| 1492 | * Added the [/help/reparent|fossil reparent] command |
| 1493 | * Added --include and --exclude options to [/help/tarball|fossil tarball] |
| 1494 | and [/help/zip|fossil zip] and the in= and ex= query parameters to the |
| 1495 | [/help/www/tarball|/tarball] and [/help/www/zip|/zip] web pages. |
| 1496 | * Add support for [./encryptedrepos.wiki|encrypted Fossil repositories]. |
| 1497 | * If the FOSSIL_PWREADER environment variable is set, then use the program it |
| 1498 | names in place of getpass() to read passwords and passphrases |
| 1499 | * Option --baseurl now works on Windows. |
| 1500 | * Numerous documentation improvements. |
| 1501 | * Update the built-in SQLite to version 3.13.0. |
| 1502 | |
| 1503 | <h2 id='v1_34'>Changes for Version 1.34 (2015-11-02)</h2> |
| 1504 | |
| 1505 | * Make the [/help/clean|fossil clean] command undoable for files less |
| 1506 | than 10MiB. |
| 1507 | * Update internal Unicode character tables, used in regular expression |
| 1508 | handling, from version 7.0 to 8.0. |
| 1509 | * Add the new [/help/amend|amend] command which is used to modify |
| 1510 | tags of a "check-in". |
| 1511 | * Fix bug in [/help/import|import] command, handling version 3 of |
| 1512 | the svndump format for subversion. |
| 1513 | * Add the [/help/all|all cache] command. |
| 1514 | * TH1 enhancements: |
| 1515 | <ul><li>Add minimal <nowiki>[lsearch]</nowiki> command. Only exact |
| 1516 | case-sensitive matching is supported.</li> |
| 1517 | <li>Add the <nowiki>[glob_match]</nowiki>, <nowiki>[markdown]</nowiki>, |
| 1518 | <nowiki>[dir]</nowiki>, and <nowiki>[encode64]</nowiki> commands.</li> |
| @@ -1455,106 +1519,106 @@ | |
| 1519 | <li>Add the <nowiki>[tclIsSafe] and [tclMakeSafe]</nowiki> commands to |
| 1520 | the Tcl integration subsystem.</li> |
| 1521 | <li>Add 'double', 'integer', and 'list' classes to the |
| 1522 | <nowiki>[string is]</nowiki> command.</li> |
| 1523 | </ul> |
| 1524 | * Add the --undo option to the [/help/diff|diff] command. |
| 1525 | * Build-in Antirez's "linenoise" command-line editing library for use with |
| 1526 | the [/help/sqlite3|fossil sql] command on Unix platforms. |
| 1527 | * Add [/help/stash|stash cat] as an alias for the |
| 1528 | [/help/stash|stash show] command. |
| 1529 | * Automatically pull before [/help/merge|fossil merge] when auto-sync |
| 1530 | is enabled. |
| 1531 | * Fix --hard option to [/help/mv|fossil mv] and [/help/rm|fossil rm] |
| 1532 | to enable them to work properly with certain relative paths. |
| 1533 | * Change the mimetype for ".n" and ".man" files to text/plain. |
| 1534 | * Display improvements in the [/help/bisect|fossil bisect chart] command. |
| 1535 | * Updated the built-in SQLite to version 3.9.1 and activated JSON1 and FTS5 |
| 1536 | support (both currently unused within Fossil). |
| 1537 | |
| 1538 | <h2 id='v1_33'>Changes for Version 1.33 (2015-05-23)</h2> |
| 1539 | * Improved fork detection on [/help/update|fossil update], |
| 1540 | [/help/status|fossil status] and related commands. |
| 1541 | * Change the default skin to what used to be called "San Francisco Modern". |
| 1542 | * Add the [/repo-tabsize] web page |
| 1543 | * Add [/help/import|fossil import --svn], for importing a subversion |
| 1544 | repository into fossil which was exported using "svnadmin dump". |
| 1545 | * Add the "--compress-only" option to [/help/rebuild|fossil rebuild]. |
| 1546 | * Use a pie chart on the [/reports?view=byuser] page. |
| 1547 | * Enhanced [/help/clean|fossil clean --verily] so that it ignores |
| 1548 | keep-glob and ignore-glob settings. Added the -x alias for --verily. |
| 1549 | * Add the --soft and --hard options to [/help/rm|fossil rm] and |
| 1550 | [/help/mv|fossil mv]. The default is still --soft, but that is |
| 1551 | now configurable at compile-time or by the mv-rm-files setting. |
| 1552 | * Improved ability to [./customgraph.md|customize the timeline graph]. |
| 1553 | * Improvements to the [/sitemap] page. |
| 1554 | * Automatically adjust the [/help/timeline|CLI timeline] to the terminal |
| 1555 | width on Linux. |
| 1556 | * Added <nowiki>[info commands] and [info vars]</nowiki> commands to TH1. |
| 1557 | These commands perform the same function as their Tcl counterparts, |
| 1558 | except they do not accept a pattern argument. |
| 1559 | * Fix some obscure issues with TH1 expression processing. |
| 1560 | * Fix titles in search results for documents that are not wiki, markdown, |
| 1561 | or HTML. |
| 1562 | * Formally translate TH1 to Tcl return codes and vice-versa, where |
| 1563 | necessary, in the Tcl integration subsystem. |
| 1564 | * Add [/help/leaves|fossil leaves -multiple], for finding multiple |
| 1565 | leaves on the same branch. |
| 1566 | * Added the "Blitz" skin option. |
| 1567 | * Removed the ".fossil-settings/keep-glob" file. It should not have been |
| 1568 | checked into the repository. |
| 1569 | * Update the built-in SQLite to version 3.8.10.2. |
| 1570 | * Make [/help/open|fossil open] honor ".fossil-settings/allow-symlinks". |
| 1571 | * Allow [/help/add|fossil add] to be used on symlinks to nonexistent or |
| 1572 | unreadable files in the same way as [/help/addremove|fossil addremove]. |
| 1573 | * Added fork warning to be issued if sync produced a fork |
| 1574 | * Update the [/help/www/info|info] page to report when a file becomes a |
| 1575 | symlink. Additionally show the UUID for files whose types have changed |
| 1576 | without changing contents or symlink target. |
| 1577 | * Have [/help/changes|fossil changes] and |
| 1578 | [/help/status|fossil status] report when executable or symlink status |
| 1579 | changes on otherwise unmodified files. |
| 1580 | * Permit filtering weekday and file [/help/www/reports|reports] by user. |
| 1581 | Also ensure the user parameter is preserved when changing types. Add a |
| 1582 | field for direct entry of the user name to each applicable report. |
| 1583 | * Create parent directories of [/help/settings|empty-dirs] if they don't |
| 1584 | already exist. |
| 1585 | * Inhibit timeline links to wiki pages that have been deleted. |
| 1586 | |
| 1587 | <h2 id='v1_33'>Changes for Version 1.32 (2015-03-14)</h2> |
| 1588 | * When creating a new repository using [/help/init|fossil init], ensure |
| 1589 | that the new repository is fully compatible with historical versions of |
| 1590 | Fossil by having a valid manifest as RID 1. |
| 1591 | * Anti-aliased rendering of arrowheads on timeline graphs. |
| 1592 | * Added vi/less-style key bindings to the --tk diff GUI. |
| 1593 | * Documentation updates to fix spellings and changes all "checkins" to |
| 1594 | "check-ins". |
| 1595 | * Add the --repolist option to server commands such as |
| 1596 | [/help/server|fossil server] or [/help/http|fossil http]. |
| 1597 | * Added the "Xekri" skin. |
| 1598 | * Enhance the "ln=" query parameter on artifact displays to accept multiple |
| 1599 | ranges, separate by spaces (or "+" when URL-encoded). |
| 1600 | * Added [/help/forget|fossil forget] as an alias for |
| 1601 | [/help/rm|fossil rm]. |
| 1602 | |
| 1603 | <h2 id='v1_31'>Changes For Version 1.31 (2015-02-23)</h2> |
| 1604 | * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID |
| 1605 | columns to the schema, to support better drawing of file change graphs. |
| 1606 | A [/help/rebuild|fossil rebuild] is recommended but is not required. |
| 1607 | so that the new graph drawing logic can work effectively. |
| 1608 | * Added [/search|search] over Check-in comments, Documents, Tickets and |
| 1609 | Wiki. Disabled by default. The search can be either a full-scan or it |
| 1610 | can use an index that is kept up-to-date automatically. The new |
| 1611 | /srchsetup web-page and the [/help/fts-config|fts-config] command |
| 1612 | were added to help configure the search capability. Expect further |
| 1613 | enhancements to the search capabilities in subsequent releases. |
| 1614 | * Added form elements to some submenus (in particular the /timeline) |
| 1615 | for easier operation. |
| 1616 | * Added the --ifneeded option to [/help/rebuild|fossil rebuild]. |
| 1617 | * Added "override skins" using the "skin:" line of the CGI script or |
| 1618 | using the --skin LABEL option on the [/help/server|server], |
| 1619 | [/help/ui|ui], or [/help/http|http] commands. |
| 1620 | * Embedded html documents that begin with |
| 1621 | <doc class="fossil-doc"> are displayed with standard |
| 1622 | headers and footers added. |
| 1623 | * Allow <div style='...'> markup in [/wiki_rules|wiki]. |
| 1624 | * Renamed "Events" to "Technical Notes", while updating the technote |
| @@ -1561,24 +1625,24 @@ | |
| 1625 | display and control pages. Add support for technotes as plain text |
| 1626 | or as Markdown. |
| 1627 | * Added the [/md_rules] pages containing summary instructions on the |
| 1628 | Markdown format. |
| 1629 | * Added the --repolist and --nojail options to the various server commands |
| 1630 | (ex: [/help/server|fossil server]). |
| 1631 | * Added the [/help/all|fossil all add] subcommand to "fossil all". |
| 1632 | * Improvements to the /login page. Some hyperlinks to pages that require |
| 1633 | "anonymous" privileges are displayed even if the current user is "nobody" |
| 1634 | but automatically redirect to /login. |
| 1635 | * The [/help/www/doc|/doc] web-page will now try to deliver the file |
| 1636 | "404.md" from the top-level directory (if such a file exists) in |
| 1637 | place of its built-in 404 text. |
| 1638 | * Download of Tarballs and ZIP Archives by user "nobody" is now enabled |
| 1639 | by default in new repositories. |
| 1640 | * Enhancements to the table sorting controls. More display tables |
| 1641 | are now sortable. |
| 1642 | * Add IPv6 support to [/help/sync|fossil sync] and |
| 1643 | [/help/clone|fossil clone] |
| 1644 | * Add more skins such as "San Francisco Modern" and "Eagle". |
| 1645 | * During shutdown, check to see if the check-out database (".fslckout") |
| 1646 | contains a lot of free space, and if it does, VACUUM it. |
| 1647 | * Added the [/mimetype_list] page. |
| 1648 | * Added the [/hash-collisions] page. |
| @@ -1586,14 +1650,14 @@ | |
| 1650 | ticket reports. |
| 1651 | * Break out the components (css, footer, and header) for the |
| 1652 | various built-in skins into separate files in the source tree. |
| 1653 | |
| 1654 | <h2 id='v1_30'>Changes For Version 1.30 (2015-01-19)</h2> |
| 1655 | * Added the [/help/bundle|fossil bundle] command. |
| 1656 | * Added the [/help/purge|fossil purge] command. |
| 1657 | * Added the [/help/publish|fossil publish] command. |
| 1658 | * Added the [/help/unpublished|fossil unpublished] command. |
| 1659 | * Enhance the [/tree] webpage to show the age of each file with the option |
| 1660 | to sort by age. |
| 1661 | * Enhance the [/brlist] webpage to show additional information about each branch |
| 1662 | and to be sortable by clicking on column headers. |
| 1663 | * Add support for Docker. Just install docker and type |
| @@ -1603,17 +1667,17 @@ | |
| 1667 | copies of all historical check-ins. This only works on systems that |
| 1668 | support FuseFS. |
| 1669 | * Add the administrative log that records all configuration. |
| 1670 | * Added the [/sitemap] webpage. |
| 1671 | * Added the [/bloblist] web page. |
| 1672 | * Let [/help/new|fossil new] no longer create an initial empty commit |
| 1673 | by default. The first commit after checking out an empty repository will |
| 1674 | become the initial commit. |
| 1675 | * Added the [/help/all|fossil all dbstat] and |
| 1676 | [/help/all|fossil all info] commands. |
| 1677 | * Update SQLite to version 3.8.8. |
| 1678 | * Added the --verily option to the [/help/clean|fossil clean] command. |
| 1679 | * Add the "autosync-tries" setting to control the number of autosync attempts |
| 1680 | before returning an error. |
| 1681 | * Added a compile-time option (--with-miniz) to build using miniz instead |
| 1682 | of zlib. Disabled by default. |
| 1683 | * Support customization of commands and webpages, including the ability to |
| @@ -1623,12 +1687,12 @@ | |
| 1687 | [trace], [getParameter], [setParameter], [artifact], and |
| 1688 | [globalState]</nowiki> commands to TH1, primarily for use by TH1 hooks. |
| 1689 | * Automatically adjust the width of command-line timeline output according to the |
| 1690 | detected width of the terminal. |
| 1691 | * Prompt the user to optionally fix invalid UTF-8 at check-in. |
| 1692 | * Added a line-number toggle option to the [/help/www/info|/info] |
| 1693 | and [/help/www/artifact|/artifact] pages. |
| 1694 | * Most commands now issue errors rather than silently ignoring unrecognized |
| 1695 | command-line options. |
| 1696 | * Use full 40-character SHA1 hashes (instead of abbreviations) in most |
| 1697 | internal URLs. |
| 1698 | * The "ssh:" sync method on Windows now uses "plink.exe" instead of "ssh" as |
| @@ -1642,11 +1706,11 @@ | |
| 1706 | * Fix a rare and long-standing sync protocol bug |
| 1707 | that would silently prevent the sync from running to completion. Before |
| 1708 | this bug-fix it was sometimes necessary to do "fossil sync --verily" to |
| 1709 | get two repositories in sync. |
| 1710 | * Add the [/finfo?name=src/foci.c|files_of_checkin] virtual table - useful |
| 1711 | for ad hoc queries in the [/help/sqlite3|fossil sql] interface, |
| 1712 | and also used internally. |
| 1713 | * Added the "$secureurl" TH1 variable for use in headers and footers. |
| 1714 | * (Internal:) Add the ability to include resources as separate files in the |
| 1715 | source tree that are converted into constant byte arrays in the compiled |
| 1716 | binary. Use this feature to store the Tk script that implements the --tk |
| @@ -1666,143 +1730,143 @@ | |
| 1730 | * The [/reports] page now requires Read ("o") permissions. The "byweek" |
| 1731 | report now properly propagates the selected year through the event type |
| 1732 | filter links. |
| 1733 | * The [/help/info | info command] now shows leaf status of the checkout. |
| 1734 | * Add support for tunneling https through a http proxy (Ticket [e854101c4f]). |
| 1735 | * Add option --empty to the "[/help/open | fossil open]" command. |
| 1736 | * Enhanced [/help/www/fileage|the fileage page] to support a glob parameter. |
| 1737 | * Add -w|--ignore-all-space and -Z|--ignore-trailing-space options to |
| 1738 | [/help/annotate|fossil annotate], [/help/blame|fossil blame], |
| 1739 | [/help/diff|fossil (g)diff], [/help/stash|fossil stash diff]. |
| 1740 | * Add --strip-trailing-cr option to [/help/diff|fossil (g)diff] and |
| 1741 | [/help/stash|fossil stash diff]. |
| 1742 | * Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff |
| 1743 | and /vdiff UI pages. |
| 1744 | * Enhance [/reports?view=byweekday|/reports] with a "byweekday" view. |
| 1745 | * Enhance the [/help/cat|fossil cat] command so that it works outside |
| 1746 | of a checkout when using the -R command-line option. |
| 1747 | * Use full-length SHA1 hashes, not abbreviations, in most hyperlinks. |
| 1748 | * Correctly render the <title> markup on wiki pages in the |
| 1749 | [/help/www/artifact|/artifact] webpage. |
| 1750 | * Enhance the [/help/whatis|fossil whatis] command to report on attachments |
| 1751 | and cluster artifacts. Added the [/help/test-whatis-all] command for |
| 1752 | testing purposes. |
| 1753 | * Add support for HTTP Basic Authentication on [/help/clone|clone] and |
| 1754 | [/help/sync|sync]. |
| 1755 | * Fix the [/help/stash|stash] so that it remembers added files and re-adds |
| 1756 | them when the stash is applied. |
| 1757 | * Fix the server so that it avoids writing to the database (and thus avoids |
| 1758 | database locking issues) on a |
| 1759 | [/help/pull|pull] or [/help/clone|clone]. |
| 1760 | * Add support for [./server.wiki#loadmgmt|server load management] using both |
| 1761 | a cache of expensive pages (the [/help/cache|fossil cache] command) |
| 1762 | and by rejecting expensive page requests when the server load average is too |
| 1763 | high. |
| 1764 | * Add the [/help/praise|fossil praise] command as an alias for |
| 1765 | [/help/blame|fossil blame] for subversion compatibility. |
| 1766 | * Enhance the [/help/test-diff|fossil test-diff] command with -y or --tk |
| 1767 | options so that it shows both filenames above their respective columns in |
| 1768 | the side-by-side diff output. |
| 1769 | * Issue a warning if a [/help/add|fossil add] command tries to add a file |
| 1770 | that matches the ignore-glob. |
| 1771 | * Add option -W|--width to "[/help/stash|fossil stash ls]" |
| 1772 | and "[/help/leaves|fossil leaves]" commands. |
| 1773 | * Enhance support for running as the root user. Now works on Haiku. |
| 1774 | * Added the <tt>-empty</tt> option to [/help/new|fossil new], which |
| 1775 | causes it to not create an initial empty commit. The first commit after |
| 1776 | checking out a repo created this way will become the initial commit. |
| 1777 | * Enhance sync operations by committing each round-trip to minimize number |
| 1778 | of retransmits when autosync fails. Include option for |
| 1779 | [/help/update| fossil update] and [/help/merge| fossil merge] to |
| 1780 | continue even if missing content. |
| 1781 | * Minor portability fixes for platforms where the char type is unsigned |
| 1782 | by default. |
| 1783 | |
| 1784 | <h2>Changes For Version 1.28 (2014-01-27)</h2> |
| 1785 | * Enhance [/help/www/reports | /reports] to support event type filtering. |
| 1786 | * When cloning a repository, the user name passed via the URL (if any) |
| 1787 | is now used as the default local admin user's name. |
| 1788 | * Enhance the SSH transport mechanism so that it runs a single instance of |
| 1789 | the "fossil" executable on the remote side, obviating the need for a shell |
| 1790 | on the remote side. Some users may need to add the "?fossil=/path/to/fossil" |
| 1791 | query parameter to "ssh:" URIs if their fossil binary is not in a standard |
| 1792 | place. |
| 1793 | * Add the "[/help/blame | fossil blame]" command that works just like |
| 1794 | "fossil annotate" but uses a different output format that includes the |
| 1795 | user who made each change and omits line numbers. |
| 1796 | * Add the "Tarball and ZIP-archive Prefix" configuration parameter under |
| 1797 | Admin/Configuration. |
| 1798 | * Fix CGI processing so that it works on web servers that do not |
| 1799 | supply REQUEST_URI. |
| 1800 | * Add options --dirsonly, --emptydirs, and --allckouts to the |
| 1801 | "[/help/clean | fossil clean]" command. |
| 1802 | * Ten-fold performance improvement in large "fossil blame" or |
| 1803 | "fossil annotate" commands. |
| 1804 | * Add option -W|--width and --offset to "[/help/timeline | fossil timeline]" |
| 1805 | and "[/help/finfo | fossil finfo]" commands. |
| 1806 | * Option -n|--limit of "[/help/timeline | fossil timeline]" now |
| 1807 | specifies the number of entries, just like all other commands which |
| 1808 | have the -n|--limit option. The various timeline-related functions |
| 1809 | now output "--- ?? limit (??) reached ---" at the end whenever |
| 1810 | appropriate. Use "-n 0" if no limit is desired. |
| 1811 | * Fix handling of password embedded in Fossil URL. |
| 1812 | * New <tt>--once</tt> option to [/help/clone | fossil clone] command |
| 1813 | which does not store the URL or password when cloning. |
| 1814 | * Modify [/help/ui | fossil ui] to respect "default user" in an open |
| 1815 | repository. |
| 1816 | * Fossil now hides check-ins that have the "hidden" tag in timeline webpages. |
| 1817 | * Enhance <tt>/ci_edit</tt> page to add the "hidden" tag to check-ins. |
| 1818 | * Advanced possibilities for commit and ticket change notifications over |
| 1819 | http using TH1 scripting. |
| 1820 | * Add --sha1sum and --integrate options |
| 1821 | to the "[/help/commit | fossil commit]" command. |
| 1822 | * Add the "clean" and "extra" subcommands to the |
| 1823 | "[/help/all | fossil all]" command |
| 1824 | * Add the --whatif option to "[/help/clean|fossil clean]" that works the |
| 1825 | same as "--dry-run", |
| 1826 | so that the name does not collide with the --dry-run option of "fossil all". |
| 1827 | * Provide a configuration option to show dates on the web timeline |
| 1828 | as "YYMMMDD HH:MM" |
| 1829 | * Add an option to the "stats" webpage that allows an administrator to see |
| 1830 | the current repository schema. |
| 1831 | * Enhancements to the "[/help/www/vdiff|/vdiff]" webpage for more difference |
| 1832 | display options. |
| 1833 | * Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative |
| 1834 | to "/dir" and make it the default way of showing file lists. |
| 1835 | * Send gzipped HTTP responses to clients that support it. |
| 1836 | |
| 1837 | <h2>Changes For Version 1.27 (2013-09-11)</h2> |
| 1838 | * Enhance the [/help/changes | fossil changes], |
| 1839 | [/help/clean | fossil clean], [/help/extras | fossil extras], |
| 1840 | [/help/ls | fossil ls] and [/help/status | fossil status] commands |
| 1841 | to restrict operation to files and directories named on the command-line. |
| 1842 | * New --integrate option to [/help/merge | fossil merge], which |
| 1843 | automatically closes the merged branch when committing. |
| 1844 | * Renamed <tt>/stats_report</tt> page to [/reports]. Graph width is now |
| 1845 | relative, not absolute. |
| 1846 | * Added <tt>yw=YYYY-WW</tt> (year-week) filter to timeline to limit the results |
| 1847 | to a specific year and calendar week number, e.g. [/timeline?yw=2013-01]. |
| 1848 | * Updates to SQLite to prevent opening a repository file using file descriptors |
| 1849 | 1 or 2 on Unix. This fixes a bug under which an assertion failure could |
| 1850 | overwrite part of a repository database file, corrupting it. |
| 1851 | * Added support for unlimited line lengths in side-by-side diffs. |
| 1852 | * New --close option to [/help/commit | fossil commit], which |
| 1853 | immediately closes the branch being committed. |
| 1854 | * Added <tt>chart</tt> option to [/help/bisect | fossil bisect]. |
| 1855 | * Improvements to the "human or bot?" determination. |
| 1856 | * Reports errors about missing CGI-standard environment variables for HTTP |
| 1857 | servers which do not support them. |
| 1858 | * Minor improvements to sync support on Windows. |
| 1859 | * Added <tt>--scgi</tt> option to [/help/server | fossil server]. |
| 1860 | * Internal improvements to the sync process. |
| 1861 | * The internals of the JSON API are now MIT-licensed, so downstream |
| 1862 | users/packagers are no longer affected by the "do no evil" license |
| 1863 | clause. |
| 1864 | |
| 1865 | <h2>Changes For Version 1.26 (2013-06-18)</h2> |
| 1866 | * The argument to the --port option for the [/help/ui | fossil ui] and |
| 1867 | [/help/server | fossil server] commands can take an IP address in addition |
| 1868 | to the port number, causing Fossil to bind to just that one IP address. |
| 1869 | * After prompting for a password, also ask if that password should be |
| 1870 | remembered. |
| 1871 | * Performance improvements to the diff engine. |
| 1872 | * Fix the side-by-side diff engine to work better with multi-byte Unicode text. |
| @@ -1809,11 +1873,11 @@ | |
| 1873 | * Color-coding in the web-based annotation (blame) display. Fix the annotation |
| 1874 | engine so that it is no longer confused by time-warps. |
| 1875 | * The markdown formatter is now available by default and can be used for |
| 1876 | tickets, wiki, and embedded documentation. |
| 1877 | * Add subcommands "fossil bisect log" and "fossil bisect status" to the |
| 1878 | [/help/bisect | fossil bisect] command, as well as other bisect enhancements. |
| 1879 | * Enhanced defenses that prevent spiders from using excessive CPU and bandwidth. |
| 1880 | * Consistent use of the -n or --dry-run command line options. |
| 1881 | * Win32: Fossil now understands Cygwin paths containing one or more of |
| 1882 | the characters <nowiki>"*:<>?|</nowiki>. Those are normally forbidden in |
| 1883 | win32. This means that the win32 fossil.exe is better usable in a Cygwin |
| 1884 |
+8
-8
| --- www/chat.md | ||
| +++ www/chat.md | ||
| @@ -55,11 +55,11 @@ | ||
| 55 | 55 | from the repository database. |
| 56 | 56 | |
| 57 | 57 | ## <a id="usage"></a>Usage |
| 58 | 58 | |
| 59 | 59 | For users with appropriate permissions, simply browse to the |
| 60 | -[/chat](/help?cmd=/chat) to start up a chat session. The default | |
| 60 | +[/chat](/help/www/chat) to start up a chat session. The default | |
| 61 | 61 | skin includes a "Chat" entry on the menu bar on wide screens for |
| 62 | 62 | people with chat privilege. There is also a "Chat" option on |
| 63 | 63 | the [Sitemap page](/sitemap), which means that chat will appear |
| 64 | 64 | as an option under the hamburger menu for many [skins](./customskin.md). |
| 65 | 65 | |
| @@ -122,19 +122,19 @@ | ||
| 122 | 122 | specific user by tapping on that user's name, tapping a second time to |
| 123 | 123 | remove the filter. |
| 124 | 124 | |
| 125 | 125 | ### <a id="cli"></a> The `fossil chat` Command |
| 126 | 126 | |
| 127 | -Type [fossil chat](/help?cmd=chat) from within any open check-out | |
| 127 | +Type [fossil chat](/help/chat) from within any open check-out | |
| 128 | 128 | to bring up a chatroom for the project that is in that checkout. |
| 129 | 129 | The new chat window will attempt to connect to the default sync |
| 130 | 130 | target for that check-out (the server whose URL is shown by the |
| 131 | -[fossil remote](/help?cmd=remote) command). | |
| 131 | +[fossil remote](/help/remote) command). | |
| 132 | 132 | |
| 133 | 133 | ### <a id="robots"></a> Chat Messages From Robots |
| 134 | 134 | |
| 135 | -The [fossil chat send](/help?cmd=chat) can be used by project-specific | |
| 135 | +The [fossil chat send](/help/chat) can be used by project-specific | |
| 136 | 136 | robots to send notifications to the chatroom. For example, on the |
| 137 | 137 | [SQLite project](https://sqlite.org/) (for which the Fossil chatroom |
| 138 | 138 | feature, and indeed all of Fossil, was invented) there are long-running |
| 139 | 139 | fuzz servers that sometimes run across obscure problems. Whenever this |
| 140 | 140 | happens, a message is sent to the SQLite developers chatroom alerting |
| @@ -155,11 +155,11 @@ | ||
| 155 | 155 | Substitute the appropriate project URL, robot account |
| 156 | 156 | name and password, message text and file attachment, of course. |
| 157 | 157 | |
| 158 | 158 | ### <a id="chat-robot"></a> Chat Messages For Timeline Events |
| 159 | 159 | |
| 160 | -If the [chat-timeline-user setting](/help?cmd=chat-timeline-user) is not an | |
| 160 | +If the [chat-timeline-user setting](/help/chat-timeline-user) is not an | |
| 161 | 161 | empty string, then any change to the repository that would normally result |
| 162 | 162 | in a new timeline entry is announced in the chatroom. The announcement |
| 163 | 163 | appears to come from a user whose name is given by the chat-timeline-user |
| 164 | 164 | setting. |
| 165 | 165 | |
| @@ -189,11 +189,11 @@ | ||
| 189 | 189 | |
| 190 | 190 | *You do not need to understand how Fossil chat works in order to use it. |
| 191 | 191 | But many developers prefer to know how their tools work. |
| 192 | 192 | This section is provided for the benefit of those curious developers.* |
| 193 | 193 | |
| 194 | -The [/chat](/help?cmd=/chat) webpage downloads a small amount of HTML | |
| 194 | +The [/chat](/help/www/chat) webpage downloads a small amount of HTML | |
| 195 | 195 | and a small amount of javascript to run the chat session. The |
| 196 | 196 | javascript uses XMLHttpRequest (XHR) to download chat content, post |
| 197 | 197 | new content, or delete historical messages. The following web |
| 198 | 198 | interfaces are used by the XHR: |
| 199 | 199 | |
| @@ -246,17 +246,17 @@ | ||
| 246 | 246 | ~~~ |
| 247 | 247 | |
| 248 | 248 | The CHAT table is not cross-linked with any other tables in the repository |
| 249 | 249 | schema. An administrator can "DROP TABLE chat;" at any time, without |
| 250 | 250 | harm (apart from deleting all chat history, of course). The CHAT table |
| 251 | -is dropped when running [fossil scrub --verily](/help?cmd=scrub). | |
| 251 | +is dropped when running [fossil scrub --verily](/help/scrub). | |
| 252 | 252 | |
| 253 | 253 | On the server-side, message text is stored exactly as entered by the |
| 254 | 254 | users. The /chat-poll page queries the CHAT table and constructs a |
| 255 | 255 | JSON reply described in the [/chat-poll |
| 256 | -documentation](/help?cmd=/chat-poll). The message text is translated | |
| 256 | +documentation](/help/www/chat-poll). The message text is translated | |
| 257 | 257 | into HTML before being converted to JSON so that the text can be |
| 258 | 258 | safely added to the display using assignment to `innerHTML`. Though |
| 259 | 259 | `innerHTML` assignment is generally considered unsafe, it is only so |
| 260 | 260 | with untrusted content from untrusted sources. The chat content goes |
| 261 | 261 | through sanitization steps which eliminate any potential security |
| 262 | 262 | vulnerabilities of assigning that content to `innerHTML`. |
| 263 | 263 |
| --- www/chat.md | |
| +++ www/chat.md | |
| @@ -55,11 +55,11 @@ | |
| 55 | from the repository database. |
| 56 | |
| 57 | ## <a id="usage"></a>Usage |
| 58 | |
| 59 | For users with appropriate permissions, simply browse to the |
| 60 | [/chat](/help?cmd=/chat) to start up a chat session. The default |
| 61 | skin includes a "Chat" entry on the menu bar on wide screens for |
| 62 | people with chat privilege. There is also a "Chat" option on |
| 63 | the [Sitemap page](/sitemap), which means that chat will appear |
| 64 | as an option under the hamburger menu for many [skins](./customskin.md). |
| 65 | |
| @@ -122,19 +122,19 @@ | |
| 122 | specific user by tapping on that user's name, tapping a second time to |
| 123 | remove the filter. |
| 124 | |
| 125 | ### <a id="cli"></a> The `fossil chat` Command |
| 126 | |
| 127 | Type [fossil chat](/help?cmd=chat) from within any open check-out |
| 128 | to bring up a chatroom for the project that is in that checkout. |
| 129 | The new chat window will attempt to connect to the default sync |
| 130 | target for that check-out (the server whose URL is shown by the |
| 131 | [fossil remote](/help?cmd=remote) command). |
| 132 | |
| 133 | ### <a id="robots"></a> Chat Messages From Robots |
| 134 | |
| 135 | The [fossil chat send](/help?cmd=chat) can be used by project-specific |
| 136 | robots to send notifications to the chatroom. For example, on the |
| 137 | [SQLite project](https://sqlite.org/) (for which the Fossil chatroom |
| 138 | feature, and indeed all of Fossil, was invented) there are long-running |
| 139 | fuzz servers that sometimes run across obscure problems. Whenever this |
| 140 | happens, a message is sent to the SQLite developers chatroom alerting |
| @@ -155,11 +155,11 @@ | |
| 155 | Substitute the appropriate project URL, robot account |
| 156 | name and password, message text and file attachment, of course. |
| 157 | |
| 158 | ### <a id="chat-robot"></a> Chat Messages For Timeline Events |
| 159 | |
| 160 | If the [chat-timeline-user setting](/help?cmd=chat-timeline-user) is not an |
| 161 | empty string, then any change to the repository that would normally result |
| 162 | in a new timeline entry is announced in the chatroom. The announcement |
| 163 | appears to come from a user whose name is given by the chat-timeline-user |
| 164 | setting. |
| 165 | |
| @@ -189,11 +189,11 @@ | |
| 189 | |
| 190 | *You do not need to understand how Fossil chat works in order to use it. |
| 191 | But many developers prefer to know how their tools work. |
| 192 | This section is provided for the benefit of those curious developers.* |
| 193 | |
| 194 | The [/chat](/help?cmd=/chat) webpage downloads a small amount of HTML |
| 195 | and a small amount of javascript to run the chat session. The |
| 196 | javascript uses XMLHttpRequest (XHR) to download chat content, post |
| 197 | new content, or delete historical messages. The following web |
| 198 | interfaces are used by the XHR: |
| 199 | |
| @@ -246,17 +246,17 @@ | |
| 246 | ~~~ |
| 247 | |
| 248 | The CHAT table is not cross-linked with any other tables in the repository |
| 249 | schema. An administrator can "DROP TABLE chat;" at any time, without |
| 250 | harm (apart from deleting all chat history, of course). The CHAT table |
| 251 | is dropped when running [fossil scrub --verily](/help?cmd=scrub). |
| 252 | |
| 253 | On the server-side, message text is stored exactly as entered by the |
| 254 | users. The /chat-poll page queries the CHAT table and constructs a |
| 255 | JSON reply described in the [/chat-poll |
| 256 | documentation](/help?cmd=/chat-poll). The message text is translated |
| 257 | into HTML before being converted to JSON so that the text can be |
| 258 | safely added to the display using assignment to `innerHTML`. Though |
| 259 | `innerHTML` assignment is generally considered unsafe, it is only so |
| 260 | with untrusted content from untrusted sources. The chat content goes |
| 261 | through sanitization steps which eliminate any potential security |
| 262 | vulnerabilities of assigning that content to `innerHTML`. |
| 263 |
| --- www/chat.md | |
| +++ www/chat.md | |
| @@ -55,11 +55,11 @@ | |
| 55 | from the repository database. |
| 56 | |
| 57 | ## <a id="usage"></a>Usage |
| 58 | |
| 59 | For users with appropriate permissions, simply browse to the |
| 60 | [/chat](/help/www/chat) to start up a chat session. The default |
| 61 | skin includes a "Chat" entry on the menu bar on wide screens for |
| 62 | people with chat privilege. There is also a "Chat" option on |
| 63 | the [Sitemap page](/sitemap), which means that chat will appear |
| 64 | as an option under the hamburger menu for many [skins](./customskin.md). |
| 65 | |
| @@ -122,19 +122,19 @@ | |
| 122 | specific user by tapping on that user's name, tapping a second time to |
| 123 | remove the filter. |
| 124 | |
| 125 | ### <a id="cli"></a> The `fossil chat` Command |
| 126 | |
| 127 | Type [fossil chat](/help/chat) from within any open check-out |
| 128 | to bring up a chatroom for the project that is in that checkout. |
| 129 | The new chat window will attempt to connect to the default sync |
| 130 | target for that check-out (the server whose URL is shown by the |
| 131 | [fossil remote](/help/remote) command). |
| 132 | |
| 133 | ### <a id="robots"></a> Chat Messages From Robots |
| 134 | |
| 135 | The [fossil chat send](/help/chat) can be used by project-specific |
| 136 | robots to send notifications to the chatroom. For example, on the |
| 137 | [SQLite project](https://sqlite.org/) (for which the Fossil chatroom |
| 138 | feature, and indeed all of Fossil, was invented) there are long-running |
| 139 | fuzz servers that sometimes run across obscure problems. Whenever this |
| 140 | happens, a message is sent to the SQLite developers chatroom alerting |
| @@ -155,11 +155,11 @@ | |
| 155 | Substitute the appropriate project URL, robot account |
| 156 | name and password, message text and file attachment, of course. |
| 157 | |
| 158 | ### <a id="chat-robot"></a> Chat Messages For Timeline Events |
| 159 | |
| 160 | If the [chat-timeline-user setting](/help/chat-timeline-user) is not an |
| 161 | empty string, then any change to the repository that would normally result |
| 162 | in a new timeline entry is announced in the chatroom. The announcement |
| 163 | appears to come from a user whose name is given by the chat-timeline-user |
| 164 | setting. |
| 165 | |
| @@ -189,11 +189,11 @@ | |
| 189 | |
| 190 | *You do not need to understand how Fossil chat works in order to use it. |
| 191 | But many developers prefer to know how their tools work. |
| 192 | This section is provided for the benefit of those curious developers.* |
| 193 | |
| 194 | The [/chat](/help/www/chat) webpage downloads a small amount of HTML |
| 195 | and a small amount of javascript to run the chat session. The |
| 196 | javascript uses XMLHttpRequest (XHR) to download chat content, post |
| 197 | new content, or delete historical messages. The following web |
| 198 | interfaces are used by the XHR: |
| 199 | |
| @@ -246,17 +246,17 @@ | |
| 246 | ~~~ |
| 247 | |
| 248 | The CHAT table is not cross-linked with any other tables in the repository |
| 249 | schema. An administrator can "DROP TABLE chat;" at any time, without |
| 250 | harm (apart from deleting all chat history, of course). The CHAT table |
| 251 | is dropped when running [fossil scrub --verily](/help/scrub). |
| 252 | |
| 253 | On the server-side, message text is stored exactly as entered by the |
| 254 | users. The /chat-poll page queries the CHAT table and constructs a |
| 255 | JSON reply described in the [/chat-poll |
| 256 | documentation](/help/www/chat-poll). The message text is translated |
| 257 | into HTML before being converted to JSON so that the text can be |
| 258 | safely added to the display using assignment to `innerHTML`. Though |
| 259 | `innerHTML` assignment is generally considered unsafe, it is only so |
| 260 | with untrusted content from untrusted sources. The chat content goes |
| 261 | through sanitization steps which eliminate any potential security |
| 262 | vulnerabilities of assigning that content to `innerHTML`. |
| 263 |
+1
-1
| --- www/childprojects.wiki | ||
| +++ www/childprojects.wiki | ||
| @@ -47,11 +47,11 @@ | ||
| 47 | 47 | |
| 48 | 48 | The child project and the parent project will not normally be able to sync |
| 49 | 49 | with one another, since they are now separate projects with distinct |
| 50 | 50 | project codes. However, if the |
| 51 | 51 | "--from-parent-project" command-line option is provided to the |
| 52 | -"[/help?cmd=pull|fossil pull]" command in the child, and the URL of | |
| 52 | +"[/help/pull|fossil pull]" command in the child, and the URL of | |
| 53 | 53 | parent repository is also provided on the command-line, then updates to |
| 54 | 54 | the parent project that occurred after the child was created will be added |
| 55 | 55 | to the child repository. Thus, by periodically doing a |
| 56 | 56 | pull --from-parent-project, the child project is able to stay up to date |
| 57 | 57 | with all the latest changes in the parent. |
| 58 | 58 |
| --- www/childprojects.wiki | |
| +++ www/childprojects.wiki | |
| @@ -47,11 +47,11 @@ | |
| 47 | |
| 48 | The child project and the parent project will not normally be able to sync |
| 49 | with one another, since they are now separate projects with distinct |
| 50 | project codes. However, if the |
| 51 | "--from-parent-project" command-line option is provided to the |
| 52 | "[/help?cmd=pull|fossil pull]" command in the child, and the URL of |
| 53 | parent repository is also provided on the command-line, then updates to |
| 54 | the parent project that occurred after the child was created will be added |
| 55 | to the child repository. Thus, by periodically doing a |
| 56 | pull --from-parent-project, the child project is able to stay up to date |
| 57 | with all the latest changes in the parent. |
| 58 |
| --- www/childprojects.wiki | |
| +++ www/childprojects.wiki | |
| @@ -47,11 +47,11 @@ | |
| 47 | |
| 48 | The child project and the parent project will not normally be able to sync |
| 49 | with one another, since they are now separate projects with distinct |
| 50 | project codes. However, if the |
| 51 | "--from-parent-project" command-line option is provided to the |
| 52 | "[/help/pull|fossil pull]" command in the child, and the URL of |
| 53 | parent repository is also provided on the command-line, then updates to |
| 54 | the parent project that occurred after the child was created will be added |
| 55 | to the child repository. Thus, by periodically doing a |
| 56 | pull --from-parent-project, the child project is able to stay up to date |
| 57 | with all the latest changes in the parent. |
| 58 |
+1
-1
| --- www/chroot.md | ||
| +++ www/chroot.md | ||
| @@ -30,11 +30,11 @@ | ||
| 30 | 30 | [configured Fossil with `--static`][bld] to avoid it |
| 31 | 31 | |
| 32 | 32 | Fossil does all of this as one of many layers of defense against |
| 33 | 33 | hacks and exploits. You can prevent Fossil from entering the chroot |
| 34 | 34 | jail using the <tt>--nojail</tt> option to the |
| 35 | -[fossil server command](/help?cmd=server) | |
| 35 | +[fossil server command](/help/server) | |
| 36 | 36 | but you cannot make Fossil hold onto root privileges. Fossil always drops |
| 37 | 37 | root privilege before accepting inputs, for security. |
| 38 | 38 | |
| 39 | 39 | |
| 40 | 40 | [bld]: https://fossil-scm.org/home/doc/trunk/www/build.wiki |
| 41 | 41 |
| --- www/chroot.md | |
| +++ www/chroot.md | |
| @@ -30,11 +30,11 @@ | |
| 30 | [configured Fossil with `--static`][bld] to avoid it |
| 31 | |
| 32 | Fossil does all of this as one of many layers of defense against |
| 33 | hacks and exploits. You can prevent Fossil from entering the chroot |
| 34 | jail using the <tt>--nojail</tt> option to the |
| 35 | [fossil server command](/help?cmd=server) |
| 36 | but you cannot make Fossil hold onto root privileges. Fossil always drops |
| 37 | root privilege before accepting inputs, for security. |
| 38 | |
| 39 | |
| 40 | [bld]: https://fossil-scm.org/home/doc/trunk/www/build.wiki |
| 41 |
| --- www/chroot.md | |
| +++ www/chroot.md | |
| @@ -30,11 +30,11 @@ | |
| 30 | [configured Fossil with `--static`][bld] to avoid it |
| 31 | |
| 32 | Fossil does all of this as one of many layers of defense against |
| 33 | hacks and exploits. You can prevent Fossil from entering the chroot |
| 34 | jail using the <tt>--nojail</tt> option to the |
| 35 | [fossil server command](/help/server) |
| 36 | but you cannot make Fossil hold onto root privileges. Fossil always drops |
| 37 | root privilege before accepting inputs, for security. |
| 38 | |
| 39 | |
| 40 | [bld]: https://fossil-scm.org/home/doc/trunk/www/build.wiki |
| 41 |
+1
-1
| --- www/ckout-workflows.md | ||
| +++ www/ckout-workflows.md | ||
| @@ -135,8 +135,8 @@ | ||
| 135 | 135 | |
| 136 | 136 | The `/repo` addition is the key: whatever comes after is used as the |
| 137 | 137 | repository name. [See the docs][clone] for more details. |
| 138 | 138 | |
| 139 | 139 | [caod]: https://fossil-scm.org/forum/forumpost/3f143cec74 |
| 140 | -[clone]: /help?cmd=clone | |
| 140 | +[clone]: /help/clone | |
| 141 | 141 | |
| 142 | 142 | <div style="height:50em" id="this-space-intentionally-left-blank"></div> |
| 143 | 143 |
| --- www/ckout-workflows.md | |
| +++ www/ckout-workflows.md | |
| @@ -135,8 +135,8 @@ | |
| 135 | |
| 136 | The `/repo` addition is the key: whatever comes after is used as the |
| 137 | repository name. [See the docs][clone] for more details. |
| 138 | |
| 139 | [caod]: https://fossil-scm.org/forum/forumpost/3f143cec74 |
| 140 | [clone]: /help?cmd=clone |
| 141 | |
| 142 | <div style="height:50em" id="this-space-intentionally-left-blank"></div> |
| 143 |
| --- www/ckout-workflows.md | |
| +++ www/ckout-workflows.md | |
| @@ -135,8 +135,8 @@ | |
| 135 | |
| 136 | The `/repo` addition is the key: whatever comes after is used as the |
| 137 | repository name. [See the docs][clone] for more details. |
| 138 | |
| 139 | [caod]: https://fossil-scm.org/forum/forumpost/3f143cec74 |
| 140 | [clone]: /help/clone |
| 141 | |
| 142 | <div style="height:50em" id="this-space-intentionally-left-blank"></div> |
| 143 |
+2
-2
| --- www/co-vs-up.md | ||
| +++ www/co-vs-up.md | ||
| @@ -24,8 +24,8 @@ | ||
| 24 | 24 | |
| 25 | 25 | In summary, these are two separate commands; neither is an alias for the |
| 26 | 26 | other. They overlap enough that they can be used interchangeably for |
| 27 | 27 | some use cases, but `update` is more powerful and more broadly useful. |
| 28 | 28 | |
| 29 | -[co]: /help?cmd=checkout | |
| 29 | +[co]: /help/checkout | |
| 30 | 30 | [cvsmu]: http://web.mit.edu/gnu/doc/html/cvs_7.html#SEC37 |
| 31 | -[up]: /help?cmd=update | |
| 31 | +[up]: /help/update | |
| 32 | 32 |
| --- www/co-vs-up.md | |
| +++ www/co-vs-up.md | |
| @@ -24,8 +24,8 @@ | |
| 24 | |
| 25 | In summary, these are two separate commands; neither is an alias for the |
| 26 | other. They overlap enough that they can be used interchangeably for |
| 27 | some use cases, but `update` is more powerful and more broadly useful. |
| 28 | |
| 29 | [co]: /help?cmd=checkout |
| 30 | [cvsmu]: http://web.mit.edu/gnu/doc/html/cvs_7.html#SEC37 |
| 31 | [up]: /help?cmd=update |
| 32 |
| --- www/co-vs-up.md | |
| +++ www/co-vs-up.md | |
| @@ -24,8 +24,8 @@ | |
| 24 | |
| 25 | In summary, these are two separate commands; neither is an alias for the |
| 26 | other. They overlap enough that they can be used interchangeably for |
| 27 | some use cases, but `update` is more powerful and more broadly useful. |
| 28 | |
| 29 | [co]: /help/checkout |
| 30 | [cvsmu]: http://web.mit.edu/gnu/doc/html/cvs_7.html#SEC37 |
| 31 | [up]: /help/update |
| 32 |
+4
-4
| --- www/concepts.wiki | ||
| +++ www/concepts.wiki | ||
| @@ -435,24 +435,24 @@ | ||
| 435 | 435 | a lot of work and normally takes time, patience, and a lot of system |
| 436 | 436 | knowledge. Fossil is designed to avoid this frustration. Setting up |
| 437 | 437 | a server with Fossil is ridiculously easy. You have four options: |
| 438 | 438 | |
| 439 | 439 | # <b>Stand-alone server.</b> |
| 440 | - Simply run the [/help?cmd=server|fossil server] or | |
| 441 | - [/help?cmd=ui|fossil ui] command from the command-line. | |
| 440 | + Simply run the [/help/server|fossil server] or | |
| 441 | + [/help/ui|fossil ui] command from the command-line. | |
| 442 | 442 | <br><br> |
| 443 | 443 | # <b>CGI.</b> |
| 444 | 444 | Install a 2-line CGI script on a CGI-enabled web-server like Apache. |
| 445 | 445 | <br><br> |
| 446 | 446 | # <b>SCGI.</b> |
| 447 | 447 | Start an SCGI server using the |
| 448 | - [/help?cmd=server| fossil server --scgi] command for handling | |
| 448 | + [/help/server| fossil server --scgi] command for handling | |
| 449 | 449 | SCGI requests from web-servers like Nginx. |
| 450 | 450 | <br><br> |
| 451 | 451 | # <b>Inetd or Stunnel.</b> |
| 452 | 452 | Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests |
| 453 | - directly to the [/help?cmd=http|fossil http] command. | |
| 453 | + directly to the [/help/http|fossil http] command. | |
| 454 | 454 | |
| 455 | 455 | See the [./server/ | How To Configure A Fossil Server] document |
| 456 | 456 | for details. |
| 457 | 457 | |
| 458 | 458 | <h2>6.0 Review Of Key Concepts</h2> |
| 459 | 459 |
| --- www/concepts.wiki | |
| +++ www/concepts.wiki | |
| @@ -435,24 +435,24 @@ | |
| 435 | a lot of work and normally takes time, patience, and a lot of system |
| 436 | knowledge. Fossil is designed to avoid this frustration. Setting up |
| 437 | a server with Fossil is ridiculously easy. You have four options: |
| 438 | |
| 439 | # <b>Stand-alone server.</b> |
| 440 | Simply run the [/help?cmd=server|fossil server] or |
| 441 | [/help?cmd=ui|fossil ui] command from the command-line. |
| 442 | <br><br> |
| 443 | # <b>CGI.</b> |
| 444 | Install a 2-line CGI script on a CGI-enabled web-server like Apache. |
| 445 | <br><br> |
| 446 | # <b>SCGI.</b> |
| 447 | Start an SCGI server using the |
| 448 | [/help?cmd=server| fossil server --scgi] command for handling |
| 449 | SCGI requests from web-servers like Nginx. |
| 450 | <br><br> |
| 451 | # <b>Inetd or Stunnel.</b> |
| 452 | Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests |
| 453 | directly to the [/help?cmd=http|fossil http] command. |
| 454 | |
| 455 | See the [./server/ | How To Configure A Fossil Server] document |
| 456 | for details. |
| 457 | |
| 458 | <h2>6.0 Review Of Key Concepts</h2> |
| 459 |
| --- www/concepts.wiki | |
| +++ www/concepts.wiki | |
| @@ -435,24 +435,24 @@ | |
| 435 | a lot of work and normally takes time, patience, and a lot of system |
| 436 | knowledge. Fossil is designed to avoid this frustration. Setting up |
| 437 | a server with Fossil is ridiculously easy. You have four options: |
| 438 | |
| 439 | # <b>Stand-alone server.</b> |
| 440 | Simply run the [/help/server|fossil server] or |
| 441 | [/help/ui|fossil ui] command from the command-line. |
| 442 | <br><br> |
| 443 | # <b>CGI.</b> |
| 444 | Install a 2-line CGI script on a CGI-enabled web-server like Apache. |
| 445 | <br><br> |
| 446 | # <b>SCGI.</b> |
| 447 | Start an SCGI server using the |
| 448 | [/help/server| fossil server --scgi] command for handling |
| 449 | SCGI requests from web-servers like Nginx. |
| 450 | <br><br> |
| 451 | # <b>Inetd or Stunnel.</b> |
| 452 | Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests |
| 453 | directly to the [/help/http|fossil http] command. |
| 454 | |
| 455 | See the [./server/ | How To Configure A Fossil Server] document |
| 456 | for details. |
| 457 | |
| 458 | <h2>6.0 Review Of Key Concepts</h2> |
| 459 |
+1
-2
| --- www/containers.md | ||
| +++ www/containers.md | ||
| @@ -541,12 +541,11 @@ | ||
| 541 | 541 | |
| 542 | 542 | I was then able to enable email alert forwarding for select repositories |
| 543 | 543 | after configuring them per [the docs](./alerts.md) by saying: |
| 544 | 544 | |
| 545 | 545 | $ systemctl --user daemon-reload |
| 546 | - $ systemctl --user enable alert-sender@myproject | |
| 547 | - $ systemctl --user start alert-sender@myproject | |
| 546 | + $ systemctl --user enable --now alert-sender@myproject | |
| 548 | 547 | |
| 549 | 548 | Because this is a parameterized script and we’ve set our repository |
| 550 | 549 | paths predictably, you can do this for as many repositories as you need |
| 551 | 550 | to by passing their names after the “`@`” sign in the commands above. |
| 552 | 551 | |
| 553 | 552 |
| --- www/containers.md | |
| +++ www/containers.md | |
| @@ -541,12 +541,11 @@ | |
| 541 | |
| 542 | I was then able to enable email alert forwarding for select repositories |
| 543 | after configuring them per [the docs](./alerts.md) by saying: |
| 544 | |
| 545 | $ systemctl --user daemon-reload |
| 546 | $ systemctl --user enable alert-sender@myproject |
| 547 | $ systemctl --user start alert-sender@myproject |
| 548 | |
| 549 | Because this is a parameterized script and we’ve set our repository |
| 550 | paths predictably, you can do this for as many repositories as you need |
| 551 | to by passing their names after the “`@`” sign in the commands above. |
| 552 | |
| 553 |
| --- www/containers.md | |
| +++ www/containers.md | |
| @@ -541,12 +541,11 @@ | |
| 541 | |
| 542 | I was then able to enable email alert forwarding for select repositories |
| 543 | after configuring them per [the docs](./alerts.md) by saying: |
| 544 | |
| 545 | $ systemctl --user daemon-reload |
| 546 | $ systemctl --user enable --now alert-sender@myproject |
| 547 | |
| 548 | Because this is a parameterized script and we’ve set our repository |
| 549 | paths predictably, you can do this for as many repositories as you need |
| 550 | to by passing their names after the “`@`” sign in the commands above. |
| 551 | |
| 552 |
+1
-1
| --- www/contribute.wiki | ||
| +++ www/contribute.wiki | ||
| @@ -40,11 +40,11 @@ | ||
| 40 | 40 | describe in detail what the patch does and which version of Fossil |
| 41 | 41 | it is written against. It's best to make patches against tip-of-trunk |
| 42 | 42 | rather than against past releases. |
| 43 | 43 | |
| 44 | 44 | If your change is more complicated than a patch can properly encode, you |
| 45 | -may submit [/help?cmd=bundle | a Fossil bundle] instead. Unlike patches, | |
| 45 | +may submit [/help/bundle | a Fossil bundle] instead. Unlike patches, | |
| 46 | 46 | bundles can contain multiple commits, check-in comments, file renames, |
| 47 | 47 | file deletions, branching decisions, and more which <tt>patch(1)</tt> |
| 48 | 48 | files cannot. It's best to make a bundle of a new branch so the change |
| 49 | 49 | can be integrated, tested, enhanced, and merged down to trunk in a |
| 50 | 50 | controlled fashion. |
| 51 | 51 |
| --- www/contribute.wiki | |
| +++ www/contribute.wiki | |
| @@ -40,11 +40,11 @@ | |
| 40 | describe in detail what the patch does and which version of Fossil |
| 41 | it is written against. It's best to make patches against tip-of-trunk |
| 42 | rather than against past releases. |
| 43 | |
| 44 | If your change is more complicated than a patch can properly encode, you |
| 45 | may submit [/help?cmd=bundle | a Fossil bundle] instead. Unlike patches, |
| 46 | bundles can contain multiple commits, check-in comments, file renames, |
| 47 | file deletions, branching decisions, and more which <tt>patch(1)</tt> |
| 48 | files cannot. It's best to make a bundle of a new branch so the change |
| 49 | can be integrated, tested, enhanced, and merged down to trunk in a |
| 50 | controlled fashion. |
| 51 |
| --- www/contribute.wiki | |
| +++ www/contribute.wiki | |
| @@ -40,11 +40,11 @@ | |
| 40 | describe in detail what the patch does and which version of Fossil |
| 41 | it is written against. It's best to make patches against tip-of-trunk |
| 42 | rather than against past releases. |
| 43 | |
| 44 | If your change is more complicated than a patch can properly encode, you |
| 45 | may submit [/help/bundle | a Fossil bundle] instead. Unlike patches, |
| 46 | bundles can contain multiple commits, check-in comments, file renames, |
| 47 | file deletions, branching decisions, and more which <tt>patch(1)</tt> |
| 48 | files cannot. It's best to make a bundle of a new branch so the change |
| 49 | can be integrated, tested, enhanced, and merged down to trunk in a |
| 50 | controlled fashion. |
| 51 |
+4
-4
| --- www/customskin.md | ||
| +++ www/customskin.md | ||
| @@ -23,18 +23,18 @@ | ||
| 23 | 23 | * footer.txt |
| 24 | 24 | * header.txt |
| 25 | 25 | * js.txt |
| 26 | 26 | |
| 27 | 27 | Try out the built-in skins by using the --skin option on the |
| 28 | -[fossil ui](/help?cmd=ui) or [fossil server](/help?cmd=server) commands. | |
| 28 | +[fossil ui](/help/ui) or [fossil server](/help/server) commands. | |
| 29 | 29 | |
| 30 | 30 | ## <a id="sharing"></a>Sharing Skins |
| 31 | 31 | |
| 32 | 32 | The skin of a repository is not part of the versioned state and does not |
| 33 | 33 | "push" or "pull" like checked-in files. The skin is local to the |
| 34 | 34 | repository. However, skins can be shared between repositories using |
| 35 | -the [fossil config](/help?cmd=configuration) command. | |
| 35 | +the [fossil config](/help/configuration) command. | |
| 36 | 36 | The "fossil config push skin" command will send the local skin to a remote |
| 37 | 37 | repository and the "fossil config pull skin" command will import a skin |
| 38 | 38 | from a remote repository. The "fossil config export skin FILENAME" |
| 39 | 39 | will export the skin for a repository into a file FILENAME. This file |
| 40 | 40 | can then be imported into a different repository using the |
| @@ -304,11 +304,11 @@ | ||
| 304 | 304 | |
| 305 | 305 | ### Skin Development Using A Local Text Editor |
| 306 | 306 | |
| 307 | 307 | An alternative approach is to copy the five control files for your |
| 308 | 308 | baseline skin into a temporary working directory (here called |
| 309 | -"./newskin") and then launch the [fossil ui](/help?cmd=ui) command | |
| 309 | +"./newskin") and then launch the [fossil ui](/help/ui) command | |
| 310 | 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | 311 | option contains a "/" character, then the five control files are |
| 312 | 312 | read out of the directory named. You can then edit the control |
| 313 | 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | 314 | press "Reload" on your browser to see the effects. |
| @@ -515,11 +515,11 @@ | ||
| 515 | 515 | CSS, footer, and header editing screens under the Admin menu will |
| 516 | 516 | work just as well. The important point is that the three files |
| 517 | 517 | be named exactly "css.txt", "footer.txt", and "header.txt" and that |
| 518 | 518 | they all be in the same directory. |
| 519 | 519 | |
| 520 | - 2. Run the [fossil ui](/help?cmd=ui) command with an extra | |
| 520 | + 2. Run the [fossil ui](/help/ui) command with an extra | |
| 521 | 521 | option "--skin SKINDIR" where SKINDIR is the name of the directory |
| 522 | 522 | in which the three txt files were stored in step 1. This will bring |
| 523 | 523 | up the Fossil website using the tree files in SKINDIR. |
| 524 | 524 | |
| 525 | 525 | 3. Edit the *.txt files in SKINDIR. After making each small change, |
| 526 | 526 |
| --- www/customskin.md | |
| +++ www/customskin.md | |
| @@ -23,18 +23,18 @@ | |
| 23 | * footer.txt |
| 24 | * header.txt |
| 25 | * js.txt |
| 26 | |
| 27 | Try out the built-in skins by using the --skin option on the |
| 28 | [fossil ui](/help?cmd=ui) or [fossil server](/help?cmd=server) commands. |
| 29 | |
| 30 | ## <a id="sharing"></a>Sharing Skins |
| 31 | |
| 32 | The skin of a repository is not part of the versioned state and does not |
| 33 | "push" or "pull" like checked-in files. The skin is local to the |
| 34 | repository. However, skins can be shared between repositories using |
| 35 | the [fossil config](/help?cmd=configuration) command. |
| 36 | The "fossil config push skin" command will send the local skin to a remote |
| 37 | repository and the "fossil config pull skin" command will import a skin |
| 38 | from a remote repository. The "fossil config export skin FILENAME" |
| 39 | will export the skin for a repository into a file FILENAME. This file |
| 40 | can then be imported into a different repository using the |
| @@ -304,11 +304,11 @@ | |
| 304 | |
| 305 | ### Skin Development Using A Local Text Editor |
| 306 | |
| 307 | An alternative approach is to copy the five control files for your |
| 308 | baseline skin into a temporary working directory (here called |
| 309 | "./newskin") and then launch the [fossil ui](/help?cmd=ui) command |
| 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | option contains a "/" character, then the five control files are |
| 312 | read out of the directory named. You can then edit the control |
| 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | press "Reload" on your browser to see the effects. |
| @@ -515,11 +515,11 @@ | |
| 515 | CSS, footer, and header editing screens under the Admin menu will |
| 516 | work just as well. The important point is that the three files |
| 517 | be named exactly "css.txt", "footer.txt", and "header.txt" and that |
| 518 | they all be in the same directory. |
| 519 | |
| 520 | 2. Run the [fossil ui](/help?cmd=ui) command with an extra |
| 521 | option "--skin SKINDIR" where SKINDIR is the name of the directory |
| 522 | in which the three txt files were stored in step 1. This will bring |
| 523 | up the Fossil website using the tree files in SKINDIR. |
| 524 | |
| 525 | 3. Edit the *.txt files in SKINDIR. After making each small change, |
| 526 |
| --- www/customskin.md | |
| +++ www/customskin.md | |
| @@ -23,18 +23,18 @@ | |
| 23 | * footer.txt |
| 24 | * header.txt |
| 25 | * js.txt |
| 26 | |
| 27 | Try out the built-in skins by using the --skin option on the |
| 28 | [fossil ui](/help/ui) or [fossil server](/help/server) commands. |
| 29 | |
| 30 | ## <a id="sharing"></a>Sharing Skins |
| 31 | |
| 32 | The skin of a repository is not part of the versioned state and does not |
| 33 | "push" or "pull" like checked-in files. The skin is local to the |
| 34 | repository. However, skins can be shared between repositories using |
| 35 | the [fossil config](/help/configuration) command. |
| 36 | The "fossil config push skin" command will send the local skin to a remote |
| 37 | repository and the "fossil config pull skin" command will import a skin |
| 38 | from a remote repository. The "fossil config export skin FILENAME" |
| 39 | will export the skin for a repository into a file FILENAME. This file |
| 40 | can then be imported into a different repository using the |
| @@ -304,11 +304,11 @@ | |
| 304 | |
| 305 | ### Skin Development Using A Local Text Editor |
| 306 | |
| 307 | An alternative approach is to copy the five control files for your |
| 308 | baseline skin into a temporary working directory (here called |
| 309 | "./newskin") and then launch the [fossil ui](/help/ui) command |
| 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | option contains a "/" character, then the five control files are |
| 312 | read out of the directory named. You can then edit the control |
| 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | press "Reload" on your browser to see the effects. |
| @@ -515,11 +515,11 @@ | |
| 515 | CSS, footer, and header editing screens under the Admin menu will |
| 516 | work just as well. The important point is that the three files |
| 517 | be named exactly "css.txt", "footer.txt", and "header.txt" and that |
| 518 | they all be in the same directory. |
| 519 | |
| 520 | 2. Run the [fossil ui](/help/ui) command with an extra |
| 521 | option "--skin SKINDIR" where SKINDIR is the name of the directory |
| 522 | in which the three txt files were stored in step 1. This will bring |
| 523 | up the Fossil website using the tree files in SKINDIR. |
| 524 | |
| 525 | 3. Edit the *.txt files in SKINDIR. After making each small change, |
| 526 |
+6
-6
| --- www/defcsp.md | ||
| +++ www/defcsp.md | ||
| @@ -31,11 +31,11 @@ | ||
| 31 | 31 | img-src * data:; |
| 32 | 32 | </pre> |
| 33 | 33 | |
| 34 | 34 | The default is recommended for most installations. However, |
| 35 | 35 | the site administrators can overwrite this default CSP using the |
| 36 | -[default-csp setting](/help?cmd=default-csp). For example, | |
| 36 | +[default-csp setting](/help/default-csp). For example, | |
| 37 | 37 | CSP restrictions can be completely disabled by setting the default-csp to: |
| 38 | 38 | |
| 39 | 39 | default-src *; |
| 40 | 40 | |
| 41 | 41 | The following sections detail the maining of the default CSP setting. |
| @@ -286,25 +286,25 @@ | ||
| 286 | 286 | |
| 287 | 287 | Unversioned content is in the middle of the first list above — between |
| 288 | 288 | fully-external content and fully in-repo content — because it isn’t |
| 289 | 289 | included in a clone unless you give the `--unversioned` flag. If you |
| 290 | 290 | then want updates to the unversioned content to be included in syncs, |
| 291 | -you have to give the same flag to [a `sync` command](/help?cmd=sync). | |
| 291 | +you have to give the same flag to [a `sync` command](/help/sync). | |
| 292 | 292 | There is no equivalent with other commands such as `up` and `pull`, so |
| 293 | 293 | you must then remember to give `fossil uv` commands when necessary to |
| 294 | 294 | pull new unversioned content down. |
| 295 | 295 | |
| 296 | 296 | Thus our recommendation that you refer to in-repo resources exclusively. |
| 297 | 297 | |
| 298 | -[du]: /help?cmd=/doc | |
| 298 | +[du]: /help/www/doc | |
| 299 | 299 | [fp]: ./forum.wiki |
| 300 | -[ru]: /help?cmd=/raw | |
| 300 | +[ru]: /help/www/raw | |
| 301 | 301 | [spof]: https://en.wikipedia.org/wiki/Single_point_of_failure |
| 302 | 302 | [tkt]: ./tickets.wiki |
| 303 | 303 | [tn]: ./event.wiki |
| 304 | 304 | [tls]: ./server/debian/nginx.md |
| 305 | -[uu]: /help?cmd=/uv | |
| 305 | +[uu]: /help/www/uv | |
| 306 | 306 | [uv]: ./unvers.wiki |
| 307 | 307 | [wiki]: ./wikitheory.wiki |
| 308 | 308 | |
| 309 | 309 | |
| 310 | 310 | ## <a id="override"></a>Overriding the Default CSP |
| @@ -318,11 +318,11 @@ | ||
| 318 | 318 | a higher-level method. |
| 319 | 319 | |
| 320 | 320 | |
| 321 | 321 | ### <a id="cspsetting"></a>The `default-csp` Setting |
| 322 | 322 | |
| 323 | -If the [`default-csp` setting](/help?cmd=default-csp) is defined and is | |
| 323 | +If the [`default-csp` setting](/help/default-csp) is defined and is | |
| 324 | 324 | not an empty string, its value is injected into the page using |
| 325 | 325 | [TH1](./th1.md) via one or more of the methods below, depending on the |
| 326 | 326 | skin you’re using and local configuration. |
| 327 | 327 | |
| 328 | 328 | Changing this setting is the easiest way to set a nonstandard CSP on |
| 329 | 329 |
| --- www/defcsp.md | |
| +++ www/defcsp.md | |
| @@ -31,11 +31,11 @@ | |
| 31 | img-src * data:; |
| 32 | </pre> |
| 33 | |
| 34 | The default is recommended for most installations. However, |
| 35 | the site administrators can overwrite this default CSP using the |
| 36 | [default-csp setting](/help?cmd=default-csp). For example, |
| 37 | CSP restrictions can be completely disabled by setting the default-csp to: |
| 38 | |
| 39 | default-src *; |
| 40 | |
| 41 | The following sections detail the maining of the default CSP setting. |
| @@ -286,25 +286,25 @@ | |
| 286 | |
| 287 | Unversioned content is in the middle of the first list above — between |
| 288 | fully-external content and fully in-repo content — because it isn’t |
| 289 | included in a clone unless you give the `--unversioned` flag. If you |
| 290 | then want updates to the unversioned content to be included in syncs, |
| 291 | you have to give the same flag to [a `sync` command](/help?cmd=sync). |
| 292 | There is no equivalent with other commands such as `up` and `pull`, so |
| 293 | you must then remember to give `fossil uv` commands when necessary to |
| 294 | pull new unversioned content down. |
| 295 | |
| 296 | Thus our recommendation that you refer to in-repo resources exclusively. |
| 297 | |
| 298 | [du]: /help?cmd=/doc |
| 299 | [fp]: ./forum.wiki |
| 300 | [ru]: /help?cmd=/raw |
| 301 | [spof]: https://en.wikipedia.org/wiki/Single_point_of_failure |
| 302 | [tkt]: ./tickets.wiki |
| 303 | [tn]: ./event.wiki |
| 304 | [tls]: ./server/debian/nginx.md |
| 305 | [uu]: /help?cmd=/uv |
| 306 | [uv]: ./unvers.wiki |
| 307 | [wiki]: ./wikitheory.wiki |
| 308 | |
| 309 | |
| 310 | ## <a id="override"></a>Overriding the Default CSP |
| @@ -318,11 +318,11 @@ | |
| 318 | a higher-level method. |
| 319 | |
| 320 | |
| 321 | ### <a id="cspsetting"></a>The `default-csp` Setting |
| 322 | |
| 323 | If the [`default-csp` setting](/help?cmd=default-csp) is defined and is |
| 324 | not an empty string, its value is injected into the page using |
| 325 | [TH1](./th1.md) via one or more of the methods below, depending on the |
| 326 | skin you’re using and local configuration. |
| 327 | |
| 328 | Changing this setting is the easiest way to set a nonstandard CSP on |
| 329 |
| --- www/defcsp.md | |
| +++ www/defcsp.md | |
| @@ -31,11 +31,11 @@ | |
| 31 | img-src * data:; |
| 32 | </pre> |
| 33 | |
| 34 | The default is recommended for most installations. However, |
| 35 | the site administrators can overwrite this default CSP using the |
| 36 | [default-csp setting](/help/default-csp). For example, |
| 37 | CSP restrictions can be completely disabled by setting the default-csp to: |
| 38 | |
| 39 | default-src *; |
| 40 | |
| 41 | The following sections detail the maining of the default CSP setting. |
| @@ -286,25 +286,25 @@ | |
| 286 | |
| 287 | Unversioned content is in the middle of the first list above — between |
| 288 | fully-external content and fully in-repo content — because it isn’t |
| 289 | included in a clone unless you give the `--unversioned` flag. If you |
| 290 | then want updates to the unversioned content to be included in syncs, |
| 291 | you have to give the same flag to [a `sync` command](/help/sync). |
| 292 | There is no equivalent with other commands such as `up` and `pull`, so |
| 293 | you must then remember to give `fossil uv` commands when necessary to |
| 294 | pull new unversioned content down. |
| 295 | |
| 296 | Thus our recommendation that you refer to in-repo resources exclusively. |
| 297 | |
| 298 | [du]: /help/www/doc |
| 299 | [fp]: ./forum.wiki |
| 300 | [ru]: /help/www/raw |
| 301 | [spof]: https://en.wikipedia.org/wiki/Single_point_of_failure |
| 302 | [tkt]: ./tickets.wiki |
| 303 | [tn]: ./event.wiki |
| 304 | [tls]: ./server/debian/nginx.md |
| 305 | [uu]: /help/www/uv |
| 306 | [uv]: ./unvers.wiki |
| 307 | [wiki]: ./wikitheory.wiki |
| 308 | |
| 309 | |
| 310 | ## <a id="override"></a>Overriding the Default CSP |
| @@ -318,11 +318,11 @@ | |
| 318 | a higher-level method. |
| 319 | |
| 320 | |
| 321 | ### <a id="cspsetting"></a>The `default-csp` Setting |
| 322 | |
| 323 | If the [`default-csp` setting](/help/default-csp) is defined and is |
| 324 | not an empty string, its value is injected into the page using |
| 325 | [TH1](./th1.md) via one or more of the methods below, depending on the |
| 326 | skin you’re using and local configuration. |
| 327 | |
| 328 | Changing this setting is the easiest way to set a nonstandard CSP on |
| 329 |
+5
-5
| --- www/delta_format.wiki | ||
| +++ www/delta_format.wiki | ||
| @@ -26,24 +26,24 @@ | ||
| 26 | 26 | [../src/deltafunc.c|deltafunc.c] source file. |
| 27 | 27 | |
| 28 | 28 | The following command-line tools are available to create and apply |
| 29 | 29 | deltas and to test the delta logic: |
| 30 | 30 | |
| 31 | - * [/help?cmd=test-delta|fossil test-delta] → Run self-tests of | |
| 31 | + * [/help/test-delta|fossil test-delta] → Run self-tests of | |
| 32 | 32 | the delta logic |
| 33 | 33 | |
| 34 | - * [/help?cmd=test-delta-create|fossil test-delta-create X Y] → compute | |
| 34 | + * [/help/test-delta-create|fossil test-delta-create X Y] → compute | |
| 35 | 35 | a delta that converts file X into file Y. Output that delta. |
| 36 | 36 | |
| 37 | - * [/help?cmd=test-delta-apply|fossil test-delta-apply X D] → apply | |
| 37 | + * [/help/test-delta-apply|fossil test-delta-apply X D] → apply | |
| 38 | 38 | delta D to input file X and output the result. |
| 39 | 39 | |
| 40 | - * [/help?cmd=test-delta-analyze|fossil test-delta-analyze X Y] → compute | |
| 40 | + * [/help/test-delta-analyze|fossil test-delta-analyze X Y] → compute | |
| 41 | 41 | and delta that converts file X into file Y but instead of writing the |
| 42 | 42 | delta to output, write performance information about the delta. |
| 43 | 43 | |
| 44 | -When running the [/help?cmd=sqlite3|fossil sql] command to get an | |
| 44 | +When running the [/help/sqlite3|fossil sql] command to get an | |
| 45 | 45 | interactive SQL session connected to the repository, the following |
| 46 | 46 | additional SQL functions are provided: |
| 47 | 47 | |
| 48 | 48 | * <b>delta_create(</b><i>X</i><b>,</b><i>Y</i><b>)</b> → |
| 49 | 49 | Compute a data that carries blob X into blob Y and return that delta |
| 50 | 50 |
| --- www/delta_format.wiki | |
| +++ www/delta_format.wiki | |
| @@ -26,24 +26,24 @@ | |
| 26 | [../src/deltafunc.c|deltafunc.c] source file. |
| 27 | |
| 28 | The following command-line tools are available to create and apply |
| 29 | deltas and to test the delta logic: |
| 30 | |
| 31 | * [/help?cmd=test-delta|fossil test-delta] → Run self-tests of |
| 32 | the delta logic |
| 33 | |
| 34 | * [/help?cmd=test-delta-create|fossil test-delta-create X Y] → compute |
| 35 | a delta that converts file X into file Y. Output that delta. |
| 36 | |
| 37 | * [/help?cmd=test-delta-apply|fossil test-delta-apply X D] → apply |
| 38 | delta D to input file X and output the result. |
| 39 | |
| 40 | * [/help?cmd=test-delta-analyze|fossil test-delta-analyze X Y] → compute |
| 41 | and delta that converts file X into file Y but instead of writing the |
| 42 | delta to output, write performance information about the delta. |
| 43 | |
| 44 | When running the [/help?cmd=sqlite3|fossil sql] command to get an |
| 45 | interactive SQL session connected to the repository, the following |
| 46 | additional SQL functions are provided: |
| 47 | |
| 48 | * <b>delta_create(</b><i>X</i><b>,</b><i>Y</i><b>)</b> → |
| 49 | Compute a data that carries blob X into blob Y and return that delta |
| 50 |
| --- www/delta_format.wiki | |
| +++ www/delta_format.wiki | |
| @@ -26,24 +26,24 @@ | |
| 26 | [../src/deltafunc.c|deltafunc.c] source file. |
| 27 | |
| 28 | The following command-line tools are available to create and apply |
| 29 | deltas and to test the delta logic: |
| 30 | |
| 31 | * [/help/test-delta|fossil test-delta] → Run self-tests of |
| 32 | the delta logic |
| 33 | |
| 34 | * [/help/test-delta-create|fossil test-delta-create X Y] → compute |
| 35 | a delta that converts file X into file Y. Output that delta. |
| 36 | |
| 37 | * [/help/test-delta-apply|fossil test-delta-apply X D] → apply |
| 38 | delta D to input file X and output the result. |
| 39 | |
| 40 | * [/help/test-delta-analyze|fossil test-delta-analyze X Y] → compute |
| 41 | and delta that converts file X into file Y but instead of writing the |
| 42 | delta to output, write performance information about the delta. |
| 43 | |
| 44 | When running the [/help/sqlite3|fossil sql] command to get an |
| 45 | interactive SQL session connected to the repository, the following |
| 46 | additional SQL functions are provided: |
| 47 | |
| 48 | * <b>delta_create(</b><i>X</i><b>,</b><i>Y</i><b>)</b> → |
| 49 | Compute a data that carries blob X into blob Y and return that delta |
| 50 |
+2
-2
| --- www/embeddeddoc.wiki | ||
| +++ www/embeddeddoc.wiki | ||
| @@ -36,11 +36,11 @@ | ||
| 36 | 36 | </pre> |
| 37 | 37 | |
| 38 | 38 | The <i><baseurl></i> is the main URL used to access the fossil web server. |
| 39 | 39 | For example, the <i><baseurl></i> for the fossil project itself is |
| 40 | 40 | [https://fossil-scm.org/home]. |
| 41 | -If you launch the web server using the "[/help?cmd=ui|fossil ui]" command line, | |
| 41 | +If you launch the web server using the "[/help/ui|fossil ui]" command line, | |
| 42 | 42 | then the <i><baseurl></i> is usually |
| 43 | 43 | <b>http://localhost:8080/</b>. |
| 44 | 44 | |
| 45 | 45 | The <i><version></i> is the |
| 46 | 46 | [./checkin_names.wiki|name of a check-in] |
| @@ -53,11 +53,11 @@ | ||
| 53 | 53 | also be the special identifier "<b>ckout</b>". |
| 54 | 54 | The "<b>ckout</b>" keywords means to |
| 55 | 55 | pull the documentation file from the local source tree on disk, not |
| 56 | 56 | from the any check-in. The "<b>ckout</b>" keyword |
| 57 | 57 | only works when you start your server using the |
| 58 | -"[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]" | |
| 58 | +"[/help/server|fossil server]" or "[/help?cmd=ui|fossil ui]" | |
| 59 | 59 | commands. The "/doc/ckout" URL is intended to show a preview of |
| 60 | 60 | the documentation you are currently editing but have not yet checked in. |
| 61 | 61 | |
| 62 | 62 | The original designed purpose of the "ckout" feature is to allow the |
| 63 | 63 | user to preview local changes to documentation before committing the |
| 64 | 64 |
| --- www/embeddeddoc.wiki | |
| +++ www/embeddeddoc.wiki | |
| @@ -36,11 +36,11 @@ | |
| 36 | </pre> |
| 37 | |
| 38 | The <i><baseurl></i> is the main URL used to access the fossil web server. |
| 39 | For example, the <i><baseurl></i> for the fossil project itself is |
| 40 | [https://fossil-scm.org/home]. |
| 41 | If you launch the web server using the "[/help?cmd=ui|fossil ui]" command line, |
| 42 | then the <i><baseurl></i> is usually |
| 43 | <b>http://localhost:8080/</b>. |
| 44 | |
| 45 | The <i><version></i> is the |
| 46 | [./checkin_names.wiki|name of a check-in] |
| @@ -53,11 +53,11 @@ | |
| 53 | also be the special identifier "<b>ckout</b>". |
| 54 | The "<b>ckout</b>" keywords means to |
| 55 | pull the documentation file from the local source tree on disk, not |
| 56 | from the any check-in. The "<b>ckout</b>" keyword |
| 57 | only works when you start your server using the |
| 58 | "[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]" |
| 59 | commands. The "/doc/ckout" URL is intended to show a preview of |
| 60 | the documentation you are currently editing but have not yet checked in. |
| 61 | |
| 62 | The original designed purpose of the "ckout" feature is to allow the |
| 63 | user to preview local changes to documentation before committing the |
| 64 |
| --- www/embeddeddoc.wiki | |
| +++ www/embeddeddoc.wiki | |
| @@ -36,11 +36,11 @@ | |
| 36 | </pre> |
| 37 | |
| 38 | The <i><baseurl></i> is the main URL used to access the fossil web server. |
| 39 | For example, the <i><baseurl></i> for the fossil project itself is |
| 40 | [https://fossil-scm.org/home]. |
| 41 | If you launch the web server using the "[/help/ui|fossil ui]" command line, |
| 42 | then the <i><baseurl></i> is usually |
| 43 | <b>http://localhost:8080/</b>. |
| 44 | |
| 45 | The <i><version></i> is the |
| 46 | [./checkin_names.wiki|name of a check-in] |
| @@ -53,11 +53,11 @@ | |
| 53 | also be the special identifier "<b>ckout</b>". |
| 54 | The "<b>ckout</b>" keywords means to |
| 55 | pull the documentation file from the local source tree on disk, not |
| 56 | from the any check-in. The "<b>ckout</b>" keyword |
| 57 | only works when you start your server using the |
| 58 | "[/help/server|fossil server]" or "[/help?cmd=ui|fossil ui]" |
| 59 | commands. The "/doc/ckout" URL is intended to show a preview of |
| 60 | the documentation you are currently editing but have not yet checked in. |
| 61 | |
| 62 | The original designed purpose of the "ckout" feature is to allow the |
| 63 | user to preview local changes to documentation before committing the |
| 64 |
+1
-1
| --- www/env-opts.md | ||
| +++ www/env-opts.md | ||
| @@ -253,11 +253,11 @@ | ||
| 253 | 253 | `SCRIPT_NAME`: If defined, included in error log messages. |
| 254 | 254 | |
| 255 | 255 | `SSH_CONNECTION`: Informs CGI processing if the remote client is SSH. |
| 256 | 256 | |
| 257 | 257 | `SSL_CERT_FILE`, `SSL_CERT_DIR`: Override the [`ssl-ca-location`] |
| 258 | -(/help?cmd=ssl-ca-location) setting. | |
| 258 | +(/help/ssl-ca-location) setting. | |
| 259 | 259 | |
| 260 | 260 | `SQLITE_FORCE_PROXY_LOCKING`: From `sqlite3.c`, 1 means force always |
| 261 | 261 | use proxy, 0 means never use proxy, and undefined means use proxy for |
| 262 | 262 | non-local files only. |
| 263 | 263 | |
| 264 | 264 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -253,11 +253,11 @@ | |
| 253 | `SCRIPT_NAME`: If defined, included in error log messages. |
| 254 | |
| 255 | `SSH_CONNECTION`: Informs CGI processing if the remote client is SSH. |
| 256 | |
| 257 | `SSL_CERT_FILE`, `SSL_CERT_DIR`: Override the [`ssl-ca-location`] |
| 258 | (/help?cmd=ssl-ca-location) setting. |
| 259 | |
| 260 | `SQLITE_FORCE_PROXY_LOCKING`: From `sqlite3.c`, 1 means force always |
| 261 | use proxy, 0 means never use proxy, and undefined means use proxy for |
| 262 | non-local files only. |
| 263 | |
| 264 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -253,11 +253,11 @@ | |
| 253 | `SCRIPT_NAME`: If defined, included in error log messages. |
| 254 | |
| 255 | `SSH_CONNECTION`: Informs CGI processing if the remote client is SSH. |
| 256 | |
| 257 | `SSL_CERT_FILE`, `SSL_CERT_DIR`: Override the [`ssl-ca-location`] |
| 258 | (/help/ssl-ca-location) setting. |
| 259 | |
| 260 | `SQLITE_FORCE_PROXY_LOCKING`: From `sqlite3.c`, 1 means force always |
| 261 | use proxy, 0 means never use proxy, and undefined means use proxy for |
| 262 | non-local files only. |
| 263 | |
| 264 |
+1
-1
| --- www/event.wiki | ||
| +++ www/event.wiki | ||
| @@ -107,11 +107,11 @@ | ||
| 107 | 107 | This note describes changes in the Fossil snapshot for ... |
| 108 | 108 | </verbatim> |
| 109 | 109 | |
| 110 | 110 | The <b>-t|--technote</b> option to the <b>export</b> subcommand takes one of |
| 111 | 111 | three identifiers: <b>DATETIME</b>; <b>TECHNOTE-ID</b>; and <b>TAG</b>. |
| 112 | -See the [/help?cmd=wiki | wiki help] for specifics. | |
| 112 | +See the [/help/wiki | wiki help] for specifics. | |
| 113 | 113 | |
| 114 | 114 | Users must have check-in privileges (permission "i") in order to |
| 115 | 115 | create or edit technotes. In addition, users must have create-wiki |
| 116 | 116 | privilege (permission "f") to create new technotes and edit-wiki |
| 117 | 117 | privilege (permission "k") in order to edit existing technotes. |
| 118 | 118 |
| --- www/event.wiki | |
| +++ www/event.wiki | |
| @@ -107,11 +107,11 @@ | |
| 107 | This note describes changes in the Fossil snapshot for ... |
| 108 | </verbatim> |
| 109 | |
| 110 | The <b>-t|--technote</b> option to the <b>export</b> subcommand takes one of |
| 111 | three identifiers: <b>DATETIME</b>; <b>TECHNOTE-ID</b>; and <b>TAG</b>. |
| 112 | See the [/help?cmd=wiki | wiki help] for specifics. |
| 113 | |
| 114 | Users must have check-in privileges (permission "i") in order to |
| 115 | create or edit technotes. In addition, users must have create-wiki |
| 116 | privilege (permission "f") to create new technotes and edit-wiki |
| 117 | privilege (permission "k") in order to edit existing technotes. |
| 118 |
| --- www/event.wiki | |
| +++ www/event.wiki | |
| @@ -107,11 +107,11 @@ | |
| 107 | This note describes changes in the Fossil snapshot for ... |
| 108 | </verbatim> |
| 109 | |
| 110 | The <b>-t|--technote</b> option to the <b>export</b> subcommand takes one of |
| 111 | three identifiers: <b>DATETIME</b>; <b>TECHNOTE-ID</b>; and <b>TAG</b>. |
| 112 | See the [/help/wiki | wiki help] for specifics. |
| 113 | |
| 114 | Users must have check-in privileges (permission "i") in order to |
| 115 | create or edit technotes. In addition, users must have create-wiki |
| 116 | privilege (permission "f") to create new technotes and edit-wiki |
| 117 | privilege (permission "k") in order to edit existing technotes. |
| 118 |
+1
-1
| --- www/fileedit-page.md | ||
| +++ www/fileedit-page.md | ||
| @@ -13,11 +13,11 @@ | ||
| 13 | 13 | |
| 14 | 14 | ## <a id="cap"></a> `/fileedit` Does *Nothing* by Default. |
| 15 | 15 | |
| 16 | 16 | In order to "activate" it, a user with [the "setup" |
| 17 | 17 | permission](./caps/index.md) must set the |
| 18 | -[fileedit-glob](/help?cmd=fileedit-glob) repository setting to a | |
| 18 | +[fileedit-glob](/help/fileedit-glob) repository setting to a | |
| 19 | 19 | comma- or newline-delimited list of globs representing a whitelist of |
| 20 | 20 | files which may be edited online. Any user with commit access may then |
| 21 | 21 | edit files matching one of those globs. Certain pages within the UI |
| 22 | 22 | get an "edit" link added to them when the current user's permissions |
| 23 | 23 | and the whitelist both permit editing of that file. |
| 24 | 24 |
| --- www/fileedit-page.md | |
| +++ www/fileedit-page.md | |
| @@ -13,11 +13,11 @@ | |
| 13 | |
| 14 | ## <a id="cap"></a> `/fileedit` Does *Nothing* by Default. |
| 15 | |
| 16 | In order to "activate" it, a user with [the "setup" |
| 17 | permission](./caps/index.md) must set the |
| 18 | [fileedit-glob](/help?cmd=fileedit-glob) repository setting to a |
| 19 | comma- or newline-delimited list of globs representing a whitelist of |
| 20 | files which may be edited online. Any user with commit access may then |
| 21 | edit files matching one of those globs. Certain pages within the UI |
| 22 | get an "edit" link added to them when the current user's permissions |
| 23 | and the whitelist both permit editing of that file. |
| 24 |
| --- www/fileedit-page.md | |
| +++ www/fileedit-page.md | |
| @@ -13,11 +13,11 @@ | |
| 13 | |
| 14 | ## <a id="cap"></a> `/fileedit` Does *Nothing* by Default. |
| 15 | |
| 16 | In order to "activate" it, a user with [the "setup" |
| 17 | permission](./caps/index.md) must set the |
| 18 | [fileedit-glob](/help/fileedit-glob) repository setting to a |
| 19 | comma- or newline-delimited list of globs representing a whitelist of |
| 20 | files which may be edited online. Any user with commit access may then |
| 21 | edit files matching one of those globs. Certain pages within the UI |
| 22 | get an "edit" link added to them when the current user's permissions |
| 23 | and the whitelist both permit editing of that file. |
| 24 |
+1
-1
| --- www/forum.wiki | ||
| +++ www/forum.wiki | ||
| @@ -372,11 +372,11 @@ | ||
| 372 | 372 | <h2 id="close-post">Closing Forum Posts</h2> |
| 373 | 373 | |
| 374 | 374 | As of version 2.23, the forum interface supports the notion of |
| 375 | 375 | "closing" posts. By default, only users with the [./caps/index.md|'s' |
| 376 | 376 | and 'a' capabilities] may close or re-open posts, or reply to closed |
| 377 | -posts. If the [/help?cmd=forum-close-policy|forum-close-policy | |
| 377 | +posts. If the [/help/forum-close-policy|forum-close-policy | |
| 378 | 378 | configuration option] is enabled then users with |
| 379 | 379 | [./caps/index.md|forum-moderator permissions] may also perform those |
| 380 | 380 | actions. |
| 381 | 381 | |
| 382 | 382 | Closing a post has the following implications: |
| 383 | 383 |
| --- www/forum.wiki | |
| +++ www/forum.wiki | |
| @@ -372,11 +372,11 @@ | |
| 372 | <h2 id="close-post">Closing Forum Posts</h2> |
| 373 | |
| 374 | As of version 2.23, the forum interface supports the notion of |
| 375 | "closing" posts. By default, only users with the [./caps/index.md|'s' |
| 376 | and 'a' capabilities] may close or re-open posts, or reply to closed |
| 377 | posts. If the [/help?cmd=forum-close-policy|forum-close-policy |
| 378 | configuration option] is enabled then users with |
| 379 | [./caps/index.md|forum-moderator permissions] may also perform those |
| 380 | actions. |
| 381 | |
| 382 | Closing a post has the following implications: |
| 383 |
| --- www/forum.wiki | |
| +++ www/forum.wiki | |
| @@ -372,11 +372,11 @@ | |
| 372 | <h2 id="close-post">Closing Forum Posts</h2> |
| 373 | |
| 374 | As of version 2.23, the forum interface supports the notion of |
| 375 | "closing" posts. By default, only users with the [./caps/index.md|'s' |
| 376 | and 'a' capabilities] may close or re-open posts, or reply to closed |
| 377 | posts. If the [/help/forum-close-policy|forum-close-policy |
| 378 | configuration option] is enabled then users with |
| 379 | [./caps/index.md|forum-moderator permissions] may also perform those |
| 380 | actions. |
| 381 | |
| 382 | Closing a post has the following implications: |
| 383 |
+9
-9
| --- www/fossil-v-git.wiki | ||
| +++ www/fossil-v-git.wiki | ||
| @@ -105,11 +105,11 @@ | ||
| 105 | 105 | [./bugtheory.wiki | ticketing & bug tracking], |
| 106 | 106 | [./embeddeddoc.wiki | embedded documentation], |
| 107 | 107 | [./event.wiki | technical notes], a [./forum.wiki | web forum], |
| 108 | 108 | and a [./chat.md | chat service], |
| 109 | 109 | all within a single nicely-designed [./customskin.md|skinnable] web |
| 110 | -[/help?cmd=ui|UI], | |
| 110 | +[/help/ui|UI], | |
| 111 | 111 | protected by [./caps/ | a fine-grained role-based |
| 112 | 112 | access control system]. |
| 113 | 113 | These additional capabilities are available for Git as 3rd-party |
| 114 | 114 | add-ons, but with Fossil they are integrated into |
| 115 | 115 | the design, to the point that it approximates |
| @@ -130,12 +130,12 @@ | ||
| 130 | 130 | sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files |
| 131 | 131 | that you forgot to commit prior to the end of your working day, across |
| 132 | 132 | all repos. |
| 133 | 133 | |
| 134 | 134 | Whenever Fossil is told to modify the local checkout in some destructive |
| 135 | -way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update], | |
| 136 | -[/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state | |
| 135 | +way ([/help/rm|fossil rm], [/help/update|fossil update], | |
| 136 | +[/help/revert|fossil revert], etc.) Fossil remembers the prior state | |
| 137 | 137 | and is able to return the check-out directory to that state with a |
| 138 | 138 | <tt>fossil undo</tt> command. While you cannot undo a commit in Fossil |
| 139 | 139 | — [#history | on purpose!] — as long as the change remains confined to |
| 140 | 140 | the local check-out directory only, Fossil makes undo |
| 141 | 141 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| @@ -443,11 +443,11 @@ | ||
| 443 | 443 | * <b>No easy drive-by contributions:</b> Git |
| 444 | 444 | [https://www.git-scm.com/docs/git-request-pull|pull requests] offer |
| 445 | 445 | a low-friction path to accepting |
| 446 | 446 | [https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by |
| 447 | 447 | contributions]. Fossil's closest equivalents are its unique |
| 448 | - [/help?cmd=bundle|bundle] and [/help?cmd=patch|patch] features, which require higher engagement | |
| 448 | + [/help/bundle|bundle] and [/help/patch|patch] features, which require higher engagement | |
| 449 | 449 | than firing off a PR.⁷ This difference comes directly from the |
| 450 | 450 | initial designed purpose for each tool: the SQLite project doesn't |
| 451 | 451 | accept outside contributions from previously-unknown developers, but |
| 452 | 452 | the Linux kernel does. |
| 453 | 453 | |
| @@ -456,11 +456,11 @@ | ||
| 456 | 456 | committed locally. [#history|There is no rebasing mechanism in |
| 457 | 457 | Fossil, on purpose.] |
| 458 | 458 | |
| 459 | 459 | * <b>Sync over push:</b> Explicit pushes are uncommon in |
| 460 | 460 | Fossil-based projects: the default is to rely on |
| 461 | - [/help?cmd=autosync|autosync mode] instead, in which each commit | |
| 461 | + [/help/autosync|autosync mode] instead, in which each commit | |
| 462 | 462 | syncs immediately to its parent repository. This is a mode so you |
| 463 | 463 | can turn it off temporarily when needed, such as when working |
| 464 | 464 | offline. Fossil is still a truly distributed version control system; |
| 465 | 465 | it's just that its starting default is to assume you're rarely out |
| 466 | 466 | of communication with the parent repo. |
| @@ -598,11 +598,11 @@ | ||
| 598 | 598 | that repository, the default mode of operation in Git is to stick to that |
| 599 | 599 | single work/repo tree, even when that's a shortsighted way of working. |
| 600 | 600 | |
| 601 | 601 | Fossil doesn't work that way. A Fossil repository is an SQLite database |
| 602 | 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | -[/help?cmd=open | open] a Fossil repository any number of times into | |
| 603 | +[/help/open | open] a Fossil repository any number of times into | |
| 604 | 604 | any number of working directories. A common usage pattern is to have one |
| 605 | 605 | working directory per active working branch, so that switching branches |
| 606 | 606 | is done with a <tt>cd</tt> command rather than by checking out the |
| 607 | 607 | branches successively in a single working directory. |
| 608 | 608 | |
| @@ -682,11 +682,11 @@ | ||
| 682 | 682 | makes the history of a Fossil project "messy," but another point of view |
| 683 | 683 | is that this makes the history "accurate." In actual practice, the |
| 684 | 684 | superior reporting tools available in Fossil mean that this incidental mess |
| 685 | 685 | is not a factor. |
| 686 | 686 | |
| 687 | -Like Git, Fossil has an [/help?cmd=amend|amend command] for modifying | |
| 687 | +Like Git, Fossil has an [/help/amend|amend command] for modifying | |
| 688 | 688 | prior commits, but unlike in Git, this works not by replacing data in |
| 689 | 689 | the repository, but by adding a correction record to the repository that |
| 690 | 690 | affects how later Fossil operations present the corrected data. The old |
| 691 | 691 | information is still there in the repository, it is just overridden from |
| 692 | 692 | the amendment point forward. |
| @@ -712,11 +712,11 @@ | ||
| 712 | 712 | the receiving repo as well. Fossil's shun feature isn't for fixing up |
| 713 | 713 | everyday bad commits, it's for dealing with extreme situations: public |
| 714 | 714 | commits of secret material, ticket/wiki/forum spam, law enforcement |
| 715 | 715 | takedown demands, etc. |
| 716 | 716 | |
| 717 | -There is also the experimental [/help?cmd=purge | <tt>purge</tt> | |
| 717 | +There is also the experimental [/help/purge | <tt>purge</tt> | |
| 718 | 718 | command], which differs from shunning in ways that aren't especially |
| 719 | 719 | important in the context of this document. At a 30000 foot level, you |
| 720 | 720 | can think of purging as useful only when you've turned off Fossil's |
| 721 | 721 | autosync feature and want to pluck artifacts out of its hash tree before |
| 722 | 722 | they get pushed. In that sense, it's approximately the same as |
| @@ -821,11 +821,11 @@ | ||
| 821 | 821 | that Fossil's command interface is simpler than Git's: there are fewer |
| 822 | 822 | concepts to keep track of in your mental model of Fossil's internal |
| 823 | 823 | operation. |
| 824 | 824 | |
| 825 | 825 | Fossil's implementation of the feature is also simpler to describe. The |
| 826 | -brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is | |
| 826 | +brief online help for <tt>[/help/merge | fossil merge]</tt> is | |
| 827 | 827 | currently 41 lines long, to which you want to add the 600 lines of |
| 828 | 828 | [./branching.wiki | the branching document]. The equivalent |
| 829 | 829 | documentation in Git is the aggregation of the man pages for the above |
| 830 | 830 | three commands, which is over 1000 lines, much of it mutually redundant. |
| 831 | 831 | (e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get |
| 832 | 832 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -105,11 +105,11 @@ | |
| 105 | [./bugtheory.wiki | ticketing & bug tracking], |
| 106 | [./embeddeddoc.wiki | embedded documentation], |
| 107 | [./event.wiki | technical notes], a [./forum.wiki | web forum], |
| 108 | and a [./chat.md | chat service], |
| 109 | all within a single nicely-designed [./customskin.md|skinnable] web |
| 110 | [/help?cmd=ui|UI], |
| 111 | protected by [./caps/ | a fine-grained role-based |
| 112 | access control system]. |
| 113 | These additional capabilities are available for Git as 3rd-party |
| 114 | add-ons, but with Fossil they are integrated into |
| 115 | the design, to the point that it approximates |
| @@ -130,12 +130,12 @@ | |
| 130 | sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files |
| 131 | that you forgot to commit prior to the end of your working day, across |
| 132 | all repos. |
| 133 | |
| 134 | Whenever Fossil is told to modify the local checkout in some destructive |
| 135 | way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update], |
| 136 | [/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state |
| 137 | and is able to return the check-out directory to that state with a |
| 138 | <tt>fossil undo</tt> command. While you cannot undo a commit in Fossil |
| 139 | — [#history | on purpose!] — as long as the change remains confined to |
| 140 | the local check-out directory only, Fossil makes undo |
| 141 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| @@ -443,11 +443,11 @@ | |
| 443 | * <b>No easy drive-by contributions:</b> Git |
| 444 | [https://www.git-scm.com/docs/git-request-pull|pull requests] offer |
| 445 | a low-friction path to accepting |
| 446 | [https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by |
| 447 | contributions]. Fossil's closest equivalents are its unique |
| 448 | [/help?cmd=bundle|bundle] and [/help?cmd=patch|patch] features, which require higher engagement |
| 449 | than firing off a PR.⁷ This difference comes directly from the |
| 450 | initial designed purpose for each tool: the SQLite project doesn't |
| 451 | accept outside contributions from previously-unknown developers, but |
| 452 | the Linux kernel does. |
| 453 | |
| @@ -456,11 +456,11 @@ | |
| 456 | committed locally. [#history|There is no rebasing mechanism in |
| 457 | Fossil, on purpose.] |
| 458 | |
| 459 | * <b>Sync over push:</b> Explicit pushes are uncommon in |
| 460 | Fossil-based projects: the default is to rely on |
| 461 | [/help?cmd=autosync|autosync mode] instead, in which each commit |
| 462 | syncs immediately to its parent repository. This is a mode so you |
| 463 | can turn it off temporarily when needed, such as when working |
| 464 | offline. Fossil is still a truly distributed version control system; |
| 465 | it's just that its starting default is to assume you're rarely out |
| 466 | of communication with the parent repo. |
| @@ -598,11 +598,11 @@ | |
| 598 | that repository, the default mode of operation in Git is to stick to that |
| 599 | single work/repo tree, even when that's a shortsighted way of working. |
| 600 | |
| 601 | Fossil doesn't work that way. A Fossil repository is an SQLite database |
| 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | [/help?cmd=open | open] a Fossil repository any number of times into |
| 604 | any number of working directories. A common usage pattern is to have one |
| 605 | working directory per active working branch, so that switching branches |
| 606 | is done with a <tt>cd</tt> command rather than by checking out the |
| 607 | branches successively in a single working directory. |
| 608 | |
| @@ -682,11 +682,11 @@ | |
| 682 | makes the history of a Fossil project "messy," but another point of view |
| 683 | is that this makes the history "accurate." In actual practice, the |
| 684 | superior reporting tools available in Fossil mean that this incidental mess |
| 685 | is not a factor. |
| 686 | |
| 687 | Like Git, Fossil has an [/help?cmd=amend|amend command] for modifying |
| 688 | prior commits, but unlike in Git, this works not by replacing data in |
| 689 | the repository, but by adding a correction record to the repository that |
| 690 | affects how later Fossil operations present the corrected data. The old |
| 691 | information is still there in the repository, it is just overridden from |
| 692 | the amendment point forward. |
| @@ -712,11 +712,11 @@ | |
| 712 | the receiving repo as well. Fossil's shun feature isn't for fixing up |
| 713 | everyday bad commits, it's for dealing with extreme situations: public |
| 714 | commits of secret material, ticket/wiki/forum spam, law enforcement |
| 715 | takedown demands, etc. |
| 716 | |
| 717 | There is also the experimental [/help?cmd=purge | <tt>purge</tt> |
| 718 | command], which differs from shunning in ways that aren't especially |
| 719 | important in the context of this document. At a 30000 foot level, you |
| 720 | can think of purging as useful only when you've turned off Fossil's |
| 721 | autosync feature and want to pluck artifacts out of its hash tree before |
| 722 | they get pushed. In that sense, it's approximately the same as |
| @@ -821,11 +821,11 @@ | |
| 821 | that Fossil's command interface is simpler than Git's: there are fewer |
| 822 | concepts to keep track of in your mental model of Fossil's internal |
| 823 | operation. |
| 824 | |
| 825 | Fossil's implementation of the feature is also simpler to describe. The |
| 826 | brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is |
| 827 | currently 41 lines long, to which you want to add the 600 lines of |
| 828 | [./branching.wiki | the branching document]. The equivalent |
| 829 | documentation in Git is the aggregation of the man pages for the above |
| 830 | three commands, which is over 1000 lines, much of it mutually redundant. |
| 831 | (e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get |
| 832 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -105,11 +105,11 @@ | |
| 105 | [./bugtheory.wiki | ticketing & bug tracking], |
| 106 | [./embeddeddoc.wiki | embedded documentation], |
| 107 | [./event.wiki | technical notes], a [./forum.wiki | web forum], |
| 108 | and a [./chat.md | chat service], |
| 109 | all within a single nicely-designed [./customskin.md|skinnable] web |
| 110 | [/help/ui|UI], |
| 111 | protected by [./caps/ | a fine-grained role-based |
| 112 | access control system]. |
| 113 | These additional capabilities are available for Git as 3rd-party |
| 114 | add-ons, but with Fossil they are integrated into |
| 115 | the design, to the point that it approximates |
| @@ -130,12 +130,12 @@ | |
| 130 | sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files |
| 131 | that you forgot to commit prior to the end of your working day, across |
| 132 | all repos. |
| 133 | |
| 134 | Whenever Fossil is told to modify the local checkout in some destructive |
| 135 | way ([/help/rm|fossil rm], [/help/update|fossil update], |
| 136 | [/help/revert|fossil revert], etc.) Fossil remembers the prior state |
| 137 | and is able to return the check-out directory to that state with a |
| 138 | <tt>fossil undo</tt> command. While you cannot undo a commit in Fossil |
| 139 | — [#history | on purpose!] — as long as the change remains confined to |
| 140 | the local check-out directory only, Fossil makes undo |
| 141 | [https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in |
| @@ -443,11 +443,11 @@ | |
| 443 | * <b>No easy drive-by contributions:</b> Git |
| 444 | [https://www.git-scm.com/docs/git-request-pull|pull requests] offer |
| 445 | a low-friction path to accepting |
| 446 | [https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by |
| 447 | contributions]. Fossil's closest equivalents are its unique |
| 448 | [/help/bundle|bundle] and [/help/patch|patch] features, which require higher engagement |
| 449 | than firing off a PR.⁷ This difference comes directly from the |
| 450 | initial designed purpose for each tool: the SQLite project doesn't |
| 451 | accept outside contributions from previously-unknown developers, but |
| 452 | the Linux kernel does. |
| 453 | |
| @@ -456,11 +456,11 @@ | |
| 456 | committed locally. [#history|There is no rebasing mechanism in |
| 457 | Fossil, on purpose.] |
| 458 | |
| 459 | * <b>Sync over push:</b> Explicit pushes are uncommon in |
| 460 | Fossil-based projects: the default is to rely on |
| 461 | [/help/autosync|autosync mode] instead, in which each commit |
| 462 | syncs immediately to its parent repository. This is a mode so you |
| 463 | can turn it off temporarily when needed, such as when working |
| 464 | offline. Fossil is still a truly distributed version control system; |
| 465 | it's just that its starting default is to assume you're rarely out |
| 466 | of communication with the parent repo. |
| @@ -598,11 +598,11 @@ | |
| 598 | that repository, the default mode of operation in Git is to stick to that |
| 599 | single work/repo tree, even when that's a shortsighted way of working. |
| 600 | |
| 601 | Fossil doesn't work that way. A Fossil repository is an SQLite database |
| 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | [/help/open | open] a Fossil repository any number of times into |
| 604 | any number of working directories. A common usage pattern is to have one |
| 605 | working directory per active working branch, so that switching branches |
| 606 | is done with a <tt>cd</tt> command rather than by checking out the |
| 607 | branches successively in a single working directory. |
| 608 | |
| @@ -682,11 +682,11 @@ | |
| 682 | makes the history of a Fossil project "messy," but another point of view |
| 683 | is that this makes the history "accurate." In actual practice, the |
| 684 | superior reporting tools available in Fossil mean that this incidental mess |
| 685 | is not a factor. |
| 686 | |
| 687 | Like Git, Fossil has an [/help/amend|amend command] for modifying |
| 688 | prior commits, but unlike in Git, this works not by replacing data in |
| 689 | the repository, but by adding a correction record to the repository that |
| 690 | affects how later Fossil operations present the corrected data. The old |
| 691 | information is still there in the repository, it is just overridden from |
| 692 | the amendment point forward. |
| @@ -712,11 +712,11 @@ | |
| 712 | the receiving repo as well. Fossil's shun feature isn't for fixing up |
| 713 | everyday bad commits, it's for dealing with extreme situations: public |
| 714 | commits of secret material, ticket/wiki/forum spam, law enforcement |
| 715 | takedown demands, etc. |
| 716 | |
| 717 | There is also the experimental [/help/purge | <tt>purge</tt> |
| 718 | command], which differs from shunning in ways that aren't especially |
| 719 | important in the context of this document. At a 30000 foot level, you |
| 720 | can think of purging as useful only when you've turned off Fossil's |
| 721 | autosync feature and want to pluck artifacts out of its hash tree before |
| 722 | they get pushed. In that sense, it's approximately the same as |
| @@ -821,11 +821,11 @@ | |
| 821 | that Fossil's command interface is simpler than Git's: there are fewer |
| 822 | concepts to keep track of in your mental model of Fossil's internal |
| 823 | operation. |
| 824 | |
| 825 | Fossil's implementation of the feature is also simpler to describe. The |
| 826 | brief online help for <tt>[/help/merge | fossil merge]</tt> is |
| 827 | currently 41 lines long, to which you want to add the 600 lines of |
| 828 | [./branching.wiki | the branching document]. The equivalent |
| 829 | documentation in Git is the aggregation of the man pages for the above |
| 830 | three commands, which is over 1000 lines, much of it mutually redundant. |
| 831 | (e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get |
| 832 |
+22
-22
| --- www/gitusers.md | ||
| +++ www/gitusers.md | ||
| @@ -37,11 +37,11 @@ | ||
| 37 | 37 | While we do try to explain Fossil-specific terminology inline here |
| 38 | 38 | as-needed, you may find it helpful to skim [the Fossil glossary][gloss]. |
| 39 | 39 | It will give you another take on our definitions here, and it may help |
| 40 | 40 | you to understand some of the other Fossil docs better. |
| 41 | 41 | |
| 42 | -[fbis]: /help?cmd=bisect | |
| 42 | +[fbis]: /help/bisect | |
| 43 | 43 | [gbis]: https://git-scm.com/docs/git-bisect |
| 44 | 44 | [ffor]: https://fossil-scm.org/forum |
| 45 | 45 | [fvg]: ./fossil-v-git.wiki |
| 46 | 46 | |
| 47 | 47 | |
| @@ -90,11 +90,11 @@ | ||
| 90 | 90 | from the remote repository into the local clone as `fossil update` does. |
| 91 | 91 | We think this is less broadly useful, but that’s the subject of the next |
| 92 | 92 | section. |
| 93 | 93 | |
| 94 | 94 | [ckwf]: ./ckout-workflows.md |
| 95 | -[co]: /help?cmd=checkout | |
| 95 | +[co]: /help/checkout | |
| 96 | 96 | |
| 97 | 97 | |
| 98 | 98 | #### <a id="pullup"></a> Update vs Pull |
| 99 | 99 | |
| 100 | 100 | The closest equivalent to [`git pull`][gpull] is not |
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | tip of the current branch. |
| 132 | 132 | |
| 133 | 133 | We think this is a more sensible command design than `git pull` vs |
| 134 | 134 | `git checkout`. ([…vs `git checkout` vs `git checkout`!][gcokoan]) |
| 135 | 135 | |
| 136 | -[fpull]: /help?cmd=pull | |
| 136 | +[fpull]: /help/pull | |
| 137 | 137 | [gpull]: https://git-scm.com/docs/git-pull |
| 138 | 138 | [gcokoan]: https://stevelosh.com/blog/2013/04/git-koans/#s2-one-thing-well |
| 139 | 139 | |
| 140 | 140 | |
| 141 | 141 | #### <a id="close" name="dotfile"></a> Closing a Check-Out |
| @@ -169,11 +169,11 @@ | ||
| 169 | 169 | is that you’re about to delete the directory, so you want Fossil to forget about it |
| 170 | 170 | for the purposes of commands like [`fossil all`][all]. Even that isn’t |
| 171 | 171 | necessary, because Fossil will detect that this has happened and forget |
| 172 | 172 | the working directory for you. |
| 173 | 173 | |
| 174 | -[all]: /help?cmd=all | |
| 174 | +[all]: /help/all | |
| 175 | 175 | |
| 176 | 176 | |
| 177 | 177 | #### <a id="worktree"></a> Git Worktrees |
| 178 | 178 | |
| 179 | 179 | There are at least three different ways to get [Fossil-style multiple |
| @@ -271,18 +271,18 @@ | ||
| 271 | 271 | you’re following [the directory scheme exemplified in the glossary](./glossary.md#repository). That said, it |
| 272 | 272 | does emphasize an earlier point: Fossil doesn’t care where you put the |
| 273 | 273 | repo DB file or what you name it. |
| 274 | 274 | |
| 275 | 275 | |
| 276 | -[clone]: /help?cmd=clone | |
| 277 | -[close]: /help?cmd=close | |
| 276 | +[clone]: /help/clone | |
| 277 | +[close]: /help/close | |
| 278 | 278 | [gloss]: ./glossary.md |
| 279 | -[open]: /help?cmd=open | |
| 280 | -[set]: /help?cmd=setting | |
| 281 | -[server]: /help?cmd=server | |
| 282 | -[stash]: /help?cmd=stash | |
| 283 | -[undo]: /help?cmd=undo | |
| 279 | +[open]: /help/open | |
| 280 | +[set]: /help/setting | |
| 281 | +[server]: /help/server | |
| 282 | +[stash]: /help/stash | |
| 283 | +[undo]: /help/undo | |
| 284 | 284 | |
| 285 | 285 | |
| 286 | 286 | ## <a id="log"></a> Fossil’s Timeline Is the “Log” |
| 287 | 287 | |
| 288 | 288 | Git users often need to use the `git log` command to dig linearly through |
| @@ -416,16 +416,16 @@ | ||
| 416 | 416 | intermediate like “`f time desc curr`”, which is reasonably clear. |
| 417 | 417 | |
| 418 | 418 | [35pct]: https://www.sqlite.org/fasterthanfs.html |
| 419 | 419 | [btree]: https://sqlite.org/btreemodule.html |
| 420 | 420 | [gcn]: https://git-scm.com/docs/gitrevisions |
| 421 | -[infoc]: /help?cmd=info | |
| 422 | -[infow]: /help?cmd=/info | |
| 421 | +[infoc]: /help/info | |
| 422 | +[infow]: /help/www/info | |
| 423 | 423 | [ocomp]: https://www.bigocheatsheet.com/ |
| 424 | -[tlc]: /help?cmd=timeline | |
| 425 | -[tlw]: /help?cmd=/timeline | |
| 426 | -[up]: /help?cmd=update | |
| 424 | +[tlc]: /help/timeline | |
| 425 | +[tlw]: /help/www/timeline | |
| 426 | +[up]: /help/update | |
| 427 | 427 | [wdm]: ./fossil-v-git.wiki#durable |
| 428 | 428 | |
| 429 | 429 | |
| 430 | 430 | ## <a id="dhead"></a> Detached HEAD State |
| 431 | 431 | |
| @@ -631,11 +631,11 @@ | ||
| 631 | 631 | keeps using the same remote server URL you gave it last |
| 632 | 632 | until you [tell it to do something different][rem]. It pushes all |
| 633 | 633 | branches, not just one named local branch. |
| 634 | 634 | |
| 635 | 635 | [capt]: ./cap-theorem.md |
| 636 | -[rem]: /help?cmd=remote | |
| 636 | +[rem]: /help/remote | |
| 637 | 637 | |
| 638 | 638 | |
| 639 | 639 | <a id="autosync"></a> |
| 640 | 640 | ## Autosync |
| 641 | 641 | |
| @@ -941,11 +941,11 @@ | ||
| 941 | 941 | command set. |
| 942 | 942 | |
| 943 | 943 | If you leave off the `-v` flag in the second example, the `diffstat` |
| 944 | 944 | output won’t include info about any newly-added files. |
| 945 | 945 | |
| 946 | -[dcset]: https://fossil-scm.org/home/help?cmd=diff-command | |
| 946 | +[dcset]: https://fossil-scm.org/home/help/diff-command | |
| 947 | 947 | [dst]: https://invisible-island.net/diffstat/diffstat.html |
| 948 | 948 | |
| 949 | 949 | |
| 950 | 950 | <a id="btnames"></a> |
| 951 | 951 | ## Branch and Tag Names |
| @@ -964,11 +964,11 @@ | ||
| 964 | 964 | [The `fossil git export` command][fge] squashes repeated tags down to a |
| 965 | 965 | single instance to avoid confusing Git, exporting only the newest tag, |
| 966 | 966 | emulating Fossil’s own ambiguity resolution rule as best it can within |
| 967 | 967 | Git’s limitations. |
| 968 | 968 | |
| 969 | -[fge]: /help?cmd=git | |
| 969 | +[fge]: /help/git | |
| 970 | 970 | [gcrf]: https://git-scm.com/docs/git-check-ref-format |
| 971 | 971 | |
| 972 | 972 | |
| 973 | 973 | |
| 974 | 974 | |
| @@ -981,11 +981,11 @@ | ||
| 981 | 981 | both merge operations, and the two actions differ only in direction. |
| 982 | 982 | |
| 983 | 983 | Unlike in Git, the Fossil file format remembers cherrypicks and backouts |
| 984 | 984 | and can later show them as dashed lines on the graphical timeline. |
| 985 | 985 | |
| 986 | -[merge]: /help?cmd=merge | |
| 986 | +[merge]: /help/merge | |
| 987 | 987 | |
| 988 | 988 | |
| 989 | 989 | |
| 990 | 990 | <a id="mvrm"></a> |
| 991 | 991 | ## File Moves and Renames Are Soft by Default |
| @@ -1005,12 +1005,12 @@ | ||
| 1005 | 1005 | If you want to keep Fossil’s soft `mv/rm` behavior most of the time, you |
| 1006 | 1006 | can cast it away on a per-command basis: |
| 1007 | 1007 | |
| 1008 | 1008 | fossil mv --hard old-name new-name |
| 1009 | 1009 | |
| 1010 | -[mv]: /help?cmd=mv | |
| 1011 | -[rm]: /help?cmd=rm | |
| 1010 | +[mv]: /help/mv | |
| 1011 | +[rm]: /help/rm | |
| 1012 | 1012 | |
| 1013 | 1013 | |
| 1014 | 1014 | ---- |
| 1015 | 1015 | |
| 1016 | 1016 | |
| 1017 | 1017 |
| --- www/gitusers.md | |
| +++ www/gitusers.md | |
| @@ -37,11 +37,11 @@ | |
| 37 | While we do try to explain Fossil-specific terminology inline here |
| 38 | as-needed, you may find it helpful to skim [the Fossil glossary][gloss]. |
| 39 | It will give you another take on our definitions here, and it may help |
| 40 | you to understand some of the other Fossil docs better. |
| 41 | |
| 42 | [fbis]: /help?cmd=bisect |
| 43 | [gbis]: https://git-scm.com/docs/git-bisect |
| 44 | [ffor]: https://fossil-scm.org/forum |
| 45 | [fvg]: ./fossil-v-git.wiki |
| 46 | |
| 47 | |
| @@ -90,11 +90,11 @@ | |
| 90 | from the remote repository into the local clone as `fossil update` does. |
| 91 | We think this is less broadly useful, but that’s the subject of the next |
| 92 | section. |
| 93 | |
| 94 | [ckwf]: ./ckout-workflows.md |
| 95 | [co]: /help?cmd=checkout |
| 96 | |
| 97 | |
| 98 | #### <a id="pullup"></a> Update vs Pull |
| 99 | |
| 100 | The closest equivalent to [`git pull`][gpull] is not |
| @@ -131,11 +131,11 @@ | |
| 131 | tip of the current branch. |
| 132 | |
| 133 | We think this is a more sensible command design than `git pull` vs |
| 134 | `git checkout`. ([…vs `git checkout` vs `git checkout`!][gcokoan]) |
| 135 | |
| 136 | [fpull]: /help?cmd=pull |
| 137 | [gpull]: https://git-scm.com/docs/git-pull |
| 138 | [gcokoan]: https://stevelosh.com/blog/2013/04/git-koans/#s2-one-thing-well |
| 139 | |
| 140 | |
| 141 | #### <a id="close" name="dotfile"></a> Closing a Check-Out |
| @@ -169,11 +169,11 @@ | |
| 169 | is that you’re about to delete the directory, so you want Fossil to forget about it |
| 170 | for the purposes of commands like [`fossil all`][all]. Even that isn’t |
| 171 | necessary, because Fossil will detect that this has happened and forget |
| 172 | the working directory for you. |
| 173 | |
| 174 | [all]: /help?cmd=all |
| 175 | |
| 176 | |
| 177 | #### <a id="worktree"></a> Git Worktrees |
| 178 | |
| 179 | There are at least three different ways to get [Fossil-style multiple |
| @@ -271,18 +271,18 @@ | |
| 271 | you’re following [the directory scheme exemplified in the glossary](./glossary.md#repository). That said, it |
| 272 | does emphasize an earlier point: Fossil doesn’t care where you put the |
| 273 | repo DB file or what you name it. |
| 274 | |
| 275 | |
| 276 | [clone]: /help?cmd=clone |
| 277 | [close]: /help?cmd=close |
| 278 | [gloss]: ./glossary.md |
| 279 | [open]: /help?cmd=open |
| 280 | [set]: /help?cmd=setting |
| 281 | [server]: /help?cmd=server |
| 282 | [stash]: /help?cmd=stash |
| 283 | [undo]: /help?cmd=undo |
| 284 | |
| 285 | |
| 286 | ## <a id="log"></a> Fossil’s Timeline Is the “Log” |
| 287 | |
| 288 | Git users often need to use the `git log` command to dig linearly through |
| @@ -416,16 +416,16 @@ | |
| 416 | intermediate like “`f time desc curr`”, which is reasonably clear. |
| 417 | |
| 418 | [35pct]: https://www.sqlite.org/fasterthanfs.html |
| 419 | [btree]: https://sqlite.org/btreemodule.html |
| 420 | [gcn]: https://git-scm.com/docs/gitrevisions |
| 421 | [infoc]: /help?cmd=info |
| 422 | [infow]: /help?cmd=/info |
| 423 | [ocomp]: https://www.bigocheatsheet.com/ |
| 424 | [tlc]: /help?cmd=timeline |
| 425 | [tlw]: /help?cmd=/timeline |
| 426 | [up]: /help?cmd=update |
| 427 | [wdm]: ./fossil-v-git.wiki#durable |
| 428 | |
| 429 | |
| 430 | ## <a id="dhead"></a> Detached HEAD State |
| 431 | |
| @@ -631,11 +631,11 @@ | |
| 631 | keeps using the same remote server URL you gave it last |
| 632 | until you [tell it to do something different][rem]. It pushes all |
| 633 | branches, not just one named local branch. |
| 634 | |
| 635 | [capt]: ./cap-theorem.md |
| 636 | [rem]: /help?cmd=remote |
| 637 | |
| 638 | |
| 639 | <a id="autosync"></a> |
| 640 | ## Autosync |
| 641 | |
| @@ -941,11 +941,11 @@ | |
| 941 | command set. |
| 942 | |
| 943 | If you leave off the `-v` flag in the second example, the `diffstat` |
| 944 | output won’t include info about any newly-added files. |
| 945 | |
| 946 | [dcset]: https://fossil-scm.org/home/help?cmd=diff-command |
| 947 | [dst]: https://invisible-island.net/diffstat/diffstat.html |
| 948 | |
| 949 | |
| 950 | <a id="btnames"></a> |
| 951 | ## Branch and Tag Names |
| @@ -964,11 +964,11 @@ | |
| 964 | [The `fossil git export` command][fge] squashes repeated tags down to a |
| 965 | single instance to avoid confusing Git, exporting only the newest tag, |
| 966 | emulating Fossil’s own ambiguity resolution rule as best it can within |
| 967 | Git’s limitations. |
| 968 | |
| 969 | [fge]: /help?cmd=git |
| 970 | [gcrf]: https://git-scm.com/docs/git-check-ref-format |
| 971 | |
| 972 | |
| 973 | |
| 974 | |
| @@ -981,11 +981,11 @@ | |
| 981 | both merge operations, and the two actions differ only in direction. |
| 982 | |
| 983 | Unlike in Git, the Fossil file format remembers cherrypicks and backouts |
| 984 | and can later show them as dashed lines on the graphical timeline. |
| 985 | |
| 986 | [merge]: /help?cmd=merge |
| 987 | |
| 988 | |
| 989 | |
| 990 | <a id="mvrm"></a> |
| 991 | ## File Moves and Renames Are Soft by Default |
| @@ -1005,12 +1005,12 @@ | |
| 1005 | If you want to keep Fossil’s soft `mv/rm` behavior most of the time, you |
| 1006 | can cast it away on a per-command basis: |
| 1007 | |
| 1008 | fossil mv --hard old-name new-name |
| 1009 | |
| 1010 | [mv]: /help?cmd=mv |
| 1011 | [rm]: /help?cmd=rm |
| 1012 | |
| 1013 | |
| 1014 | ---- |
| 1015 | |
| 1016 | |
| 1017 |
| --- www/gitusers.md | |
| +++ www/gitusers.md | |
| @@ -37,11 +37,11 @@ | |
| 37 | While we do try to explain Fossil-specific terminology inline here |
| 38 | as-needed, you may find it helpful to skim [the Fossil glossary][gloss]. |
| 39 | It will give you another take on our definitions here, and it may help |
| 40 | you to understand some of the other Fossil docs better. |
| 41 | |
| 42 | [fbis]: /help/bisect |
| 43 | [gbis]: https://git-scm.com/docs/git-bisect |
| 44 | [ffor]: https://fossil-scm.org/forum |
| 45 | [fvg]: ./fossil-v-git.wiki |
| 46 | |
| 47 | |
| @@ -90,11 +90,11 @@ | |
| 90 | from the remote repository into the local clone as `fossil update` does. |
| 91 | We think this is less broadly useful, but that’s the subject of the next |
| 92 | section. |
| 93 | |
| 94 | [ckwf]: ./ckout-workflows.md |
| 95 | [co]: /help/checkout |
| 96 | |
| 97 | |
| 98 | #### <a id="pullup"></a> Update vs Pull |
| 99 | |
| 100 | The closest equivalent to [`git pull`][gpull] is not |
| @@ -131,11 +131,11 @@ | |
| 131 | tip of the current branch. |
| 132 | |
| 133 | We think this is a more sensible command design than `git pull` vs |
| 134 | `git checkout`. ([…vs `git checkout` vs `git checkout`!][gcokoan]) |
| 135 | |
| 136 | [fpull]: /help/pull |
| 137 | [gpull]: https://git-scm.com/docs/git-pull |
| 138 | [gcokoan]: https://stevelosh.com/blog/2013/04/git-koans/#s2-one-thing-well |
| 139 | |
| 140 | |
| 141 | #### <a id="close" name="dotfile"></a> Closing a Check-Out |
| @@ -169,11 +169,11 @@ | |
| 169 | is that you’re about to delete the directory, so you want Fossil to forget about it |
| 170 | for the purposes of commands like [`fossil all`][all]. Even that isn’t |
| 171 | necessary, because Fossil will detect that this has happened and forget |
| 172 | the working directory for you. |
| 173 | |
| 174 | [all]: /help/all |
| 175 | |
| 176 | |
| 177 | #### <a id="worktree"></a> Git Worktrees |
| 178 | |
| 179 | There are at least three different ways to get [Fossil-style multiple |
| @@ -271,18 +271,18 @@ | |
| 271 | you’re following [the directory scheme exemplified in the glossary](./glossary.md#repository). That said, it |
| 272 | does emphasize an earlier point: Fossil doesn’t care where you put the |
| 273 | repo DB file or what you name it. |
| 274 | |
| 275 | |
| 276 | [clone]: /help/clone |
| 277 | [close]: /help/close |
| 278 | [gloss]: ./glossary.md |
| 279 | [open]: /help/open |
| 280 | [set]: /help/setting |
| 281 | [server]: /help/server |
| 282 | [stash]: /help/stash |
| 283 | [undo]: /help/undo |
| 284 | |
| 285 | |
| 286 | ## <a id="log"></a> Fossil’s Timeline Is the “Log” |
| 287 | |
| 288 | Git users often need to use the `git log` command to dig linearly through |
| @@ -416,16 +416,16 @@ | |
| 416 | intermediate like “`f time desc curr`”, which is reasonably clear. |
| 417 | |
| 418 | [35pct]: https://www.sqlite.org/fasterthanfs.html |
| 419 | [btree]: https://sqlite.org/btreemodule.html |
| 420 | [gcn]: https://git-scm.com/docs/gitrevisions |
| 421 | [infoc]: /help/info |
| 422 | [infow]: /help/www/info |
| 423 | [ocomp]: https://www.bigocheatsheet.com/ |
| 424 | [tlc]: /help/timeline |
| 425 | [tlw]: /help/www/timeline |
| 426 | [up]: /help/update |
| 427 | [wdm]: ./fossil-v-git.wiki#durable |
| 428 | |
| 429 | |
| 430 | ## <a id="dhead"></a> Detached HEAD State |
| 431 | |
| @@ -631,11 +631,11 @@ | |
| 631 | keeps using the same remote server URL you gave it last |
| 632 | until you [tell it to do something different][rem]. It pushes all |
| 633 | branches, not just one named local branch. |
| 634 | |
| 635 | [capt]: ./cap-theorem.md |
| 636 | [rem]: /help/remote |
| 637 | |
| 638 | |
| 639 | <a id="autosync"></a> |
| 640 | ## Autosync |
| 641 | |
| @@ -941,11 +941,11 @@ | |
| 941 | command set. |
| 942 | |
| 943 | If you leave off the `-v` flag in the second example, the `diffstat` |
| 944 | output won’t include info about any newly-added files. |
| 945 | |
| 946 | [dcset]: https://fossil-scm.org/home/help/diff-command |
| 947 | [dst]: https://invisible-island.net/diffstat/diffstat.html |
| 948 | |
| 949 | |
| 950 | <a id="btnames"></a> |
| 951 | ## Branch and Tag Names |
| @@ -964,11 +964,11 @@ | |
| 964 | [The `fossil git export` command][fge] squashes repeated tags down to a |
| 965 | single instance to avoid confusing Git, exporting only the newest tag, |
| 966 | emulating Fossil’s own ambiguity resolution rule as best it can within |
| 967 | Git’s limitations. |
| 968 | |
| 969 | [fge]: /help/git |
| 970 | [gcrf]: https://git-scm.com/docs/git-check-ref-format |
| 971 | |
| 972 | |
| 973 | |
| 974 | |
| @@ -981,11 +981,11 @@ | |
| 981 | both merge operations, and the two actions differ only in direction. |
| 982 | |
| 983 | Unlike in Git, the Fossil file format remembers cherrypicks and backouts |
| 984 | and can later show them as dashed lines on the graphical timeline. |
| 985 | |
| 986 | [merge]: /help/merge |
| 987 | |
| 988 | |
| 989 | |
| 990 | <a id="mvrm"></a> |
| 991 | ## File Moves and Renames Are Soft by Default |
| @@ -1005,12 +1005,12 @@ | |
| 1005 | If you want to keep Fossil’s soft `mv/rm` behavior most of the time, you |
| 1006 | can cast it away on a per-command basis: |
| 1007 | |
| 1008 | fossil mv --hard old-name new-name |
| 1009 | |
| 1010 | [mv]: /help/mv |
| 1011 | [rm]: /help/rm |
| 1012 | |
| 1013 | |
| 1014 | ---- |
| 1015 | |
| 1016 | |
| 1017 |
+24
-24
| --- www/globs.md | ||
| +++ www/globs.md | ||
| @@ -242,29 +242,29 @@ | ||
| 242 | 242 | The commands [`http`][], [`cgi`][], [`server`][], and [`ui`][] that |
| 243 | 243 | implement or support with web servers provide a mechanism to name some |
| 244 | 244 | files to serve with static content where a list of glob patterns |
| 245 | 245 | specifies what content may be served. |
| 246 | 246 | |
| 247 | -[`add`]: /help?cmd=add | |
| 248 | -[`addremove`]: /help?cmd=addremove | |
| 249 | -[`changes`]: /help?cmd=changes | |
| 250 | -[`clean`]: /help?cmd=clean | |
| 251 | -[`commit`]: /help?cmd=commit | |
| 252 | -[`extras`]: /help?cmd=extras | |
| 253 | -[`merge`]: /help?cmd=merge | |
| 254 | -[`settings`]: /help?cmd=settings | |
| 255 | -[`status`]: /help?cmd=status | |
| 256 | -[`touch`]: /help?cmd=touch | |
| 257 | -[`unset`]: /help?cmd=unset | |
| 258 | - | |
| 259 | -[`tarball`]: /help?cmd=tarball | |
| 260 | -[`zip`]: /help?cmd=zip | |
| 261 | - | |
| 262 | -[`http`]: /help?cmd=http | |
| 263 | -[`cgi`]: /help?cmd=cgi | |
| 264 | -[`server`]: /help?cmd=server | |
| 265 | -[`ui`]: /help?cmd=ui | |
| 247 | +[`add`]: /help/add | |
| 248 | +[`addremove`]: /help/addremove | |
| 249 | +[`changes`]: /help/changes | |
| 250 | +[`clean`]: /help/clean | |
| 251 | +[`commit`]: /help/commit | |
| 252 | +[`extras`]: /help/extras | |
| 253 | +[`merge`]: /help/merge | |
| 254 | +[`settings`]: /help/settings | |
| 255 | +[`status`]: /help/status | |
| 256 | +[`touch`]: /help/touch | |
| 257 | +[`unset`]: /help/unset | |
| 258 | + | |
| 259 | +[`tarball`]: /help/tarball | |
| 260 | +[`zip`]: /help/zip | |
| 261 | + | |
| 262 | +[`http`]: /help/http | |
| 263 | +[`cgi`]: /help/cgi | |
| 264 | +[`server`]: /help/server | |
| 265 | +[`ui`]: /help/ui | |
| 266 | 266 | |
| 267 | 267 | |
| 268 | 268 | ### Web Pages that Refer to Globs |
| 269 | 269 | |
| 270 | 270 | The [`/timeline`][] page supports the query parameter `chng=GLOBLIST` that |
| @@ -277,13 +277,13 @@ | ||
| 277 | 277 | The pages [`/tarball`][] and [`/zip`][] generate compressed archives |
| 278 | 278 | of a specific checkin. They may be further restricted by query |
| 279 | 279 | parameters that specify glob patterns that name files to include or |
| 280 | 280 | exclude rather than taking the entire checkin. |
| 281 | 281 | |
| 282 | -[`/timeline`]: /help?cmd=/timeline | |
| 283 | -[`/tarball`]: /help?cmd=/tarball | |
| 284 | -[`/zip`]: /help?cmd=/zip | |
| 282 | +[`/timeline`]: /help/www/timeline | |
| 283 | +[`/tarball`]: /help/www/tarball | |
| 284 | +[`/zip`]: /help/www/zip | |
| 285 | 285 | |
| 286 | 286 | |
| 287 | 287 | ## Platform Quirks |
| 288 | 288 | |
| 289 | 289 | Fossil glob patterns are based on the glob pattern feature of POSIX |
| @@ -512,12 +512,12 @@ | ||
| 512 | 512 | C:\> echo * | fossil test-echo setting crlf-glob --args - |
| 513 | 513 | |
| 514 | 514 | The [`test-glob`][] command is also handy to test if a string |
| 515 | 515 | matches a glob pattern. |
| 516 | 516 | |
| 517 | -[`test-echo`]: /help?cmd=test-echo | |
| 518 | -[`test-glob`]: /help?cmd=test-glob | |
| 517 | +[`test-echo`]: /help/test-echo | |
| 518 | +[`test-glob`]: /help/test-glob | |
| 519 | 519 | |
| 520 | 520 | |
| 521 | 521 | ## Converting `.gitignore` to `ignore-glob` |
| 522 | 522 | |
| 523 | 523 | Many other version control systems handle the specific case of |
| 524 | 524 |
| --- www/globs.md | |
| +++ www/globs.md | |
| @@ -242,29 +242,29 @@ | |
| 242 | The commands [`http`][], [`cgi`][], [`server`][], and [`ui`][] that |
| 243 | implement or support with web servers provide a mechanism to name some |
| 244 | files to serve with static content where a list of glob patterns |
| 245 | specifies what content may be served. |
| 246 | |
| 247 | [`add`]: /help?cmd=add |
| 248 | [`addremove`]: /help?cmd=addremove |
| 249 | [`changes`]: /help?cmd=changes |
| 250 | [`clean`]: /help?cmd=clean |
| 251 | [`commit`]: /help?cmd=commit |
| 252 | [`extras`]: /help?cmd=extras |
| 253 | [`merge`]: /help?cmd=merge |
| 254 | [`settings`]: /help?cmd=settings |
| 255 | [`status`]: /help?cmd=status |
| 256 | [`touch`]: /help?cmd=touch |
| 257 | [`unset`]: /help?cmd=unset |
| 258 | |
| 259 | [`tarball`]: /help?cmd=tarball |
| 260 | [`zip`]: /help?cmd=zip |
| 261 | |
| 262 | [`http`]: /help?cmd=http |
| 263 | [`cgi`]: /help?cmd=cgi |
| 264 | [`server`]: /help?cmd=server |
| 265 | [`ui`]: /help?cmd=ui |
| 266 | |
| 267 | |
| 268 | ### Web Pages that Refer to Globs |
| 269 | |
| 270 | The [`/timeline`][] page supports the query parameter `chng=GLOBLIST` that |
| @@ -277,13 +277,13 @@ | |
| 277 | The pages [`/tarball`][] and [`/zip`][] generate compressed archives |
| 278 | of a specific checkin. They may be further restricted by query |
| 279 | parameters that specify glob patterns that name files to include or |
| 280 | exclude rather than taking the entire checkin. |
| 281 | |
| 282 | [`/timeline`]: /help?cmd=/timeline |
| 283 | [`/tarball`]: /help?cmd=/tarball |
| 284 | [`/zip`]: /help?cmd=/zip |
| 285 | |
| 286 | |
| 287 | ## Platform Quirks |
| 288 | |
| 289 | Fossil glob patterns are based on the glob pattern feature of POSIX |
| @@ -512,12 +512,12 @@ | |
| 512 | C:\> echo * | fossil test-echo setting crlf-glob --args - |
| 513 | |
| 514 | The [`test-glob`][] command is also handy to test if a string |
| 515 | matches a glob pattern. |
| 516 | |
| 517 | [`test-echo`]: /help?cmd=test-echo |
| 518 | [`test-glob`]: /help?cmd=test-glob |
| 519 | |
| 520 | |
| 521 | ## Converting `.gitignore` to `ignore-glob` |
| 522 | |
| 523 | Many other version control systems handle the specific case of |
| 524 |
| --- www/globs.md | |
| +++ www/globs.md | |
| @@ -242,29 +242,29 @@ | |
| 242 | The commands [`http`][], [`cgi`][], [`server`][], and [`ui`][] that |
| 243 | implement or support with web servers provide a mechanism to name some |
| 244 | files to serve with static content where a list of glob patterns |
| 245 | specifies what content may be served. |
| 246 | |
| 247 | [`add`]: /help/add |
| 248 | [`addremove`]: /help/addremove |
| 249 | [`changes`]: /help/changes |
| 250 | [`clean`]: /help/clean |
| 251 | [`commit`]: /help/commit |
| 252 | [`extras`]: /help/extras |
| 253 | [`merge`]: /help/merge |
| 254 | [`settings`]: /help/settings |
| 255 | [`status`]: /help/status |
| 256 | [`touch`]: /help/touch |
| 257 | [`unset`]: /help/unset |
| 258 | |
| 259 | [`tarball`]: /help/tarball |
| 260 | [`zip`]: /help/zip |
| 261 | |
| 262 | [`http`]: /help/http |
| 263 | [`cgi`]: /help/cgi |
| 264 | [`server`]: /help/server |
| 265 | [`ui`]: /help/ui |
| 266 | |
| 267 | |
| 268 | ### Web Pages that Refer to Globs |
| 269 | |
| 270 | The [`/timeline`][] page supports the query parameter `chng=GLOBLIST` that |
| @@ -277,13 +277,13 @@ | |
| 277 | The pages [`/tarball`][] and [`/zip`][] generate compressed archives |
| 278 | of a specific checkin. They may be further restricted by query |
| 279 | parameters that specify glob patterns that name files to include or |
| 280 | exclude rather than taking the entire checkin. |
| 281 | |
| 282 | [`/timeline`]: /help/www/timeline |
| 283 | [`/tarball`]: /help/www/tarball |
| 284 | [`/zip`]: /help/www/zip |
| 285 | |
| 286 | |
| 287 | ## Platform Quirks |
| 288 | |
| 289 | Fossil glob patterns are based on the glob pattern feature of POSIX |
| @@ -512,12 +512,12 @@ | |
| 512 | C:\> echo * | fossil test-echo setting crlf-glob --args - |
| 513 | |
| 514 | The [`test-glob`][] command is also handy to test if a string |
| 515 | matches a glob pattern. |
| 516 | |
| 517 | [`test-echo`]: /help/test-echo |
| 518 | [`test-glob`]: /help/test-glob |
| 519 | |
| 520 | |
| 521 | ## Converting `.gitignore` to `ignore-glob` |
| 522 | |
| 523 | Many other version control systems handle the specific case of |
| 524 |
+18
-18
| --- www/glossary.md | ||
| +++ www/glossary.md | ||
| @@ -90,17 +90,17 @@ | ||
| 90 | 90 | As a counterexample, a project tracking your [Vim] configuration |
| 91 | 91 | history is a much better use of Fossil, because it’s all held within |
| 92 | 92 | `~/.vim`, and your user has full rights to that subdirectory. |
| 93 | 93 | |
| 94 | 94 | [AIF]: https://docs.asciidoctor.org/asciidoc/latest/directives/include/ |
| 95 | -[IGS]: /help?cmd=ignore-glob | |
| 95 | +[IGS]: /help/ignore-glob | |
| 96 | 96 | [IFRS]: ./image-format-vs-repo-size.md |
| 97 | -[tarball]: /help?cmd=tarball | |
| 98 | -[tw]: /help?cmd=/tarball | |
| 97 | +[tarball]: /help/tarball | |
| 98 | +[tw]: /help/www/tarball | |
| 99 | 99 | [Vim]: https://www.vim.org/ |
| 100 | -[zip]: /help?cmd=zip | |
| 101 | -[zw]: /help?cmd=/zip | |
| 100 | +[zip]: /help/zip | |
| 101 | +[zw]: /help/www/zip | |
| 102 | 102 | |
| 103 | 103 | |
| 104 | 104 | ## Repository <a id="repository" name="repo"></a> |
| 105 | 105 | |
| 106 | 106 | A single file that contains all historical versions of all files in a |
| @@ -195,18 +195,18 @@ | ||
| 195 | 195 | line dotted right until even with previous line.end |
| 196 | 196 | move right 0.05 |
| 197 | 197 | box invis "clones of Fossil itself, SQLite, etc." ljust |
| 198 | 198 | ``` |
| 199 | 199 | |
| 200 | -[asdis]: /help?cmd=autosync | |
| 200 | +[asdis]: /help/autosync | |
| 201 | 201 | [backup]: ./backup.md |
| 202 | 202 | [CAP]: ./cap-theorem.md |
| 203 | -[cloned]: /help?cmd=clone | |
| 204 | -[pull]: /help?cmd=pull | |
| 205 | -[push]: /help?cmd=push | |
| 206 | -[svrcmd]: /help?cmd=server | |
| 207 | -[sync]: /help?cmd=sync | |
| 203 | +[cloned]: /help/clone | |
| 204 | +[pull]: /help/pull | |
| 205 | +[push]: /help/push | |
| 206 | +[svrcmd]: /help/server | |
| 207 | +[sync]: /help/sync | |
| 208 | 208 | |
| 209 | 209 | [repository]: #repo |
| 210 | 210 | [repositories]: #repo |
| 211 | 211 | |
| 212 | 212 | |
| @@ -310,15 +310,15 @@ | ||
| 310 | 310 | at the time of the snapshot. (Thus [the `extras` command][extras].) |
| 311 | 311 | Contrast a snapshot taken by a virtual machine system or a |
| 312 | 312 | [snapshotting file system][snfs], which captures changes to everything |
| 313 | 313 | on the managed storage volume. |
| 314 | 314 | |
| 315 | -[add]: /help?cmd=add | |
| 315 | +[add]: /help/add | |
| 316 | 316 | [ciname]: ./checkin_names.wiki |
| 317 | -[extras]: /help?cmd=extras | |
| 318 | -[stash]: /help?cmd=stash | |
| 319 | -[undo]: /help?cmd=undo | |
| 317 | +[extras]: /help/extras | |
| 318 | +[stash]: /help/stash | |
| 319 | +[undo]: /help/undo | |
| 320 | 320 | |
| 321 | 321 | |
| 322 | 322 | |
| 323 | 323 | ## Check-out <a id="check-out" name="co"></a> |
| 324 | 324 | |
| @@ -365,17 +365,17 @@ | ||
| 365 | 365 | this is because VSCode’s version control features assume it’s being |
| 366 | 366 | used with Git, where the repository is the `.git` subdirectory |
| 367 | 367 | contents. With Fossil, [different check-out workflows][cwork] are |
| 368 | 368 | preferred. |
| 369 | 369 | |
| 370 | -[commit]: /help?cmd=commit | |
| 370 | +[commit]: /help/commit | |
| 371 | 371 | [cwork]: ./ckout-workflows.md |
| 372 | 372 | [h2cflp]: https://www.sqlite.org/howtocorrupt.html#_file_locking_problems |
| 373 | 373 | [fpvsc]: https://marketplace.visualstudio.com/items?itemName=koog1000.fossil |
| 374 | -[open]: /help?cmd=open | |
| 374 | +[open]: /help/open | |
| 375 | 375 | [mwd]: ./ckout-workflows.md#mcw |
| 376 | -[update]: /help?cmd=update | |
| 376 | +[update]: /help/update | |
| 377 | 377 | |
| 378 | 378 | |
| 379 | 379 | ## <a id="docs"></a>Embedded Documentation |
| 380 | 380 | |
| 381 | 381 | Serving as an alternative to Fossil’s built-in [wiki], the [embedded |
| 382 | 382 |
| --- www/glossary.md | |
| +++ www/glossary.md | |
| @@ -90,17 +90,17 @@ | |
| 90 | As a counterexample, a project tracking your [Vim] configuration |
| 91 | history is a much better use of Fossil, because it’s all held within |
| 92 | `~/.vim`, and your user has full rights to that subdirectory. |
| 93 | |
| 94 | [AIF]: https://docs.asciidoctor.org/asciidoc/latest/directives/include/ |
| 95 | [IGS]: /help?cmd=ignore-glob |
| 96 | [IFRS]: ./image-format-vs-repo-size.md |
| 97 | [tarball]: /help?cmd=tarball |
| 98 | [tw]: /help?cmd=/tarball |
| 99 | [Vim]: https://www.vim.org/ |
| 100 | [zip]: /help?cmd=zip |
| 101 | [zw]: /help?cmd=/zip |
| 102 | |
| 103 | |
| 104 | ## Repository <a id="repository" name="repo"></a> |
| 105 | |
| 106 | A single file that contains all historical versions of all files in a |
| @@ -195,18 +195,18 @@ | |
| 195 | line dotted right until even with previous line.end |
| 196 | move right 0.05 |
| 197 | box invis "clones of Fossil itself, SQLite, etc." ljust |
| 198 | ``` |
| 199 | |
| 200 | [asdis]: /help?cmd=autosync |
| 201 | [backup]: ./backup.md |
| 202 | [CAP]: ./cap-theorem.md |
| 203 | [cloned]: /help?cmd=clone |
| 204 | [pull]: /help?cmd=pull |
| 205 | [push]: /help?cmd=push |
| 206 | [svrcmd]: /help?cmd=server |
| 207 | [sync]: /help?cmd=sync |
| 208 | |
| 209 | [repository]: #repo |
| 210 | [repositories]: #repo |
| 211 | |
| 212 | |
| @@ -310,15 +310,15 @@ | |
| 310 | at the time of the snapshot. (Thus [the `extras` command][extras].) |
| 311 | Contrast a snapshot taken by a virtual machine system or a |
| 312 | [snapshotting file system][snfs], which captures changes to everything |
| 313 | on the managed storage volume. |
| 314 | |
| 315 | [add]: /help?cmd=add |
| 316 | [ciname]: ./checkin_names.wiki |
| 317 | [extras]: /help?cmd=extras |
| 318 | [stash]: /help?cmd=stash |
| 319 | [undo]: /help?cmd=undo |
| 320 | |
| 321 | |
| 322 | |
| 323 | ## Check-out <a id="check-out" name="co"></a> |
| 324 | |
| @@ -365,17 +365,17 @@ | |
| 365 | this is because VSCode’s version control features assume it’s being |
| 366 | used with Git, where the repository is the `.git` subdirectory |
| 367 | contents. With Fossil, [different check-out workflows][cwork] are |
| 368 | preferred. |
| 369 | |
| 370 | [commit]: /help?cmd=commit |
| 371 | [cwork]: ./ckout-workflows.md |
| 372 | [h2cflp]: https://www.sqlite.org/howtocorrupt.html#_file_locking_problems |
| 373 | [fpvsc]: https://marketplace.visualstudio.com/items?itemName=koog1000.fossil |
| 374 | [open]: /help?cmd=open |
| 375 | [mwd]: ./ckout-workflows.md#mcw |
| 376 | [update]: /help?cmd=update |
| 377 | |
| 378 | |
| 379 | ## <a id="docs"></a>Embedded Documentation |
| 380 | |
| 381 | Serving as an alternative to Fossil’s built-in [wiki], the [embedded |
| 382 |
| --- www/glossary.md | |
| +++ www/glossary.md | |
| @@ -90,17 +90,17 @@ | |
| 90 | As a counterexample, a project tracking your [Vim] configuration |
| 91 | history is a much better use of Fossil, because it’s all held within |
| 92 | `~/.vim`, and your user has full rights to that subdirectory. |
| 93 | |
| 94 | [AIF]: https://docs.asciidoctor.org/asciidoc/latest/directives/include/ |
| 95 | [IGS]: /help/ignore-glob |
| 96 | [IFRS]: ./image-format-vs-repo-size.md |
| 97 | [tarball]: /help/tarball |
| 98 | [tw]: /help/www/tarball |
| 99 | [Vim]: https://www.vim.org/ |
| 100 | [zip]: /help/zip |
| 101 | [zw]: /help/www/zip |
| 102 | |
| 103 | |
| 104 | ## Repository <a id="repository" name="repo"></a> |
| 105 | |
| 106 | A single file that contains all historical versions of all files in a |
| @@ -195,18 +195,18 @@ | |
| 195 | line dotted right until even with previous line.end |
| 196 | move right 0.05 |
| 197 | box invis "clones of Fossil itself, SQLite, etc." ljust |
| 198 | ``` |
| 199 | |
| 200 | [asdis]: /help/autosync |
| 201 | [backup]: ./backup.md |
| 202 | [CAP]: ./cap-theorem.md |
| 203 | [cloned]: /help/clone |
| 204 | [pull]: /help/pull |
| 205 | [push]: /help/push |
| 206 | [svrcmd]: /help/server |
| 207 | [sync]: /help/sync |
| 208 | |
| 209 | [repository]: #repo |
| 210 | [repositories]: #repo |
| 211 | |
| 212 | |
| @@ -310,15 +310,15 @@ | |
| 310 | at the time of the snapshot. (Thus [the `extras` command][extras].) |
| 311 | Contrast a snapshot taken by a virtual machine system or a |
| 312 | [snapshotting file system][snfs], which captures changes to everything |
| 313 | on the managed storage volume. |
| 314 | |
| 315 | [add]: /help/add |
| 316 | [ciname]: ./checkin_names.wiki |
| 317 | [extras]: /help/extras |
| 318 | [stash]: /help/stash |
| 319 | [undo]: /help/undo |
| 320 | |
| 321 | |
| 322 | |
| 323 | ## Check-out <a id="check-out" name="co"></a> |
| 324 | |
| @@ -365,17 +365,17 @@ | |
| 365 | this is because VSCode’s version control features assume it’s being |
| 366 | used with Git, where the repository is the `.git` subdirectory |
| 367 | contents. With Fossil, [different check-out workflows][cwork] are |
| 368 | preferred. |
| 369 | |
| 370 | [commit]: /help/commit |
| 371 | [cwork]: ./ckout-workflows.md |
| 372 | [h2cflp]: https://www.sqlite.org/howtocorrupt.html#_file_locking_problems |
| 373 | [fpvsc]: https://marketplace.visualstudio.com/items?itemName=koog1000.fossil |
| 374 | [open]: /help/open |
| 375 | [mwd]: ./ckout-workflows.md#mcw |
| 376 | [update]: /help/update |
| 377 | |
| 378 | |
| 379 | ## <a id="docs"></a>Embedded Documentation |
| 380 | |
| 381 | Serving as an alternative to Fossil’s built-in [wiki], the [embedded |
| 382 |
+5
| --- www/grep.md | ||
| +++ www/grep.md | ||
| @@ -99,10 +99,15 @@ | ||
| 99 | 99 | There are several restrictions in Fossil `grep` relative to a fully |
| 100 | 100 | POSIX compatible regular expression engine. Among them are: |
| 101 | 101 | |
| 102 | 102 | * There is currently no support for POSIX character classes such as |
| 103 | 103 | `[:lower:]`. |
| 104 | + | |
| 105 | +* The values of `p` and `q` in the "`{p,q}`" syntax can be no greater | |
| 106 | + than 999. This is because the NFA that is used for regular expression | |
| 107 | + matching is proportional in size to the largest p or q value, and hence | |
| 108 | + allowing arbitrarily large values could result in a DoS attack. | |
| 104 | 109 | |
| 105 | 110 | * Fossil `grep` does not currently attempt to take your operating |
| 106 | 111 | system's locale settings into account when doing this match. Since |
| 107 | 112 | Fossil has no way to mark a given file as having a particular |
| 108 | 113 | encoding, Fossil `grep` assumes the input files are in UTF-8 format. |
| 109 | 114 |
| --- www/grep.md | |
| +++ www/grep.md | |
| @@ -99,10 +99,15 @@ | |
| 99 | There are several restrictions in Fossil `grep` relative to a fully |
| 100 | POSIX compatible regular expression engine. Among them are: |
| 101 | |
| 102 | * There is currently no support for POSIX character classes such as |
| 103 | `[:lower:]`. |
| 104 | |
| 105 | * Fossil `grep` does not currently attempt to take your operating |
| 106 | system's locale settings into account when doing this match. Since |
| 107 | Fossil has no way to mark a given file as having a particular |
| 108 | encoding, Fossil `grep` assumes the input files are in UTF-8 format. |
| 109 |
| --- www/grep.md | |
| +++ www/grep.md | |
| @@ -99,10 +99,15 @@ | |
| 99 | There are several restrictions in Fossil `grep` relative to a fully |
| 100 | POSIX compatible regular expression engine. Among them are: |
| 101 | |
| 102 | * There is currently no support for POSIX character classes such as |
| 103 | `[:lower:]`. |
| 104 | |
| 105 | * The values of `p` and `q` in the "`{p,q}`" syntax can be no greater |
| 106 | than 999. This is because the NFA that is used for regular expression |
| 107 | matching is proportional in size to the largest p or q value, and hence |
| 108 | allowing arbitrarily large values could result in a DoS attack. |
| 109 | |
| 110 | * Fossil `grep` does not currently attempt to take your operating |
| 111 | system's locale settings into account when doing this match. Since |
| 112 | Fossil has no way to mark a given file as having a particular |
| 113 | encoding, Fossil `grep` assumes the input files are in UTF-8 format. |
| 114 |
+2
-2
| --- www/hashes.md | ||
| +++ www/hashes.md | ||
| @@ -145,10 +145,10 @@ | ||
| 145 | 145 | [ctkt]: ./custom_ticket.wiki |
| 146 | 146 | [hpol]: ./hashpolicy.wiki |
| 147 | 147 | [japi]: ./json-api/ |
| 148 | 148 | [jart]: ./json-api/api-artifact.md |
| 149 | 149 | [jtim]: ./json-api/api-timeline.md |
| 150 | -[mset]: /help?cmd=manifest | |
| 150 | +[mset]: /help/manifest | |
| 151 | 151 | [th1]: ./th1.md |
| 152 | -[trss]: /help?cmd=/timeline.rss | |
| 152 | +[trss]: /help/www/timeline.rss | |
| 153 | 153 | [tvb]: ./branching.wiki |
| 154 | 154 | [uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier |
| 155 | 155 |
| --- www/hashes.md | |
| +++ www/hashes.md | |
| @@ -145,10 +145,10 @@ | |
| 145 | [ctkt]: ./custom_ticket.wiki |
| 146 | [hpol]: ./hashpolicy.wiki |
| 147 | [japi]: ./json-api/ |
| 148 | [jart]: ./json-api/api-artifact.md |
| 149 | [jtim]: ./json-api/api-timeline.md |
| 150 | [mset]: /help?cmd=manifest |
| 151 | [th1]: ./th1.md |
| 152 | [trss]: /help?cmd=/timeline.rss |
| 153 | [tvb]: ./branching.wiki |
| 154 | [uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier |
| 155 |
| --- www/hashes.md | |
| +++ www/hashes.md | |
| @@ -145,10 +145,10 @@ | |
| 145 | [ctkt]: ./custom_ticket.wiki |
| 146 | [hpol]: ./hashpolicy.wiki |
| 147 | [japi]: ./json-api/ |
| 148 | [jart]: ./json-api/api-artifact.md |
| 149 | [jtim]: ./json-api/api-timeline.md |
| 150 | [mset]: /help/manifest |
| 151 | [th1]: ./th1.md |
| 152 | [trss]: /help/www/timeline.rss |
| 153 | [tvb]: ./branching.wiki |
| 154 | [uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier |
| 155 |
+1
-1
| --- www/hashpolicy.wiki | ||
| +++ www/hashpolicy.wiki | ||
| @@ -158,11 +158,11 @@ | ||
| 158 | 158 | |
| 159 | 159 | When a new repository is created by cloning, the hash policy is copied |
| 160 | 160 | from the parent. |
| 161 | 161 | |
| 162 | 162 | For new repositories created using the |
| 163 | -[/help?cmd=new|fossil new] command the default hash policy is "sha3". | |
| 163 | +[/help/new|fossil new] command the default hash policy is "sha3". | |
| 164 | 164 | That means new repositories |
| 165 | 165 | will normally hold nothing except SHA3 hashes. The hash policy for new |
| 166 | 166 | repositories can be overridden using the "--sha1" option to the |
| 167 | 167 | "fossil new" command. |
| 168 | 168 | |
| 169 | 169 |
| --- www/hashpolicy.wiki | |
| +++ www/hashpolicy.wiki | |
| @@ -158,11 +158,11 @@ | |
| 158 | |
| 159 | When a new repository is created by cloning, the hash policy is copied |
| 160 | from the parent. |
| 161 | |
| 162 | For new repositories created using the |
| 163 | [/help?cmd=new|fossil new] command the default hash policy is "sha3". |
| 164 | That means new repositories |
| 165 | will normally hold nothing except SHA3 hashes. The hash policy for new |
| 166 | repositories can be overridden using the "--sha1" option to the |
| 167 | "fossil new" command. |
| 168 | |
| 169 |
| --- www/hashpolicy.wiki | |
| +++ www/hashpolicy.wiki | |
| @@ -158,11 +158,11 @@ | |
| 158 | |
| 159 | When a new repository is created by cloning, the hash policy is copied |
| 160 | from the parent. |
| 161 | |
| 162 | For new repositories created using the |
| 163 | [/help/new|fossil new] command the default hash policy is "sha3". |
| 164 | That means new repositories |
| 165 | will normally hold nothing except SHA3 hashes. The hash policy for new |
| 166 | repositories can be overridden using the "--sha1" option to the |
| 167 | "fossil new" command. |
| 168 | |
| 169 |
+7
-7
| --- www/hints.wiki | ||
| +++ www/hints.wiki | ||
| @@ -3,11 +3,11 @@ | ||
| 3 | 3 | A collection of useful hints and tricks in no particular order: |
| 4 | 4 | |
| 5 | 5 | 1. Click on two nodes of any timeline graph in succession |
| 6 | 6 | to see a diff between the two versions. |
| 7 | 7 | |
| 8 | - 2. Add the "--tk" option to "[/help?cmd=diff | fossil diff]" commands | |
| 8 | + 2. Add the "--tk" option to "[/help/diff | fossil diff]" commands | |
| 9 | 9 | to get a pop-up |
| 10 | 10 | window containing a complete side-by-side diff. (NB: The pop-up |
| 11 | 11 | window is run as a separate Tcl/Tk process, so you will need to |
| 12 | 12 | have Tcl/Tk installed on your machine for this to work. Visit |
| 13 | 13 | [http://www.activestate.com/activetcl] for a quick download of |
| @@ -17,13 +17,13 @@ | ||
| 17 | 17 | alternative to "make clean". You can use "[/help/clean | fossil clean -f]" |
| 18 | 18 | as a slightly safer alternative if the "ignore-glob" setting is |
| 19 | 19 | not set. WARNING: make sure you did a "fossil add" for all source-files |
| 20 | 20 | you plan to commit, otherwise those files will be deleted without warning. |
| 21 | 21 | |
| 22 | - 4. Use "[/help?cmd=all | fossil all changes]" to look for any uncommitted | |
| 22 | + 4. Use "[/help/all | fossil all changes]" to look for any uncommitted | |
| 23 | 23 | edits in any of your Fossil projects. Use |
| 24 | - "[/help?cmd=all | fossil all pull]" on your laptop | |
| 24 | + "[/help/all | fossil all pull]" on your laptop | |
| 25 | 25 | prior to going off network (for example, on a long plane ride) |
| 26 | 26 | to make sure you have all the latest content locally. Then run |
| 27 | 27 | "[/help/all|fossil all push]" when you get back online to upload |
| 28 | 28 | your changes. |
| 29 | 29 | |
| @@ -37,13 +37,13 @@ | ||
| 37 | 37 | on in the Fossil repository on 2008-01-01, visit |
| 38 | 38 | [/timeline?c=2008-01-01]. |
| 39 | 39 | |
| 40 | 40 | 7. Further to the previous two hints, there are lots of query parameters |
| 41 | 41 | that you can add to timeline pages. The available query parameters |
| 42 | - are tersely documented [/help?cmd=/timeline | here]. | |
| 42 | + are tersely documented [/help/www/timeline | here]. | |
| 43 | 43 | |
| 44 | - 8. You can run "[/help?cmd=xdiff | fossil xdiff --tk $file1 $file2]" | |
| 44 | + 8. You can run "[/help/xdiff | fossil xdiff --tk $file1 $file2]" | |
| 45 | 45 | to get a Tk pop-up window with side-by-side diffs of two files, even if |
| 46 | 46 | neither of the two files is part of any Fossil repository. Note that |
| 47 | 47 | this command is "xdiff", not "diff". Change <nobr>--tk</nobr> to |
| 48 | 48 | <nobr>--by</nobr> to see the diff in your web browser. |
| 49 | 49 | |
| @@ -61,14 +61,14 @@ | ||
| 61 | 61 | 10. When editing documentation to be checked in as managed files, you can |
| 62 | 62 | preview what the documentation will look like by using the special |
| 63 | 63 | "ckout" branch name in the "doc" URL while running "fossil ui". |
| 64 | 64 | See the [./embeddeddoc.wiki | embedded documentation] for details. |
| 65 | 65 | |
| 66 | - 11. Use the "[/help?cmd=ui|fossil ui /]" command to bring up a menu of | |
| 66 | + 11. Use the "[/help/ui|fossil ui /]" command to bring up a menu of | |
| 67 | 67 | all of your local Fossil repositories in your web browser. |
| 68 | 68 | |
| 69 | 69 | 12. If you have a bunch of Fossil repositories living on a remote machine |
| 70 | 70 | that you are able to access using ssh using a command like |
| 71 | 71 | "ssh login@remote", then you can bring up a user interface for all |
| 72 | 72 | those remote repositories using the command: |
| 73 | - "[/help?cmd=ui|fossil ui login@remote:/]". This works by tunneling | |
| 73 | + "[/help/ui|fossil ui login@remote:/]". This works by tunneling | |
| 74 | 74 | all HTTP traffic through SSH to the remote machine. |
| 75 | 75 |
| --- www/hints.wiki | |
| +++ www/hints.wiki | |
| @@ -3,11 +3,11 @@ | |
| 3 | A collection of useful hints and tricks in no particular order: |
| 4 | |
| 5 | 1. Click on two nodes of any timeline graph in succession |
| 6 | to see a diff between the two versions. |
| 7 | |
| 8 | 2. Add the "--tk" option to "[/help?cmd=diff | fossil diff]" commands |
| 9 | to get a pop-up |
| 10 | window containing a complete side-by-side diff. (NB: The pop-up |
| 11 | window is run as a separate Tcl/Tk process, so you will need to |
| 12 | have Tcl/Tk installed on your machine for this to work. Visit |
| 13 | [http://www.activestate.com/activetcl] for a quick download of |
| @@ -17,13 +17,13 @@ | |
| 17 | alternative to "make clean". You can use "[/help/clean | fossil clean -f]" |
| 18 | as a slightly safer alternative if the "ignore-glob" setting is |
| 19 | not set. WARNING: make sure you did a "fossil add" for all source-files |
| 20 | you plan to commit, otherwise those files will be deleted without warning. |
| 21 | |
| 22 | 4. Use "[/help?cmd=all | fossil all changes]" to look for any uncommitted |
| 23 | edits in any of your Fossil projects. Use |
| 24 | "[/help?cmd=all | fossil all pull]" on your laptop |
| 25 | prior to going off network (for example, on a long plane ride) |
| 26 | to make sure you have all the latest content locally. Then run |
| 27 | "[/help/all|fossil all push]" when you get back online to upload |
| 28 | your changes. |
| 29 | |
| @@ -37,13 +37,13 @@ | |
| 37 | on in the Fossil repository on 2008-01-01, visit |
| 38 | [/timeline?c=2008-01-01]. |
| 39 | |
| 40 | 7. Further to the previous two hints, there are lots of query parameters |
| 41 | that you can add to timeline pages. The available query parameters |
| 42 | are tersely documented [/help?cmd=/timeline | here]. |
| 43 | |
| 44 | 8. You can run "[/help?cmd=xdiff | fossil xdiff --tk $file1 $file2]" |
| 45 | to get a Tk pop-up window with side-by-side diffs of two files, even if |
| 46 | neither of the two files is part of any Fossil repository. Note that |
| 47 | this command is "xdiff", not "diff". Change <nobr>--tk</nobr> to |
| 48 | <nobr>--by</nobr> to see the diff in your web browser. |
| 49 | |
| @@ -61,14 +61,14 @@ | |
| 61 | 10. When editing documentation to be checked in as managed files, you can |
| 62 | preview what the documentation will look like by using the special |
| 63 | "ckout" branch name in the "doc" URL while running "fossil ui". |
| 64 | See the [./embeddeddoc.wiki | embedded documentation] for details. |
| 65 | |
| 66 | 11. Use the "[/help?cmd=ui|fossil ui /]" command to bring up a menu of |
| 67 | all of your local Fossil repositories in your web browser. |
| 68 | |
| 69 | 12. If you have a bunch of Fossil repositories living on a remote machine |
| 70 | that you are able to access using ssh using a command like |
| 71 | "ssh login@remote", then you can bring up a user interface for all |
| 72 | those remote repositories using the command: |
| 73 | "[/help?cmd=ui|fossil ui login@remote:/]". This works by tunneling |
| 74 | all HTTP traffic through SSH to the remote machine. |
| 75 |
| --- www/hints.wiki | |
| +++ www/hints.wiki | |
| @@ -3,11 +3,11 @@ | |
| 3 | A collection of useful hints and tricks in no particular order: |
| 4 | |
| 5 | 1. Click on two nodes of any timeline graph in succession |
| 6 | to see a diff between the two versions. |
| 7 | |
| 8 | 2. Add the "--tk" option to "[/help/diff | fossil diff]" commands |
| 9 | to get a pop-up |
| 10 | window containing a complete side-by-side diff. (NB: The pop-up |
| 11 | window is run as a separate Tcl/Tk process, so you will need to |
| 12 | have Tcl/Tk installed on your machine for this to work. Visit |
| 13 | [http://www.activestate.com/activetcl] for a quick download of |
| @@ -17,13 +17,13 @@ | |
| 17 | alternative to "make clean". You can use "[/help/clean | fossil clean -f]" |
| 18 | as a slightly safer alternative if the "ignore-glob" setting is |
| 19 | not set. WARNING: make sure you did a "fossil add" for all source-files |
| 20 | you plan to commit, otherwise those files will be deleted without warning. |
| 21 | |
| 22 | 4. Use "[/help/all | fossil all changes]" to look for any uncommitted |
| 23 | edits in any of your Fossil projects. Use |
| 24 | "[/help/all | fossil all pull]" on your laptop |
| 25 | prior to going off network (for example, on a long plane ride) |
| 26 | to make sure you have all the latest content locally. Then run |
| 27 | "[/help/all|fossil all push]" when you get back online to upload |
| 28 | your changes. |
| 29 | |
| @@ -37,13 +37,13 @@ | |
| 37 | on in the Fossil repository on 2008-01-01, visit |
| 38 | [/timeline?c=2008-01-01]. |
| 39 | |
| 40 | 7. Further to the previous two hints, there are lots of query parameters |
| 41 | that you can add to timeline pages. The available query parameters |
| 42 | are tersely documented [/help/www/timeline | here]. |
| 43 | |
| 44 | 8. You can run "[/help/xdiff | fossil xdiff --tk $file1 $file2]" |
| 45 | to get a Tk pop-up window with side-by-side diffs of two files, even if |
| 46 | neither of the two files is part of any Fossil repository. Note that |
| 47 | this command is "xdiff", not "diff". Change <nobr>--tk</nobr> to |
| 48 | <nobr>--by</nobr> to see the diff in your web browser. |
| 49 | |
| @@ -61,14 +61,14 @@ | |
| 61 | 10. When editing documentation to be checked in as managed files, you can |
| 62 | preview what the documentation will look like by using the special |
| 63 | "ckout" branch name in the "doc" URL while running "fossil ui". |
| 64 | See the [./embeddeddoc.wiki | embedded documentation] for details. |
| 65 | |
| 66 | 11. Use the "[/help/ui|fossil ui /]" command to bring up a menu of |
| 67 | all of your local Fossil repositories in your web browser. |
| 68 | |
| 69 | 12. If you have a bunch of Fossil repositories living on a remote machine |
| 70 | that you are able to access using ssh using a command like |
| 71 | "ssh login@remote", then you can bring up a user interface for all |
| 72 | those remote repositories using the command: |
| 73 | "[/help/ui|fossil ui login@remote:/]". This works by tunneling |
| 74 | all HTTP traffic through SSH to the remote machine. |
| 75 |
+4
-4
| --- www/index.wiki | ||
| +++ www/index.wiki | ||
| @@ -84,16 +84,16 @@ | ||
| 84 | 84 | the repository are consistent prior to each commit. |
| 85 | 85 | |
| 86 | 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | 87 | |
| 88 | 88 | <hr> |
| 89 | -<h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3> | |
| 89 | +<h3>Latest Release: 2.27 ([/timeline?c=version-2.27|2025-09-30])</h3> | |
| 90 | 90 | |
| 91 | 91 | * [/uv/download.html|Download] |
| 92 | - * [./changes.wiki#v2_26|Change Summary] | |
| 93 | - * [/timeline?p=version-2.26&d=version-2.25&y=ci|Check-ins in version 2.26] | |
| 94 | - * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release] | |
| 92 | + * [./changes.wiki#v2_27|Change Summary] | |
| 93 | + * [/timeline?p=version-2.27&d=version-2.26&y=ci|Check-ins in version 2.27] | |
| 94 | + * [/timeline?df=version-2.27&y=ci|Check-ins derived from the 2.27 release] | |
| 95 | 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | 96 | |
| 97 | 97 | <hr> |
| 98 | 98 | <h3>Quick Start</h3> |
| 99 | 99 | |
| 100 | 100 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -84,16 +84,16 @@ | |
| 84 | the repository are consistent prior to each commit. |
| 85 | |
| 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | |
| 88 | <hr> |
| 89 | <h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_26|Change Summary] |
| 93 | * [/timeline?p=version-2.26&d=version-2.25&y=ci|Check-ins in version 2.26] |
| 94 | * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -84,16 +84,16 @@ | |
| 84 | the repository are consistent prior to each commit. |
| 85 | |
| 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | |
| 88 | <hr> |
| 89 | <h3>Latest Release: 2.27 ([/timeline?c=version-2.27|2025-09-30])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_27|Change Summary] |
| 93 | * [/timeline?p=version-2.27&d=version-2.26&y=ci|Check-ins in version 2.27] |
| 94 | * [/timeline?df=version-2.27&y=ci|Check-ins derived from the 2.27 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
+1
-1
| --- www/inout.wiki | ||
| +++ www/inout.wiki | ||
| @@ -27,11 +27,11 @@ | ||
| 27 | 27 | |
| 28 | 28 | <a id="fx_git"></a> |
| 29 | 29 | Note that in new imports, Fossil defaults to using the email component of the |
| 30 | 30 | Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is |
| 31 | 31 | passed) to attribute check-ins in the imported repository. Alternatively, the |
| 32 | -[/help?cmd=import | <code>--attribute</code>] option can be passed to have all | |
| 32 | +[/help/import | <code>--attribute</code>] option can be passed to have all | |
| 33 | 33 | commits by a given committer attributed to a desired username. This will create |
| 34 | 34 | and populate the new <code>fx_git</code> table in the repository database to |
| 35 | 35 | maintain a record of correspondent usernames and email addresses that can be |
| 36 | 36 | used in subsequent exports or incremental imports. |
| 37 | 37 | |
| 38 | 38 |
| --- www/inout.wiki | |
| +++ www/inout.wiki | |
| @@ -27,11 +27,11 @@ | |
| 27 | |
| 28 | <a id="fx_git"></a> |
| 29 | Note that in new imports, Fossil defaults to using the email component of the |
| 30 | Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is |
| 31 | passed) to attribute check-ins in the imported repository. Alternatively, the |
| 32 | [/help?cmd=import | <code>--attribute</code>] option can be passed to have all |
| 33 | commits by a given committer attributed to a desired username. This will create |
| 34 | and populate the new <code>fx_git</code> table in the repository database to |
| 35 | maintain a record of correspondent usernames and email addresses that can be |
| 36 | used in subsequent exports or incremental imports. |
| 37 | |
| 38 |
| --- www/inout.wiki | |
| +++ www/inout.wiki | |
| @@ -27,11 +27,11 @@ | |
| 27 | |
| 28 | <a id="fx_git"></a> |
| 29 | Note that in new imports, Fossil defaults to using the email component of the |
| 30 | Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is |
| 31 | passed) to attribute check-ins in the imported repository. Alternatively, the |
| 32 | [/help/import | <code>--attribute</code>] option can be passed to have all |
| 33 | commits by a given committer attributed to a desired username. This will create |
| 34 | and populate the new <code>fx_git</code> table in the repository database to |
| 35 | maintain a record of correspondent usernames and email addresses that can be |
| 36 | used in subsequent exports or incremental imports. |
| 37 | |
| 38 |
+2
-2
| --- www/interwiki.md | ||
| +++ www/interwiki.md | ||
| @@ -64,11 +64,11 @@ | ||
| 64 | 64 | |
| 65 | 65 | The intermap defines a mapping from interwiki Tags to full URLs. The |
| 66 | 66 | Intermap can be viewed and managed using the [fossil interwiki][iwiki] |
| 67 | 67 | command or the [/intermap][imap] webpage. |
| 68 | 68 | |
| 69 | -[iwiki]: /help?cmd=interwiki | |
| 69 | +[iwiki]: /help/interwiki | |
| 70 | 70 | [imap]: /intermap |
| 71 | 71 | |
| 72 | 72 | The current intermap for a server is seen on the [/intermap][imap] page |
| 73 | 73 | (which is read-only for non-Setup users) and at the bottom of the built-in |
| 74 | 74 | [Fossil Wiki rules](/wiki_rules) and [Markdown rules](/md_rules) |
| @@ -100,11 +100,11 @@ | ||
| 100 | 100 | "_source→target_", but it also tracks "_target→source_". |
| 101 | 101 | But backtracking does not work for interwiki links, since the Fossil |
| 102 | 102 | running on the target has no way of scanning the source text and |
| 103 | 103 | hence has no way of knowing that it is a target of a link from the source. |
| 104 | 104 | |
| 105 | -[fcfg]: /help?cmd=config | |
| 105 | +[fcfg]: /help/config | |
| 106 | 106 | |
| 107 | 107 | ## Intermap Storage Details |
| 108 | 108 | |
| 109 | 109 | The intermap is stored in the CONFIG table of the repository database, |
| 110 | 110 | in entries with names of the form "<tt>interwiki:</tt><i>Tag</i>". The |
| 111 | 111 |
| --- www/interwiki.md | |
| +++ www/interwiki.md | |
| @@ -64,11 +64,11 @@ | |
| 64 | |
| 65 | The intermap defines a mapping from interwiki Tags to full URLs. The |
| 66 | Intermap can be viewed and managed using the [fossil interwiki][iwiki] |
| 67 | command or the [/intermap][imap] webpage. |
| 68 | |
| 69 | [iwiki]: /help?cmd=interwiki |
| 70 | [imap]: /intermap |
| 71 | |
| 72 | The current intermap for a server is seen on the [/intermap][imap] page |
| 73 | (which is read-only for non-Setup users) and at the bottom of the built-in |
| 74 | [Fossil Wiki rules](/wiki_rules) and [Markdown rules](/md_rules) |
| @@ -100,11 +100,11 @@ | |
| 100 | "_source→target_", but it also tracks "_target→source_". |
| 101 | But backtracking does not work for interwiki links, since the Fossil |
| 102 | running on the target has no way of scanning the source text and |
| 103 | hence has no way of knowing that it is a target of a link from the source. |
| 104 | |
| 105 | [fcfg]: /help?cmd=config |
| 106 | |
| 107 | ## Intermap Storage Details |
| 108 | |
| 109 | The intermap is stored in the CONFIG table of the repository database, |
| 110 | in entries with names of the form "<tt>interwiki:</tt><i>Tag</i>". The |
| 111 |
| --- www/interwiki.md | |
| +++ www/interwiki.md | |
| @@ -64,11 +64,11 @@ | |
| 64 | |
| 65 | The intermap defines a mapping from interwiki Tags to full URLs. The |
| 66 | Intermap can be viewed and managed using the [fossil interwiki][iwiki] |
| 67 | command or the [/intermap][imap] webpage. |
| 68 | |
| 69 | [iwiki]: /help/interwiki |
| 70 | [imap]: /intermap |
| 71 | |
| 72 | The current intermap for a server is seen on the [/intermap][imap] page |
| 73 | (which is read-only for non-Setup users) and at the bottom of the built-in |
| 74 | [Fossil Wiki rules](/wiki_rules) and [Markdown rules](/md_rules) |
| @@ -100,11 +100,11 @@ | |
| 100 | "_source→target_", but it also tracks "_target→source_". |
| 101 | But backtracking does not work for interwiki links, since the Fossil |
| 102 | running on the target has no way of scanning the source text and |
| 103 | hence has no way of knowing that it is a target of a link from the source. |
| 104 | |
| 105 | [fcfg]: /help/config |
| 106 | |
| 107 | ## Intermap Storage Details |
| 108 | |
| 109 | The intermap is stored in the CONFIG table of the repository database, |
| 110 | in entries with names of the form "<tt>interwiki:</tt><i>Tag</i>". The |
| 111 |
+4
-4
| --- www/javascript.md | ||
| +++ www/javascript.md | ||
| @@ -263,17 +263,17 @@ | ||
| 263 | 263 | [ciu]: https://caniuse.com/ |
| 264 | 264 | [cskin]: ./customskin.md |
| 265 | 265 | [dcsp]: ./defcsp.md |
| 266 | 266 | [es2015]: https://ecma-international.org/ecma-262/6.0/ |
| 267 | 267 | [es6dep]: https://caniuse.com/#feat=es6 |
| 268 | -[fcgi]: /help?cmd=cgi | |
| 268 | +[fcgi]: /help/cgi | |
| 269 | 269 | [ffor]: https://fossil-scm.org/forum/ |
| 270 | 270 | [flic]: /doc/trunk/COPYRIGHT-BSD2.txt |
| 271 | 271 | [fshome]: /doc/trunk/www/server/ |
| 272 | 272 | [fslpl]: /doc/trunk/www/fossil-v-git.wiki#portable |
| 273 | 273 | [fsrc]: https://fossil-scm.org/home/file/src |
| 274 | -[fsrv]: /help?cmd=server | |
| 274 | +[fsrv]: /help/server | |
| 275 | 275 | [hljs]: https://fossil-scm.org/forum/forumpost/9150bc22ca |
| 276 | 276 | [ie11x]: https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666 |
| 277 | 277 | [ns]: https://noscript.net/ |
| 278 | 278 | [pjs]: https://fossil-scm.org/forum/forumpost/1198651c6d |
| 279 | 279 | [s1]: https://blockmetry.com/blog/javascript-disabled |
| @@ -390,11 +390,11 @@ | ||
| 390 | 390 | ``` |
| 391 | 391 | |
| 392 | 392 | Extending this concept to other text editors is an exercise left to the |
| 393 | 393 | reader. |
| 394 | 394 | |
| 395 | -[fwc]: /help?cmd=wiki | |
| 395 | +[fwc]: /help/wiki | |
| 396 | 396 | [fwt]: ./wikitheory.wiki |
| 397 | 397 | |
| 398 | 398 | |
| 399 | 399 | ### <a id="fedit"></a>The File Editor |
| 400 | 400 | |
| @@ -430,11 +430,11 @@ | ||
| 430 | 430 | When viewing source files, Fossil offers to show line numbers in some |
| 431 | 431 | cases. ([Example][mainc].) Toggling them on and off is currently handled |
| 432 | 432 | in JavaScript, rather than forcing a page-reload via a button click. |
| 433 | 433 | |
| 434 | 434 | _Workaround:_ Manually edit the URL to give the “`ln`” query parameter |
| 435 | -per [the `/file` docs](/help?cmd=/file). | |
| 435 | +per [the `/file` docs](/help/www/file). | |
| 436 | 436 | |
| 437 | 437 | _Potential Better Workaround:_ Someone sufficiently interested could |
| 438 | 438 | [provide a patch][cg] to add a `<noscript>` wrapped HTML button that |
| 439 | 439 | would reload the page with this parameter included/excluded to implement |
| 440 | 440 | the toggle via a server round-trip. |
| 441 | 441 |
| --- www/javascript.md | |
| +++ www/javascript.md | |
| @@ -263,17 +263,17 @@ | |
| 263 | [ciu]: https://caniuse.com/ |
| 264 | [cskin]: ./customskin.md |
| 265 | [dcsp]: ./defcsp.md |
| 266 | [es2015]: https://ecma-international.org/ecma-262/6.0/ |
| 267 | [es6dep]: https://caniuse.com/#feat=es6 |
| 268 | [fcgi]: /help?cmd=cgi |
| 269 | [ffor]: https://fossil-scm.org/forum/ |
| 270 | [flic]: /doc/trunk/COPYRIGHT-BSD2.txt |
| 271 | [fshome]: /doc/trunk/www/server/ |
| 272 | [fslpl]: /doc/trunk/www/fossil-v-git.wiki#portable |
| 273 | [fsrc]: https://fossil-scm.org/home/file/src |
| 274 | [fsrv]: /help?cmd=server |
| 275 | [hljs]: https://fossil-scm.org/forum/forumpost/9150bc22ca |
| 276 | [ie11x]: https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666 |
| 277 | [ns]: https://noscript.net/ |
| 278 | [pjs]: https://fossil-scm.org/forum/forumpost/1198651c6d |
| 279 | [s1]: https://blockmetry.com/blog/javascript-disabled |
| @@ -390,11 +390,11 @@ | |
| 390 | ``` |
| 391 | |
| 392 | Extending this concept to other text editors is an exercise left to the |
| 393 | reader. |
| 394 | |
| 395 | [fwc]: /help?cmd=wiki |
| 396 | [fwt]: ./wikitheory.wiki |
| 397 | |
| 398 | |
| 399 | ### <a id="fedit"></a>The File Editor |
| 400 | |
| @@ -430,11 +430,11 @@ | |
| 430 | When viewing source files, Fossil offers to show line numbers in some |
| 431 | cases. ([Example][mainc].) Toggling them on and off is currently handled |
| 432 | in JavaScript, rather than forcing a page-reload via a button click. |
| 433 | |
| 434 | _Workaround:_ Manually edit the URL to give the “`ln`” query parameter |
| 435 | per [the `/file` docs](/help?cmd=/file). |
| 436 | |
| 437 | _Potential Better Workaround:_ Someone sufficiently interested could |
| 438 | [provide a patch][cg] to add a `<noscript>` wrapped HTML button that |
| 439 | would reload the page with this parameter included/excluded to implement |
| 440 | the toggle via a server round-trip. |
| 441 |
| --- www/javascript.md | |
| +++ www/javascript.md | |
| @@ -263,17 +263,17 @@ | |
| 263 | [ciu]: https://caniuse.com/ |
| 264 | [cskin]: ./customskin.md |
| 265 | [dcsp]: ./defcsp.md |
| 266 | [es2015]: https://ecma-international.org/ecma-262/6.0/ |
| 267 | [es6dep]: https://caniuse.com/#feat=es6 |
| 268 | [fcgi]: /help/cgi |
| 269 | [ffor]: https://fossil-scm.org/forum/ |
| 270 | [flic]: /doc/trunk/COPYRIGHT-BSD2.txt |
| 271 | [fshome]: /doc/trunk/www/server/ |
| 272 | [fslpl]: /doc/trunk/www/fossil-v-git.wiki#portable |
| 273 | [fsrc]: https://fossil-scm.org/home/file/src |
| 274 | [fsrv]: /help/server |
| 275 | [hljs]: https://fossil-scm.org/forum/forumpost/9150bc22ca |
| 276 | [ie11x]: https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666 |
| 277 | [ns]: https://noscript.net/ |
| 278 | [pjs]: https://fossil-scm.org/forum/forumpost/1198651c6d |
| 279 | [s1]: https://blockmetry.com/blog/javascript-disabled |
| @@ -390,11 +390,11 @@ | |
| 390 | ``` |
| 391 | |
| 392 | Extending this concept to other text editors is an exercise left to the |
| 393 | reader. |
| 394 | |
| 395 | [fwc]: /help/wiki |
| 396 | [fwt]: ./wikitheory.wiki |
| 397 | |
| 398 | |
| 399 | ### <a id="fedit"></a>The File Editor |
| 400 | |
| @@ -430,11 +430,11 @@ | |
| 430 | When viewing source files, Fossil offers to show line numbers in some |
| 431 | cases. ([Example][mainc].) Toggling them on and off is currently handled |
| 432 | in JavaScript, rather than forcing a page-reload via a button click. |
| 433 | |
| 434 | _Workaround:_ Manually edit the URL to give the “`ln`” query parameter |
| 435 | per [the `/file` docs](/help/www/file). |
| 436 | |
| 437 | _Potential Better Workaround:_ Someone sufficiently interested could |
| 438 | [provide a patch][cg] to add a `<noscript>` wrapped HTML button that |
| 439 | would reload the page with this parameter included/excluded to implement |
| 440 | the toggle via a server round-trip. |
| 441 |
+1
-1
| --- www/json-api/intro.md | ||
| +++ www/json-api/intro.md | ||
| @@ -146,11 +146,11 @@ | ||
| 146 | 146 | up the “payload” area of each HTTP request, so there is no |
| 147 | 147 | reasonable way to include binary data in the JSON message without |
| 148 | 148 | some sort of codec like Base64, for which there is no provision in |
| 149 | 149 | the current JSON API. You will therefore find no JSON API for |
| 150 | 150 | committing changes to a file in the repository, for example. Other |
| 151 | - Fossil APIs such as [`/raw`](/help?cmd=/raw) or | |
| 151 | + Fossil APIs such as [`/raw`](/help/www/raw) or | |
| 152 | 152 | [`/fileedit`](../fileedit-page.md) may serve you better. |
| 153 | 153 | - **64-bit integers:** The JSON standard does not specify integer precision, |
| 154 | 154 | because it targets many different platforms, and not all of |
| 155 | 155 | them can support more than 32 bits. JavaScript (from which JSON |
| 156 | 156 | derives) supports 53 bits of integer precision, which may affect how |
| 157 | 157 |
| --- www/json-api/intro.md | |
| +++ www/json-api/intro.md | |
| @@ -146,11 +146,11 @@ | |
| 146 | up the “payload” area of each HTTP request, so there is no |
| 147 | reasonable way to include binary data in the JSON message without |
| 148 | some sort of codec like Base64, for which there is no provision in |
| 149 | the current JSON API. You will therefore find no JSON API for |
| 150 | committing changes to a file in the repository, for example. Other |
| 151 | Fossil APIs such as [`/raw`](/help?cmd=/raw) or |
| 152 | [`/fileedit`](../fileedit-page.md) may serve you better. |
| 153 | - **64-bit integers:** The JSON standard does not specify integer precision, |
| 154 | because it targets many different platforms, and not all of |
| 155 | them can support more than 32 bits. JavaScript (from which JSON |
| 156 | derives) supports 53 bits of integer precision, which may affect how |
| 157 |
| --- www/json-api/intro.md | |
| +++ www/json-api/intro.md | |
| @@ -146,11 +146,11 @@ | |
| 146 | up the “payload” area of each HTTP request, so there is no |
| 147 | reasonable way to include binary data in the JSON message without |
| 148 | some sort of codec like Base64, for which there is no provision in |
| 149 | the current JSON API. You will therefore find no JSON API for |
| 150 | committing changes to a file in the repository, for example. Other |
| 151 | Fossil APIs such as [`/raw`](/help/www/raw) or |
| 152 | [`/fileedit`](../fileedit-page.md) may serve you better. |
| 153 | - **64-bit integers:** The JSON standard does not specify integer precision, |
| 154 | because it targets many different platforms, and not all of |
| 155 | them can support more than 32 bits. JavaScript (from which JSON |
| 156 | derives) supports 53 bits of integer precision, which may affect how |
| 157 |
+1
-1
| --- www/loadmgmt.md | ||
| +++ www/loadmgmt.md | ||
| @@ -88,9 +88,9 @@ | ||
| 88 | 88 | system that does not support `getloadavg()` and so the load-average |
| 89 | 89 | limiter will not function. |
| 90 | 90 | |
| 91 | 91 | |
| 92 | 92 | [503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4 |
| 93 | -[hte]: /help?cmd=/test-env | |
| 93 | +[hte]: /help/www/test-env | |
| 94 | 94 | [gla]: https://linux.die.net/man/3/getloadavg |
| 95 | 95 | [lin]: http://www.linode.com |
| 96 | 96 | [sh]: ./selfhost.wiki |
| 97 | 97 |
| --- www/loadmgmt.md | |
| +++ www/loadmgmt.md | |
| @@ -88,9 +88,9 @@ | |
| 88 | system that does not support `getloadavg()` and so the load-average |
| 89 | limiter will not function. |
| 90 | |
| 91 | |
| 92 | [503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4 |
| 93 | [hte]: /help?cmd=/test-env |
| 94 | [gla]: https://linux.die.net/man/3/getloadavg |
| 95 | [lin]: http://www.linode.com |
| 96 | [sh]: ./selfhost.wiki |
| 97 |
| --- www/loadmgmt.md | |
| +++ www/loadmgmt.md | |
| @@ -88,9 +88,9 @@ | |
| 88 | system that does not support `getloadavg()` and so the load-average |
| 89 | limiter will not function. |
| 90 | |
| 91 | |
| 92 | [503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4 |
| 93 | [hte]: /help/www/test-env |
| 94 | [gla]: https://linux.die.net/man/3/getloadavg |
| 95 | [lin]: http://www.linode.com |
| 96 | [sh]: ./selfhost.wiki |
| 97 |
+4
-4
| --- www/makefile.wiki | ||
| +++ www/makefile.wiki | ||
| @@ -41,11 +41,11 @@ | ||
| 41 | 41 | All three SQLite source files are byte-for-byte copies of files by |
| 42 | 42 | the same name in the |
| 43 | 43 | standard [http://www.sqlite.org/amalgamation.html | amalgamation]. |
| 44 | 44 | The sqlite3.c file implements the database engine. The shell.c file |
| 45 | 45 | implements the command-line shell, which is accessed in fossil using |
| 46 | -the [/help?cmd=sqlite3 | fossil sql] command. | |
| 46 | +the [/help/sqlite3 | fossil sql] command. | |
| 47 | 47 | |
| 48 | 48 | The shell.c command-line shell uses the [https://github.com/antirez/linenoise | |
| 49 | 49 | linenoise] library to implement line editing. linenoise comprises two |
| 50 | 50 | source files which were copied from the upstream repository with only |
| 51 | 51 | very minor portability edits: |
| @@ -72,11 +72,11 @@ | ||
| 72 | 72 | resource files using a small program called: |
| 73 | 73 | |
| 74 | 74 | 12 [/file/tools/mkbuiltin.c | mkbuiltin.c] |
| 75 | 75 | |
| 76 | 76 | Examples of built-in resources include the [/file/src/diff.tcl | diff.tcl] |
| 77 | -script used to implement the --tk option to [/help?cmd=diff| fossil diff], | |
| 77 | +script used to implement the --tk option to [/help/diff| fossil diff], | |
| 78 | 78 | the [/file/src/markdown.md | markdown documentation], and the various |
| 79 | 79 | CSS scripts, headers, and footers used to implement built-in skins. New |
| 80 | 80 | resources files are added to the "extra_files" variable in |
| 81 | 81 | [/file/tools/makemake.tcl | makemake.tcl]. |
| 82 | 82 | |
| @@ -265,15 +265,15 @@ | ||
| 265 | 265 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 266 | 266 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 267 | 267 | |
| 268 | 268 | The first three symbol definitions above are required; the others are merely |
| 269 | 269 | recommended. Extension loading is omitted as a security measure. The dbstat |
| 270 | -virtual table is needed for the [/help?cmd=/repo-tabsize|/repo-tabsize] page. | |
| 270 | +virtual table is needed for the [/help/www/repo-tabsize|/repo-tabsize] page. | |
| 271 | 271 | FTS4 is needed for the search feature. Fossil is single-threaded so mutexing |
| 272 | 272 | is disabled in SQLite as a performance enhancement. The |
| 273 | 273 | SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries |
| 274 | -in the "[/help?cmd=sqlite3|fossil sql]" command much more readable. | |
| 274 | +in the "[/help/sqlite3|fossil sql]" command much more readable. | |
| 275 | 275 | |
| 276 | 276 | When compiling the shell.c source file, these macros are required: |
| 277 | 277 | |
| 278 | 278 | * -Dmain=sqlite3_main |
| 279 | 279 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 280 | 280 |
| --- www/makefile.wiki | |
| +++ www/makefile.wiki | |
| @@ -41,11 +41,11 @@ | |
| 41 | All three SQLite source files are byte-for-byte copies of files by |
| 42 | the same name in the |
| 43 | standard [http://www.sqlite.org/amalgamation.html | amalgamation]. |
| 44 | The sqlite3.c file implements the database engine. The shell.c file |
| 45 | implements the command-line shell, which is accessed in fossil using |
| 46 | the [/help?cmd=sqlite3 | fossil sql] command. |
| 47 | |
| 48 | The shell.c command-line shell uses the [https://github.com/antirez/linenoise | |
| 49 | linenoise] library to implement line editing. linenoise comprises two |
| 50 | source files which were copied from the upstream repository with only |
| 51 | very minor portability edits: |
| @@ -72,11 +72,11 @@ | |
| 72 | resource files using a small program called: |
| 73 | |
| 74 | 12 [/file/tools/mkbuiltin.c | mkbuiltin.c] |
| 75 | |
| 76 | Examples of built-in resources include the [/file/src/diff.tcl | diff.tcl] |
| 77 | script used to implement the --tk option to [/help?cmd=diff| fossil diff], |
| 78 | the [/file/src/markdown.md | markdown documentation], and the various |
| 79 | CSS scripts, headers, and footers used to implement built-in skins. New |
| 80 | resources files are added to the "extra_files" variable in |
| 81 | [/file/tools/makemake.tcl | makemake.tcl]. |
| 82 | |
| @@ -265,15 +265,15 @@ | |
| 265 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 266 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 267 | |
| 268 | The first three symbol definitions above are required; the others are merely |
| 269 | recommended. Extension loading is omitted as a security measure. The dbstat |
| 270 | virtual table is needed for the [/help?cmd=/repo-tabsize|/repo-tabsize] page. |
| 271 | FTS4 is needed for the search feature. Fossil is single-threaded so mutexing |
| 272 | is disabled in SQLite as a performance enhancement. The |
| 273 | SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries |
| 274 | in the "[/help?cmd=sqlite3|fossil sql]" command much more readable. |
| 275 | |
| 276 | When compiling the shell.c source file, these macros are required: |
| 277 | |
| 278 | * -Dmain=sqlite3_main |
| 279 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 280 |
| --- www/makefile.wiki | |
| +++ www/makefile.wiki | |
| @@ -41,11 +41,11 @@ | |
| 41 | All three SQLite source files are byte-for-byte copies of files by |
| 42 | the same name in the |
| 43 | standard [http://www.sqlite.org/amalgamation.html | amalgamation]. |
| 44 | The sqlite3.c file implements the database engine. The shell.c file |
| 45 | implements the command-line shell, which is accessed in fossil using |
| 46 | the [/help/sqlite3 | fossil sql] command. |
| 47 | |
| 48 | The shell.c command-line shell uses the [https://github.com/antirez/linenoise | |
| 49 | linenoise] library to implement line editing. linenoise comprises two |
| 50 | source files which were copied from the upstream repository with only |
| 51 | very minor portability edits: |
| @@ -72,11 +72,11 @@ | |
| 72 | resource files using a small program called: |
| 73 | |
| 74 | 12 [/file/tools/mkbuiltin.c | mkbuiltin.c] |
| 75 | |
| 76 | Examples of built-in resources include the [/file/src/diff.tcl | diff.tcl] |
| 77 | script used to implement the --tk option to [/help/diff| fossil diff], |
| 78 | the [/file/src/markdown.md | markdown documentation], and the various |
| 79 | CSS scripts, headers, and footers used to implement built-in skins. New |
| 80 | resources files are added to the "extra_files" variable in |
| 81 | [/file/tools/makemake.tcl | makemake.tcl]. |
| 82 | |
| @@ -265,15 +265,15 @@ | |
| 265 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 266 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 267 | |
| 268 | The first three symbol definitions above are required; the others are merely |
| 269 | recommended. Extension loading is omitted as a security measure. The dbstat |
| 270 | virtual table is needed for the [/help/www/repo-tabsize|/repo-tabsize] page. |
| 271 | FTS4 is needed for the search feature. Fossil is single-threaded so mutexing |
| 272 | is disabled in SQLite as a performance enhancement. The |
| 273 | SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries |
| 274 | in the "[/help/sqlite3|fossil sql]" command much more readable. |
| 275 | |
| 276 | When compiling the shell.c source file, these macros are required: |
| 277 | |
| 278 | * -Dmain=sqlite3_main |
| 279 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 280 |
+1
-1
| --- www/mdtest/test1.md | ||
| +++ www/mdtest/test1.md | ||
| @@ -31,11 +31,11 @@ | ||
| 31 | 31 | works without the $ROOT prefix. (Though: the $ROOT prefix is required |
| 32 | 32 | for HTML documents.) |
| 33 | 33 | |
| 34 | 34 | * Timeline: [](/timeline) |
| 35 | 35 | |
| 36 | - * Help: [](/help?cmd=help) | |
| 36 | + * Help: [](/help/help) | |
| 37 | 37 | |
| 38 | 38 | * Site-map: [](/sitemap) |
| 39 | 39 | |
| 40 | 40 | ## The Magic $CURRENT Document Version Translation |
| 41 | 41 | |
| 42 | 42 |
| --- www/mdtest/test1.md | |
| +++ www/mdtest/test1.md | |
| @@ -31,11 +31,11 @@ | |
| 31 | works without the $ROOT prefix. (Though: the $ROOT prefix is required |
| 32 | for HTML documents.) |
| 33 | |
| 34 | * Timeline: [](/timeline) |
| 35 | |
| 36 | * Help: [](/help?cmd=help) |
| 37 | |
| 38 | * Site-map: [](/sitemap) |
| 39 | |
| 40 | ## The Magic $CURRENT Document Version Translation |
| 41 | |
| 42 |
| --- www/mdtest/test1.md | |
| +++ www/mdtest/test1.md | |
| @@ -31,11 +31,11 @@ | |
| 31 | works without the $ROOT prefix. (Though: the $ROOT prefix is required |
| 32 | for HTML documents.) |
| 33 | |
| 34 | * Timeline: [](/timeline) |
| 35 | |
| 36 | * Help: [](/help/help) |
| 37 | |
| 38 | * Site-map: [](/sitemap) |
| 39 | |
| 40 | ## The Magic $CURRENT Document Version Translation |
| 41 | |
| 42 |
+1
-1
| --- www/mirrorlimitations.md | ||
| +++ www/mirrorlimitations.md | ||
| @@ -1,8 +1,8 @@ | ||
| 1 | 1 | # Limitations On Git Mirrors |
| 2 | 2 | |
| 3 | -The "<tt>[fossil git export](/help?cmd=git)</tt>" command can be used to | |
| 3 | +The "<tt>[fossil git export](/help/git)</tt>" command can be used to | |
| 4 | 4 | mirror a Fossil repository to Git. |
| 5 | 5 | ([Setup instructions](./mirrortogithub.md) and an |
| 6 | 6 | [example](https://github.com/drhsqlite/fossil-mirror).) |
| 7 | 7 | But the export to Git is not perfect. Some information is lost during |
| 8 | 8 | export due to limitations in Git. This page describes what content of |
| 9 | 9 |
| --- www/mirrorlimitations.md | |
| +++ www/mirrorlimitations.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # Limitations On Git Mirrors |
| 2 | |
| 3 | The "<tt>[fossil git export](/help?cmd=git)</tt>" command can be used to |
| 4 | mirror a Fossil repository to Git. |
| 5 | ([Setup instructions](./mirrortogithub.md) and an |
| 6 | [example](https://github.com/drhsqlite/fossil-mirror).) |
| 7 | But the export to Git is not perfect. Some information is lost during |
| 8 | export due to limitations in Git. This page describes what content of |
| 9 |
| --- www/mirrorlimitations.md | |
| +++ www/mirrorlimitations.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # Limitations On Git Mirrors |
| 2 | |
| 3 | The "<tt>[fossil git export](/help/git)</tt>" command can be used to |
| 4 | mirror a Fossil repository to Git. |
| 5 | ([Setup instructions](./mirrortogithub.md) and an |
| 6 | [example](https://github.com/drhsqlite/fossil-mirror).) |
| 7 | But the export to Git is not perfect. Some information is lost during |
| 8 | export due to limitations in Git. This page describes what content of |
| 9 |
+3
-3
| --- www/mirrortogithub.md | ||
| +++ www/mirrortogithub.md | ||
| @@ -130,14 +130,14 @@ | ||
| 130 | 130 | previously imported from Git using the [`--attribute`][attr] option, the |
| 131 | 131 | [`fx_git`][fxgit] table will be queried for correspondent email addresses. |
| 132 | 132 | Only if neither of these methods produce a user specified email will the |
| 133 | 133 | abovementioned generic address be used. |
| 134 | 134 | |
| 135 | -[attr]: /help?cmd=import | |
| 135 | +[attr]: /help/import | |
| 136 | 136 | [fxgit]: ./inout.wiki#fx_git |
| 137 | -[ui]: /help?cmd=ui | |
| 138 | -[usercmd]: /help?cmd=user | |
| 137 | +[ui]: /help/ui | |
| 138 | +[usercmd]: /help/user | |
| 139 | 139 | |
| 140 | 140 | |
| 141 | 141 | ## <a id='ex1'></a>Example GitHub Mirrors |
| 142 | 142 | |
| 143 | 143 | As of this writing (2019-03-16) Fossil’s own repository is mirrored |
| 144 | 144 |
| --- www/mirrortogithub.md | |
| +++ www/mirrortogithub.md | |
| @@ -130,14 +130,14 @@ | |
| 130 | previously imported from Git using the [`--attribute`][attr] option, the |
| 131 | [`fx_git`][fxgit] table will be queried for correspondent email addresses. |
| 132 | Only if neither of these methods produce a user specified email will the |
| 133 | abovementioned generic address be used. |
| 134 | |
| 135 | [attr]: /help?cmd=import |
| 136 | [fxgit]: ./inout.wiki#fx_git |
| 137 | [ui]: /help?cmd=ui |
| 138 | [usercmd]: /help?cmd=user |
| 139 | |
| 140 | |
| 141 | ## <a id='ex1'></a>Example GitHub Mirrors |
| 142 | |
| 143 | As of this writing (2019-03-16) Fossil’s own repository is mirrored |
| 144 |
| --- www/mirrortogithub.md | |
| +++ www/mirrortogithub.md | |
| @@ -130,14 +130,14 @@ | |
| 130 | previously imported from Git using the [`--attribute`][attr] option, the |
| 131 | [`fx_git`][fxgit] table will be queried for correspondent email addresses. |
| 132 | Only if neither of these methods produce a user specified email will the |
| 133 | abovementioned generic address be used. |
| 134 | |
| 135 | [attr]: /help/import |
| 136 | [fxgit]: ./inout.wiki#fx_git |
| 137 | [ui]: /help/ui |
| 138 | [usercmd]: /help/user |
| 139 | |
| 140 | |
| 141 | ## <a id='ex1'></a>Example GitHub Mirrors |
| 142 | |
| 143 | As of this writing (2019-03-16) Fossil’s own repository is mirrored |
| 144 |
+1
-1
| --- www/password.wiki | ||
| +++ www/password.wiki | ||
| @@ -64,11 +64,11 @@ | ||
| 64 | 64 | |
| 65 | 65 | <h2>Web Interface Authentication</h2> |
| 66 | 66 | |
| 67 | 67 | When a user logs into Fossil using the web interface, the login name |
| 68 | 68 | and password are sent in the clear to the server. For most modern fossil |
| 69 | -server setups with [/help?cmd=redirect-to-https|redirect-to-https] enabled, | |
| 69 | +server setups with [/help/redirect-to-https|redirect-to-https] enabled, | |
| 70 | 70 | this will be protected by the |
| 71 | 71 | SSL connection over HTTPS so it cannot be easily viewed. The server then |
| 72 | 72 | hashes the password and compares it against the value stored in USER.PW. |
| 73 | 73 | If they match, the server sets a cookie on the client to record the |
| 74 | 74 | login. This cookie contains a large amount of high-quality randomness |
| 75 | 75 |
| --- www/password.wiki | |
| +++ www/password.wiki | |
| @@ -64,11 +64,11 @@ | |
| 64 | |
| 65 | <h2>Web Interface Authentication</h2> |
| 66 | |
| 67 | When a user logs into Fossil using the web interface, the login name |
| 68 | and password are sent in the clear to the server. For most modern fossil |
| 69 | server setups with [/help?cmd=redirect-to-https|redirect-to-https] enabled, |
| 70 | this will be protected by the |
| 71 | SSL connection over HTTPS so it cannot be easily viewed. The server then |
| 72 | hashes the password and compares it against the value stored in USER.PW. |
| 73 | If they match, the server sets a cookie on the client to record the |
| 74 | login. This cookie contains a large amount of high-quality randomness |
| 75 |
| --- www/password.wiki | |
| +++ www/password.wiki | |
| @@ -64,11 +64,11 @@ | |
| 64 | |
| 65 | <h2>Web Interface Authentication</h2> |
| 66 | |
| 67 | When a user logs into Fossil using the web interface, the login name |
| 68 | and password are sent in the clear to the server. For most modern fossil |
| 69 | server setups with [/help/redirect-to-https|redirect-to-https] enabled, |
| 70 | this will be protected by the |
| 71 | SSL connection over HTTPS so it cannot be easily viewed. The server then |
| 72 | hashes the password and compares it against the value stored in USER.PW. |
| 73 | If they match, the server sets a cookie on the client to record the |
| 74 | login. This cookie contains a large amount of high-quality randomness |
| 75 |
+1
-1
| --- www/patchcmd.md | ||
| +++ www/patchcmd.md | ||
| @@ -1,8 +1,8 @@ | ||
| 1 | 1 | # The "fossil patch" command |
| 2 | 2 | |
| 3 | -The "[fossil patch](/help?cmd=patch)" command is designed to transfer | |
| 3 | +The "[fossil patch](/help/patch)" command is designed to transfer | |
| 4 | 4 | uncommitted changes from one check-out to another, including transfering |
| 5 | 5 | those changes to other machines. |
| 6 | 6 | |
| 7 | 7 | For example, if you are working on a Windows desktop and you want to |
| 8 | 8 | test your changes on a Linux server before you commit, you can use the |
| 9 | 9 |
| --- www/patchcmd.md | |
| +++ www/patchcmd.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # The "fossil patch" command |
| 2 | |
| 3 | The "[fossil patch](/help?cmd=patch)" command is designed to transfer |
| 4 | uncommitted changes from one check-out to another, including transfering |
| 5 | those changes to other machines. |
| 6 | |
| 7 | For example, if you are working on a Windows desktop and you want to |
| 8 | test your changes on a Linux server before you commit, you can use the |
| 9 |
| --- www/patchcmd.md | |
| +++ www/patchcmd.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # The "fossil patch" command |
| 2 | |
| 3 | The "[fossil patch](/help/patch)" command is designed to transfer |
| 4 | uncommitted changes from one check-out to another, including transfering |
| 5 | those changes to other machines. |
| 6 | |
| 7 | For example, if you are working on a Windows desktop and you want to |
| 8 | test your changes on a Linux server before you commit, you can use the |
| 9 |
+1
-1
| --- www/private.wiki | ||
| +++ www/private.wiki | ||
| @@ -47,11 +47,11 @@ | ||
| 47 | 47 | To avoid generating a missing artifact |
| 48 | 48 | reference on peer repositories without the private branch, the merge parent |
| 49 | 49 | is not recorded when merging the private branch into a public branch. As a |
| 50 | 50 | consequence, the web UI timeline does not draw a merge line from the private |
| 51 | 51 | merge parent to the public merge child. Moreover, repeat private-to-public |
| 52 | -merge operations (without the [/help?cmd=merge | --force option]) with files | |
| 52 | +merge operations (without the [/help/merge | --force option]) with files | |
| 53 | 53 | added on the private branch may only work once, but later abort with |
| 54 | 54 | "WARNING: no common ancestor for FILE", as the parent-child relationship is |
| 55 | 55 | not recorded. (See the [/doc/trunk/www/branching.wiki | Branching, Forking, |
| 56 | 56 | Merging, and Tagging] document for more information.) |
| 57 | 57 | </div> |
| 58 | 58 |
| --- www/private.wiki | |
| +++ www/private.wiki | |
| @@ -47,11 +47,11 @@ | |
| 47 | To avoid generating a missing artifact |
| 48 | reference on peer repositories without the private branch, the merge parent |
| 49 | is not recorded when merging the private branch into a public branch. As a |
| 50 | consequence, the web UI timeline does not draw a merge line from the private |
| 51 | merge parent to the public merge child. Moreover, repeat private-to-public |
| 52 | merge operations (without the [/help?cmd=merge | --force option]) with files |
| 53 | added on the private branch may only work once, but later abort with |
| 54 | "WARNING: no common ancestor for FILE", as the parent-child relationship is |
| 55 | not recorded. (See the [/doc/trunk/www/branching.wiki | Branching, Forking, |
| 56 | Merging, and Tagging] document for more information.) |
| 57 | </div> |
| 58 |
| --- www/private.wiki | |
| +++ www/private.wiki | |
| @@ -47,11 +47,11 @@ | |
| 47 | To avoid generating a missing artifact |
| 48 | reference on peer repositories without the private branch, the merge parent |
| 49 | is not recorded when merging the private branch into a public branch. As a |
| 50 | consequence, the web UI timeline does not draw a merge line from the private |
| 51 | merge parent to the public merge child. Moreover, repeat private-to-public |
| 52 | merge operations (without the [/help/merge | --force option]) with files |
| 53 | added on the private branch may only work once, but later abort with |
| 54 | "WARNING: no common ancestor for FILE", as the parent-child relationship is |
| 55 | not recorded. (See the [/doc/trunk/www/branching.wiki | Branching, Forking, |
| 56 | Merging, and Tagging] document for more information.) |
| 57 | </div> |
| 58 |
+5
-5
| --- www/quickstart.wiki | ||
| +++ www/quickstart.wiki | ||
| @@ -254,12 +254,12 @@ | ||
| 254 | 254 | |
| 255 | 255 | To see the most recent changes made to the repository by other users, use "fossil timeline" to |
| 256 | 256 | find out the most recent commit, and then "fossil diff" between that commit and the |
| 257 | 257 | current tree: |
| 258 | 258 | |
| 259 | -<pre><b>fossil timeline | |
| 260 | -=== 2021-03-28 === | |
| 259 | +<pre><b><verbatim>fossil timeline | |
| 260 | +=== 2021-03-28 === | |
| 261 | 261 | 03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) |
| 262 | 262 | === 2021-03-27 === |
| 263 | 263 | 23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) |
| 264 | 264 | ⋮ |
| 265 | 265 | |
| @@ -269,11 +269,11 @@ | ||
| 269 | 269 | --- frobnicate.c |
| 270 | 270 | +++ frobnicate.c |
| 271 | 271 | @@ -1,10 +1,11 @@ |
| 272 | 272 | +/* made a change to the source file */ |
| 273 | 273 | # Original text |
| 274 | -</b></pre> | |
| 274 | +</verbatim></b></pre> | |
| 275 | 275 | |
| 276 | 276 | "current" is an alias for the checkout version, so the command |
| 277 | 277 | "fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results. |
| 278 | 278 | |
| 279 | 279 | To commit your changes to a local-only repository: |
| @@ -296,11 +296,11 @@ | ||
| 296 | 296 | To commit your changes to a repository that was cloned from a remote |
| 297 | 297 | repository, you give the same command, but the results are different. |
| 298 | 298 | Fossil defaults to [./concepts.wiki#workflow|autosync] mode, a |
| 299 | 299 | single-stage commit that sends all changes committed to the local |
| 300 | 300 | repository immediately on to the remote parent repository. This only |
| 301 | -works if you have write permission to the remote respository. | |
| 301 | +works if you have write permission to the remote repository. | |
| 302 | 302 | |
| 303 | 303 | <h2 id="naming">Naming of Files, Checkins, and Branches</h2> |
| 304 | 304 | |
| 305 | 305 | Fossil deals with information artifacts. This Quickstart document only deals |
| 306 | 306 | with files and collections of files, but be aware there are also tickets, wiki pages and more. |
| @@ -363,11 +363,11 @@ | ||
| 363 | 363 | as an HTTP server, where the repo DB is on the other side of the HTTP |
| 364 | 364 | server wall, inaccessible by all means other than Fossil's own |
| 365 | 365 | mediation. For this reason, the "localhost bypasses access control" |
| 366 | 366 | policy does <i>not</i> apply to these other interfaces. That is a very |
| 367 | 367 | good thing, since without this difference in policy, it would be unsafe |
| 368 | -to bind a [/help?cmd=server | <b>fossil server</b>] instance to | |
| 368 | +to bind a [/help/server | <b>fossil server</b>] instance to | |
| 369 | 369 | localhost on a high-numbered port and then reverse-proxy it out to the |
| 370 | 370 | world via HTTPS, a practice this author does engage in, with confidence.) |
| 371 | 371 | |
| 372 | 372 | Once you are finished configuring Fossil, you may safely Control-C out |
| 373 | 373 | of the <b>fossil ui</b> command to shut down this privileged |
| 374 | 374 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -254,12 +254,12 @@ | |
| 254 | |
| 255 | To see the most recent changes made to the repository by other users, use "fossil timeline" to |
| 256 | find out the most recent commit, and then "fossil diff" between that commit and the |
| 257 | current tree: |
| 258 | |
| 259 | <pre><b>fossil timeline |
| 260 | === 2021-03-28 === |
| 261 | 03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) |
| 262 | === 2021-03-27 === |
| 263 | 23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) |
| 264 | ⋮ |
| 265 | |
| @@ -269,11 +269,11 @@ | |
| 269 | --- frobnicate.c |
| 270 | +++ frobnicate.c |
| 271 | @@ -1,10 +1,11 @@ |
| 272 | +/* made a change to the source file */ |
| 273 | # Original text |
| 274 | </b></pre> |
| 275 | |
| 276 | "current" is an alias for the checkout version, so the command |
| 277 | "fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results. |
| 278 | |
| 279 | To commit your changes to a local-only repository: |
| @@ -296,11 +296,11 @@ | |
| 296 | To commit your changes to a repository that was cloned from a remote |
| 297 | repository, you give the same command, but the results are different. |
| 298 | Fossil defaults to [./concepts.wiki#workflow|autosync] mode, a |
| 299 | single-stage commit that sends all changes committed to the local |
| 300 | repository immediately on to the remote parent repository. This only |
| 301 | works if you have write permission to the remote respository. |
| 302 | |
| 303 | <h2 id="naming">Naming of Files, Checkins, and Branches</h2> |
| 304 | |
| 305 | Fossil deals with information artifacts. This Quickstart document only deals |
| 306 | with files and collections of files, but be aware there are also tickets, wiki pages and more. |
| @@ -363,11 +363,11 @@ | |
| 363 | as an HTTP server, where the repo DB is on the other side of the HTTP |
| 364 | server wall, inaccessible by all means other than Fossil's own |
| 365 | mediation. For this reason, the "localhost bypasses access control" |
| 366 | policy does <i>not</i> apply to these other interfaces. That is a very |
| 367 | good thing, since without this difference in policy, it would be unsafe |
| 368 | to bind a [/help?cmd=server | <b>fossil server</b>] instance to |
| 369 | localhost on a high-numbered port and then reverse-proxy it out to the |
| 370 | world via HTTPS, a practice this author does engage in, with confidence.) |
| 371 | |
| 372 | Once you are finished configuring Fossil, you may safely Control-C out |
| 373 | of the <b>fossil ui</b> command to shut down this privileged |
| 374 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -254,12 +254,12 @@ | |
| 254 | |
| 255 | To see the most recent changes made to the repository by other users, use "fossil timeline" to |
| 256 | find out the most recent commit, and then "fossil diff" between that commit and the |
| 257 | current tree: |
| 258 | |
| 259 | <pre><b><verbatim>fossil timeline |
| 260 | === 2021-03-28 === |
| 261 | 03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) |
| 262 | === 2021-03-27 === |
| 263 | 23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) |
| 264 | ⋮ |
| 265 | |
| @@ -269,11 +269,11 @@ | |
| 269 | --- frobnicate.c |
| 270 | +++ frobnicate.c |
| 271 | @@ -1,10 +1,11 @@ |
| 272 | +/* made a change to the source file */ |
| 273 | # Original text |
| 274 | </verbatim></b></pre> |
| 275 | |
| 276 | "current" is an alias for the checkout version, so the command |
| 277 | "fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results. |
| 278 | |
| 279 | To commit your changes to a local-only repository: |
| @@ -296,11 +296,11 @@ | |
| 296 | To commit your changes to a repository that was cloned from a remote |
| 297 | repository, you give the same command, but the results are different. |
| 298 | Fossil defaults to [./concepts.wiki#workflow|autosync] mode, a |
| 299 | single-stage commit that sends all changes committed to the local |
| 300 | repository immediately on to the remote parent repository. This only |
| 301 | works if you have write permission to the remote repository. |
| 302 | |
| 303 | <h2 id="naming">Naming of Files, Checkins, and Branches</h2> |
| 304 | |
| 305 | Fossil deals with information artifacts. This Quickstart document only deals |
| 306 | with files and collections of files, but be aware there are also tickets, wiki pages and more. |
| @@ -363,11 +363,11 @@ | |
| 363 | as an HTTP server, where the repo DB is on the other side of the HTTP |
| 364 | server wall, inaccessible by all means other than Fossil's own |
| 365 | mediation. For this reason, the "localhost bypasses access control" |
| 366 | policy does <i>not</i> apply to these other interfaces. That is a very |
| 367 | good thing, since without this difference in policy, it would be unsafe |
| 368 | to bind a [/help/server | <b>fossil server</b>] instance to |
| 369 | localhost on a high-numbered port and then reverse-proxy it out to the |
| 370 | world via HTTPS, a practice this author does engage in, with confidence.) |
| 371 | |
| 372 | Once you are finished configuring Fossil, you may safely Control-C out |
| 373 | of the <b>fossil ui</b> command to shut down this privileged |
| 374 |
+1
-1
| --- www/rebaseharm.md | ||
| +++ www/rebaseharm.md | ||
| @@ -373,11 +373,11 @@ | ||
| 373 | 373 | developers to be smarter than the original developers! That's a |
| 374 | 374 | beautiful wish, but there's a sharp limit to how far you can carry it. |
| 375 | 375 | Eventually you hit the limits of human brilliance. |
| 376 | 376 | |
| 377 | 377 | When the operation of some bit of code is not obvious, both Fossil and |
| 378 | -Git let you run a [`blame`](/help?cmd=blame) on the code file to get | |
| 378 | +Git let you run a [`blame`](/help/blame) on the code file to get | |
| 379 | 379 | information about each line of code, and from that which check-in last |
| 380 | 380 | touched a given line of code. If you squash the check-ins on a branch |
| 381 | 381 | down to a single check-in, you throw away the information leading up to |
| 382 | 382 | that finished form. Fossil not only preserves the check-ins surrounding |
| 383 | 383 | the one that included the line of code you're trying to understand, its |
| 384 | 384 |
| --- www/rebaseharm.md | |
| +++ www/rebaseharm.md | |
| @@ -373,11 +373,11 @@ | |
| 373 | developers to be smarter than the original developers! That's a |
| 374 | beautiful wish, but there's a sharp limit to how far you can carry it. |
| 375 | Eventually you hit the limits of human brilliance. |
| 376 | |
| 377 | When the operation of some bit of code is not obvious, both Fossil and |
| 378 | Git let you run a [`blame`](/help?cmd=blame) on the code file to get |
| 379 | information about each line of code, and from that which check-in last |
| 380 | touched a given line of code. If you squash the check-ins on a branch |
| 381 | down to a single check-in, you throw away the information leading up to |
| 382 | that finished form. Fossil not only preserves the check-ins surrounding |
| 383 | the one that included the line of code you're trying to understand, its |
| 384 |
| --- www/rebaseharm.md | |
| +++ www/rebaseharm.md | |
| @@ -373,11 +373,11 @@ | |
| 373 | developers to be smarter than the original developers! That's a |
| 374 | beautiful wish, but there's a sharp limit to how far you can carry it. |
| 375 | Eventually you hit the limits of human brilliance. |
| 376 | |
| 377 | When the operation of some bit of code is not obvious, both Fossil and |
| 378 | Git let you run a [`blame`](/help/blame) on the code file to get |
| 379 | information about each line of code, and from that which check-in last |
| 380 | touched a given line of code. If you squash the check-ins on a branch |
| 381 | down to a single check-in, you throw away the information leading up to |
| 382 | that finished form. Fossil not only preserves the check-ins surrounding |
| 383 | the one that included the line of code you're trying to understand, its |
| 384 |
+1
-1
| --- www/relatedwork.md | ||
| +++ www/relatedwork.md | ||
| @@ -66,11 +66,11 @@ | ||
| 66 | 66 | [corec66]: https://corecursive.com/066-sqlite-with-richard-hipp/ |
| 67 | 67 | [Darcs]: http://darcs.net/ |
| 68 | 68 | [db2w]: https://youtu.be/2eaQzahCeh4 |
| 69 | 69 | [emacsfsl]: https://chiselapp.com/user/venks/repository/emacs-fossil/doc/tip/README.md |
| 70 | 70 | [floss26]: https://twit.tv/shows/floss-weekly/episodes/26 |
| 71 | -[fnc]: https://fnc.bsdbox.org | |
| 71 | +[fnc]: https://fnc.sh | |
| 72 | 72 | [fsl]: http://fossil.0branch.com/fsl |
| 73 | 73 | [Fuel]: https://fuel-scm.org/fossil/index |
| 74 | 74 | [Git]: https://git-scm.com |
| 75 | 75 | [GoLand]: https://www.jetbrains.com/go/ |
| 76 | 76 | [got]: https://gameoftrees.org |
| 77 | 77 |
| --- www/relatedwork.md | |
| +++ www/relatedwork.md | |
| @@ -66,11 +66,11 @@ | |
| 66 | [corec66]: https://corecursive.com/066-sqlite-with-richard-hipp/ |
| 67 | [Darcs]: http://darcs.net/ |
| 68 | [db2w]: https://youtu.be/2eaQzahCeh4 |
| 69 | [emacsfsl]: https://chiselapp.com/user/venks/repository/emacs-fossil/doc/tip/README.md |
| 70 | [floss26]: https://twit.tv/shows/floss-weekly/episodes/26 |
| 71 | [fnc]: https://fnc.bsdbox.org |
| 72 | [fsl]: http://fossil.0branch.com/fsl |
| 73 | [Fuel]: https://fuel-scm.org/fossil/index |
| 74 | [Git]: https://git-scm.com |
| 75 | [GoLand]: https://www.jetbrains.com/go/ |
| 76 | [got]: https://gameoftrees.org |
| 77 |
| --- www/relatedwork.md | |
| +++ www/relatedwork.md | |
| @@ -66,11 +66,11 @@ | |
| 66 | [corec66]: https://corecursive.com/066-sqlite-with-richard-hipp/ |
| 67 | [Darcs]: http://darcs.net/ |
| 68 | [db2w]: https://youtu.be/2eaQzahCeh4 |
| 69 | [emacsfsl]: https://chiselapp.com/user/venks/repository/emacs-fossil/doc/tip/README.md |
| 70 | [floss26]: https://twit.tv/shows/floss-weekly/episodes/26 |
| 71 | [fnc]: https://fnc.sh |
| 72 | [fsl]: http://fossil.0branch.com/fsl |
| 73 | [Fuel]: https://fuel-scm.org/fossil/index |
| 74 | [Git]: https://git-scm.com |
| 75 | [GoLand]: https://www.jetbrains.com/go/ |
| 76 | [got]: https://gameoftrees.org |
| 77 |
+1
-1
| --- www/selfhost.wiki | ||
| +++ www/selfhost.wiki | ||
| @@ -72,7 +72,7 @@ | ||
| 72 | 72 | |
| 73 | 73 | Server (2) is a |
| 74 | 74 | <a href="http://www.linode.com/">Linode</a> located in Newark, NJ |
| 75 | 75 | and set up just like the canonical server (1) with the addition of a |
| 76 | 76 | cron job for synchronization. The same cron job also runs the |
| 77 | -[/help?cmd=git|fossil git export] command after each sync in order to | |
| 77 | +[/help/git|fossil git export] command after each sync in order to | |
| 78 | 78 | [./mirrortogithub.md#ex1|mirror all changes to GitHub]. |
| 79 | 79 |
| --- www/selfhost.wiki | |
| +++ www/selfhost.wiki | |
| @@ -72,7 +72,7 @@ | |
| 72 | |
| 73 | Server (2) is a |
| 74 | <a href="http://www.linode.com/">Linode</a> located in Newark, NJ |
| 75 | and set up just like the canonical server (1) with the addition of a |
| 76 | cron job for synchronization. The same cron job also runs the |
| 77 | [/help?cmd=git|fossil git export] command after each sync in order to |
| 78 | [./mirrortogithub.md#ex1|mirror all changes to GitHub]. |
| 79 |
| --- www/selfhost.wiki | |
| +++ www/selfhost.wiki | |
| @@ -72,7 +72,7 @@ | |
| 72 | |
| 73 | Server (2) is a |
| 74 | <a href="http://www.linode.com/">Linode</a> located in Newark, NJ |
| 75 | and set up just like the canonical server (1) with the addition of a |
| 76 | cron job for synchronization. The same cron job also runs the |
| 77 | [/help/git|fossil git export] command after each sync in order to |
| 78 | [./mirrortogithub.md#ex1|mirror all changes to GitHub]. |
| 79 |
+7
-7
| --- www/server/index.html | ||
| +++ www/server/index.html | ||
| @@ -38,16 +38,16 @@ | ||
| 38 | 38 | |
| 39 | 39 | |
| 40 | 40 | <h2 id="prep">Repository Prep</h2> |
| 41 | 41 | |
| 42 | 42 | <p>Prior to serving a Fossil repository to others, consider running <a |
| 43 | -href="$ROOT/help?cmd=ui"><tt>fossil ui</tt></a> locally and taking these | |
| 43 | +href="$ROOT/help/ui"><tt>fossil ui</tt></a> locally and taking these | |
| 44 | 44 | minimum recommended preparation steps:</p> |
| 45 | 45 | |
| 46 | 46 | <ol> |
| 47 | 47 | <li><p>Fossil creates only one user in a <a |
| 48 | - href="$ROOT/help?cmd=new">new repository</a> and gives it the <a | |
| 48 | + href="$ROOT/help/new">new repository</a> and gives it the <a | |
| 49 | 49 | href="../caps/admin-v-setup.md#apsu">all-powerful Setup capability</a>. |
| 50 | 50 | The 10-digit random password generated for that user is fairly strong |
| 51 | 51 | against remote attack, even without explicit password guess rate |
| 52 | 52 | limiting, but because that user has so much power, you may want to |
| 53 | 53 | give it a much stronger password under Admin → Users.</a></li> |
| @@ -95,11 +95,11 @@ | ||
| 95 | 95 | |
| 96 | 96 | <p>Most ordinary web servers can <a href="any/cgi.md">run Fossil as a |
| 97 | 97 | CGI script</a>. This method is known to work with Apache, |
| 98 | 98 | <tt>lighttpd</tt>, and <a |
| 99 | 99 | href="any/althttpd.md"><tt>althttpd</tt></a>. The Fossil server |
| 100 | -administrator places a <a href="$ROOT/help?cmd=cgi">short CGI script</a> in | |
| 100 | +administrator places a <a href="$ROOT/help/cgi">short CGI script</a> in | |
| 101 | 101 | the web server's document hierarchy and when a client requests the URL |
| 102 | 102 | that corresponds to that script, Fossil runs and generates the |
| 103 | 103 | response.</p> |
| 104 | 104 | |
| 105 | 105 | <p>CGI is a good choice for merging Fossil into an existing web site, |
| @@ -114,11 +114,11 @@ | ||
| 114 | 114 | href="any/xinetd.md"><tt>xinetd</tt></a>, <a id="stunnel" |
| 115 | 115 | href="any/stunnel.md"><tt>stunnel</tt></a>, <a |
| 116 | 116 | href="macos/service.md"><tt>launchd</tt></a>, and <a |
| 117 | 117 | href="debian/service.md"><tt>systemd</tt></a> |
| 118 | 118 | can be configured to invoke the the |
| 119 | -<a href="$ROOT/help?cmd=http"><tt>fossil http</tt></a> command to handle | |
| 119 | +<a href="$ROOT/help/http"><tt>fossil http</tt></a> command to handle | |
| 120 | 120 | each incoming HTTP request. The "<tt>fossil http</tt>" command reads |
| 121 | 121 | the HTTP request off of standard input, computes an appropriate |
| 122 | 122 | reply, and writes the reply on standard output. There is a separate |
| 123 | 123 | invocation of the "<tt>fossil http</tt>" command for each HTTP request. |
| 124 | 124 | The socket listener daemon takes care of relaying content to and from |
| @@ -127,21 +127,21 @@ | ||
| 127 | 127 | |
| 128 | 128 | <h3 id="standalone">Stand-alone HTTP Server</h3> |
| 129 | 129 | |
| 130 | 130 | <p>This is the <a href="any/none.md">easiest method</a>. |
| 131 | 131 | A stand-alone server uses the |
| 132 | -<a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command to run a | |
| 132 | +<a href="$ROOT/help/server"><tt>fossil server</tt></a> command to run a | |
| 133 | 133 | process that listens for incoming HTTP requests on a socket and then |
| 134 | 134 | dispatches a copy of itself to deal with each incoming request. You can |
| 135 | 135 | expose Fossil directly to the clients in this way or you can interpose a |
| 136 | 136 | <a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> |
| 137 | 137 | layer between the clients and Fossil.</p> |
| 138 | 138 | |
| 139 | 139 | <h3 id="scgi">SCGI</h3> |
| 140 | 140 | |
| 141 | 141 | <p>The Fossil standalone server can also handle <a href="any/scgi.md">SCGI</a>. |
| 142 | -When the <a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command is | |
| 142 | +When the <a href="$ROOT/help/server"><tt>fossil server</tt></a> command is | |
| 143 | 143 | run with the extra <tt>--scgi</tt> option, it listens for incoming |
| 144 | 144 | SCGI requests rather than HTTP requests. This allows Fossil to |
| 145 | 145 | respond to requests from web servers <a href="debian/nginx.md">such as |
| 146 | 146 | nginx</a> that don't support CGI. SCGI is a simpler protocol to proxy |
| 147 | 147 | than HTTP, since the HTTP doesn't have to be re-interpreted in terms of |
| @@ -287,11 +287,11 @@ | ||
| 287 | 287 | activating the search feature (Admin → Search) so that visitors can do |
| 288 | 288 | full-text search on your documentation.</p></li> |
| 289 | 289 | |
| 290 | 290 | <li><p>Now that others can be making changes to the repository, |
| 291 | 291 | consider monitoring them via <a href="../alerts.md">email alerts</a> |
| 292 | - or the <a href="$ROOT/help?cmd=/timeline.rss">timeline RSS | |
| 292 | + or the <a href="$ROOT/help/www/timeline.rss">timeline RSS | |
| 293 | 293 | feed</a>.</p></li> |
| 294 | 294 | |
| 295 | 295 | <li><p>Turn on the various logging features.</p></li> |
| 296 | 296 | </ol> |
| 297 | 297 | |
| 298 | 298 |
| --- www/server/index.html | |
| +++ www/server/index.html | |
| @@ -38,16 +38,16 @@ | |
| 38 | |
| 39 | |
| 40 | <h2 id="prep">Repository Prep</h2> |
| 41 | |
| 42 | <p>Prior to serving a Fossil repository to others, consider running <a |
| 43 | href="$ROOT/help?cmd=ui"><tt>fossil ui</tt></a> locally and taking these |
| 44 | minimum recommended preparation steps:</p> |
| 45 | |
| 46 | <ol> |
| 47 | <li><p>Fossil creates only one user in a <a |
| 48 | href="$ROOT/help?cmd=new">new repository</a> and gives it the <a |
| 49 | href="../caps/admin-v-setup.md#apsu">all-powerful Setup capability</a>. |
| 50 | The 10-digit random password generated for that user is fairly strong |
| 51 | against remote attack, even without explicit password guess rate |
| 52 | limiting, but because that user has so much power, you may want to |
| 53 | give it a much stronger password under Admin → Users.</a></li> |
| @@ -95,11 +95,11 @@ | |
| 95 | |
| 96 | <p>Most ordinary web servers can <a href="any/cgi.md">run Fossil as a |
| 97 | CGI script</a>. This method is known to work with Apache, |
| 98 | <tt>lighttpd</tt>, and <a |
| 99 | href="any/althttpd.md"><tt>althttpd</tt></a>. The Fossil server |
| 100 | administrator places a <a href="$ROOT/help?cmd=cgi">short CGI script</a> in |
| 101 | the web server's document hierarchy and when a client requests the URL |
| 102 | that corresponds to that script, Fossil runs and generates the |
| 103 | response.</p> |
| 104 | |
| 105 | <p>CGI is a good choice for merging Fossil into an existing web site, |
| @@ -114,11 +114,11 @@ | |
| 114 | href="any/xinetd.md"><tt>xinetd</tt></a>, <a id="stunnel" |
| 115 | href="any/stunnel.md"><tt>stunnel</tt></a>, <a |
| 116 | href="macos/service.md"><tt>launchd</tt></a>, and <a |
| 117 | href="debian/service.md"><tt>systemd</tt></a> |
| 118 | can be configured to invoke the the |
| 119 | <a href="$ROOT/help?cmd=http"><tt>fossil http</tt></a> command to handle |
| 120 | each incoming HTTP request. The "<tt>fossil http</tt>" command reads |
| 121 | the HTTP request off of standard input, computes an appropriate |
| 122 | reply, and writes the reply on standard output. There is a separate |
| 123 | invocation of the "<tt>fossil http</tt>" command for each HTTP request. |
| 124 | The socket listener daemon takes care of relaying content to and from |
| @@ -127,21 +127,21 @@ | |
| 127 | |
| 128 | <h3 id="standalone">Stand-alone HTTP Server</h3> |
| 129 | |
| 130 | <p>This is the <a href="any/none.md">easiest method</a>. |
| 131 | A stand-alone server uses the |
| 132 | <a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command to run a |
| 133 | process that listens for incoming HTTP requests on a socket and then |
| 134 | dispatches a copy of itself to deal with each incoming request. You can |
| 135 | expose Fossil directly to the clients in this way or you can interpose a |
| 136 | <a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> |
| 137 | layer between the clients and Fossil.</p> |
| 138 | |
| 139 | <h3 id="scgi">SCGI</h3> |
| 140 | |
| 141 | <p>The Fossil standalone server can also handle <a href="any/scgi.md">SCGI</a>. |
| 142 | When the <a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command is |
| 143 | run with the extra <tt>--scgi</tt> option, it listens for incoming |
| 144 | SCGI requests rather than HTTP requests. This allows Fossil to |
| 145 | respond to requests from web servers <a href="debian/nginx.md">such as |
| 146 | nginx</a> that don't support CGI. SCGI is a simpler protocol to proxy |
| 147 | than HTTP, since the HTTP doesn't have to be re-interpreted in terms of |
| @@ -287,11 +287,11 @@ | |
| 287 | activating the search feature (Admin → Search) so that visitors can do |
| 288 | full-text search on your documentation.</p></li> |
| 289 | |
| 290 | <li><p>Now that others can be making changes to the repository, |
| 291 | consider monitoring them via <a href="../alerts.md">email alerts</a> |
| 292 | or the <a href="$ROOT/help?cmd=/timeline.rss">timeline RSS |
| 293 | feed</a>.</p></li> |
| 294 | |
| 295 | <li><p>Turn on the various logging features.</p></li> |
| 296 | </ol> |
| 297 | |
| 298 |
| --- www/server/index.html | |
| +++ www/server/index.html | |
| @@ -38,16 +38,16 @@ | |
| 38 | |
| 39 | |
| 40 | <h2 id="prep">Repository Prep</h2> |
| 41 | |
| 42 | <p>Prior to serving a Fossil repository to others, consider running <a |
| 43 | href="$ROOT/help/ui"><tt>fossil ui</tt></a> locally and taking these |
| 44 | minimum recommended preparation steps:</p> |
| 45 | |
| 46 | <ol> |
| 47 | <li><p>Fossil creates only one user in a <a |
| 48 | href="$ROOT/help/new">new repository</a> and gives it the <a |
| 49 | href="../caps/admin-v-setup.md#apsu">all-powerful Setup capability</a>. |
| 50 | The 10-digit random password generated for that user is fairly strong |
| 51 | against remote attack, even without explicit password guess rate |
| 52 | limiting, but because that user has so much power, you may want to |
| 53 | give it a much stronger password under Admin → Users.</a></li> |
| @@ -95,11 +95,11 @@ | |
| 95 | |
| 96 | <p>Most ordinary web servers can <a href="any/cgi.md">run Fossil as a |
| 97 | CGI script</a>. This method is known to work with Apache, |
| 98 | <tt>lighttpd</tt>, and <a |
| 99 | href="any/althttpd.md"><tt>althttpd</tt></a>. The Fossil server |
| 100 | administrator places a <a href="$ROOT/help/cgi">short CGI script</a> in |
| 101 | the web server's document hierarchy and when a client requests the URL |
| 102 | that corresponds to that script, Fossil runs and generates the |
| 103 | response.</p> |
| 104 | |
| 105 | <p>CGI is a good choice for merging Fossil into an existing web site, |
| @@ -114,11 +114,11 @@ | |
| 114 | href="any/xinetd.md"><tt>xinetd</tt></a>, <a id="stunnel" |
| 115 | href="any/stunnel.md"><tt>stunnel</tt></a>, <a |
| 116 | href="macos/service.md"><tt>launchd</tt></a>, and <a |
| 117 | href="debian/service.md"><tt>systemd</tt></a> |
| 118 | can be configured to invoke the the |
| 119 | <a href="$ROOT/help/http"><tt>fossil http</tt></a> command to handle |
| 120 | each incoming HTTP request. The "<tt>fossil http</tt>" command reads |
| 121 | the HTTP request off of standard input, computes an appropriate |
| 122 | reply, and writes the reply on standard output. There is a separate |
| 123 | invocation of the "<tt>fossil http</tt>" command for each HTTP request. |
| 124 | The socket listener daemon takes care of relaying content to and from |
| @@ -127,21 +127,21 @@ | |
| 127 | |
| 128 | <h3 id="standalone">Stand-alone HTTP Server</h3> |
| 129 | |
| 130 | <p>This is the <a href="any/none.md">easiest method</a>. |
| 131 | A stand-alone server uses the |
| 132 | <a href="$ROOT/help/server"><tt>fossil server</tt></a> command to run a |
| 133 | process that listens for incoming HTTP requests on a socket and then |
| 134 | dispatches a copy of itself to deal with each incoming request. You can |
| 135 | expose Fossil directly to the clients in this way or you can interpose a |
| 136 | <a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> |
| 137 | layer between the clients and Fossil.</p> |
| 138 | |
| 139 | <h3 id="scgi">SCGI</h3> |
| 140 | |
| 141 | <p>The Fossil standalone server can also handle <a href="any/scgi.md">SCGI</a>. |
| 142 | When the <a href="$ROOT/help/server"><tt>fossil server</tt></a> command is |
| 143 | run with the extra <tt>--scgi</tt> option, it listens for incoming |
| 144 | SCGI requests rather than HTTP requests. This allows Fossil to |
| 145 | respond to requests from web servers <a href="debian/nginx.md">such as |
| 146 | nginx</a> that don't support CGI. SCGI is a simpler protocol to proxy |
| 147 | than HTTP, since the HTTP doesn't have to be re-interpreted in terms of |
| @@ -287,11 +287,11 @@ | |
| 287 | activating the search feature (Admin → Search) so that visitors can do |
| 288 | full-text search on your documentation.</p></li> |
| 289 | |
| 290 | <li><p>Now that others can be making changes to the repository, |
| 291 | consider monitoring them via <a href="../alerts.md">email alerts</a> |
| 292 | or the <a href="$ROOT/help/www/timeline.rss">timeline RSS |
| 293 | feed</a>.</p></li> |
| 294 | |
| 295 | <li><p>Turn on the various logging features.</p></li> |
| 296 | </ol> |
| 297 | |
| 298 |
+2
-2
| --- www/server/windows/service.md | ||
| +++ www/server/windows/service.md | ||
| @@ -62,11 +62,11 @@ | ||
| 62 | 62 | fossil.exe process is unable to use the directory normally during a scan. |
| 63 | 63 | |
| 64 | 64 | ### <a id='PowerShell'></a>Advanced service installation using PowerShell |
| 65 | 65 | |
| 66 | 66 | As great as `fossil winsrv` is, it does not have one to one reflection of all of |
| 67 | -the `fossil server` [options](/help?cmd=server). When you need to use some of | |
| 67 | +the `fossil server` [options](/help/server). When you need to use some of | |
| 68 | 68 | the more advanced options, such as `--https`, `--skin`, or `--extroot`, you will |
| 69 | 69 | need to use PowerShell to configure and install the Windows service. |
| 70 | 70 | |
| 71 | 71 | PowerShell provides the [New-Service](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-5.1) |
| 72 | 72 | command, which we can use to install and configure Fossil as a service. The |
| @@ -81,11 +81,11 @@ | ||
| 81 | 81 | Windows will accept either back slashes or forward slashes in path names, but |
| 82 | 82 | Fossil has a preference for forward slashes. The use of `--repolist` will make |
| 83 | 83 | this a multiple repository server. If you want to serve only a single |
| 84 | 84 | repository, then leave off the `--repolist` parameter and provide the full path |
| 85 | 85 | to the proper repository file. Other options are listed in the |
| 86 | -[fossil server](/help?cmd=server) documentation. | |
| 86 | +[fossil server](/help/server) documentation. | |
| 87 | 87 | |
| 88 | 88 | The service will be installed by default to use the Local Service account. |
| 89 | 89 | Since Fossil only needs access to local files, this is fine and causes no |
| 90 | 90 | issues. The service will not be running once installed. You will need to start |
| 91 | 91 | it to proceed (the `-StartupType Automatic` parameter to `New-Service` will |
| 92 | 92 |
| --- www/server/windows/service.md | |
| +++ www/server/windows/service.md | |
| @@ -62,11 +62,11 @@ | |
| 62 | fossil.exe process is unable to use the directory normally during a scan. |
| 63 | |
| 64 | ### <a id='PowerShell'></a>Advanced service installation using PowerShell |
| 65 | |
| 66 | As great as `fossil winsrv` is, it does not have one to one reflection of all of |
| 67 | the `fossil server` [options](/help?cmd=server). When you need to use some of |
| 68 | the more advanced options, such as `--https`, `--skin`, or `--extroot`, you will |
| 69 | need to use PowerShell to configure and install the Windows service. |
| 70 | |
| 71 | PowerShell provides the [New-Service](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-5.1) |
| 72 | command, which we can use to install and configure Fossil as a service. The |
| @@ -81,11 +81,11 @@ | |
| 81 | Windows will accept either back slashes or forward slashes in path names, but |
| 82 | Fossil has a preference for forward slashes. The use of `--repolist` will make |
| 83 | this a multiple repository server. If you want to serve only a single |
| 84 | repository, then leave off the `--repolist` parameter and provide the full path |
| 85 | to the proper repository file. Other options are listed in the |
| 86 | [fossil server](/help?cmd=server) documentation. |
| 87 | |
| 88 | The service will be installed by default to use the Local Service account. |
| 89 | Since Fossil only needs access to local files, this is fine and causes no |
| 90 | issues. The service will not be running once installed. You will need to start |
| 91 | it to proceed (the `-StartupType Automatic` parameter to `New-Service` will |
| 92 |
| --- www/server/windows/service.md | |
| +++ www/server/windows/service.md | |
| @@ -62,11 +62,11 @@ | |
| 62 | fossil.exe process is unable to use the directory normally during a scan. |
| 63 | |
| 64 | ### <a id='PowerShell'></a>Advanced service installation using PowerShell |
| 65 | |
| 66 | As great as `fossil winsrv` is, it does not have one to one reflection of all of |
| 67 | the `fossil server` [options](/help/server). When you need to use some of |
| 68 | the more advanced options, such as `--https`, `--skin`, or `--extroot`, you will |
| 69 | need to use PowerShell to configure and install the Windows service. |
| 70 | |
| 71 | PowerShell provides the [New-Service](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-5.1) |
| 72 | command, which we can use to install and configure Fossil as a service. The |
| @@ -81,11 +81,11 @@ | |
| 81 | Windows will accept either back slashes or forward slashes in path names, but |
| 82 | Fossil has a preference for forward slashes. The use of `--repolist` will make |
| 83 | this a multiple repository server. If you want to serve only a single |
| 84 | repository, then leave off the `--repolist` parameter and provide the full path |
| 85 | to the proper repository file. Other options are listed in the |
| 86 | [fossil server](/help/server) documentation. |
| 87 | |
| 88 | The service will be installed by default to use the Local Service account. |
| 89 | Since Fossil only needs access to local files, this is fine and causes no |
| 90 | issues. The service will not be running once installed. You will need to start |
| 91 | it to proceed (the `-StartupType Automatic` parameter to `New-Service` will |
| 92 |
+2
-2
| --- www/serverext.wiki | ||
| +++ www/serverext.wiki | ||
| @@ -49,11 +49,11 @@ | ||
| 49 | 49 | <pre> |
| 50 | 50 | https://example-project.org/ext/<i>FILENAME</i> |
| 51 | 51 | </pre> |
| 52 | 52 | |
| 53 | 53 | In other words, access files in DOCUMENT_ROOT by appending the filename |
| 54 | -relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext] | |
| 54 | +relative to DOCUMENT_ROOT to the [/help/www/ext|/ext] | |
| 55 | 55 | page of the Fossil server. |
| 56 | 56 | |
| 57 | 57 | * Files that are readable but not executable are returned as static |
| 58 | 58 | content. |
| 59 | 59 | |
| @@ -124,11 +124,11 @@ | ||
| 124 | 124 | of its own source-code, so you can see how it works. |
| 125 | 125 | |
| 126 | 126 | <h3>2.3 Example #3</h3> |
| 127 | 127 | |
| 128 | 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 | |
| 129 | +option to the [/help/ui|fossil ui] command is a short cut that treats | |
| 130 | 130 | FILENAME as a CGI extension. When the ui command starts up a new web browser |
| 131 | 131 | pages, it points that page to the FILENAME extension. So if FILENAME is |
| 132 | 132 | a static content file (such as an HTML file or |
| 133 | 133 | [/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the |
| 134 | 134 | rendered content of the file is displayed. Meanwhile, the user can be |
| 135 | 135 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -49,11 +49,11 @@ | |
| 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 | |
| @@ -124,11 +124,11 @@ | |
| 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 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -49,11 +49,11 @@ | |
| 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/www/ext|/ext] |
| 55 | page of the Fossil server. |
| 56 | |
| 57 | * Files that are readable but not executable are returned as static |
| 58 | content. |
| 59 | |
| @@ -124,11 +124,11 @@ | |
| 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/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 |
+8
-8
| --- www/ssl-server.md | ||
| +++ www/ssl-server.md | ||
| @@ -1,13 +1,13 @@ | ||
| 1 | 1 | # SSL/TLS Server Mode |
| 2 | 2 | |
| 3 | 3 | ## History |
| 4 | 4 | |
| 5 | 5 | Fossil has supported [client-side SSL/TLS][0] since [2010][1]. This means |
| 6 | -that commands like "[fossil sync](/help?cmd=sync)" could use SSL/TLS when | |
| 6 | +that commands like "[fossil sync](/help/sync)" could use SSL/TLS when | |
| 7 | 7 | contacting a server. But on the server side, commands like |
| 8 | -"[fossil server](/help?cmd=server)" operated in clear-text only. To implement | |
| 8 | +"[fossil server](/help/server)" operated in clear-text only. To implement | |
| 9 | 9 | an encrypted server, you had to put Fossil behind a web server or reverse |
| 10 | 10 | proxy that handled the SSL/TLS decryption/encryption and passed cleartext |
| 11 | 11 | down to Fossil. |
| 12 | 12 | |
| 13 | 13 | [0]: ./ssl.wiki |
| @@ -14,13 +14,13 @@ | ||
| 14 | 14 | [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13 |
| 15 | 15 | |
| 16 | 16 | Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13), |
| 17 | 17 | Fossil servers are now able to converse directly over TLS. Commands like |
| 18 | 18 | |
| 19 | - * "[fossil server](/help?cmd=server)" | |
| 20 | - * "[fossil ui](/help?cmd=ui)", and | |
| 21 | - * "[fossil http](/help?cmd=http)" | |
| 19 | + * "[fossil server](/help/server)" | |
| 20 | + * "[fossil ui](/help/ui)", and | |
| 21 | + * "[fossil http](/help/http)" | |
| 22 | 22 | |
| 23 | 23 | may now handle the encryption natively when suitably configured, without |
| 24 | 24 | requiring a third-party proxy layer. |
| 25 | 25 | |
| 26 | 26 | ## <a id="usage"></a>Usage |
| @@ -135,11 +135,11 @@ | ||
| 135 | 135 | individual components will still be easily accessible. |
| 136 | 136 | |
| 137 | 137 | ### <a id="cat"></a>Separate or Concatenated? |
| 138 | 138 | |
| 139 | 139 | Given a single concatenated file that holds both your private key and your |
| 140 | -cert, you can hand it off to the "[fossil server](/help?cmd=server)" | |
| 140 | +cert, you can hand it off to the "[fossil server](/help/server)" | |
| 141 | 141 | command using the `--cert` option, like this: |
| 142 | 142 | |
| 143 | 143 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 144 | 144 | |
| 145 | 145 | The command above is sufficient to run a fully-encrypted web site for |
| @@ -257,12 +257,12 @@ | ||
| 257 | 257 | domain control. ACME’s design precludes replay attacks. |
| 258 | 258 | |
| 259 | 259 | In order for all of this to happen, certbot needs to be able to create |
| 260 | 260 | a subdirectory named ".well-known", within a directory you specify, |
| 261 | 261 | then populate that subdirectory with a token file of some kind. To support |
| 262 | -this, the "[fossil server](/help?cmd=server)" and | |
| 263 | -"[fossil http](/help?cmd=http)" commands have the --acme option. | |
| 262 | +this, the "[fossil server](/help/server)" and | |
| 263 | +"[fossil http](/help/http)" commands have the --acme option. | |
| 264 | 264 | |
| 265 | 265 | When specified, Fossil sees a URL where the path |
| 266 | 266 | begins with ".well-known", then instead of doing its normal processing, it |
| 267 | 267 | looks for a file with that pathname and returns it to the client. If |
| 268 | 268 | the "server" or "http" command is referencing a single Fossil repository, |
| 269 | 269 |
| --- www/ssl-server.md | |
| +++ www/ssl-server.md | |
| @@ -1,13 +1,13 @@ | |
| 1 | # SSL/TLS Server Mode |
| 2 | |
| 3 | ## History |
| 4 | |
| 5 | Fossil has supported [client-side SSL/TLS][0] since [2010][1]. This means |
| 6 | that commands like "[fossil sync](/help?cmd=sync)" could use SSL/TLS when |
| 7 | contacting a server. But on the server side, commands like |
| 8 | "[fossil server](/help?cmd=server)" operated in clear-text only. To implement |
| 9 | an encrypted server, you had to put Fossil behind a web server or reverse |
| 10 | proxy that handled the SSL/TLS decryption/encryption and passed cleartext |
| 11 | down to Fossil. |
| 12 | |
| 13 | [0]: ./ssl.wiki |
| @@ -14,13 +14,13 @@ | |
| 14 | [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13 |
| 15 | |
| 16 | Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13), |
| 17 | Fossil servers are now able to converse directly over TLS. Commands like |
| 18 | |
| 19 | * "[fossil server](/help?cmd=server)" |
| 20 | * "[fossil ui](/help?cmd=ui)", and |
| 21 | * "[fossil http](/help?cmd=http)" |
| 22 | |
| 23 | may now handle the encryption natively when suitably configured, without |
| 24 | requiring a third-party proxy layer. |
| 25 | |
| 26 | ## <a id="usage"></a>Usage |
| @@ -135,11 +135,11 @@ | |
| 135 | individual components will still be easily accessible. |
| 136 | |
| 137 | ### <a id="cat"></a>Separate or Concatenated? |
| 138 | |
| 139 | Given a single concatenated file that holds both your private key and your |
| 140 | cert, you can hand it off to the "[fossil server](/help?cmd=server)" |
| 141 | command using the `--cert` option, like this: |
| 142 | |
| 143 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 144 | |
| 145 | The command above is sufficient to run a fully-encrypted web site for |
| @@ -257,12 +257,12 @@ | |
| 257 | domain control. ACME’s design precludes replay attacks. |
| 258 | |
| 259 | In order for all of this to happen, certbot needs to be able to create |
| 260 | a subdirectory named ".well-known", within a directory you specify, |
| 261 | then populate that subdirectory with a token file of some kind. To support |
| 262 | this, the "[fossil server](/help?cmd=server)" and |
| 263 | "[fossil http](/help?cmd=http)" commands have the --acme option. |
| 264 | |
| 265 | When specified, Fossil sees a URL where the path |
| 266 | begins with ".well-known", then instead of doing its normal processing, it |
| 267 | looks for a file with that pathname and returns it to the client. If |
| 268 | the "server" or "http" command is referencing a single Fossil repository, |
| 269 |
| --- www/ssl-server.md | |
| +++ www/ssl-server.md | |
| @@ -1,13 +1,13 @@ | |
| 1 | # SSL/TLS Server Mode |
| 2 | |
| 3 | ## History |
| 4 | |
| 5 | Fossil has supported [client-side SSL/TLS][0] since [2010][1]. This means |
| 6 | that commands like "[fossil sync](/help/sync)" could use SSL/TLS when |
| 7 | contacting a server. But on the server side, commands like |
| 8 | "[fossil server](/help/server)" operated in clear-text only. To implement |
| 9 | an encrypted server, you had to put Fossil behind a web server or reverse |
| 10 | proxy that handled the SSL/TLS decryption/encryption and passed cleartext |
| 11 | down to Fossil. |
| 12 | |
| 13 | [0]: ./ssl.wiki |
| @@ -14,13 +14,13 @@ | |
| 14 | [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13 |
| 15 | |
| 16 | Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13), |
| 17 | Fossil servers are now able to converse directly over TLS. Commands like |
| 18 | |
| 19 | * "[fossil server](/help/server)" |
| 20 | * "[fossil ui](/help/ui)", and |
| 21 | * "[fossil http](/help/http)" |
| 22 | |
| 23 | may now handle the encryption natively when suitably configured, without |
| 24 | requiring a third-party proxy layer. |
| 25 | |
| 26 | ## <a id="usage"></a>Usage |
| @@ -135,11 +135,11 @@ | |
| 135 | individual components will still be easily accessible. |
| 136 | |
| 137 | ### <a id="cat"></a>Separate or Concatenated? |
| 138 | |
| 139 | Given a single concatenated file that holds both your private key and your |
| 140 | cert, you can hand it off to the "[fossil server](/help/server)" |
| 141 | command using the `--cert` option, like this: |
| 142 | |
| 143 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 144 | |
| 145 | The command above is sufficient to run a fully-encrypted web site for |
| @@ -257,12 +257,12 @@ | |
| 257 | domain control. ACME’s design precludes replay attacks. |
| 258 | |
| 259 | In order for all of this to happen, certbot needs to be able to create |
| 260 | a subdirectory named ".well-known", within a directory you specify, |
| 261 | then populate that subdirectory with a token file of some kind. To support |
| 262 | this, the "[fossil server](/help/server)" and |
| 263 | "[fossil http](/help/http)" commands have the --acme option. |
| 264 | |
| 265 | When specified, Fossil sees a URL where the path |
| 266 | begins with ".well-known", then instead of doing its normal processing, it |
| 267 | looks for a file with that pathname and returns it to the client. If |
| 268 | the "server" or "http" command is referencing a single Fossil repository, |
| 269 |
+7
-7
| --- www/sync.wiki | ||
| +++ www/sync.wiki | ||
| @@ -24,15 +24,15 @@ | ||
| 24 | 24 | |
| 25 | 25 | Each repository also has local state. The local state determines |
| 26 | 26 | the web-page formatting preferences, authorized users, ticket formats, |
| 27 | 27 | and similar information that varies from one repository to another. |
| 28 | 28 | The local state is not usually transferred during a sync. Except, |
| 29 | -some local state is transferred during a [/help?cmd=clone|clone] | |
| 29 | +some local state is transferred during a [/help/clone|clone] | |
| 30 | 30 | in order to initialize the local state of the new repository. Also, |
| 31 | 31 | an administrator can sync local state using |
| 32 | -the [/help?cmd=configuration|config push] and | |
| 33 | -[/help?cmd=configuration|config pull] | |
| 32 | +the [/help/configuration|config push] and | |
| 33 | +[/help/configuration|config pull] | |
| 34 | 34 | commands. |
| 35 | 35 | |
| 36 | 36 | <h3 id="crdt">1.1 Conflict-Free Replicated Datatypes</h3> |
| 37 | 37 | |
| 38 | 38 | The "bag of artifacts" data model used by Fossil is apparently an |
| @@ -56,23 +56,23 @@ | ||
| 56 | 56 | The server is listening for incoming HTTP requests. The client |
| 57 | 57 | issues one or more HTTP requests and receives replies for each |
| 58 | 58 | request. |
| 59 | 59 | |
| 60 | 60 | The server might be running as an independent server |
| 61 | -using the [/help?cmd=server|"fossil server" command], or it | |
| 61 | +using the [/help/server|"fossil server" command], or it | |
| 62 | 62 | might be launched from inetd or xinetd using the |
| 63 | -[/help?cmd=http|"fossil http" command]. Or the server might | |
| 63 | +[/help/http|"fossil http" command]. Or the server might | |
| 64 | 64 | be [./server/any/cgi.md|launched from CGI] or from |
| 65 | 65 | [./server/any/scgi.md|SCGI]. |
| 66 | 66 | (See "[./server/|How To Configure A Fossil Server]" for details.) |
| 67 | 67 | The specifics of how the server listens |
| 68 | 68 | for incoming HTTP requests is immaterial to this protocol. |
| 69 | 69 | The important point is that the server is listening for requests and |
| 70 | 70 | the client is the issuer of the requests. |
| 71 | 71 | |
| 72 | -A single [/help?cmd=push|push], | |
| 73 | -[/help?cmd=pull|pull], or [/help?cmd=sync|sync] | |
| 72 | +A single [/help/push|push], | |
| 73 | +[/help/pull|pull], or [/help?cmd=sync|sync] | |
| 74 | 74 | might involve multiple HTTP requests. |
| 75 | 75 | The client maintains state between all requests. But on the server |
| 76 | 76 | side, each request is independent. The server does not preserve |
| 77 | 77 | any information about the client from one request to the next. |
| 78 | 78 | |
| 79 | 79 |
| --- www/sync.wiki | |
| +++ www/sync.wiki | |
| @@ -24,15 +24,15 @@ | |
| 24 | |
| 25 | Each repository also has local state. The local state determines |
| 26 | the web-page formatting preferences, authorized users, ticket formats, |
| 27 | and similar information that varies from one repository to another. |
| 28 | The local state is not usually transferred during a sync. Except, |
| 29 | some local state is transferred during a [/help?cmd=clone|clone] |
| 30 | in order to initialize the local state of the new repository. Also, |
| 31 | an administrator can sync local state using |
| 32 | the [/help?cmd=configuration|config push] and |
| 33 | [/help?cmd=configuration|config pull] |
| 34 | commands. |
| 35 | |
| 36 | <h3 id="crdt">1.1 Conflict-Free Replicated Datatypes</h3> |
| 37 | |
| 38 | The "bag of artifacts" data model used by Fossil is apparently an |
| @@ -56,23 +56,23 @@ | |
| 56 | The server is listening for incoming HTTP requests. The client |
| 57 | issues one or more HTTP requests and receives replies for each |
| 58 | request. |
| 59 | |
| 60 | The server might be running as an independent server |
| 61 | using the [/help?cmd=server|"fossil server" command], or it |
| 62 | might be launched from inetd or xinetd using the |
| 63 | [/help?cmd=http|"fossil http" command]. Or the server might |
| 64 | be [./server/any/cgi.md|launched from CGI] or from |
| 65 | [./server/any/scgi.md|SCGI]. |
| 66 | (See "[./server/|How To Configure A Fossil Server]" for details.) |
| 67 | The specifics of how the server listens |
| 68 | for incoming HTTP requests is immaterial to this protocol. |
| 69 | The important point is that the server is listening for requests and |
| 70 | the client is the issuer of the requests. |
| 71 | |
| 72 | A single [/help?cmd=push|push], |
| 73 | [/help?cmd=pull|pull], or [/help?cmd=sync|sync] |
| 74 | might involve multiple HTTP requests. |
| 75 | The client maintains state between all requests. But on the server |
| 76 | side, each request is independent. The server does not preserve |
| 77 | any information about the client from one request to the next. |
| 78 | |
| 79 |
| --- www/sync.wiki | |
| +++ www/sync.wiki | |
| @@ -24,15 +24,15 @@ | |
| 24 | |
| 25 | Each repository also has local state. The local state determines |
| 26 | the web-page formatting preferences, authorized users, ticket formats, |
| 27 | and similar information that varies from one repository to another. |
| 28 | The local state is not usually transferred during a sync. Except, |
| 29 | some local state is transferred during a [/help/clone|clone] |
| 30 | in order to initialize the local state of the new repository. Also, |
| 31 | an administrator can sync local state using |
| 32 | the [/help/configuration|config push] and |
| 33 | [/help/configuration|config pull] |
| 34 | commands. |
| 35 | |
| 36 | <h3 id="crdt">1.1 Conflict-Free Replicated Datatypes</h3> |
| 37 | |
| 38 | The "bag of artifacts" data model used by Fossil is apparently an |
| @@ -56,23 +56,23 @@ | |
| 56 | The server is listening for incoming HTTP requests. The client |
| 57 | issues one or more HTTP requests and receives replies for each |
| 58 | request. |
| 59 | |
| 60 | The server might be running as an independent server |
| 61 | using the [/help/server|"fossil server" command], or it |
| 62 | might be launched from inetd or xinetd using the |
| 63 | [/help/http|"fossil http" command]. Or the server might |
| 64 | be [./server/any/cgi.md|launched from CGI] or from |
| 65 | [./server/any/scgi.md|SCGI]. |
| 66 | (See "[./server/|How To Configure A Fossil Server]" for details.) |
| 67 | The specifics of how the server listens |
| 68 | for incoming HTTP requests is immaterial to this protocol. |
| 69 | The important point is that the server is listening for requests and |
| 70 | the client is the issuer of the requests. |
| 71 | |
| 72 | A single [/help/push|push], |
| 73 | [/help/pull|pull], or [/help?cmd=sync|sync] |
| 74 | might involve multiple HTTP requests. |
| 75 | The client maintains state between all requests. But on the server |
| 76 | side, each request is independent. The server does not preserve |
| 77 | any information about the client from one request to the next. |
| 78 | |
| 79 |
+1
-1
| --- www/tech_overview.wiki | ||
| +++ www/tech_overview.wiki | ||
| @@ -162,11 +162,11 @@ | ||
| 162 | 162 | FOSSIL_HOME environment variable can always be set to determine the |
| 163 | 163 | location of the configuration database. Note also that the configuration |
| 164 | 164 | database file itself is called ".fossil" or "fossil.db" on unix but |
| 165 | 165 | "_fossil" on windows. |
| 166 | 166 | |
| 167 | -The [/help?cmd=info|fossil info] command will show the location of | |
| 167 | +The [/help/info|fossil info] command will show the location of | |
| 168 | 168 | the configuration database on a line that starts with "config-db:". |
| 169 | 169 | |
| 170 | 170 | <h3>2.2 Repository Databases</h3> |
| 171 | 171 | |
| 172 | 172 | The repository database is the file that is commonly referred to as |
| 173 | 173 |
| --- www/tech_overview.wiki | |
| +++ www/tech_overview.wiki | |
| @@ -162,11 +162,11 @@ | |
| 162 | FOSSIL_HOME environment variable can always be set to determine the |
| 163 | location of the configuration database. Note also that the configuration |
| 164 | database file itself is called ".fossil" or "fossil.db" on unix but |
| 165 | "_fossil" on windows. |
| 166 | |
| 167 | The [/help?cmd=info|fossil info] command will show the location of |
| 168 | the configuration database on a line that starts with "config-db:". |
| 169 | |
| 170 | <h3>2.2 Repository Databases</h3> |
| 171 | |
| 172 | The repository database is the file that is commonly referred to as |
| 173 |
| --- www/tech_overview.wiki | |
| +++ www/tech_overview.wiki | |
| @@ -162,11 +162,11 @@ | |
| 162 | FOSSIL_HOME environment variable can always be set to determine the |
| 163 | location of the configuration database. Note also that the configuration |
| 164 | database file itself is called ".fossil" or "fossil.db" on unix but |
| 165 | "_fossil" on windows. |
| 166 | |
| 167 | The [/help/info|fossil info] command will show the location of |
| 168 | the configuration database on a line that starts with "config-db:". |
| 169 | |
| 170 | <h3>2.2 Repository Databases</h3> |
| 171 | |
| 172 | The repository database is the file that is commonly referred to as |
| 173 |
+1
-1
| --- www/th1.md | ||
| +++ www/th1.md | ||
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | derived from user inputs that might contain text that is designed to subvert |
| 132 | 132 | the script. Untainted strings are known to come from secure sources and |
| 133 | 133 | are assumed to contain no malicious content. |
| 134 | 134 | |
| 135 | 135 | Beginning with Fossil version 2.26, and depending on the value of the |
| 136 | -[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted | |
| 136 | +[vuln-report setting](/help/vuln-report), TH1 will prevent tainted | |
| 137 | 137 | strings from being used in ways that might lead to XSS or SQL-injection |
| 138 | 138 | attacks. This feature helps to ensure that XSS and SQL-injection |
| 139 | 139 | vulnerabilities are not *accidentally* added to Fossil when |
| 140 | 140 | custom TH1 scripts for headers or footers or tickets are added to a |
| 141 | 141 | repository. Note that the tainted/untainted distinction in strings does |
| 142 | 142 |
| --- www/th1.md | |
| +++ www/th1.md | |
| @@ -131,11 +131,11 @@ | |
| 131 | derived from user inputs that might contain text that is designed to subvert |
| 132 | the script. Untainted strings are known to come from secure sources and |
| 133 | are assumed to contain no malicious content. |
| 134 | |
| 135 | Beginning with Fossil version 2.26, and depending on the value of the |
| 136 | [vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted |
| 137 | strings from being used in ways that might lead to XSS or SQL-injection |
| 138 | attacks. This feature helps to ensure that XSS and SQL-injection |
| 139 | vulnerabilities are not *accidentally* added to Fossil when |
| 140 | custom TH1 scripts for headers or footers or tickets are added to a |
| 141 | repository. Note that the tainted/untainted distinction in strings does |
| 142 |
| --- www/th1.md | |
| +++ www/th1.md | |
| @@ -131,11 +131,11 @@ | |
| 131 | derived from user inputs that might contain text that is designed to subvert |
| 132 | the script. Untainted strings are known to come from secure sources and |
| 133 | are assumed to contain no malicious content. |
| 134 | |
| 135 | Beginning with Fossil version 2.26, and depending on the value of the |
| 136 | [vuln-report setting](/help/vuln-report), TH1 will prevent tainted |
| 137 | strings from being used in ways that might lead to XSS or SQL-injection |
| 138 | attacks. This feature helps to ensure that XSS and SQL-injection |
| 139 | vulnerabilities are not *accidentally* added to Fossil when |
| 140 | custom TH1 scripts for headers or footers or tickets are added to a |
| 141 | repository. Note that the tainted/untainted distinction in strings does |
| 142 |
+6
-6
| --- www/unvers.wiki | ||
| +++ www/unvers.wiki | ||
| @@ -24,15 +24,15 @@ | ||
| 24 | 24 | In other words, the URI method "<b>uv</b>" (short for "unversioned") |
| 25 | 25 | followed by the name of the unversioned file will retrieve the content |
| 26 | 26 | of the file. The MIME type is inferred from the filename suffix. |
| 27 | 27 | |
| 28 | 28 | The content of unversioned files can also be retrieved using the |
| 29 | -[/help?cmd=unversioned|fossil unvers cat <i>FILENAME...</i>] | |
| 30 | -or [/help?cmd=unversioned|fossil unvers export <i>FILENAME</i>] commands. | |
| 29 | +[/help/unversioned|fossil unvers cat <i>FILENAME...</i>] | |
| 30 | +or [/help/unversioned|fossil unvers export <i>FILENAME</i>] commands. | |
| 31 | 31 | |
| 32 | 32 | A list of all unversioned files on a server can be seen using |
| 33 | -the [/help?cmd=/uvlist|/uvlist] URL. ([/uvlist|example].) | |
| 33 | +the [/help/www/uvlist|/uvlist] URL. ([/uvlist|example].) | |
| 34 | 34 | |
| 35 | 35 | <h2>Syncing Unversioned Files</h2> |
| 36 | 36 | |
| 37 | 37 | Unversioned content does not sync between repositories by default. |
| 38 | 38 | One must request it via commands such as: |
| @@ -41,18 +41,18 @@ | ||
| 41 | 41 | fossil sync <b>-u</b> |
| 42 | 42 | fossil clone <b>-u</b> <i>URL local-repo-name</i> |
| 43 | 43 | fossil unversioned sync |
| 44 | 44 | </pre> |
| 45 | 45 | |
| 46 | -The [/help?cmd=sync|fossil sync] and [/help?cmd=clone|fossil clone] | |
| 46 | +The [/help/sync|fossil sync] and [/help/clone|fossil clone] | |
| 47 | 47 | commands will synchronize unversioned content if and only if they're |
| 48 | 48 | given the "-u" (or "--unversioned") command-line option. The |
| 49 | -[/help?cmd=unversioned|fossil unversioned sync] command synchronizes the | |
| 49 | +[/help/unversioned|fossil unversioned sync] command synchronizes the | |
| 50 | 50 | unversioned content without synchronizing anything else. |
| 51 | 51 | |
| 52 | 52 | Notice that the "-u" option does not work on |
| 53 | -[/help?cmd=push|fossil push] or [/help?cmd=pull|fossil pull]. | |
| 53 | +[/help/push|fossil push] or [/help?cmd=pull|fossil pull]. | |
| 54 | 54 | The "-u" option is only available on "sync" and "clone". |
| 55 | 55 | A rough equivalent of an unversioned pull would be the |
| 56 | 56 | [/help?cmd=unversioned|fossil unversioned revert] command. The |
| 57 | 57 | "unversioned revert" |
| 58 | 58 | command causes the unversioned content on the local repository to be |
| 59 | 59 |
| --- www/unvers.wiki | |
| +++ www/unvers.wiki | |
| @@ -24,15 +24,15 @@ | |
| 24 | In other words, the URI method "<b>uv</b>" (short for "unversioned") |
| 25 | followed by the name of the unversioned file will retrieve the content |
| 26 | of the file. The MIME type is inferred from the filename suffix. |
| 27 | |
| 28 | The content of unversioned files can also be retrieved using the |
| 29 | [/help?cmd=unversioned|fossil unvers cat <i>FILENAME...</i>] |
| 30 | or [/help?cmd=unversioned|fossil unvers export <i>FILENAME</i>] commands. |
| 31 | |
| 32 | A list of all unversioned files on a server can be seen using |
| 33 | the [/help?cmd=/uvlist|/uvlist] URL. ([/uvlist|example].) |
| 34 | |
| 35 | <h2>Syncing Unversioned Files</h2> |
| 36 | |
| 37 | Unversioned content does not sync between repositories by default. |
| 38 | One must request it via commands such as: |
| @@ -41,18 +41,18 @@ | |
| 41 | fossil sync <b>-u</b> |
| 42 | fossil clone <b>-u</b> <i>URL local-repo-name</i> |
| 43 | fossil unversioned sync |
| 44 | </pre> |
| 45 | |
| 46 | The [/help?cmd=sync|fossil sync] and [/help?cmd=clone|fossil clone] |
| 47 | commands will synchronize unversioned content if and only if they're |
| 48 | given the "-u" (or "--unversioned") command-line option. The |
| 49 | [/help?cmd=unversioned|fossil unversioned sync] command synchronizes the |
| 50 | unversioned content without synchronizing anything else. |
| 51 | |
| 52 | Notice that the "-u" option does not work on |
| 53 | [/help?cmd=push|fossil push] or [/help?cmd=pull|fossil pull]. |
| 54 | The "-u" option is only available on "sync" and "clone". |
| 55 | A rough equivalent of an unversioned pull would be the |
| 56 | [/help?cmd=unversioned|fossil unversioned revert] command. The |
| 57 | "unversioned revert" |
| 58 | command causes the unversioned content on the local repository to be |
| 59 |
| --- www/unvers.wiki | |
| +++ www/unvers.wiki | |
| @@ -24,15 +24,15 @@ | |
| 24 | In other words, the URI method "<b>uv</b>" (short for "unversioned") |
| 25 | followed by the name of the unversioned file will retrieve the content |
| 26 | of the file. The MIME type is inferred from the filename suffix. |
| 27 | |
| 28 | The content of unversioned files can also be retrieved using the |
| 29 | [/help/unversioned|fossil unvers cat <i>FILENAME...</i>] |
| 30 | or [/help/unversioned|fossil unvers export <i>FILENAME</i>] commands. |
| 31 | |
| 32 | A list of all unversioned files on a server can be seen using |
| 33 | the [/help/www/uvlist|/uvlist] URL. ([/uvlist|example].) |
| 34 | |
| 35 | <h2>Syncing Unversioned Files</h2> |
| 36 | |
| 37 | Unversioned content does not sync between repositories by default. |
| 38 | One must request it via commands such as: |
| @@ -41,18 +41,18 @@ | |
| 41 | fossil sync <b>-u</b> |
| 42 | fossil clone <b>-u</b> <i>URL local-repo-name</i> |
| 43 | fossil unversioned sync |
| 44 | </pre> |
| 45 | |
| 46 | The [/help/sync|fossil sync] and [/help/clone|fossil clone] |
| 47 | commands will synchronize unversioned content if and only if they're |
| 48 | given the "-u" (or "--unversioned") command-line option. The |
| 49 | [/help/unversioned|fossil unversioned sync] command synchronizes the |
| 50 | unversioned content without synchronizing anything else. |
| 51 | |
| 52 | Notice that the "-u" option does not work on |
| 53 | [/help/push|fossil push] or [/help?cmd=pull|fossil pull]. |
| 54 | The "-u" option is only available on "sync" and "clone". |
| 55 | A rough equivalent of an unversioned pull would be the |
| 56 | [/help?cmd=unversioned|fossil unversioned revert] command. The |
| 57 | "unversioned revert" |
| 58 | command causes the unversioned content on the local repository to be |
| 59 |