Fossil SCM
Merge trunk.
Commit
02cdfa5e0866368cbb2d5bf881b544fbcf924c6ad76d20f42c91f62ae266eb7b
Parent
d2bfab5888ba0e0…
86 files changed
+1
-1
+6
-6
+1
-1
+448
-156
+1286
-709
+54
-8
+22
-17
+4
-1
+7
-6
+2
-3
+23
-6
+8
-1
+38
-27
+49
-1
+2
-2
+1
-1
+77
-16
+3
-4
+110
-40
+10
-4
+50
-48
+49
-39
+145
-6
+6
-2
+97
-21
+26
-1
+8
-1
+18
+1
-1
+94
-8
+94
-8
+60
-35
+80
-26
+3
-1
+1
-1
+28
-12
+6
+4
-1
+466
-36
+2
-1
+13
-1
+24
-10
+14
-1
+386
+477
-14
+581
+804
-168
+151
-24
+50
-17
+1
+14
-1
+9
-3
+7
-7
+12
-1
+7
-5
+5
-40
+4
-4
+2
+474
-462
+1
-1
+71
-13
+4
-4
+68
+55
-2
+25
+28
-25
+6
-4
+2
+1
-1
+3
-1
+1
-1
+16
-5
+10
-4
+13
+12
+2
-2
+1
-1
+51
-1
+1
-1
+1
-1
+2
-2
+1
-1
+4
-4
+1
-1
+197
-88
+8
~
BUILD.txt
~
Dockerfile
~
VERSION
~
extsrc/shell.c
~
extsrc/sqlite3.c
~
extsrc/sqlite3.h
~
fossil.1
~
skins/darkmode/css.txt
~
skins/default/css.txt
~
src/add.c
~
src/allrepo.c
~
src/attach.c
~
src/bisect.c
~
src/blob.c
~
src/branch.c
~
src/chat.c
~
src/checkin.c
~
src/checkout.c
~
src/db.c
~
src/default.css
~
src/delta.c
~
src/descendants.c
~
src/diff.c
~
src/diff.tcl
~
src/diffcmd.c
~
src/dispatch.c
~
src/doc.c
~
src/encode.c
~
src/event.c
~
src/file.c
~
src/file.c
~
src/forum.c
~
src/fossil.diff.js
~
src/fossil.dom.js
~
src/fossil.page.chat.js
~
src/graph.c
~
src/http.c
~
src/http_transport.c
~
src/info.c
~
src/interwiki.c
~
src/login.c
~
src/main.c
~
src/main.mk
~
src/match.c
~
src/merge.c
~
src/merge.tcl
~
src/merge3.c
~
src/name.c
~
src/patch.c
~
src/path.c
~
src/printf.c
~
src/rebuild.c
~
src/schema.c
~
src/setup.c
~
src/setupuser.c
~
src/sitemap.c
~
src/statrep.c
~
src/style.c
~
src/timeline.c
~
src/tkt.c
~
src/update.c
~
src/wiki.c
~
src/winfile.c
~
src/xfer.c
~
test/merge1.test
~
test/merge3.test
~
test/merge4.test
~
test/tester.tcl
~
test/update.test
~
tools/makemake.tcl
~
tools/man_page_command_list.tcl
~
tools/translate.c
~
win/Makefile.dmc
~
win/Makefile.mingw
~
win/Makefile.msc
~
www/alerts.md
~
www/caps/ref.html
~
www/changes.wiki
~
www/fossil-is-not-relational.md
~
www/fossil-v-git.wiki
~
www/gitusers.md
~
www/glossary.md
~
www/index.wiki
~
www/qandc.wiki
~
www/ssl-server.md
~
www/sync.wiki
+1
-1
| --- BUILD.txt | ||
| +++ BUILD.txt | ||
| @@ -42,11 +42,11 @@ | ||
| 42 | 42 | mkdir build |
| 43 | 43 | cd build |
| 44 | 44 | ../configure |
| 45 | 45 | make |
| 46 | 46 | |
| 47 | -This will now keep all generates files separate from the maintained | |
| 47 | +This will now keep all generated files separate from the maintained | |
| 48 | 48 | source code. |
| 49 | 49 | |
| 50 | 50 | -------------------------------------------------------------------------- |
| 51 | 51 | |
| 52 | 52 | Here are some notes on what is happening behind the scenes: |
| 53 | 53 |
| --- BUILD.txt | |
| +++ BUILD.txt | |
| @@ -42,11 +42,11 @@ | |
| 42 | mkdir build |
| 43 | cd build |
| 44 | ../configure |
| 45 | make |
| 46 | |
| 47 | This will now keep all generates files separate from the maintained |
| 48 | source code. |
| 49 | |
| 50 | -------------------------------------------------------------------------- |
| 51 | |
| 52 | Here are some notes on what is happening behind the scenes: |
| 53 |
| --- BUILD.txt | |
| +++ BUILD.txt | |
| @@ -42,11 +42,11 @@ | |
| 42 | mkdir build |
| 43 | cd build |
| 44 | ../configure |
| 45 | make |
| 46 | |
| 47 | This will now keep all generated files separate from the maintained |
| 48 | source code. |
| 49 | |
| 50 | -------------------------------------------------------------------------- |
| 51 | |
| 52 | Here are some notes on what is happening behind the scenes: |
| 53 |
+6
-6
| --- Dockerfile | ||
| +++ Dockerfile | ||
| @@ -3,19 +3,19 @@ | ||
| 3 | 3 | |
| 4 | 4 | ## --------------------------------------------------------------------- |
| 5 | 5 | ## STAGE 1: Build static Fossil binary |
| 6 | 6 | ## --------------------------------------------------------------------- |
| 7 | 7 | |
| 8 | -### We aren't pinning to a more stable version of Alpine because we want | |
| 8 | +### We don't pin a more stable version of our base layer because we want | |
| 9 | 9 | ### to build with the latest tools and libraries available in case they |
| 10 | 10 | ### fixed something that matters to us since the last build. Everything |
| 11 | 11 | ### below depends on this layer, and so, alas, we toss this container's |
| 12 | 12 | ### cache on Alpine's release schedule, roughly once a month. |
| 13 | 13 | FROM alpine:latest AS bld |
| 14 | 14 | WORKDIR /fsl |
| 15 | 15 | |
| 16 | -### Bake the basic Alpine Linux into a base layer so it only changes | |
| 16 | +### Bake the build-time userland into a base layer so it only changes | |
| 17 | 17 | ### when the upstream image is updated or we change the package set. |
| 18 | 18 | RUN set -x \ |
| 19 | 19 | && apk update \ |
| 20 | 20 | && apk upgrade --no-cache \ |
| 21 | 21 | && apk add --no-cache \ |
| @@ -23,19 +23,19 @@ | ||
| 23 | 23 | linux-headers musl-dev \ |
| 24 | 24 | openssl-dev openssl-libs-static \ |
| 25 | 25 | zlib-dev zlib-static |
| 26 | 26 | |
| 27 | 27 | ### Build Fossil as a separate layer so we don't have to rebuild the |
| 28 | -### Alpine environment for each iteration of Fossil's dev cycle. | |
| 28 | +### userland for each iteration of Fossil's dev cycle. | |
| 29 | 29 | ### |
| 30 | 30 | ### We must cope with a bizarre ADD misfeature here: it unpacks tarballs |
| 31 | 31 | ### automatically when you give it a local file name but not if you give |
| 32 | 32 | ### it a /tarball URL! It matters because we default to a URL in case |
| 33 | 33 | ### you're building outside a Fossil checkout, but when building via the |
| 34 | -### container-image target, we avoid a costly hit on fossil-scm.org | |
| 35 | -### by leveraging its DVCS nature via the "tarball" command and passing | |
| 36 | -### the resulting file's name in. | |
| 34 | +### container-image target, we avoid a costly hit on fossil-scm.org by | |
| 35 | +### leveraging its DVCS nature via the "tarball" command and passing the | |
| 36 | +### resulting file's name in. | |
| 37 | 37 | ARG FSLCFG="" |
| 38 | 38 | ARG FSLVER="trunk" |
| 39 | 39 | ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}" |
| 40 | 40 | ENV FSLSTB=/fsl/src.tar.gz |
| 41 | 41 | ADD $FSLURL $FSLSTB |
| 42 | 42 |
| --- Dockerfile | |
| +++ Dockerfile | |
| @@ -3,19 +3,19 @@ | |
| 3 | |
| 4 | ## --------------------------------------------------------------------- |
| 5 | ## STAGE 1: Build static Fossil binary |
| 6 | ## --------------------------------------------------------------------- |
| 7 | |
| 8 | ### We aren't pinning to a more stable version of Alpine because we want |
| 9 | ### to build with the latest tools and libraries available in case they |
| 10 | ### fixed something that matters to us since the last build. Everything |
| 11 | ### below depends on this layer, and so, alas, we toss this container's |
| 12 | ### cache on Alpine's release schedule, roughly once a month. |
| 13 | FROM alpine:latest AS bld |
| 14 | WORKDIR /fsl |
| 15 | |
| 16 | ### Bake the basic Alpine Linux into a base layer so it only changes |
| 17 | ### when the upstream image is updated or we change the package set. |
| 18 | RUN set -x \ |
| 19 | && apk update \ |
| 20 | && apk upgrade --no-cache \ |
| 21 | && apk add --no-cache \ |
| @@ -23,19 +23,19 @@ | |
| 23 | linux-headers musl-dev \ |
| 24 | openssl-dev openssl-libs-static \ |
| 25 | zlib-dev zlib-static |
| 26 | |
| 27 | ### Build Fossil as a separate layer so we don't have to rebuild the |
| 28 | ### Alpine environment for each iteration of Fossil's dev cycle. |
| 29 | ### |
| 30 | ### We must cope with a bizarre ADD misfeature here: it unpacks tarballs |
| 31 | ### automatically when you give it a local file name but not if you give |
| 32 | ### it a /tarball URL! It matters because we default to a URL in case |
| 33 | ### you're building outside a Fossil checkout, but when building via the |
| 34 | ### container-image target, we avoid a costly hit on fossil-scm.org |
| 35 | ### by leveraging its DVCS nature via the "tarball" command and passing |
| 36 | ### the resulting file's name in. |
| 37 | ARG FSLCFG="" |
| 38 | ARG FSLVER="trunk" |
| 39 | ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}" |
| 40 | ENV FSLSTB=/fsl/src.tar.gz |
| 41 | ADD $FSLURL $FSLSTB |
| 42 |
| --- Dockerfile | |
| +++ Dockerfile | |
| @@ -3,19 +3,19 @@ | |
| 3 | |
| 4 | ## --------------------------------------------------------------------- |
| 5 | ## STAGE 1: Build static Fossil binary |
| 6 | ## --------------------------------------------------------------------- |
| 7 | |
| 8 | ### We don't pin a more stable version of our base layer because we want |
| 9 | ### to build with the latest tools and libraries available in case they |
| 10 | ### fixed something that matters to us since the last build. Everything |
| 11 | ### below depends on this layer, and so, alas, we toss this container's |
| 12 | ### cache on Alpine's release schedule, roughly once a month. |
| 13 | FROM alpine:latest AS bld |
| 14 | WORKDIR /fsl |
| 15 | |
| 16 | ### Bake the build-time userland into a base layer so it only changes |
| 17 | ### when the upstream image is updated or we change the package set. |
| 18 | RUN set -x \ |
| 19 | && apk update \ |
| 20 | && apk upgrade --no-cache \ |
| 21 | && apk add --no-cache \ |
| @@ -23,19 +23,19 @@ | |
| 23 | linux-headers musl-dev \ |
| 24 | openssl-dev openssl-libs-static \ |
| 25 | zlib-dev zlib-static |
| 26 | |
| 27 | ### Build Fossil as a separate layer so we don't have to rebuild the |
| 28 | ### userland for each iteration of Fossil's dev cycle. |
| 29 | ### |
| 30 | ### We must cope with a bizarre ADD misfeature here: it unpacks tarballs |
| 31 | ### automatically when you give it a local file name but not if you give |
| 32 | ### it a /tarball URL! It matters because we default to a URL in case |
| 33 | ### you're building outside a Fossil checkout, but when building via the |
| 34 | ### container-image target, we avoid a costly hit on fossil-scm.org by |
| 35 | ### leveraging its DVCS nature via the "tarball" command and passing the |
| 36 | ### resulting file's name in. |
| 37 | ARG FSLCFG="" |
| 38 | ARG FSLVER="trunk" |
| 39 | ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}" |
| 40 | ENV FSLSTB=/fsl/src.tar.gz |
| 41 | ADD $FSLURL $FSLSTB |
| 42 |
M
VERSION
+1
-1
| --- VERSION | ||
| +++ VERSION | ||
| @@ -1,1 +1,1 @@ | ||
| 1 | -2.25 | |
| 1 | +2.26 | |
| 2 | 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.25 |
| 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.26 |
| 2 |
+448
-156
| --- extsrc/shell.c | ||
| +++ extsrc/shell.c | ||
| @@ -120,13 +120,10 @@ | ||
| 120 | 120 | #include <math.h> |
| 121 | 121 | #include "sqlite3.h" |
| 122 | 122 | typedef sqlite3_int64 i64; |
| 123 | 123 | typedef sqlite3_uint64 u64; |
| 124 | 124 | typedef unsigned char u8; |
| 125 | -#if SQLITE_USER_AUTHENTICATION | |
| 126 | -# include "sqlite3userauth.h" | |
| 127 | -#endif | |
| 128 | 125 | #include <ctype.h> |
| 129 | 126 | #include <stdarg.h> |
| 130 | 127 | |
| 131 | 128 | #if !defined(_WIN32) && !defined(WIN32) |
| 132 | 129 | # include <signal.h> |
| @@ -408,21 +405,21 @@ | ||
| 408 | 405 | wchar_t *b1, *b2; |
| 409 | 406 | int sz1, sz2; |
| 410 | 407 | |
| 411 | 408 | sz1 = (int)strlen(zFilename); |
| 412 | 409 | sz2 = (int)strlen(zMode); |
| 413 | - b1 = malloc( (sz1+1)*sizeof(b1[0]) ); | |
| 414 | - b2 = malloc( (sz2+1)*sizeof(b1[0]) ); | |
| 410 | + b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) ); | |
| 411 | + b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) ); | |
| 415 | 412 | if( b1 && b2 ){ |
| 416 | 413 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1); |
| 417 | 414 | b1[sz1] = 0; |
| 418 | 415 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 419 | 416 | b2[sz2] = 0; |
| 420 | 417 | fp = _wfopen(b1, b2); |
| 421 | 418 | } |
| 422 | - free(b1); | |
| 423 | - free(b2); | |
| 419 | + sqlite3_free(b1); | |
| 420 | + sqlite3_free(b2); | |
| 424 | 421 | simBinaryOther = 0; |
| 425 | 422 | return fp; |
| 426 | 423 | } |
| 427 | 424 | |
| 428 | 425 | |
| @@ -434,21 +431,21 @@ | ||
| 434 | 431 | wchar_t *b1, *b2; |
| 435 | 432 | int sz1, sz2; |
| 436 | 433 | |
| 437 | 434 | sz1 = (int)strlen(zCommand); |
| 438 | 435 | sz2 = (int)strlen(zMode); |
| 439 | - b1 = malloc( (sz1+1)*sizeof(b1[0]) ); | |
| 440 | - b2 = malloc( (sz2+1)*sizeof(b1[0]) ); | |
| 436 | + b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) ); | |
| 437 | + b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) ); | |
| 441 | 438 | if( b1 && b2 ){ |
| 442 | 439 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1); |
| 443 | 440 | b1[sz1] = 0; |
| 444 | 441 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 445 | 442 | b2[sz2] = 0; |
| 446 | 443 | fp = _wpopen(b1, b2); |
| 447 | 444 | } |
| 448 | - free(b1); | |
| 449 | - free(b2); | |
| 445 | + sqlite3_free(b1); | |
| 446 | + sqlite3_free(b2); | |
| 450 | 447 | return fp; |
| 451 | 448 | } |
| 452 | 449 | |
| 453 | 450 | /* |
| 454 | 451 | ** Work-alike for fgets() from the standard C library. |
| @@ -458,16 +455,26 @@ | ||
| 458 | 455 | /* When reading from the command-prompt in Windows, it is necessary |
| 459 | 456 | ** to use _O_WTEXT input mode to read UTF-16 characters, then translate |
| 460 | 457 | ** that into UTF-8. Otherwise, non-ASCII characters all get translated |
| 461 | 458 | ** into '?'. |
| 462 | 459 | */ |
| 463 | - wchar_t *b1 = malloc( sz*sizeof(wchar_t) ); | |
| 460 | + wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) ); | |
| 464 | 461 | if( b1==0 ) return 0; |
| 465 | - _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT); | |
| 466 | - if( fgetws(b1, sz/4, in)==0 ){ | |
| 467 | - sqlite3_free(b1); | |
| 468 | - return 0; | |
| 462 | +#ifndef SQLITE_USE_STDIO_FOR_CONSOLE | |
| 463 | + DWORD nRead = 0; | |
| 464 | + if( IsConsole(in) | |
| 465 | + && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0) | |
| 466 | + ){ | |
| 467 | + b1[nRead] = 0; | |
| 468 | + }else | |
| 469 | +#endif | |
| 470 | + { | |
| 471 | + _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT); | |
| 472 | + if( fgetws(b1, sz/4, in)==0 ){ | |
| 473 | + sqlite3_free(b1); | |
| 474 | + return 0; | |
| 475 | + } | |
| 469 | 476 | } |
| 470 | 477 | WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0); |
| 471 | 478 | sqlite3_free(b1); |
| 472 | 479 | return buf; |
| 473 | 480 | }else{ |
| @@ -519,24 +526,37 @@ | ||
| 519 | 526 | if( !UseWtextForOutput(out) ){ |
| 520 | 527 | /* Writing to a file or other destination, just write bytes without |
| 521 | 528 | ** any translation. */ |
| 522 | 529 | return fputs(z, out); |
| 523 | 530 | }else{ |
| 524 | - /* When writing to the command-prompt in Windows, it is necessary | |
| 525 | - ** to use O_U8TEXT to render Unicode U+0080 and greater. Go ahead | |
| 526 | - ** use O_U8TEXT for everything in text mode. | |
| 531 | + /* One must use UTF16 in order to get unicode support when writing | |
| 532 | + ** to the console on Windows. | |
| 527 | 533 | */ |
| 528 | 534 | int sz = (int)strlen(z); |
| 529 | - wchar_t *b1 = malloc( (sz+1)*sizeof(wchar_t) ); | |
| 535 | + wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) ); | |
| 530 | 536 | if( b1==0 ) return 0; |
| 531 | 537 | sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz); |
| 532 | 538 | b1[sz] = 0; |
| 533 | - _setmode(_fileno(out), _O_U8TEXT); | |
| 534 | - if( UseBinaryWText(out) ){ | |
| 535 | - piecemealOutput(b1, sz, out); | |
| 536 | - }else{ | |
| 537 | - fputws(b1, out); | |
| 539 | + | |
| 540 | +#ifndef SQLITE_STDIO_FOR_CONSOLE | |
| 541 | + DWORD nWr = 0; | |
| 542 | + if( IsConsole(out) | |
| 543 | + && WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0) | |
| 544 | + ){ | |
| 545 | + /* If writing to the console, then the WriteConsoleW() is all we | |
| 546 | + ** need to do. */ | |
| 547 | + }else | |
| 548 | +#endif | |
| 549 | + { | |
| 550 | + /* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined | |
| 551 | + ** then write using the standard library. */ | |
| 552 | + _setmode(_fileno(out), _O_U8TEXT); | |
| 553 | + if( UseBinaryWText(out) ){ | |
| 554 | + piecemealOutput(b1, sz, out); | |
| 555 | + }else{ | |
| 556 | + fputws(b1, out); | |
| 557 | + } | |
| 538 | 558 | } |
| 539 | 559 | sqlite3_free(b1); |
| 540 | 560 | return 0; |
| 541 | 561 | } |
| 542 | 562 | } |
| @@ -2347,11 +2367,11 @@ | ||
| 2347 | 2367 | ** |
| 2348 | 2368 | ****************************************************************************** |
| 2349 | 2369 | ** |
| 2350 | 2370 | ** This SQLite extension implements functions that compute SHA3 hashes |
| 2351 | 2371 | ** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. |
| 2352 | -** Two SQL functions are implemented: | |
| 2372 | +** Three SQL functions are implemented: | |
| 2353 | 2373 | ** |
| 2354 | 2374 | ** sha3(X,SIZE) |
| 2355 | 2375 | ** sha3_agg(Y,SIZE) |
| 2356 | 2376 | ** sha3_query(Z,SIZE) |
| 2357 | 2377 | ** |
| @@ -5070,14 +5090,14 @@ | ||
| 5070 | 5090 | char **pzErrMsg, |
| 5071 | 5091 | const sqlite3_api_routines *pApi |
| 5072 | 5092 | ){ |
| 5073 | 5093 | int rc = SQLITE_OK; |
| 5074 | 5094 | unsigned int i; |
| 5075 | -#if defined(SQLITE3_H) || defined(SQLITE_STATIC_PERCENTILE) | |
| 5076 | - (void)pApi; /* Unused parameter */ | |
| 5077 | -#else | |
| 5095 | +#ifdef SQLITE3EXT_H | |
| 5078 | 5096 | SQLITE_EXTENSION_INIT2(pApi); |
| 5097 | +#else | |
| 5098 | + (void)pApi; /* Unused parameter */ | |
| 5079 | 5099 | #endif |
| 5080 | 5100 | (void)pzErrMsg; /* Unused parameter */ |
| 5081 | 5101 | for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){ |
| 5082 | 5102 | rc = sqlite3_create_window_function(db, |
| 5083 | 5103 | aPercentFunc[i].zName, |
| @@ -6831,35 +6851,41 @@ | ||
| 6831 | 6851 | idxNum |= 0x40; |
| 6832 | 6852 | } |
| 6833 | 6853 | continue; |
| 6834 | 6854 | } |
| 6835 | 6855 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6836 | - if( pConstraint->iColumn==SERIES_COLUMN_VALUE ){ | |
| 6856 | + if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){ | |
| 6837 | 6857 | switch( op ){ |
| 6838 | 6858 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6839 | 6859 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6840 | 6860 | idxNum |= 0x0080; |
| 6841 | 6861 | idxNum &= ~0x3300; |
| 6842 | 6862 | aIdx[5] = i; |
| 6843 | 6863 | aIdx[6] = -1; |
| 6864 | +#ifndef ZERO_ARGUMENT_GENERATE_SERIES | |
| 6844 | 6865 | bStartSeen = 1; |
| 6866 | +#endif | |
| 6845 | 6867 | break; |
| 6846 | 6868 | } |
| 6847 | 6869 | case SQLITE_INDEX_CONSTRAINT_GE: { |
| 6848 | 6870 | if( idxNum & 0x0080 ) break; |
| 6849 | 6871 | idxNum |= 0x0100; |
| 6850 | 6872 | idxNum &= ~0x0200; |
| 6851 | 6873 | aIdx[5] = i; |
| 6874 | +#ifndef ZERO_ARGUMENT_GENERATE_SERIES | |
| 6852 | 6875 | bStartSeen = 1; |
| 6876 | +#endif | |
| 6853 | 6877 | break; |
| 6854 | 6878 | } |
| 6855 | 6879 | case SQLITE_INDEX_CONSTRAINT_GT: { |
| 6856 | 6880 | if( idxNum & 0x0080 ) break; |
| 6857 | 6881 | idxNum |= 0x0200; |
| 6858 | 6882 | idxNum &= ~0x0100; |
| 6859 | 6883 | aIdx[5] = i; |
| 6884 | +#ifndef ZERO_ARGUMENT_GENERATE_SERIES | |
| 6860 | 6885 | bStartSeen = 1; |
| 6886 | +#endif | |
| 6861 | 6887 | break; |
| 6862 | 6888 | } |
| 6863 | 6889 | case SQLITE_INDEX_CONSTRAINT_LE: { |
| 6864 | 6890 | if( idxNum & 0x0080 ) break; |
| 6865 | 6891 | idxNum |= 0x1000; |
| @@ -14167,11 +14193,11 @@ | ||
| 14167 | 14193 | /* A view. Or a trigger on a view. */ |
| 14168 | 14194 | if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg); |
| 14169 | 14195 | }else{ |
| 14170 | 14196 | IdxTable *pTab; |
| 14171 | 14197 | rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); |
| 14172 | - if( rc==SQLITE_OK ){ | |
| 14198 | + if( rc==SQLITE_OK && ALWAYS(pTab!=0) ){ | |
| 14173 | 14199 | int i; |
| 14174 | 14200 | char *zInner = 0; |
| 14175 | 14201 | char *zOuter = 0; |
| 14176 | 14202 | pTab->pNext = p->pTable; |
| 14177 | 14203 | p->pTable = pTab; |
| @@ -16235,11 +16261,31 @@ | ||
| 16235 | 16261 | ** |
| 16236 | 16262 | ** Similar compiler commands will work on different systems. The key |
| 16237 | 16263 | ** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that |
| 16238 | 16264 | ** the shell.c source file will know to include the -vfstrace command-line |
| 16239 | 16265 | ** option and (2) you must compile and link the three source files |
| 16240 | -** shell,c, test_vfstrace.c, and sqlite3.c. | |
| 16266 | +** shell,c, test_vfstrace.c, and sqlite3.c. | |
| 16267 | +** | |
| 16268 | +** RUNTIME CONTROL OF VFSTRACE OUTPUT | |
| 16269 | +** | |
| 16270 | +** The application can use the "vfstrace" pragma to control which VFS | |
| 16271 | +** APIs are traced. To disable all output: | |
| 16272 | +** | |
| 16273 | +** PRAGMA vfstrace('-all'); | |
| 16274 | +** | |
| 16275 | +** To enable all output (which is the default setting): | |
| 16276 | +** | |
| 16277 | +** PRAGMA vfstrace('+all'); | |
| 16278 | +** | |
| 16279 | +** Individual APIs can be enabled or disabled by name, with or without | |
| 16280 | +** the initial "x" character. For example, to set up for tracing lock | |
| 16281 | +** primatives only: | |
| 16282 | +** | |
| 16283 | +** PRAGMA vfstrace('-all, +Lock,Unlock,ShmLock'); | |
| 16284 | +** | |
| 16285 | +** The argument to the vfstrace pragma ignores capitalization and any | |
| 16286 | +** characters other than alphabetics, '+', and '-'. | |
| 16241 | 16287 | */ |
| 16242 | 16288 | #include <stdlib.h> |
| 16243 | 16289 | #include <string.h> |
| 16244 | 16290 | /* #include "sqlite3.h" */ |
| 16245 | 16291 | |
| @@ -16249,10 +16295,12 @@ | ||
| 16249 | 16295 | */ |
| 16250 | 16296 | typedef struct vfstrace_info vfstrace_info; |
| 16251 | 16297 | struct vfstrace_info { |
| 16252 | 16298 | sqlite3_vfs *pRootVfs; /* The underlying real VFS */ |
| 16253 | 16299 | int (*xOut)(const char*, void*); /* Send output here */ |
| 16300 | + unsigned int mTrace; /* Mask of interfaces to trace */ | |
| 16301 | + u8 bOn; /* Tracing on/off */ | |
| 16254 | 16302 | void *pOutArg; /* First argument to xOut */ |
| 16255 | 16303 | const char *zVfsName; /* Name of this trace-VFS */ |
| 16256 | 16304 | sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */ |
| 16257 | 16305 | }; |
| 16258 | 16306 | |
| @@ -16265,10 +16313,42 @@ | ||
| 16265 | 16313 | vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */ |
| 16266 | 16314 | const char *zFName; /* Base name of the file */ |
| 16267 | 16315 | sqlite3_file *pReal; /* The real underlying file */ |
| 16268 | 16316 | }; |
| 16269 | 16317 | |
| 16318 | +/* | |
| 16319 | +** Bit values for vfstrace_info.mTrace. | |
| 16320 | +*/ | |
| 16321 | +#define VTR_CLOSE 0x00000001 | |
| 16322 | +#define VTR_READ 0x00000002 | |
| 16323 | +#define VTR_WRITE 0x00000004 | |
| 16324 | +#define VTR_TRUNC 0x00000008 | |
| 16325 | +#define VTR_SYNC 0x00000010 | |
| 16326 | +#define VTR_FSIZE 0x00000020 | |
| 16327 | +#define VTR_LOCK 0x00000040 | |
| 16328 | +#define VTR_UNLOCK 0x00000080 | |
| 16329 | +#define VTR_CRL 0x00000100 | |
| 16330 | +#define VTR_FCTRL 0x00000200 | |
| 16331 | +#define VTR_SECSZ 0x00000400 | |
| 16332 | +#define VTR_DEVCHAR 0x00000800 | |
| 16333 | +#define VTR_SHMLOCK 0x00001000 | |
| 16334 | +#define VTR_SHMMAP 0x00002000 | |
| 16335 | +#define VTR_SHMBAR 0x00004000 | |
| 16336 | +#define VTR_SHMUNMAP 0x00008000 | |
| 16337 | +#define VTR_OPEN 0x00010000 | |
| 16338 | +#define VTR_DELETE 0x00020000 | |
| 16339 | +#define VTR_ACCESS 0x00040000 | |
| 16340 | +#define VTR_FULLPATH 0x00080000 | |
| 16341 | +#define VTR_DLOPEN 0x00100000 | |
| 16342 | +#define VTR_DLERR 0x00200000 | |
| 16343 | +#define VTR_DLSYM 0x00400000 | |
| 16344 | +#define VTR_DLCLOSE 0x00800000 | |
| 16345 | +#define VTR_RAND 0x01000000 | |
| 16346 | +#define VTR_SLEEP 0x02000000 | |
| 16347 | +#define VTR_CURTIME 0x04000000 | |
| 16348 | +#define VTR_LASTERR 0x08000000 | |
| 16349 | + | |
| 16270 | 16350 | /* |
| 16271 | 16351 | ** Method declarations for vfstrace_file. |
| 16272 | 16352 | */ |
| 16273 | 16353 | static int vfstraceClose(sqlite3_file*); |
| 16274 | 16354 | static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); |
| @@ -16329,15 +16409,17 @@ | ||
| 16329 | 16409 | const char *zFormat, |
| 16330 | 16410 | ... |
| 16331 | 16411 | ){ |
| 16332 | 16412 | va_list ap; |
| 16333 | 16413 | char *zMsg; |
| 16334 | - va_start(ap, zFormat); | |
| 16335 | - zMsg = sqlite3_vmprintf(zFormat, ap); | |
| 16336 | - va_end(ap); | |
| 16337 | - pInfo->xOut(zMsg, pInfo->pOutArg); | |
| 16338 | - sqlite3_free(zMsg); | |
| 16414 | + if( pInfo->bOn ){ | |
| 16415 | + va_start(ap, zFormat); | |
| 16416 | + zMsg = sqlite3_vmprintf(zFormat, ap); | |
| 16417 | + va_end(ap); | |
| 16418 | + pInfo->xOut(zMsg, pInfo->pOutArg); | |
| 16419 | + sqlite3_free(zMsg); | |
| 16420 | + } | |
| 16339 | 16421 | } |
| 16340 | 16422 | |
| 16341 | 16423 | /* |
| 16342 | 16424 | ** Try to convert an error code into a symbolic name for that error code. |
| 16343 | 16425 | */ |
| @@ -16431,18 +16513,26 @@ | ||
| 16431 | 16513 | int i = *pI; |
| 16432 | 16514 | while( zAppend[0] ){ z[i++] = *(zAppend++); } |
| 16433 | 16515 | z[i] = 0; |
| 16434 | 16516 | *pI = i; |
| 16435 | 16517 | } |
| 16518 | + | |
| 16519 | +/* | |
| 16520 | +** Turn tracing output on or off according to mMask. | |
| 16521 | +*/ | |
| 16522 | +static void vfstraceOnOff(vfstrace_info *pInfo, unsigned int mMask){ | |
| 16523 | + pInfo->bOn = (pInfo->mTrace & mMask)!=0; | |
| 16524 | +} | |
| 16436 | 16525 | |
| 16437 | 16526 | /* |
| 16438 | 16527 | ** Close an vfstrace-file. |
| 16439 | 16528 | */ |
| 16440 | 16529 | static int vfstraceClose(sqlite3_file *pFile){ |
| 16441 | 16530 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16442 | 16531 | vfstrace_info *pInfo = p->pInfo; |
| 16443 | 16532 | int rc; |
| 16533 | + vfstraceOnOff(pInfo, VTR_CLOSE); | |
| 16444 | 16534 | vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName); |
| 16445 | 16535 | rc = p->pReal->pMethods->xClose(p->pReal); |
| 16446 | 16536 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16447 | 16537 | if( rc==SQLITE_OK ){ |
| 16448 | 16538 | sqlite3_free((void*)p->base.pMethods); |
| @@ -16461,10 +16551,11 @@ | ||
| 16461 | 16551 | sqlite_int64 iOfst |
| 16462 | 16552 | ){ |
| 16463 | 16553 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16464 | 16554 | vfstrace_info *pInfo = p->pInfo; |
| 16465 | 16555 | int rc; |
| 16556 | + vfstraceOnOff(pInfo, VTR_READ); | |
| 16466 | 16557 | vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)", |
| 16467 | 16558 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16468 | 16559 | rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); |
| 16469 | 16560 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16470 | 16561 | return rc; |
| @@ -16480,10 +16571,11 @@ | ||
| 16480 | 16571 | sqlite_int64 iOfst |
| 16481 | 16572 | ){ |
| 16482 | 16573 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16483 | 16574 | vfstrace_info *pInfo = p->pInfo; |
| 16484 | 16575 | int rc; |
| 16576 | + vfstraceOnOff(pInfo, VTR_WRITE); | |
| 16485 | 16577 | vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)", |
| 16486 | 16578 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16487 | 16579 | rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); |
| 16488 | 16580 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16489 | 16581 | return rc; |
| @@ -16494,10 +16586,11 @@ | ||
| 16494 | 16586 | */ |
| 16495 | 16587 | static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){ |
| 16496 | 16588 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16497 | 16589 | vfstrace_info *pInfo = p->pInfo; |
| 16498 | 16590 | int rc; |
| 16591 | + vfstraceOnOff(pInfo, VTR_TRUNC); | |
| 16499 | 16592 | vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName, |
| 16500 | 16593 | size); |
| 16501 | 16594 | rc = p->pReal->pMethods->xTruncate(p->pReal, size); |
| 16502 | 16595 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16503 | 16596 | return rc; |
| @@ -16518,10 +16611,11 @@ | ||
| 16518 | 16611 | else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL"); |
| 16519 | 16612 | if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY"); |
| 16520 | 16613 | if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){ |
| 16521 | 16614 | sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags); |
| 16522 | 16615 | } |
| 16616 | + vfstraceOnOff(pInfo, VTR_SYNC); | |
| 16523 | 16617 | vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16524 | 16618 | &zBuf[1]); |
| 16525 | 16619 | rc = p->pReal->pMethods->xSync(p->pReal, flags); |
| 16526 | 16620 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16527 | 16621 | return rc; |
| @@ -16532,10 +16626,11 @@ | ||
| 16532 | 16626 | */ |
| 16533 | 16627 | static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ |
| 16534 | 16628 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16535 | 16629 | vfstrace_info *pInfo = p->pInfo; |
| 16536 | 16630 | int rc; |
| 16631 | + vfstraceOnOff(pInfo, VTR_FSIZE); | |
| 16537 | 16632 | vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName); |
| 16538 | 16633 | rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); |
| 16539 | 16634 | vfstrace_print_errcode(pInfo, " -> %s,", rc); |
| 16540 | 16635 | vfstrace_printf(pInfo, " size=%lld\n", *pSize); |
| 16541 | 16636 | return rc; |
| @@ -16560,10 +16655,11 @@ | ||
| 16560 | 16655 | */ |
| 16561 | 16656 | static int vfstraceLock(sqlite3_file *pFile, int eLock){ |
| 16562 | 16657 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16563 | 16658 | vfstrace_info *pInfo = p->pInfo; |
| 16564 | 16659 | int rc; |
| 16660 | + vfstraceOnOff(pInfo, VTR_LOCK); | |
| 16565 | 16661 | vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16566 | 16662 | lockName(eLock)); |
| 16567 | 16663 | rc = p->pReal->pMethods->xLock(p->pReal, eLock); |
| 16568 | 16664 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16569 | 16665 | return rc; |
| @@ -16574,10 +16670,11 @@ | ||
| 16574 | 16670 | */ |
| 16575 | 16671 | static int vfstraceUnlock(sqlite3_file *pFile, int eLock){ |
| 16576 | 16672 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16577 | 16673 | vfstrace_info *pInfo = p->pInfo; |
| 16578 | 16674 | int rc; |
| 16675 | + vfstraceOnOff(pInfo, VTR_UNLOCK); | |
| 16579 | 16676 | vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16580 | 16677 | lockName(eLock)); |
| 16581 | 16678 | rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); |
| 16582 | 16679 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16583 | 16680 | return rc; |
| @@ -16588,10 +16685,11 @@ | ||
| 16588 | 16685 | */ |
| 16589 | 16686 | static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){ |
| 16590 | 16687 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16591 | 16688 | vfstrace_info *pInfo = p->pInfo; |
| 16592 | 16689 | int rc; |
| 16690 | + vfstraceOnOff(pInfo, VTR_CRL); | |
| 16593 | 16691 | vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)", |
| 16594 | 16692 | pInfo->zVfsName, p->zFName); |
| 16595 | 16693 | rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); |
| 16596 | 16694 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16597 | 16695 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16607,10 +16705,11 @@ | ||
| 16607 | 16705 | int rc; |
| 16608 | 16706 | char zBuf[100]; |
| 16609 | 16707 | char zBuf2[100]; |
| 16610 | 16708 | char *zOp; |
| 16611 | 16709 | char *zRVal = 0; |
| 16710 | + vfstraceOnOff(pInfo, VTR_FCTRL); | |
| 16612 | 16711 | switch( op ){ |
| 16613 | 16712 | case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break; |
| 16614 | 16713 | case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break; |
| 16615 | 16714 | case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break; |
| 16616 | 16715 | case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break; |
| @@ -16635,10 +16734,83 @@ | ||
| 16635 | 16734 | case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break; |
| 16636 | 16735 | case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break; |
| 16637 | 16736 | case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break; |
| 16638 | 16737 | case SQLITE_FCNTL_PRAGMA: { |
| 16639 | 16738 | const char *const* a = (const char*const*)pArg; |
| 16739 | + if( a[1] && strcmp(a[1],"vfstrace")==0 && a[2] ){ | |
| 16740 | + const u8 *zArg = (const u8*)a[2]; | |
| 16741 | + if( zArg[0]>='0' && zArg[0]<=9 ){ | |
| 16742 | + pInfo->mTrace = (sqlite3_uint64)strtoll(a[2], 0, 0); | |
| 16743 | + }else{ | |
| 16744 | + static const struct { | |
| 16745 | + const char *z; | |
| 16746 | + unsigned int m; | |
| 16747 | + } aKw[] = { | |
| 16748 | + { "all", 0xffffffff }, | |
| 16749 | + { "close", VTR_CLOSE }, | |
| 16750 | + { "read", VTR_READ }, | |
| 16751 | + { "write", VTR_WRITE }, | |
| 16752 | + { "truncate", VTR_TRUNC }, | |
| 16753 | + { "sync", VTR_SYNC }, | |
| 16754 | + { "filesize", VTR_FSIZE }, | |
| 16755 | + { "lock", VTR_LOCK }, | |
| 16756 | + { "unlock", VTR_UNLOCK }, | |
| 16757 | + { "checkreservedlock", VTR_CRL }, | |
| 16758 | + { "filecontrol", VTR_FCTRL }, | |
| 16759 | + { "sectorsize", VTR_SECSZ }, | |
| 16760 | + { "devicecharacteristics", VTR_DEVCHAR }, | |
| 16761 | + { "shmlock", VTR_SHMLOCK }, | |
| 16762 | + { "shmmap", VTR_SHMMAP }, | |
| 16763 | + { "shmummap", VTR_SHMUNMAP }, | |
| 16764 | + { "shmbarrier", VTR_SHMBAR }, | |
| 16765 | + { "open", VTR_OPEN }, | |
| 16766 | + { "delete", VTR_DELETE }, | |
| 16767 | + { "access", VTR_ACCESS }, | |
| 16768 | + { "fullpathname", VTR_FULLPATH }, | |
| 16769 | + { "dlopen", VTR_DLOPEN }, | |
| 16770 | + { "dlerror", VTR_DLERR }, | |
| 16771 | + { "dlsym", VTR_DLSYM }, | |
| 16772 | + { "dlclose", VTR_DLCLOSE }, | |
| 16773 | + { "randomness", VTR_RAND }, | |
| 16774 | + { "sleep", VTR_SLEEP }, | |
| 16775 | + { "currenttime", VTR_CURTIME }, | |
| 16776 | + { "currenttimeint64", VTR_CURTIME }, | |
| 16777 | + { "getlasterror", VTR_LASTERR }, | |
| 16778 | + }; | |
| 16779 | + int onOff = 1; | |
| 16780 | + while( zArg[0] ){ | |
| 16781 | + int jj, n; | |
| 16782 | + while( zArg[0]!=0 && zArg[0]!='-' && zArg[0]!='+' | |
| 16783 | + && !isalpha(zArg[0]) ) zArg++; | |
| 16784 | + if( zArg[0]==0 ) break; | |
| 16785 | + if( zArg[0]=='-' ){ | |
| 16786 | + onOff = 0; | |
| 16787 | + zArg++; | |
| 16788 | + }else if( zArg[0]=='+' ){ | |
| 16789 | + onOff = 1; | |
| 16790 | + zArg++; | |
| 16791 | + } | |
| 16792 | + while( !isalpha(zArg[0]) ){ | |
| 16793 | + if( zArg[0]==0 ) break; | |
| 16794 | + zArg++; | |
| 16795 | + } | |
| 16796 | + if( zArg[0]=='x' && isalpha(zArg[1]) ) zArg++; | |
| 16797 | + for(n=0; isalpha(zArg[n]); n++){} | |
| 16798 | + for(jj=0; jj<(int)(sizeof(aKw)/sizeof(aKw[0])); jj++){ | |
| 16799 | + if( sqlite3_strnicmp(aKw[jj].z,(const char*)zArg,n)==0 ){ | |
| 16800 | + if( onOff ){ | |
| 16801 | + pInfo->mTrace |= aKw[jj].m; | |
| 16802 | + }else{ | |
| 16803 | + pInfo->mTrace &= ~aKw[jj].m; | |
| 16804 | + } | |
| 16805 | + break; | |
| 16806 | + } | |
| 16807 | + } | |
| 16808 | + zArg += n; | |
| 16809 | + } | |
| 16810 | + } | |
| 16811 | + } | |
| 16640 | 16812 | sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]); |
| 16641 | 16813 | zOp = zBuf; |
| 16642 | 16814 | break; |
| 16643 | 16815 | } |
| 16644 | 16816 | case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break; |
| @@ -16730,10 +16902,11 @@ | ||
| 16730 | 16902 | */ |
| 16731 | 16903 | static int vfstraceSectorSize(sqlite3_file *pFile){ |
| 16732 | 16904 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16733 | 16905 | vfstrace_info *pInfo = p->pInfo; |
| 16734 | 16906 | int rc; |
| 16907 | + vfstraceOnOff(pInfo, VTR_SECSZ); | |
| 16735 | 16908 | vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName); |
| 16736 | 16909 | rc = p->pReal->pMethods->xSectorSize(p->pReal); |
| 16737 | 16910 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16738 | 16911 | return rc; |
| 16739 | 16912 | } |
| @@ -16743,10 +16916,11 @@ | ||
| 16743 | 16916 | */ |
| 16744 | 16917 | static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){ |
| 16745 | 16918 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16746 | 16919 | vfstrace_info *pInfo = p->pInfo; |
| 16747 | 16920 | int rc; |
| 16921 | + vfstraceOnOff(pInfo, VTR_DEVCHAR); | |
| 16748 | 16922 | vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)", |
| 16749 | 16923 | pInfo->zVfsName, p->zFName); |
| 16750 | 16924 | rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); |
| 16751 | 16925 | vfstrace_printf(pInfo, " -> 0x%08x\n", rc); |
| 16752 | 16926 | return rc; |
| @@ -16754,25 +16928,43 @@ | ||
| 16754 | 16928 | |
| 16755 | 16929 | /* |
| 16756 | 16930 | ** Shared-memory operations. |
| 16757 | 16931 | */ |
| 16758 | 16932 | static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ |
| 16933 | + static const char *azLockName[] = { | |
| 16934 | + "WRITE", | |
| 16935 | + "CKPT", | |
| 16936 | + "RECOVER", | |
| 16937 | + "READ0", | |
| 16938 | + "READ1", | |
| 16939 | + "READ2", | |
| 16940 | + "READ3", | |
| 16941 | + "READ4", | |
| 16942 | + }; | |
| 16759 | 16943 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16760 | 16944 | vfstrace_info *pInfo = p->pInfo; |
| 16761 | 16945 | int rc; |
| 16762 | 16946 | char zLck[100]; |
| 16763 | 16947 | int i = 0; |
| 16948 | + vfstraceOnOff(pInfo, VTR_SHMLOCK); | |
| 16764 | 16949 | memcpy(zLck, "|0", 3); |
| 16765 | 16950 | if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK"); |
| 16766 | 16951 | if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK"); |
| 16767 | 16952 | if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED"); |
| 16768 | 16953 | if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE"); |
| 16769 | 16954 | if( flags & ~(0xf) ){ |
| 16770 | 16955 | sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags); |
| 16771 | 16956 | } |
| 16772 | - vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)", | |
| 16773 | - pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]); | |
| 16957 | + if( ofst>=0 && ofst<(int)(sizeof(azLockName)/sizeof(azLockName[0])) ){ | |
| 16958 | + vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d(%s),n=%d,%s)", | |
| 16959 | + pInfo->zVfsName, p->zFName, ofst, azLockName[ofst], | |
| 16960 | + n, &zLck[1]); | |
| 16961 | + }else{ | |
| 16962 | + vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=5d,n=%d,%s)", | |
| 16963 | + pInfo->zVfsName, p->zFName, ofst, | |
| 16964 | + n, &zLck[1]); | |
| 16965 | + } | |
| 16774 | 16966 | rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); |
| 16775 | 16967 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16776 | 16968 | return rc; |
| 16777 | 16969 | } |
| 16778 | 16970 | static int vfstraceShmMap( |
| @@ -16783,26 +16975,29 @@ | ||
| 16783 | 16975 | void volatile **pp |
| 16784 | 16976 | ){ |
| 16785 | 16977 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16786 | 16978 | vfstrace_info *pInfo = p->pInfo; |
| 16787 | 16979 | int rc; |
| 16980 | + vfstraceOnOff(pInfo, VTR_SHMMAP); | |
| 16788 | 16981 | vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)", |
| 16789 | 16982 | pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite); |
| 16790 | 16983 | rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); |
| 16791 | 16984 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16792 | 16985 | return rc; |
| 16793 | 16986 | } |
| 16794 | 16987 | static void vfstraceShmBarrier(sqlite3_file *pFile){ |
| 16795 | 16988 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16796 | 16989 | vfstrace_info *pInfo = p->pInfo; |
| 16990 | + vfstraceOnOff(pInfo, VTR_SHMBAR); | |
| 16797 | 16991 | vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName); |
| 16798 | 16992 | p->pReal->pMethods->xShmBarrier(p->pReal); |
| 16799 | 16993 | } |
| 16800 | 16994 | static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){ |
| 16801 | 16995 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16802 | 16996 | vfstrace_info *pInfo = p->pInfo; |
| 16803 | 16997 | int rc; |
| 16998 | + vfstraceOnOff(pInfo, VTR_SHMUNMAP); | |
| 16804 | 16999 | vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)", |
| 16805 | 17000 | pInfo->zVfsName, p->zFName, delFlag); |
| 16806 | 17001 | rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); |
| 16807 | 17002 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16808 | 17003 | return rc; |
| @@ -16826,10 +17021,11 @@ | ||
| 16826 | 17021 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16827 | 17022 | p->pInfo = pInfo; |
| 16828 | 17023 | p->zFName = zName ? fileTail(zName) : "<temp>"; |
| 16829 | 17024 | p->pReal = (sqlite3_file *)&p[1]; |
| 16830 | 17025 | rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags); |
| 17026 | + vfstraceOnOff(pInfo, VTR_OPEN); | |
| 16831 | 17027 | vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)", |
| 16832 | 17028 | pInfo->zVfsName, p->zFName, flags); |
| 16833 | 17029 | if( p->pReal->pMethods ){ |
| 16834 | 17030 | sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) ); |
| 16835 | 17031 | const sqlite3_io_methods *pSub = p->pReal->pMethods; |
| @@ -16871,10 +17067,11 @@ | ||
| 16871 | 17067 | */ |
| 16872 | 17068 | static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ |
| 16873 | 17069 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16874 | 17070 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16875 | 17071 | int rc; |
| 17072 | + vfstraceOnOff(pInfo, VTR_DELETE); | |
| 16876 | 17073 | vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)", |
| 16877 | 17074 | pInfo->zVfsName, zPath, dirSync); |
| 16878 | 17075 | rc = pRoot->xDelete(pRoot, zPath, dirSync); |
| 16879 | 17076 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16880 | 17077 | return rc; |
| @@ -16891,10 +17088,11 @@ | ||
| 16891 | 17088 | int *pResOut |
| 16892 | 17089 | ){ |
| 16893 | 17090 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16894 | 17091 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16895 | 17092 | int rc; |
| 17093 | + vfstraceOnOff(pInfo, VTR_ACCESS); | |
| 16896 | 17094 | vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)", |
| 16897 | 17095 | pInfo->zVfsName, zPath, flags); |
| 16898 | 17096 | rc = pRoot->xAccess(pRoot, zPath, flags, pResOut); |
| 16899 | 17097 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16900 | 17098 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16913,10 +17111,11 @@ | ||
| 16913 | 17111 | char *zOut |
| 16914 | 17112 | ){ |
| 16915 | 17113 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16916 | 17114 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16917 | 17115 | int rc; |
| 17116 | + vfstraceOnOff(pInfo, VTR_FULLPATH); | |
| 16918 | 17117 | vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")", |
| 16919 | 17118 | pInfo->zVfsName, zPath); |
| 16920 | 17119 | rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut); |
| 16921 | 17120 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16922 | 17121 | vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut); |
| @@ -16927,10 +17126,11 @@ | ||
| 16927 | 17126 | ** Open the dynamic library located at zPath and return a handle. |
| 16928 | 17127 | */ |
| 16929 | 17128 | static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){ |
| 16930 | 17129 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16931 | 17130 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17131 | + vfstraceOnOff(pInfo, VTR_DLOPEN); | |
| 16932 | 17132 | vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath); |
| 16933 | 17133 | return pRoot->xDlOpen(pRoot, zPath); |
| 16934 | 17134 | } |
| 16935 | 17135 | |
| 16936 | 17136 | /* |
| @@ -16939,10 +17139,11 @@ | ||
| 16939 | 17139 | ** with dynamic libraries. |
| 16940 | 17140 | */ |
| 16941 | 17141 | static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ |
| 16942 | 17142 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16943 | 17143 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17144 | + vfstraceOnOff(pInfo, VTR_DLERR); | |
| 16944 | 17145 | vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte); |
| 16945 | 17146 | pRoot->xDlError(pRoot, nByte, zErrMsg); |
| 16946 | 17147 | vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg); |
| 16947 | 17148 | } |
| 16948 | 17149 | |
| @@ -16960,10 +17161,11 @@ | ||
| 16960 | 17161 | ** Close the dynamic library handle pHandle. |
| 16961 | 17162 | */ |
| 16962 | 17163 | static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){ |
| 16963 | 17164 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16964 | 17165 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17166 | + vfstraceOnOff(pInfo, VTR_DLCLOSE); | |
| 16965 | 17167 | vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName); |
| 16966 | 17168 | pRoot->xDlClose(pRoot, pHandle); |
| 16967 | 17169 | } |
| 16968 | 17170 | |
| 16969 | 17171 | /* |
| @@ -16971,10 +17173,11 @@ | ||
| 16971 | 17173 | ** random data. |
| 16972 | 17174 | */ |
| 16973 | 17175 | static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ |
| 16974 | 17176 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16975 | 17177 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17178 | + vfstraceOnOff(pInfo, VTR_RAND); | |
| 16976 | 17179 | vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte); |
| 16977 | 17180 | return pRoot->xRandomness(pRoot, nByte, zBufOut); |
| 16978 | 17181 | } |
| 16979 | 17182 | |
| 16980 | 17183 | /* |
| @@ -16982,34 +17185,52 @@ | ||
| 16982 | 17185 | ** actually slept. |
| 16983 | 17186 | */ |
| 16984 | 17187 | static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){ |
| 16985 | 17188 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16986 | 17189 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17190 | + vfstraceOnOff(pInfo, VTR_SLEEP); | |
| 17191 | + vfstrace_printf(pInfo, "%s.xSleep(%d)\n", pInfo->zVfsName, nMicro); | |
| 16987 | 17192 | return pRoot->xSleep(pRoot, nMicro); |
| 16988 | 17193 | } |
| 16989 | 17194 | |
| 16990 | 17195 | /* |
| 16991 | 17196 | ** Return the current time as a Julian Day number in *pTimeOut. |
| 16992 | 17197 | */ |
| 16993 | 17198 | static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ |
| 16994 | 17199 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16995 | 17200 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16996 | - return pRoot->xCurrentTime(pRoot, pTimeOut); | |
| 17201 | + int rc; | |
| 17202 | + vfstraceOnOff(pInfo, VTR_CURTIME); | |
| 17203 | + vfstrace_printf(pInfo, "%s.xCurrentTime()", pInfo->zVfsName); | |
| 17204 | + rc = pRoot->xCurrentTime(pRoot, pTimeOut); | |
| 17205 | + vfstrace_printf(pInfo, " -> %.17g\n", *pTimeOut); | |
| 17206 | + return rc; | |
| 16997 | 17207 | } |
| 16998 | 17208 | static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ |
| 16999 | 17209 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17000 | 17210 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17001 | - return pRoot->xCurrentTimeInt64(pRoot, pTimeOut); | |
| 17211 | + int rc; | |
| 17212 | + vfstraceOnOff(pInfo, VTR_CURTIME); | |
| 17213 | + vfstrace_printf(pInfo, "%s.xCurrentTimeInt64()", pInfo->zVfsName); | |
| 17214 | + rc = pRoot->xCurrentTimeInt64(pRoot, pTimeOut); | |
| 17215 | + vfstrace_printf(pInfo, " -> %lld\n", *pTimeOut); | |
| 17216 | + return rc; | |
| 17002 | 17217 | } |
| 17003 | 17218 | |
| 17004 | 17219 | /* |
| 17005 | -** Return th3 most recent error code and message | |
| 17220 | +** Return the most recent error code and message | |
| 17006 | 17221 | */ |
| 17007 | -static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){ | |
| 17222 | +static int vfstraceGetLastError(sqlite3_vfs *pVfs, int nErr, char *zErr){ | |
| 17008 | 17223 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17009 | 17224 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17010 | - return pRoot->xGetLastError(pRoot, iErr, zErr); | |
| 17225 | + int rc; | |
| 17226 | + vfstraceOnOff(pInfo, VTR_LASTERR); | |
| 17227 | + vfstrace_printf(pInfo, "%s.xGetLastError(%d,zBuf)", pInfo->zVfsName, nErr); | |
| 17228 | + if( nErr ) zErr[0] = 0; | |
| 17229 | + rc = pRoot->xGetLastError(pRoot, nErr, zErr); | |
| 17230 | + vfstrace_printf(pInfo, " -> zBuf[] = \"%s\", rc = %d\n", nErr?zErr:"", rc); | |
| 17231 | + return rc; | |
| 17011 | 17232 | } |
| 17012 | 17233 | |
| 17013 | 17234 | /* |
| 17014 | 17235 | ** Override system calls. |
| 17015 | 17236 | */ |
| @@ -17099,10 +17320,12 @@ | ||
| 17099 | 17320 | pInfo->pRootVfs = pRoot; |
| 17100 | 17321 | pInfo->xOut = xOut; |
| 17101 | 17322 | pInfo->pOutArg = pOutArg; |
| 17102 | 17323 | pInfo->zVfsName = pNew->zName; |
| 17103 | 17324 | pInfo->pTraceVfs = pNew; |
| 17325 | + pInfo->mTrace = 0xffffffff; | |
| 17326 | + pInfo->bOn = 1; | |
| 17104 | 17327 | vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n", |
| 17105 | 17328 | pInfo->zVfsName, pRoot->zName); |
| 17106 | 17329 | return sqlite3_vfs_register(pNew, makeDefault); |
| 17107 | 17330 | } |
| 17108 | 17331 | |
| @@ -20228,10 +20451,12 @@ | ||
| 20228 | 20451 | apVal[iField] = sqlite3_value_dup( pVal ); |
| 20229 | 20452 | if( apVal[iField]==0 ){ |
| 20230 | 20453 | recoverError(p, SQLITE_NOMEM, 0); |
| 20231 | 20454 | } |
| 20232 | 20455 | p1->nVal = iField+1; |
| 20456 | + }else if( pTab->nCol==0 ){ | |
| 20457 | + p1->nVal = pTab->nCol; | |
| 20233 | 20458 | } |
| 20234 | 20459 | p1->iPrevCell = iCell; |
| 20235 | 20460 | p1->iPrevPage = iPage; |
| 20236 | 20461 | } |
| 20237 | 20462 | }else{ |
| @@ -21935,12 +22160,12 @@ | ||
| 21935 | 22160 | const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){ |
| 21936 | 22161 | int ng = (nAccept<0)? -nAccept : 0; |
| 21937 | 22162 | const char *pcLimit = (nAccept>=0)? z+nAccept : 0; |
| 21938 | 22163 | assert(z!=0); |
| 21939 | 22164 | while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){ |
| 21940 | - char c = *z; | |
| 21941 | - if( (c & 0x80) == 0 ){ | |
| 22165 | + unsigned char c = *(u8*)z; | |
| 22166 | + if( c<0x7f ){ | |
| 21942 | 22167 | if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z; |
| 21943 | 22168 | ++z; /* ASCII */ |
| 21944 | 22169 | }else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */ |
| 21945 | 22170 | else{ |
| 21946 | 22171 | const char *zt = z+1; /* Got lead byte, look at trail bytes.*/ |
| @@ -22004,14 +22229,14 @@ | ||
| 22004 | 22229 | } |
| 22005 | 22230 | sqlite3_fputs(zq, out); |
| 22006 | 22231 | } |
| 22007 | 22232 | |
| 22008 | 22233 | /* |
| 22009 | -** Output the given string as a quoted according to JSON quoting rules. | |
| 22234 | +** Output the given string as quoted according to JSON quoting rules. | |
| 22010 | 22235 | */ |
| 22011 | 22236 | static void output_json_string(FILE *out, const char *z, i64 n){ |
| 22012 | - char c; | |
| 22237 | + unsigned char c; | |
| 22013 | 22238 | static const char *zq = "\""; |
| 22014 | 22239 | static long ctrlMask = ~0L; |
| 22015 | 22240 | static const char *zDQBS = "\"\\"; |
| 22016 | 22241 | const char *pcLimit; |
| 22017 | 22242 | char ace[3] = "\\?"; |
| @@ -22027,11 +22252,11 @@ | ||
| 22027 | 22252 | if( pcEnd > z ){ |
| 22028 | 22253 | sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); |
| 22029 | 22254 | z = pcEnd; |
| 22030 | 22255 | } |
| 22031 | 22256 | if( z >= pcLimit ) break; |
| 22032 | - c = *(z++); | |
| 22257 | + c = (unsigned char)*(z++); | |
| 22033 | 22258 | switch( c ){ |
| 22034 | 22259 | case '"': case '\\': |
| 22035 | 22260 | cbsSay = (char)c; |
| 22036 | 22261 | break; |
| 22037 | 22262 | case '\b': cbsSay = 'b'; break; |
| @@ -22042,12 +22267,12 @@ | ||
| 22042 | 22267 | default: cbsSay = 0; break; |
| 22043 | 22268 | } |
| 22044 | 22269 | if( cbsSay ){ |
| 22045 | 22270 | ace[1] = cbsSay; |
| 22046 | 22271 | sqlite3_fputs(ace, out); |
| 22047 | - }else if( c<=0x1f ){ | |
| 22048 | - sqlite3_fprintf(out, "u%04x", c); | |
| 22272 | + }else if( c<=0x1f || c>=0x7f ){ | |
| 22273 | + sqlite3_fprintf(out, "\\u%04x", c); | |
| 22049 | 22274 | }else{ |
| 22050 | 22275 | ace[1] = (char)c; |
| 22051 | 22276 | sqlite3_fputs(ace+1, out); |
| 22052 | 22277 | } |
| 22053 | 22278 | } |
| @@ -24784,13 +25009,13 @@ | ||
| 24784 | 25009 | if( rc ){ |
| 24785 | 25010 | sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); |
| 24786 | 25011 | }else{ |
| 24787 | 25012 | rc = SQLITE_CORRUPT; |
| 24788 | 25013 | } |
| 24789 | - sqlite3_free(zErr); | |
| 24790 | 25014 | free(zQ2); |
| 24791 | 25015 | } |
| 25016 | + sqlite3_free(zErr); | |
| 24792 | 25017 | return rc; |
| 24793 | 25018 | } |
| 24794 | 25019 | |
| 24795 | 25020 | /* |
| 24796 | 25021 | ** Text of help messages. |
| @@ -24849,10 +25074,11 @@ | ||
| 24849 | 25074 | ".databases List names and files of attached databases", |
| 24850 | 25075 | ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", |
| 24851 | 25076 | #if SQLITE_SHELL_HAVE_RECOVER |
| 24852 | 25077 | ".dbinfo ?DB? Show status information about the database", |
| 24853 | 25078 | #endif |
| 25079 | + ".dbtotxt Hex dump of the database file", | |
| 24854 | 25080 | ".dump ?OBJECTS? Render database content as SQL", |
| 24855 | 25081 | " Options:", |
| 24856 | 25082 | " --data-only Output only INSERT statements", |
| 24857 | 25083 | " --newlines Allow unescaped newline characters in output", |
| 24858 | 25084 | " --nosys Omit system tables (ex: \"sqlite_stat1\")", |
| @@ -25654,11 +25880,12 @@ | ||
| 25654 | 25880 | sqlite3_fprintf(stderr, |
| 25655 | 25881 | "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); |
| 25656 | 25882 | } |
| 25657 | 25883 | } |
| 25658 | 25884 | |
| 25659 | -#if HAVE_READLINE || HAVE_EDITLINE | |
| 25885 | +#if (HAVE_READLINE || HAVE_EDITLINE) \ | |
| 25886 | + && !defined(SQLITE_OMIT_READLINE_COMPLETION) | |
| 25660 | 25887 | /* |
| 25661 | 25888 | ** Readline completion callbacks |
| 25662 | 25889 | */ |
| 25663 | 25890 | static char *readline_completion_generator(const char *text, int state){ |
| 25664 | 25891 | static sqlite3_stmt *pStmt = 0; |
| @@ -25692,19 +25919,26 @@ | ||
| 25692 | 25919 | #elif HAVE_LINENOISE |
| 25693 | 25920 | /* |
| 25694 | 25921 | ** Linenoise completion callback. Note that the 3rd argument is from |
| 25695 | 25922 | ** the "msteveb" version of linenoise, not the "antirez" version. |
| 25696 | 25923 | */ |
| 25697 | -static void linenoise_completion(const char *zLine, linenoiseCompletions *lc, | |
| 25698 | - void *pUserData){ | |
| 25924 | +static void linenoise_completion( | |
| 25925 | + const char *zLine, | |
| 25926 | + linenoiseCompletions *lc | |
| 25927 | +#if HAVE_LINENOISE==2 | |
| 25928 | + ,void *pUserData | |
| 25929 | +#endif | |
| 25930 | +){ | |
| 25699 | 25931 | i64 nLine = strlen(zLine); |
| 25700 | 25932 | i64 i, iStart; |
| 25701 | 25933 | sqlite3_stmt *pStmt = 0; |
| 25702 | 25934 | char *zSql; |
| 25703 | 25935 | char zBuf[1000]; |
| 25704 | 25936 | |
| 25937 | +#if HAVE_LINENOISE==2 | |
| 25705 | 25938 | UNUSED_PARAMETER(pUserData); |
| 25939 | +#endif | |
| 25706 | 25940 | if( nLine>(i64)sizeof(zBuf)-30 ) return; |
| 25707 | 25941 | if( zLine[0]=='.' || zLine[0]=='#') return; |
| 25708 | 25942 | for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} |
| 25709 | 25943 | if( i==nLine-1 ) return; |
| 25710 | 25944 | iStart = i+1; |
| @@ -26390,18 +26624,24 @@ | ||
| 26390 | 26624 | #endif |
| 26391 | 26625 | |
| 26392 | 26626 | /* |
| 26393 | 26627 | ** Run an SQL command and return the single integer result. |
| 26394 | 26628 | */ |
| 26395 | -static int db_int(sqlite3 *db, const char *zSql){ | |
| 26629 | +static int db_int(sqlite3 *db, const char *zSql, ...){ | |
| 26396 | 26630 | sqlite3_stmt *pStmt; |
| 26397 | 26631 | int res = 0; |
| 26398 | - sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); | |
| 26632 | + char *z; | |
| 26633 | + va_list ap; | |
| 26634 | + va_start(ap, zSql); | |
| 26635 | + z = sqlite3_vmprintf(zSql, ap); | |
| 26636 | + va_end(ap); | |
| 26637 | + sqlite3_prepare_v2(db, z, -1, &pStmt, 0); | |
| 26399 | 26638 | if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 26400 | 26639 | res = sqlite3_column_int(pStmt,0); |
| 26401 | 26640 | } |
| 26402 | 26641 | sqlite3_finalize(pStmt); |
| 26642 | + sqlite3_free(z); | |
| 26403 | 26643 | return res; |
| 26404 | 26644 | } |
| 26405 | 26645 | |
| 26406 | 26646 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26407 | 26647 | /* |
| @@ -26500,21 +26740,112 @@ | ||
| 26500 | 26740 | zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); |
| 26501 | 26741 | }else{ |
| 26502 | 26742 | zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); |
| 26503 | 26743 | } |
| 26504 | 26744 | for(i=0; i<ArraySize(aQuery); i++){ |
| 26505 | - char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab); | |
| 26506 | - int val = db_int(p->db, zSql); | |
| 26507 | - sqlite3_free(zSql); | |
| 26745 | + int val = db_int(p->db, aQuery[i].zSql, zSchemaTab); | |
| 26508 | 26746 | sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val); |
| 26509 | 26747 | } |
| 26510 | 26748 | sqlite3_free(zSchemaTab); |
| 26511 | 26749 | sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); |
| 26512 | 26750 | sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion); |
| 26513 | 26751 | return 0; |
| 26514 | 26752 | } |
| 26515 | 26753 | #endif /* SQLITE_SHELL_HAVE_RECOVER */ |
| 26754 | + | |
| 26755 | +/* | |
| 26756 | +** Implementation of the ".dbtotxt" command. | |
| 26757 | +** | |
| 26758 | +** Return 1 on error, 2 to exit, and 0 otherwise. | |
| 26759 | +*/ | |
| 26760 | +static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ | |
| 26761 | + sqlite3_stmt *pStmt = 0; | |
| 26762 | + sqlite3_int64 nPage = 0; | |
| 26763 | + int pgSz = 0; | |
| 26764 | + const char *zTail; | |
| 26765 | + char *zName = 0; | |
| 26766 | + int rc, i, j; | |
| 26767 | + unsigned char bShow[256]; /* Characters ok to display */ | |
| 26768 | + | |
| 26769 | + UNUSED_PARAMETER(nArg); | |
| 26770 | + UNUSED_PARAMETER(azArg); | |
| 26771 | + memset(bShow, '.', sizeof(bShow)); | |
| 26772 | + for(i=' '; i<='~'; i++){ | |
| 26773 | + if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i; | |
| 26774 | + } | |
| 26775 | + rc = sqlite3_prepare_v2(p->db, "PRAGMA page_size", -1, &pStmt, 0); | |
| 26776 | + if( rc ) goto dbtotxt_error; | |
| 26777 | + rc = 0; | |
| 26778 | + if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; | |
| 26779 | + pgSz = sqlite3_column_int(pStmt, 0); | |
| 26780 | + sqlite3_finalize(pStmt); | |
| 26781 | + pStmt = 0; | |
| 26782 | + if( pgSz<512 || pgSz>65536 || (pgSz&(pgSz-1))!=0 ) goto dbtotxt_error; | |
| 26783 | + rc = sqlite3_prepare_v2(p->db, "PRAGMA page_count", -1, &pStmt, 0); | |
| 26784 | + if( rc ) goto dbtotxt_error; | |
| 26785 | + rc = 0; | |
| 26786 | + if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; | |
| 26787 | + nPage = sqlite3_column_int64(pStmt, 0); | |
| 26788 | + sqlite3_finalize(pStmt); | |
| 26789 | + pStmt = 0; | |
| 26790 | + if( nPage<1 ) goto dbtotxt_error; | |
| 26791 | + rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0); | |
| 26792 | + if( rc ) goto dbtotxt_error; | |
| 26793 | + if( sqlite3_step(pStmt)!=SQLITE_ROW ){ | |
| 26794 | + zTail = "unk.db"; | |
| 26795 | + }else{ | |
| 26796 | + const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2); | |
| 26797 | + if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db"; | |
| 26798 | + zTail = strrchr(zFilename, '/'); | |
| 26799 | +#if defined(_WIN32) | |
| 26800 | + if( zTail==0 ) zTail = strrchr(zFilename, '\\'); | |
| 26801 | +#endif | |
| 26802 | + } | |
| 26803 | + zName = strdup(zTail); | |
| 26804 | + shell_check_oom(zName); | |
| 26805 | + sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", | |
| 26806 | + nPage*pgSz, pgSz, zName); | |
| 26807 | + sqlite3_finalize(pStmt); | |
| 26808 | + pStmt = 0; | |
| 26809 | + rc = sqlite3_prepare_v2(p->db, | |
| 26810 | + "SELECT pgno, data FROM sqlite_dbpage ORDER BY pgno", -1, &pStmt, 0); | |
| 26811 | + if( rc ) goto dbtotxt_error; | |
| 26812 | + while( sqlite3_step(pStmt)==SQLITE_ROW ){ | |
| 26813 | + sqlite3_int64 pgno = sqlite3_column_int64(pStmt, 0); | |
| 26814 | + const u8 *aData = sqlite3_column_blob(pStmt, 1); | |
| 26815 | + int seenPageLabel = 0; | |
| 26816 | + for(i=0; i<pgSz; i+=16){ | |
| 26817 | + const u8 *aLine = aData+i; | |
| 26818 | + for(j=0; j<16 && aLine[j]==0; j++){} | |
| 26819 | + if( j==16 ) continue; | |
| 26820 | + if( !seenPageLabel ){ | |
| 26821 | + sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz); | |
| 26822 | + seenPageLabel = 1; | |
| 26823 | + } | |
| 26824 | + sqlite3_fprintf(p->out, "| %5d:", i); | |
| 26825 | + for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); | |
| 26826 | + sqlite3_fprintf(p->out, " "); | |
| 26827 | + for(j=0; j<16; j++){ | |
| 26828 | + unsigned char c = (unsigned char)aLine[j]; | |
| 26829 | + sqlite3_fprintf(p->out, "%c", bShow[c]); | |
| 26830 | + } | |
| 26831 | + sqlite3_fprintf(p->out, "\n"); | |
| 26832 | + } | |
| 26833 | + } | |
| 26834 | + sqlite3_finalize(pStmt); | |
| 26835 | + sqlite3_fprintf(p->out, "| end %s\n", zName); | |
| 26836 | + free(zName); | |
| 26837 | + return 0; | |
| 26838 | + | |
| 26839 | +dbtotxt_error: | |
| 26840 | + if( rc ){ | |
| 26841 | + sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); | |
| 26842 | + } | |
| 26843 | + sqlite3_finalize(pStmt); | |
| 26844 | + free(zName); | |
| 26845 | + return 1; | |
| 26846 | +} | |
| 26516 | 26847 | |
| 26517 | 26848 | /* |
| 26518 | 26849 | ** Print the given string as an error message. |
| 26519 | 26850 | */ |
| 26520 | 26851 | static void shellEmitError(const char *zErr){ |
| @@ -28067,12 +28398,12 @@ | ||
| 28067 | 28398 | }else if( *pDb==0 ){ |
| 28068 | 28399 | return 0; |
| 28069 | 28400 | }else{ |
| 28070 | 28401 | /* Formulate the columns spec, close the DB, zero *pDb. */ |
| 28071 | 28402 | char *zColsSpec = 0; |
| 28072 | - int hasDupes = db_int(*pDb, zHasDupes); | |
| 28073 | - int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; | |
| 28403 | + int hasDupes = db_int(*pDb, "%s", zHasDupes); | |
| 28404 | + int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0; | |
| 28074 | 28405 | if( hasDupes ){ |
| 28075 | 28406 | #ifdef SHELL_COLUMN_RENAME_CLEAN |
| 28076 | 28407 | rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); |
| 28077 | 28408 | rc_err_oom_die(rc); |
| 28078 | 28409 | #endif |
| @@ -28083,11 +28414,11 @@ | ||
| 28083 | 28414 | sqlite3_bind_int(pStmt, 1, nDigits); |
| 28084 | 28415 | rc = sqlite3_step(pStmt); |
| 28085 | 28416 | sqlite3_finalize(pStmt); |
| 28086 | 28417 | if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); |
| 28087 | 28418 | } |
| 28088 | - assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ | |
| 28419 | + assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */ | |
| 28089 | 28420 | rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); |
| 28090 | 28421 | rc_err_oom_die(rc); |
| 28091 | 28422 | rc = sqlite3_step(pStmt); |
| 28092 | 28423 | if( rc==SQLITE_ROW ){ |
| 28093 | 28424 | zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| @@ -28688,10 +29019,14 @@ | ||
| 28688 | 29019 | }else{ |
| 28689 | 29020 | eputz("Usage: .echo on|off\n"); |
| 28690 | 29021 | rc = 1; |
| 28691 | 29022 | } |
| 28692 | 29023 | }else |
| 29024 | + | |
| 29025 | + if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ | |
| 29026 | + rc = shell_dbtotxt_command(p, nArg, azArg); | |
| 29027 | + }else | |
| 28693 | 29028 | |
| 28694 | 29029 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 28695 | 29030 | if( nArg==2 ){ |
| 28696 | 29031 | p->autoEQPtest = 0; |
| 28697 | 29032 | if( p->autoEQPtrace ){ |
| @@ -29129,11 +29464,15 @@ | ||
| 29129 | 29464 | /* Below, resources must be freed before exit. */ |
| 29130 | 29465 | while( (nSkip--)>0 ){ |
| 29131 | 29466 | while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} |
| 29132 | 29467 | } |
| 29133 | 29468 | import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ |
| 29134 | - if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){ | |
| 29469 | + if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) | |
| 29470 | + && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" | |
| 29471 | + " WHERE name=%Q AND type='view'", | |
| 29472 | + zSchema ? zSchema : "main", zTable) | |
| 29473 | + ){ | |
| 29135 | 29474 | /* Table does not exist. Create it. */ |
| 29136 | 29475 | sqlite3 *dbCols = 0; |
| 29137 | 29476 | char *zRenames = 0; |
| 29138 | 29477 | char *zColDefs; |
| 29139 | 29478 | zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", |
| @@ -30201,11 +30540,14 @@ | ||
| 30201 | 30540 | } |
| 30202 | 30541 | close_db(pSrc); |
| 30203 | 30542 | }else |
| 30204 | 30543 | #endif /* !defined(SQLITE_SHELL_FIDDLE) */ |
| 30205 | 30544 | |
| 30206 | - if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ | |
| 30545 | + if( c=='s' && | |
| 30546 | + (cli_strncmp(azArg[0], "scanstats", n)==0 || | |
| 30547 | + cli_strncmp(azArg[0], "scanstatus", n)==0) | |
| 30548 | + ){ | |
| 30207 | 30549 | if( nArg==2 ){ |
| 30208 | 30550 | if( cli_strcmp(azArg[1], "vm")==0 ){ |
| 30209 | 30551 | p->scanstatsOn = 3; |
| 30210 | 30552 | }else |
| 30211 | 30553 | if( cli_strcmp(azArg[1], "est")==0 ){ |
| @@ -31256,11 +31598,13 @@ | ||
| 31256 | 31598 | { 0x10000000, 1, "OrderBySubq" }, |
| 31257 | 31599 | { 0xffffffff, 0, "All" }, |
| 31258 | 31600 | }; |
| 31259 | 31601 | unsigned int curOpt; |
| 31260 | 31602 | unsigned int newOpt; |
| 31603 | + unsigned int m; | |
| 31261 | 31604 | int ii; |
| 31605 | + int nOff; | |
| 31262 | 31606 | sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt); |
| 31263 | 31607 | newOpt = curOpt; |
| 31264 | 31608 | for(ii=2; ii<nArg; ii++){ |
| 31265 | 31609 | const char *z = azArg[ii]; |
| 31266 | 31610 | int useLabel = 0; |
| @@ -31297,28 +31641,32 @@ | ||
| 31297 | 31641 | } |
| 31298 | 31642 | } |
| 31299 | 31643 | } |
| 31300 | 31644 | if( curOpt!=newOpt ){ |
| 31301 | 31645 | sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt); |
| 31302 | - }else if( nArg<3 ){ | |
| 31303 | - curOpt = ~newOpt; | |
| 31646 | + } | |
| 31647 | + for(ii=nOff=0, m=1; ii<32; ii++, m <<= 1){ | |
| 31648 | + if( m & newOpt ) nOff++; | |
| 31304 | 31649 | } |
| 31305 | - if( newOpt==0 ){ | |
| 31306 | - sqlite3_fputs("+All\n", p->out); | |
| 31307 | - }else if( newOpt==0xffffffff ){ | |
| 31308 | - sqlite3_fputs("-All\n", p->out); | |
| 31650 | + if( nOff<12 ){ | |
| 31651 | + sqlite3_fputs("+All", p->out); | |
| 31652 | + for(ii=0; ii<ArraySize(aLabel); ii++){ | |
| 31653 | + if( !aLabel[ii].bDsply ) continue; | |
| 31654 | + if( (newOpt & aLabel[ii].mask)!=0 ){ | |
| 31655 | + sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel); | |
| 31656 | + } | |
| 31657 | + } | |
| 31309 | 31658 | }else{ |
| 31310 | - int jj; | |
| 31311 | - for(jj=0; jj<ArraySize(aLabel); jj++){ | |
| 31312 | - unsigned int m = aLabel[jj].mask; | |
| 31313 | - if( !aLabel[jj].bDsply ) continue; | |
| 31314 | - if( (curOpt&m)!=(newOpt&m) ){ | |
| 31315 | - sqlite3_fprintf(p->out, "%c%s\n", (newOpt & m)==0 ? '+' : '-', | |
| 31316 | - aLabel[jj].zLabel); | |
| 31659 | + sqlite3_fputs("-All", p->out); | |
| 31660 | + for(ii=0; ii<ArraySize(aLabel); ii++){ | |
| 31661 | + if( !aLabel[ii].bDsply ) continue; | |
| 31662 | + if( (newOpt & aLabel[ii].mask)==0 ){ | |
| 31663 | + sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel); | |
| 31317 | 31664 | } |
| 31318 | 31665 | } |
| 31319 | 31666 | } |
| 31667 | + sqlite3_fputs("\n", p->out); | |
| 31320 | 31668 | rc2 = isOk = 3; |
| 31321 | 31669 | break; |
| 31322 | 31670 | } |
| 31323 | 31671 | |
| 31324 | 31672 | /* sqlite3_test_control(int, db, int) */ |
| @@ -31652,73 +32000,10 @@ | ||
| 31652 | 32000 | } |
| 31653 | 32001 | } |
| 31654 | 32002 | }else |
| 31655 | 32003 | #endif |
| 31656 | 32004 | |
| 31657 | -#if SQLITE_USER_AUTHENTICATION | |
| 31658 | - if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ | |
| 31659 | - if( nArg<2 ){ | |
| 31660 | - eputz("Usage: .user SUBCOMMAND ...\n"); | |
| 31661 | - rc = 1; | |
| 31662 | - goto meta_command_exit; | |
| 31663 | - } | |
| 31664 | - open_db(p, 0); | |
| 31665 | - if( cli_strcmp(azArg[1],"login")==0 ){ | |
| 31666 | - if( nArg!=4 ){ | |
| 31667 | - eputz("Usage: .user login USER PASSWORD\n"); | |
| 31668 | - rc = 1; | |
| 31669 | - goto meta_command_exit; | |
| 31670 | - } | |
| 31671 | - rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], | |
| 31672 | - strlen30(azArg[3])); | |
| 31673 | - if( rc ){ | |
| 31674 | - sqlite3_fprintf(stderr,"Authentication failed for user %s\n", azArg[2]); | |
| 31675 | - rc = 1; | |
| 31676 | - } | |
| 31677 | - }else if( cli_strcmp(azArg[1],"add")==0 ){ | |
| 31678 | - if( nArg!=5 ){ | |
| 31679 | - eputz("Usage: .user add USER PASSWORD ISADMIN\n"); | |
| 31680 | - rc = 1; | |
| 31681 | - goto meta_command_exit; | |
| 31682 | - } | |
| 31683 | - rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), | |
| 31684 | - booleanValue(azArg[4])); | |
| 31685 | - if( rc ){ | |
| 31686 | - sqlite3_fprintf(stderr,"User-Add failed: %d\n", rc); | |
| 31687 | - rc = 1; | |
| 31688 | - } | |
| 31689 | - }else if( cli_strcmp(azArg[1],"edit")==0 ){ | |
| 31690 | - if( nArg!=5 ){ | |
| 31691 | - eputz("Usage: .user edit USER PASSWORD ISADMIN\n"); | |
| 31692 | - rc = 1; | |
| 31693 | - goto meta_command_exit; | |
| 31694 | - } | |
| 31695 | - rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), | |
| 31696 | - booleanValue(azArg[4])); | |
| 31697 | - if( rc ){ | |
| 31698 | - sqlite3_fprintf(stderr,"User-Edit failed: %d\n", rc); | |
| 31699 | - rc = 1; | |
| 31700 | - } | |
| 31701 | - }else if( cli_strcmp(azArg[1],"delete")==0 ){ | |
| 31702 | - if( nArg!=3 ){ | |
| 31703 | - eputz("Usage: .user delete USER\n"); | |
| 31704 | - rc = 1; | |
| 31705 | - goto meta_command_exit; | |
| 31706 | - } | |
| 31707 | - rc = sqlite3_user_delete(p->db, azArg[2]); | |
| 31708 | - if( rc ){ | |
| 31709 | - sqlite3_fprintf(stderr,"User-Delete failed: %d\n", rc); | |
| 31710 | - rc = 1; | |
| 31711 | - } | |
| 31712 | - }else{ | |
| 31713 | - eputz("Usage: .user login|add|edit|delete ...\n"); | |
| 31714 | - rc = 1; | |
| 31715 | - goto meta_command_exit; | |
| 31716 | - } | |
| 31717 | - }else | |
| 31718 | -#endif /* SQLITE_USER_AUTHENTICATION */ | |
| 31719 | - | |
| 31720 | 32005 | if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ |
| 31721 | 32006 | char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; |
| 31722 | 32007 | sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, |
| 31723 | 32008 | sqlite3_libversion(), sqlite3_sourceid()); |
| 31724 | 32009 | #if SQLITE_HAVE_ZLIB |
| @@ -31838,11 +32123,10 @@ | ||
| 31838 | 32123 | SCAN_TRACKER_REFTYPE pst){ |
| 31839 | 32124 | char cin; |
| 31840 | 32125 | char cWait = (char)qss; /* intentional narrowing loss */ |
| 31841 | 32126 | if( cWait==0 ){ |
| 31842 | 32127 | PlainScan: |
| 31843 | - assert( cWait==0 ); | |
| 31844 | 32128 | while( (cin = *zLine++)!=0 ){ |
| 31845 | 32129 | if( IsSpace(cin) ) |
| 31846 | 32130 | continue; |
| 31847 | 32131 | switch (cin){ |
| 31848 | 32132 | case '-': |
| @@ -31890,11 +32174,10 @@ | ||
| 31890 | 32174 | switch( cWait ){ |
| 31891 | 32175 | case '*': |
| 31892 | 32176 | if( *zLine != '/' ) |
| 31893 | 32177 | continue; |
| 31894 | 32178 | ++zLine; |
| 31895 | - cWait = 0; | |
| 31896 | 32179 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 31897 | 32180 | qss = QSS_SETV(qss, 0); |
| 31898 | 32181 | goto PlainScan; |
| 31899 | 32182 | case '`': case '\'': case '"': |
| 31900 | 32183 | if(*zLine==cWait){ |
| @@ -31902,11 +32185,10 @@ | ||
| 31902 | 32185 | ++zLine; |
| 31903 | 32186 | continue; |
| 31904 | 32187 | } |
| 31905 | 32188 | deliberate_fall_through; |
| 31906 | 32189 | case ']': |
| 31907 | - cWait = 0; | |
| 31908 | 32190 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 31909 | 32191 | qss = QSS_SETV(qss, 0); |
| 31910 | 32192 | goto PlainScan; |
| 31911 | 32193 | default: assert(0); |
| 31912 | 32194 | } |
| @@ -32090,11 +32372,14 @@ | ||
| 32090 | 32372 | if( doAutoDetectRestore(p, zSql) ) return 1; |
| 32091 | 32373 | return 0; |
| 32092 | 32374 | } |
| 32093 | 32375 | |
| 32094 | 32376 | static void echo_group_input(ShellState *p, const char *zDo){ |
| 32095 | - if( ShellHasFlag(p, SHFLG_Echo) ) sqlite3_fprintf(p->out, "%s\n", zDo); | |
| 32377 | + if( ShellHasFlag(p, SHFLG_Echo) ){ | |
| 32378 | + sqlite3_fprintf(p->out, "%s\n", zDo); | |
| 32379 | + fflush(p->out); | |
| 32380 | + } | |
| 32096 | 32381 | } |
| 32097 | 32382 | |
| 32098 | 32383 | #ifdef SQLITE_SHELL_FIDDLE |
| 32099 | 32384 | /* |
| 32100 | 32385 | ** Alternate one_input_line() impl for wasm mode. This is not in the primary |
| @@ -32550,10 +32835,19 @@ | ||
| 32550 | 32835 | } |
| 32551 | 32836 | |
| 32552 | 32837 | static void sayAbnormalExit(void){ |
| 32553 | 32838 | if( seenInterrupt ) eputz("Program interrupted.\n"); |
| 32554 | 32839 | } |
| 32840 | + | |
| 32841 | +/* Routine to output from vfstrace | |
| 32842 | +*/ | |
| 32843 | +static int vfstraceOut(const char *z, void *pArg){ | |
| 32844 | + ShellState *p = (ShellState*)pArg; | |
| 32845 | + sqlite3_fputs(z, p->out); | |
| 32846 | + fflush(p->out); | |
| 32847 | + return 1; | |
| 32848 | +} | |
| 32555 | 32849 | |
| 32556 | 32850 | #ifndef SQLITE_SHELL_IS_UTF8 |
| 32557 | 32851 | # if (defined(_WIN32) || defined(WIN32)) \ |
| 32558 | 32852 | && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) |
| 32559 | 32853 | # define SQLITE_SHELL_IS_UTF8 (0) |
| @@ -32787,12 +33081,10 @@ | ||
| 32787 | 33081 | case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; |
| 32788 | 33082 | case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; |
| 32789 | 33083 | default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; |
| 32790 | 33084 | } |
| 32791 | 33085 | }else if( cli_strcmp(z,"-vfstrace")==0 ){ |
| 32792 | - vfstrace_register("trace",0,(int(*)(const char*,void*))sqlite3_fputs, | |
| 32793 | - stderr,1); | |
| 32794 | 33086 | bEnableVfstrace = 1; |
| 32795 | 33087 | #ifdef SQLITE_ENABLE_MULTIPLEX |
| 32796 | 33088 | }else if( cli_strcmp(z,"-multiplex")==0 ){ |
| 32797 | 33089 | extern int sqlite3_multiplex_initialize(const char*,int); |
| 32798 | 33090 | sqlite3_multiplex_initialize(0, 1); |
| @@ -32885,10 +33177,13 @@ | ||
| 32885 | 33177 | "%s: Error: no database filename specified\n", Argv0); |
| 32886 | 33178 | return 1; |
| 32887 | 33179 | #endif |
| 32888 | 33180 | } |
| 32889 | 33181 | data.out = stdout; |
| 33182 | + if( bEnableVfstrace ){ | |
| 33183 | + vfstrace_register("trace",0,vfstraceOut, &data, 1); | |
| 33184 | + } | |
| 32890 | 33185 | #ifndef SQLITE_SHELL_FIDDLE |
| 32891 | 33186 | sqlite3_appendvfs_init(0,0,0); |
| 32892 | 33187 | #endif |
| 32893 | 33188 | |
| 32894 | 33189 | /* Go ahead and open the database file if it already exists. If the |
| @@ -33129,19 +33424,14 @@ | ||
| 33129 | 33424 | */ |
| 33130 | 33425 | if( stdin_is_interactive ){ |
| 33131 | 33426 | char *zHome; |
| 33132 | 33427 | char *zHistory; |
| 33133 | 33428 | int nHistory; |
| 33134 | -#if CIO_WIN_WC_XLATE | |
| 33135 | -# define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") | |
| 33136 | -#else | |
| 33137 | -# define SHELL_CIO_CHAR_SET "" | |
| 33138 | -#endif | |
| 33139 | 33429 | sqlite3_fprintf(stdout, |
| 33140 | - "SQLite version %s %.19s%s\n" /*extra-version-info*/ | |
| 33430 | + "SQLite version %s %.19s\n" /*extra-version-info*/ | |
| 33141 | 33431 | "Enter \".help\" for usage hints.\n", |
| 33142 | - sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET); | |
| 33432 | + sqlite3_libversion(), sqlite3_sourceid()); | |
| 33143 | 33433 | if( warnInmemoryDb ){ |
| 33144 | 33434 | sputz(stdout, "Connected to a "); |
| 33145 | 33435 | printBold("transient in-memory database"); |
| 33146 | 33436 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33147 | 33437 | " persistent database.\n"); |
| @@ -33154,13 +33444,15 @@ | ||
| 33154 | 33444 | if( (zHistory = malloc(nHistory))!=0 ){ |
| 33155 | 33445 | sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); |
| 33156 | 33446 | } |
| 33157 | 33447 | } |
| 33158 | 33448 | if( zHistory ){ shell_read_history(zHistory); } |
| 33159 | -#if HAVE_READLINE || HAVE_EDITLINE | |
| 33449 | +#if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION) | |
| 33160 | 33450 | rl_attempted_completion_function = readline_completion; |
| 33161 | -#elif HAVE_LINENOISE | |
| 33451 | +#elif HAVE_LINENOISE==1 | |
| 33452 | + linenoiseSetCompletionCallback(linenoise_completion); | |
| 33453 | +#elif HAVE_LINENOISE==2 | |
| 33162 | 33454 | linenoiseSetCompletionCallback(linenoise_completion, NULL); |
| 33163 | 33455 | #endif |
| 33164 | 33456 | data.in = 0; |
| 33165 | 33457 | rc = process_input(&data); |
| 33166 | 33458 | if( zHistory ){ |
| 33167 | 33459 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -120,13 +120,10 @@ | |
| 120 | #include <math.h> |
| 121 | #include "sqlite3.h" |
| 122 | typedef sqlite3_int64 i64; |
| 123 | typedef sqlite3_uint64 u64; |
| 124 | typedef unsigned char u8; |
| 125 | #if SQLITE_USER_AUTHENTICATION |
| 126 | # include "sqlite3userauth.h" |
| 127 | #endif |
| 128 | #include <ctype.h> |
| 129 | #include <stdarg.h> |
| 130 | |
| 131 | #if !defined(_WIN32) && !defined(WIN32) |
| 132 | # include <signal.h> |
| @@ -408,21 +405,21 @@ | |
| 408 | wchar_t *b1, *b2; |
| 409 | int sz1, sz2; |
| 410 | |
| 411 | sz1 = (int)strlen(zFilename); |
| 412 | sz2 = (int)strlen(zMode); |
| 413 | b1 = malloc( (sz1+1)*sizeof(b1[0]) ); |
| 414 | b2 = malloc( (sz2+1)*sizeof(b1[0]) ); |
| 415 | if( b1 && b2 ){ |
| 416 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1); |
| 417 | b1[sz1] = 0; |
| 418 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 419 | b2[sz2] = 0; |
| 420 | fp = _wfopen(b1, b2); |
| 421 | } |
| 422 | free(b1); |
| 423 | free(b2); |
| 424 | simBinaryOther = 0; |
| 425 | return fp; |
| 426 | } |
| 427 | |
| 428 | |
| @@ -434,21 +431,21 @@ | |
| 434 | wchar_t *b1, *b2; |
| 435 | int sz1, sz2; |
| 436 | |
| 437 | sz1 = (int)strlen(zCommand); |
| 438 | sz2 = (int)strlen(zMode); |
| 439 | b1 = malloc( (sz1+1)*sizeof(b1[0]) ); |
| 440 | b2 = malloc( (sz2+1)*sizeof(b1[0]) ); |
| 441 | if( b1 && b2 ){ |
| 442 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1); |
| 443 | b1[sz1] = 0; |
| 444 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 445 | b2[sz2] = 0; |
| 446 | fp = _wpopen(b1, b2); |
| 447 | } |
| 448 | free(b1); |
| 449 | free(b2); |
| 450 | return fp; |
| 451 | } |
| 452 | |
| 453 | /* |
| 454 | ** Work-alike for fgets() from the standard C library. |
| @@ -458,16 +455,26 @@ | |
| 458 | /* When reading from the command-prompt in Windows, it is necessary |
| 459 | ** to use _O_WTEXT input mode to read UTF-16 characters, then translate |
| 460 | ** that into UTF-8. Otherwise, non-ASCII characters all get translated |
| 461 | ** into '?'. |
| 462 | */ |
| 463 | wchar_t *b1 = malloc( sz*sizeof(wchar_t) ); |
| 464 | if( b1==0 ) return 0; |
| 465 | _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT); |
| 466 | if( fgetws(b1, sz/4, in)==0 ){ |
| 467 | sqlite3_free(b1); |
| 468 | return 0; |
| 469 | } |
| 470 | WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0); |
| 471 | sqlite3_free(b1); |
| 472 | return buf; |
| 473 | }else{ |
| @@ -519,24 +526,37 @@ | |
| 519 | if( !UseWtextForOutput(out) ){ |
| 520 | /* Writing to a file or other destination, just write bytes without |
| 521 | ** any translation. */ |
| 522 | return fputs(z, out); |
| 523 | }else{ |
| 524 | /* When writing to the command-prompt in Windows, it is necessary |
| 525 | ** to use O_U8TEXT to render Unicode U+0080 and greater. Go ahead |
| 526 | ** use O_U8TEXT for everything in text mode. |
| 527 | */ |
| 528 | int sz = (int)strlen(z); |
| 529 | wchar_t *b1 = malloc( (sz+1)*sizeof(wchar_t) ); |
| 530 | if( b1==0 ) return 0; |
| 531 | sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz); |
| 532 | b1[sz] = 0; |
| 533 | _setmode(_fileno(out), _O_U8TEXT); |
| 534 | if( UseBinaryWText(out) ){ |
| 535 | piecemealOutput(b1, sz, out); |
| 536 | }else{ |
| 537 | fputws(b1, out); |
| 538 | } |
| 539 | sqlite3_free(b1); |
| 540 | return 0; |
| 541 | } |
| 542 | } |
| @@ -2347,11 +2367,11 @@ | |
| 2347 | ** |
| 2348 | ****************************************************************************** |
| 2349 | ** |
| 2350 | ** This SQLite extension implements functions that compute SHA3 hashes |
| 2351 | ** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. |
| 2352 | ** Two SQL functions are implemented: |
| 2353 | ** |
| 2354 | ** sha3(X,SIZE) |
| 2355 | ** sha3_agg(Y,SIZE) |
| 2356 | ** sha3_query(Z,SIZE) |
| 2357 | ** |
| @@ -5070,14 +5090,14 @@ | |
| 5070 | char **pzErrMsg, |
| 5071 | const sqlite3_api_routines *pApi |
| 5072 | ){ |
| 5073 | int rc = SQLITE_OK; |
| 5074 | unsigned int i; |
| 5075 | #if defined(SQLITE3_H) || defined(SQLITE_STATIC_PERCENTILE) |
| 5076 | (void)pApi; /* Unused parameter */ |
| 5077 | #else |
| 5078 | SQLITE_EXTENSION_INIT2(pApi); |
| 5079 | #endif |
| 5080 | (void)pzErrMsg; /* Unused parameter */ |
| 5081 | for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){ |
| 5082 | rc = sqlite3_create_window_function(db, |
| 5083 | aPercentFunc[i].zName, |
| @@ -6831,35 +6851,41 @@ | |
| 6831 | idxNum |= 0x40; |
| 6832 | } |
| 6833 | continue; |
| 6834 | } |
| 6835 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6836 | if( pConstraint->iColumn==SERIES_COLUMN_VALUE ){ |
| 6837 | switch( op ){ |
| 6838 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6839 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6840 | idxNum |= 0x0080; |
| 6841 | idxNum &= ~0x3300; |
| 6842 | aIdx[5] = i; |
| 6843 | aIdx[6] = -1; |
| 6844 | bStartSeen = 1; |
| 6845 | break; |
| 6846 | } |
| 6847 | case SQLITE_INDEX_CONSTRAINT_GE: { |
| 6848 | if( idxNum & 0x0080 ) break; |
| 6849 | idxNum |= 0x0100; |
| 6850 | idxNum &= ~0x0200; |
| 6851 | aIdx[5] = i; |
| 6852 | bStartSeen = 1; |
| 6853 | break; |
| 6854 | } |
| 6855 | case SQLITE_INDEX_CONSTRAINT_GT: { |
| 6856 | if( idxNum & 0x0080 ) break; |
| 6857 | idxNum |= 0x0200; |
| 6858 | idxNum &= ~0x0100; |
| 6859 | aIdx[5] = i; |
| 6860 | bStartSeen = 1; |
| 6861 | break; |
| 6862 | } |
| 6863 | case SQLITE_INDEX_CONSTRAINT_LE: { |
| 6864 | if( idxNum & 0x0080 ) break; |
| 6865 | idxNum |= 0x1000; |
| @@ -14167,11 +14193,11 @@ | |
| 14167 | /* A view. Or a trigger on a view. */ |
| 14168 | if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg); |
| 14169 | }else{ |
| 14170 | IdxTable *pTab; |
| 14171 | rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); |
| 14172 | if( rc==SQLITE_OK ){ |
| 14173 | int i; |
| 14174 | char *zInner = 0; |
| 14175 | char *zOuter = 0; |
| 14176 | pTab->pNext = p->pTable; |
| 14177 | p->pTable = pTab; |
| @@ -16235,11 +16261,31 @@ | |
| 16235 | ** |
| 16236 | ** Similar compiler commands will work on different systems. The key |
| 16237 | ** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that |
| 16238 | ** the shell.c source file will know to include the -vfstrace command-line |
| 16239 | ** option and (2) you must compile and link the three source files |
| 16240 | ** shell,c, test_vfstrace.c, and sqlite3.c. |
| 16241 | */ |
| 16242 | #include <stdlib.h> |
| 16243 | #include <string.h> |
| 16244 | /* #include "sqlite3.h" */ |
| 16245 | |
| @@ -16249,10 +16295,12 @@ | |
| 16249 | */ |
| 16250 | typedef struct vfstrace_info vfstrace_info; |
| 16251 | struct vfstrace_info { |
| 16252 | sqlite3_vfs *pRootVfs; /* The underlying real VFS */ |
| 16253 | int (*xOut)(const char*, void*); /* Send output here */ |
| 16254 | void *pOutArg; /* First argument to xOut */ |
| 16255 | const char *zVfsName; /* Name of this trace-VFS */ |
| 16256 | sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */ |
| 16257 | }; |
| 16258 | |
| @@ -16265,10 +16313,42 @@ | |
| 16265 | vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */ |
| 16266 | const char *zFName; /* Base name of the file */ |
| 16267 | sqlite3_file *pReal; /* The real underlying file */ |
| 16268 | }; |
| 16269 | |
| 16270 | /* |
| 16271 | ** Method declarations for vfstrace_file. |
| 16272 | */ |
| 16273 | static int vfstraceClose(sqlite3_file*); |
| 16274 | static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); |
| @@ -16329,15 +16409,17 @@ | |
| 16329 | const char *zFormat, |
| 16330 | ... |
| 16331 | ){ |
| 16332 | va_list ap; |
| 16333 | char *zMsg; |
| 16334 | va_start(ap, zFormat); |
| 16335 | zMsg = sqlite3_vmprintf(zFormat, ap); |
| 16336 | va_end(ap); |
| 16337 | pInfo->xOut(zMsg, pInfo->pOutArg); |
| 16338 | sqlite3_free(zMsg); |
| 16339 | } |
| 16340 | |
| 16341 | /* |
| 16342 | ** Try to convert an error code into a symbolic name for that error code. |
| 16343 | */ |
| @@ -16431,18 +16513,26 @@ | |
| 16431 | int i = *pI; |
| 16432 | while( zAppend[0] ){ z[i++] = *(zAppend++); } |
| 16433 | z[i] = 0; |
| 16434 | *pI = i; |
| 16435 | } |
| 16436 | |
| 16437 | /* |
| 16438 | ** Close an vfstrace-file. |
| 16439 | */ |
| 16440 | static int vfstraceClose(sqlite3_file *pFile){ |
| 16441 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16442 | vfstrace_info *pInfo = p->pInfo; |
| 16443 | int rc; |
| 16444 | vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName); |
| 16445 | rc = p->pReal->pMethods->xClose(p->pReal); |
| 16446 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16447 | if( rc==SQLITE_OK ){ |
| 16448 | sqlite3_free((void*)p->base.pMethods); |
| @@ -16461,10 +16551,11 @@ | |
| 16461 | sqlite_int64 iOfst |
| 16462 | ){ |
| 16463 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16464 | vfstrace_info *pInfo = p->pInfo; |
| 16465 | int rc; |
| 16466 | vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)", |
| 16467 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16468 | rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); |
| 16469 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16470 | return rc; |
| @@ -16480,10 +16571,11 @@ | |
| 16480 | sqlite_int64 iOfst |
| 16481 | ){ |
| 16482 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16483 | vfstrace_info *pInfo = p->pInfo; |
| 16484 | int rc; |
| 16485 | vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)", |
| 16486 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16487 | rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); |
| 16488 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16489 | return rc; |
| @@ -16494,10 +16586,11 @@ | |
| 16494 | */ |
| 16495 | static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){ |
| 16496 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16497 | vfstrace_info *pInfo = p->pInfo; |
| 16498 | int rc; |
| 16499 | vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName, |
| 16500 | size); |
| 16501 | rc = p->pReal->pMethods->xTruncate(p->pReal, size); |
| 16502 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16503 | return rc; |
| @@ -16518,10 +16611,11 @@ | |
| 16518 | else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL"); |
| 16519 | if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY"); |
| 16520 | if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){ |
| 16521 | sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags); |
| 16522 | } |
| 16523 | vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16524 | &zBuf[1]); |
| 16525 | rc = p->pReal->pMethods->xSync(p->pReal, flags); |
| 16526 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16527 | return rc; |
| @@ -16532,10 +16626,11 @@ | |
| 16532 | */ |
| 16533 | static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ |
| 16534 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16535 | vfstrace_info *pInfo = p->pInfo; |
| 16536 | int rc; |
| 16537 | vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName); |
| 16538 | rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); |
| 16539 | vfstrace_print_errcode(pInfo, " -> %s,", rc); |
| 16540 | vfstrace_printf(pInfo, " size=%lld\n", *pSize); |
| 16541 | return rc; |
| @@ -16560,10 +16655,11 @@ | |
| 16560 | */ |
| 16561 | static int vfstraceLock(sqlite3_file *pFile, int eLock){ |
| 16562 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16563 | vfstrace_info *pInfo = p->pInfo; |
| 16564 | int rc; |
| 16565 | vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16566 | lockName(eLock)); |
| 16567 | rc = p->pReal->pMethods->xLock(p->pReal, eLock); |
| 16568 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16569 | return rc; |
| @@ -16574,10 +16670,11 @@ | |
| 16574 | */ |
| 16575 | static int vfstraceUnlock(sqlite3_file *pFile, int eLock){ |
| 16576 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16577 | vfstrace_info *pInfo = p->pInfo; |
| 16578 | int rc; |
| 16579 | vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16580 | lockName(eLock)); |
| 16581 | rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); |
| 16582 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16583 | return rc; |
| @@ -16588,10 +16685,11 @@ | |
| 16588 | */ |
| 16589 | static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){ |
| 16590 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16591 | vfstrace_info *pInfo = p->pInfo; |
| 16592 | int rc; |
| 16593 | vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)", |
| 16594 | pInfo->zVfsName, p->zFName); |
| 16595 | rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); |
| 16596 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16597 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16607,10 +16705,11 @@ | |
| 16607 | int rc; |
| 16608 | char zBuf[100]; |
| 16609 | char zBuf2[100]; |
| 16610 | char *zOp; |
| 16611 | char *zRVal = 0; |
| 16612 | switch( op ){ |
| 16613 | case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break; |
| 16614 | case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break; |
| 16615 | case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break; |
| 16616 | case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break; |
| @@ -16635,10 +16734,83 @@ | |
| 16635 | case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break; |
| 16636 | case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break; |
| 16637 | case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break; |
| 16638 | case SQLITE_FCNTL_PRAGMA: { |
| 16639 | const char *const* a = (const char*const*)pArg; |
| 16640 | sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]); |
| 16641 | zOp = zBuf; |
| 16642 | break; |
| 16643 | } |
| 16644 | case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break; |
| @@ -16730,10 +16902,11 @@ | |
| 16730 | */ |
| 16731 | static int vfstraceSectorSize(sqlite3_file *pFile){ |
| 16732 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16733 | vfstrace_info *pInfo = p->pInfo; |
| 16734 | int rc; |
| 16735 | vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName); |
| 16736 | rc = p->pReal->pMethods->xSectorSize(p->pReal); |
| 16737 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16738 | return rc; |
| 16739 | } |
| @@ -16743,10 +16916,11 @@ | |
| 16743 | */ |
| 16744 | static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){ |
| 16745 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16746 | vfstrace_info *pInfo = p->pInfo; |
| 16747 | int rc; |
| 16748 | vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)", |
| 16749 | pInfo->zVfsName, p->zFName); |
| 16750 | rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); |
| 16751 | vfstrace_printf(pInfo, " -> 0x%08x\n", rc); |
| 16752 | return rc; |
| @@ -16754,25 +16928,43 @@ | |
| 16754 | |
| 16755 | /* |
| 16756 | ** Shared-memory operations. |
| 16757 | */ |
| 16758 | static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ |
| 16759 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16760 | vfstrace_info *pInfo = p->pInfo; |
| 16761 | int rc; |
| 16762 | char zLck[100]; |
| 16763 | int i = 0; |
| 16764 | memcpy(zLck, "|0", 3); |
| 16765 | if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK"); |
| 16766 | if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK"); |
| 16767 | if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED"); |
| 16768 | if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE"); |
| 16769 | if( flags & ~(0xf) ){ |
| 16770 | sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags); |
| 16771 | } |
| 16772 | vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)", |
| 16773 | pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]); |
| 16774 | rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); |
| 16775 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16776 | return rc; |
| 16777 | } |
| 16778 | static int vfstraceShmMap( |
| @@ -16783,26 +16975,29 @@ | |
| 16783 | void volatile **pp |
| 16784 | ){ |
| 16785 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16786 | vfstrace_info *pInfo = p->pInfo; |
| 16787 | int rc; |
| 16788 | vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)", |
| 16789 | pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite); |
| 16790 | rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); |
| 16791 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16792 | return rc; |
| 16793 | } |
| 16794 | static void vfstraceShmBarrier(sqlite3_file *pFile){ |
| 16795 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16796 | vfstrace_info *pInfo = p->pInfo; |
| 16797 | vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName); |
| 16798 | p->pReal->pMethods->xShmBarrier(p->pReal); |
| 16799 | } |
| 16800 | static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){ |
| 16801 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16802 | vfstrace_info *pInfo = p->pInfo; |
| 16803 | int rc; |
| 16804 | vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)", |
| 16805 | pInfo->zVfsName, p->zFName, delFlag); |
| 16806 | rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); |
| 16807 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16808 | return rc; |
| @@ -16826,10 +17021,11 @@ | |
| 16826 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16827 | p->pInfo = pInfo; |
| 16828 | p->zFName = zName ? fileTail(zName) : "<temp>"; |
| 16829 | p->pReal = (sqlite3_file *)&p[1]; |
| 16830 | rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags); |
| 16831 | vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)", |
| 16832 | pInfo->zVfsName, p->zFName, flags); |
| 16833 | if( p->pReal->pMethods ){ |
| 16834 | sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) ); |
| 16835 | const sqlite3_io_methods *pSub = p->pReal->pMethods; |
| @@ -16871,10 +17067,11 @@ | |
| 16871 | */ |
| 16872 | static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ |
| 16873 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16874 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16875 | int rc; |
| 16876 | vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)", |
| 16877 | pInfo->zVfsName, zPath, dirSync); |
| 16878 | rc = pRoot->xDelete(pRoot, zPath, dirSync); |
| 16879 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16880 | return rc; |
| @@ -16891,10 +17088,11 @@ | |
| 16891 | int *pResOut |
| 16892 | ){ |
| 16893 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16894 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16895 | int rc; |
| 16896 | vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)", |
| 16897 | pInfo->zVfsName, zPath, flags); |
| 16898 | rc = pRoot->xAccess(pRoot, zPath, flags, pResOut); |
| 16899 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16900 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16913,10 +17111,11 @@ | |
| 16913 | char *zOut |
| 16914 | ){ |
| 16915 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16916 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16917 | int rc; |
| 16918 | vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")", |
| 16919 | pInfo->zVfsName, zPath); |
| 16920 | rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut); |
| 16921 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16922 | vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut); |
| @@ -16927,10 +17126,11 @@ | |
| 16927 | ** Open the dynamic library located at zPath and return a handle. |
| 16928 | */ |
| 16929 | static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){ |
| 16930 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16931 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16932 | vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath); |
| 16933 | return pRoot->xDlOpen(pRoot, zPath); |
| 16934 | } |
| 16935 | |
| 16936 | /* |
| @@ -16939,10 +17139,11 @@ | |
| 16939 | ** with dynamic libraries. |
| 16940 | */ |
| 16941 | static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ |
| 16942 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16943 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16944 | vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte); |
| 16945 | pRoot->xDlError(pRoot, nByte, zErrMsg); |
| 16946 | vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg); |
| 16947 | } |
| 16948 | |
| @@ -16960,10 +17161,11 @@ | |
| 16960 | ** Close the dynamic library handle pHandle. |
| 16961 | */ |
| 16962 | static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){ |
| 16963 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16964 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16965 | vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName); |
| 16966 | pRoot->xDlClose(pRoot, pHandle); |
| 16967 | } |
| 16968 | |
| 16969 | /* |
| @@ -16971,10 +17173,11 @@ | |
| 16971 | ** random data. |
| 16972 | */ |
| 16973 | static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ |
| 16974 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16975 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16976 | vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte); |
| 16977 | return pRoot->xRandomness(pRoot, nByte, zBufOut); |
| 16978 | } |
| 16979 | |
| 16980 | /* |
| @@ -16982,34 +17185,52 @@ | |
| 16982 | ** actually slept. |
| 16983 | */ |
| 16984 | static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){ |
| 16985 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16986 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16987 | return pRoot->xSleep(pRoot, nMicro); |
| 16988 | } |
| 16989 | |
| 16990 | /* |
| 16991 | ** Return the current time as a Julian Day number in *pTimeOut. |
| 16992 | */ |
| 16993 | static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ |
| 16994 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 16995 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 16996 | return pRoot->xCurrentTime(pRoot, pTimeOut); |
| 16997 | } |
| 16998 | static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ |
| 16999 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17000 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17001 | return pRoot->xCurrentTimeInt64(pRoot, pTimeOut); |
| 17002 | } |
| 17003 | |
| 17004 | /* |
| 17005 | ** Return th3 most recent error code and message |
| 17006 | */ |
| 17007 | static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){ |
| 17008 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17009 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17010 | return pRoot->xGetLastError(pRoot, iErr, zErr); |
| 17011 | } |
| 17012 | |
| 17013 | /* |
| 17014 | ** Override system calls. |
| 17015 | */ |
| @@ -17099,10 +17320,12 @@ | |
| 17099 | pInfo->pRootVfs = pRoot; |
| 17100 | pInfo->xOut = xOut; |
| 17101 | pInfo->pOutArg = pOutArg; |
| 17102 | pInfo->zVfsName = pNew->zName; |
| 17103 | pInfo->pTraceVfs = pNew; |
| 17104 | vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n", |
| 17105 | pInfo->zVfsName, pRoot->zName); |
| 17106 | return sqlite3_vfs_register(pNew, makeDefault); |
| 17107 | } |
| 17108 | |
| @@ -20228,10 +20451,12 @@ | |
| 20228 | apVal[iField] = sqlite3_value_dup( pVal ); |
| 20229 | if( apVal[iField]==0 ){ |
| 20230 | recoverError(p, SQLITE_NOMEM, 0); |
| 20231 | } |
| 20232 | p1->nVal = iField+1; |
| 20233 | } |
| 20234 | p1->iPrevCell = iCell; |
| 20235 | p1->iPrevPage = iPage; |
| 20236 | } |
| 20237 | }else{ |
| @@ -21935,12 +22160,12 @@ | |
| 21935 | const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){ |
| 21936 | int ng = (nAccept<0)? -nAccept : 0; |
| 21937 | const char *pcLimit = (nAccept>=0)? z+nAccept : 0; |
| 21938 | assert(z!=0); |
| 21939 | while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){ |
| 21940 | char c = *z; |
| 21941 | if( (c & 0x80) == 0 ){ |
| 21942 | if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z; |
| 21943 | ++z; /* ASCII */ |
| 21944 | }else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */ |
| 21945 | else{ |
| 21946 | const char *zt = z+1; /* Got lead byte, look at trail bytes.*/ |
| @@ -22004,14 +22229,14 @@ | |
| 22004 | } |
| 22005 | sqlite3_fputs(zq, out); |
| 22006 | } |
| 22007 | |
| 22008 | /* |
| 22009 | ** Output the given string as a quoted according to JSON quoting rules. |
| 22010 | */ |
| 22011 | static void output_json_string(FILE *out, const char *z, i64 n){ |
| 22012 | char c; |
| 22013 | static const char *zq = "\""; |
| 22014 | static long ctrlMask = ~0L; |
| 22015 | static const char *zDQBS = "\"\\"; |
| 22016 | const char *pcLimit; |
| 22017 | char ace[3] = "\\?"; |
| @@ -22027,11 +22252,11 @@ | |
| 22027 | if( pcEnd > z ){ |
| 22028 | sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); |
| 22029 | z = pcEnd; |
| 22030 | } |
| 22031 | if( z >= pcLimit ) break; |
| 22032 | c = *(z++); |
| 22033 | switch( c ){ |
| 22034 | case '"': case '\\': |
| 22035 | cbsSay = (char)c; |
| 22036 | break; |
| 22037 | case '\b': cbsSay = 'b'; break; |
| @@ -22042,12 +22267,12 @@ | |
| 22042 | default: cbsSay = 0; break; |
| 22043 | } |
| 22044 | if( cbsSay ){ |
| 22045 | ace[1] = cbsSay; |
| 22046 | sqlite3_fputs(ace, out); |
| 22047 | }else if( c<=0x1f ){ |
| 22048 | sqlite3_fprintf(out, "u%04x", c); |
| 22049 | }else{ |
| 22050 | ace[1] = (char)c; |
| 22051 | sqlite3_fputs(ace+1, out); |
| 22052 | } |
| 22053 | } |
| @@ -24784,13 +25009,13 @@ | |
| 24784 | if( rc ){ |
| 24785 | sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); |
| 24786 | }else{ |
| 24787 | rc = SQLITE_CORRUPT; |
| 24788 | } |
| 24789 | sqlite3_free(zErr); |
| 24790 | free(zQ2); |
| 24791 | } |
| 24792 | return rc; |
| 24793 | } |
| 24794 | |
| 24795 | /* |
| 24796 | ** Text of help messages. |
| @@ -24849,10 +25074,11 @@ | |
| 24849 | ".databases List names and files of attached databases", |
| 24850 | ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", |
| 24851 | #if SQLITE_SHELL_HAVE_RECOVER |
| 24852 | ".dbinfo ?DB? Show status information about the database", |
| 24853 | #endif |
| 24854 | ".dump ?OBJECTS? Render database content as SQL", |
| 24855 | " Options:", |
| 24856 | " --data-only Output only INSERT statements", |
| 24857 | " --newlines Allow unescaped newline characters in output", |
| 24858 | " --nosys Omit system tables (ex: \"sqlite_stat1\")", |
| @@ -25654,11 +25880,12 @@ | |
| 25654 | sqlite3_fprintf(stderr, |
| 25655 | "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); |
| 25656 | } |
| 25657 | } |
| 25658 | |
| 25659 | #if HAVE_READLINE || HAVE_EDITLINE |
| 25660 | /* |
| 25661 | ** Readline completion callbacks |
| 25662 | */ |
| 25663 | static char *readline_completion_generator(const char *text, int state){ |
| 25664 | static sqlite3_stmt *pStmt = 0; |
| @@ -25692,19 +25919,26 @@ | |
| 25692 | #elif HAVE_LINENOISE |
| 25693 | /* |
| 25694 | ** Linenoise completion callback. Note that the 3rd argument is from |
| 25695 | ** the "msteveb" version of linenoise, not the "antirez" version. |
| 25696 | */ |
| 25697 | static void linenoise_completion(const char *zLine, linenoiseCompletions *lc, |
| 25698 | void *pUserData){ |
| 25699 | i64 nLine = strlen(zLine); |
| 25700 | i64 i, iStart; |
| 25701 | sqlite3_stmt *pStmt = 0; |
| 25702 | char *zSql; |
| 25703 | char zBuf[1000]; |
| 25704 | |
| 25705 | UNUSED_PARAMETER(pUserData); |
| 25706 | if( nLine>(i64)sizeof(zBuf)-30 ) return; |
| 25707 | if( zLine[0]=='.' || zLine[0]=='#') return; |
| 25708 | for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} |
| 25709 | if( i==nLine-1 ) return; |
| 25710 | iStart = i+1; |
| @@ -26390,18 +26624,24 @@ | |
| 26390 | #endif |
| 26391 | |
| 26392 | /* |
| 26393 | ** Run an SQL command and return the single integer result. |
| 26394 | */ |
| 26395 | static int db_int(sqlite3 *db, const char *zSql){ |
| 26396 | sqlite3_stmt *pStmt; |
| 26397 | int res = 0; |
| 26398 | sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| 26399 | if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 26400 | res = sqlite3_column_int(pStmt,0); |
| 26401 | } |
| 26402 | sqlite3_finalize(pStmt); |
| 26403 | return res; |
| 26404 | } |
| 26405 | |
| 26406 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26407 | /* |
| @@ -26500,21 +26740,112 @@ | |
| 26500 | zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); |
| 26501 | }else{ |
| 26502 | zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); |
| 26503 | } |
| 26504 | for(i=0; i<ArraySize(aQuery); i++){ |
| 26505 | char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab); |
| 26506 | int val = db_int(p->db, zSql); |
| 26507 | sqlite3_free(zSql); |
| 26508 | sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val); |
| 26509 | } |
| 26510 | sqlite3_free(zSchemaTab); |
| 26511 | sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); |
| 26512 | sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion); |
| 26513 | return 0; |
| 26514 | } |
| 26515 | #endif /* SQLITE_SHELL_HAVE_RECOVER */ |
| 26516 | |
| 26517 | /* |
| 26518 | ** Print the given string as an error message. |
| 26519 | */ |
| 26520 | static void shellEmitError(const char *zErr){ |
| @@ -28067,12 +28398,12 @@ | |
| 28067 | }else if( *pDb==0 ){ |
| 28068 | return 0; |
| 28069 | }else{ |
| 28070 | /* Formulate the columns spec, close the DB, zero *pDb. */ |
| 28071 | char *zColsSpec = 0; |
| 28072 | int hasDupes = db_int(*pDb, zHasDupes); |
| 28073 | int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0; |
| 28074 | if( hasDupes ){ |
| 28075 | #ifdef SHELL_COLUMN_RENAME_CLEAN |
| 28076 | rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); |
| 28077 | rc_err_oom_die(rc); |
| 28078 | #endif |
| @@ -28083,11 +28414,11 @@ | |
| 28083 | sqlite3_bind_int(pStmt, 1, nDigits); |
| 28084 | rc = sqlite3_step(pStmt); |
| 28085 | sqlite3_finalize(pStmt); |
| 28086 | if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); |
| 28087 | } |
| 28088 | assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */ |
| 28089 | rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); |
| 28090 | rc_err_oom_die(rc); |
| 28091 | rc = sqlite3_step(pStmt); |
| 28092 | if( rc==SQLITE_ROW ){ |
| 28093 | zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| @@ -28688,10 +29019,14 @@ | |
| 28688 | }else{ |
| 28689 | eputz("Usage: .echo on|off\n"); |
| 28690 | rc = 1; |
| 28691 | } |
| 28692 | }else |
| 28693 | |
| 28694 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 28695 | if( nArg==2 ){ |
| 28696 | p->autoEQPtest = 0; |
| 28697 | if( p->autoEQPtrace ){ |
| @@ -29129,11 +29464,15 @@ | |
| 29129 | /* Below, resources must be freed before exit. */ |
| 29130 | while( (nSkip--)>0 ){ |
| 29131 | while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} |
| 29132 | } |
| 29133 | import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ |
| 29134 | if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){ |
| 29135 | /* Table does not exist. Create it. */ |
| 29136 | sqlite3 *dbCols = 0; |
| 29137 | char *zRenames = 0; |
| 29138 | char *zColDefs; |
| 29139 | zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", |
| @@ -30201,11 +30540,14 @@ | |
| 30201 | } |
| 30202 | close_db(pSrc); |
| 30203 | }else |
| 30204 | #endif /* !defined(SQLITE_SHELL_FIDDLE) */ |
| 30205 | |
| 30206 | if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ |
| 30207 | if( nArg==2 ){ |
| 30208 | if( cli_strcmp(azArg[1], "vm")==0 ){ |
| 30209 | p->scanstatsOn = 3; |
| 30210 | }else |
| 30211 | if( cli_strcmp(azArg[1], "est")==0 ){ |
| @@ -31256,11 +31598,13 @@ | |
| 31256 | { 0x10000000, 1, "OrderBySubq" }, |
| 31257 | { 0xffffffff, 0, "All" }, |
| 31258 | }; |
| 31259 | unsigned int curOpt; |
| 31260 | unsigned int newOpt; |
| 31261 | int ii; |
| 31262 | sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt); |
| 31263 | newOpt = curOpt; |
| 31264 | for(ii=2; ii<nArg; ii++){ |
| 31265 | const char *z = azArg[ii]; |
| 31266 | int useLabel = 0; |
| @@ -31297,28 +31641,32 @@ | |
| 31297 | } |
| 31298 | } |
| 31299 | } |
| 31300 | if( curOpt!=newOpt ){ |
| 31301 | sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt); |
| 31302 | }else if( nArg<3 ){ |
| 31303 | curOpt = ~newOpt; |
| 31304 | } |
| 31305 | if( newOpt==0 ){ |
| 31306 | sqlite3_fputs("+All\n", p->out); |
| 31307 | }else if( newOpt==0xffffffff ){ |
| 31308 | sqlite3_fputs("-All\n", p->out); |
| 31309 | }else{ |
| 31310 | int jj; |
| 31311 | for(jj=0; jj<ArraySize(aLabel); jj++){ |
| 31312 | unsigned int m = aLabel[jj].mask; |
| 31313 | if( !aLabel[jj].bDsply ) continue; |
| 31314 | if( (curOpt&m)!=(newOpt&m) ){ |
| 31315 | sqlite3_fprintf(p->out, "%c%s\n", (newOpt & m)==0 ? '+' : '-', |
| 31316 | aLabel[jj].zLabel); |
| 31317 | } |
| 31318 | } |
| 31319 | } |
| 31320 | rc2 = isOk = 3; |
| 31321 | break; |
| 31322 | } |
| 31323 | |
| 31324 | /* sqlite3_test_control(int, db, int) */ |
| @@ -31652,73 +32000,10 @@ | |
| 31652 | } |
| 31653 | } |
| 31654 | }else |
| 31655 | #endif |
| 31656 | |
| 31657 | #if SQLITE_USER_AUTHENTICATION |
| 31658 | if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ |
| 31659 | if( nArg<2 ){ |
| 31660 | eputz("Usage: .user SUBCOMMAND ...\n"); |
| 31661 | rc = 1; |
| 31662 | goto meta_command_exit; |
| 31663 | } |
| 31664 | open_db(p, 0); |
| 31665 | if( cli_strcmp(azArg[1],"login")==0 ){ |
| 31666 | if( nArg!=4 ){ |
| 31667 | eputz("Usage: .user login USER PASSWORD\n"); |
| 31668 | rc = 1; |
| 31669 | goto meta_command_exit; |
| 31670 | } |
| 31671 | rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], |
| 31672 | strlen30(azArg[3])); |
| 31673 | if( rc ){ |
| 31674 | sqlite3_fprintf(stderr,"Authentication failed for user %s\n", azArg[2]); |
| 31675 | rc = 1; |
| 31676 | } |
| 31677 | }else if( cli_strcmp(azArg[1],"add")==0 ){ |
| 31678 | if( nArg!=5 ){ |
| 31679 | eputz("Usage: .user add USER PASSWORD ISADMIN\n"); |
| 31680 | rc = 1; |
| 31681 | goto meta_command_exit; |
| 31682 | } |
| 31683 | rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), |
| 31684 | booleanValue(azArg[4])); |
| 31685 | if( rc ){ |
| 31686 | sqlite3_fprintf(stderr,"User-Add failed: %d\n", rc); |
| 31687 | rc = 1; |
| 31688 | } |
| 31689 | }else if( cli_strcmp(azArg[1],"edit")==0 ){ |
| 31690 | if( nArg!=5 ){ |
| 31691 | eputz("Usage: .user edit USER PASSWORD ISADMIN\n"); |
| 31692 | rc = 1; |
| 31693 | goto meta_command_exit; |
| 31694 | } |
| 31695 | rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), |
| 31696 | booleanValue(azArg[4])); |
| 31697 | if( rc ){ |
| 31698 | sqlite3_fprintf(stderr,"User-Edit failed: %d\n", rc); |
| 31699 | rc = 1; |
| 31700 | } |
| 31701 | }else if( cli_strcmp(azArg[1],"delete")==0 ){ |
| 31702 | if( nArg!=3 ){ |
| 31703 | eputz("Usage: .user delete USER\n"); |
| 31704 | rc = 1; |
| 31705 | goto meta_command_exit; |
| 31706 | } |
| 31707 | rc = sqlite3_user_delete(p->db, azArg[2]); |
| 31708 | if( rc ){ |
| 31709 | sqlite3_fprintf(stderr,"User-Delete failed: %d\n", rc); |
| 31710 | rc = 1; |
| 31711 | } |
| 31712 | }else{ |
| 31713 | eputz("Usage: .user login|add|edit|delete ...\n"); |
| 31714 | rc = 1; |
| 31715 | goto meta_command_exit; |
| 31716 | } |
| 31717 | }else |
| 31718 | #endif /* SQLITE_USER_AUTHENTICATION */ |
| 31719 | |
| 31720 | if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ |
| 31721 | char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; |
| 31722 | sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, |
| 31723 | sqlite3_libversion(), sqlite3_sourceid()); |
| 31724 | #if SQLITE_HAVE_ZLIB |
| @@ -31838,11 +32123,10 @@ | |
| 31838 | SCAN_TRACKER_REFTYPE pst){ |
| 31839 | char cin; |
| 31840 | char cWait = (char)qss; /* intentional narrowing loss */ |
| 31841 | if( cWait==0 ){ |
| 31842 | PlainScan: |
| 31843 | assert( cWait==0 ); |
| 31844 | while( (cin = *zLine++)!=0 ){ |
| 31845 | if( IsSpace(cin) ) |
| 31846 | continue; |
| 31847 | switch (cin){ |
| 31848 | case '-': |
| @@ -31890,11 +32174,10 @@ | |
| 31890 | switch( cWait ){ |
| 31891 | case '*': |
| 31892 | if( *zLine != '/' ) |
| 31893 | continue; |
| 31894 | ++zLine; |
| 31895 | cWait = 0; |
| 31896 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 31897 | qss = QSS_SETV(qss, 0); |
| 31898 | goto PlainScan; |
| 31899 | case '`': case '\'': case '"': |
| 31900 | if(*zLine==cWait){ |
| @@ -31902,11 +32185,10 @@ | |
| 31902 | ++zLine; |
| 31903 | continue; |
| 31904 | } |
| 31905 | deliberate_fall_through; |
| 31906 | case ']': |
| 31907 | cWait = 0; |
| 31908 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 31909 | qss = QSS_SETV(qss, 0); |
| 31910 | goto PlainScan; |
| 31911 | default: assert(0); |
| 31912 | } |
| @@ -32090,11 +32372,14 @@ | |
| 32090 | if( doAutoDetectRestore(p, zSql) ) return 1; |
| 32091 | return 0; |
| 32092 | } |
| 32093 | |
| 32094 | static void echo_group_input(ShellState *p, const char *zDo){ |
| 32095 | if( ShellHasFlag(p, SHFLG_Echo) ) sqlite3_fprintf(p->out, "%s\n", zDo); |
| 32096 | } |
| 32097 | |
| 32098 | #ifdef SQLITE_SHELL_FIDDLE |
| 32099 | /* |
| 32100 | ** Alternate one_input_line() impl for wasm mode. This is not in the primary |
| @@ -32550,10 +32835,19 @@ | |
| 32550 | } |
| 32551 | |
| 32552 | static void sayAbnormalExit(void){ |
| 32553 | if( seenInterrupt ) eputz("Program interrupted.\n"); |
| 32554 | } |
| 32555 | |
| 32556 | #ifndef SQLITE_SHELL_IS_UTF8 |
| 32557 | # if (defined(_WIN32) || defined(WIN32)) \ |
| 32558 | && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) |
| 32559 | # define SQLITE_SHELL_IS_UTF8 (0) |
| @@ -32787,12 +33081,10 @@ | |
| 32787 | case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; |
| 32788 | case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; |
| 32789 | default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; |
| 32790 | } |
| 32791 | }else if( cli_strcmp(z,"-vfstrace")==0 ){ |
| 32792 | vfstrace_register("trace",0,(int(*)(const char*,void*))sqlite3_fputs, |
| 32793 | stderr,1); |
| 32794 | bEnableVfstrace = 1; |
| 32795 | #ifdef SQLITE_ENABLE_MULTIPLEX |
| 32796 | }else if( cli_strcmp(z,"-multiplex")==0 ){ |
| 32797 | extern int sqlite3_multiplex_initialize(const char*,int); |
| 32798 | sqlite3_multiplex_initialize(0, 1); |
| @@ -32885,10 +33177,13 @@ | |
| 32885 | "%s: Error: no database filename specified\n", Argv0); |
| 32886 | return 1; |
| 32887 | #endif |
| 32888 | } |
| 32889 | data.out = stdout; |
| 32890 | #ifndef SQLITE_SHELL_FIDDLE |
| 32891 | sqlite3_appendvfs_init(0,0,0); |
| 32892 | #endif |
| 32893 | |
| 32894 | /* Go ahead and open the database file if it already exists. If the |
| @@ -33129,19 +33424,14 @@ | |
| 33129 | */ |
| 33130 | if( stdin_is_interactive ){ |
| 33131 | char *zHome; |
| 33132 | char *zHistory; |
| 33133 | int nHistory; |
| 33134 | #if CIO_WIN_WC_XLATE |
| 33135 | # define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") |
| 33136 | #else |
| 33137 | # define SHELL_CIO_CHAR_SET "" |
| 33138 | #endif |
| 33139 | sqlite3_fprintf(stdout, |
| 33140 | "SQLite version %s %.19s%s\n" /*extra-version-info*/ |
| 33141 | "Enter \".help\" for usage hints.\n", |
| 33142 | sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET); |
| 33143 | if( warnInmemoryDb ){ |
| 33144 | sputz(stdout, "Connected to a "); |
| 33145 | printBold("transient in-memory database"); |
| 33146 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33147 | " persistent database.\n"); |
| @@ -33154,13 +33444,15 @@ | |
| 33154 | if( (zHistory = malloc(nHistory))!=0 ){ |
| 33155 | sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); |
| 33156 | } |
| 33157 | } |
| 33158 | if( zHistory ){ shell_read_history(zHistory); } |
| 33159 | #if HAVE_READLINE || HAVE_EDITLINE |
| 33160 | rl_attempted_completion_function = readline_completion; |
| 33161 | #elif HAVE_LINENOISE |
| 33162 | linenoiseSetCompletionCallback(linenoise_completion, NULL); |
| 33163 | #endif |
| 33164 | data.in = 0; |
| 33165 | rc = process_input(&data); |
| 33166 | if( zHistory ){ |
| 33167 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -120,13 +120,10 @@ | |
| 120 | #include <math.h> |
| 121 | #include "sqlite3.h" |
| 122 | typedef sqlite3_int64 i64; |
| 123 | typedef sqlite3_uint64 u64; |
| 124 | typedef unsigned char u8; |
| 125 | #include <ctype.h> |
| 126 | #include <stdarg.h> |
| 127 | |
| 128 | #if !defined(_WIN32) && !defined(WIN32) |
| 129 | # include <signal.h> |
| @@ -408,21 +405,21 @@ | |
| 405 | wchar_t *b1, *b2; |
| 406 | int sz1, sz2; |
| 407 | |
| 408 | sz1 = (int)strlen(zFilename); |
| 409 | sz2 = (int)strlen(zMode); |
| 410 | b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) ); |
| 411 | b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) ); |
| 412 | if( b1 && b2 ){ |
| 413 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1); |
| 414 | b1[sz1] = 0; |
| 415 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 416 | b2[sz2] = 0; |
| 417 | fp = _wfopen(b1, b2); |
| 418 | } |
| 419 | sqlite3_free(b1); |
| 420 | sqlite3_free(b2); |
| 421 | simBinaryOther = 0; |
| 422 | return fp; |
| 423 | } |
| 424 | |
| 425 | |
| @@ -434,21 +431,21 @@ | |
| 431 | wchar_t *b1, *b2; |
| 432 | int sz1, sz2; |
| 433 | |
| 434 | sz1 = (int)strlen(zCommand); |
| 435 | sz2 = (int)strlen(zMode); |
| 436 | b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) ); |
| 437 | b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) ); |
| 438 | if( b1 && b2 ){ |
| 439 | sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1); |
| 440 | b1[sz1] = 0; |
| 441 | sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); |
| 442 | b2[sz2] = 0; |
| 443 | fp = _wpopen(b1, b2); |
| 444 | } |
| 445 | sqlite3_free(b1); |
| 446 | sqlite3_free(b2); |
| 447 | return fp; |
| 448 | } |
| 449 | |
| 450 | /* |
| 451 | ** Work-alike for fgets() from the standard C library. |
| @@ -458,16 +455,26 @@ | |
| 455 | /* When reading from the command-prompt in Windows, it is necessary |
| 456 | ** to use _O_WTEXT input mode to read UTF-16 characters, then translate |
| 457 | ** that into UTF-8. Otherwise, non-ASCII characters all get translated |
| 458 | ** into '?'. |
| 459 | */ |
| 460 | wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) ); |
| 461 | if( b1==0 ) return 0; |
| 462 | #ifndef SQLITE_USE_STDIO_FOR_CONSOLE |
| 463 | DWORD nRead = 0; |
| 464 | if( IsConsole(in) |
| 465 | && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0) |
| 466 | ){ |
| 467 | b1[nRead] = 0; |
| 468 | }else |
| 469 | #endif |
| 470 | { |
| 471 | _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT); |
| 472 | if( fgetws(b1, sz/4, in)==0 ){ |
| 473 | sqlite3_free(b1); |
| 474 | return 0; |
| 475 | } |
| 476 | } |
| 477 | WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0); |
| 478 | sqlite3_free(b1); |
| 479 | return buf; |
| 480 | }else{ |
| @@ -519,24 +526,37 @@ | |
| 526 | if( !UseWtextForOutput(out) ){ |
| 527 | /* Writing to a file or other destination, just write bytes without |
| 528 | ** any translation. */ |
| 529 | return fputs(z, out); |
| 530 | }else{ |
| 531 | /* One must use UTF16 in order to get unicode support when writing |
| 532 | ** to the console on Windows. |
| 533 | */ |
| 534 | int sz = (int)strlen(z); |
| 535 | wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) ); |
| 536 | if( b1==0 ) return 0; |
| 537 | sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz); |
| 538 | b1[sz] = 0; |
| 539 | |
| 540 | #ifndef SQLITE_STDIO_FOR_CONSOLE |
| 541 | DWORD nWr = 0; |
| 542 | if( IsConsole(out) |
| 543 | && WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0) |
| 544 | ){ |
| 545 | /* If writing to the console, then the WriteConsoleW() is all we |
| 546 | ** need to do. */ |
| 547 | }else |
| 548 | #endif |
| 549 | { |
| 550 | /* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined |
| 551 | ** then write using the standard library. */ |
| 552 | _setmode(_fileno(out), _O_U8TEXT); |
| 553 | if( UseBinaryWText(out) ){ |
| 554 | piecemealOutput(b1, sz, out); |
| 555 | }else{ |
| 556 | fputws(b1, out); |
| 557 | } |
| 558 | } |
| 559 | sqlite3_free(b1); |
| 560 | return 0; |
| 561 | } |
| 562 | } |
| @@ -2347,11 +2367,11 @@ | |
| 2367 | ** |
| 2368 | ****************************************************************************** |
| 2369 | ** |
| 2370 | ** This SQLite extension implements functions that compute SHA3 hashes |
| 2371 | ** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. |
| 2372 | ** Three SQL functions are implemented: |
| 2373 | ** |
| 2374 | ** sha3(X,SIZE) |
| 2375 | ** sha3_agg(Y,SIZE) |
| 2376 | ** sha3_query(Z,SIZE) |
| 2377 | ** |
| @@ -5070,14 +5090,14 @@ | |
| 5090 | char **pzErrMsg, |
| 5091 | const sqlite3_api_routines *pApi |
| 5092 | ){ |
| 5093 | int rc = SQLITE_OK; |
| 5094 | unsigned int i; |
| 5095 | #ifdef SQLITE3EXT_H |
| 5096 | SQLITE_EXTENSION_INIT2(pApi); |
| 5097 | #else |
| 5098 | (void)pApi; /* Unused parameter */ |
| 5099 | #endif |
| 5100 | (void)pzErrMsg; /* Unused parameter */ |
| 5101 | for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){ |
| 5102 | rc = sqlite3_create_window_function(db, |
| 5103 | aPercentFunc[i].zName, |
| @@ -6831,35 +6851,41 @@ | |
| 6851 | idxNum |= 0x40; |
| 6852 | } |
| 6853 | continue; |
| 6854 | } |
| 6855 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6856 | if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){ |
| 6857 | switch( op ){ |
| 6858 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6859 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6860 | idxNum |= 0x0080; |
| 6861 | idxNum &= ~0x3300; |
| 6862 | aIdx[5] = i; |
| 6863 | aIdx[6] = -1; |
| 6864 | #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| 6865 | bStartSeen = 1; |
| 6866 | #endif |
| 6867 | break; |
| 6868 | } |
| 6869 | case SQLITE_INDEX_CONSTRAINT_GE: { |
| 6870 | if( idxNum & 0x0080 ) break; |
| 6871 | idxNum |= 0x0100; |
| 6872 | idxNum &= ~0x0200; |
| 6873 | aIdx[5] = i; |
| 6874 | #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| 6875 | bStartSeen = 1; |
| 6876 | #endif |
| 6877 | break; |
| 6878 | } |
| 6879 | case SQLITE_INDEX_CONSTRAINT_GT: { |
| 6880 | if( idxNum & 0x0080 ) break; |
| 6881 | idxNum |= 0x0200; |
| 6882 | idxNum &= ~0x0100; |
| 6883 | aIdx[5] = i; |
| 6884 | #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| 6885 | bStartSeen = 1; |
| 6886 | #endif |
| 6887 | break; |
| 6888 | } |
| 6889 | case SQLITE_INDEX_CONSTRAINT_LE: { |
| 6890 | if( idxNum & 0x0080 ) break; |
| 6891 | idxNum |= 0x1000; |
| @@ -14167,11 +14193,11 @@ | |
| 14193 | /* A view. Or a trigger on a view. */ |
| 14194 | if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg); |
| 14195 | }else{ |
| 14196 | IdxTable *pTab; |
| 14197 | rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg); |
| 14198 | if( rc==SQLITE_OK && ALWAYS(pTab!=0) ){ |
| 14199 | int i; |
| 14200 | char *zInner = 0; |
| 14201 | char *zOuter = 0; |
| 14202 | pTab->pNext = p->pTable; |
| 14203 | p->pTable = pTab; |
| @@ -16235,11 +16261,31 @@ | |
| 16261 | ** |
| 16262 | ** Similar compiler commands will work on different systems. The key |
| 16263 | ** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that |
| 16264 | ** the shell.c source file will know to include the -vfstrace command-line |
| 16265 | ** option and (2) you must compile and link the three source files |
| 16266 | ** shell,c, test_vfstrace.c, and sqlite3.c. |
| 16267 | ** |
| 16268 | ** RUNTIME CONTROL OF VFSTRACE OUTPUT |
| 16269 | ** |
| 16270 | ** The application can use the "vfstrace" pragma to control which VFS |
| 16271 | ** APIs are traced. To disable all output: |
| 16272 | ** |
| 16273 | ** PRAGMA vfstrace('-all'); |
| 16274 | ** |
| 16275 | ** To enable all output (which is the default setting): |
| 16276 | ** |
| 16277 | ** PRAGMA vfstrace('+all'); |
| 16278 | ** |
| 16279 | ** Individual APIs can be enabled or disabled by name, with or without |
| 16280 | ** the initial "x" character. For example, to set up for tracing lock |
| 16281 | ** primatives only: |
| 16282 | ** |
| 16283 | ** PRAGMA vfstrace('-all, +Lock,Unlock,ShmLock'); |
| 16284 | ** |
| 16285 | ** The argument to the vfstrace pragma ignores capitalization and any |
| 16286 | ** characters other than alphabetics, '+', and '-'. |
| 16287 | */ |
| 16288 | #include <stdlib.h> |
| 16289 | #include <string.h> |
| 16290 | /* #include "sqlite3.h" */ |
| 16291 | |
| @@ -16249,10 +16295,12 @@ | |
| 16295 | */ |
| 16296 | typedef struct vfstrace_info vfstrace_info; |
| 16297 | struct vfstrace_info { |
| 16298 | sqlite3_vfs *pRootVfs; /* The underlying real VFS */ |
| 16299 | int (*xOut)(const char*, void*); /* Send output here */ |
| 16300 | unsigned int mTrace; /* Mask of interfaces to trace */ |
| 16301 | u8 bOn; /* Tracing on/off */ |
| 16302 | void *pOutArg; /* First argument to xOut */ |
| 16303 | const char *zVfsName; /* Name of this trace-VFS */ |
| 16304 | sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */ |
| 16305 | }; |
| 16306 | |
| @@ -16265,10 +16313,42 @@ | |
| 16313 | vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */ |
| 16314 | const char *zFName; /* Base name of the file */ |
| 16315 | sqlite3_file *pReal; /* The real underlying file */ |
| 16316 | }; |
| 16317 | |
| 16318 | /* |
| 16319 | ** Bit values for vfstrace_info.mTrace. |
| 16320 | */ |
| 16321 | #define VTR_CLOSE 0x00000001 |
| 16322 | #define VTR_READ 0x00000002 |
| 16323 | #define VTR_WRITE 0x00000004 |
| 16324 | #define VTR_TRUNC 0x00000008 |
| 16325 | #define VTR_SYNC 0x00000010 |
| 16326 | #define VTR_FSIZE 0x00000020 |
| 16327 | #define VTR_LOCK 0x00000040 |
| 16328 | #define VTR_UNLOCK 0x00000080 |
| 16329 | #define VTR_CRL 0x00000100 |
| 16330 | #define VTR_FCTRL 0x00000200 |
| 16331 | #define VTR_SECSZ 0x00000400 |
| 16332 | #define VTR_DEVCHAR 0x00000800 |
| 16333 | #define VTR_SHMLOCK 0x00001000 |
| 16334 | #define VTR_SHMMAP 0x00002000 |
| 16335 | #define VTR_SHMBAR 0x00004000 |
| 16336 | #define VTR_SHMUNMAP 0x00008000 |
| 16337 | #define VTR_OPEN 0x00010000 |
| 16338 | #define VTR_DELETE 0x00020000 |
| 16339 | #define VTR_ACCESS 0x00040000 |
| 16340 | #define VTR_FULLPATH 0x00080000 |
| 16341 | #define VTR_DLOPEN 0x00100000 |
| 16342 | #define VTR_DLERR 0x00200000 |
| 16343 | #define VTR_DLSYM 0x00400000 |
| 16344 | #define VTR_DLCLOSE 0x00800000 |
| 16345 | #define VTR_RAND 0x01000000 |
| 16346 | #define VTR_SLEEP 0x02000000 |
| 16347 | #define VTR_CURTIME 0x04000000 |
| 16348 | #define VTR_LASTERR 0x08000000 |
| 16349 | |
| 16350 | /* |
| 16351 | ** Method declarations for vfstrace_file. |
| 16352 | */ |
| 16353 | static int vfstraceClose(sqlite3_file*); |
| 16354 | static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); |
| @@ -16329,15 +16409,17 @@ | |
| 16409 | const char *zFormat, |
| 16410 | ... |
| 16411 | ){ |
| 16412 | va_list ap; |
| 16413 | char *zMsg; |
| 16414 | if( pInfo->bOn ){ |
| 16415 | va_start(ap, zFormat); |
| 16416 | zMsg = sqlite3_vmprintf(zFormat, ap); |
| 16417 | va_end(ap); |
| 16418 | pInfo->xOut(zMsg, pInfo->pOutArg); |
| 16419 | sqlite3_free(zMsg); |
| 16420 | } |
| 16421 | } |
| 16422 | |
| 16423 | /* |
| 16424 | ** Try to convert an error code into a symbolic name for that error code. |
| 16425 | */ |
| @@ -16431,18 +16513,26 @@ | |
| 16513 | int i = *pI; |
| 16514 | while( zAppend[0] ){ z[i++] = *(zAppend++); } |
| 16515 | z[i] = 0; |
| 16516 | *pI = i; |
| 16517 | } |
| 16518 | |
| 16519 | /* |
| 16520 | ** Turn tracing output on or off according to mMask. |
| 16521 | */ |
| 16522 | static void vfstraceOnOff(vfstrace_info *pInfo, unsigned int mMask){ |
| 16523 | pInfo->bOn = (pInfo->mTrace & mMask)!=0; |
| 16524 | } |
| 16525 | |
| 16526 | /* |
| 16527 | ** Close an vfstrace-file. |
| 16528 | */ |
| 16529 | static int vfstraceClose(sqlite3_file *pFile){ |
| 16530 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16531 | vfstrace_info *pInfo = p->pInfo; |
| 16532 | int rc; |
| 16533 | vfstraceOnOff(pInfo, VTR_CLOSE); |
| 16534 | vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName); |
| 16535 | rc = p->pReal->pMethods->xClose(p->pReal); |
| 16536 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16537 | if( rc==SQLITE_OK ){ |
| 16538 | sqlite3_free((void*)p->base.pMethods); |
| @@ -16461,10 +16551,11 @@ | |
| 16551 | sqlite_int64 iOfst |
| 16552 | ){ |
| 16553 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16554 | vfstrace_info *pInfo = p->pInfo; |
| 16555 | int rc; |
| 16556 | vfstraceOnOff(pInfo, VTR_READ); |
| 16557 | vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)", |
| 16558 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16559 | rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); |
| 16560 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16561 | return rc; |
| @@ -16480,10 +16571,11 @@ | |
| 16571 | sqlite_int64 iOfst |
| 16572 | ){ |
| 16573 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16574 | vfstrace_info *pInfo = p->pInfo; |
| 16575 | int rc; |
| 16576 | vfstraceOnOff(pInfo, VTR_WRITE); |
| 16577 | vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)", |
| 16578 | pInfo->zVfsName, p->zFName, iAmt, iOfst); |
| 16579 | rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); |
| 16580 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16581 | return rc; |
| @@ -16494,10 +16586,11 @@ | |
| 16586 | */ |
| 16587 | static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){ |
| 16588 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16589 | vfstrace_info *pInfo = p->pInfo; |
| 16590 | int rc; |
| 16591 | vfstraceOnOff(pInfo, VTR_TRUNC); |
| 16592 | vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName, |
| 16593 | size); |
| 16594 | rc = p->pReal->pMethods->xTruncate(p->pReal, size); |
| 16595 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16596 | return rc; |
| @@ -16518,10 +16611,11 @@ | |
| 16611 | else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL"); |
| 16612 | if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY"); |
| 16613 | if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){ |
| 16614 | sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags); |
| 16615 | } |
| 16616 | vfstraceOnOff(pInfo, VTR_SYNC); |
| 16617 | vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16618 | &zBuf[1]); |
| 16619 | rc = p->pReal->pMethods->xSync(p->pReal, flags); |
| 16620 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16621 | return rc; |
| @@ -16532,10 +16626,11 @@ | |
| 16626 | */ |
| 16627 | static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ |
| 16628 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16629 | vfstrace_info *pInfo = p->pInfo; |
| 16630 | int rc; |
| 16631 | vfstraceOnOff(pInfo, VTR_FSIZE); |
| 16632 | vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName); |
| 16633 | rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); |
| 16634 | vfstrace_print_errcode(pInfo, " -> %s,", rc); |
| 16635 | vfstrace_printf(pInfo, " size=%lld\n", *pSize); |
| 16636 | return rc; |
| @@ -16560,10 +16655,11 @@ | |
| 16655 | */ |
| 16656 | static int vfstraceLock(sqlite3_file *pFile, int eLock){ |
| 16657 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16658 | vfstrace_info *pInfo = p->pInfo; |
| 16659 | int rc; |
| 16660 | vfstraceOnOff(pInfo, VTR_LOCK); |
| 16661 | vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16662 | lockName(eLock)); |
| 16663 | rc = p->pReal->pMethods->xLock(p->pReal, eLock); |
| 16664 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16665 | return rc; |
| @@ -16574,10 +16670,11 @@ | |
| 16670 | */ |
| 16671 | static int vfstraceUnlock(sqlite3_file *pFile, int eLock){ |
| 16672 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16673 | vfstrace_info *pInfo = p->pInfo; |
| 16674 | int rc; |
| 16675 | vfstraceOnOff(pInfo, VTR_UNLOCK); |
| 16676 | vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName, |
| 16677 | lockName(eLock)); |
| 16678 | rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); |
| 16679 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16680 | return rc; |
| @@ -16588,10 +16685,11 @@ | |
| 16685 | */ |
| 16686 | static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){ |
| 16687 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16688 | vfstrace_info *pInfo = p->pInfo; |
| 16689 | int rc; |
| 16690 | vfstraceOnOff(pInfo, VTR_CRL); |
| 16691 | vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)", |
| 16692 | pInfo->zVfsName, p->zFName); |
| 16693 | rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); |
| 16694 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 16695 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16607,10 +16705,11 @@ | |
| 16705 | int rc; |
| 16706 | char zBuf[100]; |
| 16707 | char zBuf2[100]; |
| 16708 | char *zOp; |
| 16709 | char *zRVal = 0; |
| 16710 | vfstraceOnOff(pInfo, VTR_FCTRL); |
| 16711 | switch( op ){ |
| 16712 | case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break; |
| 16713 | case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break; |
| 16714 | case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break; |
| 16715 | case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break; |
| @@ -16635,10 +16734,83 @@ | |
| 16734 | case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break; |
| 16735 | case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break; |
| 16736 | case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break; |
| 16737 | case SQLITE_FCNTL_PRAGMA: { |
| 16738 | const char *const* a = (const char*const*)pArg; |
| 16739 | if( a[1] && strcmp(a[1],"vfstrace")==0 && a[2] ){ |
| 16740 | const u8 *zArg = (const u8*)a[2]; |
| 16741 | if( zArg[0]>='0' && zArg[0]<=9 ){ |
| 16742 | pInfo->mTrace = (sqlite3_uint64)strtoll(a[2], 0, 0); |
| 16743 | }else{ |
| 16744 | static const struct { |
| 16745 | const char *z; |
| 16746 | unsigned int m; |
| 16747 | } aKw[] = { |
| 16748 | { "all", 0xffffffff }, |
| 16749 | { "close", VTR_CLOSE }, |
| 16750 | { "read", VTR_READ }, |
| 16751 | { "write", VTR_WRITE }, |
| 16752 | { "truncate", VTR_TRUNC }, |
| 16753 | { "sync", VTR_SYNC }, |
| 16754 | { "filesize", VTR_FSIZE }, |
| 16755 | { "lock", VTR_LOCK }, |
| 16756 | { "unlock", VTR_UNLOCK }, |
| 16757 | { "checkreservedlock", VTR_CRL }, |
| 16758 | { "filecontrol", VTR_FCTRL }, |
| 16759 | { "sectorsize", VTR_SECSZ }, |
| 16760 | { "devicecharacteristics", VTR_DEVCHAR }, |
| 16761 | { "shmlock", VTR_SHMLOCK }, |
| 16762 | { "shmmap", VTR_SHMMAP }, |
| 16763 | { "shmummap", VTR_SHMUNMAP }, |
| 16764 | { "shmbarrier", VTR_SHMBAR }, |
| 16765 | { "open", VTR_OPEN }, |
| 16766 | { "delete", VTR_DELETE }, |
| 16767 | { "access", VTR_ACCESS }, |
| 16768 | { "fullpathname", VTR_FULLPATH }, |
| 16769 | { "dlopen", VTR_DLOPEN }, |
| 16770 | { "dlerror", VTR_DLERR }, |
| 16771 | { "dlsym", VTR_DLSYM }, |
| 16772 | { "dlclose", VTR_DLCLOSE }, |
| 16773 | { "randomness", VTR_RAND }, |
| 16774 | { "sleep", VTR_SLEEP }, |
| 16775 | { "currenttime", VTR_CURTIME }, |
| 16776 | { "currenttimeint64", VTR_CURTIME }, |
| 16777 | { "getlasterror", VTR_LASTERR }, |
| 16778 | }; |
| 16779 | int onOff = 1; |
| 16780 | while( zArg[0] ){ |
| 16781 | int jj, n; |
| 16782 | while( zArg[0]!=0 && zArg[0]!='-' && zArg[0]!='+' |
| 16783 | && !isalpha(zArg[0]) ) zArg++; |
| 16784 | if( zArg[0]==0 ) break; |
| 16785 | if( zArg[0]=='-' ){ |
| 16786 | onOff = 0; |
| 16787 | zArg++; |
| 16788 | }else if( zArg[0]=='+' ){ |
| 16789 | onOff = 1; |
| 16790 | zArg++; |
| 16791 | } |
| 16792 | while( !isalpha(zArg[0]) ){ |
| 16793 | if( zArg[0]==0 ) break; |
| 16794 | zArg++; |
| 16795 | } |
| 16796 | if( zArg[0]=='x' && isalpha(zArg[1]) ) zArg++; |
| 16797 | for(n=0; isalpha(zArg[n]); n++){} |
| 16798 | for(jj=0; jj<(int)(sizeof(aKw)/sizeof(aKw[0])); jj++){ |
| 16799 | if( sqlite3_strnicmp(aKw[jj].z,(const char*)zArg,n)==0 ){ |
| 16800 | if( onOff ){ |
| 16801 | pInfo->mTrace |= aKw[jj].m; |
| 16802 | }else{ |
| 16803 | pInfo->mTrace &= ~aKw[jj].m; |
| 16804 | } |
| 16805 | break; |
| 16806 | } |
| 16807 | } |
| 16808 | zArg += n; |
| 16809 | } |
| 16810 | } |
| 16811 | } |
| 16812 | sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]); |
| 16813 | zOp = zBuf; |
| 16814 | break; |
| 16815 | } |
| 16816 | case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break; |
| @@ -16730,10 +16902,11 @@ | |
| 16902 | */ |
| 16903 | static int vfstraceSectorSize(sqlite3_file *pFile){ |
| 16904 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16905 | vfstrace_info *pInfo = p->pInfo; |
| 16906 | int rc; |
| 16907 | vfstraceOnOff(pInfo, VTR_SECSZ); |
| 16908 | vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName); |
| 16909 | rc = p->pReal->pMethods->xSectorSize(p->pReal); |
| 16910 | vfstrace_printf(pInfo, " -> %d\n", rc); |
| 16911 | return rc; |
| 16912 | } |
| @@ -16743,10 +16916,11 @@ | |
| 16916 | */ |
| 16917 | static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){ |
| 16918 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16919 | vfstrace_info *pInfo = p->pInfo; |
| 16920 | int rc; |
| 16921 | vfstraceOnOff(pInfo, VTR_DEVCHAR); |
| 16922 | vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)", |
| 16923 | pInfo->zVfsName, p->zFName); |
| 16924 | rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); |
| 16925 | vfstrace_printf(pInfo, " -> 0x%08x\n", rc); |
| 16926 | return rc; |
| @@ -16754,25 +16928,43 @@ | |
| 16928 | |
| 16929 | /* |
| 16930 | ** Shared-memory operations. |
| 16931 | */ |
| 16932 | static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ |
| 16933 | static const char *azLockName[] = { |
| 16934 | "WRITE", |
| 16935 | "CKPT", |
| 16936 | "RECOVER", |
| 16937 | "READ0", |
| 16938 | "READ1", |
| 16939 | "READ2", |
| 16940 | "READ3", |
| 16941 | "READ4", |
| 16942 | }; |
| 16943 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16944 | vfstrace_info *pInfo = p->pInfo; |
| 16945 | int rc; |
| 16946 | char zLck[100]; |
| 16947 | int i = 0; |
| 16948 | vfstraceOnOff(pInfo, VTR_SHMLOCK); |
| 16949 | memcpy(zLck, "|0", 3); |
| 16950 | if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK"); |
| 16951 | if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK"); |
| 16952 | if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED"); |
| 16953 | if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE"); |
| 16954 | if( flags & ~(0xf) ){ |
| 16955 | sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags); |
| 16956 | } |
| 16957 | if( ofst>=0 && ofst<(int)(sizeof(azLockName)/sizeof(azLockName[0])) ){ |
| 16958 | vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d(%s),n=%d,%s)", |
| 16959 | pInfo->zVfsName, p->zFName, ofst, azLockName[ofst], |
| 16960 | n, &zLck[1]); |
| 16961 | }else{ |
| 16962 | vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=5d,n=%d,%s)", |
| 16963 | pInfo->zVfsName, p->zFName, ofst, |
| 16964 | n, &zLck[1]); |
| 16965 | } |
| 16966 | rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); |
| 16967 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16968 | return rc; |
| 16969 | } |
| 16970 | static int vfstraceShmMap( |
| @@ -16783,26 +16975,29 @@ | |
| 16975 | void volatile **pp |
| 16976 | ){ |
| 16977 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16978 | vfstrace_info *pInfo = p->pInfo; |
| 16979 | int rc; |
| 16980 | vfstraceOnOff(pInfo, VTR_SHMMAP); |
| 16981 | vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)", |
| 16982 | pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite); |
| 16983 | rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); |
| 16984 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 16985 | return rc; |
| 16986 | } |
| 16987 | static void vfstraceShmBarrier(sqlite3_file *pFile){ |
| 16988 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16989 | vfstrace_info *pInfo = p->pInfo; |
| 16990 | vfstraceOnOff(pInfo, VTR_SHMBAR); |
| 16991 | vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName); |
| 16992 | p->pReal->pMethods->xShmBarrier(p->pReal); |
| 16993 | } |
| 16994 | static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){ |
| 16995 | vfstrace_file *p = (vfstrace_file *)pFile; |
| 16996 | vfstrace_info *pInfo = p->pInfo; |
| 16997 | int rc; |
| 16998 | vfstraceOnOff(pInfo, VTR_SHMUNMAP); |
| 16999 | vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)", |
| 17000 | pInfo->zVfsName, p->zFName, delFlag); |
| 17001 | rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); |
| 17002 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 17003 | return rc; |
| @@ -16826,10 +17021,11 @@ | |
| 17021 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17022 | p->pInfo = pInfo; |
| 17023 | p->zFName = zName ? fileTail(zName) : "<temp>"; |
| 17024 | p->pReal = (sqlite3_file *)&p[1]; |
| 17025 | rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags); |
| 17026 | vfstraceOnOff(pInfo, VTR_OPEN); |
| 17027 | vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)", |
| 17028 | pInfo->zVfsName, p->zFName, flags); |
| 17029 | if( p->pReal->pMethods ){ |
| 17030 | sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) ); |
| 17031 | const sqlite3_io_methods *pSub = p->pReal->pMethods; |
| @@ -16871,10 +17067,11 @@ | |
| 17067 | */ |
| 17068 | static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ |
| 17069 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17070 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17071 | int rc; |
| 17072 | vfstraceOnOff(pInfo, VTR_DELETE); |
| 17073 | vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)", |
| 17074 | pInfo->zVfsName, zPath, dirSync); |
| 17075 | rc = pRoot->xDelete(pRoot, zPath, dirSync); |
| 17076 | vfstrace_print_errcode(pInfo, " -> %s\n", rc); |
| 17077 | return rc; |
| @@ -16891,10 +17088,11 @@ | |
| 17088 | int *pResOut |
| 17089 | ){ |
| 17090 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17091 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17092 | int rc; |
| 17093 | vfstraceOnOff(pInfo, VTR_ACCESS); |
| 17094 | vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)", |
| 17095 | pInfo->zVfsName, zPath, flags); |
| 17096 | rc = pRoot->xAccess(pRoot, zPath, flags, pResOut); |
| 17097 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 17098 | vfstrace_printf(pInfo, ", out=%d\n", *pResOut); |
| @@ -16913,10 +17111,11 @@ | |
| 17111 | char *zOut |
| 17112 | ){ |
| 17113 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17114 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17115 | int rc; |
| 17116 | vfstraceOnOff(pInfo, VTR_FULLPATH); |
| 17117 | vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")", |
| 17118 | pInfo->zVfsName, zPath); |
| 17119 | rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut); |
| 17120 | vfstrace_print_errcode(pInfo, " -> %s", rc); |
| 17121 | vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut); |
| @@ -16927,10 +17126,11 @@ | |
| 17126 | ** Open the dynamic library located at zPath and return a handle. |
| 17127 | */ |
| 17128 | static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){ |
| 17129 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17130 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17131 | vfstraceOnOff(pInfo, VTR_DLOPEN); |
| 17132 | vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath); |
| 17133 | return pRoot->xDlOpen(pRoot, zPath); |
| 17134 | } |
| 17135 | |
| 17136 | /* |
| @@ -16939,10 +17139,11 @@ | |
| 17139 | ** with dynamic libraries. |
| 17140 | */ |
| 17141 | static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ |
| 17142 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17143 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17144 | vfstraceOnOff(pInfo, VTR_DLERR); |
| 17145 | vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte); |
| 17146 | pRoot->xDlError(pRoot, nByte, zErrMsg); |
| 17147 | vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg); |
| 17148 | } |
| 17149 | |
| @@ -16960,10 +17161,11 @@ | |
| 17161 | ** Close the dynamic library handle pHandle. |
| 17162 | */ |
| 17163 | static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){ |
| 17164 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17165 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17166 | vfstraceOnOff(pInfo, VTR_DLCLOSE); |
| 17167 | vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName); |
| 17168 | pRoot->xDlClose(pRoot, pHandle); |
| 17169 | } |
| 17170 | |
| 17171 | /* |
| @@ -16971,10 +17173,11 @@ | |
| 17173 | ** random data. |
| 17174 | */ |
| 17175 | static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ |
| 17176 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17177 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17178 | vfstraceOnOff(pInfo, VTR_RAND); |
| 17179 | vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte); |
| 17180 | return pRoot->xRandomness(pRoot, nByte, zBufOut); |
| 17181 | } |
| 17182 | |
| 17183 | /* |
| @@ -16982,34 +17185,52 @@ | |
| 17185 | ** actually slept. |
| 17186 | */ |
| 17187 | static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){ |
| 17188 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17189 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17190 | vfstraceOnOff(pInfo, VTR_SLEEP); |
| 17191 | vfstrace_printf(pInfo, "%s.xSleep(%d)\n", pInfo->zVfsName, nMicro); |
| 17192 | return pRoot->xSleep(pRoot, nMicro); |
| 17193 | } |
| 17194 | |
| 17195 | /* |
| 17196 | ** Return the current time as a Julian Day number in *pTimeOut. |
| 17197 | */ |
| 17198 | static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ |
| 17199 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17200 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17201 | int rc; |
| 17202 | vfstraceOnOff(pInfo, VTR_CURTIME); |
| 17203 | vfstrace_printf(pInfo, "%s.xCurrentTime()", pInfo->zVfsName); |
| 17204 | rc = pRoot->xCurrentTime(pRoot, pTimeOut); |
| 17205 | vfstrace_printf(pInfo, " -> %.17g\n", *pTimeOut); |
| 17206 | return rc; |
| 17207 | } |
| 17208 | static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ |
| 17209 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17210 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17211 | int rc; |
| 17212 | vfstraceOnOff(pInfo, VTR_CURTIME); |
| 17213 | vfstrace_printf(pInfo, "%s.xCurrentTimeInt64()", pInfo->zVfsName); |
| 17214 | rc = pRoot->xCurrentTimeInt64(pRoot, pTimeOut); |
| 17215 | vfstrace_printf(pInfo, " -> %lld\n", *pTimeOut); |
| 17216 | return rc; |
| 17217 | } |
| 17218 | |
| 17219 | /* |
| 17220 | ** Return the most recent error code and message |
| 17221 | */ |
| 17222 | static int vfstraceGetLastError(sqlite3_vfs *pVfs, int nErr, char *zErr){ |
| 17223 | vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; |
| 17224 | sqlite3_vfs *pRoot = pInfo->pRootVfs; |
| 17225 | int rc; |
| 17226 | vfstraceOnOff(pInfo, VTR_LASTERR); |
| 17227 | vfstrace_printf(pInfo, "%s.xGetLastError(%d,zBuf)", pInfo->zVfsName, nErr); |
| 17228 | if( nErr ) zErr[0] = 0; |
| 17229 | rc = pRoot->xGetLastError(pRoot, nErr, zErr); |
| 17230 | vfstrace_printf(pInfo, " -> zBuf[] = \"%s\", rc = %d\n", nErr?zErr:"", rc); |
| 17231 | return rc; |
| 17232 | } |
| 17233 | |
| 17234 | /* |
| 17235 | ** Override system calls. |
| 17236 | */ |
| @@ -17099,10 +17320,12 @@ | |
| 17320 | pInfo->pRootVfs = pRoot; |
| 17321 | pInfo->xOut = xOut; |
| 17322 | pInfo->pOutArg = pOutArg; |
| 17323 | pInfo->zVfsName = pNew->zName; |
| 17324 | pInfo->pTraceVfs = pNew; |
| 17325 | pInfo->mTrace = 0xffffffff; |
| 17326 | pInfo->bOn = 1; |
| 17327 | vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n", |
| 17328 | pInfo->zVfsName, pRoot->zName); |
| 17329 | return sqlite3_vfs_register(pNew, makeDefault); |
| 17330 | } |
| 17331 | |
| @@ -20228,10 +20451,12 @@ | |
| 20451 | apVal[iField] = sqlite3_value_dup( pVal ); |
| 20452 | if( apVal[iField]==0 ){ |
| 20453 | recoverError(p, SQLITE_NOMEM, 0); |
| 20454 | } |
| 20455 | p1->nVal = iField+1; |
| 20456 | }else if( pTab->nCol==0 ){ |
| 20457 | p1->nVal = pTab->nCol; |
| 20458 | } |
| 20459 | p1->iPrevCell = iCell; |
| 20460 | p1->iPrevPage = iPage; |
| 20461 | } |
| 20462 | }else{ |
| @@ -21935,12 +22160,12 @@ | |
| 22160 | const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){ |
| 22161 | int ng = (nAccept<0)? -nAccept : 0; |
| 22162 | const char *pcLimit = (nAccept>=0)? z+nAccept : 0; |
| 22163 | assert(z!=0); |
| 22164 | while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){ |
| 22165 | unsigned char c = *(u8*)z; |
| 22166 | if( c<0x7f ){ |
| 22167 | if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z; |
| 22168 | ++z; /* ASCII */ |
| 22169 | }else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */ |
| 22170 | else{ |
| 22171 | const char *zt = z+1; /* Got lead byte, look at trail bytes.*/ |
| @@ -22004,14 +22229,14 @@ | |
| 22229 | } |
| 22230 | sqlite3_fputs(zq, out); |
| 22231 | } |
| 22232 | |
| 22233 | /* |
| 22234 | ** Output the given string as quoted according to JSON quoting rules. |
| 22235 | */ |
| 22236 | static void output_json_string(FILE *out, const char *z, i64 n){ |
| 22237 | unsigned char c; |
| 22238 | static const char *zq = "\""; |
| 22239 | static long ctrlMask = ~0L; |
| 22240 | static const char *zDQBS = "\"\\"; |
| 22241 | const char *pcLimit; |
| 22242 | char ace[3] = "\\?"; |
| @@ -22027,11 +22252,11 @@ | |
| 22252 | if( pcEnd > z ){ |
| 22253 | sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z); |
| 22254 | z = pcEnd; |
| 22255 | } |
| 22256 | if( z >= pcLimit ) break; |
| 22257 | c = (unsigned char)*(z++); |
| 22258 | switch( c ){ |
| 22259 | case '"': case '\\': |
| 22260 | cbsSay = (char)c; |
| 22261 | break; |
| 22262 | case '\b': cbsSay = 'b'; break; |
| @@ -22042,12 +22267,12 @@ | |
| 22267 | default: cbsSay = 0; break; |
| 22268 | } |
| 22269 | if( cbsSay ){ |
| 22270 | ace[1] = cbsSay; |
| 22271 | sqlite3_fputs(ace, out); |
| 22272 | }else if( c<=0x1f || c>=0x7f ){ |
| 22273 | sqlite3_fprintf(out, "\\u%04x", c); |
| 22274 | }else{ |
| 22275 | ace[1] = (char)c; |
| 22276 | sqlite3_fputs(ace+1, out); |
| 22277 | } |
| 22278 | } |
| @@ -24784,13 +25009,13 @@ | |
| 25009 | if( rc ){ |
| 25010 | sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); |
| 25011 | }else{ |
| 25012 | rc = SQLITE_CORRUPT; |
| 25013 | } |
| 25014 | free(zQ2); |
| 25015 | } |
| 25016 | sqlite3_free(zErr); |
| 25017 | return rc; |
| 25018 | } |
| 25019 | |
| 25020 | /* |
| 25021 | ** Text of help messages. |
| @@ -24849,10 +25074,11 @@ | |
| 25074 | ".databases List names and files of attached databases", |
| 25075 | ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", |
| 25076 | #if SQLITE_SHELL_HAVE_RECOVER |
| 25077 | ".dbinfo ?DB? Show status information about the database", |
| 25078 | #endif |
| 25079 | ".dbtotxt Hex dump of the database file", |
| 25080 | ".dump ?OBJECTS? Render database content as SQL", |
| 25081 | " Options:", |
| 25082 | " --data-only Output only INSERT statements", |
| 25083 | " --newlines Allow unescaped newline characters in output", |
| 25084 | " --nosys Omit system tables (ex: \"sqlite_stat1\")", |
| @@ -25654,11 +25880,12 @@ | |
| 25880 | sqlite3_fprintf(stderr, |
| 25881 | "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); |
| 25882 | } |
| 25883 | } |
| 25884 | |
| 25885 | #if (HAVE_READLINE || HAVE_EDITLINE) \ |
| 25886 | && !defined(SQLITE_OMIT_READLINE_COMPLETION) |
| 25887 | /* |
| 25888 | ** Readline completion callbacks |
| 25889 | */ |
| 25890 | static char *readline_completion_generator(const char *text, int state){ |
| 25891 | static sqlite3_stmt *pStmt = 0; |
| @@ -25692,19 +25919,26 @@ | |
| 25919 | #elif HAVE_LINENOISE |
| 25920 | /* |
| 25921 | ** Linenoise completion callback. Note that the 3rd argument is from |
| 25922 | ** the "msteveb" version of linenoise, not the "antirez" version. |
| 25923 | */ |
| 25924 | static void linenoise_completion( |
| 25925 | const char *zLine, |
| 25926 | linenoiseCompletions *lc |
| 25927 | #if HAVE_LINENOISE==2 |
| 25928 | ,void *pUserData |
| 25929 | #endif |
| 25930 | ){ |
| 25931 | i64 nLine = strlen(zLine); |
| 25932 | i64 i, iStart; |
| 25933 | sqlite3_stmt *pStmt = 0; |
| 25934 | char *zSql; |
| 25935 | char zBuf[1000]; |
| 25936 | |
| 25937 | #if HAVE_LINENOISE==2 |
| 25938 | UNUSED_PARAMETER(pUserData); |
| 25939 | #endif |
| 25940 | if( nLine>(i64)sizeof(zBuf)-30 ) return; |
| 25941 | if( zLine[0]=='.' || zLine[0]=='#') return; |
| 25942 | for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){} |
| 25943 | if( i==nLine-1 ) return; |
| 25944 | iStart = i+1; |
| @@ -26390,18 +26624,24 @@ | |
| 26624 | #endif |
| 26625 | |
| 26626 | /* |
| 26627 | ** Run an SQL command and return the single integer result. |
| 26628 | */ |
| 26629 | static int db_int(sqlite3 *db, const char *zSql, ...){ |
| 26630 | sqlite3_stmt *pStmt; |
| 26631 | int res = 0; |
| 26632 | char *z; |
| 26633 | va_list ap; |
| 26634 | va_start(ap, zSql); |
| 26635 | z = sqlite3_vmprintf(zSql, ap); |
| 26636 | va_end(ap); |
| 26637 | sqlite3_prepare_v2(db, z, -1, &pStmt, 0); |
| 26638 | if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 26639 | res = sqlite3_column_int(pStmt,0); |
| 26640 | } |
| 26641 | sqlite3_finalize(pStmt); |
| 26642 | sqlite3_free(z); |
| 26643 | return res; |
| 26644 | } |
| 26645 | |
| 26646 | #if SQLITE_SHELL_HAVE_RECOVER |
| 26647 | /* |
| @@ -26500,21 +26740,112 @@ | |
| 26740 | zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema"); |
| 26741 | }else{ |
| 26742 | zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb); |
| 26743 | } |
| 26744 | for(i=0; i<ArraySize(aQuery); i++){ |
| 26745 | int val = db_int(p->db, aQuery[i].zSql, zSchemaTab); |
| 26746 | sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val); |
| 26747 | } |
| 26748 | sqlite3_free(zSchemaTab); |
| 26749 | sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); |
| 26750 | sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion); |
| 26751 | return 0; |
| 26752 | } |
| 26753 | #endif /* SQLITE_SHELL_HAVE_RECOVER */ |
| 26754 | |
| 26755 | /* |
| 26756 | ** Implementation of the ".dbtotxt" command. |
| 26757 | ** |
| 26758 | ** Return 1 on error, 2 to exit, and 0 otherwise. |
| 26759 | */ |
| 26760 | static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){ |
| 26761 | sqlite3_stmt *pStmt = 0; |
| 26762 | sqlite3_int64 nPage = 0; |
| 26763 | int pgSz = 0; |
| 26764 | const char *zTail; |
| 26765 | char *zName = 0; |
| 26766 | int rc, i, j; |
| 26767 | unsigned char bShow[256]; /* Characters ok to display */ |
| 26768 | |
| 26769 | UNUSED_PARAMETER(nArg); |
| 26770 | UNUSED_PARAMETER(azArg); |
| 26771 | memset(bShow, '.', sizeof(bShow)); |
| 26772 | for(i=' '; i<='~'; i++){ |
| 26773 | if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i; |
| 26774 | } |
| 26775 | rc = sqlite3_prepare_v2(p->db, "PRAGMA page_size", -1, &pStmt, 0); |
| 26776 | if( rc ) goto dbtotxt_error; |
| 26777 | rc = 0; |
| 26778 | if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; |
| 26779 | pgSz = sqlite3_column_int(pStmt, 0); |
| 26780 | sqlite3_finalize(pStmt); |
| 26781 | pStmt = 0; |
| 26782 | if( pgSz<512 || pgSz>65536 || (pgSz&(pgSz-1))!=0 ) goto dbtotxt_error; |
| 26783 | rc = sqlite3_prepare_v2(p->db, "PRAGMA page_count", -1, &pStmt, 0); |
| 26784 | if( rc ) goto dbtotxt_error; |
| 26785 | rc = 0; |
| 26786 | if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error; |
| 26787 | nPage = sqlite3_column_int64(pStmt, 0); |
| 26788 | sqlite3_finalize(pStmt); |
| 26789 | pStmt = 0; |
| 26790 | if( nPage<1 ) goto dbtotxt_error; |
| 26791 | rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0); |
| 26792 | if( rc ) goto dbtotxt_error; |
| 26793 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 26794 | zTail = "unk.db"; |
| 26795 | }else{ |
| 26796 | const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2); |
| 26797 | if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db"; |
| 26798 | zTail = strrchr(zFilename, '/'); |
| 26799 | #if defined(_WIN32) |
| 26800 | if( zTail==0 ) zTail = strrchr(zFilename, '\\'); |
| 26801 | #endif |
| 26802 | } |
| 26803 | zName = strdup(zTail); |
| 26804 | shell_check_oom(zName); |
| 26805 | sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n", |
| 26806 | nPage*pgSz, pgSz, zName); |
| 26807 | sqlite3_finalize(pStmt); |
| 26808 | pStmt = 0; |
| 26809 | rc = sqlite3_prepare_v2(p->db, |
| 26810 | "SELECT pgno, data FROM sqlite_dbpage ORDER BY pgno", -1, &pStmt, 0); |
| 26811 | if( rc ) goto dbtotxt_error; |
| 26812 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 26813 | sqlite3_int64 pgno = sqlite3_column_int64(pStmt, 0); |
| 26814 | const u8 *aData = sqlite3_column_blob(pStmt, 1); |
| 26815 | int seenPageLabel = 0; |
| 26816 | for(i=0; i<pgSz; i+=16){ |
| 26817 | const u8 *aLine = aData+i; |
| 26818 | for(j=0; j<16 && aLine[j]==0; j++){} |
| 26819 | if( j==16 ) continue; |
| 26820 | if( !seenPageLabel ){ |
| 26821 | sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz); |
| 26822 | seenPageLabel = 1; |
| 26823 | } |
| 26824 | sqlite3_fprintf(p->out, "| %5d:", i); |
| 26825 | for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); |
| 26826 | sqlite3_fprintf(p->out, " "); |
| 26827 | for(j=0; j<16; j++){ |
| 26828 | unsigned char c = (unsigned char)aLine[j]; |
| 26829 | sqlite3_fprintf(p->out, "%c", bShow[c]); |
| 26830 | } |
| 26831 | sqlite3_fprintf(p->out, "\n"); |
| 26832 | } |
| 26833 | } |
| 26834 | sqlite3_finalize(pStmt); |
| 26835 | sqlite3_fprintf(p->out, "| end %s\n", zName); |
| 26836 | free(zName); |
| 26837 | return 0; |
| 26838 | |
| 26839 | dbtotxt_error: |
| 26840 | if( rc ){ |
| 26841 | sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db)); |
| 26842 | } |
| 26843 | sqlite3_finalize(pStmt); |
| 26844 | free(zName); |
| 26845 | return 1; |
| 26846 | } |
| 26847 | |
| 26848 | /* |
| 26849 | ** Print the given string as an error message. |
| 26850 | */ |
| 26851 | static void shellEmitError(const char *zErr){ |
| @@ -28067,12 +28398,12 @@ | |
| 28398 | }else if( *pDb==0 ){ |
| 28399 | return 0; |
| 28400 | }else{ |
| 28401 | /* Formulate the columns spec, close the DB, zero *pDb. */ |
| 28402 | char *zColsSpec = 0; |
| 28403 | int hasDupes = db_int(*pDb, "%s", zHasDupes); |
| 28404 | int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0; |
| 28405 | if( hasDupes ){ |
| 28406 | #ifdef SHELL_COLUMN_RENAME_CLEAN |
| 28407 | rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0); |
| 28408 | rc_err_oom_die(rc); |
| 28409 | #endif |
| @@ -28083,11 +28414,11 @@ | |
| 28414 | sqlite3_bind_int(pStmt, 1, nDigits); |
| 28415 | rc = sqlite3_step(pStmt); |
| 28416 | sqlite3_finalize(pStmt); |
| 28417 | if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM); |
| 28418 | } |
| 28419 | assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */ |
| 28420 | rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0); |
| 28421 | rc_err_oom_die(rc); |
| 28422 | rc = sqlite3_step(pStmt); |
| 28423 | if( rc==SQLITE_ROW ){ |
| 28424 | zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); |
| @@ -28688,10 +29019,14 @@ | |
| 29019 | }else{ |
| 29020 | eputz("Usage: .echo on|off\n"); |
| 29021 | rc = 1; |
| 29022 | } |
| 29023 | }else |
| 29024 | |
| 29025 | if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){ |
| 29026 | rc = shell_dbtotxt_command(p, nArg, azArg); |
| 29027 | }else |
| 29028 | |
| 29029 | if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ |
| 29030 | if( nArg==2 ){ |
| 29031 | p->autoEQPtest = 0; |
| 29032 | if( p->autoEQPtrace ){ |
| @@ -29129,11 +29464,15 @@ | |
| 29464 | /* Below, resources must be freed before exit. */ |
| 29465 | while( (nSkip--)>0 ){ |
| 29466 | while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){} |
| 29467 | } |
| 29468 | import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */ |
| 29469 | if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) |
| 29470 | && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema" |
| 29471 | " WHERE name=%Q AND type='view'", |
| 29472 | zSchema ? zSchema : "main", zTable) |
| 29473 | ){ |
| 29474 | /* Table does not exist. Create it. */ |
| 29475 | sqlite3 *dbCols = 0; |
| 29476 | char *zRenames = 0; |
| 29477 | char *zColDefs; |
| 29478 | zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", |
| @@ -30201,11 +30540,14 @@ | |
| 30540 | } |
| 30541 | close_db(pSrc); |
| 30542 | }else |
| 30543 | #endif /* !defined(SQLITE_SHELL_FIDDLE) */ |
| 30544 | |
| 30545 | if( c=='s' && |
| 30546 | (cli_strncmp(azArg[0], "scanstats", n)==0 || |
| 30547 | cli_strncmp(azArg[0], "scanstatus", n)==0) |
| 30548 | ){ |
| 30549 | if( nArg==2 ){ |
| 30550 | if( cli_strcmp(azArg[1], "vm")==0 ){ |
| 30551 | p->scanstatsOn = 3; |
| 30552 | }else |
| 30553 | if( cli_strcmp(azArg[1], "est")==0 ){ |
| @@ -31256,11 +31598,13 @@ | |
| 31598 | { 0x10000000, 1, "OrderBySubq" }, |
| 31599 | { 0xffffffff, 0, "All" }, |
| 31600 | }; |
| 31601 | unsigned int curOpt; |
| 31602 | unsigned int newOpt; |
| 31603 | unsigned int m; |
| 31604 | int ii; |
| 31605 | int nOff; |
| 31606 | sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt); |
| 31607 | newOpt = curOpt; |
| 31608 | for(ii=2; ii<nArg; ii++){ |
| 31609 | const char *z = azArg[ii]; |
| 31610 | int useLabel = 0; |
| @@ -31297,28 +31641,32 @@ | |
| 31641 | } |
| 31642 | } |
| 31643 | } |
| 31644 | if( curOpt!=newOpt ){ |
| 31645 | sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt); |
| 31646 | } |
| 31647 | for(ii=nOff=0, m=1; ii<32; ii++, m <<= 1){ |
| 31648 | if( m & newOpt ) nOff++; |
| 31649 | } |
| 31650 | if( nOff<12 ){ |
| 31651 | sqlite3_fputs("+All", p->out); |
| 31652 | for(ii=0; ii<ArraySize(aLabel); ii++){ |
| 31653 | if( !aLabel[ii].bDsply ) continue; |
| 31654 | if( (newOpt & aLabel[ii].mask)!=0 ){ |
| 31655 | sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel); |
| 31656 | } |
| 31657 | } |
| 31658 | }else{ |
| 31659 | sqlite3_fputs("-All", p->out); |
| 31660 | for(ii=0; ii<ArraySize(aLabel); ii++){ |
| 31661 | if( !aLabel[ii].bDsply ) continue; |
| 31662 | if( (newOpt & aLabel[ii].mask)==0 ){ |
| 31663 | sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel); |
| 31664 | } |
| 31665 | } |
| 31666 | } |
| 31667 | sqlite3_fputs("\n", p->out); |
| 31668 | rc2 = isOk = 3; |
| 31669 | break; |
| 31670 | } |
| 31671 | |
| 31672 | /* sqlite3_test_control(int, db, int) */ |
| @@ -31652,73 +32000,10 @@ | |
| 32000 | } |
| 32001 | } |
| 32002 | }else |
| 32003 | #endif |
| 32004 | |
| 32005 | if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ |
| 32006 | char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; |
| 32007 | sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/, |
| 32008 | sqlite3_libversion(), sqlite3_sourceid()); |
| 32009 | #if SQLITE_HAVE_ZLIB |
| @@ -31838,11 +32123,10 @@ | |
| 32123 | SCAN_TRACKER_REFTYPE pst){ |
| 32124 | char cin; |
| 32125 | char cWait = (char)qss; /* intentional narrowing loss */ |
| 32126 | if( cWait==0 ){ |
| 32127 | PlainScan: |
| 32128 | while( (cin = *zLine++)!=0 ){ |
| 32129 | if( IsSpace(cin) ) |
| 32130 | continue; |
| 32131 | switch (cin){ |
| 32132 | case '-': |
| @@ -31890,11 +32174,10 @@ | |
| 32174 | switch( cWait ){ |
| 32175 | case '*': |
| 32176 | if( *zLine != '/' ) |
| 32177 | continue; |
| 32178 | ++zLine; |
| 32179 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 32180 | qss = QSS_SETV(qss, 0); |
| 32181 | goto PlainScan; |
| 32182 | case '`': case '\'': case '"': |
| 32183 | if(*zLine==cWait){ |
| @@ -31902,11 +32185,10 @@ | |
| 32185 | ++zLine; |
| 32186 | continue; |
| 32187 | } |
| 32188 | deliberate_fall_through; |
| 32189 | case ']': |
| 32190 | CONTINUE_PROMPT_AWAITC(pst, 0); |
| 32191 | qss = QSS_SETV(qss, 0); |
| 32192 | goto PlainScan; |
| 32193 | default: assert(0); |
| 32194 | } |
| @@ -32090,11 +32372,14 @@ | |
| 32372 | if( doAutoDetectRestore(p, zSql) ) return 1; |
| 32373 | return 0; |
| 32374 | } |
| 32375 | |
| 32376 | static void echo_group_input(ShellState *p, const char *zDo){ |
| 32377 | if( ShellHasFlag(p, SHFLG_Echo) ){ |
| 32378 | sqlite3_fprintf(p->out, "%s\n", zDo); |
| 32379 | fflush(p->out); |
| 32380 | } |
| 32381 | } |
| 32382 | |
| 32383 | #ifdef SQLITE_SHELL_FIDDLE |
| 32384 | /* |
| 32385 | ** Alternate one_input_line() impl for wasm mode. This is not in the primary |
| @@ -32550,10 +32835,19 @@ | |
| 32835 | } |
| 32836 | |
| 32837 | static void sayAbnormalExit(void){ |
| 32838 | if( seenInterrupt ) eputz("Program interrupted.\n"); |
| 32839 | } |
| 32840 | |
| 32841 | /* Routine to output from vfstrace |
| 32842 | */ |
| 32843 | static int vfstraceOut(const char *z, void *pArg){ |
| 32844 | ShellState *p = (ShellState*)pArg; |
| 32845 | sqlite3_fputs(z, p->out); |
| 32846 | fflush(p->out); |
| 32847 | return 1; |
| 32848 | } |
| 32849 | |
| 32850 | #ifndef SQLITE_SHELL_IS_UTF8 |
| 32851 | # if (defined(_WIN32) || defined(WIN32)) \ |
| 32852 | && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__))) |
| 32853 | # define SQLITE_SHELL_IS_UTF8 (0) |
| @@ -32787,12 +33081,10 @@ | |
| 33081 | case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break; |
| 33082 | case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break; |
| 33083 | default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break; |
| 33084 | } |
| 33085 | }else if( cli_strcmp(z,"-vfstrace")==0 ){ |
| 33086 | bEnableVfstrace = 1; |
| 33087 | #ifdef SQLITE_ENABLE_MULTIPLEX |
| 33088 | }else if( cli_strcmp(z,"-multiplex")==0 ){ |
| 33089 | extern int sqlite3_multiplex_initialize(const char*,int); |
| 33090 | sqlite3_multiplex_initialize(0, 1); |
| @@ -32885,10 +33177,13 @@ | |
| 33177 | "%s: Error: no database filename specified\n", Argv0); |
| 33178 | return 1; |
| 33179 | #endif |
| 33180 | } |
| 33181 | data.out = stdout; |
| 33182 | if( bEnableVfstrace ){ |
| 33183 | vfstrace_register("trace",0,vfstraceOut, &data, 1); |
| 33184 | } |
| 33185 | #ifndef SQLITE_SHELL_FIDDLE |
| 33186 | sqlite3_appendvfs_init(0,0,0); |
| 33187 | #endif |
| 33188 | |
| 33189 | /* Go ahead and open the database file if it already exists. If the |
| @@ -33129,19 +33424,14 @@ | |
| 33424 | */ |
| 33425 | if( stdin_is_interactive ){ |
| 33426 | char *zHome; |
| 33427 | char *zHistory; |
| 33428 | int nHistory; |
| 33429 | sqlite3_fprintf(stdout, |
| 33430 | "SQLite version %s %.19s\n" /*extra-version-info*/ |
| 33431 | "Enter \".help\" for usage hints.\n", |
| 33432 | sqlite3_libversion(), sqlite3_sourceid()); |
| 33433 | if( warnInmemoryDb ){ |
| 33434 | sputz(stdout, "Connected to a "); |
| 33435 | printBold("transient in-memory database"); |
| 33436 | sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a" |
| 33437 | " persistent database.\n"); |
| @@ -33154,13 +33444,15 @@ | |
| 33444 | if( (zHistory = malloc(nHistory))!=0 ){ |
| 33445 | sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome); |
| 33446 | } |
| 33447 | } |
| 33448 | if( zHistory ){ shell_read_history(zHistory); } |
| 33449 | #if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION) |
| 33450 | rl_attempted_completion_function = readline_completion; |
| 33451 | #elif HAVE_LINENOISE==1 |
| 33452 | linenoiseSetCompletionCallback(linenoise_completion); |
| 33453 | #elif HAVE_LINENOISE==2 |
| 33454 | linenoiseSetCompletionCallback(linenoise_completion, NULL); |
| 33455 | #endif |
| 33456 | data.in = 0; |
| 33457 | rc = process_input(&data); |
| 33458 | if( zHistory ){ |
| 33459 |
+1286
-709
| --- extsrc/sqlite3.c | ||
| +++ extsrc/sqlite3.c | ||
| @@ -1,8 +1,8 @@ | ||
| 1 | 1 | /****************************************************************************** |
| 2 | 2 | ** This file is an amalgamation of many separate C source files from SQLite |
| 3 | -** version 3.47.0. By combining all the individual C code files into this | |
| 3 | +** version 3.48.0. By combining all the individual C code files into this | |
| 4 | 4 | ** single large file, the entire code can be compiled as a single translation |
| 5 | 5 | ** unit. This allows many compilers to do optimizations that would not be |
| 6 | 6 | ** possible if the files were compiled separately. Performance improvements |
| 7 | 7 | ** of 5% or more are commonly seen when SQLite is compiled as a single |
| 8 | 8 | ** translation unit. |
| @@ -16,12 +16,15 @@ | ||
| 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 | -** 03a9703e27c44437c39363d0baf82db4ebc9. | |
| 21 | +** 2b17bc49655c577029919c2d409de994b0d2 with changes in files: | |
| 22 | +** | |
| 23 | +** | |
| 22 | 24 | */ |
| 25 | +#ifndef SQLITE_AMALGAMATION | |
| 23 | 26 | #define SQLITE_CORE 1 |
| 24 | 27 | #define SQLITE_AMALGAMATION 1 |
| 25 | 28 | #ifndef SQLITE_PRIVATE |
| 26 | 29 | # define SQLITE_PRIVATE static |
| 27 | 30 | #endif |
| @@ -460,13 +463,13 @@ | ||
| 460 | 463 | ** |
| 461 | 464 | ** See also: [sqlite3_libversion()], |
| 462 | 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 463 | 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 464 | 467 | */ |
| 465 | -#define SQLITE_VERSION "3.47.0" | |
| 466 | -#define SQLITE_VERSION_NUMBER 3047000 | |
| 467 | -#define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e" | |
| 468 | +#define SQLITE_VERSION "3.48.0" | |
| 469 | +#define SQLITE_VERSION_NUMBER 3048000 | |
| 470 | +#define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552" | |
| 468 | 471 | |
| 469 | 472 | /* |
| 470 | 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 471 | 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 472 | 475 | ** |
| @@ -966,10 +969,17 @@ | ||
| 966 | 969 | ** |
| 967 | 970 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 968 | 971 | ** filesystem supports doing multiple write operations atomically when those |
| 969 | 972 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 970 | 973 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 974 | +** | |
| 975 | +** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read | |
| 976 | +** from the database file in amounts that are not a multiple of the | |
| 977 | +** page size and that do not begin at a page boundary. Without this | |
| 978 | +** property, SQLite is careful to only do full-page reads and write | |
| 979 | +** on aligned pages, with the one exception that it will do a sub-page | |
| 980 | +** read of the first page to access the database header. | |
| 971 | 981 | */ |
| 972 | 982 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 973 | 983 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 974 | 984 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 975 | 985 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -982,10 +992,11 @@ | ||
| 982 | 992 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 983 | 993 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 984 | 994 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 985 | 995 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 986 | 996 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 997 | +#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 | |
| 987 | 998 | |
| 988 | 999 | /* |
| 989 | 1000 | ** CAPI3REF: File Locking Levels |
| 990 | 1001 | ** |
| 991 | 1002 | ** SQLite uses one of these integer values as the second |
| @@ -1128,10 +1139,11 @@ | ||
| 1128 | 1139 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 1129 | 1140 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 1130 | 1141 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 1131 | 1142 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 1132 | 1143 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 1144 | +** <li> [SQLITE_IOCAP_SUBPAGE_READ] | |
| 1133 | 1145 | ** </ul> |
| 1134 | 1146 | ** |
| 1135 | 1147 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 1136 | 1148 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 1137 | 1149 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1405,10 +1417,15 @@ | ||
| 1405 | 1417 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1406 | 1418 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1407 | 1419 | ** pointed to by the pArg argument. This capability is used during testing |
| 1408 | 1420 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1409 | 1421 | ** |
| 1422 | +** <li>[[SQLITE_FCNTL_NULL_IO]] | |
| 1423 | +** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor | |
| 1424 | +** or file handle for the [sqlite3_file] object such that it will no longer | |
| 1425 | +** read or write to the database file. | |
| 1426 | +** | |
| 1410 | 1427 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1411 | 1428 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1412 | 1429 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1413 | 1430 | ** available. The WAL subsystem issues this signal during rare |
| 1414 | 1431 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1558,10 +1575,11 @@ | ||
| 1558 | 1575 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1559 | 1576 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1560 | 1577 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1561 | 1578 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1562 | 1579 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1580 | +#define SQLITE_FCNTL_NULL_IO 43 | |
| 1563 | 1581 | |
| 1564 | 1582 | /* deprecated names */ |
| 1565 | 1583 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1566 | 1584 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1567 | 1585 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2936,14 +2954,18 @@ | ||
| 2936 | 2954 | ** |
| 2937 | 2955 | ** ^These functions return the number of rows modified, inserted or |
| 2938 | 2956 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2939 | 2957 | ** statement on the database connection specified by the only parameter. |
| 2940 | 2958 | ** The two functions are identical except for the type of the return value |
| 2941 | -** and that if the number of rows modified by the most recent INSERT, UPDATE | |
| 2959 | +** and that if the number of rows modified by the most recent INSERT, UPDATE, | |
| 2942 | 2960 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2943 | 2961 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2944 | 2962 | ** type of SQL statement does not modify the value returned by these functions. |
| 2963 | +** For the purposes of this interface, a CREATE TABLE AS SELECT statement | |
| 2964 | +** does not count as an INSERT, UPDATE or DELETE statement and hence the rows | |
| 2965 | +** added to the new table by the CREATE TABLE AS SELECT statement are not | |
| 2966 | +** counted. | |
| 2945 | 2967 | ** |
| 2946 | 2968 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2947 | 2969 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2948 | 2970 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2949 | 2971 | ** |
| @@ -4499,15 +4521,26 @@ | ||
| 4499 | 4521 | ** |
| 4500 | 4522 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4501 | 4523 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4502 | 4524 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4503 | 4525 | ** any virtual tables. |
| 4526 | +** | |
| 4527 | +** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt> | |
| 4528 | +** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler | |
| 4529 | +** errors from being sent to the error log defined by | |
| 4530 | +** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test | |
| 4531 | +** compiles to see if some SQL syntax is well-formed, without generating | |
| 4532 | +** messages on the global error log when it is not. If the test compile | |
| 4533 | +** fails, the sqlite3_prepare_v3() call returns the same error indications | |
| 4534 | +** with or without this flag; it just omits the call to [sqlite3_log()] that | |
| 4535 | +** logs the error. | |
| 4504 | 4536 | ** </dl> |
| 4505 | 4537 | */ |
| 4506 | 4538 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4507 | 4539 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4508 | 4540 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4541 | +#define SQLITE_PREPARE_DONT_LOG 0x10 | |
| 4509 | 4542 | |
| 4510 | 4543 | /* |
| 4511 | 4544 | ** CAPI3REF: Compiling An SQL Statement |
| 4512 | 4545 | ** KEYWORDS: {SQL statement compiler} |
| 4513 | 4546 | ** METHOD: sqlite3 |
| @@ -11194,11 +11227,11 @@ | ||
| 11194 | 11227 | #endif |
| 11195 | 11228 | |
| 11196 | 11229 | #if 0 |
| 11197 | 11230 | } /* End of the 'extern "C"' block */ |
| 11198 | 11231 | #endif |
| 11199 | -#endif /* SQLITE3_H */ | |
| 11232 | +/* #endif for SQLITE3_H will be added by mksqlite3.tcl */ | |
| 11200 | 11233 | |
| 11201 | 11234 | /******** Begin file sqlite3rtree.h *********/ |
| 11202 | 11235 | /* |
| 11203 | 11236 | ** 2010 August 30 |
| 11204 | 11237 | ** |
| @@ -13445,17 +13478,32 @@ | ||
| 13445 | 13478 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13446 | 13479 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13447 | 13480 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13448 | 13481 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13449 | 13482 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13450 | -** bytes. This API is not available if the specified token matches a | |
| 13451 | -** prefix query term. In that case both output variables are always set | |
| 13452 | -** to 0. | |
| 13483 | +** bytes. | |
| 13453 | 13484 | ** |
| 13454 | 13485 | ** The output text is not a copy of the document text that was tokenized. |
| 13455 | 13486 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13456 | 13487 | ** includes any embedded 0x00 and trailing data. |
| 13488 | +** | |
| 13489 | +** This API may be slow in some cases if the token identified by parameters | |
| 13490 | +** iIdx and iToken matched a prefix token in the query. In most cases, the | |
| 13491 | +** first call to this API for each prefix token in the query is forced | |
| 13492 | +** to scan the portion of the full-text index that matches the prefix | |
| 13493 | +** token to collect the extra data required by this API. If the prefix | |
| 13494 | +** token matches a large number of token instances in the document set, | |
| 13495 | +** this may be a performance problem. | |
| 13496 | +** | |
| 13497 | +** If the user knows in advance that a query may use this API for a | |
| 13498 | +** prefix token, FTS5 may be configured to collect all required data as part | |
| 13499 | +** of the initial querying of the full-text index, avoiding the second scan | |
| 13500 | +** entirely. This also causes prefix queries that do not use this API to | |
| 13501 | +** run more slowly and use more memory. FTS5 may be configured in this way | |
| 13502 | +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] | |
| 13503 | +** option, or on a per-query basis using the | |
| 13504 | +** [fts5_insttoken | fts5_insttoken()] user function. | |
| 13457 | 13505 | ** |
| 13458 | 13506 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13459 | 13507 | ** "detail=none" or "detail=column" option. |
| 13460 | 13508 | ** |
| 13461 | 13509 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13886,10 +13934,11 @@ | ||
| 13886 | 13934 | #endif |
| 13887 | 13935 | |
| 13888 | 13936 | #endif /* _FTS5_H */ |
| 13889 | 13937 | |
| 13890 | 13938 | /******** End of fts5.h *********/ |
| 13939 | +#endif /* SQLITE3_H */ | |
| 13891 | 13940 | |
| 13892 | 13941 | /************** End of sqlite3.h *********************************************/ |
| 13893 | 13942 | /************** Continuing where we left off in sqliteInt.h ******************/ |
| 13894 | 13943 | |
| 13895 | 13944 | /* |
| @@ -13931,10 +13980,11 @@ | ||
| 13931 | 13980 | ** to count the size: 2^31-1 or 2147483647. |
| 13932 | 13981 | */ |
| 13933 | 13982 | #ifndef SQLITE_MAX_LENGTH |
| 13934 | 13983 | # define SQLITE_MAX_LENGTH 1000000000 |
| 13935 | 13984 | #endif |
| 13985 | +#define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */ | |
| 13936 | 13986 | |
| 13937 | 13987 | /* |
| 13938 | 13988 | ** This is the maximum number of |
| 13939 | 13989 | ** |
| 13940 | 13990 | ** * Columns in a table |
| @@ -13996,13 +14046,17 @@ | ||
| 13996 | 14046 | # define SQLITE_MAX_VDBE_OP 250000000 |
| 13997 | 14047 | #endif |
| 13998 | 14048 | |
| 13999 | 14049 | /* |
| 14000 | 14050 | ** The maximum number of arguments to an SQL function. |
| 14051 | +** | |
| 14052 | +** This value has a hard upper limit of 32767 due to storage | |
| 14053 | +** constraints (it needs to fit inside a i16). We keep it | |
| 14054 | +** lower than that to prevent abuse. | |
| 14001 | 14055 | */ |
| 14002 | 14056 | #ifndef SQLITE_MAX_FUNCTION_ARG |
| 14003 | -# define SQLITE_MAX_FUNCTION_ARG 127 | |
| 14057 | +# define SQLITE_MAX_FUNCTION_ARG 1000 | |
| 14004 | 14058 | #endif |
| 14005 | 14059 | |
| 14006 | 14060 | /* |
| 14007 | 14061 | ** The suggested maximum number of in-memory pages to use for |
| 14008 | 14062 | ** the main database table and for temporary tables. |
| @@ -16000,10 +16054,26 @@ | ||
| 16000 | 16054 | #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ |
| 16001 | 16055 | #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ |
| 16002 | 16056 | #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ |
| 16003 | 16057 | #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ |
| 16004 | 16058 | |
| 16059 | +#define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL) | |
| 16060 | + | |
| 16061 | +/* | |
| 16062 | +** The argument to this macro is a file descriptor (type sqlite3_file*). | |
| 16063 | +** Return 0 if it is not open, or non-zero (but not 1) if it is. | |
| 16064 | +** | |
| 16065 | +** This is so that expressions can be written as: | |
| 16066 | +** | |
| 16067 | +** if( isOpen(pPager->jfd) ){ ... | |
| 16068 | +** | |
| 16069 | +** instead of | |
| 16070 | +** | |
| 16071 | +** if( pPager->jfd->pMethods ){ ... | |
| 16072 | +*/ | |
| 16073 | +#define isOpen(pFd) ((pFd)->pMethods!=0) | |
| 16074 | + | |
| 16005 | 16075 | /* |
| 16006 | 16076 | ** Flags that make up the mask passed to sqlite3PagerGet(). |
| 16007 | 16077 | */ |
| 16008 | 16078 | #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ |
| 16009 | 16079 | #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ |
| @@ -17025,11 +17095,11 @@ | ||
| 17025 | 17095 | |
| 17026 | 17096 | /* |
| 17027 | 17097 | ** Additional non-public SQLITE_PREPARE_* flags |
| 17028 | 17098 | */ |
| 17029 | 17099 | #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ |
| 17030 | -#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ | |
| 17100 | +#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ | |
| 17031 | 17101 | |
| 17032 | 17102 | /* |
| 17033 | 17103 | ** Prototypes for the VDBE interface. See comments on the implementation |
| 17034 | 17104 | ** for a description of what each of these routines does. |
| 17035 | 17105 | */ |
| @@ -17740,51 +17810,15 @@ | ||
| 17740 | 17810 | struct FuncDefHash { |
| 17741 | 17811 | FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */ |
| 17742 | 17812 | }; |
| 17743 | 17813 | #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) |
| 17744 | 17814 | |
| 17745 | -#if defined(SQLITE_USER_AUTHENTICATION) | |
| 17746 | -# warning "The SQLITE_USER_AUTHENTICATION extension is deprecated. \ | |
| 17747 | - See ext/userauth/user-auth.txt for details." | |
| 17748 | -#endif | |
| 17749 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 17750 | -/* | |
| 17751 | -** Information held in the "sqlite3" database connection object and used | |
| 17752 | -** to manage user authentication. | |
| 17753 | -*/ | |
| 17754 | -typedef struct sqlite3_userauth sqlite3_userauth; | |
| 17755 | -struct sqlite3_userauth { | |
| 17756 | - u8 authLevel; /* Current authentication level */ | |
| 17757 | - int nAuthPW; /* Size of the zAuthPW in bytes */ | |
| 17758 | - char *zAuthPW; /* Password used to authenticate */ | |
| 17759 | - char *zAuthUser; /* User name used to authenticate */ | |
| 17760 | -}; | |
| 17761 | - | |
| 17762 | -/* Allowed values for sqlite3_userauth.authLevel */ | |
| 17763 | -#define UAUTH_Unknown 0 /* Authentication not yet checked */ | |
| 17764 | -#define UAUTH_Fail 1 /* User authentication failed */ | |
| 17765 | -#define UAUTH_User 2 /* Authenticated as a normal user */ | |
| 17766 | -#define UAUTH_Admin 3 /* Authenticated as an administrator */ | |
| 17767 | - | |
| 17768 | -/* Functions used only by user authorization logic */ | |
| 17769 | -SQLITE_PRIVATE int sqlite3UserAuthTable(const char*); | |
| 17770 | -SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*); | |
| 17771 | -SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*); | |
| 17772 | -SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); | |
| 17773 | - | |
| 17774 | -#endif /* SQLITE_USER_AUTHENTICATION */ | |
| 17775 | - | |
| 17776 | 17815 | /* |
| 17777 | 17816 | ** typedef for the authorization callback function. |
| 17778 | 17817 | */ |
| 17779 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 17780 | - typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, | |
| 17781 | - const char*, const char*); | |
| 17782 | -#else | |
| 17783 | - typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, | |
| 17784 | - const char*); | |
| 17785 | -#endif | |
| 17818 | +typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, | |
| 17819 | + const char*); | |
| 17786 | 17820 | |
| 17787 | 17821 | #ifndef SQLITE_OMIT_DEPRECATED |
| 17788 | 17822 | /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing |
| 17789 | 17823 | ** in the style of sqlite3_trace() |
| 17790 | 17824 | */ |
| @@ -17941,13 +17975,10 @@ | ||
| 17941 | 17975 | sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ |
| 17942 | 17976 | void *pUnlockArg; /* Argument to xUnlockNotify */ |
| 17943 | 17977 | void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ |
| 17944 | 17978 | sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ |
| 17945 | 17979 | #endif |
| 17946 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 17947 | - sqlite3_userauth auth; /* User authentication information */ | |
| 17948 | -#endif | |
| 17949 | 17980 | }; |
| 17950 | 17981 | |
| 17951 | 17982 | /* |
| 17952 | 17983 | ** A macro to discover the encoding of a database. |
| 17953 | 17984 | */ |
| @@ -18102,11 +18133,11 @@ | ||
| 18102 | 18133 | ** |
| 18103 | 18134 | ** The u.pHash field is used by the global built-ins. The u.pDestructor |
| 18104 | 18135 | ** field is used by per-connection app-def functions. |
| 18105 | 18136 | */ |
| 18106 | 18137 | struct FuncDef { |
| 18107 | - i8 nArg; /* Number of arguments. -1 means unlimited */ | |
| 18138 | + i16 nArg; /* Number of arguments. -1 means unlimited */ | |
| 18108 | 18139 | u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ |
| 18109 | 18140 | void *pUserData; /* User data parameter */ |
| 18110 | 18141 | FuncDef *pNext; /* Next function with same name */ |
| 18111 | 18142 | void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ |
| 18112 | 18143 | void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ |
| @@ -22850,13 +22881,10 @@ | ||
| 22850 | 22881 | "UNLINK_AFTER_CLOSE", |
| 22851 | 22882 | #endif |
| 22852 | 22883 | #ifdef SQLITE_UNTESTABLE |
| 22853 | 22884 | "UNTESTABLE", |
| 22854 | 22885 | #endif |
| 22855 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 22856 | - "USER_AUTHENTICATION", | |
| 22857 | -#endif | |
| 22858 | 22886 | #ifdef SQLITE_USE_ALLOCA |
| 22859 | 22887 | "USE_ALLOCA", |
| 22860 | 22888 | #endif |
| 22861 | 22889 | #ifdef SQLITE_USE_FCNTL_TRACE |
| 22862 | 22890 | "USE_FCNTL_TRACE", |
| @@ -23700,11 +23728,11 @@ | ||
| 23700 | 23728 | Vdbe *pVdbe; /* The VM that owns this context */ |
| 23701 | 23729 | int iOp; /* Instruction number of OP_Function */ |
| 23702 | 23730 | int isError; /* Error code returned by the function. */ |
| 23703 | 23731 | u8 enc; /* Encoding to use for results */ |
| 23704 | 23732 | u8 skipFlag; /* Skip accumulator loading if true */ |
| 23705 | - u8 argc; /* Number of arguments */ | |
| 23733 | + u16 argc; /* Number of arguments */ | |
| 23706 | 23734 | sqlite3_value *argv[1]; /* Argument set */ |
| 23707 | 23735 | }; |
| 23708 | 23736 | |
| 23709 | 23737 | /* A bitfield type for use inside of structures. Always follow with :N where |
| 23710 | 23738 | ** N is the number of bits. |
| @@ -23847,10 +23875,11 @@ | ||
| 23847 | 23875 | UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ |
| 23848 | 23876 | int iNewReg; /* Register for new.* values */ |
| 23849 | 23877 | int iBlobWrite; /* Value returned by preupdate_blobwrite() */ |
| 23850 | 23878 | i64 iKey1; /* First key value passed to hook */ |
| 23851 | 23879 | i64 iKey2; /* Second key value passed to hook */ |
| 23880 | + Mem oldipk; /* Memory cell holding "old" IPK value */ | |
| 23852 | 23881 | Mem *aNew; /* Array of new.* values */ |
| 23853 | 23882 | Table *pTab; /* Schema object being updated */ |
| 23854 | 23883 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 23855 | 23884 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 23856 | 23885 | }; |
| @@ -32296,10 +32325,11 @@ | ||
| 32296 | 32325 | && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) |
| 32297 | 32326 | ){ |
| 32298 | 32327 | pExpr = pExpr->pLeft; |
| 32299 | 32328 | } |
| 32300 | 32329 | if( pExpr==0 ) return; |
| 32330 | + if( ExprHasProperty(pExpr, EP_FromDDL) ) return; | |
| 32301 | 32331 | db->errByteOffset = pExpr->w.iOfst; |
| 32302 | 32332 | } |
| 32303 | 32333 | |
| 32304 | 32334 | /* |
| 32305 | 32335 | ** Enlarge the memory allocation on a StrAccum object so that it is |
| @@ -33025,11 +33055,11 @@ | ||
| 33025 | 33055 | } |
| 33026 | 33056 | if( pItem->fg.isCte ){ |
| 33027 | 33057 | sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); |
| 33028 | 33058 | } |
| 33029 | 33059 | if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ |
| 33030 | - sqlite3_str_appendf(&x, " ON"); | |
| 33060 | + sqlite3_str_appendf(&x, " isOn"); | |
| 33031 | 33061 | } |
| 33032 | 33062 | if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); |
| 33033 | 33063 | if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); |
| 33034 | 33064 | if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); |
| 33035 | 33065 | if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); |
| @@ -34109,10 +34139,14 @@ | ||
| 34109 | 34139 | ** |
| 34110 | 34140 | ** This routines are given external linkage so that they will always be |
| 34111 | 34141 | ** accessible to the debugging, and to avoid warnings about unused |
| 34112 | 34142 | ** functions. But these routines only exist in debugging builds, so they |
| 34113 | 34143 | ** do not contaminate the interface. |
| 34144 | +** | |
| 34145 | +** See Also: | |
| 34146 | +** | |
| 34147 | +** sqlite3ShowWhereTerm() in where.c | |
| 34114 | 34148 | */ |
| 34115 | 34149 | SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } |
| 34116 | 34150 | SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} |
| 34117 | 34151 | SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } |
| 34118 | 34152 | SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } |
| @@ -35685,12 +35719,12 @@ | ||
| 35685 | 35719 | int esign = 1; /* sign of exponent */ |
| 35686 | 35720 | int e = 0; /* exponent */ |
| 35687 | 35721 | int eValid = 1; /* True exponent is either not used or is well-formed */ |
| 35688 | 35722 | int nDigit = 0; /* Number of digits processed */ |
| 35689 | 35723 | int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ |
| 35724 | + u64 s2; /* round-tripped significand */ | |
| 35690 | 35725 | double rr[2]; |
| 35691 | - u64 s2; | |
| 35692 | 35726 | |
| 35693 | 35727 | assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); |
| 35694 | 35728 | *pResult = 0.0; /* Default return value, in case of an error */ |
| 35695 | 35729 | if( length==0 ) return 0; |
| 35696 | 35730 | |
| @@ -35789,25 +35823,36 @@ | ||
| 35789 | 35823 | |
| 35790 | 35824 | /* adjust exponent by d, and update sign */ |
| 35791 | 35825 | e = (e*esign) + d; |
| 35792 | 35826 | |
| 35793 | 35827 | /* Try to adjust the exponent to make it smaller */ |
| 35794 | - while( e>0 && s<(LARGEST_UINT64/10) ){ | |
| 35828 | + while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){ | |
| 35795 | 35829 | s *= 10; |
| 35796 | 35830 | e--; |
| 35797 | 35831 | } |
| 35798 | 35832 | while( e<0 && (s%10)==0 ){ |
| 35799 | 35833 | s /= 10; |
| 35800 | 35834 | e++; |
| 35801 | 35835 | } |
| 35802 | 35836 | |
| 35803 | 35837 | rr[0] = (double)s; |
| 35804 | - s2 = (u64)rr[0]; | |
| 35805 | -#if defined(_MSC_VER) && _MSC_VER<1700 | |
| 35806 | - if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); } | |
| 35838 | + assert( sizeof(s2)==sizeof(rr[0]) ); | |
| 35839 | +#ifdef SQLITE_DEBUG | |
| 35840 | + rr[1] = 18446744073709549568.0; | |
| 35841 | + memcpy(&s2, &rr[1], sizeof(s2)); | |
| 35842 | + assert( s2==0x43efffffffffffffLL ); | |
| 35807 | 35843 | #endif |
| 35808 | - rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); | |
| 35844 | + /* Largest double that can be safely converted to u64 | |
| 35845 | + ** vvvvvvvvvvvvvvvvvvvvvv */ | |
| 35846 | + if( rr[0]<=18446744073709549568.0 ){ | |
| 35847 | + s2 = (u64)rr[0]; | |
| 35848 | + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); | |
| 35849 | + }else{ | |
| 35850 | + rr[1] = 0.0; | |
| 35851 | + } | |
| 35852 | + assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */ | |
| 35853 | + | |
| 35809 | 35854 | if( e>0 ){ |
| 35810 | 35855 | while( e>=100 ){ |
| 35811 | 35856 | e -= 100; |
| 35812 | 35857 | dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); |
| 35813 | 35858 | } |
| @@ -38674,11 +38719,11 @@ | ||
| 38674 | 38719 | # define F_SETLKW 7 |
| 38675 | 38720 | # endif |
| 38676 | 38721 | # endif |
| 38677 | 38722 | #else /* !SQLITE_WASI */ |
| 38678 | 38723 | # ifndef HAVE_FCHMOD |
| 38679 | -# define HAVE_FCHMOD | |
| 38724 | +# define HAVE_FCHMOD 1 | |
| 38680 | 38725 | # endif |
| 38681 | 38726 | #endif /* SQLITE_WASI */ |
| 38682 | 38727 | |
| 38683 | 38728 | #ifdef SQLITE_WASI |
| 38684 | 38729 | # define osGetpid(X) (pid_t)1 |
| @@ -42448,10 +42493,15 @@ | ||
| 42448 | 42493 | int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE); |
| 42449 | 42494 | return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK; |
| 42450 | 42495 | } |
| 42451 | 42496 | #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ |
| 42452 | 42497 | |
| 42498 | + case SQLITE_FCNTL_NULL_IO: { | |
| 42499 | + osClose(pFile->h); | |
| 42500 | + pFile->h = -1; | |
| 42501 | + return SQLITE_OK; | |
| 42502 | + } | |
| 42453 | 42503 | case SQLITE_FCNTL_LOCKSTATE: { |
| 42454 | 42504 | *(int*)pArg = pFile->eFileLock; |
| 42455 | 42505 | return SQLITE_OK; |
| 42456 | 42506 | } |
| 42457 | 42507 | case SQLITE_FCNTL_LAST_ERRNO: { |
| @@ -42589,10 +42639,11 @@ | ||
| 42589 | 42639 | |
| 42590 | 42640 | /* Set the POWERSAFE_OVERWRITE flag if requested. */ |
| 42591 | 42641 | if( pFd->ctrlFlags & UNIXFILE_PSOW ){ |
| 42592 | 42642 | pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; |
| 42593 | 42643 | } |
| 42644 | + pFd->deviceCharacteristics |= SQLITE_IOCAP_SUBPAGE_READ; | |
| 42594 | 42645 | |
| 42595 | 42646 | pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; |
| 42596 | 42647 | } |
| 42597 | 42648 | } |
| 42598 | 42649 | #else |
| @@ -50328,10 +50379,15 @@ | ||
| 50328 | 50379 | OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n", |
| 50329 | 50380 | hOldFile, pFile->h)); |
| 50330 | 50381 | return SQLITE_OK; |
| 50331 | 50382 | } |
| 50332 | 50383 | #endif |
| 50384 | + case SQLITE_FCNTL_NULL_IO: { | |
| 50385 | + (void)osCloseHandle(pFile->h); | |
| 50386 | + pFile->h = NULL; | |
| 50387 | + return SQLITE_OK; | |
| 50388 | + } | |
| 50333 | 50389 | case SQLITE_FCNTL_TEMPFILENAME: { |
| 50334 | 50390 | char *zTFile = 0; |
| 50335 | 50391 | int rc = winGetTempname(pFile->pVfs, &zTFile); |
| 50336 | 50392 | if( rc==SQLITE_OK ){ |
| 50337 | 50393 | *(char**)pArg = zTFile; |
| @@ -50389,11 +50445,11 @@ | ||
| 50389 | 50445 | /* |
| 50390 | 50446 | ** Return a vector of device characteristics. |
| 50391 | 50447 | */ |
| 50392 | 50448 | static int winDeviceCharacteristics(sqlite3_file *id){ |
| 50393 | 50449 | winFile *p = (winFile*)id; |
| 50394 | - return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | | |
| 50450 | + return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | SQLITE_IOCAP_SUBPAGE_READ | | |
| 50395 | 50451 | ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); |
| 50396 | 50452 | } |
| 50397 | 50453 | |
| 50398 | 50454 | /* |
| 50399 | 50455 | ** Windows will only let you create file view mappings |
| @@ -51777,11 +51833,11 @@ | ||
| 51777 | 51833 | */ |
| 51778 | 51834 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| 51779 | 51835 | |
| 51780 | 51836 | int rc = SQLITE_OK; /* Function Return Code */ |
| 51781 | 51837 | #if !defined(NDEBUG) || SQLITE_OS_WINCE |
| 51782 | - int eType = flags&0xFFFFFF00; /* Type of file to open */ | |
| 51838 | + int eType = flags&0x0FFF00; /* Type of file to open */ | |
| 51783 | 51839 | #endif |
| 51784 | 51840 | |
| 51785 | 51841 | int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); |
| 51786 | 51842 | int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); |
| 51787 | 51843 | int isCreate = (flags & SQLITE_OPEN_CREATE); |
| @@ -57978,43 +58034,37 @@ | ||
| 57978 | 58034 | # define USEFETCH(x) ((x)->bUseFetch) |
| 57979 | 58035 | #else |
| 57980 | 58036 | # define USEFETCH(x) 0 |
| 57981 | 58037 | #endif |
| 57982 | 58038 | |
| 57983 | -/* | |
| 57984 | -** The argument to this macro is a file descriptor (type sqlite3_file*). | |
| 57985 | -** Return 0 if it is not open, or non-zero (but not 1) if it is. | |
| 57986 | -** | |
| 57987 | -** This is so that expressions can be written as: | |
| 57988 | -** | |
| 57989 | -** if( isOpen(pPager->jfd) ){ ... | |
| 57990 | -** | |
| 57991 | -** instead of | |
| 57992 | -** | |
| 57993 | -** if( pPager->jfd->pMethods ){ ... | |
| 57994 | -*/ | |
| 57995 | -#define isOpen(pFd) ((pFd)->pMethods!=0) | |
| 57996 | - | |
| 57997 | 58039 | #ifdef SQLITE_DIRECT_OVERFLOW_READ |
| 57998 | 58040 | /* |
| 57999 | 58041 | ** Return true if page pgno can be read directly from the database file |
| 58000 | 58042 | ** by the b-tree layer. This is the case if: |
| 58001 | 58043 | ** |
| 58002 | -** * the database file is open, | |
| 58003 | -** * there are no dirty pages in the cache, and | |
| 58004 | -** * the desired page is not currently in the wal file. | |
| 58044 | +** (1) the database file is open | |
| 58045 | +** (2) the VFS for the database is able to do unaligned sub-page reads | |
| 58046 | +** (3) there are no dirty pages in the cache, and | |
| 58047 | +** (4) the desired page is not currently in the wal file. | |
| 58005 | 58048 | */ |
| 58006 | 58049 | SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ |
| 58007 | - if( pPager->fd->pMethods==0 ) return 0; | |
| 58008 | - if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; | |
| 58050 | + assert( pPager!=0 ); | |
| 58051 | + assert( pPager->fd!=0 ); | |
| 58052 | + if( pPager->fd->pMethods==0 ) return 0; /* Case (1) */ | |
| 58053 | + if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */ | |
| 58009 | 58054 | #ifndef SQLITE_OMIT_WAL |
| 58010 | 58055 | if( pPager->pWal ){ |
| 58011 | 58056 | u32 iRead = 0; |
| 58012 | 58057 | (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); |
| 58013 | - return iRead==0; | |
| 58058 | + return iRead==0; /* Condition (4) */ | |
| 58014 | 58059 | } |
| 58015 | 58060 | #endif |
| 58061 | + assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); | |
| 58062 | + if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) | |
| 58063 | + & SQLITE_IOCAP_SUBPAGE_READ)==0 ){ | |
| 58064 | + return 0; /* Case (2) */ | |
| 58065 | + } | |
| 58016 | 58066 | return 1; |
| 58017 | 58067 | } |
| 58018 | 58068 | #endif |
| 58019 | 58069 | |
| 58020 | 58070 | #ifndef SQLITE_OMIT_WAL |
| @@ -59269,11 +59319,11 @@ | ||
| 59269 | 59319 | rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); |
| 59270 | 59320 | } |
| 59271 | 59321 | } |
| 59272 | 59322 | pPager->journalOff = 0; |
| 59273 | 59323 | }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST |
| 59274 | - || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) | |
| 59324 | + || (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL) | |
| 59275 | 59325 | ){ |
| 59276 | 59326 | rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile); |
| 59277 | 59327 | pPager->journalOff = 0; |
| 59278 | 59328 | }else{ |
| 59279 | 59329 | /* This branch may be executed with Pager.journalMode==MEMORY if |
| @@ -67979,15 +68029,11 @@ | ||
| 67979 | 68029 | ** so it takes care to hold an exclusive lock on the corresponding |
| 67980 | 68030 | ** WAL_READ_LOCK() while changing values. |
| 67981 | 68031 | */ |
| 67982 | 68032 | static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ |
| 67983 | 68033 | volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ |
| 67984 | - u32 mxReadMark; /* Largest aReadMark[] value */ | |
| 67985 | - int mxI; /* Index of largest aReadMark[] value */ | |
| 67986 | - int i; /* Loop counter */ | |
| 67987 | 68034 | int rc = SQLITE_OK; /* Return code */ |
| 67988 | - u32 mxFrame; /* Wal frame to lock to */ | |
| 67989 | 68035 | #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| 67990 | 68036 | int nBlockTmout = 0; |
| 67991 | 68037 | #endif |
| 67992 | 68038 | |
| 67993 | 68039 | assert( pWal->readLock<0 ); /* Not currently locked */ |
| @@ -68089,145 +68135,151 @@ | ||
| 68089 | 68135 | |
| 68090 | 68136 | assert( pWal->nWiData>0 ); |
| 68091 | 68137 | assert( pWal->apWiData[0]!=0 ); |
| 68092 | 68138 | pInfo = walCkptInfo(pWal); |
| 68093 | 68139 | SEH_INJECT_FAULT; |
| 68094 | - if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame | |
| 68095 | -#ifdef SQLITE_ENABLE_SNAPSHOT | |
| 68096 | - && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0) | |
| 68097 | -#endif | |
| 68098 | - ){ | |
| 68099 | - /* The WAL has been completely backfilled (or it is empty). | |
| 68100 | - ** and can be safely ignored. | |
| 68101 | - */ | |
| 68102 | - rc = walLockShared(pWal, WAL_READ_LOCK(0)); | |
| 68103 | - walShmBarrier(pWal); | |
| 68104 | - if( rc==SQLITE_OK ){ | |
| 68105 | - if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ | |
| 68106 | - /* It is not safe to allow the reader to continue here if frames | |
| 68107 | - ** may have been appended to the log before READ_LOCK(0) was obtained. | |
| 68108 | - ** When holding READ_LOCK(0), the reader ignores the entire log file, | |
| 68109 | - ** which implies that the database file contains a trustworthy | |
| 68110 | - ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from | |
| 68111 | - ** happening, this is usually correct. | |
| 68112 | - ** | |
| 68113 | - ** However, if frames have been appended to the log (or if the log | |
| 68114 | - ** is wrapped and written for that matter) before the READ_LOCK(0) | |
| 68115 | - ** is obtained, that is not necessarily true. A checkpointer may | |
| 68116 | - ** have started to backfill the appended frames but crashed before | |
| 68117 | - ** it finished. Leaving a corrupt image in the database file. | |
| 68118 | - */ | |
| 68119 | - walUnlockShared(pWal, WAL_READ_LOCK(0)); | |
| 68120 | - return WAL_RETRY; | |
| 68121 | - } | |
| 68122 | - pWal->readLock = 0; | |
| 68123 | - return SQLITE_OK; | |
| 68124 | - }else if( rc!=SQLITE_BUSY ){ | |
| 68125 | - return rc; | |
| 68126 | - } | |
| 68127 | - } | |
| 68128 | - | |
| 68129 | - /* If we get this far, it means that the reader will want to use | |
| 68130 | - ** the WAL to get at content from recent commits. The job now is | |
| 68131 | - ** to select one of the aReadMark[] entries that is closest to | |
| 68132 | - ** but not exceeding pWal->hdr.mxFrame and lock that entry. | |
| 68133 | - */ | |
| 68134 | - mxReadMark = 0; | |
| 68135 | - mxI = 0; | |
| 68136 | - mxFrame = pWal->hdr.mxFrame; | |
| 68137 | -#ifdef SQLITE_ENABLE_SNAPSHOT | |
| 68138 | - if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){ | |
| 68139 | - mxFrame = pWal->pSnapshot->mxFrame; | |
| 68140 | - } | |
| 68141 | -#endif | |
| 68142 | - for(i=1; i<WAL_NREADER; i++){ | |
| 68143 | - u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; | |
| 68144 | - if( mxReadMark<=thisMark && thisMark<=mxFrame ){ | |
| 68145 | - assert( thisMark!=READMARK_NOT_USED ); | |
| 68146 | - mxReadMark = thisMark; | |
| 68147 | - mxI = i; | |
| 68148 | - } | |
| 68149 | - } | |
| 68150 | - if( (pWal->readOnly & WAL_SHM_RDONLY)==0 | |
| 68151 | - && (mxReadMark<mxFrame || mxI==0) | |
| 68152 | - ){ | |
| 68153 | - for(i=1; i<WAL_NREADER; i++){ | |
| 68154 | - rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 68155 | - if( rc==SQLITE_OK ){ | |
| 68156 | - AtomicStore(pInfo->aReadMark+i,mxFrame); | |
| 68157 | - mxReadMark = mxFrame; | |
| 68158 | - mxI = i; | |
| 68159 | - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 68160 | - break; | |
| 68161 | - }else if( rc!=SQLITE_BUSY ){ | |
| 68162 | - return rc; | |
| 68163 | - } | |
| 68164 | - } | |
| 68165 | - } | |
| 68166 | - if( mxI==0 ){ | |
| 68167 | - assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); | |
| 68168 | - return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; | |
| 68169 | - } | |
| 68170 | - | |
| 68171 | - (void)walEnableBlockingMs(pWal, nBlockTmout); | |
| 68172 | - rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); | |
| 68173 | - walDisableBlocking(pWal); | |
| 68174 | - if( rc ){ | |
| 68175 | -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT | |
| 68176 | - if( rc==SQLITE_BUSY_TIMEOUT ){ | |
| 68177 | - *pCnt |= WAL_RETRY_BLOCKED_MASK; | |
| 68178 | - } | |
| 68179 | -#else | |
| 68180 | - assert( rc!=SQLITE_BUSY_TIMEOUT ); | |
| 68181 | -#endif | |
| 68182 | - assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT ); | |
| 68183 | - return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; | |
| 68184 | - } | |
| 68185 | - /* Now that the read-lock has been obtained, check that neither the | |
| 68186 | - ** value in the aReadMark[] array or the contents of the wal-index | |
| 68187 | - ** header have changed. | |
| 68188 | - ** | |
| 68189 | - ** It is necessary to check that the wal-index header did not change | |
| 68190 | - ** between the time it was read and when the shared-lock was obtained | |
| 68191 | - ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility | |
| 68192 | - ** that the log file may have been wrapped by a writer, or that frames | |
| 68193 | - ** that occur later in the log than pWal->hdr.mxFrame may have been | |
| 68194 | - ** copied into the database by a checkpointer. If either of these things | |
| 68195 | - ** happened, then reading the database with the current value of | |
| 68196 | - ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry | |
| 68197 | - ** instead. | |
| 68198 | - ** | |
| 68199 | - ** Before checking that the live wal-index header has not changed | |
| 68200 | - ** since it was read, set Wal.minFrame to the first frame in the wal | |
| 68201 | - ** file that has not yet been checkpointed. This client will not need | |
| 68202 | - ** to read any frames earlier than minFrame from the wal file - they | |
| 68203 | - ** can be safely read directly from the database file. | |
| 68204 | - ** | |
| 68205 | - ** Because a ShmBarrier() call is made between taking the copy of | |
| 68206 | - ** nBackfill and checking that the wal-header in shared-memory still | |
| 68207 | - ** matches the one cached in pWal->hdr, it is guaranteed that the | |
| 68208 | - ** checkpointer that set nBackfill was not working with a wal-index | |
| 68209 | - ** header newer than that cached in pWal->hdr. If it were, that could | |
| 68210 | - ** cause a problem. The checkpointer could omit to checkpoint | |
| 68211 | - ** a version of page X that lies before pWal->minFrame (call that version | |
| 68212 | - ** A) on the basis that there is a newer version (version B) of the same | |
| 68213 | - ** page later in the wal file. But if version B happens to like past | |
| 68214 | - ** frame pWal->hdr.mxFrame - then the client would incorrectly assume | |
| 68215 | - ** that it can read version A from the database file. However, since | |
| 68216 | - ** we can guarantee that the checkpointer that set nBackfill could not | |
| 68217 | - ** see any pages past pWal->hdr.mxFrame, this problem does not come up. | |
| 68218 | - */ | |
| 68219 | - pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; | |
| 68220 | - walShmBarrier(pWal); | |
| 68221 | - if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark | |
| 68222 | - || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) | |
| 68223 | - ){ | |
| 68224 | - walUnlockShared(pWal, WAL_READ_LOCK(mxI)); | |
| 68225 | - return WAL_RETRY; | |
| 68226 | - }else{ | |
| 68227 | - assert( mxReadMark<=pWal->hdr.mxFrame ); | |
| 68228 | - pWal->readLock = (i16)mxI; | |
| 68140 | + { | |
| 68141 | + u32 mxReadMark; /* Largest aReadMark[] value */ | |
| 68142 | + int mxI; /* Index of largest aReadMark[] value */ | |
| 68143 | + int i; /* Loop counter */ | |
| 68144 | + u32 mxFrame; /* Wal frame to lock to */ | |
| 68145 | + if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame | |
| 68146 | +#ifdef SQLITE_ENABLE_SNAPSHOT | |
| 68147 | + && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0) | |
| 68148 | +#endif | |
| 68149 | + ){ | |
| 68150 | + /* The WAL has been completely backfilled (or it is empty). | |
| 68151 | + ** and can be safely ignored. | |
| 68152 | + */ | |
| 68153 | + rc = walLockShared(pWal, WAL_READ_LOCK(0)); | |
| 68154 | + walShmBarrier(pWal); | |
| 68155 | + if( rc==SQLITE_OK ){ | |
| 68156 | + if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr,sizeof(WalIndexHdr)) ){ | |
| 68157 | + /* It is not safe to allow the reader to continue here if frames | |
| 68158 | + ** may have been appended to the log before READ_LOCK(0) was obtained. | |
| 68159 | + ** When holding READ_LOCK(0), the reader ignores the entire log file, | |
| 68160 | + ** which implies that the database file contains a trustworthy | |
| 68161 | + ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from | |
| 68162 | + ** happening, this is usually correct. | |
| 68163 | + ** | |
| 68164 | + ** However, if frames have been appended to the log (or if the log | |
| 68165 | + ** is wrapped and written for that matter) before the READ_LOCK(0) | |
| 68166 | + ** is obtained, that is not necessarily true. A checkpointer may | |
| 68167 | + ** have started to backfill the appended frames but crashed before | |
| 68168 | + ** it finished. Leaving a corrupt image in the database file. | |
| 68169 | + */ | |
| 68170 | + walUnlockShared(pWal, WAL_READ_LOCK(0)); | |
| 68171 | + return WAL_RETRY; | |
| 68172 | + } | |
| 68173 | + pWal->readLock = 0; | |
| 68174 | + return SQLITE_OK; | |
| 68175 | + }else if( rc!=SQLITE_BUSY ){ | |
| 68176 | + return rc; | |
| 68177 | + } | |
| 68178 | + } | |
| 68179 | + | |
| 68180 | + /* If we get this far, it means that the reader will want to use | |
| 68181 | + ** the WAL to get at content from recent commits. The job now is | |
| 68182 | + ** to select one of the aReadMark[] entries that is closest to | |
| 68183 | + ** but not exceeding pWal->hdr.mxFrame and lock that entry. | |
| 68184 | + */ | |
| 68185 | + mxReadMark = 0; | |
| 68186 | + mxI = 0; | |
| 68187 | + mxFrame = pWal->hdr.mxFrame; | |
| 68188 | +#ifdef SQLITE_ENABLE_SNAPSHOT | |
| 68189 | + if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){ | |
| 68190 | + mxFrame = pWal->pSnapshot->mxFrame; | |
| 68191 | + } | |
| 68192 | +#endif | |
| 68193 | + for(i=1; i<WAL_NREADER; i++){ | |
| 68194 | + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; | |
| 68195 | + if( mxReadMark<=thisMark && thisMark<=mxFrame ){ | |
| 68196 | + assert( thisMark!=READMARK_NOT_USED ); | |
| 68197 | + mxReadMark = thisMark; | |
| 68198 | + mxI = i; | |
| 68199 | + } | |
| 68200 | + } | |
| 68201 | + if( (pWal->readOnly & WAL_SHM_RDONLY)==0 | |
| 68202 | + && (mxReadMark<mxFrame || mxI==0) | |
| 68203 | + ){ | |
| 68204 | + for(i=1; i<WAL_NREADER; i++){ | |
| 68205 | + rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 68206 | + if( rc==SQLITE_OK ){ | |
| 68207 | + AtomicStore(pInfo->aReadMark+i,mxFrame); | |
| 68208 | + mxReadMark = mxFrame; | |
| 68209 | + mxI = i; | |
| 68210 | + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 68211 | + break; | |
| 68212 | + }else if( rc!=SQLITE_BUSY ){ | |
| 68213 | + return rc; | |
| 68214 | + } | |
| 68215 | + } | |
| 68216 | + } | |
| 68217 | + if( mxI==0 ){ | |
| 68218 | + assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); | |
| 68219 | + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; | |
| 68220 | + } | |
| 68221 | + | |
| 68222 | + (void)walEnableBlockingMs(pWal, nBlockTmout); | |
| 68223 | + rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); | |
| 68224 | + walDisableBlocking(pWal); | |
| 68225 | + if( rc ){ | |
| 68226 | +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT | |
| 68227 | + if( rc==SQLITE_BUSY_TIMEOUT ){ | |
| 68228 | + *pCnt |= WAL_RETRY_BLOCKED_MASK; | |
| 68229 | + } | |
| 68230 | +#else | |
| 68231 | + assert( rc!=SQLITE_BUSY_TIMEOUT ); | |
| 68232 | +#endif | |
| 68233 | + assert((rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT); | |
| 68234 | + return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; | |
| 68235 | + } | |
| 68236 | + /* Now that the read-lock has been obtained, check that neither the | |
| 68237 | + ** value in the aReadMark[] array or the contents of the wal-index | |
| 68238 | + ** header have changed. | |
| 68239 | + ** | |
| 68240 | + ** It is necessary to check that the wal-index header did not change | |
| 68241 | + ** between the time it was read and when the shared-lock was obtained | |
| 68242 | + ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility | |
| 68243 | + ** that the log file may have been wrapped by a writer, or that frames | |
| 68244 | + ** that occur later in the log than pWal->hdr.mxFrame may have been | |
| 68245 | + ** copied into the database by a checkpointer. If either of these things | |
| 68246 | + ** happened, then reading the database with the current value of | |
| 68247 | + ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry | |
| 68248 | + ** instead. | |
| 68249 | + ** | |
| 68250 | + ** Before checking that the live wal-index header has not changed | |
| 68251 | + ** since it was read, set Wal.minFrame to the first frame in the wal | |
| 68252 | + ** file that has not yet been checkpointed. This client will not need | |
| 68253 | + ** to read any frames earlier than minFrame from the wal file - they | |
| 68254 | + ** can be safely read directly from the database file. | |
| 68255 | + ** | |
| 68256 | + ** Because a ShmBarrier() call is made between taking the copy of | |
| 68257 | + ** nBackfill and checking that the wal-header in shared-memory still | |
| 68258 | + ** matches the one cached in pWal->hdr, it is guaranteed that the | |
| 68259 | + ** checkpointer that set nBackfill was not working with a wal-index | |
| 68260 | + ** header newer than that cached in pWal->hdr. If it were, that could | |
| 68261 | + ** cause a problem. The checkpointer could omit to checkpoint | |
| 68262 | + ** a version of page X that lies before pWal->minFrame (call that version | |
| 68263 | + ** A) on the basis that there is a newer version (version B) of the same | |
| 68264 | + ** page later in the wal file. But if version B happens to like past | |
| 68265 | + ** frame pWal->hdr.mxFrame - then the client would incorrectly assume | |
| 68266 | + ** that it can read version A from the database file. However, since | |
| 68267 | + ** we can guarantee that the checkpointer that set nBackfill could not | |
| 68268 | + ** see any pages past pWal->hdr.mxFrame, this problem does not come up. | |
| 68269 | + */ | |
| 68270 | + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; | |
| 68271 | + walShmBarrier(pWal); | |
| 68272 | + if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark | |
| 68273 | + || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) | |
| 68274 | + ){ | |
| 68275 | + walUnlockShared(pWal, WAL_READ_LOCK(mxI)); | |
| 68276 | + return WAL_RETRY; | |
| 68277 | + }else{ | |
| 68278 | + assert( mxReadMark<=pWal->hdr.mxFrame ); | |
| 68279 | + pWal->readLock = (i16)mxI; | |
| 68280 | + } | |
| 68229 | 68281 | } |
| 68230 | 68282 | return rc; |
| 68231 | 68283 | } |
| 68232 | 68284 | |
| 68233 | 68285 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| @@ -87103,10 +87155,11 @@ | ||
| 87103 | 87155 | ** |
| 87104 | 87156 | ** All other fields of Mem can safely remain uninitialized for now. They |
| 87105 | 87157 | ** will be initialized before use. |
| 87106 | 87158 | */ |
| 87107 | 87159 | static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ |
| 87160 | + assert( db!=0 ); | |
| 87108 | 87161 | if( N>0 ){ |
| 87109 | 87162 | do{ |
| 87110 | 87163 | p->flags = flags; |
| 87111 | 87164 | p->db = db; |
| 87112 | 87165 | p->szMalloc = 0; |
| @@ -87128,10 +87181,11 @@ | ||
| 87128 | 87181 | */ |
| 87129 | 87182 | static void releaseMemArray(Mem *p, int N){ |
| 87130 | 87183 | if( p && N ){ |
| 87131 | 87184 | Mem *pEnd = &p[N]; |
| 87132 | 87185 | sqlite3 *db = p->db; |
| 87186 | + assert( db!=0 ); | |
| 87133 | 87187 | if( db->pnBytesFreed ){ |
| 87134 | 87188 | do{ |
| 87135 | 87189 | if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); |
| 87136 | 87190 | }while( (++p)<pEnd ); |
| 87137 | 87191 | return; |
| @@ -87608,10 +87662,11 @@ | ||
| 87608 | 87662 | assert( p!=0 ); |
| 87609 | 87663 | assert( p->nOp>0 ); |
| 87610 | 87664 | assert( pParse!=0 ); |
| 87611 | 87665 | assert( p->eVdbeState==VDBE_INIT_STATE ); |
| 87612 | 87666 | assert( pParse==p->pParse ); |
| 87667 | + assert( pParse->db==p->db ); | |
| 87613 | 87668 | p->pVList = pParse->pVList; |
| 87614 | 87669 | pParse->pVList = 0; |
| 87615 | 87670 | db = p->db; |
| 87616 | 87671 | assert( db->mallocFailed==0 ); |
| 87617 | 87672 | nVar = pParse->nVar; |
| @@ -90488,10 +90543,11 @@ | ||
| 90488 | 90543 | db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); |
| 90489 | 90544 | db->pPreUpdate = 0; |
| 90490 | 90545 | sqlite3DbFree(db, preupdate.aRecord); |
| 90491 | 90546 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked); |
| 90492 | 90547 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked); |
| 90548 | + sqlite3VdbeMemRelease(&preupdate.oldipk); | |
| 90493 | 90549 | if( preupdate.aNew ){ |
| 90494 | 90550 | int i; |
| 90495 | 90551 | for(i=0; i<pCsr->nField; i++){ |
| 90496 | 90552 | sqlite3VdbeMemRelease(&preupdate.aNew[i]); |
| 90497 | 90553 | } |
| @@ -91844,11 +91900,11 @@ | ||
| 91844 | 91900 | ** |
| 91845 | 91901 | ** sqlite3_column_int() |
| 91846 | 91902 | ** sqlite3_column_int64() |
| 91847 | 91903 | ** sqlite3_column_text() |
| 91848 | 91904 | ** sqlite3_column_text16() |
| 91849 | -** sqlite3_column_real() | |
| 91905 | +** sqlite3_column_double() | |
| 91850 | 91906 | ** sqlite3_column_bytes() |
| 91851 | 91907 | ** sqlite3_column_bytes16() |
| 91852 | 91908 | ** sqlite3_column_blob() |
| 91853 | 91909 | */ |
| 91854 | 91910 | static void columnMallocFailure(sqlite3_stmt *pStmt) |
| @@ -92706,64 +92762,68 @@ | ||
| 92706 | 92762 | if( iIdx>=p->pCsr->nField || iIdx<0 ){ |
| 92707 | 92763 | rc = SQLITE_RANGE; |
| 92708 | 92764 | goto preupdate_old_out; |
| 92709 | 92765 | } |
| 92710 | 92766 | |
| 92711 | - /* If the old.* record has not yet been loaded into memory, do so now. */ | |
| 92712 | - if( p->pUnpacked==0 ){ | |
| 92713 | - u32 nRec; | |
| 92714 | - u8 *aRec; | |
| 92715 | - | |
| 92716 | - assert( p->pCsr->eCurType==CURTYPE_BTREE ); | |
| 92717 | - nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); | |
| 92718 | - aRec = sqlite3DbMallocRaw(db, nRec); | |
| 92719 | - if( !aRec ) goto preupdate_old_out; | |
| 92720 | - rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); | |
| 92721 | - if( rc==SQLITE_OK ){ | |
| 92722 | - p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); | |
| 92723 | - if( !p->pUnpacked ) rc = SQLITE_NOMEM; | |
| 92724 | - } | |
| 92725 | - if( rc!=SQLITE_OK ){ | |
| 92726 | - sqlite3DbFree(db, aRec); | |
| 92727 | - goto preupdate_old_out; | |
| 92728 | - } | |
| 92729 | - p->aRecord = aRec; | |
| 92730 | - } | |
| 92731 | - | |
| 92732 | - pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; | |
| 92733 | 92767 | if( iIdx==p->pTab->iPKey ){ |
| 92768 | + *ppValue = pMem = &p->oldipk; | |
| 92734 | 92769 | sqlite3VdbeMemSetInt64(pMem, p->iKey1); |
| 92735 | - }else if( iIdx>=p->pUnpacked->nField ){ | |
| 92736 | - /* This occurs when the table has been extended using ALTER TABLE | |
| 92737 | - ** ADD COLUMN. The value to return is the default value of the column. */ | |
| 92738 | - Column *pCol = &p->pTab->aCol[iIdx]; | |
| 92739 | - if( pCol->iDflt>0 ){ | |
| 92740 | - if( p->apDflt==0 ){ | |
| 92741 | - int nByte = sizeof(sqlite3_value*)*p->pTab->nCol; | |
| 92742 | - p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); | |
| 92743 | - if( p->apDflt==0 ) goto preupdate_old_out; | |
| 92744 | - } | |
| 92745 | - if( p->apDflt[iIdx]==0 ){ | |
| 92746 | - sqlite3_value *pVal = 0; | |
| 92747 | - Expr *pDflt; | |
| 92748 | - assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) ); | |
| 92749 | - pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; | |
| 92750 | - rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); | |
| 92751 | - if( rc==SQLITE_OK && pVal==0 ){ | |
| 92752 | - rc = SQLITE_CORRUPT_BKPT; | |
| 92753 | - } | |
| 92754 | - p->apDflt[iIdx] = pVal; | |
| 92755 | - } | |
| 92756 | - *ppValue = p->apDflt[iIdx]; | |
| 92757 | - }else{ | |
| 92758 | - *ppValue = (sqlite3_value *)columnNullValue(); | |
| 92759 | - } | |
| 92760 | - }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ | |
| 92761 | - if( pMem->flags & (MEM_Int|MEM_IntReal) ){ | |
| 92762 | - testcase( pMem->flags & MEM_Int ); | |
| 92763 | - testcase( pMem->flags & MEM_IntReal ); | |
| 92764 | - sqlite3VdbeMemRealify(pMem); | |
| 92770 | + }else{ | |
| 92771 | + | |
| 92772 | + /* If the old.* record has not yet been loaded into memory, do so now. */ | |
| 92773 | + if( p->pUnpacked==0 ){ | |
| 92774 | + u32 nRec; | |
| 92775 | + u8 *aRec; | |
| 92776 | + | |
| 92777 | + assert( p->pCsr->eCurType==CURTYPE_BTREE ); | |
| 92778 | + nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); | |
| 92779 | + aRec = sqlite3DbMallocRaw(db, nRec); | |
| 92780 | + if( !aRec ) goto preupdate_old_out; | |
| 92781 | + rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); | |
| 92782 | + if( rc==SQLITE_OK ){ | |
| 92783 | + p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); | |
| 92784 | + if( !p->pUnpacked ) rc = SQLITE_NOMEM; | |
| 92785 | + } | |
| 92786 | + if( rc!=SQLITE_OK ){ | |
| 92787 | + sqlite3DbFree(db, aRec); | |
| 92788 | + goto preupdate_old_out; | |
| 92789 | + } | |
| 92790 | + p->aRecord = aRec; | |
| 92791 | + } | |
| 92792 | + | |
| 92793 | + pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; | |
| 92794 | + if( iIdx>=p->pUnpacked->nField ){ | |
| 92795 | + /* This occurs when the table has been extended using ALTER TABLE | |
| 92796 | + ** ADD COLUMN. The value to return is the default value of the column. */ | |
| 92797 | + Column *pCol = &p->pTab->aCol[iIdx]; | |
| 92798 | + if( pCol->iDflt>0 ){ | |
| 92799 | + if( p->apDflt==0 ){ | |
| 92800 | + int nByte = sizeof(sqlite3_value*)*p->pTab->nCol; | |
| 92801 | + p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); | |
| 92802 | + if( p->apDflt==0 ) goto preupdate_old_out; | |
| 92803 | + } | |
| 92804 | + if( p->apDflt[iIdx]==0 ){ | |
| 92805 | + sqlite3_value *pVal = 0; | |
| 92806 | + Expr *pDflt; | |
| 92807 | + assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) ); | |
| 92808 | + pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; | |
| 92809 | + rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); | |
| 92810 | + if( rc==SQLITE_OK && pVal==0 ){ | |
| 92811 | + rc = SQLITE_CORRUPT_BKPT; | |
| 92812 | + } | |
| 92813 | + p->apDflt[iIdx] = pVal; | |
| 92814 | + } | |
| 92815 | + *ppValue = p->apDflt[iIdx]; | |
| 92816 | + }else{ | |
| 92817 | + *ppValue = (sqlite3_value *)columnNullValue(); | |
| 92818 | + } | |
| 92819 | + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ | |
| 92820 | + if( pMem->flags & (MEM_Int|MEM_IntReal) ){ | |
| 92821 | + testcase( pMem->flags & MEM_Int ); | |
| 92822 | + testcase( pMem->flags & MEM_IntReal ); | |
| 92823 | + sqlite3VdbeMemRealify(pMem); | |
| 92824 | + } | |
| 92765 | 92825 | } |
| 92766 | 92826 | } |
| 92767 | 92827 | |
| 92768 | 92828 | preupdate_old_out: |
| 92769 | 92829 | sqlite3Error(db, rc); |
| @@ -97913,13 +97973,15 @@ | ||
| 97913 | 97973 | 0, pCx->uc.pCursor); |
| 97914 | 97974 | pCx->isTable = 1; |
| 97915 | 97975 | } |
| 97916 | 97976 | } |
| 97917 | 97977 | pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); |
| 97978 | + assert( p->apCsr[pOp->p1]==pCx ); | |
| 97918 | 97979 | if( rc ){ |
| 97919 | 97980 | assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97920 | 97981 | sqlite3BtreeClose(pCx->ub.pBtx); |
| 97982 | + p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */ | |
| 97921 | 97983 | }else{ |
| 97922 | 97984 | assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97923 | 97985 | } |
| 97924 | 97986 | } |
| 97925 | 97987 | } |
| @@ -109845,11 +109907,11 @@ | ||
| 109845 | 109907 | p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); |
| 109846 | 109908 | } |
| 109847 | 109909 | p5 = binaryCompareP5(pLeft, pRight, jumpIfNull); |
| 109848 | 109910 | addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1, |
| 109849 | 109911 | (void*)p4, P4_COLLSEQ); |
| 109850 | - sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5); | |
| 109912 | + sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5); | |
| 109851 | 109913 | return addr; |
| 109852 | 109914 | } |
| 109853 | 109915 | |
| 109854 | 109916 | /* |
| 109855 | 109917 | ** Return true if expression pExpr is a vector, or false otherwise. |
| @@ -112012,11 +112074,11 @@ | ||
| 112012 | 112074 | ** |
| 112013 | 112075 | ** (4) If pSrc is the right operand of a LEFT JOIN, then... |
| 112014 | 112076 | ** (4a) pExpr must come from an ON clause.. |
| 112015 | 112077 | ** (4b) and specifically the ON clause associated with the LEFT JOIN. |
| 112016 | 112078 | ** |
| 112017 | -** (5) If pSrc is not the right operand of a LEFT JOIN or the left | |
| 112079 | +** (5) If pSrc is the right operand of a LEFT JOIN or the left | |
| 112018 | 112080 | ** operand of a RIGHT JOIN, then pExpr must be from the WHERE |
| 112019 | 112081 | ** clause, not an ON clause. |
| 112020 | 112082 | ** |
| 112021 | 112083 | ** (6) Either: |
| 112022 | 112084 | ** |
| @@ -115546,35 +115608,41 @@ | ||
| 115546 | 115608 | ** |
| 115547 | 115609 | ** Additionally, if pExpr is a simple SQL value and the value is the |
| 115548 | 115610 | ** same as that currently bound to variable pVar, non-zero is returned. |
| 115549 | 115611 | ** Otherwise, if the values are not the same or if pExpr is not a simple |
| 115550 | 115612 | ** SQL value, zero is returned. |
| 115613 | +** | |
| 115614 | +** If the SQLITE_EnableQPSG flag is set on the database connection, then | |
| 115615 | +** this routine always returns false. | |
| 115551 | 115616 | */ |
| 115552 | -static int exprCompareVariable( | |
| 115617 | +static SQLITE_NOINLINE int exprCompareVariable( | |
| 115553 | 115618 | const Parse *pParse, |
| 115554 | 115619 | const Expr *pVar, |
| 115555 | 115620 | const Expr *pExpr |
| 115556 | 115621 | ){ |
| 115557 | - int res = 0; | |
| 115622 | + int res = 2; | |
| 115558 | 115623 | int iVar; |
| 115559 | 115624 | sqlite3_value *pL, *pR = 0; |
| 115560 | 115625 | |
| 115626 | + if( pExpr->op==TK_VARIABLE && pVar->iColumn==pExpr->iColumn ){ | |
| 115627 | + return 0; | |
| 115628 | + } | |
| 115629 | + if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2; | |
| 115561 | 115630 | sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); |
| 115562 | 115631 | if( pR ){ |
| 115563 | 115632 | iVar = pVar->iColumn; |
| 115564 | 115633 | sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); |
| 115565 | 115634 | pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB); |
| 115566 | 115635 | if( pL ){ |
| 115567 | 115636 | if( sqlite3_value_type(pL)==SQLITE_TEXT ){ |
| 115568 | 115637 | sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ |
| 115569 | 115638 | } |
| 115570 | - res = 0==sqlite3MemCompare(pL, pR, 0); | |
| 115639 | + res = sqlite3MemCompare(pL, pR, 0) ? 2 : 0; | |
| 115571 | 115640 | } |
| 115572 | 115641 | sqlite3ValueFree(pR); |
| 115573 | 115642 | sqlite3ValueFree(pL); |
| 115574 | 115643 | } |
| 115575 | - | |
| 115576 | 115644 | return res; |
| 115577 | 115645 | } |
| 115578 | 115646 | |
| 115579 | 115647 | /* |
| 115580 | 115648 | ** Do a deep comparison of two expression trees. Return 0 if the two |
| @@ -115596,16 +115664,14 @@ | ||
| 115596 | 115664 | ** can be sure the expressions are the same. In the places where |
| 115597 | 115665 | ** this routine is used, it does not hurt to get an extra 2 - that |
| 115598 | 115666 | ** just might result in some slightly slower code. But returning |
| 115599 | 115667 | ** an incorrect 0 or 1 could lead to a malfunction. |
| 115600 | 115668 | ** |
| 115601 | -** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in | |
| 115602 | -** pParse->pReprepare can be matched against literals in pB. The | |
| 115603 | -** pParse->pVdbe->expmask bitmask is updated for each variable referenced. | |
| 115604 | -** If pParse is NULL (the normal case) then any TK_VARIABLE term in | |
| 115605 | -** Argument pParse should normally be NULL. If it is not NULL and pA or | |
| 115606 | -** pB causes a return value of 2. | |
| 115669 | +** If pParse is not NULL and SQLITE_EnableQPSG is off then TK_VARIABLE | |
| 115670 | +** terms in pA with bindings in pParse->pReprepare can be matched against | |
| 115671 | +** literals in pB. The pParse->pVdbe->expmask bitmask is updated for | |
| 115672 | +** each variable referenced. | |
| 115607 | 115673 | */ |
| 115608 | 115674 | SQLITE_PRIVATE int sqlite3ExprCompare( |
| 115609 | 115675 | const Parse *pParse, |
| 115610 | 115676 | const Expr *pA, |
| 115611 | 115677 | const Expr *pB, |
| @@ -115613,12 +115679,12 @@ | ||
| 115613 | 115679 | ){ |
| 115614 | 115680 | u32 combinedFlags; |
| 115615 | 115681 | if( pA==0 || pB==0 ){ |
| 115616 | 115682 | return pB==pA ? 0 : 2; |
| 115617 | 115683 | } |
| 115618 | - if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){ | |
| 115619 | - return 0; | |
| 115684 | + if( pParse && pA->op==TK_VARIABLE ){ | |
| 115685 | + return exprCompareVariable(pParse, pA, pB); | |
| 115620 | 115686 | } |
| 115621 | 115687 | combinedFlags = pA->flags | pB->flags; |
| 115622 | 115688 | if( combinedFlags & EP_IntValue ){ |
| 115623 | 115689 | if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ |
| 115624 | 115690 | return 0; |
| @@ -115808,23 +115874,75 @@ | ||
| 115808 | 115874 | return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); |
| 115809 | 115875 | } |
| 115810 | 115876 | } |
| 115811 | 115877 | return 0; |
| 115812 | 115878 | } |
| 115879 | + | |
| 115880 | +/* | |
| 115881 | +** Return true if the boolean value of the expression is always either | |
| 115882 | +** FALSE or NULL. | |
| 115883 | +*/ | |
| 115884 | +static int sqlite3ExprIsNotTrue(Expr *pExpr){ | |
| 115885 | + int v; | |
| 115886 | + if( pExpr->op==TK_NULL ) return 1; | |
| 115887 | + if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1; | |
| 115888 | + v = 1; | |
| 115889 | + if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1; | |
| 115890 | + return 0; | |
| 115891 | +} | |
| 115892 | + | |
| 115893 | +/* | |
| 115894 | +** Return true if the expression is one of the following: | |
| 115895 | +** | |
| 115896 | +** CASE WHEN x THEN y END | |
| 115897 | +** CASE WHEN x THEN y ELSE NULL END | |
| 115898 | +** CASE WHEN x THEN y ELSE false END | |
| 115899 | +** iif(x,y) | |
| 115900 | +** iif(x,y,NULL) | |
| 115901 | +** iif(x,y,false) | |
| 115902 | +*/ | |
| 115903 | +static int sqlite3ExprIsIIF(sqlite3 *db, const Expr *pExpr){ | |
| 115904 | + ExprList *pList; | |
| 115905 | + if( pExpr->op==TK_FUNCTION ){ | |
| 115906 | + const char *z = pExpr->u.zToken; | |
| 115907 | + FuncDef *pDef; | |
| 115908 | + if( (z[0]!='i' && z[0]!='I') ) return 0; | |
| 115909 | + if( pExpr->x.pList==0 ) return 0; | |
| 115910 | + pDef = sqlite3FindFunction(db, z, pExpr->x.pList->nExpr, ENC(db), 0); | |
| 115911 | +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION | |
| 115912 | + if( pDef==0 ) return 0; | |
| 115913 | +#else | |
| 115914 | + if( NEVER(pDef==0) ) return 0; | |
| 115915 | +#endif | |
| 115916 | + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)==0 ) return 0; | |
| 115917 | + if( SQLITE_PTR_TO_INT(pDef->pUserData)!=INLINEFUNC_iif ) return 0; | |
| 115918 | + }else if( pExpr->op==TK_CASE ){ | |
| 115919 | + if( pExpr->pLeft!=0 ) return 0; | |
| 115920 | + }else{ | |
| 115921 | + return 0; | |
| 115922 | + } | |
| 115923 | + pList = pExpr->x.pList; | |
| 115924 | + assert( pList!=0 ); | |
| 115925 | + if( pList->nExpr==2 ) return 1; | |
| 115926 | + if( pList->nExpr==3 && sqlite3ExprIsNotTrue(pList->a[2].pExpr) ) return 1; | |
| 115927 | + return 0; | |
| 115928 | +} | |
| 115813 | 115929 | |
| 115814 | 115930 | /* |
| 115815 | 115931 | ** Return true if we can prove the pE2 will always be true if pE1 is |
| 115816 | 115932 | ** true. Return false if we cannot complete the proof or if pE2 might |
| 115817 | 115933 | ** be false. Examples: |
| 115818 | 115934 | ** |
| 115819 | -** pE1: x==5 pE2: x==5 Result: true | |
| 115820 | -** pE1: x>0 pE2: x==5 Result: false | |
| 115821 | -** pE1: x=21 pE2: x=21 OR y=43 Result: true | |
| 115822 | -** pE1: x!=123 pE2: x IS NOT NULL Result: true | |
| 115823 | -** pE1: x!=?1 pE2: x IS NOT NULL Result: true | |
| 115824 | -** pE1: x IS NULL pE2: x IS NOT NULL Result: false | |
| 115825 | -** pE1: x IS ?2 pE2: x IS NOT NULL Result: false | |
| 115935 | +** pE1: x==5 pE2: x==5 Result: true | |
| 115936 | +** pE1: x>0 pE2: x==5 Result: false | |
| 115937 | +** pE1: x=21 pE2: x=21 OR y=43 Result: true | |
| 115938 | +** pE1: x!=123 pE2: x IS NOT NULL Result: true | |
| 115939 | +** pE1: x!=?1 pE2: x IS NOT NULL Result: true | |
| 115940 | +** pE1: x IS NULL pE2: x IS NOT NULL Result: false | |
| 115941 | +** pE1: x IS ?2 pE2: x IS NOT NULL Result: false | |
| 115942 | +** pE1: iif(x,y) pE2: x Result: true | |
| 115943 | +** PE1: iif(x,y,0) pE2: x Result: true | |
| 115826 | 115944 | ** |
| 115827 | 115945 | ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has |
| 115828 | 115946 | ** Expr.iTable<0 then assume a table number given by iTab. |
| 115829 | 115947 | ** |
| 115830 | 115948 | ** If pParse is not NULL, then the values of bound variables in pE1 are |
| @@ -115854,10 +115972,13 @@ | ||
| 115854 | 115972 | if( pE2->op==TK_NOTNULL |
| 115855 | 115973 | && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0) |
| 115856 | 115974 | ){ |
| 115857 | 115975 | return 1; |
| 115858 | 115976 | } |
| 115977 | + if( sqlite3ExprIsIIF(pParse->db, pE1) ){ | |
| 115978 | + return sqlite3ExprImpliesExpr(pParse,pE1->x.pList->a[0].pExpr,pE2,iTab); | |
| 115979 | + } | |
| 115859 | 115980 | return 0; |
| 115860 | 115981 | } |
| 115861 | 115982 | |
| 115862 | 115983 | /* This is a helper function to impliesNotNullRow(). In this routine, |
| 115863 | 115984 | ** set pWalker->eCode to one only if *both* of the input expressions |
| @@ -121265,19 +121386,10 @@ | ||
| 121265 | 121386 | rc = sqlite3Init(db, &zErrDyn); |
| 121266 | 121387 | } |
| 121267 | 121388 | sqlite3BtreeLeaveAll(db); |
| 121268 | 121389 | assert( zErrDyn==0 || rc!=SQLITE_OK ); |
| 121269 | 121390 | } |
| 121270 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 121271 | - if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){ | |
| 121272 | - u8 newAuth = 0; | |
| 121273 | - rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); | |
| 121274 | - if( newAuth<db->auth.authLevel ){ | |
| 121275 | - rc = SQLITE_AUTH_USER; | |
| 121276 | - } | |
| 121277 | - } | |
| 121278 | -#endif | |
| 121279 | 121391 | if( rc ){ |
| 121280 | 121392 | if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ |
| 121281 | 121393 | int iDb = db->nDb - 1; |
| 121282 | 121394 | assert( iDb>=2 ); |
| 121283 | 121395 | if( db->aDb[iDb].pBt ){ |
| @@ -121771,15 +121883,11 @@ | ||
| 121771 | 121883 | sqlite3 *db = pParse->db; /* Database handle */ |
| 121772 | 121884 | char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ |
| 121773 | 121885 | int rc; /* Auth callback return code */ |
| 121774 | 121886 | |
| 121775 | 121887 | if( db->init.busy ) return SQLITE_OK; |
| 121776 | - rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext | |
| 121777 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 121778 | - ,db->auth.zAuthUser | |
| 121779 | -#endif | |
| 121780 | - ); | |
| 121888 | + rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext); | |
| 121781 | 121889 | if( rc==SQLITE_DENY ){ |
| 121782 | 121890 | char *z = sqlite3_mprintf("%s.%s", zTab, zCol); |
| 121783 | 121891 | if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z); |
| 121784 | 121892 | sqlite3ErrorMsg(pParse, "access to %z is prohibited", z); |
| 121785 | 121893 | pParse->rc = SQLITE_AUTH; |
| @@ -121882,15 +121990,11 @@ | ||
| 121882 | 121990 | testcase( zArg1==0 ); |
| 121883 | 121991 | testcase( zArg2==0 ); |
| 121884 | 121992 | testcase( zArg3==0 ); |
| 121885 | 121993 | testcase( pParse->zAuthContext==0 ); |
| 121886 | 121994 | |
| 121887 | - rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext | |
| 121888 | -#ifdef SQLITE_USER_AUTHENTICATION | |
| 121889 | - ,db->auth.zAuthUser | |
| 121890 | -#endif | |
| 121891 | - ); | |
| 121995 | + rc = db->xAuth(db->pAuthArg,code,zArg1,zArg2,zArg3,pParse->zAuthContext); | |
| 121892 | 121996 | if( rc==SQLITE_DENY ){ |
| 121893 | 121997 | sqlite3ErrorMsg(pParse, "not authorized"); |
| 121894 | 121998 | pParse->rc = SQLITE_AUTH; |
| 121895 | 121999 | }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ |
| 121896 | 122000 | rc = SQLITE_DENY; |
| @@ -122119,21 +122223,10 @@ | ||
| 122119 | 122223 | sqlite3VdbeJumpHere(v, addrRewind); |
| 122120 | 122224 | } |
| 122121 | 122225 | } |
| 122122 | 122226 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 122123 | 122227 | |
| 122124 | -#if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE) | |
| 122125 | - if( pParse->nTableLock>0 && db->init.busy==0 ){ | |
| 122126 | - sqlite3UserAuthInit(db); | |
| 122127 | - if( db->auth.authLevel<UAUTH_User ){ | |
| 122128 | - sqlite3ErrorMsg(pParse, "user not authenticated"); | |
| 122129 | - pParse->rc = SQLITE_AUTH_USER; | |
| 122130 | - return; | |
| 122131 | - } | |
| 122132 | - } | |
| 122133 | -#endif | |
| 122134 | - | |
| 122135 | 122228 | /* The cookie mask contains one bit for each database file open. |
| 122136 | 122229 | ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are |
| 122137 | 122230 | ** set for each database that is used. Generate code to start a |
| 122138 | 122231 | ** transaction on each used database and to verify the schema cookie |
| 122139 | 122232 | ** on each used database. |
| @@ -122258,20 +122351,10 @@ | ||
| 122258 | 122351 | sqlite3DbFree(db, zSql); |
| 122259 | 122352 | memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); |
| 122260 | 122353 | pParse->nested--; |
| 122261 | 122354 | } |
| 122262 | 122355 | |
| 122263 | -#if SQLITE_USER_AUTHENTICATION | |
| 122264 | -/* | |
| 122265 | -** Return TRUE if zTable is the name of the system table that stores the | |
| 122266 | -** list of users and their access credentials. | |
| 122267 | -*/ | |
| 122268 | -SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ | |
| 122269 | - return sqlite3_stricmp(zTable, "sqlite_user")==0; | |
| 122270 | -} | |
| 122271 | -#endif | |
| 122272 | - | |
| 122273 | 122356 | /* |
| 122274 | 122357 | ** Locate the in-memory structure that describes a particular database |
| 122275 | 122358 | ** table given the name of that table and (optionally) the name of the |
| 122276 | 122359 | ** database containing the table. Return NULL if not found. |
| 122277 | 122360 | ** |
| @@ -122286,17 +122369,10 @@ | ||
| 122286 | 122369 | Table *p = 0; |
| 122287 | 122370 | int i; |
| 122288 | 122371 | |
| 122289 | 122372 | /* All mutexes are required for schema access. Make sure we hold them. */ |
| 122290 | 122373 | assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); |
| 122291 | -#if SQLITE_USER_AUTHENTICATION | |
| 122292 | - /* Only the admin user is allowed to know that the sqlite_user table | |
| 122293 | - ** exists */ | |
| 122294 | - if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){ | |
| 122295 | - return 0; | |
| 122296 | - } | |
| 122297 | -#endif | |
| 122298 | 122374 | if( zDatabase ){ |
| 122299 | 122375 | for(i=0; i<db->nDb; i++){ |
| 122300 | 122376 | if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; |
| 122301 | 122377 | } |
| 122302 | 122378 | if( i>=db->nDb ){ |
| @@ -125951,13 +126027,10 @@ | ||
| 125951 | 126027 | |
| 125952 | 126028 | assert( pTab!=0 ); |
| 125953 | 126029 | if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 |
| 125954 | 126030 | && db->init.busy==0 |
| 125955 | 126031 | && pTblName!=0 |
| 125956 | -#if SQLITE_USER_AUTHENTICATION | |
| 125957 | - && sqlite3UserAuthTable(pTab->zName)==0 | |
| 125958 | -#endif | |
| 125959 | 126032 | ){ |
| 125960 | 126033 | sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); |
| 125961 | 126034 | goto exit_create_index; |
| 125962 | 126035 | } |
| 125963 | 126036 | #ifndef SQLITE_OMIT_VIEW |
| @@ -129661,20 +129734,19 @@ | ||
| 129661 | 129734 | const unsigned char *z; |
| 129662 | 129735 | const unsigned char *z2; |
| 129663 | 129736 | int len; |
| 129664 | 129737 | int p0type; |
| 129665 | 129738 | i64 p1, p2; |
| 129666 | - int negP2 = 0; | |
| 129667 | 129739 | |
| 129668 | 129740 | assert( argc==3 || argc==2 ); |
| 129669 | 129741 | if( sqlite3_value_type(argv[1])==SQLITE_NULL |
| 129670 | 129742 | || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL) |
| 129671 | 129743 | ){ |
| 129672 | 129744 | return; |
| 129673 | 129745 | } |
| 129674 | 129746 | p0type = sqlite3_value_type(argv[0]); |
| 129675 | - p1 = sqlite3_value_int(argv[1]); | |
| 129747 | + p1 = sqlite3_value_int64(argv[1]); | |
| 129676 | 129748 | if( p0type==SQLITE_BLOB ){ |
| 129677 | 129749 | len = sqlite3_value_bytes(argv[0]); |
| 129678 | 129750 | z = sqlite3_value_blob(argv[0]); |
| 129679 | 129751 | if( z==0 ) return; |
| 129680 | 129752 | assert( len==sqlite3_value_bytes(argv[0]) ); |
| @@ -129695,36 +129767,36 @@ | ||
| 129695 | 129767 | ** from 2009-02-02 for compatibility of applications that exploited the |
| 129696 | 129768 | ** old buggy behavior. */ |
| 129697 | 129769 | if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */ |
| 129698 | 129770 | #endif |
| 129699 | 129771 | if( argc==3 ){ |
| 129700 | - p2 = sqlite3_value_int(argv[2]); | |
| 129701 | - if( p2<0 ){ | |
| 129702 | - p2 = -p2; | |
| 129703 | - negP2 = 1; | |
| 129704 | - } | |
| 129772 | + p2 = sqlite3_value_int64(argv[2]); | |
| 129705 | 129773 | }else{ |
| 129706 | 129774 | p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH]; |
| 129707 | 129775 | } |
| 129708 | 129776 | if( p1<0 ){ |
| 129709 | 129777 | p1 += len; |
| 129710 | 129778 | if( p1<0 ){ |
| 129711 | - p2 += p1; | |
| 129712 | - if( p2<0 ) p2 = 0; | |
| 129779 | + if( p2<0 ){ | |
| 129780 | + p2 = 0; | |
| 129781 | + }else{ | |
| 129782 | + p2 += p1; | |
| 129783 | + } | |
| 129713 | 129784 | p1 = 0; |
| 129714 | 129785 | } |
| 129715 | 129786 | }else if( p1>0 ){ |
| 129716 | 129787 | p1--; |
| 129717 | 129788 | }else if( p2>0 ){ |
| 129718 | 129789 | p2--; |
| 129719 | 129790 | } |
| 129720 | - if( negP2 ){ | |
| 129791 | + if( p2<0 ){ | |
| 129792 | + if( p2<-p1 ){ | |
| 129793 | + p2 = p1; | |
| 129794 | + }else{ | |
| 129795 | + p2 = -p2; | |
| 129796 | + } | |
| 129721 | 129797 | p1 -= p2; |
| 129722 | - if( p1<0 ){ | |
| 129723 | - p2 += p1; | |
| 129724 | - p1 = 0; | |
| 129725 | - } | |
| 129726 | 129798 | } |
| 129727 | 129799 | assert( p1>=0 && p2>=0 ); |
| 129728 | 129800 | if( p0type!=SQLITE_BLOB ){ |
| 129729 | 129801 | while( *z && p1 ){ |
| 129730 | 129802 | SQLITE_SKIP_UTF8(z); |
| @@ -129734,13 +129806,15 @@ | ||
| 129734 | 129806 | SQLITE_SKIP_UTF8(z2); |
| 129735 | 129807 | } |
| 129736 | 129808 | sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, |
| 129737 | 129809 | SQLITE_UTF8); |
| 129738 | 129810 | }else{ |
| 129739 | - if( p1+p2>len ){ | |
| 129811 | + if( p1>=len ){ | |
| 129812 | + p1 = p2 = 0; | |
| 129813 | + }else if( p2>len-p1 ){ | |
| 129740 | 129814 | p2 = len-p1; |
| 129741 | - if( p2<0 ) p2 = 0; | |
| 129815 | + assert( p2>0 ); | |
| 129742 | 129816 | } |
| 129743 | 129817 | sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); |
| 129744 | 129818 | } |
| 129745 | 129819 | } |
| 129746 | 129820 | |
| @@ -129747,17 +129821,17 @@ | ||
| 129747 | 129821 | /* |
| 129748 | 129822 | ** Implementation of the round() function |
| 129749 | 129823 | */ |
| 129750 | 129824 | #ifndef SQLITE_OMIT_FLOATING_POINT |
| 129751 | 129825 | static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ |
| 129752 | - int n = 0; | |
| 129826 | + i64 n = 0; | |
| 129753 | 129827 | double r; |
| 129754 | 129828 | char *zBuf; |
| 129755 | 129829 | assert( argc==1 || argc==2 ); |
| 129756 | 129830 | if( argc==2 ){ |
| 129757 | 129831 | if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; |
| 129758 | - n = sqlite3_value_int(argv[1]); | |
| 129832 | + n = sqlite3_value_int64(argv[1]); | |
| 129759 | 129833 | if( n>30 ) n = 30; |
| 129760 | 129834 | if( n<0 ) n = 0; |
| 129761 | 129835 | } |
| 129762 | 129836 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; |
| 129763 | 129837 | r = sqlite3_value_double(argv[0]); |
| @@ -129768,11 +129842,11 @@ | ||
| 129768 | 129842 | if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ |
| 129769 | 129843 | /* The value has no fractional part so there is nothing to round */ |
| 129770 | 129844 | }else if( n==0 ){ |
| 129771 | 129845 | r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); |
| 129772 | 129846 | }else{ |
| 129773 | - zBuf = sqlite3_mprintf("%!.*f",n,r); | |
| 129847 | + zBuf = sqlite3_mprintf("%!.*f",(int)n,r); | |
| 129774 | 129848 | if( zBuf==0 ){ |
| 129775 | 129849 | sqlite3_result_error_nomem(context); |
| 129776 | 129850 | return; |
| 129777 | 129851 | } |
| 129778 | 129852 | sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); |
| @@ -131985,13 +132059,10 @@ | ||
| 131985 | 132059 | #endif |
| 131986 | 132060 | #ifndef SQLITE_OMIT_LOAD_EXTENSION |
| 131987 | 132061 | SFUNCTION(load_extension, 1, 0, 0, loadExt ), |
| 131988 | 132062 | SFUNCTION(load_extension, 2, 0, 0, loadExt ), |
| 131989 | 132063 | #endif |
| 131990 | -#if SQLITE_USER_AUTHENTICATION | |
| 131991 | - FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), | |
| 131992 | -#endif | |
| 131993 | 132064 | #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS |
| 131994 | 132065 | DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ), |
| 131995 | 132066 | DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ), |
| 131996 | 132067 | #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ |
| 131997 | 132068 | INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), |
| @@ -132124,11 +132195,14 @@ | ||
| 132124 | 132195 | MFUNCTION(degrees, 1, radToDeg, math1Func ), |
| 132125 | 132196 | MFUNCTION(pi, 0, 0, piFunc ), |
| 132126 | 132197 | #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ |
| 132127 | 132198 | FUNCTION(sign, 1, 0, 0, signFunc ), |
| 132128 | 132199 | INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ), |
| 132200 | + INLINE_FUNC(iif, 2, INLINEFUNC_iif, 0 ), | |
| 132129 | 132201 | INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ), |
| 132202 | + INLINE_FUNC(if, 2, INLINEFUNC_iif, 0 ), | |
| 132203 | + INLINE_FUNC(if, 3, INLINEFUNC_iif, 0 ), | |
| 132130 | 132204 | }; |
| 132131 | 132205 | #ifndef SQLITE_OMIT_ALTERTABLE |
| 132132 | 132206 | sqlite3AlterFunctions(); |
| 132133 | 132207 | #endif |
| 132134 | 132208 | sqlite3WindowFunctions(); |
| @@ -140637,16 +140711,10 @@ | ||
| 140637 | 140711 | if( db->autoCommit==0 ){ |
| 140638 | 140712 | /* Foreign key support may not be enabled or disabled while not |
| 140639 | 140713 | ** in auto-commit mode. */ |
| 140640 | 140714 | mask &= ~(SQLITE_ForeignKeys); |
| 140641 | 140715 | } |
| 140642 | -#if SQLITE_USER_AUTHENTICATION | |
| 140643 | - if( db->auth.authLevel==UAUTH_User ){ | |
| 140644 | - /* Do not allow non-admin users to modify the schema arbitrarily */ | |
| 140645 | - mask &= ~(SQLITE_WriteSchema); | |
| 140646 | - } | |
| 140647 | -#endif | |
| 140648 | 140716 | |
| 140649 | 140717 | if( sqlite3GetBoolean(zRight, 0) ){ |
| 140650 | 140718 | if( (mask & SQLITE_WriteSchema)==0 |
| 140651 | 140719 | || (db->flags & SQLITE_Defensive)==0 |
| 140652 | 140720 | ){ |
| @@ -140778,11 +140846,12 @@ | ||
| 140778 | 140846 | pTab = sqliteHashData(k); |
| 140779 | 140847 | if( pTab->nCol==0 ){ |
| 140780 | 140848 | char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); |
| 140781 | 140849 | if( zSql ){ |
| 140782 | 140850 | sqlite3_stmt *pDummy = 0; |
| 140783 | - (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0); | |
| 140851 | + (void)sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_DONT_LOG, | |
| 140852 | + &pDummy, 0); | |
| 140784 | 140853 | (void)sqlite3_finalize(pDummy); |
| 140785 | 140854 | sqlite3DbFree(db, zSql); |
| 140786 | 140855 | } |
| 140787 | 140856 | if( db->mallocFailed ){ |
| 140788 | 140857 | sqlite3ErrorMsg(db->pParse, "out of memory"); |
| @@ -141259,11 +141328,11 @@ | ||
| 141259 | 141328 | sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt); |
| 141260 | 141329 | sqlite3ClearTempRegCache(pParse); |
| 141261 | 141330 | |
| 141262 | 141331 | /* Do the b-tree integrity checks */ |
| 141263 | 141332 | sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY); |
| 141264 | - sqlite3VdbeChangeP5(v, (u8)i); | |
| 141333 | + sqlite3VdbeChangeP5(v, (u16)i); | |
| 141265 | 141334 | addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); |
| 141266 | 141335 | sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, |
| 141267 | 141336 | sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), |
| 141268 | 141337 | P4_DYNAMIC); |
| 141269 | 141338 | sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3); |
| @@ -142879,18 +142948,11 @@ | ||
| 142879 | 142948 | encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; |
| 142880 | 142949 | if( encoding==0 ) encoding = SQLITE_UTF8; |
| 142881 | 142950 | #else |
| 142882 | 142951 | encoding = SQLITE_UTF8; |
| 142883 | 142952 | #endif |
| 142884 | - if( db->nVdbeActive>0 && encoding!=ENC(db) | |
| 142885 | - && (db->mDbFlags & DBFLAG_Vacuum)==0 | |
| 142886 | - ){ | |
| 142887 | - rc = SQLITE_LOCKED; | |
| 142888 | - goto initone_error_out; | |
| 142889 | - }else{ | |
| 142890 | - sqlite3SetTextEncoding(db, encoding); | |
| 142891 | - } | |
| 142953 | + sqlite3SetTextEncoding(db, encoding); | |
| 142892 | 142954 | }else{ |
| 142893 | 142955 | /* If opening an attached database, the encoding much match ENC(db) */ |
| 142894 | 142956 | if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ |
| 142895 | 142957 | sqlite3SetString(pzErrMsg, db, "attached databases must use the same" |
| 142896 | 142958 | " text encoding as main database"); |
| @@ -147584,36 +147646,36 @@ | ||
| 147584 | 147646 | return pExpr; |
| 147585 | 147647 | } |
| 147586 | 147648 | if( pSubst->isOuterJoin ){ |
| 147587 | 147649 | ExprSetProperty(pNew, EP_CanBeNull); |
| 147588 | 147650 | } |
| 147651 | + if( pNew->op==TK_TRUEFALSE ){ | |
| 147652 | + pNew->u.iValue = sqlite3ExprTruthValue(pNew); | |
| 147653 | + pNew->op = TK_INTEGER; | |
| 147654 | + ExprSetProperty(pNew, EP_IntValue); | |
| 147655 | + } | |
| 147656 | + | |
| 147657 | + /* Ensure that the expression now has an implicit collation sequence, | |
| 147658 | + ** just as it did when it was a column of a view or sub-query. */ | |
| 147659 | + { | |
| 147660 | + CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew); | |
| 147661 | + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, | |
| 147662 | + pSubst->pCList->a[iColumn].pExpr | |
| 147663 | + ); | |
| 147664 | + if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){ | |
| 147665 | + pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew, | |
| 147666 | + (pColl ? pColl->zName : "BINARY") | |
| 147667 | + ); | |
| 147668 | + } | |
| 147669 | + } | |
| 147670 | + ExprClearProperty(pNew, EP_Collate); | |
| 147589 | 147671 | if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ |
| 147590 | 147672 | sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, |
| 147591 | 147673 | pExpr->flags & (EP_OuterON|EP_InnerON)); |
| 147592 | 147674 | } |
| 147593 | 147675 | sqlite3ExprDelete(db, pExpr); |
| 147594 | 147676 | pExpr = pNew; |
| 147595 | - if( pExpr->op==TK_TRUEFALSE ){ | |
| 147596 | - pExpr->u.iValue = sqlite3ExprTruthValue(pExpr); | |
| 147597 | - pExpr->op = TK_INTEGER; | |
| 147598 | - ExprSetProperty(pExpr, EP_IntValue); | |
| 147599 | - } | |
| 147600 | - | |
| 147601 | - /* Ensure that the expression now has an implicit collation sequence, | |
| 147602 | - ** just as it did when it was a column of a view or sub-query. */ | |
| 147603 | - { | |
| 147604 | - CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr); | |
| 147605 | - CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, | |
| 147606 | - pSubst->pCList->a[iColumn].pExpr | |
| 147607 | - ); | |
| 147608 | - if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){ | |
| 147609 | - pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, | |
| 147610 | - (pColl ? pColl->zName : "BINARY") | |
| 147611 | - ); | |
| 147612 | - } | |
| 147613 | - } | |
| 147614 | - ExprClearProperty(pExpr, EP_Collate); | |
| 147615 | 147677 | } |
| 147616 | 147678 | } |
| 147617 | 147679 | }else{ |
| 147618 | 147680 | if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ |
| 147619 | 147681 | pExpr->iTable = pSubst->iNewTable; |
| @@ -148346,20 +148408,20 @@ | ||
| 148346 | 148408 | } |
| 148347 | 148409 | |
| 148348 | 148410 | /* Transfer the FROM clause terms from the subquery into the |
| 148349 | 148411 | ** outer query. |
| 148350 | 148412 | */ |
| 148413 | + iNewParent = pSubSrc->a[0].iCursor; | |
| 148351 | 148414 | for(i=0; i<nSubSrc; i++){ |
| 148352 | 148415 | SrcItem *pItem = &pSrc->a[i+iFrom]; |
| 148353 | 148416 | assert( pItem->fg.isTabFunc==0 ); |
| 148354 | 148417 | assert( pItem->fg.isSubquery |
| 148355 | 148418 | || pItem->fg.fixedSchema |
| 148356 | 148419 | || pItem->u4.zDatabase==0 ); |
| 148357 | 148420 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 148358 | 148421 | *pItem = pSubSrc->a[i]; |
| 148359 | 148422 | pItem->fg.jointype |= ltorj; |
| 148360 | - iNewParent = pSubSrc->a[i].iCursor; | |
| 148361 | 148423 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 148362 | 148424 | } |
| 148363 | 148425 | pSrc->a[iFrom].fg.jointype &= JT_LTORJ; |
| 148364 | 148426 | pSrc->a[iFrom].fg.jointype |= jointype | ltorj; |
| 148365 | 148427 | |
| @@ -148395,10 +148457,11 @@ | ||
| 148395 | 148457 | pSub->pOrderBy = 0; |
| 148396 | 148458 | } |
| 148397 | 148459 | pWhere = pSub->pWhere; |
| 148398 | 148460 | pSub->pWhere = 0; |
| 148399 | 148461 | if( isOuterJoin>0 ){ |
| 148462 | + assert( pSubSrc->nSrc==1 ); | |
| 148400 | 148463 | sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); |
| 148401 | 148464 | } |
| 148402 | 148465 | if( pWhere ){ |
| 148403 | 148466 | if( pParent->pWhere ){ |
| 148404 | 148467 | pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere); |
| @@ -150498,11 +150561,11 @@ | ||
| 150498 | 150561 | } |
| 150499 | 150562 | sqlite3ReleaseTempReg(pParse, regSubtype); |
| 150500 | 150563 | } |
| 150501 | 150564 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150502 | 150565 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150503 | - sqlite3VdbeChangeP5(v, (u8)nArg); | |
| 150566 | + sqlite3VdbeChangeP5(v, (u16)nArg); | |
| 150504 | 150567 | sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v); |
| 150505 | 150568 | sqlite3VdbeJumpHere(v, iTop); |
| 150506 | 150569 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150507 | 150570 | } |
| 150508 | 150571 | sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), |
| @@ -150661,11 +150724,11 @@ | ||
| 150661 | 150724 | sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, |
| 150662 | 150725 | (char *)pColl, P4_COLLSEQ); |
| 150663 | 150726 | } |
| 150664 | 150727 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150665 | 150728 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150666 | - sqlite3VdbeChangeP5(v, (u8)nArg); | |
| 150729 | + sqlite3VdbeChangeP5(v, (u16)nArg); | |
| 150667 | 150730 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150668 | 150731 | } |
| 150669 | 150732 | if( addrNext ){ |
| 150670 | 150733 | sqlite3VdbeResolveLabel(v, addrNext); |
| 150671 | 150734 | } |
| @@ -151494,11 +151557,11 @@ | ||
| 151494 | 151557 | sqlite3TreeViewSelect(0, p, 0); |
| 151495 | 151558 | } |
| 151496 | 151559 | #endif |
| 151497 | 151560 | assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 ); |
| 151498 | 151561 | }else{ |
| 151499 | - TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n")); | |
| 151562 | + TREETRACE(0x4000,pParse,p,("WHERE-clause push-down not possible\n")); | |
| 151500 | 151563 | } |
| 151501 | 151564 | |
| 151502 | 151565 | /* Convert unused result columns of the subquery into simple NULL |
| 151503 | 151566 | ** expressions, to avoid unneeded searching and computation. |
| 151504 | 151567 | ** tag-select-0440 |
| @@ -154055,11 +154118,11 @@ | ||
| 154055 | 154118 | /* Set the P5 operand of the OP_Program instruction to non-zero if |
| 154056 | 154119 | ** recursive invocation of this trigger program is disallowed. Recursive |
| 154057 | 154120 | ** invocation is disallowed if (a) the sub-program is really a trigger, |
| 154058 | 154121 | ** not a foreign key action, and (b) the flag to enable recursive triggers |
| 154059 | 154122 | ** is clear. */ |
| 154060 | - sqlite3VdbeChangeP5(v, (u8)bRecursive); | |
| 154123 | + sqlite3VdbeChangeP5(v, (u16)bRecursive); | |
| 154061 | 154124 | } |
| 154062 | 154125 | } |
| 154063 | 154126 | |
| 154064 | 154127 | /* |
| 154065 | 154128 | ** This is called to code the required FOR EACH ROW triggers for an operation |
| @@ -158268,13 +158331,21 @@ | ||
| 158268 | 158331 | SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( |
| 158269 | 158332 | const Parse *pParse, /* Parse context */ |
| 158270 | 158333 | const WhereInfo *pWInfo, /* WHERE clause */ |
| 158271 | 158334 | const WhereLevel *pLevel /* Bloom filter on this level */ |
| 158272 | 158335 | ); |
| 158336 | +SQLITE_PRIVATE void sqlite3WhereAddExplainText( | |
| 158337 | + Parse *pParse, /* Parse context */ | |
| 158338 | + int addr, | |
| 158339 | + SrcList *pTabList, /* Table list this loop refers to */ | |
| 158340 | + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ | |
| 158341 | + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ | |
| 158342 | +); | |
| 158273 | 158343 | #else |
| 158274 | 158344 | # define sqlite3WhereExplainOneScan(u,v,w,x) 0 |
| 158275 | 158345 | # define sqlite3WhereExplainBloomFilter(u,v,w) 0 |
| 158346 | +# define sqlite3WhereAddExplainText(u,v,w,x,y) | |
| 158276 | 158347 | #endif /* SQLITE_OMIT_EXPLAIN */ |
| 158277 | 158348 | #ifdef SQLITE_ENABLE_STMT_SCANSTATUS |
| 158278 | 158349 | SQLITE_PRIVATE void sqlite3WhereAddScanStatus( |
| 158279 | 158350 | Vdbe *v, /* Vdbe to add scanstatus entry to */ |
| 158280 | 158351 | SrcList *pSrclist, /* FROM clause pLvl reads data from */ |
| @@ -158472,42 +158543,42 @@ | ||
| 158472 | 158543 | } |
| 158473 | 158544 | sqlite3_str_append(pStr, ")", 1); |
| 158474 | 158545 | } |
| 158475 | 158546 | |
| 158476 | 158547 | /* |
| 158477 | -** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN | |
| 158478 | -** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG | |
| 158479 | -** was defined at compile-time. If it is not a no-op, a single OP_Explain | |
| 158480 | -** opcode is added to the output to describe the table scan strategy in pLevel. | |
| 158481 | -** | |
| 158482 | -** If an OP_Explain opcode is added to the VM, its address is returned. | |
| 158483 | -** Otherwise, if no OP_Explain is coded, zero is returned. | |
| 158548 | +** This function sets the P4 value of an existing OP_Explain opcode to | |
| 158549 | +** text describing the loop in pLevel. If the OP_Explain opcode already has | |
| 158550 | +** a P4 value, it is freed before it is overwritten. | |
| 158484 | 158551 | */ |
| 158485 | -SQLITE_PRIVATE int sqlite3WhereExplainOneScan( | |
| 158552 | +SQLITE_PRIVATE void sqlite3WhereAddExplainText( | |
| 158486 | 158553 | Parse *pParse, /* Parse context */ |
| 158554 | + int addr, /* Address of OP_Explain opcode */ | |
| 158487 | 158555 | SrcList *pTabList, /* Table list this loop refers to */ |
| 158488 | 158556 | WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ |
| 158489 | 158557 | u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ |
| 158490 | 158558 | ){ |
| 158491 | - int ret = 0; | |
| 158492 | 158559 | #if !defined(SQLITE_DEBUG) |
| 158493 | 158560 | if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) |
| 158494 | 158561 | #endif |
| 158495 | 158562 | { |
| 158563 | + VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr); | |
| 158564 | + | |
| 158496 | 158565 | SrcItem *pItem = &pTabList->a[pLevel->iFrom]; |
| 158497 | - Vdbe *v = pParse->pVdbe; /* VM being constructed */ | |
| 158498 | 158566 | sqlite3 *db = pParse->db; /* Database handle */ |
| 158499 | 158567 | int isSearch; /* True for a SEARCH. False for SCAN. */ |
| 158500 | 158568 | WhereLoop *pLoop; /* The controlling WhereLoop object */ |
| 158501 | 158569 | u32 flags; /* Flags that describe this loop */ |
| 158570 | +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) | |
| 158502 | 158571 | char *zMsg; /* Text to add to EQP output */ |
| 158572 | +#endif | |
| 158503 | 158573 | StrAccum str; /* EQP output string */ |
| 158504 | 158574 | char zBuf[100]; /* Initial space for EQP output string */ |
| 158505 | 158575 | |
| 158576 | + if( db->mallocFailed ) return; | |
| 158577 | + | |
| 158506 | 158578 | pLoop = pLevel->pWLoop; |
| 158507 | 158579 | flags = pLoop->wsFlags; |
| 158508 | - if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0; | |
| 158509 | 158580 | |
| 158510 | 158581 | isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 |
| 158511 | 158582 | || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) |
| 158512 | 158583 | || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); |
| 158513 | 158584 | |
| @@ -158527,11 +158598,11 @@ | ||
| 158527 | 158598 | } |
| 158528 | 158599 | }else if( flags & WHERE_PARTIALIDX ){ |
| 158529 | 158600 | zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; |
| 158530 | 158601 | }else if( flags & WHERE_AUTO_INDEX ){ |
| 158531 | 158602 | zFmt = "AUTOMATIC COVERING INDEX"; |
| 158532 | - }else if( flags & WHERE_IDX_ONLY ){ | |
| 158603 | + }else if( flags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ | |
| 158533 | 158604 | zFmt = "COVERING INDEX %s"; |
| 158534 | 158605 | }else{ |
| 158535 | 158606 | zFmt = "INDEX %s"; |
| 158536 | 158607 | } |
| 158537 | 158608 | if( zFmt ){ |
| @@ -158579,15 +158650,54 @@ | ||
| 158579 | 158650 | sqlite3LogEstToInt(pLoop->nOut)); |
| 158580 | 158651 | }else{ |
| 158581 | 158652 | sqlite3_str_append(&str, " (~1 row)", 9); |
| 158582 | 158653 | } |
| 158583 | 158654 | #endif |
| 158655 | +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) | |
| 158584 | 158656 | zMsg = sqlite3StrAccumFinish(&str); |
| 158585 | 158657 | sqlite3ExplainBreakpoint("",zMsg); |
| 158586 | - ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), | |
| 158587 | - pParse->addrExplain, pLoop->rRun, | |
| 158588 | - zMsg, P4_DYNAMIC); | |
| 158658 | +#endif | |
| 158659 | + | |
| 158660 | + assert( pOp->opcode==OP_Explain ); | |
| 158661 | + assert( pOp->p4type==P4_DYNAMIC || pOp->p4.z==0 ); | |
| 158662 | + sqlite3DbFree(db, pOp->p4.z); | |
| 158663 | + pOp->p4type = P4_DYNAMIC; | |
| 158664 | + pOp->p4.z = sqlite3StrAccumFinish(&str); | |
| 158665 | + } | |
| 158666 | +} | |
| 158667 | + | |
| 158668 | + | |
| 158669 | +/* | |
| 158670 | +** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN | |
| 158671 | +** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG | |
| 158672 | +** was defined at compile-time. If it is not a no-op, a single OP_Explain | |
| 158673 | +** opcode is added to the output to describe the table scan strategy in pLevel. | |
| 158674 | +** | |
| 158675 | +** If an OP_Explain opcode is added to the VM, its address is returned. | |
| 158676 | +** Otherwise, if no OP_Explain is coded, zero is returned. | |
| 158677 | +*/ | |
| 158678 | +SQLITE_PRIVATE int sqlite3WhereExplainOneScan( | |
| 158679 | + Parse *pParse, /* Parse context */ | |
| 158680 | + SrcList *pTabList, /* Table list this loop refers to */ | |
| 158681 | + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ | |
| 158682 | + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ | |
| 158683 | +){ | |
| 158684 | + int ret = 0; | |
| 158685 | +#if !defined(SQLITE_DEBUG) | |
| 158686 | + if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) | |
| 158687 | +#endif | |
| 158688 | + { | |
| 158689 | + if( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 | |
| 158690 | + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 | |
| 158691 | + ){ | |
| 158692 | + Vdbe *v = pParse->pVdbe; | |
| 158693 | + int addr = sqlite3VdbeCurrentAddr(v); | |
| 158694 | + ret = sqlite3VdbeAddOp3( | |
| 158695 | + v, OP_Explain, addr, pParse->addrExplain, pLevel->pWLoop->rRun | |
| 158696 | + ); | |
| 158697 | + sqlite3WhereAddExplainText(pParse, addr, pTabList, pLevel, wctrlFlags); | |
| 158698 | + } | |
| 158589 | 158699 | } |
| 158590 | 158700 | return ret; |
| 158591 | 158701 | } |
| 158592 | 158702 | |
| 158593 | 158703 | /* |
| @@ -158682,13 +158792,14 @@ | ||
| 158682 | 158792 | if( wsFlags & WHERE_INDEXED ){ |
| 158683 | 158793 | sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); |
| 158684 | 158794 | } |
| 158685 | 158795 | }else{ |
| 158686 | 158796 | int addr; |
| 158797 | + VdbeOp *pOp; | |
| 158687 | 158798 | assert( pSrclist->a[pLvl->iFrom].fg.isSubquery ); |
| 158688 | 158799 | addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub; |
| 158689 | - VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1); | |
| 158800 | + pOp = sqlite3VdbeGetOp(v, addr-1); | |
| 158690 | 158801 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); |
| 158691 | 158802 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); |
| 158692 | 158803 | sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); |
| 158693 | 158804 | } |
| 158694 | 158805 | } |
| @@ -158937,10 +159048,11 @@ | ||
| 158937 | 159048 | if( pOrigLhs ){ |
| 158938 | 159049 | sqlite3ExprListDelete(db, pOrigLhs); |
| 158939 | 159050 | pNew->pLeft->x.pList = pLhs; |
| 158940 | 159051 | } |
| 158941 | 159052 | pSelect->pEList = pRhs; |
| 159053 | + pSelect->selId = ++pParse->nSelect; /* Req'd for SubrtnSig validity */ | |
| 158942 | 159054 | if( pLhs && pLhs->nExpr==1 ){ |
| 158943 | 159055 | /* Take care here not to generate a TK_VECTOR containing only a |
| 158944 | 159056 | ** single value. Since the parser never creates such a vector, some |
| 158945 | 159057 | ** of the subroutines do not handle this case. */ |
| 158946 | 159058 | Expr *p = pLhs->a[0].pExpr; |
| @@ -164009,11 +164121,11 @@ | ||
| 164009 | 164121 | || pTerm->pExpr->w.iJoin != pSrc->iCursor |
| 164010 | 164122 | ){ |
| 164011 | 164123 | return 0; |
| 164012 | 164124 | } |
| 164013 | 164125 | if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 |
| 164014 | - && ExprHasProperty(pTerm->pExpr, EP_InnerON) | |
| 164126 | + && NEVER(ExprHasProperty(pTerm->pExpr, EP_InnerON)) | |
| 164015 | 164127 | ){ |
| 164016 | 164128 | return 0; |
| 164017 | 164129 | } |
| 164018 | 164130 | return 1; |
| 164019 | 164131 | } |
| @@ -165502,11 +165614,11 @@ | ||
| 165502 | 165614 | return rc; |
| 165503 | 165615 | } |
| 165504 | 165616 | #endif /* SQLITE_ENABLE_STAT4 */ |
| 165505 | 165617 | |
| 165506 | 165618 | |
| 165507 | -#ifdef WHERETRACE_ENABLED | |
| 165619 | +#if defined(WHERETRACE_ENABLED) || defined(SQLITE_DEBUG) | |
| 165508 | 165620 | /* |
| 165509 | 165621 | ** Print the content of a WhereTerm object |
| 165510 | 165622 | */ |
| 165511 | 165623 | SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ |
| 165512 | 165624 | if( pTerm==0 ){ |
| @@ -165545,10 +165657,13 @@ | ||
| 165545 | 165657 | sqlite3DebugPrintf(" iParent=%d", pTerm->iParent); |
| 165546 | 165658 | } |
| 165547 | 165659 | sqlite3DebugPrintf("\n"); |
| 165548 | 165660 | sqlite3TreeViewExpr(0, pTerm->pExpr, 0); |
| 165549 | 165661 | } |
| 165662 | +} | |
| 165663 | +SQLITE_PRIVATE void sqlite3ShowWhereTerm(WhereTerm *pTerm){ | |
| 165664 | + sqlite3WhereTermPrint(pTerm, 0); | |
| 165550 | 165665 | } |
| 165551 | 165666 | #endif |
| 165552 | 165667 | |
| 165553 | 165668 | #ifdef WHERETRACE_ENABLED |
| 165554 | 165669 | /* |
| @@ -166731,11 +166846,10 @@ | ||
| 166731 | 166846 | pParse = pWC->pWInfo->pParse; |
| 166732 | 166847 | while( pWhere->op==TK_AND ){ |
| 166733 | 166848 | if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; |
| 166734 | 166849 | pWhere = pWhere->pRight; |
| 166735 | 166850 | } |
| 166736 | - if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; | |
| 166737 | 166851 | for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ |
| 166738 | 166852 | Expr *pExpr; |
| 166739 | 166853 | pExpr = pTerm->pExpr; |
| 166740 | 166854 | if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) |
| 166741 | 166855 | && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) |
| @@ -169392,11 +169506,11 @@ | ||
| 169392 | 169506 | break; |
| 169393 | 169507 | } |
| 169394 | 169508 | } |
| 169395 | 169509 | if( hasRightJoin |
| 169396 | 169510 | && ExprHasProperty(pTerm->pExpr, EP_InnerON) |
| 169397 | - && pTerm->pExpr->w.iJoin==pItem->iCursor | |
| 169511 | + && NEVER(pTerm->pExpr->w.iJoin==pItem->iCursor) | |
| 169398 | 169512 | ){ |
| 169399 | 169513 | break; /* restriction (5) */ |
| 169400 | 169514 | } |
| 169401 | 169515 | } |
| 169402 | 169516 | if( pTerm<pEnd ) continue; |
| @@ -170312,10 +170426,11 @@ | ||
| 170312 | 170426 | int pc, |
| 170313 | 170427 | VdbeOp *pOp |
| 170314 | 170428 | ){ |
| 170315 | 170429 | if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return; |
| 170316 | 170430 | sqlite3VdbePrintOp(0, pc, pOp); |
| 170431 | + sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */ | |
| 170317 | 170432 | } |
| 170318 | 170433 | #endif |
| 170319 | 170434 | |
| 170320 | 170435 | /* |
| 170321 | 170436 | ** Generate the end of the WHERE loop. See comments on |
| @@ -170611,18 +170726,32 @@ | ||
| 170611 | 170726 | x = sqlite3TableColumnToIndex(pIdx, x); |
| 170612 | 170727 | if( x>=0 ){ |
| 170613 | 170728 | pOp->p2 = x; |
| 170614 | 170729 | pOp->p1 = pLevel->iIdxCur; |
| 170615 | 170730 | OpcodeRewriteTrace(db, k, pOp); |
| 170616 | - }else{ | |
| 170617 | - /* Unable to translate the table reference into an index | |
| 170618 | - ** reference. Verify that this is harmless - that the | |
| 170619 | - ** table being referenced really is open. | |
| 170620 | - */ | |
| 170731 | + }else if( pLoop->wsFlags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ | |
| 170621 | 170732 | if( pLoop->wsFlags & WHERE_IDX_ONLY ){ |
| 170733 | + /* An error. pLoop is supposed to be a covering index loop, | |
| 170734 | + ** and yet the VM code refers to a column of the table that | |
| 170735 | + ** is not part of the index. */ | |
| 170622 | 170736 | sqlite3ErrorMsg(pParse, "internal query planner error"); |
| 170623 | 170737 | pParse->rc = SQLITE_INTERNAL; |
| 170738 | + }else{ | |
| 170739 | + /* The WHERE_EXPRIDX flag is set by the planner when it is likely | |
| 170740 | + ** that pLoop is a covering index loop, but it is not possible | |
| 170741 | + ** to be 100% sure. In this case, any OP_Explain opcode | |
| 170742 | + ** corresponding to this loop describes the index as a "COVERING | |
| 170743 | + ** INDEX". But, pOp proves that pLoop is not actually a covering | |
| 170744 | + ** index loop. So clear the WHERE_EXPRIDX flag and rewrite the | |
| 170745 | + ** text that accompanies the OP_Explain opcode, if any. */ | |
| 170746 | + pLoop->wsFlags &= ~WHERE_EXPRIDX; | |
| 170747 | + sqlite3WhereAddExplainText(pParse, | |
| 170748 | + pLevel->addrBody-1, | |
| 170749 | + pTabList, | |
| 170750 | + pLevel, | |
| 170751 | + pWInfo->wctrlFlags | |
| 170752 | + ); | |
| 170624 | 170753 | } |
| 170625 | 170754 | } |
| 170626 | 170755 | }else if( pOp->opcode==OP_Rowid ){ |
| 170627 | 170756 | pOp->p1 = pLevel->iIdxCur; |
| 170628 | 170757 | pOp->opcode = OP_IdxRowid; |
| @@ -172326,10 +172455,11 @@ | ||
| 172326 | 172455 | for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ |
| 172327 | 172456 | FuncDef *pFunc = pWin->pWFunc; |
| 172328 | 172457 | int regArg; |
| 172329 | 172458 | int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); |
| 172330 | 172459 | int i; |
| 172460 | + int addrIf = 0; | |
| 172331 | 172461 | |
| 172332 | 172462 | assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED ); |
| 172333 | 172463 | |
| 172334 | 172464 | /* All OVER clauses in the same window function aggregate step must |
| 172335 | 172465 | ** be the same. */ |
| @@ -172341,10 +172471,22 @@ | ||
| 172341 | 172471 | }else{ |
| 172342 | 172472 | sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i); |
| 172343 | 172473 | } |
| 172344 | 172474 | } |
| 172345 | 172475 | regArg = reg; |
| 172476 | + | |
| 172477 | + if( pWin->pFilter ){ | |
| 172478 | + int regTmp; | |
| 172479 | + assert( ExprUseXList(pWin->pOwner) ); | |
| 172480 | + assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); | |
| 172481 | + assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); | |
| 172482 | + regTmp = sqlite3GetTempReg(pParse); | |
| 172483 | + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); | |
| 172484 | + addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); | |
| 172485 | + VdbeCoverage(v); | |
| 172486 | + sqlite3ReleaseTempReg(pParse, regTmp); | |
| 172487 | + } | |
| 172346 | 172488 | |
| 172347 | 172489 | if( pMWin->regStartRowid==0 |
| 172348 | 172490 | && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) |
| 172349 | 172491 | && (pWin->eStart!=TK_UNBOUNDED) |
| 172350 | 172492 | ){ |
| @@ -172361,29 +172503,17 @@ | ||
| 172361 | 172503 | sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp); |
| 172362 | 172504 | sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); |
| 172363 | 172505 | } |
| 172364 | 172506 | sqlite3VdbeJumpHere(v, addrIsNull); |
| 172365 | 172507 | }else if( pWin->regApp ){ |
| 172508 | + assert( pWin->pFilter==0 ); | |
| 172366 | 172509 | assert( pFunc->zName==nth_valueName |
| 172367 | 172510 | || pFunc->zName==first_valueName |
| 172368 | 172511 | ); |
| 172369 | 172512 | assert( bInverse==0 || bInverse==1 ); |
| 172370 | 172513 | sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1); |
| 172371 | 172514 | }else if( pFunc->xSFunc!=noopStepFunc ){ |
| 172372 | - int addrIf = 0; | |
| 172373 | - if( pWin->pFilter ){ | |
| 172374 | - int regTmp; | |
| 172375 | - assert( ExprUseXList(pWin->pOwner) ); | |
| 172376 | - assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); | |
| 172377 | - assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); | |
| 172378 | - regTmp = sqlite3GetTempReg(pParse); | |
| 172379 | - sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); | |
| 172380 | - addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); | |
| 172381 | - VdbeCoverage(v); | |
| 172382 | - sqlite3ReleaseTempReg(pParse, regTmp); | |
| 172383 | - } | |
| 172384 | - | |
| 172385 | 172515 | if( pWin->bExprArgs ){ |
| 172386 | 172516 | int iOp = sqlite3VdbeCurrentAddr(v); |
| 172387 | 172517 | int iEnd; |
| 172388 | 172518 | |
| 172389 | 172519 | assert( ExprUseXList(pWin->pOwner) ); |
| @@ -172406,16 +172536,17 @@ | ||
| 172406 | 172536 | sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); |
| 172407 | 172537 | } |
| 172408 | 172538 | sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, |
| 172409 | 172539 | bInverse, regArg, pWin->regAccum); |
| 172410 | 172540 | sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); |
| 172411 | - sqlite3VdbeChangeP5(v, (u8)nArg); | |
| 172541 | + sqlite3VdbeChangeP5(v, (u16)nArg); | |
| 172412 | 172542 | if( pWin->bExprArgs ){ |
| 172413 | 172543 | sqlite3ReleaseTempRange(pParse, regArg, nArg); |
| 172414 | 172544 | } |
| 172415 | - if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); | |
| 172416 | 172545 | } |
| 172546 | + | |
| 172547 | + if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); | |
| 172417 | 172548 | } |
| 172418 | 172549 | } |
| 172419 | 172550 | |
| 172420 | 172551 | /* |
| 172421 | 172552 | ** Values that may be passed as the second argument to windowCodeOp(). |
| @@ -173837,10 +173968,17 @@ | ||
| 173837 | 173968 | ** Then the "b" IdList records the list "a,b,c". |
| 173838 | 173969 | */ |
| 173839 | 173970 | struct TrigEvent { int a; IdList * b; }; |
| 173840 | 173971 | |
| 173841 | 173972 | struct FrameBound { int eType; Expr *pExpr; }; |
| 173973 | + | |
| 173974 | +/* | |
| 173975 | +** Generate a syntax error | |
| 173976 | +*/ | |
| 173977 | +static void parserSyntaxError(Parse *pParse, Token *p){ | |
| 173978 | + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p); | |
| 173979 | +} | |
| 173842 | 173980 | |
| 173843 | 173981 | /* |
| 173844 | 173982 | ** Disable lookaside memory allocation for objects that might be |
| 173845 | 173983 | ** shared across database connections. |
| 173846 | 173984 | */ |
| @@ -177730,11 +177868,15 @@ | ||
| 177730 | 177868 | } |
| 177731 | 177869 | break; |
| 177732 | 177870 | case 84: /* cmd ::= select */ |
| 177733 | 177871 | { |
| 177734 | 177872 | SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; |
| 177735 | - sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); | |
| 177873 | + if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0 | |
| 177874 | + || sqlite3ReadSchema(pParse)==SQLITE_OK | |
| 177875 | + ){ | |
| 177876 | + sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); | |
| 177877 | + } | |
| 177736 | 177878 | sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); |
| 177737 | 177879 | } |
| 177738 | 177880 | break; |
| 177739 | 177881 | case 85: /* select ::= WITH wqlist selectnowith */ |
| 177740 | 177882 | {yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} |
| @@ -178201,11 +178343,11 @@ | ||
| 178201 | 178343 | ** that look like this: #1 #2 ... These terms refer to registers |
| 178202 | 178344 | ** in the virtual machine. #N is the N-th register. */ |
| 178203 | 178345 | Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/ |
| 178204 | 178346 | assert( t.n>=2 ); |
| 178205 | 178347 | if( pParse->nested==0 ){ |
| 178206 | - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); | |
| 178348 | + parserSyntaxError(pParse, &t); | |
| 178207 | 178349 | yymsp[0].minor.yy454 = 0; |
| 178208 | 178350 | }else{ |
| 178209 | 178351 | yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); |
| 178210 | 178352 | if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable); |
| 178211 | 178353 | } |
| @@ -179049,11 +179191,11 @@ | ||
| 179049 | 179191 | #define TOKEN yyminor |
| 179050 | 179192 | /************ Begin %syntax_error code ****************************************/ |
| 179051 | 179193 | |
| 179052 | 179194 | UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ |
| 179053 | 179195 | if( TOKEN.z[0] ){ |
| 179054 | - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); | |
| 179196 | + parserSyntaxError(pParse, &TOKEN); | |
| 179055 | 179197 | }else{ |
| 179056 | 179198 | sqlite3ErrorMsg(pParse, "incomplete input"); |
| 179057 | 179199 | } |
| 179058 | 179200 | /************ End %syntax_error code ******************************************/ |
| 179059 | 179201 | sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ |
| @@ -180540,11 +180682,13 @@ | ||
| 180540 | 180682 | } |
| 180541 | 180683 | if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ |
| 180542 | 180684 | if( pParse->zErrMsg==0 ){ |
| 180543 | 180685 | pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); |
| 180544 | 180686 | } |
| 180545 | - sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); | |
| 180687 | + if( (pParse->prepFlags & SQLITE_PREPARE_DONT_LOG)==0 ){ | |
| 180688 | + sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); | |
| 180689 | + } | |
| 180546 | 180690 | nErr++; |
| 180547 | 180691 | } |
| 180548 | 180692 | pParse->zTail = zSql; |
| 180549 | 180693 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 180550 | 180694 | sqlite3_free(pParse->apVtabLock); |
| @@ -182513,14 +182657,10 @@ | ||
| 182513 | 182657 | #endif |
| 182514 | 182658 | |
| 182515 | 182659 | sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ |
| 182516 | 182660 | sqlite3ValueFree(db->pErr); |
| 182517 | 182661 | sqlite3CloseExtensions(db); |
| 182518 | -#if SQLITE_USER_AUTHENTICATION | |
| 182519 | - sqlite3_free(db->auth.zAuthUser); | |
| 182520 | - sqlite3_free(db->auth.zAuthPW); | |
| 182521 | -#endif | |
| 182522 | 182662 | |
| 182523 | 182663 | db->eOpenState = SQLITE_STATE_ERROR; |
| 182524 | 182664 | |
| 182525 | 182665 | /* The temp-database schema is allocated differently from the other schema |
| 182526 | 182666 | ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). |
| @@ -183951,12 +184091,12 @@ | ||
| 183951 | 184091 | # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 |
| 183952 | 184092 | #endif |
| 183953 | 184093 | #if SQLITE_MAX_VDBE_OP<40 |
| 183954 | 184094 | # error SQLITE_MAX_VDBE_OP must be at least 40 |
| 183955 | 184095 | #endif |
| 183956 | -#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127 | |
| 183957 | -# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127 | |
| 184096 | +#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767 | |
| 184097 | +# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767 | |
| 183958 | 184098 | #endif |
| 183959 | 184099 | #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 |
| 183960 | 184100 | # error SQLITE_MAX_ATTACHED must be between 0 and 125 |
| 183961 | 184101 | #endif |
| 183962 | 184102 | #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1 |
| @@ -184019,12 +184159,12 @@ | ||
| 184019 | 184159 | } |
| 184020 | 184160 | oldLimit = db->aLimit[limitId]; |
| 184021 | 184161 | if( newLimit>=0 ){ /* IMP: R-52476-28732 */ |
| 184022 | 184162 | if( newLimit>aHardLimit[limitId] ){ |
| 184023 | 184163 | newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ |
| 184024 | - }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){ | |
| 184025 | - newLimit = 1; | |
| 184164 | + }else if( newLimit<SQLITE_MIN_LENGTH && limitId==SQLITE_LIMIT_LENGTH ){ | |
| 184165 | + newLimit = SQLITE_MIN_LENGTH; | |
| 184026 | 184166 | } |
| 184027 | 184167 | db->aLimit[limitId] = newLimit; |
| 184028 | 184168 | } |
| 184029 | 184169 | return oldLimit; /* IMP: R-53341-35419 */ |
| 184030 | 184170 | } |
| @@ -185364,11 +185504,10 @@ | ||
| 185364 | 185504 | rc = x; |
| 185365 | 185505 | #if defined(SQLITE_DEBUG) |
| 185366 | 185506 | /* Invoke these debugging routines so that the compiler does not |
| 185367 | 185507 | ** issue "defined but not used" warnings. */ |
| 185368 | 185508 | if( x==9999 ){ |
| 185369 | - sqlite3ShowExpr(0); | |
| 185370 | 185509 | sqlite3ShowExpr(0); |
| 185371 | 185510 | sqlite3ShowExprList(0); |
| 185372 | 185511 | sqlite3ShowIdList(0); |
| 185373 | 185512 | sqlite3ShowSrcList(0); |
| 185374 | 185513 | sqlite3ShowWith(0); |
| @@ -189796,14 +189935,19 @@ | ||
| 189796 | 189935 | |
| 189797 | 189936 | assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 ); |
| 189798 | 189937 | if( *p1==POS_COLUMN ){ |
| 189799 | 189938 | p1++; |
| 189800 | 189939 | p1 += fts3GetVarint32(p1, &iCol1); |
| 189940 | + /* iCol1==0 indicates corruption. Column 0 does not have a POS_COLUMN | |
| 189941 | + ** entry, so this is actually end-of-doclist. */ | |
| 189942 | + if( iCol1==0 ) return 0; | |
| 189801 | 189943 | } |
| 189802 | 189944 | if( *p2==POS_COLUMN ){ |
| 189803 | 189945 | p2++; |
| 189804 | 189946 | p2 += fts3GetVarint32(p2, &iCol2); |
| 189947 | + /* As above, iCol2==0 indicates corruption. */ | |
| 189948 | + if( iCol2==0 ) return 0; | |
| 189805 | 189949 | } |
| 189806 | 189950 | |
| 189807 | 189951 | while( 1 ){ |
| 189808 | 189952 | if( iCol1==iCol2 ){ |
| 189809 | 189953 | char *pSave = p; |
| @@ -192970,11 +193114,11 @@ | ||
| 192970 | 193114 | for(p=pExpr; p->pLeft; p=p->pLeft){ |
| 192971 | 193115 | assert( p->pRight->pPhrase->doclist.nList>0 ); |
| 192972 | 193116 | nTmp += p->pRight->pPhrase->doclist.nList; |
| 192973 | 193117 | } |
| 192974 | 193118 | nTmp += p->pPhrase->doclist.nList; |
| 192975 | - aTmp = sqlite3_malloc64(nTmp*2); | |
| 193119 | + aTmp = sqlite3_malloc64(nTmp*2 + FTS3_VARINT_MAX); | |
| 192976 | 193120 | if( !aTmp ){ |
| 192977 | 193121 | *pRc = SQLITE_NOMEM; |
| 192978 | 193122 | res = 0; |
| 192979 | 193123 | }else{ |
| 192980 | 193124 | char *aPoslist = p->pPhrase->doclist.pList; |
| @@ -193621,11 +193765,11 @@ | ||
| 193621 | 193765 | SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ |
| 193622 | 193766 | return SQLITE_CORRUPT_VTAB; |
| 193623 | 193767 | } |
| 193624 | 193768 | #endif |
| 193625 | 193769 | |
| 193626 | -#if !SQLITE_CORE | |
| 193770 | +#if !defined(SQLITE_CORE) | |
| 193627 | 193771 | /* |
| 193628 | 193772 | ** Initialize API pointer table, if required. |
| 193629 | 193773 | */ |
| 193630 | 193774 | #ifdef _WIN32 |
| 193631 | 193775 | __declspec(dllexport) |
| @@ -194523,14 +194667,15 @@ | ||
| 194523 | 194667 | rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos); |
| 194524 | 194668 | if( rc==SQLITE_OK ){ |
| 194525 | 194669 | Fts3PhraseToken *pToken; |
| 194526 | 194670 | |
| 194527 | 194671 | p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); |
| 194528 | - if( !p ) goto no_mem; | |
| 194529 | - | |
| 194530 | 194672 | zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); |
| 194531 | - if( !zTemp ) goto no_mem; | |
| 194673 | + if( !zTemp || !p ){ | |
| 194674 | + rc = SQLITE_NOMEM; | |
| 194675 | + goto getnextstring_out; | |
| 194676 | + } | |
| 194532 | 194677 | |
| 194533 | 194678 | assert( nToken==ii ); |
| 194534 | 194679 | pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; |
| 194535 | 194680 | memset(pToken, 0, sizeof(Fts3PhraseToken)); |
| 194536 | 194681 | |
| @@ -194541,53 +194686,51 @@ | ||
| 194541 | 194686 | pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*'); |
| 194542 | 194687 | pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^'); |
| 194543 | 194688 | nToken = ii+1; |
| 194544 | 194689 | } |
| 194545 | 194690 | } |
| 194546 | - | |
| 194547 | - pModule->xClose(pCursor); | |
| 194548 | - pCursor = 0; | |
| 194549 | 194691 | } |
| 194550 | 194692 | |
| 194551 | 194693 | if( rc==SQLITE_DONE ){ |
| 194552 | 194694 | int jj; |
| 194553 | 194695 | char *zBuf = 0; |
| 194554 | 194696 | |
| 194555 | 194697 | p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); |
| 194556 | - if( !p ) goto no_mem; | |
| 194698 | + if( !p ){ | |
| 194699 | + rc = SQLITE_NOMEM; | |
| 194700 | + goto getnextstring_out; | |
| 194701 | + } | |
| 194557 | 194702 | memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); |
| 194558 | 194703 | p->eType = FTSQUERY_PHRASE; |
| 194559 | 194704 | p->pPhrase = (Fts3Phrase *)&p[1]; |
| 194560 | 194705 | p->pPhrase->iColumn = pParse->iDefaultCol; |
| 194561 | 194706 | p->pPhrase->nToken = nToken; |
| 194562 | 194707 | |
| 194563 | 194708 | zBuf = (char *)&p->pPhrase->aToken[nToken]; |
| 194709 | + assert( nTemp==0 || zTemp ); | |
| 194564 | 194710 | if( zTemp ){ |
| 194565 | 194711 | memcpy(zBuf, zTemp, nTemp); |
| 194566 | - sqlite3_free(zTemp); | |
| 194567 | - }else{ | |
| 194568 | - assert( nTemp==0 ); | |
| 194569 | 194712 | } |
| 194570 | 194713 | |
| 194571 | 194714 | for(jj=0; jj<p->pPhrase->nToken; jj++){ |
| 194572 | 194715 | p->pPhrase->aToken[jj].z = zBuf; |
| 194573 | 194716 | zBuf += p->pPhrase->aToken[jj].n; |
| 194574 | 194717 | } |
| 194575 | 194718 | rc = SQLITE_OK; |
| 194576 | 194719 | } |
| 194577 | 194720 | |
| 194578 | - *ppExpr = p; | |
| 194579 | - return rc; | |
| 194580 | -no_mem: | |
| 194581 | - | |
| 194721 | + getnextstring_out: | |
| 194582 | 194722 | if( pCursor ){ |
| 194583 | 194723 | pModule->xClose(pCursor); |
| 194584 | 194724 | } |
| 194585 | 194725 | sqlite3_free(zTemp); |
| 194586 | - sqlite3_free(p); | |
| 194587 | - *ppExpr = 0; | |
| 194588 | - return SQLITE_NOMEM; | |
| 194726 | + if( rc!=SQLITE_OK ){ | |
| 194727 | + sqlite3_free(p); | |
| 194728 | + p = 0; | |
| 194729 | + } | |
| 194730 | + *ppExpr = p; | |
| 194731 | + return rc; | |
| 194589 | 194732 | } |
| 194590 | 194733 | |
| 194591 | 194734 | /* |
| 194592 | 194735 | ** The output variable *ppExpr is populated with an allocated Fts3Expr |
| 194593 | 194736 | ** structure, or set to 0 if the end of the input buffer is reached. |
| @@ -215395,12 +215538,12 @@ | ||
| 215395 | 215538 | #endif |
| 215396 | 215539 | } |
| 215397 | 215540 | sqlite3_str_append(pOut, "}", 1); |
| 215398 | 215541 | } |
| 215399 | 215542 | errCode = sqlite3_str_errcode(pOut); |
| 215400 | - sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free); | |
| 215401 | 215543 | sqlite3_result_error_code(ctx, errCode); |
| 215544 | + sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free); | |
| 215402 | 215545 | } |
| 215403 | 215546 | |
| 215404 | 215547 | /* This routine implements an SQL function that returns the "depth" parameter |
| 215405 | 215548 | ** from the front of a blob that is an r-tree node. For example: |
| 215406 | 215549 | ** |
| @@ -217912,11 +218055,11 @@ | ||
| 217912 | 218055 | return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, |
| 217913 | 218056 | (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback |
| 217914 | 218057 | ); |
| 217915 | 218058 | } |
| 217916 | 218059 | |
| 217917 | -#if !SQLITE_CORE | |
| 218060 | +#ifndef SQLITE_CORE | |
| 217918 | 218061 | #ifdef _WIN32 |
| 217919 | 218062 | __declspec(dllexport) |
| 217920 | 218063 | #endif |
| 217921 | 218064 | SQLITE_API int sqlite3_rtree_init( |
| 217922 | 218065 | sqlite3 *db, |
| @@ -218503,11 +218646,11 @@ | ||
| 218503 | 218646 | } |
| 218504 | 218647 | |
| 218505 | 218648 | return rc; |
| 218506 | 218649 | } |
| 218507 | 218650 | |
| 218508 | -#if !SQLITE_CORE | |
| 218651 | +#ifndef SQLITE_CORE | |
| 218509 | 218652 | #ifdef _WIN32 |
| 218510 | 218653 | __declspec(dllexport) |
| 218511 | 218654 | #endif |
| 218512 | 218655 | SQLITE_API int sqlite3_icu_init( |
| 218513 | 218656 | sqlite3 *db, |
| @@ -226177,10 +226320,12 @@ | ||
| 226177 | 226320 | if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ |
| 226178 | 226321 | unsigned char *aPage = sqlite3PagerGetData(pDbPage); |
| 226179 | 226322 | memcpy(aPage, pData, szPage); |
| 226180 | 226323 | pTab->pgnoTrunc = 0; |
| 226181 | 226324 | } |
| 226325 | + }else{ | |
| 226326 | + pTab->pgnoTrunc = 0; | |
| 226182 | 226327 | } |
| 226183 | 226328 | sqlite3PagerUnref(pDbPage); |
| 226184 | 226329 | return rc; |
| 226185 | 226330 | |
| 226186 | 226331 | update_fail: |
| @@ -226210,11 +226355,15 @@ | ||
| 226210 | 226355 | static int dbpageSync(sqlite3_vtab *pVtab){ |
| 226211 | 226356 | DbpageTable *pTab = (DbpageTable *)pVtab; |
| 226212 | 226357 | if( pTab->pgnoTrunc>0 ){ |
| 226213 | 226358 | Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; |
| 226214 | 226359 | Pager *pPager = sqlite3BtreePager(pBt); |
| 226215 | - sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); | |
| 226360 | + sqlite3BtreeEnter(pBt); | |
| 226361 | + if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){ | |
| 226362 | + sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); | |
| 226363 | + } | |
| 226364 | + sqlite3BtreeLeave(pBt); | |
| 226216 | 226365 | } |
| 226217 | 226366 | pTab->pgnoTrunc = 0; |
| 226218 | 226367 | return SQLITE_OK; |
| 226219 | 226368 | } |
| 226220 | 226369 | |
| @@ -232804,20 +232953,46 @@ | ||
| 232804 | 232953 | #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 232805 | 232954 | |
| 232806 | 232955 | /************** End of sqlite3session.c **************************************/ |
| 232807 | 232956 | /************** Begin file fts5.c ********************************************/ |
| 232808 | 232957 | |
| 232809 | - | |
| 232958 | +/* | |
| 232959 | +** This, the "fts5.c" source file, is a composite file that is itself | |
| 232960 | +** assembled from the following files: | |
| 232961 | +** | |
| 232962 | +** fts5.h | |
| 232963 | +** fts5Int.h | |
| 232964 | +** fts5parse.h <--- Generated from fts5parse.y by Lemon | |
| 232965 | +** fts5parse.c <--- Generated from fts5parse.y by Lemon | |
| 232966 | +** fts5_aux.c | |
| 232967 | +** fts5_buffer.c | |
| 232968 | +** fts5_config.c | |
| 232969 | +** fts5_expr.c | |
| 232970 | +** fts5_hash.c | |
| 232971 | +** fts5_index.c | |
| 232972 | +** fts5_main.c | |
| 232973 | +** fts5_storage.c | |
| 232974 | +** fts5_tokenize.c | |
| 232975 | +** fts5_unicode2.c | |
| 232976 | +** fts5_varint.c | |
| 232977 | +** fts5_vocab.c | |
| 232978 | +*/ | |
| 232810 | 232979 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) |
| 232811 | 232980 | |
| 232812 | 232981 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 232813 | 232982 | # define NDEBUG 1 |
| 232814 | 232983 | #endif |
| 232815 | 232984 | #if defined(NDEBUG) && defined(SQLITE_DEBUG) |
| 232816 | 232985 | # undef NDEBUG |
| 232817 | 232986 | #endif |
| 232818 | 232987 | |
| 232988 | +#ifdef HAVE_STDINT_H | |
| 232989 | +/* #include <stdint.h> */ | |
| 232990 | +#endif | |
| 232991 | +#ifdef HAVE_INTTYPES_H | |
| 232992 | +/* #include <inttypes.h> */ | |
| 232993 | +#endif | |
| 232819 | 232994 | /* |
| 232820 | 232995 | ** 2014 May 31 |
| 232821 | 232996 | ** |
| 232822 | 232997 | ** The author disclaims copyright to this source code. In place of |
| 232823 | 232998 | ** a legal notice, here is a blessing: |
| @@ -233114,17 +233289,32 @@ | ||
| 233114 | 233289 | ** This is used to access token iToken of phrase hit iIdx within the |
| 233115 | 233290 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 233116 | 233291 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 233117 | 233292 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 233118 | 233293 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 233119 | -** bytes. This API is not available if the specified token matches a | |
| 233120 | -** prefix query term. In that case both output variables are always set | |
| 233121 | -** to 0. | |
| 233294 | +** bytes. | |
| 233122 | 233295 | ** |
| 233123 | 233296 | ** The output text is not a copy of the document text that was tokenized. |
| 233124 | 233297 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 233125 | 233298 | ** includes any embedded 0x00 and trailing data. |
| 233299 | +** | |
| 233300 | +** This API may be slow in some cases if the token identified by parameters | |
| 233301 | +** iIdx and iToken matched a prefix token in the query. In most cases, the | |
| 233302 | +** first call to this API for each prefix token in the query is forced | |
| 233303 | +** to scan the portion of the full-text index that matches the prefix | |
| 233304 | +** token to collect the extra data required by this API. If the prefix | |
| 233305 | +** token matches a large number of token instances in the document set, | |
| 233306 | +** this may be a performance problem. | |
| 233307 | +** | |
| 233308 | +** If the user knows in advance that a query may use this API for a | |
| 233309 | +** prefix token, FTS5 may be configured to collect all required data as part | |
| 233310 | +** of the initial querying of the full-text index, avoiding the second scan | |
| 233311 | +** entirely. This also causes prefix queries that do not use this API to | |
| 233312 | +** run more slowly and use more memory. FTS5 may be configured in this way | |
| 233313 | +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] | |
| 233314 | +** option, or on a per-query basis using the | |
| 233315 | +** [fts5_insttoken | fts5_insttoken()] user function. | |
| 233126 | 233316 | ** |
| 233127 | 233317 | ** This API can be quite slow if used with an FTS5 table created with the |
| 233128 | 233318 | ** "detail=none" or "detail=column" option. |
| 233129 | 233319 | ** |
| 233130 | 233320 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -233803,11 +233993,12 @@ | ||
| 233803 | 233993 | int nUsermerge; /* 'usermerge' setting */ |
| 233804 | 233994 | int nHashSize; /* Bytes of memory for in-memory hash */ |
| 233805 | 233995 | char *zRank; /* Name of rank function */ |
| 233806 | 233996 | char *zRankArgs; /* Arguments to rank function */ |
| 233807 | 233997 | int bSecureDelete; /* 'secure-delete' */ |
| 233808 | - int nDeleteMerge; /* 'deletemerge' */ | |
| 233998 | + int nDeleteMerge; /* 'deletemerge' */ | |
| 233999 | + int bPrefixInsttoken; /* 'prefix-insttoken' */ | |
| 233809 | 234000 | |
| 233810 | 234001 | /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ |
| 233811 | 234002 | char **pzErrmsg; |
| 233812 | 234003 | |
| 233813 | 234004 | #ifdef SQLITE_DEBUG |
| @@ -234060,11 +234251,18 @@ | ||
| 234060 | 234251 | static int sqlite3Fts5StructureTest(Fts5Index*, void*); |
| 234061 | 234252 | |
| 234062 | 234253 | /* |
| 234063 | 234254 | ** Used by xInstToken(): |
| 234064 | 234255 | */ |
| 234065 | -static int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*); | |
| 234256 | +static int sqlite3Fts5IterToken( | |
| 234257 | + Fts5IndexIter *pIndexIter, | |
| 234258 | + const char *pToken, int nToken, | |
| 234259 | + i64 iRowid, | |
| 234260 | + int iCol, | |
| 234261 | + int iOff, | |
| 234262 | + const char **ppOut, int *pnOut | |
| 234263 | +); | |
| 234066 | 234264 | |
| 234067 | 234265 | /* |
| 234068 | 234266 | ** Insert or remove data to or from the index. Each time a document is |
| 234069 | 234267 | ** added to or removed from the index, this function is called one or more |
| 234070 | 234268 | ** times. |
| @@ -238274,10 +238472,23 @@ | ||
| 238274 | 238472 | if( bVal<0 ){ |
| 238275 | 238473 | *pbBadkey = 1; |
| 238276 | 238474 | }else{ |
| 238277 | 238475 | pConfig->bSecureDelete = (bVal ? 1 : 0); |
| 238278 | 238476 | } |
| 238477 | + } | |
| 238478 | + | |
| 238479 | + else if( 0==sqlite3_stricmp(zKey, "insttoken") ){ | |
| 238480 | + int bVal = -1; | |
| 238481 | + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ | |
| 238482 | + bVal = sqlite3_value_int(pVal); | |
| 238483 | + } | |
| 238484 | + if( bVal<0 ){ | |
| 238485 | + *pbBadkey = 1; | |
| 238486 | + }else{ | |
| 238487 | + pConfig->bPrefixInsttoken = (bVal ? 1 : 0); | |
| 238488 | + } | |
| 238489 | + | |
| 238279 | 238490 | }else{ |
| 238280 | 238491 | *pbBadkey = 1; |
| 238281 | 238492 | } |
| 238282 | 238493 | return rc; |
| 238283 | 238494 | } |
| @@ -241409,11 +241620,11 @@ | ||
| 241409 | 241620 | && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 |
| 241410 | 241621 | ){ |
| 241411 | 241622 | int rc = sqlite3Fts5PoslistWriterAppend( |
| 241412 | 241623 | &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff |
| 241413 | 241624 | ); |
| 241414 | - if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){ | |
| 241625 | + if( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){ | |
| 241415 | 241626 | int iCol = p->iOff>>32; |
| 241416 | 241627 | int iTokOff = p->iOff & 0x7FFFFFFF; |
| 241417 | 241628 | rc = sqlite3Fts5IndexIterWriteTokendata( |
| 241418 | 241629 | pT->pIter, pToken, nToken, iRowid, iCol, iTokOff |
| 241419 | 241630 | ); |
| @@ -241602,19 +241813,18 @@ | ||
| 241602 | 241813 | pPhrase = pExpr->apExprPhrase[iPhrase]; |
| 241603 | 241814 | if( iToken<0 || iToken>=pPhrase->nTerm ){ |
| 241604 | 241815 | return SQLITE_RANGE; |
| 241605 | 241816 | } |
| 241606 | 241817 | pTerm = &pPhrase->aTerm[iToken]; |
| 241607 | - if( pTerm->bPrefix==0 ){ | |
| 241608 | - if( pExpr->pConfig->bTokendata ){ | |
| 241609 | - rc = sqlite3Fts5IterToken( | |
| 241610 | - pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut | |
| 241611 | - ); | |
| 241612 | - }else{ | |
| 241613 | - *ppOut = pTerm->pTerm; | |
| 241614 | - *pnOut = pTerm->nFullTerm; | |
| 241615 | - } | |
| 241818 | + if( pExpr->pConfig->bTokendata || pTerm->bPrefix ){ | |
| 241819 | + rc = sqlite3Fts5IterToken( | |
| 241820 | + pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm, | |
| 241821 | + iRowid, iCol, iOff+iToken, ppOut, pnOut | |
| 241822 | + ); | |
| 241823 | + }else{ | |
| 241824 | + *ppOut = pTerm->pTerm; | |
| 241825 | + *pnOut = pTerm->nFullTerm; | |
| 241616 | 241826 | } |
| 241617 | 241827 | return rc; |
| 241618 | 241828 | } |
| 241619 | 241829 | |
| 241620 | 241830 | /* |
| @@ -248425,10 +248635,387 @@ | ||
| 248425 | 248635 | fts5BufferFree(&tmp); |
| 248426 | 248636 | memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); |
| 248427 | 248637 | *p1 = out; |
| 248428 | 248638 | } |
| 248429 | 248639 | |
| 248640 | + | |
| 248641 | +/* | |
| 248642 | +** Iterate through a range of entries in the FTS index, invoking the xVisit | |
| 248643 | +** callback for each of them. | |
| 248644 | +** | |
| 248645 | +** Parameter pToken points to an nToken buffer containing an FTS index term | |
| 248646 | +** (i.e. a document term with the preceding 1 byte index identifier - | |
| 248647 | +** FTS5_MAIN_PREFIX or similar). If bPrefix is true, then the call visits | |
| 248648 | +** all entries for terms that have pToken/nToken as a prefix. If bPrefix | |
| 248649 | +** is false, then only entries with pToken/nToken as the entire key are | |
| 248650 | +** visited. | |
| 248651 | +** | |
| 248652 | +** If the current table is a tokendata=1 table, then if bPrefix is true then | |
| 248653 | +** each index term is treated separately. However, if bPrefix is false, then | |
| 248654 | +** all index terms corresponding to pToken/nToken are collapsed into a single | |
| 248655 | +** term before the callback is invoked. | |
| 248656 | +** | |
| 248657 | +** The callback invoked for each entry visited is specified by paramter xVisit. | |
| 248658 | +** Each time it is invoked, it is passed a pointer to the Fts5Index object, | |
| 248659 | +** a copy of the 7th paramter to this function (pCtx) and a pointer to the | |
| 248660 | +** iterator that indicates the current entry. If the current entry is the | |
| 248661 | +** first with a new term (i.e. different from that of the previous entry, | |
| 248662 | +** including the very first term), then the final two parameters are passed | |
| 248663 | +** a pointer to the term and its size in bytes, respectively. If the current | |
| 248664 | +** entry is not the first associated with its term, these two parameters | |
| 248665 | +** are passed 0. | |
| 248666 | +** | |
| 248667 | +** If parameter pColset is not NULL, then it is used to filter entries before | |
| 248668 | +** the callback is invoked. | |
| 248669 | +*/ | |
| 248670 | +static int fts5VisitEntries( | |
| 248671 | + Fts5Index *p, /* Fts5 index object */ | |
| 248672 | + Fts5Colset *pColset, /* Columns filter to apply, or NULL */ | |
| 248673 | + u8 *pToken, /* Buffer containing token */ | |
| 248674 | + int nToken, /* Size of buffer pToken in bytes */ | |
| 248675 | + int bPrefix, /* True for a prefix scan */ | |
| 248676 | + void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int), | |
| 248677 | + void *pCtx /* Passed as second argument to xVisit() */ | |
| 248678 | +){ | |
| 248679 | + const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0) | |
| 248680 | + | FTS5INDEX_QUERY_SKIPEMPTY | |
| 248681 | + | FTS5INDEX_QUERY_NOOUTPUT; | |
| 248682 | + Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ | |
| 248683 | + int bNewTerm = 1; | |
| 248684 | + Fts5Structure *pStruct = fts5StructureRead(p); | |
| 248685 | + | |
| 248686 | + fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); | |
| 248687 | + fts5IterSetOutputCb(&p->rc, p1); | |
| 248688 | + for( /* no-op */ ; | |
| 248689 | + fts5MultiIterEof(p, p1)==0; | |
| 248690 | + fts5MultiIterNext2(p, p1, &bNewTerm) | |
| 248691 | + ){ | |
| 248692 | + Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; | |
| 248693 | + int nNew = 0; | |
| 248694 | + const u8 *pNew = 0; | |
| 248695 | + | |
| 248696 | + p1->xSetOutputs(p1, pSeg); | |
| 248697 | + if( p->rc ) break; | |
| 248698 | + | |
| 248699 | + if( bNewTerm ){ | |
| 248700 | + nNew = pSeg->term.n; | |
| 248701 | + pNew = pSeg->term.p; | |
| 248702 | + if( nNew<nToken || memcmp(pToken, pNew, nToken) ) break; | |
| 248703 | + } | |
| 248704 | + | |
| 248705 | + xVisit(p, pCtx, p1, pNew, nNew); | |
| 248706 | + } | |
| 248707 | + fts5MultiIterFree(p1); | |
| 248708 | + | |
| 248709 | + fts5StructureRelease(pStruct); | |
| 248710 | + return p->rc; | |
| 248711 | +} | |
| 248712 | + | |
| 248713 | + | |
| 248714 | +/* | |
| 248715 | +** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an | |
| 248716 | +** array of these for each row it visits (so all iRowid fields are the same). | |
| 248717 | +** Or, for an iterator used by an "ORDER BY rank" query, it accumulates an | |
| 248718 | +** array of these for the entire query (in which case iRowid fields may take | |
| 248719 | +** a variety of values). | |
| 248720 | +** | |
| 248721 | +** Each instance in the array indicates the iterator (and therefore term) | |
| 248722 | +** associated with position iPos of rowid iRowid. This is used by the | |
| 248723 | +** xInstToken() API. | |
| 248724 | +** | |
| 248725 | +** iRowid: | |
| 248726 | +** Rowid for the current entry. | |
| 248727 | +** | |
| 248728 | +** iPos: | |
| 248729 | +** Position of current entry within row. In the usual ((iCol<<32)+iOff) | |
| 248730 | +** format (e.g. see macros FTS5_POS2COLUMN() and FTS5_POS2OFFSET()). | |
| 248731 | +** | |
| 248732 | +** iIter: | |
| 248733 | +** If the Fts5TokenDataIter iterator that the entry is part of is | |
| 248734 | +** actually an iterator (i.e. with nIter>0, not just a container for | |
| 248735 | +** Fts5TokenDataMap structures), then this variable is an index into | |
| 248736 | +** the apIter[] array. The corresponding term is that which the iterator | |
| 248737 | +** at apIter[iIter] currently points to. | |
| 248738 | +** | |
| 248739 | +** Or, if the Fts5TokenDataIter iterator is just a container object | |
| 248740 | +** (nIter==0), then iIter is an index into the term.p[] buffer where | |
| 248741 | +** the term is stored. | |
| 248742 | +** | |
| 248743 | +** nByte: | |
| 248744 | +** In the case where iIter is an index into term.p[], this variable | |
| 248745 | +** is the size of the term in bytes. If iIter is an index into apIter[], | |
| 248746 | +** this variable is unused. | |
| 248747 | +*/ | |
| 248748 | +struct Fts5TokenDataMap { | |
| 248749 | + i64 iRowid; /* Row this token is located in */ | |
| 248750 | + i64 iPos; /* Position of token */ | |
| 248751 | + int iIter; /* Iterator token was read from */ | |
| 248752 | + int nByte; /* Length of token in bytes (or 0) */ | |
| 248753 | +}; | |
| 248754 | + | |
| 248755 | +/* | |
| 248756 | +** An object used to supplement Fts5Iter for tokendata=1 iterators. | |
| 248757 | +** | |
| 248758 | +** This object serves two purposes. The first is as a container for an array | |
| 248759 | +** of Fts5TokenDataMap structures, which are used to find the token required | |
| 248760 | +** when the xInstToken() API is used. This is done by the nMapAlloc, nMap and | |
| 248761 | +** aMap[] variables. | |
| 248762 | +*/ | |
| 248763 | +struct Fts5TokenDataIter { | |
| 248764 | + int nMapAlloc; /* Allocated size of aMap[] in entries */ | |
| 248765 | + int nMap; /* Number of valid entries in aMap[] */ | |
| 248766 | + Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */ | |
| 248767 | + | |
| 248768 | + /* The following are used for prefix-queries only. */ | |
| 248769 | + Fts5Buffer terms; | |
| 248770 | + | |
| 248771 | + /* The following are used for other full-token tokendata queries only. */ | |
| 248772 | + int nIter; | |
| 248773 | + int nIterAlloc; | |
| 248774 | + Fts5PoslistReader *aPoslistReader; | |
| 248775 | + int *aPoslistToIter; | |
| 248776 | + Fts5Iter *apIter[1]; | |
| 248777 | +}; | |
| 248778 | + | |
| 248779 | +/* | |
| 248780 | +** The two input arrays - a1[] and a2[] - are in sorted order. This function | |
| 248781 | +** merges the two arrays together and writes the result to output array | |
| 248782 | +** aOut[]. aOut[] is guaranteed to be large enough to hold the result. | |
| 248783 | +** | |
| 248784 | +** Duplicate entries are copied into the output. So the size of the output | |
| 248785 | +** array is always (n1+n2) entries. | |
| 248786 | +*/ | |
| 248787 | +static void fts5TokendataMerge( | |
| 248788 | + Fts5TokenDataMap *a1, int n1, /* Input array 1 */ | |
| 248789 | + Fts5TokenDataMap *a2, int n2, /* Input array 2 */ | |
| 248790 | + Fts5TokenDataMap *aOut /* Output array */ | |
| 248791 | +){ | |
| 248792 | + int i1 = 0; | |
| 248793 | + int i2 = 0; | |
| 248794 | + | |
| 248795 | + assert( n1>=0 && n2>=0 ); | |
| 248796 | + while( i1<n1 || i2<n2 ){ | |
| 248797 | + Fts5TokenDataMap *pOut = &aOut[i1+i2]; | |
| 248798 | + if( i2>=n2 || (i1<n1 && ( | |
| 248799 | + a1[i1].iRowid<a2[i2].iRowid | |
| 248800 | + || (a1[i1].iRowid==a2[i2].iRowid && a1[i1].iPos<=a2[i2].iPos) | |
| 248801 | + ))){ | |
| 248802 | + memcpy(pOut, &a1[i1], sizeof(Fts5TokenDataMap)); | |
| 248803 | + i1++; | |
| 248804 | + }else{ | |
| 248805 | + memcpy(pOut, &a2[i2], sizeof(Fts5TokenDataMap)); | |
| 248806 | + i2++; | |
| 248807 | + } | |
| 248808 | + } | |
| 248809 | +} | |
| 248810 | + | |
| 248811 | + | |
| 248812 | +/* | |
| 248813 | +** Append a mapping to the token-map belonging to object pT. | |
| 248814 | +*/ | |
| 248815 | +static void fts5TokendataIterAppendMap( | |
| 248816 | + Fts5Index *p, | |
| 248817 | + Fts5TokenDataIter *pT, | |
| 248818 | + int iIter, | |
| 248819 | + int nByte, | |
| 248820 | + i64 iRowid, | |
| 248821 | + i64 iPos | |
| 248822 | +){ | |
| 248823 | + if( p->rc==SQLITE_OK ){ | |
| 248824 | + if( pT->nMap==pT->nMapAlloc ){ | |
| 248825 | + int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; | |
| 248826 | + int nAlloc = nNew * sizeof(Fts5TokenDataMap); | |
| 248827 | + Fts5TokenDataMap *aNew; | |
| 248828 | + | |
| 248829 | + aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc); | |
| 248830 | + if( aNew==0 ){ | |
| 248831 | + p->rc = SQLITE_NOMEM; | |
| 248832 | + return; | |
| 248833 | + } | |
| 248834 | + | |
| 248835 | + pT->aMap = aNew; | |
| 248836 | + pT->nMapAlloc = nNew; | |
| 248837 | + } | |
| 248838 | + | |
| 248839 | + pT->aMap[pT->nMap].iRowid = iRowid; | |
| 248840 | + pT->aMap[pT->nMap].iPos = iPos; | |
| 248841 | + pT->aMap[pT->nMap].iIter = iIter; | |
| 248842 | + pT->aMap[pT->nMap].nByte = nByte; | |
| 248843 | + pT->nMap++; | |
| 248844 | + } | |
| 248845 | +} | |
| 248846 | + | |
| 248847 | +/* | |
| 248848 | +** Sort the contents of the pT->aMap[] array. | |
| 248849 | +** | |
| 248850 | +** The sorting algorithm requries a malloc(). If this fails, an error code | |
| 248851 | +** is left in Fts5Index.rc before returning. | |
| 248852 | +*/ | |
| 248853 | +static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){ | |
| 248854 | + Fts5TokenDataMap *aTmp = 0; | |
| 248855 | + int nByte = pT->nMap * sizeof(Fts5TokenDataMap); | |
| 248856 | + | |
| 248857 | + aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte); | |
| 248858 | + if( aTmp ){ | |
| 248859 | + Fts5TokenDataMap *a1 = pT->aMap; | |
| 248860 | + Fts5TokenDataMap *a2 = aTmp; | |
| 248861 | + i64 nHalf; | |
| 248862 | + | |
| 248863 | + for(nHalf=1; nHalf<pT->nMap; nHalf=nHalf*2){ | |
| 248864 | + int i1; | |
| 248865 | + for(i1=0; i1<pT->nMap; i1+=(nHalf*2)){ | |
| 248866 | + int n1 = MIN(nHalf, pT->nMap-i1); | |
| 248867 | + int n2 = MIN(nHalf, pT->nMap-i1-n1); | |
| 248868 | + fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]); | |
| 248869 | + } | |
| 248870 | + SWAPVAL(Fts5TokenDataMap*, a1, a2); | |
| 248871 | + } | |
| 248872 | + | |
| 248873 | + if( a1!=pT->aMap ){ | |
| 248874 | + memcpy(pT->aMap, a1, pT->nMap*sizeof(Fts5TokenDataMap)); | |
| 248875 | + } | |
| 248876 | + sqlite3_free(aTmp); | |
| 248877 | + | |
| 248878 | +#ifdef SQLITE_DEBUG | |
| 248879 | + { | |
| 248880 | + int ii; | |
| 248881 | + for(ii=1; ii<pT->nMap; ii++){ | |
| 248882 | + Fts5TokenDataMap *p1 = &pT->aMap[ii-1]; | |
| 248883 | + Fts5TokenDataMap *p2 = &pT->aMap[ii]; | |
| 248884 | + assert( p1->iRowid<p2->iRowid | |
| 248885 | + || (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos) | |
| 248886 | + ); | |
| 248887 | + } | |
| 248888 | + } | |
| 248889 | +#endif | |
| 248890 | + } | |
| 248891 | +} | |
| 248892 | + | |
| 248893 | +/* | |
| 248894 | +** Delete an Fts5TokenDataIter structure and its contents. | |
| 248895 | +*/ | |
| 248896 | +static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ | |
| 248897 | + if( pSet ){ | |
| 248898 | + int ii; | |
| 248899 | + for(ii=0; ii<pSet->nIter; ii++){ | |
| 248900 | + fts5MultiIterFree(pSet->apIter[ii]); | |
| 248901 | + } | |
| 248902 | + fts5BufferFree(&pSet->terms); | |
| 248903 | + sqlite3_free(pSet->aPoslistReader); | |
| 248904 | + sqlite3_free(pSet->aMap); | |
| 248905 | + sqlite3_free(pSet); | |
| 248906 | + } | |
| 248907 | +} | |
| 248908 | + | |
| 248909 | + | |
| 248910 | +/* | |
| 248911 | +** fts5VisitEntries() context object used by fts5SetupPrefixIterTokendata() | |
| 248912 | +** to pass data to prefixIterSetupTokendataCb(). | |
| 248913 | +*/ | |
| 248914 | +typedef struct TokendataSetupCtx TokendataSetupCtx; | |
| 248915 | +struct TokendataSetupCtx { | |
| 248916 | + Fts5TokenDataIter *pT; /* Object being populated with mappings */ | |
| 248917 | + int iTermOff; /* Offset of current term in terms.p[] */ | |
| 248918 | + int nTermByte; /* Size of current term in bytes */ | |
| 248919 | +}; | |
| 248920 | + | |
| 248921 | +/* | |
| 248922 | +** fts5VisitEntries() callback used by fts5SetupPrefixIterTokendata(). This | |
| 248923 | +** callback adds an entry to the Fts5TokenDataIter.aMap[] array for each | |
| 248924 | +** position in the current position-list. It doesn't matter that some of | |
| 248925 | +** these may be out of order - they will be sorted later. | |
| 248926 | +*/ | |
| 248927 | +static void prefixIterSetupTokendataCb( | |
| 248928 | + Fts5Index *p, | |
| 248929 | + void *pCtx, | |
| 248930 | + Fts5Iter *p1, | |
| 248931 | + const u8 *pNew, | |
| 248932 | + int nNew | |
| 248933 | +){ | |
| 248934 | + TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx; | |
| 248935 | + int iPosOff = 0; | |
| 248936 | + i64 iPos = 0; | |
| 248937 | + | |
| 248938 | + if( pNew ){ | |
| 248939 | + pSetup->nTermByte = nNew-1; | |
| 248940 | + pSetup->iTermOff = pSetup->pT->terms.n; | |
| 248941 | + fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1, pNew+1); | |
| 248942 | + } | |
| 248943 | + | |
| 248944 | + while( 0==sqlite3Fts5PoslistNext64( | |
| 248945 | + p1->base.pData, p1->base.nData, &iPosOff, &iPos | |
| 248946 | + ) ){ | |
| 248947 | + fts5TokendataIterAppendMap(p, | |
| 248948 | + pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos | |
| 248949 | + ); | |
| 248950 | + } | |
| 248951 | +} | |
| 248952 | + | |
| 248953 | + | |
| 248954 | +/* | |
| 248955 | +** Context object passed by fts5SetupPrefixIter() to fts5VisitEntries(). | |
| 248956 | +*/ | |
| 248957 | +typedef struct PrefixSetupCtx PrefixSetupCtx; | |
| 248958 | +struct PrefixSetupCtx { | |
| 248959 | + void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); | |
| 248960 | + void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); | |
| 248961 | + i64 iLastRowid; | |
| 248962 | + int nMerge; | |
| 248963 | + Fts5Buffer *aBuf; | |
| 248964 | + int nBuf; | |
| 248965 | + Fts5Buffer doclist; | |
| 248966 | + TokendataSetupCtx *pTokendata; | |
| 248967 | +}; | |
| 248968 | + | |
| 248969 | +/* | |
| 248970 | +** fts5VisitEntries() callback used by fts5SetupPrefixIter() | |
| 248971 | +*/ | |
| 248972 | +static void prefixIterSetupCb( | |
| 248973 | + Fts5Index *p, | |
| 248974 | + void *pCtx, | |
| 248975 | + Fts5Iter *p1, | |
| 248976 | + const u8 *pNew, | |
| 248977 | + int nNew | |
| 248978 | +){ | |
| 248979 | + PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx; | |
| 248980 | + const int nMerge = pSetup->nMerge; | |
| 248981 | + | |
| 248982 | + if( p1->base.nData>0 ){ | |
| 248983 | + if( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){ | |
| 248984 | + int i; | |
| 248985 | + for(i=0; p->rc==SQLITE_OK && pSetup->doclist.n; i++){ | |
| 248986 | + int i1 = i*nMerge; | |
| 248987 | + int iStore; | |
| 248988 | + assert( i1+nMerge<=pSetup->nBuf ); | |
| 248989 | + for(iStore=i1; iStore<i1+nMerge; iStore++){ | |
| 248990 | + if( pSetup->aBuf[iStore].n==0 ){ | |
| 248991 | + fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]); | |
| 248992 | + fts5BufferZero(&pSetup->doclist); | |
| 248993 | + break; | |
| 248994 | + } | |
| 248995 | + } | |
| 248996 | + if( iStore==i1+nMerge ){ | |
| 248997 | + pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]); | |
| 248998 | + for(iStore=i1; iStore<i1+nMerge; iStore++){ | |
| 248999 | + fts5BufferZero(&pSetup->aBuf[iStore]); | |
| 249000 | + } | |
| 249001 | + } | |
| 249002 | + } | |
| 249003 | + pSetup->iLastRowid = 0; | |
| 249004 | + } | |
| 249005 | + | |
| 249006 | + pSetup->xAppend( | |
| 249007 | + p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist | |
| 249008 | + ); | |
| 249009 | + pSetup->iLastRowid = p1->base.iRowid; | |
| 249010 | + } | |
| 249011 | + | |
| 249012 | + if( pSetup->pTokendata ){ | |
| 249013 | + prefixIterSetupTokendataCb(p, (void*)pSetup->pTokendata, p1, pNew, nNew); | |
| 249014 | + } | |
| 249015 | +} | |
| 249016 | + | |
| 248430 | 249017 | static void fts5SetupPrefixIter( |
| 248431 | 249018 | Fts5Index *p, /* Index to read from */ |
| 248432 | 249019 | int bDesc, /* True for "ORDER BY rowid DESC" */ |
| 248433 | 249020 | int iIdx, /* Index to scan for data */ |
| 248434 | 249021 | u8 *pToken, /* Buffer containing prefix to match */ |
| @@ -248435,137 +249022,91 @@ | ||
| 248435 | 249022 | int nToken, /* Size of buffer pToken in bytes */ |
| 248436 | 249023 | Fts5Colset *pColset, /* Restrict matches to these columns */ |
| 248437 | 249024 | Fts5Iter **ppIter /* OUT: New iterator */ |
| 248438 | 249025 | ){ |
| 248439 | 249026 | Fts5Structure *pStruct; |
| 248440 | - Fts5Buffer *aBuf; | |
| 248441 | - int nBuf = 32; | |
| 248442 | - int nMerge = 1; | |
| 249027 | + PrefixSetupCtx s; | |
| 249028 | + TokendataSetupCtx s2; | |
| 248443 | 249029 | |
| 248444 | - void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); | |
| 248445 | - void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); | |
| 249030 | + memset(&s, 0, sizeof(s)); | |
| 249031 | + memset(&s2, 0, sizeof(s2)); | |
| 249032 | + | |
| 249033 | + s.nMerge = 1; | |
| 249034 | + s.iLastRowid = 0; | |
| 249035 | + s.nBuf = 32; | |
| 249036 | + if( iIdx==0 | |
| 249037 | + && p->pConfig->eDetail==FTS5_DETAIL_FULL | |
| 249038 | + && p->pConfig->bPrefixInsttoken | |
| 249039 | + ){ | |
| 249040 | + s.pTokendata = &s2; | |
| 249041 | + s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, sizeof(*s2.pT)); | |
| 249042 | + } | |
| 249043 | + | |
| 248446 | 249044 | if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
| 248447 | - xMerge = fts5MergeRowidLists; | |
| 248448 | - xAppend = fts5AppendRowid; | |
| 249045 | + s.xMerge = fts5MergeRowidLists; | |
| 249046 | + s.xAppend = fts5AppendRowid; | |
| 248449 | 249047 | }else{ |
| 248450 | - nMerge = FTS5_MERGE_NLIST-1; | |
| 248451 | - nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ | |
| 248452 | - xMerge = fts5MergePrefixLists; | |
| 248453 | - xAppend = fts5AppendPoslist; | |
| 249048 | + s.nMerge = FTS5_MERGE_NLIST-1; | |
| 249049 | + s.nBuf = s.nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ | |
| 249050 | + s.xMerge = fts5MergePrefixLists; | |
| 249051 | + s.xAppend = fts5AppendPoslist; | |
| 248454 | 249052 | } |
| 248455 | 249053 | |
| 248456 | - aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); | |
| 249054 | + s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*s.nBuf); | |
| 248457 | 249055 | pStruct = fts5StructureRead(p); |
| 248458 | - assert( p->rc!=SQLITE_OK || (aBuf && pStruct) ); | |
| 249056 | + assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) ); | |
| 248459 | 249057 | |
| 248460 | 249058 | if( p->rc==SQLITE_OK ){ |
| 248461 | - const int flags = FTS5INDEX_QUERY_SCAN | |
| 248462 | - | FTS5INDEX_QUERY_SKIPEMPTY | |
| 248463 | - | FTS5INDEX_QUERY_NOOUTPUT; | |
| 249059 | + void *pCtx = (void*)&s; | |
| 248464 | 249060 | int i; |
| 248465 | - i64 iLastRowid = 0; | |
| 248466 | - Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ | |
| 248467 | 249061 | Fts5Data *pData; |
| 248468 | - Fts5Buffer doclist; | |
| 248469 | - int bNewTerm = 1; | |
| 248470 | - | |
| 248471 | - memset(&doclist, 0, sizeof(doclist)); | |
| 248472 | 249062 | |
| 248473 | 249063 | /* If iIdx is non-zero, then it is the number of a prefix-index for |
| 248474 | 249064 | ** prefixes 1 character longer than the prefix being queried for. That |
| 248475 | 249065 | ** index contains all the doclists required, except for the one |
| 248476 | 249066 | ** corresponding to the prefix itself. That one is extracted from the |
| 248477 | 249067 | ** main term index here. */ |
| 248478 | 249068 | if( iIdx!=0 ){ |
| 248479 | - int dummy = 0; | |
| 248480 | - const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; | |
| 248481 | 249069 | pToken[0] = FTS5_MAIN_PREFIX; |
| 248482 | - fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1); | |
| 248483 | - fts5IterSetOutputCb(&p->rc, p1); | |
| 248484 | - for(; | |
| 248485 | - fts5MultiIterEof(p, p1)==0; | |
| 248486 | - fts5MultiIterNext2(p, p1, &dummy) | |
| 248487 | - ){ | |
| 248488 | - Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; | |
| 248489 | - p1->xSetOutputs(p1, pSeg); | |
| 248490 | - if( p1->base.nData ){ | |
| 248491 | - xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); | |
| 248492 | - iLastRowid = p1->base.iRowid; | |
| 248493 | - } | |
| 248494 | - } | |
| 248495 | - fts5MultiIterFree(p1); | |
| 249070 | + fts5VisitEntries(p, pColset, pToken, nToken, 0, prefixIterSetupCb, pCtx); | |
| 248496 | 249071 | } |
| 248497 | 249072 | |
| 248498 | 249073 | pToken[0] = FTS5_MAIN_PREFIX + iIdx; |
| 248499 | - fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); | |
| 248500 | - fts5IterSetOutputCb(&p->rc, p1); | |
| 248501 | - | |
| 248502 | - for( /* no-op */ ; | |
| 248503 | - fts5MultiIterEof(p, p1)==0; | |
| 248504 | - fts5MultiIterNext2(p, p1, &bNewTerm) | |
| 248505 | - ){ | |
| 248506 | - Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; | |
| 248507 | - int nTerm = pSeg->term.n; | |
| 248508 | - const u8 *pTerm = pSeg->term.p; | |
| 248509 | - p1->xSetOutputs(p1, pSeg); | |
| 248510 | - | |
| 248511 | - assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); | |
| 248512 | - if( bNewTerm ){ | |
| 248513 | - if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break; | |
| 248514 | - } | |
| 248515 | - | |
| 248516 | - if( p1->base.nData==0 ) continue; | |
| 248517 | - if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ | |
| 248518 | - for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ | |
| 248519 | - int i1 = i*nMerge; | |
| 248520 | - int iStore; | |
| 248521 | - assert( i1+nMerge<=nBuf ); | |
| 248522 | - for(iStore=i1; iStore<i1+nMerge; iStore++){ | |
| 248523 | - if( aBuf[iStore].n==0 ){ | |
| 248524 | - fts5BufferSwap(&doclist, &aBuf[iStore]); | |
| 248525 | - fts5BufferZero(&doclist); | |
| 248526 | - break; | |
| 248527 | - } | |
| 248528 | - } | |
| 248529 | - if( iStore==i1+nMerge ){ | |
| 248530 | - xMerge(p, &doclist, nMerge, &aBuf[i1]); | |
| 248531 | - for(iStore=i1; iStore<i1+nMerge; iStore++){ | |
| 248532 | - fts5BufferZero(&aBuf[iStore]); | |
| 248533 | - } | |
| 248534 | - } | |
| 248535 | - } | |
| 248536 | - iLastRowid = 0; | |
| 248537 | - } | |
| 248538 | - | |
| 248539 | - xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); | |
| 248540 | - iLastRowid = p1->base.iRowid; | |
| 248541 | - } | |
| 248542 | - | |
| 248543 | - assert( (nBuf%nMerge)==0 ); | |
| 248544 | - for(i=0; i<nBuf; i+=nMerge){ | |
| 249074 | + fts5VisitEntries(p, pColset, pToken, nToken, 1, prefixIterSetupCb, pCtx); | |
| 249075 | + | |
| 249076 | + assert( (s.nBuf%s.nMerge)==0 ); | |
| 249077 | + for(i=0; i<s.nBuf; i+=s.nMerge){ | |
| 248545 | 249078 | int iFree; |
| 248546 | 249079 | if( p->rc==SQLITE_OK ){ |
| 248547 | - xMerge(p, &doclist, nMerge, &aBuf[i]); | |
| 249080 | + s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]); | |
| 248548 | 249081 | } |
| 248549 | - for(iFree=i; iFree<i+nMerge; iFree++){ | |
| 248550 | - fts5BufferFree(&aBuf[iFree]); | |
| 249082 | + for(iFree=i; iFree<i+s.nMerge; iFree++){ | |
| 249083 | + fts5BufferFree(&s.aBuf[iFree]); | |
| 248551 | 249084 | } |
| 248552 | 249085 | } |
| 248553 | - fts5MultiIterFree(p1); | |
| 248554 | 249086 | |
| 248555 | - pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING); | |
| 249087 | + pData = fts5IdxMalloc(p, sizeof(*pData)+s.doclist.n+FTS5_DATA_ZERO_PADDING); | |
| 249088 | + assert( pData!=0 || p->rc!=SQLITE_OK ); | |
| 248556 | 249089 | if( pData ){ |
| 248557 | 249090 | pData->p = (u8*)&pData[1]; |
| 248558 | - pData->nn = pData->szLeaf = doclist.n; | |
| 248559 | - if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); | |
| 249091 | + pData->nn = pData->szLeaf = s.doclist.n; | |
| 249092 | + if( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n); | |
| 248560 | 249093 | fts5MultiIterNew2(p, pData, bDesc, ppIter); |
| 248561 | 249094 | } |
| 248562 | - fts5BufferFree(&doclist); | |
| 249095 | + | |
| 249096 | + assert( (*ppIter)!=0 || p->rc!=SQLITE_OK ); | |
| 249097 | + if( p->rc==SQLITE_OK && s.pTokendata ){ | |
| 249098 | + fts5TokendataIterSortMap(p, s2.pT); | |
| 249099 | + (*ppIter)->pTokenDataIter = s2.pT; | |
| 249100 | + s2.pT = 0; | |
| 249101 | + } | |
| 248563 | 249102 | } |
| 248564 | 249103 | |
| 249104 | + fts5TokendataIterDelete(s2.pT); | |
| 249105 | + fts5BufferFree(&s.doclist); | |
| 248565 | 249106 | fts5StructureRelease(pStruct); |
| 248566 | - sqlite3_free(aBuf); | |
| 249107 | + sqlite3_free(s.aBuf); | |
| 248567 | 249108 | } |
| 248568 | 249109 | |
| 248569 | 249110 | |
| 248570 | 249111 | /* |
| 248571 | 249112 | ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain |
| @@ -248815,42 +249356,10 @@ | ||
| 248815 | 249356 | static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ |
| 248816 | 249357 | fts5DataRelease(pSeg->pLeaf); |
| 248817 | 249358 | pSeg->pLeaf = 0; |
| 248818 | 249359 | } |
| 248819 | 249360 | |
| 248820 | -/* | |
| 248821 | -** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an | |
| 248822 | -** array of these for each row it visits. Or, for an iterator used by an | |
| 248823 | -** "ORDER BY rank" query, it accumulates an array of these for the entire | |
| 248824 | -** query. | |
| 248825 | -** | |
| 248826 | -** Each instance in the array indicates the iterator (and therefore term) | |
| 248827 | -** associated with position iPos of rowid iRowid. This is used by the | |
| 248828 | -** xInstToken() API. | |
| 248829 | -*/ | |
| 248830 | -struct Fts5TokenDataMap { | |
| 248831 | - i64 iRowid; /* Row this token is located in */ | |
| 248832 | - i64 iPos; /* Position of token */ | |
| 248833 | - int iIter; /* Iterator token was read from */ | |
| 248834 | -}; | |
| 248835 | - | |
| 248836 | -/* | |
| 248837 | -** An object used to supplement Fts5Iter for tokendata=1 iterators. | |
| 248838 | -*/ | |
| 248839 | -struct Fts5TokenDataIter { | |
| 248840 | - int nIter; | |
| 248841 | - int nIterAlloc; | |
| 248842 | - | |
| 248843 | - int nMap; | |
| 248844 | - int nMapAlloc; | |
| 248845 | - Fts5TokenDataMap *aMap; | |
| 248846 | - | |
| 248847 | - Fts5PoslistReader *aPoslistReader; | |
| 248848 | - int *aPoslistToIter; | |
| 248849 | - Fts5Iter *apIter[1]; | |
| 248850 | -}; | |
| 248851 | - | |
| 248852 | 249361 | /* |
| 248853 | 249362 | ** This function appends iterator pAppend to Fts5TokenDataIter pIn and |
| 248854 | 249363 | ** returns the result. |
| 248855 | 249364 | */ |
| 248856 | 249365 | static Fts5TokenDataIter *fts5AppendTokendataIter( |
| @@ -248883,58 +249392,10 @@ | ||
| 248883 | 249392 | assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); |
| 248884 | 249393 | |
| 248885 | 249394 | return pRet; |
| 248886 | 249395 | } |
| 248887 | 249396 | |
| 248888 | -/* | |
| 248889 | -** Delete an Fts5TokenDataIter structure and its contents. | |
| 248890 | -*/ | |
| 248891 | -static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ | |
| 248892 | - if( pSet ){ | |
| 248893 | - int ii; | |
| 248894 | - for(ii=0; ii<pSet->nIter; ii++){ | |
| 248895 | - fts5MultiIterFree(pSet->apIter[ii]); | |
| 248896 | - } | |
| 248897 | - sqlite3_free(pSet->aPoslistReader); | |
| 248898 | - sqlite3_free(pSet->aMap); | |
| 248899 | - sqlite3_free(pSet); | |
| 248900 | - } | |
| 248901 | -} | |
| 248902 | - | |
| 248903 | -/* | |
| 248904 | -** Append a mapping to the token-map belonging to object pT. | |
| 248905 | -*/ | |
| 248906 | -static void fts5TokendataIterAppendMap( | |
| 248907 | - Fts5Index *p, | |
| 248908 | - Fts5TokenDataIter *pT, | |
| 248909 | - int iIter, | |
| 248910 | - i64 iRowid, | |
| 248911 | - i64 iPos | |
| 248912 | -){ | |
| 248913 | - if( p->rc==SQLITE_OK ){ | |
| 248914 | - if( pT->nMap==pT->nMapAlloc ){ | |
| 248915 | - int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; | |
| 248916 | - int nByte = nNew * sizeof(Fts5TokenDataMap); | |
| 248917 | - Fts5TokenDataMap *aNew; | |
| 248918 | - | |
| 248919 | - aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte); | |
| 248920 | - if( aNew==0 ){ | |
| 248921 | - p->rc = SQLITE_NOMEM; | |
| 248922 | - return; | |
| 248923 | - } | |
| 248924 | - | |
| 248925 | - pT->aMap = aNew; | |
| 248926 | - pT->nMapAlloc = nNew; | |
| 248927 | - } | |
| 248928 | - | |
| 248929 | - pT->aMap[pT->nMap].iRowid = iRowid; | |
| 248930 | - pT->aMap[pT->nMap].iPos = iPos; | |
| 248931 | - pT->aMap[pT->nMap].iIter = iIter; | |
| 248932 | - pT->nMap++; | |
| 248933 | - } | |
| 248934 | -} | |
| 248935 | - | |
| 248936 | 249397 | /* |
| 248937 | 249398 | ** The iterator passed as the only argument must be a tokendata=1 iterator |
| 248938 | 249399 | ** (pIter->pTokenDataIter!=0). This function sets the iterator output |
| 248939 | 249400 | ** variables (pIter->base.*) according to the contents of the current |
| 248940 | 249401 | ** row. |
| @@ -248971,11 +249432,11 @@ | ||
| 248971 | 249432 | int eDetail = pIter->pIndex->pConfig->eDetail; |
| 248972 | 249433 | pIter->base.bEof = 0; |
| 248973 | 249434 | pIter->base.iRowid = iRowid; |
| 248974 | 249435 | |
| 248975 | 249436 | if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ |
| 248976 | - fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1); | |
| 249437 | + fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, 0, iRowid, -1); | |
| 248977 | 249438 | }else |
| 248978 | 249439 | if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ |
| 248979 | 249440 | int nReader = 0; |
| 248980 | 249441 | int nByte = 0; |
| 248981 | 249442 | i64 iPrev = 0; |
| @@ -249224,10 +249685,11 @@ | ||
| 249224 | 249685 | |
| 249225 | 249686 | if( p->rc==SQLITE_OK ){ |
| 249226 | 249687 | pRet = fts5MultiIterAlloc(p, 0); |
| 249227 | 249688 | } |
| 249228 | 249689 | if( pRet ){ |
| 249690 | + pRet->nSeg = 0; | |
| 249229 | 249691 | pRet->pTokenDataIter = pSet; |
| 249230 | 249692 | if( pSet ){ |
| 249231 | 249693 | fts5IterSetOutputsTokendata(pRet); |
| 249232 | 249694 | }else{ |
| 249233 | 249695 | pRet->base.bEof = 1; |
| @@ -249238,11 +249700,10 @@ | ||
| 249238 | 249700 | |
| 249239 | 249701 | fts5StructureRelease(pStruct); |
| 249240 | 249702 | fts5BufferFree(&bSeek); |
| 249241 | 249703 | return pRet; |
| 249242 | 249704 | } |
| 249243 | - | |
| 249244 | 249705 | |
| 249245 | 249706 | /* |
| 249246 | 249707 | ** Open a new iterator to iterate though all rowid that match the |
| 249247 | 249708 | ** specified token or token prefix. |
| 249248 | 249709 | */ |
| @@ -249262,12 +249723,18 @@ | ||
| 249262 | 249723 | |
| 249263 | 249724 | if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ |
| 249264 | 249725 | int iIdx = 0; /* Index to search */ |
| 249265 | 249726 | int iPrefixIdx = 0; /* +1 prefix index */ |
| 249266 | 249727 | int bTokendata = pConfig->bTokendata; |
| 249728 | + assert( buf.p!=0 ); | |
| 249267 | 249729 | if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); |
| 249268 | 249730 | |
| 249731 | + /* The NOTOKENDATA flag is set when each token in a tokendata=1 table | |
| 249732 | + ** should be treated individually, instead of merging all those with | |
| 249733 | + ** a common prefix into a single entry. This is used, for example, by | |
| 249734 | + ** queries performed as part of an integrity-check, or by the fts5vocab | |
| 249735 | + ** module. */ | |
| 249269 | 249736 | if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ |
| 249270 | 249737 | bTokendata = 0; |
| 249271 | 249738 | } |
| 249272 | 249739 | |
| 249273 | 249740 | /* Figure out which index to search and set iIdx accordingly. If this |
| @@ -249294,11 +249761,11 @@ | ||
| 249294 | 249761 | if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; |
| 249295 | 249762 | } |
| 249296 | 249763 | } |
| 249297 | 249764 | |
| 249298 | 249765 | if( bTokendata && iIdx==0 ){ |
| 249299 | - buf.p[0] = '0'; | |
| 249766 | + buf.p[0] = FTS5_MAIN_PREFIX; | |
| 249300 | 249767 | pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); |
| 249301 | 249768 | }else if( iIdx<=pConfig->nPrefix ){ |
| 249302 | 249769 | /* Straight index lookup */ |
| 249303 | 249770 | Fts5Structure *pStruct = fts5StructureRead(p); |
| 249304 | 249771 | buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); |
| @@ -249307,11 +249774,11 @@ | ||
| 249307 | 249774 | pColset, buf.p, nToken+1, -1, 0, &pRet |
| 249308 | 249775 | ); |
| 249309 | 249776 | fts5StructureRelease(pStruct); |
| 249310 | 249777 | } |
| 249311 | 249778 | }else{ |
| 249312 | - /* Scan multiple terms in the main index */ | |
| 249779 | + /* Scan multiple terms in the main index for a prefix query. */ | |
| 249313 | 249780 | int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; |
| 249314 | 249781 | fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); |
| 249315 | 249782 | if( pRet==0 ){ |
| 249316 | 249783 | assert( p->rc!=SQLITE_OK ); |
| 249317 | 249784 | }else{ |
| @@ -249343,11 +249810,12 @@ | ||
| 249343 | 249810 | ** Move to the next matching rowid. |
| 249344 | 249811 | */ |
| 249345 | 249812 | static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ |
| 249346 | 249813 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249347 | 249814 | assert( pIter->pIndex->rc==SQLITE_OK ); |
| 249348 | - if( pIter->pTokenDataIter ){ | |
| 249815 | + if( pIter->nSeg==0 ){ | |
| 249816 | + assert( pIter->pTokenDataIter ); | |
| 249349 | 249817 | fts5TokendataIterNext(pIter, 0, 0); |
| 249350 | 249818 | }else{ |
| 249351 | 249819 | fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); |
| 249352 | 249820 | } |
| 249353 | 249821 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249380,11 +249848,12 @@ | ||
| 249380 | 249848 | ** definition of "at or after" depends on whether this iterator iterates |
| 249381 | 249849 | ** in ascending or descending rowid order. |
| 249382 | 249850 | */ |
| 249383 | 249851 | static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ |
| 249384 | 249852 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249385 | - if( pIter->pTokenDataIter ){ | |
| 249853 | + if( pIter->nSeg==0 ){ | |
| 249854 | + assert( pIter->pTokenDataIter ); | |
| 249386 | 249855 | fts5TokendataIterNext(pIter, 1, iMatch); |
| 249387 | 249856 | }else{ |
| 249388 | 249857 | fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); |
| 249389 | 249858 | } |
| 249390 | 249859 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249398,32 +249867,88 @@ | ||
| 249398 | 249867 | const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); |
| 249399 | 249868 | assert_nc( z || n<=1 ); |
| 249400 | 249869 | *pn = n-1; |
| 249401 | 249870 | return (z ? &z[1] : 0); |
| 249402 | 249871 | } |
| 249872 | + | |
| 249873 | +/* | |
| 249874 | +** pIter is a prefix query. This function populates pIter->pTokenDataIter | |
| 249875 | +** with an Fts5TokenDataIter object containing mappings for all rows | |
| 249876 | +** matched by the query. | |
| 249877 | +*/ | |
| 249878 | +static int fts5SetupPrefixIterTokendata( | |
| 249879 | + Fts5Iter *pIter, | |
| 249880 | + const char *pToken, /* Token prefix to search for */ | |
| 249881 | + int nToken /* Size of pToken in bytes */ | |
| 249882 | +){ | |
| 249883 | + Fts5Index *p = pIter->pIndex; | |
| 249884 | + Fts5Buffer token = {0, 0, 0}; | |
| 249885 | + TokendataSetupCtx ctx; | |
| 249886 | + | |
| 249887 | + memset(&ctx, 0, sizeof(ctx)); | |
| 249888 | + | |
| 249889 | + fts5BufferGrow(&p->rc, &token, nToken+1); | |
| 249890 | + assert( token.p!=0 || p->rc!=SQLITE_OK ); | |
| 249891 | + ctx.pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*ctx.pT)); | |
| 249892 | + | |
| 249893 | + if( p->rc==SQLITE_OK ){ | |
| 249894 | + | |
| 249895 | + /* Fill in the token prefix to search for */ | |
| 249896 | + token.p[0] = FTS5_MAIN_PREFIX; | |
| 249897 | + memcpy(&token.p[1], pToken, nToken); | |
| 249898 | + token.n = nToken+1; | |
| 249899 | + | |
| 249900 | + fts5VisitEntries( | |
| 249901 | + p, 0, token.p, token.n, 1, prefixIterSetupTokendataCb, (void*)&ctx | |
| 249902 | + ); | |
| 249903 | + | |
| 249904 | + fts5TokendataIterSortMap(p, ctx.pT); | |
| 249905 | + } | |
| 249906 | + | |
| 249907 | + if( p->rc==SQLITE_OK ){ | |
| 249908 | + pIter->pTokenDataIter = ctx.pT; | |
| 249909 | + }else{ | |
| 249910 | + fts5TokendataIterDelete(ctx.pT); | |
| 249911 | + } | |
| 249912 | + fts5BufferFree(&token); | |
| 249913 | + | |
| 249914 | + return fts5IndexReturn(p); | |
| 249915 | +} | |
| 249403 | 249916 | |
| 249404 | 249917 | /* |
| 249405 | 249918 | ** This is used by xInstToken() to access the token at offset iOff, column |
| 249406 | 249919 | ** iCol of row iRowid. The token is returned via output variables *ppOut |
| 249407 | 249920 | ** and *pnOut. The iterator passed as the first argument must be a tokendata=1 |
| 249408 | 249921 | ** iterator (pIter->pTokenDataIter!=0). |
| 249922 | +** | |
| 249923 | +** pToken/nToken: | |
| 249409 | 249924 | */ |
| 249410 | 249925 | static int sqlite3Fts5IterToken( |
| 249411 | 249926 | Fts5IndexIter *pIndexIter, |
| 249927 | + const char *pToken, int nToken, | |
| 249412 | 249928 | i64 iRowid, |
| 249413 | 249929 | int iCol, |
| 249414 | 249930 | int iOff, |
| 249415 | 249931 | const char **ppOut, int *pnOut |
| 249416 | 249932 | ){ |
| 249417 | 249933 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249418 | 249934 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 249419 | - Fts5TokenDataMap *aMap = pT->aMap; | |
| 249420 | 249935 | i64 iPos = (((i64)iCol)<<32) + iOff; |
| 249421 | - | |
| 249936 | + Fts5TokenDataMap *aMap = 0; | |
| 249422 | 249937 | int i1 = 0; |
| 249423 | - int i2 = pT->nMap; | |
| 249938 | + int i2 = 0; | |
| 249424 | 249939 | int iTest = 0; |
| 249940 | + | |
| 249941 | + assert( pT || (pToken && pIter->nSeg>0) ); | |
| 249942 | + if( pT==0 ){ | |
| 249943 | + int rc = fts5SetupPrefixIterTokendata(pIter, pToken, nToken); | |
| 249944 | + if( rc!=SQLITE_OK ) return rc; | |
| 249945 | + pT = pIter->pTokenDataIter; | |
| 249946 | + } | |
| 249947 | + | |
| 249948 | + i2 = pT->nMap; | |
| 249949 | + aMap = pT->aMap; | |
| 249425 | 249950 | |
| 249426 | 249951 | while( i2>i1 ){ |
| 249427 | 249952 | iTest = (i1 + i2) / 2; |
| 249428 | 249953 | |
| 249429 | 249954 | if( aMap[iTest].iRowid<iRowid ){ |
| @@ -249443,13 +249968,19 @@ | ||
| 249443 | 249968 | } |
| 249444 | 249969 | } |
| 249445 | 249970 | } |
| 249446 | 249971 | |
| 249447 | 249972 | if( i2>i1 ){ |
| 249448 | - Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; | |
| 249449 | - *ppOut = (const char*)pMap->aSeg[0].term.p+1; | |
| 249450 | - *pnOut = pMap->aSeg[0].term.n-1; | |
| 249973 | + if( pIter->nSeg==0 ){ | |
| 249974 | + Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; | |
| 249975 | + *ppOut = (const char*)pMap->aSeg[0].term.p+1; | |
| 249976 | + *pnOut = pMap->aSeg[0].term.n-1; | |
| 249977 | + }else{ | |
| 249978 | + Fts5TokenDataMap *p = &aMap[iTest]; | |
| 249979 | + *ppOut = (const char*)&pT->terms.p[p->iIter]; | |
| 249980 | + *pnOut = aMap[iTest].nByte; | |
| 249981 | + } | |
| 249451 | 249982 | } |
| 249452 | 249983 | |
| 249453 | 249984 | return SQLITE_OK; |
| 249454 | 249985 | } |
| 249455 | 249986 | |
| @@ -249457,11 +249988,13 @@ | ||
| 249457 | 249988 | ** Clear any existing entries from the token-map associated with the |
| 249458 | 249989 | ** iterator passed as the only argument. |
| 249459 | 249990 | */ |
| 249460 | 249991 | static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ |
| 249461 | 249992 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249462 | - if( pIter && pIter->pTokenDataIter ){ | |
| 249993 | + if( pIter && pIter->pTokenDataIter | |
| 249994 | + && (pIter->nSeg==0 || pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_FULL) | |
| 249995 | + ){ | |
| 249463 | 249996 | pIter->pTokenDataIter->nMap = 0; |
| 249464 | 249997 | } |
| 249465 | 249998 | } |
| 249466 | 249999 | |
| 249467 | 250000 | /* |
| @@ -249477,21 +250010,33 @@ | ||
| 249477 | 250010 | i64 iRowid, int iCol, int iOff |
| 249478 | 250011 | ){ |
| 249479 | 250012 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249480 | 250013 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 249481 | 250014 | Fts5Index *p = pIter->pIndex; |
| 249482 | - int ii; | |
| 250015 | + i64 iPos = (((i64)iCol)<<32) + iOff; | |
| 249483 | 250016 | |
| 249484 | 250017 | assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); |
| 249485 | - assert( pIter->pTokenDataIter ); | |
| 249486 | - | |
| 249487 | - for(ii=0; ii<pT->nIter; ii++){ | |
| 249488 | - Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; | |
| 249489 | - if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; | |
| 249490 | - } | |
| 249491 | - if( ii<pT->nIter ){ | |
| 249492 | - fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff); | |
| 250018 | + assert( pIter->pTokenDataIter || pIter->nSeg>0 ); | |
| 250019 | + if( pIter->nSeg>0 ){ | |
| 250020 | + /* This is a prefix term iterator. */ | |
| 250021 | + if( pT==0 ){ | |
| 250022 | + pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*pT)); | |
| 250023 | + pIter->pTokenDataIter = pT; | |
| 250024 | + } | |
| 250025 | + if( pT ){ | |
| 250026 | + fts5TokendataIterAppendMap(p, pT, pT->terms.n, nToken, iRowid, iPos); | |
| 250027 | + fts5BufferAppendBlob(&p->rc, &pT->terms, nToken, (const u8*)pToken); | |
| 250028 | + } | |
| 250029 | + }else{ | |
| 250030 | + int ii; | |
| 250031 | + for(ii=0; ii<pT->nIter; ii++){ | |
| 250032 | + Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; | |
| 250033 | + if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; | |
| 250034 | + } | |
| 250035 | + if( ii<pT->nIter ){ | |
| 250036 | + fts5TokendataIterAppendMap(p, pT, ii, 0, iRowid, iPos); | |
| 250037 | + } | |
| 249493 | 250038 | } |
| 249494 | 250039 | return fts5IndexReturn(p); |
| 249495 | 250040 | } |
| 249496 | 250041 | |
| 249497 | 250042 | /* |
| @@ -251392,10 +251937,11 @@ | ||
| 251392 | 251937 | ** containing a copy of the header from an Fts5Config pointer. |
| 251393 | 251938 | */ |
| 251394 | 251939 | #define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr )) |
| 251395 | 251940 | #define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr)) |
| 251396 | 251941 | |
| 251942 | +#define FTS5_INSTTOKEN_SUBTYPE 73 | |
| 251397 | 251943 | |
| 251398 | 251944 | /* |
| 251399 | 251945 | ** Each auxiliary function registered with the FTS5 module is represented |
| 251400 | 251946 | ** by an object of the following type. All such objects are stored as part |
| 251401 | 251947 | ** of the Fts5Global.pAux list. |
| @@ -251931,10 +252477,11 @@ | ||
| 251931 | 252477 | ){ |
| 251932 | 252478 | /* A MATCH operator or equivalent */ |
| 251933 | 252479 | if( p->usable==0 || iCol<0 ){ |
| 251934 | 252480 | /* As there exists an unusable MATCH constraint this is an |
| 251935 | 252481 | ** unusable plan. Return SQLITE_CONSTRAINT. */ |
| 252482 | + idxStr[iIdxStr] = 0; | |
| 251936 | 252483 | return SQLITE_CONSTRAINT; |
| 251937 | 252484 | }else{ |
| 251938 | 252485 | if( iCol==nCol+1 ){ |
| 251939 | 252486 | if( bSeenRank ) continue; |
| 251940 | 252487 | idxStr[iIdxStr++] = 'r'; |
| @@ -252716,10 +253263,11 @@ | ||
| 252716 | 253263 | sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ |
| 252717 | 253264 | sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ |
| 252718 | 253265 | sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ |
| 252719 | 253266 | int iCol; /* Column on LHS of MATCH operator */ |
| 252720 | 253267 | char **pzErrmsg = pConfig->pzErrmsg; |
| 253268 | + int bPrefixInsttoken = pConfig->bPrefixInsttoken; | |
| 252721 | 253269 | int i; |
| 252722 | 253270 | int iIdxStr = 0; |
| 252723 | 253271 | Fts5Expr *pExpr = 0; |
| 252724 | 253272 | |
| 252725 | 253273 | assert( pConfig->bLock==0 ); |
| @@ -252751,10 +253299,13 @@ | ||
| 252751 | 253299 | int bInternal = 0; |
| 252752 | 253300 | |
| 252753 | 253301 | rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset); |
| 252754 | 253302 | if( rc!=SQLITE_OK ) goto filter_out; |
| 252755 | 253303 | if( zText==0 ) zText = ""; |
| 253304 | + if( sqlite3_value_subtype(apVal[i])==FTS5_INSTTOKEN_SUBTYPE ){ | |
| 253305 | + pConfig->bPrefixInsttoken = 1; | |
| 253306 | + } | |
| 252756 | 253307 | |
| 252757 | 253308 | iCol = 0; |
| 252758 | 253309 | do{ |
| 252759 | 253310 | iCol = iCol*10 + (idxStr[iIdxStr]-'0'); |
| 252760 | 253311 | iIdxStr++; |
| @@ -252891,10 +253442,11 @@ | ||
| 252891 | 253442 | } |
| 252892 | 253443 | |
| 252893 | 253444 | filter_out: |
| 252894 | 253445 | sqlite3Fts5ExprFree(pExpr); |
| 252895 | 253446 | pConfig->pzErrmsg = pzErrmsg; |
| 253447 | + pConfig->bPrefixInsttoken = bPrefixInsttoken; | |
| 252896 | 253448 | return rc; |
| 252897 | 253449 | } |
| 252898 | 253450 | |
| 252899 | 253451 | /* |
| 252900 | 253452 | ** This is the xEof method of the virtual table. SQLite calls this |
| @@ -254886,11 +255438,11 @@ | ||
| 254886 | 255438 | int nArg, /* Number of args */ |
| 254887 | 255439 | sqlite3_value **apUnused /* Function arguments */ |
| 254888 | 255440 | ){ |
| 254889 | 255441 | assert( nArg==0 ); |
| 254890 | 255442 | UNUSED_PARAM2(nArg, apUnused); |
| 254891 | - sqlite3_result_text(pCtx, "fts5: 2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e", -1, SQLITE_TRANSIENT); | |
| 255443 | + sqlite3_result_text(pCtx, "fts5: 2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552", -1, SQLITE_TRANSIENT); | |
| 254892 | 255444 | } |
| 254893 | 255445 | |
| 254894 | 255446 | /* |
| 254895 | 255447 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 254896 | 255448 | ** |
| @@ -254949,10 +255501,24 @@ | ||
| 254949 | 255501 | assert( &pCsr[nText]==&pBlob[nBlob] ); |
| 254950 | 255502 | |
| 254951 | 255503 | sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free); |
| 254952 | 255504 | } |
| 254953 | 255505 | } |
| 255506 | + | |
| 255507 | +/* | |
| 255508 | +** Implementation of fts5_insttoken() function. | |
| 255509 | +*/ | |
| 255510 | +static void fts5InsttokenFunc( | |
| 255511 | + sqlite3_context *pCtx, /* Function call context */ | |
| 255512 | + int nArg, /* Number of args */ | |
| 255513 | + sqlite3_value **apArg /* Function arguments */ | |
| 255514 | +){ | |
| 255515 | + assert( nArg==1 ); | |
| 255516 | + (void)nArg; | |
| 255517 | + sqlite3_result_value(pCtx, apArg[0]); | |
| 255518 | + sqlite3_result_subtype(pCtx, FTS5_INSTTOKEN_SUBTYPE); | |
| 255519 | +} | |
| 254954 | 255520 | |
| 254955 | 255521 | /* |
| 254956 | 255522 | ** Return true if zName is the extension on one of the shadow tables used |
| 254957 | 255523 | ** by this module. |
| 254958 | 255524 | */ |
| @@ -255079,13 +255645,20 @@ | ||
| 255079 | 255645 | ); |
| 255080 | 255646 | } |
| 255081 | 255647 | if( rc==SQLITE_OK ){ |
| 255082 | 255648 | rc = sqlite3_create_function( |
| 255083 | 255649 | db, "fts5_locale", 2, |
| 255084 | - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, | |
| 255650 | + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE|SQLITE_SUBTYPE, | |
| 255085 | 255651 | p, fts5LocaleFunc, 0, 0 |
| 255086 | 255652 | ); |
| 255653 | + } | |
| 255654 | + if( rc==SQLITE_OK ){ | |
| 255655 | + rc = sqlite3_create_function( | |
| 255656 | + db, "fts5_insttoken", 1, | |
| 255657 | + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, | |
| 255658 | + p, fts5InsttokenFunc, 0, 0 | |
| 255659 | + ); | |
| 255087 | 255660 | } |
| 255088 | 255661 | } |
| 255089 | 255662 | |
| 255090 | 255663 | /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file |
| 255091 | 255664 | ** fts5_test_mi.c is compiled and linked into the executable. And call |
| @@ -258007,22 +258580,22 @@ | ||
| 258007 | 258580 | int rc = SQLITE_OK; |
| 258008 | 258581 | char aBuf[32]; |
| 258009 | 258582 | char *zOut = aBuf; |
| 258010 | 258583 | int ii; |
| 258011 | 258584 | const unsigned char *zIn = (const unsigned char*)pText; |
| 258012 | - const unsigned char *zEof = &zIn[nText]; | |
| 258013 | - u32 iCode; | |
| 258585 | + const unsigned char *zEof = (zIn ? &zIn[nText] : 0); | |
| 258586 | + u32 iCode = 0; | |
| 258014 | 258587 | int aStart[3]; /* Input offset of each character in aBuf[] */ |
| 258015 | 258588 | |
| 258016 | 258589 | UNUSED_PARAM(unusedFlags); |
| 258017 | 258590 | |
| 258018 | 258591 | /* Populate aBuf[] with the characters for the first trigram. */ |
| 258019 | 258592 | for(ii=0; ii<3; ii++){ |
| 258020 | 258593 | do { |
| 258021 | 258594 | aStart[ii] = zIn - (const unsigned char*)pText; |
| 258595 | + if( zIn>=zEof ) return SQLITE_OK; | |
| 258022 | 258596 | READ_UTF8(zIn, zEof, iCode); |
| 258023 | - if( iCode==0 ) return SQLITE_OK; | |
| 258024 | 258597 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258025 | 258598 | }while( iCode==0 ); |
| 258026 | 258599 | WRITE_UTF8(zOut, iCode); |
| 258027 | 258600 | } |
| 258028 | 258601 | |
| @@ -258039,12 +258612,15 @@ | ||
| 258039 | 258612 | const char *z1; |
| 258040 | 258613 | |
| 258041 | 258614 | /* Read characters from the input up until the first non-diacritic */ |
| 258042 | 258615 | do { |
| 258043 | 258616 | iNext = zIn - (const unsigned char*)pText; |
| 258617 | + if( zIn>=zEof ){ | |
| 258618 | + iCode = 0; | |
| 258619 | + break; | |
| 258620 | + } | |
| 258044 | 258621 | READ_UTF8(zIn, zEof, iCode); |
| 258045 | - if( iCode==0 ) break; | |
| 258046 | 258622 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258047 | 258623 | }while( iCode==0 ); |
| 258048 | 258624 | |
| 258049 | 258625 | /* Pass the current trigram back to fts5 */ |
| 258050 | 258626 | rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); |
| @@ -260077,11 +260653,11 @@ | ||
| 260077 | 260653 | |
| 260078 | 260654 | return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0); |
| 260079 | 260655 | } |
| 260080 | 260656 | |
| 260081 | 260657 | |
| 260082 | - | |
| 260658 | +/* Here ends the fts5.c composite file. */ | |
| 260083 | 260659 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ |
| 260084 | 260660 | |
| 260085 | 260661 | /************** End of fts5.c ************************************************/ |
| 260086 | 260662 | /************** Begin file stmt.c ********************************************/ |
| 260087 | 260663 | /* |
| @@ -260433,6 +261009,7 @@ | ||
| 260433 | 261009 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ |
| 260434 | 261010 | |
| 260435 | 261011 | /************** End of stmt.c ************************************************/ |
| 260436 | 261012 | /* Return the source-id for this library */ |
| 260437 | 261013 | SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } |
| 261014 | +#endif /* SQLITE_AMALGAMATION */ | |
| 260438 | 261015 | /************************** End of sqlite3.c ******************************/ |
| 260439 | 261016 |
| --- extsrc/sqlite3.c | |
| +++ extsrc/sqlite3.c | |
| @@ -1,8 +1,8 @@ | |
| 1 | /****************************************************************************** |
| 2 | ** This file is an amalgamation of many separate C source files from SQLite |
| 3 | ** version 3.47.0. By combining all the individual C code files into this |
| 4 | ** single large file, the entire code can be compiled as a single translation |
| 5 | ** unit. This allows many compilers to do optimizations that would not be |
| 6 | ** possible if the files were compiled separately. Performance improvements |
| 7 | ** of 5% or more are commonly seen when SQLite is compiled as a single |
| 8 | ** translation unit. |
| @@ -16,12 +16,15 @@ | |
| 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | ** |
| 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | ** 03a9703e27c44437c39363d0baf82db4ebc9. |
| 22 | */ |
| 23 | #define SQLITE_CORE 1 |
| 24 | #define SQLITE_AMALGAMATION 1 |
| 25 | #ifndef SQLITE_PRIVATE |
| 26 | # define SQLITE_PRIVATE static |
| 27 | #endif |
| @@ -460,13 +463,13 @@ | |
| 460 | ** |
| 461 | ** See also: [sqlite3_libversion()], |
| 462 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 463 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 464 | */ |
| 465 | #define SQLITE_VERSION "3.47.0" |
| 466 | #define SQLITE_VERSION_NUMBER 3047000 |
| 467 | #define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e" |
| 468 | |
| 469 | /* |
| 470 | ** CAPI3REF: Run-Time Library Version Numbers |
| 471 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 472 | ** |
| @@ -966,10 +969,17 @@ | |
| 966 | ** |
| 967 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 968 | ** filesystem supports doing multiple write operations atomically when those |
| 969 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 970 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 971 | */ |
| 972 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 973 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 974 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 975 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -982,10 +992,11 @@ | |
| 982 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 983 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 984 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 985 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 986 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 987 | |
| 988 | /* |
| 989 | ** CAPI3REF: File Locking Levels |
| 990 | ** |
| 991 | ** SQLite uses one of these integer values as the second |
| @@ -1128,10 +1139,11 @@ | |
| 1128 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 1129 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 1130 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 1131 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 1132 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 1133 | ** </ul> |
| 1134 | ** |
| 1135 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 1136 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 1137 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1405,10 +1417,15 @@ | |
| 1405 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1406 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1407 | ** pointed to by the pArg argument. This capability is used during testing |
| 1408 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1409 | ** |
| 1410 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1411 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1412 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1413 | ** available. The WAL subsystem issues this signal during rare |
| 1414 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1558,10 +1575,11 @@ | |
| 1558 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1559 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1560 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1561 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1562 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1563 | |
| 1564 | /* deprecated names */ |
| 1565 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1566 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1567 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2936,14 +2954,18 @@ | |
| 2936 | ** |
| 2937 | ** ^These functions return the number of rows modified, inserted or |
| 2938 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2939 | ** statement on the database connection specified by the only parameter. |
| 2940 | ** The two functions are identical except for the type of the return value |
| 2941 | ** and that if the number of rows modified by the most recent INSERT, UPDATE |
| 2942 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2943 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2944 | ** type of SQL statement does not modify the value returned by these functions. |
| 2945 | ** |
| 2946 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2947 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2948 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2949 | ** |
| @@ -4499,15 +4521,26 @@ | |
| 4499 | ** |
| 4500 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4501 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4502 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4503 | ** any virtual tables. |
| 4504 | ** </dl> |
| 4505 | */ |
| 4506 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4507 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4508 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4509 | |
| 4510 | /* |
| 4511 | ** CAPI3REF: Compiling An SQL Statement |
| 4512 | ** KEYWORDS: {SQL statement compiler} |
| 4513 | ** METHOD: sqlite3 |
| @@ -11194,11 +11227,11 @@ | |
| 11194 | #endif |
| 11195 | |
| 11196 | #if 0 |
| 11197 | } /* End of the 'extern "C"' block */ |
| 11198 | #endif |
| 11199 | #endif /* SQLITE3_H */ |
| 11200 | |
| 11201 | /******** Begin file sqlite3rtree.h *********/ |
| 11202 | /* |
| 11203 | ** 2010 August 30 |
| 11204 | ** |
| @@ -13445,17 +13478,32 @@ | |
| 13445 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13446 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13447 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13448 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13449 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13450 | ** bytes. This API is not available if the specified token matches a |
| 13451 | ** prefix query term. In that case both output variables are always set |
| 13452 | ** to 0. |
| 13453 | ** |
| 13454 | ** The output text is not a copy of the document text that was tokenized. |
| 13455 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13456 | ** includes any embedded 0x00 and trailing data. |
| 13457 | ** |
| 13458 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13459 | ** "detail=none" or "detail=column" option. |
| 13460 | ** |
| 13461 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13886,10 +13934,11 @@ | |
| 13886 | #endif |
| 13887 | |
| 13888 | #endif /* _FTS5_H */ |
| 13889 | |
| 13890 | /******** End of fts5.h *********/ |
| 13891 | |
| 13892 | /************** End of sqlite3.h *********************************************/ |
| 13893 | /************** Continuing where we left off in sqliteInt.h ******************/ |
| 13894 | |
| 13895 | /* |
| @@ -13931,10 +13980,11 @@ | |
| 13931 | ** to count the size: 2^31-1 or 2147483647. |
| 13932 | */ |
| 13933 | #ifndef SQLITE_MAX_LENGTH |
| 13934 | # define SQLITE_MAX_LENGTH 1000000000 |
| 13935 | #endif |
| 13936 | |
| 13937 | /* |
| 13938 | ** This is the maximum number of |
| 13939 | ** |
| 13940 | ** * Columns in a table |
| @@ -13996,13 +14046,17 @@ | |
| 13996 | # define SQLITE_MAX_VDBE_OP 250000000 |
| 13997 | #endif |
| 13998 | |
| 13999 | /* |
| 14000 | ** The maximum number of arguments to an SQL function. |
| 14001 | */ |
| 14002 | #ifndef SQLITE_MAX_FUNCTION_ARG |
| 14003 | # define SQLITE_MAX_FUNCTION_ARG 127 |
| 14004 | #endif |
| 14005 | |
| 14006 | /* |
| 14007 | ** The suggested maximum number of in-memory pages to use for |
| 14008 | ** the main database table and for temporary tables. |
| @@ -16000,10 +16054,26 @@ | |
| 16000 | #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ |
| 16001 | #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ |
| 16002 | #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ |
| 16003 | #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ |
| 16004 | |
| 16005 | /* |
| 16006 | ** Flags that make up the mask passed to sqlite3PagerGet(). |
| 16007 | */ |
| 16008 | #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ |
| 16009 | #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ |
| @@ -17025,11 +17095,11 @@ | |
| 17025 | |
| 17026 | /* |
| 17027 | ** Additional non-public SQLITE_PREPARE_* flags |
| 17028 | */ |
| 17029 | #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ |
| 17030 | #define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ |
| 17031 | |
| 17032 | /* |
| 17033 | ** Prototypes for the VDBE interface. See comments on the implementation |
| 17034 | ** for a description of what each of these routines does. |
| 17035 | */ |
| @@ -17740,51 +17810,15 @@ | |
| 17740 | struct FuncDefHash { |
| 17741 | FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */ |
| 17742 | }; |
| 17743 | #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) |
| 17744 | |
| 17745 | #if defined(SQLITE_USER_AUTHENTICATION) |
| 17746 | # warning "The SQLITE_USER_AUTHENTICATION extension is deprecated. \ |
| 17747 | See ext/userauth/user-auth.txt for details." |
| 17748 | #endif |
| 17749 | #ifdef SQLITE_USER_AUTHENTICATION |
| 17750 | /* |
| 17751 | ** Information held in the "sqlite3" database connection object and used |
| 17752 | ** to manage user authentication. |
| 17753 | */ |
| 17754 | typedef struct sqlite3_userauth sqlite3_userauth; |
| 17755 | struct sqlite3_userauth { |
| 17756 | u8 authLevel; /* Current authentication level */ |
| 17757 | int nAuthPW; /* Size of the zAuthPW in bytes */ |
| 17758 | char *zAuthPW; /* Password used to authenticate */ |
| 17759 | char *zAuthUser; /* User name used to authenticate */ |
| 17760 | }; |
| 17761 | |
| 17762 | /* Allowed values for sqlite3_userauth.authLevel */ |
| 17763 | #define UAUTH_Unknown 0 /* Authentication not yet checked */ |
| 17764 | #define UAUTH_Fail 1 /* User authentication failed */ |
| 17765 | #define UAUTH_User 2 /* Authenticated as a normal user */ |
| 17766 | #define UAUTH_Admin 3 /* Authenticated as an administrator */ |
| 17767 | |
| 17768 | /* Functions used only by user authorization logic */ |
| 17769 | SQLITE_PRIVATE int sqlite3UserAuthTable(const char*); |
| 17770 | SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*); |
| 17771 | SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*); |
| 17772 | SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); |
| 17773 | |
| 17774 | #endif /* SQLITE_USER_AUTHENTICATION */ |
| 17775 | |
| 17776 | /* |
| 17777 | ** typedef for the authorization callback function. |
| 17778 | */ |
| 17779 | #ifdef SQLITE_USER_AUTHENTICATION |
| 17780 | typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, |
| 17781 | const char*, const char*); |
| 17782 | #else |
| 17783 | typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, |
| 17784 | const char*); |
| 17785 | #endif |
| 17786 | |
| 17787 | #ifndef SQLITE_OMIT_DEPRECATED |
| 17788 | /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing |
| 17789 | ** in the style of sqlite3_trace() |
| 17790 | */ |
| @@ -17941,13 +17975,10 @@ | |
| 17941 | sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ |
| 17942 | void *pUnlockArg; /* Argument to xUnlockNotify */ |
| 17943 | void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ |
| 17944 | sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ |
| 17945 | #endif |
| 17946 | #ifdef SQLITE_USER_AUTHENTICATION |
| 17947 | sqlite3_userauth auth; /* User authentication information */ |
| 17948 | #endif |
| 17949 | }; |
| 17950 | |
| 17951 | /* |
| 17952 | ** A macro to discover the encoding of a database. |
| 17953 | */ |
| @@ -18102,11 +18133,11 @@ | |
| 18102 | ** |
| 18103 | ** The u.pHash field is used by the global built-ins. The u.pDestructor |
| 18104 | ** field is used by per-connection app-def functions. |
| 18105 | */ |
| 18106 | struct FuncDef { |
| 18107 | i8 nArg; /* Number of arguments. -1 means unlimited */ |
| 18108 | u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ |
| 18109 | void *pUserData; /* User data parameter */ |
| 18110 | FuncDef *pNext; /* Next function with same name */ |
| 18111 | void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ |
| 18112 | void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ |
| @@ -22850,13 +22881,10 @@ | |
| 22850 | "UNLINK_AFTER_CLOSE", |
| 22851 | #endif |
| 22852 | #ifdef SQLITE_UNTESTABLE |
| 22853 | "UNTESTABLE", |
| 22854 | #endif |
| 22855 | #ifdef SQLITE_USER_AUTHENTICATION |
| 22856 | "USER_AUTHENTICATION", |
| 22857 | #endif |
| 22858 | #ifdef SQLITE_USE_ALLOCA |
| 22859 | "USE_ALLOCA", |
| 22860 | #endif |
| 22861 | #ifdef SQLITE_USE_FCNTL_TRACE |
| 22862 | "USE_FCNTL_TRACE", |
| @@ -23700,11 +23728,11 @@ | |
| 23700 | Vdbe *pVdbe; /* The VM that owns this context */ |
| 23701 | int iOp; /* Instruction number of OP_Function */ |
| 23702 | int isError; /* Error code returned by the function. */ |
| 23703 | u8 enc; /* Encoding to use for results */ |
| 23704 | u8 skipFlag; /* Skip accumulator loading if true */ |
| 23705 | u8 argc; /* Number of arguments */ |
| 23706 | sqlite3_value *argv[1]; /* Argument set */ |
| 23707 | }; |
| 23708 | |
| 23709 | /* A bitfield type for use inside of structures. Always follow with :N where |
| 23710 | ** N is the number of bits. |
| @@ -23847,10 +23875,11 @@ | |
| 23847 | UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ |
| 23848 | int iNewReg; /* Register for new.* values */ |
| 23849 | int iBlobWrite; /* Value returned by preupdate_blobwrite() */ |
| 23850 | i64 iKey1; /* First key value passed to hook */ |
| 23851 | i64 iKey2; /* Second key value passed to hook */ |
| 23852 | Mem *aNew; /* Array of new.* values */ |
| 23853 | Table *pTab; /* Schema object being updated */ |
| 23854 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 23855 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 23856 | }; |
| @@ -32296,10 +32325,11 @@ | |
| 32296 | && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) |
| 32297 | ){ |
| 32298 | pExpr = pExpr->pLeft; |
| 32299 | } |
| 32300 | if( pExpr==0 ) return; |
| 32301 | db->errByteOffset = pExpr->w.iOfst; |
| 32302 | } |
| 32303 | |
| 32304 | /* |
| 32305 | ** Enlarge the memory allocation on a StrAccum object so that it is |
| @@ -33025,11 +33055,11 @@ | |
| 33025 | } |
| 33026 | if( pItem->fg.isCte ){ |
| 33027 | sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); |
| 33028 | } |
| 33029 | if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ |
| 33030 | sqlite3_str_appendf(&x, " ON"); |
| 33031 | } |
| 33032 | if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); |
| 33033 | if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); |
| 33034 | if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); |
| 33035 | if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); |
| @@ -34109,10 +34139,14 @@ | |
| 34109 | ** |
| 34110 | ** This routines are given external linkage so that they will always be |
| 34111 | ** accessible to the debugging, and to avoid warnings about unused |
| 34112 | ** functions. But these routines only exist in debugging builds, so they |
| 34113 | ** do not contaminate the interface. |
| 34114 | */ |
| 34115 | SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } |
| 34116 | SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} |
| 34117 | SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } |
| 34118 | SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } |
| @@ -35685,12 +35719,12 @@ | |
| 35685 | int esign = 1; /* sign of exponent */ |
| 35686 | int e = 0; /* exponent */ |
| 35687 | int eValid = 1; /* True exponent is either not used or is well-formed */ |
| 35688 | int nDigit = 0; /* Number of digits processed */ |
| 35689 | int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ |
| 35690 | double rr[2]; |
| 35691 | u64 s2; |
| 35692 | |
| 35693 | assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); |
| 35694 | *pResult = 0.0; /* Default return value, in case of an error */ |
| 35695 | if( length==0 ) return 0; |
| 35696 | |
| @@ -35789,25 +35823,36 @@ | |
| 35789 | |
| 35790 | /* adjust exponent by d, and update sign */ |
| 35791 | e = (e*esign) + d; |
| 35792 | |
| 35793 | /* Try to adjust the exponent to make it smaller */ |
| 35794 | while( e>0 && s<(LARGEST_UINT64/10) ){ |
| 35795 | s *= 10; |
| 35796 | e--; |
| 35797 | } |
| 35798 | while( e<0 && (s%10)==0 ){ |
| 35799 | s /= 10; |
| 35800 | e++; |
| 35801 | } |
| 35802 | |
| 35803 | rr[0] = (double)s; |
| 35804 | s2 = (u64)rr[0]; |
| 35805 | #if defined(_MSC_VER) && _MSC_VER<1700 |
| 35806 | if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); } |
| 35807 | #endif |
| 35808 | rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); |
| 35809 | if( e>0 ){ |
| 35810 | while( e>=100 ){ |
| 35811 | e -= 100; |
| 35812 | dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); |
| 35813 | } |
| @@ -38674,11 +38719,11 @@ | |
| 38674 | # define F_SETLKW 7 |
| 38675 | # endif |
| 38676 | # endif |
| 38677 | #else /* !SQLITE_WASI */ |
| 38678 | # ifndef HAVE_FCHMOD |
| 38679 | # define HAVE_FCHMOD |
| 38680 | # endif |
| 38681 | #endif /* SQLITE_WASI */ |
| 38682 | |
| 38683 | #ifdef SQLITE_WASI |
| 38684 | # define osGetpid(X) (pid_t)1 |
| @@ -42448,10 +42493,15 @@ | |
| 42448 | int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE); |
| 42449 | return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK; |
| 42450 | } |
| 42451 | #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ |
| 42452 | |
| 42453 | case SQLITE_FCNTL_LOCKSTATE: { |
| 42454 | *(int*)pArg = pFile->eFileLock; |
| 42455 | return SQLITE_OK; |
| 42456 | } |
| 42457 | case SQLITE_FCNTL_LAST_ERRNO: { |
| @@ -42589,10 +42639,11 @@ | |
| 42589 | |
| 42590 | /* Set the POWERSAFE_OVERWRITE flag if requested. */ |
| 42591 | if( pFd->ctrlFlags & UNIXFILE_PSOW ){ |
| 42592 | pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; |
| 42593 | } |
| 42594 | |
| 42595 | pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; |
| 42596 | } |
| 42597 | } |
| 42598 | #else |
| @@ -50328,10 +50379,15 @@ | |
| 50328 | OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n", |
| 50329 | hOldFile, pFile->h)); |
| 50330 | return SQLITE_OK; |
| 50331 | } |
| 50332 | #endif |
| 50333 | case SQLITE_FCNTL_TEMPFILENAME: { |
| 50334 | char *zTFile = 0; |
| 50335 | int rc = winGetTempname(pFile->pVfs, &zTFile); |
| 50336 | if( rc==SQLITE_OK ){ |
| 50337 | *(char**)pArg = zTFile; |
| @@ -50389,11 +50445,11 @@ | |
| 50389 | /* |
| 50390 | ** Return a vector of device characteristics. |
| 50391 | */ |
| 50392 | static int winDeviceCharacteristics(sqlite3_file *id){ |
| 50393 | winFile *p = (winFile*)id; |
| 50394 | return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | |
| 50395 | ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); |
| 50396 | } |
| 50397 | |
| 50398 | /* |
| 50399 | ** Windows will only let you create file view mappings |
| @@ -51777,11 +51833,11 @@ | |
| 51777 | */ |
| 51778 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| 51779 | |
| 51780 | int rc = SQLITE_OK; /* Function Return Code */ |
| 51781 | #if !defined(NDEBUG) || SQLITE_OS_WINCE |
| 51782 | int eType = flags&0xFFFFFF00; /* Type of file to open */ |
| 51783 | #endif |
| 51784 | |
| 51785 | int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); |
| 51786 | int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); |
| 51787 | int isCreate = (flags & SQLITE_OPEN_CREATE); |
| @@ -57978,43 +58034,37 @@ | |
| 57978 | # define USEFETCH(x) ((x)->bUseFetch) |
| 57979 | #else |
| 57980 | # define USEFETCH(x) 0 |
| 57981 | #endif |
| 57982 | |
| 57983 | /* |
| 57984 | ** The argument to this macro is a file descriptor (type sqlite3_file*). |
| 57985 | ** Return 0 if it is not open, or non-zero (but not 1) if it is. |
| 57986 | ** |
| 57987 | ** This is so that expressions can be written as: |
| 57988 | ** |
| 57989 | ** if( isOpen(pPager->jfd) ){ ... |
| 57990 | ** |
| 57991 | ** instead of |
| 57992 | ** |
| 57993 | ** if( pPager->jfd->pMethods ){ ... |
| 57994 | */ |
| 57995 | #define isOpen(pFd) ((pFd)->pMethods!=0) |
| 57996 | |
| 57997 | #ifdef SQLITE_DIRECT_OVERFLOW_READ |
| 57998 | /* |
| 57999 | ** Return true if page pgno can be read directly from the database file |
| 58000 | ** by the b-tree layer. This is the case if: |
| 58001 | ** |
| 58002 | ** * the database file is open, |
| 58003 | ** * there are no dirty pages in the cache, and |
| 58004 | ** * the desired page is not currently in the wal file. |
| 58005 | */ |
| 58006 | SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ |
| 58007 | if( pPager->fd->pMethods==0 ) return 0; |
| 58008 | if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; |
| 58009 | #ifndef SQLITE_OMIT_WAL |
| 58010 | if( pPager->pWal ){ |
| 58011 | u32 iRead = 0; |
| 58012 | (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); |
| 58013 | return iRead==0; |
| 58014 | } |
| 58015 | #endif |
| 58016 | return 1; |
| 58017 | } |
| 58018 | #endif |
| 58019 | |
| 58020 | #ifndef SQLITE_OMIT_WAL |
| @@ -59269,11 +59319,11 @@ | |
| 59269 | rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); |
| 59270 | } |
| 59271 | } |
| 59272 | pPager->journalOff = 0; |
| 59273 | }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST |
| 59274 | || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) |
| 59275 | ){ |
| 59276 | rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile); |
| 59277 | pPager->journalOff = 0; |
| 59278 | }else{ |
| 59279 | /* This branch may be executed with Pager.journalMode==MEMORY if |
| @@ -67979,15 +68029,11 @@ | |
| 67979 | ** so it takes care to hold an exclusive lock on the corresponding |
| 67980 | ** WAL_READ_LOCK() while changing values. |
| 67981 | */ |
| 67982 | static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ |
| 67983 | volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ |
| 67984 | u32 mxReadMark; /* Largest aReadMark[] value */ |
| 67985 | int mxI; /* Index of largest aReadMark[] value */ |
| 67986 | int i; /* Loop counter */ |
| 67987 | int rc = SQLITE_OK; /* Return code */ |
| 67988 | u32 mxFrame; /* Wal frame to lock to */ |
| 67989 | #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| 67990 | int nBlockTmout = 0; |
| 67991 | #endif |
| 67992 | |
| 67993 | assert( pWal->readLock<0 ); /* Not currently locked */ |
| @@ -68089,145 +68135,151 @@ | |
| 68089 | |
| 68090 | assert( pWal->nWiData>0 ); |
| 68091 | assert( pWal->apWiData[0]!=0 ); |
| 68092 | pInfo = walCkptInfo(pWal); |
| 68093 | SEH_INJECT_FAULT; |
| 68094 | if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame |
| 68095 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| 68096 | && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0) |
| 68097 | #endif |
| 68098 | ){ |
| 68099 | /* The WAL has been completely backfilled (or it is empty). |
| 68100 | ** and can be safely ignored. |
| 68101 | */ |
| 68102 | rc = walLockShared(pWal, WAL_READ_LOCK(0)); |
| 68103 | walShmBarrier(pWal); |
| 68104 | if( rc==SQLITE_OK ){ |
| 68105 | if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ |
| 68106 | /* It is not safe to allow the reader to continue here if frames |
| 68107 | ** may have been appended to the log before READ_LOCK(0) was obtained. |
| 68108 | ** When holding READ_LOCK(0), the reader ignores the entire log file, |
| 68109 | ** which implies that the database file contains a trustworthy |
| 68110 | ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from |
| 68111 | ** happening, this is usually correct. |
| 68112 | ** |
| 68113 | ** However, if frames have been appended to the log (or if the log |
| 68114 | ** is wrapped and written for that matter) before the READ_LOCK(0) |
| 68115 | ** is obtained, that is not necessarily true. A checkpointer may |
| 68116 | ** have started to backfill the appended frames but crashed before |
| 68117 | ** it finished. Leaving a corrupt image in the database file. |
| 68118 | */ |
| 68119 | walUnlockShared(pWal, WAL_READ_LOCK(0)); |
| 68120 | return WAL_RETRY; |
| 68121 | } |
| 68122 | pWal->readLock = 0; |
| 68123 | return SQLITE_OK; |
| 68124 | }else if( rc!=SQLITE_BUSY ){ |
| 68125 | return rc; |
| 68126 | } |
| 68127 | } |
| 68128 | |
| 68129 | /* If we get this far, it means that the reader will want to use |
| 68130 | ** the WAL to get at content from recent commits. The job now is |
| 68131 | ** to select one of the aReadMark[] entries that is closest to |
| 68132 | ** but not exceeding pWal->hdr.mxFrame and lock that entry. |
| 68133 | */ |
| 68134 | mxReadMark = 0; |
| 68135 | mxI = 0; |
| 68136 | mxFrame = pWal->hdr.mxFrame; |
| 68137 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| 68138 | if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){ |
| 68139 | mxFrame = pWal->pSnapshot->mxFrame; |
| 68140 | } |
| 68141 | #endif |
| 68142 | for(i=1; i<WAL_NREADER; i++){ |
| 68143 | u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; |
| 68144 | if( mxReadMark<=thisMark && thisMark<=mxFrame ){ |
| 68145 | assert( thisMark!=READMARK_NOT_USED ); |
| 68146 | mxReadMark = thisMark; |
| 68147 | mxI = i; |
| 68148 | } |
| 68149 | } |
| 68150 | if( (pWal->readOnly & WAL_SHM_RDONLY)==0 |
| 68151 | && (mxReadMark<mxFrame || mxI==0) |
| 68152 | ){ |
| 68153 | for(i=1; i<WAL_NREADER; i++){ |
| 68154 | rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 68155 | if( rc==SQLITE_OK ){ |
| 68156 | AtomicStore(pInfo->aReadMark+i,mxFrame); |
| 68157 | mxReadMark = mxFrame; |
| 68158 | mxI = i; |
| 68159 | walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 68160 | break; |
| 68161 | }else if( rc!=SQLITE_BUSY ){ |
| 68162 | return rc; |
| 68163 | } |
| 68164 | } |
| 68165 | } |
| 68166 | if( mxI==0 ){ |
| 68167 | assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); |
| 68168 | return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; |
| 68169 | } |
| 68170 | |
| 68171 | (void)walEnableBlockingMs(pWal, nBlockTmout); |
| 68172 | rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); |
| 68173 | walDisableBlocking(pWal); |
| 68174 | if( rc ){ |
| 68175 | #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| 68176 | if( rc==SQLITE_BUSY_TIMEOUT ){ |
| 68177 | *pCnt |= WAL_RETRY_BLOCKED_MASK; |
| 68178 | } |
| 68179 | #else |
| 68180 | assert( rc!=SQLITE_BUSY_TIMEOUT ); |
| 68181 | #endif |
| 68182 | assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT ); |
| 68183 | return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; |
| 68184 | } |
| 68185 | /* Now that the read-lock has been obtained, check that neither the |
| 68186 | ** value in the aReadMark[] array or the contents of the wal-index |
| 68187 | ** header have changed. |
| 68188 | ** |
| 68189 | ** It is necessary to check that the wal-index header did not change |
| 68190 | ** between the time it was read and when the shared-lock was obtained |
| 68191 | ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility |
| 68192 | ** that the log file may have been wrapped by a writer, or that frames |
| 68193 | ** that occur later in the log than pWal->hdr.mxFrame may have been |
| 68194 | ** copied into the database by a checkpointer. If either of these things |
| 68195 | ** happened, then reading the database with the current value of |
| 68196 | ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry |
| 68197 | ** instead. |
| 68198 | ** |
| 68199 | ** Before checking that the live wal-index header has not changed |
| 68200 | ** since it was read, set Wal.minFrame to the first frame in the wal |
| 68201 | ** file that has not yet been checkpointed. This client will not need |
| 68202 | ** to read any frames earlier than minFrame from the wal file - they |
| 68203 | ** can be safely read directly from the database file. |
| 68204 | ** |
| 68205 | ** Because a ShmBarrier() call is made between taking the copy of |
| 68206 | ** nBackfill and checking that the wal-header in shared-memory still |
| 68207 | ** matches the one cached in pWal->hdr, it is guaranteed that the |
| 68208 | ** checkpointer that set nBackfill was not working with a wal-index |
| 68209 | ** header newer than that cached in pWal->hdr. If it were, that could |
| 68210 | ** cause a problem. The checkpointer could omit to checkpoint |
| 68211 | ** a version of page X that lies before pWal->minFrame (call that version |
| 68212 | ** A) on the basis that there is a newer version (version B) of the same |
| 68213 | ** page later in the wal file. But if version B happens to like past |
| 68214 | ** frame pWal->hdr.mxFrame - then the client would incorrectly assume |
| 68215 | ** that it can read version A from the database file. However, since |
| 68216 | ** we can guarantee that the checkpointer that set nBackfill could not |
| 68217 | ** see any pages past pWal->hdr.mxFrame, this problem does not come up. |
| 68218 | */ |
| 68219 | pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; |
| 68220 | walShmBarrier(pWal); |
| 68221 | if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark |
| 68222 | || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) |
| 68223 | ){ |
| 68224 | walUnlockShared(pWal, WAL_READ_LOCK(mxI)); |
| 68225 | return WAL_RETRY; |
| 68226 | }else{ |
| 68227 | assert( mxReadMark<=pWal->hdr.mxFrame ); |
| 68228 | pWal->readLock = (i16)mxI; |
| 68229 | } |
| 68230 | return rc; |
| 68231 | } |
| 68232 | |
| 68233 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| @@ -87103,10 +87155,11 @@ | |
| 87103 | ** |
| 87104 | ** All other fields of Mem can safely remain uninitialized for now. They |
| 87105 | ** will be initialized before use. |
| 87106 | */ |
| 87107 | static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ |
| 87108 | if( N>0 ){ |
| 87109 | do{ |
| 87110 | p->flags = flags; |
| 87111 | p->db = db; |
| 87112 | p->szMalloc = 0; |
| @@ -87128,10 +87181,11 @@ | |
| 87128 | */ |
| 87129 | static void releaseMemArray(Mem *p, int N){ |
| 87130 | if( p && N ){ |
| 87131 | Mem *pEnd = &p[N]; |
| 87132 | sqlite3 *db = p->db; |
| 87133 | if( db->pnBytesFreed ){ |
| 87134 | do{ |
| 87135 | if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); |
| 87136 | }while( (++p)<pEnd ); |
| 87137 | return; |
| @@ -87608,10 +87662,11 @@ | |
| 87608 | assert( p!=0 ); |
| 87609 | assert( p->nOp>0 ); |
| 87610 | assert( pParse!=0 ); |
| 87611 | assert( p->eVdbeState==VDBE_INIT_STATE ); |
| 87612 | assert( pParse==p->pParse ); |
| 87613 | p->pVList = pParse->pVList; |
| 87614 | pParse->pVList = 0; |
| 87615 | db = p->db; |
| 87616 | assert( db->mallocFailed==0 ); |
| 87617 | nVar = pParse->nVar; |
| @@ -90488,10 +90543,11 @@ | |
| 90488 | db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); |
| 90489 | db->pPreUpdate = 0; |
| 90490 | sqlite3DbFree(db, preupdate.aRecord); |
| 90491 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked); |
| 90492 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked); |
| 90493 | if( preupdate.aNew ){ |
| 90494 | int i; |
| 90495 | for(i=0; i<pCsr->nField; i++){ |
| 90496 | sqlite3VdbeMemRelease(&preupdate.aNew[i]); |
| 90497 | } |
| @@ -91844,11 +91900,11 @@ | |
| 91844 | ** |
| 91845 | ** sqlite3_column_int() |
| 91846 | ** sqlite3_column_int64() |
| 91847 | ** sqlite3_column_text() |
| 91848 | ** sqlite3_column_text16() |
| 91849 | ** sqlite3_column_real() |
| 91850 | ** sqlite3_column_bytes() |
| 91851 | ** sqlite3_column_bytes16() |
| 91852 | ** sqlite3_column_blob() |
| 91853 | */ |
| 91854 | static void columnMallocFailure(sqlite3_stmt *pStmt) |
| @@ -92706,64 +92762,68 @@ | |
| 92706 | if( iIdx>=p->pCsr->nField || iIdx<0 ){ |
| 92707 | rc = SQLITE_RANGE; |
| 92708 | goto preupdate_old_out; |
| 92709 | } |
| 92710 | |
| 92711 | /* If the old.* record has not yet been loaded into memory, do so now. */ |
| 92712 | if( p->pUnpacked==0 ){ |
| 92713 | u32 nRec; |
| 92714 | u8 *aRec; |
| 92715 | |
| 92716 | assert( p->pCsr->eCurType==CURTYPE_BTREE ); |
| 92717 | nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); |
| 92718 | aRec = sqlite3DbMallocRaw(db, nRec); |
| 92719 | if( !aRec ) goto preupdate_old_out; |
| 92720 | rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); |
| 92721 | if( rc==SQLITE_OK ){ |
| 92722 | p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); |
| 92723 | if( !p->pUnpacked ) rc = SQLITE_NOMEM; |
| 92724 | } |
| 92725 | if( rc!=SQLITE_OK ){ |
| 92726 | sqlite3DbFree(db, aRec); |
| 92727 | goto preupdate_old_out; |
| 92728 | } |
| 92729 | p->aRecord = aRec; |
| 92730 | } |
| 92731 | |
| 92732 | pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; |
| 92733 | if( iIdx==p->pTab->iPKey ){ |
| 92734 | sqlite3VdbeMemSetInt64(pMem, p->iKey1); |
| 92735 | }else if( iIdx>=p->pUnpacked->nField ){ |
| 92736 | /* This occurs when the table has been extended using ALTER TABLE |
| 92737 | ** ADD COLUMN. The value to return is the default value of the column. */ |
| 92738 | Column *pCol = &p->pTab->aCol[iIdx]; |
| 92739 | if( pCol->iDflt>0 ){ |
| 92740 | if( p->apDflt==0 ){ |
| 92741 | int nByte = sizeof(sqlite3_value*)*p->pTab->nCol; |
| 92742 | p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); |
| 92743 | if( p->apDflt==0 ) goto preupdate_old_out; |
| 92744 | } |
| 92745 | if( p->apDflt[iIdx]==0 ){ |
| 92746 | sqlite3_value *pVal = 0; |
| 92747 | Expr *pDflt; |
| 92748 | assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) ); |
| 92749 | pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; |
| 92750 | rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); |
| 92751 | if( rc==SQLITE_OK && pVal==0 ){ |
| 92752 | rc = SQLITE_CORRUPT_BKPT; |
| 92753 | } |
| 92754 | p->apDflt[iIdx] = pVal; |
| 92755 | } |
| 92756 | *ppValue = p->apDflt[iIdx]; |
| 92757 | }else{ |
| 92758 | *ppValue = (sqlite3_value *)columnNullValue(); |
| 92759 | } |
| 92760 | }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ |
| 92761 | if( pMem->flags & (MEM_Int|MEM_IntReal) ){ |
| 92762 | testcase( pMem->flags & MEM_Int ); |
| 92763 | testcase( pMem->flags & MEM_IntReal ); |
| 92764 | sqlite3VdbeMemRealify(pMem); |
| 92765 | } |
| 92766 | } |
| 92767 | |
| 92768 | preupdate_old_out: |
| 92769 | sqlite3Error(db, rc); |
| @@ -97913,13 +97973,15 @@ | |
| 97913 | 0, pCx->uc.pCursor); |
| 97914 | pCx->isTable = 1; |
| 97915 | } |
| 97916 | } |
| 97917 | pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); |
| 97918 | if( rc ){ |
| 97919 | assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97920 | sqlite3BtreeClose(pCx->ub.pBtx); |
| 97921 | }else{ |
| 97922 | assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97923 | } |
| 97924 | } |
| 97925 | } |
| @@ -109845,11 +109907,11 @@ | |
| 109845 | p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); |
| 109846 | } |
| 109847 | p5 = binaryCompareP5(pLeft, pRight, jumpIfNull); |
| 109848 | addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1, |
| 109849 | (void*)p4, P4_COLLSEQ); |
| 109850 | sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5); |
| 109851 | return addr; |
| 109852 | } |
| 109853 | |
| 109854 | /* |
| 109855 | ** Return true if expression pExpr is a vector, or false otherwise. |
| @@ -112012,11 +112074,11 @@ | |
| 112012 | ** |
| 112013 | ** (4) If pSrc is the right operand of a LEFT JOIN, then... |
| 112014 | ** (4a) pExpr must come from an ON clause.. |
| 112015 | ** (4b) and specifically the ON clause associated with the LEFT JOIN. |
| 112016 | ** |
| 112017 | ** (5) If pSrc is not the right operand of a LEFT JOIN or the left |
| 112018 | ** operand of a RIGHT JOIN, then pExpr must be from the WHERE |
| 112019 | ** clause, not an ON clause. |
| 112020 | ** |
| 112021 | ** (6) Either: |
| 112022 | ** |
| @@ -115546,35 +115608,41 @@ | |
| 115546 | ** |
| 115547 | ** Additionally, if pExpr is a simple SQL value and the value is the |
| 115548 | ** same as that currently bound to variable pVar, non-zero is returned. |
| 115549 | ** Otherwise, if the values are not the same or if pExpr is not a simple |
| 115550 | ** SQL value, zero is returned. |
| 115551 | */ |
| 115552 | static int exprCompareVariable( |
| 115553 | const Parse *pParse, |
| 115554 | const Expr *pVar, |
| 115555 | const Expr *pExpr |
| 115556 | ){ |
| 115557 | int res = 0; |
| 115558 | int iVar; |
| 115559 | sqlite3_value *pL, *pR = 0; |
| 115560 | |
| 115561 | sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); |
| 115562 | if( pR ){ |
| 115563 | iVar = pVar->iColumn; |
| 115564 | sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); |
| 115565 | pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB); |
| 115566 | if( pL ){ |
| 115567 | if( sqlite3_value_type(pL)==SQLITE_TEXT ){ |
| 115568 | sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ |
| 115569 | } |
| 115570 | res = 0==sqlite3MemCompare(pL, pR, 0); |
| 115571 | } |
| 115572 | sqlite3ValueFree(pR); |
| 115573 | sqlite3ValueFree(pL); |
| 115574 | } |
| 115575 | |
| 115576 | return res; |
| 115577 | } |
| 115578 | |
| 115579 | /* |
| 115580 | ** Do a deep comparison of two expression trees. Return 0 if the two |
| @@ -115596,16 +115664,14 @@ | |
| 115596 | ** can be sure the expressions are the same. In the places where |
| 115597 | ** this routine is used, it does not hurt to get an extra 2 - that |
| 115598 | ** just might result in some slightly slower code. But returning |
| 115599 | ** an incorrect 0 or 1 could lead to a malfunction. |
| 115600 | ** |
| 115601 | ** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in |
| 115602 | ** pParse->pReprepare can be matched against literals in pB. The |
| 115603 | ** pParse->pVdbe->expmask bitmask is updated for each variable referenced. |
| 115604 | ** If pParse is NULL (the normal case) then any TK_VARIABLE term in |
| 115605 | ** Argument pParse should normally be NULL. If it is not NULL and pA or |
| 115606 | ** pB causes a return value of 2. |
| 115607 | */ |
| 115608 | SQLITE_PRIVATE int sqlite3ExprCompare( |
| 115609 | const Parse *pParse, |
| 115610 | const Expr *pA, |
| 115611 | const Expr *pB, |
| @@ -115613,12 +115679,12 @@ | |
| 115613 | ){ |
| 115614 | u32 combinedFlags; |
| 115615 | if( pA==0 || pB==0 ){ |
| 115616 | return pB==pA ? 0 : 2; |
| 115617 | } |
| 115618 | if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){ |
| 115619 | return 0; |
| 115620 | } |
| 115621 | combinedFlags = pA->flags | pB->flags; |
| 115622 | if( combinedFlags & EP_IntValue ){ |
| 115623 | if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ |
| 115624 | return 0; |
| @@ -115808,23 +115874,75 @@ | |
| 115808 | return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); |
| 115809 | } |
| 115810 | } |
| 115811 | return 0; |
| 115812 | } |
| 115813 | |
| 115814 | /* |
| 115815 | ** Return true if we can prove the pE2 will always be true if pE1 is |
| 115816 | ** true. Return false if we cannot complete the proof or if pE2 might |
| 115817 | ** be false. Examples: |
| 115818 | ** |
| 115819 | ** pE1: x==5 pE2: x==5 Result: true |
| 115820 | ** pE1: x>0 pE2: x==5 Result: false |
| 115821 | ** pE1: x=21 pE2: x=21 OR y=43 Result: true |
| 115822 | ** pE1: x!=123 pE2: x IS NOT NULL Result: true |
| 115823 | ** pE1: x!=?1 pE2: x IS NOT NULL Result: true |
| 115824 | ** pE1: x IS NULL pE2: x IS NOT NULL Result: false |
| 115825 | ** pE1: x IS ?2 pE2: x IS NOT NULL Result: false |
| 115826 | ** |
| 115827 | ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has |
| 115828 | ** Expr.iTable<0 then assume a table number given by iTab. |
| 115829 | ** |
| 115830 | ** If pParse is not NULL, then the values of bound variables in pE1 are |
| @@ -115854,10 +115972,13 @@ | |
| 115854 | if( pE2->op==TK_NOTNULL |
| 115855 | && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0) |
| 115856 | ){ |
| 115857 | return 1; |
| 115858 | } |
| 115859 | return 0; |
| 115860 | } |
| 115861 | |
| 115862 | /* This is a helper function to impliesNotNullRow(). In this routine, |
| 115863 | ** set pWalker->eCode to one only if *both* of the input expressions |
| @@ -121265,19 +121386,10 @@ | |
| 121265 | rc = sqlite3Init(db, &zErrDyn); |
| 121266 | } |
| 121267 | sqlite3BtreeLeaveAll(db); |
| 121268 | assert( zErrDyn==0 || rc!=SQLITE_OK ); |
| 121269 | } |
| 121270 | #ifdef SQLITE_USER_AUTHENTICATION |
| 121271 | if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){ |
| 121272 | u8 newAuth = 0; |
| 121273 | rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); |
| 121274 | if( newAuth<db->auth.authLevel ){ |
| 121275 | rc = SQLITE_AUTH_USER; |
| 121276 | } |
| 121277 | } |
| 121278 | #endif |
| 121279 | if( rc ){ |
| 121280 | if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ |
| 121281 | int iDb = db->nDb - 1; |
| 121282 | assert( iDb>=2 ); |
| 121283 | if( db->aDb[iDb].pBt ){ |
| @@ -121771,15 +121883,11 @@ | |
| 121771 | sqlite3 *db = pParse->db; /* Database handle */ |
| 121772 | char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ |
| 121773 | int rc; /* Auth callback return code */ |
| 121774 | |
| 121775 | if( db->init.busy ) return SQLITE_OK; |
| 121776 | rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext |
| 121777 | #ifdef SQLITE_USER_AUTHENTICATION |
| 121778 | ,db->auth.zAuthUser |
| 121779 | #endif |
| 121780 | ); |
| 121781 | if( rc==SQLITE_DENY ){ |
| 121782 | char *z = sqlite3_mprintf("%s.%s", zTab, zCol); |
| 121783 | if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z); |
| 121784 | sqlite3ErrorMsg(pParse, "access to %z is prohibited", z); |
| 121785 | pParse->rc = SQLITE_AUTH; |
| @@ -121882,15 +121990,11 @@ | |
| 121882 | testcase( zArg1==0 ); |
| 121883 | testcase( zArg2==0 ); |
| 121884 | testcase( zArg3==0 ); |
| 121885 | testcase( pParse->zAuthContext==0 ); |
| 121886 | |
| 121887 | rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext |
| 121888 | #ifdef SQLITE_USER_AUTHENTICATION |
| 121889 | ,db->auth.zAuthUser |
| 121890 | #endif |
| 121891 | ); |
| 121892 | if( rc==SQLITE_DENY ){ |
| 121893 | sqlite3ErrorMsg(pParse, "not authorized"); |
| 121894 | pParse->rc = SQLITE_AUTH; |
| 121895 | }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ |
| 121896 | rc = SQLITE_DENY; |
| @@ -122119,21 +122223,10 @@ | |
| 122119 | sqlite3VdbeJumpHere(v, addrRewind); |
| 122120 | } |
| 122121 | } |
| 122122 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 122123 | |
| 122124 | #if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE) |
| 122125 | if( pParse->nTableLock>0 && db->init.busy==0 ){ |
| 122126 | sqlite3UserAuthInit(db); |
| 122127 | if( db->auth.authLevel<UAUTH_User ){ |
| 122128 | sqlite3ErrorMsg(pParse, "user not authenticated"); |
| 122129 | pParse->rc = SQLITE_AUTH_USER; |
| 122130 | return; |
| 122131 | } |
| 122132 | } |
| 122133 | #endif |
| 122134 | |
| 122135 | /* The cookie mask contains one bit for each database file open. |
| 122136 | ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are |
| 122137 | ** set for each database that is used. Generate code to start a |
| 122138 | ** transaction on each used database and to verify the schema cookie |
| 122139 | ** on each used database. |
| @@ -122258,20 +122351,10 @@ | |
| 122258 | sqlite3DbFree(db, zSql); |
| 122259 | memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); |
| 122260 | pParse->nested--; |
| 122261 | } |
| 122262 | |
| 122263 | #if SQLITE_USER_AUTHENTICATION |
| 122264 | /* |
| 122265 | ** Return TRUE if zTable is the name of the system table that stores the |
| 122266 | ** list of users and their access credentials. |
| 122267 | */ |
| 122268 | SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ |
| 122269 | return sqlite3_stricmp(zTable, "sqlite_user")==0; |
| 122270 | } |
| 122271 | #endif |
| 122272 | |
| 122273 | /* |
| 122274 | ** Locate the in-memory structure that describes a particular database |
| 122275 | ** table given the name of that table and (optionally) the name of the |
| 122276 | ** database containing the table. Return NULL if not found. |
| 122277 | ** |
| @@ -122286,17 +122369,10 @@ | |
| 122286 | Table *p = 0; |
| 122287 | int i; |
| 122288 | |
| 122289 | /* All mutexes are required for schema access. Make sure we hold them. */ |
| 122290 | assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); |
| 122291 | #if SQLITE_USER_AUTHENTICATION |
| 122292 | /* Only the admin user is allowed to know that the sqlite_user table |
| 122293 | ** exists */ |
| 122294 | if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){ |
| 122295 | return 0; |
| 122296 | } |
| 122297 | #endif |
| 122298 | if( zDatabase ){ |
| 122299 | for(i=0; i<db->nDb; i++){ |
| 122300 | if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; |
| 122301 | } |
| 122302 | if( i>=db->nDb ){ |
| @@ -125951,13 +126027,10 @@ | |
| 125951 | |
| 125952 | assert( pTab!=0 ); |
| 125953 | if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 |
| 125954 | && db->init.busy==0 |
| 125955 | && pTblName!=0 |
| 125956 | #if SQLITE_USER_AUTHENTICATION |
| 125957 | && sqlite3UserAuthTable(pTab->zName)==0 |
| 125958 | #endif |
| 125959 | ){ |
| 125960 | sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); |
| 125961 | goto exit_create_index; |
| 125962 | } |
| 125963 | #ifndef SQLITE_OMIT_VIEW |
| @@ -129661,20 +129734,19 @@ | |
| 129661 | const unsigned char *z; |
| 129662 | const unsigned char *z2; |
| 129663 | int len; |
| 129664 | int p0type; |
| 129665 | i64 p1, p2; |
| 129666 | int negP2 = 0; |
| 129667 | |
| 129668 | assert( argc==3 || argc==2 ); |
| 129669 | if( sqlite3_value_type(argv[1])==SQLITE_NULL |
| 129670 | || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL) |
| 129671 | ){ |
| 129672 | return; |
| 129673 | } |
| 129674 | p0type = sqlite3_value_type(argv[0]); |
| 129675 | p1 = sqlite3_value_int(argv[1]); |
| 129676 | if( p0type==SQLITE_BLOB ){ |
| 129677 | len = sqlite3_value_bytes(argv[0]); |
| 129678 | z = sqlite3_value_blob(argv[0]); |
| 129679 | if( z==0 ) return; |
| 129680 | assert( len==sqlite3_value_bytes(argv[0]) ); |
| @@ -129695,36 +129767,36 @@ | |
| 129695 | ** from 2009-02-02 for compatibility of applications that exploited the |
| 129696 | ** old buggy behavior. */ |
| 129697 | if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */ |
| 129698 | #endif |
| 129699 | if( argc==3 ){ |
| 129700 | p2 = sqlite3_value_int(argv[2]); |
| 129701 | if( p2<0 ){ |
| 129702 | p2 = -p2; |
| 129703 | negP2 = 1; |
| 129704 | } |
| 129705 | }else{ |
| 129706 | p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH]; |
| 129707 | } |
| 129708 | if( p1<0 ){ |
| 129709 | p1 += len; |
| 129710 | if( p1<0 ){ |
| 129711 | p2 += p1; |
| 129712 | if( p2<0 ) p2 = 0; |
| 129713 | p1 = 0; |
| 129714 | } |
| 129715 | }else if( p1>0 ){ |
| 129716 | p1--; |
| 129717 | }else if( p2>0 ){ |
| 129718 | p2--; |
| 129719 | } |
| 129720 | if( negP2 ){ |
| 129721 | p1 -= p2; |
| 129722 | if( p1<0 ){ |
| 129723 | p2 += p1; |
| 129724 | p1 = 0; |
| 129725 | } |
| 129726 | } |
| 129727 | assert( p1>=0 && p2>=0 ); |
| 129728 | if( p0type!=SQLITE_BLOB ){ |
| 129729 | while( *z && p1 ){ |
| 129730 | SQLITE_SKIP_UTF8(z); |
| @@ -129734,13 +129806,15 @@ | |
| 129734 | SQLITE_SKIP_UTF8(z2); |
| 129735 | } |
| 129736 | sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, |
| 129737 | SQLITE_UTF8); |
| 129738 | }else{ |
| 129739 | if( p1+p2>len ){ |
| 129740 | p2 = len-p1; |
| 129741 | if( p2<0 ) p2 = 0; |
| 129742 | } |
| 129743 | sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); |
| 129744 | } |
| 129745 | } |
| 129746 | |
| @@ -129747,17 +129821,17 @@ | |
| 129747 | /* |
| 129748 | ** Implementation of the round() function |
| 129749 | */ |
| 129750 | #ifndef SQLITE_OMIT_FLOATING_POINT |
| 129751 | static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ |
| 129752 | int n = 0; |
| 129753 | double r; |
| 129754 | char *zBuf; |
| 129755 | assert( argc==1 || argc==2 ); |
| 129756 | if( argc==2 ){ |
| 129757 | if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; |
| 129758 | n = sqlite3_value_int(argv[1]); |
| 129759 | if( n>30 ) n = 30; |
| 129760 | if( n<0 ) n = 0; |
| 129761 | } |
| 129762 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; |
| 129763 | r = sqlite3_value_double(argv[0]); |
| @@ -129768,11 +129842,11 @@ | |
| 129768 | if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ |
| 129769 | /* The value has no fractional part so there is nothing to round */ |
| 129770 | }else if( n==0 ){ |
| 129771 | r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); |
| 129772 | }else{ |
| 129773 | zBuf = sqlite3_mprintf("%!.*f",n,r); |
| 129774 | if( zBuf==0 ){ |
| 129775 | sqlite3_result_error_nomem(context); |
| 129776 | return; |
| 129777 | } |
| 129778 | sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); |
| @@ -131985,13 +132059,10 @@ | |
| 131985 | #endif |
| 131986 | #ifndef SQLITE_OMIT_LOAD_EXTENSION |
| 131987 | SFUNCTION(load_extension, 1, 0, 0, loadExt ), |
| 131988 | SFUNCTION(load_extension, 2, 0, 0, loadExt ), |
| 131989 | #endif |
| 131990 | #if SQLITE_USER_AUTHENTICATION |
| 131991 | FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ), |
| 131992 | #endif |
| 131993 | #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS |
| 131994 | DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ), |
| 131995 | DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ), |
| 131996 | #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ |
| 131997 | INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), |
| @@ -132124,11 +132195,14 @@ | |
| 132124 | MFUNCTION(degrees, 1, radToDeg, math1Func ), |
| 132125 | MFUNCTION(pi, 0, 0, piFunc ), |
| 132126 | #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ |
| 132127 | FUNCTION(sign, 1, 0, 0, signFunc ), |
| 132128 | INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ), |
| 132129 | INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ), |
| 132130 | }; |
| 132131 | #ifndef SQLITE_OMIT_ALTERTABLE |
| 132132 | sqlite3AlterFunctions(); |
| 132133 | #endif |
| 132134 | sqlite3WindowFunctions(); |
| @@ -140637,16 +140711,10 @@ | |
| 140637 | if( db->autoCommit==0 ){ |
| 140638 | /* Foreign key support may not be enabled or disabled while not |
| 140639 | ** in auto-commit mode. */ |
| 140640 | mask &= ~(SQLITE_ForeignKeys); |
| 140641 | } |
| 140642 | #if SQLITE_USER_AUTHENTICATION |
| 140643 | if( db->auth.authLevel==UAUTH_User ){ |
| 140644 | /* Do not allow non-admin users to modify the schema arbitrarily */ |
| 140645 | mask &= ~(SQLITE_WriteSchema); |
| 140646 | } |
| 140647 | #endif |
| 140648 | |
| 140649 | if( sqlite3GetBoolean(zRight, 0) ){ |
| 140650 | if( (mask & SQLITE_WriteSchema)==0 |
| 140651 | || (db->flags & SQLITE_Defensive)==0 |
| 140652 | ){ |
| @@ -140778,11 +140846,12 @@ | |
| 140778 | pTab = sqliteHashData(k); |
| 140779 | if( pTab->nCol==0 ){ |
| 140780 | char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); |
| 140781 | if( zSql ){ |
| 140782 | sqlite3_stmt *pDummy = 0; |
| 140783 | (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0); |
| 140784 | (void)sqlite3_finalize(pDummy); |
| 140785 | sqlite3DbFree(db, zSql); |
| 140786 | } |
| 140787 | if( db->mallocFailed ){ |
| 140788 | sqlite3ErrorMsg(db->pParse, "out of memory"); |
| @@ -141259,11 +141328,11 @@ | |
| 141259 | sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt); |
| 141260 | sqlite3ClearTempRegCache(pParse); |
| 141261 | |
| 141262 | /* Do the b-tree integrity checks */ |
| 141263 | sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY); |
| 141264 | sqlite3VdbeChangeP5(v, (u8)i); |
| 141265 | addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); |
| 141266 | sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, |
| 141267 | sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), |
| 141268 | P4_DYNAMIC); |
| 141269 | sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3); |
| @@ -142879,18 +142948,11 @@ | |
| 142879 | encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; |
| 142880 | if( encoding==0 ) encoding = SQLITE_UTF8; |
| 142881 | #else |
| 142882 | encoding = SQLITE_UTF8; |
| 142883 | #endif |
| 142884 | if( db->nVdbeActive>0 && encoding!=ENC(db) |
| 142885 | && (db->mDbFlags & DBFLAG_Vacuum)==0 |
| 142886 | ){ |
| 142887 | rc = SQLITE_LOCKED; |
| 142888 | goto initone_error_out; |
| 142889 | }else{ |
| 142890 | sqlite3SetTextEncoding(db, encoding); |
| 142891 | } |
| 142892 | }else{ |
| 142893 | /* If opening an attached database, the encoding much match ENC(db) */ |
| 142894 | if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ |
| 142895 | sqlite3SetString(pzErrMsg, db, "attached databases must use the same" |
| 142896 | " text encoding as main database"); |
| @@ -147584,36 +147646,36 @@ | |
| 147584 | return pExpr; |
| 147585 | } |
| 147586 | if( pSubst->isOuterJoin ){ |
| 147587 | ExprSetProperty(pNew, EP_CanBeNull); |
| 147588 | } |
| 147589 | if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ |
| 147590 | sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, |
| 147591 | pExpr->flags & (EP_OuterON|EP_InnerON)); |
| 147592 | } |
| 147593 | sqlite3ExprDelete(db, pExpr); |
| 147594 | pExpr = pNew; |
| 147595 | if( pExpr->op==TK_TRUEFALSE ){ |
| 147596 | pExpr->u.iValue = sqlite3ExprTruthValue(pExpr); |
| 147597 | pExpr->op = TK_INTEGER; |
| 147598 | ExprSetProperty(pExpr, EP_IntValue); |
| 147599 | } |
| 147600 | |
| 147601 | /* Ensure that the expression now has an implicit collation sequence, |
| 147602 | ** just as it did when it was a column of a view or sub-query. */ |
| 147603 | { |
| 147604 | CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr); |
| 147605 | CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, |
| 147606 | pSubst->pCList->a[iColumn].pExpr |
| 147607 | ); |
| 147608 | if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){ |
| 147609 | pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr, |
| 147610 | (pColl ? pColl->zName : "BINARY") |
| 147611 | ); |
| 147612 | } |
| 147613 | } |
| 147614 | ExprClearProperty(pExpr, EP_Collate); |
| 147615 | } |
| 147616 | } |
| 147617 | }else{ |
| 147618 | if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ |
| 147619 | pExpr->iTable = pSubst->iNewTable; |
| @@ -148346,20 +148408,20 @@ | |
| 148346 | } |
| 148347 | |
| 148348 | /* Transfer the FROM clause terms from the subquery into the |
| 148349 | ** outer query. |
| 148350 | */ |
| 148351 | for(i=0; i<nSubSrc; i++){ |
| 148352 | SrcItem *pItem = &pSrc->a[i+iFrom]; |
| 148353 | assert( pItem->fg.isTabFunc==0 ); |
| 148354 | assert( pItem->fg.isSubquery |
| 148355 | || pItem->fg.fixedSchema |
| 148356 | || pItem->u4.zDatabase==0 ); |
| 148357 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 148358 | *pItem = pSubSrc->a[i]; |
| 148359 | pItem->fg.jointype |= ltorj; |
| 148360 | iNewParent = pSubSrc->a[i].iCursor; |
| 148361 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 148362 | } |
| 148363 | pSrc->a[iFrom].fg.jointype &= JT_LTORJ; |
| 148364 | pSrc->a[iFrom].fg.jointype |= jointype | ltorj; |
| 148365 | |
| @@ -148395,10 +148457,11 @@ | |
| 148395 | pSub->pOrderBy = 0; |
| 148396 | } |
| 148397 | pWhere = pSub->pWhere; |
| 148398 | pSub->pWhere = 0; |
| 148399 | if( isOuterJoin>0 ){ |
| 148400 | sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); |
| 148401 | } |
| 148402 | if( pWhere ){ |
| 148403 | if( pParent->pWhere ){ |
| 148404 | pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere); |
| @@ -150498,11 +150561,11 @@ | |
| 150498 | } |
| 150499 | sqlite3ReleaseTempReg(pParse, regSubtype); |
| 150500 | } |
| 150501 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150502 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150503 | sqlite3VdbeChangeP5(v, (u8)nArg); |
| 150504 | sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v); |
| 150505 | sqlite3VdbeJumpHere(v, iTop); |
| 150506 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150507 | } |
| 150508 | sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), |
| @@ -150661,11 +150724,11 @@ | |
| 150661 | sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, |
| 150662 | (char *)pColl, P4_COLLSEQ); |
| 150663 | } |
| 150664 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150665 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150666 | sqlite3VdbeChangeP5(v, (u8)nArg); |
| 150667 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150668 | } |
| 150669 | if( addrNext ){ |
| 150670 | sqlite3VdbeResolveLabel(v, addrNext); |
| 150671 | } |
| @@ -151494,11 +151557,11 @@ | |
| 151494 | sqlite3TreeViewSelect(0, p, 0); |
| 151495 | } |
| 151496 | #endif |
| 151497 | assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 ); |
| 151498 | }else{ |
| 151499 | TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n")); |
| 151500 | } |
| 151501 | |
| 151502 | /* Convert unused result columns of the subquery into simple NULL |
| 151503 | ** expressions, to avoid unneeded searching and computation. |
| 151504 | ** tag-select-0440 |
| @@ -154055,11 +154118,11 @@ | |
| 154055 | /* Set the P5 operand of the OP_Program instruction to non-zero if |
| 154056 | ** recursive invocation of this trigger program is disallowed. Recursive |
| 154057 | ** invocation is disallowed if (a) the sub-program is really a trigger, |
| 154058 | ** not a foreign key action, and (b) the flag to enable recursive triggers |
| 154059 | ** is clear. */ |
| 154060 | sqlite3VdbeChangeP5(v, (u8)bRecursive); |
| 154061 | } |
| 154062 | } |
| 154063 | |
| 154064 | /* |
| 154065 | ** This is called to code the required FOR EACH ROW triggers for an operation |
| @@ -158268,13 +158331,21 @@ | |
| 158268 | SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( |
| 158269 | const Parse *pParse, /* Parse context */ |
| 158270 | const WhereInfo *pWInfo, /* WHERE clause */ |
| 158271 | const WhereLevel *pLevel /* Bloom filter on this level */ |
| 158272 | ); |
| 158273 | #else |
| 158274 | # define sqlite3WhereExplainOneScan(u,v,w,x) 0 |
| 158275 | # define sqlite3WhereExplainBloomFilter(u,v,w) 0 |
| 158276 | #endif /* SQLITE_OMIT_EXPLAIN */ |
| 158277 | #ifdef SQLITE_ENABLE_STMT_SCANSTATUS |
| 158278 | SQLITE_PRIVATE void sqlite3WhereAddScanStatus( |
| 158279 | Vdbe *v, /* Vdbe to add scanstatus entry to */ |
| 158280 | SrcList *pSrclist, /* FROM clause pLvl reads data from */ |
| @@ -158472,42 +158543,42 @@ | |
| 158472 | } |
| 158473 | sqlite3_str_append(pStr, ")", 1); |
| 158474 | } |
| 158475 | |
| 158476 | /* |
| 158477 | ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN |
| 158478 | ** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG |
| 158479 | ** was defined at compile-time. If it is not a no-op, a single OP_Explain |
| 158480 | ** opcode is added to the output to describe the table scan strategy in pLevel. |
| 158481 | ** |
| 158482 | ** If an OP_Explain opcode is added to the VM, its address is returned. |
| 158483 | ** Otherwise, if no OP_Explain is coded, zero is returned. |
| 158484 | */ |
| 158485 | SQLITE_PRIVATE int sqlite3WhereExplainOneScan( |
| 158486 | Parse *pParse, /* Parse context */ |
| 158487 | SrcList *pTabList, /* Table list this loop refers to */ |
| 158488 | WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ |
| 158489 | u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ |
| 158490 | ){ |
| 158491 | int ret = 0; |
| 158492 | #if !defined(SQLITE_DEBUG) |
| 158493 | if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) |
| 158494 | #endif |
| 158495 | { |
| 158496 | SrcItem *pItem = &pTabList->a[pLevel->iFrom]; |
| 158497 | Vdbe *v = pParse->pVdbe; /* VM being constructed */ |
| 158498 | sqlite3 *db = pParse->db; /* Database handle */ |
| 158499 | int isSearch; /* True for a SEARCH. False for SCAN. */ |
| 158500 | WhereLoop *pLoop; /* The controlling WhereLoop object */ |
| 158501 | u32 flags; /* Flags that describe this loop */ |
| 158502 | char *zMsg; /* Text to add to EQP output */ |
| 158503 | StrAccum str; /* EQP output string */ |
| 158504 | char zBuf[100]; /* Initial space for EQP output string */ |
| 158505 | |
| 158506 | pLoop = pLevel->pWLoop; |
| 158507 | flags = pLoop->wsFlags; |
| 158508 | if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0; |
| 158509 | |
| 158510 | isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 |
| 158511 | || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) |
| 158512 | || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); |
| 158513 | |
| @@ -158527,11 +158598,11 @@ | |
| 158527 | } |
| 158528 | }else if( flags & WHERE_PARTIALIDX ){ |
| 158529 | zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; |
| 158530 | }else if( flags & WHERE_AUTO_INDEX ){ |
| 158531 | zFmt = "AUTOMATIC COVERING INDEX"; |
| 158532 | }else if( flags & WHERE_IDX_ONLY ){ |
| 158533 | zFmt = "COVERING INDEX %s"; |
| 158534 | }else{ |
| 158535 | zFmt = "INDEX %s"; |
| 158536 | } |
| 158537 | if( zFmt ){ |
| @@ -158579,15 +158650,54 @@ | |
| 158579 | sqlite3LogEstToInt(pLoop->nOut)); |
| 158580 | }else{ |
| 158581 | sqlite3_str_append(&str, " (~1 row)", 9); |
| 158582 | } |
| 158583 | #endif |
| 158584 | zMsg = sqlite3StrAccumFinish(&str); |
| 158585 | sqlite3ExplainBreakpoint("",zMsg); |
| 158586 | ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), |
| 158587 | pParse->addrExplain, pLoop->rRun, |
| 158588 | zMsg, P4_DYNAMIC); |
| 158589 | } |
| 158590 | return ret; |
| 158591 | } |
| 158592 | |
| 158593 | /* |
| @@ -158682,13 +158792,14 @@ | |
| 158682 | if( wsFlags & WHERE_INDEXED ){ |
| 158683 | sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); |
| 158684 | } |
| 158685 | }else{ |
| 158686 | int addr; |
| 158687 | assert( pSrclist->a[pLvl->iFrom].fg.isSubquery ); |
| 158688 | addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub; |
| 158689 | VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1); |
| 158690 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); |
| 158691 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); |
| 158692 | sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); |
| 158693 | } |
| 158694 | } |
| @@ -158937,10 +159048,11 @@ | |
| 158937 | if( pOrigLhs ){ |
| 158938 | sqlite3ExprListDelete(db, pOrigLhs); |
| 158939 | pNew->pLeft->x.pList = pLhs; |
| 158940 | } |
| 158941 | pSelect->pEList = pRhs; |
| 158942 | if( pLhs && pLhs->nExpr==1 ){ |
| 158943 | /* Take care here not to generate a TK_VECTOR containing only a |
| 158944 | ** single value. Since the parser never creates such a vector, some |
| 158945 | ** of the subroutines do not handle this case. */ |
| 158946 | Expr *p = pLhs->a[0].pExpr; |
| @@ -164009,11 +164121,11 @@ | |
| 164009 | || pTerm->pExpr->w.iJoin != pSrc->iCursor |
| 164010 | ){ |
| 164011 | return 0; |
| 164012 | } |
| 164013 | if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 |
| 164014 | && ExprHasProperty(pTerm->pExpr, EP_InnerON) |
| 164015 | ){ |
| 164016 | return 0; |
| 164017 | } |
| 164018 | return 1; |
| 164019 | } |
| @@ -165502,11 +165614,11 @@ | |
| 165502 | return rc; |
| 165503 | } |
| 165504 | #endif /* SQLITE_ENABLE_STAT4 */ |
| 165505 | |
| 165506 | |
| 165507 | #ifdef WHERETRACE_ENABLED |
| 165508 | /* |
| 165509 | ** Print the content of a WhereTerm object |
| 165510 | */ |
| 165511 | SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ |
| 165512 | if( pTerm==0 ){ |
| @@ -165545,10 +165657,13 @@ | |
| 165545 | sqlite3DebugPrintf(" iParent=%d", pTerm->iParent); |
| 165546 | } |
| 165547 | sqlite3DebugPrintf("\n"); |
| 165548 | sqlite3TreeViewExpr(0, pTerm->pExpr, 0); |
| 165549 | } |
| 165550 | } |
| 165551 | #endif |
| 165552 | |
| 165553 | #ifdef WHERETRACE_ENABLED |
| 165554 | /* |
| @@ -166731,11 +166846,10 @@ | |
| 166731 | pParse = pWC->pWInfo->pParse; |
| 166732 | while( pWhere->op==TK_AND ){ |
| 166733 | if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; |
| 166734 | pWhere = pWhere->pRight; |
| 166735 | } |
| 166736 | if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; |
| 166737 | for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ |
| 166738 | Expr *pExpr; |
| 166739 | pExpr = pTerm->pExpr; |
| 166740 | if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) |
| 166741 | && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) |
| @@ -169392,11 +169506,11 @@ | |
| 169392 | break; |
| 169393 | } |
| 169394 | } |
| 169395 | if( hasRightJoin |
| 169396 | && ExprHasProperty(pTerm->pExpr, EP_InnerON) |
| 169397 | && pTerm->pExpr->w.iJoin==pItem->iCursor |
| 169398 | ){ |
| 169399 | break; /* restriction (5) */ |
| 169400 | } |
| 169401 | } |
| 169402 | if( pTerm<pEnd ) continue; |
| @@ -170312,10 +170426,11 @@ | |
| 170312 | int pc, |
| 170313 | VdbeOp *pOp |
| 170314 | ){ |
| 170315 | if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return; |
| 170316 | sqlite3VdbePrintOp(0, pc, pOp); |
| 170317 | } |
| 170318 | #endif |
| 170319 | |
| 170320 | /* |
| 170321 | ** Generate the end of the WHERE loop. See comments on |
| @@ -170611,18 +170726,32 @@ | |
| 170611 | x = sqlite3TableColumnToIndex(pIdx, x); |
| 170612 | if( x>=0 ){ |
| 170613 | pOp->p2 = x; |
| 170614 | pOp->p1 = pLevel->iIdxCur; |
| 170615 | OpcodeRewriteTrace(db, k, pOp); |
| 170616 | }else{ |
| 170617 | /* Unable to translate the table reference into an index |
| 170618 | ** reference. Verify that this is harmless - that the |
| 170619 | ** table being referenced really is open. |
| 170620 | */ |
| 170621 | if( pLoop->wsFlags & WHERE_IDX_ONLY ){ |
| 170622 | sqlite3ErrorMsg(pParse, "internal query planner error"); |
| 170623 | pParse->rc = SQLITE_INTERNAL; |
| 170624 | } |
| 170625 | } |
| 170626 | }else if( pOp->opcode==OP_Rowid ){ |
| 170627 | pOp->p1 = pLevel->iIdxCur; |
| 170628 | pOp->opcode = OP_IdxRowid; |
| @@ -172326,10 +172455,11 @@ | |
| 172326 | for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ |
| 172327 | FuncDef *pFunc = pWin->pWFunc; |
| 172328 | int regArg; |
| 172329 | int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); |
| 172330 | int i; |
| 172331 | |
| 172332 | assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED ); |
| 172333 | |
| 172334 | /* All OVER clauses in the same window function aggregate step must |
| 172335 | ** be the same. */ |
| @@ -172341,10 +172471,22 @@ | |
| 172341 | }else{ |
| 172342 | sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i); |
| 172343 | } |
| 172344 | } |
| 172345 | regArg = reg; |
| 172346 | |
| 172347 | if( pMWin->regStartRowid==0 |
| 172348 | && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) |
| 172349 | && (pWin->eStart!=TK_UNBOUNDED) |
| 172350 | ){ |
| @@ -172361,29 +172503,17 @@ | |
| 172361 | sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp); |
| 172362 | sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); |
| 172363 | } |
| 172364 | sqlite3VdbeJumpHere(v, addrIsNull); |
| 172365 | }else if( pWin->regApp ){ |
| 172366 | assert( pFunc->zName==nth_valueName |
| 172367 | || pFunc->zName==first_valueName |
| 172368 | ); |
| 172369 | assert( bInverse==0 || bInverse==1 ); |
| 172370 | sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1); |
| 172371 | }else if( pFunc->xSFunc!=noopStepFunc ){ |
| 172372 | int addrIf = 0; |
| 172373 | if( pWin->pFilter ){ |
| 172374 | int regTmp; |
| 172375 | assert( ExprUseXList(pWin->pOwner) ); |
| 172376 | assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); |
| 172377 | assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); |
| 172378 | regTmp = sqlite3GetTempReg(pParse); |
| 172379 | sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); |
| 172380 | addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); |
| 172381 | VdbeCoverage(v); |
| 172382 | sqlite3ReleaseTempReg(pParse, regTmp); |
| 172383 | } |
| 172384 | |
| 172385 | if( pWin->bExprArgs ){ |
| 172386 | int iOp = sqlite3VdbeCurrentAddr(v); |
| 172387 | int iEnd; |
| 172388 | |
| 172389 | assert( ExprUseXList(pWin->pOwner) ); |
| @@ -172406,16 +172536,17 @@ | |
| 172406 | sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); |
| 172407 | } |
| 172408 | sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, |
| 172409 | bInverse, regArg, pWin->regAccum); |
| 172410 | sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); |
| 172411 | sqlite3VdbeChangeP5(v, (u8)nArg); |
| 172412 | if( pWin->bExprArgs ){ |
| 172413 | sqlite3ReleaseTempRange(pParse, regArg, nArg); |
| 172414 | } |
| 172415 | if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); |
| 172416 | } |
| 172417 | } |
| 172418 | } |
| 172419 | |
| 172420 | /* |
| 172421 | ** Values that may be passed as the second argument to windowCodeOp(). |
| @@ -173837,10 +173968,17 @@ | |
| 173837 | ** Then the "b" IdList records the list "a,b,c". |
| 173838 | */ |
| 173839 | struct TrigEvent { int a; IdList * b; }; |
| 173840 | |
| 173841 | struct FrameBound { int eType; Expr *pExpr; }; |
| 173842 | |
| 173843 | /* |
| 173844 | ** Disable lookaside memory allocation for objects that might be |
| 173845 | ** shared across database connections. |
| 173846 | */ |
| @@ -177730,11 +177868,15 @@ | |
| 177730 | } |
| 177731 | break; |
| 177732 | case 84: /* cmd ::= select */ |
| 177733 | { |
| 177734 | SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; |
| 177735 | sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); |
| 177736 | sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); |
| 177737 | } |
| 177738 | break; |
| 177739 | case 85: /* select ::= WITH wqlist selectnowith */ |
| 177740 | {yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} |
| @@ -178201,11 +178343,11 @@ | |
| 178201 | ** that look like this: #1 #2 ... These terms refer to registers |
| 178202 | ** in the virtual machine. #N is the N-th register. */ |
| 178203 | Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/ |
| 178204 | assert( t.n>=2 ); |
| 178205 | if( pParse->nested==0 ){ |
| 178206 | sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); |
| 178207 | yymsp[0].minor.yy454 = 0; |
| 178208 | }else{ |
| 178209 | yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); |
| 178210 | if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable); |
| 178211 | } |
| @@ -179049,11 +179191,11 @@ | |
| 179049 | #define TOKEN yyminor |
| 179050 | /************ Begin %syntax_error code ****************************************/ |
| 179051 | |
| 179052 | UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ |
| 179053 | if( TOKEN.z[0] ){ |
| 179054 | sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); |
| 179055 | }else{ |
| 179056 | sqlite3ErrorMsg(pParse, "incomplete input"); |
| 179057 | } |
| 179058 | /************ End %syntax_error code ******************************************/ |
| 179059 | sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ |
| @@ -180540,11 +180682,13 @@ | |
| 180540 | } |
| 180541 | if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ |
| 180542 | if( pParse->zErrMsg==0 ){ |
| 180543 | pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); |
| 180544 | } |
| 180545 | sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); |
| 180546 | nErr++; |
| 180547 | } |
| 180548 | pParse->zTail = zSql; |
| 180549 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 180550 | sqlite3_free(pParse->apVtabLock); |
| @@ -182513,14 +182657,10 @@ | |
| 182513 | #endif |
| 182514 | |
| 182515 | sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ |
| 182516 | sqlite3ValueFree(db->pErr); |
| 182517 | sqlite3CloseExtensions(db); |
| 182518 | #if SQLITE_USER_AUTHENTICATION |
| 182519 | sqlite3_free(db->auth.zAuthUser); |
| 182520 | sqlite3_free(db->auth.zAuthPW); |
| 182521 | #endif |
| 182522 | |
| 182523 | db->eOpenState = SQLITE_STATE_ERROR; |
| 182524 | |
| 182525 | /* The temp-database schema is allocated differently from the other schema |
| 182526 | ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). |
| @@ -183951,12 +184091,12 @@ | |
| 183951 | # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 |
| 183952 | #endif |
| 183953 | #if SQLITE_MAX_VDBE_OP<40 |
| 183954 | # error SQLITE_MAX_VDBE_OP must be at least 40 |
| 183955 | #endif |
| 183956 | #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127 |
| 183957 | # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127 |
| 183958 | #endif |
| 183959 | #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 |
| 183960 | # error SQLITE_MAX_ATTACHED must be between 0 and 125 |
| 183961 | #endif |
| 183962 | #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1 |
| @@ -184019,12 +184159,12 @@ | |
| 184019 | } |
| 184020 | oldLimit = db->aLimit[limitId]; |
| 184021 | if( newLimit>=0 ){ /* IMP: R-52476-28732 */ |
| 184022 | if( newLimit>aHardLimit[limitId] ){ |
| 184023 | newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ |
| 184024 | }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){ |
| 184025 | newLimit = 1; |
| 184026 | } |
| 184027 | db->aLimit[limitId] = newLimit; |
| 184028 | } |
| 184029 | return oldLimit; /* IMP: R-53341-35419 */ |
| 184030 | } |
| @@ -185364,11 +185504,10 @@ | |
| 185364 | rc = x; |
| 185365 | #if defined(SQLITE_DEBUG) |
| 185366 | /* Invoke these debugging routines so that the compiler does not |
| 185367 | ** issue "defined but not used" warnings. */ |
| 185368 | if( x==9999 ){ |
| 185369 | sqlite3ShowExpr(0); |
| 185370 | sqlite3ShowExpr(0); |
| 185371 | sqlite3ShowExprList(0); |
| 185372 | sqlite3ShowIdList(0); |
| 185373 | sqlite3ShowSrcList(0); |
| 185374 | sqlite3ShowWith(0); |
| @@ -189796,14 +189935,19 @@ | |
| 189796 | |
| 189797 | assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 ); |
| 189798 | if( *p1==POS_COLUMN ){ |
| 189799 | p1++; |
| 189800 | p1 += fts3GetVarint32(p1, &iCol1); |
| 189801 | } |
| 189802 | if( *p2==POS_COLUMN ){ |
| 189803 | p2++; |
| 189804 | p2 += fts3GetVarint32(p2, &iCol2); |
| 189805 | } |
| 189806 | |
| 189807 | while( 1 ){ |
| 189808 | if( iCol1==iCol2 ){ |
| 189809 | char *pSave = p; |
| @@ -192970,11 +193114,11 @@ | |
| 192970 | for(p=pExpr; p->pLeft; p=p->pLeft){ |
| 192971 | assert( p->pRight->pPhrase->doclist.nList>0 ); |
| 192972 | nTmp += p->pRight->pPhrase->doclist.nList; |
| 192973 | } |
| 192974 | nTmp += p->pPhrase->doclist.nList; |
| 192975 | aTmp = sqlite3_malloc64(nTmp*2); |
| 192976 | if( !aTmp ){ |
| 192977 | *pRc = SQLITE_NOMEM; |
| 192978 | res = 0; |
| 192979 | }else{ |
| 192980 | char *aPoslist = p->pPhrase->doclist.pList; |
| @@ -193621,11 +193765,11 @@ | |
| 193621 | SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ |
| 193622 | return SQLITE_CORRUPT_VTAB; |
| 193623 | } |
| 193624 | #endif |
| 193625 | |
| 193626 | #if !SQLITE_CORE |
| 193627 | /* |
| 193628 | ** Initialize API pointer table, if required. |
| 193629 | */ |
| 193630 | #ifdef _WIN32 |
| 193631 | __declspec(dllexport) |
| @@ -194523,14 +194667,15 @@ | |
| 194523 | rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos); |
| 194524 | if( rc==SQLITE_OK ){ |
| 194525 | Fts3PhraseToken *pToken; |
| 194526 | |
| 194527 | p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); |
| 194528 | if( !p ) goto no_mem; |
| 194529 | |
| 194530 | zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); |
| 194531 | if( !zTemp ) goto no_mem; |
| 194532 | |
| 194533 | assert( nToken==ii ); |
| 194534 | pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; |
| 194535 | memset(pToken, 0, sizeof(Fts3PhraseToken)); |
| 194536 | |
| @@ -194541,53 +194686,51 @@ | |
| 194541 | pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*'); |
| 194542 | pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^'); |
| 194543 | nToken = ii+1; |
| 194544 | } |
| 194545 | } |
| 194546 | |
| 194547 | pModule->xClose(pCursor); |
| 194548 | pCursor = 0; |
| 194549 | } |
| 194550 | |
| 194551 | if( rc==SQLITE_DONE ){ |
| 194552 | int jj; |
| 194553 | char *zBuf = 0; |
| 194554 | |
| 194555 | p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); |
| 194556 | if( !p ) goto no_mem; |
| 194557 | memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); |
| 194558 | p->eType = FTSQUERY_PHRASE; |
| 194559 | p->pPhrase = (Fts3Phrase *)&p[1]; |
| 194560 | p->pPhrase->iColumn = pParse->iDefaultCol; |
| 194561 | p->pPhrase->nToken = nToken; |
| 194562 | |
| 194563 | zBuf = (char *)&p->pPhrase->aToken[nToken]; |
| 194564 | if( zTemp ){ |
| 194565 | memcpy(zBuf, zTemp, nTemp); |
| 194566 | sqlite3_free(zTemp); |
| 194567 | }else{ |
| 194568 | assert( nTemp==0 ); |
| 194569 | } |
| 194570 | |
| 194571 | for(jj=0; jj<p->pPhrase->nToken; jj++){ |
| 194572 | p->pPhrase->aToken[jj].z = zBuf; |
| 194573 | zBuf += p->pPhrase->aToken[jj].n; |
| 194574 | } |
| 194575 | rc = SQLITE_OK; |
| 194576 | } |
| 194577 | |
| 194578 | *ppExpr = p; |
| 194579 | return rc; |
| 194580 | no_mem: |
| 194581 | |
| 194582 | if( pCursor ){ |
| 194583 | pModule->xClose(pCursor); |
| 194584 | } |
| 194585 | sqlite3_free(zTemp); |
| 194586 | sqlite3_free(p); |
| 194587 | *ppExpr = 0; |
| 194588 | return SQLITE_NOMEM; |
| 194589 | } |
| 194590 | |
| 194591 | /* |
| 194592 | ** The output variable *ppExpr is populated with an allocated Fts3Expr |
| 194593 | ** structure, or set to 0 if the end of the input buffer is reached. |
| @@ -215395,12 +215538,12 @@ | |
| 215395 | #endif |
| 215396 | } |
| 215397 | sqlite3_str_append(pOut, "}", 1); |
| 215398 | } |
| 215399 | errCode = sqlite3_str_errcode(pOut); |
| 215400 | sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free); |
| 215401 | sqlite3_result_error_code(ctx, errCode); |
| 215402 | } |
| 215403 | |
| 215404 | /* This routine implements an SQL function that returns the "depth" parameter |
| 215405 | ** from the front of a blob that is an r-tree node. For example: |
| 215406 | ** |
| @@ -217912,11 +218055,11 @@ | |
| 217912 | return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, |
| 217913 | (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback |
| 217914 | ); |
| 217915 | } |
| 217916 | |
| 217917 | #if !SQLITE_CORE |
| 217918 | #ifdef _WIN32 |
| 217919 | __declspec(dllexport) |
| 217920 | #endif |
| 217921 | SQLITE_API int sqlite3_rtree_init( |
| 217922 | sqlite3 *db, |
| @@ -218503,11 +218646,11 @@ | |
| 218503 | } |
| 218504 | |
| 218505 | return rc; |
| 218506 | } |
| 218507 | |
| 218508 | #if !SQLITE_CORE |
| 218509 | #ifdef _WIN32 |
| 218510 | __declspec(dllexport) |
| 218511 | #endif |
| 218512 | SQLITE_API int sqlite3_icu_init( |
| 218513 | sqlite3 *db, |
| @@ -226177,10 +226320,12 @@ | |
| 226177 | if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ |
| 226178 | unsigned char *aPage = sqlite3PagerGetData(pDbPage); |
| 226179 | memcpy(aPage, pData, szPage); |
| 226180 | pTab->pgnoTrunc = 0; |
| 226181 | } |
| 226182 | } |
| 226183 | sqlite3PagerUnref(pDbPage); |
| 226184 | return rc; |
| 226185 | |
| 226186 | update_fail: |
| @@ -226210,11 +226355,15 @@ | |
| 226210 | static int dbpageSync(sqlite3_vtab *pVtab){ |
| 226211 | DbpageTable *pTab = (DbpageTable *)pVtab; |
| 226212 | if( pTab->pgnoTrunc>0 ){ |
| 226213 | Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; |
| 226214 | Pager *pPager = sqlite3BtreePager(pBt); |
| 226215 | sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); |
| 226216 | } |
| 226217 | pTab->pgnoTrunc = 0; |
| 226218 | return SQLITE_OK; |
| 226219 | } |
| 226220 | |
| @@ -232804,20 +232953,46 @@ | |
| 232804 | #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 232805 | |
| 232806 | /************** End of sqlite3session.c **************************************/ |
| 232807 | /************** Begin file fts5.c ********************************************/ |
| 232808 | |
| 232809 | |
| 232810 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) |
| 232811 | |
| 232812 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 232813 | # define NDEBUG 1 |
| 232814 | #endif |
| 232815 | #if defined(NDEBUG) && defined(SQLITE_DEBUG) |
| 232816 | # undef NDEBUG |
| 232817 | #endif |
| 232818 | |
| 232819 | /* |
| 232820 | ** 2014 May 31 |
| 232821 | ** |
| 232822 | ** The author disclaims copyright to this source code. In place of |
| 232823 | ** a legal notice, here is a blessing: |
| @@ -233114,17 +233289,32 @@ | |
| 233114 | ** This is used to access token iToken of phrase hit iIdx within the |
| 233115 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 233116 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 233117 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 233118 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 233119 | ** bytes. This API is not available if the specified token matches a |
| 233120 | ** prefix query term. In that case both output variables are always set |
| 233121 | ** to 0. |
| 233122 | ** |
| 233123 | ** The output text is not a copy of the document text that was tokenized. |
| 233124 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 233125 | ** includes any embedded 0x00 and trailing data. |
| 233126 | ** |
| 233127 | ** This API can be quite slow if used with an FTS5 table created with the |
| 233128 | ** "detail=none" or "detail=column" option. |
| 233129 | ** |
| 233130 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -233803,11 +233993,12 @@ | |
| 233803 | int nUsermerge; /* 'usermerge' setting */ |
| 233804 | int nHashSize; /* Bytes of memory for in-memory hash */ |
| 233805 | char *zRank; /* Name of rank function */ |
| 233806 | char *zRankArgs; /* Arguments to rank function */ |
| 233807 | int bSecureDelete; /* 'secure-delete' */ |
| 233808 | int nDeleteMerge; /* 'deletemerge' */ |
| 233809 | |
| 233810 | /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ |
| 233811 | char **pzErrmsg; |
| 233812 | |
| 233813 | #ifdef SQLITE_DEBUG |
| @@ -234060,11 +234251,18 @@ | |
| 234060 | static int sqlite3Fts5StructureTest(Fts5Index*, void*); |
| 234061 | |
| 234062 | /* |
| 234063 | ** Used by xInstToken(): |
| 234064 | */ |
| 234065 | static int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*); |
| 234066 | |
| 234067 | /* |
| 234068 | ** Insert or remove data to or from the index. Each time a document is |
| 234069 | ** added to or removed from the index, this function is called one or more |
| 234070 | ** times. |
| @@ -238274,10 +238472,23 @@ | |
| 238274 | if( bVal<0 ){ |
| 238275 | *pbBadkey = 1; |
| 238276 | }else{ |
| 238277 | pConfig->bSecureDelete = (bVal ? 1 : 0); |
| 238278 | } |
| 238279 | }else{ |
| 238280 | *pbBadkey = 1; |
| 238281 | } |
| 238282 | return rc; |
| 238283 | } |
| @@ -241409,11 +241620,11 @@ | |
| 241409 | && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 |
| 241410 | ){ |
| 241411 | int rc = sqlite3Fts5PoslistWriterAppend( |
| 241412 | &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff |
| 241413 | ); |
| 241414 | if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){ |
| 241415 | int iCol = p->iOff>>32; |
| 241416 | int iTokOff = p->iOff & 0x7FFFFFFF; |
| 241417 | rc = sqlite3Fts5IndexIterWriteTokendata( |
| 241418 | pT->pIter, pToken, nToken, iRowid, iCol, iTokOff |
| 241419 | ); |
| @@ -241602,19 +241813,18 @@ | |
| 241602 | pPhrase = pExpr->apExprPhrase[iPhrase]; |
| 241603 | if( iToken<0 || iToken>=pPhrase->nTerm ){ |
| 241604 | return SQLITE_RANGE; |
| 241605 | } |
| 241606 | pTerm = &pPhrase->aTerm[iToken]; |
| 241607 | if( pTerm->bPrefix==0 ){ |
| 241608 | if( pExpr->pConfig->bTokendata ){ |
| 241609 | rc = sqlite3Fts5IterToken( |
| 241610 | pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut |
| 241611 | ); |
| 241612 | }else{ |
| 241613 | *ppOut = pTerm->pTerm; |
| 241614 | *pnOut = pTerm->nFullTerm; |
| 241615 | } |
| 241616 | } |
| 241617 | return rc; |
| 241618 | } |
| 241619 | |
| 241620 | /* |
| @@ -248425,10 +248635,387 @@ | |
| 248425 | fts5BufferFree(&tmp); |
| 248426 | memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); |
| 248427 | *p1 = out; |
| 248428 | } |
| 248429 | |
| 248430 | static void fts5SetupPrefixIter( |
| 248431 | Fts5Index *p, /* Index to read from */ |
| 248432 | int bDesc, /* True for "ORDER BY rowid DESC" */ |
| 248433 | int iIdx, /* Index to scan for data */ |
| 248434 | u8 *pToken, /* Buffer containing prefix to match */ |
| @@ -248435,137 +249022,91 @@ | |
| 248435 | int nToken, /* Size of buffer pToken in bytes */ |
| 248436 | Fts5Colset *pColset, /* Restrict matches to these columns */ |
| 248437 | Fts5Iter **ppIter /* OUT: New iterator */ |
| 248438 | ){ |
| 248439 | Fts5Structure *pStruct; |
| 248440 | Fts5Buffer *aBuf; |
| 248441 | int nBuf = 32; |
| 248442 | int nMerge = 1; |
| 248443 | |
| 248444 | void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); |
| 248445 | void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); |
| 248446 | if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
| 248447 | xMerge = fts5MergeRowidLists; |
| 248448 | xAppend = fts5AppendRowid; |
| 248449 | }else{ |
| 248450 | nMerge = FTS5_MERGE_NLIST-1; |
| 248451 | nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ |
| 248452 | xMerge = fts5MergePrefixLists; |
| 248453 | xAppend = fts5AppendPoslist; |
| 248454 | } |
| 248455 | |
| 248456 | aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); |
| 248457 | pStruct = fts5StructureRead(p); |
| 248458 | assert( p->rc!=SQLITE_OK || (aBuf && pStruct) ); |
| 248459 | |
| 248460 | if( p->rc==SQLITE_OK ){ |
| 248461 | const int flags = FTS5INDEX_QUERY_SCAN |
| 248462 | | FTS5INDEX_QUERY_SKIPEMPTY |
| 248463 | | FTS5INDEX_QUERY_NOOUTPUT; |
| 248464 | int i; |
| 248465 | i64 iLastRowid = 0; |
| 248466 | Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ |
| 248467 | Fts5Data *pData; |
| 248468 | Fts5Buffer doclist; |
| 248469 | int bNewTerm = 1; |
| 248470 | |
| 248471 | memset(&doclist, 0, sizeof(doclist)); |
| 248472 | |
| 248473 | /* If iIdx is non-zero, then it is the number of a prefix-index for |
| 248474 | ** prefixes 1 character longer than the prefix being queried for. That |
| 248475 | ** index contains all the doclists required, except for the one |
| 248476 | ** corresponding to the prefix itself. That one is extracted from the |
| 248477 | ** main term index here. */ |
| 248478 | if( iIdx!=0 ){ |
| 248479 | int dummy = 0; |
| 248480 | const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT; |
| 248481 | pToken[0] = FTS5_MAIN_PREFIX; |
| 248482 | fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1); |
| 248483 | fts5IterSetOutputCb(&p->rc, p1); |
| 248484 | for(; |
| 248485 | fts5MultiIterEof(p, p1)==0; |
| 248486 | fts5MultiIterNext2(p, p1, &dummy) |
| 248487 | ){ |
| 248488 | Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; |
| 248489 | p1->xSetOutputs(p1, pSeg); |
| 248490 | if( p1->base.nData ){ |
| 248491 | xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); |
| 248492 | iLastRowid = p1->base.iRowid; |
| 248493 | } |
| 248494 | } |
| 248495 | fts5MultiIterFree(p1); |
| 248496 | } |
| 248497 | |
| 248498 | pToken[0] = FTS5_MAIN_PREFIX + iIdx; |
| 248499 | fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); |
| 248500 | fts5IterSetOutputCb(&p->rc, p1); |
| 248501 | |
| 248502 | for( /* no-op */ ; |
| 248503 | fts5MultiIterEof(p, p1)==0; |
| 248504 | fts5MultiIterNext2(p, p1, &bNewTerm) |
| 248505 | ){ |
| 248506 | Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; |
| 248507 | int nTerm = pSeg->term.n; |
| 248508 | const u8 *pTerm = pSeg->term.p; |
| 248509 | p1->xSetOutputs(p1, pSeg); |
| 248510 | |
| 248511 | assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); |
| 248512 | if( bNewTerm ){ |
| 248513 | if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break; |
| 248514 | } |
| 248515 | |
| 248516 | if( p1->base.nData==0 ) continue; |
| 248517 | if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ |
| 248518 | for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ |
| 248519 | int i1 = i*nMerge; |
| 248520 | int iStore; |
| 248521 | assert( i1+nMerge<=nBuf ); |
| 248522 | for(iStore=i1; iStore<i1+nMerge; iStore++){ |
| 248523 | if( aBuf[iStore].n==0 ){ |
| 248524 | fts5BufferSwap(&doclist, &aBuf[iStore]); |
| 248525 | fts5BufferZero(&doclist); |
| 248526 | break; |
| 248527 | } |
| 248528 | } |
| 248529 | if( iStore==i1+nMerge ){ |
| 248530 | xMerge(p, &doclist, nMerge, &aBuf[i1]); |
| 248531 | for(iStore=i1; iStore<i1+nMerge; iStore++){ |
| 248532 | fts5BufferZero(&aBuf[iStore]); |
| 248533 | } |
| 248534 | } |
| 248535 | } |
| 248536 | iLastRowid = 0; |
| 248537 | } |
| 248538 | |
| 248539 | xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist); |
| 248540 | iLastRowid = p1->base.iRowid; |
| 248541 | } |
| 248542 | |
| 248543 | assert( (nBuf%nMerge)==0 ); |
| 248544 | for(i=0; i<nBuf; i+=nMerge){ |
| 248545 | int iFree; |
| 248546 | if( p->rc==SQLITE_OK ){ |
| 248547 | xMerge(p, &doclist, nMerge, &aBuf[i]); |
| 248548 | } |
| 248549 | for(iFree=i; iFree<i+nMerge; iFree++){ |
| 248550 | fts5BufferFree(&aBuf[iFree]); |
| 248551 | } |
| 248552 | } |
| 248553 | fts5MultiIterFree(p1); |
| 248554 | |
| 248555 | pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING); |
| 248556 | if( pData ){ |
| 248557 | pData->p = (u8*)&pData[1]; |
| 248558 | pData->nn = pData->szLeaf = doclist.n; |
| 248559 | if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); |
| 248560 | fts5MultiIterNew2(p, pData, bDesc, ppIter); |
| 248561 | } |
| 248562 | fts5BufferFree(&doclist); |
| 248563 | } |
| 248564 | |
| 248565 | fts5StructureRelease(pStruct); |
| 248566 | sqlite3_free(aBuf); |
| 248567 | } |
| 248568 | |
| 248569 | |
| 248570 | /* |
| 248571 | ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain |
| @@ -248815,42 +249356,10 @@ | |
| 248815 | static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ |
| 248816 | fts5DataRelease(pSeg->pLeaf); |
| 248817 | pSeg->pLeaf = 0; |
| 248818 | } |
| 248819 | |
| 248820 | /* |
| 248821 | ** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an |
| 248822 | ** array of these for each row it visits. Or, for an iterator used by an |
| 248823 | ** "ORDER BY rank" query, it accumulates an array of these for the entire |
| 248824 | ** query. |
| 248825 | ** |
| 248826 | ** Each instance in the array indicates the iterator (and therefore term) |
| 248827 | ** associated with position iPos of rowid iRowid. This is used by the |
| 248828 | ** xInstToken() API. |
| 248829 | */ |
| 248830 | struct Fts5TokenDataMap { |
| 248831 | i64 iRowid; /* Row this token is located in */ |
| 248832 | i64 iPos; /* Position of token */ |
| 248833 | int iIter; /* Iterator token was read from */ |
| 248834 | }; |
| 248835 | |
| 248836 | /* |
| 248837 | ** An object used to supplement Fts5Iter for tokendata=1 iterators. |
| 248838 | */ |
| 248839 | struct Fts5TokenDataIter { |
| 248840 | int nIter; |
| 248841 | int nIterAlloc; |
| 248842 | |
| 248843 | int nMap; |
| 248844 | int nMapAlloc; |
| 248845 | Fts5TokenDataMap *aMap; |
| 248846 | |
| 248847 | Fts5PoslistReader *aPoslistReader; |
| 248848 | int *aPoslistToIter; |
| 248849 | Fts5Iter *apIter[1]; |
| 248850 | }; |
| 248851 | |
| 248852 | /* |
| 248853 | ** This function appends iterator pAppend to Fts5TokenDataIter pIn and |
| 248854 | ** returns the result. |
| 248855 | */ |
| 248856 | static Fts5TokenDataIter *fts5AppendTokendataIter( |
| @@ -248883,58 +249392,10 @@ | |
| 248883 | assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); |
| 248884 | |
| 248885 | return pRet; |
| 248886 | } |
| 248887 | |
| 248888 | /* |
| 248889 | ** Delete an Fts5TokenDataIter structure and its contents. |
| 248890 | */ |
| 248891 | static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ |
| 248892 | if( pSet ){ |
| 248893 | int ii; |
| 248894 | for(ii=0; ii<pSet->nIter; ii++){ |
| 248895 | fts5MultiIterFree(pSet->apIter[ii]); |
| 248896 | } |
| 248897 | sqlite3_free(pSet->aPoslistReader); |
| 248898 | sqlite3_free(pSet->aMap); |
| 248899 | sqlite3_free(pSet); |
| 248900 | } |
| 248901 | } |
| 248902 | |
| 248903 | /* |
| 248904 | ** Append a mapping to the token-map belonging to object pT. |
| 248905 | */ |
| 248906 | static void fts5TokendataIterAppendMap( |
| 248907 | Fts5Index *p, |
| 248908 | Fts5TokenDataIter *pT, |
| 248909 | int iIter, |
| 248910 | i64 iRowid, |
| 248911 | i64 iPos |
| 248912 | ){ |
| 248913 | if( p->rc==SQLITE_OK ){ |
| 248914 | if( pT->nMap==pT->nMapAlloc ){ |
| 248915 | int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; |
| 248916 | int nByte = nNew * sizeof(Fts5TokenDataMap); |
| 248917 | Fts5TokenDataMap *aNew; |
| 248918 | |
| 248919 | aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte); |
| 248920 | if( aNew==0 ){ |
| 248921 | p->rc = SQLITE_NOMEM; |
| 248922 | return; |
| 248923 | } |
| 248924 | |
| 248925 | pT->aMap = aNew; |
| 248926 | pT->nMapAlloc = nNew; |
| 248927 | } |
| 248928 | |
| 248929 | pT->aMap[pT->nMap].iRowid = iRowid; |
| 248930 | pT->aMap[pT->nMap].iPos = iPos; |
| 248931 | pT->aMap[pT->nMap].iIter = iIter; |
| 248932 | pT->nMap++; |
| 248933 | } |
| 248934 | } |
| 248935 | |
| 248936 | /* |
| 248937 | ** The iterator passed as the only argument must be a tokendata=1 iterator |
| 248938 | ** (pIter->pTokenDataIter!=0). This function sets the iterator output |
| 248939 | ** variables (pIter->base.*) according to the contents of the current |
| 248940 | ** row. |
| @@ -248971,11 +249432,11 @@ | |
| 248971 | int eDetail = pIter->pIndex->pConfig->eDetail; |
| 248972 | pIter->base.bEof = 0; |
| 248973 | pIter->base.iRowid = iRowid; |
| 248974 | |
| 248975 | if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ |
| 248976 | fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1); |
| 248977 | }else |
| 248978 | if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ |
| 248979 | int nReader = 0; |
| 248980 | int nByte = 0; |
| 248981 | i64 iPrev = 0; |
| @@ -249224,10 +249685,11 @@ | |
| 249224 | |
| 249225 | if( p->rc==SQLITE_OK ){ |
| 249226 | pRet = fts5MultiIterAlloc(p, 0); |
| 249227 | } |
| 249228 | if( pRet ){ |
| 249229 | pRet->pTokenDataIter = pSet; |
| 249230 | if( pSet ){ |
| 249231 | fts5IterSetOutputsTokendata(pRet); |
| 249232 | }else{ |
| 249233 | pRet->base.bEof = 1; |
| @@ -249238,11 +249700,10 @@ | |
| 249238 | |
| 249239 | fts5StructureRelease(pStruct); |
| 249240 | fts5BufferFree(&bSeek); |
| 249241 | return pRet; |
| 249242 | } |
| 249243 | |
| 249244 | |
| 249245 | /* |
| 249246 | ** Open a new iterator to iterate though all rowid that match the |
| 249247 | ** specified token or token prefix. |
| 249248 | */ |
| @@ -249262,12 +249723,18 @@ | |
| 249262 | |
| 249263 | if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ |
| 249264 | int iIdx = 0; /* Index to search */ |
| 249265 | int iPrefixIdx = 0; /* +1 prefix index */ |
| 249266 | int bTokendata = pConfig->bTokendata; |
| 249267 | if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); |
| 249268 | |
| 249269 | if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ |
| 249270 | bTokendata = 0; |
| 249271 | } |
| 249272 | |
| 249273 | /* Figure out which index to search and set iIdx accordingly. If this |
| @@ -249294,11 +249761,11 @@ | |
| 249294 | if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; |
| 249295 | } |
| 249296 | } |
| 249297 | |
| 249298 | if( bTokendata && iIdx==0 ){ |
| 249299 | buf.p[0] = '0'; |
| 249300 | pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); |
| 249301 | }else if( iIdx<=pConfig->nPrefix ){ |
| 249302 | /* Straight index lookup */ |
| 249303 | Fts5Structure *pStruct = fts5StructureRead(p); |
| 249304 | buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); |
| @@ -249307,11 +249774,11 @@ | |
| 249307 | pColset, buf.p, nToken+1, -1, 0, &pRet |
| 249308 | ); |
| 249309 | fts5StructureRelease(pStruct); |
| 249310 | } |
| 249311 | }else{ |
| 249312 | /* Scan multiple terms in the main index */ |
| 249313 | int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; |
| 249314 | fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); |
| 249315 | if( pRet==0 ){ |
| 249316 | assert( p->rc!=SQLITE_OK ); |
| 249317 | }else{ |
| @@ -249343,11 +249810,12 @@ | |
| 249343 | ** Move to the next matching rowid. |
| 249344 | */ |
| 249345 | static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ |
| 249346 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249347 | assert( pIter->pIndex->rc==SQLITE_OK ); |
| 249348 | if( pIter->pTokenDataIter ){ |
| 249349 | fts5TokendataIterNext(pIter, 0, 0); |
| 249350 | }else{ |
| 249351 | fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); |
| 249352 | } |
| 249353 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249380,11 +249848,12 @@ | |
| 249380 | ** definition of "at or after" depends on whether this iterator iterates |
| 249381 | ** in ascending or descending rowid order. |
| 249382 | */ |
| 249383 | static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ |
| 249384 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249385 | if( pIter->pTokenDataIter ){ |
| 249386 | fts5TokendataIterNext(pIter, 1, iMatch); |
| 249387 | }else{ |
| 249388 | fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); |
| 249389 | } |
| 249390 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249398,32 +249867,88 @@ | |
| 249398 | const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); |
| 249399 | assert_nc( z || n<=1 ); |
| 249400 | *pn = n-1; |
| 249401 | return (z ? &z[1] : 0); |
| 249402 | } |
| 249403 | |
| 249404 | /* |
| 249405 | ** This is used by xInstToken() to access the token at offset iOff, column |
| 249406 | ** iCol of row iRowid. The token is returned via output variables *ppOut |
| 249407 | ** and *pnOut. The iterator passed as the first argument must be a tokendata=1 |
| 249408 | ** iterator (pIter->pTokenDataIter!=0). |
| 249409 | */ |
| 249410 | static int sqlite3Fts5IterToken( |
| 249411 | Fts5IndexIter *pIndexIter, |
| 249412 | i64 iRowid, |
| 249413 | int iCol, |
| 249414 | int iOff, |
| 249415 | const char **ppOut, int *pnOut |
| 249416 | ){ |
| 249417 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249418 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 249419 | Fts5TokenDataMap *aMap = pT->aMap; |
| 249420 | i64 iPos = (((i64)iCol)<<32) + iOff; |
| 249421 | |
| 249422 | int i1 = 0; |
| 249423 | int i2 = pT->nMap; |
| 249424 | int iTest = 0; |
| 249425 | |
| 249426 | while( i2>i1 ){ |
| 249427 | iTest = (i1 + i2) / 2; |
| 249428 | |
| 249429 | if( aMap[iTest].iRowid<iRowid ){ |
| @@ -249443,13 +249968,19 @@ | |
| 249443 | } |
| 249444 | } |
| 249445 | } |
| 249446 | |
| 249447 | if( i2>i1 ){ |
| 249448 | Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; |
| 249449 | *ppOut = (const char*)pMap->aSeg[0].term.p+1; |
| 249450 | *pnOut = pMap->aSeg[0].term.n-1; |
| 249451 | } |
| 249452 | |
| 249453 | return SQLITE_OK; |
| 249454 | } |
| 249455 | |
| @@ -249457,11 +249988,13 @@ | |
| 249457 | ** Clear any existing entries from the token-map associated with the |
| 249458 | ** iterator passed as the only argument. |
| 249459 | */ |
| 249460 | static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ |
| 249461 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249462 | if( pIter && pIter->pTokenDataIter ){ |
| 249463 | pIter->pTokenDataIter->nMap = 0; |
| 249464 | } |
| 249465 | } |
| 249466 | |
| 249467 | /* |
| @@ -249477,21 +250010,33 @@ | |
| 249477 | i64 iRowid, int iCol, int iOff |
| 249478 | ){ |
| 249479 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249480 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 249481 | Fts5Index *p = pIter->pIndex; |
| 249482 | int ii; |
| 249483 | |
| 249484 | assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); |
| 249485 | assert( pIter->pTokenDataIter ); |
| 249486 | |
| 249487 | for(ii=0; ii<pT->nIter; ii++){ |
| 249488 | Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; |
| 249489 | if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; |
| 249490 | } |
| 249491 | if( ii<pT->nIter ){ |
| 249492 | fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff); |
| 249493 | } |
| 249494 | return fts5IndexReturn(p); |
| 249495 | } |
| 249496 | |
| 249497 | /* |
| @@ -251392,10 +251937,11 @@ | |
| 251392 | ** containing a copy of the header from an Fts5Config pointer. |
| 251393 | */ |
| 251394 | #define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr )) |
| 251395 | #define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr)) |
| 251396 | |
| 251397 | |
| 251398 | /* |
| 251399 | ** Each auxiliary function registered with the FTS5 module is represented |
| 251400 | ** by an object of the following type. All such objects are stored as part |
| 251401 | ** of the Fts5Global.pAux list. |
| @@ -251931,10 +252477,11 @@ | |
| 251931 | ){ |
| 251932 | /* A MATCH operator or equivalent */ |
| 251933 | if( p->usable==0 || iCol<0 ){ |
| 251934 | /* As there exists an unusable MATCH constraint this is an |
| 251935 | ** unusable plan. Return SQLITE_CONSTRAINT. */ |
| 251936 | return SQLITE_CONSTRAINT; |
| 251937 | }else{ |
| 251938 | if( iCol==nCol+1 ){ |
| 251939 | if( bSeenRank ) continue; |
| 251940 | idxStr[iIdxStr++] = 'r'; |
| @@ -252716,10 +253263,11 @@ | |
| 252716 | sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ |
| 252717 | sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ |
| 252718 | sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ |
| 252719 | int iCol; /* Column on LHS of MATCH operator */ |
| 252720 | char **pzErrmsg = pConfig->pzErrmsg; |
| 252721 | int i; |
| 252722 | int iIdxStr = 0; |
| 252723 | Fts5Expr *pExpr = 0; |
| 252724 | |
| 252725 | assert( pConfig->bLock==0 ); |
| @@ -252751,10 +253299,13 @@ | |
| 252751 | int bInternal = 0; |
| 252752 | |
| 252753 | rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset); |
| 252754 | if( rc!=SQLITE_OK ) goto filter_out; |
| 252755 | if( zText==0 ) zText = ""; |
| 252756 | |
| 252757 | iCol = 0; |
| 252758 | do{ |
| 252759 | iCol = iCol*10 + (idxStr[iIdxStr]-'0'); |
| 252760 | iIdxStr++; |
| @@ -252891,10 +253442,11 @@ | |
| 252891 | } |
| 252892 | |
| 252893 | filter_out: |
| 252894 | sqlite3Fts5ExprFree(pExpr); |
| 252895 | pConfig->pzErrmsg = pzErrmsg; |
| 252896 | return rc; |
| 252897 | } |
| 252898 | |
| 252899 | /* |
| 252900 | ** This is the xEof method of the virtual table. SQLite calls this |
| @@ -254886,11 +255438,11 @@ | |
| 254886 | int nArg, /* Number of args */ |
| 254887 | sqlite3_value **apUnused /* Function arguments */ |
| 254888 | ){ |
| 254889 | assert( nArg==0 ); |
| 254890 | UNUSED_PARAM2(nArg, apUnused); |
| 254891 | sqlite3_result_text(pCtx, "fts5: 2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e", -1, SQLITE_TRANSIENT); |
| 254892 | } |
| 254893 | |
| 254894 | /* |
| 254895 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 254896 | ** |
| @@ -254949,10 +255501,24 @@ | |
| 254949 | assert( &pCsr[nText]==&pBlob[nBlob] ); |
| 254950 | |
| 254951 | sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free); |
| 254952 | } |
| 254953 | } |
| 254954 | |
| 254955 | /* |
| 254956 | ** Return true if zName is the extension on one of the shadow tables used |
| 254957 | ** by this module. |
| 254958 | */ |
| @@ -255079,13 +255645,20 @@ | |
| 255079 | ); |
| 255080 | } |
| 255081 | if( rc==SQLITE_OK ){ |
| 255082 | rc = sqlite3_create_function( |
| 255083 | db, "fts5_locale", 2, |
| 255084 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, |
| 255085 | p, fts5LocaleFunc, 0, 0 |
| 255086 | ); |
| 255087 | } |
| 255088 | } |
| 255089 | |
| 255090 | /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file |
| 255091 | ** fts5_test_mi.c is compiled and linked into the executable. And call |
| @@ -258007,22 +258580,22 @@ | |
| 258007 | int rc = SQLITE_OK; |
| 258008 | char aBuf[32]; |
| 258009 | char *zOut = aBuf; |
| 258010 | int ii; |
| 258011 | const unsigned char *zIn = (const unsigned char*)pText; |
| 258012 | const unsigned char *zEof = &zIn[nText]; |
| 258013 | u32 iCode; |
| 258014 | int aStart[3]; /* Input offset of each character in aBuf[] */ |
| 258015 | |
| 258016 | UNUSED_PARAM(unusedFlags); |
| 258017 | |
| 258018 | /* Populate aBuf[] with the characters for the first trigram. */ |
| 258019 | for(ii=0; ii<3; ii++){ |
| 258020 | do { |
| 258021 | aStart[ii] = zIn - (const unsigned char*)pText; |
| 258022 | READ_UTF8(zIn, zEof, iCode); |
| 258023 | if( iCode==0 ) return SQLITE_OK; |
| 258024 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258025 | }while( iCode==0 ); |
| 258026 | WRITE_UTF8(zOut, iCode); |
| 258027 | } |
| 258028 | |
| @@ -258039,12 +258612,15 @@ | |
| 258039 | const char *z1; |
| 258040 | |
| 258041 | /* Read characters from the input up until the first non-diacritic */ |
| 258042 | do { |
| 258043 | iNext = zIn - (const unsigned char*)pText; |
| 258044 | READ_UTF8(zIn, zEof, iCode); |
| 258045 | if( iCode==0 ) break; |
| 258046 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258047 | }while( iCode==0 ); |
| 258048 | |
| 258049 | /* Pass the current trigram back to fts5 */ |
| 258050 | rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); |
| @@ -260077,11 +260653,11 @@ | |
| 260077 | |
| 260078 | return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0); |
| 260079 | } |
| 260080 | |
| 260081 | |
| 260082 | |
| 260083 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ |
| 260084 | |
| 260085 | /************** End of fts5.c ************************************************/ |
| 260086 | /************** Begin file stmt.c ********************************************/ |
| 260087 | /* |
| @@ -260433,6 +261009,7 @@ | |
| 260433 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ |
| 260434 | |
| 260435 | /************** End of stmt.c ************************************************/ |
| 260436 | /* Return the source-id for this library */ |
| 260437 | SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } |
| 260438 | /************************** End of sqlite3.c ******************************/ |
| 260439 |
| --- extsrc/sqlite3.c | |
| +++ extsrc/sqlite3.c | |
| @@ -1,8 +1,8 @@ | |
| 1 | /****************************************************************************** |
| 2 | ** This file is an amalgamation of many separate C source files from SQLite |
| 3 | ** version 3.48.0. By combining all the individual C code files into this |
| 4 | ** single large file, the entire code can be compiled as a single translation |
| 5 | ** unit. This allows many compilers to do optimizations that would not be |
| 6 | ** possible if the files were compiled separately. Performance improvements |
| 7 | ** of 5% or more are commonly seen when SQLite is compiled as a single |
| 8 | ** translation unit. |
| @@ -16,12 +16,15 @@ | |
| 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | ** |
| 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | ** 2b17bc49655c577029919c2d409de994b0d2 with changes in files: |
| 22 | ** |
| 23 | ** |
| 24 | */ |
| 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | #define SQLITE_CORE 1 |
| 27 | #define SQLITE_AMALGAMATION 1 |
| 28 | #ifndef SQLITE_PRIVATE |
| 29 | # define SQLITE_PRIVATE static |
| 30 | #endif |
| @@ -460,13 +463,13 @@ | |
| 463 | ** |
| 464 | ** See also: [sqlite3_libversion()], |
| 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | */ |
| 468 | #define SQLITE_VERSION "3.48.0" |
| 469 | #define SQLITE_VERSION_NUMBER 3048000 |
| 470 | #define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -966,10 +969,17 @@ | |
| 969 | ** |
| 970 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 971 | ** filesystem supports doing multiple write operations atomically when those |
| 972 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 973 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 974 | ** |
| 975 | ** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read |
| 976 | ** from the database file in amounts that are not a multiple of the |
| 977 | ** page size and that do not begin at a page boundary. Without this |
| 978 | ** property, SQLite is careful to only do full-page reads and write |
| 979 | ** on aligned pages, with the one exception that it will do a sub-page |
| 980 | ** read of the first page to access the database header. |
| 981 | */ |
| 982 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 983 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 984 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 985 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -982,10 +992,11 @@ | |
| 992 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 993 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 994 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 995 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 996 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 997 | #define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 |
| 998 | |
| 999 | /* |
| 1000 | ** CAPI3REF: File Locking Levels |
| 1001 | ** |
| 1002 | ** SQLite uses one of these integer values as the second |
| @@ -1128,10 +1139,11 @@ | |
| 1139 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 1140 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 1141 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 1142 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 1143 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 1144 | ** <li> [SQLITE_IOCAP_SUBPAGE_READ] |
| 1145 | ** </ul> |
| 1146 | ** |
| 1147 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 1148 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 1149 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1405,10 +1417,15 @@ | |
| 1417 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1418 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1419 | ** pointed to by the pArg argument. This capability is used during testing |
| 1420 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1421 | ** |
| 1422 | ** <li>[[SQLITE_FCNTL_NULL_IO]] |
| 1423 | ** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor |
| 1424 | ** or file handle for the [sqlite3_file] object such that it will no longer |
| 1425 | ** read or write to the database file. |
| 1426 | ** |
| 1427 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1428 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1429 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1430 | ** available. The WAL subsystem issues this signal during rare |
| 1431 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1558,10 +1575,11 @@ | |
| 1575 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1576 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1577 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1578 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1579 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1580 | #define SQLITE_FCNTL_NULL_IO 43 |
| 1581 | |
| 1582 | /* deprecated names */ |
| 1583 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1584 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1585 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2936,14 +2954,18 @@ | |
| 2954 | ** |
| 2955 | ** ^These functions return the number of rows modified, inserted or |
| 2956 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2957 | ** statement on the database connection specified by the only parameter. |
| 2958 | ** The two functions are identical except for the type of the return value |
| 2959 | ** and that if the number of rows modified by the most recent INSERT, UPDATE, |
| 2960 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2961 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2962 | ** type of SQL statement does not modify the value returned by these functions. |
| 2963 | ** For the purposes of this interface, a CREATE TABLE AS SELECT statement |
| 2964 | ** does not count as an INSERT, UPDATE or DELETE statement and hence the rows |
| 2965 | ** added to the new table by the CREATE TABLE AS SELECT statement are not |
| 2966 | ** counted. |
| 2967 | ** |
| 2968 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2969 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2970 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2971 | ** |
| @@ -4499,15 +4521,26 @@ | |
| 4521 | ** |
| 4522 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4523 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4524 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4525 | ** any virtual tables. |
| 4526 | ** |
| 4527 | ** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt> |
| 4528 | ** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler |
| 4529 | ** errors from being sent to the error log defined by |
| 4530 | ** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test |
| 4531 | ** compiles to see if some SQL syntax is well-formed, without generating |
| 4532 | ** messages on the global error log when it is not. If the test compile |
| 4533 | ** fails, the sqlite3_prepare_v3() call returns the same error indications |
| 4534 | ** with or without this flag; it just omits the call to [sqlite3_log()] that |
| 4535 | ** logs the error. |
| 4536 | ** </dl> |
| 4537 | */ |
| 4538 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4539 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4540 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4541 | #define SQLITE_PREPARE_DONT_LOG 0x10 |
| 4542 | |
| 4543 | /* |
| 4544 | ** CAPI3REF: Compiling An SQL Statement |
| 4545 | ** KEYWORDS: {SQL statement compiler} |
| 4546 | ** METHOD: sqlite3 |
| @@ -11194,11 +11227,11 @@ | |
| 11227 | #endif |
| 11228 | |
| 11229 | #if 0 |
| 11230 | } /* End of the 'extern "C"' block */ |
| 11231 | #endif |
| 11232 | /* #endif for SQLITE3_H will be added by mksqlite3.tcl */ |
| 11233 | |
| 11234 | /******** Begin file sqlite3rtree.h *********/ |
| 11235 | /* |
| 11236 | ** 2010 August 30 |
| 11237 | ** |
| @@ -13445,17 +13478,32 @@ | |
| 13478 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13479 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13480 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13481 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13482 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13483 | ** bytes. |
| 13484 | ** |
| 13485 | ** The output text is not a copy of the document text that was tokenized. |
| 13486 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13487 | ** includes any embedded 0x00 and trailing data. |
| 13488 | ** |
| 13489 | ** This API may be slow in some cases if the token identified by parameters |
| 13490 | ** iIdx and iToken matched a prefix token in the query. In most cases, the |
| 13491 | ** first call to this API for each prefix token in the query is forced |
| 13492 | ** to scan the portion of the full-text index that matches the prefix |
| 13493 | ** token to collect the extra data required by this API. If the prefix |
| 13494 | ** token matches a large number of token instances in the document set, |
| 13495 | ** this may be a performance problem. |
| 13496 | ** |
| 13497 | ** If the user knows in advance that a query may use this API for a |
| 13498 | ** prefix token, FTS5 may be configured to collect all required data as part |
| 13499 | ** of the initial querying of the full-text index, avoiding the second scan |
| 13500 | ** entirely. This also causes prefix queries that do not use this API to |
| 13501 | ** run more slowly and use more memory. FTS5 may be configured in this way |
| 13502 | ** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] |
| 13503 | ** option, or on a per-query basis using the |
| 13504 | ** [fts5_insttoken | fts5_insttoken()] user function. |
| 13505 | ** |
| 13506 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13507 | ** "detail=none" or "detail=column" option. |
| 13508 | ** |
| 13509 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13886,10 +13934,11 @@ | |
| 13934 | #endif |
| 13935 | |
| 13936 | #endif /* _FTS5_H */ |
| 13937 | |
| 13938 | /******** End of fts5.h *********/ |
| 13939 | #endif /* SQLITE3_H */ |
| 13940 | |
| 13941 | /************** End of sqlite3.h *********************************************/ |
| 13942 | /************** Continuing where we left off in sqliteInt.h ******************/ |
| 13943 | |
| 13944 | /* |
| @@ -13931,10 +13980,11 @@ | |
| 13980 | ** to count the size: 2^31-1 or 2147483647. |
| 13981 | */ |
| 13982 | #ifndef SQLITE_MAX_LENGTH |
| 13983 | # define SQLITE_MAX_LENGTH 1000000000 |
| 13984 | #endif |
| 13985 | #define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */ |
| 13986 | |
| 13987 | /* |
| 13988 | ** This is the maximum number of |
| 13989 | ** |
| 13990 | ** * Columns in a table |
| @@ -13996,13 +14046,17 @@ | |
| 14046 | # define SQLITE_MAX_VDBE_OP 250000000 |
| 14047 | #endif |
| 14048 | |
| 14049 | /* |
| 14050 | ** The maximum number of arguments to an SQL function. |
| 14051 | ** |
| 14052 | ** This value has a hard upper limit of 32767 due to storage |
| 14053 | ** constraints (it needs to fit inside a i16). We keep it |
| 14054 | ** lower than that to prevent abuse. |
| 14055 | */ |
| 14056 | #ifndef SQLITE_MAX_FUNCTION_ARG |
| 14057 | # define SQLITE_MAX_FUNCTION_ARG 1000 |
| 14058 | #endif |
| 14059 | |
| 14060 | /* |
| 14061 | ** The suggested maximum number of in-memory pages to use for |
| 14062 | ** the main database table and for temporary tables. |
| @@ -16000,10 +16054,26 @@ | |
| 16054 | #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ |
| 16055 | #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ |
| 16056 | #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ |
| 16057 | #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ |
| 16058 | |
| 16059 | #define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL) |
| 16060 | |
| 16061 | /* |
| 16062 | ** The argument to this macro is a file descriptor (type sqlite3_file*). |
| 16063 | ** Return 0 if it is not open, or non-zero (but not 1) if it is. |
| 16064 | ** |
| 16065 | ** This is so that expressions can be written as: |
| 16066 | ** |
| 16067 | ** if( isOpen(pPager->jfd) ){ ... |
| 16068 | ** |
| 16069 | ** instead of |
| 16070 | ** |
| 16071 | ** if( pPager->jfd->pMethods ){ ... |
| 16072 | */ |
| 16073 | #define isOpen(pFd) ((pFd)->pMethods!=0) |
| 16074 | |
| 16075 | /* |
| 16076 | ** Flags that make up the mask passed to sqlite3PagerGet(). |
| 16077 | */ |
| 16078 | #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ |
| 16079 | #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ |
| @@ -17025,11 +17095,11 @@ | |
| 17095 | |
| 17096 | /* |
| 17097 | ** Additional non-public SQLITE_PREPARE_* flags |
| 17098 | */ |
| 17099 | #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ |
| 17100 | #define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ |
| 17101 | |
| 17102 | /* |
| 17103 | ** Prototypes for the VDBE interface. See comments on the implementation |
| 17104 | ** for a description of what each of these routines does. |
| 17105 | */ |
| @@ -17740,51 +17810,15 @@ | |
| 17810 | struct FuncDefHash { |
| 17811 | FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */ |
| 17812 | }; |
| 17813 | #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) |
| 17814 | |
| 17815 | /* |
| 17816 | ** typedef for the authorization callback function. |
| 17817 | */ |
| 17818 | typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, |
| 17819 | const char*); |
| 17820 | |
| 17821 | #ifndef SQLITE_OMIT_DEPRECATED |
| 17822 | /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing |
| 17823 | ** in the style of sqlite3_trace() |
| 17824 | */ |
| @@ -17941,13 +17975,10 @@ | |
| 17975 | sqlite3 *pUnlockConnection; /* Connection to watch for unlock */ |
| 17976 | void *pUnlockArg; /* Argument to xUnlockNotify */ |
| 17977 | void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ |
| 17978 | sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ |
| 17979 | #endif |
| 17980 | }; |
| 17981 | |
| 17982 | /* |
| 17983 | ** A macro to discover the encoding of a database. |
| 17984 | */ |
| @@ -18102,11 +18133,11 @@ | |
| 18133 | ** |
| 18134 | ** The u.pHash field is used by the global built-ins. The u.pDestructor |
| 18135 | ** field is used by per-connection app-def functions. |
| 18136 | */ |
| 18137 | struct FuncDef { |
| 18138 | i16 nArg; /* Number of arguments. -1 means unlimited */ |
| 18139 | u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ |
| 18140 | void *pUserData; /* User data parameter */ |
| 18141 | FuncDef *pNext; /* Next function with same name */ |
| 18142 | void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */ |
| 18143 | void (*xFinalize)(sqlite3_context*); /* Agg finalizer */ |
| @@ -22850,13 +22881,10 @@ | |
| 22881 | "UNLINK_AFTER_CLOSE", |
| 22882 | #endif |
| 22883 | #ifdef SQLITE_UNTESTABLE |
| 22884 | "UNTESTABLE", |
| 22885 | #endif |
| 22886 | #ifdef SQLITE_USE_ALLOCA |
| 22887 | "USE_ALLOCA", |
| 22888 | #endif |
| 22889 | #ifdef SQLITE_USE_FCNTL_TRACE |
| 22890 | "USE_FCNTL_TRACE", |
| @@ -23700,11 +23728,11 @@ | |
| 23728 | Vdbe *pVdbe; /* The VM that owns this context */ |
| 23729 | int iOp; /* Instruction number of OP_Function */ |
| 23730 | int isError; /* Error code returned by the function. */ |
| 23731 | u8 enc; /* Encoding to use for results */ |
| 23732 | u8 skipFlag; /* Skip accumulator loading if true */ |
| 23733 | u16 argc; /* Number of arguments */ |
| 23734 | sqlite3_value *argv[1]; /* Argument set */ |
| 23735 | }; |
| 23736 | |
| 23737 | /* A bitfield type for use inside of structures. Always follow with :N where |
| 23738 | ** N is the number of bits. |
| @@ -23847,10 +23875,11 @@ | |
| 23875 | UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ |
| 23876 | int iNewReg; /* Register for new.* values */ |
| 23877 | int iBlobWrite; /* Value returned by preupdate_blobwrite() */ |
| 23878 | i64 iKey1; /* First key value passed to hook */ |
| 23879 | i64 iKey2; /* Second key value passed to hook */ |
| 23880 | Mem oldipk; /* Memory cell holding "old" IPK value */ |
| 23881 | Mem *aNew; /* Array of new.* values */ |
| 23882 | Table *pTab; /* Schema object being updated */ |
| 23883 | Index *pPk; /* PK index if pTab is WITHOUT ROWID */ |
| 23884 | sqlite3_value **apDflt; /* Array of default values, if required */ |
| 23885 | }; |
| @@ -32296,10 +32325,11 @@ | |
| 32325 | && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) |
| 32326 | ){ |
| 32327 | pExpr = pExpr->pLeft; |
| 32328 | } |
| 32329 | if( pExpr==0 ) return; |
| 32330 | if( ExprHasProperty(pExpr, EP_FromDDL) ) return; |
| 32331 | db->errByteOffset = pExpr->w.iOfst; |
| 32332 | } |
| 32333 | |
| 32334 | /* |
| 32335 | ** Enlarge the memory allocation on a StrAccum object so that it is |
| @@ -33025,11 +33055,11 @@ | |
| 33055 | } |
| 33056 | if( pItem->fg.isCte ){ |
| 33057 | sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse); |
| 33058 | } |
| 33059 | if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ |
| 33060 | sqlite3_str_appendf(&x, " isOn"); |
| 33061 | } |
| 33062 | if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); |
| 33063 | if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); |
| 33064 | if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); |
| 33065 | if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); |
| @@ -34109,10 +34139,14 @@ | |
| 34139 | ** |
| 34140 | ** This routines are given external linkage so that they will always be |
| 34141 | ** accessible to the debugging, and to avoid warnings about unused |
| 34142 | ** functions. But these routines only exist in debugging builds, so they |
| 34143 | ** do not contaminate the interface. |
| 34144 | ** |
| 34145 | ** See Also: |
| 34146 | ** |
| 34147 | ** sqlite3ShowWhereTerm() in where.c |
| 34148 | */ |
| 34149 | SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } |
| 34150 | SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} |
| 34151 | SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } |
| 34152 | SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } |
| @@ -35685,12 +35719,12 @@ | |
| 35719 | int esign = 1; /* sign of exponent */ |
| 35720 | int e = 0; /* exponent */ |
| 35721 | int eValid = 1; /* True exponent is either not used or is well-formed */ |
| 35722 | int nDigit = 0; /* Number of digits processed */ |
| 35723 | int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ |
| 35724 | u64 s2; /* round-tripped significand */ |
| 35725 | double rr[2]; |
| 35726 | |
| 35727 | assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); |
| 35728 | *pResult = 0.0; /* Default return value, in case of an error */ |
| 35729 | if( length==0 ) return 0; |
| 35730 | |
| @@ -35789,25 +35823,36 @@ | |
| 35823 | |
| 35824 | /* adjust exponent by d, and update sign */ |
| 35825 | e = (e*esign) + d; |
| 35826 | |
| 35827 | /* Try to adjust the exponent to make it smaller */ |
| 35828 | while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){ |
| 35829 | s *= 10; |
| 35830 | e--; |
| 35831 | } |
| 35832 | while( e<0 && (s%10)==0 ){ |
| 35833 | s /= 10; |
| 35834 | e++; |
| 35835 | } |
| 35836 | |
| 35837 | rr[0] = (double)s; |
| 35838 | assert( sizeof(s2)==sizeof(rr[0]) ); |
| 35839 | #ifdef SQLITE_DEBUG |
| 35840 | rr[1] = 18446744073709549568.0; |
| 35841 | memcpy(&s2, &rr[1], sizeof(s2)); |
| 35842 | assert( s2==0x43efffffffffffffLL ); |
| 35843 | #endif |
| 35844 | /* Largest double that can be safely converted to u64 |
| 35845 | ** vvvvvvvvvvvvvvvvvvvvvv */ |
| 35846 | if( rr[0]<=18446744073709549568.0 ){ |
| 35847 | s2 = (u64)rr[0]; |
| 35848 | rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); |
| 35849 | }else{ |
| 35850 | rr[1] = 0.0; |
| 35851 | } |
| 35852 | assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */ |
| 35853 | |
| 35854 | if( e>0 ){ |
| 35855 | while( e>=100 ){ |
| 35856 | e -= 100; |
| 35857 | dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); |
| 35858 | } |
| @@ -38674,11 +38719,11 @@ | |
| 38719 | # define F_SETLKW 7 |
| 38720 | # endif |
| 38721 | # endif |
| 38722 | #else /* !SQLITE_WASI */ |
| 38723 | # ifndef HAVE_FCHMOD |
| 38724 | # define HAVE_FCHMOD 1 |
| 38725 | # endif |
| 38726 | #endif /* SQLITE_WASI */ |
| 38727 | |
| 38728 | #ifdef SQLITE_WASI |
| 38729 | # define osGetpid(X) (pid_t)1 |
| @@ -42448,10 +42493,15 @@ | |
| 42493 | int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE); |
| 42494 | return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK; |
| 42495 | } |
| 42496 | #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ |
| 42497 | |
| 42498 | case SQLITE_FCNTL_NULL_IO: { |
| 42499 | osClose(pFile->h); |
| 42500 | pFile->h = -1; |
| 42501 | return SQLITE_OK; |
| 42502 | } |
| 42503 | case SQLITE_FCNTL_LOCKSTATE: { |
| 42504 | *(int*)pArg = pFile->eFileLock; |
| 42505 | return SQLITE_OK; |
| 42506 | } |
| 42507 | case SQLITE_FCNTL_LAST_ERRNO: { |
| @@ -42589,10 +42639,11 @@ | |
| 42639 | |
| 42640 | /* Set the POWERSAFE_OVERWRITE flag if requested. */ |
| 42641 | if( pFd->ctrlFlags & UNIXFILE_PSOW ){ |
| 42642 | pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; |
| 42643 | } |
| 42644 | pFd->deviceCharacteristics |= SQLITE_IOCAP_SUBPAGE_READ; |
| 42645 | |
| 42646 | pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; |
| 42647 | } |
| 42648 | } |
| 42649 | #else |
| @@ -50328,10 +50379,15 @@ | |
| 50379 | OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n", |
| 50380 | hOldFile, pFile->h)); |
| 50381 | return SQLITE_OK; |
| 50382 | } |
| 50383 | #endif |
| 50384 | case SQLITE_FCNTL_NULL_IO: { |
| 50385 | (void)osCloseHandle(pFile->h); |
| 50386 | pFile->h = NULL; |
| 50387 | return SQLITE_OK; |
| 50388 | } |
| 50389 | case SQLITE_FCNTL_TEMPFILENAME: { |
| 50390 | char *zTFile = 0; |
| 50391 | int rc = winGetTempname(pFile->pVfs, &zTFile); |
| 50392 | if( rc==SQLITE_OK ){ |
| 50393 | *(char**)pArg = zTFile; |
| @@ -50389,11 +50445,11 @@ | |
| 50445 | /* |
| 50446 | ** Return a vector of device characteristics. |
| 50447 | */ |
| 50448 | static int winDeviceCharacteristics(sqlite3_file *id){ |
| 50449 | winFile *p = (winFile*)id; |
| 50450 | return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | SQLITE_IOCAP_SUBPAGE_READ | |
| 50451 | ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); |
| 50452 | } |
| 50453 | |
| 50454 | /* |
| 50455 | ** Windows will only let you create file view mappings |
| @@ -51777,11 +51833,11 @@ | |
| 51833 | */ |
| 51834 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| 51835 | |
| 51836 | int rc = SQLITE_OK; /* Function Return Code */ |
| 51837 | #if !defined(NDEBUG) || SQLITE_OS_WINCE |
| 51838 | int eType = flags&0x0FFF00; /* Type of file to open */ |
| 51839 | #endif |
| 51840 | |
| 51841 | int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); |
| 51842 | int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); |
| 51843 | int isCreate = (flags & SQLITE_OPEN_CREATE); |
| @@ -57978,43 +58034,37 @@ | |
| 58034 | # define USEFETCH(x) ((x)->bUseFetch) |
| 58035 | #else |
| 58036 | # define USEFETCH(x) 0 |
| 58037 | #endif |
| 58038 | |
| 58039 | #ifdef SQLITE_DIRECT_OVERFLOW_READ |
| 58040 | /* |
| 58041 | ** Return true if page pgno can be read directly from the database file |
| 58042 | ** by the b-tree layer. This is the case if: |
| 58043 | ** |
| 58044 | ** (1) the database file is open |
| 58045 | ** (2) the VFS for the database is able to do unaligned sub-page reads |
| 58046 | ** (3) there are no dirty pages in the cache, and |
| 58047 | ** (4) the desired page is not currently in the wal file. |
| 58048 | */ |
| 58049 | SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ |
| 58050 | assert( pPager!=0 ); |
| 58051 | assert( pPager->fd!=0 ); |
| 58052 | if( pPager->fd->pMethods==0 ) return 0; /* Case (1) */ |
| 58053 | if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */ |
| 58054 | #ifndef SQLITE_OMIT_WAL |
| 58055 | if( pPager->pWal ){ |
| 58056 | u32 iRead = 0; |
| 58057 | (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); |
| 58058 | return iRead==0; /* Condition (4) */ |
| 58059 | } |
| 58060 | #endif |
| 58061 | assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); |
| 58062 | if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) |
| 58063 | & SQLITE_IOCAP_SUBPAGE_READ)==0 ){ |
| 58064 | return 0; /* Case (2) */ |
| 58065 | } |
| 58066 | return 1; |
| 58067 | } |
| 58068 | #endif |
| 58069 | |
| 58070 | #ifndef SQLITE_OMIT_WAL |
| @@ -59269,11 +59319,11 @@ | |
| 59319 | rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); |
| 59320 | } |
| 59321 | } |
| 59322 | pPager->journalOff = 0; |
| 59323 | }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST |
| 59324 | || (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL) |
| 59325 | ){ |
| 59326 | rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile); |
| 59327 | pPager->journalOff = 0; |
| 59328 | }else{ |
| 59329 | /* This branch may be executed with Pager.journalMode==MEMORY if |
| @@ -67979,15 +68029,11 @@ | |
| 68029 | ** so it takes care to hold an exclusive lock on the corresponding |
| 68030 | ** WAL_READ_LOCK() while changing values. |
| 68031 | */ |
| 68032 | static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ |
| 68033 | volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ |
| 68034 | int rc = SQLITE_OK; /* Return code */ |
| 68035 | #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| 68036 | int nBlockTmout = 0; |
| 68037 | #endif |
| 68038 | |
| 68039 | assert( pWal->readLock<0 ); /* Not currently locked */ |
| @@ -68089,145 +68135,151 @@ | |
| 68135 | |
| 68136 | assert( pWal->nWiData>0 ); |
| 68137 | assert( pWal->apWiData[0]!=0 ); |
| 68138 | pInfo = walCkptInfo(pWal); |
| 68139 | SEH_INJECT_FAULT; |
| 68140 | { |
| 68141 | u32 mxReadMark; /* Largest aReadMark[] value */ |
| 68142 | int mxI; /* Index of largest aReadMark[] value */ |
| 68143 | int i; /* Loop counter */ |
| 68144 | u32 mxFrame; /* Wal frame to lock to */ |
| 68145 | if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame |
| 68146 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| 68147 | && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0) |
| 68148 | #endif |
| 68149 | ){ |
| 68150 | /* The WAL has been completely backfilled (or it is empty). |
| 68151 | ** and can be safely ignored. |
| 68152 | */ |
| 68153 | rc = walLockShared(pWal, WAL_READ_LOCK(0)); |
| 68154 | walShmBarrier(pWal); |
| 68155 | if( rc==SQLITE_OK ){ |
| 68156 | if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr,sizeof(WalIndexHdr)) ){ |
| 68157 | /* It is not safe to allow the reader to continue here if frames |
| 68158 | ** may have been appended to the log before READ_LOCK(0) was obtained. |
| 68159 | ** When holding READ_LOCK(0), the reader ignores the entire log file, |
| 68160 | ** which implies that the database file contains a trustworthy |
| 68161 | ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from |
| 68162 | ** happening, this is usually correct. |
| 68163 | ** |
| 68164 | ** However, if frames have been appended to the log (or if the log |
| 68165 | ** is wrapped and written for that matter) before the READ_LOCK(0) |
| 68166 | ** is obtained, that is not necessarily true. A checkpointer may |
| 68167 | ** have started to backfill the appended frames but crashed before |
| 68168 | ** it finished. Leaving a corrupt image in the database file. |
| 68169 | */ |
| 68170 | walUnlockShared(pWal, WAL_READ_LOCK(0)); |
| 68171 | return WAL_RETRY; |
| 68172 | } |
| 68173 | pWal->readLock = 0; |
| 68174 | return SQLITE_OK; |
| 68175 | }else if( rc!=SQLITE_BUSY ){ |
| 68176 | return rc; |
| 68177 | } |
| 68178 | } |
| 68179 | |
| 68180 | /* If we get this far, it means that the reader will want to use |
| 68181 | ** the WAL to get at content from recent commits. The job now is |
| 68182 | ** to select one of the aReadMark[] entries that is closest to |
| 68183 | ** but not exceeding pWal->hdr.mxFrame and lock that entry. |
| 68184 | */ |
| 68185 | mxReadMark = 0; |
| 68186 | mxI = 0; |
| 68187 | mxFrame = pWal->hdr.mxFrame; |
| 68188 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| 68189 | if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){ |
| 68190 | mxFrame = pWal->pSnapshot->mxFrame; |
| 68191 | } |
| 68192 | #endif |
| 68193 | for(i=1; i<WAL_NREADER; i++){ |
| 68194 | u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; |
| 68195 | if( mxReadMark<=thisMark && thisMark<=mxFrame ){ |
| 68196 | assert( thisMark!=READMARK_NOT_USED ); |
| 68197 | mxReadMark = thisMark; |
| 68198 | mxI = i; |
| 68199 | } |
| 68200 | } |
| 68201 | if( (pWal->readOnly & WAL_SHM_RDONLY)==0 |
| 68202 | && (mxReadMark<mxFrame || mxI==0) |
| 68203 | ){ |
| 68204 | for(i=1; i<WAL_NREADER; i++){ |
| 68205 | rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 68206 | if( rc==SQLITE_OK ){ |
| 68207 | AtomicStore(pInfo->aReadMark+i,mxFrame); |
| 68208 | mxReadMark = mxFrame; |
| 68209 | mxI = i; |
| 68210 | walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 68211 | break; |
| 68212 | }else if( rc!=SQLITE_BUSY ){ |
| 68213 | return rc; |
| 68214 | } |
| 68215 | } |
| 68216 | } |
| 68217 | if( mxI==0 ){ |
| 68218 | assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); |
| 68219 | return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; |
| 68220 | } |
| 68221 | |
| 68222 | (void)walEnableBlockingMs(pWal, nBlockTmout); |
| 68223 | rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); |
| 68224 | walDisableBlocking(pWal); |
| 68225 | if( rc ){ |
| 68226 | #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| 68227 | if( rc==SQLITE_BUSY_TIMEOUT ){ |
| 68228 | *pCnt |= WAL_RETRY_BLOCKED_MASK; |
| 68229 | } |
| 68230 | #else |
| 68231 | assert( rc!=SQLITE_BUSY_TIMEOUT ); |
| 68232 | #endif |
| 68233 | assert((rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT); |
| 68234 | return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; |
| 68235 | } |
| 68236 | /* Now that the read-lock has been obtained, check that neither the |
| 68237 | ** value in the aReadMark[] array or the contents of the wal-index |
| 68238 | ** header have changed. |
| 68239 | ** |
| 68240 | ** It is necessary to check that the wal-index header did not change |
| 68241 | ** between the time it was read and when the shared-lock was obtained |
| 68242 | ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility |
| 68243 | ** that the log file may have been wrapped by a writer, or that frames |
| 68244 | ** that occur later in the log than pWal->hdr.mxFrame may have been |
| 68245 | ** copied into the database by a checkpointer. If either of these things |
| 68246 | ** happened, then reading the database with the current value of |
| 68247 | ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry |
| 68248 | ** instead. |
| 68249 | ** |
| 68250 | ** Before checking that the live wal-index header has not changed |
| 68251 | ** since it was read, set Wal.minFrame to the first frame in the wal |
| 68252 | ** file that has not yet been checkpointed. This client will not need |
| 68253 | ** to read any frames earlier than minFrame from the wal file - they |
| 68254 | ** can be safely read directly from the database file. |
| 68255 | ** |
| 68256 | ** Because a ShmBarrier() call is made between taking the copy of |
| 68257 | ** nBackfill and checking that the wal-header in shared-memory still |
| 68258 | ** matches the one cached in pWal->hdr, it is guaranteed that the |
| 68259 | ** checkpointer that set nBackfill was not working with a wal-index |
| 68260 | ** header newer than that cached in pWal->hdr. If it were, that could |
| 68261 | ** cause a problem. The checkpointer could omit to checkpoint |
| 68262 | ** a version of page X that lies before pWal->minFrame (call that version |
| 68263 | ** A) on the basis that there is a newer version (version B) of the same |
| 68264 | ** page later in the wal file. But if version B happens to like past |
| 68265 | ** frame pWal->hdr.mxFrame - then the client would incorrectly assume |
| 68266 | ** that it can read version A from the database file. However, since |
| 68267 | ** we can guarantee that the checkpointer that set nBackfill could not |
| 68268 | ** see any pages past pWal->hdr.mxFrame, this problem does not come up. |
| 68269 | */ |
| 68270 | pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; |
| 68271 | walShmBarrier(pWal); |
| 68272 | if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark |
| 68273 | || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) |
| 68274 | ){ |
| 68275 | walUnlockShared(pWal, WAL_READ_LOCK(mxI)); |
| 68276 | return WAL_RETRY; |
| 68277 | }else{ |
| 68278 | assert( mxReadMark<=pWal->hdr.mxFrame ); |
| 68279 | pWal->readLock = (i16)mxI; |
| 68280 | } |
| 68281 | } |
| 68282 | return rc; |
| 68283 | } |
| 68284 | |
| 68285 | #ifdef SQLITE_ENABLE_SNAPSHOT |
| @@ -87103,10 +87155,11 @@ | |
| 87155 | ** |
| 87156 | ** All other fields of Mem can safely remain uninitialized for now. They |
| 87157 | ** will be initialized before use. |
| 87158 | */ |
| 87159 | static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ |
| 87160 | assert( db!=0 ); |
| 87161 | if( N>0 ){ |
| 87162 | do{ |
| 87163 | p->flags = flags; |
| 87164 | p->db = db; |
| 87165 | p->szMalloc = 0; |
| @@ -87128,10 +87181,11 @@ | |
| 87181 | */ |
| 87182 | static void releaseMemArray(Mem *p, int N){ |
| 87183 | if( p && N ){ |
| 87184 | Mem *pEnd = &p[N]; |
| 87185 | sqlite3 *db = p->db; |
| 87186 | assert( db!=0 ); |
| 87187 | if( db->pnBytesFreed ){ |
| 87188 | do{ |
| 87189 | if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); |
| 87190 | }while( (++p)<pEnd ); |
| 87191 | return; |
| @@ -87608,10 +87662,11 @@ | |
| 87662 | assert( p!=0 ); |
| 87663 | assert( p->nOp>0 ); |
| 87664 | assert( pParse!=0 ); |
| 87665 | assert( p->eVdbeState==VDBE_INIT_STATE ); |
| 87666 | assert( pParse==p->pParse ); |
| 87667 | assert( pParse->db==p->db ); |
| 87668 | p->pVList = pParse->pVList; |
| 87669 | pParse->pVList = 0; |
| 87670 | db = p->db; |
| 87671 | assert( db->mallocFailed==0 ); |
| 87672 | nVar = pParse->nVar; |
| @@ -90488,10 +90543,11 @@ | |
| 90543 | db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); |
| 90544 | db->pPreUpdate = 0; |
| 90545 | sqlite3DbFree(db, preupdate.aRecord); |
| 90546 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked); |
| 90547 | vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked); |
| 90548 | sqlite3VdbeMemRelease(&preupdate.oldipk); |
| 90549 | if( preupdate.aNew ){ |
| 90550 | int i; |
| 90551 | for(i=0; i<pCsr->nField; i++){ |
| 90552 | sqlite3VdbeMemRelease(&preupdate.aNew[i]); |
| 90553 | } |
| @@ -91844,11 +91900,11 @@ | |
| 91900 | ** |
| 91901 | ** sqlite3_column_int() |
| 91902 | ** sqlite3_column_int64() |
| 91903 | ** sqlite3_column_text() |
| 91904 | ** sqlite3_column_text16() |
| 91905 | ** sqlite3_column_double() |
| 91906 | ** sqlite3_column_bytes() |
| 91907 | ** sqlite3_column_bytes16() |
| 91908 | ** sqlite3_column_blob() |
| 91909 | */ |
| 91910 | static void columnMallocFailure(sqlite3_stmt *pStmt) |
| @@ -92706,64 +92762,68 @@ | |
| 92762 | if( iIdx>=p->pCsr->nField || iIdx<0 ){ |
| 92763 | rc = SQLITE_RANGE; |
| 92764 | goto preupdate_old_out; |
| 92765 | } |
| 92766 | |
| 92767 | if( iIdx==p->pTab->iPKey ){ |
| 92768 | *ppValue = pMem = &p->oldipk; |
| 92769 | sqlite3VdbeMemSetInt64(pMem, p->iKey1); |
| 92770 | }else{ |
| 92771 | |
| 92772 | /* If the old.* record has not yet been loaded into memory, do so now. */ |
| 92773 | if( p->pUnpacked==0 ){ |
| 92774 | u32 nRec; |
| 92775 | u8 *aRec; |
| 92776 | |
| 92777 | assert( p->pCsr->eCurType==CURTYPE_BTREE ); |
| 92778 | nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); |
| 92779 | aRec = sqlite3DbMallocRaw(db, nRec); |
| 92780 | if( !aRec ) goto preupdate_old_out; |
| 92781 | rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); |
| 92782 | if( rc==SQLITE_OK ){ |
| 92783 | p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); |
| 92784 | if( !p->pUnpacked ) rc = SQLITE_NOMEM; |
| 92785 | } |
| 92786 | if( rc!=SQLITE_OK ){ |
| 92787 | sqlite3DbFree(db, aRec); |
| 92788 | goto preupdate_old_out; |
| 92789 | } |
| 92790 | p->aRecord = aRec; |
| 92791 | } |
| 92792 | |
| 92793 | pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; |
| 92794 | if( iIdx>=p->pUnpacked->nField ){ |
| 92795 | /* This occurs when the table has been extended using ALTER TABLE |
| 92796 | ** ADD COLUMN. The value to return is the default value of the column. */ |
| 92797 | Column *pCol = &p->pTab->aCol[iIdx]; |
| 92798 | if( pCol->iDflt>0 ){ |
| 92799 | if( p->apDflt==0 ){ |
| 92800 | int nByte = sizeof(sqlite3_value*)*p->pTab->nCol; |
| 92801 | p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); |
| 92802 | if( p->apDflt==0 ) goto preupdate_old_out; |
| 92803 | } |
| 92804 | if( p->apDflt[iIdx]==0 ){ |
| 92805 | sqlite3_value *pVal = 0; |
| 92806 | Expr *pDflt; |
| 92807 | assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) ); |
| 92808 | pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; |
| 92809 | rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); |
| 92810 | if( rc==SQLITE_OK && pVal==0 ){ |
| 92811 | rc = SQLITE_CORRUPT_BKPT; |
| 92812 | } |
| 92813 | p->apDflt[iIdx] = pVal; |
| 92814 | } |
| 92815 | *ppValue = p->apDflt[iIdx]; |
| 92816 | }else{ |
| 92817 | *ppValue = (sqlite3_value *)columnNullValue(); |
| 92818 | } |
| 92819 | }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ |
| 92820 | if( pMem->flags & (MEM_Int|MEM_IntReal) ){ |
| 92821 | testcase( pMem->flags & MEM_Int ); |
| 92822 | testcase( pMem->flags & MEM_IntReal ); |
| 92823 | sqlite3VdbeMemRealify(pMem); |
| 92824 | } |
| 92825 | } |
| 92826 | } |
| 92827 | |
| 92828 | preupdate_old_out: |
| 92829 | sqlite3Error(db, rc); |
| @@ -97913,13 +97973,15 @@ | |
| 97973 | 0, pCx->uc.pCursor); |
| 97974 | pCx->isTable = 1; |
| 97975 | } |
| 97976 | } |
| 97977 | pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); |
| 97978 | assert( p->apCsr[pOp->p1]==pCx ); |
| 97979 | if( rc ){ |
| 97980 | assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97981 | sqlite3BtreeClose(pCx->ub.pBtx); |
| 97982 | p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */ |
| 97983 | }else{ |
| 97984 | assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); |
| 97985 | } |
| 97986 | } |
| 97987 | } |
| @@ -109845,11 +109907,11 @@ | |
| 109907 | p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); |
| 109908 | } |
| 109909 | p5 = binaryCompareP5(pLeft, pRight, jumpIfNull); |
| 109910 | addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1, |
| 109911 | (void*)p4, P4_COLLSEQ); |
| 109912 | sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5); |
| 109913 | return addr; |
| 109914 | } |
| 109915 | |
| 109916 | /* |
| 109917 | ** Return true if expression pExpr is a vector, or false otherwise. |
| @@ -112012,11 +112074,11 @@ | |
| 112074 | ** |
| 112075 | ** (4) If pSrc is the right operand of a LEFT JOIN, then... |
| 112076 | ** (4a) pExpr must come from an ON clause.. |
| 112077 | ** (4b) and specifically the ON clause associated with the LEFT JOIN. |
| 112078 | ** |
| 112079 | ** (5) If pSrc is the right operand of a LEFT JOIN or the left |
| 112080 | ** operand of a RIGHT JOIN, then pExpr must be from the WHERE |
| 112081 | ** clause, not an ON clause. |
| 112082 | ** |
| 112083 | ** (6) Either: |
| 112084 | ** |
| @@ -115546,35 +115608,41 @@ | |
| 115608 | ** |
| 115609 | ** Additionally, if pExpr is a simple SQL value and the value is the |
| 115610 | ** same as that currently bound to variable pVar, non-zero is returned. |
| 115611 | ** Otherwise, if the values are not the same or if pExpr is not a simple |
| 115612 | ** SQL value, zero is returned. |
| 115613 | ** |
| 115614 | ** If the SQLITE_EnableQPSG flag is set on the database connection, then |
| 115615 | ** this routine always returns false. |
| 115616 | */ |
| 115617 | static SQLITE_NOINLINE int exprCompareVariable( |
| 115618 | const Parse *pParse, |
| 115619 | const Expr *pVar, |
| 115620 | const Expr *pExpr |
| 115621 | ){ |
| 115622 | int res = 2; |
| 115623 | int iVar; |
| 115624 | sqlite3_value *pL, *pR = 0; |
| 115625 | |
| 115626 | if( pExpr->op==TK_VARIABLE && pVar->iColumn==pExpr->iColumn ){ |
| 115627 | return 0; |
| 115628 | } |
| 115629 | if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2; |
| 115630 | sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); |
| 115631 | if( pR ){ |
| 115632 | iVar = pVar->iColumn; |
| 115633 | sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); |
| 115634 | pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB); |
| 115635 | if( pL ){ |
| 115636 | if( sqlite3_value_type(pL)==SQLITE_TEXT ){ |
| 115637 | sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ |
| 115638 | } |
| 115639 | res = sqlite3MemCompare(pL, pR, 0) ? 2 : 0; |
| 115640 | } |
| 115641 | sqlite3ValueFree(pR); |
| 115642 | sqlite3ValueFree(pL); |
| 115643 | } |
| 115644 | return res; |
| 115645 | } |
| 115646 | |
| 115647 | /* |
| 115648 | ** Do a deep comparison of two expression trees. Return 0 if the two |
| @@ -115596,16 +115664,14 @@ | |
| 115664 | ** can be sure the expressions are the same. In the places where |
| 115665 | ** this routine is used, it does not hurt to get an extra 2 - that |
| 115666 | ** just might result in some slightly slower code. But returning |
| 115667 | ** an incorrect 0 or 1 could lead to a malfunction. |
| 115668 | ** |
| 115669 | ** If pParse is not NULL and SQLITE_EnableQPSG is off then TK_VARIABLE |
| 115670 | ** terms in pA with bindings in pParse->pReprepare can be matched against |
| 115671 | ** literals in pB. The pParse->pVdbe->expmask bitmask is updated for |
| 115672 | ** each variable referenced. |
| 115673 | */ |
| 115674 | SQLITE_PRIVATE int sqlite3ExprCompare( |
| 115675 | const Parse *pParse, |
| 115676 | const Expr *pA, |
| 115677 | const Expr *pB, |
| @@ -115613,12 +115679,12 @@ | |
| 115679 | ){ |
| 115680 | u32 combinedFlags; |
| 115681 | if( pA==0 || pB==0 ){ |
| 115682 | return pB==pA ? 0 : 2; |
| 115683 | } |
| 115684 | if( pParse && pA->op==TK_VARIABLE ){ |
| 115685 | return exprCompareVariable(pParse, pA, pB); |
| 115686 | } |
| 115687 | combinedFlags = pA->flags | pB->flags; |
| 115688 | if( combinedFlags & EP_IntValue ){ |
| 115689 | if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){ |
| 115690 | return 0; |
| @@ -115808,23 +115874,75 @@ | |
| 115874 | return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); |
| 115875 | } |
| 115876 | } |
| 115877 | return 0; |
| 115878 | } |
| 115879 | |
| 115880 | /* |
| 115881 | ** Return true if the boolean value of the expression is always either |
| 115882 | ** FALSE or NULL. |
| 115883 | */ |
| 115884 | static int sqlite3ExprIsNotTrue(Expr *pExpr){ |
| 115885 | int v; |
| 115886 | if( pExpr->op==TK_NULL ) return 1; |
| 115887 | if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1; |
| 115888 | v = 1; |
| 115889 | if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1; |
| 115890 | return 0; |
| 115891 | } |
| 115892 | |
| 115893 | /* |
| 115894 | ** Return true if the expression is one of the following: |
| 115895 | ** |
| 115896 | ** CASE WHEN x THEN y END |
| 115897 | ** CASE WHEN x THEN y ELSE NULL END |
| 115898 | ** CASE WHEN x THEN y ELSE false END |
| 115899 | ** iif(x,y) |
| 115900 | ** iif(x,y,NULL) |
| 115901 | ** iif(x,y,false) |
| 115902 | */ |
| 115903 | static int sqlite3ExprIsIIF(sqlite3 *db, const Expr *pExpr){ |
| 115904 | ExprList *pList; |
| 115905 | if( pExpr->op==TK_FUNCTION ){ |
| 115906 | const char *z = pExpr->u.zToken; |
| 115907 | FuncDef *pDef; |
| 115908 | if( (z[0]!='i' && z[0]!='I') ) return 0; |
| 115909 | if( pExpr->x.pList==0 ) return 0; |
| 115910 | pDef = sqlite3FindFunction(db, z, pExpr->x.pList->nExpr, ENC(db), 0); |
| 115911 | #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION |
| 115912 | if( pDef==0 ) return 0; |
| 115913 | #else |
| 115914 | if( NEVER(pDef==0) ) return 0; |
| 115915 | #endif |
| 115916 | if( (pDef->funcFlags & SQLITE_FUNC_INLINE)==0 ) return 0; |
| 115917 | if( SQLITE_PTR_TO_INT(pDef->pUserData)!=INLINEFUNC_iif ) return 0; |
| 115918 | }else if( pExpr->op==TK_CASE ){ |
| 115919 | if( pExpr->pLeft!=0 ) return 0; |
| 115920 | }else{ |
| 115921 | return 0; |
| 115922 | } |
| 115923 | pList = pExpr->x.pList; |
| 115924 | assert( pList!=0 ); |
| 115925 | if( pList->nExpr==2 ) return 1; |
| 115926 | if( pList->nExpr==3 && sqlite3ExprIsNotTrue(pList->a[2].pExpr) ) return 1; |
| 115927 | return 0; |
| 115928 | } |
| 115929 | |
| 115930 | /* |
| 115931 | ** Return true if we can prove the pE2 will always be true if pE1 is |
| 115932 | ** true. Return false if we cannot complete the proof or if pE2 might |
| 115933 | ** be false. Examples: |
| 115934 | ** |
| 115935 | ** pE1: x==5 pE2: x==5 Result: true |
| 115936 | ** pE1: x>0 pE2: x==5 Result: false |
| 115937 | ** pE1: x=21 pE2: x=21 OR y=43 Result: true |
| 115938 | ** pE1: x!=123 pE2: x IS NOT NULL Result: true |
| 115939 | ** pE1: x!=?1 pE2: x IS NOT NULL Result: true |
| 115940 | ** pE1: x IS NULL pE2: x IS NOT NULL Result: false |
| 115941 | ** pE1: x IS ?2 pE2: x IS NOT NULL Result: false |
| 115942 | ** pE1: iif(x,y) pE2: x Result: true |
| 115943 | ** PE1: iif(x,y,0) pE2: x Result: true |
| 115944 | ** |
| 115945 | ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has |
| 115946 | ** Expr.iTable<0 then assume a table number given by iTab. |
| 115947 | ** |
| 115948 | ** If pParse is not NULL, then the values of bound variables in pE1 are |
| @@ -115854,10 +115972,13 @@ | |
| 115972 | if( pE2->op==TK_NOTNULL |
| 115973 | && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0) |
| 115974 | ){ |
| 115975 | return 1; |
| 115976 | } |
| 115977 | if( sqlite3ExprIsIIF(pParse->db, pE1) ){ |
| 115978 | return sqlite3ExprImpliesExpr(pParse,pE1->x.pList->a[0].pExpr,pE2,iTab); |
| 115979 | } |
| 115980 | return 0; |
| 115981 | } |
| 115982 | |
| 115983 | /* This is a helper function to impliesNotNullRow(). In this routine, |
| 115984 | ** set pWalker->eCode to one only if *both* of the input expressions |
| @@ -121265,19 +121386,10 @@ | |
| 121386 | rc = sqlite3Init(db, &zErrDyn); |
| 121387 | } |
| 121388 | sqlite3BtreeLeaveAll(db); |
| 121389 | assert( zErrDyn==0 || rc!=SQLITE_OK ); |
| 121390 | } |
| 121391 | if( rc ){ |
| 121392 | if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ |
| 121393 | int iDb = db->nDb - 1; |
| 121394 | assert( iDb>=2 ); |
| 121395 | if( db->aDb[iDb].pBt ){ |
| @@ -121771,15 +121883,11 @@ | |
| 121883 | sqlite3 *db = pParse->db; /* Database handle */ |
| 121884 | char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ |
| 121885 | int rc; /* Auth callback return code */ |
| 121886 | |
| 121887 | if( db->init.busy ) return SQLITE_OK; |
| 121888 | rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext); |
| 121889 | if( rc==SQLITE_DENY ){ |
| 121890 | char *z = sqlite3_mprintf("%s.%s", zTab, zCol); |
| 121891 | if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z); |
| 121892 | sqlite3ErrorMsg(pParse, "access to %z is prohibited", z); |
| 121893 | pParse->rc = SQLITE_AUTH; |
| @@ -121882,15 +121990,11 @@ | |
| 121990 | testcase( zArg1==0 ); |
| 121991 | testcase( zArg2==0 ); |
| 121992 | testcase( zArg3==0 ); |
| 121993 | testcase( pParse->zAuthContext==0 ); |
| 121994 | |
| 121995 | rc = db->xAuth(db->pAuthArg,code,zArg1,zArg2,zArg3,pParse->zAuthContext); |
| 121996 | if( rc==SQLITE_DENY ){ |
| 121997 | sqlite3ErrorMsg(pParse, "not authorized"); |
| 121998 | pParse->rc = SQLITE_AUTH; |
| 121999 | }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){ |
| 122000 | rc = SQLITE_DENY; |
| @@ -122119,21 +122223,10 @@ | |
| 122223 | sqlite3VdbeJumpHere(v, addrRewind); |
| 122224 | } |
| 122225 | } |
| 122226 | sqlite3VdbeAddOp0(v, OP_Halt); |
| 122227 | |
| 122228 | /* The cookie mask contains one bit for each database file open. |
| 122229 | ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are |
| 122230 | ** set for each database that is used. Generate code to start a |
| 122231 | ** transaction on each used database and to verify the schema cookie |
| 122232 | ** on each used database. |
| @@ -122258,20 +122351,10 @@ | |
| 122351 | sqlite3DbFree(db, zSql); |
| 122352 | memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); |
| 122353 | pParse->nested--; |
| 122354 | } |
| 122355 | |
| 122356 | /* |
| 122357 | ** Locate the in-memory structure that describes a particular database |
| 122358 | ** table given the name of that table and (optionally) the name of the |
| 122359 | ** database containing the table. Return NULL if not found. |
| 122360 | ** |
| @@ -122286,17 +122369,10 @@ | |
| 122369 | Table *p = 0; |
| 122370 | int i; |
| 122371 | |
| 122372 | /* All mutexes are required for schema access. Make sure we hold them. */ |
| 122373 | assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); |
| 122374 | if( zDatabase ){ |
| 122375 | for(i=0; i<db->nDb; i++){ |
| 122376 | if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; |
| 122377 | } |
| 122378 | if( i>=db->nDb ){ |
| @@ -125951,13 +126027,10 @@ | |
| 126027 | |
| 126028 | assert( pTab!=0 ); |
| 126029 | if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 |
| 126030 | && db->init.busy==0 |
| 126031 | && pTblName!=0 |
| 126032 | ){ |
| 126033 | sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); |
| 126034 | goto exit_create_index; |
| 126035 | } |
| 126036 | #ifndef SQLITE_OMIT_VIEW |
| @@ -129661,20 +129734,19 @@ | |
| 129734 | const unsigned char *z; |
| 129735 | const unsigned char *z2; |
| 129736 | int len; |
| 129737 | int p0type; |
| 129738 | i64 p1, p2; |
| 129739 | |
| 129740 | assert( argc==3 || argc==2 ); |
| 129741 | if( sqlite3_value_type(argv[1])==SQLITE_NULL |
| 129742 | || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL) |
| 129743 | ){ |
| 129744 | return; |
| 129745 | } |
| 129746 | p0type = sqlite3_value_type(argv[0]); |
| 129747 | p1 = sqlite3_value_int64(argv[1]); |
| 129748 | if( p0type==SQLITE_BLOB ){ |
| 129749 | len = sqlite3_value_bytes(argv[0]); |
| 129750 | z = sqlite3_value_blob(argv[0]); |
| 129751 | if( z==0 ) return; |
| 129752 | assert( len==sqlite3_value_bytes(argv[0]) ); |
| @@ -129695,36 +129767,36 @@ | |
| 129767 | ** from 2009-02-02 for compatibility of applications that exploited the |
| 129768 | ** old buggy behavior. */ |
| 129769 | if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */ |
| 129770 | #endif |
| 129771 | if( argc==3 ){ |
| 129772 | p2 = sqlite3_value_int64(argv[2]); |
| 129773 | }else{ |
| 129774 | p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH]; |
| 129775 | } |
| 129776 | if( p1<0 ){ |
| 129777 | p1 += len; |
| 129778 | if( p1<0 ){ |
| 129779 | if( p2<0 ){ |
| 129780 | p2 = 0; |
| 129781 | }else{ |
| 129782 | p2 += p1; |
| 129783 | } |
| 129784 | p1 = 0; |
| 129785 | } |
| 129786 | }else if( p1>0 ){ |
| 129787 | p1--; |
| 129788 | }else if( p2>0 ){ |
| 129789 | p2--; |
| 129790 | } |
| 129791 | if( p2<0 ){ |
| 129792 | if( p2<-p1 ){ |
| 129793 | p2 = p1; |
| 129794 | }else{ |
| 129795 | p2 = -p2; |
| 129796 | } |
| 129797 | p1 -= p2; |
| 129798 | } |
| 129799 | assert( p1>=0 && p2>=0 ); |
| 129800 | if( p0type!=SQLITE_BLOB ){ |
| 129801 | while( *z && p1 ){ |
| 129802 | SQLITE_SKIP_UTF8(z); |
| @@ -129734,13 +129806,15 @@ | |
| 129806 | SQLITE_SKIP_UTF8(z2); |
| 129807 | } |
| 129808 | sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, |
| 129809 | SQLITE_UTF8); |
| 129810 | }else{ |
| 129811 | if( p1>=len ){ |
| 129812 | p1 = p2 = 0; |
| 129813 | }else if( p2>len-p1 ){ |
| 129814 | p2 = len-p1; |
| 129815 | assert( p2>0 ); |
| 129816 | } |
| 129817 | sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); |
| 129818 | } |
| 129819 | } |
| 129820 | |
| @@ -129747,17 +129821,17 @@ | |
| 129821 | /* |
| 129822 | ** Implementation of the round() function |
| 129823 | */ |
| 129824 | #ifndef SQLITE_OMIT_FLOATING_POINT |
| 129825 | static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ |
| 129826 | i64 n = 0; |
| 129827 | double r; |
| 129828 | char *zBuf; |
| 129829 | assert( argc==1 || argc==2 ); |
| 129830 | if( argc==2 ){ |
| 129831 | if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; |
| 129832 | n = sqlite3_value_int64(argv[1]); |
| 129833 | if( n>30 ) n = 30; |
| 129834 | if( n<0 ) n = 0; |
| 129835 | } |
| 129836 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; |
| 129837 | r = sqlite3_value_double(argv[0]); |
| @@ -129768,11 +129842,11 @@ | |
| 129842 | if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ |
| 129843 | /* The value has no fractional part so there is nothing to round */ |
| 129844 | }else if( n==0 ){ |
| 129845 | r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); |
| 129846 | }else{ |
| 129847 | zBuf = sqlite3_mprintf("%!.*f",(int)n,r); |
| 129848 | if( zBuf==0 ){ |
| 129849 | sqlite3_result_error_nomem(context); |
| 129850 | return; |
| 129851 | } |
| 129852 | sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); |
| @@ -131985,13 +132059,10 @@ | |
| 132059 | #endif |
| 132060 | #ifndef SQLITE_OMIT_LOAD_EXTENSION |
| 132061 | SFUNCTION(load_extension, 1, 0, 0, loadExt ), |
| 132062 | SFUNCTION(load_extension, 2, 0, 0, loadExt ), |
| 132063 | #endif |
| 132064 | #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS |
| 132065 | DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ), |
| 132066 | DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ), |
| 132067 | #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ |
| 132068 | INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY), |
| @@ -132124,11 +132195,14 @@ | |
| 132195 | MFUNCTION(degrees, 1, radToDeg, math1Func ), |
| 132196 | MFUNCTION(pi, 0, 0, piFunc ), |
| 132197 | #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ |
| 132198 | FUNCTION(sign, 1, 0, 0, signFunc ), |
| 132199 | INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ), |
| 132200 | INLINE_FUNC(iif, 2, INLINEFUNC_iif, 0 ), |
| 132201 | INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ), |
| 132202 | INLINE_FUNC(if, 2, INLINEFUNC_iif, 0 ), |
| 132203 | INLINE_FUNC(if, 3, INLINEFUNC_iif, 0 ), |
| 132204 | }; |
| 132205 | #ifndef SQLITE_OMIT_ALTERTABLE |
| 132206 | sqlite3AlterFunctions(); |
| 132207 | #endif |
| 132208 | sqlite3WindowFunctions(); |
| @@ -140637,16 +140711,10 @@ | |
| 140711 | if( db->autoCommit==0 ){ |
| 140712 | /* Foreign key support may not be enabled or disabled while not |
| 140713 | ** in auto-commit mode. */ |
| 140714 | mask &= ~(SQLITE_ForeignKeys); |
| 140715 | } |
| 140716 | |
| 140717 | if( sqlite3GetBoolean(zRight, 0) ){ |
| 140718 | if( (mask & SQLITE_WriteSchema)==0 |
| 140719 | || (db->flags & SQLITE_Defensive)==0 |
| 140720 | ){ |
| @@ -140778,11 +140846,12 @@ | |
| 140846 | pTab = sqliteHashData(k); |
| 140847 | if( pTab->nCol==0 ){ |
| 140848 | char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); |
| 140849 | if( zSql ){ |
| 140850 | sqlite3_stmt *pDummy = 0; |
| 140851 | (void)sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_DONT_LOG, |
| 140852 | &pDummy, 0); |
| 140853 | (void)sqlite3_finalize(pDummy); |
| 140854 | sqlite3DbFree(db, zSql); |
| 140855 | } |
| 140856 | if( db->mallocFailed ){ |
| 140857 | sqlite3ErrorMsg(db->pParse, "out of memory"); |
| @@ -141259,11 +141328,11 @@ | |
| 141328 | sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt); |
| 141329 | sqlite3ClearTempRegCache(pParse); |
| 141330 | |
| 141331 | /* Do the b-tree integrity checks */ |
| 141332 | sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY); |
| 141333 | sqlite3VdbeChangeP5(v, (u16)i); |
| 141334 | addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); |
| 141335 | sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, |
| 141336 | sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), |
| 141337 | P4_DYNAMIC); |
| 141338 | sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3); |
| @@ -142879,18 +142948,11 @@ | |
| 142948 | encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; |
| 142949 | if( encoding==0 ) encoding = SQLITE_UTF8; |
| 142950 | #else |
| 142951 | encoding = SQLITE_UTF8; |
| 142952 | #endif |
| 142953 | sqlite3SetTextEncoding(db, encoding); |
| 142954 | }else{ |
| 142955 | /* If opening an attached database, the encoding much match ENC(db) */ |
| 142956 | if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ |
| 142957 | sqlite3SetString(pzErrMsg, db, "attached databases must use the same" |
| 142958 | " text encoding as main database"); |
| @@ -147584,36 +147646,36 @@ | |
| 147646 | return pExpr; |
| 147647 | } |
| 147648 | if( pSubst->isOuterJoin ){ |
| 147649 | ExprSetProperty(pNew, EP_CanBeNull); |
| 147650 | } |
| 147651 | if( pNew->op==TK_TRUEFALSE ){ |
| 147652 | pNew->u.iValue = sqlite3ExprTruthValue(pNew); |
| 147653 | pNew->op = TK_INTEGER; |
| 147654 | ExprSetProperty(pNew, EP_IntValue); |
| 147655 | } |
| 147656 | |
| 147657 | /* Ensure that the expression now has an implicit collation sequence, |
| 147658 | ** just as it did when it was a column of a view or sub-query. */ |
| 147659 | { |
| 147660 | CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew); |
| 147661 | CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, |
| 147662 | pSubst->pCList->a[iColumn].pExpr |
| 147663 | ); |
| 147664 | if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){ |
| 147665 | pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew, |
| 147666 | (pColl ? pColl->zName : "BINARY") |
| 147667 | ); |
| 147668 | } |
| 147669 | } |
| 147670 | ExprClearProperty(pNew, EP_Collate); |
| 147671 | if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ |
| 147672 | sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, |
| 147673 | pExpr->flags & (EP_OuterON|EP_InnerON)); |
| 147674 | } |
| 147675 | sqlite3ExprDelete(db, pExpr); |
| 147676 | pExpr = pNew; |
| 147677 | } |
| 147678 | } |
| 147679 | }else{ |
| 147680 | if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ |
| 147681 | pExpr->iTable = pSubst->iNewTable; |
| @@ -148346,20 +148408,20 @@ | |
| 148408 | } |
| 148409 | |
| 148410 | /* Transfer the FROM clause terms from the subquery into the |
| 148411 | ** outer query. |
| 148412 | */ |
| 148413 | iNewParent = pSubSrc->a[0].iCursor; |
| 148414 | for(i=0; i<nSubSrc; i++){ |
| 148415 | SrcItem *pItem = &pSrc->a[i+iFrom]; |
| 148416 | assert( pItem->fg.isTabFunc==0 ); |
| 148417 | assert( pItem->fg.isSubquery |
| 148418 | || pItem->fg.fixedSchema |
| 148419 | || pItem->u4.zDatabase==0 ); |
| 148420 | if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); |
| 148421 | *pItem = pSubSrc->a[i]; |
| 148422 | pItem->fg.jointype |= ltorj; |
| 148423 | memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); |
| 148424 | } |
| 148425 | pSrc->a[iFrom].fg.jointype &= JT_LTORJ; |
| 148426 | pSrc->a[iFrom].fg.jointype |= jointype | ltorj; |
| 148427 | |
| @@ -148395,10 +148457,11 @@ | |
| 148457 | pSub->pOrderBy = 0; |
| 148458 | } |
| 148459 | pWhere = pSub->pWhere; |
| 148460 | pSub->pWhere = 0; |
| 148461 | if( isOuterJoin>0 ){ |
| 148462 | assert( pSubSrc->nSrc==1 ); |
| 148463 | sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); |
| 148464 | } |
| 148465 | if( pWhere ){ |
| 148466 | if( pParent->pWhere ){ |
| 148467 | pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere); |
| @@ -150498,11 +150561,11 @@ | |
| 150561 | } |
| 150562 | sqlite3ReleaseTempReg(pParse, regSubtype); |
| 150563 | } |
| 150564 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150565 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150566 | sqlite3VdbeChangeP5(v, (u16)nArg); |
| 150567 | sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v); |
| 150568 | sqlite3VdbeJumpHere(v, iTop); |
| 150569 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150570 | } |
| 150571 | sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), |
| @@ -150661,11 +150724,11 @@ | |
| 150724 | sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, |
| 150725 | (char *)pColl, P4_COLLSEQ); |
| 150726 | } |
| 150727 | sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); |
| 150728 | sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); |
| 150729 | sqlite3VdbeChangeP5(v, (u16)nArg); |
| 150730 | sqlite3ReleaseTempRange(pParse, regAgg, nArg); |
| 150731 | } |
| 150732 | if( addrNext ){ |
| 150733 | sqlite3VdbeResolveLabel(v, addrNext); |
| 150734 | } |
| @@ -151494,11 +151557,11 @@ | |
| 151557 | sqlite3TreeViewSelect(0, p, 0); |
| 151558 | } |
| 151559 | #endif |
| 151560 | assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 ); |
| 151561 | }else{ |
| 151562 | TREETRACE(0x4000,pParse,p,("WHERE-clause push-down not possible\n")); |
| 151563 | } |
| 151564 | |
| 151565 | /* Convert unused result columns of the subquery into simple NULL |
| 151566 | ** expressions, to avoid unneeded searching and computation. |
| 151567 | ** tag-select-0440 |
| @@ -154055,11 +154118,11 @@ | |
| 154118 | /* Set the P5 operand of the OP_Program instruction to non-zero if |
| 154119 | ** recursive invocation of this trigger program is disallowed. Recursive |
| 154120 | ** invocation is disallowed if (a) the sub-program is really a trigger, |
| 154121 | ** not a foreign key action, and (b) the flag to enable recursive triggers |
| 154122 | ** is clear. */ |
| 154123 | sqlite3VdbeChangeP5(v, (u16)bRecursive); |
| 154124 | } |
| 154125 | } |
| 154126 | |
| 154127 | /* |
| 154128 | ** This is called to code the required FOR EACH ROW triggers for an operation |
| @@ -158268,13 +158331,21 @@ | |
| 158331 | SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( |
| 158332 | const Parse *pParse, /* Parse context */ |
| 158333 | const WhereInfo *pWInfo, /* WHERE clause */ |
| 158334 | const WhereLevel *pLevel /* Bloom filter on this level */ |
| 158335 | ); |
| 158336 | SQLITE_PRIVATE void sqlite3WhereAddExplainText( |
| 158337 | Parse *pParse, /* Parse context */ |
| 158338 | int addr, |
| 158339 | SrcList *pTabList, /* Table list this loop refers to */ |
| 158340 | WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ |
| 158341 | u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ |
| 158342 | ); |
| 158343 | #else |
| 158344 | # define sqlite3WhereExplainOneScan(u,v,w,x) 0 |
| 158345 | # define sqlite3WhereExplainBloomFilter(u,v,w) 0 |
| 158346 | # define sqlite3WhereAddExplainText(u,v,w,x,y) |
| 158347 | #endif /* SQLITE_OMIT_EXPLAIN */ |
| 158348 | #ifdef SQLITE_ENABLE_STMT_SCANSTATUS |
| 158349 | SQLITE_PRIVATE void sqlite3WhereAddScanStatus( |
| 158350 | Vdbe *v, /* Vdbe to add scanstatus entry to */ |
| 158351 | SrcList *pSrclist, /* FROM clause pLvl reads data from */ |
| @@ -158472,42 +158543,42 @@ | |
| 158543 | } |
| 158544 | sqlite3_str_append(pStr, ")", 1); |
| 158545 | } |
| 158546 | |
| 158547 | /* |
| 158548 | ** This function sets the P4 value of an existing OP_Explain opcode to |
| 158549 | ** text describing the loop in pLevel. If the OP_Explain opcode already has |
| 158550 | ** a P4 value, it is freed before it is overwritten. |
| 158551 | */ |
| 158552 | SQLITE_PRIVATE void sqlite3WhereAddExplainText( |
| 158553 | Parse *pParse, /* Parse context */ |
| 158554 | int addr, /* Address of OP_Explain opcode */ |
| 158555 | SrcList *pTabList, /* Table list this loop refers to */ |
| 158556 | WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ |
| 158557 | u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ |
| 158558 | ){ |
| 158559 | #if !defined(SQLITE_DEBUG) |
| 158560 | if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) |
| 158561 | #endif |
| 158562 | { |
| 158563 | VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr); |
| 158564 | |
| 158565 | SrcItem *pItem = &pTabList->a[pLevel->iFrom]; |
| 158566 | sqlite3 *db = pParse->db; /* Database handle */ |
| 158567 | int isSearch; /* True for a SEARCH. False for SCAN. */ |
| 158568 | WhereLoop *pLoop; /* The controlling WhereLoop object */ |
| 158569 | u32 flags; /* Flags that describe this loop */ |
| 158570 | #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) |
| 158571 | char *zMsg; /* Text to add to EQP output */ |
| 158572 | #endif |
| 158573 | StrAccum str; /* EQP output string */ |
| 158574 | char zBuf[100]; /* Initial space for EQP output string */ |
| 158575 | |
| 158576 | if( db->mallocFailed ) return; |
| 158577 | |
| 158578 | pLoop = pLevel->pWLoop; |
| 158579 | flags = pLoop->wsFlags; |
| 158580 | |
| 158581 | isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 |
| 158582 | || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) |
| 158583 | || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); |
| 158584 | |
| @@ -158527,11 +158598,11 @@ | |
| 158598 | } |
| 158599 | }else if( flags & WHERE_PARTIALIDX ){ |
| 158600 | zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; |
| 158601 | }else if( flags & WHERE_AUTO_INDEX ){ |
| 158602 | zFmt = "AUTOMATIC COVERING INDEX"; |
| 158603 | }else if( flags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ |
| 158604 | zFmt = "COVERING INDEX %s"; |
| 158605 | }else{ |
| 158606 | zFmt = "INDEX %s"; |
| 158607 | } |
| 158608 | if( zFmt ){ |
| @@ -158579,15 +158650,54 @@ | |
| 158650 | sqlite3LogEstToInt(pLoop->nOut)); |
| 158651 | }else{ |
| 158652 | sqlite3_str_append(&str, " (~1 row)", 9); |
| 158653 | } |
| 158654 | #endif |
| 158655 | #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) |
| 158656 | zMsg = sqlite3StrAccumFinish(&str); |
| 158657 | sqlite3ExplainBreakpoint("",zMsg); |
| 158658 | #endif |
| 158659 | |
| 158660 | assert( pOp->opcode==OP_Explain ); |
| 158661 | assert( pOp->p4type==P4_DYNAMIC || pOp->p4.z==0 ); |
| 158662 | sqlite3DbFree(db, pOp->p4.z); |
| 158663 | pOp->p4type = P4_DYNAMIC; |
| 158664 | pOp->p4.z = sqlite3StrAccumFinish(&str); |
| 158665 | } |
| 158666 | } |
| 158667 | |
| 158668 | |
| 158669 | /* |
| 158670 | ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN |
| 158671 | ** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG |
| 158672 | ** was defined at compile-time. If it is not a no-op, a single OP_Explain |
| 158673 | ** opcode is added to the output to describe the table scan strategy in pLevel. |
| 158674 | ** |
| 158675 | ** If an OP_Explain opcode is added to the VM, its address is returned. |
| 158676 | ** Otherwise, if no OP_Explain is coded, zero is returned. |
| 158677 | */ |
| 158678 | SQLITE_PRIVATE int sqlite3WhereExplainOneScan( |
| 158679 | Parse *pParse, /* Parse context */ |
| 158680 | SrcList *pTabList, /* Table list this loop refers to */ |
| 158681 | WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ |
| 158682 | u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ |
| 158683 | ){ |
| 158684 | int ret = 0; |
| 158685 | #if !defined(SQLITE_DEBUG) |
| 158686 | if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) |
| 158687 | #endif |
| 158688 | { |
| 158689 | if( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 |
| 158690 | && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 |
| 158691 | ){ |
| 158692 | Vdbe *v = pParse->pVdbe; |
| 158693 | int addr = sqlite3VdbeCurrentAddr(v); |
| 158694 | ret = sqlite3VdbeAddOp3( |
| 158695 | v, OP_Explain, addr, pParse->addrExplain, pLevel->pWLoop->rRun |
| 158696 | ); |
| 158697 | sqlite3WhereAddExplainText(pParse, addr, pTabList, pLevel, wctrlFlags); |
| 158698 | } |
| 158699 | } |
| 158700 | return ret; |
| 158701 | } |
| 158702 | |
| 158703 | /* |
| @@ -158682,13 +158792,14 @@ | |
| 158792 | if( wsFlags & WHERE_INDEXED ){ |
| 158793 | sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); |
| 158794 | } |
| 158795 | }else{ |
| 158796 | int addr; |
| 158797 | VdbeOp *pOp; |
| 158798 | assert( pSrclist->a[pLvl->iFrom].fg.isSubquery ); |
| 158799 | addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub; |
| 158800 | pOp = sqlite3VdbeGetOp(v, addr-1); |
| 158801 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); |
| 158802 | assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); |
| 158803 | sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); |
| 158804 | } |
| 158805 | } |
| @@ -158937,10 +159048,11 @@ | |
| 159048 | if( pOrigLhs ){ |
| 159049 | sqlite3ExprListDelete(db, pOrigLhs); |
| 159050 | pNew->pLeft->x.pList = pLhs; |
| 159051 | } |
| 159052 | pSelect->pEList = pRhs; |
| 159053 | pSelect->selId = ++pParse->nSelect; /* Req'd for SubrtnSig validity */ |
| 159054 | if( pLhs && pLhs->nExpr==1 ){ |
| 159055 | /* Take care here not to generate a TK_VECTOR containing only a |
| 159056 | ** single value. Since the parser never creates such a vector, some |
| 159057 | ** of the subroutines do not handle this case. */ |
| 159058 | Expr *p = pLhs->a[0].pExpr; |
| @@ -164009,11 +164121,11 @@ | |
| 164121 | || pTerm->pExpr->w.iJoin != pSrc->iCursor |
| 164122 | ){ |
| 164123 | return 0; |
| 164124 | } |
| 164125 | if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 |
| 164126 | && NEVER(ExprHasProperty(pTerm->pExpr, EP_InnerON)) |
| 164127 | ){ |
| 164128 | return 0; |
| 164129 | } |
| 164130 | return 1; |
| 164131 | } |
| @@ -165502,11 +165614,11 @@ | |
| 165614 | return rc; |
| 165615 | } |
| 165616 | #endif /* SQLITE_ENABLE_STAT4 */ |
| 165617 | |
| 165618 | |
| 165619 | #if defined(WHERETRACE_ENABLED) || defined(SQLITE_DEBUG) |
| 165620 | /* |
| 165621 | ** Print the content of a WhereTerm object |
| 165622 | */ |
| 165623 | SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ |
| 165624 | if( pTerm==0 ){ |
| @@ -165545,10 +165657,13 @@ | |
| 165657 | sqlite3DebugPrintf(" iParent=%d", pTerm->iParent); |
| 165658 | } |
| 165659 | sqlite3DebugPrintf("\n"); |
| 165660 | sqlite3TreeViewExpr(0, pTerm->pExpr, 0); |
| 165661 | } |
| 165662 | } |
| 165663 | SQLITE_PRIVATE void sqlite3ShowWhereTerm(WhereTerm *pTerm){ |
| 165664 | sqlite3WhereTermPrint(pTerm, 0); |
| 165665 | } |
| 165666 | #endif |
| 165667 | |
| 165668 | #ifdef WHERETRACE_ENABLED |
| 165669 | /* |
| @@ -166731,11 +166846,10 @@ | |
| 166846 | pParse = pWC->pWInfo->pParse; |
| 166847 | while( pWhere->op==TK_AND ){ |
| 166848 | if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; |
| 166849 | pWhere = pWhere->pRight; |
| 166850 | } |
| 166851 | for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){ |
| 166852 | Expr *pExpr; |
| 166853 | pExpr = pTerm->pExpr; |
| 166854 | if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) |
| 166855 | && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) |
| @@ -169392,11 +169506,11 @@ | |
| 169506 | break; |
| 169507 | } |
| 169508 | } |
| 169509 | if( hasRightJoin |
| 169510 | && ExprHasProperty(pTerm->pExpr, EP_InnerON) |
| 169511 | && NEVER(pTerm->pExpr->w.iJoin==pItem->iCursor) |
| 169512 | ){ |
| 169513 | break; /* restriction (5) */ |
| 169514 | } |
| 169515 | } |
| 169516 | if( pTerm<pEnd ) continue; |
| @@ -170312,10 +170426,11 @@ | |
| 170426 | int pc, |
| 170427 | VdbeOp *pOp |
| 170428 | ){ |
| 170429 | if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return; |
| 170430 | sqlite3VdbePrintOp(0, pc, pOp); |
| 170431 | sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */ |
| 170432 | } |
| 170433 | #endif |
| 170434 | |
| 170435 | /* |
| 170436 | ** Generate the end of the WHERE loop. See comments on |
| @@ -170611,18 +170726,32 @@ | |
| 170726 | x = sqlite3TableColumnToIndex(pIdx, x); |
| 170727 | if( x>=0 ){ |
| 170728 | pOp->p2 = x; |
| 170729 | pOp->p1 = pLevel->iIdxCur; |
| 170730 | OpcodeRewriteTrace(db, k, pOp); |
| 170731 | }else if( pLoop->wsFlags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ |
| 170732 | if( pLoop->wsFlags & WHERE_IDX_ONLY ){ |
| 170733 | /* An error. pLoop is supposed to be a covering index loop, |
| 170734 | ** and yet the VM code refers to a column of the table that |
| 170735 | ** is not part of the index. */ |
| 170736 | sqlite3ErrorMsg(pParse, "internal query planner error"); |
| 170737 | pParse->rc = SQLITE_INTERNAL; |
| 170738 | }else{ |
| 170739 | /* The WHERE_EXPRIDX flag is set by the planner when it is likely |
| 170740 | ** that pLoop is a covering index loop, but it is not possible |
| 170741 | ** to be 100% sure. In this case, any OP_Explain opcode |
| 170742 | ** corresponding to this loop describes the index as a "COVERING |
| 170743 | ** INDEX". But, pOp proves that pLoop is not actually a covering |
| 170744 | ** index loop. So clear the WHERE_EXPRIDX flag and rewrite the |
| 170745 | ** text that accompanies the OP_Explain opcode, if any. */ |
| 170746 | pLoop->wsFlags &= ~WHERE_EXPRIDX; |
| 170747 | sqlite3WhereAddExplainText(pParse, |
| 170748 | pLevel->addrBody-1, |
| 170749 | pTabList, |
| 170750 | pLevel, |
| 170751 | pWInfo->wctrlFlags |
| 170752 | ); |
| 170753 | } |
| 170754 | } |
| 170755 | }else if( pOp->opcode==OP_Rowid ){ |
| 170756 | pOp->p1 = pLevel->iIdxCur; |
| 170757 | pOp->opcode = OP_IdxRowid; |
| @@ -172326,10 +172455,11 @@ | |
| 172455 | for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ |
| 172456 | FuncDef *pFunc = pWin->pWFunc; |
| 172457 | int regArg; |
| 172458 | int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); |
| 172459 | int i; |
| 172460 | int addrIf = 0; |
| 172461 | |
| 172462 | assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED ); |
| 172463 | |
| 172464 | /* All OVER clauses in the same window function aggregate step must |
| 172465 | ** be the same. */ |
| @@ -172341,10 +172471,22 @@ | |
| 172471 | }else{ |
| 172472 | sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i); |
| 172473 | } |
| 172474 | } |
| 172475 | regArg = reg; |
| 172476 | |
| 172477 | if( pWin->pFilter ){ |
| 172478 | int regTmp; |
| 172479 | assert( ExprUseXList(pWin->pOwner) ); |
| 172480 | assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); |
| 172481 | assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); |
| 172482 | regTmp = sqlite3GetTempReg(pParse); |
| 172483 | sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); |
| 172484 | addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); |
| 172485 | VdbeCoverage(v); |
| 172486 | sqlite3ReleaseTempReg(pParse, regTmp); |
| 172487 | } |
| 172488 | |
| 172489 | if( pMWin->regStartRowid==0 |
| 172490 | && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) |
| 172491 | && (pWin->eStart!=TK_UNBOUNDED) |
| 172492 | ){ |
| @@ -172361,29 +172503,17 @@ | |
| 172503 | sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp); |
| 172504 | sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); |
| 172505 | } |
| 172506 | sqlite3VdbeJumpHere(v, addrIsNull); |
| 172507 | }else if( pWin->regApp ){ |
| 172508 | assert( pWin->pFilter==0 ); |
| 172509 | assert( pFunc->zName==nth_valueName |
| 172510 | || pFunc->zName==first_valueName |
| 172511 | ); |
| 172512 | assert( bInverse==0 || bInverse==1 ); |
| 172513 | sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1); |
| 172514 | }else if( pFunc->xSFunc!=noopStepFunc ){ |
| 172515 | if( pWin->bExprArgs ){ |
| 172516 | int iOp = sqlite3VdbeCurrentAddr(v); |
| 172517 | int iEnd; |
| 172518 | |
| 172519 | assert( ExprUseXList(pWin->pOwner) ); |
| @@ -172406,16 +172536,17 @@ | |
| 172536 | sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); |
| 172537 | } |
| 172538 | sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, |
| 172539 | bInverse, regArg, pWin->regAccum); |
| 172540 | sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); |
| 172541 | sqlite3VdbeChangeP5(v, (u16)nArg); |
| 172542 | if( pWin->bExprArgs ){ |
| 172543 | sqlite3ReleaseTempRange(pParse, regArg, nArg); |
| 172544 | } |
| 172545 | } |
| 172546 | |
| 172547 | if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); |
| 172548 | } |
| 172549 | } |
| 172550 | |
| 172551 | /* |
| 172552 | ** Values that may be passed as the second argument to windowCodeOp(). |
| @@ -173837,10 +173968,17 @@ | |
| 173968 | ** Then the "b" IdList records the list "a,b,c". |
| 173969 | */ |
| 173970 | struct TrigEvent { int a; IdList * b; }; |
| 173971 | |
| 173972 | struct FrameBound { int eType; Expr *pExpr; }; |
| 173973 | |
| 173974 | /* |
| 173975 | ** Generate a syntax error |
| 173976 | */ |
| 173977 | static void parserSyntaxError(Parse *pParse, Token *p){ |
| 173978 | sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p); |
| 173979 | } |
| 173980 | |
| 173981 | /* |
| 173982 | ** Disable lookaside memory allocation for objects that might be |
| 173983 | ** shared across database connections. |
| 173984 | */ |
| @@ -177730,11 +177868,15 @@ | |
| 177868 | } |
| 177869 | break; |
| 177870 | case 84: /* cmd ::= select */ |
| 177871 | { |
| 177872 | SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; |
| 177873 | if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0 |
| 177874 | || sqlite3ReadSchema(pParse)==SQLITE_OK |
| 177875 | ){ |
| 177876 | sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); |
| 177877 | } |
| 177878 | sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); |
| 177879 | } |
| 177880 | break; |
| 177881 | case 85: /* select ::= WITH wqlist selectnowith */ |
| 177882 | {yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} |
| @@ -178201,11 +178343,11 @@ | |
| 178343 | ** that look like this: #1 #2 ... These terms refer to registers |
| 178344 | ** in the virtual machine. #N is the N-th register. */ |
| 178345 | Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/ |
| 178346 | assert( t.n>=2 ); |
| 178347 | if( pParse->nested==0 ){ |
| 178348 | parserSyntaxError(pParse, &t); |
| 178349 | yymsp[0].minor.yy454 = 0; |
| 178350 | }else{ |
| 178351 | yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); |
| 178352 | if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable); |
| 178353 | } |
| @@ -179049,11 +179191,11 @@ | |
| 179191 | #define TOKEN yyminor |
| 179192 | /************ Begin %syntax_error code ****************************************/ |
| 179193 | |
| 179194 | UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ |
| 179195 | if( TOKEN.z[0] ){ |
| 179196 | parserSyntaxError(pParse, &TOKEN); |
| 179197 | }else{ |
| 179198 | sqlite3ErrorMsg(pParse, "incomplete input"); |
| 179199 | } |
| 179200 | /************ End %syntax_error code ******************************************/ |
| 179201 | sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */ |
| @@ -180540,11 +180682,13 @@ | |
| 180682 | } |
| 180683 | if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ |
| 180684 | if( pParse->zErrMsg==0 ){ |
| 180685 | pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); |
| 180686 | } |
| 180687 | if( (pParse->prepFlags & SQLITE_PREPARE_DONT_LOG)==0 ){ |
| 180688 | sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); |
| 180689 | } |
| 180690 | nErr++; |
| 180691 | } |
| 180692 | pParse->zTail = zSql; |
| 180693 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 180694 | sqlite3_free(pParse->apVtabLock); |
| @@ -182513,14 +182657,10 @@ | |
| 182657 | #endif |
| 182658 | |
| 182659 | sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ |
| 182660 | sqlite3ValueFree(db->pErr); |
| 182661 | sqlite3CloseExtensions(db); |
| 182662 | |
| 182663 | db->eOpenState = SQLITE_STATE_ERROR; |
| 182664 | |
| 182665 | /* The temp-database schema is allocated differently from the other schema |
| 182666 | ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). |
| @@ -183951,12 +184091,12 @@ | |
| 184091 | # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 |
| 184092 | #endif |
| 184093 | #if SQLITE_MAX_VDBE_OP<40 |
| 184094 | # error SQLITE_MAX_VDBE_OP must be at least 40 |
| 184095 | #endif |
| 184096 | #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767 |
| 184097 | # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767 |
| 184098 | #endif |
| 184099 | #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 |
| 184100 | # error SQLITE_MAX_ATTACHED must be between 0 and 125 |
| 184101 | #endif |
| 184102 | #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1 |
| @@ -184019,12 +184159,12 @@ | |
| 184159 | } |
| 184160 | oldLimit = db->aLimit[limitId]; |
| 184161 | if( newLimit>=0 ){ /* IMP: R-52476-28732 */ |
| 184162 | if( newLimit>aHardLimit[limitId] ){ |
| 184163 | newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ |
| 184164 | }else if( newLimit<SQLITE_MIN_LENGTH && limitId==SQLITE_LIMIT_LENGTH ){ |
| 184165 | newLimit = SQLITE_MIN_LENGTH; |
| 184166 | } |
| 184167 | db->aLimit[limitId] = newLimit; |
| 184168 | } |
| 184169 | return oldLimit; /* IMP: R-53341-35419 */ |
| 184170 | } |
| @@ -185364,11 +185504,10 @@ | |
| 185504 | rc = x; |
| 185505 | #if defined(SQLITE_DEBUG) |
| 185506 | /* Invoke these debugging routines so that the compiler does not |
| 185507 | ** issue "defined but not used" warnings. */ |
| 185508 | if( x==9999 ){ |
| 185509 | sqlite3ShowExpr(0); |
| 185510 | sqlite3ShowExprList(0); |
| 185511 | sqlite3ShowIdList(0); |
| 185512 | sqlite3ShowSrcList(0); |
| 185513 | sqlite3ShowWith(0); |
| @@ -189796,14 +189935,19 @@ | |
| 189935 | |
| 189936 | assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 ); |
| 189937 | if( *p1==POS_COLUMN ){ |
| 189938 | p1++; |
| 189939 | p1 += fts3GetVarint32(p1, &iCol1); |
| 189940 | /* iCol1==0 indicates corruption. Column 0 does not have a POS_COLUMN |
| 189941 | ** entry, so this is actually end-of-doclist. */ |
| 189942 | if( iCol1==0 ) return 0; |
| 189943 | } |
| 189944 | if( *p2==POS_COLUMN ){ |
| 189945 | p2++; |
| 189946 | p2 += fts3GetVarint32(p2, &iCol2); |
| 189947 | /* As above, iCol2==0 indicates corruption. */ |
| 189948 | if( iCol2==0 ) return 0; |
| 189949 | } |
| 189950 | |
| 189951 | while( 1 ){ |
| 189952 | if( iCol1==iCol2 ){ |
| 189953 | char *pSave = p; |
| @@ -192970,11 +193114,11 @@ | |
| 193114 | for(p=pExpr; p->pLeft; p=p->pLeft){ |
| 193115 | assert( p->pRight->pPhrase->doclist.nList>0 ); |
| 193116 | nTmp += p->pRight->pPhrase->doclist.nList; |
| 193117 | } |
| 193118 | nTmp += p->pPhrase->doclist.nList; |
| 193119 | aTmp = sqlite3_malloc64(nTmp*2 + FTS3_VARINT_MAX); |
| 193120 | if( !aTmp ){ |
| 193121 | *pRc = SQLITE_NOMEM; |
| 193122 | res = 0; |
| 193123 | }else{ |
| 193124 | char *aPoslist = p->pPhrase->doclist.pList; |
| @@ -193621,11 +193765,11 @@ | |
| 193765 | SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ |
| 193766 | return SQLITE_CORRUPT_VTAB; |
| 193767 | } |
| 193768 | #endif |
| 193769 | |
| 193770 | #if !defined(SQLITE_CORE) |
| 193771 | /* |
| 193772 | ** Initialize API pointer table, if required. |
| 193773 | */ |
| 193774 | #ifdef _WIN32 |
| 193775 | __declspec(dllexport) |
| @@ -194523,14 +194667,15 @@ | |
| 194667 | rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos); |
| 194668 | if( rc==SQLITE_OK ){ |
| 194669 | Fts3PhraseToken *pToken; |
| 194670 | |
| 194671 | p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); |
| 194672 | zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); |
| 194673 | if( !zTemp || !p ){ |
| 194674 | rc = SQLITE_NOMEM; |
| 194675 | goto getnextstring_out; |
| 194676 | } |
| 194677 | |
| 194678 | assert( nToken==ii ); |
| 194679 | pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; |
| 194680 | memset(pToken, 0, sizeof(Fts3PhraseToken)); |
| 194681 | |
| @@ -194541,53 +194686,51 @@ | |
| 194686 | pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*'); |
| 194687 | pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^'); |
| 194688 | nToken = ii+1; |
| 194689 | } |
| 194690 | } |
| 194691 | } |
| 194692 | |
| 194693 | if( rc==SQLITE_DONE ){ |
| 194694 | int jj; |
| 194695 | char *zBuf = 0; |
| 194696 | |
| 194697 | p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); |
| 194698 | if( !p ){ |
| 194699 | rc = SQLITE_NOMEM; |
| 194700 | goto getnextstring_out; |
| 194701 | } |
| 194702 | memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); |
| 194703 | p->eType = FTSQUERY_PHRASE; |
| 194704 | p->pPhrase = (Fts3Phrase *)&p[1]; |
| 194705 | p->pPhrase->iColumn = pParse->iDefaultCol; |
| 194706 | p->pPhrase->nToken = nToken; |
| 194707 | |
| 194708 | zBuf = (char *)&p->pPhrase->aToken[nToken]; |
| 194709 | assert( nTemp==0 || zTemp ); |
| 194710 | if( zTemp ){ |
| 194711 | memcpy(zBuf, zTemp, nTemp); |
| 194712 | } |
| 194713 | |
| 194714 | for(jj=0; jj<p->pPhrase->nToken; jj++){ |
| 194715 | p->pPhrase->aToken[jj].z = zBuf; |
| 194716 | zBuf += p->pPhrase->aToken[jj].n; |
| 194717 | } |
| 194718 | rc = SQLITE_OK; |
| 194719 | } |
| 194720 | |
| 194721 | getnextstring_out: |
| 194722 | if( pCursor ){ |
| 194723 | pModule->xClose(pCursor); |
| 194724 | } |
| 194725 | sqlite3_free(zTemp); |
| 194726 | if( rc!=SQLITE_OK ){ |
| 194727 | sqlite3_free(p); |
| 194728 | p = 0; |
| 194729 | } |
| 194730 | *ppExpr = p; |
| 194731 | return rc; |
| 194732 | } |
| 194733 | |
| 194734 | /* |
| 194735 | ** The output variable *ppExpr is populated with an allocated Fts3Expr |
| 194736 | ** structure, or set to 0 if the end of the input buffer is reached. |
| @@ -215395,12 +215538,12 @@ | |
| 215538 | #endif |
| 215539 | } |
| 215540 | sqlite3_str_append(pOut, "}", 1); |
| 215541 | } |
| 215542 | errCode = sqlite3_str_errcode(pOut); |
| 215543 | sqlite3_result_error_code(ctx, errCode); |
| 215544 | sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free); |
| 215545 | } |
| 215546 | |
| 215547 | /* This routine implements an SQL function that returns the "depth" parameter |
| 215548 | ** from the front of a blob that is an r-tree node. For example: |
| 215549 | ** |
| @@ -217912,11 +218055,11 @@ | |
| 218055 | return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, |
| 218056 | (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback |
| 218057 | ); |
| 218058 | } |
| 218059 | |
| 218060 | #ifndef SQLITE_CORE |
| 218061 | #ifdef _WIN32 |
| 218062 | __declspec(dllexport) |
| 218063 | #endif |
| 218064 | SQLITE_API int sqlite3_rtree_init( |
| 218065 | sqlite3 *db, |
| @@ -218503,11 +218646,11 @@ | |
| 218646 | } |
| 218647 | |
| 218648 | return rc; |
| 218649 | } |
| 218650 | |
| 218651 | #ifndef SQLITE_CORE |
| 218652 | #ifdef _WIN32 |
| 218653 | __declspec(dllexport) |
| 218654 | #endif |
| 218655 | SQLITE_API int sqlite3_icu_init( |
| 218656 | sqlite3 *db, |
| @@ -226177,10 +226320,12 @@ | |
| 226320 | if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ |
| 226321 | unsigned char *aPage = sqlite3PagerGetData(pDbPage); |
| 226322 | memcpy(aPage, pData, szPage); |
| 226323 | pTab->pgnoTrunc = 0; |
| 226324 | } |
| 226325 | }else{ |
| 226326 | pTab->pgnoTrunc = 0; |
| 226327 | } |
| 226328 | sqlite3PagerUnref(pDbPage); |
| 226329 | return rc; |
| 226330 | |
| 226331 | update_fail: |
| @@ -226210,11 +226355,15 @@ | |
| 226355 | static int dbpageSync(sqlite3_vtab *pVtab){ |
| 226356 | DbpageTable *pTab = (DbpageTable *)pVtab; |
| 226357 | if( pTab->pgnoTrunc>0 ){ |
| 226358 | Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; |
| 226359 | Pager *pPager = sqlite3BtreePager(pBt); |
| 226360 | sqlite3BtreeEnter(pBt); |
| 226361 | if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){ |
| 226362 | sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); |
| 226363 | } |
| 226364 | sqlite3BtreeLeave(pBt); |
| 226365 | } |
| 226366 | pTab->pgnoTrunc = 0; |
| 226367 | return SQLITE_OK; |
| 226368 | } |
| 226369 | |
| @@ -232804,20 +232953,46 @@ | |
| 232953 | #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ |
| 232954 | |
| 232955 | /************** End of sqlite3session.c **************************************/ |
| 232956 | /************** Begin file fts5.c ********************************************/ |
| 232957 | |
| 232958 | /* |
| 232959 | ** This, the "fts5.c" source file, is a composite file that is itself |
| 232960 | ** assembled from the following files: |
| 232961 | ** |
| 232962 | ** fts5.h |
| 232963 | ** fts5Int.h |
| 232964 | ** fts5parse.h <--- Generated from fts5parse.y by Lemon |
| 232965 | ** fts5parse.c <--- Generated from fts5parse.y by Lemon |
| 232966 | ** fts5_aux.c |
| 232967 | ** fts5_buffer.c |
| 232968 | ** fts5_config.c |
| 232969 | ** fts5_expr.c |
| 232970 | ** fts5_hash.c |
| 232971 | ** fts5_index.c |
| 232972 | ** fts5_main.c |
| 232973 | ** fts5_storage.c |
| 232974 | ** fts5_tokenize.c |
| 232975 | ** fts5_unicode2.c |
| 232976 | ** fts5_varint.c |
| 232977 | ** fts5_vocab.c |
| 232978 | */ |
| 232979 | #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) |
| 232980 | |
| 232981 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 232982 | # define NDEBUG 1 |
| 232983 | #endif |
| 232984 | #if defined(NDEBUG) && defined(SQLITE_DEBUG) |
| 232985 | # undef NDEBUG |
| 232986 | #endif |
| 232987 | |
| 232988 | #ifdef HAVE_STDINT_H |
| 232989 | /* #include <stdint.h> */ |
| 232990 | #endif |
| 232991 | #ifdef HAVE_INTTYPES_H |
| 232992 | /* #include <inttypes.h> */ |
| 232993 | #endif |
| 232994 | /* |
| 232995 | ** 2014 May 31 |
| 232996 | ** |
| 232997 | ** The author disclaims copyright to this source code. In place of |
| 232998 | ** a legal notice, here is a blessing: |
| @@ -233114,17 +233289,32 @@ | |
| 233289 | ** This is used to access token iToken of phrase hit iIdx within the |
| 233290 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 233291 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 233292 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 233293 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 233294 | ** bytes. |
| 233295 | ** |
| 233296 | ** The output text is not a copy of the document text that was tokenized. |
| 233297 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 233298 | ** includes any embedded 0x00 and trailing data. |
| 233299 | ** |
| 233300 | ** This API may be slow in some cases if the token identified by parameters |
| 233301 | ** iIdx and iToken matched a prefix token in the query. In most cases, the |
| 233302 | ** first call to this API for each prefix token in the query is forced |
| 233303 | ** to scan the portion of the full-text index that matches the prefix |
| 233304 | ** token to collect the extra data required by this API. If the prefix |
| 233305 | ** token matches a large number of token instances in the document set, |
| 233306 | ** this may be a performance problem. |
| 233307 | ** |
| 233308 | ** If the user knows in advance that a query may use this API for a |
| 233309 | ** prefix token, FTS5 may be configured to collect all required data as part |
| 233310 | ** of the initial querying of the full-text index, avoiding the second scan |
| 233311 | ** entirely. This also causes prefix queries that do not use this API to |
| 233312 | ** run more slowly and use more memory. FTS5 may be configured in this way |
| 233313 | ** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] |
| 233314 | ** option, or on a per-query basis using the |
| 233315 | ** [fts5_insttoken | fts5_insttoken()] user function. |
| 233316 | ** |
| 233317 | ** This API can be quite slow if used with an FTS5 table created with the |
| 233318 | ** "detail=none" or "detail=column" option. |
| 233319 | ** |
| 233320 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -233803,11 +233993,12 @@ | |
| 233993 | int nUsermerge; /* 'usermerge' setting */ |
| 233994 | int nHashSize; /* Bytes of memory for in-memory hash */ |
| 233995 | char *zRank; /* Name of rank function */ |
| 233996 | char *zRankArgs; /* Arguments to rank function */ |
| 233997 | int bSecureDelete; /* 'secure-delete' */ |
| 233998 | int nDeleteMerge; /* 'deletemerge' */ |
| 233999 | int bPrefixInsttoken; /* 'prefix-insttoken' */ |
| 234000 | |
| 234001 | /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ |
| 234002 | char **pzErrmsg; |
| 234003 | |
| 234004 | #ifdef SQLITE_DEBUG |
| @@ -234060,11 +234251,18 @@ | |
| 234251 | static int sqlite3Fts5StructureTest(Fts5Index*, void*); |
| 234252 | |
| 234253 | /* |
| 234254 | ** Used by xInstToken(): |
| 234255 | */ |
| 234256 | static int sqlite3Fts5IterToken( |
| 234257 | Fts5IndexIter *pIndexIter, |
| 234258 | const char *pToken, int nToken, |
| 234259 | i64 iRowid, |
| 234260 | int iCol, |
| 234261 | int iOff, |
| 234262 | const char **ppOut, int *pnOut |
| 234263 | ); |
| 234264 | |
| 234265 | /* |
| 234266 | ** Insert or remove data to or from the index. Each time a document is |
| 234267 | ** added to or removed from the index, this function is called one or more |
| 234268 | ** times. |
| @@ -238274,10 +238472,23 @@ | |
| 238472 | if( bVal<0 ){ |
| 238473 | *pbBadkey = 1; |
| 238474 | }else{ |
| 238475 | pConfig->bSecureDelete = (bVal ? 1 : 0); |
| 238476 | } |
| 238477 | } |
| 238478 | |
| 238479 | else if( 0==sqlite3_stricmp(zKey, "insttoken") ){ |
| 238480 | int bVal = -1; |
| 238481 | if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ |
| 238482 | bVal = sqlite3_value_int(pVal); |
| 238483 | } |
| 238484 | if( bVal<0 ){ |
| 238485 | *pbBadkey = 1; |
| 238486 | }else{ |
| 238487 | pConfig->bPrefixInsttoken = (bVal ? 1 : 0); |
| 238488 | } |
| 238489 | |
| 238490 | }else{ |
| 238491 | *pbBadkey = 1; |
| 238492 | } |
| 238493 | return rc; |
| 238494 | } |
| @@ -241409,11 +241620,11 @@ | |
| 241620 | && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 |
| 241621 | ){ |
| 241622 | int rc = sqlite3Fts5PoslistWriterAppend( |
| 241623 | &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff |
| 241624 | ); |
| 241625 | if( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){ |
| 241626 | int iCol = p->iOff>>32; |
| 241627 | int iTokOff = p->iOff & 0x7FFFFFFF; |
| 241628 | rc = sqlite3Fts5IndexIterWriteTokendata( |
| 241629 | pT->pIter, pToken, nToken, iRowid, iCol, iTokOff |
| 241630 | ); |
| @@ -241602,19 +241813,18 @@ | |
| 241813 | pPhrase = pExpr->apExprPhrase[iPhrase]; |
| 241814 | if( iToken<0 || iToken>=pPhrase->nTerm ){ |
| 241815 | return SQLITE_RANGE; |
| 241816 | } |
| 241817 | pTerm = &pPhrase->aTerm[iToken]; |
| 241818 | if( pExpr->pConfig->bTokendata || pTerm->bPrefix ){ |
| 241819 | rc = sqlite3Fts5IterToken( |
| 241820 | pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm, |
| 241821 | iRowid, iCol, iOff+iToken, ppOut, pnOut |
| 241822 | ); |
| 241823 | }else{ |
| 241824 | *ppOut = pTerm->pTerm; |
| 241825 | *pnOut = pTerm->nFullTerm; |
| 241826 | } |
| 241827 | return rc; |
| 241828 | } |
| 241829 | |
| 241830 | /* |
| @@ -248425,10 +248635,387 @@ | |
| 248635 | fts5BufferFree(&tmp); |
| 248636 | memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); |
| 248637 | *p1 = out; |
| 248638 | } |
| 248639 | |
| 248640 | |
| 248641 | /* |
| 248642 | ** Iterate through a range of entries in the FTS index, invoking the xVisit |
| 248643 | ** callback for each of them. |
| 248644 | ** |
| 248645 | ** Parameter pToken points to an nToken buffer containing an FTS index term |
| 248646 | ** (i.e. a document term with the preceding 1 byte index identifier - |
| 248647 | ** FTS5_MAIN_PREFIX or similar). If bPrefix is true, then the call visits |
| 248648 | ** all entries for terms that have pToken/nToken as a prefix. If bPrefix |
| 248649 | ** is false, then only entries with pToken/nToken as the entire key are |
| 248650 | ** visited. |
| 248651 | ** |
| 248652 | ** If the current table is a tokendata=1 table, then if bPrefix is true then |
| 248653 | ** each index term is treated separately. However, if bPrefix is false, then |
| 248654 | ** all index terms corresponding to pToken/nToken are collapsed into a single |
| 248655 | ** term before the callback is invoked. |
| 248656 | ** |
| 248657 | ** The callback invoked for each entry visited is specified by paramter xVisit. |
| 248658 | ** Each time it is invoked, it is passed a pointer to the Fts5Index object, |
| 248659 | ** a copy of the 7th paramter to this function (pCtx) and a pointer to the |
| 248660 | ** iterator that indicates the current entry. If the current entry is the |
| 248661 | ** first with a new term (i.e. different from that of the previous entry, |
| 248662 | ** including the very first term), then the final two parameters are passed |
| 248663 | ** a pointer to the term and its size in bytes, respectively. If the current |
| 248664 | ** entry is not the first associated with its term, these two parameters |
| 248665 | ** are passed 0. |
| 248666 | ** |
| 248667 | ** If parameter pColset is not NULL, then it is used to filter entries before |
| 248668 | ** the callback is invoked. |
| 248669 | */ |
| 248670 | static int fts5VisitEntries( |
| 248671 | Fts5Index *p, /* Fts5 index object */ |
| 248672 | Fts5Colset *pColset, /* Columns filter to apply, or NULL */ |
| 248673 | u8 *pToken, /* Buffer containing token */ |
| 248674 | int nToken, /* Size of buffer pToken in bytes */ |
| 248675 | int bPrefix, /* True for a prefix scan */ |
| 248676 | void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int), |
| 248677 | void *pCtx /* Passed as second argument to xVisit() */ |
| 248678 | ){ |
| 248679 | const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0) |
| 248680 | | FTS5INDEX_QUERY_SKIPEMPTY |
| 248681 | | FTS5INDEX_QUERY_NOOUTPUT; |
| 248682 | Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ |
| 248683 | int bNewTerm = 1; |
| 248684 | Fts5Structure *pStruct = fts5StructureRead(p); |
| 248685 | |
| 248686 | fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); |
| 248687 | fts5IterSetOutputCb(&p->rc, p1); |
| 248688 | for( /* no-op */ ; |
| 248689 | fts5MultiIterEof(p, p1)==0; |
| 248690 | fts5MultiIterNext2(p, p1, &bNewTerm) |
| 248691 | ){ |
| 248692 | Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; |
| 248693 | int nNew = 0; |
| 248694 | const u8 *pNew = 0; |
| 248695 | |
| 248696 | p1->xSetOutputs(p1, pSeg); |
| 248697 | if( p->rc ) break; |
| 248698 | |
| 248699 | if( bNewTerm ){ |
| 248700 | nNew = pSeg->term.n; |
| 248701 | pNew = pSeg->term.p; |
| 248702 | if( nNew<nToken || memcmp(pToken, pNew, nToken) ) break; |
| 248703 | } |
| 248704 | |
| 248705 | xVisit(p, pCtx, p1, pNew, nNew); |
| 248706 | } |
| 248707 | fts5MultiIterFree(p1); |
| 248708 | |
| 248709 | fts5StructureRelease(pStruct); |
| 248710 | return p->rc; |
| 248711 | } |
| 248712 | |
| 248713 | |
| 248714 | /* |
| 248715 | ** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an |
| 248716 | ** array of these for each row it visits (so all iRowid fields are the same). |
| 248717 | ** Or, for an iterator used by an "ORDER BY rank" query, it accumulates an |
| 248718 | ** array of these for the entire query (in which case iRowid fields may take |
| 248719 | ** a variety of values). |
| 248720 | ** |
| 248721 | ** Each instance in the array indicates the iterator (and therefore term) |
| 248722 | ** associated with position iPos of rowid iRowid. This is used by the |
| 248723 | ** xInstToken() API. |
| 248724 | ** |
| 248725 | ** iRowid: |
| 248726 | ** Rowid for the current entry. |
| 248727 | ** |
| 248728 | ** iPos: |
| 248729 | ** Position of current entry within row. In the usual ((iCol<<32)+iOff) |
| 248730 | ** format (e.g. see macros FTS5_POS2COLUMN() and FTS5_POS2OFFSET()). |
| 248731 | ** |
| 248732 | ** iIter: |
| 248733 | ** If the Fts5TokenDataIter iterator that the entry is part of is |
| 248734 | ** actually an iterator (i.e. with nIter>0, not just a container for |
| 248735 | ** Fts5TokenDataMap structures), then this variable is an index into |
| 248736 | ** the apIter[] array. The corresponding term is that which the iterator |
| 248737 | ** at apIter[iIter] currently points to. |
| 248738 | ** |
| 248739 | ** Or, if the Fts5TokenDataIter iterator is just a container object |
| 248740 | ** (nIter==0), then iIter is an index into the term.p[] buffer where |
| 248741 | ** the term is stored. |
| 248742 | ** |
| 248743 | ** nByte: |
| 248744 | ** In the case where iIter is an index into term.p[], this variable |
| 248745 | ** is the size of the term in bytes. If iIter is an index into apIter[], |
| 248746 | ** this variable is unused. |
| 248747 | */ |
| 248748 | struct Fts5TokenDataMap { |
| 248749 | i64 iRowid; /* Row this token is located in */ |
| 248750 | i64 iPos; /* Position of token */ |
| 248751 | int iIter; /* Iterator token was read from */ |
| 248752 | int nByte; /* Length of token in bytes (or 0) */ |
| 248753 | }; |
| 248754 | |
| 248755 | /* |
| 248756 | ** An object used to supplement Fts5Iter for tokendata=1 iterators. |
| 248757 | ** |
| 248758 | ** This object serves two purposes. The first is as a container for an array |
| 248759 | ** of Fts5TokenDataMap structures, which are used to find the token required |
| 248760 | ** when the xInstToken() API is used. This is done by the nMapAlloc, nMap and |
| 248761 | ** aMap[] variables. |
| 248762 | */ |
| 248763 | struct Fts5TokenDataIter { |
| 248764 | int nMapAlloc; /* Allocated size of aMap[] in entries */ |
| 248765 | int nMap; /* Number of valid entries in aMap[] */ |
| 248766 | Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */ |
| 248767 | |
| 248768 | /* The following are used for prefix-queries only. */ |
| 248769 | Fts5Buffer terms; |
| 248770 | |
| 248771 | /* The following are used for other full-token tokendata queries only. */ |
| 248772 | int nIter; |
| 248773 | int nIterAlloc; |
| 248774 | Fts5PoslistReader *aPoslistReader; |
| 248775 | int *aPoslistToIter; |
| 248776 | Fts5Iter *apIter[1]; |
| 248777 | }; |
| 248778 | |
| 248779 | /* |
| 248780 | ** The two input arrays - a1[] and a2[] - are in sorted order. This function |
| 248781 | ** merges the two arrays together and writes the result to output array |
| 248782 | ** aOut[]. aOut[] is guaranteed to be large enough to hold the result. |
| 248783 | ** |
| 248784 | ** Duplicate entries are copied into the output. So the size of the output |
| 248785 | ** array is always (n1+n2) entries. |
| 248786 | */ |
| 248787 | static void fts5TokendataMerge( |
| 248788 | Fts5TokenDataMap *a1, int n1, /* Input array 1 */ |
| 248789 | Fts5TokenDataMap *a2, int n2, /* Input array 2 */ |
| 248790 | Fts5TokenDataMap *aOut /* Output array */ |
| 248791 | ){ |
| 248792 | int i1 = 0; |
| 248793 | int i2 = 0; |
| 248794 | |
| 248795 | assert( n1>=0 && n2>=0 ); |
| 248796 | while( i1<n1 || i2<n2 ){ |
| 248797 | Fts5TokenDataMap *pOut = &aOut[i1+i2]; |
| 248798 | if( i2>=n2 || (i1<n1 && ( |
| 248799 | a1[i1].iRowid<a2[i2].iRowid |
| 248800 | || (a1[i1].iRowid==a2[i2].iRowid && a1[i1].iPos<=a2[i2].iPos) |
| 248801 | ))){ |
| 248802 | memcpy(pOut, &a1[i1], sizeof(Fts5TokenDataMap)); |
| 248803 | i1++; |
| 248804 | }else{ |
| 248805 | memcpy(pOut, &a2[i2], sizeof(Fts5TokenDataMap)); |
| 248806 | i2++; |
| 248807 | } |
| 248808 | } |
| 248809 | } |
| 248810 | |
| 248811 | |
| 248812 | /* |
| 248813 | ** Append a mapping to the token-map belonging to object pT. |
| 248814 | */ |
| 248815 | static void fts5TokendataIterAppendMap( |
| 248816 | Fts5Index *p, |
| 248817 | Fts5TokenDataIter *pT, |
| 248818 | int iIter, |
| 248819 | int nByte, |
| 248820 | i64 iRowid, |
| 248821 | i64 iPos |
| 248822 | ){ |
| 248823 | if( p->rc==SQLITE_OK ){ |
| 248824 | if( pT->nMap==pT->nMapAlloc ){ |
| 248825 | int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; |
| 248826 | int nAlloc = nNew * sizeof(Fts5TokenDataMap); |
| 248827 | Fts5TokenDataMap *aNew; |
| 248828 | |
| 248829 | aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc); |
| 248830 | if( aNew==0 ){ |
| 248831 | p->rc = SQLITE_NOMEM; |
| 248832 | return; |
| 248833 | } |
| 248834 | |
| 248835 | pT->aMap = aNew; |
| 248836 | pT->nMapAlloc = nNew; |
| 248837 | } |
| 248838 | |
| 248839 | pT->aMap[pT->nMap].iRowid = iRowid; |
| 248840 | pT->aMap[pT->nMap].iPos = iPos; |
| 248841 | pT->aMap[pT->nMap].iIter = iIter; |
| 248842 | pT->aMap[pT->nMap].nByte = nByte; |
| 248843 | pT->nMap++; |
| 248844 | } |
| 248845 | } |
| 248846 | |
| 248847 | /* |
| 248848 | ** Sort the contents of the pT->aMap[] array. |
| 248849 | ** |
| 248850 | ** The sorting algorithm requries a malloc(). If this fails, an error code |
| 248851 | ** is left in Fts5Index.rc before returning. |
| 248852 | */ |
| 248853 | static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){ |
| 248854 | Fts5TokenDataMap *aTmp = 0; |
| 248855 | int nByte = pT->nMap * sizeof(Fts5TokenDataMap); |
| 248856 | |
| 248857 | aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte); |
| 248858 | if( aTmp ){ |
| 248859 | Fts5TokenDataMap *a1 = pT->aMap; |
| 248860 | Fts5TokenDataMap *a2 = aTmp; |
| 248861 | i64 nHalf; |
| 248862 | |
| 248863 | for(nHalf=1; nHalf<pT->nMap; nHalf=nHalf*2){ |
| 248864 | int i1; |
| 248865 | for(i1=0; i1<pT->nMap; i1+=(nHalf*2)){ |
| 248866 | int n1 = MIN(nHalf, pT->nMap-i1); |
| 248867 | int n2 = MIN(nHalf, pT->nMap-i1-n1); |
| 248868 | fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]); |
| 248869 | } |
| 248870 | SWAPVAL(Fts5TokenDataMap*, a1, a2); |
| 248871 | } |
| 248872 | |
| 248873 | if( a1!=pT->aMap ){ |
| 248874 | memcpy(pT->aMap, a1, pT->nMap*sizeof(Fts5TokenDataMap)); |
| 248875 | } |
| 248876 | sqlite3_free(aTmp); |
| 248877 | |
| 248878 | #ifdef SQLITE_DEBUG |
| 248879 | { |
| 248880 | int ii; |
| 248881 | for(ii=1; ii<pT->nMap; ii++){ |
| 248882 | Fts5TokenDataMap *p1 = &pT->aMap[ii-1]; |
| 248883 | Fts5TokenDataMap *p2 = &pT->aMap[ii]; |
| 248884 | assert( p1->iRowid<p2->iRowid |
| 248885 | || (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos) |
| 248886 | ); |
| 248887 | } |
| 248888 | } |
| 248889 | #endif |
| 248890 | } |
| 248891 | } |
| 248892 | |
| 248893 | /* |
| 248894 | ** Delete an Fts5TokenDataIter structure and its contents. |
| 248895 | */ |
| 248896 | static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ |
| 248897 | if( pSet ){ |
| 248898 | int ii; |
| 248899 | for(ii=0; ii<pSet->nIter; ii++){ |
| 248900 | fts5MultiIterFree(pSet->apIter[ii]); |
| 248901 | } |
| 248902 | fts5BufferFree(&pSet->terms); |
| 248903 | sqlite3_free(pSet->aPoslistReader); |
| 248904 | sqlite3_free(pSet->aMap); |
| 248905 | sqlite3_free(pSet); |
| 248906 | } |
| 248907 | } |
| 248908 | |
| 248909 | |
| 248910 | /* |
| 248911 | ** fts5VisitEntries() context object used by fts5SetupPrefixIterTokendata() |
| 248912 | ** to pass data to prefixIterSetupTokendataCb(). |
| 248913 | */ |
| 248914 | typedef struct TokendataSetupCtx TokendataSetupCtx; |
| 248915 | struct TokendataSetupCtx { |
| 248916 | Fts5TokenDataIter *pT; /* Object being populated with mappings */ |
| 248917 | int iTermOff; /* Offset of current term in terms.p[] */ |
| 248918 | int nTermByte; /* Size of current term in bytes */ |
| 248919 | }; |
| 248920 | |
| 248921 | /* |
| 248922 | ** fts5VisitEntries() callback used by fts5SetupPrefixIterTokendata(). This |
| 248923 | ** callback adds an entry to the Fts5TokenDataIter.aMap[] array for each |
| 248924 | ** position in the current position-list. It doesn't matter that some of |
| 248925 | ** these may be out of order - they will be sorted later. |
| 248926 | */ |
| 248927 | static void prefixIterSetupTokendataCb( |
| 248928 | Fts5Index *p, |
| 248929 | void *pCtx, |
| 248930 | Fts5Iter *p1, |
| 248931 | const u8 *pNew, |
| 248932 | int nNew |
| 248933 | ){ |
| 248934 | TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx; |
| 248935 | int iPosOff = 0; |
| 248936 | i64 iPos = 0; |
| 248937 | |
| 248938 | if( pNew ){ |
| 248939 | pSetup->nTermByte = nNew-1; |
| 248940 | pSetup->iTermOff = pSetup->pT->terms.n; |
| 248941 | fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1, pNew+1); |
| 248942 | } |
| 248943 | |
| 248944 | while( 0==sqlite3Fts5PoslistNext64( |
| 248945 | p1->base.pData, p1->base.nData, &iPosOff, &iPos |
| 248946 | ) ){ |
| 248947 | fts5TokendataIterAppendMap(p, |
| 248948 | pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos |
| 248949 | ); |
| 248950 | } |
| 248951 | } |
| 248952 | |
| 248953 | |
| 248954 | /* |
| 248955 | ** Context object passed by fts5SetupPrefixIter() to fts5VisitEntries(). |
| 248956 | */ |
| 248957 | typedef struct PrefixSetupCtx PrefixSetupCtx; |
| 248958 | struct PrefixSetupCtx { |
| 248959 | void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); |
| 248960 | void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); |
| 248961 | i64 iLastRowid; |
| 248962 | int nMerge; |
| 248963 | Fts5Buffer *aBuf; |
| 248964 | int nBuf; |
| 248965 | Fts5Buffer doclist; |
| 248966 | TokendataSetupCtx *pTokendata; |
| 248967 | }; |
| 248968 | |
| 248969 | /* |
| 248970 | ** fts5VisitEntries() callback used by fts5SetupPrefixIter() |
| 248971 | */ |
| 248972 | static void prefixIterSetupCb( |
| 248973 | Fts5Index *p, |
| 248974 | void *pCtx, |
| 248975 | Fts5Iter *p1, |
| 248976 | const u8 *pNew, |
| 248977 | int nNew |
| 248978 | ){ |
| 248979 | PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx; |
| 248980 | const int nMerge = pSetup->nMerge; |
| 248981 | |
| 248982 | if( p1->base.nData>0 ){ |
| 248983 | if( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){ |
| 248984 | int i; |
| 248985 | for(i=0; p->rc==SQLITE_OK && pSetup->doclist.n; i++){ |
| 248986 | int i1 = i*nMerge; |
| 248987 | int iStore; |
| 248988 | assert( i1+nMerge<=pSetup->nBuf ); |
| 248989 | for(iStore=i1; iStore<i1+nMerge; iStore++){ |
| 248990 | if( pSetup->aBuf[iStore].n==0 ){ |
| 248991 | fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]); |
| 248992 | fts5BufferZero(&pSetup->doclist); |
| 248993 | break; |
| 248994 | } |
| 248995 | } |
| 248996 | if( iStore==i1+nMerge ){ |
| 248997 | pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]); |
| 248998 | for(iStore=i1; iStore<i1+nMerge; iStore++){ |
| 248999 | fts5BufferZero(&pSetup->aBuf[iStore]); |
| 249000 | } |
| 249001 | } |
| 249002 | } |
| 249003 | pSetup->iLastRowid = 0; |
| 249004 | } |
| 249005 | |
| 249006 | pSetup->xAppend( |
| 249007 | p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist |
| 249008 | ); |
| 249009 | pSetup->iLastRowid = p1->base.iRowid; |
| 249010 | } |
| 249011 | |
| 249012 | if( pSetup->pTokendata ){ |
| 249013 | prefixIterSetupTokendataCb(p, (void*)pSetup->pTokendata, p1, pNew, nNew); |
| 249014 | } |
| 249015 | } |
| 249016 | |
| 249017 | static void fts5SetupPrefixIter( |
| 249018 | Fts5Index *p, /* Index to read from */ |
| 249019 | int bDesc, /* True for "ORDER BY rowid DESC" */ |
| 249020 | int iIdx, /* Index to scan for data */ |
| 249021 | u8 *pToken, /* Buffer containing prefix to match */ |
| @@ -248435,137 +249022,91 @@ | |
| 249022 | int nToken, /* Size of buffer pToken in bytes */ |
| 249023 | Fts5Colset *pColset, /* Restrict matches to these columns */ |
| 249024 | Fts5Iter **ppIter /* OUT: New iterator */ |
| 249025 | ){ |
| 249026 | Fts5Structure *pStruct; |
| 249027 | PrefixSetupCtx s; |
| 249028 | TokendataSetupCtx s2; |
| 249029 | |
| 249030 | memset(&s, 0, sizeof(s)); |
| 249031 | memset(&s2, 0, sizeof(s2)); |
| 249032 | |
| 249033 | s.nMerge = 1; |
| 249034 | s.iLastRowid = 0; |
| 249035 | s.nBuf = 32; |
| 249036 | if( iIdx==0 |
| 249037 | && p->pConfig->eDetail==FTS5_DETAIL_FULL |
| 249038 | && p->pConfig->bPrefixInsttoken |
| 249039 | ){ |
| 249040 | s.pTokendata = &s2; |
| 249041 | s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, sizeof(*s2.pT)); |
| 249042 | } |
| 249043 | |
| 249044 | if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ |
| 249045 | s.xMerge = fts5MergeRowidLists; |
| 249046 | s.xAppend = fts5AppendRowid; |
| 249047 | }else{ |
| 249048 | s.nMerge = FTS5_MERGE_NLIST-1; |
| 249049 | s.nBuf = s.nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ |
| 249050 | s.xMerge = fts5MergePrefixLists; |
| 249051 | s.xAppend = fts5AppendPoslist; |
| 249052 | } |
| 249053 | |
| 249054 | s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*s.nBuf); |
| 249055 | pStruct = fts5StructureRead(p); |
| 249056 | assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) ); |
| 249057 | |
| 249058 | if( p->rc==SQLITE_OK ){ |
| 249059 | void *pCtx = (void*)&s; |
| 249060 | int i; |
| 249061 | Fts5Data *pData; |
| 249062 | |
| 249063 | /* If iIdx is non-zero, then it is the number of a prefix-index for |
| 249064 | ** prefixes 1 character longer than the prefix being queried for. That |
| 249065 | ** index contains all the doclists required, except for the one |
| 249066 | ** corresponding to the prefix itself. That one is extracted from the |
| 249067 | ** main term index here. */ |
| 249068 | if( iIdx!=0 ){ |
| 249069 | pToken[0] = FTS5_MAIN_PREFIX; |
| 249070 | fts5VisitEntries(p, pColset, pToken, nToken, 0, prefixIterSetupCb, pCtx); |
| 249071 | } |
| 249072 | |
| 249073 | pToken[0] = FTS5_MAIN_PREFIX + iIdx; |
| 249074 | fts5VisitEntries(p, pColset, pToken, nToken, 1, prefixIterSetupCb, pCtx); |
| 249075 | |
| 249076 | assert( (s.nBuf%s.nMerge)==0 ); |
| 249077 | for(i=0; i<s.nBuf; i+=s.nMerge){ |
| 249078 | int iFree; |
| 249079 | if( p->rc==SQLITE_OK ){ |
| 249080 | s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]); |
| 249081 | } |
| 249082 | for(iFree=i; iFree<i+s.nMerge; iFree++){ |
| 249083 | fts5BufferFree(&s.aBuf[iFree]); |
| 249084 | } |
| 249085 | } |
| 249086 | |
| 249087 | pData = fts5IdxMalloc(p, sizeof(*pData)+s.doclist.n+FTS5_DATA_ZERO_PADDING); |
| 249088 | assert( pData!=0 || p->rc!=SQLITE_OK ); |
| 249089 | if( pData ){ |
| 249090 | pData->p = (u8*)&pData[1]; |
| 249091 | pData->nn = pData->szLeaf = s.doclist.n; |
| 249092 | if( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n); |
| 249093 | fts5MultiIterNew2(p, pData, bDesc, ppIter); |
| 249094 | } |
| 249095 | |
| 249096 | assert( (*ppIter)!=0 || p->rc!=SQLITE_OK ); |
| 249097 | if( p->rc==SQLITE_OK && s.pTokendata ){ |
| 249098 | fts5TokendataIterSortMap(p, s2.pT); |
| 249099 | (*ppIter)->pTokenDataIter = s2.pT; |
| 249100 | s2.pT = 0; |
| 249101 | } |
| 249102 | } |
| 249103 | |
| 249104 | fts5TokendataIterDelete(s2.pT); |
| 249105 | fts5BufferFree(&s.doclist); |
| 249106 | fts5StructureRelease(pStruct); |
| 249107 | sqlite3_free(s.aBuf); |
| 249108 | } |
| 249109 | |
| 249110 | |
| 249111 | /* |
| 249112 | ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain |
| @@ -248815,42 +249356,10 @@ | |
| 249356 | static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ |
| 249357 | fts5DataRelease(pSeg->pLeaf); |
| 249358 | pSeg->pLeaf = 0; |
| 249359 | } |
| 249360 | |
| 249361 | /* |
| 249362 | ** This function appends iterator pAppend to Fts5TokenDataIter pIn and |
| 249363 | ** returns the result. |
| 249364 | */ |
| 249365 | static Fts5TokenDataIter *fts5AppendTokendataIter( |
| @@ -248883,58 +249392,10 @@ | |
| 249392 | assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); |
| 249393 | |
| 249394 | return pRet; |
| 249395 | } |
| 249396 | |
| 249397 | /* |
| 249398 | ** The iterator passed as the only argument must be a tokendata=1 iterator |
| 249399 | ** (pIter->pTokenDataIter!=0). This function sets the iterator output |
| 249400 | ** variables (pIter->base.*) according to the contents of the current |
| 249401 | ** row. |
| @@ -248971,11 +249432,11 @@ | |
| 249432 | int eDetail = pIter->pIndex->pConfig->eDetail; |
| 249433 | pIter->base.bEof = 0; |
| 249434 | pIter->base.iRowid = iRowid; |
| 249435 | |
| 249436 | if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ |
| 249437 | fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, 0, iRowid, -1); |
| 249438 | }else |
| 249439 | if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ |
| 249440 | int nReader = 0; |
| 249441 | int nByte = 0; |
| 249442 | i64 iPrev = 0; |
| @@ -249224,10 +249685,11 @@ | |
| 249685 | |
| 249686 | if( p->rc==SQLITE_OK ){ |
| 249687 | pRet = fts5MultiIterAlloc(p, 0); |
| 249688 | } |
| 249689 | if( pRet ){ |
| 249690 | pRet->nSeg = 0; |
| 249691 | pRet->pTokenDataIter = pSet; |
| 249692 | if( pSet ){ |
| 249693 | fts5IterSetOutputsTokendata(pRet); |
| 249694 | }else{ |
| 249695 | pRet->base.bEof = 1; |
| @@ -249238,11 +249700,10 @@ | |
| 249700 | |
| 249701 | fts5StructureRelease(pStruct); |
| 249702 | fts5BufferFree(&bSeek); |
| 249703 | return pRet; |
| 249704 | } |
| 249705 | |
| 249706 | /* |
| 249707 | ** Open a new iterator to iterate though all rowid that match the |
| 249708 | ** specified token or token prefix. |
| 249709 | */ |
| @@ -249262,12 +249723,18 @@ | |
| 249723 | |
| 249724 | if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ |
| 249725 | int iIdx = 0; /* Index to search */ |
| 249726 | int iPrefixIdx = 0; /* +1 prefix index */ |
| 249727 | int bTokendata = pConfig->bTokendata; |
| 249728 | assert( buf.p!=0 ); |
| 249729 | if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); |
| 249730 | |
| 249731 | /* The NOTOKENDATA flag is set when each token in a tokendata=1 table |
| 249732 | ** should be treated individually, instead of merging all those with |
| 249733 | ** a common prefix into a single entry. This is used, for example, by |
| 249734 | ** queries performed as part of an integrity-check, or by the fts5vocab |
| 249735 | ** module. */ |
| 249736 | if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ |
| 249737 | bTokendata = 0; |
| 249738 | } |
| 249739 | |
| 249740 | /* Figure out which index to search and set iIdx accordingly. If this |
| @@ -249294,11 +249761,11 @@ | |
| 249761 | if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; |
| 249762 | } |
| 249763 | } |
| 249764 | |
| 249765 | if( bTokendata && iIdx==0 ){ |
| 249766 | buf.p[0] = FTS5_MAIN_PREFIX; |
| 249767 | pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); |
| 249768 | }else if( iIdx<=pConfig->nPrefix ){ |
| 249769 | /* Straight index lookup */ |
| 249770 | Fts5Structure *pStruct = fts5StructureRead(p); |
| 249771 | buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); |
| @@ -249307,11 +249774,11 @@ | |
| 249774 | pColset, buf.p, nToken+1, -1, 0, &pRet |
| 249775 | ); |
| 249776 | fts5StructureRelease(pStruct); |
| 249777 | } |
| 249778 | }else{ |
| 249779 | /* Scan multiple terms in the main index for a prefix query. */ |
| 249780 | int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; |
| 249781 | fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); |
| 249782 | if( pRet==0 ){ |
| 249783 | assert( p->rc!=SQLITE_OK ); |
| 249784 | }else{ |
| @@ -249343,11 +249810,12 @@ | |
| 249810 | ** Move to the next matching rowid. |
| 249811 | */ |
| 249812 | static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ |
| 249813 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249814 | assert( pIter->pIndex->rc==SQLITE_OK ); |
| 249815 | if( pIter->nSeg==0 ){ |
| 249816 | assert( pIter->pTokenDataIter ); |
| 249817 | fts5TokendataIterNext(pIter, 0, 0); |
| 249818 | }else{ |
| 249819 | fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); |
| 249820 | } |
| 249821 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249380,11 +249848,12 @@ | |
| 249848 | ** definition of "at or after" depends on whether this iterator iterates |
| 249849 | ** in ascending or descending rowid order. |
| 249850 | */ |
| 249851 | static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ |
| 249852 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249853 | if( pIter->nSeg==0 ){ |
| 249854 | assert( pIter->pTokenDataIter ); |
| 249855 | fts5TokendataIterNext(pIter, 1, iMatch); |
| 249856 | }else{ |
| 249857 | fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); |
| 249858 | } |
| 249859 | return fts5IndexReturn(pIter->pIndex); |
| @@ -249398,32 +249867,88 @@ | |
| 249867 | const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); |
| 249868 | assert_nc( z || n<=1 ); |
| 249869 | *pn = n-1; |
| 249870 | return (z ? &z[1] : 0); |
| 249871 | } |
| 249872 | |
| 249873 | /* |
| 249874 | ** pIter is a prefix query. This function populates pIter->pTokenDataIter |
| 249875 | ** with an Fts5TokenDataIter object containing mappings for all rows |
| 249876 | ** matched by the query. |
| 249877 | */ |
| 249878 | static int fts5SetupPrefixIterTokendata( |
| 249879 | Fts5Iter *pIter, |
| 249880 | const char *pToken, /* Token prefix to search for */ |
| 249881 | int nToken /* Size of pToken in bytes */ |
| 249882 | ){ |
| 249883 | Fts5Index *p = pIter->pIndex; |
| 249884 | Fts5Buffer token = {0, 0, 0}; |
| 249885 | TokendataSetupCtx ctx; |
| 249886 | |
| 249887 | memset(&ctx, 0, sizeof(ctx)); |
| 249888 | |
| 249889 | fts5BufferGrow(&p->rc, &token, nToken+1); |
| 249890 | assert( token.p!=0 || p->rc!=SQLITE_OK ); |
| 249891 | ctx.pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*ctx.pT)); |
| 249892 | |
| 249893 | if( p->rc==SQLITE_OK ){ |
| 249894 | |
| 249895 | /* Fill in the token prefix to search for */ |
| 249896 | token.p[0] = FTS5_MAIN_PREFIX; |
| 249897 | memcpy(&token.p[1], pToken, nToken); |
| 249898 | token.n = nToken+1; |
| 249899 | |
| 249900 | fts5VisitEntries( |
| 249901 | p, 0, token.p, token.n, 1, prefixIterSetupTokendataCb, (void*)&ctx |
| 249902 | ); |
| 249903 | |
| 249904 | fts5TokendataIterSortMap(p, ctx.pT); |
| 249905 | } |
| 249906 | |
| 249907 | if( p->rc==SQLITE_OK ){ |
| 249908 | pIter->pTokenDataIter = ctx.pT; |
| 249909 | }else{ |
| 249910 | fts5TokendataIterDelete(ctx.pT); |
| 249911 | } |
| 249912 | fts5BufferFree(&token); |
| 249913 | |
| 249914 | return fts5IndexReturn(p); |
| 249915 | } |
| 249916 | |
| 249917 | /* |
| 249918 | ** This is used by xInstToken() to access the token at offset iOff, column |
| 249919 | ** iCol of row iRowid. The token is returned via output variables *ppOut |
| 249920 | ** and *pnOut. The iterator passed as the first argument must be a tokendata=1 |
| 249921 | ** iterator (pIter->pTokenDataIter!=0). |
| 249922 | ** |
| 249923 | ** pToken/nToken: |
| 249924 | */ |
| 249925 | static int sqlite3Fts5IterToken( |
| 249926 | Fts5IndexIter *pIndexIter, |
| 249927 | const char *pToken, int nToken, |
| 249928 | i64 iRowid, |
| 249929 | int iCol, |
| 249930 | int iOff, |
| 249931 | const char **ppOut, int *pnOut |
| 249932 | ){ |
| 249933 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249934 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 249935 | i64 iPos = (((i64)iCol)<<32) + iOff; |
| 249936 | Fts5TokenDataMap *aMap = 0; |
| 249937 | int i1 = 0; |
| 249938 | int i2 = 0; |
| 249939 | int iTest = 0; |
| 249940 | |
| 249941 | assert( pT || (pToken && pIter->nSeg>0) ); |
| 249942 | if( pT==0 ){ |
| 249943 | int rc = fts5SetupPrefixIterTokendata(pIter, pToken, nToken); |
| 249944 | if( rc!=SQLITE_OK ) return rc; |
| 249945 | pT = pIter->pTokenDataIter; |
| 249946 | } |
| 249947 | |
| 249948 | i2 = pT->nMap; |
| 249949 | aMap = pT->aMap; |
| 249950 | |
| 249951 | while( i2>i1 ){ |
| 249952 | iTest = (i1 + i2) / 2; |
| 249953 | |
| 249954 | if( aMap[iTest].iRowid<iRowid ){ |
| @@ -249443,13 +249968,19 @@ | |
| 249968 | } |
| 249969 | } |
| 249970 | } |
| 249971 | |
| 249972 | if( i2>i1 ){ |
| 249973 | if( pIter->nSeg==0 ){ |
| 249974 | Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; |
| 249975 | *ppOut = (const char*)pMap->aSeg[0].term.p+1; |
| 249976 | *pnOut = pMap->aSeg[0].term.n-1; |
| 249977 | }else{ |
| 249978 | Fts5TokenDataMap *p = &aMap[iTest]; |
| 249979 | *ppOut = (const char*)&pT->terms.p[p->iIter]; |
| 249980 | *pnOut = aMap[iTest].nByte; |
| 249981 | } |
| 249982 | } |
| 249983 | |
| 249984 | return SQLITE_OK; |
| 249985 | } |
| 249986 | |
| @@ -249457,11 +249988,13 @@ | |
| 249988 | ** Clear any existing entries from the token-map associated with the |
| 249989 | ** iterator passed as the only argument. |
| 249990 | */ |
| 249991 | static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ |
| 249992 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 249993 | if( pIter && pIter->pTokenDataIter |
| 249994 | && (pIter->nSeg==0 || pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_FULL) |
| 249995 | ){ |
| 249996 | pIter->pTokenDataIter->nMap = 0; |
| 249997 | } |
| 249998 | } |
| 249999 | |
| 250000 | /* |
| @@ -249477,21 +250010,33 @@ | |
| 250010 | i64 iRowid, int iCol, int iOff |
| 250011 | ){ |
| 250012 | Fts5Iter *pIter = (Fts5Iter*)pIndexIter; |
| 250013 | Fts5TokenDataIter *pT = pIter->pTokenDataIter; |
| 250014 | Fts5Index *p = pIter->pIndex; |
| 250015 | i64 iPos = (((i64)iCol)<<32) + iOff; |
| 250016 | |
| 250017 | assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); |
| 250018 | assert( pIter->pTokenDataIter || pIter->nSeg>0 ); |
| 250019 | if( pIter->nSeg>0 ){ |
| 250020 | /* This is a prefix term iterator. */ |
| 250021 | if( pT==0 ){ |
| 250022 | pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*pT)); |
| 250023 | pIter->pTokenDataIter = pT; |
| 250024 | } |
| 250025 | if( pT ){ |
| 250026 | fts5TokendataIterAppendMap(p, pT, pT->terms.n, nToken, iRowid, iPos); |
| 250027 | fts5BufferAppendBlob(&p->rc, &pT->terms, nToken, (const u8*)pToken); |
| 250028 | } |
| 250029 | }else{ |
| 250030 | int ii; |
| 250031 | for(ii=0; ii<pT->nIter; ii++){ |
| 250032 | Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; |
| 250033 | if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; |
| 250034 | } |
| 250035 | if( ii<pT->nIter ){ |
| 250036 | fts5TokendataIterAppendMap(p, pT, ii, 0, iRowid, iPos); |
| 250037 | } |
| 250038 | } |
| 250039 | return fts5IndexReturn(p); |
| 250040 | } |
| 250041 | |
| 250042 | /* |
| @@ -251392,10 +251937,11 @@ | |
| 251937 | ** containing a copy of the header from an Fts5Config pointer. |
| 251938 | */ |
| 251939 | #define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr )) |
| 251940 | #define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr)) |
| 251941 | |
| 251942 | #define FTS5_INSTTOKEN_SUBTYPE 73 |
| 251943 | |
| 251944 | /* |
| 251945 | ** Each auxiliary function registered with the FTS5 module is represented |
| 251946 | ** by an object of the following type. All such objects are stored as part |
| 251947 | ** of the Fts5Global.pAux list. |
| @@ -251931,10 +252477,11 @@ | |
| 252477 | ){ |
| 252478 | /* A MATCH operator or equivalent */ |
| 252479 | if( p->usable==0 || iCol<0 ){ |
| 252480 | /* As there exists an unusable MATCH constraint this is an |
| 252481 | ** unusable plan. Return SQLITE_CONSTRAINT. */ |
| 252482 | idxStr[iIdxStr] = 0; |
| 252483 | return SQLITE_CONSTRAINT; |
| 252484 | }else{ |
| 252485 | if( iCol==nCol+1 ){ |
| 252486 | if( bSeenRank ) continue; |
| 252487 | idxStr[iIdxStr++] = 'r'; |
| @@ -252716,10 +253263,11 @@ | |
| 253263 | sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ |
| 253264 | sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ |
| 253265 | sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ |
| 253266 | int iCol; /* Column on LHS of MATCH operator */ |
| 253267 | char **pzErrmsg = pConfig->pzErrmsg; |
| 253268 | int bPrefixInsttoken = pConfig->bPrefixInsttoken; |
| 253269 | int i; |
| 253270 | int iIdxStr = 0; |
| 253271 | Fts5Expr *pExpr = 0; |
| 253272 | |
| 253273 | assert( pConfig->bLock==0 ); |
| @@ -252751,10 +253299,13 @@ | |
| 253299 | int bInternal = 0; |
| 253300 | |
| 253301 | rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset); |
| 253302 | if( rc!=SQLITE_OK ) goto filter_out; |
| 253303 | if( zText==0 ) zText = ""; |
| 253304 | if( sqlite3_value_subtype(apVal[i])==FTS5_INSTTOKEN_SUBTYPE ){ |
| 253305 | pConfig->bPrefixInsttoken = 1; |
| 253306 | } |
| 253307 | |
| 253308 | iCol = 0; |
| 253309 | do{ |
| 253310 | iCol = iCol*10 + (idxStr[iIdxStr]-'0'); |
| 253311 | iIdxStr++; |
| @@ -252891,10 +253442,11 @@ | |
| 253442 | } |
| 253443 | |
| 253444 | filter_out: |
| 253445 | sqlite3Fts5ExprFree(pExpr); |
| 253446 | pConfig->pzErrmsg = pzErrmsg; |
| 253447 | pConfig->bPrefixInsttoken = bPrefixInsttoken; |
| 253448 | return rc; |
| 253449 | } |
| 253450 | |
| 253451 | /* |
| 253452 | ** This is the xEof method of the virtual table. SQLite calls this |
| @@ -254886,11 +255438,11 @@ | |
| 255438 | int nArg, /* Number of args */ |
| 255439 | sqlite3_value **apUnused /* Function arguments */ |
| 255440 | ){ |
| 255441 | assert( nArg==0 ); |
| 255442 | UNUSED_PARAM2(nArg, apUnused); |
| 255443 | sqlite3_result_text(pCtx, "fts5: 2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552", -1, SQLITE_TRANSIENT); |
| 255444 | } |
| 255445 | |
| 255446 | /* |
| 255447 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 255448 | ** |
| @@ -254949,10 +255501,24 @@ | |
| 255501 | assert( &pCsr[nText]==&pBlob[nBlob] ); |
| 255502 | |
| 255503 | sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free); |
| 255504 | } |
| 255505 | } |
| 255506 | |
| 255507 | /* |
| 255508 | ** Implementation of fts5_insttoken() function. |
| 255509 | */ |
| 255510 | static void fts5InsttokenFunc( |
| 255511 | sqlite3_context *pCtx, /* Function call context */ |
| 255512 | int nArg, /* Number of args */ |
| 255513 | sqlite3_value **apArg /* Function arguments */ |
| 255514 | ){ |
| 255515 | assert( nArg==1 ); |
| 255516 | (void)nArg; |
| 255517 | sqlite3_result_value(pCtx, apArg[0]); |
| 255518 | sqlite3_result_subtype(pCtx, FTS5_INSTTOKEN_SUBTYPE); |
| 255519 | } |
| 255520 | |
| 255521 | /* |
| 255522 | ** Return true if zName is the extension on one of the shadow tables used |
| 255523 | ** by this module. |
| 255524 | */ |
| @@ -255079,13 +255645,20 @@ | |
| 255645 | ); |
| 255646 | } |
| 255647 | if( rc==SQLITE_OK ){ |
| 255648 | rc = sqlite3_create_function( |
| 255649 | db, "fts5_locale", 2, |
| 255650 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE|SQLITE_SUBTYPE, |
| 255651 | p, fts5LocaleFunc, 0, 0 |
| 255652 | ); |
| 255653 | } |
| 255654 | if( rc==SQLITE_OK ){ |
| 255655 | rc = sqlite3_create_function( |
| 255656 | db, "fts5_insttoken", 1, |
| 255657 | SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, |
| 255658 | p, fts5InsttokenFunc, 0, 0 |
| 255659 | ); |
| 255660 | } |
| 255661 | } |
| 255662 | |
| 255663 | /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file |
| 255664 | ** fts5_test_mi.c is compiled and linked into the executable. And call |
| @@ -258007,22 +258580,22 @@ | |
| 258580 | int rc = SQLITE_OK; |
| 258581 | char aBuf[32]; |
| 258582 | char *zOut = aBuf; |
| 258583 | int ii; |
| 258584 | const unsigned char *zIn = (const unsigned char*)pText; |
| 258585 | const unsigned char *zEof = (zIn ? &zIn[nText] : 0); |
| 258586 | u32 iCode = 0; |
| 258587 | int aStart[3]; /* Input offset of each character in aBuf[] */ |
| 258588 | |
| 258589 | UNUSED_PARAM(unusedFlags); |
| 258590 | |
| 258591 | /* Populate aBuf[] with the characters for the first trigram. */ |
| 258592 | for(ii=0; ii<3; ii++){ |
| 258593 | do { |
| 258594 | aStart[ii] = zIn - (const unsigned char*)pText; |
| 258595 | if( zIn>=zEof ) return SQLITE_OK; |
| 258596 | READ_UTF8(zIn, zEof, iCode); |
| 258597 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258598 | }while( iCode==0 ); |
| 258599 | WRITE_UTF8(zOut, iCode); |
| 258600 | } |
| 258601 | |
| @@ -258039,12 +258612,15 @@ | |
| 258612 | const char *z1; |
| 258613 | |
| 258614 | /* Read characters from the input up until the first non-diacritic */ |
| 258615 | do { |
| 258616 | iNext = zIn - (const unsigned char*)pText; |
| 258617 | if( zIn>=zEof ){ |
| 258618 | iCode = 0; |
| 258619 | break; |
| 258620 | } |
| 258621 | READ_UTF8(zIn, zEof, iCode); |
| 258622 | if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); |
| 258623 | }while( iCode==0 ); |
| 258624 | |
| 258625 | /* Pass the current trigram back to fts5 */ |
| 258626 | rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); |
| @@ -260077,11 +260653,11 @@ | |
| 260653 | |
| 260654 | return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0); |
| 260655 | } |
| 260656 | |
| 260657 | |
| 260658 | /* Here ends the fts5.c composite file. */ |
| 260659 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ |
| 260660 | |
| 260661 | /************** End of fts5.c ************************************************/ |
| 260662 | /************** Begin file stmt.c ********************************************/ |
| 260663 | /* |
| @@ -260433,6 +261009,7 @@ | |
| 261009 | #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ |
| 261010 | |
| 261011 | /************** End of stmt.c ************************************************/ |
| 261012 | /* Return the source-id for this library */ |
| 261013 | SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } |
| 261014 | #endif /* SQLITE_AMALGAMATION */ |
| 261015 | /************************** End of sqlite3.c ******************************/ |
| 261016 |
+54
-8
| --- extsrc/sqlite3.h | ||
| +++ extsrc/sqlite3.h | ||
| @@ -144,13 +144,13 @@ | ||
| 144 | 144 | ** |
| 145 | 145 | ** See also: [sqlite3_libversion()], |
| 146 | 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | 148 | */ |
| 149 | -#define SQLITE_VERSION "3.47.0" | |
| 150 | -#define SQLITE_VERSION_NUMBER 3047000 | |
| 151 | -#define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e" | |
| 149 | +#define SQLITE_VERSION "3.48.0" | |
| 150 | +#define SQLITE_VERSION_NUMBER 3048000 | |
| 151 | +#define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552" | |
| 152 | 152 | |
| 153 | 153 | /* |
| 154 | 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | 156 | ** |
| @@ -650,10 +650,17 @@ | ||
| 650 | 650 | ** |
| 651 | 651 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 652 | 652 | ** filesystem supports doing multiple write operations atomically when those |
| 653 | 653 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 654 | 654 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 655 | +** | |
| 656 | +** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read | |
| 657 | +** from the database file in amounts that are not a multiple of the | |
| 658 | +** page size and that do not begin at a page boundary. Without this | |
| 659 | +** property, SQLite is careful to only do full-page reads and write | |
| 660 | +** on aligned pages, with the one exception that it will do a sub-page | |
| 661 | +** read of the first page to access the database header. | |
| 655 | 662 | */ |
| 656 | 663 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 657 | 664 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 658 | 665 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 659 | 666 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -666,10 +673,11 @@ | ||
| 666 | 673 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 667 | 674 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 668 | 675 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 669 | 676 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 670 | 677 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 678 | +#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 | |
| 671 | 679 | |
| 672 | 680 | /* |
| 673 | 681 | ** CAPI3REF: File Locking Levels |
| 674 | 682 | ** |
| 675 | 683 | ** SQLite uses one of these integer values as the second |
| @@ -812,10 +820,11 @@ | ||
| 812 | 820 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 813 | 821 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 814 | 822 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 815 | 823 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 816 | 824 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 825 | +** <li> [SQLITE_IOCAP_SUBPAGE_READ] | |
| 817 | 826 | ** </ul> |
| 818 | 827 | ** |
| 819 | 828 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 820 | 829 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 821 | 830 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1089,10 +1098,15 @@ | ||
| 1089 | 1098 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1090 | 1099 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1091 | 1100 | ** pointed to by the pArg argument. This capability is used during testing |
| 1092 | 1101 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1093 | 1102 | ** |
| 1103 | +** <li>[[SQLITE_FCNTL_NULL_IO]] | |
| 1104 | +** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor | |
| 1105 | +** or file handle for the [sqlite3_file] object such that it will no longer | |
| 1106 | +** read or write to the database file. | |
| 1107 | +** | |
| 1094 | 1108 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1095 | 1109 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1096 | 1110 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1097 | 1111 | ** available. The WAL subsystem issues this signal during rare |
| 1098 | 1112 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1242,10 +1256,11 @@ | ||
| 1242 | 1256 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1243 | 1257 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1244 | 1258 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1245 | 1259 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1246 | 1260 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1261 | +#define SQLITE_FCNTL_NULL_IO 43 | |
| 1247 | 1262 | |
| 1248 | 1263 | /* deprecated names */ |
| 1249 | 1264 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1250 | 1265 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1251 | 1266 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2620,14 +2635,18 @@ | ||
| 2620 | 2635 | ** |
| 2621 | 2636 | ** ^These functions return the number of rows modified, inserted or |
| 2622 | 2637 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2623 | 2638 | ** statement on the database connection specified by the only parameter. |
| 2624 | 2639 | ** The two functions are identical except for the type of the return value |
| 2625 | -** and that if the number of rows modified by the most recent INSERT, UPDATE | |
| 2640 | +** and that if the number of rows modified by the most recent INSERT, UPDATE, | |
| 2626 | 2641 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2627 | 2642 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2628 | 2643 | ** type of SQL statement does not modify the value returned by these functions. |
| 2644 | +** For the purposes of this interface, a CREATE TABLE AS SELECT statement | |
| 2645 | +** does not count as an INSERT, UPDATE or DELETE statement and hence the rows | |
| 2646 | +** added to the new table by the CREATE TABLE AS SELECT statement are not | |
| 2647 | +** counted. | |
| 2629 | 2648 | ** |
| 2630 | 2649 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2631 | 2650 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2632 | 2651 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2633 | 2652 | ** |
| @@ -4183,15 +4202,26 @@ | ||
| 4183 | 4202 | ** |
| 4184 | 4203 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4185 | 4204 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4186 | 4205 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4187 | 4206 | ** any virtual tables. |
| 4207 | +** | |
| 4208 | +** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt> | |
| 4209 | +** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler | |
| 4210 | +** errors from being sent to the error log defined by | |
| 4211 | +** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test | |
| 4212 | +** compiles to see if some SQL syntax is well-formed, without generating | |
| 4213 | +** messages on the global error log when it is not. If the test compile | |
| 4214 | +** fails, the sqlite3_prepare_v3() call returns the same error indications | |
| 4215 | +** with or without this flag; it just omits the call to [sqlite3_log()] that | |
| 4216 | +** logs the error. | |
| 4188 | 4217 | ** </dl> |
| 4189 | 4218 | */ |
| 4190 | 4219 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4191 | 4220 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4192 | 4221 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4222 | +#define SQLITE_PREPARE_DONT_LOG 0x10 | |
| 4193 | 4223 | |
| 4194 | 4224 | /* |
| 4195 | 4225 | ** CAPI3REF: Compiling An SQL Statement |
| 4196 | 4226 | ** KEYWORDS: {SQL statement compiler} |
| 4197 | 4227 | ** METHOD: sqlite3 |
| @@ -10878,11 +10908,11 @@ | ||
| 10878 | 10908 | #endif |
| 10879 | 10909 | |
| 10880 | 10910 | #ifdef __cplusplus |
| 10881 | 10911 | } /* End of the 'extern "C"' block */ |
| 10882 | 10912 | #endif |
| 10883 | -#endif /* SQLITE3_H */ | |
| 10913 | +/* #endif for SQLITE3_H will be added by mksqlite3.tcl */ | |
| 10884 | 10914 | |
| 10885 | 10915 | /******** Begin file sqlite3rtree.h *********/ |
| 10886 | 10916 | /* |
| 10887 | 10917 | ** 2010 August 30 |
| 10888 | 10918 | ** |
| @@ -13129,17 +13159,32 @@ | ||
| 13129 | 13159 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13130 | 13160 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13131 | 13161 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13132 | 13162 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13133 | 13163 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13134 | -** bytes. This API is not available if the specified token matches a | |
| 13135 | -** prefix query term. In that case both output variables are always set | |
| 13136 | -** to 0. | |
| 13164 | +** bytes. | |
| 13137 | 13165 | ** |
| 13138 | 13166 | ** The output text is not a copy of the document text that was tokenized. |
| 13139 | 13167 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13140 | 13168 | ** includes any embedded 0x00 and trailing data. |
| 13169 | +** | |
| 13170 | +** This API may be slow in some cases if the token identified by parameters | |
| 13171 | +** iIdx and iToken matched a prefix token in the query. In most cases, the | |
| 13172 | +** first call to this API for each prefix token in the query is forced | |
| 13173 | +** to scan the portion of the full-text index that matches the prefix | |
| 13174 | +** token to collect the extra data required by this API. If the prefix | |
| 13175 | +** token matches a large number of token instances in the document set, | |
| 13176 | +** this may be a performance problem. | |
| 13177 | +** | |
| 13178 | +** If the user knows in advance that a query may use this API for a | |
| 13179 | +** prefix token, FTS5 may be configured to collect all required data as part | |
| 13180 | +** of the initial querying of the full-text index, avoiding the second scan | |
| 13181 | +** entirely. This also causes prefix queries that do not use this API to | |
| 13182 | +** run more slowly and use more memory. FTS5 may be configured in this way | |
| 13183 | +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] | |
| 13184 | +** option, or on a per-query basis using the | |
| 13185 | +** [fts5_insttoken | fts5_insttoken()] user function. | |
| 13141 | 13186 | ** |
| 13142 | 13187 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13143 | 13188 | ** "detail=none" or "detail=column" option. |
| 13144 | 13189 | ** |
| 13145 | 13190 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13570,5 +13615,6 @@ | ||
| 13570 | 13615 | #endif |
| 13571 | 13616 | |
| 13572 | 13617 | #endif /* _FTS5_H */ |
| 13573 | 13618 | |
| 13574 | 13619 | /******** End of fts5.h *********/ |
| 13620 | +#endif /* SQLITE3_H */ | |
| 13575 | 13621 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -144,13 +144,13 @@ | |
| 144 | ** |
| 145 | ** See also: [sqlite3_libversion()], |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.47.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3047000 |
| 151 | #define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| @@ -650,10 +650,17 @@ | |
| 650 | ** |
| 651 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 652 | ** filesystem supports doing multiple write operations atomically when those |
| 653 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 654 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 655 | */ |
| 656 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 657 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 658 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 659 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -666,10 +673,11 @@ | |
| 666 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 667 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 668 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 669 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 670 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 671 | |
| 672 | /* |
| 673 | ** CAPI3REF: File Locking Levels |
| 674 | ** |
| 675 | ** SQLite uses one of these integer values as the second |
| @@ -812,10 +820,11 @@ | |
| 812 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 813 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 814 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 815 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 816 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 817 | ** </ul> |
| 818 | ** |
| 819 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 820 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 821 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1089,10 +1098,15 @@ | |
| 1089 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1090 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1091 | ** pointed to by the pArg argument. This capability is used during testing |
| 1092 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1093 | ** |
| 1094 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1095 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1096 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1097 | ** available. The WAL subsystem issues this signal during rare |
| 1098 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1242,10 +1256,11 @@ | |
| 1242 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1243 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1244 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1245 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1246 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1247 | |
| 1248 | /* deprecated names */ |
| 1249 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1250 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1251 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2620,14 +2635,18 @@ | |
| 2620 | ** |
| 2621 | ** ^These functions return the number of rows modified, inserted or |
| 2622 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2623 | ** statement on the database connection specified by the only parameter. |
| 2624 | ** The two functions are identical except for the type of the return value |
| 2625 | ** and that if the number of rows modified by the most recent INSERT, UPDATE |
| 2626 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2627 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2628 | ** type of SQL statement does not modify the value returned by these functions. |
| 2629 | ** |
| 2630 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2631 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2632 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2633 | ** |
| @@ -4183,15 +4202,26 @@ | |
| 4183 | ** |
| 4184 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4185 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4186 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4187 | ** any virtual tables. |
| 4188 | ** </dl> |
| 4189 | */ |
| 4190 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4191 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4192 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4193 | |
| 4194 | /* |
| 4195 | ** CAPI3REF: Compiling An SQL Statement |
| 4196 | ** KEYWORDS: {SQL statement compiler} |
| 4197 | ** METHOD: sqlite3 |
| @@ -10878,11 +10908,11 @@ | |
| 10878 | #endif |
| 10879 | |
| 10880 | #ifdef __cplusplus |
| 10881 | } /* End of the 'extern "C"' block */ |
| 10882 | #endif |
| 10883 | #endif /* SQLITE3_H */ |
| 10884 | |
| 10885 | /******** Begin file sqlite3rtree.h *********/ |
| 10886 | /* |
| 10887 | ** 2010 August 30 |
| 10888 | ** |
| @@ -13129,17 +13159,32 @@ | |
| 13129 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13130 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13131 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13132 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13133 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13134 | ** bytes. This API is not available if the specified token matches a |
| 13135 | ** prefix query term. In that case both output variables are always set |
| 13136 | ** to 0. |
| 13137 | ** |
| 13138 | ** The output text is not a copy of the document text that was tokenized. |
| 13139 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13140 | ** includes any embedded 0x00 and trailing data. |
| 13141 | ** |
| 13142 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13143 | ** "detail=none" or "detail=column" option. |
| 13144 | ** |
| 13145 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13570,5 +13615,6 @@ | |
| 13570 | #endif |
| 13571 | |
| 13572 | #endif /* _FTS5_H */ |
| 13573 | |
| 13574 | /******** End of fts5.h *********/ |
| 13575 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -144,13 +144,13 @@ | |
| 144 | ** |
| 145 | ** See also: [sqlite3_libversion()], |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.48.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3048000 |
| 151 | #define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| @@ -650,10 +650,17 @@ | |
| 650 | ** |
| 651 | ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying |
| 652 | ** filesystem supports doing multiple write operations atomically when those |
| 653 | ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and |
| 654 | ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. |
| 655 | ** |
| 656 | ** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read |
| 657 | ** from the database file in amounts that are not a multiple of the |
| 658 | ** page size and that do not begin at a page boundary. Without this |
| 659 | ** property, SQLite is careful to only do full-page reads and write |
| 660 | ** on aligned pages, with the one exception that it will do a sub-page |
| 661 | ** read of the first page to access the database header. |
| 662 | */ |
| 663 | #define SQLITE_IOCAP_ATOMIC 0x00000001 |
| 664 | #define SQLITE_IOCAP_ATOMIC512 0x00000002 |
| 665 | #define SQLITE_IOCAP_ATOMIC1K 0x00000004 |
| 666 | #define SQLITE_IOCAP_ATOMIC2K 0x00000008 |
| @@ -666,10 +673,11 @@ | |
| 673 | #define SQLITE_IOCAP_SEQUENTIAL 0x00000400 |
| 674 | #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 |
| 675 | #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 |
| 676 | #define SQLITE_IOCAP_IMMUTABLE 0x00002000 |
| 677 | #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 |
| 678 | #define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 |
| 679 | |
| 680 | /* |
| 681 | ** CAPI3REF: File Locking Levels |
| 682 | ** |
| 683 | ** SQLite uses one of these integer values as the second |
| @@ -812,10 +820,11 @@ | |
| 820 | ** <li> [SQLITE_IOCAP_SEQUENTIAL] |
| 821 | ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] |
| 822 | ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE] |
| 823 | ** <li> [SQLITE_IOCAP_IMMUTABLE] |
| 824 | ** <li> [SQLITE_IOCAP_BATCH_ATOMIC] |
| 825 | ** <li> [SQLITE_IOCAP_SUBPAGE_READ] |
| 826 | ** </ul> |
| 827 | ** |
| 828 | ** The SQLITE_IOCAP_ATOMIC property means that all writes of |
| 829 | ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values |
| 830 | ** mean that writes of blocks that are nnn bytes in size and |
| @@ -1089,10 +1098,15 @@ | |
| 1098 | ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This |
| 1099 | ** opcode causes the xFileControl method to swap the file handle with the one |
| 1100 | ** pointed to by the pArg argument. This capability is used during testing |
| 1101 | ** and only needs to be supported when SQLITE_TEST is defined. |
| 1102 | ** |
| 1103 | ** <li>[[SQLITE_FCNTL_NULL_IO]] |
| 1104 | ** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor |
| 1105 | ** or file handle for the [sqlite3_file] object such that it will no longer |
| 1106 | ** read or write to the database file. |
| 1107 | ** |
| 1108 | ** <li>[[SQLITE_FCNTL_WAL_BLOCK]] |
| 1109 | ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might |
| 1110 | ** be advantageous to block on the next WAL lock if the lock is not immediately |
| 1111 | ** available. The WAL subsystem issues this signal during rare |
| 1112 | ** circumstances in order to fix a problem with priority inversion. |
| @@ -1242,10 +1256,11 @@ | |
| 1256 | #define SQLITE_FCNTL_RESERVE_BYTES 38 |
| 1257 | #define SQLITE_FCNTL_CKPT_START 39 |
| 1258 | #define SQLITE_FCNTL_EXTERNAL_READER 40 |
| 1259 | #define SQLITE_FCNTL_CKSM_FILE 41 |
| 1260 | #define SQLITE_FCNTL_RESET_CACHE 42 |
| 1261 | #define SQLITE_FCNTL_NULL_IO 43 |
| 1262 | |
| 1263 | /* deprecated names */ |
| 1264 | #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE |
| 1265 | #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE |
| 1266 | #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| @@ -2620,14 +2635,18 @@ | |
| 2635 | ** |
| 2636 | ** ^These functions return the number of rows modified, inserted or |
| 2637 | ** deleted by the most recently completed INSERT, UPDATE or DELETE |
| 2638 | ** statement on the database connection specified by the only parameter. |
| 2639 | ** The two functions are identical except for the type of the return value |
| 2640 | ** and that if the number of rows modified by the most recent INSERT, UPDATE, |
| 2641 | ** or DELETE is greater than the maximum value supported by type "int", then |
| 2642 | ** the return value of sqlite3_changes() is undefined. ^Executing any other |
| 2643 | ** type of SQL statement does not modify the value returned by these functions. |
| 2644 | ** For the purposes of this interface, a CREATE TABLE AS SELECT statement |
| 2645 | ** does not count as an INSERT, UPDATE or DELETE statement and hence the rows |
| 2646 | ** added to the new table by the CREATE TABLE AS SELECT statement are not |
| 2647 | ** counted. |
| 2648 | ** |
| 2649 | ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are |
| 2650 | ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], |
| 2651 | ** [foreign key actions] or [REPLACE] constraint resolution are not counted. |
| 2652 | ** |
| @@ -4183,15 +4202,26 @@ | |
| 4202 | ** |
| 4203 | ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt> |
| 4204 | ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler |
| 4205 | ** to return an error (error code SQLITE_ERROR) if the statement uses |
| 4206 | ** any virtual tables. |
| 4207 | ** |
| 4208 | ** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt> |
| 4209 | ** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler |
| 4210 | ** errors from being sent to the error log defined by |
| 4211 | ** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test |
| 4212 | ** compiles to see if some SQL syntax is well-formed, without generating |
| 4213 | ** messages on the global error log when it is not. If the test compile |
| 4214 | ** fails, the sqlite3_prepare_v3() call returns the same error indications |
| 4215 | ** with or without this flag; it just omits the call to [sqlite3_log()] that |
| 4216 | ** logs the error. |
| 4217 | ** </dl> |
| 4218 | */ |
| 4219 | #define SQLITE_PREPARE_PERSISTENT 0x01 |
| 4220 | #define SQLITE_PREPARE_NORMALIZE 0x02 |
| 4221 | #define SQLITE_PREPARE_NO_VTAB 0x04 |
| 4222 | #define SQLITE_PREPARE_DONT_LOG 0x10 |
| 4223 | |
| 4224 | /* |
| 4225 | ** CAPI3REF: Compiling An SQL Statement |
| 4226 | ** KEYWORDS: {SQL statement compiler} |
| 4227 | ** METHOD: sqlite3 |
| @@ -10878,11 +10908,11 @@ | |
| 10908 | #endif |
| 10909 | |
| 10910 | #ifdef __cplusplus |
| 10911 | } /* End of the 'extern "C"' block */ |
| 10912 | #endif |
| 10913 | /* #endif for SQLITE3_H will be added by mksqlite3.tcl */ |
| 10914 | |
| 10915 | /******** Begin file sqlite3rtree.h *********/ |
| 10916 | /* |
| 10917 | ** 2010 August 30 |
| 10918 | ** |
| @@ -13129,17 +13159,32 @@ | |
| 13159 | ** This is used to access token iToken of phrase hit iIdx within the |
| 13160 | ** current row. If iIdx is less than zero or greater than or equal to the |
| 13161 | ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, |
| 13162 | ** output variable (*ppToken) is set to point to a buffer containing the |
| 13163 | ** matching document token, and (*pnToken) to the size of that buffer in |
| 13164 | ** bytes. |
| 13165 | ** |
| 13166 | ** The output text is not a copy of the document text that was tokenized. |
| 13167 | ** It is the output of the tokenizer module. For tokendata=1 tables, this |
| 13168 | ** includes any embedded 0x00 and trailing data. |
| 13169 | ** |
| 13170 | ** This API may be slow in some cases if the token identified by parameters |
| 13171 | ** iIdx and iToken matched a prefix token in the query. In most cases, the |
| 13172 | ** first call to this API for each prefix token in the query is forced |
| 13173 | ** to scan the portion of the full-text index that matches the prefix |
| 13174 | ** token to collect the extra data required by this API. If the prefix |
| 13175 | ** token matches a large number of token instances in the document set, |
| 13176 | ** this may be a performance problem. |
| 13177 | ** |
| 13178 | ** If the user knows in advance that a query may use this API for a |
| 13179 | ** prefix token, FTS5 may be configured to collect all required data as part |
| 13180 | ** of the initial querying of the full-text index, avoiding the second scan |
| 13181 | ** entirely. This also causes prefix queries that do not use this API to |
| 13182 | ** run more slowly and use more memory. FTS5 may be configured in this way |
| 13183 | ** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] |
| 13184 | ** option, or on a per-query basis using the |
| 13185 | ** [fts5_insttoken | fts5_insttoken()] user function. |
| 13186 | ** |
| 13187 | ** This API can be quite slow if used with an FTS5 table created with the |
| 13188 | ** "detail=none" or "detail=column" option. |
| 13189 | ** |
| 13190 | ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) |
| @@ -13570,5 +13615,6 @@ | |
| 13615 | #endif |
| 13616 | |
| 13617 | #endif /* _FTS5_H */ |
| 13618 | |
| 13619 | /******** End of fts5.h *********/ |
| 13620 | #endif /* SQLITE3_H */ |
| 13621 |
M
fossil.1
+22
-17
| --- fossil.1 | ||
| +++ fossil.1 | ||
| @@ -1,6 +1,6 @@ | ||
| 1 | -.TH FOSSIL "1" "July 2021" "https://fossil-scm.org" "User Commands" | |
| 1 | +.TH FOSSIL "1" "Oct 2024" "https://fossil-scm.org" "User Commands" | |
| 2 | 2 | .SH NAME |
| 3 | 3 | fossil \- Distributed Version Control System |
| 4 | 4 | .SH SYNOPSIS |
| 5 | 5 | .B fossil |
| 6 | 6 | \fIhelp\fR |
| @@ -14,26 +14,31 @@ | ||
| 14 | 14 | Fossil is a distributed version control system (DVCS) with built-in |
| 15 | 15 | forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server. |
| 16 | 16 | |
| 17 | 17 | .SH Common COMMANDs: |
| 18 | 18 | |
| 19 | -add cat diff ls revert timeline | |
| 20 | -.br | |
| 21 | -addremove changes extras merge rm ui | |
| 22 | -.br | |
| 23 | -all chat finfo mv settings undo | |
| 24 | -.br | |
| 25 | -amend clean gdiff open sql unversioned | |
| 26 | -.br | |
| 27 | -annotate clone grep pull stash update | |
| 28 | -.br | |
| 29 | -bisect commit help push status version | |
| 30 | -.br | |
| 31 | -blame dbstat info rebuild sync | |
| 32 | -.br | |
| 33 | -branch delete init remote tag | |
| 34 | -.br | |
| 19 | +add cherrypick grep push sync | |
| 20 | +.br | |
| 21 | +addremove clean help rebuild tag | |
| 22 | +.br | |
| 23 | +all clone info remote timeline | |
| 24 | +.br | |
| 25 | +amend commit init repack tree | |
| 26 | +.br | |
| 27 | +annotate dbstat ls revert ui | |
| 28 | +.br | |
| 29 | +bisect delete merge rm undo | |
| 30 | +.br | |
| 31 | +blame describe merge-base settings unversioned | |
| 32 | +.br | |
| 33 | +branch diff mv sql update | |
| 34 | +.br | |
| 35 | +cat extras open ssl-config version | |
| 36 | +.br | |
| 37 | +changes finfo patch stash xdiff | |
| 38 | +.br | |
| 39 | +chat gdiff pull status | |
| 35 | 40 | |
| 36 | 41 | .SH FEATURES |
| 37 | 42 | |
| 38 | 43 | Features as described on the fossil home page. |
| 39 | 44 | |
| 40 | 45 |
| --- fossil.1 | |
| +++ fossil.1 | |
| @@ -1,6 +1,6 @@ | |
| 1 | .TH FOSSIL "1" "July 2021" "https://fossil-scm.org" "User Commands" |
| 2 | .SH NAME |
| 3 | fossil \- Distributed Version Control System |
| 4 | .SH SYNOPSIS |
| 5 | .B fossil |
| 6 | \fIhelp\fR |
| @@ -14,26 +14,31 @@ | |
| 14 | Fossil is a distributed version control system (DVCS) with built-in |
| 15 | forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server. |
| 16 | |
| 17 | .SH Common COMMANDs: |
| 18 | |
| 19 | add cat diff ls revert timeline |
| 20 | .br |
| 21 | addremove changes extras merge rm ui |
| 22 | .br |
| 23 | all chat finfo mv settings undo |
| 24 | .br |
| 25 | amend clean gdiff open sql unversioned |
| 26 | .br |
| 27 | annotate clone grep pull stash update |
| 28 | .br |
| 29 | bisect commit help push status version |
| 30 | .br |
| 31 | blame dbstat info rebuild sync |
| 32 | .br |
| 33 | branch delete init remote tag |
| 34 | .br |
| 35 | |
| 36 | .SH FEATURES |
| 37 | |
| 38 | Features as described on the fossil home page. |
| 39 | |
| 40 |
| --- fossil.1 | |
| +++ fossil.1 | |
| @@ -1,6 +1,6 @@ | |
| 1 | .TH FOSSIL "1" "Oct 2024" "https://fossil-scm.org" "User Commands" |
| 2 | .SH NAME |
| 3 | fossil \- Distributed Version Control System |
| 4 | .SH SYNOPSIS |
| 5 | .B fossil |
| 6 | \fIhelp\fR |
| @@ -14,26 +14,31 @@ | |
| 14 | Fossil is a distributed version control system (DVCS) with built-in |
| 15 | forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server. |
| 16 | |
| 17 | .SH Common COMMANDs: |
| 18 | |
| 19 | add cherrypick grep push sync |
| 20 | .br |
| 21 | addremove clean help rebuild tag |
| 22 | .br |
| 23 | all clone info remote timeline |
| 24 | .br |
| 25 | amend commit init repack tree |
| 26 | .br |
| 27 | annotate dbstat ls revert ui |
| 28 | .br |
| 29 | bisect delete merge rm undo |
| 30 | .br |
| 31 | blame describe merge-base settings unversioned |
| 32 | .br |
| 33 | branch diff mv sql update |
| 34 | .br |
| 35 | cat extras open ssl-config version |
| 36 | .br |
| 37 | changes finfo patch stash xdiff |
| 38 | .br |
| 39 | chat gdiff pull status |
| 40 | |
| 41 | .SH FEATURES |
| 42 | |
| 43 | Features as described on the fossil home page. |
| 44 | |
| 45 |
+4
-1
| --- skins/darkmode/css.txt | ||
| +++ skins/darkmode/css.txt | ||
| @@ -98,14 +98,17 @@ | ||
| 98 | 98 | } |
| 99 | 99 | .fileage tr:hover, |
| 100 | 100 | div.filetreeline:hover { |
| 101 | 101 | background-color: #333; |
| 102 | 102 | } |
| 103 | +div.file-change-line button { | |
| 104 | + background-color: #484848 | |
| 105 | +} | |
| 103 | 106 | .button, |
| 104 | 107 | button { |
| 105 | 108 | color: #aaa; |
| 106 | - background-color: #444; | |
| 109 | + background-color: #484848; | |
| 107 | 110 | border-radius: 5px; |
| 108 | 111 | border: 0 |
| 109 | 112 | } |
| 110 | 113 | .button:hover, |
| 111 | 114 | button:hover { |
| 112 | 115 |
| --- skins/darkmode/css.txt | |
| +++ skins/darkmode/css.txt | |
| @@ -98,14 +98,17 @@ | |
| 98 | } |
| 99 | .fileage tr:hover, |
| 100 | div.filetreeline:hover { |
| 101 | background-color: #333; |
| 102 | } |
| 103 | .button, |
| 104 | button { |
| 105 | color: #aaa; |
| 106 | background-color: #444; |
| 107 | border-radius: 5px; |
| 108 | border: 0 |
| 109 | } |
| 110 | .button:hover, |
| 111 | button:hover { |
| 112 |
| --- skins/darkmode/css.txt | |
| +++ skins/darkmode/css.txt | |
| @@ -98,14 +98,17 @@ | |
| 98 | } |
| 99 | .fileage tr:hover, |
| 100 | div.filetreeline:hover { |
| 101 | background-color: #333; |
| 102 | } |
| 103 | div.file-change-line button { |
| 104 | background-color: #484848 |
| 105 | } |
| 106 | .button, |
| 107 | button { |
| 108 | color: #aaa; |
| 109 | background-color: #484848; |
| 110 | border-radius: 5px; |
| 111 | border: 0 |
| 112 | } |
| 113 | .button:hover, |
| 114 | button:hover { |
| 115 |
+7
-6
| --- skins/default/css.txt | ||
| +++ skins/default/css.txt | ||
| @@ -42,10 +42,11 @@ | ||
| 42 | 42 | .artifact h1.page-title, |
| 43 | 43 | .dir h1.page-title, |
| 44 | 44 | .doc h1.page-title, |
| 45 | 45 | .wiki h1.page-title { |
| 46 | 46 | display: block; /* …for potentially long doc titles… */ |
| 47 | + color: #444; | |
| 47 | 48 | } |
| 48 | 49 | .artifact .title > .page-title, |
| 49 | 50 | .dir .title > .page-title, |
| 50 | 51 | .doc .title > .page-title, |
| 51 | 52 | .wiki .title > .page-title { |
| @@ -725,16 +726,16 @@ | ||
| 725 | 726 | margin-left: 20pt; /* special case for MD in forum; need less indent */ |
| 726 | 727 | } |
| 727 | 728 | |
| 728 | 729 | /* Fossil UI uses these, but in sufficiently constrained ways that we |
| 729 | 730 | * don't have to be nearly as careful to avoid an overreach. */ |
| 730 | - .doc > .content h1, .artifact h1, .dir h1, .fileedit h1, .wiki h1 { margin-left: 10pt; } | |
| 731 | - .doc > .content h2, .artifact h2, .dir h2, .fileedit h2, .wiki h2 { margin-left: 20pt; } | |
| 732 | - .doc > .content h3, .artifact h3, .dir h3, .fileedit h3, .wiki h3 { margin-left: 30pt; } | |
| 733 | - .doc > .content h4, .artifact h4, .dir h4, .fileedit h4, .wiki h4 { margin-left: 40pt; } | |
| 734 | - .doc > .content h5, .artifact h5, .dir h5, .fileedit h5, .wiki h5 { margin-left: 50pt; } | |
| 735 | - .doc > .content hr, .artifact hr, .dir hr, .fileedit hr, .wiki hr { margin-left: 10pt; } | |
| 731 | + .doc > .content h1, .artifact .content h1, .dir .content h1, .fileedit .content h1, .wiki .content h1 { margin-left: 10pt; } | |
| 732 | + .doc > .content h2, .artifact .content h2, .dir .content h2, .fileedit .content h2, .wiki .content h2 { margin-left: 20pt; } | |
| 733 | + .doc > .content h3, .artifact .content h3, .dir .content h3, .fileedit .content h3, .wiki .content h3 { margin-left: 30pt; } | |
| 734 | + .doc > .content h4, .artifact .content h4, .dir .content h4, .fileedit .content h4, .wiki .content h4 { margin-left: 40pt; } | |
| 735 | + .doc > .content h5, .artifact .content h5, .dir .content h5, .fileedit .content h5, .wiki .content h5 { margin-left: 50pt; } | |
| 736 | + .doc > .content hr, .artifact .content hr, .dir .content hr, .fileedit .content hr, .wiki .content hr { margin-left: 10pt; } | |
| 736 | 737 | |
| 737 | 738 | /* Don't need to be nearly as careful with tags Fossil UI doesn't use. */ |
| 738 | 739 | .doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; } |
| 739 | 740 | .doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; } |
| 740 | 741 | .doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; } |
| 741 | 742 |
| --- skins/default/css.txt | |
| +++ skins/default/css.txt | |
| @@ -42,10 +42,11 @@ | |
| 42 | .artifact h1.page-title, |
| 43 | .dir h1.page-title, |
| 44 | .doc h1.page-title, |
| 45 | .wiki h1.page-title { |
| 46 | display: block; /* …for potentially long doc titles… */ |
| 47 | } |
| 48 | .artifact .title > .page-title, |
| 49 | .dir .title > .page-title, |
| 50 | .doc .title > .page-title, |
| 51 | .wiki .title > .page-title { |
| @@ -725,16 +726,16 @@ | |
| 725 | margin-left: 20pt; /* special case for MD in forum; need less indent */ |
| 726 | } |
| 727 | |
| 728 | /* Fossil UI uses these, but in sufficiently constrained ways that we |
| 729 | * don't have to be nearly as careful to avoid an overreach. */ |
| 730 | .doc > .content h1, .artifact h1, .dir h1, .fileedit h1, .wiki h1 { margin-left: 10pt; } |
| 731 | .doc > .content h2, .artifact h2, .dir h2, .fileedit h2, .wiki h2 { margin-left: 20pt; } |
| 732 | .doc > .content h3, .artifact h3, .dir h3, .fileedit h3, .wiki h3 { margin-left: 30pt; } |
| 733 | .doc > .content h4, .artifact h4, .dir h4, .fileedit h4, .wiki h4 { margin-left: 40pt; } |
| 734 | .doc > .content h5, .artifact h5, .dir h5, .fileedit h5, .wiki h5 { margin-left: 50pt; } |
| 735 | .doc > .content hr, .artifact hr, .dir hr, .fileedit hr, .wiki hr { margin-left: 10pt; } |
| 736 | |
| 737 | /* Don't need to be nearly as careful with tags Fossil UI doesn't use. */ |
| 738 | .doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; } |
| 739 | .doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; } |
| 740 | .doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; } |
| 741 |
| --- skins/default/css.txt | |
| +++ skins/default/css.txt | |
| @@ -42,10 +42,11 @@ | |
| 42 | .artifact h1.page-title, |
| 43 | .dir h1.page-title, |
| 44 | .doc h1.page-title, |
| 45 | .wiki h1.page-title { |
| 46 | display: block; /* …for potentially long doc titles… */ |
| 47 | color: #444; |
| 48 | } |
| 49 | .artifact .title > .page-title, |
| 50 | .dir .title > .page-title, |
| 51 | .doc .title > .page-title, |
| 52 | .wiki .title > .page-title { |
| @@ -725,16 +726,16 @@ | |
| 726 | margin-left: 20pt; /* special case for MD in forum; need less indent */ |
| 727 | } |
| 728 | |
| 729 | /* Fossil UI uses these, but in sufficiently constrained ways that we |
| 730 | * don't have to be nearly as careful to avoid an overreach. */ |
| 731 | .doc > .content h1, .artifact .content h1, .dir .content h1, .fileedit .content h1, .wiki .content h1 { margin-left: 10pt; } |
| 732 | .doc > .content h2, .artifact .content h2, .dir .content h2, .fileedit .content h2, .wiki .content h2 { margin-left: 20pt; } |
| 733 | .doc > .content h3, .artifact .content h3, .dir .content h3, .fileedit .content h3, .wiki .content h3 { margin-left: 30pt; } |
| 734 | .doc > .content h4, .artifact .content h4, .dir .content h4, .fileedit .content h4, .wiki .content h4 { margin-left: 40pt; } |
| 735 | .doc > .content h5, .artifact .content h5, .dir .content h5, .fileedit .content h5, .wiki .content h5 { margin-left: 50pt; } |
| 736 | .doc > .content hr, .artifact .content hr, .dir .content hr, .fileedit .content hr, .wiki .content hr { margin-left: 10pt; } |
| 737 | |
| 738 | /* Don't need to be nearly as careful with tags Fossil UI doesn't use. */ |
| 739 | .doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; } |
| 740 | .doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; } |
| 741 | .doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; } |
| 742 |
+2
-3
| --- src/add.c | ||
| +++ src/add.c | ||
| @@ -353,11 +353,11 @@ | ||
| 353 | 353 | ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore |
| 354 | 354 | ** option does not appear on the command line then the "ignore-glob" setting |
| 355 | 355 | ** is used. If the --clean option does not appear on the command line then |
| 356 | 356 | ** the "clean-glob" setting is used. |
| 357 | 357 | ** |
| 358 | -** If files are attempted to be added explicitly on the command line which | |
| 358 | +** When attempting to explicitly add files on the commandline, and if those | |
| 359 | 359 | ** match "ignore-glob", a confirmation is asked first. This can be prevented |
| 360 | 360 | ** using the -f|--force option. |
| 361 | 361 | ** |
| 362 | 362 | ** The --case-sensitive option determines whether or not filenames should |
| 363 | 363 | ** be treated case sensitive or not. If the option is not given, the default |
| @@ -753,12 +753,11 @@ | ||
| 753 | 753 | ** |
| 754 | 754 | ** * All files in the repository but missing from the check-out (that is, |
| 755 | 755 | ** all files that show as MISSING with the "status" command) are |
| 756 | 756 | ** removed as if by the "[[rm]]" command. |
| 757 | 757 | ** |
| 758 | -** The command does not "[[commit]]". You must run the "[[commit]]" separately | |
| 759 | -** as a separate step. | |
| 758 | +** Note that this command does not "commit", as that is a separate step. | |
| 760 | 759 | ** |
| 761 | 760 | ** Files and directories whose names begin with "." are ignored unless |
| 762 | 761 | ** the --dotfiles option is used. |
| 763 | 762 | ** |
| 764 | 763 | ** The --ignore option overrides the "ignore-glob" setting, as do the |
| 765 | 764 |
| --- src/add.c | |
| +++ src/add.c | |
| @@ -353,11 +353,11 @@ | |
| 353 | ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore |
| 354 | ** option does not appear on the command line then the "ignore-glob" setting |
| 355 | ** is used. If the --clean option does not appear on the command line then |
| 356 | ** the "clean-glob" setting is used. |
| 357 | ** |
| 358 | ** If files are attempted to be added explicitly on the command line which |
| 359 | ** match "ignore-glob", a confirmation is asked first. This can be prevented |
| 360 | ** using the -f|--force option. |
| 361 | ** |
| 362 | ** The --case-sensitive option determines whether or not filenames should |
| 363 | ** be treated case sensitive or not. If the option is not given, the default |
| @@ -753,12 +753,11 @@ | |
| 753 | ** |
| 754 | ** * All files in the repository but missing from the check-out (that is, |
| 755 | ** all files that show as MISSING with the "status" command) are |
| 756 | ** removed as if by the "[[rm]]" command. |
| 757 | ** |
| 758 | ** The command does not "[[commit]]". You must run the "[[commit]]" separately |
| 759 | ** as a separate step. |
| 760 | ** |
| 761 | ** Files and directories whose names begin with "." are ignored unless |
| 762 | ** the --dotfiles option is used. |
| 763 | ** |
| 764 | ** The --ignore option overrides the "ignore-glob" setting, as do the |
| 765 |
| --- src/add.c | |
| +++ src/add.c | |
| @@ -353,11 +353,11 @@ | |
| 353 | ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore |
| 354 | ** option does not appear on the command line then the "ignore-glob" setting |
| 355 | ** is used. If the --clean option does not appear on the command line then |
| 356 | ** the "clean-glob" setting is used. |
| 357 | ** |
| 358 | ** When attempting to explicitly add files on the commandline, and if those |
| 359 | ** match "ignore-glob", a confirmation is asked first. This can be prevented |
| 360 | ** using the -f|--force option. |
| 361 | ** |
| 362 | ** The --case-sensitive option determines whether or not filenames should |
| 363 | ** be treated case sensitive or not. If the option is not given, the default |
| @@ -753,12 +753,11 @@ | |
| 753 | ** |
| 754 | ** * All files in the repository but missing from the check-out (that is, |
| 755 | ** all files that show as MISSING with the "status" command) are |
| 756 | ** removed as if by the "[[rm]]" command. |
| 757 | ** |
| 758 | ** Note that this command does not "commit", as that is a separate step. |
| 759 | ** |
| 760 | ** Files and directories whose names begin with "." are ignored unless |
| 761 | ** the --dotfiles option is used. |
| 762 | ** |
| 763 | ** The --ignore option overrides the "ignore-glob" setting, as do the |
| 764 |
+23
-6
| --- src/allrepo.c | ||
| +++ src/allrepo.c | ||
| @@ -50,11 +50,10 @@ | ||
| 50 | 50 | for(i=iStart; i<g.argc; i++){ |
| 51 | 51 | blob_appendf(pExtra, " %s", g.argv[i]); |
| 52 | 52 | } |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | - | |
| 56 | 55 | /* |
| 57 | 56 | ** COMMAND: all |
| 58 | 57 | ** |
| 59 | 58 | ** Usage: %fossil all SUBCOMMAND ... |
| 60 | 59 | ** |
| @@ -429,44 +428,62 @@ | ||
| 429 | 428 | "add cache changes clean dbstat extras fts-config git ignore " |
| 430 | 429 | "info list ls pull push rebuild remote " |
| 431 | 430 | "server settings sync ui unset whatis"); |
| 432 | 431 | } |
| 433 | 432 | verify_all_options(); |
| 434 | - db_multi_exec("CREATE TEMP TABLE repolist(name,tag);"); | |
| 433 | + db_multi_exec( | |
| 434 | + "CREATE TEMP TABLE repolist(\n" | |
| 435 | + " name TEXT, -- Filename\n" | |
| 436 | + " tag TEXT, -- Key for the GLOBAL_CONFIG table entry\n" | |
| 437 | + " inode TEXT -- Unique identifier for this file\n" | |
| 438 | + ");\n" | |
| 439 | + | |
| 440 | + /* The seenFile() table holds inode names for entries that have | |
| 441 | + ** already been processed. */ | |
| 442 | + "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n" | |
| 443 | + | |
| 444 | + /* The toDel() table holds the "tag" for entries that need to be | |
| 445 | + ** deleted because they are redundant or no longer exist */ | |
| 446 | + "CREATE TEMP TABLE toDel(x TEXT);\n" | |
| 447 | + ); | |
| 448 | + sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, | |
| 449 | + file_inode_sql_func, 0, 0); | |
| 435 | 450 | if( useCheckouts ){ |
| 436 | 451 | db_multi_exec( |
| 437 | 452 | "INSERT INTO repolist " |
| 438 | - "SELECT DISTINCT substr(name, 7), name COLLATE nocase" | |
| 453 | + "SELECT substr(name, 7), name, inode(substr(name,7))" | |
| 439 | 454 | " FROM global_config" |
| 440 | 455 | " WHERE substr(name, 1, 6)=='ckout:'" |
| 441 | 456 | " ORDER BY 1" |
| 442 | 457 | ); |
| 443 | 458 | }else{ |
| 444 | 459 | db_multi_exec( |
| 445 | 460 | "INSERT INTO repolist " |
| 446 | - "SELECT DISTINCT substr(name, 6), name COLLATE nocase" | |
| 461 | + "SELECT substr(name, 6), name, inode(substr(name,6))" | |
| 447 | 462 | " FROM global_config" |
| 448 | 463 | " WHERE substr(name, 1, 5)=='repo:'" |
| 449 | 464 | " ORDER BY 1" |
| 450 | 465 | ); |
| 451 | 466 | } |
| 452 | - db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)"); | |
| 453 | - db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1"); | |
| 467 | + db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1"); | |
| 454 | 468 | while( db_step(&q)==SQLITE_ROW ){ |
| 455 | 469 | int rc; |
| 456 | 470 | const char *zFilename = db_column_text(&q, 0); |
| 471 | + const char *zInode = db_column_text(&q,2); | |
| 457 | 472 | #if !USE_SEE |
| 458 | 473 | if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue; |
| 459 | 474 | #endif |
| 460 | 475 | if( file_access(zFilename, F_OK) |
| 461 | 476 | || !file_is_canonical(zFilename) |
| 462 | 477 | || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1) |
| 478 | + || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode) | |
| 463 | 479 | ){ |
| 464 | 480 | db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1)); |
| 465 | 481 | nToDel++; |
| 466 | 482 | continue; |
| 467 | 483 | } |
| 484 | + db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode); | |
| 468 | 485 | if( zCmd[0]=='l' ){ |
| 469 | 486 | fossil_print("%s\n", zFilename); |
| 470 | 487 | continue; |
| 471 | 488 | }else if( showFile ){ |
| 472 | 489 | fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository", |
| 473 | 490 |
| --- src/allrepo.c | |
| +++ src/allrepo.c | |
| @@ -50,11 +50,10 @@ | |
| 50 | for(i=iStart; i<g.argc; i++){ |
| 51 | blob_appendf(pExtra, " %s", g.argv[i]); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | |
| 56 | /* |
| 57 | ** COMMAND: all |
| 58 | ** |
| 59 | ** Usage: %fossil all SUBCOMMAND ... |
| 60 | ** |
| @@ -429,44 +428,62 @@ | |
| 429 | "add cache changes clean dbstat extras fts-config git ignore " |
| 430 | "info list ls pull push rebuild remote " |
| 431 | "server settings sync ui unset whatis"); |
| 432 | } |
| 433 | verify_all_options(); |
| 434 | db_multi_exec("CREATE TEMP TABLE repolist(name,tag);"); |
| 435 | if( useCheckouts ){ |
| 436 | db_multi_exec( |
| 437 | "INSERT INTO repolist " |
| 438 | "SELECT DISTINCT substr(name, 7), name COLLATE nocase" |
| 439 | " FROM global_config" |
| 440 | " WHERE substr(name, 1, 6)=='ckout:'" |
| 441 | " ORDER BY 1" |
| 442 | ); |
| 443 | }else{ |
| 444 | db_multi_exec( |
| 445 | "INSERT INTO repolist " |
| 446 | "SELECT DISTINCT substr(name, 6), name COLLATE nocase" |
| 447 | " FROM global_config" |
| 448 | " WHERE substr(name, 1, 5)=='repo:'" |
| 449 | " ORDER BY 1" |
| 450 | ); |
| 451 | } |
| 452 | db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)"); |
| 453 | db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1"); |
| 454 | while( db_step(&q)==SQLITE_ROW ){ |
| 455 | int rc; |
| 456 | const char *zFilename = db_column_text(&q, 0); |
| 457 | #if !USE_SEE |
| 458 | if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue; |
| 459 | #endif |
| 460 | if( file_access(zFilename, F_OK) |
| 461 | || !file_is_canonical(zFilename) |
| 462 | || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1) |
| 463 | ){ |
| 464 | db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1)); |
| 465 | nToDel++; |
| 466 | continue; |
| 467 | } |
| 468 | if( zCmd[0]=='l' ){ |
| 469 | fossil_print("%s\n", zFilename); |
| 470 | continue; |
| 471 | }else if( showFile ){ |
| 472 | fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository", |
| 473 |
| --- src/allrepo.c | |
| +++ src/allrepo.c | |
| @@ -50,11 +50,10 @@ | |
| 50 | for(i=iStart; i<g.argc; i++){ |
| 51 | blob_appendf(pExtra, " %s", g.argv[i]); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | /* |
| 56 | ** COMMAND: all |
| 57 | ** |
| 58 | ** Usage: %fossil all SUBCOMMAND ... |
| 59 | ** |
| @@ -429,44 +428,62 @@ | |
| 428 | "add cache changes clean dbstat extras fts-config git ignore " |
| 429 | "info list ls pull push rebuild remote " |
| 430 | "server settings sync ui unset whatis"); |
| 431 | } |
| 432 | verify_all_options(); |
| 433 | db_multi_exec( |
| 434 | "CREATE TEMP TABLE repolist(\n" |
| 435 | " name TEXT, -- Filename\n" |
| 436 | " tag TEXT, -- Key for the GLOBAL_CONFIG table entry\n" |
| 437 | " inode TEXT -- Unique identifier for this file\n" |
| 438 | ");\n" |
| 439 | |
| 440 | /* The seenFile() table holds inode names for entries that have |
| 441 | ** already been processed. */ |
| 442 | "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n" |
| 443 | |
| 444 | /* The toDel() table holds the "tag" for entries that need to be |
| 445 | ** deleted because they are redundant or no longer exist */ |
| 446 | "CREATE TEMP TABLE toDel(x TEXT);\n" |
| 447 | ); |
| 448 | sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, |
| 449 | file_inode_sql_func, 0, 0); |
| 450 | if( useCheckouts ){ |
| 451 | db_multi_exec( |
| 452 | "INSERT INTO repolist " |
| 453 | "SELECT substr(name, 7), name, inode(substr(name,7))" |
| 454 | " FROM global_config" |
| 455 | " WHERE substr(name, 1, 6)=='ckout:'" |
| 456 | " ORDER BY 1" |
| 457 | ); |
| 458 | }else{ |
| 459 | db_multi_exec( |
| 460 | "INSERT INTO repolist " |
| 461 | "SELECT substr(name, 6), name, inode(substr(name,6))" |
| 462 | " FROM global_config" |
| 463 | " WHERE substr(name, 1, 5)=='repo:'" |
| 464 | " ORDER BY 1" |
| 465 | ); |
| 466 | } |
| 467 | db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1"); |
| 468 | while( db_step(&q)==SQLITE_ROW ){ |
| 469 | int rc; |
| 470 | const char *zFilename = db_column_text(&q, 0); |
| 471 | const char *zInode = db_column_text(&q,2); |
| 472 | #if !USE_SEE |
| 473 | if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue; |
| 474 | #endif |
| 475 | if( file_access(zFilename, F_OK) |
| 476 | || !file_is_canonical(zFilename) |
| 477 | || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1) |
| 478 | || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode) |
| 479 | ){ |
| 480 | db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1)); |
| 481 | nToDel++; |
| 482 | continue; |
| 483 | } |
| 484 | db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode); |
| 485 | if( zCmd[0]=='l' ){ |
| 486 | fossil_print("%s\n", zFilename); |
| 487 | continue; |
| 488 | }else if( showFile ){ |
| 489 | fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository", |
| 490 |
+8
-1
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -634,11 +634,12 @@ | ||
| 634 | 634 | /* |
| 635 | 635 | ** Output HTML to show a list of attachments. |
| 636 | 636 | */ |
| 637 | 637 | void attachment_list( |
| 638 | 638 | const char *zTarget, /* Object that things are attached to */ |
| 639 | - const char *zHeader /* Header to display with attachments */ | |
| 639 | + const char *zHeader, /* Header to display with attachments */ | |
| 640 | + int fHorizontalRule /* Insert <hr> separator above header */ | |
| 640 | 641 | ){ |
| 641 | 642 | int cnt = 0; |
| 642 | 643 | Stmt q; |
| 643 | 644 | db_prepare(&q, |
| 644 | 645 | "SELECT datetime(mtime,toLocal()), filename, user," |
| @@ -654,11 +655,16 @@ | ||
| 654 | 655 | const char *zUser = db_column_text(&q, 2); |
| 655 | 656 | const char *zUuid = db_column_text(&q, 3); |
| 656 | 657 | const char *zSrc = db_column_text(&q, 4); |
| 657 | 658 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 658 | 659 | if( cnt==0 ){ |
| 660 | + @ <section class='attachlist'> | |
| 661 | + if( fHorizontalRule ){ | |
| 662 | + @ <hr> | |
| 663 | + } | |
| 659 | 664 | @ %s(zHeader) |
| 665 | + @ <ul> | |
| 660 | 666 | } |
| 661 | 667 | cnt++; |
| 662 | 668 | @ <li> |
| 663 | 669 | @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> |
| 664 | 670 | @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>] |
| @@ -667,10 +673,11 @@ | ||
| 667 | 673 | @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] |
| 668 | 674 | @ </li> |
| 669 | 675 | } |
| 670 | 676 | if( cnt ){ |
| 671 | 677 | @ </ul> |
| 678 | + @ </section> | |
| 672 | 679 | } |
| 673 | 680 | db_finalize(&q); |
| 674 | 681 | |
| 675 | 682 | } |
| 676 | 683 | |
| 677 | 684 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -634,11 +634,12 @@ | |
| 634 | /* |
| 635 | ** Output HTML to show a list of attachments. |
| 636 | */ |
| 637 | void attachment_list( |
| 638 | const char *zTarget, /* Object that things are attached to */ |
| 639 | const char *zHeader /* Header to display with attachments */ |
| 640 | ){ |
| 641 | int cnt = 0; |
| 642 | Stmt q; |
| 643 | db_prepare(&q, |
| 644 | "SELECT datetime(mtime,toLocal()), filename, user," |
| @@ -654,11 +655,16 @@ | |
| 654 | const char *zUser = db_column_text(&q, 2); |
| 655 | const char *zUuid = db_column_text(&q, 3); |
| 656 | const char *zSrc = db_column_text(&q, 4); |
| 657 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 658 | if( cnt==0 ){ |
| 659 | @ %s(zHeader) |
| 660 | } |
| 661 | cnt++; |
| 662 | @ <li> |
| 663 | @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> |
| 664 | @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>] |
| @@ -667,10 +673,11 @@ | |
| 667 | @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] |
| 668 | @ </li> |
| 669 | } |
| 670 | if( cnt ){ |
| 671 | @ </ul> |
| 672 | } |
| 673 | db_finalize(&q); |
| 674 | |
| 675 | } |
| 676 | |
| 677 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -634,11 +634,12 @@ | |
| 634 | /* |
| 635 | ** Output HTML to show a list of attachments. |
| 636 | */ |
| 637 | void attachment_list( |
| 638 | const char *zTarget, /* Object that things are attached to */ |
| 639 | const char *zHeader, /* Header to display with attachments */ |
| 640 | int fHorizontalRule /* Insert <hr> separator above header */ |
| 641 | ){ |
| 642 | int cnt = 0; |
| 643 | Stmt q; |
| 644 | db_prepare(&q, |
| 645 | "SELECT datetime(mtime,toLocal()), filename, user," |
| @@ -654,11 +655,16 @@ | |
| 655 | const char *zUser = db_column_text(&q, 2); |
| 656 | const char *zUuid = db_column_text(&q, 3); |
| 657 | const char *zSrc = db_column_text(&q, 4); |
| 658 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 659 | if( cnt==0 ){ |
| 660 | @ <section class='attachlist'> |
| 661 | if( fHorizontalRule ){ |
| 662 | @ <hr> |
| 663 | } |
| 664 | @ %s(zHeader) |
| 665 | @ <ul> |
| 666 | } |
| 667 | cnt++; |
| 668 | @ <li> |
| 669 | @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> |
| 670 | @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>] |
| @@ -667,10 +673,11 @@ | |
| 673 | @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] |
| 674 | @ </li> |
| 675 | } |
| 676 | if( cnt ){ |
| 677 | @ </ul> |
| 678 | @ </section> |
| 679 | } |
| 680 | db_finalize(&q); |
| 681 | |
| 682 | } |
| 683 | |
| 684 |
+38
-27
| --- src/bisect.c | ||
| +++ src/bisect.c | ||
| @@ -197,10 +197,26 @@ | ||
| 197 | 197 | static void bisect_append_skip(int rid){ |
| 198 | 198 | db_multi_exec( |
| 199 | 199 | "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid |
| 200 | 200 | ); |
| 201 | 201 | } |
| 202 | + | |
| 203 | +/* | |
| 204 | +** Append a VALUES entry to the bilog table insert | |
| 205 | +*/ | |
| 206 | +static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){ | |
| 207 | + if( (iSeq%6)==3 ){ | |
| 208 | + blob_append_sql(pSql, ",\n "); | |
| 209 | + }else if( iSeq>1 ){ | |
| 210 | + blob_append_sql(pSql, ","); | |
| 211 | + } | |
| 212 | + if( zStat ){ | |
| 213 | + blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid); | |
| 214 | + }else{ | |
| 215 | + blob_append_sql(pSql, "(NULL,NULL,%d)", iRid); | |
| 216 | + } | |
| 217 | +} | |
| 202 | 218 | |
| 203 | 219 | /* |
| 204 | 220 | ** Create a TEMP table named "bilog" that contains the complete history |
| 205 | 221 | ** of the current bisect. |
| 206 | 222 | ** |
| @@ -215,14 +231,14 @@ | ||
| 215 | 231 | ** in between the inner-most GOOD and BAD nodes. |
| 216 | 232 | */ |
| 217 | 233 | int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){ |
| 218 | 234 | char *zLog; |
| 219 | 235 | Blob log, id; |
| 220 | - Stmt q; | |
| 221 | 236 | int cnt = 0; |
| 222 | 237 | int lastGood = -1; |
| 223 | 238 | int lastBad = -1; |
| 239 | + Blob ins = BLOB_INITIALIZER; | |
| 224 | 240 | |
| 225 | 241 | if( zDesc!=0 ){ |
| 226 | 242 | blob_init(&log, 0, 0); |
| 227 | 243 | while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){ |
| 228 | 244 | int i; |
| @@ -253,55 +269,42 @@ | ||
| 253 | 269 | " rid INTEGER PRIMARY KEY," /* Sequence of events */ |
| 254 | 270 | " stat TEXT," /* Type of occurrence */ |
| 255 | 271 | " seq INTEGER UNIQUE" /* Check-in number */ |
| 256 | 272 | ");" |
| 257 | 273 | ); |
| 258 | - db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)" | |
| 259 | - " VALUES(:seq,:stat,:rid)"); | |
| 274 | + blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES"); | |
| 260 | 275 | while( blob_token(&log, &id) ){ |
| 261 | 276 | int rid; |
| 262 | - db_bind_int(&q, ":seq", ++cnt); | |
| 277 | + cnt++; | |
| 263 | 278 | if( blob_str(&id)[0]=='s' ){ |
| 264 | 279 | rid = atoi(blob_str(&id)+1); |
| 265 | - db_bind_text(&q, ":stat", "SKIP"); | |
| 266 | - db_bind_int(&q, ":rid", rid); | |
| 280 | + bisect_log_append(&ins, cnt, "SKIP", rid); | |
| 267 | 281 | }else{ |
| 268 | 282 | rid = atoi(blob_str(&id)); |
| 269 | 283 | if( rid>0 ){ |
| 270 | - db_bind_text(&q, ":stat","GOOD"); | |
| 271 | - db_bind_int(&q, ":rid", rid); | |
| 284 | + bisect_log_append(&ins, cnt, "GOOD", rid); | |
| 272 | 285 | lastGood = rid; |
| 273 | 286 | }else{ |
| 274 | - db_bind_text(&q, ":stat", "BAD"); | |
| 275 | - db_bind_int(&q, ":rid", -rid); | |
| 287 | + bisect_log_append(&ins, cnt, "BAD", rid); | |
| 276 | 288 | lastBad = -rid; |
| 277 | 289 | } |
| 278 | 290 | } |
| 279 | - db_step(&q); | |
| 280 | - db_reset(&q); | |
| 281 | 291 | } |
| 282 | 292 | if( iCurrent>0 ){ |
| 283 | - db_bind_int(&q, ":seq", ++cnt); | |
| 284 | - db_bind_text(&q, ":stat", "CURRENT"); | |
| 285 | - db_bind_int(&q, ":rid", iCurrent); | |
| 286 | - db_step(&q); | |
| 287 | - db_reset(&q); | |
| 293 | + bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent); | |
| 288 | 294 | } |
| 289 | 295 | if( bDetail && lastGood>0 && lastBad>0 ){ |
| 290 | 296 | PathNode *p; |
| 291 | 297 | p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0); |
| 292 | 298 | while( p ){ |
| 293 | - db_bind_null(&q, ":seq"); | |
| 294 | - db_bind_null(&q, ":stat"); | |
| 295 | - db_bind_int(&q, ":rid", p->rid); | |
| 296 | - db_step(&q); | |
| 297 | - db_reset(&q); | |
| 299 | + bisect_log_append(&ins, ++cnt, 0, p->rid); | |
| 298 | 300 | p = p->u.pTo; |
| 299 | 301 | } |
| 300 | 302 | path_reset(); |
| 301 | 303 | } |
| 302 | - db_finalize(&q); | |
| 304 | + db_exec_sql(blob_sql_text(&ins)); | |
| 305 | + blob_reset(&ins); | |
| 303 | 306 | return 1; |
| 304 | 307 | } |
| 305 | 308 | |
| 306 | 309 | /* Return a permalink description of a bisect. Space is obtained from |
| 307 | 310 | ** fossil_malloc() and should be freed by the caller. |
| @@ -543,14 +546,16 @@ | ||
| 543 | 546 | ** |
| 544 | 547 | ** > fossil bisect vlist|ls|status ?-a|--all? |
| 545 | 548 | ** |
| 546 | 549 | ** List the versions in between the inner-most "bad" and "good". |
| 547 | 550 | ** |
| 548 | -** > fossil bisect ui | |
| 551 | +** > fossil bisect ui ?HOST@USER:PATH? | |
| 549 | 552 | ** |
| 550 | 553 | ** Like "fossil ui" except start on a timeline that shows only the |
| 551 | -** check-ins that are part of the current bisect. | |
| 554 | +** check-ins that are part of the current bisect. If the optional | |
| 555 | +** fourth term is added, then information is shown for the bisect that | |
| 556 | +** occurred in the PATH directory by USER on remote machine HOST. | |
| 552 | 557 | ** |
| 553 | 558 | ** > fossil bisect undo |
| 554 | 559 | ** |
| 555 | 560 | ** Undo the most recent "good", "bad", or "skip" command. |
| 556 | 561 | */ |
| @@ -719,17 +724,23 @@ | ||
| 719 | 724 | } |
| 720 | 725 | }else if( strncmp(zCmd, "reset", n)==0 ){ |
| 721 | 726 | bisect_reset(); |
| 722 | 727 | }else if( strcmp(zCmd, "ui")==0 ){ |
| 723 | 728 | char *newArgv[8]; |
| 729 | + verify_all_options(); | |
| 724 | 730 | newArgv[0] = g.argv[0]; |
| 725 | 731 | newArgv[1] = "ui"; |
| 726 | 732 | newArgv[2] = "--page"; |
| 727 | 733 | newArgv[3] = "timeline?bisect"; |
| 728 | - newArgv[4] = 0; | |
| 734 | + if( g.argc==4 ){ | |
| 735 | + newArgv[4] = g.argv[3]; | |
| 736 | + g.argc = 5; | |
| 737 | + }else{ | |
| 738 | + g.argc = 4; | |
| 739 | + } | |
| 740 | + newArgv[g.argc] = 0; | |
| 729 | 741 | g.argv = newArgv; |
| 730 | - g.argc = 4; | |
| 731 | 742 | cmd_webserver(); |
| 732 | 743 | }else if( strncmp(zCmd, "vlist", n)==0 |
| 733 | 744 | || strncmp(zCmd, "ls", n)==0 |
| 734 | 745 | || strncmp(zCmd, "status", n)==0 |
| 735 | 746 | ){ |
| 736 | 747 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -197,10 +197,26 @@ | |
| 197 | static void bisect_append_skip(int rid){ |
| 198 | db_multi_exec( |
| 199 | "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid |
| 200 | ); |
| 201 | } |
| 202 | |
| 203 | /* |
| 204 | ** Create a TEMP table named "bilog" that contains the complete history |
| 205 | ** of the current bisect. |
| 206 | ** |
| @@ -215,14 +231,14 @@ | |
| 215 | ** in between the inner-most GOOD and BAD nodes. |
| 216 | */ |
| 217 | int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){ |
| 218 | char *zLog; |
| 219 | Blob log, id; |
| 220 | Stmt q; |
| 221 | int cnt = 0; |
| 222 | int lastGood = -1; |
| 223 | int lastBad = -1; |
| 224 | |
| 225 | if( zDesc!=0 ){ |
| 226 | blob_init(&log, 0, 0); |
| 227 | while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){ |
| 228 | int i; |
| @@ -253,55 +269,42 @@ | |
| 253 | " rid INTEGER PRIMARY KEY," /* Sequence of events */ |
| 254 | " stat TEXT," /* Type of occurrence */ |
| 255 | " seq INTEGER UNIQUE" /* Check-in number */ |
| 256 | ");" |
| 257 | ); |
| 258 | db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)" |
| 259 | " VALUES(:seq,:stat,:rid)"); |
| 260 | while( blob_token(&log, &id) ){ |
| 261 | int rid; |
| 262 | db_bind_int(&q, ":seq", ++cnt); |
| 263 | if( blob_str(&id)[0]=='s' ){ |
| 264 | rid = atoi(blob_str(&id)+1); |
| 265 | db_bind_text(&q, ":stat", "SKIP"); |
| 266 | db_bind_int(&q, ":rid", rid); |
| 267 | }else{ |
| 268 | rid = atoi(blob_str(&id)); |
| 269 | if( rid>0 ){ |
| 270 | db_bind_text(&q, ":stat","GOOD"); |
| 271 | db_bind_int(&q, ":rid", rid); |
| 272 | lastGood = rid; |
| 273 | }else{ |
| 274 | db_bind_text(&q, ":stat", "BAD"); |
| 275 | db_bind_int(&q, ":rid", -rid); |
| 276 | lastBad = -rid; |
| 277 | } |
| 278 | } |
| 279 | db_step(&q); |
| 280 | db_reset(&q); |
| 281 | } |
| 282 | if( iCurrent>0 ){ |
| 283 | db_bind_int(&q, ":seq", ++cnt); |
| 284 | db_bind_text(&q, ":stat", "CURRENT"); |
| 285 | db_bind_int(&q, ":rid", iCurrent); |
| 286 | db_step(&q); |
| 287 | db_reset(&q); |
| 288 | } |
| 289 | if( bDetail && lastGood>0 && lastBad>0 ){ |
| 290 | PathNode *p; |
| 291 | p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0); |
| 292 | while( p ){ |
| 293 | db_bind_null(&q, ":seq"); |
| 294 | db_bind_null(&q, ":stat"); |
| 295 | db_bind_int(&q, ":rid", p->rid); |
| 296 | db_step(&q); |
| 297 | db_reset(&q); |
| 298 | p = p->u.pTo; |
| 299 | } |
| 300 | path_reset(); |
| 301 | } |
| 302 | db_finalize(&q); |
| 303 | return 1; |
| 304 | } |
| 305 | |
| 306 | /* Return a permalink description of a bisect. Space is obtained from |
| 307 | ** fossil_malloc() and should be freed by the caller. |
| @@ -543,14 +546,16 @@ | |
| 543 | ** |
| 544 | ** > fossil bisect vlist|ls|status ?-a|--all? |
| 545 | ** |
| 546 | ** List the versions in between the inner-most "bad" and "good". |
| 547 | ** |
| 548 | ** > fossil bisect ui |
| 549 | ** |
| 550 | ** Like "fossil ui" except start on a timeline that shows only the |
| 551 | ** check-ins that are part of the current bisect. |
| 552 | ** |
| 553 | ** > fossil bisect undo |
| 554 | ** |
| 555 | ** Undo the most recent "good", "bad", or "skip" command. |
| 556 | */ |
| @@ -719,17 +724,23 @@ | |
| 719 | } |
| 720 | }else if( strncmp(zCmd, "reset", n)==0 ){ |
| 721 | bisect_reset(); |
| 722 | }else if( strcmp(zCmd, "ui")==0 ){ |
| 723 | char *newArgv[8]; |
| 724 | newArgv[0] = g.argv[0]; |
| 725 | newArgv[1] = "ui"; |
| 726 | newArgv[2] = "--page"; |
| 727 | newArgv[3] = "timeline?bisect"; |
| 728 | newArgv[4] = 0; |
| 729 | g.argv = newArgv; |
| 730 | g.argc = 4; |
| 731 | cmd_webserver(); |
| 732 | }else if( strncmp(zCmd, "vlist", n)==0 |
| 733 | || strncmp(zCmd, "ls", n)==0 |
| 734 | || strncmp(zCmd, "status", n)==0 |
| 735 | ){ |
| 736 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -197,10 +197,26 @@ | |
| 197 | static void bisect_append_skip(int rid){ |
| 198 | db_multi_exec( |
| 199 | "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid |
| 200 | ); |
| 201 | } |
| 202 | |
| 203 | /* |
| 204 | ** Append a VALUES entry to the bilog table insert |
| 205 | */ |
| 206 | static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){ |
| 207 | if( (iSeq%6)==3 ){ |
| 208 | blob_append_sql(pSql, ",\n "); |
| 209 | }else if( iSeq>1 ){ |
| 210 | blob_append_sql(pSql, ","); |
| 211 | } |
| 212 | if( zStat ){ |
| 213 | blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid); |
| 214 | }else{ |
| 215 | blob_append_sql(pSql, "(NULL,NULL,%d)", iRid); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /* |
| 220 | ** Create a TEMP table named "bilog" that contains the complete history |
| 221 | ** of the current bisect. |
| 222 | ** |
| @@ -215,14 +231,14 @@ | |
| 231 | ** in between the inner-most GOOD and BAD nodes. |
| 232 | */ |
| 233 | int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){ |
| 234 | char *zLog; |
| 235 | Blob log, id; |
| 236 | int cnt = 0; |
| 237 | int lastGood = -1; |
| 238 | int lastBad = -1; |
| 239 | Blob ins = BLOB_INITIALIZER; |
| 240 | |
| 241 | if( zDesc!=0 ){ |
| 242 | blob_init(&log, 0, 0); |
| 243 | while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){ |
| 244 | int i; |
| @@ -253,55 +269,42 @@ | |
| 269 | " rid INTEGER PRIMARY KEY," /* Sequence of events */ |
| 270 | " stat TEXT," /* Type of occurrence */ |
| 271 | " seq INTEGER UNIQUE" /* Check-in number */ |
| 272 | ");" |
| 273 | ); |
| 274 | blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES"); |
| 275 | while( blob_token(&log, &id) ){ |
| 276 | int rid; |
| 277 | cnt++; |
| 278 | if( blob_str(&id)[0]=='s' ){ |
| 279 | rid = atoi(blob_str(&id)+1); |
| 280 | bisect_log_append(&ins, cnt, "SKIP", rid); |
| 281 | }else{ |
| 282 | rid = atoi(blob_str(&id)); |
| 283 | if( rid>0 ){ |
| 284 | bisect_log_append(&ins, cnt, "GOOD", rid); |
| 285 | lastGood = rid; |
| 286 | }else{ |
| 287 | bisect_log_append(&ins, cnt, "BAD", rid); |
| 288 | lastBad = -rid; |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | if( iCurrent>0 ){ |
| 293 | bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent); |
| 294 | } |
| 295 | if( bDetail && lastGood>0 && lastBad>0 ){ |
| 296 | PathNode *p; |
| 297 | p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0); |
| 298 | while( p ){ |
| 299 | bisect_log_append(&ins, ++cnt, 0, p->rid); |
| 300 | p = p->u.pTo; |
| 301 | } |
| 302 | path_reset(); |
| 303 | } |
| 304 | db_exec_sql(blob_sql_text(&ins)); |
| 305 | blob_reset(&ins); |
| 306 | return 1; |
| 307 | } |
| 308 | |
| 309 | /* Return a permalink description of a bisect. Space is obtained from |
| 310 | ** fossil_malloc() and should be freed by the caller. |
| @@ -543,14 +546,16 @@ | |
| 546 | ** |
| 547 | ** > fossil bisect vlist|ls|status ?-a|--all? |
| 548 | ** |
| 549 | ** List the versions in between the inner-most "bad" and "good". |
| 550 | ** |
| 551 | ** > fossil bisect ui ?HOST@USER:PATH? |
| 552 | ** |
| 553 | ** Like "fossil ui" except start on a timeline that shows only the |
| 554 | ** check-ins that are part of the current bisect. If the optional |
| 555 | ** fourth term is added, then information is shown for the bisect that |
| 556 | ** occurred in the PATH directory by USER on remote machine HOST. |
| 557 | ** |
| 558 | ** > fossil bisect undo |
| 559 | ** |
| 560 | ** Undo the most recent "good", "bad", or "skip" command. |
| 561 | */ |
| @@ -719,17 +724,23 @@ | |
| 724 | } |
| 725 | }else if( strncmp(zCmd, "reset", n)==0 ){ |
| 726 | bisect_reset(); |
| 727 | }else if( strcmp(zCmd, "ui")==0 ){ |
| 728 | char *newArgv[8]; |
| 729 | verify_all_options(); |
| 730 | newArgv[0] = g.argv[0]; |
| 731 | newArgv[1] = "ui"; |
| 732 | newArgv[2] = "--page"; |
| 733 | newArgv[3] = "timeline?bisect"; |
| 734 | if( g.argc==4 ){ |
| 735 | newArgv[4] = g.argv[3]; |
| 736 | g.argc = 5; |
| 737 | }else{ |
| 738 | g.argc = 4; |
| 739 | } |
| 740 | newArgv[g.argc] = 0; |
| 741 | g.argv = newArgv; |
| 742 | cmd_webserver(); |
| 743 | }else if( strncmp(zCmd, "vlist", n)==0 |
| 744 | || strncmp(zCmd, "ls", n)==0 |
| 745 | || strncmp(zCmd, "status", n)==0 |
| 746 | ){ |
| 747 |
+49
-1
| --- src/blob.c | ||
| +++ src/blob.c | ||
| @@ -665,11 +665,12 @@ | ||
| 665 | 665 | pBlob->nUsed = dehttpize(pBlob->aData); |
| 666 | 666 | } |
| 667 | 667 | |
| 668 | 668 | /* |
| 669 | 669 | ** Extract N bytes from blob pFrom and use it to initialize blob pTo. |
| 670 | -** Return the actual number of bytes extracted. | |
| 670 | +** Return the actual number of bytes extracted. The cursor position | |
| 671 | +** is advanced by the number of bytes extracted. | |
| 671 | 672 | ** |
| 672 | 673 | ** After this call completes, pTo will be an ephemeral blob. |
| 673 | 674 | */ |
| 674 | 675 | int blob_extract(Blob *pFrom, int N, Blob *pTo){ |
| 675 | 676 | blob_is_init(pFrom); |
| @@ -687,10 +688,57 @@ | ||
| 687 | 688 | pTo->iCursor = 0; |
| 688 | 689 | pTo->xRealloc = blobReallocStatic; |
| 689 | 690 | pFrom->iCursor += N; |
| 690 | 691 | return N; |
| 691 | 692 | } |
| 693 | + | |
| 694 | +/* | |
| 695 | +** Extract N **lines** of text from blob pFrom beginning at the current | |
| 696 | +** cursor position and use that text to initialize blob pTo. Unlike the | |
| 697 | +** blob_extract() routine, the cursor position is unchanged. | |
| 698 | +** | |
| 699 | +** pTo is assumed to be uninitialized. | |
| 700 | +** | |
| 701 | +** After this call completes, pTo will be an ephemeral blob. | |
| 702 | +*/ | |
| 703 | +int blob_extract_lines(Blob *pFrom, int N, Blob *pTo){ | |
| 704 | + int i; | |
| 705 | + int mx; | |
| 706 | + int iStart; | |
| 707 | + int n; | |
| 708 | + const char *z; | |
| 709 | + | |
| 710 | + blob_zero(pTo); | |
| 711 | + z = pFrom->aData; | |
| 712 | + i = pFrom->iCursor; | |
| 713 | + mx = pFrom->nUsed; | |
| 714 | + while( N>0 ){ | |
| 715 | + while( i<mx && z[i]!='\n' ){ i++; } | |
| 716 | + if( i>=mx ) break; | |
| 717 | + i++; | |
| 718 | + N--; | |
| 719 | + } | |
| 720 | + iStart = pFrom->iCursor; | |
| 721 | + n = blob_extract(pFrom, i-pFrom->iCursor, pTo); | |
| 722 | + pFrom->iCursor = iStart; | |
| 723 | + return n; | |
| 724 | +} | |
| 725 | + | |
| 726 | +/* | |
| 727 | +** Return the number of lines of text in the blob. If the last | |
| 728 | +** line is incomplete (if it does not have a \n at the end) then | |
| 729 | +** it still counts. | |
| 730 | +*/ | |
| 731 | +int blob_linecount(Blob *p){ | |
| 732 | + int n = 0; | |
| 733 | + int i; | |
| 734 | + for(i=0; i<p->nUsed; i++){ | |
| 735 | + if( p->aData[i]=='\n' ) n++; | |
| 736 | + } | |
| 737 | + if( p->nUsed>0 && p->aData[p->nUsed-1]!='\n' ) n++; | |
| 738 | + return n; | |
| 739 | +} | |
| 692 | 740 | |
| 693 | 741 | /* |
| 694 | 742 | ** Rewind the cursor on a blob back to the beginning. |
| 695 | 743 | */ |
| 696 | 744 | void blob_rewind(Blob *p){ |
| 697 | 745 |
| --- src/blob.c | |
| +++ src/blob.c | |
| @@ -665,11 +665,12 @@ | |
| 665 | pBlob->nUsed = dehttpize(pBlob->aData); |
| 666 | } |
| 667 | |
| 668 | /* |
| 669 | ** Extract N bytes from blob pFrom and use it to initialize blob pTo. |
| 670 | ** Return the actual number of bytes extracted. |
| 671 | ** |
| 672 | ** After this call completes, pTo will be an ephemeral blob. |
| 673 | */ |
| 674 | int blob_extract(Blob *pFrom, int N, Blob *pTo){ |
| 675 | blob_is_init(pFrom); |
| @@ -687,10 +688,57 @@ | |
| 687 | pTo->iCursor = 0; |
| 688 | pTo->xRealloc = blobReallocStatic; |
| 689 | pFrom->iCursor += N; |
| 690 | return N; |
| 691 | } |
| 692 | |
| 693 | /* |
| 694 | ** Rewind the cursor on a blob back to the beginning. |
| 695 | */ |
| 696 | void blob_rewind(Blob *p){ |
| 697 |
| --- src/blob.c | |
| +++ src/blob.c | |
| @@ -665,11 +665,12 @@ | |
| 665 | pBlob->nUsed = dehttpize(pBlob->aData); |
| 666 | } |
| 667 | |
| 668 | /* |
| 669 | ** Extract N bytes from blob pFrom and use it to initialize blob pTo. |
| 670 | ** Return the actual number of bytes extracted. The cursor position |
| 671 | ** is advanced by the number of bytes extracted. |
| 672 | ** |
| 673 | ** After this call completes, pTo will be an ephemeral blob. |
| 674 | */ |
| 675 | int blob_extract(Blob *pFrom, int N, Blob *pTo){ |
| 676 | blob_is_init(pFrom); |
| @@ -687,10 +688,57 @@ | |
| 688 | pTo->iCursor = 0; |
| 689 | pTo->xRealloc = blobReallocStatic; |
| 690 | pFrom->iCursor += N; |
| 691 | return N; |
| 692 | } |
| 693 | |
| 694 | /* |
| 695 | ** Extract N **lines** of text from blob pFrom beginning at the current |
| 696 | ** cursor position and use that text to initialize blob pTo. Unlike the |
| 697 | ** blob_extract() routine, the cursor position is unchanged. |
| 698 | ** |
| 699 | ** pTo is assumed to be uninitialized. |
| 700 | ** |
| 701 | ** After this call completes, pTo will be an ephemeral blob. |
| 702 | */ |
| 703 | int blob_extract_lines(Blob *pFrom, int N, Blob *pTo){ |
| 704 | int i; |
| 705 | int mx; |
| 706 | int iStart; |
| 707 | int n; |
| 708 | const char *z; |
| 709 | |
| 710 | blob_zero(pTo); |
| 711 | z = pFrom->aData; |
| 712 | i = pFrom->iCursor; |
| 713 | mx = pFrom->nUsed; |
| 714 | while( N>0 ){ |
| 715 | while( i<mx && z[i]!='\n' ){ i++; } |
| 716 | if( i>=mx ) break; |
| 717 | i++; |
| 718 | N--; |
| 719 | } |
| 720 | iStart = pFrom->iCursor; |
| 721 | n = blob_extract(pFrom, i-pFrom->iCursor, pTo); |
| 722 | pFrom->iCursor = iStart; |
| 723 | return n; |
| 724 | } |
| 725 | |
| 726 | /* |
| 727 | ** Return the number of lines of text in the blob. If the last |
| 728 | ** line is incomplete (if it does not have a \n at the end) then |
| 729 | ** it still counts. |
| 730 | */ |
| 731 | int blob_linecount(Blob *p){ |
| 732 | int n = 0; |
| 733 | int i; |
| 734 | for(i=0; i<p->nUsed; i++){ |
| 735 | if( p->aData[i]=='\n' ) n++; |
| 736 | } |
| 737 | if( p->nUsed>0 && p->aData[p->nUsed-1]!='\n' ) n++; |
| 738 | return n; |
| 739 | } |
| 740 | |
| 741 | /* |
| 742 | ** Rewind the cursor on a blob back to the beginning. |
| 743 | */ |
| 744 | void blob_rewind(Blob *p){ |
| 745 |
+2
-2
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -640,12 +640,12 @@ | ||
| 640 | 640 | ** -M|--unmerged List branches not merged into the current branch |
| 641 | 641 | ** -p List only private branches |
| 642 | 642 | ** -r Reverse the sort order |
| 643 | 643 | ** -t Show recently changed branches first |
| 644 | 644 | ** --self List only branches where you participate |
| 645 | -** --username USER List only branches where USER participate | |
| 646 | -** --users N List up to N users partipiating | |
| 645 | +** --username USER List only branches where USER participates | |
| 646 | +** --users N List up to N users participating | |
| 647 | 647 | ** |
| 648 | 648 | ** The current branch is marked with an asterisk. Private branches are |
| 649 | 649 | ** marked with a hash sign. |
| 650 | 650 | ** |
| 651 | 651 | ** If GLOB is given, show only branches matching the pattern. |
| 652 | 652 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -640,12 +640,12 @@ | |
| 640 | ** -M|--unmerged List branches not merged into the current branch |
| 641 | ** -p List only private branches |
| 642 | ** -r Reverse the sort order |
| 643 | ** -t Show recently changed branches first |
| 644 | ** --self List only branches where you participate |
| 645 | ** --username USER List only branches where USER participate |
| 646 | ** --users N List up to N users partipiating |
| 647 | ** |
| 648 | ** The current branch is marked with an asterisk. Private branches are |
| 649 | ** marked with a hash sign. |
| 650 | ** |
| 651 | ** If GLOB is given, show only branches matching the pattern. |
| 652 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -640,12 +640,12 @@ | |
| 640 | ** -M|--unmerged List branches not merged into the current branch |
| 641 | ** -p List only private branches |
| 642 | ** -r Reverse the sort order |
| 643 | ** -t Show recently changed branches first |
| 644 | ** --self List only branches where you participate |
| 645 | ** --username USER List only branches where USER participates |
| 646 | ** --users N List up to N users participating |
| 647 | ** |
| 648 | ** The current branch is marked with an asterisk. Private branches are |
| 649 | ** marked with a hash sign. |
| 650 | ** |
| 651 | ** If GLOB is given, show only branches matching the pattern. |
| 652 |
+1
-1
| --- src/chat.c | ||
| +++ src/chat.c | ||
| @@ -1202,11 +1202,11 @@ | ||
| 1202 | 1202 | ** |
| 1203 | 1203 | ** --all Download all chat content. Normally only |
| 1204 | 1204 | ** previously undownloaded content is retrieved. |
| 1205 | 1205 | ** --debug Additional debugging output |
| 1206 | 1206 | ** --out DATABASE Store CHAT table in separate database file |
| 1207 | -** DATABASE rather that adding to local clone | |
| 1207 | +** DATABASE rather than adding to local clone | |
| 1208 | 1208 | ** --unsafe Allow the use of unencrypted http:// |
| 1209 | 1209 | ** |
| 1210 | 1210 | ** > fossil chat send [ARGUMENTS] |
| 1211 | 1211 | ** |
| 1212 | 1212 | ** This command sends a new message to the chatroom. The message |
| 1213 | 1213 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -1202,11 +1202,11 @@ | |
| 1202 | ** |
| 1203 | ** --all Download all chat content. Normally only |
| 1204 | ** previously undownloaded content is retrieved. |
| 1205 | ** --debug Additional debugging output |
| 1206 | ** --out DATABASE Store CHAT table in separate database file |
| 1207 | ** DATABASE rather that adding to local clone |
| 1208 | ** --unsafe Allow the use of unencrypted http:// |
| 1209 | ** |
| 1210 | ** > fossil chat send [ARGUMENTS] |
| 1211 | ** |
| 1212 | ** This command sends a new message to the chatroom. The message |
| 1213 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -1202,11 +1202,11 @@ | |
| 1202 | ** |
| 1203 | ** --all Download all chat content. Normally only |
| 1204 | ** previously undownloaded content is retrieved. |
| 1205 | ** --debug Additional debugging output |
| 1206 | ** --out DATABASE Store CHAT table in separate database file |
| 1207 | ** DATABASE rather than adding to local clone |
| 1208 | ** --unsafe Allow the use of unencrypted http:// |
| 1209 | ** |
| 1210 | ** > fossil chat send [ARGUMENTS] |
| 1211 | ** |
| 1212 | ** This command sends a new message to the chatroom. The message |
| 1213 |
+77
-16
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -567,11 +567,11 @@ | ||
| 567 | 567 | } |
| 568 | 568 | |
| 569 | 569 | /* Confirm current working directory is within check-out. */ |
| 570 | 570 | db_must_be_within_tree(); |
| 571 | 571 | |
| 572 | - /* Get check-out version. l*/ | |
| 572 | + /* Get check-out version. */ | |
| 573 | 573 | vid = db_lget_int("checkout", 0); |
| 574 | 574 | |
| 575 | 575 | /* Relative path flag determination is done by a shared function. */ |
| 576 | 576 | if( determine_cwd_relative_option() ){ |
| 577 | 577 | flags |= C_RELPATH; |
| @@ -967,12 +967,12 @@ | ||
| 967 | 967 | /* |
| 968 | 968 | ** COMMAND: tree |
| 969 | 969 | ** |
| 970 | 970 | ** Usage: %fossil tree ?OPTIONS? ?PATHS ...? |
| 971 | 971 | ** |
| 972 | -** List all files in the current check-out in after the fashion of the | |
| 973 | -** "tree" command. If PATHS is included, only the named files | |
| 972 | +** List all files in the current check-out much like the "tree" | |
| 973 | +** command does. If PATHS is included, only the named files | |
| 974 | 974 | ** (or their children if directories) are shown. |
| 975 | 975 | ** |
| 976 | 976 | ** Options: |
| 977 | 977 | ** -r VERSION The specific check-in to list |
| 978 | 978 | ** -R|--repository REPO Extract info from repository REPO |
| @@ -1563,17 +1563,17 @@ | ||
| 1563 | 1563 | break; |
| 1564 | 1564 | } |
| 1565 | 1565 | diffFiles[i].nName = strlen(diffFiles[i].zName); |
| 1566 | 1566 | diffFiles[i].nUsed = 0; |
| 1567 | 1567 | } |
| 1568 | - diff_against_disk(0, &DCfg, diffFiles, &prompt); | |
| 1568 | + diff_version_to_checkout(0, &DCfg, diffFiles, &prompt); | |
| 1569 | 1569 | for( i=0; diffFiles[i].zName; ++i ){ |
| 1570 | 1570 | fossil_free(diffFiles[i].zName); |
| 1571 | 1571 | } |
| 1572 | 1572 | fossil_free(diffFiles); |
| 1573 | 1573 | }else{ |
| 1574 | - diff_against_disk(0, &DCfg, 0, &prompt); | |
| 1574 | + diff_version_to_checkout(0, &DCfg, 0, &prompt); | |
| 1575 | 1575 | } |
| 1576 | 1576 | } |
| 1577 | 1577 | prompt_for_user_comment(pComment, &prompt); |
| 1578 | 1578 | blob_reset(&prompt); |
| 1579 | 1579 | } |
| @@ -2318,11 +2318,11 @@ | ||
| 2318 | 2318 | ** |
| 2319 | 2319 | ** A check-in is not permitted to fork unless the --allow-fork option |
| 2320 | 2320 | ** appears. An empty check-in (i.e. with nothing changed) is not |
| 2321 | 2321 | ** allowed unless the --allow-empty option appears. A check-in may not |
| 2322 | 2322 | ** be older than its ancestor unless the --allow-older option appears. |
| 2323 | -** If any of files in the check-in appear to contain unresolved merge | |
| 2323 | +** If any files in the check-in appear to contain unresolved merge | |
| 2324 | 2324 | ** conflicts, the check-in will not be allowed unless the |
| 2325 | 2325 | ** --allow-conflict option is present. In addition, the entire |
| 2326 | 2326 | ** check-in process may be aborted if a file contains content that |
| 2327 | 2327 | ** appears to be binary, Unicode text, or text with CR/LF line endings |
| 2328 | 2328 | ** unless the interactive user chooses to proceed. If there is no |
| @@ -2358,11 +2358,11 @@ | ||
| 2358 | 2358 | ** than relying on file mtimes |
| 2359 | 2359 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| 2360 | 2360 | ** behave as if the user had entered 'yes' to |
| 2361 | 2361 | ** the question of whether to proceed despite |
| 2362 | 2362 | ** the skew. |
| 2363 | -** --ignore-oversize Do not warning the user about oversized files | |
| 2363 | +** --ignore-oversize Do not warn the user about oversized files | |
| 2364 | 2364 | ** --integrate Close all merged-in branches |
| 2365 | 2365 | ** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment |
| 2366 | 2366 | ** -M|--message-file FILE Read the commit comment from given file |
| 2367 | 2367 | ** --mimetype MIMETYPE Mimetype of check-in comment |
| 2368 | 2368 | ** -n|--dry-run If given, display instead of run actions |
| @@ -2434,10 +2434,12 @@ | ||
| 2434 | 2434 | Blob ans; /* Answer to continuation prompts */ |
| 2435 | 2435 | char cReply; /* First character of ans */ |
| 2436 | 2436 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2437 | 2437 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2438 | 2438 | int mxSize; |
| 2439 | + char *zCurBranch = 0; /* The current branch name of checkout */ | |
| 2440 | + char *zNewBranch = 0; /* The branch name after update */ | |
| 2439 | 2441 | |
| 2440 | 2442 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2441 | 2443 | url_proxy_options(); |
| 2442 | 2444 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2443 | 2445 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2508,10 +2510,55 @@ | ||
| 2508 | 2510 | } |
| 2509 | 2511 | }else{ |
| 2510 | 2512 | privateParent = content_is_private(vid); |
| 2511 | 2513 | } |
| 2512 | 2514 | |
| 2515 | + user_select(); | |
| 2516 | + /* | |
| 2517 | + ** Check that the user exists. | |
| 2518 | + */ | |
| 2519 | + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ | |
| 2520 | + fossil_fatal("no such user: %s", g.zLogin); | |
| 2521 | + } | |
| 2522 | + | |
| 2523 | + /* | |
| 2524 | + ** Detect if the branch name has changed from the parent check-in | |
| 2525 | + ** and prompt if necessary | |
| 2526 | + **/ | |
| 2527 | + zCurBranch = db_text(0, | |
| 2528 | + " SELECT value FROM tagxref AS tx" | |
| 2529 | + " WHERE rid=(SELECT pid" | |
| 2530 | + " FROM tagxref LEFT JOIN event ON srcid=objid" | |
| 2531 | + " LEFT JOIN plink ON rid=cid" | |
| 2532 | + " WHERE rid=%d AND tagxref.tagid=%d" | |
| 2533 | + " AND srcid!=origid" | |
| 2534 | + " AND tagtype=2 AND coalesce(euser,user)!=%Q)" | |
| 2535 | + " AND tx.tagid=%d", | |
| 2536 | + vid, TAG_BRANCH, g.zLogin, TAG_BRANCH | |
| 2537 | + ); | |
| 2538 | + if( zCurBranch!=0 && zCurBranch[0]!=0 | |
| 2539 | + && forceFlag==0 | |
| 2540 | + && noPrompt==0 | |
| 2541 | + ){ | |
| 2542 | + zNewBranch = branch_of_rid(vid); | |
| 2543 | + fossil_warning( | |
| 2544 | + "WARNING: The parent check-in [%.10s] has been moved from branch\n" | |
| 2545 | + " '%s' over to branch '%s'.", | |
| 2546 | + rid_to_uuid(vid), zCurBranch, zNewBranch | |
| 2547 | + ); | |
| 2548 | + prompt_user("Commit anyway? (y/N) ", &ans); | |
| 2549 | + cReply = blob_str(&ans)[0]; | |
| 2550 | + blob_reset(&ans); | |
| 2551 | + if( cReply!='y' && cReply!='Y' ){ | |
| 2552 | + fossil_fatal("Abandoning commit because branch has changed"); | |
| 2553 | + } | |
| 2554 | + fossil_free(zNewBranch); | |
| 2555 | + fossil_free(zCurBranch); | |
| 2556 | + zCurBranch = branch_of_rid(vid); | |
| 2557 | + } | |
| 2558 | + if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid); | |
| 2559 | + | |
| 2513 | 2560 | /* Track the "private" status */ |
| 2514 | 2561 | g.markPrivate = privateFlag || privateParent; |
| 2515 | 2562 | if( privateFlag && !privateParent ){ |
| 2516 | 2563 | /* Apply default branch name ("private") and color ("orange") if not |
| 2517 | 2564 | ** specified otherwise on the command-line, and if the parent is not |
| @@ -2560,11 +2607,11 @@ | ||
| 2560 | 2607 | ** |
| 2561 | 2608 | ** If the remote repository sent an avoid-delta-manifests pragma on |
| 2562 | 2609 | ** the autosync above, then also try to avoid deltas, unless the |
| 2563 | 2610 | ** --delta option is specified. The remote repo will send the |
| 2564 | 2611 | ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests" |
| 2565 | - ** setting is enabled. | |
| 2612 | + ** setting enabled. | |
| 2566 | 2613 | */ |
| 2567 | 2614 | if( !db_get_boolean("seen-delta-manifest",0) |
| 2568 | 2615 | || db_get_boolean("forbid-delta-manifests",0) |
| 2569 | 2616 | || g.bAvoidDeltaManifests |
| 2570 | 2617 | ){ |
| @@ -2639,18 +2686,10 @@ | ||
| 2639 | 2686 | "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo); |
| 2640 | 2687 | } |
| 2641 | 2688 | db_finalize(&q); |
| 2642 | 2689 | } |
| 2643 | 2690 | |
| 2644 | - user_select(); | |
| 2645 | - /* | |
| 2646 | - ** Check that the user exists. | |
| 2647 | - */ | |
| 2648 | - if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ | |
| 2649 | - fossil_fatal("no such user: %s", g.zLogin); | |
| 2650 | - } | |
| 2651 | - | |
| 2652 | 2691 | hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0); |
| 2653 | 2692 | db_begin_transaction(); |
| 2654 | 2693 | db_record_repository_filename(0); |
| 2655 | 2694 | if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ |
| 2656 | 2695 | fossil_fatal("nothing has changed; use --allow-empty to override"); |
| @@ -2713,10 +2752,32 @@ | ||
| 2713 | 2752 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| 2714 | 2753 | " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) |
| 2715 | 2754 | ){ |
| 2716 | 2755 | fossil_fatal("cannot commit against a closed leaf"); |
| 2717 | 2756 | } |
| 2757 | + | |
| 2758 | + /* Require confirmation to continue with the check-in if the branch | |
| 2759 | + ** has changed and the committer did not provide the same branch | |
| 2760 | + */ | |
| 2761 | + zNewBranch = branch_of_rid(vid); | |
| 2762 | + if( fossil_strcmp(zCurBranch, zNewBranch)!=0 | |
| 2763 | + && fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0 | |
| 2764 | + && forceFlag==0 | |
| 2765 | + && noPrompt==0 | |
| 2766 | + ){ | |
| 2767 | + fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'", | |
| 2768 | + rid_to_uuid(vid), zCurBranch, zNewBranch); | |
| 2769 | + prompt_user("continue (y/N)? ", &ans); | |
| 2770 | + cReply = blob_str(&ans)[0]; | |
| 2771 | + blob_reset(&ans); | |
| 2772 | + if( cReply!='y' && cReply!='Y' ){ | |
| 2773 | + fossil_fatal("Abandoning commit because branch has changed"); | |
| 2774 | + } | |
| 2775 | + fossil_free(zCurBranch); | |
| 2776 | + zCurBranch = branch_of_rid(vid); | |
| 2777 | + } | |
| 2778 | + fossil_free(zNewBranch); | |
| 2718 | 2779 | |
| 2719 | 2780 | /* Always exit the loop on the second pass */ |
| 2720 | 2781 | if( bRecheck ) break; |
| 2721 | 2782 | |
| 2722 | 2783 | |
| 2723 | 2784 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -567,11 +567,11 @@ | |
| 567 | } |
| 568 | |
| 569 | /* Confirm current working directory is within check-out. */ |
| 570 | db_must_be_within_tree(); |
| 571 | |
| 572 | /* Get check-out version. l*/ |
| 573 | vid = db_lget_int("checkout", 0); |
| 574 | |
| 575 | /* Relative path flag determination is done by a shared function. */ |
| 576 | if( determine_cwd_relative_option() ){ |
| 577 | flags |= C_RELPATH; |
| @@ -967,12 +967,12 @@ | |
| 967 | /* |
| 968 | ** COMMAND: tree |
| 969 | ** |
| 970 | ** Usage: %fossil tree ?OPTIONS? ?PATHS ...? |
| 971 | ** |
| 972 | ** List all files in the current check-out in after the fashion of the |
| 973 | ** "tree" command. If PATHS is included, only the named files |
| 974 | ** (or their children if directories) are shown. |
| 975 | ** |
| 976 | ** Options: |
| 977 | ** -r VERSION The specific check-in to list |
| 978 | ** -R|--repository REPO Extract info from repository REPO |
| @@ -1563,17 +1563,17 @@ | |
| 1563 | break; |
| 1564 | } |
| 1565 | diffFiles[i].nName = strlen(diffFiles[i].zName); |
| 1566 | diffFiles[i].nUsed = 0; |
| 1567 | } |
| 1568 | diff_against_disk(0, &DCfg, diffFiles, &prompt); |
| 1569 | for( i=0; diffFiles[i].zName; ++i ){ |
| 1570 | fossil_free(diffFiles[i].zName); |
| 1571 | } |
| 1572 | fossil_free(diffFiles); |
| 1573 | }else{ |
| 1574 | diff_against_disk(0, &DCfg, 0, &prompt); |
| 1575 | } |
| 1576 | } |
| 1577 | prompt_for_user_comment(pComment, &prompt); |
| 1578 | blob_reset(&prompt); |
| 1579 | } |
| @@ -2318,11 +2318,11 @@ | |
| 2318 | ** |
| 2319 | ** A check-in is not permitted to fork unless the --allow-fork option |
| 2320 | ** appears. An empty check-in (i.e. with nothing changed) is not |
| 2321 | ** allowed unless the --allow-empty option appears. A check-in may not |
| 2322 | ** be older than its ancestor unless the --allow-older option appears. |
| 2323 | ** If any of files in the check-in appear to contain unresolved merge |
| 2324 | ** conflicts, the check-in will not be allowed unless the |
| 2325 | ** --allow-conflict option is present. In addition, the entire |
| 2326 | ** check-in process may be aborted if a file contains content that |
| 2327 | ** appears to be binary, Unicode text, or text with CR/LF line endings |
| 2328 | ** unless the interactive user chooses to proceed. If there is no |
| @@ -2358,11 +2358,11 @@ | |
| 2358 | ** than relying on file mtimes |
| 2359 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| 2360 | ** behave as if the user had entered 'yes' to |
| 2361 | ** the question of whether to proceed despite |
| 2362 | ** the skew. |
| 2363 | ** --ignore-oversize Do not warning the user about oversized files |
| 2364 | ** --integrate Close all merged-in branches |
| 2365 | ** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment |
| 2366 | ** -M|--message-file FILE Read the commit comment from given file |
| 2367 | ** --mimetype MIMETYPE Mimetype of check-in comment |
| 2368 | ** -n|--dry-run If given, display instead of run actions |
| @@ -2434,10 +2434,12 @@ | |
| 2434 | Blob ans; /* Answer to continuation prompts */ |
| 2435 | char cReply; /* First character of ans */ |
| 2436 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2437 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2438 | int mxSize; |
| 2439 | |
| 2440 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2441 | url_proxy_options(); |
| 2442 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2443 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2508,10 +2510,55 @@ | |
| 2508 | } |
| 2509 | }else{ |
| 2510 | privateParent = content_is_private(vid); |
| 2511 | } |
| 2512 | |
| 2513 | /* Track the "private" status */ |
| 2514 | g.markPrivate = privateFlag || privateParent; |
| 2515 | if( privateFlag && !privateParent ){ |
| 2516 | /* Apply default branch name ("private") and color ("orange") if not |
| 2517 | ** specified otherwise on the command-line, and if the parent is not |
| @@ -2560,11 +2607,11 @@ | |
| 2560 | ** |
| 2561 | ** If the remote repository sent an avoid-delta-manifests pragma on |
| 2562 | ** the autosync above, then also try to avoid deltas, unless the |
| 2563 | ** --delta option is specified. The remote repo will send the |
| 2564 | ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests" |
| 2565 | ** setting is enabled. |
| 2566 | */ |
| 2567 | if( !db_get_boolean("seen-delta-manifest",0) |
| 2568 | || db_get_boolean("forbid-delta-manifests",0) |
| 2569 | || g.bAvoidDeltaManifests |
| 2570 | ){ |
| @@ -2639,18 +2686,10 @@ | |
| 2639 | "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo); |
| 2640 | } |
| 2641 | db_finalize(&q); |
| 2642 | } |
| 2643 | |
| 2644 | user_select(); |
| 2645 | /* |
| 2646 | ** Check that the user exists. |
| 2647 | */ |
| 2648 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 2649 | fossil_fatal("no such user: %s", g.zLogin); |
| 2650 | } |
| 2651 | |
| 2652 | hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0); |
| 2653 | db_begin_transaction(); |
| 2654 | db_record_repository_filename(0); |
| 2655 | if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ |
| 2656 | fossil_fatal("nothing has changed; use --allow-empty to override"); |
| @@ -2713,10 +2752,32 @@ | |
| 2713 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| 2714 | " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) |
| 2715 | ){ |
| 2716 | fossil_fatal("cannot commit against a closed leaf"); |
| 2717 | } |
| 2718 | |
| 2719 | /* Always exit the loop on the second pass */ |
| 2720 | if( bRecheck ) break; |
| 2721 | |
| 2722 | |
| 2723 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -567,11 +567,11 @@ | |
| 567 | } |
| 568 | |
| 569 | /* Confirm current working directory is within check-out. */ |
| 570 | db_must_be_within_tree(); |
| 571 | |
| 572 | /* Get check-out version. */ |
| 573 | vid = db_lget_int("checkout", 0); |
| 574 | |
| 575 | /* Relative path flag determination is done by a shared function. */ |
| 576 | if( determine_cwd_relative_option() ){ |
| 577 | flags |= C_RELPATH; |
| @@ -967,12 +967,12 @@ | |
| 967 | /* |
| 968 | ** COMMAND: tree |
| 969 | ** |
| 970 | ** Usage: %fossil tree ?OPTIONS? ?PATHS ...? |
| 971 | ** |
| 972 | ** List all files in the current check-out much like the "tree" |
| 973 | ** command does. If PATHS is included, only the named files |
| 974 | ** (or their children if directories) are shown. |
| 975 | ** |
| 976 | ** Options: |
| 977 | ** -r VERSION The specific check-in to list |
| 978 | ** -R|--repository REPO Extract info from repository REPO |
| @@ -1563,17 +1563,17 @@ | |
| 1563 | break; |
| 1564 | } |
| 1565 | diffFiles[i].nName = strlen(diffFiles[i].zName); |
| 1566 | diffFiles[i].nUsed = 0; |
| 1567 | } |
| 1568 | diff_version_to_checkout(0, &DCfg, diffFiles, &prompt); |
| 1569 | for( i=0; diffFiles[i].zName; ++i ){ |
| 1570 | fossil_free(diffFiles[i].zName); |
| 1571 | } |
| 1572 | fossil_free(diffFiles); |
| 1573 | }else{ |
| 1574 | diff_version_to_checkout(0, &DCfg, 0, &prompt); |
| 1575 | } |
| 1576 | } |
| 1577 | prompt_for_user_comment(pComment, &prompt); |
| 1578 | blob_reset(&prompt); |
| 1579 | } |
| @@ -2318,11 +2318,11 @@ | |
| 2318 | ** |
| 2319 | ** A check-in is not permitted to fork unless the --allow-fork option |
| 2320 | ** appears. An empty check-in (i.e. with nothing changed) is not |
| 2321 | ** allowed unless the --allow-empty option appears. A check-in may not |
| 2322 | ** be older than its ancestor unless the --allow-older option appears. |
| 2323 | ** If any files in the check-in appear to contain unresolved merge |
| 2324 | ** conflicts, the check-in will not be allowed unless the |
| 2325 | ** --allow-conflict option is present. In addition, the entire |
| 2326 | ** check-in process may be aborted if a file contains content that |
| 2327 | ** appears to be binary, Unicode text, or text with CR/LF line endings |
| 2328 | ** unless the interactive user chooses to proceed. If there is no |
| @@ -2358,11 +2358,11 @@ | |
| 2358 | ** than relying on file mtimes |
| 2359 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| 2360 | ** behave as if the user had entered 'yes' to |
| 2361 | ** the question of whether to proceed despite |
| 2362 | ** the skew. |
| 2363 | ** --ignore-oversize Do not warn the user about oversized files |
| 2364 | ** --integrate Close all merged-in branches |
| 2365 | ** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment |
| 2366 | ** -M|--message-file FILE Read the commit comment from given file |
| 2367 | ** --mimetype MIMETYPE Mimetype of check-in comment |
| 2368 | ** -n|--dry-run If given, display instead of run actions |
| @@ -2434,10 +2434,12 @@ | |
| 2434 | Blob ans; /* Answer to continuation prompts */ |
| 2435 | char cReply; /* First character of ans */ |
| 2436 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2437 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2438 | int mxSize; |
| 2439 | char *zCurBranch = 0; /* The current branch name of checkout */ |
| 2440 | char *zNewBranch = 0; /* The branch name after update */ |
| 2441 | |
| 2442 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2443 | url_proxy_options(); |
| 2444 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2445 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2508,10 +2510,55 @@ | |
| 2510 | } |
| 2511 | }else{ |
| 2512 | privateParent = content_is_private(vid); |
| 2513 | } |
| 2514 | |
| 2515 | user_select(); |
| 2516 | /* |
| 2517 | ** Check that the user exists. |
| 2518 | */ |
| 2519 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 2520 | fossil_fatal("no such user: %s", g.zLogin); |
| 2521 | } |
| 2522 | |
| 2523 | /* |
| 2524 | ** Detect if the branch name has changed from the parent check-in |
| 2525 | ** and prompt if necessary |
| 2526 | **/ |
| 2527 | zCurBranch = db_text(0, |
| 2528 | " SELECT value FROM tagxref AS tx" |
| 2529 | " WHERE rid=(SELECT pid" |
| 2530 | " FROM tagxref LEFT JOIN event ON srcid=objid" |
| 2531 | " LEFT JOIN plink ON rid=cid" |
| 2532 | " WHERE rid=%d AND tagxref.tagid=%d" |
| 2533 | " AND srcid!=origid" |
| 2534 | " AND tagtype=2 AND coalesce(euser,user)!=%Q)" |
| 2535 | " AND tx.tagid=%d", |
| 2536 | vid, TAG_BRANCH, g.zLogin, TAG_BRANCH |
| 2537 | ); |
| 2538 | if( zCurBranch!=0 && zCurBranch[0]!=0 |
| 2539 | && forceFlag==0 |
| 2540 | && noPrompt==0 |
| 2541 | ){ |
| 2542 | zNewBranch = branch_of_rid(vid); |
| 2543 | fossil_warning( |
| 2544 | "WARNING: The parent check-in [%.10s] has been moved from branch\n" |
| 2545 | " '%s' over to branch '%s'.", |
| 2546 | rid_to_uuid(vid), zCurBranch, zNewBranch |
| 2547 | ); |
| 2548 | prompt_user("Commit anyway? (y/N) ", &ans); |
| 2549 | cReply = blob_str(&ans)[0]; |
| 2550 | blob_reset(&ans); |
| 2551 | if( cReply!='y' && cReply!='Y' ){ |
| 2552 | fossil_fatal("Abandoning commit because branch has changed"); |
| 2553 | } |
| 2554 | fossil_free(zNewBranch); |
| 2555 | fossil_free(zCurBranch); |
| 2556 | zCurBranch = branch_of_rid(vid); |
| 2557 | } |
| 2558 | if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid); |
| 2559 | |
| 2560 | /* Track the "private" status */ |
| 2561 | g.markPrivate = privateFlag || privateParent; |
| 2562 | if( privateFlag && !privateParent ){ |
| 2563 | /* Apply default branch name ("private") and color ("orange") if not |
| 2564 | ** specified otherwise on the command-line, and if the parent is not |
| @@ -2560,11 +2607,11 @@ | |
| 2607 | ** |
| 2608 | ** If the remote repository sent an avoid-delta-manifests pragma on |
| 2609 | ** the autosync above, then also try to avoid deltas, unless the |
| 2610 | ** --delta option is specified. The remote repo will send the |
| 2611 | ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests" |
| 2612 | ** setting enabled. |
| 2613 | */ |
| 2614 | if( !db_get_boolean("seen-delta-manifest",0) |
| 2615 | || db_get_boolean("forbid-delta-manifests",0) |
| 2616 | || g.bAvoidDeltaManifests |
| 2617 | ){ |
| @@ -2639,18 +2686,10 @@ | |
| 2686 | "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo); |
| 2687 | } |
| 2688 | db_finalize(&q); |
| 2689 | } |
| 2690 | |
| 2691 | hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0); |
| 2692 | db_begin_transaction(); |
| 2693 | db_record_repository_filename(0); |
| 2694 | if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ |
| 2695 | fossil_fatal("nothing has changed; use --allow-empty to override"); |
| @@ -2713,10 +2752,32 @@ | |
| 2752 | " WHERE tagid=%d AND rid=%d AND tagtype>0" |
| 2753 | " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) |
| 2754 | ){ |
| 2755 | fossil_fatal("cannot commit against a closed leaf"); |
| 2756 | } |
| 2757 | |
| 2758 | /* Require confirmation to continue with the check-in if the branch |
| 2759 | ** has changed and the committer did not provide the same branch |
| 2760 | */ |
| 2761 | zNewBranch = branch_of_rid(vid); |
| 2762 | if( fossil_strcmp(zCurBranch, zNewBranch)!=0 |
| 2763 | && fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0 |
| 2764 | && forceFlag==0 |
| 2765 | && noPrompt==0 |
| 2766 | ){ |
| 2767 | fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'", |
| 2768 | rid_to_uuid(vid), zCurBranch, zNewBranch); |
| 2769 | prompt_user("continue (y/N)? ", &ans); |
| 2770 | cReply = blob_str(&ans)[0]; |
| 2771 | blob_reset(&ans); |
| 2772 | if( cReply!='y' && cReply!='Y' ){ |
| 2773 | fossil_fatal("Abandoning commit because branch has changed"); |
| 2774 | } |
| 2775 | fossil_free(zCurBranch); |
| 2776 | zCurBranch = branch_of_rid(vid); |
| 2777 | } |
| 2778 | fossil_free(zNewBranch); |
| 2779 | |
| 2780 | /* Always exit the loop on the second pass */ |
| 2781 | if( bRecheck ) break; |
| 2782 | |
| 2783 | |
| 2784 |
+3
-4
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -171,23 +171,22 @@ | ||
| 171 | 171 | ** each character as a flag to enable writing "manifest", "manifest.uuid" or |
| 172 | 172 | ** "manifest.tags". |
| 173 | 173 | */ |
| 174 | 174 | void manifest_to_disk(int vid){ |
| 175 | 175 | char *zManFile; |
| 176 | - Blob manifest; | |
| 177 | - Blob taglist; | |
| 178 | 176 | int flg; |
| 179 | 177 | |
| 180 | 178 | flg = db_get_manifest_setting(); |
| 181 | 179 | |
| 182 | 180 | if( flg & MFESTFLG_RAW ){ |
| 183 | - blob_zero(&manifest); | |
| 181 | + Blob manifest = BLOB_INITIALIZER; | |
| 184 | 182 | content_get(vid, &manifest); |
| 185 | 183 | sterilize_manifest(&manifest, CFTYPE_MANIFEST); |
| 186 | 184 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 187 | 185 | blob_write_to_file(&manifest, zManFile); |
| 188 | 186 | free(zManFile); |
| 187 | + blob_reset(&manifest); | |
| 189 | 188 | }else{ |
| 190 | 189 | if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ |
| 191 | 190 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 192 | 191 | file_delete(zManFile); |
| 193 | 192 | free(zManFile); |
| @@ -207,11 +206,11 @@ | ||
| 207 | 206 | file_delete(zManFile); |
| 208 | 207 | free(zManFile); |
| 209 | 208 | } |
| 210 | 209 | } |
| 211 | 210 | if( flg & MFESTFLG_TAGS ){ |
| 212 | - blob_zero(&taglist); | |
| 211 | + Blob taglist = BLOB_INITIALIZER; | |
| 213 | 212 | zManFile = mprintf("%smanifest.tags", g.zLocalRoot); |
| 214 | 213 | get_checkin_taglist(vid, &taglist); |
| 215 | 214 | blob_write_to_file(&taglist, zManFile); |
| 216 | 215 | free(zManFile); |
| 217 | 216 | blob_reset(&taglist); |
| 218 | 217 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -171,23 +171,22 @@ | |
| 171 | ** each character as a flag to enable writing "manifest", "manifest.uuid" or |
| 172 | ** "manifest.tags". |
| 173 | */ |
| 174 | void manifest_to_disk(int vid){ |
| 175 | char *zManFile; |
| 176 | Blob manifest; |
| 177 | Blob taglist; |
| 178 | int flg; |
| 179 | |
| 180 | flg = db_get_manifest_setting(); |
| 181 | |
| 182 | if( flg & MFESTFLG_RAW ){ |
| 183 | blob_zero(&manifest); |
| 184 | content_get(vid, &manifest); |
| 185 | sterilize_manifest(&manifest, CFTYPE_MANIFEST); |
| 186 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 187 | blob_write_to_file(&manifest, zManFile); |
| 188 | free(zManFile); |
| 189 | }else{ |
| 190 | if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ |
| 191 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 192 | file_delete(zManFile); |
| 193 | free(zManFile); |
| @@ -207,11 +206,11 @@ | |
| 207 | file_delete(zManFile); |
| 208 | free(zManFile); |
| 209 | } |
| 210 | } |
| 211 | if( flg & MFESTFLG_TAGS ){ |
| 212 | blob_zero(&taglist); |
| 213 | zManFile = mprintf("%smanifest.tags", g.zLocalRoot); |
| 214 | get_checkin_taglist(vid, &taglist); |
| 215 | blob_write_to_file(&taglist, zManFile); |
| 216 | free(zManFile); |
| 217 | blob_reset(&taglist); |
| 218 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -171,23 +171,22 @@ | |
| 171 | ** each character as a flag to enable writing "manifest", "manifest.uuid" or |
| 172 | ** "manifest.tags". |
| 173 | */ |
| 174 | void manifest_to_disk(int vid){ |
| 175 | char *zManFile; |
| 176 | int flg; |
| 177 | |
| 178 | flg = db_get_manifest_setting(); |
| 179 | |
| 180 | if( flg & MFESTFLG_RAW ){ |
| 181 | Blob manifest = BLOB_INITIALIZER; |
| 182 | content_get(vid, &manifest); |
| 183 | sterilize_manifest(&manifest, CFTYPE_MANIFEST); |
| 184 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 185 | blob_write_to_file(&manifest, zManFile); |
| 186 | free(zManFile); |
| 187 | blob_reset(&manifest); |
| 188 | }else{ |
| 189 | if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ |
| 190 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 191 | file_delete(zManFile); |
| 192 | free(zManFile); |
| @@ -207,11 +206,11 @@ | |
| 206 | file_delete(zManFile); |
| 207 | free(zManFile); |
| 208 | } |
| 209 | } |
| 210 | if( flg & MFESTFLG_TAGS ){ |
| 211 | Blob taglist = BLOB_INITIALIZER; |
| 212 | zManFile = mprintf("%smanifest.tags", g.zLocalRoot); |
| 213 | get_checkin_taglist(vid, &taglist); |
| 214 | blob_write_to_file(&taglist, zManFile); |
| 215 | free(zManFile); |
| 216 | blob_reset(&taglist); |
| 217 |
M
src/db.c
+110
-40
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -170,10 +170,12 @@ | ||
| 170 | 170 | void *pAuthArg; /* Argument to the authorizer */ |
| 171 | 171 | const char *zAuthName; /* Name of the authorizer */ |
| 172 | 172 | int bProtectTriggers; /* True if protection triggers already exist */ |
| 173 | 173 | int nProtect; /* Slots of aProtect used */ |
| 174 | 174 | unsigned aProtect[12]; /* Saved values of protectMask */ |
| 175 | + int pauseDmlLog; /* Ignore pDmlLog if positive */ | |
| 176 | + Blob *pDmlLog; /* Append DML statements here, of not NULL */ | |
| 175 | 177 | } db = { |
| 176 | 178 | PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */ |
| 177 | 179 | 0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}}; |
| 178 | 180 | |
| 179 | 181 | /* |
| @@ -643,10 +645,43 @@ | ||
| 643 | 645 | */ |
| 644 | 646 | #define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */ |
| 645 | 647 | #define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */ |
| 646 | 648 | #endif |
| 647 | 649 | |
| 650 | +/* | |
| 651 | +** If zSql is a DML statement, append it db.pDmlLog. | |
| 652 | +*/ | |
| 653 | +static void db_append_dml(const char *zSql){ | |
| 654 | + size_t nSql; | |
| 655 | + if( db.pDmlLog==0 ) return; | |
| 656 | + if( db.pauseDmlLog ) return; | |
| 657 | + if( zSql==0 ) return; | |
| 658 | + nSql = strlen(zSql); | |
| 659 | + while( nSql>0 && fossil_isspace(zSql[0]) ){ nSql--; zSql++; } | |
| 660 | + while( nSql>0 && fossil_isspace(zSql[nSql-1]) ) nSql--; | |
| 661 | + if( nSql<6 ) return; | |
| 662 | + if( fossil_strnicmp(zSql, "SELECT", 6)==0 ) return; | |
| 663 | + if( fossil_strnicmp(zSql, "PRAGMA", 6)==0 ) return; | |
| 664 | + blob_append(db.pDmlLog, zSql, nSql); | |
| 665 | + if( zSql[nSql-1]!=';' ) blob_append_char(db.pDmlLog, ';'); | |
| 666 | + blob_append_char(db.pDmlLog, '\n'); | |
| 667 | +} | |
| 668 | + | |
| 669 | +/* | |
| 670 | +** Set the Blob to which DML statement text should be appended. Set it | |
| 671 | +** to zero to stop appending DML statement text. | |
| 672 | +*/ | |
| 673 | +void db_append_dml_to_blob(Blob *pBlob){ | |
| 674 | + db.pDmlLog = pBlob; | |
| 675 | +} | |
| 676 | + | |
| 677 | +/* | |
| 678 | +** Pause or unpause the DML log | |
| 679 | +*/ | |
| 680 | +void db_pause_dml_log(void){ db.pauseDmlLog++; } | |
| 681 | +void db_unpause_dml_log(void){ db.pauseDmlLog--; } | |
| 682 | + | |
| 648 | 683 | /* |
| 649 | 684 | ** Prepare a Stmt. Assume that the Stmt is previously uninitialized. |
| 650 | 685 | ** If the input string contains multiple SQL statements, only the first |
| 651 | 686 | ** one is processed. All statements beyond the first are silently ignored. |
| 652 | 687 | */ |
| @@ -658,10 +693,11 @@ | ||
| 658 | 693 | blob_zero(&pStmt->sql); |
| 659 | 694 | blob_vappendf(&pStmt->sql, zFormat, ap); |
| 660 | 695 | va_end(ap); |
| 661 | 696 | zSql = blob_str(&pStmt->sql); |
| 662 | 697 | db.nPrepare++; |
| 698 | + db_append_dml(zSql); | |
| 663 | 699 | if( flags & DB_PREPARE_PERSISTENT ){ |
| 664 | 700 | prepFlags = SQLITE_PREPARE_PERSISTENT; |
| 665 | 701 | } |
| 666 | 702 | rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra); |
| 667 | 703 | if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){ |
| @@ -1047,10 +1083,11 @@ | ||
| 1047 | 1083 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 1048 | 1084 | if( rc ){ |
| 1049 | 1085 | db_err("%s: {%s}", sqlite3_errmsg(g.db), z); |
| 1050 | 1086 | }else if( pStmt ){ |
| 1051 | 1087 | db.nPrepare++; |
| 1088 | + db_append_dml(sqlite3_sql(pStmt)); | |
| 1052 | 1089 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 1053 | 1090 | rc = sqlite3_finalize(pStmt); |
| 1054 | 1091 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 1055 | 1092 | } |
| 1056 | 1093 | z = zEnd; |
| @@ -1312,10 +1349,50 @@ | ||
| 1312 | 1349 | sqlite3_result_int64(context, rid); |
| 1313 | 1350 | } |
| 1314 | 1351 | } |
| 1315 | 1352 | } |
| 1316 | 1353 | |
| 1354 | + | |
| 1355 | +/* | |
| 1356 | +** SETTING: timeline-utc boolean default=on | |
| 1357 | +** | |
| 1358 | +** If the timeline-utc setting is true, then Fossil tries to understand | |
| 1359 | +** and display all time values using UTC. If this setting is false, Fossil | |
| 1360 | +** tries to understand and display time values using the local timezone. | |
| 1361 | +** | |
| 1362 | +** The word "timeline" in the name of this setting is historical. | |
| 1363 | +** This setting applies to all user interfaces of Fossil, | |
| 1364 | +** not just the timeline. | |
| 1365 | +** | |
| 1366 | +** Note that when accessing Fossil using the web interface, the localtime | |
| 1367 | +** used is the localtime on the server, not on the client. | |
| 1368 | +*/ | |
| 1369 | +/* | |
| 1370 | +** Return true if Fossil is set to display times as UTC. Return false | |
| 1371 | +** if it wants to display times using the local timezone. | |
| 1372 | +** | |
| 1373 | +** False is returned if display is set to localtime even if the localtime | |
| 1374 | +** happens to be the same as UTC. | |
| 1375 | +*/ | |
| 1376 | +int fossil_ui_utctime(void){ | |
| 1377 | + if( g.fTimeFormat==0 ){ | |
| 1378 | + if( db_get_int("timeline-utc", 1) ){ | |
| 1379 | + g.fTimeFormat = 1; /* UTC */ | |
| 1380 | + }else{ | |
| 1381 | + g.fTimeFormat = 2; /* Localtime */ | |
| 1382 | + } | |
| 1383 | + } | |
| 1384 | + return g.fTimeFormat==1; | |
| 1385 | +} | |
| 1386 | + | |
| 1387 | +/* | |
| 1388 | +** Return true if Fossil is set to display times using the local timezone. | |
| 1389 | +*/ | |
| 1390 | +int fossil_ui_localtime(void){ | |
| 1391 | + return fossil_ui_utctime()==0; | |
| 1392 | +} | |
| 1393 | + | |
| 1317 | 1394 | /* |
| 1318 | 1395 | ** The toLocal() SQL function returns a string that is an argument to a |
| 1319 | 1396 | ** date/time function that is appropriate for modifying the time for display. |
| 1320 | 1397 | ** If UTC time display is selected, no modification occurs. If local time |
| 1321 | 1398 | ** display is selected, the time is adjusted appropriately. |
| @@ -1327,18 +1404,11 @@ | ||
| 1327 | 1404 | void db_tolocal_function( |
| 1328 | 1405 | sqlite3_context *context, |
| 1329 | 1406 | int argc, |
| 1330 | 1407 | sqlite3_value **argv |
| 1331 | 1408 | ){ |
| 1332 | - if( g.fTimeFormat==0 ){ | |
| 1333 | - if( db_get_int("timeline-utc", 1) ){ | |
| 1334 | - g.fTimeFormat = 1; | |
| 1335 | - }else{ | |
| 1336 | - g.fTimeFormat = 2; | |
| 1337 | - } | |
| 1338 | - } | |
| 1339 | - if( g.fTimeFormat==1 ){ | |
| 1409 | + if( fossil_ui_utctime() ){ | |
| 1340 | 1410 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1341 | 1411 | }else{ |
| 1342 | 1412 | sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC); |
| 1343 | 1413 | } |
| 1344 | 1414 | } |
| @@ -1356,18 +1426,11 @@ | ||
| 1356 | 1426 | void db_fromlocal_function( |
| 1357 | 1427 | sqlite3_context *context, |
| 1358 | 1428 | int argc, |
| 1359 | 1429 | sqlite3_value **argv |
| 1360 | 1430 | ){ |
| 1361 | - if( g.fTimeFormat==0 ){ | |
| 1362 | - if( db_get_int("timeline-utc", 1) ){ | |
| 1363 | - g.fTimeFormat = 1; | |
| 1364 | - }else{ | |
| 1365 | - g.fTimeFormat = 2; | |
| 1366 | - } | |
| 1367 | - } | |
| 1368 | - if( g.fTimeFormat==1 ){ | |
| 1431 | + if( fossil_ui_utctime() ){ | |
| 1369 | 1432 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1370 | 1433 | }else{ |
| 1371 | 1434 | sqlite3_result_text(context, "utc", -1, SQLITE_STATIC); |
| 1372 | 1435 | } |
| 1373 | 1436 | } |
| @@ -1524,24 +1587,31 @@ | ||
| 1524 | 1587 | ** Register the SQL functions that are useful both to the internal |
| 1525 | 1588 | ** representation and to the "fossil sql" command. |
| 1526 | 1589 | */ |
| 1527 | 1590 | void db_add_aux_functions(sqlite3 *db){ |
| 1528 | 1591 | sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc); |
| 1529 | - sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0, | |
| 1530 | - db_checkin_mtime_function, 0, 0); | |
| 1531 | - sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0, | |
| 1532 | - db_sym2rid_function, 0, 0); | |
| 1533 | - sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0, | |
| 1534 | - db_sym2rid_function, 0, 0); | |
| 1535 | - sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0, | |
| 1592 | + sqlite3_create_function(db, "checkin_mtime", 2, | |
| 1593 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1594 | + 0, db_checkin_mtime_function, 0, 0); | |
| 1595 | + sqlite3_create_function(db, "symbolic_name_to_rid", 1, | |
| 1596 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1597 | + 0, db_sym2rid_function, 0, 0); | |
| 1598 | + sqlite3_create_function(db, "symbolic_name_to_rid", 2, | |
| 1599 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1600 | + 0, db_sym2rid_function, 0, 0); | |
| 1601 | + sqlite3_create_function(db, "now", 0, | |
| 1602 | + SQLITE_UTF8|SQLITE_INNOCUOUS, 0, | |
| 1536 | 1603 | db_now_function, 0, 0); |
| 1537 | - sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0, | |
| 1538 | - db_tolocal_function, 0, 0); | |
| 1539 | - sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0, | |
| 1540 | - db_fromlocal_function, 0, 0); | |
| 1541 | - sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0, | |
| 1542 | - db_hextoblob, 0, 0); | |
| 1604 | + sqlite3_create_function(db, "toLocal", 0, | |
| 1605 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1606 | + 0, db_tolocal_function, 0, 0); | |
| 1607 | + sqlite3_create_function(db, "fromLocal", 0, | |
| 1608 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1609 | + 0, db_fromlocal_function, 0, 0); | |
| 1610 | + sqlite3_create_function(db, "hextoblob", 1, | |
| 1611 | + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, | |
| 1612 | + 0, db_hextoblob, 0, 0); | |
| 1543 | 1613 | sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0, |
| 1544 | 1614 | 0, capability_union_step, capability_union_finalize); |
| 1545 | 1615 | sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0, |
| 1546 | 1616 | capability_fullcap, 0, 0); |
| 1547 | 1617 | sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0, |
| @@ -1557,10 +1627,12 @@ | ||
| 1557 | 1627 | sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0, |
| 1558 | 1628 | url_nouser_func,0,0); |
| 1559 | 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1560 | 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1561 | 1631 | chat_msg_from_event, 0, 0); |
| 1632 | + sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, | |
| 1633 | + file_inode_sql_func,0,0); | |
| 1562 | 1634 | |
| 1563 | 1635 | } |
| 1564 | 1636 | |
| 1565 | 1637 | #if USE_SEE |
| 1566 | 1638 | /* |
| @@ -2103,11 +2175,11 @@ | ||
| 2103 | 2175 | sqlite3 *, char **, const sqlite3_api_routines * |
| 2104 | 2176 | ); |
| 2105 | 2177 | sqlite3_appendvfs_init(0,0,0); |
| 2106 | 2178 | g.zVfsName = "apndvfs"; |
| 2107 | 2179 | } |
| 2108 | - blob_zero(&bNameCheck); | |
| 2180 | + blob_reset(&bNameCheck); | |
| 2109 | 2181 | rc = sqlite3_open_v2( |
| 2110 | 2182 | zDbName, &db, |
| 2111 | 2183 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, |
| 2112 | 2184 | g.zVfsName |
| 2113 | 2185 | ); |
| @@ -2623,13 +2695,18 @@ | ||
| 2623 | 2695 | return res; |
| 2624 | 2696 | } |
| 2625 | 2697 | |
| 2626 | 2698 | /* |
| 2627 | 2699 | ** COMMAND: test-is-repo |
| 2700 | +** Usage: %fossil test-is-repo FILENAME... | |
| 2701 | +** | |
| 2702 | +** Test whether the specified files look like a SQLite database | |
| 2703 | +** containing a Fossil repository schema. | |
| 2628 | 2704 | */ |
| 2629 | 2705 | void test_is_repo(void){ |
| 2630 | 2706 | int i; |
| 2707 | + verify_all_options(); | |
| 2631 | 2708 | for(i=2; i<g.argc; i++){ |
| 2632 | 2709 | fossil_print("%s: %s\n", |
| 2633 | 2710 | db_looks_like_a_repository(g.argv[i]) ? "yes" : " no", |
| 2634 | 2711 | g.argv[i] |
| 2635 | 2712 | ); |
| @@ -4080,11 +4157,11 @@ | ||
| 4080 | 4157 | ** specified then that version is checked out. Otherwise the most recent |
| 4081 | 4158 | ** check-in on the main branch (usually "trunk") is used. |
| 4082 | 4159 | ** |
| 4083 | 4160 | ** REPOSITORY can be the filename for a repository that already exists on the |
| 4084 | 4161 | ** local machine or it can be a URI for a remote repository. If REPOSITORY |
| 4085 | -** is a URI in one of the formats recognized by the [[clone]] command, then | |
| 4162 | +** is a URI in one of the formats recognized by the [[clone]] command, the | |
| 4086 | 4163 | ** remote repo is first cloned, then the clone is opened. The clone will be |
| 4087 | 4164 | ** stored in the current directory, or in DIR if the "--repodir DIR" option |
| 4088 | 4165 | ** is used. The name of the clone will be taken from the last term of the URI. |
| 4089 | 4166 | ** For "http:" and "https:" URIs, you can append an extra term to the end of |
| 4090 | 4167 | ** the URI to get any repository name you like. For example: |
| @@ -4681,17 +4758,10 @@ | ||
| 4681 | 4758 | ** to obtain a check-in lock during auto-sync, the server will |
| 4682 | 4759 | ** send the "pragma avoid-delta-manifests" statement in its reply, |
| 4683 | 4760 | ** which will cause the client to avoid generating a delta |
| 4684 | 4761 | ** manifest. |
| 4685 | 4762 | */ |
| 4686 | -/* | |
| 4687 | -** SETTING: forum-close-policy boolean default=off | |
| 4688 | -** If true, forum moderators may close/re-open forum posts, and reply | |
| 4689 | -** to closed posts. If false, only administrators may do so. Note that | |
| 4690 | -** this only affects the forum web UI, not post-closing tags which | |
| 4691 | -** arrive via the command-line or from synchronization with a remote. | |
| 4692 | -*/ | |
| 4693 | 4763 | /* |
| 4694 | 4764 | ** SETTING: gdiff-command width=40 default=gdiff sensitive |
| 4695 | 4765 | ** The value is an external command to run when performing a graphical |
| 4696 | 4766 | ** diff. If undefined, text diff will be used. |
| 4697 | 4767 | */ |
| @@ -5362,11 +5432,11 @@ | ||
| 5362 | 5432 | } |
| 5363 | 5433 | |
| 5364 | 5434 | /* |
| 5365 | 5435 | ** Compute a "fingerprint" on the repository. A fingerprint is used |
| 5366 | 5436 | ** to verify that that the repository has not been replaced by a clone |
| 5367 | -** of the same repository. More precisely, a fingerprint are used to | |
| 5437 | +** of the same repository. More precisely, a fingerprint is used to | |
| 5368 | 5438 | ** verify that the mapping between SHA3 hashes and RID values is unchanged. |
| 5369 | 5439 | ** |
| 5370 | 5440 | ** The check-out database ("localdb") stores RID values. When associating |
| 5371 | 5441 | ** a check-out database against a repository database, it is useful to verify |
| 5372 | 5442 | ** the fingerprint so that we know tha the RID values in the check-out |
| @@ -5427,11 +5497,11 @@ | ||
| 5427 | 5497 | ** COMMAND: test-fingerprint |
| 5428 | 5498 | ** |
| 5429 | 5499 | ** Usage: %fossil test-fingerprint ?RCVID? |
| 5430 | 5500 | ** |
| 5431 | 5501 | ** Display the repository fingerprint using the supplied RCVID or |
| 5432 | -** using the latest RCVID if not is given on the command line. | |
| 5502 | +** using the latest RCVID if none is given on the command line. | |
| 5433 | 5503 | ** Show both the legacy and the newer version of the fingerprint, |
| 5434 | 5504 | ** and the currently stored fingerprint if there is one. |
| 5435 | 5505 | */ |
| 5436 | 5506 | void test_fingerprint(void){ |
| 5437 | 5507 | int rcvid = 0; |
| 5438 | 5508 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -170,10 +170,12 @@ | |
| 170 | void *pAuthArg; /* Argument to the authorizer */ |
| 171 | const char *zAuthName; /* Name of the authorizer */ |
| 172 | int bProtectTriggers; /* True if protection triggers already exist */ |
| 173 | int nProtect; /* Slots of aProtect used */ |
| 174 | unsigned aProtect[12]; /* Saved values of protectMask */ |
| 175 | } db = { |
| 176 | PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */ |
| 177 | 0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}}; |
| 178 | |
| 179 | /* |
| @@ -643,10 +645,43 @@ | |
| 643 | */ |
| 644 | #define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */ |
| 645 | #define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */ |
| 646 | #endif |
| 647 | |
| 648 | /* |
| 649 | ** Prepare a Stmt. Assume that the Stmt is previously uninitialized. |
| 650 | ** If the input string contains multiple SQL statements, only the first |
| 651 | ** one is processed. All statements beyond the first are silently ignored. |
| 652 | */ |
| @@ -658,10 +693,11 @@ | |
| 658 | blob_zero(&pStmt->sql); |
| 659 | blob_vappendf(&pStmt->sql, zFormat, ap); |
| 660 | va_end(ap); |
| 661 | zSql = blob_str(&pStmt->sql); |
| 662 | db.nPrepare++; |
| 663 | if( flags & DB_PREPARE_PERSISTENT ){ |
| 664 | prepFlags = SQLITE_PREPARE_PERSISTENT; |
| 665 | } |
| 666 | rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra); |
| 667 | if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){ |
| @@ -1047,10 +1083,11 @@ | |
| 1047 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 1048 | if( rc ){ |
| 1049 | db_err("%s: {%s}", sqlite3_errmsg(g.db), z); |
| 1050 | }else if( pStmt ){ |
| 1051 | db.nPrepare++; |
| 1052 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 1053 | rc = sqlite3_finalize(pStmt); |
| 1054 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 1055 | } |
| 1056 | z = zEnd; |
| @@ -1312,10 +1349,50 @@ | |
| 1312 | sqlite3_result_int64(context, rid); |
| 1313 | } |
| 1314 | } |
| 1315 | } |
| 1316 | |
| 1317 | /* |
| 1318 | ** The toLocal() SQL function returns a string that is an argument to a |
| 1319 | ** date/time function that is appropriate for modifying the time for display. |
| 1320 | ** If UTC time display is selected, no modification occurs. If local time |
| 1321 | ** display is selected, the time is adjusted appropriately. |
| @@ -1327,18 +1404,11 @@ | |
| 1327 | void db_tolocal_function( |
| 1328 | sqlite3_context *context, |
| 1329 | int argc, |
| 1330 | sqlite3_value **argv |
| 1331 | ){ |
| 1332 | if( g.fTimeFormat==0 ){ |
| 1333 | if( db_get_int("timeline-utc", 1) ){ |
| 1334 | g.fTimeFormat = 1; |
| 1335 | }else{ |
| 1336 | g.fTimeFormat = 2; |
| 1337 | } |
| 1338 | } |
| 1339 | if( g.fTimeFormat==1 ){ |
| 1340 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1341 | }else{ |
| 1342 | sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC); |
| 1343 | } |
| 1344 | } |
| @@ -1356,18 +1426,11 @@ | |
| 1356 | void db_fromlocal_function( |
| 1357 | sqlite3_context *context, |
| 1358 | int argc, |
| 1359 | sqlite3_value **argv |
| 1360 | ){ |
| 1361 | if( g.fTimeFormat==0 ){ |
| 1362 | if( db_get_int("timeline-utc", 1) ){ |
| 1363 | g.fTimeFormat = 1; |
| 1364 | }else{ |
| 1365 | g.fTimeFormat = 2; |
| 1366 | } |
| 1367 | } |
| 1368 | if( g.fTimeFormat==1 ){ |
| 1369 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1370 | }else{ |
| 1371 | sqlite3_result_text(context, "utc", -1, SQLITE_STATIC); |
| 1372 | } |
| 1373 | } |
| @@ -1524,24 +1587,31 @@ | |
| 1524 | ** Register the SQL functions that are useful both to the internal |
| 1525 | ** representation and to the "fossil sql" command. |
| 1526 | */ |
| 1527 | void db_add_aux_functions(sqlite3 *db){ |
| 1528 | sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc); |
| 1529 | sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0, |
| 1530 | db_checkin_mtime_function, 0, 0); |
| 1531 | sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0, |
| 1532 | db_sym2rid_function, 0, 0); |
| 1533 | sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0, |
| 1534 | db_sym2rid_function, 0, 0); |
| 1535 | sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0, |
| 1536 | db_now_function, 0, 0); |
| 1537 | sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0, |
| 1538 | db_tolocal_function, 0, 0); |
| 1539 | sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0, |
| 1540 | db_fromlocal_function, 0, 0); |
| 1541 | sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0, |
| 1542 | db_hextoblob, 0, 0); |
| 1543 | sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0, |
| 1544 | 0, capability_union_step, capability_union_finalize); |
| 1545 | sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0, |
| 1546 | capability_fullcap, 0, 0); |
| 1547 | sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0, |
| @@ -1557,10 +1627,12 @@ | |
| 1557 | sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0, |
| 1558 | url_nouser_func,0,0); |
| 1559 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1560 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1561 | chat_msg_from_event, 0, 0); |
| 1562 | |
| 1563 | } |
| 1564 | |
| 1565 | #if USE_SEE |
| 1566 | /* |
| @@ -2103,11 +2175,11 @@ | |
| 2103 | sqlite3 *, char **, const sqlite3_api_routines * |
| 2104 | ); |
| 2105 | sqlite3_appendvfs_init(0,0,0); |
| 2106 | g.zVfsName = "apndvfs"; |
| 2107 | } |
| 2108 | blob_zero(&bNameCheck); |
| 2109 | rc = sqlite3_open_v2( |
| 2110 | zDbName, &db, |
| 2111 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, |
| 2112 | g.zVfsName |
| 2113 | ); |
| @@ -2623,13 +2695,18 @@ | |
| 2623 | return res; |
| 2624 | } |
| 2625 | |
| 2626 | /* |
| 2627 | ** COMMAND: test-is-repo |
| 2628 | */ |
| 2629 | void test_is_repo(void){ |
| 2630 | int i; |
| 2631 | for(i=2; i<g.argc; i++){ |
| 2632 | fossil_print("%s: %s\n", |
| 2633 | db_looks_like_a_repository(g.argv[i]) ? "yes" : " no", |
| 2634 | g.argv[i] |
| 2635 | ); |
| @@ -4080,11 +4157,11 @@ | |
| 4080 | ** specified then that version is checked out. Otherwise the most recent |
| 4081 | ** check-in on the main branch (usually "trunk") is used. |
| 4082 | ** |
| 4083 | ** REPOSITORY can be the filename for a repository that already exists on the |
| 4084 | ** local machine or it can be a URI for a remote repository. If REPOSITORY |
| 4085 | ** is a URI in one of the formats recognized by the [[clone]] command, then |
| 4086 | ** remote repo is first cloned, then the clone is opened. The clone will be |
| 4087 | ** stored in the current directory, or in DIR if the "--repodir DIR" option |
| 4088 | ** is used. The name of the clone will be taken from the last term of the URI. |
| 4089 | ** For "http:" and "https:" URIs, you can append an extra term to the end of |
| 4090 | ** the URI to get any repository name you like. For example: |
| @@ -4681,17 +4758,10 @@ | |
| 4681 | ** to obtain a check-in lock during auto-sync, the server will |
| 4682 | ** send the "pragma avoid-delta-manifests" statement in its reply, |
| 4683 | ** which will cause the client to avoid generating a delta |
| 4684 | ** manifest. |
| 4685 | */ |
| 4686 | /* |
| 4687 | ** SETTING: forum-close-policy boolean default=off |
| 4688 | ** If true, forum moderators may close/re-open forum posts, and reply |
| 4689 | ** to closed posts. If false, only administrators may do so. Note that |
| 4690 | ** this only affects the forum web UI, not post-closing tags which |
| 4691 | ** arrive via the command-line or from synchronization with a remote. |
| 4692 | */ |
| 4693 | /* |
| 4694 | ** SETTING: gdiff-command width=40 default=gdiff sensitive |
| 4695 | ** The value is an external command to run when performing a graphical |
| 4696 | ** diff. If undefined, text diff will be used. |
| 4697 | */ |
| @@ -5362,11 +5432,11 @@ | |
| 5362 | } |
| 5363 | |
| 5364 | /* |
| 5365 | ** Compute a "fingerprint" on the repository. A fingerprint is used |
| 5366 | ** to verify that that the repository has not been replaced by a clone |
| 5367 | ** of the same repository. More precisely, a fingerprint are used to |
| 5368 | ** verify that the mapping between SHA3 hashes and RID values is unchanged. |
| 5369 | ** |
| 5370 | ** The check-out database ("localdb") stores RID values. When associating |
| 5371 | ** a check-out database against a repository database, it is useful to verify |
| 5372 | ** the fingerprint so that we know tha the RID values in the check-out |
| @@ -5427,11 +5497,11 @@ | |
| 5427 | ** COMMAND: test-fingerprint |
| 5428 | ** |
| 5429 | ** Usage: %fossil test-fingerprint ?RCVID? |
| 5430 | ** |
| 5431 | ** Display the repository fingerprint using the supplied RCVID or |
| 5432 | ** using the latest RCVID if not is given on the command line. |
| 5433 | ** Show both the legacy and the newer version of the fingerprint, |
| 5434 | ** and the currently stored fingerprint if there is one. |
| 5435 | */ |
| 5436 | void test_fingerprint(void){ |
| 5437 | int rcvid = 0; |
| 5438 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -170,10 +170,12 @@ | |
| 170 | void *pAuthArg; /* Argument to the authorizer */ |
| 171 | const char *zAuthName; /* Name of the authorizer */ |
| 172 | int bProtectTriggers; /* True if protection triggers already exist */ |
| 173 | int nProtect; /* Slots of aProtect used */ |
| 174 | unsigned aProtect[12]; /* Saved values of protectMask */ |
| 175 | int pauseDmlLog; /* Ignore pDmlLog if positive */ |
| 176 | Blob *pDmlLog; /* Append DML statements here, of not NULL */ |
| 177 | } db = { |
| 178 | PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */ |
| 179 | 0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}}; |
| 180 | |
| 181 | /* |
| @@ -643,10 +645,43 @@ | |
| 645 | */ |
| 646 | #define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */ |
| 647 | #define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */ |
| 648 | #endif |
| 649 | |
| 650 | /* |
| 651 | ** If zSql is a DML statement, append it db.pDmlLog. |
| 652 | */ |
| 653 | static void db_append_dml(const char *zSql){ |
| 654 | size_t nSql; |
| 655 | if( db.pDmlLog==0 ) return; |
| 656 | if( db.pauseDmlLog ) return; |
| 657 | if( zSql==0 ) return; |
| 658 | nSql = strlen(zSql); |
| 659 | while( nSql>0 && fossil_isspace(zSql[0]) ){ nSql--; zSql++; } |
| 660 | while( nSql>0 && fossil_isspace(zSql[nSql-1]) ) nSql--; |
| 661 | if( nSql<6 ) return; |
| 662 | if( fossil_strnicmp(zSql, "SELECT", 6)==0 ) return; |
| 663 | if( fossil_strnicmp(zSql, "PRAGMA", 6)==0 ) return; |
| 664 | blob_append(db.pDmlLog, zSql, nSql); |
| 665 | if( zSql[nSql-1]!=';' ) blob_append_char(db.pDmlLog, ';'); |
| 666 | blob_append_char(db.pDmlLog, '\n'); |
| 667 | } |
| 668 | |
| 669 | /* |
| 670 | ** Set the Blob to which DML statement text should be appended. Set it |
| 671 | ** to zero to stop appending DML statement text. |
| 672 | */ |
| 673 | void db_append_dml_to_blob(Blob *pBlob){ |
| 674 | db.pDmlLog = pBlob; |
| 675 | } |
| 676 | |
| 677 | /* |
| 678 | ** Pause or unpause the DML log |
| 679 | */ |
| 680 | void db_pause_dml_log(void){ db.pauseDmlLog++; } |
| 681 | void db_unpause_dml_log(void){ db.pauseDmlLog--; } |
| 682 | |
| 683 | /* |
| 684 | ** Prepare a Stmt. Assume that the Stmt is previously uninitialized. |
| 685 | ** If the input string contains multiple SQL statements, only the first |
| 686 | ** one is processed. All statements beyond the first are silently ignored. |
| 687 | */ |
| @@ -658,10 +693,11 @@ | |
| 693 | blob_zero(&pStmt->sql); |
| 694 | blob_vappendf(&pStmt->sql, zFormat, ap); |
| 695 | va_end(ap); |
| 696 | zSql = blob_str(&pStmt->sql); |
| 697 | db.nPrepare++; |
| 698 | db_append_dml(zSql); |
| 699 | if( flags & DB_PREPARE_PERSISTENT ){ |
| 700 | prepFlags = SQLITE_PREPARE_PERSISTENT; |
| 701 | } |
| 702 | rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra); |
| 703 | if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){ |
| @@ -1047,10 +1083,11 @@ | |
| 1083 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 1084 | if( rc ){ |
| 1085 | db_err("%s: {%s}", sqlite3_errmsg(g.db), z); |
| 1086 | }else if( pStmt ){ |
| 1087 | db.nPrepare++; |
| 1088 | db_append_dml(sqlite3_sql(pStmt)); |
| 1089 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 1090 | rc = sqlite3_finalize(pStmt); |
| 1091 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 1092 | } |
| 1093 | z = zEnd; |
| @@ -1312,10 +1349,50 @@ | |
| 1349 | sqlite3_result_int64(context, rid); |
| 1350 | } |
| 1351 | } |
| 1352 | } |
| 1353 | |
| 1354 | |
| 1355 | /* |
| 1356 | ** SETTING: timeline-utc boolean default=on |
| 1357 | ** |
| 1358 | ** If the timeline-utc setting is true, then Fossil tries to understand |
| 1359 | ** and display all time values using UTC. If this setting is false, Fossil |
| 1360 | ** tries to understand and display time values using the local timezone. |
| 1361 | ** |
| 1362 | ** The word "timeline" in the name of this setting is historical. |
| 1363 | ** This setting applies to all user interfaces of Fossil, |
| 1364 | ** not just the timeline. |
| 1365 | ** |
| 1366 | ** Note that when accessing Fossil using the web interface, the localtime |
| 1367 | ** used is the localtime on the server, not on the client. |
| 1368 | */ |
| 1369 | /* |
| 1370 | ** Return true if Fossil is set to display times as UTC. Return false |
| 1371 | ** if it wants to display times using the local timezone. |
| 1372 | ** |
| 1373 | ** False is returned if display is set to localtime even if the localtime |
| 1374 | ** happens to be the same as UTC. |
| 1375 | */ |
| 1376 | int fossil_ui_utctime(void){ |
| 1377 | if( g.fTimeFormat==0 ){ |
| 1378 | if( db_get_int("timeline-utc", 1) ){ |
| 1379 | g.fTimeFormat = 1; /* UTC */ |
| 1380 | }else{ |
| 1381 | g.fTimeFormat = 2; /* Localtime */ |
| 1382 | } |
| 1383 | } |
| 1384 | return g.fTimeFormat==1; |
| 1385 | } |
| 1386 | |
| 1387 | /* |
| 1388 | ** Return true if Fossil is set to display times using the local timezone. |
| 1389 | */ |
| 1390 | int fossil_ui_localtime(void){ |
| 1391 | return fossil_ui_utctime()==0; |
| 1392 | } |
| 1393 | |
| 1394 | /* |
| 1395 | ** The toLocal() SQL function returns a string that is an argument to a |
| 1396 | ** date/time function that is appropriate for modifying the time for display. |
| 1397 | ** If UTC time display is selected, no modification occurs. If local time |
| 1398 | ** display is selected, the time is adjusted appropriately. |
| @@ -1327,18 +1404,11 @@ | |
| 1404 | void db_tolocal_function( |
| 1405 | sqlite3_context *context, |
| 1406 | int argc, |
| 1407 | sqlite3_value **argv |
| 1408 | ){ |
| 1409 | if( fossil_ui_utctime() ){ |
| 1410 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1411 | }else{ |
| 1412 | sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC); |
| 1413 | } |
| 1414 | } |
| @@ -1356,18 +1426,11 @@ | |
| 1426 | void db_fromlocal_function( |
| 1427 | sqlite3_context *context, |
| 1428 | int argc, |
| 1429 | sqlite3_value **argv |
| 1430 | ){ |
| 1431 | if( fossil_ui_utctime() ){ |
| 1432 | sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC); |
| 1433 | }else{ |
| 1434 | sqlite3_result_text(context, "utc", -1, SQLITE_STATIC); |
| 1435 | } |
| 1436 | } |
| @@ -1524,24 +1587,31 @@ | |
| 1587 | ** Register the SQL functions that are useful both to the internal |
| 1588 | ** representation and to the "fossil sql" command. |
| 1589 | */ |
| 1590 | void db_add_aux_functions(sqlite3 *db){ |
| 1591 | sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc); |
| 1592 | sqlite3_create_function(db, "checkin_mtime", 2, |
| 1593 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1594 | 0, db_checkin_mtime_function, 0, 0); |
| 1595 | sqlite3_create_function(db, "symbolic_name_to_rid", 1, |
| 1596 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1597 | 0, db_sym2rid_function, 0, 0); |
| 1598 | sqlite3_create_function(db, "symbolic_name_to_rid", 2, |
| 1599 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1600 | 0, db_sym2rid_function, 0, 0); |
| 1601 | sqlite3_create_function(db, "now", 0, |
| 1602 | SQLITE_UTF8|SQLITE_INNOCUOUS, 0, |
| 1603 | db_now_function, 0, 0); |
| 1604 | sqlite3_create_function(db, "toLocal", 0, |
| 1605 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1606 | 0, db_tolocal_function, 0, 0); |
| 1607 | sqlite3_create_function(db, "fromLocal", 0, |
| 1608 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1609 | 0, db_fromlocal_function, 0, 0); |
| 1610 | sqlite3_create_function(db, "hextoblob", 1, |
| 1611 | SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, |
| 1612 | 0, db_hextoblob, 0, 0); |
| 1613 | sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0, |
| 1614 | 0, capability_union_step, capability_union_finalize); |
| 1615 | sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0, |
| 1616 | capability_fullcap, 0, 0); |
| 1617 | sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0, |
| @@ -1557,10 +1627,12 @@ | |
| 1627 | sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0, |
| 1628 | url_nouser_func,0,0); |
| 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | chat_msg_from_event, 0, 0); |
| 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | file_inode_sql_func,0,0); |
| 1634 | |
| 1635 | } |
| 1636 | |
| 1637 | #if USE_SEE |
| 1638 | /* |
| @@ -2103,11 +2175,11 @@ | |
| 2175 | sqlite3 *, char **, const sqlite3_api_routines * |
| 2176 | ); |
| 2177 | sqlite3_appendvfs_init(0,0,0); |
| 2178 | g.zVfsName = "apndvfs"; |
| 2179 | } |
| 2180 | blob_reset(&bNameCheck); |
| 2181 | rc = sqlite3_open_v2( |
| 2182 | zDbName, &db, |
| 2183 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, |
| 2184 | g.zVfsName |
| 2185 | ); |
| @@ -2623,13 +2695,18 @@ | |
| 2695 | return res; |
| 2696 | } |
| 2697 | |
| 2698 | /* |
| 2699 | ** COMMAND: test-is-repo |
| 2700 | ** Usage: %fossil test-is-repo FILENAME... |
| 2701 | ** |
| 2702 | ** Test whether the specified files look like a SQLite database |
| 2703 | ** containing a Fossil repository schema. |
| 2704 | */ |
| 2705 | void test_is_repo(void){ |
| 2706 | int i; |
| 2707 | verify_all_options(); |
| 2708 | for(i=2; i<g.argc; i++){ |
| 2709 | fossil_print("%s: %s\n", |
| 2710 | db_looks_like_a_repository(g.argv[i]) ? "yes" : " no", |
| 2711 | g.argv[i] |
| 2712 | ); |
| @@ -4080,11 +4157,11 @@ | |
| 4157 | ** specified then that version is checked out. Otherwise the most recent |
| 4158 | ** check-in on the main branch (usually "trunk") is used. |
| 4159 | ** |
| 4160 | ** REPOSITORY can be the filename for a repository that already exists on the |
| 4161 | ** local machine or it can be a URI for a remote repository. If REPOSITORY |
| 4162 | ** is a URI in one of the formats recognized by the [[clone]] command, the |
| 4163 | ** remote repo is first cloned, then the clone is opened. The clone will be |
| 4164 | ** stored in the current directory, or in DIR if the "--repodir DIR" option |
| 4165 | ** is used. The name of the clone will be taken from the last term of the URI. |
| 4166 | ** For "http:" and "https:" URIs, you can append an extra term to the end of |
| 4167 | ** the URI to get any repository name you like. For example: |
| @@ -4681,17 +4758,10 @@ | |
| 4758 | ** to obtain a check-in lock during auto-sync, the server will |
| 4759 | ** send the "pragma avoid-delta-manifests" statement in its reply, |
| 4760 | ** which will cause the client to avoid generating a delta |
| 4761 | ** manifest. |
| 4762 | */ |
| 4763 | /* |
| 4764 | ** SETTING: gdiff-command width=40 default=gdiff sensitive |
| 4765 | ** The value is an external command to run when performing a graphical |
| 4766 | ** diff. If undefined, text diff will be used. |
| 4767 | */ |
| @@ -5362,11 +5432,11 @@ | |
| 5432 | } |
| 5433 | |
| 5434 | /* |
| 5435 | ** Compute a "fingerprint" on the repository. A fingerprint is used |
| 5436 | ** to verify that that the repository has not been replaced by a clone |
| 5437 | ** of the same repository. More precisely, a fingerprint is used to |
| 5438 | ** verify that the mapping between SHA3 hashes and RID values is unchanged. |
| 5439 | ** |
| 5440 | ** The check-out database ("localdb") stores RID values. When associating |
| 5441 | ** a check-out database against a repository database, it is useful to verify |
| 5442 | ** the fingerprint so that we know tha the RID values in the check-out |
| @@ -5427,11 +5497,11 @@ | |
| 5497 | ** COMMAND: test-fingerprint |
| 5498 | ** |
| 5499 | ** Usage: %fossil test-fingerprint ?RCVID? |
| 5500 | ** |
| 5501 | ** Display the repository fingerprint using the supplied RCVID or |
| 5502 | ** using the latest RCVID if none is given on the command line. |
| 5503 | ** Show both the legacy and the newer version of the fingerprint, |
| 5504 | ** and the currently stored fingerprint if there is one. |
| 5505 | */ |
| 5506 | void test_fingerprint(void){ |
| 5507 | int rcvid = 0; |
| 5508 |
+10
-4
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -748,10 +748,20 @@ | ||
| 748 | 748 | border-bottom: 3px solid gold; |
| 749 | 749 | } |
| 750 | 750 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 751 | 751 | border-left: 1px solid gold; |
| 752 | 752 | } |
| 753 | +body.cpage-ckout .file-change-line, | |
| 754 | +body.cpage-info .file-change-line, | |
| 755 | +body.cpage-vdiff .file-change-line { | |
| 756 | + margin-top: 16px; | |
| 757 | + margin-bottom: 16px; | |
| 758 | + margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; | |
| 759 | + display: flex; | |
| 760 | + flex-direction: row; | |
| 761 | + justify-content: space-between; | |
| 762 | +} | |
| 753 | 763 | |
| 754 | 764 | span.modpending { |
| 755 | 765 | color: #b03800; |
| 756 | 766 | font-style: italic; |
| 757 | 767 | } |
| @@ -1818,14 +1828,10 @@ | ||
| 1818 | 1828 | } |
| 1819 | 1829 | body.fossil-dark-style .settings-icon { |
| 1820 | 1830 | filter: invert(100%); |
| 1821 | 1831 | } |
| 1822 | 1832 | |
| 1823 | -input[type="checkbox"].diff-toggle { | |
| 1824 | - float: right; | |
| 1825 | -} | |
| 1826 | - | |
| 1827 | 1833 | body.branch .brlist > table > tbody > tr:hover:not(.selected), |
| 1828 | 1834 | body.branch .brlist > table > tbody > tr.selected { |
| 1829 | 1835 | background-color: #ffc; |
| 1830 | 1836 | } |
| 1831 | 1837 | body.branch .brlist > table > tbody td:first-child > input { |
| 1832 | 1838 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -748,10 +748,20 @@ | |
| 748 | border-bottom: 3px solid gold; |
| 749 | } |
| 750 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 751 | border-left: 1px solid gold; |
| 752 | } |
| 753 | |
| 754 | span.modpending { |
| 755 | color: #b03800; |
| 756 | font-style: italic; |
| 757 | } |
| @@ -1818,14 +1828,10 @@ | |
| 1818 | } |
| 1819 | body.fossil-dark-style .settings-icon { |
| 1820 | filter: invert(100%); |
| 1821 | } |
| 1822 | |
| 1823 | input[type="checkbox"].diff-toggle { |
| 1824 | float: right; |
| 1825 | } |
| 1826 | |
| 1827 | body.branch .brlist > table > tbody > tr:hover:not(.selected), |
| 1828 | body.branch .brlist > table > tbody > tr.selected { |
| 1829 | background-color: #ffc; |
| 1830 | } |
| 1831 | body.branch .brlist > table > tbody td:first-child > input { |
| 1832 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -748,10 +748,20 @@ | |
| 748 | border-bottom: 3px solid gold; |
| 749 | } |
| 750 | body.tkt div.content ol.tkt-changes > li:target > ol { |
| 751 | border-left: 1px solid gold; |
| 752 | } |
| 753 | body.cpage-ckout .file-change-line, |
| 754 | body.cpage-info .file-change-line, |
| 755 | body.cpage-vdiff .file-change-line { |
| 756 | margin-top: 16px; |
| 757 | margin-bottom: 16px; |
| 758 | margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; |
| 759 | display: flex; |
| 760 | flex-direction: row; |
| 761 | justify-content: space-between; |
| 762 | } |
| 763 | |
| 764 | span.modpending { |
| 765 | color: #b03800; |
| 766 | font-style: italic; |
| 767 | } |
| @@ -1818,14 +1828,10 @@ | |
| 1828 | } |
| 1829 | body.fossil-dark-style .settings-icon { |
| 1830 | filter: invert(100%); |
| 1831 | } |
| 1832 | |
| 1833 | body.branch .brlist > table > tbody > tr:hover:not(.selected), |
| 1834 | body.branch .brlist > table > tbody > tr.selected { |
| 1835 | background-color: #ffc; |
| 1836 | } |
| 1837 | body.branch .brlist > table > tbody td:first-child > input { |
| 1838 |
+50
-48
| --- src/delta.c | ||
| +++ src/delta.c | ||
| @@ -225,59 +225,61 @@ | ||
| 225 | 225 | ** of four bytes. |
| 226 | 226 | */ |
| 227 | 227 | static unsigned int checksum(const char *zIn, size_t N){ |
| 228 | 228 | static const int byteOrderTest = 1; |
| 229 | 229 | const unsigned char *z = (const unsigned char *)zIn; |
| 230 | - const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3]; | |
| 231 | 230 | unsigned sum = 0; |
| 232 | - assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */ | |
| 233 | - if( 0==*(char*)&byteOrderTest ){ | |
| 234 | - /* This is a big-endian machine */ | |
| 235 | - while( z<zEnd ){ | |
| 236 | - sum += *(unsigned*)z; | |
| 237 | - z += 4; | |
| 238 | - } | |
| 239 | - }else{ | |
| 240 | - /* A little-endian machine */ | |
| 241 | -#if GCC_VERSION>=4003000 | |
| 242 | - while( z<zEnd ){ | |
| 243 | - sum += __builtin_bswap32(*(unsigned*)z); | |
| 244 | - z += 4; | |
| 245 | - } | |
| 246 | -#elif defined(_MSC_VER) && _MSC_VER>=1300 | |
| 247 | - while( z<zEnd ){ | |
| 248 | - sum += _byteswap_ulong(*(unsigned*)z); | |
| 249 | - z += 4; | |
| 250 | - } | |
| 251 | -#else | |
| 252 | - unsigned sum0 = 0; | |
| 253 | - unsigned sum1 = 0; | |
| 254 | - unsigned sum2 = 0; | |
| 255 | - while(N >= 16){ | |
| 256 | - sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); | |
| 257 | - sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); | |
| 258 | - sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); | |
| 259 | - sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); | |
| 260 | - z += 16; | |
| 261 | - N -= 16; | |
| 262 | - } | |
| 263 | - while(N >= 4){ | |
| 264 | - sum0 += z[0]; | |
| 265 | - sum1 += z[1]; | |
| 266 | - sum2 += z[2]; | |
| 267 | - sum += z[3]; | |
| 268 | - z += 4; | |
| 269 | - N -= 4; | |
| 270 | - } | |
| 271 | - sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); | |
| 272 | -#endif | |
| 273 | - } | |
| 274 | - switch(N&3){ | |
| 275 | - case 3: sum += (z[2] << 8); | |
| 276 | - case 2: sum += (z[1] << 16); | |
| 277 | - case 1: sum += (z[0] << 24); | |
| 278 | - default: ; | |
| 231 | + if( N>0 ){ | |
| 232 | + const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3]; | |
| 233 | + assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */ | |
| 234 | + if( 0==*(char*)&byteOrderTest ){ | |
| 235 | + /* This is a big-endian machine */ | |
| 236 | + while( z<zEnd ){ | |
| 237 | + sum += *(unsigned*)z; | |
| 238 | + z += 4; | |
| 239 | + } | |
| 240 | + }else{ | |
| 241 | + /* A little-endian machine */ | |
| 242 | + #if GCC_VERSION>=4003000 | |
| 243 | + while( z<zEnd ){ | |
| 244 | + sum += __builtin_bswap32(*(unsigned*)z); | |
| 245 | + z += 4; | |
| 246 | + } | |
| 247 | + #elif defined(_MSC_VER) && _MSC_VER>=1300 | |
| 248 | + while( z<zEnd ){ | |
| 249 | + sum += _byteswap_ulong(*(unsigned*)z); | |
| 250 | + z += 4; | |
| 251 | + } | |
| 252 | + #else | |
| 253 | + unsigned sum0 = 0; | |
| 254 | + unsigned sum1 = 0; | |
| 255 | + unsigned sum2 = 0; | |
| 256 | + while(N >= 16){ | |
| 257 | + sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); | |
| 258 | + sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); | |
| 259 | + sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); | |
| 260 | + sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); | |
| 261 | + z += 16; | |
| 262 | + N -= 16; | |
| 263 | + } | |
| 264 | + while(N >= 4){ | |
| 265 | + sum0 += z[0]; | |
| 266 | + sum1 += z[1]; | |
| 267 | + sum2 += z[2]; | |
| 268 | + sum += z[3]; | |
| 269 | + z += 4; | |
| 270 | + N -= 4; | |
| 271 | + } | |
| 272 | + sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); | |
| 273 | + #endif | |
| 274 | + } | |
| 275 | + switch(N&3){ | |
| 276 | + case 3: sum += (z[2] << 8); | |
| 277 | + case 2: sum += (z[1] << 16); | |
| 278 | + case 1: sum += (z[0] << 24); | |
| 279 | + default: ; | |
| 280 | + } | |
| 279 | 281 | } |
| 280 | 282 | return sum; |
| 281 | 283 | } |
| 282 | 284 | |
| 283 | 285 | /* |
| 284 | 286 |
| --- src/delta.c | |
| +++ src/delta.c | |
| @@ -225,59 +225,61 @@ | |
| 225 | ** of four bytes. |
| 226 | */ |
| 227 | static unsigned int checksum(const char *zIn, size_t N){ |
| 228 | static const int byteOrderTest = 1; |
| 229 | const unsigned char *z = (const unsigned char *)zIn; |
| 230 | const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3]; |
| 231 | unsigned sum = 0; |
| 232 | assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */ |
| 233 | if( 0==*(char*)&byteOrderTest ){ |
| 234 | /* This is a big-endian machine */ |
| 235 | while( z<zEnd ){ |
| 236 | sum += *(unsigned*)z; |
| 237 | z += 4; |
| 238 | } |
| 239 | }else{ |
| 240 | /* A little-endian machine */ |
| 241 | #if GCC_VERSION>=4003000 |
| 242 | while( z<zEnd ){ |
| 243 | sum += __builtin_bswap32(*(unsigned*)z); |
| 244 | z += 4; |
| 245 | } |
| 246 | #elif defined(_MSC_VER) && _MSC_VER>=1300 |
| 247 | while( z<zEnd ){ |
| 248 | sum += _byteswap_ulong(*(unsigned*)z); |
| 249 | z += 4; |
| 250 | } |
| 251 | #else |
| 252 | unsigned sum0 = 0; |
| 253 | unsigned sum1 = 0; |
| 254 | unsigned sum2 = 0; |
| 255 | while(N >= 16){ |
| 256 | sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); |
| 257 | sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); |
| 258 | sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); |
| 259 | sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); |
| 260 | z += 16; |
| 261 | N -= 16; |
| 262 | } |
| 263 | while(N >= 4){ |
| 264 | sum0 += z[0]; |
| 265 | sum1 += z[1]; |
| 266 | sum2 += z[2]; |
| 267 | sum += z[3]; |
| 268 | z += 4; |
| 269 | N -= 4; |
| 270 | } |
| 271 | sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); |
| 272 | #endif |
| 273 | } |
| 274 | switch(N&3){ |
| 275 | case 3: sum += (z[2] << 8); |
| 276 | case 2: sum += (z[1] << 16); |
| 277 | case 1: sum += (z[0] << 24); |
| 278 | default: ; |
| 279 | } |
| 280 | return sum; |
| 281 | } |
| 282 | |
| 283 | /* |
| 284 |
| --- src/delta.c | |
| +++ src/delta.c | |
| @@ -225,59 +225,61 @@ | |
| 225 | ** of four bytes. |
| 226 | */ |
| 227 | static unsigned int checksum(const char *zIn, size_t N){ |
| 228 | static const int byteOrderTest = 1; |
| 229 | const unsigned char *z = (const unsigned char *)zIn; |
| 230 | unsigned sum = 0; |
| 231 | if( N>0 ){ |
| 232 | const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3]; |
| 233 | assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */ |
| 234 | if( 0==*(char*)&byteOrderTest ){ |
| 235 | /* This is a big-endian machine */ |
| 236 | while( z<zEnd ){ |
| 237 | sum += *(unsigned*)z; |
| 238 | z += 4; |
| 239 | } |
| 240 | }else{ |
| 241 | /* A little-endian machine */ |
| 242 | #if GCC_VERSION>=4003000 |
| 243 | while( z<zEnd ){ |
| 244 | sum += __builtin_bswap32(*(unsigned*)z); |
| 245 | z += 4; |
| 246 | } |
| 247 | #elif defined(_MSC_VER) && _MSC_VER>=1300 |
| 248 | while( z<zEnd ){ |
| 249 | sum += _byteswap_ulong(*(unsigned*)z); |
| 250 | z += 4; |
| 251 | } |
| 252 | #else |
| 253 | unsigned sum0 = 0; |
| 254 | unsigned sum1 = 0; |
| 255 | unsigned sum2 = 0; |
| 256 | while(N >= 16){ |
| 257 | sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); |
| 258 | sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); |
| 259 | sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); |
| 260 | sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); |
| 261 | z += 16; |
| 262 | N -= 16; |
| 263 | } |
| 264 | while(N >= 4){ |
| 265 | sum0 += z[0]; |
| 266 | sum1 += z[1]; |
| 267 | sum2 += z[2]; |
| 268 | sum += z[3]; |
| 269 | z += 4; |
| 270 | N -= 4; |
| 271 | } |
| 272 | sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); |
| 273 | #endif |
| 274 | } |
| 275 | switch(N&3){ |
| 276 | case 3: sum += (z[2] << 8); |
| 277 | case 2: sum += (z[1] << 16); |
| 278 | case 1: sum += (z[0] << 24); |
| 279 | default: ; |
| 280 | } |
| 281 | } |
| 282 | return sum; |
| 283 | } |
| 284 | |
| 285 | /* |
| 286 |
+49
-39
| --- src/descendants.c | ||
| +++ src/descendants.c | ||
| @@ -202,29 +202,28 @@ | ||
| 202 | 202 | rLimitMtime = db_double(0.0, |
| 203 | 203 | "SELECT mtime FROM event WHERE objid=%d", |
| 204 | 204 | ridBackTo); |
| 205 | 205 | } |
| 206 | 206 | db_multi_exec( |
| 207 | - "WITH RECURSIVE " | |
| 208 | - " parent(pid,cid,isCP) AS (" | |
| 209 | - " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink" | |
| 210 | - " UNION ALL" | |
| 211 | - " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude" | |
| 212 | - " )," | |
| 213 | - " ancestor(rid, mtime, isCP) AS (" | |
| 214 | - " SELECT %d, mtime, 0 FROM event WHERE objid=%d " | |
| 215 | - " UNION " | |
| 216 | - " SELECT parent.pid, event.mtime, parent.isCP" | |
| 217 | - " FROM ancestor, parent, event" | |
| 218 | - " WHERE parent.cid=ancestor.rid" | |
| 219 | - " AND event.objid=parent.pid" | |
| 220 | - " AND NOT ancestor.isCP" | |
| 221 | - " AND (event.mtime>=%.17g OR parent.pid=%d)" | |
| 222 | - " ORDER BY mtime DESC LIMIT %d" | |
| 223 | - " )" | |
| 224 | - "INSERT OR IGNORE INTO ok" | |
| 225 | - " SELECT rid FROM ancestor;", | |
| 207 | + "WITH RECURSIVE\n" | |
| 208 | + " parent(pid,cid,isCP) AS (\n" | |
| 209 | + " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n" | |
| 210 | + " UNION ALL\n" | |
| 211 | + " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n" | |
| 212 | + " ),\n" | |
| 213 | + " ancestor(rid, mtime, isCP) AS (\n" | |
| 214 | + " SELECT %d, mtime, 0 FROM event WHERE objid=%d\n" | |
| 215 | + " UNION\n" | |
| 216 | + " SELECT parent.pid, event.mtime, parent.isCP\n" | |
| 217 | + " FROM ancestor, parent, event\n" | |
| 218 | + " WHERE parent.cid=ancestor.rid\n" | |
| 219 | + " AND event.objid=parent.pid\n" | |
| 220 | + " AND NOT ancestor.isCP\n" | |
| 221 | + " AND (event.mtime>=%.17g OR parent.pid=%d)\n" | |
| 222 | + " ORDER BY mtime DESC LIMIT %d\n" | |
| 223 | + " )\n" | |
| 224 | + "INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;", | |
| 226 | 225 | rid, rid, rLimitMtime, ridBackTo, N |
| 227 | 226 | ); |
| 228 | 227 | if( ridBackTo && db_changes()>1 ){ |
| 229 | 228 | db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo); |
| 230 | 229 | } |
| @@ -322,18 +321,18 @@ | ||
| 322 | 321 | N = -1; |
| 323 | 322 | }else if( N<0 ){ |
| 324 | 323 | N = -N; |
| 325 | 324 | } |
| 326 | 325 | db_multi_exec( |
| 327 | - "WITH RECURSIVE" | |
| 328 | - " dx(rid,mtime) AS (" | |
| 329 | - " SELECT %d, 0" | |
| 330 | - " UNION" | |
| 331 | - " SELECT plink.cid, plink.mtime FROM dx, plink" | |
| 332 | - " WHERE plink.pid=dx.rid" | |
| 333 | - " ORDER BY 2" | |
| 334 | - " )" | |
| 326 | + "WITH RECURSIVE\n" | |
| 327 | + " dx(rid,mtime) AS (\n" | |
| 328 | + " SELECT %d, 0\n" | |
| 329 | + " UNION\n" | |
| 330 | + " SELECT plink.cid, plink.mtime FROM dx, plink\n" | |
| 331 | + " WHERE plink.pid=dx.rid\n" | |
| 332 | + " ORDER BY 2\n" | |
| 333 | + " )\n" | |
| 335 | 334 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 336 | 335 | rid, N |
| 337 | 336 | ); |
| 338 | 337 | } |
| 339 | 338 | |
| @@ -639,44 +638,56 @@ | ||
| 639 | 638 | /* Flag parameters to compute_uses_file() */ |
| 640 | 639 | #define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */ |
| 641 | 640 | |
| 642 | 641 | #endif |
| 643 | 642 | |
| 643 | +/* | |
| 644 | +** Append a new VALUES term. | |
| 645 | +*/ | |
| 646 | +static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){ | |
| 647 | + if( *pnCnt==0 ){ | |
| 648 | + blob_append_sql(pSql, "(%d)", rid); | |
| 649 | + *pnCnt = 4; | |
| 650 | + }else if( (*pnCnt)%10==9 ){ | |
| 651 | + blob_append_sql(pSql, ",\n (%d)", rid); | |
| 652 | + }else{ | |
| 653 | + blob_append_sql(pSql, ",(%d)", rid); | |
| 654 | + } | |
| 655 | + ++*pnCnt; | |
| 656 | +} | |
| 657 | + | |
| 644 | 658 | |
| 645 | 659 | /* |
| 646 | 660 | ** Add to table zTab the record ID (rid) of every check-in that contains |
| 647 | 661 | ** the file fid. |
| 648 | 662 | */ |
| 649 | 663 | void compute_uses_file(const char *zTab, int fid, int usesFlags){ |
| 650 | 664 | Bag seen; |
| 651 | 665 | Bag pending; |
| 652 | - Stmt ins; | |
| 666 | + Blob ins = BLOB_INITIALIZER; | |
| 667 | + int nIns = 0; | |
| 653 | 668 | Stmt q; |
| 654 | 669 | int rid; |
| 655 | 670 | |
| 656 | 671 | bag_init(&seen); |
| 657 | 672 | bag_init(&pending); |
| 658 | - db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab); | |
| 673 | + blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab); | |
| 659 | 674 | db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid); |
| 660 | 675 | while( db_step(&q)==SQLITE_ROW ){ |
| 661 | 676 | int mid = db_column_int(&q, 0); |
| 662 | 677 | bag_insert(&pending, mid); |
| 663 | 678 | bag_insert(&seen, mid); |
| 664 | - db_bind_int(&ins, ":rid", mid); | |
| 665 | - db_step(&ins); | |
| 666 | - db_reset(&ins); | |
| 679 | + uses_file_append_term(&ins, &nIns, mid); | |
| 667 | 680 | } |
| 668 | 681 | db_finalize(&q); |
| 669 | 682 | |
| 670 | 683 | db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid); |
| 671 | 684 | while( db_step(&q)==SQLITE_ROW ){ |
| 672 | 685 | int mid = db_column_int(&q, 0); |
| 673 | 686 | bag_insert(&seen, mid); |
| 674 | 687 | if( usesFlags & USESFILE_DELETE ){ |
| 675 | - db_bind_int(&ins, ":rid", mid); | |
| 676 | - db_step(&ins); | |
| 677 | - db_reset(&ins); | |
| 688 | + uses_file_append_term(&ins, &nIns, mid); | |
| 678 | 689 | } |
| 679 | 690 | } |
| 680 | 691 | db_finalize(&q); |
| 681 | 692 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim"); |
| 682 | 693 | |
| @@ -686,16 +697,15 @@ | ||
| 686 | 697 | while( db_step(&q)==SQLITE_ROW ){ |
| 687 | 698 | int mid = db_column_int(&q, 0); |
| 688 | 699 | if( bag_find(&seen, mid) ) continue; |
| 689 | 700 | bag_insert(&seen, mid); |
| 690 | 701 | bag_insert(&pending, mid); |
| 691 | - db_bind_int(&ins, ":rid", mid); | |
| 692 | - db_step(&ins); | |
| 693 | - db_reset(&ins); | |
| 702 | + uses_file_append_term(&ins, &nIns, mid); | |
| 694 | 703 | } |
| 695 | 704 | db_reset(&q); |
| 696 | 705 | } |
| 697 | 706 | db_finalize(&q); |
| 698 | - db_finalize(&ins); | |
| 707 | + db_exec_sql(blob_str(&ins)); | |
| 708 | + blob_reset(&ins); | |
| 699 | 709 | bag_clear(&seen); |
| 700 | 710 | bag_clear(&pending); |
| 701 | 711 | } |
| 702 | 712 |
| --- src/descendants.c | |
| +++ src/descendants.c | |
| @@ -202,29 +202,28 @@ | |
| 202 | rLimitMtime = db_double(0.0, |
| 203 | "SELECT mtime FROM event WHERE objid=%d", |
| 204 | ridBackTo); |
| 205 | } |
| 206 | db_multi_exec( |
| 207 | "WITH RECURSIVE " |
| 208 | " parent(pid,cid,isCP) AS (" |
| 209 | " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink" |
| 210 | " UNION ALL" |
| 211 | " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude" |
| 212 | " )," |
| 213 | " ancestor(rid, mtime, isCP) AS (" |
| 214 | " SELECT %d, mtime, 0 FROM event WHERE objid=%d " |
| 215 | " UNION " |
| 216 | " SELECT parent.pid, event.mtime, parent.isCP" |
| 217 | " FROM ancestor, parent, event" |
| 218 | " WHERE parent.cid=ancestor.rid" |
| 219 | " AND event.objid=parent.pid" |
| 220 | " AND NOT ancestor.isCP" |
| 221 | " AND (event.mtime>=%.17g OR parent.pid=%d)" |
| 222 | " ORDER BY mtime DESC LIMIT %d" |
| 223 | " )" |
| 224 | "INSERT OR IGNORE INTO ok" |
| 225 | " SELECT rid FROM ancestor;", |
| 226 | rid, rid, rLimitMtime, ridBackTo, N |
| 227 | ); |
| 228 | if( ridBackTo && db_changes()>1 ){ |
| 229 | db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo); |
| 230 | } |
| @@ -322,18 +321,18 @@ | |
| 322 | N = -1; |
| 323 | }else if( N<0 ){ |
| 324 | N = -N; |
| 325 | } |
| 326 | db_multi_exec( |
| 327 | "WITH RECURSIVE" |
| 328 | " dx(rid,mtime) AS (" |
| 329 | " SELECT %d, 0" |
| 330 | " UNION" |
| 331 | " SELECT plink.cid, plink.mtime FROM dx, plink" |
| 332 | " WHERE plink.pid=dx.rid" |
| 333 | " ORDER BY 2" |
| 334 | " )" |
| 335 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 336 | rid, N |
| 337 | ); |
| 338 | } |
| 339 | |
| @@ -639,44 +638,56 @@ | |
| 639 | /* Flag parameters to compute_uses_file() */ |
| 640 | #define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */ |
| 641 | |
| 642 | #endif |
| 643 | |
| 644 | |
| 645 | /* |
| 646 | ** Add to table zTab the record ID (rid) of every check-in that contains |
| 647 | ** the file fid. |
| 648 | */ |
| 649 | void compute_uses_file(const char *zTab, int fid, int usesFlags){ |
| 650 | Bag seen; |
| 651 | Bag pending; |
| 652 | Stmt ins; |
| 653 | Stmt q; |
| 654 | int rid; |
| 655 | |
| 656 | bag_init(&seen); |
| 657 | bag_init(&pending); |
| 658 | db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab); |
| 659 | db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid); |
| 660 | while( db_step(&q)==SQLITE_ROW ){ |
| 661 | int mid = db_column_int(&q, 0); |
| 662 | bag_insert(&pending, mid); |
| 663 | bag_insert(&seen, mid); |
| 664 | db_bind_int(&ins, ":rid", mid); |
| 665 | db_step(&ins); |
| 666 | db_reset(&ins); |
| 667 | } |
| 668 | db_finalize(&q); |
| 669 | |
| 670 | db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid); |
| 671 | while( db_step(&q)==SQLITE_ROW ){ |
| 672 | int mid = db_column_int(&q, 0); |
| 673 | bag_insert(&seen, mid); |
| 674 | if( usesFlags & USESFILE_DELETE ){ |
| 675 | db_bind_int(&ins, ":rid", mid); |
| 676 | db_step(&ins); |
| 677 | db_reset(&ins); |
| 678 | } |
| 679 | } |
| 680 | db_finalize(&q); |
| 681 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim"); |
| 682 | |
| @@ -686,16 +697,15 @@ | |
| 686 | while( db_step(&q)==SQLITE_ROW ){ |
| 687 | int mid = db_column_int(&q, 0); |
| 688 | if( bag_find(&seen, mid) ) continue; |
| 689 | bag_insert(&seen, mid); |
| 690 | bag_insert(&pending, mid); |
| 691 | db_bind_int(&ins, ":rid", mid); |
| 692 | db_step(&ins); |
| 693 | db_reset(&ins); |
| 694 | } |
| 695 | db_reset(&q); |
| 696 | } |
| 697 | db_finalize(&q); |
| 698 | db_finalize(&ins); |
| 699 | bag_clear(&seen); |
| 700 | bag_clear(&pending); |
| 701 | } |
| 702 |
| --- src/descendants.c | |
| +++ src/descendants.c | |
| @@ -202,29 +202,28 @@ | |
| 202 | rLimitMtime = db_double(0.0, |
| 203 | "SELECT mtime FROM event WHERE objid=%d", |
| 204 | ridBackTo); |
| 205 | } |
| 206 | db_multi_exec( |
| 207 | "WITH RECURSIVE\n" |
| 208 | " parent(pid,cid,isCP) AS (\n" |
| 209 | " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n" |
| 210 | " UNION ALL\n" |
| 211 | " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n" |
| 212 | " ),\n" |
| 213 | " ancestor(rid, mtime, isCP) AS (\n" |
| 214 | " SELECT %d, mtime, 0 FROM event WHERE objid=%d\n" |
| 215 | " UNION\n" |
| 216 | " SELECT parent.pid, event.mtime, parent.isCP\n" |
| 217 | " FROM ancestor, parent, event\n" |
| 218 | " WHERE parent.cid=ancestor.rid\n" |
| 219 | " AND event.objid=parent.pid\n" |
| 220 | " AND NOT ancestor.isCP\n" |
| 221 | " AND (event.mtime>=%.17g OR parent.pid=%d)\n" |
| 222 | " ORDER BY mtime DESC LIMIT %d\n" |
| 223 | " )\n" |
| 224 | "INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;", |
| 225 | rid, rid, rLimitMtime, ridBackTo, N |
| 226 | ); |
| 227 | if( ridBackTo && db_changes()>1 ){ |
| 228 | db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo); |
| 229 | } |
| @@ -322,18 +321,18 @@ | |
| 321 | N = -1; |
| 322 | }else if( N<0 ){ |
| 323 | N = -N; |
| 324 | } |
| 325 | db_multi_exec( |
| 326 | "WITH RECURSIVE\n" |
| 327 | " dx(rid,mtime) AS (\n" |
| 328 | " SELECT %d, 0\n" |
| 329 | " UNION\n" |
| 330 | " SELECT plink.cid, plink.mtime FROM dx, plink\n" |
| 331 | " WHERE plink.pid=dx.rid\n" |
| 332 | " ORDER BY 2\n" |
| 333 | " )\n" |
| 334 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 335 | rid, N |
| 336 | ); |
| 337 | } |
| 338 | |
| @@ -639,44 +638,56 @@ | |
| 638 | /* Flag parameters to compute_uses_file() */ |
| 639 | #define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */ |
| 640 | |
| 641 | #endif |
| 642 | |
| 643 | /* |
| 644 | ** Append a new VALUES term. |
| 645 | */ |
| 646 | static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){ |
| 647 | if( *pnCnt==0 ){ |
| 648 | blob_append_sql(pSql, "(%d)", rid); |
| 649 | *pnCnt = 4; |
| 650 | }else if( (*pnCnt)%10==9 ){ |
| 651 | blob_append_sql(pSql, ",\n (%d)", rid); |
| 652 | }else{ |
| 653 | blob_append_sql(pSql, ",(%d)", rid); |
| 654 | } |
| 655 | ++*pnCnt; |
| 656 | } |
| 657 | |
| 658 | |
| 659 | /* |
| 660 | ** Add to table zTab the record ID (rid) of every check-in that contains |
| 661 | ** the file fid. |
| 662 | */ |
| 663 | void compute_uses_file(const char *zTab, int fid, int usesFlags){ |
| 664 | Bag seen; |
| 665 | Bag pending; |
| 666 | Blob ins = BLOB_INITIALIZER; |
| 667 | int nIns = 0; |
| 668 | Stmt q; |
| 669 | int rid; |
| 670 | |
| 671 | bag_init(&seen); |
| 672 | bag_init(&pending); |
| 673 | blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab); |
| 674 | db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid); |
| 675 | while( db_step(&q)==SQLITE_ROW ){ |
| 676 | int mid = db_column_int(&q, 0); |
| 677 | bag_insert(&pending, mid); |
| 678 | bag_insert(&seen, mid); |
| 679 | uses_file_append_term(&ins, &nIns, mid); |
| 680 | } |
| 681 | db_finalize(&q); |
| 682 | |
| 683 | db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid); |
| 684 | while( db_step(&q)==SQLITE_ROW ){ |
| 685 | int mid = db_column_int(&q, 0); |
| 686 | bag_insert(&seen, mid); |
| 687 | if( usesFlags & USESFILE_DELETE ){ |
| 688 | uses_file_append_term(&ins, &nIns, mid); |
| 689 | } |
| 690 | } |
| 691 | db_finalize(&q); |
| 692 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim"); |
| 693 | |
| @@ -686,16 +697,15 @@ | |
| 697 | while( db_step(&q)==SQLITE_ROW ){ |
| 698 | int mid = db_column_int(&q, 0); |
| 699 | if( bag_find(&seen, mid) ) continue; |
| 700 | bag_insert(&seen, mid); |
| 701 | bag_insert(&pending, mid); |
| 702 | uses_file_append_term(&ins, &nIns, mid); |
| 703 | } |
| 704 | db_reset(&q); |
| 705 | } |
| 706 | db_finalize(&q); |
| 707 | db_exec_sql(blob_str(&ins)); |
| 708 | blob_reset(&ins); |
| 709 | bag_clear(&seen); |
| 710 | bag_clear(&pending); |
| 711 | } |
| 712 |
+145
-6
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -50,10 +50,11 @@ | ||
| 50 | 50 | #define DIFF_RAW 0x00040000 /* Raw triples - for debugging */ |
| 51 | 51 | #define DIFF_TCL 0x00080000 /* For the --tk option */ |
| 52 | 52 | #define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */ |
| 53 | 53 | #define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */ |
| 54 | 54 | #define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */ |
| 55 | +#define DIFF_BY_TOKEN 0x01000000 /* Split on tokens, not lines */ | |
| 55 | 56 | |
| 56 | 57 | /* |
| 57 | 58 | ** Per file information that may influence output. |
| 58 | 59 | */ |
| 59 | 60 | #define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */ |
| @@ -319,10 +320,113 @@ | ||
| 319 | 320 | |
| 320 | 321 | /* Return results */ |
| 321 | 322 | *pnLine = nLine; |
| 322 | 323 | return a; |
| 323 | 324 | } |
| 325 | + | |
| 326 | +/* | |
| 327 | +** Character classes for the purpose of tokenization. | |
| 328 | +** | |
| 329 | +** 1 - alphanumeric | |
| 330 | +** 2 - whitespace | |
| 331 | +** 3 - punctuation | |
| 332 | +*/ | |
| 333 | +static char aTCharClass[256] = { | |
| 334 | + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |
| 335 | + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |
| 336 | + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, | |
| 337 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, | |
| 338 | + | |
| 339 | + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 340 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, | |
| 341 | + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 342 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, | |
| 343 | + | |
| 344 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 345 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 346 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 347 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 348 | + | |
| 349 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 350 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 351 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 352 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 | |
| 353 | +}; | |
| 354 | + | |
| 355 | +/* | |
| 356 | +** Count the number of tokens in the given string. | |
| 357 | +*/ | |
| 358 | +static int count_tokens(const unsigned char *p, int n){ | |
| 359 | + int nToken = 0; | |
| 360 | + int iPrev = 0; | |
| 361 | + int i; | |
| 362 | + for(i=0; i<n; i++){ | |
| 363 | + char x = aTCharClass[p[i]]; | |
| 364 | + if( x!=iPrev ){ | |
| 365 | + iPrev = x; | |
| 366 | + nToken++; | |
| 367 | + } | |
| 368 | + } | |
| 369 | + return nToken; | |
| 370 | +} | |
| 371 | + | |
| 372 | +/* | |
| 373 | +** Return an array of DLine objects containing a pointer to the | |
| 374 | +** start of each token and a hash of that token. The lower | |
| 375 | +** bits of the hash store the length of each token. | |
| 376 | +** | |
| 377 | +** This is like break_into_lines() except that it works with tokens | |
| 378 | +** instead of lines. A token is: | |
| 379 | +** | |
| 380 | +** * A contiguous sequence of alphanumeric characters. | |
| 381 | +** * A contiguous sequence of whitespace | |
| 382 | +** * A contiguous sequence of punctuation characters. | |
| 383 | +** | |
| 384 | +** Return 0 if the file is binary or contains a line that is | |
| 385 | +** too long. | |
| 386 | +*/ | |
| 387 | +static DLine *break_into_tokens( | |
| 388 | + const char *z, | |
| 389 | + int n, | |
| 390 | + int *pnToken, | |
| 391 | + u64 diffFlags | |
| 392 | +){ | |
| 393 | + int nToken, i, k; | |
| 394 | + u64 h, h2; | |
| 395 | + DLine *a; | |
| 396 | + unsigned char *p = (unsigned char*)z; | |
| 397 | + | |
| 398 | + nToken = count_tokens(p, n); | |
| 399 | + a = fossil_malloc( sizeof(a[0])*(nToken+1) ); | |
| 400 | + memset(a, 0, sizeof(a[0])*(nToken+1)); | |
| 401 | + if( n==0 ){ | |
| 402 | + *pnToken = 0; | |
| 403 | + return a; | |
| 404 | + } | |
| 405 | + i = 0; | |
| 406 | + while( n>0 ){ | |
| 407 | + char x = aTCharClass[*p]; | |
| 408 | + h = 0xcbf29ce484222325LL; | |
| 409 | + for(k=1; k<n && aTCharClass[p[k]]==x; k++){ | |
| 410 | + h ^= p[k]; | |
| 411 | + h *= 0x100000001b3LL; | |
| 412 | + } | |
| 413 | + a[i].z = (char*)p; | |
| 414 | + a[i].n = k; | |
| 415 | + a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k; | |
| 416 | + h2 = h % nToken; | |
| 417 | + a[i].iNext = a[h2].iHash; | |
| 418 | + a[h2].iHash = i+1; | |
| 419 | + p += k; n -= k; | |
| 420 | + i++; | |
| 421 | + }; | |
| 422 | + assert( i==nToken ); | |
| 423 | + | |
| 424 | + /* Return results */ | |
| 425 | + *pnToken = nToken; | |
| 426 | + return a; | |
| 427 | +} | |
| 324 | 428 | |
| 325 | 429 | /* |
| 326 | 430 | ** Return zero if two DLine elements are identical. |
| 327 | 431 | */ |
| 328 | 432 | static int compare_dline(const DLine *pA, const DLine *pB){ |
| @@ -2462,11 +2566,11 @@ | ||
| 2462 | 2566 | int span; /* combined width of the input sequences */ |
| 2463 | 2567 | int cutoff = 4; /* Max hash chain entries to follow */ |
| 2464 | 2568 | int nextCutoff = -1; /* Value of cutoff for next iteration */ |
| 2465 | 2569 | |
| 2466 | 2570 | span = (iE1 - iS1) + (iE2 - iS2); |
| 2467 | - bestScore = -10000; | |
| 2571 | + bestScore = -9223300000*(sqlite3_int64)1000000000; | |
| 2468 | 2572 | score = 0; |
| 2469 | 2573 | iSXb = iSXp = iS1; |
| 2470 | 2574 | iEXb = iEXp = iS1; |
| 2471 | 2575 | iSYb = iSYp = iS2; |
| 2472 | 2576 | iEYb = iEYp = iS2; |
| @@ -2997,14 +3101,21 @@ | ||
| 2997 | 3101 | if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ |
| 2998 | 3102 | c.xDiffer = compare_dline_ignore_allws; |
| 2999 | 3103 | }else{ |
| 3000 | 3104 | c.xDiffer = compare_dline; |
| 3001 | 3105 | } |
| 3002 | - c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), | |
| 3003 | - &c.nFrom, pCfg->diffFlags); | |
| 3004 | - c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), | |
| 3005 | - &c.nTo, pCfg->diffFlags); | |
| 3106 | + if( pCfg->diffFlags & DIFF_BY_TOKEN ){ | |
| 3107 | + c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob), | |
| 3108 | + &c.nFrom, pCfg->diffFlags); | |
| 3109 | + c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob), | |
| 3110 | + &c.nTo, pCfg->diffFlags); | |
| 3111 | + }else{ | |
| 3112 | + c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), | |
| 3113 | + &c.nFrom, pCfg->diffFlags); | |
| 3114 | + c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), | |
| 3115 | + &c.nTo, pCfg->diffFlags); | |
| 3116 | + } | |
| 3006 | 3117 | if( c.aFrom==0 || c.aTo==0 ){ |
| 3007 | 3118 | fossil_free(c.aFrom); |
| 3008 | 3119 | fossil_free(c.aTo); |
| 3009 | 3120 | if( pOut ){ |
| 3010 | 3121 | diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags); |
| @@ -3035,10 +3146,26 @@ | ||
| 3035 | 3146 | } |
| 3036 | 3147 | } |
| 3037 | 3148 | if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){ |
| 3038 | 3149 | diff_optimize(&c); |
| 3039 | 3150 | } |
| 3151 | + if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){ | |
| 3152 | + /* Convert token counts into byte counts. */ | |
| 3153 | + int i; | |
| 3154 | + int iA = 0; | |
| 3155 | + int iB = 0; | |
| 3156 | + for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ | |
| 3157 | + int k, sum; | |
| 3158 | + for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n; | |
| 3159 | + iB += c.aEdit[i]; | |
| 3160 | + c.aEdit[i] = sum; | |
| 3161 | + for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n; | |
| 3162 | + c.aEdit[i+1] = sum; | |
| 3163 | + for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n; | |
| 3164 | + c.aEdit[i+2] = sum; | |
| 3165 | + } | |
| 3166 | + } | |
| 3040 | 3167 | |
| 3041 | 3168 | if( pOut ){ |
| 3042 | 3169 | if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
| 3043 | 3170 | int nDel = 0, nIns = 0, i; |
| 3044 | 3171 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| @@ -3049,11 +3176,11 @@ | ||
| 3049 | 3176 | g.diffCnt[2] += nDel; |
| 3050 | 3177 | if( nIns+nDel ){ |
| 3051 | 3178 | g.diffCnt[0]++; |
| 3052 | 3179 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3053 | 3180 | } |
| 3054 | - }else if( pCfg->diffFlags & DIFF_RAW ){ | |
| 3181 | + }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){ | |
| 3055 | 3182 | const int *R = c.aEdit; |
| 3056 | 3183 | unsigned int r; |
| 3057 | 3184 | for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){ |
| 3058 | 3185 | blob_appendf(pOut, " copy %6d delete %6d insert %6d\n", |
| 3059 | 3186 | R[r], R[r+1], R[r+2]); |
| @@ -3100,20 +3227,29 @@ | ||
| 3100 | 3227 | ** Initialize the DiffConfig object using command-line options. |
| 3101 | 3228 | ** |
| 3102 | 3229 | ** Process diff-related command-line options and return an appropriate |
| 3103 | 3230 | ** "diffFlags" integer. |
| 3104 | 3231 | ** |
| 3232 | +** -b|--browser Show the diff output in a web-browser | |
| 3105 | 3233 | ** --brief Show filenames only DIFF_BRIEF |
| 3234 | +** --by Shorthand for "--browser -y" | |
| 3106 | 3235 | ** -c|--context N N lines of context. nContext |
| 3236 | +** --dark Use dark mode for Tcl/Tk and HTML output | |
| 3107 | 3237 | ** --html Format for HTML DIFF_HTML |
| 3238 | +** -i|--internal Use built-in diff, not an external tool | |
| 3108 | 3239 | ** --invert Invert the diff DIFF_INVERT |
| 3240 | +** --json Output formatted as JSON | |
| 3109 | 3241 | ** -n|--linenum Show line numbers DIFF_LINENO |
| 3242 | +** -N|--new-file Alias for --verbose | |
| 3110 | 3243 | ** --noopt Disable optimization DIFF_NOOPT |
| 3111 | 3244 | ** --numstat Show change counts DIFF_NUMSTAT |
| 3112 | 3245 | ** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR |
| 3246 | +** --tcl Tcl-formatted output used internally by --tk | |
| 3113 | 3247 | ** --unified Unified diff. ~DIFF_SIDEBYSIDE |
| 3248 | +** -v|--verbose Show complete text of added or deleted files | |
| 3114 | 3249 | ** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS |
| 3250 | +** --webpage Format output as a stand-alone HTML webpage | |
| 3115 | 3251 | ** -W|--width N N character lines. wColumn |
| 3116 | 3252 | ** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE |
| 3117 | 3253 | ** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS |
| 3118 | 3254 | */ |
| 3119 | 3255 | void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){ |
| @@ -3157,10 +3293,13 @@ | ||
| 3157 | 3293 | |
| 3158 | 3294 | /* Undocumented and unsupported flags used for development |
| 3159 | 3295 | ** debugging and analysis: */ |
| 3160 | 3296 | if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG; |
| 3161 | 3297 | if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW; |
| 3298 | + if( find_option("bytoken",0,0)!=0 ){ | |
| 3299 | + diffFlags = DIFF_RAW|DIFF_BY_TOKEN; | |
| 3300 | + } | |
| 3162 | 3301 | } |
| 3163 | 3302 | if( (z = find_option("context","c",1))!=0 ){ |
| 3164 | 3303 | char *zEnd; |
| 3165 | 3304 | f = (int)strtol(z, &zEnd, 10); |
| 3166 | 3305 | if( zEnd[0]==0 && errno!=ERANGE ){ |
| 3167 | 3306 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -50,10 +50,11 @@ | |
| 50 | #define DIFF_RAW 0x00040000 /* Raw triples - for debugging */ |
| 51 | #define DIFF_TCL 0x00080000 /* For the --tk option */ |
| 52 | #define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */ |
| 53 | #define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */ |
| 54 | #define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */ |
| 55 | |
| 56 | /* |
| 57 | ** Per file information that may influence output. |
| 58 | */ |
| 59 | #define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */ |
| @@ -319,10 +320,113 @@ | |
| 319 | |
| 320 | /* Return results */ |
| 321 | *pnLine = nLine; |
| 322 | return a; |
| 323 | } |
| 324 | |
| 325 | /* |
| 326 | ** Return zero if two DLine elements are identical. |
| 327 | */ |
| 328 | static int compare_dline(const DLine *pA, const DLine *pB){ |
| @@ -2462,11 +2566,11 @@ | |
| 2462 | int span; /* combined width of the input sequences */ |
| 2463 | int cutoff = 4; /* Max hash chain entries to follow */ |
| 2464 | int nextCutoff = -1; /* Value of cutoff for next iteration */ |
| 2465 | |
| 2466 | span = (iE1 - iS1) + (iE2 - iS2); |
| 2467 | bestScore = -10000; |
| 2468 | score = 0; |
| 2469 | iSXb = iSXp = iS1; |
| 2470 | iEXb = iEXp = iS1; |
| 2471 | iSYb = iSYp = iS2; |
| 2472 | iEYb = iEYp = iS2; |
| @@ -2997,14 +3101,21 @@ | |
| 2997 | if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ |
| 2998 | c.xDiffer = compare_dline_ignore_allws; |
| 2999 | }else{ |
| 3000 | c.xDiffer = compare_dline; |
| 3001 | } |
| 3002 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 3003 | &c.nFrom, pCfg->diffFlags); |
| 3004 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| 3005 | &c.nTo, pCfg->diffFlags); |
| 3006 | if( c.aFrom==0 || c.aTo==0 ){ |
| 3007 | fossil_free(c.aFrom); |
| 3008 | fossil_free(c.aTo); |
| 3009 | if( pOut ){ |
| 3010 | diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags); |
| @@ -3035,10 +3146,26 @@ | |
| 3035 | } |
| 3036 | } |
| 3037 | if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){ |
| 3038 | diff_optimize(&c); |
| 3039 | } |
| 3040 | |
| 3041 | if( pOut ){ |
| 3042 | if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
| 3043 | int nDel = 0, nIns = 0, i; |
| 3044 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| @@ -3049,11 +3176,11 @@ | |
| 3049 | g.diffCnt[2] += nDel; |
| 3050 | if( nIns+nDel ){ |
| 3051 | g.diffCnt[0]++; |
| 3052 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3053 | } |
| 3054 | }else if( pCfg->diffFlags & DIFF_RAW ){ |
| 3055 | const int *R = c.aEdit; |
| 3056 | unsigned int r; |
| 3057 | for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){ |
| 3058 | blob_appendf(pOut, " copy %6d delete %6d insert %6d\n", |
| 3059 | R[r], R[r+1], R[r+2]); |
| @@ -3100,20 +3227,29 @@ | |
| 3100 | ** Initialize the DiffConfig object using command-line options. |
| 3101 | ** |
| 3102 | ** Process diff-related command-line options and return an appropriate |
| 3103 | ** "diffFlags" integer. |
| 3104 | ** |
| 3105 | ** --brief Show filenames only DIFF_BRIEF |
| 3106 | ** -c|--context N N lines of context. nContext |
| 3107 | ** --html Format for HTML DIFF_HTML |
| 3108 | ** --invert Invert the diff DIFF_INVERT |
| 3109 | ** -n|--linenum Show line numbers DIFF_LINENO |
| 3110 | ** --noopt Disable optimization DIFF_NOOPT |
| 3111 | ** --numstat Show change counts DIFF_NUMSTAT |
| 3112 | ** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR |
| 3113 | ** --unified Unified diff. ~DIFF_SIDEBYSIDE |
| 3114 | ** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS |
| 3115 | ** -W|--width N N character lines. wColumn |
| 3116 | ** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE |
| 3117 | ** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS |
| 3118 | */ |
| 3119 | void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){ |
| @@ -3157,10 +3293,13 @@ | |
| 3157 | |
| 3158 | /* Undocumented and unsupported flags used for development |
| 3159 | ** debugging and analysis: */ |
| 3160 | if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG; |
| 3161 | if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW; |
| 3162 | } |
| 3163 | if( (z = find_option("context","c",1))!=0 ){ |
| 3164 | char *zEnd; |
| 3165 | f = (int)strtol(z, &zEnd, 10); |
| 3166 | if( zEnd[0]==0 && errno!=ERANGE ){ |
| 3167 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -50,10 +50,11 @@ | |
| 50 | #define DIFF_RAW 0x00040000 /* Raw triples - for debugging */ |
| 51 | #define DIFF_TCL 0x00080000 /* For the --tk option */ |
| 52 | #define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */ |
| 53 | #define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */ |
| 54 | #define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */ |
| 55 | #define DIFF_BY_TOKEN 0x01000000 /* Split on tokens, not lines */ |
| 56 | |
| 57 | /* |
| 58 | ** Per file information that may influence output. |
| 59 | */ |
| 60 | #define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */ |
| @@ -319,10 +320,113 @@ | |
| 320 | |
| 321 | /* Return results */ |
| 322 | *pnLine = nLine; |
| 323 | return a; |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | ** Character classes for the purpose of tokenization. |
| 328 | ** |
| 329 | ** 1 - alphanumeric |
| 330 | ** 2 - whitespace |
| 331 | ** 3 - punctuation |
| 332 | */ |
| 333 | static char aTCharClass[256] = { |
| 334 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
| 335 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
| 336 | 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
| 337 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, |
| 338 | |
| 339 | 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 340 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, |
| 341 | 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 342 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, |
| 343 | |
| 344 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 345 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 346 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 347 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 348 | |
| 349 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 350 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 351 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 352 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
| 353 | }; |
| 354 | |
| 355 | /* |
| 356 | ** Count the number of tokens in the given string. |
| 357 | */ |
| 358 | static int count_tokens(const unsigned char *p, int n){ |
| 359 | int nToken = 0; |
| 360 | int iPrev = 0; |
| 361 | int i; |
| 362 | for(i=0; i<n; i++){ |
| 363 | char x = aTCharClass[p[i]]; |
| 364 | if( x!=iPrev ){ |
| 365 | iPrev = x; |
| 366 | nToken++; |
| 367 | } |
| 368 | } |
| 369 | return nToken; |
| 370 | } |
| 371 | |
| 372 | /* |
| 373 | ** Return an array of DLine objects containing a pointer to the |
| 374 | ** start of each token and a hash of that token. The lower |
| 375 | ** bits of the hash store the length of each token. |
| 376 | ** |
| 377 | ** This is like break_into_lines() except that it works with tokens |
| 378 | ** instead of lines. A token is: |
| 379 | ** |
| 380 | ** * A contiguous sequence of alphanumeric characters. |
| 381 | ** * A contiguous sequence of whitespace |
| 382 | ** * A contiguous sequence of punctuation characters. |
| 383 | ** |
| 384 | ** Return 0 if the file is binary or contains a line that is |
| 385 | ** too long. |
| 386 | */ |
| 387 | static DLine *break_into_tokens( |
| 388 | const char *z, |
| 389 | int n, |
| 390 | int *pnToken, |
| 391 | u64 diffFlags |
| 392 | ){ |
| 393 | int nToken, i, k; |
| 394 | u64 h, h2; |
| 395 | DLine *a; |
| 396 | unsigned char *p = (unsigned char*)z; |
| 397 | |
| 398 | nToken = count_tokens(p, n); |
| 399 | a = fossil_malloc( sizeof(a[0])*(nToken+1) ); |
| 400 | memset(a, 0, sizeof(a[0])*(nToken+1)); |
| 401 | if( n==0 ){ |
| 402 | *pnToken = 0; |
| 403 | return a; |
| 404 | } |
| 405 | i = 0; |
| 406 | while( n>0 ){ |
| 407 | char x = aTCharClass[*p]; |
| 408 | h = 0xcbf29ce484222325LL; |
| 409 | for(k=1; k<n && aTCharClass[p[k]]==x; k++){ |
| 410 | h ^= p[k]; |
| 411 | h *= 0x100000001b3LL; |
| 412 | } |
| 413 | a[i].z = (char*)p; |
| 414 | a[i].n = k; |
| 415 | a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k; |
| 416 | h2 = h % nToken; |
| 417 | a[i].iNext = a[h2].iHash; |
| 418 | a[h2].iHash = i+1; |
| 419 | p += k; n -= k; |
| 420 | i++; |
| 421 | }; |
| 422 | assert( i==nToken ); |
| 423 | |
| 424 | /* Return results */ |
| 425 | *pnToken = nToken; |
| 426 | return a; |
| 427 | } |
| 428 | |
| 429 | /* |
| 430 | ** Return zero if two DLine elements are identical. |
| 431 | */ |
| 432 | static int compare_dline(const DLine *pA, const DLine *pB){ |
| @@ -2462,11 +2566,11 @@ | |
| 2566 | int span; /* combined width of the input sequences */ |
| 2567 | int cutoff = 4; /* Max hash chain entries to follow */ |
| 2568 | int nextCutoff = -1; /* Value of cutoff for next iteration */ |
| 2569 | |
| 2570 | span = (iE1 - iS1) + (iE2 - iS2); |
| 2571 | bestScore = -9223300000*(sqlite3_int64)1000000000; |
| 2572 | score = 0; |
| 2573 | iSXb = iSXp = iS1; |
| 2574 | iEXb = iEXp = iS1; |
| 2575 | iSYb = iSYp = iS2; |
| 2576 | iEYb = iEYp = iS2; |
| @@ -2997,14 +3101,21 @@ | |
| 3101 | if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ |
| 3102 | c.xDiffer = compare_dline_ignore_allws; |
| 3103 | }else{ |
| 3104 | c.xDiffer = compare_dline; |
| 3105 | } |
| 3106 | if( pCfg->diffFlags & DIFF_BY_TOKEN ){ |
| 3107 | c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob), |
| 3108 | &c.nFrom, pCfg->diffFlags); |
| 3109 | c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob), |
| 3110 | &c.nTo, pCfg->diffFlags); |
| 3111 | }else{ |
| 3112 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 3113 | &c.nFrom, pCfg->diffFlags); |
| 3114 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| 3115 | &c.nTo, pCfg->diffFlags); |
| 3116 | } |
| 3117 | if( c.aFrom==0 || c.aTo==0 ){ |
| 3118 | fossil_free(c.aFrom); |
| 3119 | fossil_free(c.aTo); |
| 3120 | if( pOut ){ |
| 3121 | diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags); |
| @@ -3035,10 +3146,26 @@ | |
| 3146 | } |
| 3147 | } |
| 3148 | if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){ |
| 3149 | diff_optimize(&c); |
| 3150 | } |
| 3151 | if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){ |
| 3152 | /* Convert token counts into byte counts. */ |
| 3153 | int i; |
| 3154 | int iA = 0; |
| 3155 | int iB = 0; |
| 3156 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| 3157 | int k, sum; |
| 3158 | for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n; |
| 3159 | iB += c.aEdit[i]; |
| 3160 | c.aEdit[i] = sum; |
| 3161 | for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n; |
| 3162 | c.aEdit[i+1] = sum; |
| 3163 | for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n; |
| 3164 | c.aEdit[i+2] = sum; |
| 3165 | } |
| 3166 | } |
| 3167 | |
| 3168 | if( pOut ){ |
| 3169 | if( pCfg->diffFlags & DIFF_NUMSTAT ){ |
| 3170 | int nDel = 0, nIns = 0, i; |
| 3171 | for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){ |
| @@ -3049,11 +3176,11 @@ | |
| 3176 | g.diffCnt[2] += nDel; |
| 3177 | if( nIns+nDel ){ |
| 3178 | g.diffCnt[0]++; |
| 3179 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 3180 | } |
| 3181 | }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){ |
| 3182 | const int *R = c.aEdit; |
| 3183 | unsigned int r; |
| 3184 | for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){ |
| 3185 | blob_appendf(pOut, " copy %6d delete %6d insert %6d\n", |
| 3186 | R[r], R[r+1], R[r+2]); |
| @@ -3100,20 +3227,29 @@ | |
| 3227 | ** Initialize the DiffConfig object using command-line options. |
| 3228 | ** |
| 3229 | ** Process diff-related command-line options and return an appropriate |
| 3230 | ** "diffFlags" integer. |
| 3231 | ** |
| 3232 | ** -b|--browser Show the diff output in a web-browser |
| 3233 | ** --brief Show filenames only DIFF_BRIEF |
| 3234 | ** --by Shorthand for "--browser -y" |
| 3235 | ** -c|--context N N lines of context. nContext |
| 3236 | ** --dark Use dark mode for Tcl/Tk and HTML output |
| 3237 | ** --html Format for HTML DIFF_HTML |
| 3238 | ** -i|--internal Use built-in diff, not an external tool |
| 3239 | ** --invert Invert the diff DIFF_INVERT |
| 3240 | ** --json Output formatted as JSON |
| 3241 | ** -n|--linenum Show line numbers DIFF_LINENO |
| 3242 | ** -N|--new-file Alias for --verbose |
| 3243 | ** --noopt Disable optimization DIFF_NOOPT |
| 3244 | ** --numstat Show change counts DIFF_NUMSTAT |
| 3245 | ** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR |
| 3246 | ** --tcl Tcl-formatted output used internally by --tk |
| 3247 | ** --unified Unified diff. ~DIFF_SIDEBYSIDE |
| 3248 | ** -v|--verbose Show complete text of added or deleted files |
| 3249 | ** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS |
| 3250 | ** --webpage Format output as a stand-alone HTML webpage |
| 3251 | ** -W|--width N N character lines. wColumn |
| 3252 | ** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE |
| 3253 | ** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS |
| 3254 | */ |
| 3255 | void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){ |
| @@ -3157,10 +3293,13 @@ | |
| 3293 | |
| 3294 | /* Undocumented and unsupported flags used for development |
| 3295 | ** debugging and analysis: */ |
| 3296 | if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG; |
| 3297 | if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW; |
| 3298 | if( find_option("bytoken",0,0)!=0 ){ |
| 3299 | diffFlags = DIFF_RAW|DIFF_BY_TOKEN; |
| 3300 | } |
| 3301 | } |
| 3302 | if( (z = find_option("context","c",1))!=0 ){ |
| 3303 | char *zEnd; |
| 3304 | f = (int)strtol(z, &zEnd, 10); |
| 3305 | if( zEnd[0]==0 && errno!=ERANGE ){ |
| 3306 |
+6
-2
| --- src/diff.tcl | ||
| +++ src/diff.tcl | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line |
| 2 | 2 | # to this file, then runs this file using "tclsh" in order to display the |
| 3 | 3 | # graphical diff in a separate window. A typical "set fossilcmd" line |
| 4 | 4 | # looks like this: |
| 5 | 5 | # |
| 6 | -# set fossilcmd {| "./fossil" diff --html -y -i -v} | |
| 6 | +# set fossilcmd {| "./fossil" diff --tcl -i -v} | |
| 7 | 7 | # |
| 8 | 8 | # This header comment is stripped off by the "mkbuiltin.c" program. |
| 9 | 9 | # |
| 10 | 10 | set prog { |
| 11 | 11 | package require Tk |
| @@ -110,11 +110,15 @@ | ||
| 110 | 110 | |
| 111 | 111 | set fromIndex [lsearch -glob $fossilcmd *-from] |
| 112 | 112 | set toIndex [lsearch -glob $fossilcmd *-to] |
| 113 | 113 | set branchIndex [lsearch -glob $fossilcmd *-branch] |
| 114 | 114 | set checkinIndex [lsearch -glob $fossilcmd *-checkin] |
| 115 | - set fA {base check-in} | |
| 115 | + if {[string match *?--external-baseline* $fossilcmd]} { | |
| 116 | + set fA {external baseline} | |
| 117 | + } else { | |
| 118 | + set fA {base check-in} | |
| 119 | + } | |
| 116 | 120 | set fB {current check-out} |
| 117 | 121 | if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]} |
| 118 | 122 | if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]} |
| 119 | 123 | if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"} |
| 120 | 124 | if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]} |
| 121 | 125 |
| --- src/diff.tcl | |
| +++ src/diff.tcl | |
| @@ -1,11 +1,11 @@ | |
| 1 | # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line |
| 2 | # to this file, then runs this file using "tclsh" in order to display the |
| 3 | # graphical diff in a separate window. A typical "set fossilcmd" line |
| 4 | # looks like this: |
| 5 | # |
| 6 | # set fossilcmd {| "./fossil" diff --html -y -i -v} |
| 7 | # |
| 8 | # This header comment is stripped off by the "mkbuiltin.c" program. |
| 9 | # |
| 10 | set prog { |
| 11 | package require Tk |
| @@ -110,11 +110,15 @@ | |
| 110 | |
| 111 | set fromIndex [lsearch -glob $fossilcmd *-from] |
| 112 | set toIndex [lsearch -glob $fossilcmd *-to] |
| 113 | set branchIndex [lsearch -glob $fossilcmd *-branch] |
| 114 | set checkinIndex [lsearch -glob $fossilcmd *-checkin] |
| 115 | set fA {base check-in} |
| 116 | set fB {current check-out} |
| 117 | if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]} |
| 118 | if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]} |
| 119 | if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"} |
| 120 | if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]} |
| 121 |
| --- src/diff.tcl | |
| +++ src/diff.tcl | |
| @@ -1,11 +1,11 @@ | |
| 1 | # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line |
| 2 | # to this file, then runs this file using "tclsh" in order to display the |
| 3 | # graphical diff in a separate window. A typical "set fossilcmd" line |
| 4 | # looks like this: |
| 5 | # |
| 6 | # set fossilcmd {| "./fossil" diff --tcl -i -v} |
| 7 | # |
| 8 | # This header comment is stripped off by the "mkbuiltin.c" program. |
| 9 | # |
| 10 | set prog { |
| 11 | package require Tk |
| @@ -110,11 +110,15 @@ | |
| 110 | |
| 111 | set fromIndex [lsearch -glob $fossilcmd *-from] |
| 112 | set toIndex [lsearch -glob $fossilcmd *-to] |
| 113 | set branchIndex [lsearch -glob $fossilcmd *-branch] |
| 114 | set checkinIndex [lsearch -glob $fossilcmd *-checkin] |
| 115 | if {[string match *?--external-baseline* $fossilcmd]} { |
| 116 | set fA {external baseline} |
| 117 | } else { |
| 118 | set fA {base check-in} |
| 119 | } |
| 120 | set fB {current check-out} |
| 121 | if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]} |
| 122 | if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]} |
| 123 | if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"} |
| 124 | if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]} |
| 125 |
+97
-21
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -249,19 +249,19 @@ | ||
| 249 | 249 | @ margin: 0 0 0 0; |
| 250 | 250 | @ line-height: inherit; |
| 251 | 251 | @ font-size: inherit; |
| 252 | 252 | @ } |
| 253 | 253 | @ td.diffln { |
| 254 | -@ width: 1px; | |
| 254 | +@ width: fit-content; | |
| 255 | 255 | @ text-align: right; |
| 256 | 256 | @ padding: 0 1em 0 0; |
| 257 | 257 | @ } |
| 258 | 258 | @ td.difflne { |
| 259 | 259 | @ padding-bottom: 0.4em; |
| 260 | 260 | @ } |
| 261 | 261 | @ td.diffsep { |
| 262 | -@ width: 1px; | |
| 262 | +@ width: fit-content; | |
| 263 | 263 | @ padding: 0 0.3em 0 1em; |
| 264 | 264 | @ line-height: inherit; |
| 265 | 265 | @ font-size: inherit; |
| 266 | 266 | @ } |
| 267 | 267 | @ td.diffsep pre { |
| @@ -379,19 +379,19 @@ | ||
| 379 | 379 | @ margin: 0 0 0 0; |
| 380 | 380 | @ line-height: inherit; |
| 381 | 381 | @ font-size: inherit; |
| 382 | 382 | @ } |
| 383 | 383 | @ td.diffln { |
| 384 | -@ width: 1px; | |
| 384 | +@ width: fit-content; | |
| 385 | 385 | @ text-align: right; |
| 386 | 386 | @ padding: 0 1em 0 0; |
| 387 | 387 | @ } |
| 388 | 388 | @ td.difflne { |
| 389 | 389 | @ padding-bottom: 0.4em; |
| 390 | 390 | @ } |
| 391 | 391 | @ td.diffsep { |
| 392 | -@ width: 1px; | |
| 392 | +@ width: fit-content; | |
| 393 | 393 | @ padding: 0 0.3em 0 1em; |
| 394 | 394 | @ line-height: inherit; |
| 395 | 395 | @ font-size: inherit; |
| 396 | 396 | @ } |
| 397 | 397 | @ td.diffsep pre { |
| @@ -784,26 +784,27 @@ | ||
| 784 | 784 | blob_reset(&file); |
| 785 | 785 | return rc; |
| 786 | 786 | } |
| 787 | 787 | |
| 788 | 788 | /* |
| 789 | -** Run a diff between the version zFrom and files on disk. zFrom might | |
| 790 | -** be NULL which means to simply show the difference between the edited | |
| 791 | -** files on disk and the check-out on which they are based. | |
| 789 | +** Run a diff between the version zFrom and files on disk in the current | |
| 790 | +** working checkout. zFrom might be NULL which means to simply show the | |
| 791 | +** difference between the edited files on disk and the check-out on which | |
| 792 | +** they are based. | |
| 792 | 793 | ** |
| 793 | 794 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 794 | 795 | ** command zDiffCmd to do the diffing. |
| 795 | 796 | ** |
| 796 | 797 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 797 | 798 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 798 | 799 | ** will be skipped in addition to files that may contain binary content. |
| 799 | 800 | */ |
| 800 | -void diff_against_disk( | |
| 801 | +void diff_version_to_checkout( | |
| 801 | 802 | const char *zFrom, /* Version to difference from */ |
| 802 | 803 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 803 | 804 | FileDirList *pFileDir, /* Which files to diff */ |
| 804 | - Blob *pOut /* Blob to output diff instead of stdout */ | |
| 805 | + Blob *pOut /* Blob to output diff instead of stdout */ | |
| 805 | 806 | ){ |
| 806 | 807 | int vid; |
| 807 | 808 | Blob sql; |
| 808 | 809 | Stmt q; |
| 809 | 810 | int asNewFile; /* Treat non-existant files as empty files */ |
| @@ -928,20 +929,20 @@ | ||
| 928 | 929 | db_finalize(&q); |
| 929 | 930 | db_end_transaction(1); /* ROLLBACK */ |
| 930 | 931 | } |
| 931 | 932 | |
| 932 | 933 | /* |
| 933 | -** Run a diff between the undo buffer and files on disk. | |
| 934 | +** Run a diff from the undo buffer to files on disk. | |
| 934 | 935 | ** |
| 935 | 936 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 936 | 937 | ** command zDiffCmd to do the diffing. |
| 937 | 938 | ** |
| 938 | 939 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 939 | 940 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 940 | 941 | ** will be skipped in addition to files that may contain binary content. |
| 941 | 942 | */ |
| 942 | -static void diff_against_undo( | |
| 943 | +static void diff_undo_to_checkout( | |
| 943 | 944 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 944 | 945 | FileDirList *pFileDir /* List of files and directories to diff */ |
| 945 | 946 | ){ |
| 946 | 947 | Stmt q; |
| 947 | 948 | Blob content; |
| @@ -1088,10 +1089,67 @@ | ||
| 1088 | 1089 | } |
| 1089 | 1090 | } |
| 1090 | 1091 | manifest_destroy(pFrom); |
| 1091 | 1092 | manifest_destroy(pTo); |
| 1092 | 1093 | } |
| 1094 | + | |
| 1095 | +/* | |
| 1096 | +** Compute the difference from an external tree of files to the current | |
| 1097 | +** working checkout with its edits. | |
| 1098 | +** | |
| 1099 | +** To put it another way: Every managed file in the current working | |
| 1100 | +** checkout is compared to the file with same name under zExternBase. The | |
| 1101 | +** zExternBase files are on the left and the files in the current working | |
| 1102 | +** directory are on the right. | |
| 1103 | +*/ | |
| 1104 | +void diff_externbase_to_checkout( | |
| 1105 | + const char *zExternBase, /* Remote tree to use as the baseline */ | |
| 1106 | + DiffConfig *pCfg, /* Diff settings */ | |
| 1107 | + FileDirList *pFileDir /* Only look at these files */ | |
| 1108 | +){ | |
| 1109 | + int vid; | |
| 1110 | + Stmt q; | |
| 1111 | + | |
| 1112 | + vid = db_lget_int("checkout",0); | |
| 1113 | + if( file_isdir(zExternBase, ExtFILE)!=1 ){ | |
| 1114 | + fossil_fatal("\"%s\" is not a directory", zExternBase); | |
| 1115 | + } | |
| 1116 | + db_prepare(&q, | |
| 1117 | + "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", | |
| 1118 | + vid | |
| 1119 | + ); | |
| 1120 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1121 | + const char *zFile; /* Name of file in the repository */ | |
| 1122 | + char *zLhs; /* Full name of left-hand side file */ | |
| 1123 | + char *zRhs; /* Full name of right-hand side file */ | |
| 1124 | + Blob rhs; /* Full text of RHS */ | |
| 1125 | + Blob lhs; /* Full text of LHS */ | |
| 1126 | + | |
| 1127 | + zFile = db_column_text(&q,0); | |
| 1128 | + if( !file_dir_match(pFileDir, zFile) ) continue; | |
| 1129 | + zLhs = mprintf("%s/%s", zExternBase, zFile); | |
| 1130 | + zRhs = mprintf("%s%s", g.zLocalRoot, zFile); | |
| 1131 | + if( file_size(zLhs, ExtFILE)<0 ){ | |
| 1132 | + blob_zero(&lhs); | |
| 1133 | + }else{ | |
| 1134 | + blob_read_from_file(&lhs, zLhs, ExtFILE); | |
| 1135 | + } | |
| 1136 | + blob_read_from_file(&rhs, zRhs, ExtFILE); | |
| 1137 | + if( blob_size(&lhs)!=blob_size(&rhs) | |
| 1138 | + || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 | |
| 1139 | + ){ | |
| 1140 | + diff_print_index(zFile, pCfg, 0); | |
| 1141 | + diff_file_mem(&lhs, &rhs, zFile, pCfg); | |
| 1142 | + } | |
| 1143 | + blob_reset(&lhs); | |
| 1144 | + blob_reset(&rhs); | |
| 1145 | + fossil_free(zLhs); | |
| 1146 | + fossil_free(zRhs); | |
| 1147 | + } | |
| 1148 | + db_finalize(&q); | |
| 1149 | +} | |
| 1150 | + | |
| 1093 | 1151 | |
| 1094 | 1152 | /* |
| 1095 | 1153 | ** Return the name of the external diff command, or return NULL if |
| 1096 | 1154 | ** no external diff command is defined. |
| 1097 | 1155 | */ |
| @@ -1224,10 +1282,14 @@ | ||
| 1224 | 1282 | ** option specifies the check-in from which the second version of the file |
| 1225 | 1283 | ** or files is taken. If there is no "--to" option then the (possibly edited) |
| 1226 | 1284 | ** files in the current check-out are used. The "--checkin VERSION" option |
| 1227 | 1285 | ** shows the changes made by check-in VERSION relative to its primary parent. |
| 1228 | 1286 | ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME. |
| 1287 | +** | |
| 1288 | +** With the "--from VERSION" option, if VERSION is actually a directory name | |
| 1289 | +** (not a tag or check-in hash) then the files under that directory are used | |
| 1290 | +** as the baseline for the diff. | |
| 1229 | 1291 | ** |
| 1230 | 1292 | ** The "-i" command-line option forces the use of Fossil's own internal |
| 1231 | 1293 | ** diff logic rather than any external diff program that might be configured |
| 1232 | 1294 | ** using the "setting" command. If no external diff program is configured, |
| 1233 | 1295 | ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into |
| @@ -1256,25 +1318,27 @@ | ||
| 1256 | 1318 | ** with negative N meaning show all content |
| 1257 | 1319 | ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML |
| 1258 | 1320 | ** --diff-binary BOOL Include binary files with external commands |
| 1259 | 1321 | ** --exec-abs-paths Force absolute path names on external commands |
| 1260 | 1322 | ** --exec-rel-paths Force relative path names on external commands |
| 1261 | -** -r|--from VERSION Select VERSION as source for the diff | |
| 1323 | +** -r|--from VERSION Use VERSION as the baseline for the diff, or | |
| 1324 | +** if VERSION is a directory name, use files in | |
| 1325 | +** that directory as the baseline. | |
| 1262 | 1326 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 1263 | 1327 | ** -i|--internal Use internal diff logic |
| 1264 | 1328 | ** --invert Invert the diff |
| 1265 | 1329 | ** --json Output formatted as JSON |
| 1266 | 1330 | ** -n|--linenum Show line numbers |
| 1267 | 1331 | ** -N|--new-file Alias for --verbose |
| 1268 | 1332 | ** --numstat Show only the number of added and deleted lines |
| 1269 | 1333 | ** -y|--side-by-side Side-by-side diff |
| 1270 | 1334 | ** --strip-trailing-cr Strip trailing CR |
| 1271 | -** --tcl Tcl-formated output used internally by --tk | |
| 1335 | +** --tcl Tcl-formatted output used internally by --tk | |
| 1272 | 1336 | ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh") |
| 1273 | 1337 | ** --tk Launch a Tcl/Tk GUI for display |
| 1274 | 1338 | ** --to VERSION Select VERSION as target for the diff |
| 1275 | -** --undo Diff against the "undo" buffer | |
| 1339 | +** --undo Use the undo buffer as the baseline | |
| 1276 | 1340 | ** --unified Unified diff |
| 1277 | 1341 | ** -v|--verbose Output complete text of added or deleted files |
| 1278 | 1342 | ** -h|--versions Show compared versions in the diff header |
| 1279 | 1343 | ** --webpage Format output as a stand-alone HTML webpage |
| 1280 | 1344 | ** -W|--width N Width of lines in side-by-side diff |
| @@ -1287,10 +1351,11 @@ | ||
| 1287 | 1351 | const char *zCheckin; /* Check-in version number */ |
| 1288 | 1352 | const char *zBranch; /* Branch to diff */ |
| 1289 | 1353 | int againstUndo = 0; /* Diff against files in the undo buffer */ |
| 1290 | 1354 | FileDirList *pFileDir = 0; /* Restrict the diff to these files */ |
| 1291 | 1355 | DiffConfig DCfg; /* Diff configuration object */ |
| 1356 | + int bFromIsDir = 0; /* True if zFrom is a directory name */ | |
| 1292 | 1357 | |
| 1293 | 1358 | if( find_option("tk",0,0)!=0 || has_option("tclsh") ){ |
| 1294 | 1359 | diff_tk("diff", 2); |
| 1295 | 1360 | return; |
| 1296 | 1361 | } |
| @@ -1298,11 +1363,11 @@ | ||
| 1298 | 1363 | zFrom = find_option("from", "r", 1); |
| 1299 | 1364 | zTo = find_option("to", 0, 1); |
| 1300 | 1365 | zCheckin = find_option("checkin", "ci", 1); |
| 1301 | 1366 | zBranch = find_option("branch", 0, 1); |
| 1302 | 1367 | againstUndo = find_option("undo",0,0)!=0; |
| 1303 | - if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){ | |
| 1368 | + if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){ | |
| 1304 | 1369 | fossil_fatal("cannot use --undo together with --from, --to, --checkin," |
| 1305 | 1370 | " or --branch"); |
| 1306 | 1371 | } |
| 1307 | 1372 | if( zBranch ){ |
| 1308 | 1373 | if( zTo || zFrom || zCheckin ){ |
| @@ -1309,14 +1374,13 @@ | ||
| 1309 | 1374 | fossil_fatal("cannot use --from, --to, or --checkin with --branch"); |
| 1310 | 1375 | } |
| 1311 | 1376 | zTo = zBranch; |
| 1312 | 1377 | zFrom = mprintf("root:%s", zBranch); |
| 1313 | 1378 | } |
| 1314 | - if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){ | |
| 1379 | + if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){ | |
| 1315 | 1380 | fossil_fatal("cannot use --checkin together with --from or --to"); |
| 1316 | 1381 | } |
| 1317 | - g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; | |
| 1318 | 1382 | if( 0==zCheckin ){ |
| 1319 | 1383 | if( zTo==0 || againstUndo ){ |
| 1320 | 1384 | db_must_be_within_tree(); |
| 1321 | 1385 | }else if( zFrom==0 ){ |
| 1322 | 1386 | fossil_fatal("must use --from if --to is present"); |
| @@ -1324,13 +1388,23 @@ | ||
| 1324 | 1388 | db_find_and_open_repository(0, 0); |
| 1325 | 1389 | } |
| 1326 | 1390 | }else{ |
| 1327 | 1391 | db_find_and_open_repository(0, 0); |
| 1328 | 1392 | } |
| 1329 | - diff_options(&DCfg, isGDiff, 0); | |
| 1330 | 1393 | determine_exec_relative_option(1); |
| 1394 | + if( zFrom!=file_tail(zFrom) | |
| 1395 | + && file_isdir(zFrom, ExtFILE)==1 | |
| 1396 | + && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom) | |
| 1397 | + ){ | |
| 1398 | + bFromIsDir = 1; | |
| 1399 | + if( zTo ){ | |
| 1400 | + fossil_fatal("cannot use --to together with \"--from PATH\""); | |
| 1401 | + } | |
| 1402 | + } | |
| 1403 | + diff_options(&DCfg, isGDiff, 0); | |
| 1331 | 1404 | verify_all_options(); |
| 1405 | + g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; | |
| 1332 | 1406 | if( g.argc>=3 ){ |
| 1333 | 1407 | int i; |
| 1334 | 1408 | Blob fname; |
| 1335 | 1409 | pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) ); |
| 1336 | 1410 | memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1)); |
| @@ -1357,18 +1431,20 @@ | ||
| 1357 | 1431 | if( zFrom==0 ){ |
| 1358 | 1432 | fossil_fatal("check-in %s has no parent", zTo); |
| 1359 | 1433 | } |
| 1360 | 1434 | } |
| 1361 | 1435 | diff_begin(&DCfg); |
| 1362 | - if( againstUndo ){ | |
| 1436 | + if( bFromIsDir ){ | |
| 1437 | + diff_externbase_to_checkout(zFrom, &DCfg, pFileDir); | |
| 1438 | + }else if( againstUndo ){ | |
| 1363 | 1439 | if( db_lget_int("undo_available",0)==0 ){ |
| 1364 | 1440 | fossil_print("No undo or redo is available\n"); |
| 1365 | 1441 | return; |
| 1366 | 1442 | } |
| 1367 | - diff_against_undo(&DCfg, pFileDir); | |
| 1443 | + diff_undo_to_checkout(&DCfg, pFileDir); | |
| 1368 | 1444 | }else if( zTo==0 ){ |
| 1369 | - diff_against_disk(zFrom, &DCfg, pFileDir, 0); | |
| 1445 | + diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0); | |
| 1370 | 1446 | }else{ |
| 1371 | 1447 | diff_two_versions(zFrom, zTo, &DCfg, pFileDir); |
| 1372 | 1448 | } |
| 1373 | 1449 | if( pFileDir ){ |
| 1374 | 1450 | int i; |
| 1375 | 1451 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -249,19 +249,19 @@ | |
| 249 | @ margin: 0 0 0 0; |
| 250 | @ line-height: inherit; |
| 251 | @ font-size: inherit; |
| 252 | @ } |
| 253 | @ td.diffln { |
| 254 | @ width: 1px; |
| 255 | @ text-align: right; |
| 256 | @ padding: 0 1em 0 0; |
| 257 | @ } |
| 258 | @ td.difflne { |
| 259 | @ padding-bottom: 0.4em; |
| 260 | @ } |
| 261 | @ td.diffsep { |
| 262 | @ width: 1px; |
| 263 | @ padding: 0 0.3em 0 1em; |
| 264 | @ line-height: inherit; |
| 265 | @ font-size: inherit; |
| 266 | @ } |
| 267 | @ td.diffsep pre { |
| @@ -379,19 +379,19 @@ | |
| 379 | @ margin: 0 0 0 0; |
| 380 | @ line-height: inherit; |
| 381 | @ font-size: inherit; |
| 382 | @ } |
| 383 | @ td.diffln { |
| 384 | @ width: 1px; |
| 385 | @ text-align: right; |
| 386 | @ padding: 0 1em 0 0; |
| 387 | @ } |
| 388 | @ td.difflne { |
| 389 | @ padding-bottom: 0.4em; |
| 390 | @ } |
| 391 | @ td.diffsep { |
| 392 | @ width: 1px; |
| 393 | @ padding: 0 0.3em 0 1em; |
| 394 | @ line-height: inherit; |
| 395 | @ font-size: inherit; |
| 396 | @ } |
| 397 | @ td.diffsep pre { |
| @@ -784,26 +784,27 @@ | |
| 784 | blob_reset(&file); |
| 785 | return rc; |
| 786 | } |
| 787 | |
| 788 | /* |
| 789 | ** Run a diff between the version zFrom and files on disk. zFrom might |
| 790 | ** be NULL which means to simply show the difference between the edited |
| 791 | ** files on disk and the check-out on which they are based. |
| 792 | ** |
| 793 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 794 | ** command zDiffCmd to do the diffing. |
| 795 | ** |
| 796 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 797 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 798 | ** will be skipped in addition to files that may contain binary content. |
| 799 | */ |
| 800 | void diff_against_disk( |
| 801 | const char *zFrom, /* Version to difference from */ |
| 802 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 803 | FileDirList *pFileDir, /* Which files to diff */ |
| 804 | Blob *pOut /* Blob to output diff instead of stdout */ |
| 805 | ){ |
| 806 | int vid; |
| 807 | Blob sql; |
| 808 | Stmt q; |
| 809 | int asNewFile; /* Treat non-existant files as empty files */ |
| @@ -928,20 +929,20 @@ | |
| 928 | db_finalize(&q); |
| 929 | db_end_transaction(1); /* ROLLBACK */ |
| 930 | } |
| 931 | |
| 932 | /* |
| 933 | ** Run a diff between the undo buffer and files on disk. |
| 934 | ** |
| 935 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 936 | ** command zDiffCmd to do the diffing. |
| 937 | ** |
| 938 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 939 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 940 | ** will be skipped in addition to files that may contain binary content. |
| 941 | */ |
| 942 | static void diff_against_undo( |
| 943 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 944 | FileDirList *pFileDir /* List of files and directories to diff */ |
| 945 | ){ |
| 946 | Stmt q; |
| 947 | Blob content; |
| @@ -1088,10 +1089,67 @@ | |
| 1088 | } |
| 1089 | } |
| 1090 | manifest_destroy(pFrom); |
| 1091 | manifest_destroy(pTo); |
| 1092 | } |
| 1093 | |
| 1094 | /* |
| 1095 | ** Return the name of the external diff command, or return NULL if |
| 1096 | ** no external diff command is defined. |
| 1097 | */ |
| @@ -1224,10 +1282,14 @@ | |
| 1224 | ** option specifies the check-in from which the second version of the file |
| 1225 | ** or files is taken. If there is no "--to" option then the (possibly edited) |
| 1226 | ** files in the current check-out are used. The "--checkin VERSION" option |
| 1227 | ** shows the changes made by check-in VERSION relative to its primary parent. |
| 1228 | ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME. |
| 1229 | ** |
| 1230 | ** The "-i" command-line option forces the use of Fossil's own internal |
| 1231 | ** diff logic rather than any external diff program that might be configured |
| 1232 | ** using the "setting" command. If no external diff program is configured, |
| 1233 | ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into |
| @@ -1256,25 +1318,27 @@ | |
| 1256 | ** with negative N meaning show all content |
| 1257 | ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML |
| 1258 | ** --diff-binary BOOL Include binary files with external commands |
| 1259 | ** --exec-abs-paths Force absolute path names on external commands |
| 1260 | ** --exec-rel-paths Force relative path names on external commands |
| 1261 | ** -r|--from VERSION Select VERSION as source for the diff |
| 1262 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 1263 | ** -i|--internal Use internal diff logic |
| 1264 | ** --invert Invert the diff |
| 1265 | ** --json Output formatted as JSON |
| 1266 | ** -n|--linenum Show line numbers |
| 1267 | ** -N|--new-file Alias for --verbose |
| 1268 | ** --numstat Show only the number of added and deleted lines |
| 1269 | ** -y|--side-by-side Side-by-side diff |
| 1270 | ** --strip-trailing-cr Strip trailing CR |
| 1271 | ** --tcl Tcl-formated output used internally by --tk |
| 1272 | ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh") |
| 1273 | ** --tk Launch a Tcl/Tk GUI for display |
| 1274 | ** --to VERSION Select VERSION as target for the diff |
| 1275 | ** --undo Diff against the "undo" buffer |
| 1276 | ** --unified Unified diff |
| 1277 | ** -v|--verbose Output complete text of added or deleted files |
| 1278 | ** -h|--versions Show compared versions in the diff header |
| 1279 | ** --webpage Format output as a stand-alone HTML webpage |
| 1280 | ** -W|--width N Width of lines in side-by-side diff |
| @@ -1287,10 +1351,11 @@ | |
| 1287 | const char *zCheckin; /* Check-in version number */ |
| 1288 | const char *zBranch; /* Branch to diff */ |
| 1289 | int againstUndo = 0; /* Diff against files in the undo buffer */ |
| 1290 | FileDirList *pFileDir = 0; /* Restrict the diff to these files */ |
| 1291 | DiffConfig DCfg; /* Diff configuration object */ |
| 1292 | |
| 1293 | if( find_option("tk",0,0)!=0 || has_option("tclsh") ){ |
| 1294 | diff_tk("diff", 2); |
| 1295 | return; |
| 1296 | } |
| @@ -1298,11 +1363,11 @@ | |
| 1298 | zFrom = find_option("from", "r", 1); |
| 1299 | zTo = find_option("to", 0, 1); |
| 1300 | zCheckin = find_option("checkin", "ci", 1); |
| 1301 | zBranch = find_option("branch", 0, 1); |
| 1302 | againstUndo = find_option("undo",0,0)!=0; |
| 1303 | if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){ |
| 1304 | fossil_fatal("cannot use --undo together with --from, --to, --checkin," |
| 1305 | " or --branch"); |
| 1306 | } |
| 1307 | if( zBranch ){ |
| 1308 | if( zTo || zFrom || zCheckin ){ |
| @@ -1309,14 +1374,13 @@ | |
| 1309 | fossil_fatal("cannot use --from, --to, or --checkin with --branch"); |
| 1310 | } |
| 1311 | zTo = zBranch; |
| 1312 | zFrom = mprintf("root:%s", zBranch); |
| 1313 | } |
| 1314 | if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){ |
| 1315 | fossil_fatal("cannot use --checkin together with --from or --to"); |
| 1316 | } |
| 1317 | g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; |
| 1318 | if( 0==zCheckin ){ |
| 1319 | if( zTo==0 || againstUndo ){ |
| 1320 | db_must_be_within_tree(); |
| 1321 | }else if( zFrom==0 ){ |
| 1322 | fossil_fatal("must use --from if --to is present"); |
| @@ -1324,13 +1388,23 @@ | |
| 1324 | db_find_and_open_repository(0, 0); |
| 1325 | } |
| 1326 | }else{ |
| 1327 | db_find_and_open_repository(0, 0); |
| 1328 | } |
| 1329 | diff_options(&DCfg, isGDiff, 0); |
| 1330 | determine_exec_relative_option(1); |
| 1331 | verify_all_options(); |
| 1332 | if( g.argc>=3 ){ |
| 1333 | int i; |
| 1334 | Blob fname; |
| 1335 | pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) ); |
| 1336 | memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1)); |
| @@ -1357,18 +1431,20 @@ | |
| 1357 | if( zFrom==0 ){ |
| 1358 | fossil_fatal("check-in %s has no parent", zTo); |
| 1359 | } |
| 1360 | } |
| 1361 | diff_begin(&DCfg); |
| 1362 | if( againstUndo ){ |
| 1363 | if( db_lget_int("undo_available",0)==0 ){ |
| 1364 | fossil_print("No undo or redo is available\n"); |
| 1365 | return; |
| 1366 | } |
| 1367 | diff_against_undo(&DCfg, pFileDir); |
| 1368 | }else if( zTo==0 ){ |
| 1369 | diff_against_disk(zFrom, &DCfg, pFileDir, 0); |
| 1370 | }else{ |
| 1371 | diff_two_versions(zFrom, zTo, &DCfg, pFileDir); |
| 1372 | } |
| 1373 | if( pFileDir ){ |
| 1374 | int i; |
| 1375 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -249,19 +249,19 @@ | |
| 249 | @ margin: 0 0 0 0; |
| 250 | @ line-height: inherit; |
| 251 | @ font-size: inherit; |
| 252 | @ } |
| 253 | @ td.diffln { |
| 254 | @ width: fit-content; |
| 255 | @ text-align: right; |
| 256 | @ padding: 0 1em 0 0; |
| 257 | @ } |
| 258 | @ td.difflne { |
| 259 | @ padding-bottom: 0.4em; |
| 260 | @ } |
| 261 | @ td.diffsep { |
| 262 | @ width: fit-content; |
| 263 | @ padding: 0 0.3em 0 1em; |
| 264 | @ line-height: inherit; |
| 265 | @ font-size: inherit; |
| 266 | @ } |
| 267 | @ td.diffsep pre { |
| @@ -379,19 +379,19 @@ | |
| 379 | @ margin: 0 0 0 0; |
| 380 | @ line-height: inherit; |
| 381 | @ font-size: inherit; |
| 382 | @ } |
| 383 | @ td.diffln { |
| 384 | @ width: fit-content; |
| 385 | @ text-align: right; |
| 386 | @ padding: 0 1em 0 0; |
| 387 | @ } |
| 388 | @ td.difflne { |
| 389 | @ padding-bottom: 0.4em; |
| 390 | @ } |
| 391 | @ td.diffsep { |
| 392 | @ width: fit-content; |
| 393 | @ padding: 0 0.3em 0 1em; |
| 394 | @ line-height: inherit; |
| 395 | @ font-size: inherit; |
| 396 | @ } |
| 397 | @ td.diffsep pre { |
| @@ -784,26 +784,27 @@ | |
| 784 | blob_reset(&file); |
| 785 | return rc; |
| 786 | } |
| 787 | |
| 788 | /* |
| 789 | ** Run a diff between the version zFrom and files on disk in the current |
| 790 | ** working checkout. zFrom might be NULL which means to simply show the |
| 791 | ** difference between the edited files on disk and the check-out on which |
| 792 | ** they are based. |
| 793 | ** |
| 794 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 795 | ** command zDiffCmd to do the diffing. |
| 796 | ** |
| 797 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 798 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 799 | ** will be skipped in addition to files that may contain binary content. |
| 800 | */ |
| 801 | void diff_version_to_checkout( |
| 802 | const char *zFrom, /* Version to difference from */ |
| 803 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 804 | FileDirList *pFileDir, /* Which files to diff */ |
| 805 | Blob *pOut /* Blob to output diff instead of stdout */ |
| 806 | ){ |
| 807 | int vid; |
| 808 | Blob sql; |
| 809 | Stmt q; |
| 810 | int asNewFile; /* Treat non-existant files as empty files */ |
| @@ -928,20 +929,20 @@ | |
| 929 | db_finalize(&q); |
| 930 | db_end_transaction(1); /* ROLLBACK */ |
| 931 | } |
| 932 | |
| 933 | /* |
| 934 | ** Run a diff from the undo buffer to files on disk. |
| 935 | ** |
| 936 | ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the |
| 937 | ** command zDiffCmd to do the diffing. |
| 938 | ** |
| 939 | ** When using an external diff program, zBinGlob contains the GLOB patterns |
| 940 | ** for file names to treat as binary. If fIncludeBinary is zero, these files |
| 941 | ** will be skipped in addition to files that may contain binary content. |
| 942 | */ |
| 943 | static void diff_undo_to_checkout( |
| 944 | DiffConfig *pCfg, /* Flags controlling diff output */ |
| 945 | FileDirList *pFileDir /* List of files and directories to diff */ |
| 946 | ){ |
| 947 | Stmt q; |
| 948 | Blob content; |
| @@ -1088,10 +1089,67 @@ | |
| 1089 | } |
| 1090 | } |
| 1091 | manifest_destroy(pFrom); |
| 1092 | manifest_destroy(pTo); |
| 1093 | } |
| 1094 | |
| 1095 | /* |
| 1096 | ** Compute the difference from an external tree of files to the current |
| 1097 | ** working checkout with its edits. |
| 1098 | ** |
| 1099 | ** To put it another way: Every managed file in the current working |
| 1100 | ** checkout is compared to the file with same name under zExternBase. The |
| 1101 | ** zExternBase files are on the left and the files in the current working |
| 1102 | ** directory are on the right. |
| 1103 | */ |
| 1104 | void diff_externbase_to_checkout( |
| 1105 | const char *zExternBase, /* Remote tree to use as the baseline */ |
| 1106 | DiffConfig *pCfg, /* Diff settings */ |
| 1107 | FileDirList *pFileDir /* Only look at these files */ |
| 1108 | ){ |
| 1109 | int vid; |
| 1110 | Stmt q; |
| 1111 | |
| 1112 | vid = db_lget_int("checkout",0); |
| 1113 | if( file_isdir(zExternBase, ExtFILE)!=1 ){ |
| 1114 | fossil_fatal("\"%s\" is not a directory", zExternBase); |
| 1115 | } |
| 1116 | db_prepare(&q, |
| 1117 | "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", |
| 1118 | vid |
| 1119 | ); |
| 1120 | while( db_step(&q)==SQLITE_ROW ){ |
| 1121 | const char *zFile; /* Name of file in the repository */ |
| 1122 | char *zLhs; /* Full name of left-hand side file */ |
| 1123 | char *zRhs; /* Full name of right-hand side file */ |
| 1124 | Blob rhs; /* Full text of RHS */ |
| 1125 | Blob lhs; /* Full text of LHS */ |
| 1126 | |
| 1127 | zFile = db_column_text(&q,0); |
| 1128 | if( !file_dir_match(pFileDir, zFile) ) continue; |
| 1129 | zLhs = mprintf("%s/%s", zExternBase, zFile); |
| 1130 | zRhs = mprintf("%s%s", g.zLocalRoot, zFile); |
| 1131 | if( file_size(zLhs, ExtFILE)<0 ){ |
| 1132 | blob_zero(&lhs); |
| 1133 | }else{ |
| 1134 | blob_read_from_file(&lhs, zLhs, ExtFILE); |
| 1135 | } |
| 1136 | blob_read_from_file(&rhs, zRhs, ExtFILE); |
| 1137 | if( blob_size(&lhs)!=blob_size(&rhs) |
| 1138 | || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 |
| 1139 | ){ |
| 1140 | diff_print_index(zFile, pCfg, 0); |
| 1141 | diff_file_mem(&lhs, &rhs, zFile, pCfg); |
| 1142 | } |
| 1143 | blob_reset(&lhs); |
| 1144 | blob_reset(&rhs); |
| 1145 | fossil_free(zLhs); |
| 1146 | fossil_free(zRhs); |
| 1147 | } |
| 1148 | db_finalize(&q); |
| 1149 | } |
| 1150 | |
| 1151 | |
| 1152 | /* |
| 1153 | ** Return the name of the external diff command, or return NULL if |
| 1154 | ** no external diff command is defined. |
| 1155 | */ |
| @@ -1224,10 +1282,14 @@ | |
| 1282 | ** option specifies the check-in from which the second version of the file |
| 1283 | ** or files is taken. If there is no "--to" option then the (possibly edited) |
| 1284 | ** files in the current check-out are used. The "--checkin VERSION" option |
| 1285 | ** shows the changes made by check-in VERSION relative to its primary parent. |
| 1286 | ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME. |
| 1287 | ** |
| 1288 | ** With the "--from VERSION" option, if VERSION is actually a directory name |
| 1289 | ** (not a tag or check-in hash) then the files under that directory are used |
| 1290 | ** as the baseline for the diff. |
| 1291 | ** |
| 1292 | ** The "-i" command-line option forces the use of Fossil's own internal |
| 1293 | ** diff logic rather than any external diff program that might be configured |
| 1294 | ** using the "setting" command. If no external diff program is configured, |
| 1295 | ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into |
| @@ -1256,25 +1318,27 @@ | |
| 1318 | ** with negative N meaning show all content |
| 1319 | ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML |
| 1320 | ** --diff-binary BOOL Include binary files with external commands |
| 1321 | ** --exec-abs-paths Force absolute path names on external commands |
| 1322 | ** --exec-rel-paths Force relative path names on external commands |
| 1323 | ** -r|--from VERSION Use VERSION as the baseline for the diff, or |
| 1324 | ** if VERSION is a directory name, use files in |
| 1325 | ** that directory as the baseline. |
| 1326 | ** -w|--ignore-all-space Ignore white space when comparing lines |
| 1327 | ** -i|--internal Use internal diff logic |
| 1328 | ** --invert Invert the diff |
| 1329 | ** --json Output formatted as JSON |
| 1330 | ** -n|--linenum Show line numbers |
| 1331 | ** -N|--new-file Alias for --verbose |
| 1332 | ** --numstat Show only the number of added and deleted lines |
| 1333 | ** -y|--side-by-side Side-by-side diff |
| 1334 | ** --strip-trailing-cr Strip trailing CR |
| 1335 | ** --tcl Tcl-formatted output used internally by --tk |
| 1336 | ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh") |
| 1337 | ** --tk Launch a Tcl/Tk GUI for display |
| 1338 | ** --to VERSION Select VERSION as target for the diff |
| 1339 | ** --undo Use the undo buffer as the baseline |
| 1340 | ** --unified Unified diff |
| 1341 | ** -v|--verbose Output complete text of added or deleted files |
| 1342 | ** -h|--versions Show compared versions in the diff header |
| 1343 | ** --webpage Format output as a stand-alone HTML webpage |
| 1344 | ** -W|--width N Width of lines in side-by-side diff |
| @@ -1287,10 +1351,11 @@ | |
| 1351 | const char *zCheckin; /* Check-in version number */ |
| 1352 | const char *zBranch; /* Branch to diff */ |
| 1353 | int againstUndo = 0; /* Diff against files in the undo buffer */ |
| 1354 | FileDirList *pFileDir = 0; /* Restrict the diff to these files */ |
| 1355 | DiffConfig DCfg; /* Diff configuration object */ |
| 1356 | int bFromIsDir = 0; /* True if zFrom is a directory name */ |
| 1357 | |
| 1358 | if( find_option("tk",0,0)!=0 || has_option("tclsh") ){ |
| 1359 | diff_tk("diff", 2); |
| 1360 | return; |
| 1361 | } |
| @@ -1298,11 +1363,11 @@ | |
| 1363 | zFrom = find_option("from", "r", 1); |
| 1364 | zTo = find_option("to", 0, 1); |
| 1365 | zCheckin = find_option("checkin", "ci", 1); |
| 1366 | zBranch = find_option("branch", 0, 1); |
| 1367 | againstUndo = find_option("undo",0,0)!=0; |
| 1368 | if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){ |
| 1369 | fossil_fatal("cannot use --undo together with --from, --to, --checkin," |
| 1370 | " or --branch"); |
| 1371 | } |
| 1372 | if( zBranch ){ |
| 1373 | if( zTo || zFrom || zCheckin ){ |
| @@ -1309,14 +1374,13 @@ | |
| 1374 | fossil_fatal("cannot use --from, --to, or --checkin with --branch"); |
| 1375 | } |
| 1376 | zTo = zBranch; |
| 1377 | zFrom = mprintf("root:%s", zBranch); |
| 1378 | } |
| 1379 | if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){ |
| 1380 | fossil_fatal("cannot use --checkin together with --from or --to"); |
| 1381 | } |
| 1382 | if( 0==zCheckin ){ |
| 1383 | if( zTo==0 || againstUndo ){ |
| 1384 | db_must_be_within_tree(); |
| 1385 | }else if( zFrom==0 ){ |
| 1386 | fossil_fatal("must use --from if --to is present"); |
| @@ -1324,13 +1388,23 @@ | |
| 1388 | db_find_and_open_repository(0, 0); |
| 1389 | } |
| 1390 | }else{ |
| 1391 | db_find_and_open_repository(0, 0); |
| 1392 | } |
| 1393 | determine_exec_relative_option(1); |
| 1394 | if( zFrom!=file_tail(zFrom) |
| 1395 | && file_isdir(zFrom, ExtFILE)==1 |
| 1396 | && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom) |
| 1397 | ){ |
| 1398 | bFromIsDir = 1; |
| 1399 | if( zTo ){ |
| 1400 | fossil_fatal("cannot use --to together with \"--from PATH\""); |
| 1401 | } |
| 1402 | } |
| 1403 | diff_options(&DCfg, isGDiff, 0); |
| 1404 | verify_all_options(); |
| 1405 | g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0; |
| 1406 | if( g.argc>=3 ){ |
| 1407 | int i; |
| 1408 | Blob fname; |
| 1409 | pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) ); |
| 1410 | memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1)); |
| @@ -1357,18 +1431,20 @@ | |
| 1431 | if( zFrom==0 ){ |
| 1432 | fossil_fatal("check-in %s has no parent", zTo); |
| 1433 | } |
| 1434 | } |
| 1435 | diff_begin(&DCfg); |
| 1436 | if( bFromIsDir ){ |
| 1437 | diff_externbase_to_checkout(zFrom, &DCfg, pFileDir); |
| 1438 | }else if( againstUndo ){ |
| 1439 | if( db_lget_int("undo_available",0)==0 ){ |
| 1440 | fossil_print("No undo or redo is available\n"); |
| 1441 | return; |
| 1442 | } |
| 1443 | diff_undo_to_checkout(&DCfg, pFileDir); |
| 1444 | }else if( zTo==0 ){ |
| 1445 | diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0); |
| 1446 | }else{ |
| 1447 | diff_two_versions(zFrom, zTo, &DCfg, pFileDir); |
| 1448 | } |
| 1449 | if( pFileDir ){ |
| 1450 | int i; |
| 1451 |
+26
-1
| --- src/dispatch.c | ||
| +++ src/dispatch.c | ||
| @@ -1307,17 +1307,42 @@ | ||
| 1307 | 1307 | } |
| 1308 | 1308 | |
| 1309 | 1309 | /* |
| 1310 | 1310 | ** Return a pointer to the setting information array. |
| 1311 | 1311 | ** |
| 1312 | -** This routine provides access to the aSetting2[] array which is created | |
| 1312 | +** This routine provides access to the aSetting[] array which is created | |
| 1313 | 1313 | ** by the mkindex utility program and included with <page_index.h>. |
| 1314 | 1314 | */ |
| 1315 | 1315 | const Setting *setting_info(int *pnCount){ |
| 1316 | 1316 | if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1; |
| 1317 | 1317 | return aSetting; |
| 1318 | 1318 | } |
| 1319 | + | |
| 1320 | +/* | |
| 1321 | +** Return a pointer to a specific Setting entry for the setting named | |
| 1322 | +** in the argument. Or return NULL if no such setting exists. | |
| 1323 | +** | |
| 1324 | +** The pointer returned points into the middle of the global aSetting[] | |
| 1325 | +** array that is generated by mkindex. Use setting_info() to fetch the | |
| 1326 | +** whole array. Use this routine to fetch a specific entry. | |
| 1327 | +*/ | |
| 1328 | +const Setting *setting_find(const char *zName){ | |
| 1329 | + int iFirst = 0; | |
| 1330 | + int iLast = ArraySize(aSetting)-1; | |
| 1331 | + while( iFirst<=iLast ){ | |
| 1332 | + int iCur = (iFirst+iLast)/2; | |
| 1333 | + int c = strcmp(aSetting[iCur].name, zName); | |
| 1334 | + if( c<0 ){ | |
| 1335 | + iFirst = iCur+1; | |
| 1336 | + }else if( c>0 ){ | |
| 1337 | + iLast = iCur-1; | |
| 1338 | + }else{ | |
| 1339 | + return &aSetting[iCur]; | |
| 1340 | + } | |
| 1341 | + } | |
| 1342 | + return 0; | |
| 1343 | +} | |
| 1319 | 1344 | |
| 1320 | 1345 | /***************************************************************************** |
| 1321 | 1346 | ** A virtual table for accessing the information in aCommand[], and |
| 1322 | 1347 | ** especially the help-text |
| 1323 | 1348 | */ |
| 1324 | 1349 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -1307,17 +1307,42 @@ | |
| 1307 | } |
| 1308 | |
| 1309 | /* |
| 1310 | ** Return a pointer to the setting information array. |
| 1311 | ** |
| 1312 | ** This routine provides access to the aSetting2[] array which is created |
| 1313 | ** by the mkindex utility program and included with <page_index.h>. |
| 1314 | */ |
| 1315 | const Setting *setting_info(int *pnCount){ |
| 1316 | if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1; |
| 1317 | return aSetting; |
| 1318 | } |
| 1319 | |
| 1320 | /***************************************************************************** |
| 1321 | ** A virtual table for accessing the information in aCommand[], and |
| 1322 | ** especially the help-text |
| 1323 | */ |
| 1324 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -1307,17 +1307,42 @@ | |
| 1307 | } |
| 1308 | |
| 1309 | /* |
| 1310 | ** Return a pointer to the setting information array. |
| 1311 | ** |
| 1312 | ** This routine provides access to the aSetting[] array which is created |
| 1313 | ** by the mkindex utility program and included with <page_index.h>. |
| 1314 | */ |
| 1315 | const Setting *setting_info(int *pnCount){ |
| 1316 | if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1; |
| 1317 | return aSetting; |
| 1318 | } |
| 1319 | |
| 1320 | /* |
| 1321 | ** Return a pointer to a specific Setting entry for the setting named |
| 1322 | ** in the argument. Or return NULL if no such setting exists. |
| 1323 | ** |
| 1324 | ** The pointer returned points into the middle of the global aSetting[] |
| 1325 | ** array that is generated by mkindex. Use setting_info() to fetch the |
| 1326 | ** whole array. Use this routine to fetch a specific entry. |
| 1327 | */ |
| 1328 | const Setting *setting_find(const char *zName){ |
| 1329 | int iFirst = 0; |
| 1330 | int iLast = ArraySize(aSetting)-1; |
| 1331 | while( iFirst<=iLast ){ |
| 1332 | int iCur = (iFirst+iLast)/2; |
| 1333 | int c = strcmp(aSetting[iCur].name, zName); |
| 1334 | if( c<0 ){ |
| 1335 | iFirst = iCur+1; |
| 1336 | }else if( c>0 ){ |
| 1337 | iLast = iCur-1; |
| 1338 | }else{ |
| 1339 | return &aSetting[iCur]; |
| 1340 | } |
| 1341 | } |
| 1342 | return 0; |
| 1343 | } |
| 1344 | |
| 1345 | /***************************************************************************** |
| 1346 | ** A virtual table for accessing the information in aCommand[], and |
| 1347 | ** especially the help-text |
| 1348 | */ |
| 1349 |
+8
-1
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -788,11 +788,11 @@ | ||
| 788 | 788 | ){ |
| 789 | 789 | Blob title; |
| 790 | 790 | int isPopup = P("popup")!=0; |
| 791 | 791 | blob_init(&title,0,0); |
| 792 | 792 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 793 | - Blob tail; | |
| 793 | + Blob tail = BLOB_INITIALIZER; | |
| 794 | 794 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 795 | 795 | if( wiki_find_title(pBody, &title, &tail) ){ |
| 796 | 796 | if( !isPopup ) style_header("%s", blob_str(&title)); |
| 797 | 797 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 798 | 798 | }else{ |
| @@ -801,10 +801,11 @@ | ||
| 801 | 801 | } |
| 802 | 802 | if( !isPopup ){ |
| 803 | 803 | document_emit_js(); |
| 804 | 804 | style_finish_page(); |
| 805 | 805 | } |
| 806 | + blob_reset(&tail); | |
| 806 | 807 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 807 | 808 | Blob tail = BLOB_INITIALIZER; |
| 808 | 809 | markdown_to_html(pBody, &title, &tail); |
| 809 | 810 | if( !isPopup ){ |
| 810 | 811 | if( blob_size(&title)>0 ){ |
| @@ -816,10 +817,11 @@ | ||
| 816 | 817 | convert_href_and_output(&tail); |
| 817 | 818 | if( !isPopup ){ |
| 818 | 819 | document_emit_js(); |
| 819 | 820 | style_finish_page(); |
| 820 | 821 | } |
| 822 | + blob_reset(&tail); | |
| 821 | 823 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 822 | 824 | style_header("%s", zDefaultTitle); |
| 823 | 825 | @ <blockquote><pre> |
| 824 | 826 | @ %h(blob_str(pBody)) |
| 825 | 827 | @ </pre></blockquote> |
| @@ -949,10 +951,11 @@ | ||
| 949 | 951 | |
| 950 | 952 | login_check_credentials(); |
| 951 | 953 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 952 | 954 | style_set_current_feature("doc"); |
| 953 | 955 | blob_init(&title, 0, 0); |
| 956 | + blob_init(&filebody, 0, 0); | |
| 954 | 957 | zDfltTitle = isUV ? "" : "Documentation"; |
| 955 | 958 | db_begin_transaction(); |
| 956 | 959 | while( rid==0 && (++nMiss)<=count(azSuffix) ){ |
| 957 | 960 | zName = P("name"); |
| 958 | 961 | if( isUV ){ |
| @@ -1059,10 +1062,12 @@ | ||
| 1059 | 1062 | } |
| 1060 | 1063 | cgi_check_for_malice(); |
| 1061 | 1064 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 1062 | 1065 | if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found"); |
| 1063 | 1066 | db_end_transaction(0); |
| 1067 | + blob_reset(&title); | |
| 1068 | + blob_reset(&filebody); | |
| 1064 | 1069 | return; |
| 1065 | 1070 | |
| 1066 | 1071 | /* Jump here when unable to locate the document */ |
| 1067 | 1072 | doc_not_found: |
| 1068 | 1073 | db_end_transaction(0); |
| @@ -1075,10 +1080,12 @@ | ||
| 1075 | 1080 | @ <p>Document %h(zOrigName) not found |
| 1076 | 1081 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 1077 | 1082 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 1078 | 1083 | } |
| 1079 | 1084 | style_finish_page(); |
| 1085 | + blob_reset(&title); | |
| 1086 | + blob_reset(&filebody); | |
| 1080 | 1087 | return; |
| 1081 | 1088 | } |
| 1082 | 1089 | |
| 1083 | 1090 | /* |
| 1084 | 1091 | ** The default logo. |
| 1085 | 1092 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -788,11 +788,11 @@ | |
| 788 | ){ |
| 789 | Blob title; |
| 790 | int isPopup = P("popup")!=0; |
| 791 | blob_init(&title,0,0); |
| 792 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 793 | Blob tail; |
| 794 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 795 | if( wiki_find_title(pBody, &title, &tail) ){ |
| 796 | if( !isPopup ) style_header("%s", blob_str(&title)); |
| 797 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 798 | }else{ |
| @@ -801,10 +801,11 @@ | |
| 801 | } |
| 802 | if( !isPopup ){ |
| 803 | document_emit_js(); |
| 804 | style_finish_page(); |
| 805 | } |
| 806 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 807 | Blob tail = BLOB_INITIALIZER; |
| 808 | markdown_to_html(pBody, &title, &tail); |
| 809 | if( !isPopup ){ |
| 810 | if( blob_size(&title)>0 ){ |
| @@ -816,10 +817,11 @@ | |
| 816 | convert_href_and_output(&tail); |
| 817 | if( !isPopup ){ |
| 818 | document_emit_js(); |
| 819 | style_finish_page(); |
| 820 | } |
| 821 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 822 | style_header("%s", zDefaultTitle); |
| 823 | @ <blockquote><pre> |
| 824 | @ %h(blob_str(pBody)) |
| 825 | @ </pre></blockquote> |
| @@ -949,10 +951,11 @@ | |
| 949 | |
| 950 | login_check_credentials(); |
| 951 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 952 | style_set_current_feature("doc"); |
| 953 | blob_init(&title, 0, 0); |
| 954 | zDfltTitle = isUV ? "" : "Documentation"; |
| 955 | db_begin_transaction(); |
| 956 | while( rid==0 && (++nMiss)<=count(azSuffix) ){ |
| 957 | zName = P("name"); |
| 958 | if( isUV ){ |
| @@ -1059,10 +1062,12 @@ | |
| 1059 | } |
| 1060 | cgi_check_for_malice(); |
| 1061 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 1062 | if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found"); |
| 1063 | db_end_transaction(0); |
| 1064 | return; |
| 1065 | |
| 1066 | /* Jump here when unable to locate the document */ |
| 1067 | doc_not_found: |
| 1068 | db_end_transaction(0); |
| @@ -1075,10 +1080,12 @@ | |
| 1075 | @ <p>Document %h(zOrigName) not found |
| 1076 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 1077 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 1078 | } |
| 1079 | style_finish_page(); |
| 1080 | return; |
| 1081 | } |
| 1082 | |
| 1083 | /* |
| 1084 | ** The default logo. |
| 1085 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -788,11 +788,11 @@ | |
| 788 | ){ |
| 789 | Blob title; |
| 790 | int isPopup = P("popup")!=0; |
| 791 | blob_init(&title,0,0); |
| 792 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 793 | Blob tail = BLOB_INITIALIZER; |
| 794 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 795 | if( wiki_find_title(pBody, &title, &tail) ){ |
| 796 | if( !isPopup ) style_header("%s", blob_str(&title)); |
| 797 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 798 | }else{ |
| @@ -801,10 +801,11 @@ | |
| 801 | } |
| 802 | if( !isPopup ){ |
| 803 | document_emit_js(); |
| 804 | style_finish_page(); |
| 805 | } |
| 806 | blob_reset(&tail); |
| 807 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 808 | Blob tail = BLOB_INITIALIZER; |
| 809 | markdown_to_html(pBody, &title, &tail); |
| 810 | if( !isPopup ){ |
| 811 | if( blob_size(&title)>0 ){ |
| @@ -816,10 +817,11 @@ | |
| 817 | convert_href_and_output(&tail); |
| 818 | if( !isPopup ){ |
| 819 | document_emit_js(); |
| 820 | style_finish_page(); |
| 821 | } |
| 822 | blob_reset(&tail); |
| 823 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 824 | style_header("%s", zDefaultTitle); |
| 825 | @ <blockquote><pre> |
| 826 | @ %h(blob_str(pBody)) |
| 827 | @ </pre></blockquote> |
| @@ -949,10 +951,11 @@ | |
| 951 | |
| 952 | login_check_credentials(); |
| 953 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 954 | style_set_current_feature("doc"); |
| 955 | blob_init(&title, 0, 0); |
| 956 | blob_init(&filebody, 0, 0); |
| 957 | zDfltTitle = isUV ? "" : "Documentation"; |
| 958 | db_begin_transaction(); |
| 959 | while( rid==0 && (++nMiss)<=count(azSuffix) ){ |
| 960 | zName = P("name"); |
| 961 | if( isUV ){ |
| @@ -1059,10 +1062,12 @@ | |
| 1062 | } |
| 1063 | cgi_check_for_malice(); |
| 1064 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 1065 | if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found"); |
| 1066 | db_end_transaction(0); |
| 1067 | blob_reset(&title); |
| 1068 | blob_reset(&filebody); |
| 1069 | return; |
| 1070 | |
| 1071 | /* Jump here when unable to locate the document */ |
| 1072 | doc_not_found: |
| 1073 | db_end_transaction(0); |
| @@ -1075,10 +1080,12 @@ | |
| 1080 | @ <p>Document %h(zOrigName) not found |
| 1081 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 1082 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 1083 | } |
| 1084 | style_finish_page(); |
| 1085 | blob_reset(&title); |
| 1086 | blob_reset(&filebody); |
| 1087 | return; |
| 1088 | } |
| 1089 | |
| 1090 | /* |
| 1091 | ** The default logo. |
| 1092 |
+18
| --- src/encode.c | ||
| +++ src/encode.c | ||
| @@ -729,10 +729,28 @@ | ||
| 729 | 729 | while( *z && n-- ){ |
| 730 | 730 | *z = zEncode[zDecode[(*z)&0x7f]&0x1f]; |
| 731 | 731 | z++; |
| 732 | 732 | } |
| 733 | 733 | } |
| 734 | + | |
| 735 | +/* | |
| 736 | +** Decode hexadecimal into a string and return the new string. Space to | |
| 737 | +** hold the string is obtained from fossil_malloc() and should be released | |
| 738 | +** by the caller. | |
| 739 | +** | |
| 740 | +** If the input is not hex, return NULL. | |
| 741 | +*/ | |
| 742 | +char *decode16_dup(const char *zIn){ | |
| 743 | + int nIn = (int)strlen(zIn); | |
| 744 | + char *zOut; | |
| 745 | + if( !validate16(zIn, nIn) ) return 0; | |
| 746 | + zOut = fossil_malloc(nIn/2+1); | |
| 747 | + decode16((const u8*)zIn, (u8*)zOut, nIn); | |
| 748 | + zOut[nIn/2] = 0; | |
| 749 | + return zOut; | |
| 750 | +} | |
| 751 | + | |
| 734 | 752 | |
| 735 | 753 | /* |
| 736 | 754 | ** Decode a string encoded using "quoted-printable". |
| 737 | 755 | ** |
| 738 | 756 | ** (1) "=" followed by two hex digits becomes a single |
| 739 | 757 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -729,10 +729,28 @@ | |
| 729 | while( *z && n-- ){ |
| 730 | *z = zEncode[zDecode[(*z)&0x7f]&0x1f]; |
| 731 | z++; |
| 732 | } |
| 733 | } |
| 734 | |
| 735 | /* |
| 736 | ** Decode a string encoded using "quoted-printable". |
| 737 | ** |
| 738 | ** (1) "=" followed by two hex digits becomes a single |
| 739 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -729,10 +729,28 @@ | |
| 729 | while( *z && n-- ){ |
| 730 | *z = zEncode[zDecode[(*z)&0x7f]&0x1f]; |
| 731 | z++; |
| 732 | } |
| 733 | } |
| 734 | |
| 735 | /* |
| 736 | ** Decode hexadecimal into a string and return the new string. Space to |
| 737 | ** hold the string is obtained from fossil_malloc() and should be released |
| 738 | ** by the caller. |
| 739 | ** |
| 740 | ** If the input is not hex, return NULL. |
| 741 | */ |
| 742 | char *decode16_dup(const char *zIn){ |
| 743 | int nIn = (int)strlen(zIn); |
| 744 | char *zOut; |
| 745 | if( !validate16(zIn, nIn) ) return 0; |
| 746 | zOut = fossil_malloc(nIn/2+1); |
| 747 | decode16((const u8*)zIn, (u8*)zOut, nIn); |
| 748 | zOut[nIn/2] = 0; |
| 749 | return zOut; |
| 750 | } |
| 751 | |
| 752 | |
| 753 | /* |
| 754 | ** Decode a string encoded using "quoted-printable". |
| 755 | ** |
| 756 | ** (1) "=" followed by two hex digits becomes a single |
| 757 |
+1
-1
| --- src/event.c | ||
| +++ src/event.c | ||
| @@ -229,11 +229,11 @@ | ||
| 229 | 229 | } |
| 230 | 230 | zFullId = db_text(0, "SELECT SUBSTR(tagname,7)" |
| 231 | 231 | " FROM tag" |
| 232 | 232 | " WHERE tagname GLOB 'event-%q*'", |
| 233 | 233 | zId); |
| 234 | - attachment_list(zFullId, "<hr><h2>Attachments:</h2><ul>"); | |
| 234 | + attachment_list(zFullId, "<h2>Attachments:</h2>", 1); | |
| 235 | 235 | document_emit_js(); |
| 236 | 236 | style_finish_page(); |
| 237 | 237 | manifest_destroy(pTNote); |
| 238 | 238 | } |
| 239 | 239 | |
| 240 | 240 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -229,11 +229,11 @@ | |
| 229 | } |
| 230 | zFullId = db_text(0, "SELECT SUBSTR(tagname,7)" |
| 231 | " FROM tag" |
| 232 | " WHERE tagname GLOB 'event-%q*'", |
| 233 | zId); |
| 234 | attachment_list(zFullId, "<hr><h2>Attachments:</h2><ul>"); |
| 235 | document_emit_js(); |
| 236 | style_finish_page(); |
| 237 | manifest_destroy(pTNote); |
| 238 | } |
| 239 | |
| 240 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -229,11 +229,11 @@ | |
| 229 | } |
| 230 | zFullId = db_text(0, "SELECT SUBSTR(tagname,7)" |
| 231 | " FROM tag" |
| 232 | " WHERE tagname GLOB 'event-%q*'", |
| 233 | zId); |
| 234 | attachment_list(zFullId, "<h2>Attachments:</h2>", 1); |
| 235 | document_emit_js(); |
| 236 | style_finish_page(); |
| 237 | manifest_destroy(pTNote); |
| 238 | } |
| 239 | |
| 240 |
+94
-8
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -1337,25 +1337,37 @@ | ||
| 1337 | 1337 | } |
| 1338 | 1338 | } |
| 1339 | 1339 | |
| 1340 | 1340 | /* |
| 1341 | 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | -** Make the name absolute if it is relative. | |
| 1343 | -** Remove redundant / characters | |
| 1344 | -** Remove all /./ path elements. | |
| 1345 | -** Convert /A/../ to just / | |
| 1342 | +** | |
| 1343 | +** * Make the name absolute if it is relative. | |
| 1344 | +** * Remove redundant / characters | |
| 1345 | +** * Remove all /./ path elements. | |
| 1346 | +** * Convert /A/../ to just / | |
| 1347 | +** * On windows, add the drive letter prefix. | |
| 1348 | +** | |
| 1346 | 1349 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1347 | 1350 | ** is retained. |
| 1348 | 1351 | ** |
| 1349 | 1352 | ** See also: file_canonical_name_dup() |
| 1350 | 1353 | */ |
| 1351 | 1354 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1355 | + char zPwd[2000]; | |
| 1352 | 1356 | blob_zero(pOut); |
| 1353 | 1357 | if( file_is_absolute_path(zOrigName) ){ |
| 1354 | - blob_appendf(pOut, "%/", zOrigName); | |
| 1358 | +#if defined(_WIN32) | |
| 1359 | + if( fossil_isdirsep(zOrigName[0]) ){ | |
| 1360 | + /* Add the drive letter to the full pathname */ | |
| 1361 | + file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); | |
| 1362 | + blob_appendf(pOut, "%.2s%/", zPwd, zOrigName); | |
| 1363 | + }else | |
| 1364 | +#endif | |
| 1365 | + { | |
| 1366 | + blob_appendf(pOut, "%/", zOrigName); | |
| 1367 | + } | |
| 1355 | 1368 | }else{ |
| 1356 | - char zPwd[2000]; | |
| 1357 | 1369 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1358 | 1370 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1359 | 1371 | /* when on '/', don't add an extra '/' */ |
| 1360 | 1372 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1361 | 1373 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | ||
| 1679 | 1691 | g.allowSymlinks = !is_false(zAllow); |
| 1680 | 1692 | } |
| 1681 | 1693 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1682 | 1694 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1683 | 1695 | fossil_print("local-root = [%s]\n", zRoot); |
| 1696 | + if( g.db==0 ) sqlite3_open(":memory:", &g.db); | |
| 1697 | + sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, | |
| 1698 | + file_inode_sql_func, 0, 0); | |
| 1684 | 1699 | for(i=2; i<g.argc; i++){ |
| 1685 | 1700 | char *z; |
| 1686 | 1701 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1687 | 1702 | z = file_canonical_name_dup(g.argv[i]); |
| 1688 | 1703 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | ||
| 1692 | 1707 | }else{ |
| 1693 | 1708 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1694 | 1709 | fossil_print("%.*s\n", n, z); |
| 1695 | 1710 | } |
| 1696 | 1711 | fossil_free(z); |
| 1712 | + z = db_text(0, "SELECT inode(%Q)", g.argv[i]); | |
| 1713 | + fossil_print(" file_inode_sql_func = \"%s\"\n", z); | |
| 1714 | + fossil_free(z); | |
| 1697 | 1715 | } |
| 1698 | 1716 | } |
| 1699 | 1717 | |
| 1700 | 1718 | /* |
| 1701 | 1719 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | ||
| 1734 | 1752 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1735 | 1753 | ** contain no "/./" or "/../" terms. |
| 1736 | 1754 | */ |
| 1737 | 1755 | int file_is_canonical(const char *z){ |
| 1738 | 1756 | int i; |
| 1739 | - if( z[0]!='/' | |
| 1757 | + if( | |
| 1740 | 1758 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1741 | - && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') | |
| 1759 | + !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2]) | |
| 1760 | +#else | |
| 1761 | + z[0]!='/' | |
| 1742 | 1762 | #endif |
| 1743 | 1763 | ) return 0; |
| 1744 | 1764 | |
| 1745 | 1765 | for(i=0; z[i]; i++){ |
| 1746 | 1766 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | ||
| 2988 | 3008 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 2989 | 3009 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 2990 | 3010 | */ |
| 2991 | 3011 | int dir_has_ckout_db(const char *zDir){ |
| 2992 | 3012 | int rc = 0; |
| 3013 | + i64 sz; | |
| 2993 | 3014 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 2994 | 3015 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 2995 | 3016 | rc = 1; |
| 2996 | 3017 | }else{ |
| 2997 | 3018 | fossil_free(zCkoutDb); |
| 2998 | 3019 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 2999 | 3020 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3000 | 3021 | rc = 2; |
| 3001 | 3022 | } |
| 3023 | + } | |
| 3024 | + if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){ | |
| 3025 | + rc = 0; | |
| 3002 | 3026 | } |
| 3003 | 3027 | fossil_free(zCkoutDb); |
| 3004 | 3028 | return rc; |
| 3005 | 3029 | } |
| 3030 | + | |
| 3031 | +/* | |
| 3032 | +** This is the implementation of inode(FILENAME) SQL function. | |
| 3033 | +** | |
| 3034 | +** dev_inode(FILENAME) returns a string. If FILENAME exists and is | |
| 3035 | +** a regular file, then the return string is of the form: | |
| 3036 | +** | |
| 3037 | +** DEV/INODE | |
| 3038 | +** | |
| 3039 | +** Where DEV and INODE are the device number and inode number for | |
| 3040 | +** the file. On Windows, the volume serial number (DEV) and file | |
| 3041 | +** identifier (INODE) are used to compute the value, see comments | |
| 3042 | +** on the win32_file_id() function. | |
| 3043 | +** | |
| 3044 | +** If FILENAME does not exist, then the return is an empty string. | |
| 3045 | +** | |
| 3046 | +** The value of inode() can be used to eliminate files from a list | |
| 3047 | +** that have duplicates because they have differing names due to links. | |
| 3048 | +** | |
| 3049 | +** Code that wants to use this SQL function needs to first register | |
| 3050 | +** it using a call such as the following: | |
| 3051 | +** | |
| 3052 | +** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, | |
| 3053 | +** file_inode_sql_func, 0, 0); | |
| 3054 | +*/ | |
| 3055 | +void file_inode_sql_func( | |
| 3056 | + sqlite3_context *context, | |
| 3057 | + int argc, | |
| 3058 | + sqlite3_value **argv | |
| 3059 | +){ | |
| 3060 | + const char *zFilename; | |
| 3061 | + assert( argc==1 ); | |
| 3062 | + zFilename = (const char*)sqlite3_value_text(argv[0]); | |
| 3063 | + if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){ | |
| 3064 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3065 | + return; | |
| 3066 | + } | |
| 3067 | +#if defined(_WIN32) | |
| 3068 | + { | |
| 3069 | + char *zFileId = win32_file_id(zFilename); | |
| 3070 | + if( zFileId ){ | |
| 3071 | + sqlite3_result_text(context, zFileId, -1, fossil_free); | |
| 3072 | + }else{ | |
| 3073 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3074 | + } | |
| 3075 | + } | |
| 3076 | +#else | |
| 3077 | + { | |
| 3078 | + struct stat buf; | |
| 3079 | + int rc; | |
| 3080 | + memset(&buf, 0, sizeof(buf)); | |
| 3081 | + rc = stat(zFilename, &buf); | |
| 3082 | + if( rc ){ | |
| 3083 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3084 | + }else{ | |
| 3085 | + sqlite3_result_text(context, | |
| 3086 | + mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1, | |
| 3087 | + fossil_free); | |
| 3088 | + } | |
| 3089 | + } | |
| 3090 | +#endif | |
| 3091 | +} | |
| 3006 | 3092 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -1337,25 +1337,37 @@ | |
| 1337 | } |
| 1338 | } |
| 1339 | |
| 1340 | /* |
| 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | ** Make the name absolute if it is relative. |
| 1343 | ** Remove redundant / characters |
| 1344 | ** Remove all /./ path elements. |
| 1345 | ** Convert /A/../ to just / |
| 1346 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1347 | ** is retained. |
| 1348 | ** |
| 1349 | ** See also: file_canonical_name_dup() |
| 1350 | */ |
| 1351 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1352 | blob_zero(pOut); |
| 1353 | if( file_is_absolute_path(zOrigName) ){ |
| 1354 | blob_appendf(pOut, "%/", zOrigName); |
| 1355 | }else{ |
| 1356 | char zPwd[2000]; |
| 1357 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1358 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1359 | /* when on '/', don't add an extra '/' */ |
| 1360 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1361 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | |
| 1679 | g.allowSymlinks = !is_false(zAllow); |
| 1680 | } |
| 1681 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1682 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1683 | fossil_print("local-root = [%s]\n", zRoot); |
| 1684 | for(i=2; i<g.argc; i++){ |
| 1685 | char *z; |
| 1686 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1687 | z = file_canonical_name_dup(g.argv[i]); |
| 1688 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | |
| 1692 | }else{ |
| 1693 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1694 | fossil_print("%.*s\n", n, z); |
| 1695 | } |
| 1696 | fossil_free(z); |
| 1697 | } |
| 1698 | } |
| 1699 | |
| 1700 | /* |
| 1701 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | |
| 1734 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1735 | ** contain no "/./" or "/../" terms. |
| 1736 | */ |
| 1737 | int file_is_canonical(const char *z){ |
| 1738 | int i; |
| 1739 | if( z[0]!='/' |
| 1740 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1741 | && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') |
| 1742 | #endif |
| 1743 | ) return 0; |
| 1744 | |
| 1745 | for(i=0; z[i]; i++){ |
| 1746 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | |
| 2988 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 2989 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 2990 | */ |
| 2991 | int dir_has_ckout_db(const char *zDir){ |
| 2992 | int rc = 0; |
| 2993 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 2994 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 2995 | rc = 1; |
| 2996 | }else{ |
| 2997 | fossil_free(zCkoutDb); |
| 2998 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 2999 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3000 | rc = 2; |
| 3001 | } |
| 3002 | } |
| 3003 | fossil_free(zCkoutDb); |
| 3004 | return rc; |
| 3005 | } |
| 3006 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -1337,25 +1337,37 @@ | |
| 1337 | } |
| 1338 | } |
| 1339 | |
| 1340 | /* |
| 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | ** |
| 1343 | ** * Make the name absolute if it is relative. |
| 1344 | ** * Remove redundant / characters |
| 1345 | ** * Remove all /./ path elements. |
| 1346 | ** * Convert /A/../ to just / |
| 1347 | ** * On windows, add the drive letter prefix. |
| 1348 | ** |
| 1349 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1350 | ** is retained. |
| 1351 | ** |
| 1352 | ** See also: file_canonical_name_dup() |
| 1353 | */ |
| 1354 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1355 | char zPwd[2000]; |
| 1356 | blob_zero(pOut); |
| 1357 | if( file_is_absolute_path(zOrigName) ){ |
| 1358 | #if defined(_WIN32) |
| 1359 | if( fossil_isdirsep(zOrigName[0]) ){ |
| 1360 | /* Add the drive letter to the full pathname */ |
| 1361 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1362 | blob_appendf(pOut, "%.2s%/", zPwd, zOrigName); |
| 1363 | }else |
| 1364 | #endif |
| 1365 | { |
| 1366 | blob_appendf(pOut, "%/", zOrigName); |
| 1367 | } |
| 1368 | }else{ |
| 1369 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1370 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1371 | /* when on '/', don't add an extra '/' */ |
| 1372 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1373 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | |
| 1691 | g.allowSymlinks = !is_false(zAllow); |
| 1692 | } |
| 1693 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1694 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1695 | fossil_print("local-root = [%s]\n", zRoot); |
| 1696 | if( g.db==0 ) sqlite3_open(":memory:", &g.db); |
| 1697 | sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, |
| 1698 | file_inode_sql_func, 0, 0); |
| 1699 | for(i=2; i<g.argc; i++){ |
| 1700 | char *z; |
| 1701 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1702 | z = file_canonical_name_dup(g.argv[i]); |
| 1703 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | |
| 1707 | }else{ |
| 1708 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1709 | fossil_print("%.*s\n", n, z); |
| 1710 | } |
| 1711 | fossil_free(z); |
| 1712 | z = db_text(0, "SELECT inode(%Q)", g.argv[i]); |
| 1713 | fossil_print(" file_inode_sql_func = \"%s\"\n", z); |
| 1714 | fossil_free(z); |
| 1715 | } |
| 1716 | } |
| 1717 | |
| 1718 | /* |
| 1719 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | |
| 1752 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1753 | ** contain no "/./" or "/../" terms. |
| 1754 | */ |
| 1755 | int file_is_canonical(const char *z){ |
| 1756 | int i; |
| 1757 | if( |
| 1758 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1759 | !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2]) |
| 1760 | #else |
| 1761 | z[0]!='/' |
| 1762 | #endif |
| 1763 | ) return 0; |
| 1764 | |
| 1765 | for(i=0; z[i]; i++){ |
| 1766 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | |
| 3008 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 3009 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 3010 | */ |
| 3011 | int dir_has_ckout_db(const char *zDir){ |
| 3012 | int rc = 0; |
| 3013 | i64 sz; |
| 3014 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 3015 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3016 | rc = 1; |
| 3017 | }else{ |
| 3018 | fossil_free(zCkoutDb); |
| 3019 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 3020 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3021 | rc = 2; |
| 3022 | } |
| 3023 | } |
| 3024 | if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){ |
| 3025 | rc = 0; |
| 3026 | } |
| 3027 | fossil_free(zCkoutDb); |
| 3028 | return rc; |
| 3029 | } |
| 3030 | |
| 3031 | /* |
| 3032 | ** This is the implementation of inode(FILENAME) SQL function. |
| 3033 | ** |
| 3034 | ** dev_inode(FILENAME) returns a string. If FILENAME exists and is |
| 3035 | ** a regular file, then the return string is of the form: |
| 3036 | ** |
| 3037 | ** DEV/INODE |
| 3038 | ** |
| 3039 | ** Where DEV and INODE are the device number and inode number for |
| 3040 | ** the file. On Windows, the volume serial number (DEV) and file |
| 3041 | ** identifier (INODE) are used to compute the value, see comments |
| 3042 | ** on the win32_file_id() function. |
| 3043 | ** |
| 3044 | ** If FILENAME does not exist, then the return is an empty string. |
| 3045 | ** |
| 3046 | ** The value of inode() can be used to eliminate files from a list |
| 3047 | ** that have duplicates because they have differing names due to links. |
| 3048 | ** |
| 3049 | ** Code that wants to use this SQL function needs to first register |
| 3050 | ** it using a call such as the following: |
| 3051 | ** |
| 3052 | ** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, |
| 3053 | ** file_inode_sql_func, 0, 0); |
| 3054 | */ |
| 3055 | void file_inode_sql_func( |
| 3056 | sqlite3_context *context, |
| 3057 | int argc, |
| 3058 | sqlite3_value **argv |
| 3059 | ){ |
| 3060 | const char *zFilename; |
| 3061 | assert( argc==1 ); |
| 3062 | zFilename = (const char*)sqlite3_value_text(argv[0]); |
| 3063 | if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){ |
| 3064 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3065 | return; |
| 3066 | } |
| 3067 | #if defined(_WIN32) |
| 3068 | { |
| 3069 | char *zFileId = win32_file_id(zFilename); |
| 3070 | if( zFileId ){ |
| 3071 | sqlite3_result_text(context, zFileId, -1, fossil_free); |
| 3072 | }else{ |
| 3073 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3074 | } |
| 3075 | } |
| 3076 | #else |
| 3077 | { |
| 3078 | struct stat buf; |
| 3079 | int rc; |
| 3080 | memset(&buf, 0, sizeof(buf)); |
| 3081 | rc = stat(zFilename, &buf); |
| 3082 | if( rc ){ |
| 3083 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3084 | }else{ |
| 3085 | sqlite3_result_text(context, |
| 3086 | mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1, |
| 3087 | fossil_free); |
| 3088 | } |
| 3089 | } |
| 3090 | #endif |
| 3091 | } |
| 3092 |
+94
-8
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -1337,25 +1337,37 @@ | ||
| 1337 | 1337 | } |
| 1338 | 1338 | } |
| 1339 | 1339 | |
| 1340 | 1340 | /* |
| 1341 | 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | -** Make the name absolute if it is relative. | |
| 1343 | -** Remove redundant / characters | |
| 1344 | -** Remove all /./ path elements. | |
| 1345 | -** Convert /A/../ to just / | |
| 1342 | +** | |
| 1343 | +** * Make the name absolute if it is relative. | |
| 1344 | +** * Remove redundant / characters | |
| 1345 | +** * Remove all /./ path elements. | |
| 1346 | +** * Convert /A/../ to just / | |
| 1347 | +** * On windows, add the drive letter prefix. | |
| 1348 | +** | |
| 1346 | 1349 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1347 | 1350 | ** is retained. |
| 1348 | 1351 | ** |
| 1349 | 1352 | ** See also: file_canonical_name_dup() |
| 1350 | 1353 | */ |
| 1351 | 1354 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1355 | + char zPwd[2000]; | |
| 1352 | 1356 | blob_zero(pOut); |
| 1353 | 1357 | if( file_is_absolute_path(zOrigName) ){ |
| 1354 | - blob_appendf(pOut, "%/", zOrigName); | |
| 1358 | +#if defined(_WIN32) | |
| 1359 | + if( fossil_isdirsep(zOrigName[0]) ){ | |
| 1360 | + /* Add the drive letter to the full pathname */ | |
| 1361 | + file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); | |
| 1362 | + blob_appendf(pOut, "%.2s%/", zPwd, zOrigName); | |
| 1363 | + }else | |
| 1364 | +#endif | |
| 1365 | + { | |
| 1366 | + blob_appendf(pOut, "%/", zOrigName); | |
| 1367 | + } | |
| 1355 | 1368 | }else{ |
| 1356 | - char zPwd[2000]; | |
| 1357 | 1369 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1358 | 1370 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1359 | 1371 | /* when on '/', don't add an extra '/' */ |
| 1360 | 1372 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1361 | 1373 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | ||
| 1679 | 1691 | g.allowSymlinks = !is_false(zAllow); |
| 1680 | 1692 | } |
| 1681 | 1693 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1682 | 1694 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1683 | 1695 | fossil_print("local-root = [%s]\n", zRoot); |
| 1696 | + if( g.db==0 ) sqlite3_open(":memory:", &g.db); | |
| 1697 | + sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, | |
| 1698 | + file_inode_sql_func, 0, 0); | |
| 1684 | 1699 | for(i=2; i<g.argc; i++){ |
| 1685 | 1700 | char *z; |
| 1686 | 1701 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1687 | 1702 | z = file_canonical_name_dup(g.argv[i]); |
| 1688 | 1703 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | ||
| 1692 | 1707 | }else{ |
| 1693 | 1708 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1694 | 1709 | fossil_print("%.*s\n", n, z); |
| 1695 | 1710 | } |
| 1696 | 1711 | fossil_free(z); |
| 1712 | + z = db_text(0, "SELECT inode(%Q)", g.argv[i]); | |
| 1713 | + fossil_print(" file_inode_sql_func = \"%s\"\n", z); | |
| 1714 | + fossil_free(z); | |
| 1697 | 1715 | } |
| 1698 | 1716 | } |
| 1699 | 1717 | |
| 1700 | 1718 | /* |
| 1701 | 1719 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | ||
| 1734 | 1752 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1735 | 1753 | ** contain no "/./" or "/../" terms. |
| 1736 | 1754 | */ |
| 1737 | 1755 | int file_is_canonical(const char *z){ |
| 1738 | 1756 | int i; |
| 1739 | - if( z[0]!='/' | |
| 1757 | + if( | |
| 1740 | 1758 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1741 | - && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') | |
| 1759 | + !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2]) | |
| 1760 | +#else | |
| 1761 | + z[0]!='/' | |
| 1742 | 1762 | #endif |
| 1743 | 1763 | ) return 0; |
| 1744 | 1764 | |
| 1745 | 1765 | for(i=0; z[i]; i++){ |
| 1746 | 1766 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | ||
| 2988 | 3008 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 2989 | 3009 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 2990 | 3010 | */ |
| 2991 | 3011 | int dir_has_ckout_db(const char *zDir){ |
| 2992 | 3012 | int rc = 0; |
| 3013 | + i64 sz; | |
| 2993 | 3014 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 2994 | 3015 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 2995 | 3016 | rc = 1; |
| 2996 | 3017 | }else{ |
| 2997 | 3018 | fossil_free(zCkoutDb); |
| 2998 | 3019 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 2999 | 3020 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3000 | 3021 | rc = 2; |
| 3001 | 3022 | } |
| 3023 | + } | |
| 3024 | + if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){ | |
| 3025 | + rc = 0; | |
| 3002 | 3026 | } |
| 3003 | 3027 | fossil_free(zCkoutDb); |
| 3004 | 3028 | return rc; |
| 3005 | 3029 | } |
| 3030 | + | |
| 3031 | +/* | |
| 3032 | +** This is the implementation of inode(FILENAME) SQL function. | |
| 3033 | +** | |
| 3034 | +** dev_inode(FILENAME) returns a string. If FILENAME exists and is | |
| 3035 | +** a regular file, then the return string is of the form: | |
| 3036 | +** | |
| 3037 | +** DEV/INODE | |
| 3038 | +** | |
| 3039 | +** Where DEV and INODE are the device number and inode number for | |
| 3040 | +** the file. On Windows, the volume serial number (DEV) and file | |
| 3041 | +** identifier (INODE) are used to compute the value, see comments | |
| 3042 | +** on the win32_file_id() function. | |
| 3043 | +** | |
| 3044 | +** If FILENAME does not exist, then the return is an empty string. | |
| 3045 | +** | |
| 3046 | +** The value of inode() can be used to eliminate files from a list | |
| 3047 | +** that have duplicates because they have differing names due to links. | |
| 3048 | +** | |
| 3049 | +** Code that wants to use this SQL function needs to first register | |
| 3050 | +** it using a call such as the following: | |
| 3051 | +** | |
| 3052 | +** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, | |
| 3053 | +** file_inode_sql_func, 0, 0); | |
| 3054 | +*/ | |
| 3055 | +void file_inode_sql_func( | |
| 3056 | + sqlite3_context *context, | |
| 3057 | + int argc, | |
| 3058 | + sqlite3_value **argv | |
| 3059 | +){ | |
| 3060 | + const char *zFilename; | |
| 3061 | + assert( argc==1 ); | |
| 3062 | + zFilename = (const char*)sqlite3_value_text(argv[0]); | |
| 3063 | + if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){ | |
| 3064 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3065 | + return; | |
| 3066 | + } | |
| 3067 | +#if defined(_WIN32) | |
| 3068 | + { | |
| 3069 | + char *zFileId = win32_file_id(zFilename); | |
| 3070 | + if( zFileId ){ | |
| 3071 | + sqlite3_result_text(context, zFileId, -1, fossil_free); | |
| 3072 | + }else{ | |
| 3073 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3074 | + } | |
| 3075 | + } | |
| 3076 | +#else | |
| 3077 | + { | |
| 3078 | + struct stat buf; | |
| 3079 | + int rc; | |
| 3080 | + memset(&buf, 0, sizeof(buf)); | |
| 3081 | + rc = stat(zFilename, &buf); | |
| 3082 | + if( rc ){ | |
| 3083 | + sqlite3_result_text(context, "", 0, SQLITE_STATIC); | |
| 3084 | + }else{ | |
| 3085 | + sqlite3_result_text(context, | |
| 3086 | + mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1, | |
| 3087 | + fossil_free); | |
| 3088 | + } | |
| 3089 | + } | |
| 3090 | +#endif | |
| 3091 | +} | |
| 3006 | 3092 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -1337,25 +1337,37 @@ | |
| 1337 | } |
| 1338 | } |
| 1339 | |
| 1340 | /* |
| 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | ** Make the name absolute if it is relative. |
| 1343 | ** Remove redundant / characters |
| 1344 | ** Remove all /./ path elements. |
| 1345 | ** Convert /A/../ to just / |
| 1346 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1347 | ** is retained. |
| 1348 | ** |
| 1349 | ** See also: file_canonical_name_dup() |
| 1350 | */ |
| 1351 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1352 | blob_zero(pOut); |
| 1353 | if( file_is_absolute_path(zOrigName) ){ |
| 1354 | blob_appendf(pOut, "%/", zOrigName); |
| 1355 | }else{ |
| 1356 | char zPwd[2000]; |
| 1357 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1358 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1359 | /* when on '/', don't add an extra '/' */ |
| 1360 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1361 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | |
| 1679 | g.allowSymlinks = !is_false(zAllow); |
| 1680 | } |
| 1681 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1682 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1683 | fossil_print("local-root = [%s]\n", zRoot); |
| 1684 | for(i=2; i<g.argc; i++){ |
| 1685 | char *z; |
| 1686 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1687 | z = file_canonical_name_dup(g.argv[i]); |
| 1688 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | |
| 1692 | }else{ |
| 1693 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1694 | fossil_print("%.*s\n", n, z); |
| 1695 | } |
| 1696 | fossil_free(z); |
| 1697 | } |
| 1698 | } |
| 1699 | |
| 1700 | /* |
| 1701 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | |
| 1734 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1735 | ** contain no "/./" or "/../" terms. |
| 1736 | */ |
| 1737 | int file_is_canonical(const char *z){ |
| 1738 | int i; |
| 1739 | if( z[0]!='/' |
| 1740 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1741 | && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') |
| 1742 | #endif |
| 1743 | ) return 0; |
| 1744 | |
| 1745 | for(i=0; z[i]; i++){ |
| 1746 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | |
| 2988 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 2989 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 2990 | */ |
| 2991 | int dir_has_ckout_db(const char *zDir){ |
| 2992 | int rc = 0; |
| 2993 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 2994 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 2995 | rc = 1; |
| 2996 | }else{ |
| 2997 | fossil_free(zCkoutDb); |
| 2998 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 2999 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3000 | rc = 2; |
| 3001 | } |
| 3002 | } |
| 3003 | fossil_free(zCkoutDb); |
| 3004 | return rc; |
| 3005 | } |
| 3006 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -1337,25 +1337,37 @@ | |
| 1337 | } |
| 1338 | } |
| 1339 | |
| 1340 | /* |
| 1341 | ** Compute a canonical pathname for a file or directory. |
| 1342 | ** |
| 1343 | ** * Make the name absolute if it is relative. |
| 1344 | ** * Remove redundant / characters |
| 1345 | ** * Remove all /./ path elements. |
| 1346 | ** * Convert /A/../ to just / |
| 1347 | ** * On windows, add the drive letter prefix. |
| 1348 | ** |
| 1349 | ** If the slash parameter is non-zero, the trailing slash, if any, |
| 1350 | ** is retained. |
| 1351 | ** |
| 1352 | ** See also: file_canonical_name_dup() |
| 1353 | */ |
| 1354 | void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ |
| 1355 | char zPwd[2000]; |
| 1356 | blob_zero(pOut); |
| 1357 | if( file_is_absolute_path(zOrigName) ){ |
| 1358 | #if defined(_WIN32) |
| 1359 | if( fossil_isdirsep(zOrigName[0]) ){ |
| 1360 | /* Add the drive letter to the full pathname */ |
| 1361 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1362 | blob_appendf(pOut, "%.2s%/", zPwd, zOrigName); |
| 1363 | }else |
| 1364 | #endif |
| 1365 | { |
| 1366 | blob_appendf(pOut, "%/", zOrigName); |
| 1367 | } |
| 1368 | }else{ |
| 1369 | file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); |
| 1370 | if( zPwd[0]=='/' && strlen(zPwd)==1 ){ |
| 1371 | /* when on '/', don't add an extra '/' */ |
| 1372 | if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ |
| 1373 | /* '.' when on '/' mean '/' */ |
| @@ -1679,10 +1691,13 @@ | |
| 1691 | g.allowSymlinks = !is_false(zAllow); |
| 1692 | } |
| 1693 | if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; |
| 1694 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1695 | fossil_print("local-root = [%s]\n", zRoot); |
| 1696 | if( g.db==0 ) sqlite3_open(":memory:", &g.db); |
| 1697 | sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, |
| 1698 | file_inode_sql_func, 0, 0); |
| 1699 | for(i=2; i<g.argc; i++){ |
| 1700 | char *z; |
| 1701 | emitFileStat(g.argv[i], slashFlag, resetFlag); |
| 1702 | z = file_canonical_name_dup(g.argv[i]); |
| 1703 | fossil_print(" file_canonical_name = %s\n", z); |
| @@ -1692,10 +1707,13 @@ | |
| 1707 | }else{ |
| 1708 | int n = file_nondir_objects_on_path(zRoot, z); |
| 1709 | fossil_print("%.*s\n", n, z); |
| 1710 | } |
| 1711 | fossil_free(z); |
| 1712 | z = db_text(0, "SELECT inode(%Q)", g.argv[i]); |
| 1713 | fossil_print(" file_inode_sql_func = \"%s\"\n", z); |
| 1714 | fossil_free(z); |
| 1715 | } |
| 1716 | } |
| 1717 | |
| 1718 | /* |
| 1719 | ** COMMAND: test-canonical-name |
| @@ -1734,13 +1752,15 @@ | |
| 1752 | ** Canonical names are full pathnames using "/" not "\" and which |
| 1753 | ** contain no "/./" or "/../" terms. |
| 1754 | */ |
| 1755 | int file_is_canonical(const char *z){ |
| 1756 | int i; |
| 1757 | if( |
| 1758 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1759 | !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2]) |
| 1760 | #else |
| 1761 | z[0]!='/' |
| 1762 | #endif |
| 1763 | ) return 0; |
| 1764 | |
| 1765 | for(i=0; z[i]; i++){ |
| 1766 | if( z[i]=='\\' ) return 0; |
| @@ -2988,18 +3008,84 @@ | |
| 3008 | ** Returns 1 if the given directory contains a file named .fslckout, 2 |
| 3009 | ** if it contains a file named _FOSSIL_, else returns 0. |
| 3010 | */ |
| 3011 | int dir_has_ckout_db(const char *zDir){ |
| 3012 | int rc = 0; |
| 3013 | i64 sz; |
| 3014 | char * zCkoutDb = mprintf("%//.fslckout", zDir); |
| 3015 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3016 | rc = 1; |
| 3017 | }else{ |
| 3018 | fossil_free(zCkoutDb); |
| 3019 | zCkoutDb = mprintf("%//_FOSSIL_", zDir); |
| 3020 | if(file_isfile(zCkoutDb, ExtFILE)){ |
| 3021 | rc = 2; |
| 3022 | } |
| 3023 | } |
| 3024 | if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){ |
| 3025 | rc = 0; |
| 3026 | } |
| 3027 | fossil_free(zCkoutDb); |
| 3028 | return rc; |
| 3029 | } |
| 3030 | |
| 3031 | /* |
| 3032 | ** This is the implementation of inode(FILENAME) SQL function. |
| 3033 | ** |
| 3034 | ** dev_inode(FILENAME) returns a string. If FILENAME exists and is |
| 3035 | ** a regular file, then the return string is of the form: |
| 3036 | ** |
| 3037 | ** DEV/INODE |
| 3038 | ** |
| 3039 | ** Where DEV and INODE are the device number and inode number for |
| 3040 | ** the file. On Windows, the volume serial number (DEV) and file |
| 3041 | ** identifier (INODE) are used to compute the value, see comments |
| 3042 | ** on the win32_file_id() function. |
| 3043 | ** |
| 3044 | ** If FILENAME does not exist, then the return is an empty string. |
| 3045 | ** |
| 3046 | ** The value of inode() can be used to eliminate files from a list |
| 3047 | ** that have duplicates because they have differing names due to links. |
| 3048 | ** |
| 3049 | ** Code that wants to use this SQL function needs to first register |
| 3050 | ** it using a call such as the following: |
| 3051 | ** |
| 3052 | ** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0, |
| 3053 | ** file_inode_sql_func, 0, 0); |
| 3054 | */ |
| 3055 | void file_inode_sql_func( |
| 3056 | sqlite3_context *context, |
| 3057 | int argc, |
| 3058 | sqlite3_value **argv |
| 3059 | ){ |
| 3060 | const char *zFilename; |
| 3061 | assert( argc==1 ); |
| 3062 | zFilename = (const char*)sqlite3_value_text(argv[0]); |
| 3063 | if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){ |
| 3064 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3065 | return; |
| 3066 | } |
| 3067 | #if defined(_WIN32) |
| 3068 | { |
| 3069 | char *zFileId = win32_file_id(zFilename); |
| 3070 | if( zFileId ){ |
| 3071 | sqlite3_result_text(context, zFileId, -1, fossil_free); |
| 3072 | }else{ |
| 3073 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3074 | } |
| 3075 | } |
| 3076 | #else |
| 3077 | { |
| 3078 | struct stat buf; |
| 3079 | int rc; |
| 3080 | memset(&buf, 0, sizeof(buf)); |
| 3081 | rc = stat(zFilename, &buf); |
| 3082 | if( rc ){ |
| 3083 | sqlite3_result_text(context, "", 0, SQLITE_STATIC); |
| 3084 | }else{ |
| 3085 | sqlite3_result_text(context, |
| 3086 | mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1, |
| 3087 | fossil_free); |
| 3088 | } |
| 3089 | } |
| 3090 | #endif |
| 3091 | } |
| 3092 |
+60
-35
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -1761,20 +1761,35 @@ | ||
| 1761 | 1761 | @ </form> |
| 1762 | 1762 | forum_emit_js(); |
| 1763 | 1763 | style_finish_page(); |
| 1764 | 1764 | } |
| 1765 | 1765 | |
| 1766 | +/* | |
| 1767 | +** SETTING: forum-close-policy boolean default=off | |
| 1768 | +** If true, forum moderators may close/re-open forum posts, and reply | |
| 1769 | +** to closed posts. If false, only administrators may do so. Note that | |
| 1770 | +** this only affects the forum web UI, not post-closing tags which | |
| 1771 | +** arrive via the command-line or from synchronization with a remote. | |
| 1772 | +*/ | |
| 1773 | +/* | |
| 1774 | +** SETTING: forum-title width=20 default=Forum | |
| 1775 | +** This is the name or "title" of the Forum for this repository. The | |
| 1776 | +** default is just "Forum". But in some setups, admins might want to | |
| 1777 | +** change it to "Developer Forum" or "User Forum" or whatever other name | |
| 1778 | +** seems more appropriate for the particular usage. | |
| 1779 | +*/ | |
| 1780 | + | |
| 1766 | 1781 | /* |
| 1767 | 1782 | ** WEBPAGE: setup_forum |
| 1768 | 1783 | ** |
| 1769 | 1784 | ** Forum configuration and metrics. |
| 1770 | 1785 | */ |
| 1771 | 1786 | void forum_setup(void){ |
| 1772 | 1787 | /* boolean config settings specific to the forum. */ |
| 1773 | - const char * zSettingsBool[] = { | |
| 1774 | - "forum-close-policy", | |
| 1775 | - NULL /* sentinel entry */ | |
| 1788 | + static const char *azForumSettings[] = { | |
| 1789 | + "forum-close-policy", | |
| 1790 | + "forum-title", | |
| 1776 | 1791 | }; |
| 1777 | 1792 | |
| 1778 | 1793 | login_check_credentials(); |
| 1779 | 1794 | if( !g.perm.Setup ){ |
| 1780 | 1795 | login_needed(g.anon.Setup); |
| @@ -1789,93 +1804,102 @@ | ||
| 1789 | 1804 | @ <p><a href='%R/forum'>Forum posts</a>: |
| 1790 | 1805 | @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p> |
| 1791 | 1806 | } |
| 1792 | 1807 | |
| 1793 | 1808 | @ <h2>Supervisors</h2> |
| 1794 | - @ <p>Users with capabilities 's', 'a', or '6'.</p> | |
| 1795 | 1809 | { |
| 1796 | 1810 | Stmt q = empty_Stmt; |
| 1797 | - int nRows = 0; | |
| 1798 | 1811 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1799 | 1812 | "WHERE cap GLOB '*[as6]*' ORDER BY login"); |
| 1800 | 1813 | @ <table class='bordered'> |
| 1801 | 1814 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1802 | 1815 | @ <tbody> |
| 1803 | 1816 | while( SQLITE_ROW==db_step(&q) ){ |
| 1804 | 1817 | const int iUid = db_column_int(&q, 0); |
| 1805 | 1818 | const char *zUser = db_column_text(&q, 1); |
| 1806 | 1819 | const char *zCap = db_column_text(&q, 2); |
| 1807 | - ++nRows; | |
| 1808 | 1820 | @ <tr> |
| 1809 | 1821 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1810 | 1822 | @ <td>(%h(zCap))</td> |
| 1811 | 1823 | @ </tr> |
| 1812 | 1824 | } |
| 1813 | 1825 | db_finalize(&q); |
| 1814 | 1826 | @</tbody></table> |
| 1815 | - if( 0==nRows ){ | |
| 1816 | - @ No supervisors | |
| 1817 | - }else{ | |
| 1818 | - @ %d(nRows) supervisor(s) | |
| 1819 | - } | |
| 1820 | 1827 | } |
| 1821 | 1828 | |
| 1822 | 1829 | @ <h2>Moderators</h2> |
| 1823 | - @ <p>Users with capability '5'.</p> | |
| 1824 | - { | |
| 1830 | + if( db_int(0, "SELECT count(*) FROM user " | |
| 1831 | + " WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){ | |
| 1832 | + @ <p>No non-supervisor moderators | |
| 1833 | + }else{ | |
| 1825 | 1834 | Stmt q = empty_Stmt; |
| 1826 | - int nRows = 0; | |
| 1827 | 1835 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1828 | - "WHERE cap GLOB '*5*' ORDER BY login"); | |
| 1836 | + "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'" | |
| 1837 | + " ORDER BY login"); | |
| 1829 | 1838 | @ <table class='bordered'> |
| 1830 | 1839 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1831 | 1840 | @ <tbody> |
| 1832 | 1841 | while( SQLITE_ROW==db_step(&q) ){ |
| 1833 | 1842 | const int iUid = db_column_int(&q, 0); |
| 1834 | 1843 | const char *zUser = db_column_text(&q, 1); |
| 1835 | 1844 | const char *zCap = db_column_text(&q, 2); |
| 1836 | - ++nRows; | |
| 1837 | 1845 | @ <tr> |
| 1838 | 1846 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1839 | 1847 | @ <td>(%h(zCap))</td> |
| 1840 | 1848 | @ </tr> |
| 1841 | 1849 | } |
| 1842 | 1850 | db_finalize(&q); |
| 1843 | 1851 | @ </tbody></table> |
| 1844 | - if( 0==nRows ){ | |
| 1845 | - @ No non-supervisor moderators | |
| 1846 | - }else{ | |
| 1847 | - @ %d(nRows) moderator(s) | |
| 1848 | - } | |
| 1849 | 1852 | } |
| 1850 | 1853 | |
| 1851 | 1854 | @ <h2>Settings</h2> |
| 1852 | - @ <p>Configuration settings specific to the forum.</p> | |
| 1853 | 1855 | if( P("submit") && cgi_csrf_safe(2) ){ |
| 1854 | 1856 | int i = 0; |
| 1855 | - const char *zSetting; | |
| 1856 | 1857 | db_begin_transaction(); |
| 1857 | - while( (zSetting = zSettingsBool[i++]) ){ | |
| 1858 | - const char *z = P(zSetting); | |
| 1859 | - if( !z || !z[0] ) z = "off"; | |
| 1860 | - db_set(zSetting/*works-like:"x"*/, z, 0); | |
| 1858 | + for(i=0; i<ArraySize(azForumSettings); i++){ | |
| 1859 | + char zQP[4]; | |
| 1860 | + const char *z; | |
| 1861 | + const Setting *pSetting = setting_find(azForumSettings[i]); | |
| 1862 | + if( pSetting==0 ) continue; | |
| 1863 | + zQP[0] = 'a'+i; | |
| 1864 | + zQP[1] = zQP[0]; | |
| 1865 | + zQP[2] = 0; | |
| 1866 | + z = P(zQP); | |
| 1867 | + if( z==0 || z[0]==0 ) continue; | |
| 1868 | + db_set(pSetting->name/*works-like:"x"*/, z, 0); | |
| 1861 | 1869 | } |
| 1862 | 1870 | db_end_transaction(0); |
| 1863 | 1871 | @ <p><em>Settings saved.</em></p> |
| 1864 | 1872 | } |
| 1865 | 1873 | { |
| 1866 | 1874 | int i = 0; |
| 1867 | - const char *zSetting; | |
| 1868 | 1875 | @ <form action="%R/setup_forum" method="post"> |
| 1869 | 1876 | login_insert_csrf_secret(); |
| 1870 | 1877 | @ <table class='forum-settings-list'><tbody> |
| 1871 | - while( (zSetting = zSettingsBool[i++]) ){ | |
| 1872 | - @ <tr><td> | |
| 1873 | - onoff_attribute("", zSetting, zSetting/*works-like:"x"*/, 0, 0); | |
| 1874 | - @ </td><td> | |
| 1875 | - @ <a href='%R/help?cmd=%h(zSetting)'>%h(zSetting)</a> | |
| 1876 | - @ </td></tr> | |
| 1878 | + for(i=0; i<ArraySize(azForumSettings); i++){ | |
| 1879 | + char zQP[4]; | |
| 1880 | + const Setting *pSetting = setting_find(azForumSettings[i]); | |
| 1881 | + if( pSetting==0 ) continue; | |
| 1882 | + zQP[0] = 'a'+i; | |
| 1883 | + zQP[1] = zQP[0]; | |
| 1884 | + zQP[2] = 0; | |
| 1885 | + if( pSetting->width==0 ){ | |
| 1886 | + /* Boolean setting */ | |
| 1887 | + @ <tr><td align="right"> | |
| 1888 | + @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1889 | + @ </td><td> | |
| 1890 | + onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0); | |
| 1891 | + @ </td></tr> | |
| 1892 | + }else{ | |
| 1893 | + /* Text value setting */ | |
| 1894 | + @ <tr><td align="right"> | |
| 1895 | + @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: | |
| 1896 | + @ </td><td> | |
| 1897 | + entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/, | |
| 1898 | + pSetting->def, 0); | |
| 1899 | + @ </td></tr> | |
| 1900 | + } | |
| 1877 | 1901 | } |
| 1878 | 1902 | @ </tbody></table> |
| 1879 | 1903 | @ <input type='submit' name='submit' value='Apply changes'> |
| 1880 | 1904 | @ </form> |
| 1881 | 1905 | } |
| @@ -1910,11 +1934,12 @@ | ||
| 1910 | 1934 | login_needed(g.anon.RdForum); |
| 1911 | 1935 | return; |
| 1912 | 1936 | } |
| 1913 | 1937 | cgi_check_for_malice(); |
| 1914 | 1938 | style_set_current_feature("forum"); |
| 1915 | - style_header( "%s", isSearch ? "Forum Search Results" : "Forum" ); | |
| 1939 | + style_header("%s%s", db_get("forum-title","Forum"), | |
| 1940 | + isSearch ? " Search Results" : ""); | |
| 1916 | 1941 | style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx"); |
| 1917 | 1942 | if( g.perm.WrForum ){ |
| 1918 | 1943 | style_submenu_element("New Thread","%R/forumnew"); |
| 1919 | 1944 | }else{ |
| 1920 | 1945 | /* Can't combine this with previous case using the ternary operator |
| 1921 | 1946 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -1761,20 +1761,35 @@ | |
| 1761 | @ </form> |
| 1762 | forum_emit_js(); |
| 1763 | style_finish_page(); |
| 1764 | } |
| 1765 | |
| 1766 | /* |
| 1767 | ** WEBPAGE: setup_forum |
| 1768 | ** |
| 1769 | ** Forum configuration and metrics. |
| 1770 | */ |
| 1771 | void forum_setup(void){ |
| 1772 | /* boolean config settings specific to the forum. */ |
| 1773 | const char * zSettingsBool[] = { |
| 1774 | "forum-close-policy", |
| 1775 | NULL /* sentinel entry */ |
| 1776 | }; |
| 1777 | |
| 1778 | login_check_credentials(); |
| 1779 | if( !g.perm.Setup ){ |
| 1780 | login_needed(g.anon.Setup); |
| @@ -1789,93 +1804,102 @@ | |
| 1789 | @ <p><a href='%R/forum'>Forum posts</a>: |
| 1790 | @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p> |
| 1791 | } |
| 1792 | |
| 1793 | @ <h2>Supervisors</h2> |
| 1794 | @ <p>Users with capabilities 's', 'a', or '6'.</p> |
| 1795 | { |
| 1796 | Stmt q = empty_Stmt; |
| 1797 | int nRows = 0; |
| 1798 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1799 | "WHERE cap GLOB '*[as6]*' ORDER BY login"); |
| 1800 | @ <table class='bordered'> |
| 1801 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1802 | @ <tbody> |
| 1803 | while( SQLITE_ROW==db_step(&q) ){ |
| 1804 | const int iUid = db_column_int(&q, 0); |
| 1805 | const char *zUser = db_column_text(&q, 1); |
| 1806 | const char *zCap = db_column_text(&q, 2); |
| 1807 | ++nRows; |
| 1808 | @ <tr> |
| 1809 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1810 | @ <td>(%h(zCap))</td> |
| 1811 | @ </tr> |
| 1812 | } |
| 1813 | db_finalize(&q); |
| 1814 | @</tbody></table> |
| 1815 | if( 0==nRows ){ |
| 1816 | @ No supervisors |
| 1817 | }else{ |
| 1818 | @ %d(nRows) supervisor(s) |
| 1819 | } |
| 1820 | } |
| 1821 | |
| 1822 | @ <h2>Moderators</h2> |
| 1823 | @ <p>Users with capability '5'.</p> |
| 1824 | { |
| 1825 | Stmt q = empty_Stmt; |
| 1826 | int nRows = 0; |
| 1827 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1828 | "WHERE cap GLOB '*5*' ORDER BY login"); |
| 1829 | @ <table class='bordered'> |
| 1830 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1831 | @ <tbody> |
| 1832 | while( SQLITE_ROW==db_step(&q) ){ |
| 1833 | const int iUid = db_column_int(&q, 0); |
| 1834 | const char *zUser = db_column_text(&q, 1); |
| 1835 | const char *zCap = db_column_text(&q, 2); |
| 1836 | ++nRows; |
| 1837 | @ <tr> |
| 1838 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1839 | @ <td>(%h(zCap))</td> |
| 1840 | @ </tr> |
| 1841 | } |
| 1842 | db_finalize(&q); |
| 1843 | @ </tbody></table> |
| 1844 | if( 0==nRows ){ |
| 1845 | @ No non-supervisor moderators |
| 1846 | }else{ |
| 1847 | @ %d(nRows) moderator(s) |
| 1848 | } |
| 1849 | } |
| 1850 | |
| 1851 | @ <h2>Settings</h2> |
| 1852 | @ <p>Configuration settings specific to the forum.</p> |
| 1853 | if( P("submit") && cgi_csrf_safe(2) ){ |
| 1854 | int i = 0; |
| 1855 | const char *zSetting; |
| 1856 | db_begin_transaction(); |
| 1857 | while( (zSetting = zSettingsBool[i++]) ){ |
| 1858 | const char *z = P(zSetting); |
| 1859 | if( !z || !z[0] ) z = "off"; |
| 1860 | db_set(zSetting/*works-like:"x"*/, z, 0); |
| 1861 | } |
| 1862 | db_end_transaction(0); |
| 1863 | @ <p><em>Settings saved.</em></p> |
| 1864 | } |
| 1865 | { |
| 1866 | int i = 0; |
| 1867 | const char *zSetting; |
| 1868 | @ <form action="%R/setup_forum" method="post"> |
| 1869 | login_insert_csrf_secret(); |
| 1870 | @ <table class='forum-settings-list'><tbody> |
| 1871 | while( (zSetting = zSettingsBool[i++]) ){ |
| 1872 | @ <tr><td> |
| 1873 | onoff_attribute("", zSetting, zSetting/*works-like:"x"*/, 0, 0); |
| 1874 | @ </td><td> |
| 1875 | @ <a href='%R/help?cmd=%h(zSetting)'>%h(zSetting)</a> |
| 1876 | @ </td></tr> |
| 1877 | } |
| 1878 | @ </tbody></table> |
| 1879 | @ <input type='submit' name='submit' value='Apply changes'> |
| 1880 | @ </form> |
| 1881 | } |
| @@ -1910,11 +1934,12 @@ | |
| 1910 | login_needed(g.anon.RdForum); |
| 1911 | return; |
| 1912 | } |
| 1913 | cgi_check_for_malice(); |
| 1914 | style_set_current_feature("forum"); |
| 1915 | style_header( "%s", isSearch ? "Forum Search Results" : "Forum" ); |
| 1916 | style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx"); |
| 1917 | if( g.perm.WrForum ){ |
| 1918 | style_submenu_element("New Thread","%R/forumnew"); |
| 1919 | }else{ |
| 1920 | /* Can't combine this with previous case using the ternary operator |
| 1921 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -1761,20 +1761,35 @@ | |
| 1761 | @ </form> |
| 1762 | forum_emit_js(); |
| 1763 | style_finish_page(); |
| 1764 | } |
| 1765 | |
| 1766 | /* |
| 1767 | ** SETTING: forum-close-policy boolean default=off |
| 1768 | ** If true, forum moderators may close/re-open forum posts, and reply |
| 1769 | ** to closed posts. If false, only administrators may do so. Note that |
| 1770 | ** this only affects the forum web UI, not post-closing tags which |
| 1771 | ** arrive via the command-line or from synchronization with a remote. |
| 1772 | */ |
| 1773 | /* |
| 1774 | ** SETTING: forum-title width=20 default=Forum |
| 1775 | ** This is the name or "title" of the Forum for this repository. The |
| 1776 | ** default is just "Forum". But in some setups, admins might want to |
| 1777 | ** change it to "Developer Forum" or "User Forum" or whatever other name |
| 1778 | ** seems more appropriate for the particular usage. |
| 1779 | */ |
| 1780 | |
| 1781 | /* |
| 1782 | ** WEBPAGE: setup_forum |
| 1783 | ** |
| 1784 | ** Forum configuration and metrics. |
| 1785 | */ |
| 1786 | void forum_setup(void){ |
| 1787 | /* boolean config settings specific to the forum. */ |
| 1788 | static const char *azForumSettings[] = { |
| 1789 | "forum-close-policy", |
| 1790 | "forum-title", |
| 1791 | }; |
| 1792 | |
| 1793 | login_check_credentials(); |
| 1794 | if( !g.perm.Setup ){ |
| 1795 | login_needed(g.anon.Setup); |
| @@ -1789,93 +1804,102 @@ | |
| 1804 | @ <p><a href='%R/forum'>Forum posts</a>: |
| 1805 | @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p> |
| 1806 | } |
| 1807 | |
| 1808 | @ <h2>Supervisors</h2> |
| 1809 | { |
| 1810 | Stmt q = empty_Stmt; |
| 1811 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1812 | "WHERE cap GLOB '*[as6]*' ORDER BY login"); |
| 1813 | @ <table class='bordered'> |
| 1814 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1815 | @ <tbody> |
| 1816 | while( SQLITE_ROW==db_step(&q) ){ |
| 1817 | const int iUid = db_column_int(&q, 0); |
| 1818 | const char *zUser = db_column_text(&q, 1); |
| 1819 | const char *zCap = db_column_text(&q, 2); |
| 1820 | @ <tr> |
| 1821 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1822 | @ <td>(%h(zCap))</td> |
| 1823 | @ </tr> |
| 1824 | } |
| 1825 | db_finalize(&q); |
| 1826 | @</tbody></table> |
| 1827 | } |
| 1828 | |
| 1829 | @ <h2>Moderators</h2> |
| 1830 | if( db_int(0, "SELECT count(*) FROM user " |
| 1831 | " WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){ |
| 1832 | @ <p>No non-supervisor moderators |
| 1833 | }else{ |
| 1834 | Stmt q = empty_Stmt; |
| 1835 | db_prepare(&q, "SELECT uid, login, cap FROM user " |
| 1836 | "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'" |
| 1837 | " ORDER BY login"); |
| 1838 | @ <table class='bordered'> |
| 1839 | @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead> |
| 1840 | @ <tbody> |
| 1841 | while( SQLITE_ROW==db_step(&q) ){ |
| 1842 | const int iUid = db_column_int(&q, 0); |
| 1843 | const char *zUser = db_column_text(&q, 1); |
| 1844 | const char *zCap = db_column_text(&q, 2); |
| 1845 | @ <tr> |
| 1846 | @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td> |
| 1847 | @ <td>(%h(zCap))</td> |
| 1848 | @ </tr> |
| 1849 | } |
| 1850 | db_finalize(&q); |
| 1851 | @ </tbody></table> |
| 1852 | } |
| 1853 | |
| 1854 | @ <h2>Settings</h2> |
| 1855 | if( P("submit") && cgi_csrf_safe(2) ){ |
| 1856 | int i = 0; |
| 1857 | db_begin_transaction(); |
| 1858 | for(i=0; i<ArraySize(azForumSettings); i++){ |
| 1859 | char zQP[4]; |
| 1860 | const char *z; |
| 1861 | const Setting *pSetting = setting_find(azForumSettings[i]); |
| 1862 | if( pSetting==0 ) continue; |
| 1863 | zQP[0] = 'a'+i; |
| 1864 | zQP[1] = zQP[0]; |
| 1865 | zQP[2] = 0; |
| 1866 | z = P(zQP); |
| 1867 | if( z==0 || z[0]==0 ) continue; |
| 1868 | db_set(pSetting->name/*works-like:"x"*/, z, 0); |
| 1869 | } |
| 1870 | db_end_transaction(0); |
| 1871 | @ <p><em>Settings saved.</em></p> |
| 1872 | } |
| 1873 | { |
| 1874 | int i = 0; |
| 1875 | @ <form action="%R/setup_forum" method="post"> |
| 1876 | login_insert_csrf_secret(); |
| 1877 | @ <table class='forum-settings-list'><tbody> |
| 1878 | for(i=0; i<ArraySize(azForumSettings); i++){ |
| 1879 | char zQP[4]; |
| 1880 | const Setting *pSetting = setting_find(azForumSettings[i]); |
| 1881 | if( pSetting==0 ) continue; |
| 1882 | zQP[0] = 'a'+i; |
| 1883 | zQP[1] = zQP[0]; |
| 1884 | zQP[2] = 0; |
| 1885 | if( pSetting->width==0 ){ |
| 1886 | /* Boolean setting */ |
| 1887 | @ <tr><td align="right"> |
| 1888 | @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1889 | @ </td><td> |
| 1890 | onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0); |
| 1891 | @ </td></tr> |
| 1892 | }else{ |
| 1893 | /* Text value setting */ |
| 1894 | @ <tr><td align="right"> |
| 1895 | @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>: |
| 1896 | @ </td><td> |
| 1897 | entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/, |
| 1898 | pSetting->def, 0); |
| 1899 | @ </td></tr> |
| 1900 | } |
| 1901 | } |
| 1902 | @ </tbody></table> |
| 1903 | @ <input type='submit' name='submit' value='Apply changes'> |
| 1904 | @ </form> |
| 1905 | } |
| @@ -1910,11 +1934,12 @@ | |
| 1934 | login_needed(g.anon.RdForum); |
| 1935 | return; |
| 1936 | } |
| 1937 | cgi_check_for_malice(); |
| 1938 | style_set_current_feature("forum"); |
| 1939 | style_header("%s%s", db_get("forum-title","Forum"), |
| 1940 | isSearch ? " Search Results" : ""); |
| 1941 | style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx"); |
| 1942 | if( g.perm.WrForum ){ |
| 1943 | style_submenu_element("New Thread","%R/forumnew"); |
| 1944 | }else{ |
| 1945 | /* Can't combine this with previous case using the ternary operator |
| 1946 |
+80
-26
| --- src/fossil.diff.js | ||
| +++ src/fossil.diff.js | ||
| @@ -1,28 +1,93 @@ | ||
| 1 | 1 | /** |
| 2 | 2 | diff-related JS APIs for fossil. |
| 3 | 3 | */ |
| 4 | 4 | "use strict"; |
| 5 | +/* Locate the UI element (if any) into which we can inject some diff-related | |
| 6 | + UI controls. */ | |
| 7 | +window.fossil.onPageLoad(function(){ | |
| 8 | + const potentialParents = window.fossil.page.diffControlContainers = [ | |
| 9 | + /* CSS selectors for possible parents for injected diff-related UI | |
| 10 | + controls. */ | |
| 11 | + /* Put the most likely pages at the end, as array.pop() is more | |
| 12 | + efficient than array.shift() (see loop below). */ | |
| 13 | + /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons', | |
| 14 | + /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons', | |
| 15 | + /* /fdiff */ 'body.fdiff form div.submenu', | |
| 16 | + /* /vdiff */ 'body.vdiff form div.submenu', | |
| 17 | + /* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu' | |
| 18 | + ]; | |
| 19 | + window.fossil.page.diffControlContainer = undefined; | |
| 20 | + while( potentialParents.length ){ | |
| 21 | + if( (window.fossil.page.diffControlContainer | |
| 22 | + = document.querySelector(potentialParents.pop())) ){ | |
| 23 | + break; | |
| 24 | + } | |
| 25 | + } | |
| 26 | +}); | |
| 27 | + | |
| 5 | 28 | window.fossil.onPageLoad(function(){ |
| 6 | 29 | /** |
| 7 | 30 | Adds toggle checkboxes to each file entry in the diff views for |
| 8 | 31 | /info and similar pages. |
| 9 | 32 | */ |
| 33 | + if( !window.fossil.page.diffControlContainer ){ | |
| 34 | + return; | |
| 35 | + } | |
| 10 | 36 | const D = window.fossil.dom; |
| 11 | - const isFdiff = !!document.querySelector('body.fdiff'); | |
| 37 | + const allToggles = [/*collection of all diff-toggle checkboxes*/]; | |
| 38 | + let checkedCount = | |
| 39 | + 0 /* When showing more than one diff, keep track of how many | |
| 40 | + "show/hide" checkboxes are are checked so we can update the | |
| 41 | + "show/hide all" label dynamically. */; | |
| 42 | + let btnAll /* UI control to show/hide all diffs */; | |
| 43 | + /* Install a diff-toggle button for the given diff table element. */ | |
| 12 | 44 | const addToggle = function(diffElem){ |
| 13 | 45 | const sib = diffElem.previousElementSibling, |
| 14 | - btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; | |
| 46 | + ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; | |
| 15 | 47 | if(!sib) return; |
| 16 | - if(isFdiff) sib.parentElement.insertBefore( | |
| 17 | - D.append(D.div(),btn),sib.nextElementSibling); | |
| 18 | - else D.append(sib,btn); | |
| 19 | - btn.addEventListener('click', function(){ | |
| 20 | - diffElem.classList.toggle('hidden'); | |
| 48 | + const lblToggle = D.label(); | |
| 49 | + D.append(lblToggle, ckbox, D.text(" show/hide ")); | |
| 50 | + const wrapper = D.append(D.span(), lblToggle); | |
| 51 | + allToggles.push(ckbox); | |
| 52 | + ++checkedCount; | |
| 53 | + D.append(sib, D.append(wrapper, lblToggle)); | |
| 54 | + ckbox.addEventListener('change', function(){ | |
| 55 | + diffElem.classList[this.checked ? 'remove' : 'add']('hidden'); | |
| 56 | + if(btnAll){ | |
| 57 | + checkedCount += (this.checked ? 1 : -1); | |
| 58 | + btnAll.innerText = (checkedCount < allToggles.length) | |
| 59 | + ? "Show diffs" : "Hide diffs"; | |
| 60 | + } | |
| 61 | + }, false); | |
| 62 | + }; | |
| 63 | + if( !document.querySelector('body.fdiff') ){ | |
| 64 | + /* Don't show the diff toggle button for /fdiff because it only | |
| 65 | + has a single file to show (and also a different DOM layout). */ | |
| 66 | + document.querySelectorAll('table.diff').forEach(addToggle); | |
| 67 | + } | |
| 68 | + /** | |
| 69 | + Set up a "toggle all diffs" button which toggles all of the | |
| 70 | + above-installed checkboxes, but only if more than one diff is | |
| 71 | + rendered. | |
| 72 | + */ | |
| 73 | + const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0; | |
| 74 | + if(icm) { | |
| 75 | + btnAll = D.addClass(D.a("#", "Hide diffs"), "button"); | |
| 76 | + D.append( icm, btnAll ); | |
| 77 | + btnAll.addEventListener('click', function(ev){ | |
| 78 | + ev.preventDefault(); | |
| 79 | + ev.stopPropagation(); | |
| 80 | + const show = checkedCount < allToggles.length; | |
| 81 | + for( const ckbox of allToggles ){ | |
| 82 | + /* Toggle all entries to match this new state. We use click() | |
| 83 | + instead of ckbox.checked=... so that the on-change event handler | |
| 84 | + fires. */ | |
| 85 | + if(ckbox.checked!==show) ckbox.click(); | |
| 86 | + } | |
| 21 | 87 | }, false); |
| 22 | - }; | |
| 23 | - document.querySelectorAll('table.diff').forEach(addToggle); | |
| 88 | + } | |
| 24 | 89 | }); |
| 25 | 90 | |
| 26 | 91 | window.fossil.onPageLoad(function(){ |
| 27 | 92 | const F = window.fossil, D = F.dom; |
| 28 | 93 | const Diff = F.diff = { |
| @@ -635,26 +700,15 @@ | ||
| 635 | 700 | /* Look for a parent element to hold the sbs-sync-scroll toggle |
| 636 | 701 | checkbox. This differs per page. If we don't find one, simply |
| 637 | 702 | elide that toggle and use whatever preference the user last |
| 638 | 703 | specified (defaulting to on). */ |
| 639 | 704 | let cbSync /* scroll-sync checkbox */; |
| 640 | - let eToggleParent /* element to put the sync-scroll checkbox in */; | |
| 641 | - const potentialParents = [ /* possible parents for the checkbox */ | |
| 642 | - /* Put the most likely pages at the end, as array.pop() is more | |
| 643 | - efficient than array.shift() (see loop below). */ | |
| 644 | - /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons', | |
| 645 | - /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons', | |
| 646 | - /* /fdiff */ 'body.fdiff form div.submenu', | |
| 647 | - /* /vdiff */ 'body.vdiff form div.submenu', | |
| 648 | - /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu' | |
| 649 | - ]; | |
| 650 | - while( potentialParents.length ){ | |
| 651 | - if( (eToggleParent = document.querySelector(potentialParents.pop())) ){ | |
| 652 | - break; | |
| 653 | - } | |
| 654 | - } | |
| 655 | - const keySbsScroll = 'sync-diff-scroll' /* F.storage key */; | |
| 705 | + let eToggleParent = /* element to put the sync-scroll checkbox in */ | |
| 706 | + document.querySelector('table.diff.splitdiff') | |
| 707 | + ? window.fossil.page.diffControlContainer | |
| 708 | + : undefined; | |
| 709 | + const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */; | |
| 656 | 710 | if( eToggleParent ){ |
| 657 | 711 | /* Add a checkbox to toggle sbs scroll sync. Remember that in |
| 658 | 712 | order to be UI-consistent in the /vdiff page we have to ensure |
| 659 | 713 | that the checkbox is to the LEFT of of its label. We store the |
| 660 | 714 | sync-scroll preference in F.storage (not a cookie) so that it |
| @@ -661,11 +715,11 @@ | ||
| 661 | 715 | persists across page loads and different apps. */ |
| 662 | 716 | cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true)); |
| 663 | 717 | D.append(eToggleParent, D.append( |
| 664 | 718 | D.addClass(D.create('span'), 'input-with-label'), |
| 665 | 719 | D.append(D.create('label'), |
| 666 | - cbSync, "Sync side-by-side scrolling") | |
| 720 | + cbSync, "Scroll Sync") | |
| 667 | 721 | )); |
| 668 | 722 | cbSync.addEventListener('change', function(e){ |
| 669 | 723 | F.storage.set(keySbsScroll, e.target.checked); |
| 670 | 724 | }); |
| 671 | 725 | } |
| 672 | 726 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -1,28 +1,93 @@ | |
| 1 | /** |
| 2 | diff-related JS APIs for fossil. |
| 3 | */ |
| 4 | "use strict"; |
| 5 | window.fossil.onPageLoad(function(){ |
| 6 | /** |
| 7 | Adds toggle checkboxes to each file entry in the diff views for |
| 8 | /info and similar pages. |
| 9 | */ |
| 10 | const D = window.fossil.dom; |
| 11 | const isFdiff = !!document.querySelector('body.fdiff'); |
| 12 | const addToggle = function(diffElem){ |
| 13 | const sib = diffElem.previousElementSibling, |
| 14 | btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; |
| 15 | if(!sib) return; |
| 16 | if(isFdiff) sib.parentElement.insertBefore( |
| 17 | D.append(D.div(),btn),sib.nextElementSibling); |
| 18 | else D.append(sib,btn); |
| 19 | btn.addEventListener('click', function(){ |
| 20 | diffElem.classList.toggle('hidden'); |
| 21 | }, false); |
| 22 | }; |
| 23 | document.querySelectorAll('table.diff').forEach(addToggle); |
| 24 | }); |
| 25 | |
| 26 | window.fossil.onPageLoad(function(){ |
| 27 | const F = window.fossil, D = F.dom; |
| 28 | const Diff = F.diff = { |
| @@ -635,26 +700,15 @@ | |
| 635 | /* Look for a parent element to hold the sbs-sync-scroll toggle |
| 636 | checkbox. This differs per page. If we don't find one, simply |
| 637 | elide that toggle and use whatever preference the user last |
| 638 | specified (defaulting to on). */ |
| 639 | let cbSync /* scroll-sync checkbox */; |
| 640 | let eToggleParent /* element to put the sync-scroll checkbox in */; |
| 641 | const potentialParents = [ /* possible parents for the checkbox */ |
| 642 | /* Put the most likely pages at the end, as array.pop() is more |
| 643 | efficient than array.shift() (see loop below). */ |
| 644 | /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons', |
| 645 | /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons', |
| 646 | /* /fdiff */ 'body.fdiff form div.submenu', |
| 647 | /* /vdiff */ 'body.vdiff form div.submenu', |
| 648 | /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu' |
| 649 | ]; |
| 650 | while( potentialParents.length ){ |
| 651 | if( (eToggleParent = document.querySelector(potentialParents.pop())) ){ |
| 652 | break; |
| 653 | } |
| 654 | } |
| 655 | const keySbsScroll = 'sync-diff-scroll' /* F.storage key */; |
| 656 | if( eToggleParent ){ |
| 657 | /* Add a checkbox to toggle sbs scroll sync. Remember that in |
| 658 | order to be UI-consistent in the /vdiff page we have to ensure |
| 659 | that the checkbox is to the LEFT of of its label. We store the |
| 660 | sync-scroll preference in F.storage (not a cookie) so that it |
| @@ -661,11 +715,11 @@ | |
| 661 | persists across page loads and different apps. */ |
| 662 | cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true)); |
| 663 | D.append(eToggleParent, D.append( |
| 664 | D.addClass(D.create('span'), 'input-with-label'), |
| 665 | D.append(D.create('label'), |
| 666 | cbSync, "Sync side-by-side scrolling") |
| 667 | )); |
| 668 | cbSync.addEventListener('change', function(e){ |
| 669 | F.storage.set(keySbsScroll, e.target.checked); |
| 670 | }); |
| 671 | } |
| 672 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -1,28 +1,93 @@ | |
| 1 | /** |
| 2 | diff-related JS APIs for fossil. |
| 3 | */ |
| 4 | "use strict"; |
| 5 | /* Locate the UI element (if any) into which we can inject some diff-related |
| 6 | UI controls. */ |
| 7 | window.fossil.onPageLoad(function(){ |
| 8 | const potentialParents = window.fossil.page.diffControlContainers = [ |
| 9 | /* CSS selectors for possible parents for injected diff-related UI |
| 10 | controls. */ |
| 11 | /* Put the most likely pages at the end, as array.pop() is more |
| 12 | efficient than array.shift() (see loop below). */ |
| 13 | /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons', |
| 14 | /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons', |
| 15 | /* /fdiff */ 'body.fdiff form div.submenu', |
| 16 | /* /vdiff */ 'body.vdiff form div.submenu', |
| 17 | /* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu' |
| 18 | ]; |
| 19 | window.fossil.page.diffControlContainer = undefined; |
| 20 | while( potentialParents.length ){ |
| 21 | if( (window.fossil.page.diffControlContainer |
| 22 | = document.querySelector(potentialParents.pop())) ){ |
| 23 | break; |
| 24 | } |
| 25 | } |
| 26 | }); |
| 27 | |
| 28 | window.fossil.onPageLoad(function(){ |
| 29 | /** |
| 30 | Adds toggle checkboxes to each file entry in the diff views for |
| 31 | /info and similar pages. |
| 32 | */ |
| 33 | if( !window.fossil.page.diffControlContainer ){ |
| 34 | return; |
| 35 | } |
| 36 | const D = window.fossil.dom; |
| 37 | const allToggles = [/*collection of all diff-toggle checkboxes*/]; |
| 38 | let checkedCount = |
| 39 | 0 /* When showing more than one diff, keep track of how many |
| 40 | "show/hide" checkboxes are are checked so we can update the |
| 41 | "show/hide all" label dynamically. */; |
| 42 | let btnAll /* UI control to show/hide all diffs */; |
| 43 | /* Install a diff-toggle button for the given diff table element. */ |
| 44 | const addToggle = function(diffElem){ |
| 45 | const sib = diffElem.previousElementSibling, |
| 46 | ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; |
| 47 | if(!sib) return; |
| 48 | const lblToggle = D.label(); |
| 49 | D.append(lblToggle, ckbox, D.text(" show/hide ")); |
| 50 | const wrapper = D.append(D.span(), lblToggle); |
| 51 | allToggles.push(ckbox); |
| 52 | ++checkedCount; |
| 53 | D.append(sib, D.append(wrapper, lblToggle)); |
| 54 | ckbox.addEventListener('change', function(){ |
| 55 | diffElem.classList[this.checked ? 'remove' : 'add']('hidden'); |
| 56 | if(btnAll){ |
| 57 | checkedCount += (this.checked ? 1 : -1); |
| 58 | btnAll.innerText = (checkedCount < allToggles.length) |
| 59 | ? "Show diffs" : "Hide diffs"; |
| 60 | } |
| 61 | }, false); |
| 62 | }; |
| 63 | if( !document.querySelector('body.fdiff') ){ |
| 64 | /* Don't show the diff toggle button for /fdiff because it only |
| 65 | has a single file to show (and also a different DOM layout). */ |
| 66 | document.querySelectorAll('table.diff').forEach(addToggle); |
| 67 | } |
| 68 | /** |
| 69 | Set up a "toggle all diffs" button which toggles all of the |
| 70 | above-installed checkboxes, but only if more than one diff is |
| 71 | rendered. |
| 72 | */ |
| 73 | const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0; |
| 74 | if(icm) { |
| 75 | btnAll = D.addClass(D.a("#", "Hide diffs"), "button"); |
| 76 | D.append( icm, btnAll ); |
| 77 | btnAll.addEventListener('click', function(ev){ |
| 78 | ev.preventDefault(); |
| 79 | ev.stopPropagation(); |
| 80 | const show = checkedCount < allToggles.length; |
| 81 | for( const ckbox of allToggles ){ |
| 82 | /* Toggle all entries to match this new state. We use click() |
| 83 | instead of ckbox.checked=... so that the on-change event handler |
| 84 | fires. */ |
| 85 | if(ckbox.checked!==show) ckbox.click(); |
| 86 | } |
| 87 | }, false); |
| 88 | } |
| 89 | }); |
| 90 | |
| 91 | window.fossil.onPageLoad(function(){ |
| 92 | const F = window.fossil, D = F.dom; |
| 93 | const Diff = F.diff = { |
| @@ -635,26 +700,15 @@ | |
| 700 | /* Look for a parent element to hold the sbs-sync-scroll toggle |
| 701 | checkbox. This differs per page. If we don't find one, simply |
| 702 | elide that toggle and use whatever preference the user last |
| 703 | specified (defaulting to on). */ |
| 704 | let cbSync /* scroll-sync checkbox */; |
| 705 | let eToggleParent = /* element to put the sync-scroll checkbox in */ |
| 706 | document.querySelector('table.diff.splitdiff') |
| 707 | ? window.fossil.page.diffControlContainer |
| 708 | : undefined; |
| 709 | const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */; |
| 710 | if( eToggleParent ){ |
| 711 | /* Add a checkbox to toggle sbs scroll sync. Remember that in |
| 712 | order to be UI-consistent in the /vdiff page we have to ensure |
| 713 | that the checkbox is to the LEFT of of its label. We store the |
| 714 | sync-scroll preference in F.storage (not a cookie) so that it |
| @@ -661,11 +715,11 @@ | |
| 715 | persists across page loads and different apps. */ |
| 716 | cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true)); |
| 717 | D.append(eToggleParent, D.append( |
| 718 | D.addClass(D.create('span'), 'input-with-label'), |
| 719 | D.append(D.create('label'), |
| 720 | cbSync, "Scroll Sync") |
| 721 | )); |
| 722 | cbSync.addEventListener('change', function(e){ |
| 723 | F.storage.set(keySbsScroll, e.target.checked); |
| 724 | }); |
| 725 | } |
| 726 |
+3
-1
| --- src/fossil.dom.js | ||
| +++ src/fossil.dom.js | ||
| @@ -87,11 +87,13 @@ | ||
| 87 | 87 | const rc = document.createElement('label'); |
| 88 | 88 | if(forElem){ |
| 89 | 89 | if(forElem instanceof HTMLElement){ |
| 90 | 90 | forElem = this.attr(forElem, 'id'); |
| 91 | 91 | } |
| 92 | - dom.attr(rc, 'for', forElem); | |
| 92 | + if(forElem){ | |
| 93 | + dom.attr(rc, 'for', forElem); | |
| 94 | + } | |
| 93 | 95 | } |
| 94 | 96 | if(text) this.append(rc, text); |
| 95 | 97 | return rc; |
| 96 | 98 | }; |
| 97 | 99 | /** |
| 98 | 100 |
| --- src/fossil.dom.js | |
| +++ src/fossil.dom.js | |
| @@ -87,11 +87,13 @@ | |
| 87 | const rc = document.createElement('label'); |
| 88 | if(forElem){ |
| 89 | if(forElem instanceof HTMLElement){ |
| 90 | forElem = this.attr(forElem, 'id'); |
| 91 | } |
| 92 | dom.attr(rc, 'for', forElem); |
| 93 | } |
| 94 | if(text) this.append(rc, text); |
| 95 | return rc; |
| 96 | }; |
| 97 | /** |
| 98 |
| --- src/fossil.dom.js | |
| +++ src/fossil.dom.js | |
| @@ -87,11 +87,13 @@ | |
| 87 | const rc = document.createElement('label'); |
| 88 | if(forElem){ |
| 89 | if(forElem instanceof HTMLElement){ |
| 90 | forElem = this.attr(forElem, 'id'); |
| 91 | } |
| 92 | if(forElem){ |
| 93 | dom.attr(rc, 'for', forElem); |
| 94 | } |
| 95 | } |
| 96 | if(text) this.append(rc, text); |
| 97 | return rc; |
| 98 | }; |
| 99 | /** |
| 100 |
+1
-1
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -2132,11 +2132,11 @@ | ||
| 2132 | 2132 | s.value ? 'add' : 'remove' |
| 2133 | 2133 | ]('compact'); |
| 2134 | 2134 | Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus(); |
| 2135 | 2135 | }); |
| 2136 | 2136 | Chat.settings.addListener('edit-ctrl-send',function(s){ |
| 2137 | - const label = (s.value ? "Ctrl-" : "")+"Enter submits message"; | |
| 2137 | + const label = (s.value ? "Ctrl-" : "")+"Enter submits message."; | |
| 2138 | 2138 | Chat.e.inputFields.forEach((e)=>{ |
| 2139 | 2139 | const v = e.dataset.placeholder0 + " " +label; |
| 2140 | 2140 | if(e.isContentEditable) e.dataset.placeholder = v; |
| 2141 | 2141 | else D.attr(e,'placeholder',v); |
| 2142 | 2142 | }); |
| 2143 | 2143 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -2132,11 +2132,11 @@ | |
| 2132 | s.value ? 'add' : 'remove' |
| 2133 | ]('compact'); |
| 2134 | Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus(); |
| 2135 | }); |
| 2136 | Chat.settings.addListener('edit-ctrl-send',function(s){ |
| 2137 | const label = (s.value ? "Ctrl-" : "")+"Enter submits message"; |
| 2138 | Chat.e.inputFields.forEach((e)=>{ |
| 2139 | const v = e.dataset.placeholder0 + " " +label; |
| 2140 | if(e.isContentEditable) e.dataset.placeholder = v; |
| 2141 | else D.attr(e,'placeholder',v); |
| 2142 | }); |
| 2143 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -2132,11 +2132,11 @@ | |
| 2132 | s.value ? 'add' : 'remove' |
| 2133 | ]('compact'); |
| 2134 | Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus(); |
| 2135 | }); |
| 2136 | Chat.settings.addListener('edit-ctrl-send',function(s){ |
| 2137 | const label = (s.value ? "Ctrl-" : "")+"Enter submits message."; |
| 2138 | Chat.e.inputFields.forEach((e)=>{ |
| 2139 | const v = e.dataset.placeholder0 + " " +label; |
| 2140 | if(e.isContentEditable) e.dataset.placeholder = v; |
| 2141 | else D.attr(e,'placeholder',v); |
| 2142 | }); |
| 2143 |
+28
-12
| --- src/graph.c | ||
| +++ src/graph.c | ||
| @@ -495,11 +495,15 @@ | ||
| 495 | 495 | ** |
| 496 | 496 | ** TIMELINE_DISJOINT: Omit descenders |
| 497 | 497 | ** TIMELINE_FILLGAPS: Use step-children |
| 498 | 498 | ** TIMELINE_XMERGE: Omit off-graph merge lines |
| 499 | 499 | */ |
| 500 | -void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){ | |
| 500 | +void graph_finish( | |
| 501 | + GraphContext *p, /* The graph to be laid out */ | |
| 502 | + Matcher *pLeftBranch, /* Compares true for left-most branch */ | |
| 503 | + u32 tmFlags /* TIMELINE flags */ | |
| 504 | +){ | |
| 501 | 505 | GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent; |
| 502 | 506 | int i, j; |
| 503 | 507 | u64 mask; |
| 504 | 508 | int hasDup = 0; /* True if one or more isDup entries */ |
| 505 | 509 | const char *zTrunk; |
| @@ -963,12 +967,12 @@ | ||
| 963 | 967 | } |
| 964 | 968 | } |
| 965 | 969 | |
| 966 | 970 | /* |
| 967 | 971 | ** Compute the rail mapping that tries to put the branch named |
| 968 | - ** zLeftBranch at the left margin. Other branches that merge | |
| 969 | - ** with zLeftBranch are to the right with merge rails in between. | |
| 972 | + ** pLeftBranch at the left margin. Other branches that merge | |
| 973 | + ** with pLeftBranch are to the right with merge rails in between. | |
| 970 | 974 | ** |
| 971 | 975 | ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail. |
| 972 | 976 | ** |
| 973 | 977 | ** Do not move rails around if there are timewarps, because that can |
| 974 | 978 | ** seriously mess up the display of timewarps. Timewarps should be |
| @@ -975,10 +979,11 @@ | ||
| 975 | 979 | ** rare so this should not be a serious limitation to the algorithm. |
| 976 | 980 | */ |
| 977 | 981 | aMap = p->aiRailMap; |
| 978 | 982 | for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */ |
| 979 | 983 | if( nTimewarp==0 ){ |
| 984 | + int kk; | |
| 980 | 985 | /* Priority bits: |
| 981 | 986 | ** |
| 982 | 987 | ** 0x04 The preferred branch |
| 983 | 988 | ** |
| 984 | 989 | ** 0x02 A merge rail - a rail that contains merge lines into |
| @@ -986,17 +991,20 @@ | ||
| 986 | 991 | ** is defined. This improves the display of r=BRANCH |
| 987 | 992 | ** options to /timeline. |
| 988 | 993 | ** |
| 989 | 994 | ** 0x01 A rail that merges with the preferred branch |
| 990 | 995 | */ |
| 991 | - u8 aPriority[GR_MAX_RAIL]; | |
| 992 | - memset(aPriority, 0, p->mxRail+1); | |
| 993 | - if( zLeftBranch ){ | |
| 994 | - char *zLeft = persistBranchName(p, zLeftBranch); | |
| 996 | + u16 aPriority[GR_MAX_RAIL]; | |
| 997 | + int mxMatch = 0; | |
| 998 | + memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0])); | |
| 999 | + if( pLeftBranch ){ | |
| 995 | 1000 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 996 | - if( pRow->zBranch==zLeft ){ | |
| 997 | - aPriority[pRow->iRail] |= 4; | |
| 1001 | + int iMatch = match_text(pLeftBranch, pRow->zBranch); | |
| 1002 | + if( iMatch>0 ){ | |
| 1003 | + if( iMatch>10 ) iMatch = 10; | |
| 1004 | + aPriority[pRow->iRail] |= 1<<(iMatch+1); | |
| 1005 | + if( mxMatch<iMatch ) mxMatch = iMatch; | |
| 998 | 1006 | for(i=0; i<=p->mxRail; i++){ |
| 999 | 1007 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1000 | 1008 | } |
| 1001 | 1009 | if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1; |
| 1002 | 1010 | } |
| @@ -1007,10 +1015,11 @@ | ||
| 1007 | 1015 | } |
| 1008 | 1016 | } |
| 1009 | 1017 | }else{ |
| 1010 | 1018 | j = 1; |
| 1011 | 1019 | aPriority[0] = 4; |
| 1020 | + mxMatch = 1; | |
| 1012 | 1021 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 1013 | 1022 | if( pRow->iRail==0 ){ |
| 1014 | 1023 | for(i=0; i<=p->mxRail; i++){ |
| 1015 | 1024 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1016 | 1025 | } |
| @@ -1020,17 +1029,24 @@ | ||
| 1020 | 1029 | } |
| 1021 | 1030 | |
| 1022 | 1031 | #if 0 |
| 1023 | 1032 | fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail); |
| 1024 | 1033 | fprintf(stderr,"Priority:"); |
| 1025 | - for(i=0; i<=p->mxRail; i++) fprintf(stderr," %d", aPriority[i]); | |
| 1034 | + for(i=0; i<=p->mxRail; i++){ | |
| 1035 | + fprintf(stderr," %x.%x", | |
| 1036 | + aPriority[i]/4, aPriority[i]&3); | |
| 1037 | + } | |
| 1026 | 1038 | fprintf(stderr,"\n"); |
| 1027 | 1039 | #endif |
| 1028 | 1040 | |
| 1029 | 1041 | j = 0; |
| 1030 | - for(i=0; i<=p->mxRail; i++){ | |
| 1031 | - if( aPriority[i]>=4 ) aMap[i] = j++; | |
| 1042 | + for(kk=4; kk<=1<<(mxMatch+1); kk*=2){ | |
| 1043 | + for(i=0; i<=p->mxRail; i++){ | |
| 1044 | + if( aPriority[i]>=kk && aPriority[i]<kk*2 ){ | |
| 1045 | + aMap[i] = j++; | |
| 1046 | + } | |
| 1047 | + } | |
| 1032 | 1048 | } |
| 1033 | 1049 | for(i=p->mxRail; i>=0; i--){ |
| 1034 | 1050 | if( aPriority[i]==3 ) aMap[i] = j++; |
| 1035 | 1051 | } |
| 1036 | 1052 | for(i=0; i<=p->mxRail; i++){ |
| 1037 | 1053 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -495,11 +495,15 @@ | |
| 495 | ** |
| 496 | ** TIMELINE_DISJOINT: Omit descenders |
| 497 | ** TIMELINE_FILLGAPS: Use step-children |
| 498 | ** TIMELINE_XMERGE: Omit off-graph merge lines |
| 499 | */ |
| 500 | void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){ |
| 501 | GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent; |
| 502 | int i, j; |
| 503 | u64 mask; |
| 504 | int hasDup = 0; /* True if one or more isDup entries */ |
| 505 | const char *zTrunk; |
| @@ -963,12 +967,12 @@ | |
| 963 | } |
| 964 | } |
| 965 | |
| 966 | /* |
| 967 | ** Compute the rail mapping that tries to put the branch named |
| 968 | ** zLeftBranch at the left margin. Other branches that merge |
| 969 | ** with zLeftBranch are to the right with merge rails in between. |
| 970 | ** |
| 971 | ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail. |
| 972 | ** |
| 973 | ** Do not move rails around if there are timewarps, because that can |
| 974 | ** seriously mess up the display of timewarps. Timewarps should be |
| @@ -975,10 +979,11 @@ | |
| 975 | ** rare so this should not be a serious limitation to the algorithm. |
| 976 | */ |
| 977 | aMap = p->aiRailMap; |
| 978 | for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */ |
| 979 | if( nTimewarp==0 ){ |
| 980 | /* Priority bits: |
| 981 | ** |
| 982 | ** 0x04 The preferred branch |
| 983 | ** |
| 984 | ** 0x02 A merge rail - a rail that contains merge lines into |
| @@ -986,17 +991,20 @@ | |
| 986 | ** is defined. This improves the display of r=BRANCH |
| 987 | ** options to /timeline. |
| 988 | ** |
| 989 | ** 0x01 A rail that merges with the preferred branch |
| 990 | */ |
| 991 | u8 aPriority[GR_MAX_RAIL]; |
| 992 | memset(aPriority, 0, p->mxRail+1); |
| 993 | if( zLeftBranch ){ |
| 994 | char *zLeft = persistBranchName(p, zLeftBranch); |
| 995 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 996 | if( pRow->zBranch==zLeft ){ |
| 997 | aPriority[pRow->iRail] |= 4; |
| 998 | for(i=0; i<=p->mxRail; i++){ |
| 999 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1000 | } |
| 1001 | if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1; |
| 1002 | } |
| @@ -1007,10 +1015,11 @@ | |
| 1007 | } |
| 1008 | } |
| 1009 | }else{ |
| 1010 | j = 1; |
| 1011 | aPriority[0] = 4; |
| 1012 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 1013 | if( pRow->iRail==0 ){ |
| 1014 | for(i=0; i<=p->mxRail; i++){ |
| 1015 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1016 | } |
| @@ -1020,17 +1029,24 @@ | |
| 1020 | } |
| 1021 | |
| 1022 | #if 0 |
| 1023 | fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail); |
| 1024 | fprintf(stderr,"Priority:"); |
| 1025 | for(i=0; i<=p->mxRail; i++) fprintf(stderr," %d", aPriority[i]); |
| 1026 | fprintf(stderr,"\n"); |
| 1027 | #endif |
| 1028 | |
| 1029 | j = 0; |
| 1030 | for(i=0; i<=p->mxRail; i++){ |
| 1031 | if( aPriority[i]>=4 ) aMap[i] = j++; |
| 1032 | } |
| 1033 | for(i=p->mxRail; i>=0; i--){ |
| 1034 | if( aPriority[i]==3 ) aMap[i] = j++; |
| 1035 | } |
| 1036 | for(i=0; i<=p->mxRail; i++){ |
| 1037 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -495,11 +495,15 @@ | |
| 495 | ** |
| 496 | ** TIMELINE_DISJOINT: Omit descenders |
| 497 | ** TIMELINE_FILLGAPS: Use step-children |
| 498 | ** TIMELINE_XMERGE: Omit off-graph merge lines |
| 499 | */ |
| 500 | void graph_finish( |
| 501 | GraphContext *p, /* The graph to be laid out */ |
| 502 | Matcher *pLeftBranch, /* Compares true for left-most branch */ |
| 503 | u32 tmFlags /* TIMELINE flags */ |
| 504 | ){ |
| 505 | GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent; |
| 506 | int i, j; |
| 507 | u64 mask; |
| 508 | int hasDup = 0; /* True if one or more isDup entries */ |
| 509 | const char *zTrunk; |
| @@ -963,12 +967,12 @@ | |
| 967 | } |
| 968 | } |
| 969 | |
| 970 | /* |
| 971 | ** Compute the rail mapping that tries to put the branch named |
| 972 | ** pLeftBranch at the left margin. Other branches that merge |
| 973 | ** with pLeftBranch are to the right with merge rails in between. |
| 974 | ** |
| 975 | ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail. |
| 976 | ** |
| 977 | ** Do not move rails around if there are timewarps, because that can |
| 978 | ** seriously mess up the display of timewarps. Timewarps should be |
| @@ -975,10 +979,11 @@ | |
| 979 | ** rare so this should not be a serious limitation to the algorithm. |
| 980 | */ |
| 981 | aMap = p->aiRailMap; |
| 982 | for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */ |
| 983 | if( nTimewarp==0 ){ |
| 984 | int kk; |
| 985 | /* Priority bits: |
| 986 | ** |
| 987 | ** 0x04 The preferred branch |
| 988 | ** |
| 989 | ** 0x02 A merge rail - a rail that contains merge lines into |
| @@ -986,17 +991,20 @@ | |
| 991 | ** is defined. This improves the display of r=BRANCH |
| 992 | ** options to /timeline. |
| 993 | ** |
| 994 | ** 0x01 A rail that merges with the preferred branch |
| 995 | */ |
| 996 | u16 aPriority[GR_MAX_RAIL]; |
| 997 | int mxMatch = 0; |
| 998 | memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0])); |
| 999 | if( pLeftBranch ){ |
| 1000 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 1001 | int iMatch = match_text(pLeftBranch, pRow->zBranch); |
| 1002 | if( iMatch>0 ){ |
| 1003 | if( iMatch>10 ) iMatch = 10; |
| 1004 | aPriority[pRow->iRail] |= 1<<(iMatch+1); |
| 1005 | if( mxMatch<iMatch ) mxMatch = iMatch; |
| 1006 | for(i=0; i<=p->mxRail; i++){ |
| 1007 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1008 | } |
| 1009 | if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1; |
| 1010 | } |
| @@ -1007,10 +1015,11 @@ | |
| 1015 | } |
| 1016 | } |
| 1017 | }else{ |
| 1018 | j = 1; |
| 1019 | aPriority[0] = 4; |
| 1020 | mxMatch = 1; |
| 1021 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 1022 | if( pRow->iRail==0 ){ |
| 1023 | for(i=0; i<=p->mxRail; i++){ |
| 1024 | if( pRow->mergeIn[i] ) aPriority[i] |= 1; |
| 1025 | } |
| @@ -1020,17 +1029,24 @@ | |
| 1029 | } |
| 1030 | |
| 1031 | #if 0 |
| 1032 | fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail); |
| 1033 | fprintf(stderr,"Priority:"); |
| 1034 | for(i=0; i<=p->mxRail; i++){ |
| 1035 | fprintf(stderr," %x.%x", |
| 1036 | aPriority[i]/4, aPriority[i]&3); |
| 1037 | } |
| 1038 | fprintf(stderr,"\n"); |
| 1039 | #endif |
| 1040 | |
| 1041 | j = 0; |
| 1042 | for(kk=4; kk<=1<<(mxMatch+1); kk*=2){ |
| 1043 | for(i=0; i<=p->mxRail; i++){ |
| 1044 | if( aPriority[i]>=kk && aPriority[i]<kk*2 ){ |
| 1045 | aMap[i] = j++; |
| 1046 | } |
| 1047 | } |
| 1048 | } |
| 1049 | for(i=p->mxRail; i>=0; i--){ |
| 1050 | if( aPriority[i]==3 ) aMap[i] = j++; |
| 1051 | } |
| 1052 | for(i=0; i<=p->mxRail; i++){ |
| 1053 |
+6
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -768,10 +768,11 @@ | ||
| 768 | 768 | ** a GET request where there is no PAYLOAD. |
| 769 | 769 | ** |
| 770 | 770 | ** Options: |
| 771 | 771 | ** --compress Use ZLIB compression on the payload |
| 772 | 772 | ** --mimetype TYPE Mimetype of the payload |
| 773 | +** --no-cert-verify Disable TLS cert verification | |
| 773 | 774 | ** --out FILE Store the reply in FILE |
| 774 | 775 | ** -v Verbose output |
| 775 | 776 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 776 | 777 | */ |
| 777 | 778 | void test_httpmsg_command(void){ |
| @@ -783,10 +784,15 @@ | ||
| 783 | 784 | |
| 784 | 785 | zMimetype = find_option("mimetype",0,1); |
| 785 | 786 | zOutFile = find_option("out","o",1); |
| 786 | 787 | if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE; |
| 787 | 788 | if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS; |
| 789 | + if( find_option("no-cert-verify",0,0)!=0 ){ | |
| 790 | + #ifdef FOSSIL_ENABLE_SSL | |
| 791 | + ssl_disable_cert_verification(); | |
| 792 | + #endif | |
| 793 | + } | |
| 788 | 794 | if( find_option("xfer",0,0)!=0 ){ |
| 789 | 795 | mHttpFlags |= HTTP_USE_LOGIN; |
| 790 | 796 | mHttpFlags &= ~HTTP_GENERIC; |
| 791 | 797 | } |
| 792 | 798 | verify_all_options(); |
| 793 | 799 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -768,10 +768,11 @@ | |
| 768 | ** a GET request where there is no PAYLOAD. |
| 769 | ** |
| 770 | ** Options: |
| 771 | ** --compress Use ZLIB compression on the payload |
| 772 | ** --mimetype TYPE Mimetype of the payload |
| 773 | ** --out FILE Store the reply in FILE |
| 774 | ** -v Verbose output |
| 775 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 776 | */ |
| 777 | void test_httpmsg_command(void){ |
| @@ -783,10 +784,15 @@ | |
| 783 | |
| 784 | zMimetype = find_option("mimetype",0,1); |
| 785 | zOutFile = find_option("out","o",1); |
| 786 | if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE; |
| 787 | if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS; |
| 788 | if( find_option("xfer",0,0)!=0 ){ |
| 789 | mHttpFlags |= HTTP_USE_LOGIN; |
| 790 | mHttpFlags &= ~HTTP_GENERIC; |
| 791 | } |
| 792 | verify_all_options(); |
| 793 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -768,10 +768,11 @@ | |
| 768 | ** a GET request where there is no PAYLOAD. |
| 769 | ** |
| 770 | ** Options: |
| 771 | ** --compress Use ZLIB compression on the payload |
| 772 | ** --mimetype TYPE Mimetype of the payload |
| 773 | ** --no-cert-verify Disable TLS cert verification |
| 774 | ** --out FILE Store the reply in FILE |
| 775 | ** -v Verbose output |
| 776 | ** --xfer PAYLOAD in a Fossil xfer protocol message |
| 777 | */ |
| 778 | void test_httpmsg_command(void){ |
| @@ -783,10 +784,15 @@ | |
| 784 | |
| 785 | zMimetype = find_option("mimetype",0,1); |
| 786 | zOutFile = find_option("out","o",1); |
| 787 | if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE; |
| 788 | if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS; |
| 789 | if( find_option("no-cert-verify",0,0)!=0 ){ |
| 790 | #ifdef FOSSIL_ENABLE_SSL |
| 791 | ssl_disable_cert_verification(); |
| 792 | #endif |
| 793 | } |
| 794 | if( find_option("xfer",0,0)!=0 ){ |
| 795 | mHttpFlags |= HTTP_USE_LOGIN; |
| 796 | mHttpFlags &= ~HTTP_GENERIC; |
| 797 | } |
| 798 | verify_all_options(); |
| 799 |
+4
-1
| --- src/http_transport.c | ||
| +++ src/http_transport.c | ||
| @@ -103,11 +103,14 @@ | ||
| 103 | 103 | /* |
| 104 | 104 | ** Initialize a Blob to the name of the configured SSH command. |
| 105 | 105 | */ |
| 106 | 106 | void transport_ssh_command(Blob *p){ |
| 107 | 107 | char *zSsh; /* The base SSH command */ |
| 108 | - zSsh = db_get("ssh-command", zDefaultSshCmd); | |
| 108 | + zSsh = g.zSshCmd; | |
| 109 | + if( zSsh==0 || zSsh[0]==0 ){ | |
| 110 | + zSsh = db_get("ssh-command", zDefaultSshCmd); | |
| 111 | + } | |
| 109 | 112 | blob_init(p, zSsh, -1); |
| 110 | 113 | } |
| 111 | 114 | |
| 112 | 115 | /* |
| 113 | 116 | ** SSH initialization of the transport layer |
| 114 | 117 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -103,11 +103,14 @@ | |
| 103 | /* |
| 104 | ** Initialize a Blob to the name of the configured SSH command. |
| 105 | */ |
| 106 | void transport_ssh_command(Blob *p){ |
| 107 | char *zSsh; /* The base SSH command */ |
| 108 | zSsh = db_get("ssh-command", zDefaultSshCmd); |
| 109 | blob_init(p, zSsh, -1); |
| 110 | } |
| 111 | |
| 112 | /* |
| 113 | ** SSH initialization of the transport layer |
| 114 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -103,11 +103,14 @@ | |
| 103 | /* |
| 104 | ** Initialize a Blob to the name of the configured SSH command. |
| 105 | */ |
| 106 | void transport_ssh_command(Blob *p){ |
| 107 | char *zSsh; /* The base SSH command */ |
| 108 | zSsh = g.zSshCmd; |
| 109 | if( zSsh==0 || zSsh[0]==0 ){ |
| 110 | zSsh = db_get("ssh-command", zDefaultSshCmd); |
| 111 | } |
| 112 | blob_init(p, zSsh, -1); |
| 113 | } |
| 114 | |
| 115 | /* |
| 116 | ** SSH initialization of the transport layer |
| 117 |
+466
-36
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -320,10 +320,11 @@ | ||
| 320 | 320 | |TIMELINE_NOSCROLL |
| 321 | 321 | |TIMELINE_XMERGE |
| 322 | 322 | |TIMELINE_CHPICK, |
| 323 | 323 | 0, 0, 0, rid, rid2, 0); |
| 324 | 324 | db_finalize(&q); |
| 325 | + blob_reset(&sql); | |
| 325 | 326 | } |
| 326 | 327 | |
| 327 | 328 | |
| 328 | 329 | /* |
| 329 | 330 | ** Append the difference between artifacts to the output |
| @@ -372,11 +373,13 @@ | ||
| 372 | 373 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 373 | 374 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 374 | 375 | DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */ |
| 375 | 376 | int mperm /* executable or symlink permission for zNew */ |
| 376 | 377 | ){ |
| 377 | - @ <p> | |
| 378 | + @ <div class='file-change-line'><span> | |
| 379 | + /* Maintenance reminder: the extra level of SPAN is for | |
| 380 | + ** arranging new elements via JS. */ | |
| 378 | 381 | if( !g.perm.Hyperlink ){ |
| 379 | 382 | if( zNew==0 ){ |
| 380 | 383 | @ Deleted %h(zName). |
| 381 | 384 | }else if( zOld==0 ){ |
| 382 | 385 | @ Added %h(zName). |
| @@ -391,10 +394,11 @@ | ||
| 391 | 394 | @ %h(zName) became a regular file. |
| 392 | 395 | } |
| 393 | 396 | }else{ |
| 394 | 397 | @ Changes to %h(zName). |
| 395 | 398 | } |
| 399 | + @ </span></div> | |
| 396 | 400 | if( pCfg ){ |
| 397 | 401 | append_diff(zOld, zNew, pCfg); |
| 398 | 402 | } |
| 399 | 403 | }else{ |
| 400 | 404 | if( zOld && zNew ){ |
| @@ -438,18 +442,20 @@ | ||
| 438 | 442 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 439 | 443 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 440 | 444 | } |
| 441 | 445 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 442 | 446 | if( pCfg ){ |
| 447 | + @ </span></div> | |
| 443 | 448 | append_diff(zOld, zNew, pCfg); |
| 444 | - }else{ | |
| 445 | - @ | |
| 449 | + }else{ | |
| 446 | 450 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a> |
| 451 | + @ </span></div> | |
| 447 | 452 | } |
| 453 | + }else{ | |
| 454 | + @ </span></div> | |
| 448 | 455 | } |
| 449 | 456 | } |
| 450 | - @ </p> | |
| 451 | 457 | } |
| 452 | 458 | |
| 453 | 459 | /* |
| 454 | 460 | ** Generate javascript to enhance HTML diffs. |
| 455 | 461 | */ |
| @@ -602,10 +608,277 @@ | ||
| 602 | 608 | www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL, |
| 603 | 609 | 0, 0, 0, rid, 0, 0); |
| 604 | 610 | db_finalize(&q); |
| 605 | 611 | style_finish_page(); |
| 606 | 612 | } |
| 613 | + | |
| 614 | +/* | |
| 615 | +** Render a web-page diff of the changes in the working check-out | |
| 616 | +*/ | |
| 617 | +static void ckout_normal_diff(int vid){ | |
| 618 | + int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ | |
| 619 | + DiffConfig DCfg,*pCfg; /* Diff details */ | |
| 620 | + const char *zW; /* The "w" query parameter */ | |
| 621 | + int nChng; /* Number of changes */ | |
| 622 | + Stmt q; | |
| 623 | + | |
| 624 | + diffType = preferred_diff_type(); | |
| 625 | + pCfg = construct_diff_flags(diffType, &DCfg); | |
| 626 | + nChng = db_int(0, "SELECT count(*) FROM vfile" | |
| 627 | + " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); | |
| 628 | + if( nChng==0 ){ | |
| 629 | + @ <p>No uncommitted changes</p> | |
| 630 | + return; | |
| 631 | + } | |
| 632 | + db_prepare(&q, | |
| 633 | + /* 0 1 2 3 4 5 6 */ | |
| 634 | + "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid" | |
| 635 | + " FROM vfile LEFT JOIN blob USING(rid)" | |
| 636 | + " WHERE vid=%d" | |
| 637 | + " AND (deleted OR chnged OR rid==0)" | |
| 638 | + " ORDER BY pathname /*scan*/", | |
| 639 | + vid | |
| 640 | + ); | |
| 641 | + if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ | |
| 642 | + DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; | |
| 643 | + }else{ | |
| 644 | + DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; | |
| 645 | + } | |
| 646 | + @ <div class="sectionmenu info-changes-menu"> | |
| 647 | + zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; | |
| 648 | + if( diffType!=1 ){ | |
| 649 | + @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> | |
| 650 | + } | |
| 651 | + if( diffType!=2 ){ | |
| 652 | + @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side Diff</a> | |
| 653 | + } | |
| 654 | + if( diffType!=0 ){ | |
| 655 | + if( *zW ){ | |
| 656 | + @ %z(chref("button","%R?diff=%d",diffType))\ | |
| 657 | + @ Show Whitespace Changes</a> | |
| 658 | + }else{ | |
| 659 | + @ %z(chref("button","%R?diff=%d&w",diffType))Ignore Whitespace</a> | |
| 660 | + } | |
| 661 | + } | |
| 662 | + @ </div> | |
| 663 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 664 | + const char *zTreename = db_column_text(&q,0); | |
| 665 | + int isDeleted = db_column_int(&q, 1); | |
| 666 | + int isChnged = db_column_int(&q,2); | |
| 667 | + int isNew = db_column_int(&q,3); | |
| 668 | + int srcid = db_column_int(&q, 4); | |
| 669 | + int isLink = db_column_int(&q, 5); | |
| 670 | + const char *zUuid = db_column_text(&q, 6); | |
| 671 | + int showDiff = 1; | |
| 672 | + | |
| 673 | + DCfg.diffFlags &= (~DIFF_FILE_MASK); | |
| 674 | + @ <div class='file-change-line'><span> | |
| 675 | + if( isDeleted ){ | |
| 676 | + @ DELETED %h(zTreename) | |
| 677 | + DCfg.diffFlags |= DIFF_FILE_DELETED; | |
| 678 | + showDiff = 0; | |
| 679 | + }else if( file_access(zTreename, F_OK) ){ | |
| 680 | + @ MISSING %h(zTreename) | |
| 681 | + showDiff = 0; | |
| 682 | + }else if( isNew ){ | |
| 683 | + @ ADDED %h(zTreename) | |
| 684 | + DCfg.diffFlags |= DIFF_FILE_ADDED; | |
| 685 | + srcid = 0; | |
| 686 | + showDiff = 0; | |
| 687 | + }else if( isChnged==3 ){ | |
| 688 | + @ ADDED_BY_MERGE %h(zTreename) | |
| 689 | + DCfg.diffFlags |= DIFF_FILE_ADDED; | |
| 690 | + srcid = 0; | |
| 691 | + showDiff = 0; | |
| 692 | + }else if( isChnged==5 ){ | |
| 693 | + @ ADDED_BY_INTEGRATE %h(zTreename) | |
| 694 | + DCfg.diffFlags |= DIFF_FILE_ADDED; | |
| 695 | + srcid = 0; | |
| 696 | + showDiff = 0; | |
| 697 | + }else{ | |
| 698 | + @ CHANGED %h(zTreename) | |
| 699 | + } | |
| 700 | + @ </span></div> | |
| 701 | + if( showDiff && pCfg ){ | |
| 702 | + Blob old, new; | |
| 703 | + if( !isLink != !file_islink(zTreename) ){ | |
| 704 | + @ %s(DIFF_CANNOT_COMPUTE_SYMLINK) | |
| 705 | + continue; | |
| 706 | + } | |
| 707 | + if( srcid>0 ){ | |
| 708 | + content_get(srcid, &old); | |
| 709 | + pCfg->zLeftHash = zUuid; | |
| 710 | + }else{ | |
| 711 | + blob_zero(&old); | |
| 712 | + pCfg->zLeftHash = 0; | |
| 713 | + } | |
| 714 | + blob_read_from_file(&new, zTreename, ExtFILE); | |
| 715 | + text_diff(&old, &new, cgi_output_blob(), pCfg); | |
| 716 | + blob_reset(&old); | |
| 717 | + blob_reset(&new); | |
| 718 | + } | |
| 719 | + } | |
| 720 | + db_finalize(&q); | |
| 721 | + append_diff_javascript(diffType); | |
| 722 | +} | |
| 723 | + | |
| 724 | +/* | |
| 725 | +** Render a web-page diff of the changes in the working check-out to | |
| 726 | +** an external reference. | |
| 727 | +*/ | |
| 728 | +static void ckout_external_base_diff(int vid, const char *zExBase){ | |
| 729 | + int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ | |
| 730 | + DiffConfig DCfg,*pCfg; /* Diff details */ | |
| 731 | + const char *zW; /* The "w" query parameter */ | |
| 732 | + Stmt q; | |
| 733 | + | |
| 734 | + diffType = preferred_diff_type(); | |
| 735 | + pCfg = construct_diff_flags(diffType, &DCfg); | |
| 736 | + db_prepare(&q, | |
| 737 | + "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid | |
| 738 | + ); | |
| 739 | + if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ | |
| 740 | + DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; | |
| 741 | + }else{ | |
| 742 | + DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; | |
| 743 | + } | |
| 744 | + @ <div class="sectionmenu info-changes-menu"> | |
| 745 | + zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; | |
| 746 | + if( diffType!=1 ){ | |
| 747 | + @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ | |
| 748 | + @ Unified Diff</a> | |
| 749 | + } | |
| 750 | + if( diffType!=2 ){ | |
| 751 | + @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ | |
| 752 | + @ Side-by-Side Diff</a> | |
| 753 | + } | |
| 754 | + if( diffType!=0 ){ | |
| 755 | + if( *zW ){ | |
| 756 | + @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ | |
| 757 | + @ Show Whitespace Changes</a> | |
| 758 | + }else{ | |
| 759 | + @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ | |
| 760 | + @ Ignore Whitespace</a> | |
| 761 | + } | |
| 762 | + } | |
| 763 | + @ </div> | |
| 764 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 765 | + const char *zFile; /* Name of file in the repository */ | |
| 766 | + char *zLhs; /* Full name of left-hand side file */ | |
| 767 | + char *zRhs; /* Full name of right-hand side file */ | |
| 768 | + Blob rhs; /* Full text of RHS */ | |
| 769 | + Blob lhs; /* Full text of LHS */ | |
| 770 | + | |
| 771 | + zFile = db_column_text(&q,0); | |
| 772 | + zLhs = mprintf("%s/%s", zExBase, zFile); | |
| 773 | + zRhs = mprintf("%s%s", g.zLocalRoot, zFile); | |
| 774 | + if( file_size(zLhs, ExtFILE)<0 ){ | |
| 775 | + @ <div class='file-change-line'><span> | |
| 776 | + @ Missing from external baseline: %h(zFile) | |
| 777 | + @ </span></div> | |
| 778 | + }else{ | |
| 779 | + blob_read_from_file(&lhs, zLhs, ExtFILE); | |
| 780 | + blob_read_from_file(&rhs, zRhs, ExtFILE); | |
| 781 | + if( blob_size(&lhs)!=blob_size(&rhs) | |
| 782 | + || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 | |
| 783 | + ){ | |
| 784 | + @ <div class='file-change-line'><span> | |
| 785 | + @ Changes to %h(zFile) | |
| 786 | + @ </span></div> | |
| 787 | + if( pCfg ){ | |
| 788 | + char *zFullFN; | |
| 789 | + char *zHexFN; | |
| 790 | + zFullFN = file_canonical_name_dup(zLhs); | |
| 791 | + zHexFN = mprintf("x%H", zFullFN); | |
| 792 | + fossil_free(zFullFN); | |
| 793 | + pCfg->zLeftHash = zHexFN; | |
| 794 | + text_diff(&lhs, &rhs, cgi_output_blob(), pCfg); | |
| 795 | + pCfg->zLeftHash = 0; | |
| 796 | + fossil_free(zHexFN); | |
| 797 | + } | |
| 798 | + } | |
| 799 | + blob_reset(&lhs); | |
| 800 | + blob_reset(&rhs); | |
| 801 | + } | |
| 802 | + fossil_free(zLhs); | |
| 803 | + fossil_free(zRhs); | |
| 804 | + } | |
| 805 | + db_finalize(&q); | |
| 806 | + append_diff_javascript(diffType); | |
| 807 | +} | |
| 808 | + | |
| 809 | +/* | |
| 810 | +** WEBPAGE: ckout | |
| 811 | +** | |
| 812 | +** Show information about the current checkout. This page only functions | |
| 813 | +** if the web server is run on a loopback interface (in other words, was | |
| 814 | +** started using "fossil ui" or similar) from within an open check-out. | |
| 815 | +** | |
| 816 | +** If the "exbase=PATH" query parameter is provided, then the diff shown | |
| 817 | +** uses the files in PATH as the baseline. This is the same as using | |
| 818 | +** the "--from PATH" argument to the "fossil diff" command-line. In fact, | |
| 819 | +** when using "fossil ui --from PATH", the --from argument becomes the value | |
| 820 | +** of the exbase query parameter for the start page. Note that if PATH | |
| 821 | +** is a pure hexadecimal string, it is decoded first before being used as | |
| 822 | +** the pathname. Real pathnames should contain at least one directory | |
| 823 | +** separator character. | |
| 824 | +** | |
| 825 | +** Other query parameters related to diffs are also accepted. | |
| 826 | +*/ | |
| 827 | +void ckout_page(void){ | |
| 828 | + int vid; | |
| 829 | + const char *zHome; /* Home directory */ | |
| 830 | + int nHome; | |
| 831 | + const char *zExBase; | |
| 832 | + char *zHostname; | |
| 833 | + char *zCwd; | |
| 834 | + | |
| 835 | + if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){ | |
| 836 | + cgi_redirectf("%R/home"); | |
| 837 | + return; | |
| 838 | + } | |
| 839 | + file_chdir(g.zLocalRoot, 0); | |
| 840 | + vid = db_lget_int("checkout", 0); | |
| 841 | + db_unprotect(PROTECT_ALL); | |
| 842 | + vfile_check_signature(vid, CKSIG_ENOTFILE); | |
| 843 | + db_protect_pop(); | |
| 844 | + style_set_current_feature("vinfo"); | |
| 845 | + zHostname = fossil_hostname(); | |
| 846 | + zCwd = file_getcwd(0,0); | |
| 847 | + zHome = fossil_getenv("HOME"); | |
| 848 | + if( zHome ){ | |
| 849 | + nHome = (int)strlen(zHome); | |
| 850 | + if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ | |
| 851 | + zCwd = mprintf("~%s", zCwd+nHome); | |
| 852 | + } | |
| 853 | + }else{ | |
| 854 | + nHome = 0; | |
| 855 | + } | |
| 856 | + if( zHostname ){ | |
| 857 | + style_header("Checkout Status: %h on %h", zCwd, zHostname); | |
| 858 | + }else{ | |
| 859 | + style_header("Checkout Status: %h", zCwd); | |
| 860 | + } | |
| 861 | + render_checkin_context(vid, 0, 0, 0); | |
| 862 | + @ <hr> | |
| 863 | + zExBase = P("exbase"); | |
| 864 | + if( zExBase && zExBase[0] ){ | |
| 865 | + char *zPath = decode16_dup(zExBase); | |
| 866 | + char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase); | |
| 867 | + if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){ | |
| 868 | + @ <p>Using external baseline: ~%h(zCBase+nHome)</p> | |
| 869 | + }else{ | |
| 870 | + @ <p>Using external baseline: %h(zCBase)</p> | |
| 871 | + } | |
| 872 | + ckout_external_base_diff(vid, zCBase); | |
| 873 | + fossil_free(zCBase); | |
| 874 | + fossil_free(zPath); | |
| 875 | + }else{ | |
| 876 | + ckout_normal_diff(vid); | |
| 877 | + } | |
| 878 | + style_finish_page(); | |
| 879 | +} | |
| 607 | 880 | |
| 608 | 881 | /* |
| 609 | 882 | ** WEBPAGE: vinfo |
| 610 | 883 | ** WEBPAGE: ci |
| 611 | 884 | ** URL: /ci/ARTIFACTID |
| @@ -628,11 +901,10 @@ | ||
| 628 | 901 | const char *zParent; /* Hash of the parent check-in (if any) */ |
| 629 | 902 | const char *zRe; /* regex parameter */ |
| 630 | 903 | ReCompiled *pRe = 0; /* regex */ |
| 631 | 904 | const char *zW; /* URL param for ignoring whitespace */ |
| 632 | 905 | const char *zPage = "vinfo"; /* Page that shows diffs */ |
| 633 | - const char *zPageHide = "ci"; /* Page that hides diffs */ | |
| 634 | 906 | const char *zBrName; /* Branch name */ |
| 635 | 907 | DiffConfig DCfg,*pCfg; /* Type of diff */ |
| 636 | 908 | |
| 637 | 909 | login_check_credentials(); |
| 638 | 910 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -898,21 +1170,17 @@ | ||
| 898 | 1170 | @ <div class="sectionmenu info-changes-menu"> |
| 899 | 1171 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 900 | 1172 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 901 | 1173 | DCfg.pRe = pRe; |
| 902 | 1174 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 903 | - if( diffType!=0 ){ | |
| 904 | - @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\ | |
| 905 | - @ Hide Diffs</a> | |
| 906 | - } | |
| 907 | 1175 | if( diffType!=1 ){ |
| 908 | 1176 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 909 | - @ Unified Diffs</a> | |
| 1177 | + @ Unified Diff</a> | |
| 910 | 1178 | } |
| 911 | 1179 | if( diffType!=2 ){ |
| 912 | 1180 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 913 | - @ Side-by-Side Diffs</a> | |
| 1181 | + @ Side-by-Side Diff</a> | |
| 914 | 1182 | } |
| 915 | 1183 | if( diffType!=0 ){ |
| 916 | 1184 | if( *zW ){ |
| 917 | 1185 | @ %z(chref("button","%R/%s/%T",zPage,zName)) |
| 918 | 1186 | @ Show Whitespace Changes</a> |
| @@ -1268,13 +1536,10 @@ | ||
| 1268 | 1536 | cgi_check_for_malice(); |
| 1269 | 1537 | style_set_current_feature("vdiff"); |
| 1270 | 1538 | if( zBranch==0 ){ |
| 1271 | 1539 | style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo); |
| 1272 | 1540 | } |
| 1273 | - if( diffType!=0 ){ | |
| 1274 | - style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob); | |
| 1275 | - } | |
| 1276 | 1541 | if( diffType!=2 ){ |
| 1277 | 1542 | style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp, |
| 1278 | 1543 | &qpGlob); |
| 1279 | 1544 | } |
| 1280 | 1545 | if( diffType!=1 ) { |
| @@ -1668,11 +1933,11 @@ | ||
| 1668 | 1933 | tag_private_status(rid); |
| 1669 | 1934 | } |
| 1670 | 1935 | db_finalize(&q); |
| 1671 | 1936 | if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d", |
| 1672 | 1937 | rid, TAG_CLUSTER) ){ |
| 1673 | - @ Cluster | |
| 1938 | + @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>. | |
| 1674 | 1939 | cnt++; |
| 1675 | 1940 | } |
| 1676 | 1941 | if( cnt==0 ){ |
| 1677 | 1942 | @ Unrecognized artifact |
| 1678 | 1943 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| @@ -1933,10 +2198,16 @@ | ||
| 1933 | 2198 | ** WEBPAGE: jchunk hidden |
| 1934 | 2199 | ** URL: /jchunk/HASH?from=N&to=M |
| 1935 | 2200 | ** |
| 1936 | 2201 | ** Return lines of text from a file as a JSON array - one entry in the |
| 1937 | 2202 | ** array for each line of text. |
| 2203 | +** | |
| 2204 | +** The HASH is normally a sha1 or sha3 hash that identifies an artifact | |
| 2205 | +** in the BLOB table of the database. However, if HASH starts with an "x" | |
| 2206 | +** and is followed by valid hexadecimal, and if we are running in a | |
| 2207 | +** "fossil ui" situation (locally and with privilege), then decode the hex | |
| 2208 | +** into a filename and read the file content from that name. | |
| 1938 | 2209 | ** |
| 1939 | 2210 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 1940 | 2211 | ** change at any moment. External application should not use this interface |
| 1941 | 2212 | ** since the application will break when this interface changes, and this |
| 1942 | 2213 | ** interface will undoubtedly change. |
| @@ -1948,10 +2219,11 @@ | ||
| 1948 | 2219 | ** ajax_route_error(). |
| 1949 | 2220 | */ |
| 1950 | 2221 | void jchunk_page(void){ |
| 1951 | 2222 | int rid = 0; |
| 1952 | 2223 | const char *zName = PD("name", ""); |
| 2224 | + int nName = (int)(strlen(zName)&0x7fffffff); | |
| 1953 | 2225 | int iFrom = atoi(PD("from","0")); |
| 1954 | 2226 | int iTo = atoi(PD("to","0")); |
| 1955 | 2227 | int ln; |
| 1956 | 2228 | int go = 1; |
| 1957 | 2229 | const char *zSep; |
| @@ -1968,36 +2240,57 @@ | ||
| 1968 | 2240 | cgi_check_for_malice(); |
| 1969 | 2241 | if( !g.perm.Read ){ |
| 1970 | 2242 | ajax_route_error(403, "Access requires Read permissions."); |
| 1971 | 2243 | return; |
| 1972 | 2244 | } |
| 1973 | -#if 1 | |
| 1974 | - /* Re-enable this block once this code is integrated somewhere into | |
| 1975 | - the UI. */ | |
| 1976 | - rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); | |
| 1977 | - if( rid==0 ){ | |
| 1978 | - ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 1979 | - return; | |
| 1980 | - } | |
| 1981 | -#else | |
| 1982 | - /* This impl is only to simplify "manual" testing via the JS | |
| 1983 | - console. */ | |
| 1984 | - rid = symbolic_name_to_rid(zName, "*"); | |
| 1985 | - if( rid==0 ){ | |
| 1986 | - ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 1987 | - return; | |
| 1988 | - }else if( rid<0 ){ | |
| 1989 | - ajax_route_error(418, "Ambiguous artifact name: %h", zName); | |
| 1990 | - return; | |
| 1991 | - } | |
| 1992 | -#endif | |
| 1993 | 2245 | if( iFrom<1 || iTo<iFrom ){ |
| 1994 | 2246 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 1995 | 2247 | iFrom, iTo); |
| 1996 | 2248 | return; |
| 1997 | 2249 | } |
| 1998 | - content_get(rid, &content); | |
| 2250 | + if( zName[0]=='x' | |
| 2251 | + && ((nName-1)&1)==0 | |
| 2252 | + && validate16(&zName[1],nName-1) | |
| 2253 | + && g.perm.Admin | |
| 2254 | + && cgi_is_loopback(g.zIpAddr) | |
| 2255 | + && db_open_local(0) | |
| 2256 | + ){ | |
| 2257 | + /* Treat the HASH as a hex-encoded filename */ | |
| 2258 | + int n = (nName-1)/2; | |
| 2259 | + char *zFN = fossil_malloc(n+1); | |
| 2260 | + decode16((const u8*)&zName[1], (u8*)zFN, nName-1); | |
| 2261 | + zFN[n] = 0; | |
| 2262 | + if( file_size(zFN, ExtFILE)<0 ){ | |
| 2263 | + blob_zero(&content); | |
| 2264 | + }else{ | |
| 2265 | + blob_read_from_file(&content, zFN, ExtFILE); | |
| 2266 | + } | |
| 2267 | + fossil_free(zFN); | |
| 2268 | + }else{ | |
| 2269 | + /* Treat the HASH as an artifact hash matching BLOB.UUID */ | |
| 2270 | +#if 1 | |
| 2271 | + /* Re-enable this block once this code is integrated somewhere into | |
| 2272 | + the UI. */ | |
| 2273 | + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); | |
| 2274 | + if( rid==0 ){ | |
| 2275 | + ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2276 | + return; | |
| 2277 | + } | |
| 2278 | +#else | |
| 2279 | + /* This impl is only to simplify "manual" testing via the JS | |
| 2280 | + console. */ | |
| 2281 | + rid = symbolic_name_to_rid(zName, "*"); | |
| 2282 | + if( rid==0 ){ | |
| 2283 | + ajax_route_error(404, "Unknown artifact: %h", zName); | |
| 2284 | + return; | |
| 2285 | + }else if( rid<0 ){ | |
| 2286 | + ajax_route_error(418, "Ambiguous artifact name: %h", zName); | |
| 2287 | + return; | |
| 2288 | + } | |
| 2289 | +#endif | |
| 2290 | + content_get(rid, &content); | |
| 2291 | + } | |
| 1999 | 2292 | g.isConst = 1; |
| 2000 | 2293 | cgi_set_content_type("application/json"); |
| 2001 | 2294 | ln = 0; |
| 2002 | 2295 | while( go && ln<iFrom ){ |
| 2003 | 2296 | go = blob_line(&content, &line); |
| @@ -2875,10 +3168,143 @@ | ||
| 2875 | 3168 | ticket_output_change_artifact(pTktChng, 0, 1, 0); |
| 2876 | 3169 | manifest_destroy(pTktChng); |
| 2877 | 3170 | style_finish_page(); |
| 2878 | 3171 | } |
| 2879 | 3172 | |
| 3173 | +/* | |
| 3174 | +** rid is a cluster. Paint a page that contains detailed information | |
| 3175 | +** about that cluster. | |
| 3176 | +*/ | |
| 3177 | +static void cluster_info(int rid, const char *zName){ | |
| 3178 | + Manifest *pCluster; | |
| 3179 | + int i; | |
| 3180 | + Blob where = BLOB_INITIALIZER; | |
| 3181 | + Blob unks = BLOB_INITIALIZER; | |
| 3182 | + Stmt q; | |
| 3183 | + char *zSha1Bg; | |
| 3184 | + char *zSha3Bg; | |
| 3185 | + int badRid = 0; | |
| 3186 | + int rcvid; | |
| 3187 | + int hashClr = PB("hclr"); | |
| 3188 | + const char *zDate; | |
| 3189 | + | |
| 3190 | + pCluster = manifest_get(rid, CFTYPE_CLUSTER, 0); | |
| 3191 | + if( pCluster==0 ){ | |
| 3192 | + artifact_page(); | |
| 3193 | + return; | |
| 3194 | + } | |
| 3195 | + style_header("Cluster %S", zName); | |
| 3196 | + rcvid = db_int(0, "SELECT rcvid FROM blob WHERE rid=%d", rid); | |
| 3197 | + if( rcvid==0 ){ | |
| 3198 | + zDate = 0; | |
| 3199 | + }else{ | |
| 3200 | + zDate = db_text(0, "SELECT datetime(mtime) FROM rcvfrom WHERE rcvid=%d", | |
| 3201 | + rcvid); | |
| 3202 | + } | |
| 3203 | + @ <p>Artifact %z(href("%R/artifact/%h",zName))%S(zName)</a> is a cluster | |
| 3204 | + @ with %d(pCluster->nCChild) entries | |
| 3205 | + if( g.perm.Admin ){ | |
| 3206 | + @ received <a href="%R/rcvfrom?rcvid=%d(rcvid)">%h(zDate)</a>: | |
| 3207 | + }else{ | |
| 3208 | + @ received %h(zDate): | |
| 3209 | + } | |
| 3210 | + blob_appendf(&where,"IN(0"); | |
| 3211 | + for(i=0; i<pCluster->nCChild; i++){ | |
| 3212 | + int rid = fast_uuid_to_rid(pCluster->azCChild[i]); | |
| 3213 | + if( rid ){ | |
| 3214 | + blob_appendf(&where,",%d", rid); | |
| 3215 | + }else{ | |
| 3216 | + if( blob_size(&unks)>0 ) blob_append_char(&unks, ','); | |
| 3217 | + badRid++; | |
| 3218 | + blob_append_sql(&unks,"(%d,%Q)",-badRid,pCluster->azCChild[i]); | |
| 3219 | + } | |
| 3220 | + } | |
| 3221 | + blob_append_char(&where,')'); | |
| 3222 | + describe_artifacts(blob_str(&where)); | |
| 3223 | + blob_reset(&where); | |
| 3224 | + if( badRid>0 ){ | |
| 3225 | + db_multi_exec( | |
| 3226 | + "WITH unks(rx,hx) AS (VALUES %s)\n" | |
| 3227 | + "INSERT INTO description(rid,uuid,type,summary) " | |
| 3228 | + " SELECT rx, hx, 'phantom', '' FROM unks;", | |
| 3229 | + blob_sql_text(&unks) | |
| 3230 | + ); | |
| 3231 | + } | |
| 3232 | + blob_reset(&unks); | |
| 3233 | + db_prepare(&q, | |
| 3234 | + "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref" | |
| 3235 | + " FROM description ORDER BY uuid" | |
| 3236 | + ); | |
| 3237 | + if( skin_detail_boolean("white-foreground") ){ | |
| 3238 | + zSha1Bg = "#714417"; | |
| 3239 | + zSha3Bg = "#177117"; | |
| 3240 | + }else{ | |
| 3241 | + zSha1Bg = "#ebffb0"; | |
| 3242 | + zSha3Bg = "#b0ffb0"; | |
| 3243 | + } | |
| 3244 | + @ <table cellpadding="2" cellspacing="0" border="1"> | |
| 3245 | + if( g.perm.Admin ){ | |
| 3246 | + @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks | |
| 3247 | + }else{ | |
| 3248 | + @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks | |
| 3249 | + } | |
| 3250 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 3251 | + int rid = db_column_int(&q,0); | |
| 3252 | + const char *zUuid = db_column_text(&q, 1); | |
| 3253 | + const char *zDesc = db_column_text(&q, 2); | |
| 3254 | + int isPriv = db_column_int(&q,3); | |
| 3255 | + int isPhantom = db_column_int(&q,4); | |
| 3256 | + const char *zRef = db_column_text(&q,6); | |
| 3257 | + if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ | |
| 3258 | + /* Don't show private artifacts to users without Private (x) permission */ | |
| 3259 | + continue; | |
| 3260 | + } | |
| 3261 | + if( rid<=0 ){ | |
| 3262 | + @ <tr><td> </td> | |
| 3263 | + }else if( hashClr ){ | |
| 3264 | + const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg; | |
| 3265 | + @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td> | |
| 3266 | + }else{ | |
| 3267 | + @ <tr><td align="right">%d(rid)</td> | |
| 3268 | + } | |
| 3269 | + if( rid<=0 ){ | |
| 3270 | + @ <td> %S(zUuid) </td> | |
| 3271 | + }else{ | |
| 3272 | + @ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td> | |
| 3273 | + } | |
| 3274 | + if( g.perm.Admin ){ | |
| 3275 | + int rcvid = db_column_int(&q,5); | |
| 3276 | + if( rcvid<=0 ){ | |
| 3277 | + @ <td> | |
| 3278 | + }else{ | |
| 3279 | + @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a> | |
| 3280 | + } | |
| 3281 | + } | |
| 3282 | + @ <td align="left">%h(zDesc)</td> | |
| 3283 | + if( zRef && zRef[0] ){ | |
| 3284 | + @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a> | |
| 3285 | + }else{ | |
| 3286 | + @ <td> | |
| 3287 | + } | |
| 3288 | + if( isPriv || isPhantom ){ | |
| 3289 | + if( isPriv==0 ){ | |
| 3290 | + @ <td>phantom</td> | |
| 3291 | + }else if( isPhantom==0 ){ | |
| 3292 | + @ <td>private</td> | |
| 3293 | + }else{ | |
| 3294 | + @ <td>private,phantom</td> | |
| 3295 | + } | |
| 3296 | + }else{ | |
| 3297 | + @ <td> | |
| 3298 | + } | |
| 3299 | + @ </tr> | |
| 3300 | + } | |
| 3301 | + @ </table> | |
| 3302 | + db_finalize(&q); | |
| 3303 | + style_finish_page(); | |
| 3304 | +} | |
| 3305 | + | |
| 2880 | 3306 | |
| 2881 | 3307 | /* |
| 2882 | 3308 | ** WEBPAGE: info |
| 2883 | 3309 | ** URL: info/NAME |
| 2884 | 3310 | ** |
| @@ -2963,10 +3389,14 @@ | ||
| 2963 | 3389 | if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){ |
| 2964 | 3390 | ci_page(); |
| 2965 | 3391 | }else |
| 2966 | 3392 | if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){ |
| 2967 | 3393 | ainfo_page(); |
| 3394 | + }else | |
| 3395 | + if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d", | |
| 3396 | + rid, TAG_CLUSTER) ){ | |
| 3397 | + cluster_info(rid, zName); | |
| 2968 | 3398 | }else |
| 2969 | 3399 | { |
| 2970 | 3400 | artifact_page(); |
| 2971 | 3401 | } |
| 2972 | 3402 | } |
| 2973 | 3403 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -320,10 +320,11 @@ | |
| 320 | |TIMELINE_NOSCROLL |
| 321 | |TIMELINE_XMERGE |
| 322 | |TIMELINE_CHPICK, |
| 323 | 0, 0, 0, rid, rid2, 0); |
| 324 | db_finalize(&q); |
| 325 | } |
| 326 | |
| 327 | |
| 328 | /* |
| 329 | ** Append the difference between artifacts to the output |
| @@ -372,11 +373,13 @@ | |
| 372 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 373 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 374 | DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */ |
| 375 | int mperm /* executable or symlink permission for zNew */ |
| 376 | ){ |
| 377 | @ <p> |
| 378 | if( !g.perm.Hyperlink ){ |
| 379 | if( zNew==0 ){ |
| 380 | @ Deleted %h(zName). |
| 381 | }else if( zOld==0 ){ |
| 382 | @ Added %h(zName). |
| @@ -391,10 +394,11 @@ | |
| 391 | @ %h(zName) became a regular file. |
| 392 | } |
| 393 | }else{ |
| 394 | @ Changes to %h(zName). |
| 395 | } |
| 396 | if( pCfg ){ |
| 397 | append_diff(zOld, zNew, pCfg); |
| 398 | } |
| 399 | }else{ |
| 400 | if( zOld && zNew ){ |
| @@ -438,18 +442,20 @@ | |
| 438 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 439 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 440 | } |
| 441 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 442 | if( pCfg ){ |
| 443 | append_diff(zOld, zNew, pCfg); |
| 444 | }else{ |
| 445 | @ |
| 446 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a> |
| 447 | } |
| 448 | } |
| 449 | } |
| 450 | @ </p> |
| 451 | } |
| 452 | |
| 453 | /* |
| 454 | ** Generate javascript to enhance HTML diffs. |
| 455 | */ |
| @@ -602,10 +608,277 @@ | |
| 602 | www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL, |
| 603 | 0, 0, 0, rid, 0, 0); |
| 604 | db_finalize(&q); |
| 605 | style_finish_page(); |
| 606 | } |
| 607 | |
| 608 | /* |
| 609 | ** WEBPAGE: vinfo |
| 610 | ** WEBPAGE: ci |
| 611 | ** URL: /ci/ARTIFACTID |
| @@ -628,11 +901,10 @@ | |
| 628 | const char *zParent; /* Hash of the parent check-in (if any) */ |
| 629 | const char *zRe; /* regex parameter */ |
| 630 | ReCompiled *pRe = 0; /* regex */ |
| 631 | const char *zW; /* URL param for ignoring whitespace */ |
| 632 | const char *zPage = "vinfo"; /* Page that shows diffs */ |
| 633 | const char *zPageHide = "ci"; /* Page that hides diffs */ |
| 634 | const char *zBrName; /* Branch name */ |
| 635 | DiffConfig DCfg,*pCfg; /* Type of diff */ |
| 636 | |
| 637 | login_check_credentials(); |
| 638 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -898,21 +1170,17 @@ | |
| 898 | @ <div class="sectionmenu info-changes-menu"> |
| 899 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 900 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 901 | DCfg.pRe = pRe; |
| 902 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 903 | if( diffType!=0 ){ |
| 904 | @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\ |
| 905 | @ Hide Diffs</a> |
| 906 | } |
| 907 | if( diffType!=1 ){ |
| 908 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 909 | @ Unified Diffs</a> |
| 910 | } |
| 911 | if( diffType!=2 ){ |
| 912 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 913 | @ Side-by-Side Diffs</a> |
| 914 | } |
| 915 | if( diffType!=0 ){ |
| 916 | if( *zW ){ |
| 917 | @ %z(chref("button","%R/%s/%T",zPage,zName)) |
| 918 | @ Show Whitespace Changes</a> |
| @@ -1268,13 +1536,10 @@ | |
| 1268 | cgi_check_for_malice(); |
| 1269 | style_set_current_feature("vdiff"); |
| 1270 | if( zBranch==0 ){ |
| 1271 | style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo); |
| 1272 | } |
| 1273 | if( diffType!=0 ){ |
| 1274 | style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob); |
| 1275 | } |
| 1276 | if( diffType!=2 ){ |
| 1277 | style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp, |
| 1278 | &qpGlob); |
| 1279 | } |
| 1280 | if( diffType!=1 ) { |
| @@ -1668,11 +1933,11 @@ | |
| 1668 | tag_private_status(rid); |
| 1669 | } |
| 1670 | db_finalize(&q); |
| 1671 | if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d", |
| 1672 | rid, TAG_CLUSTER) ){ |
| 1673 | @ Cluster |
| 1674 | cnt++; |
| 1675 | } |
| 1676 | if( cnt==0 ){ |
| 1677 | @ Unrecognized artifact |
| 1678 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| @@ -1933,10 +2198,16 @@ | |
| 1933 | ** WEBPAGE: jchunk hidden |
| 1934 | ** URL: /jchunk/HASH?from=N&to=M |
| 1935 | ** |
| 1936 | ** Return lines of text from a file as a JSON array - one entry in the |
| 1937 | ** array for each line of text. |
| 1938 | ** |
| 1939 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 1940 | ** change at any moment. External application should not use this interface |
| 1941 | ** since the application will break when this interface changes, and this |
| 1942 | ** interface will undoubtedly change. |
| @@ -1948,10 +2219,11 @@ | |
| 1948 | ** ajax_route_error(). |
| 1949 | */ |
| 1950 | void jchunk_page(void){ |
| 1951 | int rid = 0; |
| 1952 | const char *zName = PD("name", ""); |
| 1953 | int iFrom = atoi(PD("from","0")); |
| 1954 | int iTo = atoi(PD("to","0")); |
| 1955 | int ln; |
| 1956 | int go = 1; |
| 1957 | const char *zSep; |
| @@ -1968,36 +2240,57 @@ | |
| 1968 | cgi_check_for_malice(); |
| 1969 | if( !g.perm.Read ){ |
| 1970 | ajax_route_error(403, "Access requires Read permissions."); |
| 1971 | return; |
| 1972 | } |
| 1973 | #if 1 |
| 1974 | /* Re-enable this block once this code is integrated somewhere into |
| 1975 | the UI. */ |
| 1976 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); |
| 1977 | if( rid==0 ){ |
| 1978 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 1979 | return; |
| 1980 | } |
| 1981 | #else |
| 1982 | /* This impl is only to simplify "manual" testing via the JS |
| 1983 | console. */ |
| 1984 | rid = symbolic_name_to_rid(zName, "*"); |
| 1985 | if( rid==0 ){ |
| 1986 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 1987 | return; |
| 1988 | }else if( rid<0 ){ |
| 1989 | ajax_route_error(418, "Ambiguous artifact name: %h", zName); |
| 1990 | return; |
| 1991 | } |
| 1992 | #endif |
| 1993 | if( iFrom<1 || iTo<iFrom ){ |
| 1994 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 1995 | iFrom, iTo); |
| 1996 | return; |
| 1997 | } |
| 1998 | content_get(rid, &content); |
| 1999 | g.isConst = 1; |
| 2000 | cgi_set_content_type("application/json"); |
| 2001 | ln = 0; |
| 2002 | while( go && ln<iFrom ){ |
| 2003 | go = blob_line(&content, &line); |
| @@ -2875,10 +3168,143 @@ | |
| 2875 | ticket_output_change_artifact(pTktChng, 0, 1, 0); |
| 2876 | manifest_destroy(pTktChng); |
| 2877 | style_finish_page(); |
| 2878 | } |
| 2879 | |
| 2880 | |
| 2881 | /* |
| 2882 | ** WEBPAGE: info |
| 2883 | ** URL: info/NAME |
| 2884 | ** |
| @@ -2963,10 +3389,14 @@ | |
| 2963 | if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){ |
| 2964 | ci_page(); |
| 2965 | }else |
| 2966 | if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){ |
| 2967 | ainfo_page(); |
| 2968 | }else |
| 2969 | { |
| 2970 | artifact_page(); |
| 2971 | } |
| 2972 | } |
| 2973 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -320,10 +320,11 @@ | |
| 320 | |TIMELINE_NOSCROLL |
| 321 | |TIMELINE_XMERGE |
| 322 | |TIMELINE_CHPICK, |
| 323 | 0, 0, 0, rid, rid2, 0); |
| 324 | db_finalize(&q); |
| 325 | blob_reset(&sql); |
| 326 | } |
| 327 | |
| 328 | |
| 329 | /* |
| 330 | ** Append the difference between artifacts to the output |
| @@ -372,11 +373,13 @@ | |
| 373 | const char *zNew, /* blob.uuid after change. NULL for deletes */ |
| 374 | const char *zOldName, /* Prior name. NULL if no name change. */ |
| 375 | DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */ |
| 376 | int mperm /* executable or symlink permission for zNew */ |
| 377 | ){ |
| 378 | @ <div class='file-change-line'><span> |
| 379 | /* Maintenance reminder: the extra level of SPAN is for |
| 380 | ** arranging new elements via JS. */ |
| 381 | if( !g.perm.Hyperlink ){ |
| 382 | if( zNew==0 ){ |
| 383 | @ Deleted %h(zName). |
| 384 | }else if( zOld==0 ){ |
| 385 | @ Added %h(zName). |
| @@ -391,10 +394,11 @@ | |
| 394 | @ %h(zName) became a regular file. |
| 395 | } |
| 396 | }else{ |
| 397 | @ Changes to %h(zName). |
| 398 | } |
| 399 | @ </span></div> |
| 400 | if( pCfg ){ |
| 401 | append_diff(zOld, zNew, pCfg); |
| 402 | } |
| 403 | }else{ |
| 404 | if( zOld && zNew ){ |
| @@ -438,18 +442,20 @@ | |
| 442 | @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\ |
| 443 | @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>. |
| 444 | } |
| 445 | if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 446 | if( pCfg ){ |
| 447 | @ </span></div> |
| 448 | append_diff(zOld, zNew, pCfg); |
| 449 | }else{ |
| 450 | @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a> |
| 451 | @ </span></div> |
| 452 | } |
| 453 | }else{ |
| 454 | @ </span></div> |
| 455 | } |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | /* |
| 460 | ** Generate javascript to enhance HTML diffs. |
| 461 | */ |
| @@ -602,10 +608,277 @@ | |
| 608 | www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL, |
| 609 | 0, 0, 0, rid, 0, 0); |
| 610 | db_finalize(&q); |
| 611 | style_finish_page(); |
| 612 | } |
| 613 | |
| 614 | /* |
| 615 | ** Render a web-page diff of the changes in the working check-out |
| 616 | */ |
| 617 | static void ckout_normal_diff(int vid){ |
| 618 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 619 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 620 | const char *zW; /* The "w" query parameter */ |
| 621 | int nChng; /* Number of changes */ |
| 622 | Stmt q; |
| 623 | |
| 624 | diffType = preferred_diff_type(); |
| 625 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 626 | nChng = db_int(0, "SELECT count(*) FROM vfile" |
| 627 | " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid); |
| 628 | if( nChng==0 ){ |
| 629 | @ <p>No uncommitted changes</p> |
| 630 | return; |
| 631 | } |
| 632 | db_prepare(&q, |
| 633 | /* 0 1 2 3 4 5 6 */ |
| 634 | "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid" |
| 635 | " FROM vfile LEFT JOIN blob USING(rid)" |
| 636 | " WHERE vid=%d" |
| 637 | " AND (deleted OR chnged OR rid==0)" |
| 638 | " ORDER BY pathname /*scan*/", |
| 639 | vid |
| 640 | ); |
| 641 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 642 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 643 | }else{ |
| 644 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 645 | } |
| 646 | @ <div class="sectionmenu info-changes-menu"> |
| 647 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 648 | if( diffType!=1 ){ |
| 649 | @ %z(chref("button","%R?diff=1%s",zW))Unified Diff</a> |
| 650 | } |
| 651 | if( diffType!=2 ){ |
| 652 | @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side Diff</a> |
| 653 | } |
| 654 | if( diffType!=0 ){ |
| 655 | if( *zW ){ |
| 656 | @ %z(chref("button","%R?diff=%d",diffType))\ |
| 657 | @ Show Whitespace Changes</a> |
| 658 | }else{ |
| 659 | @ %z(chref("button","%R?diff=%d&w",diffType))Ignore Whitespace</a> |
| 660 | } |
| 661 | } |
| 662 | @ </div> |
| 663 | while( db_step(&q)==SQLITE_ROW ){ |
| 664 | const char *zTreename = db_column_text(&q,0); |
| 665 | int isDeleted = db_column_int(&q, 1); |
| 666 | int isChnged = db_column_int(&q,2); |
| 667 | int isNew = db_column_int(&q,3); |
| 668 | int srcid = db_column_int(&q, 4); |
| 669 | int isLink = db_column_int(&q, 5); |
| 670 | const char *zUuid = db_column_text(&q, 6); |
| 671 | int showDiff = 1; |
| 672 | |
| 673 | DCfg.diffFlags &= (~DIFF_FILE_MASK); |
| 674 | @ <div class='file-change-line'><span> |
| 675 | if( isDeleted ){ |
| 676 | @ DELETED %h(zTreename) |
| 677 | DCfg.diffFlags |= DIFF_FILE_DELETED; |
| 678 | showDiff = 0; |
| 679 | }else if( file_access(zTreename, F_OK) ){ |
| 680 | @ MISSING %h(zTreename) |
| 681 | showDiff = 0; |
| 682 | }else if( isNew ){ |
| 683 | @ ADDED %h(zTreename) |
| 684 | DCfg.diffFlags |= DIFF_FILE_ADDED; |
| 685 | srcid = 0; |
| 686 | showDiff = 0; |
| 687 | }else if( isChnged==3 ){ |
| 688 | @ ADDED_BY_MERGE %h(zTreename) |
| 689 | DCfg.diffFlags |= DIFF_FILE_ADDED; |
| 690 | srcid = 0; |
| 691 | showDiff = 0; |
| 692 | }else if( isChnged==5 ){ |
| 693 | @ ADDED_BY_INTEGRATE %h(zTreename) |
| 694 | DCfg.diffFlags |= DIFF_FILE_ADDED; |
| 695 | srcid = 0; |
| 696 | showDiff = 0; |
| 697 | }else{ |
| 698 | @ CHANGED %h(zTreename) |
| 699 | } |
| 700 | @ </span></div> |
| 701 | if( showDiff && pCfg ){ |
| 702 | Blob old, new; |
| 703 | if( !isLink != !file_islink(zTreename) ){ |
| 704 | @ %s(DIFF_CANNOT_COMPUTE_SYMLINK) |
| 705 | continue; |
| 706 | } |
| 707 | if( srcid>0 ){ |
| 708 | content_get(srcid, &old); |
| 709 | pCfg->zLeftHash = zUuid; |
| 710 | }else{ |
| 711 | blob_zero(&old); |
| 712 | pCfg->zLeftHash = 0; |
| 713 | } |
| 714 | blob_read_from_file(&new, zTreename, ExtFILE); |
| 715 | text_diff(&old, &new, cgi_output_blob(), pCfg); |
| 716 | blob_reset(&old); |
| 717 | blob_reset(&new); |
| 718 | } |
| 719 | } |
| 720 | db_finalize(&q); |
| 721 | append_diff_javascript(diffType); |
| 722 | } |
| 723 | |
| 724 | /* |
| 725 | ** Render a web-page diff of the changes in the working check-out to |
| 726 | ** an external reference. |
| 727 | */ |
| 728 | static void ckout_external_base_diff(int vid, const char *zExBase){ |
| 729 | int diffType; /* 0: no diff, 1: unified, 2: side-by-side */ |
| 730 | DiffConfig DCfg,*pCfg; /* Diff details */ |
| 731 | const char *zW; /* The "w" query parameter */ |
| 732 | Stmt q; |
| 733 | |
| 734 | diffType = preferred_diff_type(); |
| 735 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 736 | db_prepare(&q, |
| 737 | "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid |
| 738 | ); |
| 739 | if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){ |
| 740 | DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG; |
| 741 | }else{ |
| 742 | DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG; |
| 743 | } |
| 744 | @ <div class="sectionmenu info-changes-menu"> |
| 745 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 746 | if( diffType!=1 ){ |
| 747 | @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\ |
| 748 | @ Unified Diff</a> |
| 749 | } |
| 750 | if( diffType!=2 ){ |
| 751 | @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\ |
| 752 | @ Side-by-Side Diff</a> |
| 753 | } |
| 754 | if( diffType!=0 ){ |
| 755 | if( *zW ){ |
| 756 | @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\ |
| 757 | @ Show Whitespace Changes</a> |
| 758 | }else{ |
| 759 | @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\ |
| 760 | @ Ignore Whitespace</a> |
| 761 | } |
| 762 | } |
| 763 | @ </div> |
| 764 | while( db_step(&q)==SQLITE_ROW ){ |
| 765 | const char *zFile; /* Name of file in the repository */ |
| 766 | char *zLhs; /* Full name of left-hand side file */ |
| 767 | char *zRhs; /* Full name of right-hand side file */ |
| 768 | Blob rhs; /* Full text of RHS */ |
| 769 | Blob lhs; /* Full text of LHS */ |
| 770 | |
| 771 | zFile = db_column_text(&q,0); |
| 772 | zLhs = mprintf("%s/%s", zExBase, zFile); |
| 773 | zRhs = mprintf("%s%s", g.zLocalRoot, zFile); |
| 774 | if( file_size(zLhs, ExtFILE)<0 ){ |
| 775 | @ <div class='file-change-line'><span> |
| 776 | @ Missing from external baseline: %h(zFile) |
| 777 | @ </span></div> |
| 778 | }else{ |
| 779 | blob_read_from_file(&lhs, zLhs, ExtFILE); |
| 780 | blob_read_from_file(&rhs, zRhs, ExtFILE); |
| 781 | if( blob_size(&lhs)!=blob_size(&rhs) |
| 782 | || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 |
| 783 | ){ |
| 784 | @ <div class='file-change-line'><span> |
| 785 | @ Changes to %h(zFile) |
| 786 | @ </span></div> |
| 787 | if( pCfg ){ |
| 788 | char *zFullFN; |
| 789 | char *zHexFN; |
| 790 | zFullFN = file_canonical_name_dup(zLhs); |
| 791 | zHexFN = mprintf("x%H", zFullFN); |
| 792 | fossil_free(zFullFN); |
| 793 | pCfg->zLeftHash = zHexFN; |
| 794 | text_diff(&lhs, &rhs, cgi_output_blob(), pCfg); |
| 795 | pCfg->zLeftHash = 0; |
| 796 | fossil_free(zHexFN); |
| 797 | } |
| 798 | } |
| 799 | blob_reset(&lhs); |
| 800 | blob_reset(&rhs); |
| 801 | } |
| 802 | fossil_free(zLhs); |
| 803 | fossil_free(zRhs); |
| 804 | } |
| 805 | db_finalize(&q); |
| 806 | append_diff_javascript(diffType); |
| 807 | } |
| 808 | |
| 809 | /* |
| 810 | ** WEBPAGE: ckout |
| 811 | ** |
| 812 | ** Show information about the current checkout. This page only functions |
| 813 | ** if the web server is run on a loopback interface (in other words, was |
| 814 | ** started using "fossil ui" or similar) from within an open check-out. |
| 815 | ** |
| 816 | ** If the "exbase=PATH" query parameter is provided, then the diff shown |
| 817 | ** uses the files in PATH as the baseline. This is the same as using |
| 818 | ** the "--from PATH" argument to the "fossil diff" command-line. In fact, |
| 819 | ** when using "fossil ui --from PATH", the --from argument becomes the value |
| 820 | ** of the exbase query parameter for the start page. Note that if PATH |
| 821 | ** is a pure hexadecimal string, it is decoded first before being used as |
| 822 | ** the pathname. Real pathnames should contain at least one directory |
| 823 | ** separator character. |
| 824 | ** |
| 825 | ** Other query parameters related to diffs are also accepted. |
| 826 | */ |
| 827 | void ckout_page(void){ |
| 828 | int vid; |
| 829 | const char *zHome; /* Home directory */ |
| 830 | int nHome; |
| 831 | const char *zExBase; |
| 832 | char *zHostname; |
| 833 | char *zCwd; |
| 834 | |
| 835 | if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){ |
| 836 | cgi_redirectf("%R/home"); |
| 837 | return; |
| 838 | } |
| 839 | file_chdir(g.zLocalRoot, 0); |
| 840 | vid = db_lget_int("checkout", 0); |
| 841 | db_unprotect(PROTECT_ALL); |
| 842 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 843 | db_protect_pop(); |
| 844 | style_set_current_feature("vinfo"); |
| 845 | zHostname = fossil_hostname(); |
| 846 | zCwd = file_getcwd(0,0); |
| 847 | zHome = fossil_getenv("HOME"); |
| 848 | if( zHome ){ |
| 849 | nHome = (int)strlen(zHome); |
| 850 | if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){ |
| 851 | zCwd = mprintf("~%s", zCwd+nHome); |
| 852 | } |
| 853 | }else{ |
| 854 | nHome = 0; |
| 855 | } |
| 856 | if( zHostname ){ |
| 857 | style_header("Checkout Status: %h on %h", zCwd, zHostname); |
| 858 | }else{ |
| 859 | style_header("Checkout Status: %h", zCwd); |
| 860 | } |
| 861 | render_checkin_context(vid, 0, 0, 0); |
| 862 | @ <hr> |
| 863 | zExBase = P("exbase"); |
| 864 | if( zExBase && zExBase[0] ){ |
| 865 | char *zPath = decode16_dup(zExBase); |
| 866 | char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase); |
| 867 | if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){ |
| 868 | @ <p>Using external baseline: ~%h(zCBase+nHome)</p> |
| 869 | }else{ |
| 870 | @ <p>Using external baseline: %h(zCBase)</p> |
| 871 | } |
| 872 | ckout_external_base_diff(vid, zCBase); |
| 873 | fossil_free(zCBase); |
| 874 | fossil_free(zPath); |
| 875 | }else{ |
| 876 | ckout_normal_diff(vid); |
| 877 | } |
| 878 | style_finish_page(); |
| 879 | } |
| 880 | |
| 881 | /* |
| 882 | ** WEBPAGE: vinfo |
| 883 | ** WEBPAGE: ci |
| 884 | ** URL: /ci/ARTIFACTID |
| @@ -628,11 +901,10 @@ | |
| 901 | const char *zParent; /* Hash of the parent check-in (if any) */ |
| 902 | const char *zRe; /* regex parameter */ |
| 903 | ReCompiled *pRe = 0; /* regex */ |
| 904 | const char *zW; /* URL param for ignoring whitespace */ |
| 905 | const char *zPage = "vinfo"; /* Page that shows diffs */ |
| 906 | const char *zBrName; /* Branch name */ |
| 907 | DiffConfig DCfg,*pCfg; /* Type of diff */ |
| 908 | |
| 909 | login_check_credentials(); |
| 910 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| @@ -898,21 +1170,17 @@ | |
| 1170 | @ <div class="sectionmenu info-changes-menu"> |
| 1171 | /* ^^^ .info-changes-menu is used by diff scroll sync */ |
| 1172 | pCfg = construct_diff_flags(diffType, &DCfg); |
| 1173 | DCfg.pRe = pRe; |
| 1174 | zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":""; |
| 1175 | if( diffType!=1 ){ |
| 1176 | @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\ |
| 1177 | @ Unified Diff</a> |
| 1178 | } |
| 1179 | if( diffType!=2 ){ |
| 1180 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 1181 | @ Side-by-Side Diff</a> |
| 1182 | } |
| 1183 | if( diffType!=0 ){ |
| 1184 | if( *zW ){ |
| 1185 | @ %z(chref("button","%R/%s/%T",zPage,zName)) |
| 1186 | @ Show Whitespace Changes</a> |
| @@ -1268,13 +1536,10 @@ | |
| 1536 | cgi_check_for_malice(); |
| 1537 | style_set_current_feature("vdiff"); |
| 1538 | if( zBranch==0 ){ |
| 1539 | style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo); |
| 1540 | } |
| 1541 | if( diffType!=2 ){ |
| 1542 | style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp, |
| 1543 | &qpGlob); |
| 1544 | } |
| 1545 | if( diffType!=1 ) { |
| @@ -1668,11 +1933,11 @@ | |
| 1933 | tag_private_status(rid); |
| 1934 | } |
| 1935 | db_finalize(&q); |
| 1936 | if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d", |
| 1937 | rid, TAG_CLUSTER) ){ |
| 1938 | @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>. |
| 1939 | cnt++; |
| 1940 | } |
| 1941 | if( cnt==0 ){ |
| 1942 | @ Unrecognized artifact |
| 1943 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| @@ -1933,10 +2198,16 @@ | |
| 2198 | ** WEBPAGE: jchunk hidden |
| 2199 | ** URL: /jchunk/HASH?from=N&to=M |
| 2200 | ** |
| 2201 | ** Return lines of text from a file as a JSON array - one entry in the |
| 2202 | ** array for each line of text. |
| 2203 | ** |
| 2204 | ** The HASH is normally a sha1 or sha3 hash that identifies an artifact |
| 2205 | ** in the BLOB table of the database. However, if HASH starts with an "x" |
| 2206 | ** and is followed by valid hexadecimal, and if we are running in a |
| 2207 | ** "fossil ui" situation (locally and with privilege), then decode the hex |
| 2208 | ** into a filename and read the file content from that name. |
| 2209 | ** |
| 2210 | ** **Warning:** This is an internal-use-only interface that is subject to |
| 2211 | ** change at any moment. External application should not use this interface |
| 2212 | ** since the application will break when this interface changes, and this |
| 2213 | ** interface will undoubtedly change. |
| @@ -1948,10 +2219,11 @@ | |
| 2219 | ** ajax_route_error(). |
| 2220 | */ |
| 2221 | void jchunk_page(void){ |
| 2222 | int rid = 0; |
| 2223 | const char *zName = PD("name", ""); |
| 2224 | int nName = (int)(strlen(zName)&0x7fffffff); |
| 2225 | int iFrom = atoi(PD("from","0")); |
| 2226 | int iTo = atoi(PD("to","0")); |
| 2227 | int ln; |
| 2228 | int go = 1; |
| 2229 | const char *zSep; |
| @@ -1968,36 +2240,57 @@ | |
| 2240 | cgi_check_for_malice(); |
| 2241 | if( !g.perm.Read ){ |
| 2242 | ajax_route_error(403, "Access requires Read permissions."); |
| 2243 | return; |
| 2244 | } |
| 2245 | if( iFrom<1 || iTo<iFrom ){ |
| 2246 | ajax_route_error(500, "Invalid line range from=%d, to=%d.", |
| 2247 | iFrom, iTo); |
| 2248 | return; |
| 2249 | } |
| 2250 | if( zName[0]=='x' |
| 2251 | && ((nName-1)&1)==0 |
| 2252 | && validate16(&zName[1],nName-1) |
| 2253 | && g.perm.Admin |
| 2254 | && cgi_is_loopback(g.zIpAddr) |
| 2255 | && db_open_local(0) |
| 2256 | ){ |
| 2257 | /* Treat the HASH as a hex-encoded filename */ |
| 2258 | int n = (nName-1)/2; |
| 2259 | char *zFN = fossil_malloc(n+1); |
| 2260 | decode16((const u8*)&zName[1], (u8*)zFN, nName-1); |
| 2261 | zFN[n] = 0; |
| 2262 | if( file_size(zFN, ExtFILE)<0 ){ |
| 2263 | blob_zero(&content); |
| 2264 | }else{ |
| 2265 | blob_read_from_file(&content, zFN, ExtFILE); |
| 2266 | } |
| 2267 | fossil_free(zFN); |
| 2268 | }else{ |
| 2269 | /* Treat the HASH as an artifact hash matching BLOB.UUID */ |
| 2270 | #if 1 |
| 2271 | /* Re-enable this block once this code is integrated somewhere into |
| 2272 | the UI. */ |
| 2273 | rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); |
| 2274 | if( rid==0 ){ |
| 2275 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2276 | return; |
| 2277 | } |
| 2278 | #else |
| 2279 | /* This impl is only to simplify "manual" testing via the JS |
| 2280 | console. */ |
| 2281 | rid = symbolic_name_to_rid(zName, "*"); |
| 2282 | if( rid==0 ){ |
| 2283 | ajax_route_error(404, "Unknown artifact: %h", zName); |
| 2284 | return; |
| 2285 | }else if( rid<0 ){ |
| 2286 | ajax_route_error(418, "Ambiguous artifact name: %h", zName); |
| 2287 | return; |
| 2288 | } |
| 2289 | #endif |
| 2290 | content_get(rid, &content); |
| 2291 | } |
| 2292 | g.isConst = 1; |
| 2293 | cgi_set_content_type("application/json"); |
| 2294 | ln = 0; |
| 2295 | while( go && ln<iFrom ){ |
| 2296 | go = blob_line(&content, &line); |
| @@ -2875,10 +3168,143 @@ | |
| 3168 | ticket_output_change_artifact(pTktChng, 0, 1, 0); |
| 3169 | manifest_destroy(pTktChng); |
| 3170 | style_finish_page(); |
| 3171 | } |
| 3172 | |
| 3173 | /* |
| 3174 | ** rid is a cluster. Paint a page that contains detailed information |
| 3175 | ** about that cluster. |
| 3176 | */ |
| 3177 | static void cluster_info(int rid, const char *zName){ |
| 3178 | Manifest *pCluster; |
| 3179 | int i; |
| 3180 | Blob where = BLOB_INITIALIZER; |
| 3181 | Blob unks = BLOB_INITIALIZER; |
| 3182 | Stmt q; |
| 3183 | char *zSha1Bg; |
| 3184 | char *zSha3Bg; |
| 3185 | int badRid = 0; |
| 3186 | int rcvid; |
| 3187 | int hashClr = PB("hclr"); |
| 3188 | const char *zDate; |
| 3189 | |
| 3190 | pCluster = manifest_get(rid, CFTYPE_CLUSTER, 0); |
| 3191 | if( pCluster==0 ){ |
| 3192 | artifact_page(); |
| 3193 | return; |
| 3194 | } |
| 3195 | style_header("Cluster %S", zName); |
| 3196 | rcvid = db_int(0, "SELECT rcvid FROM blob WHERE rid=%d", rid); |
| 3197 | if( rcvid==0 ){ |
| 3198 | zDate = 0; |
| 3199 | }else{ |
| 3200 | zDate = db_text(0, "SELECT datetime(mtime) FROM rcvfrom WHERE rcvid=%d", |
| 3201 | rcvid); |
| 3202 | } |
| 3203 | @ <p>Artifact %z(href("%R/artifact/%h",zName))%S(zName)</a> is a cluster |
| 3204 | @ with %d(pCluster->nCChild) entries |
| 3205 | if( g.perm.Admin ){ |
| 3206 | @ received <a href="%R/rcvfrom?rcvid=%d(rcvid)">%h(zDate)</a>: |
| 3207 | }else{ |
| 3208 | @ received %h(zDate): |
| 3209 | } |
| 3210 | blob_appendf(&where,"IN(0"); |
| 3211 | for(i=0; i<pCluster->nCChild; i++){ |
| 3212 | int rid = fast_uuid_to_rid(pCluster->azCChild[i]); |
| 3213 | if( rid ){ |
| 3214 | blob_appendf(&where,",%d", rid); |
| 3215 | }else{ |
| 3216 | if( blob_size(&unks)>0 ) blob_append_char(&unks, ','); |
| 3217 | badRid++; |
| 3218 | blob_append_sql(&unks,"(%d,%Q)",-badRid,pCluster->azCChild[i]); |
| 3219 | } |
| 3220 | } |
| 3221 | blob_append_char(&where,')'); |
| 3222 | describe_artifacts(blob_str(&where)); |
| 3223 | blob_reset(&where); |
| 3224 | if( badRid>0 ){ |
| 3225 | db_multi_exec( |
| 3226 | "WITH unks(rx,hx) AS (VALUES %s)\n" |
| 3227 | "INSERT INTO description(rid,uuid,type,summary) " |
| 3228 | " SELECT rx, hx, 'phantom', '' FROM unks;", |
| 3229 | blob_sql_text(&unks) |
| 3230 | ); |
| 3231 | } |
| 3232 | blob_reset(&unks); |
| 3233 | db_prepare(&q, |
| 3234 | "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref" |
| 3235 | " FROM description ORDER BY uuid" |
| 3236 | ); |
| 3237 | if( skin_detail_boolean("white-foreground") ){ |
| 3238 | zSha1Bg = "#714417"; |
| 3239 | zSha3Bg = "#177117"; |
| 3240 | }else{ |
| 3241 | zSha1Bg = "#ebffb0"; |
| 3242 | zSha3Bg = "#b0ffb0"; |
| 3243 | } |
| 3244 | @ <table cellpadding="2" cellspacing="0" border="1"> |
| 3245 | if( g.perm.Admin ){ |
| 3246 | @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks |
| 3247 | }else{ |
| 3248 | @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks |
| 3249 | } |
| 3250 | while( db_step(&q)==SQLITE_ROW ){ |
| 3251 | int rid = db_column_int(&q,0); |
| 3252 | const char *zUuid = db_column_text(&q, 1); |
| 3253 | const char *zDesc = db_column_text(&q, 2); |
| 3254 | int isPriv = db_column_int(&q,3); |
| 3255 | int isPhantom = db_column_int(&q,4); |
| 3256 | const char *zRef = db_column_text(&q,6); |
| 3257 | if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ |
| 3258 | /* Don't show private artifacts to users without Private (x) permission */ |
| 3259 | continue; |
| 3260 | } |
| 3261 | if( rid<=0 ){ |
| 3262 | @ <tr><td> </td> |
| 3263 | }else if( hashClr ){ |
| 3264 | const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg; |
| 3265 | @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td> |
| 3266 | }else{ |
| 3267 | @ <tr><td align="right">%d(rid)</td> |
| 3268 | } |
| 3269 | if( rid<=0 ){ |
| 3270 | @ <td> %S(zUuid) </td> |
| 3271 | }else{ |
| 3272 | @ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td> |
| 3273 | } |
| 3274 | if( g.perm.Admin ){ |
| 3275 | int rcvid = db_column_int(&q,5); |
| 3276 | if( rcvid<=0 ){ |
| 3277 | @ <td> |
| 3278 | }else{ |
| 3279 | @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a> |
| 3280 | } |
| 3281 | } |
| 3282 | @ <td align="left">%h(zDesc)</td> |
| 3283 | if( zRef && zRef[0] ){ |
| 3284 | @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a> |
| 3285 | }else{ |
| 3286 | @ <td> |
| 3287 | } |
| 3288 | if( isPriv || isPhantom ){ |
| 3289 | if( isPriv==0 ){ |
| 3290 | @ <td>phantom</td> |
| 3291 | }else if( isPhantom==0 ){ |
| 3292 | @ <td>private</td> |
| 3293 | }else{ |
| 3294 | @ <td>private,phantom</td> |
| 3295 | } |
| 3296 | }else{ |
| 3297 | @ <td> |
| 3298 | } |
| 3299 | @ </tr> |
| 3300 | } |
| 3301 | @ </table> |
| 3302 | db_finalize(&q); |
| 3303 | style_finish_page(); |
| 3304 | } |
| 3305 | |
| 3306 | |
| 3307 | /* |
| 3308 | ** WEBPAGE: info |
| 3309 | ** URL: info/NAME |
| 3310 | ** |
| @@ -2963,10 +3389,14 @@ | |
| 3389 | if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){ |
| 3390 | ci_page(); |
| 3391 | }else |
| 3392 | if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){ |
| 3393 | ainfo_page(); |
| 3394 | }else |
| 3395 | if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d", |
| 3396 | rid, TAG_CLUSTER) ){ |
| 3397 | cluster_info(rid, zName); |
| 3398 | }else |
| 3399 | { |
| 3400 | artifact_page(); |
| 3401 | } |
| 3402 | } |
| 3403 |
+2
-1
| --- src/interwiki.c | ||
| +++ src/interwiki.c | ||
| @@ -275,13 +275,14 @@ | ||
| 275 | 275 | db_prepare(&q, |
| 276 | 276 | "SELECT substr(name,11), value->>'base'" |
| 277 | 277 | " FROM config WHERE name glob 'interwiki:*' AND json_valid(value)" |
| 278 | 278 | " ORDER BY name;" |
| 279 | 279 | ); |
| 280 | + blob_append(out, "<blockquote>", -1); | |
| 280 | 281 | while( db_step(&q)==SQLITE_ROW ){ |
| 281 | 282 | if( n==0 ){ |
| 282 | - blob_appendf(out, "<blockquote><table>\n"); | |
| 283 | + blob_appendf(out, "<table>\n"); | |
| 283 | 284 | } |
| 284 | 285 | blob_appendf(out,"<tr><td>%h</td><td> → </td>", |
| 285 | 286 | db_column_text(&q,0)); |
| 286 | 287 | blob_appendf(out,"<td>%h</td></tr>\n", |
| 287 | 288 | db_column_text(&q,1)); |
| 288 | 289 |
| --- src/interwiki.c | |
| +++ src/interwiki.c | |
| @@ -275,13 +275,14 @@ | |
| 275 | db_prepare(&q, |
| 276 | "SELECT substr(name,11), value->>'base'" |
| 277 | " FROM config WHERE name glob 'interwiki:*' AND json_valid(value)" |
| 278 | " ORDER BY name;" |
| 279 | ); |
| 280 | while( db_step(&q)==SQLITE_ROW ){ |
| 281 | if( n==0 ){ |
| 282 | blob_appendf(out, "<blockquote><table>\n"); |
| 283 | } |
| 284 | blob_appendf(out,"<tr><td>%h</td><td> → </td>", |
| 285 | db_column_text(&q,0)); |
| 286 | blob_appendf(out,"<td>%h</td></tr>\n", |
| 287 | db_column_text(&q,1)); |
| 288 |
| --- src/interwiki.c | |
| +++ src/interwiki.c | |
| @@ -275,13 +275,14 @@ | |
| 275 | db_prepare(&q, |
| 276 | "SELECT substr(name,11), value->>'base'" |
| 277 | " FROM config WHERE name glob 'interwiki:*' AND json_valid(value)" |
| 278 | " ORDER BY name;" |
| 279 | ); |
| 280 | blob_append(out, "<blockquote>", -1); |
| 281 | while( db_step(&q)==SQLITE_ROW ){ |
| 282 | if( n==0 ){ |
| 283 | blob_appendf(out, "<table>\n"); |
| 284 | } |
| 285 | blob_appendf(out,"<tr><td>%h</td><td> → </td>", |
| 286 | db_column_text(&q,0)); |
| 287 | blob_appendf(out,"<td>%h</td></tr>\n", |
| 288 | db_column_text(&q,1)); |
| 289 |
+13
-1
| --- src/login.c | ||
| +++ src/login.c | ||
| @@ -1302,20 +1302,32 @@ | ||
| 1302 | 1302 | */ |
| 1303 | 1303 | void login_restrict_robot_access(void){ |
| 1304 | 1304 | const char *zReferer; |
| 1305 | 1305 | const char *zGlob; |
| 1306 | 1306 | int isMatch = 1; |
| 1307 | + int nQP; /* Number of query parameters other than name= */ | |
| 1307 | 1308 | if( g.zLogin!=0 ) return; |
| 1308 | 1309 | zGlob = db_get("robot-restrict",0); |
| 1309 | 1310 | if( zGlob==0 || zGlob[0]==0 ) return; |
| 1310 | 1311 | if( g.isHuman ){ |
| 1311 | 1312 | zReferer = P("HTTP_REFERER"); |
| 1312 | 1313 | if( zReferer && zReferer[0]!=0 ) return; |
| 1313 | 1314 | } |
| 1314 | - if( cgi_qp_count()<1 ) return; | |
| 1315 | + nQP = cgi_qp_count(); | |
| 1316 | + if( nQP<1 ) return; | |
| 1315 | 1317 | isMatch = glob_multi_match(zGlob, g.zPath); |
| 1316 | 1318 | if( !isMatch ) return; |
| 1319 | + | |
| 1320 | + /* Check for exceptions to the restriction on the number of query | |
| 1321 | + ** parameters. */ | |
| 1322 | + zGlob = db_get("robot-restrict-qp",0); | |
| 1323 | + if( zGlob && zGlob[0] ){ | |
| 1324 | + char *zPath = mprintf("%s/%d", g.zPath, nQP); | |
| 1325 | + isMatch = glob_multi_match(zGlob, zPath); | |
| 1326 | + fossil_free(zPath); | |
| 1327 | + if( isMatch ) return; | |
| 1328 | + } | |
| 1317 | 1329 | |
| 1318 | 1330 | /* If we reach this point, it means we have a situation where we |
| 1319 | 1331 | ** want to restrict the activity of a robot. |
| 1320 | 1332 | */ |
| 1321 | 1333 | g.isHuman = 0; |
| 1322 | 1334 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -1302,20 +1302,32 @@ | |
| 1302 | */ |
| 1303 | void login_restrict_robot_access(void){ |
| 1304 | const char *zReferer; |
| 1305 | const char *zGlob; |
| 1306 | int isMatch = 1; |
| 1307 | if( g.zLogin!=0 ) return; |
| 1308 | zGlob = db_get("robot-restrict",0); |
| 1309 | if( zGlob==0 || zGlob[0]==0 ) return; |
| 1310 | if( g.isHuman ){ |
| 1311 | zReferer = P("HTTP_REFERER"); |
| 1312 | if( zReferer && zReferer[0]!=0 ) return; |
| 1313 | } |
| 1314 | if( cgi_qp_count()<1 ) return; |
| 1315 | isMatch = glob_multi_match(zGlob, g.zPath); |
| 1316 | if( !isMatch ) return; |
| 1317 | |
| 1318 | /* If we reach this point, it means we have a situation where we |
| 1319 | ** want to restrict the activity of a robot. |
| 1320 | */ |
| 1321 | g.isHuman = 0; |
| 1322 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -1302,20 +1302,32 @@ | |
| 1302 | */ |
| 1303 | void login_restrict_robot_access(void){ |
| 1304 | const char *zReferer; |
| 1305 | const char *zGlob; |
| 1306 | int isMatch = 1; |
| 1307 | int nQP; /* Number of query parameters other than name= */ |
| 1308 | if( g.zLogin!=0 ) return; |
| 1309 | zGlob = db_get("robot-restrict",0); |
| 1310 | if( zGlob==0 || zGlob[0]==0 ) return; |
| 1311 | if( g.isHuman ){ |
| 1312 | zReferer = P("HTTP_REFERER"); |
| 1313 | if( zReferer && zReferer[0]!=0 ) return; |
| 1314 | } |
| 1315 | nQP = cgi_qp_count(); |
| 1316 | if( nQP<1 ) return; |
| 1317 | isMatch = glob_multi_match(zGlob, g.zPath); |
| 1318 | if( !isMatch ) return; |
| 1319 | |
| 1320 | /* Check for exceptions to the restriction on the number of query |
| 1321 | ** parameters. */ |
| 1322 | zGlob = db_get("robot-restrict-qp",0); |
| 1323 | if( zGlob && zGlob[0] ){ |
| 1324 | char *zPath = mprintf("%s/%d", g.zPath, nQP); |
| 1325 | isMatch = glob_multi_match(zGlob, zPath); |
| 1326 | fossil_free(zPath); |
| 1327 | if( isMatch ) return; |
| 1328 | } |
| 1329 | |
| 1330 | /* If we reach this point, it means we have a situation where we |
| 1331 | ** want to restrict the activity of a robot. |
| 1332 | */ |
| 1333 | g.isHuman = 0; |
| 1334 |
+24
-10
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -1330,10 +1330,11 @@ | ||
| 1330 | 1330 | |
| 1331 | 1331 | /* We should be done with options.. */ |
| 1332 | 1332 | verify_all_options(); |
| 1333 | 1333 | fossil_version_blob(&versionInfo, verboseFlag); |
| 1334 | 1334 | fossil_print("%s", blob_str(&versionInfo)); |
| 1335 | + blob_reset(&versionInfo); | |
| 1335 | 1336 | } |
| 1336 | 1337 | |
| 1337 | 1338 | |
| 1338 | 1339 | /* |
| 1339 | 1340 | ** WEBPAGE: version |
| @@ -2040,20 +2041,27 @@ | ||
| 2040 | 2041 | */ |
| 2041 | 2042 | set_base_url(0); |
| 2042 | 2043 | if( fossil_redirect_to_https_if_needed(2) ) return; |
| 2043 | 2044 | if( zPathInfo==0 || zPathInfo[0]==0 |
| 2044 | 2045 | || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ |
| 2045 | - /* Second special case: If the PATH_INFO is blank, issue a redirect to | |
| 2046 | - ** the home page identified by the "index-page" setting in the repository | |
| 2047 | - ** CONFIG table, to "/index" if there no "index-page" setting. */ | |
| 2046 | + /* Second special case: If the PATH_INFO is blank, issue a redirect: | |
| 2047 | + ** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set. | |
| 2048 | + ** (2) to the home page identified by the "index-page" setting | |
| 2049 | + ** in the repository CONFIG table | |
| 2050 | + ** (3) to "/index" if there no "index-page" setting in CONFIG | |
| 2051 | + */ | |
| 2048 | 2052 | #ifdef FOSSIL_ENABLE_JSON |
| 2049 | 2053 | if(g.json.isJsonMode){ |
| 2050 | 2054 | json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); |
| 2051 | 2055 | fossil_exit(0); |
| 2052 | 2056 | } |
| 2053 | 2057 | #endif |
| 2054 | - fossil_redirect_home() /*does not return*/; | |
| 2058 | + if( g.useLocalauth && g.localOpen ){ | |
| 2059 | + cgi_redirectf("%R/ckout"); | |
| 2060 | + }else{ | |
| 2061 | + fossil_redirect_home() /*does not return*/; | |
| 2062 | + } | |
| 2055 | 2063 | }else{ |
| 2056 | 2064 | zPath = mprintf("%s", zPathInfo); |
| 2057 | 2065 | } |
| 2058 | 2066 | |
| 2059 | 2067 | /* Make g.zPath point to the first element of the path. Make |
| @@ -3171,10 +3179,11 @@ | ||
| 3171 | 3179 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3172 | 3180 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3173 | 3181 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3174 | 3182 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3175 | 3183 | ** system when REPOSITORY is remote. |
| 3184 | +** --from PATH Use PATH as the diff baseline for the /ckout page | |
| 3176 | 3185 | ** --localauth Enable automatic login for requests from localhost |
| 3177 | 3186 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| 3178 | 3187 | ** --https Indicates that the input is coming through a reverse |
| 3179 | 3188 | ** proxy that has already translated HTTPS into HTTP. |
| 3180 | 3189 | ** --jsmode MODE Determine how JavaScript is delivered with pages. |
| @@ -3243,10 +3252,11 @@ | ||
| 3243 | 3252 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3244 | 3253 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3245 | 3254 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3246 | 3255 | const char *zJsMode; /* The --jsmode parameter */ |
| 3247 | 3256 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3257 | + const char *zFrom; /* Value for --from */ | |
| 3248 | 3258 | |
| 3249 | 3259 | |
| 3250 | 3260 | #if USE_SEE |
| 3251 | 3261 | db_setup_for_saved_encryption_key(); |
| 3252 | 3262 | #endif |
| @@ -3279,13 +3289,21 @@ | ||
| 3279 | 3289 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3280 | 3290 | Th_InitTraceLog(); |
| 3281 | 3291 | zPort = find_option("port", "P", 1); |
| 3282 | 3292 | isUiCmd = g.argv[1][0]=='u'; |
| 3283 | 3293 | if( isUiCmd ){ |
| 3294 | + zFrom = find_option("from", 0, 1); | |
| 3295 | + if( zFrom && zFrom==file_tail(zFrom) ){ | |
| 3296 | + fossil_fatal("the argument to --from must be a pathname for" | |
| 3297 | + " the \"ui\" command"); | |
| 3298 | + } | |
| 3284 | 3299 | zInitPage = find_option("page", "p", 1); |
| 3285 | 3300 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3286 | 3301 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3302 | + if( zFrom && zInitPage==0 ){ | |
| 3303 | + zInitPage = mprintf("ckout?exbase=%H", zFrom); | |
| 3304 | + } | |
| 3287 | 3305 | } |
| 3288 | 3306 | zNotFound = find_option("notfound", 0, 1); |
| 3289 | 3307 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3290 | 3308 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3291 | 3309 | zAltBase = find_option("baseurl", 0, 1); |
| @@ -3356,11 +3374,11 @@ | ||
| 3356 | 3374 | const char * zDir = g.argv[2]; |
| 3357 | 3375 | if(dir_has_ckout_db(zDir)){ |
| 3358 | 3376 | if(0!=file_chdir(zDir, 0)){ |
| 3359 | 3377 | fossil_fatal("Cannot chdir to %s", zDir); |
| 3360 | 3378 | } |
| 3361 | - findServerArg = 99; | |
| 3379 | + findServerArg = g.argc; | |
| 3362 | 3380 | fCreate = 0; |
| 3363 | 3381 | g.argv[2] = 0; |
| 3364 | 3382 | --g.argc; |
| 3365 | 3383 | } |
| 3366 | 3384 | } |
| @@ -3384,15 +3402,11 @@ | ||
| 3384 | 3402 | } |
| 3385 | 3403 | if( !zRemote ){ |
| 3386 | 3404 | find_server_repository(findServerArg, fCreate); |
| 3387 | 3405 | } |
| 3388 | 3406 | if( zInitPage==0 ){ |
| 3389 | - if( isUiCmd && g.localOpen ){ | |
| 3390 | - zInitPage = "timeline?c=current"; | |
| 3391 | - }else{ | |
| 3392 | - zInitPage = ""; | |
| 3393 | - } | |
| 3407 | + zInitPage = ""; | |
| 3394 | 3408 | } |
| 3395 | 3409 | if( zPort ){ |
| 3396 | 3410 | if( strchr(zPort,':') ){ |
| 3397 | 3411 | int i; |
| 3398 | 3412 | for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} |
| 3399 | 3413 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1330,10 +1330,11 @@ | |
| 1330 | |
| 1331 | /* We should be done with options.. */ |
| 1332 | verify_all_options(); |
| 1333 | fossil_version_blob(&versionInfo, verboseFlag); |
| 1334 | fossil_print("%s", blob_str(&versionInfo)); |
| 1335 | } |
| 1336 | |
| 1337 | |
| 1338 | /* |
| 1339 | ** WEBPAGE: version |
| @@ -2040,20 +2041,27 @@ | |
| 2040 | */ |
| 2041 | set_base_url(0); |
| 2042 | if( fossil_redirect_to_https_if_needed(2) ) return; |
| 2043 | if( zPathInfo==0 || zPathInfo[0]==0 |
| 2044 | || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ |
| 2045 | /* Second special case: If the PATH_INFO is blank, issue a redirect to |
| 2046 | ** the home page identified by the "index-page" setting in the repository |
| 2047 | ** CONFIG table, to "/index" if there no "index-page" setting. */ |
| 2048 | #ifdef FOSSIL_ENABLE_JSON |
| 2049 | if(g.json.isJsonMode){ |
| 2050 | json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); |
| 2051 | fossil_exit(0); |
| 2052 | } |
| 2053 | #endif |
| 2054 | fossil_redirect_home() /*does not return*/; |
| 2055 | }else{ |
| 2056 | zPath = mprintf("%s", zPathInfo); |
| 2057 | } |
| 2058 | |
| 2059 | /* Make g.zPath point to the first element of the path. Make |
| @@ -3171,10 +3179,11 @@ | |
| 3171 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3172 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3173 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3174 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3175 | ** system when REPOSITORY is remote. |
| 3176 | ** --localauth Enable automatic login for requests from localhost |
| 3177 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| 3178 | ** --https Indicates that the input is coming through a reverse |
| 3179 | ** proxy that has already translated HTTPS into HTTP. |
| 3180 | ** --jsmode MODE Determine how JavaScript is delivered with pages. |
| @@ -3243,10 +3252,11 @@ | |
| 3243 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3244 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3245 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3246 | const char *zJsMode; /* The --jsmode parameter */ |
| 3247 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3248 | |
| 3249 | |
| 3250 | #if USE_SEE |
| 3251 | db_setup_for_saved_encryption_key(); |
| 3252 | #endif |
| @@ -3279,13 +3289,21 @@ | |
| 3279 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3280 | Th_InitTraceLog(); |
| 3281 | zPort = find_option("port", "P", 1); |
| 3282 | isUiCmd = g.argv[1][0]=='u'; |
| 3283 | if( isUiCmd ){ |
| 3284 | zInitPage = find_option("page", "p", 1); |
| 3285 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3286 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3287 | } |
| 3288 | zNotFound = find_option("notfound", 0, 1); |
| 3289 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3290 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3291 | zAltBase = find_option("baseurl", 0, 1); |
| @@ -3356,11 +3374,11 @@ | |
| 3356 | const char * zDir = g.argv[2]; |
| 3357 | if(dir_has_ckout_db(zDir)){ |
| 3358 | if(0!=file_chdir(zDir, 0)){ |
| 3359 | fossil_fatal("Cannot chdir to %s", zDir); |
| 3360 | } |
| 3361 | findServerArg = 99; |
| 3362 | fCreate = 0; |
| 3363 | g.argv[2] = 0; |
| 3364 | --g.argc; |
| 3365 | } |
| 3366 | } |
| @@ -3384,15 +3402,11 @@ | |
| 3384 | } |
| 3385 | if( !zRemote ){ |
| 3386 | find_server_repository(findServerArg, fCreate); |
| 3387 | } |
| 3388 | if( zInitPage==0 ){ |
| 3389 | if( isUiCmd && g.localOpen ){ |
| 3390 | zInitPage = "timeline?c=current"; |
| 3391 | }else{ |
| 3392 | zInitPage = ""; |
| 3393 | } |
| 3394 | } |
| 3395 | if( zPort ){ |
| 3396 | if( strchr(zPort,':') ){ |
| 3397 | int i; |
| 3398 | for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} |
| 3399 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1330,10 +1330,11 @@ | |
| 1330 | |
| 1331 | /* We should be done with options.. */ |
| 1332 | verify_all_options(); |
| 1333 | fossil_version_blob(&versionInfo, verboseFlag); |
| 1334 | fossil_print("%s", blob_str(&versionInfo)); |
| 1335 | blob_reset(&versionInfo); |
| 1336 | } |
| 1337 | |
| 1338 | |
| 1339 | /* |
| 1340 | ** WEBPAGE: version |
| @@ -2040,20 +2041,27 @@ | |
| 2041 | */ |
| 2042 | set_base_url(0); |
| 2043 | if( fossil_redirect_to_https_if_needed(2) ) return; |
| 2044 | if( zPathInfo==0 || zPathInfo[0]==0 |
| 2045 | || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ |
| 2046 | /* Second special case: If the PATH_INFO is blank, issue a redirect: |
| 2047 | ** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set. |
| 2048 | ** (2) to the home page identified by the "index-page" setting |
| 2049 | ** in the repository CONFIG table |
| 2050 | ** (3) to "/index" if there no "index-page" setting in CONFIG |
| 2051 | */ |
| 2052 | #ifdef FOSSIL_ENABLE_JSON |
| 2053 | if(g.json.isJsonMode){ |
| 2054 | json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); |
| 2055 | fossil_exit(0); |
| 2056 | } |
| 2057 | #endif |
| 2058 | if( g.useLocalauth && g.localOpen ){ |
| 2059 | cgi_redirectf("%R/ckout"); |
| 2060 | }else{ |
| 2061 | fossil_redirect_home() /*does not return*/; |
| 2062 | } |
| 2063 | }else{ |
| 2064 | zPath = mprintf("%s", zPathInfo); |
| 2065 | } |
| 2066 | |
| 2067 | /* Make g.zPath point to the first element of the path. Make |
| @@ -3171,10 +3179,11 @@ | |
| 3179 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3180 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3181 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3182 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3183 | ** system when REPOSITORY is remote. |
| 3184 | ** --from PATH Use PATH as the diff baseline for the /ckout page |
| 3185 | ** --localauth Enable automatic login for requests from localhost |
| 3186 | ** --localhost Listen on 127.0.0.1 only (always true for "ui") |
| 3187 | ** --https Indicates that the input is coming through a reverse |
| 3188 | ** proxy that has already translated HTTPS into HTTP. |
| 3189 | ** --jsmode MODE Determine how JavaScript is delivered with pages. |
| @@ -3243,10 +3252,11 @@ | |
| 3252 | const char *zInitPage = 0; /* Start on this page. --page option */ |
| 3253 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3254 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3255 | const char *zJsMode; /* The --jsmode parameter */ |
| 3256 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3257 | const char *zFrom; /* Value for --from */ |
| 3258 | |
| 3259 | |
| 3260 | #if USE_SEE |
| 3261 | db_setup_for_saved_encryption_key(); |
| 3262 | #endif |
| @@ -3279,13 +3289,21 @@ | |
| 3289 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 3290 | Th_InitTraceLog(); |
| 3291 | zPort = find_option("port", "P", 1); |
| 3292 | isUiCmd = g.argv[1][0]=='u'; |
| 3293 | if( isUiCmd ){ |
| 3294 | zFrom = find_option("from", 0, 1); |
| 3295 | if( zFrom && zFrom==file_tail(zFrom) ){ |
| 3296 | fossil_fatal("the argument to --from must be a pathname for" |
| 3297 | " the \"ui\" command"); |
| 3298 | } |
| 3299 | zInitPage = find_option("page", "p", 1); |
| 3300 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3301 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3302 | if( zFrom && zInitPage==0 ){ |
| 3303 | zInitPage = mprintf("ckout?exbase=%H", zFrom); |
| 3304 | } |
| 3305 | } |
| 3306 | zNotFound = find_option("notfound", 0, 1); |
| 3307 | allowRepoList = find_option("repolist",0,0)!=0; |
| 3308 | if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; |
| 3309 | zAltBase = find_option("baseurl", 0, 1); |
| @@ -3356,11 +3374,11 @@ | |
| 3374 | const char * zDir = g.argv[2]; |
| 3375 | if(dir_has_ckout_db(zDir)){ |
| 3376 | if(0!=file_chdir(zDir, 0)){ |
| 3377 | fossil_fatal("Cannot chdir to %s", zDir); |
| 3378 | } |
| 3379 | findServerArg = g.argc; |
| 3380 | fCreate = 0; |
| 3381 | g.argv[2] = 0; |
| 3382 | --g.argc; |
| 3383 | } |
| 3384 | } |
| @@ -3384,15 +3402,11 @@ | |
| 3402 | } |
| 3403 | if( !zRemote ){ |
| 3404 | find_server_repository(findServerArg, fCreate); |
| 3405 | } |
| 3406 | if( zInitPage==0 ){ |
| 3407 | zInitPage = ""; |
| 3408 | } |
| 3409 | if( zPort ){ |
| 3410 | if( strchr(zPort,':') ){ |
| 3411 | int i; |
| 3412 | for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} |
| 3413 |
+14
-1
| --- src/main.mk | ||
| +++ src/main.mk | ||
| @@ -99,10 +99,11 @@ | ||
| 99 | 99 | $(SRCDIR)/lookslike.c \ |
| 100 | 100 | $(SRCDIR)/main.c \ |
| 101 | 101 | $(SRCDIR)/manifest.c \ |
| 102 | 102 | $(SRCDIR)/markdown.c \ |
| 103 | 103 | $(SRCDIR)/markdown_html.c \ |
| 104 | + $(SRCDIR)/match.c \ | |
| 104 | 105 | $(SRCDIR)/md5.c \ |
| 105 | 106 | $(SRCDIR)/merge.c \ |
| 106 | 107 | $(SRCDIR)/merge3.c \ |
| 107 | 108 | $(SRCDIR)/moderate.c \ |
| 108 | 109 | $(SRCDIR)/name.c \ |
| @@ -248,10 +249,11 @@ | ||
| 248 | 249 | $(SRCDIR)/hbmenu.js \ |
| 249 | 250 | $(SRCDIR)/href.js \ |
| 250 | 251 | $(SRCDIR)/login.js \ |
| 251 | 252 | $(SRCDIR)/markdown.md \ |
| 252 | 253 | $(SRCDIR)/menu.js \ |
| 254 | + $(SRCDIR)/merge.tcl \ | |
| 253 | 255 | $(SRCDIR)/scroll.js \ |
| 254 | 256 | $(SRCDIR)/skin.js \ |
| 255 | 257 | $(SRCDIR)/sorttable.js \ |
| 256 | 258 | $(SRCDIR)/sounds/0.wav \ |
| 257 | 259 | $(SRCDIR)/sounds/1.wav \ |
| @@ -363,10 +365,11 @@ | ||
| 363 | 365 | $(OBJDIR)/lookslike_.c \ |
| 364 | 366 | $(OBJDIR)/main_.c \ |
| 365 | 367 | $(OBJDIR)/manifest_.c \ |
| 366 | 368 | $(OBJDIR)/markdown_.c \ |
| 367 | 369 | $(OBJDIR)/markdown_html_.c \ |
| 370 | + $(OBJDIR)/match_.c \ | |
| 368 | 371 | $(OBJDIR)/md5_.c \ |
| 369 | 372 | $(OBJDIR)/merge_.c \ |
| 370 | 373 | $(OBJDIR)/merge3_.c \ |
| 371 | 374 | $(OBJDIR)/moderate_.c \ |
| 372 | 375 | $(OBJDIR)/name_.c \ |
| @@ -512,10 +515,11 @@ | ||
| 512 | 515 | $(OBJDIR)/lookslike.o \ |
| 513 | 516 | $(OBJDIR)/main.o \ |
| 514 | 517 | $(OBJDIR)/manifest.o \ |
| 515 | 518 | $(OBJDIR)/markdown.o \ |
| 516 | 519 | $(OBJDIR)/markdown_html.o \ |
| 520 | + $(OBJDIR)/match.o \ | |
| 517 | 521 | $(OBJDIR)/md5.o \ |
| 518 | 522 | $(OBJDIR)/merge.o \ |
| 519 | 523 | $(OBJDIR)/merge3.o \ |
| 520 | 524 | $(OBJDIR)/moderate.o \ |
| 521 | 525 | $(OBJDIR)/name.o \ |
| @@ -704,11 +708,11 @@ | ||
| 704 | 708 | |
| 705 | 709 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 706 | 710 | # to 1. If it is set to 0, then there is no need to build or link |
| 707 | 711 | # the linenoise.o object. |
| 708 | 712 | LINENOISE_DEF.0 = |
| 709 | -LINENOISE_DEF.1 = -DHAVE_LINENOISE | |
| 713 | +LINENOISE_DEF.1 = -DHAVE_LINENOISE=2 | |
| 710 | 714 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 711 | 715 | LINENOISE_OBJ.0 = |
| 712 | 716 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 713 | 717 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 714 | 718 | |
| @@ -847,10 +851,11 @@ | ||
| 847 | 851 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 848 | 852 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 849 | 853 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 850 | 854 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 851 | 855 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 856 | + $(OBJDIR)/match_.c:$(OBJDIR)/match.h \ | |
| 852 | 857 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 853 | 858 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 854 | 859 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 855 | 860 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 856 | 861 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -1596,10 +1601,18 @@ | ||
| 1596 | 1601 | |
| 1597 | 1602 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 1598 | 1603 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 1599 | 1604 | |
| 1600 | 1605 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 1606 | + | |
| 1607 | +$(OBJDIR)/match_.c: $(SRCDIR)/match.c $(OBJDIR)/translate | |
| 1608 | + $(OBJDIR)/translate $(SRCDIR)/match.c >$@ | |
| 1609 | + | |
| 1610 | +$(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h | |
| 1611 | + $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c | |
| 1612 | + | |
| 1613 | +$(OBJDIR)/match.h: $(OBJDIR)/headers | |
| 1601 | 1614 | |
| 1602 | 1615 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate |
| 1603 | 1616 | $(OBJDIR)/translate $(SRCDIR)/md5.c >$@ |
| 1604 | 1617 | |
| 1605 | 1618 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 1606 | 1619 | |
| 1607 | 1620 | ADDED src/match.c |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -99,10 +99,11 @@ | |
| 99 | $(SRCDIR)/lookslike.c \ |
| 100 | $(SRCDIR)/main.c \ |
| 101 | $(SRCDIR)/manifest.c \ |
| 102 | $(SRCDIR)/markdown.c \ |
| 103 | $(SRCDIR)/markdown_html.c \ |
| 104 | $(SRCDIR)/md5.c \ |
| 105 | $(SRCDIR)/merge.c \ |
| 106 | $(SRCDIR)/merge3.c \ |
| 107 | $(SRCDIR)/moderate.c \ |
| 108 | $(SRCDIR)/name.c \ |
| @@ -248,10 +249,11 @@ | |
| 248 | $(SRCDIR)/hbmenu.js \ |
| 249 | $(SRCDIR)/href.js \ |
| 250 | $(SRCDIR)/login.js \ |
| 251 | $(SRCDIR)/markdown.md \ |
| 252 | $(SRCDIR)/menu.js \ |
| 253 | $(SRCDIR)/scroll.js \ |
| 254 | $(SRCDIR)/skin.js \ |
| 255 | $(SRCDIR)/sorttable.js \ |
| 256 | $(SRCDIR)/sounds/0.wav \ |
| 257 | $(SRCDIR)/sounds/1.wav \ |
| @@ -363,10 +365,11 @@ | |
| 363 | $(OBJDIR)/lookslike_.c \ |
| 364 | $(OBJDIR)/main_.c \ |
| 365 | $(OBJDIR)/manifest_.c \ |
| 366 | $(OBJDIR)/markdown_.c \ |
| 367 | $(OBJDIR)/markdown_html_.c \ |
| 368 | $(OBJDIR)/md5_.c \ |
| 369 | $(OBJDIR)/merge_.c \ |
| 370 | $(OBJDIR)/merge3_.c \ |
| 371 | $(OBJDIR)/moderate_.c \ |
| 372 | $(OBJDIR)/name_.c \ |
| @@ -512,10 +515,11 @@ | |
| 512 | $(OBJDIR)/lookslike.o \ |
| 513 | $(OBJDIR)/main.o \ |
| 514 | $(OBJDIR)/manifest.o \ |
| 515 | $(OBJDIR)/markdown.o \ |
| 516 | $(OBJDIR)/markdown_html.o \ |
| 517 | $(OBJDIR)/md5.o \ |
| 518 | $(OBJDIR)/merge.o \ |
| 519 | $(OBJDIR)/merge3.o \ |
| 520 | $(OBJDIR)/moderate.o \ |
| 521 | $(OBJDIR)/name.o \ |
| @@ -704,11 +708,11 @@ | |
| 704 | |
| 705 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 706 | # to 1. If it is set to 0, then there is no need to build or link |
| 707 | # the linenoise.o object. |
| 708 | LINENOISE_DEF.0 = |
| 709 | LINENOISE_DEF.1 = -DHAVE_LINENOISE |
| 710 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 711 | LINENOISE_OBJ.0 = |
| 712 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 713 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 714 | |
| @@ -847,10 +851,11 @@ | |
| 847 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 848 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 849 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 850 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 851 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 852 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 853 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 854 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 855 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 856 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -1596,10 +1601,18 @@ | |
| 1596 | |
| 1597 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 1598 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 1599 | |
| 1600 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 1601 | |
| 1602 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate |
| 1603 | $(OBJDIR)/translate $(SRCDIR)/md5.c >$@ |
| 1604 | |
| 1605 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 1606 | |
| 1607 | DDED src/match.c |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -99,10 +99,11 @@ | |
| 99 | $(SRCDIR)/lookslike.c \ |
| 100 | $(SRCDIR)/main.c \ |
| 101 | $(SRCDIR)/manifest.c \ |
| 102 | $(SRCDIR)/markdown.c \ |
| 103 | $(SRCDIR)/markdown_html.c \ |
| 104 | $(SRCDIR)/match.c \ |
| 105 | $(SRCDIR)/md5.c \ |
| 106 | $(SRCDIR)/merge.c \ |
| 107 | $(SRCDIR)/merge3.c \ |
| 108 | $(SRCDIR)/moderate.c \ |
| 109 | $(SRCDIR)/name.c \ |
| @@ -248,10 +249,11 @@ | |
| 249 | $(SRCDIR)/hbmenu.js \ |
| 250 | $(SRCDIR)/href.js \ |
| 251 | $(SRCDIR)/login.js \ |
| 252 | $(SRCDIR)/markdown.md \ |
| 253 | $(SRCDIR)/menu.js \ |
| 254 | $(SRCDIR)/merge.tcl \ |
| 255 | $(SRCDIR)/scroll.js \ |
| 256 | $(SRCDIR)/skin.js \ |
| 257 | $(SRCDIR)/sorttable.js \ |
| 258 | $(SRCDIR)/sounds/0.wav \ |
| 259 | $(SRCDIR)/sounds/1.wav \ |
| @@ -363,10 +365,11 @@ | |
| 365 | $(OBJDIR)/lookslike_.c \ |
| 366 | $(OBJDIR)/main_.c \ |
| 367 | $(OBJDIR)/manifest_.c \ |
| 368 | $(OBJDIR)/markdown_.c \ |
| 369 | $(OBJDIR)/markdown_html_.c \ |
| 370 | $(OBJDIR)/match_.c \ |
| 371 | $(OBJDIR)/md5_.c \ |
| 372 | $(OBJDIR)/merge_.c \ |
| 373 | $(OBJDIR)/merge3_.c \ |
| 374 | $(OBJDIR)/moderate_.c \ |
| 375 | $(OBJDIR)/name_.c \ |
| @@ -512,10 +515,11 @@ | |
| 515 | $(OBJDIR)/lookslike.o \ |
| 516 | $(OBJDIR)/main.o \ |
| 517 | $(OBJDIR)/manifest.o \ |
| 518 | $(OBJDIR)/markdown.o \ |
| 519 | $(OBJDIR)/markdown_html.o \ |
| 520 | $(OBJDIR)/match.o \ |
| 521 | $(OBJDIR)/md5.o \ |
| 522 | $(OBJDIR)/merge.o \ |
| 523 | $(OBJDIR)/merge3.o \ |
| 524 | $(OBJDIR)/moderate.o \ |
| 525 | $(OBJDIR)/name.o \ |
| @@ -704,11 +708,11 @@ | |
| 708 | |
| 709 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 710 | # to 1. If it is set to 0, then there is no need to build or link |
| 711 | # the linenoise.o object. |
| 712 | LINENOISE_DEF.0 = |
| 713 | LINENOISE_DEF.1 = -DHAVE_LINENOISE=2 |
| 714 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 715 | LINENOISE_OBJ.0 = |
| 716 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 717 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 718 | |
| @@ -847,10 +851,11 @@ | |
| 851 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 852 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 853 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 854 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 855 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 856 | $(OBJDIR)/match_.c:$(OBJDIR)/match.h \ |
| 857 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 858 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 859 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 860 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 861 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -1596,10 +1601,18 @@ | |
| 1601 | |
| 1602 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 1603 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 1604 | |
| 1605 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 1606 | |
| 1607 | $(OBJDIR)/match_.c: $(SRCDIR)/match.c $(OBJDIR)/translate |
| 1608 | $(OBJDIR)/translate $(SRCDIR)/match.c >$@ |
| 1609 | |
| 1610 | $(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h |
| 1611 | $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c |
| 1612 | |
| 1613 | $(OBJDIR)/match.h: $(OBJDIR)/headers |
| 1614 | |
| 1615 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate |
| 1616 | $(OBJDIR)/translate $(SRCDIR)/md5.c >$@ |
| 1617 | |
| 1618 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 1619 | |
| 1620 | DDED src/match.c |
+386
| --- a/src/match.c | ||
| +++ b/src/match.c | ||
| @@ -0,0 +1,386 @@ | ||
| 1 | +/* | |
| 2 | +** Copyright (c) 2007 D. Richard Hipp | |
| 3 | +** | |
| 4 | +** This program is free software; you can redistribute it and/or | |
| 5 | +** modify it under the terms of the Simplified BSD License (also | |
| 6 | +** known as the "2-Clause License" or "FreeBSD License".) | |
| 7 | + | |
| 8 | +** This program is distributed in the hope that it will be useful, | |
| 9 | +** but without any warranty; without even the implied warranty of | |
| 10 | +** merchantability or fitness for a particular purpose. | |
| 11 | +** | |
| 12 | +** Author contact information: | |
| 13 | +** [email protected] | |
| 14 | +** http://www.hwaci.com/drh/ | |
| 15 | +** | |
| 16 | +******************************************************************************* | |
| 17 | +** | |
| 18 | +** This file contains code to implement string comparisons using a | |
| 19 | +** variety of algorithm. The comparison algorithm can be any of: | |
| 20 | +** | |
| 21 | +** MS_EXACT The string must exactly match the pattern. | |
| 22 | +** | |
| 23 | +** MS_BRLIST The pattern is a space- and/or comma-separated | |
| 24 | +** list of strings, any one of which may match | |
| 25 | +** the input string. | |
| 26 | +** | |
| 27 | +** MS_GLOB Like BRLIST, except each component of the pattern | |
| 28 | +** is a GLOB expression. | |
| 29 | +** | |
| 30 | +** MS_LIKE Like BRLIST, except each component of the pattern | |
| 31 | +** is an SQL LIKE expression. | |
| 32 | +** | |
| 33 | +** MS_REGEXP Like BRLIST, except each component of the pattern | |
| 34 | +** is a regular expression. | |
| 35 | +** | |
| 36 | +*/ | |
| 37 | +#include "config.h" | |
| 38 | +#include <string.h> | |
| 39 | +#include "match.h" | |
| 40 | + | |
| 41 | +#if INTERFACE | |
| 42 | +/* | |
| 43 | +** Types of comparisons that we are able to perform: | |
| 44 | +*/ | |
| 45 | +typedef enum { | |
| 46 | + MS_EXACT=1, /* Exact string comparison */ | |
| 47 | + MS_GLOB=2, /* Matches against a list of GLOB patterns. */ | |
| 48 | + MS_LIKE=3, /* Matches against a list of LIKE patterns. */ | |
| 49 | + MS_REGEXP=4, /* Matches against a list of regular expressions. */ | |
| 50 | + MS_BRLIST=5, /* Matches any element of a list */ | |
| 51 | +} MatchStyle; | |
| 52 | + | |
| 53 | +/* | |
| 54 | +** The following object represents a precompiled pattern to use for | |
| 55 | +** string matching. | |
| 56 | +** | |
| 57 | +** * Create an instance of this object using match_create(). | |
| 58 | +** * Do comparisons using match_text(). | |
| 59 | +** * Destroy using match_free() when you are done. | |
| 60 | +** | |
| 61 | +*/ | |
| 62 | +struct Matcher { | |
| 63 | + MatchStyle style; /* Which algorithm to use */ | |
| 64 | + int nPattern; /* How many patterns are their */ | |
| 65 | + char **azPattern; /* List of patterns */ | |
| 66 | + ReCompiled **aRe; /* List of compiled regular expressions */ | |
| 67 | +}; | |
| 68 | + | |
| 69 | +#endif /*INTERFACE*/ | |
| 70 | + | |
| 71 | +/* | |
| 72 | +** Translate a "match style" text name into the MS_* enum value. | |
| 73 | +** Return eDflt if no match is found. | |
| 74 | +*/ | |
| 75 | +MatchStyle match_style(const char *zStyle, MatchStyle eDflt){ | |
| 76 | + if( zStyle==0 ) return eDflt; | |
| 77 | + if( fossil_stricmp(zStyle, "brlist")==0 ) return MS_BRLIST; | |
| 78 | + if( fossil_stricmp(zStyle, "list")==0 ) return MS_BRLIST; | |
| 79 | + if( fossil_stricmp(zStyle, "regexp")==0 ) return MS_REGEXP; | |
| 80 | + if( fossil_stricmp(zStyle, "re")==0 ) return MS_REGEXP; | |
| 81 | + if( fossil_stricmp(zStyle, "glob")==0 ) return MS_GLOB; | |
| 82 | + if( fossil_stricmp(zStyle, "like")==0 ) return MS_LIKE; | |
| 83 | + if( fossil_stricmp(zStyle, "exact")==0 ) return MS_EXACT; | |
| 84 | + return eDflt; | |
| 85 | +} | |
| 86 | + | |
| 87 | + | |
| 88 | +/* | |
| 89 | +** Create a new Matcher object using the pattern provided. | |
| 90 | +*/ | |
| 91 | +Matcher *match_create(MatchStyle style, const char *zPat){ | |
| 92 | + char cDel; /* Delimiter character */ | |
| 93 | + int i; /* Loop counter */ | |
| 94 | + Matcher *p; /* The new Matcher to be constructed */ | |
| 95 | + char *zOne; /* One element of the pattern */ | |
| 96 | + | |
| 97 | + if( zPat==0 ) return 0; | |
| 98 | + p = fossil_malloc( sizeof(*p) ); | |
| 99 | + memset(p, 0, sizeof(*p)); | |
| 100 | + p->style = style; | |
| 101 | + | |
| 102 | + if( style==MS_EXACT ){ | |
| 103 | + p->nPattern = 1; | |
| 104 | + p->azPattern = fossil_malloc( sizeof(p->azPattern[0]) ); | |
| 105 | + p->azPattern[0] = fossil_strdup(zPat); | |
| 106 | + return p; | |
| 107 | + } | |
| 108 | + | |
| 109 | + while( 1 ){ | |
| 110 | + /* Skip leading delimiters. */ | |
| 111 | + for( ; fossil_isspace(*zPat) || *zPat==','; ++zPat ); | |
| 112 | + | |
| 113 | + /* Next non-delimiter character determines quoting. */ | |
| 114 | + if( zPat[0]==0 ){ | |
| 115 | + /* Terminate loop at end of string. */ | |
| 116 | + break; | |
| 117 | + }else if( zPat[0]=='\'' || zPat[0]=='"' ){ | |
| 118 | + /* If word is quoted, prepare to stop at end quote. */ | |
| 119 | + cDel = zPat[0]; | |
| 120 | + ++zPat; | |
| 121 | + }else{ | |
| 122 | + /* If word is not quoted, prepare to stop at delimiter. */ | |
| 123 | + cDel = ','; | |
| 124 | + } | |
| 125 | + | |
| 126 | + /* Find the next delimiter character or end of string. */ | |
| 127 | + for( i=0; zPat[i] && zPat[i]!=cDel; ++i ){ | |
| 128 | + /* If delimiter is comma, also recognize spaces as delimiters. */ | |
| 129 | + if( cDel==',' && fossil_isspace(zPat[i]) ){ | |
| 130 | + break; | |
| 131 | + } | |
| 132 | + | |
| 133 | + /* In regexp mode, ignore delimiters following backslashes. */ | |
| 134 | + if( style==MS_REGEXP && zPat[i]=='\\' && zPat[i+1] ){ | |
| 135 | + ++i; | |
| 136 | + } | |
| 137 | + } | |
| 138 | + | |
| 139 | + /* zOne is a zero-terminated copy of the pattern, without delimiters */ | |
| 140 | + zOne = fossil_strndup(zPat, i); | |
| 141 | + zPat += i; | |
| 142 | + if( zPat[0] ) zPat++; | |
| 143 | + | |
| 144 | + /* Check for regular expression syntax errors. */ | |
| 145 | + if( style==MS_REGEXP ){ | |
| 146 | + ReCompiled *regexp; | |
| 147 | + const char *zFail = re_compile(®exp, zOne, 0); | |
| 148 | + if( zFail ){ | |
| 149 | + re_free(regexp); | |
| 150 | + continue; | |
| 151 | + } | |
| 152 | + p->nPattern++; | |
| 153 | + p->aRe = fossil_realloc(p->aRe, sizeof(p->aRe)*p->nPattern); | |
| 154 | + p->aRe[p->nPattern-1] = regexp; | |
| 155 | + fossil_free(zOne); | |
| 156 | + }else{ | |
| 157 | + p->nPattern++; | |
| 158 | + p->azPattern = fossil_realloc(p->azPattern, sizeof(char*)*p->nPattern); | |
| 159 | + p->azPattern[p->nPattern-1] = zOne; | |
| 160 | + } | |
| 161 | + } | |
| 162 | + return p; | |
| 163 | +} | |
| 164 | + | |
| 165 | +/* | |
| 166 | +** Return non-zero (true) if the input string matches the pattern | |
| 167 | +** described by the matcher. | |
| 168 | +** | |
| 169 | +** The return value is really the 1-based index of the particular | |
| 170 | +** pattern that matched. | |
| 171 | +*/ | |
| 172 | +int match_text(Matcher *p, const char *zText){ | |
| 173 | + int i; | |
| 174 | + if( p==0 ){ | |
| 175 | + return zText==0; | |
| 176 | + } | |
| 177 | + switch( p->style ){ | |
| 178 | + case MS_BRLIST: | |
| 179 | + case MS_EXACT: { | |
| 180 | + for(i=0; i<p->nPattern; i++){ | |
| 181 | + if( strcmp(p->azPattern[i], zText)==0 ) return i+1; | |
| 182 | + } | |
| 183 | + break; | |
| 184 | + } | |
| 185 | + case MS_GLOB: { | |
| 186 | + for(i=0; i<p->nPattern; i++){ | |
| 187 | + if( sqlite3_strglob(p->azPattern[i], zText)==0 ) return i+1; | |
| 188 | + } | |
| 189 | + break; | |
| 190 | + } | |
| 191 | + case MS_LIKE: { | |
| 192 | + for(i=0; i<p->nPattern; i++){ | |
| 193 | + if( sqlite3_strlike(p->azPattern[i], zText, 0)==0 ) return i+1; | |
| 194 | + } | |
| 195 | + break; | |
| 196 | + } | |
| 197 | + case MS_REGEXP: { | |
| 198 | + int nText = (int)strlen(zText); | |
| 199 | + for(i=0; i<p->nPattern; i++){ | |
| 200 | + if( re_match(p->aRe[i], (const u8*)zText, nText) ) return i+1; | |
| 201 | + } | |
| 202 | + break; | |
| 203 | + } | |
| 204 | + } | |
| 205 | + return 0n 0; | |
| 206 | +} | |
| 207 | + | |
| 208 | + | |
| 209 | +/* | |
| 210 | +** Destroy a previously allocated Matcher object. | |
| 211 | +*/ | |
| 212 | +void match_free(Matcher *p){ | |
| 213 | + int i; | |
| 214 | + if( p==0 ) return; | |
| 215 | + if( p->style==MS_REGEXP ){ | |
| 216 | + for(i=0; i<p->nPattern; i++) re_free(p->aRe[i]); | |
| 217 | + fossil_free(p->aRe); | |
| 218 | + }else{ | |
| 219 | + for(i=0; i<p->nPattern; i++) fossil_free(p->azPattern[i]); | |
| 220 | + fossil_free(p->azPattern); | |
| 221 | + } | |
| 222 | + memset(p, 0, sizeof(*p)); | |
| 223 | + fossil_free(p); | |
| 224 | +} | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | +/* | |
| 229 | +** Quote a tag string by surrounding it with double quotes and preceding | |
| 230 | +** internal double quotes and backslashes with backslashes. | |
| 231 | +*/ | |
| 232 | +static const char *tagQuote( | |
| 233 | + int len, /* Maximum length of zTag, or negative for unlimited */ | |
| 234 | + const char *zTag /* Tag string */ | |
| 235 | +){ | |
| 236 | + Blob blob = BLOB_INITIALIZER; | |
| 237 | + int i, j; | |
| 238 | + blob_zero(&blob); | |
| 239 | + blob_append(&blob, "\"", 1); | |
| 240 | + for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){ | |
| 241 | + if( zTag[j]=='\"' || zTag[j]=='\\' ){ | |
| 242 | + if( j>i ){ | |
| 243 | + blob_append(&blob, zTag+i, j-i); | |
| 244 | + } | |
| 245 | + blob_append(&blob, "\\", 1); | |
| 246 | + i = j; | |
| 247 | + } | |
| 248 | + } | |
| 249 | + if( j>i ){ | |
| 250 | + blob_append(&blob, zTag+i, j-i); | |
| 251 | + } | |
| 252 | + blob_append(&blob, "\"", 1); | |
| 253 | + return blob_str(&blob); | |
| 254 | +} | |
| 255 | + | |
| 256 | +/* | |
| 257 | +** Construct the SQL expression that goes into the WHERE clause of a join | |
| 258 | +** that involves the TAG table and that selects a particular tag out of | |
| 259 | +** that table. | |
| 260 | +** | |
| 261 | +** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB, | |
| 262 | +** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles. | |
| 263 | +** | |
| 264 | +** For MS_EXACT, the returned expression | |
| 265 | +** checks for integer match against the tag ID which is looked up directly by | |
| 266 | +** this function. For the other modes, the returned SQL expression performs | |
| 267 | +** string comparisons against the tag names, so it is necessary to join against | |
| 268 | +** the tag table to access the "tagname" column. | |
| 269 | +** | |
| 270 | +** Each pattern is adjusted to start with "sym-" and be anchored at end. | |
| 271 | +** | |
| 272 | +** In MS_REGEXP mode, backslash can be used to protect delimiter characters. | |
| 273 | +** The backslashes are not removed from the regular expression. | |
| 274 | +** | |
| 275 | +** In addition to assembling and returning an SQL expression, this function | |
| 276 | +** makes an English-language description of the patterns being matched, suitable | |
| 277 | +** for display in the web interface. | |
| 278 | +** | |
| 279 | +** If any errors arise during processing, *zError is set to an error message. | |
| 280 | +** Otherwise it is set to NULL. | |
| 281 | +*/ | |
| 282 | +const char *match_tag_sqlexpr( | |
| 283 | + MatchStyle matchStyle, /* Match style code */ | |
| 284 | + const char *zTag, /* Tag name, match pattern, or pattern list */ | |
| 285 | + const char **zDesc, /* Output expression description string */ | |
| 286 | + const char **zError /* Output error string */ | |
| 287 | +){ | |
| 288 | + Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */ | |
| 289 | + Blob desc = BLOB_INITIALIZER; /* English description of match patterns */ | |
| 290 | + Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */ | |
| 291 | + const char *zStart; /* Text at start of expression */ | |
| 292 | + const char *zDelimiter; /* Text between expression terms */ | |
| 293 | + const char *zEnd; /* Text at end of expression */ | |
| 294 | + const char *zPrefix; /* Text before each match pattern */ | |
| 295 | + const char *zSuffix; /* Text after each match pattern */ | |
| 296 | + const char *zIntro; /* Text introducing pattern description */ | |
| 297 | + const char *zPattern = 0; /* Previous quoted pattern */ | |
| 298 | + const char *zFail = 0; /* Current failure message or NULL if okay */ | |
| 299 | + const char *zOr = " or "; /* Text before final quoted pattern */ | |
| 300 | + char cDel; /* Input delimiter character */ | |
| 301 | + int i; /* Input match pattern length counter */ | |
| 302 | + | |
| 303 | + /* Optimize exact matches by looking up the ID in advance to create a simple | |
| 304 | + * numeric comparison. Bypass the remainder of this function. */ | |
| 305 | + if( matchStyle==MS_EXACT ){ | |
| 306 | + *zDesc = tagQuote(-1, zTag); | |
| 307 | + return mprintf("(tagid=%d)", db_int(-1, | |
| 308 | + "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag)); | |
| 309 | + } | |
| 310 | + | |
| 311 | + /* Decide pattern prefix and suffix strings according to match style. */ | |
| 312 | + if( matchStyle==MS_GLOB ){ | |
| 313 | + zStart = "("; | |
| 314 | + zDelimiter = " OR "; | |
| 315 | + zEnd = ")"; | |
| 316 | + zPrefix = "tagname GLOB 'sym-"; | |
| 317 | + zSuffix = "'"; | |
| 318 | + zIntro = "glob pattern "; | |
| 319 | + }else irt = "("; | |
| 320 | + zDelimiter = " OR "; | |
| 321 | + zEnd = ")"; | |
| 322 | + zPrefix = "tagname LIKE 'sym-"; | |
| 323 | + zSuffix = "'"; | |
| 324 | + zIntro = "SQL LIKE pattern "; | |
| 325 | + }else if( matchStyle==MS_REGEXP ){ | |
| 326 | + zStart = "(tagname REGEXP '^sym-("; | |
| 327 | + zDelimiter = "|"; | |
| 328 | + zEnd = ")$')"; | |
| 329 | + zPrefix = ""; | |
| 330 | + zSuffix = ""; | |
| 331 | + zIntro = "regular expression "; | |
| 332 | + }else/* if( matchStyle==MS_BRLIST )*/{ | |
| 333 | + zStart = "tagname IN ('sym-"; | |
| 334 | + zDelimiter = "','sym-"; | |
| 335 | + zEnd = "')"; | |
| 336 | + zPrefix = ""; | |
| 337 | + zSuffix = ""; | |
| 338 | + zIntro = ""; | |
| 339 | + } | |
| 340 | + | |
| 341 | + /* Convert the list of matches into an SQL expression and text description. */ | |
| 342 | + blob_zero(&expr); | |
| 343 | + blob_zero(&desc); | |
| 344 | + blob_zero(&err); | |
| 345 | + while( 1 ){ | |
| 346 | + /* Skip leading delimiters. */ | |
| 347 | + for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag ); | |
| 348 | + | |
| 349 | + /* Next non-delimiter character determines quoting. */ | |
| 350 | + if( !*zTag ){ | |
| 351 | + /* Terminate loop at end of string. */ | |
| 352 | + break; | |
| 353 | + }else if( *zTag=='\'' || *zTag=='"' ){ | |
| 354 | + /* If word is quoted, prepare to stop at end quote. */ | |
| 355 | + cDel = *zTag; | |
| 356 | + ++zTag; | |
| 357 | + }else{ | |
| 358 | + /* If word is not quoted, prepare to stop at delimiter. */ | |
| 359 | + cDel = ','; | |
| 360 | + } | |
| 361 | + | |
| 362 | + /* Find the next delimiter character or end of string. */ | |
| 363 | + for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){ | |
| 364 | + /* If delimiter is comma, also recognize spaces as delimiters. */ | |
| 365 | + if( cDel==',' && fossil_isspace(zTag[i]) ){ | |
| 366 | + break; | |
| 367 | + } | |
| 368 | + | |
| 369 | + /* In regexp mode, ignore delimiters following backslashes. */ | |
| 370 | + if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){ | |
| 371 | + ++i; | |
| 372 | + } | |
| 373 | + } | |
| 374 | + | |
| 375 | + /* Check for regular expression syntax errors. */ | |
| 376 | + if( matchStyle==MS_REGEXP ){ | |
| 377 | + ReCompiled *regexp; | |
| 378 | + char *zTagDup = fossil_strndup(zTag, i); | |
| 379 | + zFail = fossil_re_compile(®exp, zTagDup, 0); | |
| 380 | + re_free(regexp); | |
| 381 | + fossil_free(zTagDup); | |
| 382 | + } | |
| 383 | + | |
| 384 | + /* Process success and error results. */ | |
| 385 | + if( !zFail ){ | |
| 386 | + /* Incorporate |
| --- a/src/match.c | |
| +++ b/src/match.c | |
| @@ -0,0 +1,386 @@ | |
| --- a/src/match.c | |
| +++ b/src/match.c | |
| @@ -0,0 +1,386 @@ | |
| 1 | /* |
| 2 | ** Copyright (c) 2007 D. Richard Hipp |
| 3 | ** |
| 4 | ** This program is free software; you can redistribute it and/or |
| 5 | ** modify it under the terms of the Simplified BSD License (also |
| 6 | ** known as the "2-Clause License" or "FreeBSD License".) |
| 7 | |
| 8 | ** This program is distributed in the hope that it will be useful, |
| 9 | ** but without any warranty; without even the implied warranty of |
| 10 | ** merchantability or fitness for a particular purpose. |
| 11 | ** |
| 12 | ** Author contact information: |
| 13 | ** [email protected] |
| 14 | ** http://www.hwaci.com/drh/ |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** This file contains code to implement string comparisons using a |
| 19 | ** variety of algorithm. The comparison algorithm can be any of: |
| 20 | ** |
| 21 | ** MS_EXACT The string must exactly match the pattern. |
| 22 | ** |
| 23 | ** MS_BRLIST The pattern is a space- and/or comma-separated |
| 24 | ** list of strings, any one of which may match |
| 25 | ** the input string. |
| 26 | ** |
| 27 | ** MS_GLOB Like BRLIST, except each component of the pattern |
| 28 | ** is a GLOB expression. |
| 29 | ** |
| 30 | ** MS_LIKE Like BRLIST, except each component of the pattern |
| 31 | ** is an SQL LIKE expression. |
| 32 | ** |
| 33 | ** MS_REGEXP Like BRLIST, except each component of the pattern |
| 34 | ** is a regular expression. |
| 35 | ** |
| 36 | */ |
| 37 | #include "config.h" |
| 38 | #include <string.h> |
| 39 | #include "match.h" |
| 40 | |
| 41 | #if INTERFACE |
| 42 | /* |
| 43 | ** Types of comparisons that we are able to perform: |
| 44 | */ |
| 45 | typedef enum { |
| 46 | MS_EXACT=1, /* Exact string comparison */ |
| 47 | MS_GLOB=2, /* Matches against a list of GLOB patterns. */ |
| 48 | MS_LIKE=3, /* Matches against a list of LIKE patterns. */ |
| 49 | MS_REGEXP=4, /* Matches against a list of regular expressions. */ |
| 50 | MS_BRLIST=5, /* Matches any element of a list */ |
| 51 | } MatchStyle; |
| 52 | |
| 53 | /* |
| 54 | ** The following object represents a precompiled pattern to use for |
| 55 | ** string matching. |
| 56 | ** |
| 57 | ** * Create an instance of this object using match_create(). |
| 58 | ** * Do comparisons using match_text(). |
| 59 | ** * Destroy using match_free() when you are done. |
| 60 | ** |
| 61 | */ |
| 62 | struct Matcher { |
| 63 | MatchStyle style; /* Which algorithm to use */ |
| 64 | int nPattern; /* How many patterns are their */ |
| 65 | char **azPattern; /* List of patterns */ |
| 66 | ReCompiled **aRe; /* List of compiled regular expressions */ |
| 67 | }; |
| 68 | |
| 69 | #endif /*INTERFACE*/ |
| 70 | |
| 71 | /* |
| 72 | ** Translate a "match style" text name into the MS_* enum value. |
| 73 | ** Return eDflt if no match is found. |
| 74 | */ |
| 75 | MatchStyle match_style(const char *zStyle, MatchStyle eDflt){ |
| 76 | if( zStyle==0 ) return eDflt; |
| 77 | if( fossil_stricmp(zStyle, "brlist")==0 ) return MS_BRLIST; |
| 78 | if( fossil_stricmp(zStyle, "list")==0 ) return MS_BRLIST; |
| 79 | if( fossil_stricmp(zStyle, "regexp")==0 ) return MS_REGEXP; |
| 80 | if( fossil_stricmp(zStyle, "re")==0 ) return MS_REGEXP; |
| 81 | if( fossil_stricmp(zStyle, "glob")==0 ) return MS_GLOB; |
| 82 | if( fossil_stricmp(zStyle, "like")==0 ) return MS_LIKE; |
| 83 | if( fossil_stricmp(zStyle, "exact")==0 ) return MS_EXACT; |
| 84 | return eDflt; |
| 85 | } |
| 86 | |
| 87 | |
| 88 | /* |
| 89 | ** Create a new Matcher object using the pattern provided. |
| 90 | */ |
| 91 | Matcher *match_create(MatchStyle style, const char *zPat){ |
| 92 | char cDel; /* Delimiter character */ |
| 93 | int i; /* Loop counter */ |
| 94 | Matcher *p; /* The new Matcher to be constructed */ |
| 95 | char *zOne; /* One element of the pattern */ |
| 96 | |
| 97 | if( zPat==0 ) return 0; |
| 98 | p = fossil_malloc( sizeof(*p) ); |
| 99 | memset(p, 0, sizeof(*p)); |
| 100 | p->style = style; |
| 101 | |
| 102 | if( style==MS_EXACT ){ |
| 103 | p->nPattern = 1; |
| 104 | p->azPattern = fossil_malloc( sizeof(p->azPattern[0]) ); |
| 105 | p->azPattern[0] = fossil_strdup(zPat); |
| 106 | return p; |
| 107 | } |
| 108 | |
| 109 | while( 1 ){ |
| 110 | /* Skip leading delimiters. */ |
| 111 | for( ; fossil_isspace(*zPat) || *zPat==','; ++zPat ); |
| 112 | |
| 113 | /* Next non-delimiter character determines quoting. */ |
| 114 | if( zPat[0]==0 ){ |
| 115 | /* Terminate loop at end of string. */ |
| 116 | break; |
| 117 | }else if( zPat[0]=='\'' || zPat[0]=='"' ){ |
| 118 | /* If word is quoted, prepare to stop at end quote. */ |
| 119 | cDel = zPat[0]; |
| 120 | ++zPat; |
| 121 | }else{ |
| 122 | /* If word is not quoted, prepare to stop at delimiter. */ |
| 123 | cDel = ','; |
| 124 | } |
| 125 | |
| 126 | /* Find the next delimiter character or end of string. */ |
| 127 | for( i=0; zPat[i] && zPat[i]!=cDel; ++i ){ |
| 128 | /* If delimiter is comma, also recognize spaces as delimiters. */ |
| 129 | if( cDel==',' && fossil_isspace(zPat[i]) ){ |
| 130 | break; |
| 131 | } |
| 132 | |
| 133 | /* In regexp mode, ignore delimiters following backslashes. */ |
| 134 | if( style==MS_REGEXP && zPat[i]=='\\' && zPat[i+1] ){ |
| 135 | ++i; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /* zOne is a zero-terminated copy of the pattern, without delimiters */ |
| 140 | zOne = fossil_strndup(zPat, i); |
| 141 | zPat += i; |
| 142 | if( zPat[0] ) zPat++; |
| 143 | |
| 144 | /* Check for regular expression syntax errors. */ |
| 145 | if( style==MS_REGEXP ){ |
| 146 | ReCompiled *regexp; |
| 147 | const char *zFail = re_compile(®exp, zOne, 0); |
| 148 | if( zFail ){ |
| 149 | re_free(regexp); |
| 150 | continue; |
| 151 | } |
| 152 | p->nPattern++; |
| 153 | p->aRe = fossil_realloc(p->aRe, sizeof(p->aRe)*p->nPattern); |
| 154 | p->aRe[p->nPattern-1] = regexp; |
| 155 | fossil_free(zOne); |
| 156 | }else{ |
| 157 | p->nPattern++; |
| 158 | p->azPattern = fossil_realloc(p->azPattern, sizeof(char*)*p->nPattern); |
| 159 | p->azPattern[p->nPattern-1] = zOne; |
| 160 | } |
| 161 | } |
| 162 | return p; |
| 163 | } |
| 164 | |
| 165 | /* |
| 166 | ** Return non-zero (true) if the input string matches the pattern |
| 167 | ** described by the matcher. |
| 168 | ** |
| 169 | ** The return value is really the 1-based index of the particular |
| 170 | ** pattern that matched. |
| 171 | */ |
| 172 | int match_text(Matcher *p, const char *zText){ |
| 173 | int i; |
| 174 | if( p==0 ){ |
| 175 | return zText==0; |
| 176 | } |
| 177 | switch( p->style ){ |
| 178 | case MS_BRLIST: |
| 179 | case MS_EXACT: { |
| 180 | for(i=0; i<p->nPattern; i++){ |
| 181 | if( strcmp(p->azPattern[i], zText)==0 ) return i+1; |
| 182 | } |
| 183 | break; |
| 184 | } |
| 185 | case MS_GLOB: { |
| 186 | for(i=0; i<p->nPattern; i++){ |
| 187 | if( sqlite3_strglob(p->azPattern[i], zText)==0 ) return i+1; |
| 188 | } |
| 189 | break; |
| 190 | } |
| 191 | case MS_LIKE: { |
| 192 | for(i=0; i<p->nPattern; i++){ |
| 193 | if( sqlite3_strlike(p->azPattern[i], zText, 0)==0 ) return i+1; |
| 194 | } |
| 195 | break; |
| 196 | } |
| 197 | case MS_REGEXP: { |
| 198 | int nText = (int)strlen(zText); |
| 199 | for(i=0; i<p->nPattern; i++){ |
| 200 | if( re_match(p->aRe[i], (const u8*)zText, nText) ) return i+1; |
| 201 | } |
| 202 | break; |
| 203 | } |
| 204 | } |
| 205 | return 0n 0; |
| 206 | } |
| 207 | |
| 208 | |
| 209 | /* |
| 210 | ** Destroy a previously allocated Matcher object. |
| 211 | */ |
| 212 | void match_free(Matcher *p){ |
| 213 | int i; |
| 214 | if( p==0 ) return; |
| 215 | if( p->style==MS_REGEXP ){ |
| 216 | for(i=0; i<p->nPattern; i++) re_free(p->aRe[i]); |
| 217 | fossil_free(p->aRe); |
| 218 | }else{ |
| 219 | for(i=0; i<p->nPattern; i++) fossil_free(p->azPattern[i]); |
| 220 | fossil_free(p->azPattern); |
| 221 | } |
| 222 | memset(p, 0, sizeof(*p)); |
| 223 | fossil_free(p); |
| 224 | } |
| 225 | |
| 226 | |
| 227 | |
| 228 | /* |
| 229 | ** Quote a tag string by surrounding it with double quotes and preceding |
| 230 | ** internal double quotes and backslashes with backslashes. |
| 231 | */ |
| 232 | static const char *tagQuote( |
| 233 | int len, /* Maximum length of zTag, or negative for unlimited */ |
| 234 | const char *zTag /* Tag string */ |
| 235 | ){ |
| 236 | Blob blob = BLOB_INITIALIZER; |
| 237 | int i, j; |
| 238 | blob_zero(&blob); |
| 239 | blob_append(&blob, "\"", 1); |
| 240 | for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){ |
| 241 | if( zTag[j]=='\"' || zTag[j]=='\\' ){ |
| 242 | if( j>i ){ |
| 243 | blob_append(&blob, zTag+i, j-i); |
| 244 | } |
| 245 | blob_append(&blob, "\\", 1); |
| 246 | i = j; |
| 247 | } |
| 248 | } |
| 249 | if( j>i ){ |
| 250 | blob_append(&blob, zTag+i, j-i); |
| 251 | } |
| 252 | blob_append(&blob, "\"", 1); |
| 253 | return blob_str(&blob); |
| 254 | } |
| 255 | |
| 256 | /* |
| 257 | ** Construct the SQL expression that goes into the WHERE clause of a join |
| 258 | ** that involves the TAG table and that selects a particular tag out of |
| 259 | ** that table. |
| 260 | ** |
| 261 | ** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB, |
| 262 | ** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles. |
| 263 | ** |
| 264 | ** For MS_EXACT, the returned expression |
| 265 | ** checks for integer match against the tag ID which is looked up directly by |
| 266 | ** this function. For the other modes, the returned SQL expression performs |
| 267 | ** string comparisons against the tag names, so it is necessary to join against |
| 268 | ** the tag table to access the "tagname" column. |
| 269 | ** |
| 270 | ** Each pattern is adjusted to start with "sym-" and be anchored at end. |
| 271 | ** |
| 272 | ** In MS_REGEXP mode, backslash can be used to protect delimiter characters. |
| 273 | ** The backslashes are not removed from the regular expression. |
| 274 | ** |
| 275 | ** In addition to assembling and returning an SQL expression, this function |
| 276 | ** makes an English-language description of the patterns being matched, suitable |
| 277 | ** for display in the web interface. |
| 278 | ** |
| 279 | ** If any errors arise during processing, *zError is set to an error message. |
| 280 | ** Otherwise it is set to NULL. |
| 281 | */ |
| 282 | const char *match_tag_sqlexpr( |
| 283 | MatchStyle matchStyle, /* Match style code */ |
| 284 | const char *zTag, /* Tag name, match pattern, or pattern list */ |
| 285 | const char **zDesc, /* Output expression description string */ |
| 286 | const char **zError /* Output error string */ |
| 287 | ){ |
| 288 | Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */ |
| 289 | Blob desc = BLOB_INITIALIZER; /* English description of match patterns */ |
| 290 | Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */ |
| 291 | const char *zStart; /* Text at start of expression */ |
| 292 | const char *zDelimiter; /* Text between expression terms */ |
| 293 | const char *zEnd; /* Text at end of expression */ |
| 294 | const char *zPrefix; /* Text before each match pattern */ |
| 295 | const char *zSuffix; /* Text after each match pattern */ |
| 296 | const char *zIntro; /* Text introducing pattern description */ |
| 297 | const char *zPattern = 0; /* Previous quoted pattern */ |
| 298 | const char *zFail = 0; /* Current failure message or NULL if okay */ |
| 299 | const char *zOr = " or "; /* Text before final quoted pattern */ |
| 300 | char cDel; /* Input delimiter character */ |
| 301 | int i; /* Input match pattern length counter */ |
| 302 | |
| 303 | /* Optimize exact matches by looking up the ID in advance to create a simple |
| 304 | * numeric comparison. Bypass the remainder of this function. */ |
| 305 | if( matchStyle==MS_EXACT ){ |
| 306 | *zDesc = tagQuote(-1, zTag); |
| 307 | return mprintf("(tagid=%d)", db_int(-1, |
| 308 | "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag)); |
| 309 | } |
| 310 | |
| 311 | /* Decide pattern prefix and suffix strings according to match style. */ |
| 312 | if( matchStyle==MS_GLOB ){ |
| 313 | zStart = "("; |
| 314 | zDelimiter = " OR "; |
| 315 | zEnd = ")"; |
| 316 | zPrefix = "tagname GLOB 'sym-"; |
| 317 | zSuffix = "'"; |
| 318 | zIntro = "glob pattern "; |
| 319 | }else irt = "("; |
| 320 | zDelimiter = " OR "; |
| 321 | zEnd = ")"; |
| 322 | zPrefix = "tagname LIKE 'sym-"; |
| 323 | zSuffix = "'"; |
| 324 | zIntro = "SQL LIKE pattern "; |
| 325 | }else if( matchStyle==MS_REGEXP ){ |
| 326 | zStart = "(tagname REGEXP '^sym-("; |
| 327 | zDelimiter = "|"; |
| 328 | zEnd = ")$')"; |
| 329 | zPrefix = ""; |
| 330 | zSuffix = ""; |
| 331 | zIntro = "regular expression "; |
| 332 | }else/* if( matchStyle==MS_BRLIST )*/{ |
| 333 | zStart = "tagname IN ('sym-"; |
| 334 | zDelimiter = "','sym-"; |
| 335 | zEnd = "')"; |
| 336 | zPrefix = ""; |
| 337 | zSuffix = ""; |
| 338 | zIntro = ""; |
| 339 | } |
| 340 | |
| 341 | /* Convert the list of matches into an SQL expression and text description. */ |
| 342 | blob_zero(&expr); |
| 343 | blob_zero(&desc); |
| 344 | blob_zero(&err); |
| 345 | while( 1 ){ |
| 346 | /* Skip leading delimiters. */ |
| 347 | for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag ); |
| 348 | |
| 349 | /* Next non-delimiter character determines quoting. */ |
| 350 | if( !*zTag ){ |
| 351 | /* Terminate loop at end of string. */ |
| 352 | break; |
| 353 | }else if( *zTag=='\'' || *zTag=='"' ){ |
| 354 | /* If word is quoted, prepare to stop at end quote. */ |
| 355 | cDel = *zTag; |
| 356 | ++zTag; |
| 357 | }else{ |
| 358 | /* If word is not quoted, prepare to stop at delimiter. */ |
| 359 | cDel = ','; |
| 360 | } |
| 361 | |
| 362 | /* Find the next delimiter character or end of string. */ |
| 363 | for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){ |
| 364 | /* If delimiter is comma, also recognize spaces as delimiters. */ |
| 365 | if( cDel==',' && fossil_isspace(zTag[i]) ){ |
| 366 | break; |
| 367 | } |
| 368 | |
| 369 | /* In regexp mode, ignore delimiters following backslashes. */ |
| 370 | if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){ |
| 371 | ++i; |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | /* Check for regular expression syntax errors. */ |
| 376 | if( matchStyle==MS_REGEXP ){ |
| 377 | ReCompiled *regexp; |
| 378 | char *zTagDup = fossil_strndup(zTag, i); |
| 379 | zFail = fossil_re_compile(®exp, zTagDup, 0); |
| 380 | re_free(regexp); |
| 381 | fossil_free(zTagDup); |
| 382 | } |
| 383 | |
| 384 | /* Process success and error results. */ |
| 385 | if( !zFail ){ |
| 386 | /* Incorporate |
+477
-14
| --- src/merge.c | ||
| +++ src/merge.c | ||
| @@ -20,10 +20,372 @@ | ||
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "merge.h" |
| 23 | 23 | #include <assert.h> |
| 24 | 24 | |
| 25 | + | |
| 26 | +/* | |
| 27 | +** Bring up a Tcl/Tk GUI to show details of the most recent merge. | |
| 28 | +*/ | |
| 29 | +static void merge_info_tk(int bDark, int bAll, int nContext){ | |
| 30 | + int i; | |
| 31 | + Blob script; | |
| 32 | + const char *zTempFile = 0; | |
| 33 | + char *zCmd; | |
| 34 | + const char *zTclsh; | |
| 35 | + zTclsh = find_option("tclsh",0,1); | |
| 36 | + if( zTclsh==0 ){ | |
| 37 | + zTclsh = db_get("tclsh",0); | |
| 38 | + } | |
| 39 | + /* The undocumented --script FILENAME option causes the Tk script to | |
| 40 | + ** be written into the FILENAME instead of being run. This is used | |
| 41 | + ** for testing and debugging. */ | |
| 42 | + zTempFile = find_option("script",0,1); | |
| 43 | + verify_all_options(); | |
| 44 | + | |
| 45 | + blob_zero(&script); | |
| 46 | + blob_appendf(&script, "set ncontext %d\n", nContext); | |
| 47 | + blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n", | |
| 48 | + g.nameOfExe); | |
| 49 | + blob_appendf(&script, "set filelist [list"); | |
| 50 | + if( g.argc==2 ){ | |
| 51 | + /* No files named on the command-line. Use every file mentioned | |
| 52 | + ** in the MERGESTAT table to generate the file list. */ | |
| 53 | + Stmt q; | |
| 54 | + int cnt = 0; | |
| 55 | + db_prepare(&q, | |
| 56 | + "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," | |
| 57 | + "('MERGE',1),('ADDED',2),('UPDATE',2))" | |
| 58 | + "SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)" | |
| 59 | + " %s ORDER BY pri, 1", | |
| 60 | + bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/ | |
| 61 | + ); | |
| 62 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 63 | + blob_appendf(&script," %s ", db_column_text(&q,1)); | |
| 64 | + blob_append_tcl_literal(&script, db_column_text(&q,0), | |
| 65 | + db_column_bytes(&q,0)); | |
| 66 | + cnt++; | |
| 67 | + } | |
| 68 | + db_finalize(&q); | |
| 69 | + if( cnt==0 ){ | |
| 70 | + fossil_print( | |
| 71 | + "No interesting changes in this merge. Use --all to see everything\n" | |
| 72 | + ); | |
| 73 | + return; | |
| 74 | + } | |
| 75 | + }else{ | |
| 76 | + /* Use only files named on the command-line in the file list. | |
| 77 | + ** But verify each file named is actually found in the MERGESTAT | |
| 78 | + ** table first. */ | |
| 79 | + for(i=2; i<g.argc; i++){ | |
| 80 | + char *zFile; /* Input filename */ | |
| 81 | + char *zTreename; /* Name of the file in the tree */ | |
| 82 | + Blob fname; /* Filename relative to root */ | |
| 83 | + char *zOp; /* Operation on this file */ | |
| 84 | + zFile = mprintf("%/", g.argv[i]); | |
| 85 | + file_tree_name(zFile, &fname, 0, 1); | |
| 86 | + fossil_free(zFile); | |
| 87 | + zTreename = blob_str(&fname); | |
| 88 | + zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q", | |
| 89 | + zTreename, zTreename); | |
| 90 | + blob_appendf(&script, " %s ", zOp); | |
| 91 | + fossil_free(zOp); | |
| 92 | + blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename)); | |
| 93 | + blob_reset(&fname); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + blob_appendf(&script, "]\n"); | |
| 97 | + blob_appendf(&script, "set darkmode %d\n", bDark!=0); | |
| 98 | + blob_appendf(&script, "%s", builtin_file("merge.tcl", 0)); | |
| 99 | + if( zTempFile ){ | |
| 100 | + blob_write_to_file(&script, zTempFile); | |
| 101 | + fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile); | |
| 102 | + }else{ | |
| 103 | +#if defined(FOSSIL_ENABLE_TCL) | |
| 104 | + Th_FossilInit(TH_INIT_DEFAULT); | |
| 105 | + if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), | |
| 106 | + blob_size(&script), 1, 1, 0)==TCL_OK ){ | |
| 107 | + blob_reset(&script); | |
| 108 | + return; | |
| 109 | + } | |
| 110 | + /* | |
| 111 | + * If evaluation of the Tcl script fails, the reason may be that Tk | |
| 112 | + * could not be found by the loaded Tcl, or that Tcl cannot be loaded | |
| 113 | + * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback | |
| 114 | + * to using the external "tclsh", if available. | |
| 115 | + */ | |
| 116 | +#endif | |
| 117 | + zTempFile = write_blob_to_temp_file(&script); | |
| 118 | + zCmd = mprintf("%$ %$", zTclsh, zTempFile); | |
| 119 | + fossil_system(zCmd); | |
| 120 | + file_delete(zTempFile); | |
| 121 | + fossil_free(zCmd); | |
| 122 | + } | |
| 123 | + blob_reset(&script); | |
| 124 | +} | |
| 125 | + | |
| 126 | +/* | |
| 127 | +** Generate a TCL list on standard output that can be fed into the | |
| 128 | +** merge.tcl script to show the details of the most recent merge | |
| 129 | +** command associated with file "zFName". zFName must be the filename | |
| 130 | +** relative to the root of the check-in - in other words a "tree name". | |
| 131 | +** | |
| 132 | +** When this routine is called, we know that the mergestat table | |
| 133 | +** exists, but we do not know if zFName is mentioned in that table. | |
| 134 | +*/ | |
| 135 | +static void merge_info_tcl(const char *zFName, int nContext){ | |
| 136 | + const char *zTreename;/* Name of the file in the tree */ | |
| 137 | + Stmt q; /* To query the MERGESTAT table */ | |
| 138 | + MergeBuilder mb; /* The merge builder object */ | |
| 139 | + Blob pivot,v1,v2,out; /* Blobs for holding content */ | |
| 140 | + const char *zFN; /* A filename */ | |
| 141 | + int rid; /* RID value */ | |
| 142 | + int sz; /* File size value */ | |
| 143 | + | |
| 144 | + zTreename = zFName; | |
| 145 | + db_prepare(&q, | |
| 146 | + /* 0 1 2 3 4 5 6 7 */ | |
| 147 | + "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr" | |
| 148 | + " FROM mergestat" | |
| 149 | + " WHERE fnp=%Q OR fnr=%Q", | |
| 150 | + zTreename, zTreename | |
| 151 | + ); | |
| 152 | + if( db_step(&q)!=SQLITE_ROW ){ | |
| 153 | + db_finalize(&q); | |
| 154 | + fossil_print("ERROR {don't know anything about file: %s}\n", zTreename); | |
| 155 | + return; | |
| 156 | + } | |
| 157 | + mergebuilder_init_tcl(&mb); | |
| 158 | + mb.nContext = nContext; | |
| 159 | + | |
| 160 | + /* Set up the pivot */ | |
| 161 | + zFN = db_column_text(&q, 0); | |
| 162 | + if( zFN==0 ){ | |
| 163 | + /* No pivot because the file was added */ | |
| 164 | + mb.zPivot = "(no baseline)"; | |
| 165 | + blob_zero(&pivot); | |
| 166 | + }else{ | |
| 167 | + mb.zPivot = mprintf("%s (baseline)", file_tail(zFN)); | |
| 168 | + rid = db_column_int(&q, 1); | |
| 169 | + content_get(rid, &pivot); | |
| 170 | + } | |
| 171 | + mb.pPivot = &pivot; | |
| 172 | + | |
| 173 | + /* Set up the merge-in as V2 */ | |
| 174 | + zFN = db_column_text(&q, 5); | |
| 175 | + if( zFN==0 ){ | |
| 176 | + /* File deleted in the merged-in branch */ | |
| 177 | + mb.zV2 = "(deleted file)"; | |
| 178 | + blob_zero(&v2); | |
| 179 | + }else{ | |
| 180 | + mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN)); | |
| 181 | + rid = db_column_int(&q, 6); | |
| 182 | + content_get(rid, &v2); | |
| 183 | + } | |
| 184 | + mb.pV2 = &v2; | |
| 185 | + | |
| 186 | + /* Set up the local content as V1 */ | |
| 187 | + zFN = db_column_text(&q, 2); | |
| 188 | + if( zFN==0 ){ | |
| 189 | + /* File added by merge */ | |
| 190 | + mb.zV1 = "(no original)"; | |
| 191 | + blob_zero(&v1); | |
| 192 | + }else{ | |
| 193 | + mb.zV1 = mprintf("%s (local)", file_tail(zFN)); | |
| 194 | + rid = db_column_int(&q, 3); | |
| 195 | + sz = db_column_int(&q, 4); | |
| 196 | + if( rid==0 && sz>0 ){ | |
| 197 | + /* The origin file had been edited so we'll have to pull its | |
| 198 | + ** original content out of the undo buffer */ | |
| 199 | + Stmt q2; | |
| 200 | + db_prepare(&q2, | |
| 201 | + "SELECT content FROM undo" | |
| 202 | + " WHERE pathname=%Q AND octet_length(content)=%d", | |
| 203 | + zFN, sz | |
| 204 | + ); | |
| 205 | + blob_zero(&v1); | |
| 206 | + if( db_step(&q2)==SQLITE_ROW ){ | |
| 207 | + db_column_blob(&q2, 0, &v1); | |
| 208 | + }else{ | |
| 209 | + mb.zV1 = "(local content missing)"; | |
| 210 | + } | |
| 211 | + db_finalize(&q2); | |
| 212 | + }else{ | |
| 213 | + /* The origin file was unchanged when the merge first occurred */ | |
| 214 | + content_get(rid, &v1); | |
| 215 | + } | |
| 216 | + } | |
| 217 | + mb.pV1 = &v1; | |
| 218 | + | |
| 219 | + /* Set up the output */ | |
| 220 | + zFN = db_column_text(&q, 7); | |
| 221 | + if( zFN==0 ){ | |
| 222 | + mb.zOut = "(Merge Result)"; | |
| 223 | + }else{ | |
| 224 | + mb.zOut = mprintf("%s (after merge)", file_tail(zFN)); | |
| 225 | + } | |
| 226 | + blob_zero(&out); | |
| 227 | + mb.pOut = &out; | |
| 228 | + | |
| 229 | + merge_three_blobs(&mb); | |
| 230 | + blob_write_to_file(&out, "-"); | |
| 231 | + | |
| 232 | + mb.xDestroy(&mb); | |
| 233 | + blob_reset(&pivot); | |
| 234 | + blob_reset(&v1); | |
| 235 | + blob_reset(&v2); | |
| 236 | + blob_reset(&out); | |
| 237 | + db_finalize(&q); | |
| 238 | +} | |
| 239 | + | |
| 240 | +/* | |
| 241 | +** COMMAND: merge-info | |
| 242 | +** | |
| 243 | +** Usage: %fossil merge-info [OPTIONS] | |
| 244 | +** | |
| 245 | +** Display information about the most recent merge operation. | |
| 246 | +** | |
| 247 | +** Options: | |
| 248 | +** -a|--all Show all file changes that happened because of | |
| 249 | +** the merge. Normally only MERGE, CONFLICT, and ERROR | |
| 250 | +** lines are shown | |
| 251 | +** -c|--context N Show N lines of context around each change, | |
| 252 | +** with negative N meaning show all content. Only | |
| 253 | +** meaningful in combination with --tcl or --tk. | |
| 254 | +** --dark Use dark mode for the Tcl/Tk-based GUI | |
| 255 | +** --tcl FILE Generate (to stdout) a TCL list containing | |
| 256 | +** information needed to display the changes to | |
| 257 | +** FILE caused by the most recent merge. FILE must | |
| 258 | +** be a pathname relative to the root of the check-out. | |
| 259 | +** --tk Bring up a Tcl/Tk GUI that shows the changes | |
| 260 | +** associated with the most recent merge. | |
| 261 | +** | |
| 262 | +*/ | |
| 263 | +void merge_info_cmd(void){ | |
| 264 | + const char *zCnt; | |
| 265 | + const char *zTcl; | |
| 266 | + int bTk; | |
| 267 | + int bDark; | |
| 268 | + int bAll; | |
| 269 | + int nContext; | |
| 270 | + Stmt q; | |
| 271 | + const char *zWhere; | |
| 272 | + int cnt = 0; | |
| 273 | + | |
| 274 | + db_must_be_within_tree(); | |
| 275 | + zTcl = find_option("tcl", 0, 1); | |
| 276 | + bTk = find_option("tk", 0, 0)!=0; | |
| 277 | + zCnt = find_option("context", "c", 1); | |
| 278 | + bDark = find_option("dark", 0, 0)!=0; | |
| 279 | + bAll = find_option("all", "a", 0)!=0; | |
| 280 | + if( bTk==0 ){ | |
| 281 | + verify_all_options(); | |
| 282 | + if( g.argc>2 ){ | |
| 283 | + usage("[OPTIONS]"); | |
| 284 | + } | |
| 285 | + } | |
| 286 | + if( zCnt ){ | |
| 287 | + nContext = atoi(zCnt); | |
| 288 | + if( nContext<0 ) nContext = 0xfffffff; | |
| 289 | + }else{ | |
| 290 | + nContext = 6; | |
| 291 | + } | |
| 292 | + if( !db_table_exists("localdb","mergestat") ){ | |
| 293 | + if( zTcl ){ | |
| 294 | + fossil_print("ERROR {no merge data available}\n"); | |
| 295 | + }else{ | |
| 296 | + fossil_print("No merge data is available\n"); | |
| 297 | + } | |
| 298 | + return; | |
| 299 | + } | |
| 300 | + if( bTk ){ | |
| 301 | + merge_info_tk(bDark, bAll, nContext); | |
| 302 | + return; | |
| 303 | + } | |
| 304 | + if( zTcl ){ | |
| 305 | + merge_info_tcl(zTcl, nContext); | |
| 306 | + return; | |
| 307 | + } | |
| 308 | + if( bAll ){ | |
| 309 | + zWhere = ""; | |
| 310 | + }else{ | |
| 311 | + zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')"; | |
| 312 | + } | |
| 313 | + db_prepare(&q, | |
| 314 | + "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," | |
| 315 | + "('MERGE',1),('ADDED',2),('UPDATE',2))" | |
| 316 | + | |
| 317 | + /* 0 1 2 */ | |
| 318 | + "SELECT op, coalesce(fnr,fn), msg" | |
| 319 | + " FROM mergestat JOIN priority USING(op)" | |
| 320 | + " %s" | |
| 321 | + " ORDER BY pri, coalesce(fnr,fn)", | |
| 322 | + zWhere /*safe-for-%s*/ | |
| 323 | + ); | |
| 324 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 325 | + const char *zOp = db_column_text(&q, 0); | |
| 326 | + const char *zName = db_column_text(&q, 1); | |
| 327 | + const char *zErr = db_column_text(&q, 2); | |
| 328 | + if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){ | |
| 329 | + fossil_print("%-9s %s (%s)\n", zOp, zName, zErr); | |
| 330 | + }else{ | |
| 331 | + fossil_print("%-9s %s\n", zOp, zName); | |
| 332 | + } | |
| 333 | + cnt++; | |
| 334 | + } | |
| 335 | + db_finalize(&q); | |
| 336 | + if( !bAll && cnt==0 ){ | |
| 337 | + fossil_print( | |
| 338 | + "No interesting changes in this merge. Use --all to see everything.\n" | |
| 339 | + ); | |
| 340 | + } | |
| 341 | +} | |
| 342 | + | |
| 343 | +/* | |
| 344 | +** Erase all information about prior merges. Do this, for example, after | |
| 345 | +** a commit. | |
| 346 | +*/ | |
| 347 | +void merge_info_forget(void){ | |
| 348 | + db_multi_exec( | |
| 349 | + "DROP TABLE IF EXISTS localdb.mergestat;" | |
| 350 | + "DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';" | |
| 351 | + ); | |
| 352 | +} | |
| 353 | + | |
| 354 | + | |
| 355 | +/* | |
| 356 | +** Initialize the MERGESTAT table. | |
| 357 | +** | |
| 358 | +** Notes about mergestat: | |
| 359 | +** | |
| 360 | +** * ridv is a positive integer and sz is NULL if the V file contained | |
| 361 | +** no local edits prior to the merge. If the V file was modified prior | |
| 362 | +** to the merge then ridv is NULL and sz is the size of the file prior | |
| 363 | +** to merge. | |
| 364 | +** | |
| 365 | +** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was | |
| 366 | +** added by merge. | |
| 367 | +*/ | |
| 368 | +void merge_info_init(void){ | |
| 369 | + merge_info_forget(); | |
| 370 | + db_multi_exec( | |
| 371 | + "CREATE TABLE localdb.mergestat(\n" | |
| 372 | + " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n" | |
| 373 | + " fnp TEXT, -- Name of the pivot file (P)\n" | |
| 374 | + " ridp INT, -- RID for the pivot file\n" | |
| 375 | + " fn TEXT, -- Name of origin file (V)\n" | |
| 376 | + " ridv INT, -- RID for origin file, or NULL if previously edited\n" | |
| 377 | + " sz INT, -- Size of origin file in bytes, NULL if unedited\n" | |
| 378 | + " fnm TEXT, -- Name of the file being merged in (M)\n" | |
| 379 | + " ridm INT, -- RID for the merge-in file\n" | |
| 380 | + " fnr TEXT, -- Name of the final output file, after all renaming\n" | |
| 381 | + " nc INT DEFAULT 0, -- Number of conflicts\n" | |
| 382 | + " msg TEXT -- Error message\n" | |
| 383 | + ");" | |
| 384 | + ); | |
| 385 | +} | |
| 386 | + | |
| 25 | 387 | /* |
| 26 | 388 | ** Print information about a particular check-in. |
| 27 | 389 | */ |
| 28 | 390 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| 29 | 391 | Stmt q; |
| @@ -295,10 +657,13 @@ | ||
| 295 | 657 | ** Files which are renamed in the merged-in branch will be renamed in |
| 296 | 658 | ** the current check-out. |
| 297 | 659 | ** |
| 298 | 660 | ** If the VERSION argument is omitted, then Fossil attempts to find |
| 299 | 661 | ** a recent fork on the current branch to merge. |
| 662 | +** | |
| 663 | +** Note that this command does not commit the merge, as that is a | |
| 664 | +** separate step. | |
| 300 | 665 | ** |
| 301 | 666 | ** If there are multiple VERSION arguments, then each VERSION is merged |
| 302 | 667 | ** (or cherrypicked) in the order that they appear on the command-line. |
| 303 | 668 | ** |
| 304 | 669 | ** Options: |
| @@ -320,12 +685,13 @@ | ||
| 320 | 685 | ** --force-missing Force the merge even if there is missing content |
| 321 | 686 | ** --integrate Merged branch will be closed when committing |
| 322 | 687 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 323 | 688 | ** used for merging, named *-baseline, *-original, |
| 324 | 689 | ** and *-merge. |
| 325 | -** -n|--dry-run If given, display instead of run actions | |
| 690 | +** -n|--dry-run Do not actually change files on disk | |
| 326 | 691 | ** --nosync Do not auto-sync prior to merging |
| 692 | +** --noundo Do not record changes in the undo log | |
| 327 | 693 | ** -v|--verbose Show additional details of the merge |
| 328 | 694 | */ |
| 329 | 695 | void merge_cmd(void){ |
| 330 | 696 | int vid; /* Current version "V" */ |
| 331 | 697 | int mid; /* Version we are merging from "M" */ |
| @@ -347,10 +713,11 @@ | ||
| 347 | 713 | int nOverwrite = 0; /* Number of unmanaged files overwritten */ |
| 348 | 714 | char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ |
| 349 | 715 | const char *zVersion; /* The VERSION argument */ |
| 350 | 716 | int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ |
| 351 | 717 | int nMerge = 0; /* Number of prior merges processed */ |
| 718 | + int useUndo = 1; /* True to record changes in the undo log */ | |
| 352 | 719 | Stmt q; /* SQL statment used for merge processing */ |
| 353 | 720 | |
| 354 | 721 | |
| 355 | 722 | /* Notation: |
| 356 | 723 | ** |
| @@ -395,10 +762,12 @@ | ||
| 395 | 762 | ** * The --dry-run option is also useful in combination with --debug. |
| 396 | 763 | */ |
| 397 | 764 | debugFlag = find_option("debug",0,0)!=0; |
| 398 | 765 | if( debugFlag && verboseFlag ) debugFlag = 2; |
| 399 | 766 | showVfileFlag = find_option("show-vfile",0,0)!=0; |
| 767 | + useUndo = find_option("noundo",0,0)==0; | |
| 768 | + if( dryRunFlag ) useUndo = 0; | |
| 400 | 769 | |
| 401 | 770 | verify_all_options(); |
| 402 | 771 | db_must_be_within_tree(); |
| 403 | 772 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 404 | 773 | vid = db_lget_int("checkout", 0); |
| @@ -561,11 +930,11 @@ | ||
| 561 | 930 | integrateFlag ? "integrate:" : "merge-from:"); |
| 562 | 931 | print_checkin_description(pid, 12, "baseline:"); |
| 563 | 932 | } |
| 564 | 933 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 565 | 934 | if( nMerge==0 ) db_begin_transaction(); |
| 566 | - if( !dryRunFlag ) undo_begin(); | |
| 935 | + if( useUndo ) undo_begin(); | |
| 567 | 936 | if( load_vfile_from_rid(mid) && !forceMissingFlag ){ |
| 568 | 937 | fossil_fatal("missing content, unable to merge"); |
| 569 | 938 | } |
| 570 | 939 | if( load_vfile_from_rid(pid) && !forceMissingFlag ){ |
| 571 | 940 | fossil_fatal("missing content, unable to merge"); |
| @@ -797,15 +1166,21 @@ | ||
| 797 | 1166 | |
| 798 | 1167 | /************************************************************************ |
| 799 | 1168 | ** All of the information needed to do the merge is now contained in the |
| 800 | 1169 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 801 | 1170 | ** |
| 802 | - ** First, find files that have changed from P->M but not P->V. | |
| 1171 | + ** Begin by constructing the localdb.mergestat table. | |
| 1172 | + */ | |
| 1173 | + merge_info_init(); | |
| 1174 | + | |
| 1175 | + /* | |
| 1176 | + ** Find files that have changed from P->M but not P->V. | |
| 803 | 1177 | ** Copy the M content over into V. |
| 804 | 1178 | */ |
| 805 | 1179 | db_prepare(&q, |
| 806 | - "SELECT idv, ridm, fn, islinkm FROM fv" | |
| 1180 | + /* 0 1 2 3 4 5 6 7 */ | |
| 1181 | + "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv" | |
| 807 | 1182 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 808 | 1183 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 809 | 1184 | ); |
| 810 | 1185 | while( db_step(&q)==SQLITE_ROW ){ |
| 811 | 1186 | int idv = db_column_int(&q, 0); |
| @@ -812,20 +1187,31 @@ | ||
| 812 | 1187 | int ridm = db_column_int(&q, 1); |
| 813 | 1188 | const char *zName = db_column_text(&q, 2); |
| 814 | 1189 | int islinkm = db_column_int(&q, 3); |
| 815 | 1190 | /* Copy content from idm over into idv. Overwrite idv. */ |
| 816 | 1191 | fossil_print("UPDATE %s\n", zName); |
| 1192 | + if( useUndo ) undo_save(zName); | |
| 817 | 1193 | if( !dryRunFlag ){ |
| 818 | - undo_save(zName); | |
| 819 | 1194 | db_multi_exec( |
| 820 | 1195 | "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," |
| 821 | 1196 | " mhash=CASE WHEN rid<>%d" |
| 822 | 1197 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 823 | 1198 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 824 | 1199 | ); |
| 825 | 1200 | vfile_to_disk(0, idv, 0, 0); |
| 826 | 1201 | } |
| 1202 | + db_multi_exec( | |
| 1203 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)" | |
| 1204 | + "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)", | |
| 1205 | + /* fnp */ db_column_text(&q, 4), | |
| 1206 | + /* ridp */ db_column_int(&q,5), | |
| 1207 | + /* fn */ zName, | |
| 1208 | + /* ridv */ db_column_int(&q,6), | |
| 1209 | + /* fnm */ db_column_text(&q, 7), | |
| 1210 | + /* ridm */ ridm, | |
| 1211 | + /* fnr */ zName | |
| 1212 | + ); | |
| 827 | 1213 | } |
| 828 | 1214 | db_finalize(&q); |
| 829 | 1215 | |
| 830 | 1216 | /* |
| 831 | 1217 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -833,11 +1219,15 @@ | ||
| 833 | 1219 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 834 | 1220 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 835 | 1221 | ** added to the file and user will be forced to take a decision. |
| 836 | 1222 | */ |
| 837 | 1223 | db_prepare(&q, |
| 838 | - "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" | |
| 1224 | + /* 0 1 2 3 4 5 6 7 8 */ | |
| 1225 | + "SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm," | |
| 1226 | + /* 9 10 11 */ | |
| 1227 | + " fnp, fnm, chnged" | |
| 1228 | + " FROM fv" | |
| 839 | 1229 | " WHERE idv>0 AND idm>0" |
| 840 | 1230 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 841 | 1231 | glob_expr("fv.fn", zBinGlob) |
| 842 | 1232 | ); |
| 843 | 1233 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -848,12 +1238,14 @@ | ||
| 848 | 1238 | int isBinary = db_column_int(&q, 4); |
| 849 | 1239 | const char *zName = db_column_text(&q, 5); |
| 850 | 1240 | int isExe = db_column_int(&q, 6); |
| 851 | 1241 | int islinkv = db_column_int(&q, 7); |
| 852 | 1242 | int islinkm = db_column_int(&q, 8); |
| 1243 | + int chnged = db_column_int(&q, 11); | |
| 853 | 1244 | int rc; |
| 854 | 1245 | char *zFullPath; |
| 1246 | + const char *zType = "MERGE"; | |
| 855 | 1247 | Blob m, p, r; |
| 856 | 1248 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 857 | 1249 | if( verboseFlag ){ |
| 858 | 1250 | fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", |
| 859 | 1251 | zName, ridp, ridm, ridv); |
| @@ -861,13 +1253,29 @@ | ||
| 861 | 1253 | fossil_print("MERGE %s\n", zName); |
| 862 | 1254 | } |
| 863 | 1255 | if( islinkv || islinkm ){ |
| 864 | 1256 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 865 | 1257 | nConflict++; |
| 1258 | + db_multi_exec( | |
| 1259 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)" | |
| 1260 | + "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')", | |
| 1261 | + /* fnp */ db_column_text(&q, 9), | |
| 1262 | + /* ridp */ ridp, | |
| 1263 | + /* fn */ zName, | |
| 1264 | + /* ridv */ ridv, | |
| 1265 | + /* fnm */ db_column_text(&q, 10), | |
| 1266 | + /* ridm */ ridm, | |
| 1267 | + /* fnr */ zName | |
| 1268 | + ); | |
| 866 | 1269 | }else{ |
| 867 | - if( !dryRunFlag ) undo_save(zName); | |
| 1270 | + i64 sz; | |
| 1271 | + const char *zErrMsg = 0; | |
| 1272 | + int nc = 0; | |
| 1273 | + | |
| 1274 | + if( useUndo ) undo_save(zName); | |
| 868 | 1275 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1276 | + sz = file_size(zFullPath, ExtFILE); | |
| 869 | 1277 | content_get(ridp, &p); |
| 870 | 1278 | content_get(ridm, &m); |
| 871 | 1279 | if( isBinary ){ |
| 872 | 1280 | rc = -1; |
| 873 | 1281 | blob_zero(&r); |
| @@ -884,15 +1292,38 @@ | ||
| 884 | 1292 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 885 | 1293 | if( rc>0 ){ |
| 886 | 1294 | fossil_print("***** %d merge conflict%s in %s\n", |
| 887 | 1295 | rc, rc>1 ? "s" : "", zName); |
| 888 | 1296 | nConflict++; |
| 1297 | + nc = rc; | |
| 1298 | + zErrMsg = "merge conflicts"; | |
| 1299 | + zType = "CONFLICT"; | |
| 889 | 1300 | } |
| 890 | 1301 | }else{ |
| 891 | 1302 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 892 | 1303 | nConflict++; |
| 1304 | + nc = 1; | |
| 1305 | + zErrMsg = "cannot merge binary file"; | |
| 1306 | + zType = "ERROR"; | |
| 893 | 1307 | } |
| 1308 | + db_multi_exec( | |
| 1309 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" | |
| 1310 | + "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d," | |
| 1311 | + "%Q,%d,%Q)", | |
| 1312 | + /* op */ zType, | |
| 1313 | + /* fnp */ db_column_text(&q, 9), | |
| 1314 | + /* ridp */ ridp, | |
| 1315 | + /* fn */ zName, | |
| 1316 | + /* ridv */ chnged==0, ridv, | |
| 1317 | + /* sz */ chnged!=0, sz, | |
| 1318 | + /* fnm */ db_column_text(&q, 10), | |
| 1319 | + /* ridm */ ridm, | |
| 1320 | + /* fnr */ zName, | |
| 1321 | + /* nc */ nc, | |
| 1322 | + /* msg */ zErrMsg | |
| 1323 | + ); | |
| 1324 | + fossil_free(zFullPath); | |
| 894 | 1325 | blob_reset(&p); |
| 895 | 1326 | blob_reset(&m); |
| 896 | 1327 | blob_reset(&r); |
| 897 | 1328 | } |
| 898 | 1329 | vmerge_insert(idv, ridm); |
| @@ -901,32 +1332,53 @@ | ||
| 901 | 1332 | |
| 902 | 1333 | /* |
| 903 | 1334 | ** Drop files that are in P and V but not in M |
| 904 | 1335 | */ |
| 905 | 1336 | db_prepare(&q, |
| 906 | - "SELECT idv, fn, chnged FROM fv" | |
| 1337 | + "SELECT idv, fn, chnged, ridv FROM fv" | |
| 907 | 1338 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 908 | 1339 | ); |
| 909 | 1340 | while( db_step(&q)==SQLITE_ROW ){ |
| 910 | 1341 | int idv = db_column_int(&q, 0); |
| 911 | 1342 | const char *zName = db_column_text(&q, 1); |
| 912 | 1343 | int chnged = db_column_int(&q, 2); |
| 1344 | + int ridv = db_column_int(&q, 3); | |
| 1345 | + int sz = -1; | |
| 1346 | + const char *zErrMsg = 0; | |
| 1347 | + int nc = 0; | |
| 913 | 1348 | /* Delete the file idv */ |
| 914 | 1349 | fossil_print("DELETE %s\n", zName); |
| 915 | 1350 | if( chnged ){ |
| 1351 | + char *zFullPath; | |
| 916 | 1352 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 917 | 1353 | nConflict++; |
| 1354 | + ridv = 0; | |
| 1355 | + nc = 1; | |
| 1356 | + zErrMsg = "local edits lost"; | |
| 1357 | + zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); | |
| 1358 | + sz = file_size(zFullPath, ExtFILE); | |
| 1359 | + fossil_free(zFullPath); | |
| 918 | 1360 | } |
| 919 | - if( !dryRunFlag ) undo_save(zName); | |
| 1361 | + if( useUndo ) undo_save(zName); | |
| 920 | 1362 | db_multi_exec( |
| 921 | 1363 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 922 | 1364 | ); |
| 923 | 1365 | if( !dryRunFlag ){ |
| 924 | 1366 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 925 | 1367 | file_delete(zFullPath); |
| 926 | 1368 | free(zFullPath); |
| 927 | 1369 | } |
| 1370 | + db_multi_exec( | |
| 1371 | + "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)" | |
| 1372 | + "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL)," | |
| 1373 | + "NULL,NULL,%d,%Q)", | |
| 1374 | + /* fn */ zName, | |
| 1375 | + /* ridv */ chnged==0, ridv, | |
| 1376 | + /* sz */ chnged!=0, sz, | |
| 1377 | + /* nc */ nc, | |
| 1378 | + /* msg */ zErrMsg | |
| 1379 | + ); | |
| 928 | 1380 | } |
| 929 | 1381 | db_finalize(&q); |
| 930 | 1382 | |
| 931 | 1383 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 932 | 1384 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -951,12 +1403,16 @@ | ||
| 951 | 1403 | int idv = db_column_int(&q, 0); |
| 952 | 1404 | const char *zOldName = db_column_text(&q, 1); |
| 953 | 1405 | const char *zNewName = db_column_text(&q, 2); |
| 954 | 1406 | int isExe = db_column_int(&q, 3); |
| 955 | 1407 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 956 | - if( !dryRunFlag ) undo_save(zOldName); | |
| 957 | - if( !dryRunFlag ) undo_save(zNewName); | |
| 1408 | + if( useUndo ) undo_save(zOldName); | |
| 1409 | + if( useUndo ) undo_save(zNewName); | |
| 1410 | + db_multi_exec( | |
| 1411 | + "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", | |
| 1412 | + zOldName | |
| 1413 | + ); | |
| 958 | 1414 | db_multi_exec( |
| 959 | 1415 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 960 | 1416 | " WHERE vid=%d AND pathname=%Q;" |
| 961 | 1417 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 962 | 1418 | " WHERE id=%d;", |
| @@ -1006,11 +1462,11 @@ | ||
| 1006 | 1462 | |
| 1007 | 1463 | /* |
| 1008 | 1464 | ** Insert into V any files that are not in V or P but are in M. |
| 1009 | 1465 | */ |
| 1010 | 1466 | db_prepare(&q, |
| 1011 | - "SELECT idm, fnm FROM fv" | |
| 1467 | + "SELECT idm, fnm, ridm FROM fv" | |
| 1012 | 1468 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1013 | 1469 | ); |
| 1014 | 1470 | while( db_step(&q)==SQLITE_ROW ){ |
| 1015 | 1471 | int idm = db_column_int(&q, 0); |
| 1016 | 1472 | const char *zName; |
| @@ -1039,12 +1495,19 @@ | ||
| 1039 | 1495 | nOverwrite++; |
| 1040 | 1496 | }else{ |
| 1041 | 1497 | fossil_print("ADDED %s\n", zName); |
| 1042 | 1498 | } |
| 1043 | 1499 | fossil_free(zFullName); |
| 1500 | + db_multi_exec( | |
| 1501 | + "INSERT INTO mergestat(op,fnm,ridm,fnr)" | |
| 1502 | + "VALUES('ADDED',%Q,%d,%Q)", | |
| 1503 | + /* fnm */ zName, | |
| 1504 | + /* ridm */ db_column_int(&q,2), | |
| 1505 | + /* fnr */ zName | |
| 1506 | + ); | |
| 1507 | + if( useUndo ) undo_save(zName); | |
| 1044 | 1508 | if( !dryRunFlag ){ |
| 1045 | - undo_save(zName); | |
| 1046 | 1509 | vfile_to_disk(0, idm, 0, 0); |
| 1047 | 1510 | } |
| 1048 | 1511 | } |
| 1049 | 1512 | db_finalize(&q); |
| 1050 | 1513 | |
| @@ -1099,9 +1562,9 @@ | ||
| 1099 | 1562 | } |
| 1100 | 1563 | if( bMultiMerge && nConflict==0 ){ |
| 1101 | 1564 | nMerge++; |
| 1102 | 1565 | goto merge_next_child; |
| 1103 | 1566 | } |
| 1104 | - if( !dryRunFlag ) undo_finish(); | |
| 1567 | + if( useUndo ) undo_finish(); | |
| 1105 | 1568 | |
| 1106 | 1569 | db_end_transaction(dryRunFlag); |
| 1107 | 1570 | } |
| 1108 | 1571 | |
| 1109 | 1572 | ADDED src/merge.tcl |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -20,10 +20,372 @@ | |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "merge.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | /* |
| 26 | ** Print information about a particular check-in. |
| 27 | */ |
| 28 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| 29 | Stmt q; |
| @@ -295,10 +657,13 @@ | |
| 295 | ** Files which are renamed in the merged-in branch will be renamed in |
| 296 | ** the current check-out. |
| 297 | ** |
| 298 | ** If the VERSION argument is omitted, then Fossil attempts to find |
| 299 | ** a recent fork on the current branch to merge. |
| 300 | ** |
| 301 | ** If there are multiple VERSION arguments, then each VERSION is merged |
| 302 | ** (or cherrypicked) in the order that they appear on the command-line. |
| 303 | ** |
| 304 | ** Options: |
| @@ -320,12 +685,13 @@ | |
| 320 | ** --force-missing Force the merge even if there is missing content |
| 321 | ** --integrate Merged branch will be closed when committing |
| 322 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 323 | ** used for merging, named *-baseline, *-original, |
| 324 | ** and *-merge. |
| 325 | ** -n|--dry-run If given, display instead of run actions |
| 326 | ** --nosync Do not auto-sync prior to merging |
| 327 | ** -v|--verbose Show additional details of the merge |
| 328 | */ |
| 329 | void merge_cmd(void){ |
| 330 | int vid; /* Current version "V" */ |
| 331 | int mid; /* Version we are merging from "M" */ |
| @@ -347,10 +713,11 @@ | |
| 347 | int nOverwrite = 0; /* Number of unmanaged files overwritten */ |
| 348 | char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ |
| 349 | const char *zVersion; /* The VERSION argument */ |
| 350 | int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ |
| 351 | int nMerge = 0; /* Number of prior merges processed */ |
| 352 | Stmt q; /* SQL statment used for merge processing */ |
| 353 | |
| 354 | |
| 355 | /* Notation: |
| 356 | ** |
| @@ -395,10 +762,12 @@ | |
| 395 | ** * The --dry-run option is also useful in combination with --debug. |
| 396 | */ |
| 397 | debugFlag = find_option("debug",0,0)!=0; |
| 398 | if( debugFlag && verboseFlag ) debugFlag = 2; |
| 399 | showVfileFlag = find_option("show-vfile",0,0)!=0; |
| 400 | |
| 401 | verify_all_options(); |
| 402 | db_must_be_within_tree(); |
| 403 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 404 | vid = db_lget_int("checkout", 0); |
| @@ -561,11 +930,11 @@ | |
| 561 | integrateFlag ? "integrate:" : "merge-from:"); |
| 562 | print_checkin_description(pid, 12, "baseline:"); |
| 563 | } |
| 564 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 565 | if( nMerge==0 ) db_begin_transaction(); |
| 566 | if( !dryRunFlag ) undo_begin(); |
| 567 | if( load_vfile_from_rid(mid) && !forceMissingFlag ){ |
| 568 | fossil_fatal("missing content, unable to merge"); |
| 569 | } |
| 570 | if( load_vfile_from_rid(pid) && !forceMissingFlag ){ |
| 571 | fossil_fatal("missing content, unable to merge"); |
| @@ -797,15 +1166,21 @@ | |
| 797 | |
| 798 | /************************************************************************ |
| 799 | ** All of the information needed to do the merge is now contained in the |
| 800 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 801 | ** |
| 802 | ** First, find files that have changed from P->M but not P->V. |
| 803 | ** Copy the M content over into V. |
| 804 | */ |
| 805 | db_prepare(&q, |
| 806 | "SELECT idv, ridm, fn, islinkm FROM fv" |
| 807 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 808 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 809 | ); |
| 810 | while( db_step(&q)==SQLITE_ROW ){ |
| 811 | int idv = db_column_int(&q, 0); |
| @@ -812,20 +1187,31 @@ | |
| 812 | int ridm = db_column_int(&q, 1); |
| 813 | const char *zName = db_column_text(&q, 2); |
| 814 | int islinkm = db_column_int(&q, 3); |
| 815 | /* Copy content from idm over into idv. Overwrite idv. */ |
| 816 | fossil_print("UPDATE %s\n", zName); |
| 817 | if( !dryRunFlag ){ |
| 818 | undo_save(zName); |
| 819 | db_multi_exec( |
| 820 | "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," |
| 821 | " mhash=CASE WHEN rid<>%d" |
| 822 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 823 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 824 | ); |
| 825 | vfile_to_disk(0, idv, 0, 0); |
| 826 | } |
| 827 | } |
| 828 | db_finalize(&q); |
| 829 | |
| 830 | /* |
| 831 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -833,11 +1219,15 @@ | |
| 833 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 834 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 835 | ** added to the file and user will be forced to take a decision. |
| 836 | */ |
| 837 | db_prepare(&q, |
| 838 | "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" |
| 839 | " WHERE idv>0 AND idm>0" |
| 840 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 841 | glob_expr("fv.fn", zBinGlob) |
| 842 | ); |
| 843 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -848,12 +1238,14 @@ | |
| 848 | int isBinary = db_column_int(&q, 4); |
| 849 | const char *zName = db_column_text(&q, 5); |
| 850 | int isExe = db_column_int(&q, 6); |
| 851 | int islinkv = db_column_int(&q, 7); |
| 852 | int islinkm = db_column_int(&q, 8); |
| 853 | int rc; |
| 854 | char *zFullPath; |
| 855 | Blob m, p, r; |
| 856 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 857 | if( verboseFlag ){ |
| 858 | fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", |
| 859 | zName, ridp, ridm, ridv); |
| @@ -861,13 +1253,29 @@ | |
| 861 | fossil_print("MERGE %s\n", zName); |
| 862 | } |
| 863 | if( islinkv || islinkm ){ |
| 864 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 865 | nConflict++; |
| 866 | }else{ |
| 867 | if( !dryRunFlag ) undo_save(zName); |
| 868 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 869 | content_get(ridp, &p); |
| 870 | content_get(ridm, &m); |
| 871 | if( isBinary ){ |
| 872 | rc = -1; |
| 873 | blob_zero(&r); |
| @@ -884,15 +1292,38 @@ | |
| 884 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 885 | if( rc>0 ){ |
| 886 | fossil_print("***** %d merge conflict%s in %s\n", |
| 887 | rc, rc>1 ? "s" : "", zName); |
| 888 | nConflict++; |
| 889 | } |
| 890 | }else{ |
| 891 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 892 | nConflict++; |
| 893 | } |
| 894 | blob_reset(&p); |
| 895 | blob_reset(&m); |
| 896 | blob_reset(&r); |
| 897 | } |
| 898 | vmerge_insert(idv, ridm); |
| @@ -901,32 +1332,53 @@ | |
| 901 | |
| 902 | /* |
| 903 | ** Drop files that are in P and V but not in M |
| 904 | */ |
| 905 | db_prepare(&q, |
| 906 | "SELECT idv, fn, chnged FROM fv" |
| 907 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 908 | ); |
| 909 | while( db_step(&q)==SQLITE_ROW ){ |
| 910 | int idv = db_column_int(&q, 0); |
| 911 | const char *zName = db_column_text(&q, 1); |
| 912 | int chnged = db_column_int(&q, 2); |
| 913 | /* Delete the file idv */ |
| 914 | fossil_print("DELETE %s\n", zName); |
| 915 | if( chnged ){ |
| 916 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 917 | nConflict++; |
| 918 | } |
| 919 | if( !dryRunFlag ) undo_save(zName); |
| 920 | db_multi_exec( |
| 921 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 922 | ); |
| 923 | if( !dryRunFlag ){ |
| 924 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 925 | file_delete(zFullPath); |
| 926 | free(zFullPath); |
| 927 | } |
| 928 | } |
| 929 | db_finalize(&q); |
| 930 | |
| 931 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 932 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -951,12 +1403,16 @@ | |
| 951 | int idv = db_column_int(&q, 0); |
| 952 | const char *zOldName = db_column_text(&q, 1); |
| 953 | const char *zNewName = db_column_text(&q, 2); |
| 954 | int isExe = db_column_int(&q, 3); |
| 955 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 956 | if( !dryRunFlag ) undo_save(zOldName); |
| 957 | if( !dryRunFlag ) undo_save(zNewName); |
| 958 | db_multi_exec( |
| 959 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 960 | " WHERE vid=%d AND pathname=%Q;" |
| 961 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 962 | " WHERE id=%d;", |
| @@ -1006,11 +1462,11 @@ | |
| 1006 | |
| 1007 | /* |
| 1008 | ** Insert into V any files that are not in V or P but are in M. |
| 1009 | */ |
| 1010 | db_prepare(&q, |
| 1011 | "SELECT idm, fnm FROM fv" |
| 1012 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1013 | ); |
| 1014 | while( db_step(&q)==SQLITE_ROW ){ |
| 1015 | int idm = db_column_int(&q, 0); |
| 1016 | const char *zName; |
| @@ -1039,12 +1495,19 @@ | |
| 1039 | nOverwrite++; |
| 1040 | }else{ |
| 1041 | fossil_print("ADDED %s\n", zName); |
| 1042 | } |
| 1043 | fossil_free(zFullName); |
| 1044 | if( !dryRunFlag ){ |
| 1045 | undo_save(zName); |
| 1046 | vfile_to_disk(0, idm, 0, 0); |
| 1047 | } |
| 1048 | } |
| 1049 | db_finalize(&q); |
| 1050 | |
| @@ -1099,9 +1562,9 @@ | |
| 1099 | } |
| 1100 | if( bMultiMerge && nConflict==0 ){ |
| 1101 | nMerge++; |
| 1102 | goto merge_next_child; |
| 1103 | } |
| 1104 | if( !dryRunFlag ) undo_finish(); |
| 1105 | |
| 1106 | db_end_transaction(dryRunFlag); |
| 1107 | } |
| 1108 | |
| 1109 | DDED src/merge.tcl |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -20,10 +20,372 @@ | |
| 20 | */ |
| 21 | #include "config.h" |
| 22 | #include "merge.h" |
| 23 | #include <assert.h> |
| 24 | |
| 25 | |
| 26 | /* |
| 27 | ** Bring up a Tcl/Tk GUI to show details of the most recent merge. |
| 28 | */ |
| 29 | static void merge_info_tk(int bDark, int bAll, int nContext){ |
| 30 | int i; |
| 31 | Blob script; |
| 32 | const char *zTempFile = 0; |
| 33 | char *zCmd; |
| 34 | const char *zTclsh; |
| 35 | zTclsh = find_option("tclsh",0,1); |
| 36 | if( zTclsh==0 ){ |
| 37 | zTclsh = db_get("tclsh",0); |
| 38 | } |
| 39 | /* The undocumented --script FILENAME option causes the Tk script to |
| 40 | ** be written into the FILENAME instead of being run. This is used |
| 41 | ** for testing and debugging. */ |
| 42 | zTempFile = find_option("script",0,1); |
| 43 | verify_all_options(); |
| 44 | |
| 45 | blob_zero(&script); |
| 46 | blob_appendf(&script, "set ncontext %d\n", nContext); |
| 47 | blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n", |
| 48 | g.nameOfExe); |
| 49 | blob_appendf(&script, "set filelist [list"); |
| 50 | if( g.argc==2 ){ |
| 51 | /* No files named on the command-line. Use every file mentioned |
| 52 | ** in the MERGESTAT table to generate the file list. */ |
| 53 | Stmt q; |
| 54 | int cnt = 0; |
| 55 | db_prepare(&q, |
| 56 | "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," |
| 57 | "('MERGE',1),('ADDED',2),('UPDATE',2))" |
| 58 | "SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)" |
| 59 | " %s ORDER BY pri, 1", |
| 60 | bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/ |
| 61 | ); |
| 62 | while( db_step(&q)==SQLITE_ROW ){ |
| 63 | blob_appendf(&script," %s ", db_column_text(&q,1)); |
| 64 | blob_append_tcl_literal(&script, db_column_text(&q,0), |
| 65 | db_column_bytes(&q,0)); |
| 66 | cnt++; |
| 67 | } |
| 68 | db_finalize(&q); |
| 69 | if( cnt==0 ){ |
| 70 | fossil_print( |
| 71 | "No interesting changes in this merge. Use --all to see everything\n" |
| 72 | ); |
| 73 | return; |
| 74 | } |
| 75 | }else{ |
| 76 | /* Use only files named on the command-line in the file list. |
| 77 | ** But verify each file named is actually found in the MERGESTAT |
| 78 | ** table first. */ |
| 79 | for(i=2; i<g.argc; i++){ |
| 80 | char *zFile; /* Input filename */ |
| 81 | char *zTreename; /* Name of the file in the tree */ |
| 82 | Blob fname; /* Filename relative to root */ |
| 83 | char *zOp; /* Operation on this file */ |
| 84 | zFile = mprintf("%/", g.argv[i]); |
| 85 | file_tree_name(zFile, &fname, 0, 1); |
| 86 | fossil_free(zFile); |
| 87 | zTreename = blob_str(&fname); |
| 88 | zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q", |
| 89 | zTreename, zTreename); |
| 90 | blob_appendf(&script, " %s ", zOp); |
| 91 | fossil_free(zOp); |
| 92 | blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename)); |
| 93 | blob_reset(&fname); |
| 94 | } |
| 95 | } |
| 96 | blob_appendf(&script, "]\n"); |
| 97 | blob_appendf(&script, "set darkmode %d\n", bDark!=0); |
| 98 | blob_appendf(&script, "%s", builtin_file("merge.tcl", 0)); |
| 99 | if( zTempFile ){ |
| 100 | blob_write_to_file(&script, zTempFile); |
| 101 | fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile); |
| 102 | }else{ |
| 103 | #if defined(FOSSIL_ENABLE_TCL) |
| 104 | Th_FossilInit(TH_INIT_DEFAULT); |
| 105 | if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), |
| 106 | blob_size(&script), 1, 1, 0)==TCL_OK ){ |
| 107 | blob_reset(&script); |
| 108 | return; |
| 109 | } |
| 110 | /* |
| 111 | * If evaluation of the Tcl script fails, the reason may be that Tk |
| 112 | * could not be found by the loaded Tcl, or that Tcl cannot be loaded |
| 113 | * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback |
| 114 | * to using the external "tclsh", if available. |
| 115 | */ |
| 116 | #endif |
| 117 | zTempFile = write_blob_to_temp_file(&script); |
| 118 | zCmd = mprintf("%$ %$", zTclsh, zTempFile); |
| 119 | fossil_system(zCmd); |
| 120 | file_delete(zTempFile); |
| 121 | fossil_free(zCmd); |
| 122 | } |
| 123 | blob_reset(&script); |
| 124 | } |
| 125 | |
| 126 | /* |
| 127 | ** Generate a TCL list on standard output that can be fed into the |
| 128 | ** merge.tcl script to show the details of the most recent merge |
| 129 | ** command associated with file "zFName". zFName must be the filename |
| 130 | ** relative to the root of the check-in - in other words a "tree name". |
| 131 | ** |
| 132 | ** When this routine is called, we know that the mergestat table |
| 133 | ** exists, but we do not know if zFName is mentioned in that table. |
| 134 | */ |
| 135 | static void merge_info_tcl(const char *zFName, int nContext){ |
| 136 | const char *zTreename;/* Name of the file in the tree */ |
| 137 | Stmt q; /* To query the MERGESTAT table */ |
| 138 | MergeBuilder mb; /* The merge builder object */ |
| 139 | Blob pivot,v1,v2,out; /* Blobs for holding content */ |
| 140 | const char *zFN; /* A filename */ |
| 141 | int rid; /* RID value */ |
| 142 | int sz; /* File size value */ |
| 143 | |
| 144 | zTreename = zFName; |
| 145 | db_prepare(&q, |
| 146 | /* 0 1 2 3 4 5 6 7 */ |
| 147 | "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr" |
| 148 | " FROM mergestat" |
| 149 | " WHERE fnp=%Q OR fnr=%Q", |
| 150 | zTreename, zTreename |
| 151 | ); |
| 152 | if( db_step(&q)!=SQLITE_ROW ){ |
| 153 | db_finalize(&q); |
| 154 | fossil_print("ERROR {don't know anything about file: %s}\n", zTreename); |
| 155 | return; |
| 156 | } |
| 157 | mergebuilder_init_tcl(&mb); |
| 158 | mb.nContext = nContext; |
| 159 | |
| 160 | /* Set up the pivot */ |
| 161 | zFN = db_column_text(&q, 0); |
| 162 | if( zFN==0 ){ |
| 163 | /* No pivot because the file was added */ |
| 164 | mb.zPivot = "(no baseline)"; |
| 165 | blob_zero(&pivot); |
| 166 | }else{ |
| 167 | mb.zPivot = mprintf("%s (baseline)", file_tail(zFN)); |
| 168 | rid = db_column_int(&q, 1); |
| 169 | content_get(rid, &pivot); |
| 170 | } |
| 171 | mb.pPivot = &pivot; |
| 172 | |
| 173 | /* Set up the merge-in as V2 */ |
| 174 | zFN = db_column_text(&q, 5); |
| 175 | if( zFN==0 ){ |
| 176 | /* File deleted in the merged-in branch */ |
| 177 | mb.zV2 = "(deleted file)"; |
| 178 | blob_zero(&v2); |
| 179 | }else{ |
| 180 | mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN)); |
| 181 | rid = db_column_int(&q, 6); |
| 182 | content_get(rid, &v2); |
| 183 | } |
| 184 | mb.pV2 = &v2; |
| 185 | |
| 186 | /* Set up the local content as V1 */ |
| 187 | zFN = db_column_text(&q, 2); |
| 188 | if( zFN==0 ){ |
| 189 | /* File added by merge */ |
| 190 | mb.zV1 = "(no original)"; |
| 191 | blob_zero(&v1); |
| 192 | }else{ |
| 193 | mb.zV1 = mprintf("%s (local)", file_tail(zFN)); |
| 194 | rid = db_column_int(&q, 3); |
| 195 | sz = db_column_int(&q, 4); |
| 196 | if( rid==0 && sz>0 ){ |
| 197 | /* The origin file had been edited so we'll have to pull its |
| 198 | ** original content out of the undo buffer */ |
| 199 | Stmt q2; |
| 200 | db_prepare(&q2, |
| 201 | "SELECT content FROM undo" |
| 202 | " WHERE pathname=%Q AND octet_length(content)=%d", |
| 203 | zFN, sz |
| 204 | ); |
| 205 | blob_zero(&v1); |
| 206 | if( db_step(&q2)==SQLITE_ROW ){ |
| 207 | db_column_blob(&q2, 0, &v1); |
| 208 | }else{ |
| 209 | mb.zV1 = "(local content missing)"; |
| 210 | } |
| 211 | db_finalize(&q2); |
| 212 | }else{ |
| 213 | /* The origin file was unchanged when the merge first occurred */ |
| 214 | content_get(rid, &v1); |
| 215 | } |
| 216 | } |
| 217 | mb.pV1 = &v1; |
| 218 | |
| 219 | /* Set up the output */ |
| 220 | zFN = db_column_text(&q, 7); |
| 221 | if( zFN==0 ){ |
| 222 | mb.zOut = "(Merge Result)"; |
| 223 | }else{ |
| 224 | mb.zOut = mprintf("%s (after merge)", file_tail(zFN)); |
| 225 | } |
| 226 | blob_zero(&out); |
| 227 | mb.pOut = &out; |
| 228 | |
| 229 | merge_three_blobs(&mb); |
| 230 | blob_write_to_file(&out, "-"); |
| 231 | |
| 232 | mb.xDestroy(&mb); |
| 233 | blob_reset(&pivot); |
| 234 | blob_reset(&v1); |
| 235 | blob_reset(&v2); |
| 236 | blob_reset(&out); |
| 237 | db_finalize(&q); |
| 238 | } |
| 239 | |
| 240 | /* |
| 241 | ** COMMAND: merge-info |
| 242 | ** |
| 243 | ** Usage: %fossil merge-info [OPTIONS] |
| 244 | ** |
| 245 | ** Display information about the most recent merge operation. |
| 246 | ** |
| 247 | ** Options: |
| 248 | ** -a|--all Show all file changes that happened because of |
| 249 | ** the merge. Normally only MERGE, CONFLICT, and ERROR |
| 250 | ** lines are shown |
| 251 | ** -c|--context N Show N lines of context around each change, |
| 252 | ** with negative N meaning show all content. Only |
| 253 | ** meaningful in combination with --tcl or --tk. |
| 254 | ** --dark Use dark mode for the Tcl/Tk-based GUI |
| 255 | ** --tcl FILE Generate (to stdout) a TCL list containing |
| 256 | ** information needed to display the changes to |
| 257 | ** FILE caused by the most recent merge. FILE must |
| 258 | ** be a pathname relative to the root of the check-out. |
| 259 | ** --tk Bring up a Tcl/Tk GUI that shows the changes |
| 260 | ** associated with the most recent merge. |
| 261 | ** |
| 262 | */ |
| 263 | void merge_info_cmd(void){ |
| 264 | const char *zCnt; |
| 265 | const char *zTcl; |
| 266 | int bTk; |
| 267 | int bDark; |
| 268 | int bAll; |
| 269 | int nContext; |
| 270 | Stmt q; |
| 271 | const char *zWhere; |
| 272 | int cnt = 0; |
| 273 | |
| 274 | db_must_be_within_tree(); |
| 275 | zTcl = find_option("tcl", 0, 1); |
| 276 | bTk = find_option("tk", 0, 0)!=0; |
| 277 | zCnt = find_option("context", "c", 1); |
| 278 | bDark = find_option("dark", 0, 0)!=0; |
| 279 | bAll = find_option("all", "a", 0)!=0; |
| 280 | if( bTk==0 ){ |
| 281 | verify_all_options(); |
| 282 | if( g.argc>2 ){ |
| 283 | usage("[OPTIONS]"); |
| 284 | } |
| 285 | } |
| 286 | if( zCnt ){ |
| 287 | nContext = atoi(zCnt); |
| 288 | if( nContext<0 ) nContext = 0xfffffff; |
| 289 | }else{ |
| 290 | nContext = 6; |
| 291 | } |
| 292 | if( !db_table_exists("localdb","mergestat") ){ |
| 293 | if( zTcl ){ |
| 294 | fossil_print("ERROR {no merge data available}\n"); |
| 295 | }else{ |
| 296 | fossil_print("No merge data is available\n"); |
| 297 | } |
| 298 | return; |
| 299 | } |
| 300 | if( bTk ){ |
| 301 | merge_info_tk(bDark, bAll, nContext); |
| 302 | return; |
| 303 | } |
| 304 | if( zTcl ){ |
| 305 | merge_info_tcl(zTcl, nContext); |
| 306 | return; |
| 307 | } |
| 308 | if( bAll ){ |
| 309 | zWhere = ""; |
| 310 | }else{ |
| 311 | zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')"; |
| 312 | } |
| 313 | db_prepare(&q, |
| 314 | "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," |
| 315 | "('MERGE',1),('ADDED',2),('UPDATE',2))" |
| 316 | |
| 317 | /* 0 1 2 */ |
| 318 | "SELECT op, coalesce(fnr,fn), msg" |
| 319 | " FROM mergestat JOIN priority USING(op)" |
| 320 | " %s" |
| 321 | " ORDER BY pri, coalesce(fnr,fn)", |
| 322 | zWhere /*safe-for-%s*/ |
| 323 | ); |
| 324 | while( db_step(&q)==SQLITE_ROW ){ |
| 325 | const char *zOp = db_column_text(&q, 0); |
| 326 | const char *zName = db_column_text(&q, 1); |
| 327 | const char *zErr = db_column_text(&q, 2); |
| 328 | if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){ |
| 329 | fossil_print("%-9s %s (%s)\n", zOp, zName, zErr); |
| 330 | }else{ |
| 331 | fossil_print("%-9s %s\n", zOp, zName); |
| 332 | } |
| 333 | cnt++; |
| 334 | } |
| 335 | db_finalize(&q); |
| 336 | if( !bAll && cnt==0 ){ |
| 337 | fossil_print( |
| 338 | "No interesting changes in this merge. Use --all to see everything.\n" |
| 339 | ); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | /* |
| 344 | ** Erase all information about prior merges. Do this, for example, after |
| 345 | ** a commit. |
| 346 | */ |
| 347 | void merge_info_forget(void){ |
| 348 | db_multi_exec( |
| 349 | "DROP TABLE IF EXISTS localdb.mergestat;" |
| 350 | "DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';" |
| 351 | ); |
| 352 | } |
| 353 | |
| 354 | |
| 355 | /* |
| 356 | ** Initialize the MERGESTAT table. |
| 357 | ** |
| 358 | ** Notes about mergestat: |
| 359 | ** |
| 360 | ** * ridv is a positive integer and sz is NULL if the V file contained |
| 361 | ** no local edits prior to the merge. If the V file was modified prior |
| 362 | ** to the merge then ridv is NULL and sz is the size of the file prior |
| 363 | ** to merge. |
| 364 | ** |
| 365 | ** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was |
| 366 | ** added by merge. |
| 367 | */ |
| 368 | void merge_info_init(void){ |
| 369 | merge_info_forget(); |
| 370 | db_multi_exec( |
| 371 | "CREATE TABLE localdb.mergestat(\n" |
| 372 | " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n" |
| 373 | " fnp TEXT, -- Name of the pivot file (P)\n" |
| 374 | " ridp INT, -- RID for the pivot file\n" |
| 375 | " fn TEXT, -- Name of origin file (V)\n" |
| 376 | " ridv INT, -- RID for origin file, or NULL if previously edited\n" |
| 377 | " sz INT, -- Size of origin file in bytes, NULL if unedited\n" |
| 378 | " fnm TEXT, -- Name of the file being merged in (M)\n" |
| 379 | " ridm INT, -- RID for the merge-in file\n" |
| 380 | " fnr TEXT, -- Name of the final output file, after all renaming\n" |
| 381 | " nc INT DEFAULT 0, -- Number of conflicts\n" |
| 382 | " msg TEXT -- Error message\n" |
| 383 | ");" |
| 384 | ); |
| 385 | } |
| 386 | |
| 387 | /* |
| 388 | ** Print information about a particular check-in. |
| 389 | */ |
| 390 | void print_checkin_description(int rid, int indent, const char *zLabel){ |
| 391 | Stmt q; |
| @@ -295,10 +657,13 @@ | |
| 657 | ** Files which are renamed in the merged-in branch will be renamed in |
| 658 | ** the current check-out. |
| 659 | ** |
| 660 | ** If the VERSION argument is omitted, then Fossil attempts to find |
| 661 | ** a recent fork on the current branch to merge. |
| 662 | ** |
| 663 | ** Note that this command does not commit the merge, as that is a |
| 664 | ** separate step. |
| 665 | ** |
| 666 | ** If there are multiple VERSION arguments, then each VERSION is merged |
| 667 | ** (or cherrypicked) in the order that they appear on the command-line. |
| 668 | ** |
| 669 | ** Options: |
| @@ -320,12 +685,13 @@ | |
| 685 | ** --force-missing Force the merge even if there is missing content |
| 686 | ** --integrate Merged branch will be closed when committing |
| 687 | ** -K|--keep-merge-files On merge conflict, retain the temporary files |
| 688 | ** used for merging, named *-baseline, *-original, |
| 689 | ** and *-merge. |
| 690 | ** -n|--dry-run Do not actually change files on disk |
| 691 | ** --nosync Do not auto-sync prior to merging |
| 692 | ** --noundo Do not record changes in the undo log |
| 693 | ** -v|--verbose Show additional details of the merge |
| 694 | */ |
| 695 | void merge_cmd(void){ |
| 696 | int vid; /* Current version "V" */ |
| 697 | int mid; /* Version we are merging from "M" */ |
| @@ -347,10 +713,11 @@ | |
| 713 | int nOverwrite = 0; /* Number of unmanaged files overwritten */ |
| 714 | char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ |
| 715 | const char *zVersion; /* The VERSION argument */ |
| 716 | int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ |
| 717 | int nMerge = 0; /* Number of prior merges processed */ |
| 718 | int useUndo = 1; /* True to record changes in the undo log */ |
| 719 | Stmt q; /* SQL statment used for merge processing */ |
| 720 | |
| 721 | |
| 722 | /* Notation: |
| 723 | ** |
| @@ -395,10 +762,12 @@ | |
| 762 | ** * The --dry-run option is also useful in combination with --debug. |
| 763 | */ |
| 764 | debugFlag = find_option("debug",0,0)!=0; |
| 765 | if( debugFlag && verboseFlag ) debugFlag = 2; |
| 766 | showVfileFlag = find_option("show-vfile",0,0)!=0; |
| 767 | useUndo = find_option("noundo",0,0)==0; |
| 768 | if( dryRunFlag ) useUndo = 0; |
| 769 | |
| 770 | verify_all_options(); |
| 771 | db_must_be_within_tree(); |
| 772 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 773 | vid = db_lget_int("checkout", 0); |
| @@ -561,11 +930,11 @@ | |
| 930 | integrateFlag ? "integrate:" : "merge-from:"); |
| 931 | print_checkin_description(pid, 12, "baseline:"); |
| 932 | } |
| 933 | vfile_check_signature(vid, CKSIG_ENOTFILE); |
| 934 | if( nMerge==0 ) db_begin_transaction(); |
| 935 | if( useUndo ) undo_begin(); |
| 936 | if( load_vfile_from_rid(mid) && !forceMissingFlag ){ |
| 937 | fossil_fatal("missing content, unable to merge"); |
| 938 | } |
| 939 | if( load_vfile_from_rid(pid) && !forceMissingFlag ){ |
| 940 | fossil_fatal("missing content, unable to merge"); |
| @@ -797,15 +1166,21 @@ | |
| 1166 | |
| 1167 | /************************************************************************ |
| 1168 | ** All of the information needed to do the merge is now contained in the |
| 1169 | ** FV table. Starting here, we begin to actually carry out the merge. |
| 1170 | ** |
| 1171 | ** Begin by constructing the localdb.mergestat table. |
| 1172 | */ |
| 1173 | merge_info_init(); |
| 1174 | |
| 1175 | /* |
| 1176 | ** Find files that have changed from P->M but not P->V. |
| 1177 | ** Copy the M content over into V. |
| 1178 | */ |
| 1179 | db_prepare(&q, |
| 1180 | /* 0 1 2 3 4 5 6 7 */ |
| 1181 | "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv" |
| 1182 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 1183 | " AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
| 1184 | ); |
| 1185 | while( db_step(&q)==SQLITE_ROW ){ |
| 1186 | int idv = db_column_int(&q, 0); |
| @@ -812,20 +1187,31 @@ | |
| 1187 | int ridm = db_column_int(&q, 1); |
| 1188 | const char *zName = db_column_text(&q, 2); |
| 1189 | int islinkm = db_column_int(&q, 3); |
| 1190 | /* Copy content from idm over into idv. Overwrite idv. */ |
| 1191 | fossil_print("UPDATE %s\n", zName); |
| 1192 | if( useUndo ) undo_save(zName); |
| 1193 | if( !dryRunFlag ){ |
| 1194 | db_multi_exec( |
| 1195 | "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," |
| 1196 | " mhash=CASE WHEN rid<>%d" |
| 1197 | " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
| 1198 | " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
| 1199 | ); |
| 1200 | vfile_to_disk(0, idv, 0, 0); |
| 1201 | } |
| 1202 | db_multi_exec( |
| 1203 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)" |
| 1204 | "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)", |
| 1205 | /* fnp */ db_column_text(&q, 4), |
| 1206 | /* ridp */ db_column_int(&q,5), |
| 1207 | /* fn */ zName, |
| 1208 | /* ridv */ db_column_int(&q,6), |
| 1209 | /* fnm */ db_column_text(&q, 7), |
| 1210 | /* ridm */ ridm, |
| 1211 | /* fnr */ zName |
| 1212 | ); |
| 1213 | } |
| 1214 | db_finalize(&q); |
| 1215 | |
| 1216 | /* |
| 1217 | ** Do a three-way merge on files that have changes on both P->M and P->V. |
| @@ -833,11 +1219,15 @@ | |
| 1219 | ** Proceed even if the file doesn't exist on P, just like the common ancestor |
| 1220 | ** of M and V is an empty file. In this case, merge conflict marks will be |
| 1221 | ** added to the file and user will be forced to take a decision. |
| 1222 | */ |
| 1223 | db_prepare(&q, |
| 1224 | /* 0 1 2 3 4 5 6 7 8 */ |
| 1225 | "SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm," |
| 1226 | /* 9 10 11 */ |
| 1227 | " fnp, fnm, chnged" |
| 1228 | " FROM fv" |
| 1229 | " WHERE idv>0 AND idm>0" |
| 1230 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 1231 | glob_expr("fv.fn", zBinGlob) |
| 1232 | ); |
| 1233 | while( db_step(&q)==SQLITE_ROW ){ |
| @@ -848,12 +1238,14 @@ | |
| 1238 | int isBinary = db_column_int(&q, 4); |
| 1239 | const char *zName = db_column_text(&q, 5); |
| 1240 | int isExe = db_column_int(&q, 6); |
| 1241 | int islinkv = db_column_int(&q, 7); |
| 1242 | int islinkm = db_column_int(&q, 8); |
| 1243 | int chnged = db_column_int(&q, 11); |
| 1244 | int rc; |
| 1245 | char *zFullPath; |
| 1246 | const char *zType = "MERGE"; |
| 1247 | Blob m, p, r; |
| 1248 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| 1249 | if( verboseFlag ){ |
| 1250 | fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", |
| 1251 | zName, ridp, ridm, ridv); |
| @@ -861,13 +1253,29 @@ | |
| 1253 | fossil_print("MERGE %s\n", zName); |
| 1254 | } |
| 1255 | if( islinkv || islinkm ){ |
| 1256 | fossil_print("***** Cannot merge symlink %s\n", zName); |
| 1257 | nConflict++; |
| 1258 | db_multi_exec( |
| 1259 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)" |
| 1260 | "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')", |
| 1261 | /* fnp */ db_column_text(&q, 9), |
| 1262 | /* ridp */ ridp, |
| 1263 | /* fn */ zName, |
| 1264 | /* ridv */ ridv, |
| 1265 | /* fnm */ db_column_text(&q, 10), |
| 1266 | /* ridm */ ridm, |
| 1267 | /* fnr */ zName |
| 1268 | ); |
| 1269 | }else{ |
| 1270 | i64 sz; |
| 1271 | const char *zErrMsg = 0; |
| 1272 | int nc = 0; |
| 1273 | |
| 1274 | if( useUndo ) undo_save(zName); |
| 1275 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1276 | sz = file_size(zFullPath, ExtFILE); |
| 1277 | content_get(ridp, &p); |
| 1278 | content_get(ridm, &m); |
| 1279 | if( isBinary ){ |
| 1280 | rc = -1; |
| 1281 | blob_zero(&r); |
| @@ -884,15 +1292,38 @@ | |
| 1292 | db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
| 1293 | if( rc>0 ){ |
| 1294 | fossil_print("***** %d merge conflict%s in %s\n", |
| 1295 | rc, rc>1 ? "s" : "", zName); |
| 1296 | nConflict++; |
| 1297 | nc = rc; |
| 1298 | zErrMsg = "merge conflicts"; |
| 1299 | zType = "CONFLICT"; |
| 1300 | } |
| 1301 | }else{ |
| 1302 | fossil_print("***** Cannot merge binary file %s\n", zName); |
| 1303 | nConflict++; |
| 1304 | nc = 1; |
| 1305 | zErrMsg = "cannot merge binary file"; |
| 1306 | zType = "ERROR"; |
| 1307 | } |
| 1308 | db_multi_exec( |
| 1309 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" |
| 1310 | "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d," |
| 1311 | "%Q,%d,%Q)", |
| 1312 | /* op */ zType, |
| 1313 | /* fnp */ db_column_text(&q, 9), |
| 1314 | /* ridp */ ridp, |
| 1315 | /* fn */ zName, |
| 1316 | /* ridv */ chnged==0, ridv, |
| 1317 | /* sz */ chnged!=0, sz, |
| 1318 | /* fnm */ db_column_text(&q, 10), |
| 1319 | /* ridm */ ridm, |
| 1320 | /* fnr */ zName, |
| 1321 | /* nc */ nc, |
| 1322 | /* msg */ zErrMsg |
| 1323 | ); |
| 1324 | fossil_free(zFullPath); |
| 1325 | blob_reset(&p); |
| 1326 | blob_reset(&m); |
| 1327 | blob_reset(&r); |
| 1328 | } |
| 1329 | vmerge_insert(idv, ridm); |
| @@ -901,32 +1332,53 @@ | |
| 1332 | |
| 1333 | /* |
| 1334 | ** Drop files that are in P and V but not in M |
| 1335 | */ |
| 1336 | db_prepare(&q, |
| 1337 | "SELECT idv, fn, chnged, ridv FROM fv" |
| 1338 | " WHERE idp>0 AND idv>0 AND idm=0" |
| 1339 | ); |
| 1340 | while( db_step(&q)==SQLITE_ROW ){ |
| 1341 | int idv = db_column_int(&q, 0); |
| 1342 | const char *zName = db_column_text(&q, 1); |
| 1343 | int chnged = db_column_int(&q, 2); |
| 1344 | int ridv = db_column_int(&q, 3); |
| 1345 | int sz = -1; |
| 1346 | const char *zErrMsg = 0; |
| 1347 | int nc = 0; |
| 1348 | /* Delete the file idv */ |
| 1349 | fossil_print("DELETE %s\n", zName); |
| 1350 | if( chnged ){ |
| 1351 | char *zFullPath; |
| 1352 | fossil_warning("WARNING: local edits lost for %s", zName); |
| 1353 | nConflict++; |
| 1354 | ridv = 0; |
| 1355 | nc = 1; |
| 1356 | zErrMsg = "local edits lost"; |
| 1357 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1358 | sz = file_size(zFullPath, ExtFILE); |
| 1359 | fossil_free(zFullPath); |
| 1360 | } |
| 1361 | if( useUndo ) undo_save(zName); |
| 1362 | db_multi_exec( |
| 1363 | "UPDATE vfile SET deleted=1 WHERE id=%d", idv |
| 1364 | ); |
| 1365 | if( !dryRunFlag ){ |
| 1366 | char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 1367 | file_delete(zFullPath); |
| 1368 | free(zFullPath); |
| 1369 | } |
| 1370 | db_multi_exec( |
| 1371 | "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)" |
| 1372 | "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL)," |
| 1373 | "NULL,NULL,%d,%Q)", |
| 1374 | /* fn */ zName, |
| 1375 | /* ridv */ chnged==0, ridv, |
| 1376 | /* sz */ chnged!=0, sz, |
| 1377 | /* nc */ nc, |
| 1378 | /* msg */ zErrMsg |
| 1379 | ); |
| 1380 | } |
| 1381 | db_finalize(&q); |
| 1382 | |
| 1383 | /* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
| 1384 | ** being renamed must first be moved to a temporary location to avoid |
| @@ -951,12 +1403,16 @@ | |
| 1403 | int idv = db_column_int(&q, 0); |
| 1404 | const char *zOldName = db_column_text(&q, 1); |
| 1405 | const char *zNewName = db_column_text(&q, 2); |
| 1406 | int isExe = db_column_int(&q, 3); |
| 1407 | fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
| 1408 | if( useUndo ) undo_save(zOldName); |
| 1409 | if( useUndo ) undo_save(zNewName); |
| 1410 | db_multi_exec( |
| 1411 | "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", |
| 1412 | zOldName |
| 1413 | ); |
| 1414 | db_multi_exec( |
| 1415 | "UPDATE vfile SET pathname=NULL, origname=pathname" |
| 1416 | " WHERE vid=%d AND pathname=%Q;" |
| 1417 | "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
| 1418 | " WHERE id=%d;", |
| @@ -1006,11 +1462,11 @@ | |
| 1462 | |
| 1463 | /* |
| 1464 | ** Insert into V any files that are not in V or P but are in M. |
| 1465 | */ |
| 1466 | db_prepare(&q, |
| 1467 | "SELECT idm, fnm, ridm FROM fv" |
| 1468 | " WHERE idp=0 AND idv=0 AND idm>0" |
| 1469 | ); |
| 1470 | while( db_step(&q)==SQLITE_ROW ){ |
| 1471 | int idm = db_column_int(&q, 0); |
| 1472 | const char *zName; |
| @@ -1039,12 +1495,19 @@ | |
| 1495 | nOverwrite++; |
| 1496 | }else{ |
| 1497 | fossil_print("ADDED %s\n", zName); |
| 1498 | } |
| 1499 | fossil_free(zFullName); |
| 1500 | db_multi_exec( |
| 1501 | "INSERT INTO mergestat(op,fnm,ridm,fnr)" |
| 1502 | "VALUES('ADDED',%Q,%d,%Q)", |
| 1503 | /* fnm */ zName, |
| 1504 | /* ridm */ db_column_int(&q,2), |
| 1505 | /* fnr */ zName |
| 1506 | ); |
| 1507 | if( useUndo ) undo_save(zName); |
| 1508 | if( !dryRunFlag ){ |
| 1509 | vfile_to_disk(0, idm, 0, 0); |
| 1510 | } |
| 1511 | } |
| 1512 | db_finalize(&q); |
| 1513 | |
| @@ -1099,9 +1562,9 @@ | |
| 1562 | } |
| 1563 | if( bMultiMerge && nConflict==0 ){ |
| 1564 | nMerge++; |
| 1565 | goto merge_next_child; |
| 1566 | } |
| 1567 | if( useUndo ) undo_finish(); |
| 1568 | |
| 1569 | db_end_transaction(dryRunFlag); |
| 1570 | } |
| 1571 | |
| 1572 | DDED src/merge.tcl |
+581
| --- a/src/merge.tcl | ||
| +++ b/src/merge.tcl | ||
| @@ -0,0 +1,581 @@ | ||
| 1 | +# Show details of a 3-way merge operation. The left-most column is the | |
| 2 | +# common ancestor. The next two columns are edits of that common ancestor. | |
| 3 | +# The right-most column is the result of the merge. | |
| 4 | +# | |
| 5 | +# There is always a "fossilcmd" variable which tells the script how to | |
| 6 | +# invoke Fossil to get the information it needs. This script will | |
| 7 | +# automatically append "-c N" to tell Fossil how much context it wants. True for debugging output | |
| 8 | +# | |
| 9 | +# If the "filelist" global variable is defined, then it is a list of | |
| 10 | +# alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and | |
| 11 | +# filenames. In that case, the initial display shows the changes for | |
| 12 | +# the first pair on the list and there is a optionmenu that allows the | |
| 13 | +# useelect otheere should also be a global variable named "ncontext" which is the | |
| 14 | +# number of lines of context to display. The value of this variable | |
| 15 | +# controls the "-c N" argument that is appended to fossilcmdht { | |
| 16 | + TITLE {Fossil Merge} | |
| 17 | + LN_COL_BG #dddddd | |
| 18 | + LN_COL_FG #444444 | |
| 19 | + TXT_COL_BG #ffffff | |
| 20 | + TXT_COL_FG #000000 | |
| 21 | + MKR_COL_BG #444444 | |
| 22 | + MKR_COL_FG #dddddd | |
| 23 | + CHNG_BG #d0d070 | |
| 24 | + ADD_BG #c0ffc0 | |
| 25 | + RM_BG #ffc0c0 | |
| 26 | + HR_FG #444444 | |
| 27 | + HR_PAD_TOP 4 | |
| 28 | + HR_PAD_BTM 8 | |
| 29 | + FN_BG #444444 | |
| 30 | + FN_FG #ffffff | |
| 31 | + FN_PAD 5 | |
| 32 | + ERR_FG #ee0000 | |
| 33 | + PADX 5 | |
| 34 | + WIDTH 80 | |
| 35 | + HEIGHT 45 | |
| 36 | + LB_HEIGHT 25 | |
| 37 | +} | |
| 38 | + | |
| 39 | +array set CFG_dark { | |
| 40 | + TITLE {Fossil Merge} | |
| 41 | + LN_COL_BG #dddddd | |
| 42 | + LN_COL_FG #444444 | |
| 43 | + TXT_COL_BG #3f3f3f | |
| 44 | + TXT_COL_FG #dcdccc | |
| 45 | + MKR_COL_BG #444444 | |
| 46 | + MKR_COL_FG #dddddd | |
| 47 | + CHNG_BG #6a6a00 | |
| 48 | + ADD_BG #57934c | |
| 49 | + RM_BG #ef6767 | |
| 50 | + HR_FG #444444 | |
| 51 | + HR_PAD_TOP 4 | |
| 52 | + HR_PAD_BTM 8 | |
| 53 | + FN_BG #5e5e5e | |
| 54 | + FN_FG #ffffff | |
| 55 | + FN_PAD 5 | |
| 56 | + ERR_FG #ee0000 | |
| 57 | + PADX 5 | |
| 58 | + WIDTH 80 | |
| 59 | + HEIGHT 45 | |
| 60 | + LB_HEIGHT 25 | |
| 61 | +} | |
| 62 | + | |
| 63 | +array set CFG_arr { | |
| 64 | + 0 CFG_light | |
| 65 | + 1 CFG_dark | |
| 66 | +} | |
| 67 | + | |
| 68 | +array set CFG [array get $CFG_arr($darkmode)] | |
| 69 | + | |
| 70 | +if {![namespace exists ttk]} { | |
| 71 | + interp alias {} ::ttk::scrollbar {} ::scrollbar | |
| 72 | + interp alias {} ::ttk::menubutton {} ::menubutton | |
| 73 | +} | |
| 74 | + | |
| 75 | +proc dehtml {x} { | |
| 76 | + set x [regsub -all {<[^>]*>} $x {}] | |
| 77 | + return [string map {& & < < > > ' ' " \"} $x] | |
| 78 | +} | |
| 79 | + | |
| 80 | +proc cols {} { | |
| 81 | + return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD] | |
| 82 | +} | |
| 83 | + | |
| 84 | +proc colType {c} { | |
| 85 | + regexp {[a-z]+} $c type | |
| 86 | + return $type | |
| 87 | +} | |
| 88 | + | |
| 89 | +proc readMercmdes currentails of a 3-way merge operation# Show det for | |
| 90 | +# the first pair on the list and there is a optionmenu that allows the | |
| 91 | +# user to select other fiels on the list. | |
| 92 | +# | |
| 93 | +# This header comment is stripped off by the "mkbuiltin.c" program. | |
| 94 | +# | |
| 95 | +package require Tk | |
| 96 | + | |
| 97 | +array set CFG_light { | |
| 98 | + TITLE {Fossil Merge} | |
| 99 | + LN_COL_BG #dddddd | |
| 100 | + LN_COL_FG #444444 | |
| 101 | + TXT_COL_BG #ffffff | |
| 102 | + TXT_COL_FG #000000 | |
| 103 | + MKR_COL_BG #444444 | |
| 104 | + MKR_COL_FG #dddddd | |
| 105 | + CHNG_BG #d0d070 | |
| 106 | + ADD_BG #c0ffc0 | |
| 107 | + RM_BG #ffc0c0 | |
| 108 | + HR_FG #444444 | |
| 109 | + HR_PAD_TOP 4 | |
| 110 | + HR_PAD_BTM 8 | |
| 111 | + FN_BG #444444 | |
| 112 | + FN_FG #ffffff | |
| 113 | + FN_PAD 5 | |
| 114 | + ERR_FG #ee0000 | |
| 115 | + PADX 5 | |
| 116 | + WIDTH 80 | |
| 117 | + HEIGHT 45 | |
| 118 | + LB_HEIGHT 25 | |
| 119 | +} | |
| 120 | + | |
| 121 | +array set CFG_dark { | |
| 122 | + TITLE {Fossil Merge} | |
| 123 | + LN_COL_BG #dddddd | |
| 124 | + LN_COL_FG #444444 | |
| 125 | + TXT_COL_BG #3f3f3f | |
| 126 | + TXT_COL_FG #dcdccc | |
| 127 | + MKR_COL_BG #444444 | |
| 128 | + MKR_COL_FG #dddddd | |
| 129 | + CHNG_BG #6a6a00 | |
| 130 | + ADD_BG #57934c | |
| 131 | + RM_BG #ef6767 | |
| 132 | + HR_FG #444444 | |
| 133 | + HR_PAD_TOP 4 | |
| 134 | + HR_PAD_BTM 8 | |
| 135 | + FN_BG #5e5e5e | |
| 136 | + FN_FG #ffffff | |
| 137 | + FN_PAD 5 | |
| 138 | + ERR_FG #ee0000 | |
| 139 | + PADX 5 | |
| 140 | + WIDTH 80 | |
| 141 | + HEIGHT 45 | |
| 142 | + LB_HEIGHT 25 | |
| 143 | +} | |
| 144 | + | |
| 145 | +array set CFG_arr { | |
| 146 | + 0 CFG_light | |
| 147 | + 1 CFG_dark | |
| 148 | +} | |
| 149 | + | |
| 150 | +array set CFG [array get $CFG_arr($darkmode)] | |
| 151 | + | |
| 152 | +if {![namespace exists ttk]} { | |
| 153 | + interp alias {} ::ttk::scrollbar {} ::scrollbar | |
| 154 | + interp alias {} ::ttk::menubutton {} ::menubutton | |
| 155 | +} | |
| 156 | + | |
| 157 | +proc dehtml {x} { | |
| 158 | + set x [regsub -all {<[^>]*>} $x {}] | |
| 159 | + return [string map {& & < < > > ' ' " \"} $x] | |
| 160 | +} | |
| 161 | + | |
| 162 | +proc cols {} { | |
| 163 | + return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD] | |
| 164 | +} | |
| 165 | + | |
| 166 | +proc colType {c} { | |
| 167 | + regexp {[a-z]+} $c type | |
| 168 | + return $type | |
| 169 | +} | |
| 170 | + | |
| 171 | +proc readMerge {args} { | |
| 172 | + global fossilexe ncontext current_file debug | |
| 173 | + if {$ncontext=="All"} { | |
| 174 | + set cmd "| $fossilexe merge-info -c -1" | |
| 175 | + } else { | |
| 176 | + set cmd "| $fossilexe merge-info -c $ncontext" | |
| 177 | + } | |
| 178 | + if {[info exists current_file]} { | |
| 179 | + regsub {^[A-Z]+ } $current_file {} fn | |
| 180 | + lappend cmd -tcl $fn | |
| 181 | + } | |
| 182 | + if {$debug} { | |
| 183 | + regsub {^\| +} $cmd {} cmd2 | |
| 184 | + puts $cmd2 | |
| 185 | + flush stdout | |
| 186 | + } | |
| 187 | + if {[catch { | |
| 188 | + set in [open $cmd r] | |
| 189 | + fconfigure $in -encoding utf-8 | |
| 190 | + set mergetxt [read $in] | |
| 191 | + close $in | |
| 192 | + } msg]} { | |
| 193 | + tk_messageBox -message "Unable to run command: \"$cmd\"" | |
| 194 | + set mergetxt {} | |
| 195 | + } | |
| 196 | + foreach c [cols] { | |
| 197 | + $c config -state normal | |
| 198 | + $c delete 1.0 end | |
| 199 | + } | |
| 200 | + set lnA 1 | |
| 201 | + set lnB 1 | |
| 202 | + set lnC 1 | |
| 203 | + set lnD 1 | |
| 204 | + foreach {A B C D} $mergetxt { | |
| 205 | + set key1 [string index $A 0] | |
| 206 | + if {$key1=="S"} { | |
| 207 | + scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD | |
| 208 | + foreach x {A B C D} { | |
| 209 | + set N [set n$x] | |
| 210 | + incr ln$x $N | |
| 211 | + if {$N>0} { | |
| 212 | + .ln$x insert end ...\n hrln | |
| 213 | + .txt$x insert end [string repeat . 30]\n hrtxt | |
| 214 | + } else { | |
| 215 | + .ln$x insert end \n hrln | |
| 216 | + .txt$x insert end \n hrtxt | |
| 217 | + } | |
| 218 | + } | |
| 219 | + continue | |
| 220 | + } | |
| 221 | + set key2 [string index $B 0] | |
| 222 | + set key3 [string index $C 0] | |
| 223 | + set key4 [string index $D 0] | |
| 224 | + if {$key1=="."} { | |
| 225 | + .lnA insert end \n - | |
| 226 | + .txtA insert end \n - | |
| 227 | + } elseif {$key1=="N"} { | |
| 228 | + .nameA config -text [string range $A 1 end] | |
| 229 | + } else { | |
| 230 | + .lnA insert end $lnA\n - | |
| 231 | + incr lnA | |
| 232 | + if {$key1=="X"} { | |
| 233 | + .txtA insert end [string range $A 1 end]\n rm | |
| 234 | + } else { | |
| 235 | + .txtA insert end [string range $A 1 end]\n - | |
| 236 | + } | |
| 237 | + } | |
| 238 | + if {$key2=="."} { | |
| 239 | + .lnB insert end \n - | |
| 240 | + .txtB insert end \n - | |
| 241 | + } elseif {$key2=="N"} { | |
| 242 | + .nameB config -text [string range $B 1 end] | |
| 243 | + } else { | |
| 244 | + .lnB insert end $lnB\n - | |
| 245 | + incr lnB | |
| 246 | + if {$key4=="2"} {set tag chng} {set tag -} | |
| 247 | + if {$key2=="1"} { | |
| 248 | + .txtB insert end [string range $A 1 end]\n $tag | |
| 249 | + } elseif {$key2=="X"} { | |
| 250 | + .txtB insert end [string range $B 1 end]\n rm | |
| 251 | + } else { | |
| 252 | + .txtB insert end [string range $B 1 end]\n $tag | |
| 253 | + } | |
| 254 | + } | |
| 255 | + if {$key3=="."} { | |
| 256 | + .lnC insert end \n - | |
| 257 | + .txtC insert end \n - | |
| 258 | + } elseif {$key3=="N"} { | |
| 259 | + .nameC config -text [string range $C 1 end] | |
| 260 | + } else { | |
| 261 | + .lnC insert end $lnC\n - | |
| 262 | + incr lnC | |
| 263 | + if {$key4=="3"} {set tag add} {set tag -} | |
| 264 | + if {$key3=="1"} { | |
| 265 | + .txtC insert end [string range $A 1 end]\n $tag | |
| 266 | + } elseif {$key3=="2"} { | |
| 267 | + .txtC insert end [string range $B 1 end]\n chng | |
| 268 | + } elseif {$key3=="X"} { | |
| 269 | + .txtC insert end [string range $C 1 end]\n rm | |
| 270 | + } else { | |
| 271 | + .txtC insert end [string range $C 1 end]\n $tag | |
| 272 | + } | |
| 273 | + } | |
| 274 | + if {$key4=="."} { | |
| 275 | + .lnD insert end \n - | |
| 276 | + .txtD insert end \n - | |
| 277 | + } elseif {$key4=="N"} { | |
| 278 | + .nameD config -text [string range $D 1 end] | |
| 279 | + } else { | |
| 280 | + .lnD insert end $lnD\n - | |
| 281 | + incr lnD | |
| 282 | + if {$key4=="1"} { | |
| 283 | + .txtD insert end [string range $A 1 end]\n - | |
| 284 | + } elseif {$key4=="2"} { | |
| 285 | + .txtD insert end [string range $B 1 end]\n chng | |
| 286 | + } elseif {$key4=="3"} { | |
| 287 | + .txtD insert end [string range $C 1 end]\n add | |
| 288 | + } elseif {$key4=="X"} { | |
| 289 | + .txtD insert end [string range $D 1 end]\n rm | |
| 290 | + } else { | |
| 291 | + .txtD insert end [string range $D 1 end]\n - | |
| 292 | + } | |
| 293 | + } | |
| 294 | + } | |
| 295 | + foreach c [cols] { | |
| 296 | + set type [colType $c] | |
| 297 | + if {$type ne "txt"} { | |
| 298 | + $c config -width 6; # $widths($type) | |
| 299 | + } | |
| 300 | + $c config -state disabled | |
| 301 | + } | |
| 302 | + set mx $lnA | |
| 303 | + if {$lnB>$mx} {set mx $lnB} | |
| 304 | + if {$lnC>$mx} {set mx $lnC} | |
| 305 | + if {$lnD>$mx} {set mx $lnD} | |
| 306 | + global lnWidth | |
| 307 | + set lnWidth [string length [format +%d $mx]] | |
| 308 | + .lnA config -width $lnWidth | |
| 309 | + .lnB config -width $lnWidth | |
| 310 | + .lnC config -width $lnWidth | |
| 311 | + .lnD config -width $lnWidth | |
| 312 | + grid columnconfig . {0 2 4 6} -minsize $lnWidth | |
| 313 | +} | |
| 314 | + | |
| 315 | +proc viewDiff {idx} { | |
| 316 | + .txtA yview $idx | |
| 317 | + .txtA xview moveto 0 | |
| 318 | +} | |
| 319 | + | |
| 320 | +proc cycleDiffs {{reverse 0}} { | |
| 321 | + if {$reverse} { | |
| 322 | + set range [.txtA tag prevrange fn @0,0 1.0] | |
| 323 | + if {$range eq ""} { | |
| 324 | + viewDiff {fn.last -1c} | |
| 325 | + } else { | |
| 326 | + viewDiff [lindex $range 0] | |
| 327 | + } | |
| 328 | + } else { | |
| 329 | + set range [.txtA tag nextrange fn {@0,0 +1c} end] | |
| 330 | + if {$range eq "" || [lindex [.txtA yview] 1] == 1} { | |
| 331 | + viewDiff fn.first | |
| 332 | + } else { | |
| 333 | + viewDiff [lindex $range 0] | |
| 334 | + } | |
| 335 | + } | |
| 336 | +} | |
| 337 | + | |
| 338 | +proc xvis {col} { | |
| 339 | + set view [$col xview] | |
| 340 | + return [expr {[lindex $view 1]-[lindex $view 0]}] | |
| 341 | +} | |
| 342 | + | |
| 343 | +proc scroll-x {args} { | |
| 344 | + set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}] | |
| 345 | + eval $c xview $args | |
| 346 | +} | |
| 347 | + | |
| 348 | +interp alias {} scroll-y {} .txtA yview | |
| 349 | + | |
| 350 | +proc noop {args} {} | |
| 351 | + | |
| 352 | +proc enableSync {axis} { | |
| 353 | + update idletasks | |
| 354 | + interp alias {} sync-$axis {} | |
| 355 | + rename _sync-$axis sync-$axis | |
| 356 | +} | |
| 357 | + | |
| 358 | +proc disableSync {axis} { | |
| 359 | + rename sync-$axis _sync-$axis | |
| 360 | + interp alias {} sync-$axis {} noop | |
| 361 | +} | |
| 362 | + | |
| 363 | +proc sync-y {first last} { | |
| 364 | + disableSync y | |
| 365 | + foreach c [cols] { | |
| 366 | + $c yview moveto $first | |
| 367 | + } | |
| 368 | + if {$first > 0 || $last < 1} { | |
| 369 | + grid .sby | |
| 370 | + .sby set $first $last | |
| 371 | + } else { | |
| 372 | + grid remove .sby | |
| 373 | + } | |
| 374 | + enableSync y | |
| 375 | +} | |
| 376 | + | |
| 377 | +wm withdraw . | |
| 378 | +wm title . $CFG(TITLE) | |
| 379 | +wm iconname . $CFG(TITLE) | |
| 380 | +# Keystroke bindings for on the top-level window for navigation and | |
| 381 | +# control also fire when those same keystrokes are pressed in the | |
| 382 | +# Search entry box. Disable them, to prevent the diff screen from | |
| 383 | +# disappearing abruptly and unexpectedly when searching for "q". | |
| 384 | +# | |
| 385 | +bind . <Control-q> exit | |
| 386 | +bind . <Control-p> {catch searchPrev; break} | |
| 387 | +bind . <Control-n> {catch searchNext; break} | |
| 388 | +bind . <Escape><Escape> exit | |
| 389 | +bind . <Destroy> {after 0 exit} | |
| 390 | +bind . <Tab> {cycleDiffs; break} | |
| 391 | +bind . <<PrevWindow>> {cycleDiffs 1; break} | |
| 392 | +bind . <Control-f> {searchOnOff; break} | |
| 393 | +bind . <Control-g> {catch searchNext; break} | |
| 394 | +bind . <Return> { | |
| 395 | + event generate .bb.files <1> | |
| 396 | + event generate .bb.files <ButtonRelease-1> | |
| 397 | + break | |
| 398 | +} | |
| 399 | +foreach {key axis args} { | |
| 400 | + Up y {scroll -5 units} | |
| 401 | + k y {scroll -5 units} | |
| 402 | + Down y {scroll 5 units} | |
| 403 | + j y {scroll 5 units} | |
| 404 | + Left x {scroll -5 units} | |
| 405 | + h x {scroll -5 units} | |
| 406 | + Right x {scroll 5 units} | |
| 407 | + l x {scroll 5 units} | |
| 408 | + Prior y {scroll -1 page} | |
| 409 | + b y {scroll -1 page} | |
| 410 | + Next y {scroll 1 page} | |
| 411 | + space y {scroll 1 page} | |
| 412 | + Home y {moveto 0} | |
| 413 | + g y {moveto 0} | |
| 414 | + End y {moveto 1} | |
| 415 | +} { | |
| 416 | + bind . <$key> "scroll-$axis $args; break" | |
| 417 | + bind . <Shift-$key> continue | |
| 418 | +} | |
| 419 | + | |
| 420 | +frame .bb | |
| 421 | +::ttk::menubutton .bb.diff2 -text {2-way diffs | |
| 422 | +.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12} | |
| 423 | +.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13} | |
| 424 | +.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23} | |
| 425 | + | |
| 426 | +# Bring up a separate two-way diff between a pair of columns | |
| 427 | +# the argument is one of: | |
| 428 | +# 12 Baseline versus Local | |
| 429 | +# 13 Baseline versus Merge-in | |
| 430 | +# 23 Local versus Merge-in | |
| 431 | +# | |
| 432 | +proc two-way {mode} { | |
| 433 | + global current_file fossilexe debug darkmode ncontext | |
| 434 | + regsub {^[A-Z]+ } $current_file {} fn | |
| 435 | + set cmd $fossilexe | |
| 436 | + lappend cmd merge-info --diff$mode $fn -c $ncontext | |
| 437 | + if {$darkmode} { | |
| 438 | + lappend cmd --dark | |
| 439 | + } | |
| 440 | + if {$debug} { | |
| 441 | + lappend cc {*}$cmd & | |
| 442 | +} | |
| 443 | + | |
| 444 | +set useOptionMenu 1 | |
| 445 | +if {[info exists filelist]} { | |
| 446 | + set current_file "[lindex $filelist 0] [lindex $filelist 1]" | |
| 447 | + if {[llength $filelist]>2} { | |
| 448 | + trace add variable current_file write readMerge | |
| 449 | + | |
| 450 | + if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} { | |
| 451 | + set fnlist {} | |
| 452 | + foreach {op fn} $filelist {lappend fnlist "$op $fn"} | |
| 453 | + tk_optionMenu .bb.files current_file {*}$fnlist | |
| 454 | + } else { | |
| 455 | + set useOptionMenu 0 | |
| 456 | + ::ttk::menubutton .bb.files -text $current_file | |
| 457 | + if {[tk windowingsystem] eq "win32"} { | |
| 458 | + ::ttk::style theme use winnative | |
| 459 | + .bb.files configure -padding {20 1 10 2} | |
| 460 | + } | |
| 461 | + toplevel .wfiles | |
| 462 | + wm withdraw .wfiles | |
| 463 | + update idletasks | |
| 464 | + wm transient .wfiles . | |
| 465 | + wm overrideredirect .wfiles 1 | |
| 466 | + set ht [expr {[llength $filelist]/2}] | |
| 467 | + if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)} | |
| 468 | + listbox .wfiles.lb -width 0 -height $ht -activestyle none \ | |
| 469 | + -yscroll {.wfiles.sb set} | |
| 470 | + set mx 1 | |
| 471 | + foreach {op fn} $filelist { | |
| 472 | + set n [string length $fn] | |
| 473 | + if {$n>$mx} {set mx $n} | |
| 474 | + .wfiles.lb insert end "$op $fn" | |
| 475 | + } | |
| 476 | + .bb.files config -width $mx | |
| 477 | + ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview} | |
| 478 | + grid .wfiles.lb .wfiles.sb -sticky ns | |
| 479 | + bind .bb.files <1> { | |
| 480 | + set x [winfo rootx %W] | |
| 481 | + set y [expr {[winfo rooty %W]+[winfo height %W]}] | |
| 482 | + wm geometry .wfiles +$x+$y | |
| 483 | + wm deiconify .wfiles | |
| 484 | + focus .wfiles.lb | |
| 485 | + } | |
| 486 | + bind .wfiles <FocusOut> {wm withdraw .wfiles} | |
| 487 | + bind .wfiles <Escape> {focus .} | |
| 488 | + foreach evt {1 Return} { | |
| 489 | + bind .wfiles.lb <$evt> { | |
| 490 | + set ii [%W curselection] | |
| 491 | + set ::current_file [%W get $ii] | |
| 492 | + .bb.files config -text $::current_file | |
| 493 | + focus . | |
| 494 | + break | |
| 495 | + } | |
| 496 | + } | |
| 497 | + bind .wfiles.lb <Motion> { | |
| 498 | + %W selection clear 0 end | |
| 499 | + %W selection set @%x,%y | |
| 500 | + } | |
| 501 | + } | |
| 502 | + } | |
| 503 | +} | |
| 504 | + | |
| 505 | +label .bb.ctxtag -text "Context:" | |
| 506 | +set context_choices {3 6 12 25 50 100 All} | |
| 507 | +if {$ncontext<0} {set ncontext All} | |
| 508 | +trace add variable ncontext write readMerge | |
| 509 | +if {$tcl_platform(os)=="Darwin" || $useOptionMenu} { | |
| 510 | + tk_optionMenu .bb.ctx ncontext {*}$context_choices | |
| 511 | +} else { | |
| 512 | + ::ttk::menubutton .bb.ctx -text $ncontext | |
| 513 | + if {[tk windowingsystem] eq "win32"} { | |
| 514 | + ::ttk::style theme use winnative | |
| 515 | + .bb.ctx configure -padding {20 1 10 2} | |
| 516 | + } | |
| 517 | + toplevel .wctx | |
| 518 | + wm withdraw .wctx | |
| 519 | + update idletasks | |
| 520 | + wm transient .wctx . | |
| 521 | + wm overrideredirect .wctx 1 | |
| 522 | + listbox .wctx.lb -width 0 -height 7 -activestyle none | |
| 523 | + .wctx.lb insert end {*}$context_choices | |
| 524 | + pack .wctx.lb | |
| 525 | + bind .bb.ctx <1> { | |
| 526 | + set x [winfo rootx %W] | |
| 527 | + set y [expr {[winfo rooty %W]+[winfo height %W]}] | |
| 528 | + wm geometry .wctx +$x+$y | |
| 529 | + wm deiconify .wctx | |
| 530 | + focus .wctx.lb | |
| 531 | + } | |
| 532 | + bind .wctx <FocusOut> {wm withdraw .wctx} | |
| 533 | + bind .wctx <Escape> {focus .} | |
| 534 | + foreach evt {1 Return} { | |
| 535 | + bind .wctx.lb <$evt> { | |
| 536 | + set ::ncontext [lindex $::context_choices [%W curselection]] | |
| 537 | + .bb.ctx config -text $::ncontext | |
| 538 | + focus . | |
| 539 | + break | |
| 540 | + } | |
| 541 | + } | |
| 542 | + bind .wctx.lb <Motion> { | |
| 543 | + %W selection clear 0 end | |
| 544 | + %W selection set @%x,%y | |
| 545 | + } | |
| 546 | +} | |
| 547 | + | |
| 548 | +foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} { | |
| 549 | + set ln .ln$side | |
| 550 | + text $ln -width 6 | |
| 551 | + $ln tag config - -justify right | |
| 552 | + | |
| 553 | + set txt .txt$side | |
| 554 | + text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \ | |
| 555 | + -xscroll ".sbx$side set" | |
| 556 | + catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5 | |
| 557 | + foreach tag {add rm chng} { | |
| 558 | + $txt tag config $tag -background $CFG([string toupper $tag]_BG) | |
| 559 | + $txt tag lower $tag | |
| 560 | + } | |
| 561 | + $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \ | |
| 562 | + -justify center | |
| 563 | + $txt tag config err -foreground $CFG(ERR_FG) | |
| 564 | +} | |
| 565 | +text .mkr | |
| 566 | + | |
| 567 | +set mxwidth [lindex [wm maxsize .] 0] | |
| 568 | +while {$CFG(WIDTH)>=40} { | |
| 569 | + set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}] | |
| 570 | + if {$wanted<=$mxwidth} break | |
| 571 | + incr CFG(WIDTH) -10 | |
| 572 | + .txtA config -width $CFG(WIDTH) | |
| 573 | + .txtB config -width $CFG(WIDTH) | |
| 574 | + .txtC config -width $CFG(WIDTH) | |
| 575 | + .txtD config -width $CFG(WIDTH) | |
| 576 | +} | |
| 577 | + | |
| 578 | +foreach c [cols] { | |
| 579 | + set keyPrefix [string toupper [colType $c]]_COL_ | |
| 580 | + if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}} | |
| 581 | + $c co |
| --- a/src/merge.tcl | |
| +++ b/src/merge.tcl | |
| @@ -0,0 +1,581 @@ | |
| --- a/src/merge.tcl | |
| +++ b/src/merge.tcl | |
| @@ -0,0 +1,581 @@ | |
| 1 | # Show details of a 3-way merge operation. The left-most column is the |
| 2 | # common ancestor. The next two columns are edits of that common ancestor. |
| 3 | # The right-most column is the result of the merge. |
| 4 | # |
| 5 | # There is always a "fossilcmd" variable which tells the script how to |
| 6 | # invoke Fossil to get the information it needs. This script will |
| 7 | # automatically append "-c N" to tell Fossil how much context it wants. True for debugging output |
| 8 | # |
| 9 | # If the "filelist" global variable is defined, then it is a list of |
| 10 | # alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and |
| 11 | # filenames. In that case, the initial display shows the changes for |
| 12 | # the first pair on the list and there is a optionmenu that allows the |
| 13 | # useelect otheere should also be a global variable named "ncontext" which is the |
| 14 | # number of lines of context to display. The value of this variable |
| 15 | # controls the "-c N" argument that is appended to fossilcmdht { |
| 16 | TITLE {Fossil Merge} |
| 17 | LN_COL_BG #dddddd |
| 18 | LN_COL_FG #444444 |
| 19 | TXT_COL_BG #ffffff |
| 20 | TXT_COL_FG #000000 |
| 21 | MKR_COL_BG #444444 |
| 22 | MKR_COL_FG #dddddd |
| 23 | CHNG_BG #d0d070 |
| 24 | ADD_BG #c0ffc0 |
| 25 | RM_BG #ffc0c0 |
| 26 | HR_FG #444444 |
| 27 | HR_PAD_TOP 4 |
| 28 | HR_PAD_BTM 8 |
| 29 | FN_BG #444444 |
| 30 | FN_FG #ffffff |
| 31 | FN_PAD 5 |
| 32 | ERR_FG #ee0000 |
| 33 | PADX 5 |
| 34 | WIDTH 80 |
| 35 | HEIGHT 45 |
| 36 | LB_HEIGHT 25 |
| 37 | } |
| 38 | |
| 39 | array set CFG_dark { |
| 40 | TITLE {Fossil Merge} |
| 41 | LN_COL_BG #dddddd |
| 42 | LN_COL_FG #444444 |
| 43 | TXT_COL_BG #3f3f3f |
| 44 | TXT_COL_FG #dcdccc |
| 45 | MKR_COL_BG #444444 |
| 46 | MKR_COL_FG #dddddd |
| 47 | CHNG_BG #6a6a00 |
| 48 | ADD_BG #57934c |
| 49 | RM_BG #ef6767 |
| 50 | HR_FG #444444 |
| 51 | HR_PAD_TOP 4 |
| 52 | HR_PAD_BTM 8 |
| 53 | FN_BG #5e5e5e |
| 54 | FN_FG #ffffff |
| 55 | FN_PAD 5 |
| 56 | ERR_FG #ee0000 |
| 57 | PADX 5 |
| 58 | WIDTH 80 |
| 59 | HEIGHT 45 |
| 60 | LB_HEIGHT 25 |
| 61 | } |
| 62 | |
| 63 | array set CFG_arr { |
| 64 | 0 CFG_light |
| 65 | 1 CFG_dark |
| 66 | } |
| 67 | |
| 68 | array set CFG [array get $CFG_arr($darkmode)] |
| 69 | |
| 70 | if {![namespace exists ttk]} { |
| 71 | interp alias {} ::ttk::scrollbar {} ::scrollbar |
| 72 | interp alias {} ::ttk::menubutton {} ::menubutton |
| 73 | } |
| 74 | |
| 75 | proc dehtml {x} { |
| 76 | set x [regsub -all {<[^>]*>} $x {}] |
| 77 | return [string map {& & < < > > ' ' " \"} $x] |
| 78 | } |
| 79 | |
| 80 | proc cols {} { |
| 81 | return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD] |
| 82 | } |
| 83 | |
| 84 | proc colType {c} { |
| 85 | regexp {[a-z]+} $c type |
| 86 | return $type |
| 87 | } |
| 88 | |
| 89 | proc readMercmdes currentails of a 3-way merge operation# Show det for |
| 90 | # the first pair on the list and there is a optionmenu that allows the |
| 91 | # user to select other fiels on the list. |
| 92 | # |
| 93 | # This header comment is stripped off by the "mkbuiltin.c" program. |
| 94 | # |
| 95 | package require Tk |
| 96 | |
| 97 | array set CFG_light { |
| 98 | TITLE {Fossil Merge} |
| 99 | LN_COL_BG #dddddd |
| 100 | LN_COL_FG #444444 |
| 101 | TXT_COL_BG #ffffff |
| 102 | TXT_COL_FG #000000 |
| 103 | MKR_COL_BG #444444 |
| 104 | MKR_COL_FG #dddddd |
| 105 | CHNG_BG #d0d070 |
| 106 | ADD_BG #c0ffc0 |
| 107 | RM_BG #ffc0c0 |
| 108 | HR_FG #444444 |
| 109 | HR_PAD_TOP 4 |
| 110 | HR_PAD_BTM 8 |
| 111 | FN_BG #444444 |
| 112 | FN_FG #ffffff |
| 113 | FN_PAD 5 |
| 114 | ERR_FG #ee0000 |
| 115 | PADX 5 |
| 116 | WIDTH 80 |
| 117 | HEIGHT 45 |
| 118 | LB_HEIGHT 25 |
| 119 | } |
| 120 | |
| 121 | array set CFG_dark { |
| 122 | TITLE {Fossil Merge} |
| 123 | LN_COL_BG #dddddd |
| 124 | LN_COL_FG #444444 |
| 125 | TXT_COL_BG #3f3f3f |
| 126 | TXT_COL_FG #dcdccc |
| 127 | MKR_COL_BG #444444 |
| 128 | MKR_COL_FG #dddddd |
| 129 | CHNG_BG #6a6a00 |
| 130 | ADD_BG #57934c |
| 131 | RM_BG #ef6767 |
| 132 | HR_FG #444444 |
| 133 | HR_PAD_TOP 4 |
| 134 | HR_PAD_BTM 8 |
| 135 | FN_BG #5e5e5e |
| 136 | FN_FG #ffffff |
| 137 | FN_PAD 5 |
| 138 | ERR_FG #ee0000 |
| 139 | PADX 5 |
| 140 | WIDTH 80 |
| 141 | HEIGHT 45 |
| 142 | LB_HEIGHT 25 |
| 143 | } |
| 144 | |
| 145 | array set CFG_arr { |
| 146 | 0 CFG_light |
| 147 | 1 CFG_dark |
| 148 | } |
| 149 | |
| 150 | array set CFG [array get $CFG_arr($darkmode)] |
| 151 | |
| 152 | if {![namespace exists ttk]} { |
| 153 | interp alias {} ::ttk::scrollbar {} ::scrollbar |
| 154 | interp alias {} ::ttk::menubutton {} ::menubutton |
| 155 | } |
| 156 | |
| 157 | proc dehtml {x} { |
| 158 | set x [regsub -all {<[^>]*>} $x {}] |
| 159 | return [string map {& & < < > > ' ' " \"} $x] |
| 160 | } |
| 161 | |
| 162 | proc cols {} { |
| 163 | return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD] |
| 164 | } |
| 165 | |
| 166 | proc colType {c} { |
| 167 | regexp {[a-z]+} $c type |
| 168 | return $type |
| 169 | } |
| 170 | |
| 171 | proc readMerge {args} { |
| 172 | global fossilexe ncontext current_file debug |
| 173 | if {$ncontext=="All"} { |
| 174 | set cmd "| $fossilexe merge-info -c -1" |
| 175 | } else { |
| 176 | set cmd "| $fossilexe merge-info -c $ncontext" |
| 177 | } |
| 178 | if {[info exists current_file]} { |
| 179 | regsub {^[A-Z]+ } $current_file {} fn |
| 180 | lappend cmd -tcl $fn |
| 181 | } |
| 182 | if {$debug} { |
| 183 | regsub {^\| +} $cmd {} cmd2 |
| 184 | puts $cmd2 |
| 185 | flush stdout |
| 186 | } |
| 187 | if {[catch { |
| 188 | set in [open $cmd r] |
| 189 | fconfigure $in -encoding utf-8 |
| 190 | set mergetxt [read $in] |
| 191 | close $in |
| 192 | } msg]} { |
| 193 | tk_messageBox -message "Unable to run command: \"$cmd\"" |
| 194 | set mergetxt {} |
| 195 | } |
| 196 | foreach c [cols] { |
| 197 | $c config -state normal |
| 198 | $c delete 1.0 end |
| 199 | } |
| 200 | set lnA 1 |
| 201 | set lnB 1 |
| 202 | set lnC 1 |
| 203 | set lnD 1 |
| 204 | foreach {A B C D} $mergetxt { |
| 205 | set key1 [string index $A 0] |
| 206 | if {$key1=="S"} { |
| 207 | scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD |
| 208 | foreach x {A B C D} { |
| 209 | set N [set n$x] |
| 210 | incr ln$x $N |
| 211 | if {$N>0} { |
| 212 | .ln$x insert end ...\n hrln |
| 213 | .txt$x insert end [string repeat . 30]\n hrtxt |
| 214 | } else { |
| 215 | .ln$x insert end \n hrln |
| 216 | .txt$x insert end \n hrtxt |
| 217 | } |
| 218 | } |
| 219 | continue |
| 220 | } |
| 221 | set key2 [string index $B 0] |
| 222 | set key3 [string index $C 0] |
| 223 | set key4 [string index $D 0] |
| 224 | if {$key1=="."} { |
| 225 | .lnA insert end \n - |
| 226 | .txtA insert end \n - |
| 227 | } elseif {$key1=="N"} { |
| 228 | .nameA config -text [string range $A 1 end] |
| 229 | } else { |
| 230 | .lnA insert end $lnA\n - |
| 231 | incr lnA |
| 232 | if {$key1=="X"} { |
| 233 | .txtA insert end [string range $A 1 end]\n rm |
| 234 | } else { |
| 235 | .txtA insert end [string range $A 1 end]\n - |
| 236 | } |
| 237 | } |
| 238 | if {$key2=="."} { |
| 239 | .lnB insert end \n - |
| 240 | .txtB insert end \n - |
| 241 | } elseif {$key2=="N"} { |
| 242 | .nameB config -text [string range $B 1 end] |
| 243 | } else { |
| 244 | .lnB insert end $lnB\n - |
| 245 | incr lnB |
| 246 | if {$key4=="2"} {set tag chng} {set tag -} |
| 247 | if {$key2=="1"} { |
| 248 | .txtB insert end [string range $A 1 end]\n $tag |
| 249 | } elseif {$key2=="X"} { |
| 250 | .txtB insert end [string range $B 1 end]\n rm |
| 251 | } else { |
| 252 | .txtB insert end [string range $B 1 end]\n $tag |
| 253 | } |
| 254 | } |
| 255 | if {$key3=="."} { |
| 256 | .lnC insert end \n - |
| 257 | .txtC insert end \n - |
| 258 | } elseif {$key3=="N"} { |
| 259 | .nameC config -text [string range $C 1 end] |
| 260 | } else { |
| 261 | .lnC insert end $lnC\n - |
| 262 | incr lnC |
| 263 | if {$key4=="3"} {set tag add} {set tag -} |
| 264 | if {$key3=="1"} { |
| 265 | .txtC insert end [string range $A 1 end]\n $tag |
| 266 | } elseif {$key3=="2"} { |
| 267 | .txtC insert end [string range $B 1 end]\n chng |
| 268 | } elseif {$key3=="X"} { |
| 269 | .txtC insert end [string range $C 1 end]\n rm |
| 270 | } else { |
| 271 | .txtC insert end [string range $C 1 end]\n $tag |
| 272 | } |
| 273 | } |
| 274 | if {$key4=="."} { |
| 275 | .lnD insert end \n - |
| 276 | .txtD insert end \n - |
| 277 | } elseif {$key4=="N"} { |
| 278 | .nameD config -text [string range $D 1 end] |
| 279 | } else { |
| 280 | .lnD insert end $lnD\n - |
| 281 | incr lnD |
| 282 | if {$key4=="1"} { |
| 283 | .txtD insert end [string range $A 1 end]\n - |
| 284 | } elseif {$key4=="2"} { |
| 285 | .txtD insert end [string range $B 1 end]\n chng |
| 286 | } elseif {$key4=="3"} { |
| 287 | .txtD insert end [string range $C 1 end]\n add |
| 288 | } elseif {$key4=="X"} { |
| 289 | .txtD insert end [string range $D 1 end]\n rm |
| 290 | } else { |
| 291 | .txtD insert end [string range $D 1 end]\n - |
| 292 | } |
| 293 | } |
| 294 | } |
| 295 | foreach c [cols] { |
| 296 | set type [colType $c] |
| 297 | if {$type ne "txt"} { |
| 298 | $c config -width 6; # $widths($type) |
| 299 | } |
| 300 | $c config -state disabled |
| 301 | } |
| 302 | set mx $lnA |
| 303 | if {$lnB>$mx} {set mx $lnB} |
| 304 | if {$lnC>$mx} {set mx $lnC} |
| 305 | if {$lnD>$mx} {set mx $lnD} |
| 306 | global lnWidth |
| 307 | set lnWidth [string length [format +%d $mx]] |
| 308 | .lnA config -width $lnWidth |
| 309 | .lnB config -width $lnWidth |
| 310 | .lnC config -width $lnWidth |
| 311 | .lnD config -width $lnWidth |
| 312 | grid columnconfig . {0 2 4 6} -minsize $lnWidth |
| 313 | } |
| 314 | |
| 315 | proc viewDiff {idx} { |
| 316 | .txtA yview $idx |
| 317 | .txtA xview moveto 0 |
| 318 | } |
| 319 | |
| 320 | proc cycleDiffs {{reverse 0}} { |
| 321 | if {$reverse} { |
| 322 | set range [.txtA tag prevrange fn @0,0 1.0] |
| 323 | if {$range eq ""} { |
| 324 | viewDiff {fn.last -1c} |
| 325 | } else { |
| 326 | viewDiff [lindex $range 0] |
| 327 | } |
| 328 | } else { |
| 329 | set range [.txtA tag nextrange fn {@0,0 +1c} end] |
| 330 | if {$range eq "" || [lindex [.txtA yview] 1] == 1} { |
| 331 | viewDiff fn.first |
| 332 | } else { |
| 333 | viewDiff [lindex $range 0] |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | proc xvis {col} { |
| 339 | set view [$col xview] |
| 340 | return [expr {[lindex $view 1]-[lindex $view 0]}] |
| 341 | } |
| 342 | |
| 343 | proc scroll-x {args} { |
| 344 | set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}] |
| 345 | eval $c xview $args |
| 346 | } |
| 347 | |
| 348 | interp alias {} scroll-y {} .txtA yview |
| 349 | |
| 350 | proc noop {args} {} |
| 351 | |
| 352 | proc enableSync {axis} { |
| 353 | update idletasks |
| 354 | interp alias {} sync-$axis {} |
| 355 | rename _sync-$axis sync-$axis |
| 356 | } |
| 357 | |
| 358 | proc disableSync {axis} { |
| 359 | rename sync-$axis _sync-$axis |
| 360 | interp alias {} sync-$axis {} noop |
| 361 | } |
| 362 | |
| 363 | proc sync-y {first last} { |
| 364 | disableSync y |
| 365 | foreach c [cols] { |
| 366 | $c yview moveto $first |
| 367 | } |
| 368 | if {$first > 0 || $last < 1} { |
| 369 | grid .sby |
| 370 | .sby set $first $last |
| 371 | } else { |
| 372 | grid remove .sby |
| 373 | } |
| 374 | enableSync y |
| 375 | } |
| 376 | |
| 377 | wm withdraw . |
| 378 | wm title . $CFG(TITLE) |
| 379 | wm iconname . $CFG(TITLE) |
| 380 | # Keystroke bindings for on the top-level window for navigation and |
| 381 | # control also fire when those same keystrokes are pressed in the |
| 382 | # Search entry box. Disable them, to prevent the diff screen from |
| 383 | # disappearing abruptly and unexpectedly when searching for "q". |
| 384 | # |
| 385 | bind . <Control-q> exit |
| 386 | bind . <Control-p> {catch searchPrev; break} |
| 387 | bind . <Control-n> {catch searchNext; break} |
| 388 | bind . <Escape><Escape> exit |
| 389 | bind . <Destroy> {after 0 exit} |
| 390 | bind . <Tab> {cycleDiffs; break} |
| 391 | bind . <<PrevWindow>> {cycleDiffs 1; break} |
| 392 | bind . <Control-f> {searchOnOff; break} |
| 393 | bind . <Control-g> {catch searchNext; break} |
| 394 | bind . <Return> { |
| 395 | event generate .bb.files <1> |
| 396 | event generate .bb.files <ButtonRelease-1> |
| 397 | break |
| 398 | } |
| 399 | foreach {key axis args} { |
| 400 | Up y {scroll -5 units} |
| 401 | k y {scroll -5 units} |
| 402 | Down y {scroll 5 units} |
| 403 | j y {scroll 5 units} |
| 404 | Left x {scroll -5 units} |
| 405 | h x {scroll -5 units} |
| 406 | Right x {scroll 5 units} |
| 407 | l x {scroll 5 units} |
| 408 | Prior y {scroll -1 page} |
| 409 | b y {scroll -1 page} |
| 410 | Next y {scroll 1 page} |
| 411 | space y {scroll 1 page} |
| 412 | Home y {moveto 0} |
| 413 | g y {moveto 0} |
| 414 | End y {moveto 1} |
| 415 | } { |
| 416 | bind . <$key> "scroll-$axis $args; break" |
| 417 | bind . <Shift-$key> continue |
| 418 | } |
| 419 | |
| 420 | frame .bb |
| 421 | ::ttk::menubutton .bb.diff2 -text {2-way diffs |
| 422 | .bb.diff2.m add command -label {baseline vs. local} -command {two-way 12} |
| 423 | .bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13} |
| 424 | .bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23} |
| 425 | |
| 426 | # Bring up a separate two-way diff between a pair of columns |
| 427 | # the argument is one of: |
| 428 | # 12 Baseline versus Local |
| 429 | # 13 Baseline versus Merge-in |
| 430 | # 23 Local versus Merge-in |
| 431 | # |
| 432 | proc two-way {mode} { |
| 433 | global current_file fossilexe debug darkmode ncontext |
| 434 | regsub {^[A-Z]+ } $current_file {} fn |
| 435 | set cmd $fossilexe |
| 436 | lappend cmd merge-info --diff$mode $fn -c $ncontext |
| 437 | if {$darkmode} { |
| 438 | lappend cmd --dark |
| 439 | } |
| 440 | if {$debug} { |
| 441 | lappend cc {*}$cmd & |
| 442 | } |
| 443 | |
| 444 | set useOptionMenu 1 |
| 445 | if {[info exists filelist]} { |
| 446 | set current_file "[lindex $filelist 0] [lindex $filelist 1]" |
| 447 | if {[llength $filelist]>2} { |
| 448 | trace add variable current_file write readMerge |
| 449 | |
| 450 | if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} { |
| 451 | set fnlist {} |
| 452 | foreach {op fn} $filelist {lappend fnlist "$op $fn"} |
| 453 | tk_optionMenu .bb.files current_file {*}$fnlist |
| 454 | } else { |
| 455 | set useOptionMenu 0 |
| 456 | ::ttk::menubutton .bb.files -text $current_file |
| 457 | if {[tk windowingsystem] eq "win32"} { |
| 458 | ::ttk::style theme use winnative |
| 459 | .bb.files configure -padding {20 1 10 2} |
| 460 | } |
| 461 | toplevel .wfiles |
| 462 | wm withdraw .wfiles |
| 463 | update idletasks |
| 464 | wm transient .wfiles . |
| 465 | wm overrideredirect .wfiles 1 |
| 466 | set ht [expr {[llength $filelist]/2}] |
| 467 | if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)} |
| 468 | listbox .wfiles.lb -width 0 -height $ht -activestyle none \ |
| 469 | -yscroll {.wfiles.sb set} |
| 470 | set mx 1 |
| 471 | foreach {op fn} $filelist { |
| 472 | set n [string length $fn] |
| 473 | if {$n>$mx} {set mx $n} |
| 474 | .wfiles.lb insert end "$op $fn" |
| 475 | } |
| 476 | .bb.files config -width $mx |
| 477 | ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview} |
| 478 | grid .wfiles.lb .wfiles.sb -sticky ns |
| 479 | bind .bb.files <1> { |
| 480 | set x [winfo rootx %W] |
| 481 | set y [expr {[winfo rooty %W]+[winfo height %W]}] |
| 482 | wm geometry .wfiles +$x+$y |
| 483 | wm deiconify .wfiles |
| 484 | focus .wfiles.lb |
| 485 | } |
| 486 | bind .wfiles <FocusOut> {wm withdraw .wfiles} |
| 487 | bind .wfiles <Escape> {focus .} |
| 488 | foreach evt {1 Return} { |
| 489 | bind .wfiles.lb <$evt> { |
| 490 | set ii [%W curselection] |
| 491 | set ::current_file [%W get $ii] |
| 492 | .bb.files config -text $::current_file |
| 493 | focus . |
| 494 | break |
| 495 | } |
| 496 | } |
| 497 | bind .wfiles.lb <Motion> { |
| 498 | %W selection clear 0 end |
| 499 | %W selection set @%x,%y |
| 500 | } |
| 501 | } |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | label .bb.ctxtag -text "Context:" |
| 506 | set context_choices {3 6 12 25 50 100 All} |
| 507 | if {$ncontext<0} {set ncontext All} |
| 508 | trace add variable ncontext write readMerge |
| 509 | if {$tcl_platform(os)=="Darwin" || $useOptionMenu} { |
| 510 | tk_optionMenu .bb.ctx ncontext {*}$context_choices |
| 511 | } else { |
| 512 | ::ttk::menubutton .bb.ctx -text $ncontext |
| 513 | if {[tk windowingsystem] eq "win32"} { |
| 514 | ::ttk::style theme use winnative |
| 515 | .bb.ctx configure -padding {20 1 10 2} |
| 516 | } |
| 517 | toplevel .wctx |
| 518 | wm withdraw .wctx |
| 519 | update idletasks |
| 520 | wm transient .wctx . |
| 521 | wm overrideredirect .wctx 1 |
| 522 | listbox .wctx.lb -width 0 -height 7 -activestyle none |
| 523 | .wctx.lb insert end {*}$context_choices |
| 524 | pack .wctx.lb |
| 525 | bind .bb.ctx <1> { |
| 526 | set x [winfo rootx %W] |
| 527 | set y [expr {[winfo rooty %W]+[winfo height %W]}] |
| 528 | wm geometry .wctx +$x+$y |
| 529 | wm deiconify .wctx |
| 530 | focus .wctx.lb |
| 531 | } |
| 532 | bind .wctx <FocusOut> {wm withdraw .wctx} |
| 533 | bind .wctx <Escape> {focus .} |
| 534 | foreach evt {1 Return} { |
| 535 | bind .wctx.lb <$evt> { |
| 536 | set ::ncontext [lindex $::context_choices [%W curselection]] |
| 537 | .bb.ctx config -text $::ncontext |
| 538 | focus . |
| 539 | break |
| 540 | } |
| 541 | } |
| 542 | bind .wctx.lb <Motion> { |
| 543 | %W selection clear 0 end |
| 544 | %W selection set @%x,%y |
| 545 | } |
| 546 | } |
| 547 | |
| 548 | foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} { |
| 549 | set ln .ln$side |
| 550 | text $ln -width 6 |
| 551 | $ln tag config - -justify right |
| 552 | |
| 553 | set txt .txt$side |
| 554 | text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \ |
| 555 | -xscroll ".sbx$side set" |
| 556 | catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5 |
| 557 | foreach tag {add rm chng} { |
| 558 | $txt tag config $tag -background $CFG([string toupper $tag]_BG) |
| 559 | $txt tag lower $tag |
| 560 | } |
| 561 | $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \ |
| 562 | -justify center |
| 563 | $txt tag config err -foreground $CFG(ERR_FG) |
| 564 | } |
| 565 | text .mkr |
| 566 | |
| 567 | set mxwidth [lindex [wm maxsize .] 0] |
| 568 | while {$CFG(WIDTH)>=40} { |
| 569 | set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}] |
| 570 | if {$wanted<=$mxwidth} break |
| 571 | incr CFG(WIDTH) -10 |
| 572 | .txtA config -width $CFG(WIDTH) |
| 573 | .txtB config -width $CFG(WIDTH) |
| 574 | .txtC config -width $CFG(WIDTH) |
| 575 | .txtD config -width $CFG(WIDTH) |
| 576 | } |
| 577 | |
| 578 | foreach c [cols] { |
| 579 | set keyPrefix [string toupper [colType $c]]_COL_ |
| 580 | if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}} |
| 581 | $c co |
+804
-168
| --- src/merge3.c | ||
| +++ src/merge3.c | ||
| @@ -75,75 +75,17 @@ | ||
| 75 | 75 | if( aC1[2]!=aC2[2] ) return 0; |
| 76 | 76 | if( sameLines(pV1, pV2, aC1[2]) ) return 1; |
| 77 | 77 | return 0; |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | -/* | |
| 81 | -** The aC[] array contains triples of integers. Within each triple, the | |
| 82 | -** elements are: | |
| 83 | -** | |
| 84 | -** (0) The number of lines to copy | |
| 85 | -** (1) The number of lines to delete | |
| 86 | -** (2) The number of liens to insert | |
| 87 | -** | |
| 88 | -** Suppose we want to advance over sz lines of the original file. This routine | |
| 89 | -** returns true if that advance would land us on a copy operation. It | |
| 90 | -** returns false if the advance would end on a delete. | |
| 91 | -*/ | |
| 92 | -static int ends_at_CPY(int *aC, int sz){ | |
| 93 | - while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ | |
| 94 | - if( aC[0]>=sz ) return 1; | |
| 95 | - sz -= aC[0]; | |
| 96 | - if( aC[1]>sz ) return 0; | |
| 97 | - sz -= aC[1]; | |
| 98 | - aC += 3; | |
| 99 | - } | |
| 100 | - return 1; | |
| 101 | -} | |
| 102 | - | |
| 103 | -/* | |
| 104 | -** pSrc contains an edited file where aC[] describes the edit. Part of | |
| 105 | -** pSrc has already been output. This routine outputs additional lines | |
| 106 | -** of pSrc - lines that correspond to the next sz lines of the original | |
| 107 | -** unedited file. | |
| 108 | -** | |
| 109 | -** Note that sz counts the number of lines of text in the original file. | |
| 110 | -** But text is output from the edited file. So the number of lines transfer | |
| 111 | -** to pOut might be different from sz. Fewer lines appear in pOut if there | |
| 112 | -** are deletes. More lines appear if there are inserts. | |
| 113 | -** | |
| 114 | -** The aC[] array is updated and the new index into aC[] is returned. | |
| 115 | -*/ | |
| 116 | -static int output_one_side( | |
| 117 | - Blob *pOut, /* Write to this blob */ | |
| 118 | - Blob *pSrc, /* The edited file that is to be copied to pOut */ | |
| 119 | - int *aC, /* Array of integer triples describing the edit */ | |
| 120 | - int i, /* Index in aC[] of current location in pSrc */ | |
| 121 | - int sz, /* Number of lines in unedited source to output */ | |
| 122 | - int *pLn /* Line number counter */ | |
| 123 | -){ | |
| 124 | - while( sz>0 ){ | |
| 125 | - if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; | |
| 126 | - if( aC[i]>=sz ){ | |
| 127 | - blob_copy_lines(pOut, pSrc, sz); *pLn += sz; | |
| 128 | - aC[i] -= sz; | |
| 129 | - break; | |
| 130 | - } | |
| 131 | - blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i]; | |
| 132 | - blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2]; | |
| 133 | - sz -= aC[i] + aC[i+1]; | |
| 134 | - i += 3; | |
| 135 | - } | |
| 136 | - return i; | |
| 137 | -} | |
| 138 | - | |
| 139 | 80 | /* |
| 140 | 81 | ** Text of boundary markers for merge conflicts. |
| 141 | 82 | */ |
| 142 | 83 | static const char *const mergeMarker[] = { |
| 143 | 84 | /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ |
| 144 | 85 | "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<", |
| 86 | + "####### SUGGESTED CONFLICT RESOLUTION follows ###################", | |
| 145 | 87 | "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||", |
| 146 | 88 | "======= MERGED IN content follows ===============================", |
| 147 | 89 | ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" |
| 148 | 90 | }; |
| 149 | 91 | |
| @@ -186,10 +128,615 @@ | ||
| 186 | 128 | ensure_line_end(pOut, useCrLf); |
| 187 | 129 | blob_append(pOut, mergeMarker[iMark], -1); |
| 188 | 130 | if( ln>0 ) blob_appendf(pOut, " (line %d)", ln); |
| 189 | 131 | ensure_line_end(pOut, useCrLf); |
| 190 | 132 | } |
| 133 | + | |
| 134 | +#if INTERFACE | |
| 135 | +/* | |
| 136 | +** This is an abstract class for constructing a merge. | |
| 137 | +** Subclasses of this object format the merge output in different ways. | |
| 138 | +** | |
| 139 | +** To subclass, create an instance of the MergeBuilder object and fill | |
| 140 | +** in appropriate method implementations. | |
| 141 | +*/ | |
| 142 | +struct MergeBuilder { | |
| 143 | + void (*xStart)(MergeBuilder*); | |
| 144 | + void (*xSame)(MergeBuilder*, unsigned int); | |
| 145 | + void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int); | |
| 146 | + void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int); | |
| 147 | + void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int); | |
| 148 | + void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int); | |
| 149 | + void (*xEnd)(MergeBuilder*); | |
| 150 | + void (*xDestroy)(MergeBuilder*); | |
| 151 | + const char *zPivot; /* Label or name for the pivot */ | |
| 152 | + const char *zV1; /* Label or name for the V1 file */ | |
| 153 | + const char *zV2; /* Label or name for the V2 file */ | |
| 154 | + const char *zOut; /* Label or name for the output */ | |
| 155 | + Blob *pPivot; /* The common ancestor */ | |
| 156 | + Blob *pV1; /* First variant (local copy) */ | |
| 157 | + Blob *pV2; /* Second variant (merged in) */ | |
| 158 | + Blob *pOut; /* Write merge results here */ | |
| 159 | + int useCrLf; /* Use CRLF line endings */ | |
| 160 | + int nContext; /* Size of unchanged line boundaries */ | |
| 161 | + unsigned int mxPivot; /* Number of lines in the pivot */ | |
| 162 | + unsigned int mxV1; /* Number of lines in V1 */ | |
| 163 | + unsigned int mxV2; /* Number of lines in V2 */ | |
| 164 | + unsigned int lnPivot; /* Lines read from pivot */ | |
| 165 | + unsigned int lnV1; /* Lines read from v1 */ | |
| 166 | + unsigned int lnV2; /* Lines read from v2 */ | |
| 167 | + unsigned int lnOut; /* Lines written to out */ | |
| 168 | + unsigned int nConflict; /* Number of conflicts seen */ | |
| 169 | + u64 diffFlags; /* Flags for difference engine */ | |
| 170 | +}; | |
| 171 | +#endif /* INTERFACE */ | |
| 172 | + | |
| 173 | + | |
| 174 | +/************************* Generic MergeBuilder ******************************/ | |
| 175 | +/* These are generic methods for MergeBuilder. They just output debugging | |
| 176 | +** information. But some of them are useful as base methods for other useful | |
| 177 | +** implementations of MergeBuilder. | |
| 178 | +*/ | |
| 179 | + | |
| 180 | +/* xStart() and xEnd() are called to generate header and fotter information | |
| 181 | +** in the output. This is a no-op in the generic implementation. | |
| 182 | +*/ | |
| 183 | +static void dbgStartEnd(MergeBuilder *p){ (void)p; } | |
| 184 | + | |
| 185 | +/* The next N lines of PIVOT are unchanged in both V1 and V2 | |
| 186 | +*/ | |
| 187 | +static void dbgSame(MergeBuilder *p, unsigned int N){ | |
| 188 | + blob_appendf(p->pOut, | |
| 189 | + "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n", | |
| 190 | + N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N, | |
| 191 | + p->lnV2+1, p->lnV2+N); | |
| 192 | + p->lnPivot += N; | |
| 193 | + p->lnV1 += N; | |
| 194 | + p->lnV2 += N; | |
| 195 | +} | |
| 196 | + | |
| 197 | +/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1 | |
| 198 | +*/ | |
| 199 | +static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ | |
| 200 | + blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n", | |
| 201 | + nV1, p->lnV1+1, p->lnV1+nV1); | |
| 202 | + p->lnPivot += nPivot; | |
| 203 | + p->lnV2 += nPivot; | |
| 204 | + p->lnV1 += nV1; | |
| 205 | +} | |
| 206 | + | |
| 207 | +/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2 | |
| 208 | +*/ | |
| 209 | +static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ | |
| 210 | + blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n", | |
| 211 | + nV2, p->lnV2+1, p->lnV2+nV2); | |
| 212 | + p->lnPivot += nPivot; | |
| 213 | + p->lnV1 += nPivot; | |
| 214 | + p->lnV2 += nV2; | |
| 215 | +} | |
| 216 | + | |
| 217 | +/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and | |
| 218 | +** V2, which should be the same. In other words, the same change is found | |
| 219 | +** in both V1 and V2. | |
| 220 | +*/ | |
| 221 | +static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ | |
| 222 | + blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n", | |
| 223 | + nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV); | |
| 224 | + p->lnPivot += nPivot; | |
| 225 | + p->lnV1 += nV; | |
| 226 | + p->lnV2 += nV; | |
| 227 | +} | |
| 228 | + | |
| 229 | +/* V1 and V2 have different and overlapping changes. The next nPivot lines | |
| 230 | +** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2. | |
| 231 | +*/ | |
| 232 | +static void dbgConflict( | |
| 233 | + MergeBuilder *p, | |
| 234 | + unsigned int nPivot, | |
| 235 | + unsigned int nV1, | |
| 236 | + unsigned int nV2 | |
| 237 | +){ | |
| 238 | + blob_appendf(p->pOut, | |
| 239 | + "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n", | |
| 240 | + nPivot, nV1, nV2, | |
| 241 | + p->lnPivot+1, p->lnPivot+nPivot, | |
| 242 | + p->lnV1+1, p->lnV1+nV1, | |
| 243 | + p->lnV2+1, p->lnV2+nV2); | |
| 244 | + p->lnV1 += nV1; | |
| 245 | + p->lnPivot += nPivot; | |
| 246 | + p->lnV2 += nV2; | |
| 247 | +} | |
| 248 | + | |
| 249 | +/* Generic destructor for the MergeBuilder object | |
| 250 | +*/ | |
| 251 | +static void dbgDestroy(MergeBuilder *p){ | |
| 252 | + memset(p, 0, sizeof(*p)); | |
| 253 | +} | |
| 254 | + | |
| 255 | +/* Generic initializer for a MergeBuilder object | |
| 256 | +*/ | |
| 257 | +static void mergebuilder_init(MergeBuilder *p){ | |
| 258 | + memset(p, 0, sizeof(*p)); | |
| 259 | + p->xStart = dbgStartEnd; | |
| 260 | + p->xSame = dbgSame; | |
| 261 | + p->xChngV1 = dbgChngV1; | |
| 262 | + p->xChngV2 = dbgChngV2; | |
| 263 | + p->xChngBoth = dbgChngBoth; | |
| 264 | + p->xConflict = dbgConflict; | |
| 265 | + p->xEnd = dbgStartEnd; | |
| 266 | + p->xDestroy = dbgDestroy; | |
| 267 | +} | |
| 268 | + | |
| 269 | +/************************* MergeBuilderToken ********************************/ | |
| 270 | +/* This version of MergeBuilder actually performs a merge on file that | |
| 271 | +** are broken up into tokens instead of lines, and puts the result in pOut. | |
| 272 | +*/ | |
| 273 | +static void tokenSame(MergeBuilder *p, unsigned int N){ | |
| 274 | + blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N); | |
| 275 | + p->pPivot->iCursor += N; | |
| 276 | + p->pV1->iCursor += N; | |
| 277 | + p->pV2->iCursor += N; | |
| 278 | +} | |
| 279 | +static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){ | |
| 280 | + blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1); | |
| 281 | + p->pPivot->iCursor += nPivot; | |
| 282 | + p->pV1->iCursor += nV1; | |
| 283 | + p->pV2->iCursor += nPivot; | |
| 284 | +} | |
| 285 | +static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){ | |
| 286 | + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); | |
| 287 | + p->pPivot->iCursor += nPivot; | |
| 288 | + p->pV1->iCursor += nPivot; | |
| 289 | + p->pV2->iCursor += nV2; | |
| 290 | +} | |
| 291 | +static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){ | |
| 292 | + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV); | |
| 293 | + p->pPivot->iCursor += nPivot; | |
| 294 | + p->pV1->iCursor += nV; | |
| 295 | + p->pV2->iCursor += nV; | |
| 296 | +} | |
| 297 | +static void tokenConflict( | |
| 298 | + MergeBuilder *p, | |
| 299 | + unsigned int nPivot, | |
| 300 | + unsigned int nV1, | |
| 301 | + unsigned int nV2 | |
| 302 | +){ | |
| 303 | + /* For a token-merge conflict, use the text from the merge-in */ | |
| 304 | + blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); | |
| 305 | + p->pPivot->iCursor += nPivot; | |
| 306 | + p->pV1->iCursor += nV1; | |
| 307 | + p->pV2->iCursor += nV2; | |
| 308 | +} | |
| 309 | +static void mergebuilder_init_token(MergeBuilder *p){ | |
| 310 | + mergebuilder_init(p); | |
| 311 | + p->xSame = tokenSame; | |
| 312 | + p->xChngV1 = tokenChngV1; | |
| 313 | + p->xChngV2 = tokenChngV2; | |
| 314 | + p->xChngBoth = tokenChngBoth; | |
| 315 | + p->xConflict = tokenConflict; | |
| 316 | + p->diffFlags = DIFF_BY_TOKEN; | |
| 317 | +} | |
| 318 | + | |
| 319 | +/* | |
| 320 | +** Attempt to do a low-level merge on a conflict. The conflict is | |
| 321 | +** described by the first four parameters, which are the same as the | |
| 322 | +** arguments to the xConflict method of the MergeBuilder object. | |
| 323 | +** This routine attempts to resolve the conflict by looking at | |
| 324 | +** elements of the conflict region that are finer grain than complete | |
| 325 | +** lines of text. | |
| 326 | +** | |
| 327 | +** The result is written into Blob pOut. pOut is initialized by this | |
| 328 | +** routine. | |
| 329 | +*/ | |
| 330 | +int merge_try_to_resolve_conflict( | |
| 331 | + MergeBuilder *pMB, /* MergeBuilder that encounter conflict */ | |
| 332 | + unsigned int nPivot, /* Lines of conflict in the pivot */ | |
| 333 | + unsigned int nV1, /* Lines of conflict in V1 */ | |
| 334 | + unsigned int nV2, /* Lines of conflict in V2 */ | |
| 335 | + Blob *pOut /* Write resolution text here */ | |
| 336 | +){ | |
| 337 | + int nConflict; | |
| 338 | + MergeBuilder mb; | |
| 339 | + Blob pv, v1, v2; | |
| 340 | + mergebuilder_init_token(&mb); | |
| 341 | + blob_extract_lines(pMB->pPivot, nPivot, &pv); | |
| 342 | + blob_extract_lines(pMB->pV1, nV1, &v1); | |
| 343 | + blob_extract_lines(pMB->pV2, nV2, &v2); | |
| 344 | + blob_zero(pOut); | |
| 345 | + blob_materialize(&pv); | |
| 346 | + blob_materialize(&v1); | |
| 347 | + blob_materialize(&v2); | |
| 348 | + mb.pPivot = &pv; | |
| 349 | + mb.pV1 = &v1; | |
| 350 | + mb.pV2 = &v2; | |
| 351 | + mb.pOut = pOut; | |
| 352 | + nConflict = merge_three_blobs(&mb); | |
| 353 | + blob_reset(&pv); | |
| 354 | + blob_reset(&v1); | |
| 355 | + blob_reset(&v2); | |
| 356 | + /* mb has not allocated any resources, so we do not need to invoke | |
| 357 | + ** the xDestroy method. */ | |
| 358 | + blob_add_final_newline(pOut); | |
| 359 | + return nConflict; | |
| 360 | +} | |
| 361 | + | |
| 362 | + | |
| 363 | +/************************* MergeBuilderText **********************************/ | |
| 364 | +/* This version of MergeBuilder actually performs a merge on file and puts | |
| 365 | +** the result in pOut | |
| 366 | +*/ | |
| 367 | +static void txtStart(MergeBuilder *p){ | |
| 368 | + /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM), | |
| 369 | + ** keep it in the output. This should be secure enough not to cause | |
| 370 | + ** unintended changes to the merged file and consistent with what | |
| 371 | + ** users are using in their source files. | |
| 372 | + */ | |
| 373 | + if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){ | |
| 374 | + blob_append(p->pOut, (char*)get_utf8_bom(0), -1); | |
| 375 | + } | |
| 376 | + if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){ | |
| 377 | + p->useCrLf = 1; | |
| 378 | + } | |
| 379 | +} | |
| 380 | +static void txtSame(MergeBuilder *p, unsigned int N){ | |
| 381 | + blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N; | |
| 382 | + blob_copy_lines(0, p->pV1, N); p->lnV1 += N; | |
| 383 | + blob_copy_lines(0, p->pV2, N); p->lnV2 += N; | |
| 384 | +} | |
| 385 | +static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ | |
| 386 | + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; | |
| 387 | + blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot; | |
| 388 | + blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; | |
| 389 | +} | |
| 390 | +static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ | |
| 391 | + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; | |
| 392 | + blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot; | |
| 393 | + blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; | |
| 394 | +} | |
| 395 | +static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ | |
| 396 | + blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; | |
| 397 | + blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV; | |
| 398 | + blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV; | |
| 399 | +} | |
| 400 | +static void txtConflict( | |
| 401 | + MergeBuilder *p, | |
| 402 | + unsigned int nPivot, | |
| 403 | + unsigned int nV1, | |
| 404 | + unsigned int nV2 | |
| 405 | +){ | |
| 406 | + int nRes; /* Lines in the computed conflict resolution */ | |
| 407 | + Blob res; /* Text of the conflict resolution */ | |
| 408 | + | |
| 409 | + merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); | |
| 410 | + nRes = blob_linecount(&res); | |
| 411 | + | |
| 412 | + append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf); | |
| 413 | + blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; | |
| 414 | + | |
| 415 | + if( nRes>0 ){ | |
| 416 | + append_merge_mark(p->pOut, 1, 0, p->useCrLf); | |
| 417 | + blob_copy_lines(p->pOut, &res, nRes); | |
| 418 | + } | |
| 419 | + blob_reset(&res); | |
| 420 | + | |
| 421 | + append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf); | |
| 422 | + blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot; | |
| 423 | + | |
| 424 | + append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf); | |
| 425 | + blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; | |
| 426 | + | |
| 427 | + append_merge_mark(p->pOut, 4, -1, p->useCrLf); | |
| 428 | +} | |
| 429 | +static void mergebuilder_init_text(MergeBuilder *p){ | |
| 430 | + mergebuilder_init(p); | |
| 431 | + p->xStart = txtStart; | |
| 432 | + p->xSame = txtSame; | |
| 433 | + p->xChngV1 = txtChngV1; | |
| 434 | + p->xChngV2 = txtChngV2; | |
| 435 | + p->xChngBoth = txtChngBoth; | |
| 436 | + p->xConflict = txtConflict; | |
| 437 | +} | |
| 438 | + | |
| 439 | +/************************* MergeBuilderTcl **********************************/ | |
| 440 | +/* Generate merge output formatted for reading by a TCL script. | |
| 441 | +** | |
| 442 | +** The output consists of lines of text, each with 4 tokens. The tokens | |
| 443 | +** represent the content for one line from baseline, v1, v2, and output | |
| 444 | +** respectively. The first character of each token provides auxiliary | |
| 445 | +** information: | |
| 446 | +** | |
| 447 | +** . This line is omitted. | |
| 448 | +** N Name of the file. | |
| 449 | +** T Literal text follows that should have a \n terminator. | |
| 450 | +** R Literal text follows that needs a \r\n terminator. | |
| 451 | +** X Merge conflict. | |
| 452 | +** Z Literal text without a line terminator. | |
| 453 | +** S Skipped lines. Followed by number of lines to skip. | |
| 454 | +** 1 Text is a copy of token 1 | |
| 455 | +** 2 Use data from data-token 2 | |
| 456 | +** 3 Use data from data-token 3 | |
| 457 | +*/ | |
| 458 | + | |
| 459 | +/* Write text that goes into the interior of a double-quoted string in TCL */ | |
| 460 | +static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){ | |
| 461 | + int j; | |
| 462 | + for(j=0; j<nIn; j++){ | |
| 463 | + char c = zIn[j]; | |
| 464 | + if( c=='\\' ){ | |
| 465 | + blob_append(pOut, "\\\\", 2); | |
| 466 | + }else if( c=='"' ){ | |
| 467 | + blob_append(pOut, "\\\"", 2); | |
| 468 | + }else if( c<' ' || c>0x7e ){ | |
| 469 | + char z[5]; | |
| 470 | + z[0] = '\\'; | |
| 471 | + z[1] = "01234567"[(c>>6)&0x3]; | |
| 472 | + z[2] = "01234567"[(c>>3)&0x7]; | |
| 473 | + z[3] = "01234567"[c&0x7]; | |
| 474 | + z[4] = 0; | |
| 475 | + blob_append(pOut, z, 4); | |
| 476 | + }else{ | |
| 477 | + blob_append_char(pOut, c); | |
| 478 | + } | |
| 479 | + } | |
| 480 | +} | |
| 481 | + | |
| 482 | +/* Copy one line of text from pIn and append to pOut, encoded as TCL */ | |
| 483 | +static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){ | |
| 484 | + int i, k; | |
| 485 | + for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){} | |
| 486 | + if( i==pIn->nUsed ){ | |
| 487 | + k = i; | |
| 488 | + }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){ | |
| 489 | + k = i-1; | |
| 490 | + i++; | |
| 491 | + }else{ | |
| 492 | + k = i; | |
| 493 | + i++; | |
| 494 | + } | |
| 495 | + blob_append_char(pOut, '"'); | |
| 496 | + blob_append_char(pOut, cType); | |
| 497 | + tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor); | |
| 498 | + pIn->iCursor = i; | |
| 499 | + blob_append_char(pOut, '"'); | |
| 500 | +} | |
| 501 | +static void tclStart(MergeBuilder *p){ | |
| 502 | + Blob *pOut = p->pOut; | |
| 503 | + blob_append(pOut, "\"N", 2); | |
| 504 | + tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot)); | |
| 505 | + blob_append(pOut, "\" \"N", 4); | |
| 506 | + tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1)); | |
| 507 | + blob_append(pOut, "\" \"N", 4); | |
| 508 | + tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2)); | |
| 509 | + blob_append(pOut, "\" \"N", 4); | |
| 510 | + if( p->zOut ){ | |
| 511 | + tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut)); | |
| 512 | + }else{ | |
| 513 | + blob_append(pOut, "(Merge Result)", -1); | |
| 514 | + } | |
| 515 | + blob_append(pOut, "\"\n", 2); | |
| 516 | +} | |
| 517 | +static void tclSame(MergeBuilder *p, unsigned int N){ | |
| 518 | + int i = 0; | |
| 519 | + int nSkip; | |
| 520 | + | |
| 521 | + if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){ | |
| 522 | + while( i<N && i<p->nContext ){ | |
| 523 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 524 | + blob_append(p->pOut, " 1 1 1\n", 7); | |
| 525 | + i++; | |
| 526 | + } | |
| 527 | + nSkip = N - p->nContext*2; | |
| 528 | + }else{ | |
| 529 | + nSkip = N - p->nContext; | |
| 530 | + } | |
| 531 | + if( nSkip>0 ){ | |
| 532 | + blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n", | |
| 533 | + nSkip, nSkip, nSkip, nSkip); | |
| 534 | + blob_copy_lines(0, p->pPivot, nSkip); | |
| 535 | + i += nSkip; | |
| 536 | + } | |
| 537 | + | |
| 538 | + p->lnPivot += N; | |
| 539 | + p->lnV1 += N; | |
| 540 | + p->lnV2 += N; | |
| 541 | + | |
| 542 | + if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){ | |
| 543 | + while( i<N ){ | |
| 544 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 545 | + blob_append(p->pOut, " 1 1 1\n", 7); | |
| 546 | + i++; | |
| 547 | + } | |
| 548 | + } | |
| 549 | + | |
| 550 | + blob_copy_lines(0, p->pV1, N); | |
| 551 | + blob_copy_lines(0, p->pV2, N); | |
| 552 | +} | |
| 553 | +static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ | |
| 554 | + int i; | |
| 555 | + for(i=0; i<nPivot && i<nV1; i++){ | |
| 556 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 557 | + blob_append_char(p->pOut, ' '); | |
| 558 | + tclLineOfText(p->pOut, p->pV1, 'T'); | |
| 559 | + blob_append(p->pOut, " 1 2\n", 5); | |
| 560 | + } | |
| 561 | + while( i<nPivot ){ | |
| 562 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 563 | + blob_append(p->pOut, " . 1 .\n", 7); | |
| 564 | + i++; | |
| 565 | + } | |
| 566 | + while( i<nV1 ){ | |
| 567 | + blob_append(p->pOut, ". ", 2); | |
| 568 | + tclLineOfText(p->pOut, p->pV1, 'T'); | |
| 569 | + blob_append(p->pOut, " . 2\n", 5); | |
| 570 | + i++; | |
| 571 | + } | |
| 572 | + p->lnPivot += nPivot; | |
| 573 | + p->lnV1 += nV1; | |
| 574 | + p->lnV2 += nPivot; | |
| 575 | + blob_copy_lines(0, p->pV2, nPivot); | |
| 576 | +} | |
| 577 | +static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ | |
| 578 | + int i; | |
| 579 | + for(i=0; i<nPivot && i<nV2; i++){ | |
| 580 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 581 | + blob_append(p->pOut, " 1 ", 3); | |
| 582 | + tclLineOfText(p->pOut, p->pV2, 'T'); | |
| 583 | + blob_append(p->pOut, " 3\n", 3); | |
| 584 | + } | |
| 585 | + while( i<nPivot ){ | |
| 586 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 587 | + blob_append(p->pOut, " 1 . .\n", 7); | |
| 588 | + i++; | |
| 589 | + } | |
| 590 | + while( i<nV2 ){ | |
| 591 | + blob_append(p->pOut, ". . ", 4); | |
| 592 | + tclLineOfText(p->pOut, p->pV2, 'T'); | |
| 593 | + blob_append(p->pOut, " 3\n", 3); | |
| 594 | + i++; | |
| 595 | + } | |
| 596 | + p->lnPivot += nPivot; | |
| 597 | + p->lnV1 += nPivot; | |
| 598 | + p->lnV2 += nV2; | |
| 599 | + blob_copy_lines(0, p->pV1, nPivot); | |
| 600 | +} | |
| 601 | +static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ | |
| 602 | + int i; | |
| 603 | + for(i=0; i<nPivot && i<nV; i++){ | |
| 604 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 605 | + blob_append_char(p->pOut, ' '); | |
| 606 | + tclLineOfText(p->pOut, p->pV1, 'T'); | |
| 607 | + blob_append(p->pOut, " 2 2\n", 5); | |
| 608 | + } | |
| 609 | + while( i<nPivot ){ | |
| 610 | + tclLineOfText(p->pOut, p->pPivot, 'T'); | |
| 611 | + blob_append(p->pOut, " . . .\n", 7); | |
| 612 | + i++; | |
| 613 | + } | |
| 614 | + while( i<nV ){ | |
| 615 | + blob_append(p->pOut, ". ", 2); | |
| 616 | + tclLineOfText(p->pOut, p->pV1, 'T'); | |
| 617 | + blob_append(p->pOut, " 2 2\n", 5); | |
| 618 | + i++; | |
| 619 | + } | |
| 620 | + p->lnPivot += nPivot; | |
| 621 | + p->lnV1 += nV; | |
| 622 | + p->lnV2 += nV; | |
| 623 | + blob_copy_lines(0, p->pV2, nV); | |
| 624 | +} | |
| 625 | +static void tclConflict( | |
| 626 | + MergeBuilder *p, | |
| 627 | + unsigned int nPivot, | |
| 628 | + unsigned int nV1, | |
| 629 | + unsigned int nV2 | |
| 630 | +){ | |
| 631 | + int mx = nPivot; | |
| 632 | + int i; | |
| 633 | + int nRes; | |
| 634 | + Blob res; | |
| 635 | + | |
| 636 | + merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); | |
| 637 | + nRes = blob_linecount(&res); | |
| 638 | + if( nV1>mx ) mx = nV1; | |
| 639 | + if( nV2>mx ) mx = nV2; | |
| 640 | + if( nRes>mx ) mx = nRes; | |
| 641 | + if( nRes>0 ){ | |
| 642 | + blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2); | |
| 643 | + } | |
| 644 | + for(i=0; i<mx; i++){ | |
| 645 | + if( i<nPivot ){ | |
| 646 | + tclLineOfText(p->pOut, p->pPivot, 'X'); | |
| 647 | + }else{ | |
| 648 | + blob_append_char(p->pOut, '.'); | |
| 649 | + } | |
| 650 | + blob_append_char(p->pOut, ' '); | |
| 651 | + if( i<nV1 ){ | |
| 652 | + tclLineOfText(p->pOut, p->pV1, 'X'); | |
| 653 | + }else{ | |
| 654 | + blob_append_char(p->pOut, '.'); | |
| 655 | + } | |
| 656 | + blob_append_char(p->pOut, ' '); | |
| 657 | + if( i<nV2 ){ | |
| 658 | + tclLineOfText(p->pOut, p->pV2, 'X'); | |
| 659 | + }else{ | |
| 660 | + blob_append_char(p->pOut, '.'); | |
| 661 | + } | |
| 662 | + if( i<nRes ){ | |
| 663 | + blob_append_char(p->pOut, ' '); | |
| 664 | + tclLineOfText(p->pOut, &res, 'X'); | |
| 665 | + blob_append_char(p->pOut, '\n'); | |
| 666 | + }else{ | |
| 667 | + blob_append(p->pOut, " .\n", 3); | |
| 668 | + } | |
| 669 | + if( i==mx-1 ){ | |
| 670 | + blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3); | |
| 671 | + } | |
| 672 | + } | |
| 673 | + blob_reset(&res); | |
| 674 | + p->lnPivot += nPivot; | |
| 675 | + p->lnV1 += nV1; | |
| 676 | + p->lnV2 += nV2; | |
| 677 | +} | |
| 678 | +void mergebuilder_init_tcl(MergeBuilder *p){ | |
| 679 | + mergebuilder_init(p); | |
| 680 | + p->xStart = tclStart; | |
| 681 | + p->xSame = tclSame; | |
| 682 | + p->xChngV1 = tclChngV1; | |
| 683 | + p->xChngV2 = tclChngV2; | |
| 684 | + p->xChngBoth = tclChngBoth; | |
| 685 | + p->xConflict = tclConflict; | |
| 686 | +} | |
| 687 | +/*****************************************************************************/ | |
| 688 | + | |
| 689 | +/* | |
| 690 | +** The aC[] array contains triples of integers. Within each triple, the | |
| 691 | +** elements are: | |
| 692 | +** | |
| 693 | +** (0) The number of lines to copy | |
| 694 | +** (1) The number of lines to delete | |
| 695 | +** (2) The number of liens to insert | |
| 696 | +** | |
| 697 | +** Suppose we want to advance over sz lines of the original file. This routine | |
| 698 | +** returns true if that advance would land us on a copy operation. It | |
| 699 | +** returns false if the advance would end on a delete. | |
| 700 | +*/ | |
| 701 | +static int ends_with_copy(int *aC, int sz){ | |
| 702 | + while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ | |
| 703 | + if( aC[0]>=sz ) return 1; | |
| 704 | + sz -= aC[0]; | |
| 705 | + if( aC[1]>sz ) return 0; | |
| 706 | + sz -= aC[1]; | |
| 707 | + aC += 3; | |
| 708 | + } | |
| 709 | + return 1; | |
| 710 | +} | |
| 711 | + | |
| 712 | +/* | |
| 713 | +** aC[] is an "edit triple" for changes from A to B. Advance through | |
| 714 | +** this triple to determine the number of lines to bypass on B in order | |
| 715 | +** to match an advance of sz lines on A. | |
| 716 | +*/ | |
| 717 | +static int skip_conflict( | |
| 718 | + int *aC, /* Array of integer triples describing the edit */ | |
| 719 | + int i, /* Index in aC[] of current location */ | |
| 720 | + int sz, /* Lines of A that have been skipped */ | |
| 721 | + unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */ | |
| 722 | +){ | |
| 723 | + *pLn = 0; | |
| 724 | + while( sz>0 ){ | |
| 725 | + if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; | |
| 726 | + if( aC[i]>=sz ){ | |
| 727 | + aC[i] -= sz; | |
| 728 | + *pLn += sz; | |
| 729 | + break; | |
| 730 | + } | |
| 731 | + *pLn += aC[i]; | |
| 732 | + *pLn += aC[i+2]; | |
| 733 | + sz -= aC[i] + aC[i+1]; | |
| 734 | + i += 3; | |
| 735 | + } | |
| 736 | + return i; | |
| 737 | +} | |
| 191 | 738 | |
| 192 | 739 | /* |
| 193 | 740 | ** Do a three-way merge. Initialize pOut to contain the result. |
| 194 | 741 | ** |
| 195 | 742 | ** The merge is an edit against pV2. Both pV1 and pV2 have a |
| @@ -199,156 +746,113 @@ | ||
| 199 | 746 | ** The return is 0 upon complete success. If any input file is binary, |
| 200 | 747 | ** -1 is returned and pOut is unmodified. If there are merge |
| 201 | 748 | ** conflicts, the merge proceeds as best as it can and the number |
| 202 | 749 | ** of conflicts is returns |
| 203 | 750 | */ |
| 204 | -static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ | |
| 751 | +int merge_three_blobs(MergeBuilder *p){ | |
| 205 | 752 | int *aC1; /* Changes from pPivot to pV1 */ |
| 206 | 753 | int *aC2; /* Changes from pPivot to pV2 */ |
| 207 | 754 | int i1, i2; /* Index into aC1[] and aC2[] */ |
| 208 | 755 | int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ |
| 209 | 756 | int limit1, limit2; /* Sizes of aC1[] and aC2[] */ |
| 210 | 757 | int nConflict = 0; /* Number of merge conflicts seen so far */ |
| 211 | - int useCrLf = 0; | |
| 212 | - int ln1, ln2, lnPivot; /* Line numbers for all files */ | |
| 213 | 758 | DiffConfig DCfg; |
| 214 | 759 | |
| 215 | - blob_zero(pOut); /* Merge results stored in pOut */ | |
| 216 | - | |
| 217 | - /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM), | |
| 218 | - ** keep it in the output. This should be secure enough not to cause | |
| 219 | - ** unintended changes to the merged file and consistent with what | |
| 220 | - ** users are using in their source files. | |
| 221 | - */ | |
| 222 | - if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){ | |
| 223 | - blob_append(pOut, (char*)get_utf8_bom(0), -1); | |
| 224 | - } | |
| 225 | - | |
| 226 | - /* Check once to see if both pV1 and pV2 contains CR/LF endings. | |
| 227 | - ** If true, CR/LF pair will be used later to append the | |
| 228 | - ** boundary markers for merge conflicts. | |
| 229 | - */ | |
| 230 | - if( contains_crlf(pV1) && contains_crlf(pV2) ){ | |
| 231 | - useCrLf = 1; | |
| 232 | - } | |
| 233 | - | |
| 234 | 760 | /* Compute the edits that occur from pPivot => pV1 (into aC1) |
| 235 | 761 | ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is |
| 236 | 762 | ** an array of integer triples. Within each triple, the first integer |
| 237 | 763 | ** is the number of lines of text to copy directly from the pivot, |
| 238 | 764 | ** the second integer is the number of lines of text to omit from the |
| 239 | 765 | ** pivot, and the third integer is the number of lines of text that are |
| 240 | 766 | ** inserted. The edit array ends with a triple of 0,0,0. |
| 241 | 767 | */ |
| 242 | 768 | diff_config_init(&DCfg, 0); |
| 243 | - aC1 = text_diff(pPivot, pV1, 0, &DCfg); | |
| 244 | - aC2 = text_diff(pPivot, pV2, 0, &DCfg); | |
| 769 | + DCfg.diffFlags = p->diffFlags; | |
| 770 | + aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg); | |
| 771 | + aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg); | |
| 245 | 772 | if( aC1==0 || aC2==0 ){ |
| 246 | 773 | free(aC1); |
| 247 | 774 | free(aC2); |
| 248 | 775 | return -1; |
| 249 | 776 | } |
| 250 | 777 | |
| 251 | - blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */ | |
| 252 | - blob_rewind(pV2); | |
| 253 | - blob_rewind(pPivot); | |
| 778 | + blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */ | |
| 779 | + blob_rewind(p->pV2); | |
| 780 | + blob_rewind(p->pPivot); | |
| 254 | 781 | |
| 255 | 782 | /* Determine the length of the aC1[] and aC2[] change vectors */ |
| 256 | - for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){} | |
| 783 | + p->mxPivot = 0; | |
| 784 | + p->mxV1 = 0; | |
| 785 | + for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){ | |
| 786 | + p->mxPivot += aC1[i1] + aC1[i1+1]; | |
| 787 | + p->mxV1 += aC1[i1] + aC1[i1+2]; | |
| 788 | + } | |
| 257 | 789 | limit1 = i1; |
| 258 | - for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){} | |
| 790 | + p->mxV2 = 0; | |
| 791 | + for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){ | |
| 792 | + p->mxV2 += aC2[i2] + aC2[i2+2]; | |
| 793 | + } | |
| 259 | 794 | limit2 = i2; |
| 260 | 795 | |
| 261 | - DEBUG( | |
| 262 | - for(i1=0; i1<limit1; i1+=3){ | |
| 263 | - printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]); | |
| 264 | - } | |
| 265 | - for(i2=0; i2<limit2; i2+=3){ | |
| 266 | - printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]); | |
| 267 | - } | |
| 268 | - ) | |
| 796 | + /* Output header text and do any other required initialization */ | |
| 797 | + p->xStart(p); | |
| 269 | 798 | |
| 270 | 799 | /* Loop over the two edit vectors and use them to compute merged text |
| 271 | 800 | ** which is written into pOut. i1 and i2 are multiples of 3 which are |
| 272 | 801 | ** indices into aC1[] and aC2[] to the edit triple currently being |
| 273 | 802 | ** processed |
| 274 | 803 | */ |
| 275 | 804 | i1 = i2 = 0; |
| 276 | - ln1 = ln2 = lnPivot = 1; | |
| 277 | 805 | while( i1<limit1 && i2<limit2 ){ |
| 278 | - DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n", | |
| 279 | - i1/3, aC1[i1], aC1[i1+1], aC1[i1+2], | |
| 280 | - i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); ) | |
| 281 | - | |
| 282 | 806 | if( aC1[i1]>0 && aC2[i2]>0 ){ |
| 283 | 807 | /* Output text that is unchanged in both V1 and V2 */ |
| 284 | 808 | nCpy = min(aC1[i1], aC2[i2]); |
| 285 | - DEBUG( printf("COPY %d\n", nCpy); ) | |
| 286 | - blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy; | |
| 287 | - blob_copy_lines(0, pV1, nCpy); ln1 += nCpy; | |
| 288 | - blob_copy_lines(0, pV2, nCpy); ln2 += nCpy; | |
| 809 | + p->xSame(p, nCpy); | |
| 289 | 810 | aC1[i1] -= nCpy; |
| 290 | 811 | aC2[i2] -= nCpy; |
| 291 | 812 | }else |
| 292 | 813 | if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ |
| 293 | 814 | /* Output edits to V2 that occurs within unchanged regions of V1 */ |
| 294 | 815 | nDel = aC2[i2+1]; |
| 295 | 816 | nIns = aC2[i2+2]; |
| 296 | - DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); ) | |
| 297 | - blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; | |
| 298 | - blob_copy_lines(0, pV1, nDel); ln1 += nDel; | |
| 299 | - blob_copy_lines(pOut, pV2, nIns); ln2 += nIns; | |
| 817 | + p->xChngV2(p, nDel, nIns); | |
| 300 | 818 | aC1[i1] -= nDel; |
| 301 | 819 | i2 += 3; |
| 302 | 820 | }else |
| 303 | 821 | if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ |
| 304 | 822 | /* Output edits to V1 that occur within unchanged regions of V2 */ |
| 305 | 823 | nDel = aC1[i1+1]; |
| 306 | 824 | nIns = aC1[i1+2]; |
| 307 | - DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); ) | |
| 308 | - blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; | |
| 309 | - blob_copy_lines(0, pV2, nDel); ln2 += nDel; | |
| 310 | - blob_copy_lines(pOut, pV1, nIns); ln1 += nIns; | |
| 825 | + p->xChngV1(p, nDel, nIns); | |
| 311 | 826 | aC2[i2] -= nDel; |
| 312 | 827 | i1 += 3; |
| 313 | 828 | }else |
| 314 | - if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){ | |
| 829 | + if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){ | |
| 315 | 830 | /* Output edits that are identical in both V1 and V2. */ |
| 316 | 831 | assert( aC1[i1]==0 ); |
| 317 | 832 | nDel = aC1[i1+1]; |
| 318 | 833 | nIns = aC1[i1+2]; |
| 319 | - DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); ) | |
| 320 | - blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; | |
| 321 | - blob_copy_lines(pOut, pV1, nIns); ln1 += nIns; | |
| 322 | - blob_copy_lines(0, pV2, nIns); ln2 += nIns; | |
| 834 | + p->xChngBoth(p, nDel, nIns); | |
| 323 | 835 | i1 += 3; |
| 324 | 836 | i2 += 3; |
| 325 | 837 | }else |
| 326 | 838 | { |
| 327 | 839 | /* We have found a region where different edits to V1 and V2 overlap. |
| 328 | 840 | ** This is a merge conflict. Find the size of the conflict, then |
| 329 | 841 | ** output both possible edits separated by distinctive marks. |
| 330 | 842 | */ |
| 331 | - int sz = 1; /* Size of the conflict in lines */ | |
| 843 | + unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */ | |
| 844 | + unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */ | |
| 332 | 845 | nConflict++; |
| 333 | - while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ | |
| 846 | + while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){ | |
| 334 | 847 | sz++; |
| 335 | 848 | } |
| 336 | - DEBUG( printf("CONFLICT %d\n", sz); ) | |
| 337 | - | |
| 338 | - append_merge_mark(pOut, 0, ln1, useCrLf); | |
| 339 | - i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1); | |
| 340 | - | |
| 341 | - append_merge_mark(pOut, 1, lnPivot, useCrLf); | |
| 342 | - blob_copy_lines(pOut, pPivot, sz); lnPivot += sz; | |
| 343 | - | |
| 344 | - append_merge_mark(pOut, 2, ln2, useCrLf); | |
| 345 | - i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2); | |
| 346 | - | |
| 347 | - append_merge_mark(pOut, 3, -1, useCrLf); | |
| 348 | - } | |
| 349 | - | |
| 849 | + i1 = skip_conflict(aC1, i1, sz, &nV1); | |
| 850 | + i2 = skip_conflict(aC2, i2, sz, &nV2); | |
| 851 | + p->xConflict(p, sz, nV1, nV2); | |
| 852 | + } | |
| 853 | + | |
| 350 | 854 | /* If we are finished with an edit triple, advance to the next |
| 351 | 855 | ** triple. |
| 352 | 856 | */ |
| 353 | 857 | if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3; |
| 354 | 858 | if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3; |
| @@ -356,20 +860,18 @@ | ||
| 356 | 860 | |
| 357 | 861 | /* When one of the two edit vectors reaches its end, there might still |
| 358 | 862 | ** be an insert in the other edit vector. Output this remaining |
| 359 | 863 | ** insert. |
| 360 | 864 | */ |
| 361 | - DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n", | |
| 362 | - i1/3, aC1[i1], aC1[i1+1], aC1[i1+2], | |
| 363 | - i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); ) | |
| 364 | 865 | if( i1<limit1 && aC1[i1+2]>0 ){ |
| 365 | - DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); ) | |
| 366 | - blob_copy_lines(pOut, pV1, aC1[i1+2]); | |
| 866 | + p->xChngV1(p, 0, aC1[i1+2]); | |
| 367 | 867 | }else if( i2<limit2 && aC2[i2+2]>0 ){ |
| 368 | - DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); ) | |
| 369 | - blob_copy_lines(pOut, pV2, aC2[i2+2]); | |
| 868 | + p->xChngV2(p, 0, aC2[i2+2]); | |
| 370 | 869 | } |
| 870 | + | |
| 871 | + /* Output footer text */ | |
| 872 | + p->xEnd(p); | |
| 371 | 873 | |
| 372 | 874 | free(aC1); |
| 373 | 875 | free(aC2); |
| 374 | 876 | return nConflict; |
| 375 | 877 | } |
| @@ -384,11 +886,12 @@ | ||
| 384 | 886 | const char *z = blob_buffer(p); |
| 385 | 887 | int n = blob_size(p) - len + 1; |
| 386 | 888 | assert( len==(int)strlen(mergeMarker[1]) ); |
| 387 | 889 | assert( len==(int)strlen(mergeMarker[2]) ); |
| 388 | 890 | assert( len==(int)strlen(mergeMarker[3]) ); |
| 389 | - assert( count(mergeMarker)==4 ); | |
| 891 | + assert( len==(int)strlen(mergeMarker[4]) ); | |
| 892 | + assert( count(mergeMarker)==5 ); | |
| 390 | 893 | for(i=0; i<n; ){ |
| 391 | 894 | for(j=0; j<4; j++){ |
| 392 | 895 | if( (memcmp(&z[i], mergeMarker[j], len)==0) ){ |
| 393 | 896 | return 1; |
| 394 | 897 | } |
| @@ -408,18 +911,106 @@ | ||
| 408 | 911 | blob_read_from_file(&file, zFullpath, ExtFILE); |
| 409 | 912 | rc = contains_merge_marker(&file); |
| 410 | 913 | blob_reset(&file); |
| 411 | 914 | return rc; |
| 412 | 915 | } |
| 916 | + | |
| 917 | +/* | |
| 918 | +** Show merge output in a Tcl/Tk window, in response to the --tk option | |
| 919 | +** to the "merge" or "3-way-merge" command. | |
| 920 | +** | |
| 921 | +** If fossil has direct access to a Tcl interpreter (either loaded | |
| 922 | +** dynamically through stubs or linked in statically), we can use it | |
| 923 | +** directly. Otherwise: | |
| 924 | +** (1) Write the Tcl/Tk script used for rendering into a temp file. | |
| 925 | +** (2) Invoke "tclsh" on the temp file using fossil_system(). | |
| 926 | +** (3) Delete the temp file. | |
| 927 | +*/ | |
| 928 | +void merge_tk(const char *zSubCmd, int firstArg){ | |
| 929 | + int i; | |
| 930 | + Blob script; | |
| 931 | + const char *zTempFile = 0; | |
| 932 | + char *zCmd; | |
| 933 | + const char *zTclsh; | |
| 934 | + const char *zCnt; | |
| 935 | + int bDarkMode = find_option("dark",0,0)!=0; | |
| 936 | + int nContext; | |
| 937 | + zCnt = find_option("context", "c", 1); | |
| 938 | + if( zCnt==0 ){ | |
| 939 | + nContext = 6; | |
| 940 | + }else{ | |
| 941 | + nContext = atoi(zCnt); | |
| 942 | + if( nContext<0 ) nContext = 0xfffffff; | |
| 943 | + } | |
| 944 | + blob_zero(&script); | |
| 945 | + blob_appendf(&script, "set ncontext %d\n", nContext); | |
| 946 | + blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl", | |
| 947 | + g.nameOfExe, zSubCmd); | |
| 948 | + find_option("tcl",0,0); | |
| 949 | + find_option("debug",0,0); | |
| 950 | + zTclsh = find_option("tclsh",0,1); | |
| 951 | + if( zTclsh==0 ){ | |
| 952 | + zTclsh = db_get("tclsh",0); | |
| 953 | + } | |
| 954 | + /* The undocumented --script FILENAME option causes the Tk script to | |
| 955 | + ** be written into the FILENAME instead of being run. This is used | |
| 956 | + ** for testing and debugging. */ | |
| 957 | + zTempFile = find_option("script",0,1); | |
| 958 | + verify_all_options(); | |
| 959 | + | |
| 960 | + if( (g.argc - firstArg)!=3 ){ | |
| 961 | + fossil_fatal("Requires 3 filename arguments"); | |
| 962 | + } | |
| 963 | + | |
| 964 | + for(i=firstArg; i<g.argc; i++){ | |
| 965 | + const char *z = g.argv[i]; | |
| 966 | + if( sqlite3_strglob("*}*",z) ){ | |
| 967 | + blob_appendf(&script, " {%/}", z); | |
| 968 | + }else{ | |
| 969 | + int j; | |
| 970 | + blob_append(&script, " ", 1); | |
| 971 | + for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]); | |
| 972 | + } | |
| 973 | + } | |
| 974 | + blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode); | |
| 975 | + blob_appendf(&script, "%s", builtin_file("merge.tcl", 0)); | |
| 976 | + if( zTempFile ){ | |
| 977 | + blob_write_to_file(&script, zTempFile); | |
| 978 | + fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile); | |
| 979 | + }else{ | |
| 980 | +#if defined(FOSSIL_ENABLE_TCL) | |
| 981 | + Th_FossilInit(TH_INIT_DEFAULT); | |
| 982 | + if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), | |
| 983 | + blob_size(&script), 1, 1, 0)==TCL_OK ){ | |
| 984 | + blob_reset(&script); | |
| 985 | + return; | |
| 986 | + } | |
| 987 | + /* | |
| 988 | + * If evaluation of the Tcl script fails, the reason may be that Tk | |
| 989 | + * could not be found by the loaded Tcl, or that Tcl cannot be loaded | |
| 990 | + * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback | |
| 991 | + * to using the external "tclsh", if available. | |
| 992 | + */ | |
| 993 | +#endif | |
| 994 | + zTempFile = write_blob_to_temp_file(&script); | |
| 995 | + zCmd = mprintf("%$ %$", zTclsh, zTempFile); | |
| 996 | + fossil_system(zCmd); | |
| 997 | + file_delete(zTempFile); | |
| 998 | + fossil_free(zCmd); | |
| 999 | + } | |
| 1000 | + blob_reset(&script); | |
| 1001 | +} | |
| 1002 | + | |
| 413 | 1003 | |
| 414 | 1004 | /* |
| 415 | 1005 | ** COMMAND: 3-way-merge* |
| 416 | 1006 | ** |
| 417 | -** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED | |
| 1007 | +** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED] | |
| 418 | 1008 | ** |
| 419 | 1009 | ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated |
| 420 | -** as output. | |
| 1010 | +** as output. If no MERGED file is specified, output is sent to | |
| 1011 | +** stdout. | |
| 421 | 1012 | ** |
| 422 | 1013 | ** BASELINE is a common ancestor of two files V1 and V2 that have diverging |
| 423 | 1014 | ** edits. The generated output file MERGED is the combination of all |
| 424 | 1015 | ** changes in both V1 and V2. |
| 425 | 1016 | ** |
| @@ -436,38 +1027,75 @@ | ||
| 436 | 1027 | ** cp Xup.c Xbase.c |
| 437 | 1028 | ** # Verify that everything still works |
| 438 | 1029 | ** fossil commit |
| 439 | 1030 | ** |
| 440 | 1031 | */ |
| 441 | -void delta_3waymerge_cmd(void){ | |
| 442 | - Blob pivot, v1, v2, merged; | |
| 1032 | +void merge_3way_cmd(void){ | |
| 1033 | + MergeBuilder s; | |
| 443 | 1034 | int nConflict; |
| 1035 | + Blob pivot, v1, v2, out; | |
| 1036 | + int noWarn = 0; | |
| 1037 | + const char *zCnt; | |
| 1038 | + | |
| 1039 | + if( find_option("tk", 0, 0)!=0 ){ | |
| 1040 | + merge_tk("3-way-merge", 2); | |
| 1041 | + return; | |
| 1042 | + } | |
| 1043 | + mergebuilder_init_text(&s); | |
| 1044 | + if( find_option("debug", 0, 0) ){ | |
| 1045 | + mergebuilder_init(&s); | |
| 1046 | + } | |
| 1047 | + if( find_option("tcl", 0, 0) ){ | |
| 1048 | + mergebuilder_init_tcl(&s); | |
| 1049 | + noWarn = 1; | |
| 1050 | + } | |
| 1051 | + zCnt = find_option("context", "c", 1); | |
| 1052 | + if( zCnt ){ | |
| 1053 | + s.nContext = atoi(zCnt); | |
| 1054 | + if( s.nContext<0 ) s.nContext = 0xfffffff; | |
| 1055 | + }else{ | |
| 1056 | + s.nContext = 6; | |
| 1057 | + } | |
| 1058 | + blob_zero(&pivot); s.pPivot = &pivot; | |
| 1059 | + blob_zero(&v1); s.pV1 = &v1; | |
| 1060 | + blob_zero(&v2); s.pV2 = &v2; | |
| 1061 | + blob_zero(&out); s.pOut = &out; | |
| 444 | 1062 | |
| 445 | 1063 | /* We should be done with options.. */ |
| 446 | 1064 | verify_all_options(); |
| 447 | 1065 | |
| 448 | - if( g.argc!=6 ){ | |
| 449 | - usage("PIVOT V1 V2 MERGED"); | |
| 1066 | + if( g.argc!=6 && g.argc!=5 ){ | |
| 1067 | + usage("[OPTIONS] PIVOT V1 V2 [MERGED]"); | |
| 450 | 1068 | } |
| 451 | - if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){ | |
| 1069 | + s.zPivot = file_tail(g.argv[2]); | |
| 1070 | + s.zV1 = file_tail(g.argv[3]); | |
| 1071 | + s.zV2 = file_tail(g.argv[4]); | |
| 1072 | + if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){ | |
| 452 | 1073 | fossil_fatal("cannot read %s", g.argv[2]); |
| 453 | 1074 | } |
| 454 | - if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){ | |
| 1075 | + if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){ | |
| 455 | 1076 | fossil_fatal("cannot read %s", g.argv[3]); |
| 456 | 1077 | } |
| 457 | - if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){ | |
| 1078 | + if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){ | |
| 458 | 1079 | fossil_fatal("cannot read %s", g.argv[4]); |
| 459 | 1080 | } |
| 460 | - nConflict = blob_merge(&pivot, &v1, &v2, &merged); | |
| 461 | - if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){ | |
| 462 | - fossil_fatal("cannot write %s", g.argv[4]); | |
| 1081 | + nConflict = merge_three_blobs(&s); | |
| 1082 | + if( g.argc==6 ){ | |
| 1083 | + s.zOut = file_tail(g.argv[5]); | |
| 1084 | + blob_write_to_file(s.pOut, g.argv[5]); | |
| 1085 | + }else{ | |
| 1086 | + s.zOut = "(Merge Result)"; | |
| 1087 | + blob_write_to_file(s.pOut, "-"); | |
| 463 | 1088 | } |
| 1089 | + s.xDestroy(&s); | |
| 464 | 1090 | blob_reset(&pivot); |
| 465 | 1091 | blob_reset(&v1); |
| 466 | 1092 | blob_reset(&v2); |
| 467 | - blob_reset(&merged); | |
| 468 | - if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict); | |
| 1093 | + blob_reset(&out); | |
| 1094 | + if( nConflict>0 && !noWarn ){ | |
| 1095 | + fossil_warning("WARNING: %d merge conflicts", nConflict); | |
| 1096 | + } | |
| 469 | 1097 | } |
| 470 | 1098 | |
| 471 | 1099 | /* |
| 472 | 1100 | ** aSubst is an array of string pairs. The first element of each pair is |
| 473 | 1101 | ** a string that begins with %. The second element is a replacement for that |
| @@ -505,32 +1133,32 @@ | ||
| 505 | 1133 | |
| 506 | 1134 | #if INTERFACE |
| 507 | 1135 | /* |
| 508 | 1136 | ** Flags to the 3-way merger |
| 509 | 1137 | */ |
| 510 | -#define MERGE_DRYRUN 0x0001 | |
| 1138 | +#define MERGE_DRYRUN 0x0001 | |
| 511 | 1139 | /* |
| 512 | 1140 | ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain |
| 513 | 1141 | ** its temporary files on error. By default they are removed after the |
| 514 | 1142 | ** merge, regardless of success or failure. |
| 515 | 1143 | */ |
| 516 | -#define MERGE_KEEP_FILES 0x0002 | |
| 1144 | +#define MERGE_KEEP_FILES 0x0002 | |
| 517 | 1145 | #endif |
| 518 | 1146 | |
| 519 | 1147 | |
| 520 | 1148 | /* |
| 521 | -** This routine is a wrapper around blob_merge() with the following | |
| 1149 | +** This routine is a wrapper around merge_three_blobs() with the following | |
| 522 | 1150 | ** enhancements: |
| 523 | 1151 | ** |
| 524 | 1152 | ** (1) If the merge-command is defined, then use the external merging |
| 525 | 1153 | ** program specified instead of the built-in blob-merge to do the |
| 526 | 1154 | ** merging. Panic if the external merger fails. |
| 527 | 1155 | ** ** Not currently implemented ** |
| 528 | 1156 | ** |
| 529 | 1157 | ** (2) If gmerge-command is defined and there are merge conflicts in |
| 530 | -** blob_merge() then invoke the external graphical merger to resolve | |
| 531 | -** the conflicts. | |
| 1158 | +** merge_three_blobs() then invoke the external graphical merger | |
| 1159 | +** to resolve the conflicts. | |
| 532 | 1160 | ** |
| 533 | 1161 | ** (3) If a merge conflict occurs and gmerge-command is not defined, |
| 534 | 1162 | ** then write the pivot, original, and merge-in files to the |
| 535 | 1163 | ** filesystem. |
| 536 | 1164 | */ |
| @@ -539,30 +1167,37 @@ | ||
| 539 | 1167 | const char *zV1, /* Name of file for version merging into (mine) */ |
| 540 | 1168 | Blob *pV2, /* Version merging from (yours) */ |
| 541 | 1169 | Blob *pOut, /* Output written here */ |
| 542 | 1170 | unsigned mergeFlags /* Flags that control operation */ |
| 543 | 1171 | ){ |
| 544 | - Blob v1; /* Content of zV1 */ | |
| 545 | - int rc; /* Return code of subroutines and this routine */ | |
| 1172 | + Blob v1; /* Content of zV1 */ | |
| 1173 | + int rc; /* Return code of subroutines and this routine */ | |
| 546 | 1174 | const char *zGMerge; /* Name of the gmerge command */ |
| 1175 | + MergeBuilder s; /* The merge state */ | |
| 547 | 1176 | |
| 548 | - blob_read_from_file(&v1, zV1, ExtFILE); | |
| 549 | - rc = blob_merge(pPivot, &v1, pV2, pOut); | |
| 1177 | + mergebuilder_init_text(&s); | |
| 1178 | + s.pPivot = pPivot; | |
| 1179 | + s.pV1 = &v1; | |
| 1180 | + s.pV2 = pV2; | |
| 1181 | + blob_zero(pOut); | |
| 1182 | + s.pOut = pOut; | |
| 1183 | + blob_read_from_file(s.pV1, zV1, ExtFILE); | |
| 1184 | + rc = merge_three_blobs(&s); | |
| 550 | 1185 | zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); |
| 551 | 1186 | if( (mergeFlags & MERGE_DRYRUN)==0 |
| 552 | 1187 | && ((zGMerge!=0 && zGMerge[0]!=0) |
| 553 | 1188 | || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ |
| 554 | 1189 | char *zPivot; /* Name of the pivot file */ |
| 555 | 1190 | char *zOrig; /* Name of the original content file */ |
| 556 | 1191 | char *zOther; /* Name of the merge file */ |
| 557 | 1192 | |
| 558 | 1193 | zPivot = file_newname(zV1, "baseline", 1); |
| 559 | - blob_write_to_file(pPivot, zPivot); | |
| 1194 | + blob_write_to_file(s.pPivot, zPivot); | |
| 560 | 1195 | zOrig = file_newname(zV1, "original", 1); |
| 561 | - blob_write_to_file(&v1, zOrig); | |
| 1196 | + blob_write_to_file(s.pV1, zOrig); | |
| 562 | 1197 | zOther = file_newname(zV1, "merge", 1); |
| 563 | - blob_write_to_file(pV2, zOther); | |
| 1198 | + blob_write_to_file(s.pV2, zOther); | |
| 564 | 1199 | if( rc>0 ){ |
| 565 | 1200 | if( zGMerge && zGMerge[0] ){ |
| 566 | 1201 | char *zOut; /* Temporary output file */ |
| 567 | 1202 | char *zCmd; /* Command to invoke */ |
| 568 | 1203 | const char *azSubst[8]; /* Strings to be substituted */ |
| @@ -590,7 +1225,8 @@ | ||
| 590 | 1225 | fossil_free(zPivot); |
| 591 | 1226 | fossil_free(zOrig); |
| 592 | 1227 | fossil_free(zOther); |
| 593 | 1228 | } |
| 594 | 1229 | blob_reset(&v1); |
| 1230 | + s.xDestroy(&s); | |
| 595 | 1231 | return rc; |
| 596 | 1232 | } |
| 597 | 1233 |
| --- src/merge3.c | |
| +++ src/merge3.c | |
| @@ -75,75 +75,17 @@ | |
| 75 | if( aC1[2]!=aC2[2] ) return 0; |
| 76 | if( sameLines(pV1, pV2, aC1[2]) ) return 1; |
| 77 | return 0; |
| 78 | } |
| 79 | |
| 80 | /* |
| 81 | ** The aC[] array contains triples of integers. Within each triple, the |
| 82 | ** elements are: |
| 83 | ** |
| 84 | ** (0) The number of lines to copy |
| 85 | ** (1) The number of lines to delete |
| 86 | ** (2) The number of liens to insert |
| 87 | ** |
| 88 | ** Suppose we want to advance over sz lines of the original file. This routine |
| 89 | ** returns true if that advance would land us on a copy operation. It |
| 90 | ** returns false if the advance would end on a delete. |
| 91 | */ |
| 92 | static int ends_at_CPY(int *aC, int sz){ |
| 93 | while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ |
| 94 | if( aC[0]>=sz ) return 1; |
| 95 | sz -= aC[0]; |
| 96 | if( aC[1]>sz ) return 0; |
| 97 | sz -= aC[1]; |
| 98 | aC += 3; |
| 99 | } |
| 100 | return 1; |
| 101 | } |
| 102 | |
| 103 | /* |
| 104 | ** pSrc contains an edited file where aC[] describes the edit. Part of |
| 105 | ** pSrc has already been output. This routine outputs additional lines |
| 106 | ** of pSrc - lines that correspond to the next sz lines of the original |
| 107 | ** unedited file. |
| 108 | ** |
| 109 | ** Note that sz counts the number of lines of text in the original file. |
| 110 | ** But text is output from the edited file. So the number of lines transfer |
| 111 | ** to pOut might be different from sz. Fewer lines appear in pOut if there |
| 112 | ** are deletes. More lines appear if there are inserts. |
| 113 | ** |
| 114 | ** The aC[] array is updated and the new index into aC[] is returned. |
| 115 | */ |
| 116 | static int output_one_side( |
| 117 | Blob *pOut, /* Write to this blob */ |
| 118 | Blob *pSrc, /* The edited file that is to be copied to pOut */ |
| 119 | int *aC, /* Array of integer triples describing the edit */ |
| 120 | int i, /* Index in aC[] of current location in pSrc */ |
| 121 | int sz, /* Number of lines in unedited source to output */ |
| 122 | int *pLn /* Line number counter */ |
| 123 | ){ |
| 124 | while( sz>0 ){ |
| 125 | if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; |
| 126 | if( aC[i]>=sz ){ |
| 127 | blob_copy_lines(pOut, pSrc, sz); *pLn += sz; |
| 128 | aC[i] -= sz; |
| 129 | break; |
| 130 | } |
| 131 | blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i]; |
| 132 | blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2]; |
| 133 | sz -= aC[i] + aC[i+1]; |
| 134 | i += 3; |
| 135 | } |
| 136 | return i; |
| 137 | } |
| 138 | |
| 139 | /* |
| 140 | ** Text of boundary markers for merge conflicts. |
| 141 | */ |
| 142 | static const char *const mergeMarker[] = { |
| 143 | /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ |
| 144 | "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<", |
| 145 | "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||", |
| 146 | "======= MERGED IN content follows ===============================", |
| 147 | ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" |
| 148 | }; |
| 149 | |
| @@ -186,10 +128,615 @@ | |
| 186 | ensure_line_end(pOut, useCrLf); |
| 187 | blob_append(pOut, mergeMarker[iMark], -1); |
| 188 | if( ln>0 ) blob_appendf(pOut, " (line %d)", ln); |
| 189 | ensure_line_end(pOut, useCrLf); |
| 190 | } |
| 191 | |
| 192 | /* |
| 193 | ** Do a three-way merge. Initialize pOut to contain the result. |
| 194 | ** |
| 195 | ** The merge is an edit against pV2. Both pV1 and pV2 have a |
| @@ -199,156 +746,113 @@ | |
| 199 | ** The return is 0 upon complete success. If any input file is binary, |
| 200 | ** -1 is returned and pOut is unmodified. If there are merge |
| 201 | ** conflicts, the merge proceeds as best as it can and the number |
| 202 | ** of conflicts is returns |
| 203 | */ |
| 204 | static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ |
| 205 | int *aC1; /* Changes from pPivot to pV1 */ |
| 206 | int *aC2; /* Changes from pPivot to pV2 */ |
| 207 | int i1, i2; /* Index into aC1[] and aC2[] */ |
| 208 | int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ |
| 209 | int limit1, limit2; /* Sizes of aC1[] and aC2[] */ |
| 210 | int nConflict = 0; /* Number of merge conflicts seen so far */ |
| 211 | int useCrLf = 0; |
| 212 | int ln1, ln2, lnPivot; /* Line numbers for all files */ |
| 213 | DiffConfig DCfg; |
| 214 | |
| 215 | blob_zero(pOut); /* Merge results stored in pOut */ |
| 216 | |
| 217 | /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM), |
| 218 | ** keep it in the output. This should be secure enough not to cause |
| 219 | ** unintended changes to the merged file and consistent with what |
| 220 | ** users are using in their source files. |
| 221 | */ |
| 222 | if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){ |
| 223 | blob_append(pOut, (char*)get_utf8_bom(0), -1); |
| 224 | } |
| 225 | |
| 226 | /* Check once to see if both pV1 and pV2 contains CR/LF endings. |
| 227 | ** If true, CR/LF pair will be used later to append the |
| 228 | ** boundary markers for merge conflicts. |
| 229 | */ |
| 230 | if( contains_crlf(pV1) && contains_crlf(pV2) ){ |
| 231 | useCrLf = 1; |
| 232 | } |
| 233 | |
| 234 | /* Compute the edits that occur from pPivot => pV1 (into aC1) |
| 235 | ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is |
| 236 | ** an array of integer triples. Within each triple, the first integer |
| 237 | ** is the number of lines of text to copy directly from the pivot, |
| 238 | ** the second integer is the number of lines of text to omit from the |
| 239 | ** pivot, and the third integer is the number of lines of text that are |
| 240 | ** inserted. The edit array ends with a triple of 0,0,0. |
| 241 | */ |
| 242 | diff_config_init(&DCfg, 0); |
| 243 | aC1 = text_diff(pPivot, pV1, 0, &DCfg); |
| 244 | aC2 = text_diff(pPivot, pV2, 0, &DCfg); |
| 245 | if( aC1==0 || aC2==0 ){ |
| 246 | free(aC1); |
| 247 | free(aC2); |
| 248 | return -1; |
| 249 | } |
| 250 | |
| 251 | blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */ |
| 252 | blob_rewind(pV2); |
| 253 | blob_rewind(pPivot); |
| 254 | |
| 255 | /* Determine the length of the aC1[] and aC2[] change vectors */ |
| 256 | for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){} |
| 257 | limit1 = i1; |
| 258 | for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){} |
| 259 | limit2 = i2; |
| 260 | |
| 261 | DEBUG( |
| 262 | for(i1=0; i1<limit1; i1+=3){ |
| 263 | printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]); |
| 264 | } |
| 265 | for(i2=0; i2<limit2; i2+=3){ |
| 266 | printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]); |
| 267 | } |
| 268 | ) |
| 269 | |
| 270 | /* Loop over the two edit vectors and use them to compute merged text |
| 271 | ** which is written into pOut. i1 and i2 are multiples of 3 which are |
| 272 | ** indices into aC1[] and aC2[] to the edit triple currently being |
| 273 | ** processed |
| 274 | */ |
| 275 | i1 = i2 = 0; |
| 276 | ln1 = ln2 = lnPivot = 1; |
| 277 | while( i1<limit1 && i2<limit2 ){ |
| 278 | DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n", |
| 279 | i1/3, aC1[i1], aC1[i1+1], aC1[i1+2], |
| 280 | i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); ) |
| 281 | |
| 282 | if( aC1[i1]>0 && aC2[i2]>0 ){ |
| 283 | /* Output text that is unchanged in both V1 and V2 */ |
| 284 | nCpy = min(aC1[i1], aC2[i2]); |
| 285 | DEBUG( printf("COPY %d\n", nCpy); ) |
| 286 | blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy; |
| 287 | blob_copy_lines(0, pV1, nCpy); ln1 += nCpy; |
| 288 | blob_copy_lines(0, pV2, nCpy); ln2 += nCpy; |
| 289 | aC1[i1] -= nCpy; |
| 290 | aC2[i2] -= nCpy; |
| 291 | }else |
| 292 | if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ |
| 293 | /* Output edits to V2 that occurs within unchanged regions of V1 */ |
| 294 | nDel = aC2[i2+1]; |
| 295 | nIns = aC2[i2+2]; |
| 296 | DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); ) |
| 297 | blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; |
| 298 | blob_copy_lines(0, pV1, nDel); ln1 += nDel; |
| 299 | blob_copy_lines(pOut, pV2, nIns); ln2 += nIns; |
| 300 | aC1[i1] -= nDel; |
| 301 | i2 += 3; |
| 302 | }else |
| 303 | if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ |
| 304 | /* Output edits to V1 that occur within unchanged regions of V2 */ |
| 305 | nDel = aC1[i1+1]; |
| 306 | nIns = aC1[i1+2]; |
| 307 | DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); ) |
| 308 | blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; |
| 309 | blob_copy_lines(0, pV2, nDel); ln2 += nDel; |
| 310 | blob_copy_lines(pOut, pV1, nIns); ln1 += nIns; |
| 311 | aC2[i2] -= nDel; |
| 312 | i1 += 3; |
| 313 | }else |
| 314 | if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){ |
| 315 | /* Output edits that are identical in both V1 and V2. */ |
| 316 | assert( aC1[i1]==0 ); |
| 317 | nDel = aC1[i1+1]; |
| 318 | nIns = aC1[i1+2]; |
| 319 | DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); ) |
| 320 | blob_copy_lines(0, pPivot, nDel); lnPivot += nDel; |
| 321 | blob_copy_lines(pOut, pV1, nIns); ln1 += nIns; |
| 322 | blob_copy_lines(0, pV2, nIns); ln2 += nIns; |
| 323 | i1 += 3; |
| 324 | i2 += 3; |
| 325 | }else |
| 326 | { |
| 327 | /* We have found a region where different edits to V1 and V2 overlap. |
| 328 | ** This is a merge conflict. Find the size of the conflict, then |
| 329 | ** output both possible edits separated by distinctive marks. |
| 330 | */ |
| 331 | int sz = 1; /* Size of the conflict in lines */ |
| 332 | nConflict++; |
| 333 | while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ |
| 334 | sz++; |
| 335 | } |
| 336 | DEBUG( printf("CONFLICT %d\n", sz); ) |
| 337 | |
| 338 | append_merge_mark(pOut, 0, ln1, useCrLf); |
| 339 | i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1); |
| 340 | |
| 341 | append_merge_mark(pOut, 1, lnPivot, useCrLf); |
| 342 | blob_copy_lines(pOut, pPivot, sz); lnPivot += sz; |
| 343 | |
| 344 | append_merge_mark(pOut, 2, ln2, useCrLf); |
| 345 | i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2); |
| 346 | |
| 347 | append_merge_mark(pOut, 3, -1, useCrLf); |
| 348 | } |
| 349 | |
| 350 | /* If we are finished with an edit triple, advance to the next |
| 351 | ** triple. |
| 352 | */ |
| 353 | if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3; |
| 354 | if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3; |
| @@ -356,20 +860,18 @@ | |
| 356 | |
| 357 | /* When one of the two edit vectors reaches its end, there might still |
| 358 | ** be an insert in the other edit vector. Output this remaining |
| 359 | ** insert. |
| 360 | */ |
| 361 | DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n", |
| 362 | i1/3, aC1[i1], aC1[i1+1], aC1[i1+2], |
| 363 | i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); ) |
| 364 | if( i1<limit1 && aC1[i1+2]>0 ){ |
| 365 | DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); ) |
| 366 | blob_copy_lines(pOut, pV1, aC1[i1+2]); |
| 367 | }else if( i2<limit2 && aC2[i2+2]>0 ){ |
| 368 | DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); ) |
| 369 | blob_copy_lines(pOut, pV2, aC2[i2+2]); |
| 370 | } |
| 371 | |
| 372 | free(aC1); |
| 373 | free(aC2); |
| 374 | return nConflict; |
| 375 | } |
| @@ -384,11 +886,12 @@ | |
| 384 | const char *z = blob_buffer(p); |
| 385 | int n = blob_size(p) - len + 1; |
| 386 | assert( len==(int)strlen(mergeMarker[1]) ); |
| 387 | assert( len==(int)strlen(mergeMarker[2]) ); |
| 388 | assert( len==(int)strlen(mergeMarker[3]) ); |
| 389 | assert( count(mergeMarker)==4 ); |
| 390 | for(i=0; i<n; ){ |
| 391 | for(j=0; j<4; j++){ |
| 392 | if( (memcmp(&z[i], mergeMarker[j], len)==0) ){ |
| 393 | return 1; |
| 394 | } |
| @@ -408,18 +911,106 @@ | |
| 408 | blob_read_from_file(&file, zFullpath, ExtFILE); |
| 409 | rc = contains_merge_marker(&file); |
| 410 | blob_reset(&file); |
| 411 | return rc; |
| 412 | } |
| 413 | |
| 414 | /* |
| 415 | ** COMMAND: 3-way-merge* |
| 416 | ** |
| 417 | ** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED |
| 418 | ** |
| 419 | ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated |
| 420 | ** as output. |
| 421 | ** |
| 422 | ** BASELINE is a common ancestor of two files V1 and V2 that have diverging |
| 423 | ** edits. The generated output file MERGED is the combination of all |
| 424 | ** changes in both V1 and V2. |
| 425 | ** |
| @@ -436,38 +1027,75 @@ | |
| 436 | ** cp Xup.c Xbase.c |
| 437 | ** # Verify that everything still works |
| 438 | ** fossil commit |
| 439 | ** |
| 440 | */ |
| 441 | void delta_3waymerge_cmd(void){ |
| 442 | Blob pivot, v1, v2, merged; |
| 443 | int nConflict; |
| 444 | |
| 445 | /* We should be done with options.. */ |
| 446 | verify_all_options(); |
| 447 | |
| 448 | if( g.argc!=6 ){ |
| 449 | usage("PIVOT V1 V2 MERGED"); |
| 450 | } |
| 451 | if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){ |
| 452 | fossil_fatal("cannot read %s", g.argv[2]); |
| 453 | } |
| 454 | if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){ |
| 455 | fossil_fatal("cannot read %s", g.argv[3]); |
| 456 | } |
| 457 | if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){ |
| 458 | fossil_fatal("cannot read %s", g.argv[4]); |
| 459 | } |
| 460 | nConflict = blob_merge(&pivot, &v1, &v2, &merged); |
| 461 | if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){ |
| 462 | fossil_fatal("cannot write %s", g.argv[4]); |
| 463 | } |
| 464 | blob_reset(&pivot); |
| 465 | blob_reset(&v1); |
| 466 | blob_reset(&v2); |
| 467 | blob_reset(&merged); |
| 468 | if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict); |
| 469 | } |
| 470 | |
| 471 | /* |
| 472 | ** aSubst is an array of string pairs. The first element of each pair is |
| 473 | ** a string that begins with %. The second element is a replacement for that |
| @@ -505,32 +1133,32 @@ | |
| 505 | |
| 506 | #if INTERFACE |
| 507 | /* |
| 508 | ** Flags to the 3-way merger |
| 509 | */ |
| 510 | #define MERGE_DRYRUN 0x0001 |
| 511 | /* |
| 512 | ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain |
| 513 | ** its temporary files on error. By default they are removed after the |
| 514 | ** merge, regardless of success or failure. |
| 515 | */ |
| 516 | #define MERGE_KEEP_FILES 0x0002 |
| 517 | #endif |
| 518 | |
| 519 | |
| 520 | /* |
| 521 | ** This routine is a wrapper around blob_merge() with the following |
| 522 | ** enhancements: |
| 523 | ** |
| 524 | ** (1) If the merge-command is defined, then use the external merging |
| 525 | ** program specified instead of the built-in blob-merge to do the |
| 526 | ** merging. Panic if the external merger fails. |
| 527 | ** ** Not currently implemented ** |
| 528 | ** |
| 529 | ** (2) If gmerge-command is defined and there are merge conflicts in |
| 530 | ** blob_merge() then invoke the external graphical merger to resolve |
| 531 | ** the conflicts. |
| 532 | ** |
| 533 | ** (3) If a merge conflict occurs and gmerge-command is not defined, |
| 534 | ** then write the pivot, original, and merge-in files to the |
| 535 | ** filesystem. |
| 536 | */ |
| @@ -539,30 +1167,37 @@ | |
| 539 | const char *zV1, /* Name of file for version merging into (mine) */ |
| 540 | Blob *pV2, /* Version merging from (yours) */ |
| 541 | Blob *pOut, /* Output written here */ |
| 542 | unsigned mergeFlags /* Flags that control operation */ |
| 543 | ){ |
| 544 | Blob v1; /* Content of zV1 */ |
| 545 | int rc; /* Return code of subroutines and this routine */ |
| 546 | const char *zGMerge; /* Name of the gmerge command */ |
| 547 | |
| 548 | blob_read_from_file(&v1, zV1, ExtFILE); |
| 549 | rc = blob_merge(pPivot, &v1, pV2, pOut); |
| 550 | zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); |
| 551 | if( (mergeFlags & MERGE_DRYRUN)==0 |
| 552 | && ((zGMerge!=0 && zGMerge[0]!=0) |
| 553 | || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ |
| 554 | char *zPivot; /* Name of the pivot file */ |
| 555 | char *zOrig; /* Name of the original content file */ |
| 556 | char *zOther; /* Name of the merge file */ |
| 557 | |
| 558 | zPivot = file_newname(zV1, "baseline", 1); |
| 559 | blob_write_to_file(pPivot, zPivot); |
| 560 | zOrig = file_newname(zV1, "original", 1); |
| 561 | blob_write_to_file(&v1, zOrig); |
| 562 | zOther = file_newname(zV1, "merge", 1); |
| 563 | blob_write_to_file(pV2, zOther); |
| 564 | if( rc>0 ){ |
| 565 | if( zGMerge && zGMerge[0] ){ |
| 566 | char *zOut; /* Temporary output file */ |
| 567 | char *zCmd; /* Command to invoke */ |
| 568 | const char *azSubst[8]; /* Strings to be substituted */ |
| @@ -590,7 +1225,8 @@ | |
| 590 | fossil_free(zPivot); |
| 591 | fossil_free(zOrig); |
| 592 | fossil_free(zOther); |
| 593 | } |
| 594 | blob_reset(&v1); |
| 595 | return rc; |
| 596 | } |
| 597 |
| --- src/merge3.c | |
| +++ src/merge3.c | |
| @@ -75,75 +75,17 @@ | |
| 75 | if( aC1[2]!=aC2[2] ) return 0; |
| 76 | if( sameLines(pV1, pV2, aC1[2]) ) return 1; |
| 77 | return 0; |
| 78 | } |
| 79 | |
| 80 | /* |
| 81 | ** Text of boundary markers for merge conflicts. |
| 82 | */ |
| 83 | static const char *const mergeMarker[] = { |
| 84 | /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ |
| 85 | "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<", |
| 86 | "####### SUGGESTED CONFLICT RESOLUTION follows ###################", |
| 87 | "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||", |
| 88 | "======= MERGED IN content follows ===============================", |
| 89 | ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" |
| 90 | }; |
| 91 | |
| @@ -186,10 +128,615 @@ | |
| 128 | ensure_line_end(pOut, useCrLf); |
| 129 | blob_append(pOut, mergeMarker[iMark], -1); |
| 130 | if( ln>0 ) blob_appendf(pOut, " (line %d)", ln); |
| 131 | ensure_line_end(pOut, useCrLf); |
| 132 | } |
| 133 | |
| 134 | #if INTERFACE |
| 135 | /* |
| 136 | ** This is an abstract class for constructing a merge. |
| 137 | ** Subclasses of this object format the merge output in different ways. |
| 138 | ** |
| 139 | ** To subclass, create an instance of the MergeBuilder object and fill |
| 140 | ** in appropriate method implementations. |
| 141 | */ |
| 142 | struct MergeBuilder { |
| 143 | void (*xStart)(MergeBuilder*); |
| 144 | void (*xSame)(MergeBuilder*, unsigned int); |
| 145 | void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int); |
| 146 | void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int); |
| 147 | void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int); |
| 148 | void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int); |
| 149 | void (*xEnd)(MergeBuilder*); |
| 150 | void (*xDestroy)(MergeBuilder*); |
| 151 | const char *zPivot; /* Label or name for the pivot */ |
| 152 | const char *zV1; /* Label or name for the V1 file */ |
| 153 | const char *zV2; /* Label or name for the V2 file */ |
| 154 | const char *zOut; /* Label or name for the output */ |
| 155 | Blob *pPivot; /* The common ancestor */ |
| 156 | Blob *pV1; /* First variant (local copy) */ |
| 157 | Blob *pV2; /* Second variant (merged in) */ |
| 158 | Blob *pOut; /* Write merge results here */ |
| 159 | int useCrLf; /* Use CRLF line endings */ |
| 160 | int nContext; /* Size of unchanged line boundaries */ |
| 161 | unsigned int mxPivot; /* Number of lines in the pivot */ |
| 162 | unsigned int mxV1; /* Number of lines in V1 */ |
| 163 | unsigned int mxV2; /* Number of lines in V2 */ |
| 164 | unsigned int lnPivot; /* Lines read from pivot */ |
| 165 | unsigned int lnV1; /* Lines read from v1 */ |
| 166 | unsigned int lnV2; /* Lines read from v2 */ |
| 167 | unsigned int lnOut; /* Lines written to out */ |
| 168 | unsigned int nConflict; /* Number of conflicts seen */ |
| 169 | u64 diffFlags; /* Flags for difference engine */ |
| 170 | }; |
| 171 | #endif /* INTERFACE */ |
| 172 | |
| 173 | |
| 174 | /************************* Generic MergeBuilder ******************************/ |
| 175 | /* These are generic methods for MergeBuilder. They just output debugging |
| 176 | ** information. But some of them are useful as base methods for other useful |
| 177 | ** implementations of MergeBuilder. |
| 178 | */ |
| 179 | |
| 180 | /* xStart() and xEnd() are called to generate header and fotter information |
| 181 | ** in the output. This is a no-op in the generic implementation. |
| 182 | */ |
| 183 | static void dbgStartEnd(MergeBuilder *p){ (void)p; } |
| 184 | |
| 185 | /* The next N lines of PIVOT are unchanged in both V1 and V2 |
| 186 | */ |
| 187 | static void dbgSame(MergeBuilder *p, unsigned int N){ |
| 188 | blob_appendf(p->pOut, |
| 189 | "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n", |
| 190 | N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N, |
| 191 | p->lnV2+1, p->lnV2+N); |
| 192 | p->lnPivot += N; |
| 193 | p->lnV1 += N; |
| 194 | p->lnV2 += N; |
| 195 | } |
| 196 | |
| 197 | /* The next nPivot lines of the PIVOT are changed into nV1 lines by V1 |
| 198 | */ |
| 199 | static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ |
| 200 | blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n", |
| 201 | nV1, p->lnV1+1, p->lnV1+nV1); |
| 202 | p->lnPivot += nPivot; |
| 203 | p->lnV2 += nPivot; |
| 204 | p->lnV1 += nV1; |
| 205 | } |
| 206 | |
| 207 | /* The next nPivot lines of the PIVOT are changed into nV2 lines by V2 |
| 208 | */ |
| 209 | static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ |
| 210 | blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n", |
| 211 | nV2, p->lnV2+1, p->lnV2+nV2); |
| 212 | p->lnPivot += nPivot; |
| 213 | p->lnV1 += nPivot; |
| 214 | p->lnV2 += nV2; |
| 215 | } |
| 216 | |
| 217 | /* The next nPivot lines of the PIVOT are changed into nV lines from V1 and |
| 218 | ** V2, which should be the same. In other words, the same change is found |
| 219 | ** in both V1 and V2. |
| 220 | */ |
| 221 | static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ |
| 222 | blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n", |
| 223 | nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV); |
| 224 | p->lnPivot += nPivot; |
| 225 | p->lnV1 += nV; |
| 226 | p->lnV2 += nV; |
| 227 | } |
| 228 | |
| 229 | /* V1 and V2 have different and overlapping changes. The next nPivot lines |
| 230 | ** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2. |
| 231 | */ |
| 232 | static void dbgConflict( |
| 233 | MergeBuilder *p, |
| 234 | unsigned int nPivot, |
| 235 | unsigned int nV1, |
| 236 | unsigned int nV2 |
| 237 | ){ |
| 238 | blob_appendf(p->pOut, |
| 239 | "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n", |
| 240 | nPivot, nV1, nV2, |
| 241 | p->lnPivot+1, p->lnPivot+nPivot, |
| 242 | p->lnV1+1, p->lnV1+nV1, |
| 243 | p->lnV2+1, p->lnV2+nV2); |
| 244 | p->lnV1 += nV1; |
| 245 | p->lnPivot += nPivot; |
| 246 | p->lnV2 += nV2; |
| 247 | } |
| 248 | |
| 249 | /* Generic destructor for the MergeBuilder object |
| 250 | */ |
| 251 | static void dbgDestroy(MergeBuilder *p){ |
| 252 | memset(p, 0, sizeof(*p)); |
| 253 | } |
| 254 | |
| 255 | /* Generic initializer for a MergeBuilder object |
| 256 | */ |
| 257 | static void mergebuilder_init(MergeBuilder *p){ |
| 258 | memset(p, 0, sizeof(*p)); |
| 259 | p->xStart = dbgStartEnd; |
| 260 | p->xSame = dbgSame; |
| 261 | p->xChngV1 = dbgChngV1; |
| 262 | p->xChngV2 = dbgChngV2; |
| 263 | p->xChngBoth = dbgChngBoth; |
| 264 | p->xConflict = dbgConflict; |
| 265 | p->xEnd = dbgStartEnd; |
| 266 | p->xDestroy = dbgDestroy; |
| 267 | } |
| 268 | |
| 269 | /************************* MergeBuilderToken ********************************/ |
| 270 | /* This version of MergeBuilder actually performs a merge on file that |
| 271 | ** are broken up into tokens instead of lines, and puts the result in pOut. |
| 272 | */ |
| 273 | static void tokenSame(MergeBuilder *p, unsigned int N){ |
| 274 | blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N); |
| 275 | p->pPivot->iCursor += N; |
| 276 | p->pV1->iCursor += N; |
| 277 | p->pV2->iCursor += N; |
| 278 | } |
| 279 | static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){ |
| 280 | blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1); |
| 281 | p->pPivot->iCursor += nPivot; |
| 282 | p->pV1->iCursor += nV1; |
| 283 | p->pV2->iCursor += nPivot; |
| 284 | } |
| 285 | static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){ |
| 286 | blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); |
| 287 | p->pPivot->iCursor += nPivot; |
| 288 | p->pV1->iCursor += nPivot; |
| 289 | p->pV2->iCursor += nV2; |
| 290 | } |
| 291 | static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){ |
| 292 | blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV); |
| 293 | p->pPivot->iCursor += nPivot; |
| 294 | p->pV1->iCursor += nV; |
| 295 | p->pV2->iCursor += nV; |
| 296 | } |
| 297 | static void tokenConflict( |
| 298 | MergeBuilder *p, |
| 299 | unsigned int nPivot, |
| 300 | unsigned int nV1, |
| 301 | unsigned int nV2 |
| 302 | ){ |
| 303 | /* For a token-merge conflict, use the text from the merge-in */ |
| 304 | blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2); |
| 305 | p->pPivot->iCursor += nPivot; |
| 306 | p->pV1->iCursor += nV1; |
| 307 | p->pV2->iCursor += nV2; |
| 308 | } |
| 309 | static void mergebuilder_init_token(MergeBuilder *p){ |
| 310 | mergebuilder_init(p); |
| 311 | p->xSame = tokenSame; |
| 312 | p->xChngV1 = tokenChngV1; |
| 313 | p->xChngV2 = tokenChngV2; |
| 314 | p->xChngBoth = tokenChngBoth; |
| 315 | p->xConflict = tokenConflict; |
| 316 | p->diffFlags = DIFF_BY_TOKEN; |
| 317 | } |
| 318 | |
| 319 | /* |
| 320 | ** Attempt to do a low-level merge on a conflict. The conflict is |
| 321 | ** described by the first four parameters, which are the same as the |
| 322 | ** arguments to the xConflict method of the MergeBuilder object. |
| 323 | ** This routine attempts to resolve the conflict by looking at |
| 324 | ** elements of the conflict region that are finer grain than complete |
| 325 | ** lines of text. |
| 326 | ** |
| 327 | ** The result is written into Blob pOut. pOut is initialized by this |
| 328 | ** routine. |
| 329 | */ |
| 330 | int merge_try_to_resolve_conflict( |
| 331 | MergeBuilder *pMB, /* MergeBuilder that encounter conflict */ |
| 332 | unsigned int nPivot, /* Lines of conflict in the pivot */ |
| 333 | unsigned int nV1, /* Lines of conflict in V1 */ |
| 334 | unsigned int nV2, /* Lines of conflict in V2 */ |
| 335 | Blob *pOut /* Write resolution text here */ |
| 336 | ){ |
| 337 | int nConflict; |
| 338 | MergeBuilder mb; |
| 339 | Blob pv, v1, v2; |
| 340 | mergebuilder_init_token(&mb); |
| 341 | blob_extract_lines(pMB->pPivot, nPivot, &pv); |
| 342 | blob_extract_lines(pMB->pV1, nV1, &v1); |
| 343 | blob_extract_lines(pMB->pV2, nV2, &v2); |
| 344 | blob_zero(pOut); |
| 345 | blob_materialize(&pv); |
| 346 | blob_materialize(&v1); |
| 347 | blob_materialize(&v2); |
| 348 | mb.pPivot = &pv; |
| 349 | mb.pV1 = &v1; |
| 350 | mb.pV2 = &v2; |
| 351 | mb.pOut = pOut; |
| 352 | nConflict = merge_three_blobs(&mb); |
| 353 | blob_reset(&pv); |
| 354 | blob_reset(&v1); |
| 355 | blob_reset(&v2); |
| 356 | /* mb has not allocated any resources, so we do not need to invoke |
| 357 | ** the xDestroy method. */ |
| 358 | blob_add_final_newline(pOut); |
| 359 | return nConflict; |
| 360 | } |
| 361 | |
| 362 | |
| 363 | /************************* MergeBuilderText **********************************/ |
| 364 | /* This version of MergeBuilder actually performs a merge on file and puts |
| 365 | ** the result in pOut |
| 366 | */ |
| 367 | static void txtStart(MergeBuilder *p){ |
| 368 | /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM), |
| 369 | ** keep it in the output. This should be secure enough not to cause |
| 370 | ** unintended changes to the merged file and consistent with what |
| 371 | ** users are using in their source files. |
| 372 | */ |
| 373 | if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){ |
| 374 | blob_append(p->pOut, (char*)get_utf8_bom(0), -1); |
| 375 | } |
| 376 | if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){ |
| 377 | p->useCrLf = 1; |
| 378 | } |
| 379 | } |
| 380 | static void txtSame(MergeBuilder *p, unsigned int N){ |
| 381 | blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N; |
| 382 | blob_copy_lines(0, p->pV1, N); p->lnV1 += N; |
| 383 | blob_copy_lines(0, p->pV2, N); p->lnV2 += N; |
| 384 | } |
| 385 | static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ |
| 386 | blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; |
| 387 | blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot; |
| 388 | blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; |
| 389 | } |
| 390 | static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ |
| 391 | blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; |
| 392 | blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot; |
| 393 | blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; |
| 394 | } |
| 395 | static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ |
| 396 | blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot; |
| 397 | blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV; |
| 398 | blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV; |
| 399 | } |
| 400 | static void txtConflict( |
| 401 | MergeBuilder *p, |
| 402 | unsigned int nPivot, |
| 403 | unsigned int nV1, |
| 404 | unsigned int nV2 |
| 405 | ){ |
| 406 | int nRes; /* Lines in the computed conflict resolution */ |
| 407 | Blob res; /* Text of the conflict resolution */ |
| 408 | |
| 409 | merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); |
| 410 | nRes = blob_linecount(&res); |
| 411 | |
| 412 | append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf); |
| 413 | blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1; |
| 414 | |
| 415 | if( nRes>0 ){ |
| 416 | append_merge_mark(p->pOut, 1, 0, p->useCrLf); |
| 417 | blob_copy_lines(p->pOut, &res, nRes); |
| 418 | } |
| 419 | blob_reset(&res); |
| 420 | |
| 421 | append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf); |
| 422 | blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot; |
| 423 | |
| 424 | append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf); |
| 425 | blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2; |
| 426 | |
| 427 | append_merge_mark(p->pOut, 4, -1, p->useCrLf); |
| 428 | } |
| 429 | static void mergebuilder_init_text(MergeBuilder *p){ |
| 430 | mergebuilder_init(p); |
| 431 | p->xStart = txtStart; |
| 432 | p->xSame = txtSame; |
| 433 | p->xChngV1 = txtChngV1; |
| 434 | p->xChngV2 = txtChngV2; |
| 435 | p->xChngBoth = txtChngBoth; |
| 436 | p->xConflict = txtConflict; |
| 437 | } |
| 438 | |
| 439 | /************************* MergeBuilderTcl **********************************/ |
| 440 | /* Generate merge output formatted for reading by a TCL script. |
| 441 | ** |
| 442 | ** The output consists of lines of text, each with 4 tokens. The tokens |
| 443 | ** represent the content for one line from baseline, v1, v2, and output |
| 444 | ** respectively. The first character of each token provides auxiliary |
| 445 | ** information: |
| 446 | ** |
| 447 | ** . This line is omitted. |
| 448 | ** N Name of the file. |
| 449 | ** T Literal text follows that should have a \n terminator. |
| 450 | ** R Literal text follows that needs a \r\n terminator. |
| 451 | ** X Merge conflict. |
| 452 | ** Z Literal text without a line terminator. |
| 453 | ** S Skipped lines. Followed by number of lines to skip. |
| 454 | ** 1 Text is a copy of token 1 |
| 455 | ** 2 Use data from data-token 2 |
| 456 | ** 3 Use data from data-token 3 |
| 457 | */ |
| 458 | |
| 459 | /* Write text that goes into the interior of a double-quoted string in TCL */ |
| 460 | static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){ |
| 461 | int j; |
| 462 | for(j=0; j<nIn; j++){ |
| 463 | char c = zIn[j]; |
| 464 | if( c=='\\' ){ |
| 465 | blob_append(pOut, "\\\\", 2); |
| 466 | }else if( c=='"' ){ |
| 467 | blob_append(pOut, "\\\"", 2); |
| 468 | }else if( c<' ' || c>0x7e ){ |
| 469 | char z[5]; |
| 470 | z[0] = '\\'; |
| 471 | z[1] = "01234567"[(c>>6)&0x3]; |
| 472 | z[2] = "01234567"[(c>>3)&0x7]; |
| 473 | z[3] = "01234567"[c&0x7]; |
| 474 | z[4] = 0; |
| 475 | blob_append(pOut, z, 4); |
| 476 | }else{ |
| 477 | blob_append_char(pOut, c); |
| 478 | } |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | /* Copy one line of text from pIn and append to pOut, encoded as TCL */ |
| 483 | static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){ |
| 484 | int i, k; |
| 485 | for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){} |
| 486 | if( i==pIn->nUsed ){ |
| 487 | k = i; |
| 488 | }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){ |
| 489 | k = i-1; |
| 490 | i++; |
| 491 | }else{ |
| 492 | k = i; |
| 493 | i++; |
| 494 | } |
| 495 | blob_append_char(pOut, '"'); |
| 496 | blob_append_char(pOut, cType); |
| 497 | tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor); |
| 498 | pIn->iCursor = i; |
| 499 | blob_append_char(pOut, '"'); |
| 500 | } |
| 501 | static void tclStart(MergeBuilder *p){ |
| 502 | Blob *pOut = p->pOut; |
| 503 | blob_append(pOut, "\"N", 2); |
| 504 | tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot)); |
| 505 | blob_append(pOut, "\" \"N", 4); |
| 506 | tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1)); |
| 507 | blob_append(pOut, "\" \"N", 4); |
| 508 | tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2)); |
| 509 | blob_append(pOut, "\" \"N", 4); |
| 510 | if( p->zOut ){ |
| 511 | tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut)); |
| 512 | }else{ |
| 513 | blob_append(pOut, "(Merge Result)", -1); |
| 514 | } |
| 515 | blob_append(pOut, "\"\n", 2); |
| 516 | } |
| 517 | static void tclSame(MergeBuilder *p, unsigned int N){ |
| 518 | int i = 0; |
| 519 | int nSkip; |
| 520 | |
| 521 | if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){ |
| 522 | while( i<N && i<p->nContext ){ |
| 523 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 524 | blob_append(p->pOut, " 1 1 1\n", 7); |
| 525 | i++; |
| 526 | } |
| 527 | nSkip = N - p->nContext*2; |
| 528 | }else{ |
| 529 | nSkip = N - p->nContext; |
| 530 | } |
| 531 | if( nSkip>0 ){ |
| 532 | blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n", |
| 533 | nSkip, nSkip, nSkip, nSkip); |
| 534 | blob_copy_lines(0, p->pPivot, nSkip); |
| 535 | i += nSkip; |
| 536 | } |
| 537 | |
| 538 | p->lnPivot += N; |
| 539 | p->lnV1 += N; |
| 540 | p->lnV2 += N; |
| 541 | |
| 542 | if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){ |
| 543 | while( i<N ){ |
| 544 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 545 | blob_append(p->pOut, " 1 1 1\n", 7); |
| 546 | i++; |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | blob_copy_lines(0, p->pV1, N); |
| 551 | blob_copy_lines(0, p->pV2, N); |
| 552 | } |
| 553 | static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){ |
| 554 | int i; |
| 555 | for(i=0; i<nPivot && i<nV1; i++){ |
| 556 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 557 | blob_append_char(p->pOut, ' '); |
| 558 | tclLineOfText(p->pOut, p->pV1, 'T'); |
| 559 | blob_append(p->pOut, " 1 2\n", 5); |
| 560 | } |
| 561 | while( i<nPivot ){ |
| 562 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 563 | blob_append(p->pOut, " . 1 .\n", 7); |
| 564 | i++; |
| 565 | } |
| 566 | while( i<nV1 ){ |
| 567 | blob_append(p->pOut, ". ", 2); |
| 568 | tclLineOfText(p->pOut, p->pV1, 'T'); |
| 569 | blob_append(p->pOut, " . 2\n", 5); |
| 570 | i++; |
| 571 | } |
| 572 | p->lnPivot += nPivot; |
| 573 | p->lnV1 += nV1; |
| 574 | p->lnV2 += nPivot; |
| 575 | blob_copy_lines(0, p->pV2, nPivot); |
| 576 | } |
| 577 | static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){ |
| 578 | int i; |
| 579 | for(i=0; i<nPivot && i<nV2; i++){ |
| 580 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 581 | blob_append(p->pOut, " 1 ", 3); |
| 582 | tclLineOfText(p->pOut, p->pV2, 'T'); |
| 583 | blob_append(p->pOut, " 3\n", 3); |
| 584 | } |
| 585 | while( i<nPivot ){ |
| 586 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 587 | blob_append(p->pOut, " 1 . .\n", 7); |
| 588 | i++; |
| 589 | } |
| 590 | while( i<nV2 ){ |
| 591 | blob_append(p->pOut, ". . ", 4); |
| 592 | tclLineOfText(p->pOut, p->pV2, 'T'); |
| 593 | blob_append(p->pOut, " 3\n", 3); |
| 594 | i++; |
| 595 | } |
| 596 | p->lnPivot += nPivot; |
| 597 | p->lnV1 += nPivot; |
| 598 | p->lnV2 += nV2; |
| 599 | blob_copy_lines(0, p->pV1, nPivot); |
| 600 | } |
| 601 | static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){ |
| 602 | int i; |
| 603 | for(i=0; i<nPivot && i<nV; i++){ |
| 604 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 605 | blob_append_char(p->pOut, ' '); |
| 606 | tclLineOfText(p->pOut, p->pV1, 'T'); |
| 607 | blob_append(p->pOut, " 2 2\n", 5); |
| 608 | } |
| 609 | while( i<nPivot ){ |
| 610 | tclLineOfText(p->pOut, p->pPivot, 'T'); |
| 611 | blob_append(p->pOut, " . . .\n", 7); |
| 612 | i++; |
| 613 | } |
| 614 | while( i<nV ){ |
| 615 | blob_append(p->pOut, ". ", 2); |
| 616 | tclLineOfText(p->pOut, p->pV1, 'T'); |
| 617 | blob_append(p->pOut, " 2 2\n", 5); |
| 618 | i++; |
| 619 | } |
| 620 | p->lnPivot += nPivot; |
| 621 | p->lnV1 += nV; |
| 622 | p->lnV2 += nV; |
| 623 | blob_copy_lines(0, p->pV2, nV); |
| 624 | } |
| 625 | static void tclConflict( |
| 626 | MergeBuilder *p, |
| 627 | unsigned int nPivot, |
| 628 | unsigned int nV1, |
| 629 | unsigned int nV2 |
| 630 | ){ |
| 631 | int mx = nPivot; |
| 632 | int i; |
| 633 | int nRes; |
| 634 | Blob res; |
| 635 | |
| 636 | merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res); |
| 637 | nRes = blob_linecount(&res); |
| 638 | if( nV1>mx ) mx = nV1; |
| 639 | if( nV2>mx ) mx = nV2; |
| 640 | if( nRes>mx ) mx = nRes; |
| 641 | if( nRes>0 ){ |
| 642 | blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2); |
| 643 | } |
| 644 | for(i=0; i<mx; i++){ |
| 645 | if( i<nPivot ){ |
| 646 | tclLineOfText(p->pOut, p->pPivot, 'X'); |
| 647 | }else{ |
| 648 | blob_append_char(p->pOut, '.'); |
| 649 | } |
| 650 | blob_append_char(p->pOut, ' '); |
| 651 | if( i<nV1 ){ |
| 652 | tclLineOfText(p->pOut, p->pV1, 'X'); |
| 653 | }else{ |
| 654 | blob_append_char(p->pOut, '.'); |
| 655 | } |
| 656 | blob_append_char(p->pOut, ' '); |
| 657 | if( i<nV2 ){ |
| 658 | tclLineOfText(p->pOut, p->pV2, 'X'); |
| 659 | }else{ |
| 660 | blob_append_char(p->pOut, '.'); |
| 661 | } |
| 662 | if( i<nRes ){ |
| 663 | blob_append_char(p->pOut, ' '); |
| 664 | tclLineOfText(p->pOut, &res, 'X'); |
| 665 | blob_append_char(p->pOut, '\n'); |
| 666 | }else{ |
| 667 | blob_append(p->pOut, " .\n", 3); |
| 668 | } |
| 669 | if( i==mx-1 ){ |
| 670 | blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3); |
| 671 | } |
| 672 | } |
| 673 | blob_reset(&res); |
| 674 | p->lnPivot += nPivot; |
| 675 | p->lnV1 += nV1; |
| 676 | p->lnV2 += nV2; |
| 677 | } |
| 678 | void mergebuilder_init_tcl(MergeBuilder *p){ |
| 679 | mergebuilder_init(p); |
| 680 | p->xStart = tclStart; |
| 681 | p->xSame = tclSame; |
| 682 | p->xChngV1 = tclChngV1; |
| 683 | p->xChngV2 = tclChngV2; |
| 684 | p->xChngBoth = tclChngBoth; |
| 685 | p->xConflict = tclConflict; |
| 686 | } |
| 687 | /*****************************************************************************/ |
| 688 | |
| 689 | /* |
| 690 | ** The aC[] array contains triples of integers. Within each triple, the |
| 691 | ** elements are: |
| 692 | ** |
| 693 | ** (0) The number of lines to copy |
| 694 | ** (1) The number of lines to delete |
| 695 | ** (2) The number of liens to insert |
| 696 | ** |
| 697 | ** Suppose we want to advance over sz lines of the original file. This routine |
| 698 | ** returns true if that advance would land us on a copy operation. It |
| 699 | ** returns false if the advance would end on a delete. |
| 700 | */ |
| 701 | static int ends_with_copy(int *aC, int sz){ |
| 702 | while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ |
| 703 | if( aC[0]>=sz ) return 1; |
| 704 | sz -= aC[0]; |
| 705 | if( aC[1]>sz ) return 0; |
| 706 | sz -= aC[1]; |
| 707 | aC += 3; |
| 708 | } |
| 709 | return 1; |
| 710 | } |
| 711 | |
| 712 | /* |
| 713 | ** aC[] is an "edit triple" for changes from A to B. Advance through |
| 714 | ** this triple to determine the number of lines to bypass on B in order |
| 715 | ** to match an advance of sz lines on A. |
| 716 | */ |
| 717 | static int skip_conflict( |
| 718 | int *aC, /* Array of integer triples describing the edit */ |
| 719 | int i, /* Index in aC[] of current location */ |
| 720 | int sz, /* Lines of A that have been skipped */ |
| 721 | unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */ |
| 722 | ){ |
| 723 | *pLn = 0; |
| 724 | while( sz>0 ){ |
| 725 | if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; |
| 726 | if( aC[i]>=sz ){ |
| 727 | aC[i] -= sz; |
| 728 | *pLn += sz; |
| 729 | break; |
| 730 | } |
| 731 | *pLn += aC[i]; |
| 732 | *pLn += aC[i+2]; |
| 733 | sz -= aC[i] + aC[i+1]; |
| 734 | i += 3; |
| 735 | } |
| 736 | return i; |
| 737 | } |
| 738 | |
| 739 | /* |
| 740 | ** Do a three-way merge. Initialize pOut to contain the result. |
| 741 | ** |
| 742 | ** The merge is an edit against pV2. Both pV1 and pV2 have a |
| @@ -199,156 +746,113 @@ | |
| 746 | ** The return is 0 upon complete success. If any input file is binary, |
| 747 | ** -1 is returned and pOut is unmodified. If there are merge |
| 748 | ** conflicts, the merge proceeds as best as it can and the number |
| 749 | ** of conflicts is returns |
| 750 | */ |
| 751 | int merge_three_blobs(MergeBuilder *p){ |
| 752 | int *aC1; /* Changes from pPivot to pV1 */ |
| 753 | int *aC2; /* Changes from pPivot to pV2 */ |
| 754 | int i1, i2; /* Index into aC1[] and aC2[] */ |
| 755 | int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ |
| 756 | int limit1, limit2; /* Sizes of aC1[] and aC2[] */ |
| 757 | int nConflict = 0; /* Number of merge conflicts seen so far */ |
| 758 | DiffConfig DCfg; |
| 759 | |
| 760 | /* Compute the edits that occur from pPivot => pV1 (into aC1) |
| 761 | ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is |
| 762 | ** an array of integer triples. Within each triple, the first integer |
| 763 | ** is the number of lines of text to copy directly from the pivot, |
| 764 | ** the second integer is the number of lines of text to omit from the |
| 765 | ** pivot, and the third integer is the number of lines of text that are |
| 766 | ** inserted. The edit array ends with a triple of 0,0,0. |
| 767 | */ |
| 768 | diff_config_init(&DCfg, 0); |
| 769 | DCfg.diffFlags = p->diffFlags; |
| 770 | aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg); |
| 771 | aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg); |
| 772 | if( aC1==0 || aC2==0 ){ |
| 773 | free(aC1); |
| 774 | free(aC2); |
| 775 | return -1; |
| 776 | } |
| 777 | |
| 778 | blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */ |
| 779 | blob_rewind(p->pV2); |
| 780 | blob_rewind(p->pPivot); |
| 781 | |
| 782 | /* Determine the length of the aC1[] and aC2[] change vectors */ |
| 783 | p->mxPivot = 0; |
| 784 | p->mxV1 = 0; |
| 785 | for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){ |
| 786 | p->mxPivot += aC1[i1] + aC1[i1+1]; |
| 787 | p->mxV1 += aC1[i1] + aC1[i1+2]; |
| 788 | } |
| 789 | limit1 = i1; |
| 790 | p->mxV2 = 0; |
| 791 | for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){ |
| 792 | p->mxV2 += aC2[i2] + aC2[i2+2]; |
| 793 | } |
| 794 | limit2 = i2; |
| 795 | |
| 796 | /* Output header text and do any other required initialization */ |
| 797 | p->xStart(p); |
| 798 | |
| 799 | /* Loop over the two edit vectors and use them to compute merged text |
| 800 | ** which is written into pOut. i1 and i2 are multiples of 3 which are |
| 801 | ** indices into aC1[] and aC2[] to the edit triple currently being |
| 802 | ** processed |
| 803 | */ |
| 804 | i1 = i2 = 0; |
| 805 | while( i1<limit1 && i2<limit2 ){ |
| 806 | if( aC1[i1]>0 && aC2[i2]>0 ){ |
| 807 | /* Output text that is unchanged in both V1 and V2 */ |
| 808 | nCpy = min(aC1[i1], aC2[i2]); |
| 809 | p->xSame(p, nCpy); |
| 810 | aC1[i1] -= nCpy; |
| 811 | aC2[i2] -= nCpy; |
| 812 | }else |
| 813 | if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ |
| 814 | /* Output edits to V2 that occurs within unchanged regions of V1 */ |
| 815 | nDel = aC2[i2+1]; |
| 816 | nIns = aC2[i2+2]; |
| 817 | p->xChngV2(p, nDel, nIns); |
| 818 | aC1[i1] -= nDel; |
| 819 | i2 += 3; |
| 820 | }else |
| 821 | if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ |
| 822 | /* Output edits to V1 that occur within unchanged regions of V2 */ |
| 823 | nDel = aC1[i1+1]; |
| 824 | nIns = aC1[i1+2]; |
| 825 | p->xChngV1(p, nDel, nIns); |
| 826 | aC2[i2] -= nDel; |
| 827 | i1 += 3; |
| 828 | }else |
| 829 | if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){ |
| 830 | /* Output edits that are identical in both V1 and V2. */ |
| 831 | assert( aC1[i1]==0 ); |
| 832 | nDel = aC1[i1+1]; |
| 833 | nIns = aC1[i1+2]; |
| 834 | p->xChngBoth(p, nDel, nIns); |
| 835 | i1 += 3; |
| 836 | i2 += 3; |
| 837 | }else |
| 838 | { |
| 839 | /* We have found a region where different edits to V1 and V2 overlap. |
| 840 | ** This is a merge conflict. Find the size of the conflict, then |
| 841 | ** output both possible edits separated by distinctive marks. |
| 842 | */ |
| 843 | unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */ |
| 844 | unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */ |
| 845 | nConflict++; |
| 846 | while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){ |
| 847 | sz++; |
| 848 | } |
| 849 | i1 = skip_conflict(aC1, i1, sz, &nV1); |
| 850 | i2 = skip_conflict(aC2, i2, sz, &nV2); |
| 851 | p->xConflict(p, sz, nV1, nV2); |
| 852 | } |
| 853 | |
| 854 | /* If we are finished with an edit triple, advance to the next |
| 855 | ** triple. |
| 856 | */ |
| 857 | if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3; |
| 858 | if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3; |
| @@ -356,20 +860,18 @@ | |
| 860 | |
| 861 | /* When one of the two edit vectors reaches its end, there might still |
| 862 | ** be an insert in the other edit vector. Output this remaining |
| 863 | ** insert. |
| 864 | */ |
| 865 | if( i1<limit1 && aC1[i1+2]>0 ){ |
| 866 | p->xChngV1(p, 0, aC1[i1+2]); |
| 867 | }else if( i2<limit2 && aC2[i2+2]>0 ){ |
| 868 | p->xChngV2(p, 0, aC2[i2+2]); |
| 869 | } |
| 870 | |
| 871 | /* Output footer text */ |
| 872 | p->xEnd(p); |
| 873 | |
| 874 | free(aC1); |
| 875 | free(aC2); |
| 876 | return nConflict; |
| 877 | } |
| @@ -384,11 +886,12 @@ | |
| 886 | const char *z = blob_buffer(p); |
| 887 | int n = blob_size(p) - len + 1; |
| 888 | assert( len==(int)strlen(mergeMarker[1]) ); |
| 889 | assert( len==(int)strlen(mergeMarker[2]) ); |
| 890 | assert( len==(int)strlen(mergeMarker[3]) ); |
| 891 | assert( len==(int)strlen(mergeMarker[4]) ); |
| 892 | assert( count(mergeMarker)==5 ); |
| 893 | for(i=0; i<n; ){ |
| 894 | for(j=0; j<4; j++){ |
| 895 | if( (memcmp(&z[i], mergeMarker[j], len)==0) ){ |
| 896 | return 1; |
| 897 | } |
| @@ -408,18 +911,106 @@ | |
| 911 | blob_read_from_file(&file, zFullpath, ExtFILE); |
| 912 | rc = contains_merge_marker(&file); |
| 913 | blob_reset(&file); |
| 914 | return rc; |
| 915 | } |
| 916 | |
| 917 | /* |
| 918 | ** Show merge output in a Tcl/Tk window, in response to the --tk option |
| 919 | ** to the "merge" or "3-way-merge" command. |
| 920 | ** |
| 921 | ** If fossil has direct access to a Tcl interpreter (either loaded |
| 922 | ** dynamically through stubs or linked in statically), we can use it |
| 923 | ** directly. Otherwise: |
| 924 | ** (1) Write the Tcl/Tk script used for rendering into a temp file. |
| 925 | ** (2) Invoke "tclsh" on the temp file using fossil_system(). |
| 926 | ** (3) Delete the temp file. |
| 927 | */ |
| 928 | void merge_tk(const char *zSubCmd, int firstArg){ |
| 929 | int i; |
| 930 | Blob script; |
| 931 | const char *zTempFile = 0; |
| 932 | char *zCmd; |
| 933 | const char *zTclsh; |
| 934 | const char *zCnt; |
| 935 | int bDarkMode = find_option("dark",0,0)!=0; |
| 936 | int nContext; |
| 937 | zCnt = find_option("context", "c", 1); |
| 938 | if( zCnt==0 ){ |
| 939 | nContext = 6; |
| 940 | }else{ |
| 941 | nContext = atoi(zCnt); |
| 942 | if( nContext<0 ) nContext = 0xfffffff; |
| 943 | } |
| 944 | blob_zero(&script); |
| 945 | blob_appendf(&script, "set ncontext %d\n", nContext); |
| 946 | blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl", |
| 947 | g.nameOfExe, zSubCmd); |
| 948 | find_option("tcl",0,0); |
| 949 | find_option("debug",0,0); |
| 950 | zTclsh = find_option("tclsh",0,1); |
| 951 | if( zTclsh==0 ){ |
| 952 | zTclsh = db_get("tclsh",0); |
| 953 | } |
| 954 | /* The undocumented --script FILENAME option causes the Tk script to |
| 955 | ** be written into the FILENAME instead of being run. This is used |
| 956 | ** for testing and debugging. */ |
| 957 | zTempFile = find_option("script",0,1); |
| 958 | verify_all_options(); |
| 959 | |
| 960 | if( (g.argc - firstArg)!=3 ){ |
| 961 | fossil_fatal("Requires 3 filename arguments"); |
| 962 | } |
| 963 | |
| 964 | for(i=firstArg; i<g.argc; i++){ |
| 965 | const char *z = g.argv[i]; |
| 966 | if( sqlite3_strglob("*}*",z) ){ |
| 967 | blob_appendf(&script, " {%/}", z); |
| 968 | }else{ |
| 969 | int j; |
| 970 | blob_append(&script, " ", 1); |
| 971 | for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]); |
| 972 | } |
| 973 | } |
| 974 | blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode); |
| 975 | blob_appendf(&script, "%s", builtin_file("merge.tcl", 0)); |
| 976 | if( zTempFile ){ |
| 977 | blob_write_to_file(&script, zTempFile); |
| 978 | fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile); |
| 979 | }else{ |
| 980 | #if defined(FOSSIL_ENABLE_TCL) |
| 981 | Th_FossilInit(TH_INIT_DEFAULT); |
| 982 | if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), |
| 983 | blob_size(&script), 1, 1, 0)==TCL_OK ){ |
| 984 | blob_reset(&script); |
| 985 | return; |
| 986 | } |
| 987 | /* |
| 988 | * If evaluation of the Tcl script fails, the reason may be that Tk |
| 989 | * could not be found by the loaded Tcl, or that Tcl cannot be loaded |
| 990 | * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback |
| 991 | * to using the external "tclsh", if available. |
| 992 | */ |
| 993 | #endif |
| 994 | zTempFile = write_blob_to_temp_file(&script); |
| 995 | zCmd = mprintf("%$ %$", zTclsh, zTempFile); |
| 996 | fossil_system(zCmd); |
| 997 | file_delete(zTempFile); |
| 998 | fossil_free(zCmd); |
| 999 | } |
| 1000 | blob_reset(&script); |
| 1001 | } |
| 1002 | |
| 1003 | |
| 1004 | /* |
| 1005 | ** COMMAND: 3-way-merge* |
| 1006 | ** |
| 1007 | ** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED] |
| 1008 | ** |
| 1009 | ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated |
| 1010 | ** as output. If no MERGED file is specified, output is sent to |
| 1011 | ** stdout. |
| 1012 | ** |
| 1013 | ** BASELINE is a common ancestor of two files V1 and V2 that have diverging |
| 1014 | ** edits. The generated output file MERGED is the combination of all |
| 1015 | ** changes in both V1 and V2. |
| 1016 | ** |
| @@ -436,38 +1027,75 @@ | |
| 1027 | ** cp Xup.c Xbase.c |
| 1028 | ** # Verify that everything still works |
| 1029 | ** fossil commit |
| 1030 | ** |
| 1031 | */ |
| 1032 | void merge_3way_cmd(void){ |
| 1033 | MergeBuilder s; |
| 1034 | int nConflict; |
| 1035 | Blob pivot, v1, v2, out; |
| 1036 | int noWarn = 0; |
| 1037 | const char *zCnt; |
| 1038 | |
| 1039 | if( find_option("tk", 0, 0)!=0 ){ |
| 1040 | merge_tk("3-way-merge", 2); |
| 1041 | return; |
| 1042 | } |
| 1043 | mergebuilder_init_text(&s); |
| 1044 | if( find_option("debug", 0, 0) ){ |
| 1045 | mergebuilder_init(&s); |
| 1046 | } |
| 1047 | if( find_option("tcl", 0, 0) ){ |
| 1048 | mergebuilder_init_tcl(&s); |
| 1049 | noWarn = 1; |
| 1050 | } |
| 1051 | zCnt = find_option("context", "c", 1); |
| 1052 | if( zCnt ){ |
| 1053 | s.nContext = atoi(zCnt); |
| 1054 | if( s.nContext<0 ) s.nContext = 0xfffffff; |
| 1055 | }else{ |
| 1056 | s.nContext = 6; |
| 1057 | } |
| 1058 | blob_zero(&pivot); s.pPivot = &pivot; |
| 1059 | blob_zero(&v1); s.pV1 = &v1; |
| 1060 | blob_zero(&v2); s.pV2 = &v2; |
| 1061 | blob_zero(&out); s.pOut = &out; |
| 1062 | |
| 1063 | /* We should be done with options.. */ |
| 1064 | verify_all_options(); |
| 1065 | |
| 1066 | if( g.argc!=6 && g.argc!=5 ){ |
| 1067 | usage("[OPTIONS] PIVOT V1 V2 [MERGED]"); |
| 1068 | } |
| 1069 | s.zPivot = file_tail(g.argv[2]); |
| 1070 | s.zV1 = file_tail(g.argv[3]); |
| 1071 | s.zV2 = file_tail(g.argv[4]); |
| 1072 | if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){ |
| 1073 | fossil_fatal("cannot read %s", g.argv[2]); |
| 1074 | } |
| 1075 | if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){ |
| 1076 | fossil_fatal("cannot read %s", g.argv[3]); |
| 1077 | } |
| 1078 | if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){ |
| 1079 | fossil_fatal("cannot read %s", g.argv[4]); |
| 1080 | } |
| 1081 | nConflict = merge_three_blobs(&s); |
| 1082 | if( g.argc==6 ){ |
| 1083 | s.zOut = file_tail(g.argv[5]); |
| 1084 | blob_write_to_file(s.pOut, g.argv[5]); |
| 1085 | }else{ |
| 1086 | s.zOut = "(Merge Result)"; |
| 1087 | blob_write_to_file(s.pOut, "-"); |
| 1088 | } |
| 1089 | s.xDestroy(&s); |
| 1090 | blob_reset(&pivot); |
| 1091 | blob_reset(&v1); |
| 1092 | blob_reset(&v2); |
| 1093 | blob_reset(&out); |
| 1094 | if( nConflict>0 && !noWarn ){ |
| 1095 | fossil_warning("WARNING: %d merge conflicts", nConflict); |
| 1096 | } |
| 1097 | } |
| 1098 | |
| 1099 | /* |
| 1100 | ** aSubst is an array of string pairs. The first element of each pair is |
| 1101 | ** a string that begins with %. The second element is a replacement for that |
| @@ -505,32 +1133,32 @@ | |
| 1133 | |
| 1134 | #if INTERFACE |
| 1135 | /* |
| 1136 | ** Flags to the 3-way merger |
| 1137 | */ |
| 1138 | #define MERGE_DRYRUN 0x0001 |
| 1139 | /* |
| 1140 | ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain |
| 1141 | ** its temporary files on error. By default they are removed after the |
| 1142 | ** merge, regardless of success or failure. |
| 1143 | */ |
| 1144 | #define MERGE_KEEP_FILES 0x0002 |
| 1145 | #endif |
| 1146 | |
| 1147 | |
| 1148 | /* |
| 1149 | ** This routine is a wrapper around merge_three_blobs() with the following |
| 1150 | ** enhancements: |
| 1151 | ** |
| 1152 | ** (1) If the merge-command is defined, then use the external merging |
| 1153 | ** program specified instead of the built-in blob-merge to do the |
| 1154 | ** merging. Panic if the external merger fails. |
| 1155 | ** ** Not currently implemented ** |
| 1156 | ** |
| 1157 | ** (2) If gmerge-command is defined and there are merge conflicts in |
| 1158 | ** merge_three_blobs() then invoke the external graphical merger |
| 1159 | ** to resolve the conflicts. |
| 1160 | ** |
| 1161 | ** (3) If a merge conflict occurs and gmerge-command is not defined, |
| 1162 | ** then write the pivot, original, and merge-in files to the |
| 1163 | ** filesystem. |
| 1164 | */ |
| @@ -539,30 +1167,37 @@ | |
| 1167 | const char *zV1, /* Name of file for version merging into (mine) */ |
| 1168 | Blob *pV2, /* Version merging from (yours) */ |
| 1169 | Blob *pOut, /* Output written here */ |
| 1170 | unsigned mergeFlags /* Flags that control operation */ |
| 1171 | ){ |
| 1172 | Blob v1; /* Content of zV1 */ |
| 1173 | int rc; /* Return code of subroutines and this routine */ |
| 1174 | const char *zGMerge; /* Name of the gmerge command */ |
| 1175 | MergeBuilder s; /* The merge state */ |
| 1176 | |
| 1177 | mergebuilder_init_text(&s); |
| 1178 | s.pPivot = pPivot; |
| 1179 | s.pV1 = &v1; |
| 1180 | s.pV2 = pV2; |
| 1181 | blob_zero(pOut); |
| 1182 | s.pOut = pOut; |
| 1183 | blob_read_from_file(s.pV1, zV1, ExtFILE); |
| 1184 | rc = merge_three_blobs(&s); |
| 1185 | zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); |
| 1186 | if( (mergeFlags & MERGE_DRYRUN)==0 |
| 1187 | && ((zGMerge!=0 && zGMerge[0]!=0) |
| 1188 | || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ |
| 1189 | char *zPivot; /* Name of the pivot file */ |
| 1190 | char *zOrig; /* Name of the original content file */ |
| 1191 | char *zOther; /* Name of the merge file */ |
| 1192 | |
| 1193 | zPivot = file_newname(zV1, "baseline", 1); |
| 1194 | blob_write_to_file(s.pPivot, zPivot); |
| 1195 | zOrig = file_newname(zV1, "original", 1); |
| 1196 | blob_write_to_file(s.pV1, zOrig); |
| 1197 | zOther = file_newname(zV1, "merge", 1); |
| 1198 | blob_write_to_file(s.pV2, zOther); |
| 1199 | if( rc>0 ){ |
| 1200 | if( zGMerge && zGMerge[0] ){ |
| 1201 | char *zOut; /* Temporary output file */ |
| 1202 | char *zCmd; /* Command to invoke */ |
| 1203 | const char *azSubst[8]; /* Strings to be substituted */ |
| @@ -590,7 +1225,8 @@ | |
| 1225 | fossil_free(zPivot); |
| 1226 | fossil_free(zOrig); |
| 1227 | fossil_free(zOther); |
| 1228 | } |
| 1229 | blob_reset(&v1); |
| 1230 | s.xDestroy(&s); |
| 1231 | return rc; |
| 1232 | } |
| 1233 |
+151
-24
| --- src/name.c | ||
| +++ src/name.c | ||
| @@ -65,33 +65,51 @@ | ||
| 65 | 65 | ** the string is a hash prefix and NULL is returned if it is. If the |
| 66 | 66 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 67 | 67 | ** of the input string only, without reference to the artifact table. |
| 68 | 68 | */ |
| 69 | 69 | char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){ |
| 70 | - static char zEDate[20]; | |
| 70 | + static char zEDate[24]; | |
| 71 | 71 | static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; |
| 72 | 72 | int n = (int)strlen(zIn); |
| 73 | 73 | int i, j; |
| 74 | + int addZulu = 0; | |
| 74 | 75 | |
| 75 | - /* Only three forms allowed: | |
| 76 | - ** (1) YYYYMMDD | |
| 77 | - ** (2) YYYYMMDDHHMM | |
| 78 | - ** (3) YYYYMMDDHHMMSS | |
| 76 | + /* These forms are allowed: | |
| 77 | + ** | |
| 78 | + ** 123456789 1234 123456789 123456789 | |
| 79 | + ** (1) YYYYMMDD => YYYY-MM-DD | |
| 80 | + ** (2) YYYYMMDDHHMM => YYYY-MM-DD HH:MM | |
| 81 | + ** (3) YYYYMMDDHHMMSS => YYYY-MM-DD HH:MM:SS | |
| 82 | + ** | |
| 83 | + ** An optional "Z" zulu timezone designator is allowed at the end. | |
| 79 | 84 | */ |
| 80 | - if( n!=8 && n!=12 && n!=14 ) return 0; | |
| 85 | + if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){ | |
| 86 | + n--; | |
| 87 | + addZulu = 1; | |
| 88 | + } | |
| 89 | + if( n!=8 && n!=12 && n!=14 ){ | |
| 90 | + return 0; | |
| 91 | + } | |
| 81 | 92 | |
| 82 | 93 | /* Every character must be a digit */ |
| 83 | 94 | for(i=0; fossil_isdigit(zIn[i]); i++){} |
| 84 | - if( i!=n ) return 0; | |
| 95 | + if( i!=n && (!addZulu || i!=n+1) ) return 0; | |
| 85 | 96 | |
| 86 | 97 | /* Expand the date */ |
| 87 | - for(i=j=0; zIn[i]; i++){ | |
| 98 | + for(i=j=0; i<n; i++){ | |
| 88 | 99 | if( i>=4 && (i%2)==0 ){ |
| 89 | 100 | zEDate[j++] = aPunct[i/2]; |
| 90 | 101 | } |
| 91 | 102 | zEDate[j++] = zIn[i]; |
| 92 | 103 | } |
| 104 | + if( addZulu ){ | |
| 105 | + if( j==10 ){ | |
| 106 | + memcpy(&zEDate[10]," 00:00", 6); | |
| 107 | + j += 6; | |
| 108 | + } | |
| 109 | + zEDate[j++] = 'Z'; | |
| 110 | + } | |
| 93 | 111 | zEDate[j] = 0; |
| 94 | 112 | |
| 95 | 113 | /* Check for reasonable date values. |
| 96 | 114 | ** Offset references: |
| 97 | 115 | ** YYYY-MM-DD HH:MM:SS |
| @@ -111,11 +129,11 @@ | ||
| 111 | 129 | if( i>60 ) return 0; |
| 112 | 130 | if( n==14 && atoi(zEDate+17)>60 ) return 0; |
| 113 | 131 | } |
| 114 | 132 | |
| 115 | 133 | /* The string is not also a hash prefix */ |
| 116 | - if( bVerifyNotAHash ){ | |
| 134 | + if( bVerifyNotAHash && !addZulu ){ | |
| 117 | 135 | if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0; |
| 118 | 136 | } |
| 119 | 137 | |
| 120 | 138 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 121 | 139 | return zEDate; |
| @@ -453,10 +471,12 @@ | ||
| 453 | 471 | }else if( fossil_strcmp(zTag, "next")==0 ){ |
| 454 | 472 | rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" |
| 455 | 473 | " ORDER BY isprim DESC, mtime DESC", ridCkout); |
| 456 | 474 | }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){ |
| 457 | 475 | rid = RID_CKOUT; |
| 476 | + assert(ridCkout>0); | |
| 477 | + g.localOpen = ridCkout; | |
| 458 | 478 | } |
| 459 | 479 | if( rid ) return rid; |
| 460 | 480 | } |
| 461 | 481 | |
| 462 | 482 | /* Date and times */ |
| @@ -699,10 +719,13 @@ | ||
| 699 | 719 | }else if( rid==0 ){ |
| 700 | 720 | fossil_error(iErrPriority, "cannot resolve name: %s", zName); |
| 701 | 721 | return 1; |
| 702 | 722 | }else{ |
| 703 | 723 | blob_reset(pName); |
| 724 | + if( RID_CKOUT==rid ) { | |
| 725 | + rid = g.localOpen; | |
| 726 | + } | |
| 704 | 727 | db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 705 | 728 | return 0; |
| 706 | 729 | } |
| 707 | 730 | } |
| 708 | 731 | |
| @@ -1676,34 +1699,44 @@ | ||
| 1676 | 1699 | ** n=N Show N artifacts |
| 1677 | 1700 | ** s=S Start with artifact number S |
| 1678 | 1701 | ** priv Show only unpublished or private artifacts |
| 1679 | 1702 | ** phan Show only phantom artifacts |
| 1680 | 1703 | ** hclr Color code hash types (SHA1 vs SHA3) |
| 1704 | +** recent Show the most recent N artifacts | |
| 1681 | 1705 | */ |
| 1682 | 1706 | void bloblist_page(void){ |
| 1683 | 1707 | Stmt q; |
| 1684 | 1708 | int s = atoi(PD("s","0")); |
| 1685 | 1709 | int n = atoi(PD("n","5000")); |
| 1686 | 1710 | int mx = db_int(0, "SELECT max(rid) FROM blob"); |
| 1687 | 1711 | int privOnly = PB("priv"); |
| 1688 | 1712 | int phantomOnly = PB("phan"); |
| 1689 | 1713 | int hashClr = PB("hclr"); |
| 1714 | + int bRecent = PB("recent"); | |
| 1715 | + int bUnclst = PB("unclustered"); | |
| 1690 | 1716 | char *zRange; |
| 1691 | 1717 | char *zSha1Bg; |
| 1692 | 1718 | char *zSha3Bg; |
| 1693 | 1719 | |
| 1694 | 1720 | login_check_credentials(); |
| 1695 | 1721 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1696 | 1722 | cgi_check_for_malice(); |
| 1697 | 1723 | style_header("List Of Artifacts"); |
| 1698 | 1724 | style_submenu_element("250 Largest", "bigbloblist"); |
| 1725 | + if( bRecent==0 || n!=250 ){ | |
| 1726 | + style_submenu_element("Recent","bloblist?n=250&recent"); | |
| 1727 | + } | |
| 1728 | + if( bUnclst==0 ){ | |
| 1729 | + style_submenu_element("Unclustered","bloblist?unclustered"); | |
| 1730 | + } | |
| 1699 | 1731 | if( g.perm.Admin ){ |
| 1700 | 1732 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 1701 | 1733 | } |
| 1702 | 1734 | if( !phantomOnly ){ |
| 1703 | 1735 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1704 | 1736 | } |
| 1737 | + style_submenu_element("Clusters","clusterlist"); | |
| 1705 | 1738 | if( g.perm.Private || g.perm.Admin ){ |
| 1706 | 1739 | if( !privOnly ){ |
| 1707 | 1740 | style_submenu_element("Private", "bloblist?priv"); |
| 1708 | 1741 | } |
| 1709 | 1742 | }else{ |
| @@ -1710,58 +1743,70 @@ | ||
| 1710 | 1743 | privOnly = 0; |
| 1711 | 1744 | } |
| 1712 | 1745 | if( g.perm.Write ){ |
| 1713 | 1746 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 1714 | 1747 | } |
| 1715 | - if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){ | |
| 1748 | + if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){ | |
| 1716 | 1749 | int i; |
| 1717 | 1750 | @ <p>Select a range of artifacts to view:</p> |
| 1718 | 1751 | @ <ul> |
| 1719 | 1752 | for(i=1; i<=mx; i+=n){ |
| 1720 | 1753 | @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n)) |
| 1721 | 1754 | @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a> |
| 1722 | 1755 | } |
| 1756 | + @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a> | |
| 1757 | + @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a> | |
| 1723 | 1758 | @ </ul> |
| 1724 | 1759 | style_finish_page(); |
| 1725 | 1760 | return; |
| 1726 | 1761 | } |
| 1727 | 1762 | if( phantomOnly || privOnly || mx>n ){ |
| 1728 | 1763 | style_submenu_element("Index", "bloblist"); |
| 1729 | 1764 | } |
| 1730 | 1765 | if( privOnly ){ |
| 1766 | + @ <h2>Private Artifacts</h2> | |
| 1731 | 1767 | zRange = mprintf("IN private"); |
| 1732 | 1768 | }else if( phantomOnly ){ |
| 1769 | + @ <h2>Phantom Artifacts</h2> | |
| 1733 | 1770 | zRange = mprintf("IN phantom"); |
| 1771 | + }else if( bUnclst ){ | |
| 1772 | + @ <h2>Unclustered Artifacts</h2> | |
| 1773 | + zRange = mprintf("IN unclustered"); | |
| 1774 | + }else if( bRecent ){ | |
| 1775 | + @ <h2>%d(n) Most Recent Artifacts</h2> | |
| 1776 | + zRange = mprintf(">=(SELECT rid FROM blob" | |
| 1777 | + " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n); | |
| 1734 | 1778 | }else{ |
| 1735 | 1779 | zRange = mprintf("BETWEEN %d AND %d", s, s+n-1); |
| 1736 | 1780 | } |
| 1737 | 1781 | describe_artifacts(zRange); |
| 1738 | 1782 | fossil_free(zRange); |
| 1739 | 1783 | db_prepare(&q, |
| 1740 | - "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref" | |
| 1741 | - " FROM description ORDER BY rid" | |
| 1784 | + /* 0 1 2 3 4 5 6 */ | |
| 1785 | + "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, " | |
| 1786 | + " datetime(rcvfrom.mtime)" | |
| 1787 | + " FROM description LEFT JOIN rcvfrom USING(rcvid)" | |
| 1788 | + " ORDER BY rid %s", | |
| 1789 | + ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/ | |
| 1742 | 1790 | ); |
| 1743 | 1791 | if( skin_detail_boolean("white-foreground") ){ |
| 1744 | 1792 | zSha1Bg = "#714417"; |
| 1745 | 1793 | zSha3Bg = "#177117"; |
| 1746 | 1794 | }else{ |
| 1747 | 1795 | zSha1Bg = "#ebffb0"; |
| 1748 | 1796 | zSha3Bg = "#b0ffb0"; |
| 1749 | 1797 | } |
| 1750 | 1798 | @ <table cellpadding="2" cellspacing="0" border="1"> |
| 1751 | - if( g.perm.Admin ){ | |
| 1752 | - @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks | |
| 1753 | - }else{ | |
| 1754 | - @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks | |
| 1755 | - } | |
| 1799 | + @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks | |
| 1756 | 1800 | while( db_step(&q)==SQLITE_ROW ){ |
| 1757 | 1801 | int rid = db_column_int(&q,0); |
| 1758 | 1802 | const char *zUuid = db_column_text(&q, 1); |
| 1759 | 1803 | const char *zDesc = db_column_text(&q, 2); |
| 1760 | 1804 | int isPriv = db_column_int(&q,3); |
| 1761 | 1805 | int isPhantom = db_column_int(&q,4); |
| 1762 | - const char *zRef = db_column_text(&q,6); | |
| 1806 | + const char *zRef = db_column_text(&q,5); | |
| 1807 | + const char *zDate = db_column_text(&q,7); | |
| 1763 | 1808 | if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ |
| 1764 | 1809 | /* Don't show private artifacts to users without Private (x) permission */ |
| 1765 | 1810 | continue; |
| 1766 | 1811 | } |
| 1767 | 1812 | if( hashClr ){ |
| @@ -1770,16 +1815,14 @@ | ||
| 1770 | 1815 | }else{ |
| 1771 | 1816 | @ <tr><td align="right">%d(rid)</td> |
| 1772 | 1817 | } |
| 1773 | 1818 | @ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td> |
| 1774 | 1819 | if( g.perm.Admin ){ |
| 1775 | - int rcvid = db_column_int(&q,5); | |
| 1776 | - if( rcvid<=0 ){ | |
| 1777 | - @ <td> | |
| 1778 | - }else{ | |
| 1779 | - @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a> | |
| 1780 | - } | |
| 1820 | + int rcvid = db_column_int(&q, 6); | |
| 1821 | + @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a> | |
| 1822 | + }else{ | |
| 1823 | + @ <td>%h(zDate) | |
| 1781 | 1824 | } |
| 1782 | 1825 | @ <td align="left">%h(zDesc)</td> |
| 1783 | 1826 | if( zRef && zRef[0] ){ |
| 1784 | 1827 | @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a> |
| 1785 | 1828 | }else{ |
| @@ -2163,5 +2206,89 @@ | ||
| 2163 | 2206 | " ORDER BY 1"); |
| 2164 | 2207 | @ <h1>Hash Prefix Collisions on All Artifacts</h1> |
| 2165 | 2208 | collision_report("SELECT uuid FROM blob ORDER BY 1"); |
| 2166 | 2209 | style_finish_page(); |
| 2167 | 2210 | } |
| 2211 | + | |
| 2212 | +/* | |
| 2213 | +** WEBPAGE: clusterlist | |
| 2214 | +** | |
| 2215 | +** Show information about all cluster artifacts in the database. | |
| 2216 | +*/ | |
| 2217 | +void clusterlist_page(void){ | |
| 2218 | + Stmt q; | |
| 2219 | + int cnt = 1; | |
| 2220 | + sqlite3_int64 szTotal = 0; | |
| 2221 | + sqlite3_int64 szCTotal = 0; | |
| 2222 | + login_check_credentials(); | |
| 2223 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 2224 | + style_header("All Cluster Artifacts"); | |
| 2225 | + style_submenu_element("All Artifactst", "bloblist"); | |
| 2226 | + if( g.perm.Admin ){ | |
| 2227 | + style_submenu_element("Artifact Log", "rcvfromlist"); | |
| 2228 | + } | |
| 2229 | + style_submenu_element("Phantoms", "bloblist?phan"); | |
| 2230 | + if( g.perm.Write ){ | |
| 2231 | + style_submenu_element("Artifact Stats", "artifact_stats"); | |
| 2232 | + } | |
| 2233 | + | |
| 2234 | + db_prepare(&q, | |
| 2235 | + "SELECT blob.uuid, " | |
| 2236 | + " blob.size, " | |
| 2237 | + " octet_length(blob.content), " | |
| 2238 | + " datetime(rcvfrom.mtime)," | |
| 2239 | + " user.login," | |
| 2240 | + " rcvfrom.ipaddr" | |
| 2241 | + " FROM tagxref JOIN blob ON tagxref.rid=blob.rid" | |
| 2242 | + " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid" | |
| 2243 | + " LEFT JOIN user ON user.uid=rcvfrom.uid" | |
| 2244 | + " WHERE tagxref.tagid=%d" | |
| 2245 | + " ORDER BY rcvfrom.mtime, blob.uuid", | |
| 2246 | + TAG_CLUSTER | |
| 2247 | + ); | |
| 2248 | + @ <table cellpadding="2" cellspacing="0" border="1"> | |
| 2249 | + @ <tr><th> | |
| 2250 | + @ <th>Hash | |
| 2251 | + @ <th>Date Received | |
| 2252 | + @ <th>Size | |
| 2253 | + @ <th>Compressed Size | |
| 2254 | + if( g.perm.Admin ){ | |
| 2255 | + @ <th>User<th>IP-Address | |
| 2256 | + } | |
| 2257 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 2258 | + const char *zUuid = db_column_text(&q, 0); | |
| 2259 | + sqlite3_int64 sz = db_column_int64(&q, 1); | |
| 2260 | + sqlite3_int64 szC = db_column_int64(&q, 2); | |
| 2261 | + const char *zDate = db_column_text(&q, 3); | |
| 2262 | + const char *zUser = db_column_text(&q, 4); | |
| 2263 | + const char *zIp = db_column_text(&q, 5); | |
| 2264 | + szTotal += sz; | |
| 2265 | + szCTotal += szC; | |
| 2266 | + @ <tr><td align="right">%d(cnt++) | |
| 2267 | + @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a> | |
| 2268 | + if( zDate ){ | |
| 2269 | + @ <td>%h(zDate) | |
| 2270 | + }else{ | |
| 2271 | + @ <td> | |
| 2272 | + } | |
| 2273 | + @ <td align="right">%,lld(sz) | |
| 2274 | + @ <td align="right">%,lld(szC) | |
| 2275 | + if( g.perm.Admin ){ | |
| 2276 | + if( zUser ){ | |
| 2277 | + @ <td>%h(zUser) | |
| 2278 | + }else{ | |
| 2279 | + @ <td> | |
| 2280 | + } | |
| 2281 | + if( zIp ){ | |
| 2282 | + @ <td>%h(zIp) | |
| 2283 | + }else{ | |
| 2284 | + @ <td> | |
| 2285 | + } | |
| 2286 | + } | |
| 2287 | + @ </tr> | |
| 2288 | + } | |
| 2289 | + @ </table> | |
| 2290 | + db_finalize(&q); | |
| 2291 | + @ <p>Total size of all clusters: %,lld(szTotal) bytes, | |
| 2292 | + @ %,lld(szCTotal) bytes compressed</p> | |
| 2293 | + style_finish_page(); | |
| 2294 | +} | |
| 2168 | 2295 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -65,33 +65,51 @@ | |
| 65 | ** the string is a hash prefix and NULL is returned if it is. If the |
| 66 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 67 | ** of the input string only, without reference to the artifact table. |
| 68 | */ |
| 69 | char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){ |
| 70 | static char zEDate[20]; |
| 71 | static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; |
| 72 | int n = (int)strlen(zIn); |
| 73 | int i, j; |
| 74 | |
| 75 | /* Only three forms allowed: |
| 76 | ** (1) YYYYMMDD |
| 77 | ** (2) YYYYMMDDHHMM |
| 78 | ** (3) YYYYMMDDHHMMSS |
| 79 | */ |
| 80 | if( n!=8 && n!=12 && n!=14 ) return 0; |
| 81 | |
| 82 | /* Every character must be a digit */ |
| 83 | for(i=0; fossil_isdigit(zIn[i]); i++){} |
| 84 | if( i!=n ) return 0; |
| 85 | |
| 86 | /* Expand the date */ |
| 87 | for(i=j=0; zIn[i]; i++){ |
| 88 | if( i>=4 && (i%2)==0 ){ |
| 89 | zEDate[j++] = aPunct[i/2]; |
| 90 | } |
| 91 | zEDate[j++] = zIn[i]; |
| 92 | } |
| 93 | zEDate[j] = 0; |
| 94 | |
| 95 | /* Check for reasonable date values. |
| 96 | ** Offset references: |
| 97 | ** YYYY-MM-DD HH:MM:SS |
| @@ -111,11 +129,11 @@ | |
| 111 | if( i>60 ) return 0; |
| 112 | if( n==14 && atoi(zEDate+17)>60 ) return 0; |
| 113 | } |
| 114 | |
| 115 | /* The string is not also a hash prefix */ |
| 116 | if( bVerifyNotAHash ){ |
| 117 | if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0; |
| 118 | } |
| 119 | |
| 120 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 121 | return zEDate; |
| @@ -453,10 +471,12 @@ | |
| 453 | }else if( fossil_strcmp(zTag, "next")==0 ){ |
| 454 | rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" |
| 455 | " ORDER BY isprim DESC, mtime DESC", ridCkout); |
| 456 | }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){ |
| 457 | rid = RID_CKOUT; |
| 458 | } |
| 459 | if( rid ) return rid; |
| 460 | } |
| 461 | |
| 462 | /* Date and times */ |
| @@ -699,10 +719,13 @@ | |
| 699 | }else if( rid==0 ){ |
| 700 | fossil_error(iErrPriority, "cannot resolve name: %s", zName); |
| 701 | return 1; |
| 702 | }else{ |
| 703 | blob_reset(pName); |
| 704 | db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 705 | return 0; |
| 706 | } |
| 707 | } |
| 708 | |
| @@ -1676,34 +1699,44 @@ | |
| 1676 | ** n=N Show N artifacts |
| 1677 | ** s=S Start with artifact number S |
| 1678 | ** priv Show only unpublished or private artifacts |
| 1679 | ** phan Show only phantom artifacts |
| 1680 | ** hclr Color code hash types (SHA1 vs SHA3) |
| 1681 | */ |
| 1682 | void bloblist_page(void){ |
| 1683 | Stmt q; |
| 1684 | int s = atoi(PD("s","0")); |
| 1685 | int n = atoi(PD("n","5000")); |
| 1686 | int mx = db_int(0, "SELECT max(rid) FROM blob"); |
| 1687 | int privOnly = PB("priv"); |
| 1688 | int phantomOnly = PB("phan"); |
| 1689 | int hashClr = PB("hclr"); |
| 1690 | char *zRange; |
| 1691 | char *zSha1Bg; |
| 1692 | char *zSha3Bg; |
| 1693 | |
| 1694 | login_check_credentials(); |
| 1695 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1696 | cgi_check_for_malice(); |
| 1697 | style_header("List Of Artifacts"); |
| 1698 | style_submenu_element("250 Largest", "bigbloblist"); |
| 1699 | if( g.perm.Admin ){ |
| 1700 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 1701 | } |
| 1702 | if( !phantomOnly ){ |
| 1703 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1704 | } |
| 1705 | if( g.perm.Private || g.perm.Admin ){ |
| 1706 | if( !privOnly ){ |
| 1707 | style_submenu_element("Private", "bloblist?priv"); |
| 1708 | } |
| 1709 | }else{ |
| @@ -1710,58 +1743,70 @@ | |
| 1710 | privOnly = 0; |
| 1711 | } |
| 1712 | if( g.perm.Write ){ |
| 1713 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 1714 | } |
| 1715 | if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){ |
| 1716 | int i; |
| 1717 | @ <p>Select a range of artifacts to view:</p> |
| 1718 | @ <ul> |
| 1719 | for(i=1; i<=mx; i+=n){ |
| 1720 | @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n)) |
| 1721 | @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a> |
| 1722 | } |
| 1723 | @ </ul> |
| 1724 | style_finish_page(); |
| 1725 | return; |
| 1726 | } |
| 1727 | if( phantomOnly || privOnly || mx>n ){ |
| 1728 | style_submenu_element("Index", "bloblist"); |
| 1729 | } |
| 1730 | if( privOnly ){ |
| 1731 | zRange = mprintf("IN private"); |
| 1732 | }else if( phantomOnly ){ |
| 1733 | zRange = mprintf("IN phantom"); |
| 1734 | }else{ |
| 1735 | zRange = mprintf("BETWEEN %d AND %d", s, s+n-1); |
| 1736 | } |
| 1737 | describe_artifacts(zRange); |
| 1738 | fossil_free(zRange); |
| 1739 | db_prepare(&q, |
| 1740 | "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref" |
| 1741 | " FROM description ORDER BY rid" |
| 1742 | ); |
| 1743 | if( skin_detail_boolean("white-foreground") ){ |
| 1744 | zSha1Bg = "#714417"; |
| 1745 | zSha3Bg = "#177117"; |
| 1746 | }else{ |
| 1747 | zSha1Bg = "#ebffb0"; |
| 1748 | zSha3Bg = "#b0ffb0"; |
| 1749 | } |
| 1750 | @ <table cellpadding="2" cellspacing="0" border="1"> |
| 1751 | if( g.perm.Admin ){ |
| 1752 | @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks |
| 1753 | }else{ |
| 1754 | @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks |
| 1755 | } |
| 1756 | while( db_step(&q)==SQLITE_ROW ){ |
| 1757 | int rid = db_column_int(&q,0); |
| 1758 | const char *zUuid = db_column_text(&q, 1); |
| 1759 | const char *zDesc = db_column_text(&q, 2); |
| 1760 | int isPriv = db_column_int(&q,3); |
| 1761 | int isPhantom = db_column_int(&q,4); |
| 1762 | const char *zRef = db_column_text(&q,6); |
| 1763 | if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ |
| 1764 | /* Don't show private artifacts to users without Private (x) permission */ |
| 1765 | continue; |
| 1766 | } |
| 1767 | if( hashClr ){ |
| @@ -1770,16 +1815,14 @@ | |
| 1770 | }else{ |
| 1771 | @ <tr><td align="right">%d(rid)</td> |
| 1772 | } |
| 1773 | @ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td> |
| 1774 | if( g.perm.Admin ){ |
| 1775 | int rcvid = db_column_int(&q,5); |
| 1776 | if( rcvid<=0 ){ |
| 1777 | @ <td> |
| 1778 | }else{ |
| 1779 | @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a> |
| 1780 | } |
| 1781 | } |
| 1782 | @ <td align="left">%h(zDesc)</td> |
| 1783 | if( zRef && zRef[0] ){ |
| 1784 | @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a> |
| 1785 | }else{ |
| @@ -2163,5 +2206,89 @@ | |
| 2163 | " ORDER BY 1"); |
| 2164 | @ <h1>Hash Prefix Collisions on All Artifacts</h1> |
| 2165 | collision_report("SELECT uuid FROM blob ORDER BY 1"); |
| 2166 | style_finish_page(); |
| 2167 | } |
| 2168 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -65,33 +65,51 @@ | |
| 65 | ** the string is a hash prefix and NULL is returned if it is. If the |
| 66 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 67 | ** of the input string only, without reference to the artifact table. |
| 68 | */ |
| 69 | char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){ |
| 70 | static char zEDate[24]; |
| 71 | static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; |
| 72 | int n = (int)strlen(zIn); |
| 73 | int i, j; |
| 74 | int addZulu = 0; |
| 75 | |
| 76 | /* These forms are allowed: |
| 77 | ** |
| 78 | ** 123456789 1234 123456789 123456789 |
| 79 | ** (1) YYYYMMDD => YYYY-MM-DD |
| 80 | ** (2) YYYYMMDDHHMM => YYYY-MM-DD HH:MM |
| 81 | ** (3) YYYYMMDDHHMMSS => YYYY-MM-DD HH:MM:SS |
| 82 | ** |
| 83 | ** An optional "Z" zulu timezone designator is allowed at the end. |
| 84 | */ |
| 85 | if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){ |
| 86 | n--; |
| 87 | addZulu = 1; |
| 88 | } |
| 89 | if( n!=8 && n!=12 && n!=14 ){ |
| 90 | return 0; |
| 91 | } |
| 92 | |
| 93 | /* Every character must be a digit */ |
| 94 | for(i=0; fossil_isdigit(zIn[i]); i++){} |
| 95 | if( i!=n && (!addZulu || i!=n+1) ) return 0; |
| 96 | |
| 97 | /* Expand the date */ |
| 98 | for(i=j=0; i<n; i++){ |
| 99 | if( i>=4 && (i%2)==0 ){ |
| 100 | zEDate[j++] = aPunct[i/2]; |
| 101 | } |
| 102 | zEDate[j++] = zIn[i]; |
| 103 | } |
| 104 | if( addZulu ){ |
| 105 | if( j==10 ){ |
| 106 | memcpy(&zEDate[10]," 00:00", 6); |
| 107 | j += 6; |
| 108 | } |
| 109 | zEDate[j++] = 'Z'; |
| 110 | } |
| 111 | zEDate[j] = 0; |
| 112 | |
| 113 | /* Check for reasonable date values. |
| 114 | ** Offset references: |
| 115 | ** YYYY-MM-DD HH:MM:SS |
| @@ -111,11 +129,11 @@ | |
| 129 | if( i>60 ) return 0; |
| 130 | if( n==14 && atoi(zEDate+17)>60 ) return 0; |
| 131 | } |
| 132 | |
| 133 | /* The string is not also a hash prefix */ |
| 134 | if( bVerifyNotAHash && !addZulu ){ |
| 135 | if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0; |
| 136 | } |
| 137 | |
| 138 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 139 | return zEDate; |
| @@ -453,10 +471,12 @@ | |
| 471 | }else if( fossil_strcmp(zTag, "next")==0 ){ |
| 472 | rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" |
| 473 | " ORDER BY isprim DESC, mtime DESC", ridCkout); |
| 474 | }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){ |
| 475 | rid = RID_CKOUT; |
| 476 | assert(ridCkout>0); |
| 477 | g.localOpen = ridCkout; |
| 478 | } |
| 479 | if( rid ) return rid; |
| 480 | } |
| 481 | |
| 482 | /* Date and times */ |
| @@ -699,10 +719,13 @@ | |
| 719 | }else if( rid==0 ){ |
| 720 | fossil_error(iErrPriority, "cannot resolve name: %s", zName); |
| 721 | return 1; |
| 722 | }else{ |
| 723 | blob_reset(pName); |
| 724 | if( RID_CKOUT==rid ) { |
| 725 | rid = g.localOpen; |
| 726 | } |
| 727 | db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 728 | return 0; |
| 729 | } |
| 730 | } |
| 731 | |
| @@ -1676,34 +1699,44 @@ | |
| 1699 | ** n=N Show N artifacts |
| 1700 | ** s=S Start with artifact number S |
| 1701 | ** priv Show only unpublished or private artifacts |
| 1702 | ** phan Show only phantom artifacts |
| 1703 | ** hclr Color code hash types (SHA1 vs SHA3) |
| 1704 | ** recent Show the most recent N artifacts |
| 1705 | */ |
| 1706 | void bloblist_page(void){ |
| 1707 | Stmt q; |
| 1708 | int s = atoi(PD("s","0")); |
| 1709 | int n = atoi(PD("n","5000")); |
| 1710 | int mx = db_int(0, "SELECT max(rid) FROM blob"); |
| 1711 | int privOnly = PB("priv"); |
| 1712 | int phantomOnly = PB("phan"); |
| 1713 | int hashClr = PB("hclr"); |
| 1714 | int bRecent = PB("recent"); |
| 1715 | int bUnclst = PB("unclustered"); |
| 1716 | char *zRange; |
| 1717 | char *zSha1Bg; |
| 1718 | char *zSha3Bg; |
| 1719 | |
| 1720 | login_check_credentials(); |
| 1721 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1722 | cgi_check_for_malice(); |
| 1723 | style_header("List Of Artifacts"); |
| 1724 | style_submenu_element("250 Largest", "bigbloblist"); |
| 1725 | if( bRecent==0 || n!=250 ){ |
| 1726 | style_submenu_element("Recent","bloblist?n=250&recent"); |
| 1727 | } |
| 1728 | if( bUnclst==0 ){ |
| 1729 | style_submenu_element("Unclustered","bloblist?unclustered"); |
| 1730 | } |
| 1731 | if( g.perm.Admin ){ |
| 1732 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 1733 | } |
| 1734 | if( !phantomOnly ){ |
| 1735 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1736 | } |
| 1737 | style_submenu_element("Clusters","clusterlist"); |
| 1738 | if( g.perm.Private || g.perm.Admin ){ |
| 1739 | if( !privOnly ){ |
| 1740 | style_submenu_element("Private", "bloblist?priv"); |
| 1741 | } |
| 1742 | }else{ |
| @@ -1710,58 +1743,70 @@ | |
| 1743 | privOnly = 0; |
| 1744 | } |
| 1745 | if( g.perm.Write ){ |
| 1746 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 1747 | } |
| 1748 | if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){ |
| 1749 | int i; |
| 1750 | @ <p>Select a range of artifacts to view:</p> |
| 1751 | @ <ul> |
| 1752 | for(i=1; i<=mx; i+=n){ |
| 1753 | @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n)) |
| 1754 | @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a> |
| 1755 | } |
| 1756 | @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a> |
| 1757 | @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a> |
| 1758 | @ </ul> |
| 1759 | style_finish_page(); |
| 1760 | return; |
| 1761 | } |
| 1762 | if( phantomOnly || privOnly || mx>n ){ |
| 1763 | style_submenu_element("Index", "bloblist"); |
| 1764 | } |
| 1765 | if( privOnly ){ |
| 1766 | @ <h2>Private Artifacts</h2> |
| 1767 | zRange = mprintf("IN private"); |
| 1768 | }else if( phantomOnly ){ |
| 1769 | @ <h2>Phantom Artifacts</h2> |
| 1770 | zRange = mprintf("IN phantom"); |
| 1771 | }else if( bUnclst ){ |
| 1772 | @ <h2>Unclustered Artifacts</h2> |
| 1773 | zRange = mprintf("IN unclustered"); |
| 1774 | }else if( bRecent ){ |
| 1775 | @ <h2>%d(n) Most Recent Artifacts</h2> |
| 1776 | zRange = mprintf(">=(SELECT rid FROM blob" |
| 1777 | " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n); |
| 1778 | }else{ |
| 1779 | zRange = mprintf("BETWEEN %d AND %d", s, s+n-1); |
| 1780 | } |
| 1781 | describe_artifacts(zRange); |
| 1782 | fossil_free(zRange); |
| 1783 | db_prepare(&q, |
| 1784 | /* 0 1 2 3 4 5 6 */ |
| 1785 | "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, " |
| 1786 | " datetime(rcvfrom.mtime)" |
| 1787 | " FROM description LEFT JOIN rcvfrom USING(rcvid)" |
| 1788 | " ORDER BY rid %s", |
| 1789 | ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/ |
| 1790 | ); |
| 1791 | if( skin_detail_boolean("white-foreground") ){ |
| 1792 | zSha1Bg = "#714417"; |
| 1793 | zSha3Bg = "#177117"; |
| 1794 | }else{ |
| 1795 | zSha1Bg = "#ebffb0"; |
| 1796 | zSha3Bg = "#b0ffb0"; |
| 1797 | } |
| 1798 | @ <table cellpadding="2" cellspacing="0" border="1"> |
| 1799 | @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks |
| 1800 | while( db_step(&q)==SQLITE_ROW ){ |
| 1801 | int rid = db_column_int(&q,0); |
| 1802 | const char *zUuid = db_column_text(&q, 1); |
| 1803 | const char *zDesc = db_column_text(&q, 2); |
| 1804 | int isPriv = db_column_int(&q,3); |
| 1805 | int isPhantom = db_column_int(&q,4); |
| 1806 | const char *zRef = db_column_text(&q,5); |
| 1807 | const char *zDate = db_column_text(&q,7); |
| 1808 | if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ |
| 1809 | /* Don't show private artifacts to users without Private (x) permission */ |
| 1810 | continue; |
| 1811 | } |
| 1812 | if( hashClr ){ |
| @@ -1770,16 +1815,14 @@ | |
| 1815 | }else{ |
| 1816 | @ <tr><td align="right">%d(rid)</td> |
| 1817 | } |
| 1818 | @ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td> |
| 1819 | if( g.perm.Admin ){ |
| 1820 | int rcvid = db_column_int(&q, 6); |
| 1821 | @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a> |
| 1822 | }else{ |
| 1823 | @ <td>%h(zDate) |
| 1824 | } |
| 1825 | @ <td align="left">%h(zDesc)</td> |
| 1826 | if( zRef && zRef[0] ){ |
| 1827 | @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a> |
| 1828 | }else{ |
| @@ -2163,5 +2206,89 @@ | |
| 2206 | " ORDER BY 1"); |
| 2207 | @ <h1>Hash Prefix Collisions on All Artifacts</h1> |
| 2208 | collision_report("SELECT uuid FROM blob ORDER BY 1"); |
| 2209 | style_finish_page(); |
| 2210 | } |
| 2211 | |
| 2212 | /* |
| 2213 | ** WEBPAGE: clusterlist |
| 2214 | ** |
| 2215 | ** Show information about all cluster artifacts in the database. |
| 2216 | */ |
| 2217 | void clusterlist_page(void){ |
| 2218 | Stmt q; |
| 2219 | int cnt = 1; |
| 2220 | sqlite3_int64 szTotal = 0; |
| 2221 | sqlite3_int64 szCTotal = 0; |
| 2222 | login_check_credentials(); |
| 2223 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2224 | style_header("All Cluster Artifacts"); |
| 2225 | style_submenu_element("All Artifactst", "bloblist"); |
| 2226 | if( g.perm.Admin ){ |
| 2227 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 2228 | } |
| 2229 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 2230 | if( g.perm.Write ){ |
| 2231 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2232 | } |
| 2233 | |
| 2234 | db_prepare(&q, |
| 2235 | "SELECT blob.uuid, " |
| 2236 | " blob.size, " |
| 2237 | " octet_length(blob.content), " |
| 2238 | " datetime(rcvfrom.mtime)," |
| 2239 | " user.login," |
| 2240 | " rcvfrom.ipaddr" |
| 2241 | " FROM tagxref JOIN blob ON tagxref.rid=blob.rid" |
| 2242 | " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid" |
| 2243 | " LEFT JOIN user ON user.uid=rcvfrom.uid" |
| 2244 | " WHERE tagxref.tagid=%d" |
| 2245 | " ORDER BY rcvfrom.mtime, blob.uuid", |
| 2246 | TAG_CLUSTER |
| 2247 | ); |
| 2248 | @ <table cellpadding="2" cellspacing="0" border="1"> |
| 2249 | @ <tr><th> |
| 2250 | @ <th>Hash |
| 2251 | @ <th>Date Received |
| 2252 | @ <th>Size |
| 2253 | @ <th>Compressed Size |
| 2254 | if( g.perm.Admin ){ |
| 2255 | @ <th>User<th>IP-Address |
| 2256 | } |
| 2257 | while( db_step(&q)==SQLITE_ROW ){ |
| 2258 | const char *zUuid = db_column_text(&q, 0); |
| 2259 | sqlite3_int64 sz = db_column_int64(&q, 1); |
| 2260 | sqlite3_int64 szC = db_column_int64(&q, 2); |
| 2261 | const char *zDate = db_column_text(&q, 3); |
| 2262 | const char *zUser = db_column_text(&q, 4); |
| 2263 | const char *zIp = db_column_text(&q, 5); |
| 2264 | szTotal += sz; |
| 2265 | szCTotal += szC; |
| 2266 | @ <tr><td align="right">%d(cnt++) |
| 2267 | @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a> |
| 2268 | if( zDate ){ |
| 2269 | @ <td>%h(zDate) |
| 2270 | }else{ |
| 2271 | @ <td> |
| 2272 | } |
| 2273 | @ <td align="right">%,lld(sz) |
| 2274 | @ <td align="right">%,lld(szC) |
| 2275 | if( g.perm.Admin ){ |
| 2276 | if( zUser ){ |
| 2277 | @ <td>%h(zUser) |
| 2278 | }else{ |
| 2279 | @ <td> |
| 2280 | } |
| 2281 | if( zIp ){ |
| 2282 | @ <td>%h(zIp) |
| 2283 | }else{ |
| 2284 | @ <td> |
| 2285 | } |
| 2286 | } |
| 2287 | @ </tr> |
| 2288 | } |
| 2289 | @ </table> |
| 2290 | db_finalize(&q); |
| 2291 | @ <p>Total size of all clusters: %,lld(szTotal) bytes, |
| 2292 | @ %,lld(szCTotal) bytes compressed</p> |
| 2293 | style_finish_page(); |
| 2294 | } |
| 2295 |
+50
-17
| --- src/patch.c | ||
| +++ src/patch.c | ||
| @@ -81,13 +81,12 @@ | ||
| 81 | 81 | sqlite3_context *context, |
| 82 | 82 | int argc, |
| 83 | 83 | sqlite3_value **argv |
| 84 | 84 | ){ |
| 85 | 85 | const char *zFile; |
| 86 | - Blob x, y; | |
| 86 | + Blob x, y, out; | |
| 87 | 87 | int rid; |
| 88 | - char *aOut; | |
| 89 | 88 | int nOut; |
| 90 | 89 | sqlite3_int64 sz; |
| 91 | 90 | |
| 92 | 91 | rid = sqlite3_value_int(argv[0]); |
| 93 | 92 | if( !content_get(rid, &x) ){ |
| @@ -104,34 +103,29 @@ | ||
| 104 | 103 | if( sz<0 ){ |
| 105 | 104 | sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); |
| 106 | 105 | blob_reset(&x); |
| 107 | 106 | return; |
| 108 | 107 | } |
| 109 | - aOut = sqlite3_malloc64(sz+70); | |
| 110 | - if( aOut==0 ){ | |
| 111 | - sqlite3_result_error_nomem(context); | |
| 112 | - blob_reset(&y); | |
| 113 | - blob_reset(&x); | |
| 114 | - return; | |
| 115 | - } | |
| 108 | + blob_init(&out, 0, 0); | |
| 109 | + blob_resize(&out, sz+70); | |
| 116 | 110 | if( blob_size(&x)==blob_size(&y) |
| 117 | 111 | && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 |
| 118 | 112 | ){ |
| 119 | 113 | blob_reset(&y); |
| 120 | 114 | blob_reset(&x); |
| 121 | 115 | sqlite3_result_blob64(context, "", 0, SQLITE_STATIC); |
| 122 | 116 | return; |
| 123 | 117 | } |
| 124 | 118 | nOut = delta_create(blob_buffer(&x),blob_size(&x), |
| 125 | - blob_buffer(&y),blob_size(&y), aOut); | |
| 119 | + blob_buffer(&y),blob_size(&y), blob_buffer(&out)); | |
| 120 | + blob_resize(&out, nOut); | |
| 126 | 121 | blob_reset(&x); |
| 127 | 122 | blob_reset(&y); |
| 128 | - blob_init(&x, aOut, nOut); | |
| 129 | - blob_compress(&x, &x); | |
| 130 | - sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x), | |
| 123 | + blob_compress(&out, &out); | |
| 124 | + sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out), | |
| 131 | 125 | SQLITE_TRANSIENT); |
| 132 | - blob_reset(&x); | |
| 126 | + blob_reset(&out); | |
| 133 | 127 | } |
| 134 | 128 | |
| 135 | 129 | |
| 136 | 130 | /* |
| 137 | 131 | ** Generate a binary patch file and store it into the file |
| @@ -387,11 +381,11 @@ | ||
| 387 | 381 | if( unsaved_changes(0) ){ |
| 388 | 382 | if( (mFlags & PATCH_FORCE)==0 ){ |
| 389 | 383 | fossil_fatal("Cannot apply patch: there are unsaved changes " |
| 390 | 384 | "in the current check-out"); |
| 391 | 385 | }else{ |
| 392 | - blob_appendf(&cmd, "%$ revert", g.nameOfExe); | |
| 386 | + blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe); | |
| 393 | 387 | if( mFlags & PATCH_DRYRUN ){ |
| 394 | 388 | fossil_print("%s\n", blob_str(&cmd)); |
| 395 | 389 | }else{ |
| 396 | 390 | int rc = fossil_system(blob_str(&cmd)); |
| 397 | 391 | if( rc ){ |
| @@ -429,23 +423,27 @@ | ||
| 429 | 423 | } |
| 430 | 424 | } |
| 431 | 425 | } |
| 432 | 426 | blob_reset(&cmd); |
| 433 | 427 | if( db_table_exists("patch","patchmerge") ){ |
| 428 | + int nMerge = 0; | |
| 434 | 429 | db_prepare(&q, |
| 435 | 430 | "SELECT type, mhash, upper(type) FROM patch.patchmerge" |
| 436 | 431 | " WHERE type IN ('merge','cherrypick','backout','integrate')" |
| 437 | 432 | " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
| 438 | 433 | ); |
| 439 | 434 | while( db_step(&q)==SQLITE_ROW ){ |
| 440 | 435 | const char *zType = db_column_text(&q,0); |
| 441 | 436 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 442 | 437 | if( strcmp(zType,"merge")==0 ){ |
| 443 | - blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1)); | |
| 438 | + blob_appendf(&cmd, " merge --noundo --nosync %s\n", | |
| 439 | + db_column_text(&q,1)); | |
| 444 | 440 | }else{ |
| 445 | - blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1)); | |
| 441 | + blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n", | |
| 442 | + zType, db_column_text(&q,1)); | |
| 446 | 443 | } |
| 444 | + nMerge++; | |
| 447 | 445 | if( mFlags & PATCH_VERBOSE ){ |
| 448 | 446 | fossil_print("%-10s %s\n", db_column_text(&q,2), |
| 449 | 447 | db_column_text(&q,0)); |
| 450 | 448 | } |
| 451 | 449 | } |
| @@ -458,10 +456,45 @@ | ||
| 458 | 456 | fossil_fatal("unable to do merges:\n%s", |
| 459 | 457 | blob_str(&cmd)); |
| 460 | 458 | } |
| 461 | 459 | } |
| 462 | 460 | blob_reset(&cmd); |
| 461 | + | |
| 462 | + /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054 | |
| 463 | + ** If one or more merge operations occurred in the patch and there are | |
| 464 | + ** files that are marked as "chnged' in the local VFILE but which | |
| 465 | + ** are not mentioned as having been modified in the patch, then | |
| 466 | + ** revert those files. | |
| 467 | + */ | |
| 468 | + if( nMerge ){ | |
| 469 | + int vid = db_lget_int("checkout", 0); | |
| 470 | + int nRevert = 0; | |
| 471 | + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); | |
| 472 | + blob_appendf(&cmd, " revert --noundo "); | |
| 473 | + db_prepare(&q, | |
| 474 | + "SELECT pathname FROM vfile WHERE vid=%d AND chnged " | |
| 475 | + "EXCEPT SELECT pathname FROM chng", | |
| 476 | + vid | |
| 477 | + ); | |
| 478 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 479 | + blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1); | |
| 480 | + nRevert++; | |
| 481 | + } | |
| 482 | + db_finalize(&q); | |
| 483 | + if( nRevert ){ | |
| 484 | + if( mFlags & PATCH_DRYRUN ){ | |
| 485 | + fossil_print("%s", blob_str(&cmd)); | |
| 486 | + }else{ | |
| 487 | + int rc = fossil_unsafe_system(blob_str(&cmd)); | |
| 488 | + if( rc ){ | |
| 489 | + fossil_fatal("unable to do reverts:\n%s", | |
| 490 | + blob_str(&cmd)); | |
| 491 | + } | |
| 492 | + } | |
| 493 | + } | |
| 494 | + blob_reset(&cmd); | |
| 495 | + } | |
| 463 | 496 | } |
| 464 | 497 | |
| 465 | 498 | /* Deletions */ |
| 466 | 499 | db_prepare(&q, "SELECT pathname FROM patch.chng" |
| 467 | 500 | " WHERE origname IS NULL AND delta IS NULL"); |
| 468 | 501 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -81,13 +81,12 @@ | |
| 81 | sqlite3_context *context, |
| 82 | int argc, |
| 83 | sqlite3_value **argv |
| 84 | ){ |
| 85 | const char *zFile; |
| 86 | Blob x, y; |
| 87 | int rid; |
| 88 | char *aOut; |
| 89 | int nOut; |
| 90 | sqlite3_int64 sz; |
| 91 | |
| 92 | rid = sqlite3_value_int(argv[0]); |
| 93 | if( !content_get(rid, &x) ){ |
| @@ -104,34 +103,29 @@ | |
| 104 | if( sz<0 ){ |
| 105 | sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); |
| 106 | blob_reset(&x); |
| 107 | return; |
| 108 | } |
| 109 | aOut = sqlite3_malloc64(sz+70); |
| 110 | if( aOut==0 ){ |
| 111 | sqlite3_result_error_nomem(context); |
| 112 | blob_reset(&y); |
| 113 | blob_reset(&x); |
| 114 | return; |
| 115 | } |
| 116 | if( blob_size(&x)==blob_size(&y) |
| 117 | && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 |
| 118 | ){ |
| 119 | blob_reset(&y); |
| 120 | blob_reset(&x); |
| 121 | sqlite3_result_blob64(context, "", 0, SQLITE_STATIC); |
| 122 | return; |
| 123 | } |
| 124 | nOut = delta_create(blob_buffer(&x),blob_size(&x), |
| 125 | blob_buffer(&y),blob_size(&y), aOut); |
| 126 | blob_reset(&x); |
| 127 | blob_reset(&y); |
| 128 | blob_init(&x, aOut, nOut); |
| 129 | blob_compress(&x, &x); |
| 130 | sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x), |
| 131 | SQLITE_TRANSIENT); |
| 132 | blob_reset(&x); |
| 133 | } |
| 134 | |
| 135 | |
| 136 | /* |
| 137 | ** Generate a binary patch file and store it into the file |
| @@ -387,11 +381,11 @@ | |
| 387 | if( unsaved_changes(0) ){ |
| 388 | if( (mFlags & PATCH_FORCE)==0 ){ |
| 389 | fossil_fatal("Cannot apply patch: there are unsaved changes " |
| 390 | "in the current check-out"); |
| 391 | }else{ |
| 392 | blob_appendf(&cmd, "%$ revert", g.nameOfExe); |
| 393 | if( mFlags & PATCH_DRYRUN ){ |
| 394 | fossil_print("%s\n", blob_str(&cmd)); |
| 395 | }else{ |
| 396 | int rc = fossil_system(blob_str(&cmd)); |
| 397 | if( rc ){ |
| @@ -429,23 +423,27 @@ | |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | blob_reset(&cmd); |
| 433 | if( db_table_exists("patch","patchmerge") ){ |
| 434 | db_prepare(&q, |
| 435 | "SELECT type, mhash, upper(type) FROM patch.patchmerge" |
| 436 | " WHERE type IN ('merge','cherrypick','backout','integrate')" |
| 437 | " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
| 438 | ); |
| 439 | while( db_step(&q)==SQLITE_ROW ){ |
| 440 | const char *zType = db_column_text(&q,0); |
| 441 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 442 | if( strcmp(zType,"merge")==0 ){ |
| 443 | blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1)); |
| 444 | }else{ |
| 445 | blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1)); |
| 446 | } |
| 447 | if( mFlags & PATCH_VERBOSE ){ |
| 448 | fossil_print("%-10s %s\n", db_column_text(&q,2), |
| 449 | db_column_text(&q,0)); |
| 450 | } |
| 451 | } |
| @@ -458,10 +456,45 @@ | |
| 458 | fossil_fatal("unable to do merges:\n%s", |
| 459 | blob_str(&cmd)); |
| 460 | } |
| 461 | } |
| 462 | blob_reset(&cmd); |
| 463 | } |
| 464 | |
| 465 | /* Deletions */ |
| 466 | db_prepare(&q, "SELECT pathname FROM patch.chng" |
| 467 | " WHERE origname IS NULL AND delta IS NULL"); |
| 468 |
| --- src/patch.c | |
| +++ src/patch.c | |
| @@ -81,13 +81,12 @@ | |
| 81 | sqlite3_context *context, |
| 82 | int argc, |
| 83 | sqlite3_value **argv |
| 84 | ){ |
| 85 | const char *zFile; |
| 86 | Blob x, y, out; |
| 87 | int rid; |
| 88 | int nOut; |
| 89 | sqlite3_int64 sz; |
| 90 | |
| 91 | rid = sqlite3_value_int(argv[0]); |
| 92 | if( !content_get(rid, &x) ){ |
| @@ -104,34 +103,29 @@ | |
| 103 | if( sz<0 ){ |
| 104 | sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); |
| 105 | blob_reset(&x); |
| 106 | return; |
| 107 | } |
| 108 | blob_init(&out, 0, 0); |
| 109 | blob_resize(&out, sz+70); |
| 110 | if( blob_size(&x)==blob_size(&y) |
| 111 | && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 |
| 112 | ){ |
| 113 | blob_reset(&y); |
| 114 | blob_reset(&x); |
| 115 | sqlite3_result_blob64(context, "", 0, SQLITE_STATIC); |
| 116 | return; |
| 117 | } |
| 118 | nOut = delta_create(blob_buffer(&x),blob_size(&x), |
| 119 | blob_buffer(&y),blob_size(&y), blob_buffer(&out)); |
| 120 | blob_resize(&out, nOut); |
| 121 | blob_reset(&x); |
| 122 | blob_reset(&y); |
| 123 | blob_compress(&out, &out); |
| 124 | sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out), |
| 125 | SQLITE_TRANSIENT); |
| 126 | blob_reset(&out); |
| 127 | } |
| 128 | |
| 129 | |
| 130 | /* |
| 131 | ** Generate a binary patch file and store it into the file |
| @@ -387,11 +381,11 @@ | |
| 381 | if( unsaved_changes(0) ){ |
| 382 | if( (mFlags & PATCH_FORCE)==0 ){ |
| 383 | fossil_fatal("Cannot apply patch: there are unsaved changes " |
| 384 | "in the current check-out"); |
| 385 | }else{ |
| 386 | blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe); |
| 387 | if( mFlags & PATCH_DRYRUN ){ |
| 388 | fossil_print("%s\n", blob_str(&cmd)); |
| 389 | }else{ |
| 390 | int rc = fossil_system(blob_str(&cmd)); |
| 391 | if( rc ){ |
| @@ -429,23 +423,27 @@ | |
| 423 | } |
| 424 | } |
| 425 | } |
| 426 | blob_reset(&cmd); |
| 427 | if( db_table_exists("patch","patchmerge") ){ |
| 428 | int nMerge = 0; |
| 429 | db_prepare(&q, |
| 430 | "SELECT type, mhash, upper(type) FROM patch.patchmerge" |
| 431 | " WHERE type IN ('merge','cherrypick','backout','integrate')" |
| 432 | " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" |
| 433 | ); |
| 434 | while( db_step(&q)==SQLITE_ROW ){ |
| 435 | const char *zType = db_column_text(&q,0); |
| 436 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 437 | if( strcmp(zType,"merge")==0 ){ |
| 438 | blob_appendf(&cmd, " merge --noundo --nosync %s\n", |
| 439 | db_column_text(&q,1)); |
| 440 | }else{ |
| 441 | blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n", |
| 442 | zType, db_column_text(&q,1)); |
| 443 | } |
| 444 | nMerge++; |
| 445 | if( mFlags & PATCH_VERBOSE ){ |
| 446 | fossil_print("%-10s %s\n", db_column_text(&q,2), |
| 447 | db_column_text(&q,0)); |
| 448 | } |
| 449 | } |
| @@ -458,10 +456,45 @@ | |
| 456 | fossil_fatal("unable to do merges:\n%s", |
| 457 | blob_str(&cmd)); |
| 458 | } |
| 459 | } |
| 460 | blob_reset(&cmd); |
| 461 | |
| 462 | /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054 |
| 463 | ** If one or more merge operations occurred in the patch and there are |
| 464 | ** files that are marked as "chnged' in the local VFILE but which |
| 465 | ** are not mentioned as having been modified in the patch, then |
| 466 | ** revert those files. |
| 467 | */ |
| 468 | if( nMerge ){ |
| 469 | int vid = db_lget_int("checkout", 0); |
| 470 | int nRevert = 0; |
| 471 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 472 | blob_appendf(&cmd, " revert --noundo "); |
| 473 | db_prepare(&q, |
| 474 | "SELECT pathname FROM vfile WHERE vid=%d AND chnged " |
| 475 | "EXCEPT SELECT pathname FROM chng", |
| 476 | vid |
| 477 | ); |
| 478 | while( db_step(&q)==SQLITE_ROW ){ |
| 479 | blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1); |
| 480 | nRevert++; |
| 481 | } |
| 482 | db_finalize(&q); |
| 483 | if( nRevert ){ |
| 484 | if( mFlags & PATCH_DRYRUN ){ |
| 485 | fossil_print("%s", blob_str(&cmd)); |
| 486 | }else{ |
| 487 | int rc = fossil_unsafe_system(blob_str(&cmd)); |
| 488 | if( rc ){ |
| 489 | fossil_fatal("unable to do reverts:\n%s", |
| 490 | blob_str(&cmd)); |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | blob_reset(&cmd); |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | /* Deletions */ |
| 499 | db_prepare(&q, "SELECT pathname FROM patch.chng" |
| 500 | " WHERE origname IS NULL AND delta IS NULL"); |
| 501 |
+1
| --- src/path.c | ||
| +++ src/path.c | ||
| @@ -542,10 +542,11 @@ | ||
| 542 | 542 | pChng = pAll; |
| 543 | 543 | pAll = pAll->pNext; |
| 544 | 544 | fossil_free(pChng); |
| 545 | 545 | } |
| 546 | 546 | } |
| 547 | + path_reset(); | |
| 547 | 548 | } |
| 548 | 549 | |
| 549 | 550 | /* |
| 550 | 551 | ** COMMAND: test-name-changes |
| 551 | 552 | ** |
| 552 | 553 |
| --- src/path.c | |
| +++ src/path.c | |
| @@ -542,10 +542,11 @@ | |
| 542 | pChng = pAll; |
| 543 | pAll = pAll->pNext; |
| 544 | fossil_free(pChng); |
| 545 | } |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | /* |
| 550 | ** COMMAND: test-name-changes |
| 551 | ** |
| 552 |
| --- src/path.c | |
| +++ src/path.c | |
| @@ -542,10 +542,11 @@ | |
| 542 | pChng = pAll; |
| 543 | pAll = pAll->pNext; |
| 544 | fossil_free(pChng); |
| 545 | } |
| 546 | } |
| 547 | path_reset(); |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** COMMAND: test-name-changes |
| 552 | ** |
| 553 |
+14
-1
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -105,10 +105,11 @@ | ||
| 105 | 105 | Use %!j to include double-quotes around it. */ |
| 106 | 106 | #define etSHELLESC 26 /* Escape a filename for use in a shell command: %$ |
| 107 | 107 | See blob_append_escaped_arg() for details |
| 108 | 108 | "%$" -> adds "./" prefix if necessary. |
| 109 | 109 | "%!$" -> omits the "./" prefix. */ |
| 110 | +#define etHEX 27 /* Encode a string as hexadecimal */ | |
| 110 | 111 | |
| 111 | 112 | |
| 112 | 113 | /* |
| 113 | 114 | ** An "etByte" is an 8-bit unsigned value. |
| 114 | 115 | */ |
| @@ -142,11 +143,11 @@ | ||
| 142 | 143 | ** NB: When modifying this table is it vital that you also update the fmtchr[] |
| 143 | 144 | ** variable to match!!! |
| 144 | 145 | */ |
| 145 | 146 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 146 | 147 | static const char aPrefix[] = "-x0\000X0"; |
| 147 | -static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$"; | |
| 148 | +static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H"; | |
| 148 | 149 | static const et_info fmtinfo[] = { |
| 149 | 150 | { 'd', 10, 1, etRADIX, 0, 0 }, |
| 150 | 151 | { 's', 0, 4, etSTRING, 0, 0 }, |
| 151 | 152 | { 'g', 0, 1, etGENERIC, 30, 0 }, |
| 152 | 153 | { 'z', 0, 6, etDYNSTRING, 0, 0 }, |
| @@ -176,10 +177,11 @@ | ||
| 176 | 177 | { 'n', 0, 0, etSIZE, 0, 0 }, |
| 177 | 178 | { '%', 0, 0, etPERCENT, 0, 0 }, |
| 178 | 179 | { 'p', 16, 0, etPOINTER, 0, 1 }, |
| 179 | 180 | { '/', 0, 0, etPATH, 0, 0 }, |
| 180 | 181 | { '$', 0, 0, etSHELLESC, 0, 0 }, |
| 182 | + { 'H', 0, 0, etHEX, 0, 0 }, | |
| 181 | 183 | { etERROR, 0,0,0,0,0} /* Must be last */ |
| 182 | 184 | }; |
| 183 | 185 | #define etNINFO count(fmtinfo) |
| 184 | 186 | |
| 185 | 187 | /* |
| @@ -843,10 +845,21 @@ | ||
| 843 | 845 | case etSHELLESC: { |
| 844 | 846 | char *zArg = va_arg(ap, char*); |
| 845 | 847 | blob_append_escaped_arg(pBlob, zArg, !flag_altform2); |
| 846 | 848 | length = width = 0; |
| 847 | 849 | break; |
| 850 | + } | |
| 851 | + case etHEX: { | |
| 852 | + char *zArg = va_arg(ap, char*); | |
| 853 | + int szArg = (int)strlen(zArg); | |
| 854 | + int szBlob = blob_size(pBlob); | |
| 855 | + u8 *aBuf; | |
| 856 | + blob_resize(pBlob, szBlob+szArg*2+1); | |
| 857 | + aBuf = (u8*)&blob_buffer(pBlob)[szBlob]; | |
| 858 | + encode16((const u8*)zArg, aBuf, szArg); | |
| 859 | + length = width = 0; | |
| 860 | + break; | |
| 848 | 861 | } |
| 849 | 862 | case etERROR: |
| 850 | 863 | buf[0] = '%'; |
| 851 | 864 | buf[1] = c; |
| 852 | 865 | errorflag = 0; |
| 853 | 866 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -105,10 +105,11 @@ | |
| 105 | Use %!j to include double-quotes around it. */ |
| 106 | #define etSHELLESC 26 /* Escape a filename for use in a shell command: %$ |
| 107 | See blob_append_escaped_arg() for details |
| 108 | "%$" -> adds "./" prefix if necessary. |
| 109 | "%!$" -> omits the "./" prefix. */ |
| 110 | |
| 111 | |
| 112 | /* |
| 113 | ** An "etByte" is an 8-bit unsigned value. |
| 114 | */ |
| @@ -142,11 +143,11 @@ | |
| 142 | ** NB: When modifying this table is it vital that you also update the fmtchr[] |
| 143 | ** variable to match!!! |
| 144 | */ |
| 145 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 146 | static const char aPrefix[] = "-x0\000X0"; |
| 147 | static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$"; |
| 148 | static const et_info fmtinfo[] = { |
| 149 | { 'd', 10, 1, etRADIX, 0, 0 }, |
| 150 | { 's', 0, 4, etSTRING, 0, 0 }, |
| 151 | { 'g', 0, 1, etGENERIC, 30, 0 }, |
| 152 | { 'z', 0, 6, etDYNSTRING, 0, 0 }, |
| @@ -176,10 +177,11 @@ | |
| 176 | { 'n', 0, 0, etSIZE, 0, 0 }, |
| 177 | { '%', 0, 0, etPERCENT, 0, 0 }, |
| 178 | { 'p', 16, 0, etPOINTER, 0, 1 }, |
| 179 | { '/', 0, 0, etPATH, 0, 0 }, |
| 180 | { '$', 0, 0, etSHELLESC, 0, 0 }, |
| 181 | { etERROR, 0,0,0,0,0} /* Must be last */ |
| 182 | }; |
| 183 | #define etNINFO count(fmtinfo) |
| 184 | |
| 185 | /* |
| @@ -843,10 +845,21 @@ | |
| 843 | case etSHELLESC: { |
| 844 | char *zArg = va_arg(ap, char*); |
| 845 | blob_append_escaped_arg(pBlob, zArg, !flag_altform2); |
| 846 | length = width = 0; |
| 847 | break; |
| 848 | } |
| 849 | case etERROR: |
| 850 | buf[0] = '%'; |
| 851 | buf[1] = c; |
| 852 | errorflag = 0; |
| 853 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -105,10 +105,11 @@ | |
| 105 | Use %!j to include double-quotes around it. */ |
| 106 | #define etSHELLESC 26 /* Escape a filename for use in a shell command: %$ |
| 107 | See blob_append_escaped_arg() for details |
| 108 | "%$" -> adds "./" prefix if necessary. |
| 109 | "%!$" -> omits the "./" prefix. */ |
| 110 | #define etHEX 27 /* Encode a string as hexadecimal */ |
| 111 | |
| 112 | |
| 113 | /* |
| 114 | ** An "etByte" is an 8-bit unsigned value. |
| 115 | */ |
| @@ -142,11 +143,11 @@ | |
| 143 | ** NB: When modifying this table is it vital that you also update the fmtchr[] |
| 144 | ** variable to match!!! |
| 145 | */ |
| 146 | static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; |
| 147 | static const char aPrefix[] = "-x0\000X0"; |
| 148 | static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H"; |
| 149 | static const et_info fmtinfo[] = { |
| 150 | { 'd', 10, 1, etRADIX, 0, 0 }, |
| 151 | { 's', 0, 4, etSTRING, 0, 0 }, |
| 152 | { 'g', 0, 1, etGENERIC, 30, 0 }, |
| 153 | { 'z', 0, 6, etDYNSTRING, 0, 0 }, |
| @@ -176,10 +177,11 @@ | |
| 177 | { 'n', 0, 0, etSIZE, 0, 0 }, |
| 178 | { '%', 0, 0, etPERCENT, 0, 0 }, |
| 179 | { 'p', 16, 0, etPOINTER, 0, 1 }, |
| 180 | { '/', 0, 0, etPATH, 0, 0 }, |
| 181 | { '$', 0, 0, etSHELLESC, 0, 0 }, |
| 182 | { 'H', 0, 0, etHEX, 0, 0 }, |
| 183 | { etERROR, 0,0,0,0,0} /* Must be last */ |
| 184 | }; |
| 185 | #define etNINFO count(fmtinfo) |
| 186 | |
| 187 | /* |
| @@ -843,10 +845,21 @@ | |
| 845 | case etSHELLESC: { |
| 846 | char *zArg = va_arg(ap, char*); |
| 847 | blob_append_escaped_arg(pBlob, zArg, !flag_altform2); |
| 848 | length = width = 0; |
| 849 | break; |
| 850 | } |
| 851 | case etHEX: { |
| 852 | char *zArg = va_arg(ap, char*); |
| 853 | int szArg = (int)strlen(zArg); |
| 854 | int szBlob = blob_size(pBlob); |
| 855 | u8 *aBuf; |
| 856 | blob_resize(pBlob, szBlob+szArg*2+1); |
| 857 | aBuf = (u8*)&blob_buffer(pBlob)[szBlob]; |
| 858 | encode16((const u8*)zArg, aBuf, szArg); |
| 859 | length = width = 0; |
| 860 | break; |
| 861 | } |
| 862 | case etERROR: |
| 863 | buf[0] = '%'; |
| 864 | buf[1] = c; |
| 865 | errorflag = 0; |
| 866 |
+9
-3
| --- src/rebuild.c | ||
| +++ src/rebuild.c | ||
| @@ -390,25 +390,31 @@ | ||
| 390 | 390 | } |
| 391 | 391 | manifest_disable_event_triggers(); |
| 392 | 392 | rebuild_update_schema(); |
| 393 | 393 | blob_init(&sql, 0, 0); |
| 394 | 394 | db_unprotect(PROTECT_ALL); |
| 395 | +#ifndef SQLITE_PREPARE_DONT_LOG | |
| 396 | + g.dbIgnoreErrors++; | |
| 397 | +#endif | |
| 395 | 398 | db_prepare(&q, |
| 396 | - "SELECT name FROM sqlite_schema /*scan*/" | |
| 397 | - " WHERE type='table'" | |
| 399 | + "SELECT name FROM pragma_table_list /*scan*/" | |
| 400 | + " WHERE schema='repository' AND type IN ('table','virtual')" | |
| 398 | 401 | " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias'," |
| 399 | 402 | "'config','shun','private','reportfmt'," |
| 400 | 403 | "'concealed','accesslog','modreq'," |
| 401 | 404 | "'purgeevent','purgeitem','unversioned'," |
| 402 | 405 | "'subscriber','pending_alert','chat')" |
| 403 | 406 | " AND name NOT GLOB 'sqlite_*'" |
| 404 | - " AND name NOT GLOB 'fx_*'" | |
| 407 | + " AND name NOT GLOB 'fx_*';" | |
| 405 | 408 | ); |
| 406 | 409 | while( db_step(&q)==SQLITE_ROW ){ |
| 407 | 410 | blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0)); |
| 408 | 411 | } |
| 409 | 412 | db_finalize(&q); |
| 413 | +#ifndef SQLITE_PREPARE_DONT_LOG | |
| 414 | + g.dbIgnoreErrors--; | |
| 415 | +#endif | |
| 410 | 416 | db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/); |
| 411 | 417 | blob_reset(&sql); |
| 412 | 418 | db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/); |
| 413 | 419 | ticket_create_table(0); |
| 414 | 420 | shun_artifacts(); |
| 415 | 421 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -390,25 +390,31 @@ | |
| 390 | } |
| 391 | manifest_disable_event_triggers(); |
| 392 | rebuild_update_schema(); |
| 393 | blob_init(&sql, 0, 0); |
| 394 | db_unprotect(PROTECT_ALL); |
| 395 | db_prepare(&q, |
| 396 | "SELECT name FROM sqlite_schema /*scan*/" |
| 397 | " WHERE type='table'" |
| 398 | " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias'," |
| 399 | "'config','shun','private','reportfmt'," |
| 400 | "'concealed','accesslog','modreq'," |
| 401 | "'purgeevent','purgeitem','unversioned'," |
| 402 | "'subscriber','pending_alert','chat')" |
| 403 | " AND name NOT GLOB 'sqlite_*'" |
| 404 | " AND name NOT GLOB 'fx_*'" |
| 405 | ); |
| 406 | while( db_step(&q)==SQLITE_ROW ){ |
| 407 | blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0)); |
| 408 | } |
| 409 | db_finalize(&q); |
| 410 | db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/); |
| 411 | blob_reset(&sql); |
| 412 | db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/); |
| 413 | ticket_create_table(0); |
| 414 | shun_artifacts(); |
| 415 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -390,25 +390,31 @@ | |
| 390 | } |
| 391 | manifest_disable_event_triggers(); |
| 392 | rebuild_update_schema(); |
| 393 | blob_init(&sql, 0, 0); |
| 394 | db_unprotect(PROTECT_ALL); |
| 395 | #ifndef SQLITE_PREPARE_DONT_LOG |
| 396 | g.dbIgnoreErrors++; |
| 397 | #endif |
| 398 | db_prepare(&q, |
| 399 | "SELECT name FROM pragma_table_list /*scan*/" |
| 400 | " WHERE schema='repository' AND type IN ('table','virtual')" |
| 401 | " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias'," |
| 402 | "'config','shun','private','reportfmt'," |
| 403 | "'concealed','accesslog','modreq'," |
| 404 | "'purgeevent','purgeitem','unversioned'," |
| 405 | "'subscriber','pending_alert','chat')" |
| 406 | " AND name NOT GLOB 'sqlite_*'" |
| 407 | " AND name NOT GLOB 'fx_*';" |
| 408 | ); |
| 409 | while( db_step(&q)==SQLITE_ROW ){ |
| 410 | blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0)); |
| 411 | } |
| 412 | db_finalize(&q); |
| 413 | #ifndef SQLITE_PREPARE_DONT_LOG |
| 414 | g.dbIgnoreErrors--; |
| 415 | #endif |
| 416 | db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/); |
| 417 | blob_reset(&sql); |
| 418 | db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/); |
| 419 | ticket_create_table(0); |
| 420 | shun_artifacts(); |
| 421 |
+7
-7
| --- src/schema.c | ||
| +++ src/schema.c | ||
| @@ -28,11 +28,11 @@ | ||
| 28 | 28 | @ -- ~/.fossil file and that stores information about the users setup. |
| 29 | 29 | @ -- |
| 30 | 30 | @ CREATE TABLE global_config( |
| 31 | 31 | @ name TEXT PRIMARY KEY, |
| 32 | 32 | @ value TEXT |
| 33 | -@ ); | |
| 33 | +@ ) WITHOUT ROWID; | |
| 34 | 34 | @ |
| 35 | 35 | @ -- Identifier for this file type. |
| 36 | 36 | @ -- The integer is the same as 'FSLG'. |
| 37 | 37 | @ PRAGMA application_id=252006675; |
| 38 | 38 | ; |
| @@ -138,11 +138,11 @@ | ||
| 138 | 138 | @ CREATE TABLE config( |
| 139 | 139 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 140 | 140 | @ value CLOB, -- Content of the named parameter |
| 141 | 141 | @ mtime DATE, -- last modified. seconds since 1970 |
| 142 | 142 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 143 | -@ ); | |
| 143 | +@ ) WITHOUT ROWID; | |
| 144 | 144 | @ |
| 145 | 145 | @ -- Artifacts that should not be processed are identified in the |
| 146 | 146 | @ -- "shun" table. Artifacts that are control-file forgeries or |
| 147 | 147 | @ -- spam or artifacts whose contents violate administrative policy |
| 148 | 148 | @ -- can be shunned in order to prevent them from contaminating |
| @@ -151,14 +151,14 @@ | ||
| 151 | 151 | @ -- Shunned artifacts do not exist in the blob table. Hence they |
| 152 | 152 | @ -- have not artifact ID (rid) and we thus must store their full |
| 153 | 153 | @ -- UUID. |
| 154 | 154 | @ -- |
| 155 | 155 | @ CREATE TABLE shun( |
| 156 | -@ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form | |
| 156 | +@ uuid TEXT PRIMARY KEY,-- UUID of artifact to be shunned. Canonical form | |
| 157 | 157 | @ mtime DATE, -- When added. seconds since 1970 |
| 158 | 158 | @ scom TEXT -- Optional text explaining why the shun occurred |
| 159 | -@ ); | |
| 159 | +@ ) WITHOUT ROWID; | |
| 160 | 160 | @ |
| 161 | 161 | @ -- Artifacts that should not be pushed are stored in the "private" |
| 162 | 162 | @ -- table. Private artifacts are omitted from the "unclustered" and |
| 163 | 163 | @ -- "unsent" tables. |
| 164 | 164 | @ -- |
| @@ -193,11 +193,11 @@ | ||
| 193 | 193 | @ -- |
| 194 | 194 | @ CREATE TABLE concealed( |
| 195 | 195 | @ hash TEXT PRIMARY KEY, -- The SHA1 hash of content |
| 196 | 196 | @ mtime DATE, -- Time created. Seconds since 1970 |
| 197 | 197 | @ content TEXT -- Content intended to be concealed |
| 198 | -@ ); | |
| 198 | +@ ) WITHOUT ROWID; | |
| 199 | 199 | @ |
| 200 | 200 | @ -- The application ID helps the unix "file" command to identify the |
| 201 | 201 | @ -- database as a fossil repository. |
| 202 | 202 | @ PRAGMA application_id=252006673; |
| 203 | 203 | ; |
| @@ -528,11 +528,11 @@ | ||
| 528 | 528 | ** The schema for the local FOSSIL database file found at the root |
| 529 | 529 | ** of every check-out. This database contains the complete state of |
| 530 | 530 | ** the check-out. See also the addendum in zLocalSchemaVmerge[]. |
| 531 | 531 | */ |
| 532 | 532 | const char zLocalSchema[] = |
| 533 | -@ -- The VVAR table holds miscellanous information about the local database | |
| 533 | +@ -- The VVAR table holds miscellanous information about the local checkout | |
| 534 | 534 | @ -- in the form of name-value pairs. This is similar to the VAR table |
| 535 | 535 | @ -- table in the repository except that this table holds information that |
| 536 | 536 | @ -- is specific to the local check-out. |
| 537 | 537 | @ -- |
| 538 | 538 | @ -- Important Variables: |
| @@ -542,11 +542,11 @@ | ||
| 542 | 542 | @ -- |
| 543 | 543 | @ CREATE TABLE vvar( |
| 544 | 544 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 545 | 545 | @ value CLOB, -- Content of the named parameter |
| 546 | 546 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 547 | -@ ); | |
| 547 | +@ ) WITHOUT ROWID; | |
| 548 | 548 | @ |
| 549 | 549 | @ -- Each entry in the vfile table represents a single file in the |
| 550 | 550 | @ -- current check-out. |
| 551 | 551 | @ -- |
| 552 | 552 | @ -- The file.rid field is 0 for files or folders that have been |
| 553 | 553 |
| --- src/schema.c | |
| +++ src/schema.c | |
| @@ -28,11 +28,11 @@ | |
| 28 | @ -- ~/.fossil file and that stores information about the users setup. |
| 29 | @ -- |
| 30 | @ CREATE TABLE global_config( |
| 31 | @ name TEXT PRIMARY KEY, |
| 32 | @ value TEXT |
| 33 | @ ); |
| 34 | @ |
| 35 | @ -- Identifier for this file type. |
| 36 | @ -- The integer is the same as 'FSLG'. |
| 37 | @ PRAGMA application_id=252006675; |
| 38 | ; |
| @@ -138,11 +138,11 @@ | |
| 138 | @ CREATE TABLE config( |
| 139 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 140 | @ value CLOB, -- Content of the named parameter |
| 141 | @ mtime DATE, -- last modified. seconds since 1970 |
| 142 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 143 | @ ); |
| 144 | @ |
| 145 | @ -- Artifacts that should not be processed are identified in the |
| 146 | @ -- "shun" table. Artifacts that are control-file forgeries or |
| 147 | @ -- spam or artifacts whose contents violate administrative policy |
| 148 | @ -- can be shunned in order to prevent them from contaminating |
| @@ -151,14 +151,14 @@ | |
| 151 | @ -- Shunned artifacts do not exist in the blob table. Hence they |
| 152 | @ -- have not artifact ID (rid) and we thus must store their full |
| 153 | @ -- UUID. |
| 154 | @ -- |
| 155 | @ CREATE TABLE shun( |
| 156 | @ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form |
| 157 | @ mtime DATE, -- When added. seconds since 1970 |
| 158 | @ scom TEXT -- Optional text explaining why the shun occurred |
| 159 | @ ); |
| 160 | @ |
| 161 | @ -- Artifacts that should not be pushed are stored in the "private" |
| 162 | @ -- table. Private artifacts are omitted from the "unclustered" and |
| 163 | @ -- "unsent" tables. |
| 164 | @ -- |
| @@ -193,11 +193,11 @@ | |
| 193 | @ -- |
| 194 | @ CREATE TABLE concealed( |
| 195 | @ hash TEXT PRIMARY KEY, -- The SHA1 hash of content |
| 196 | @ mtime DATE, -- Time created. Seconds since 1970 |
| 197 | @ content TEXT -- Content intended to be concealed |
| 198 | @ ); |
| 199 | @ |
| 200 | @ -- The application ID helps the unix "file" command to identify the |
| 201 | @ -- database as a fossil repository. |
| 202 | @ PRAGMA application_id=252006673; |
| 203 | ; |
| @@ -528,11 +528,11 @@ | |
| 528 | ** The schema for the local FOSSIL database file found at the root |
| 529 | ** of every check-out. This database contains the complete state of |
| 530 | ** the check-out. See also the addendum in zLocalSchemaVmerge[]. |
| 531 | */ |
| 532 | const char zLocalSchema[] = |
| 533 | @ -- The VVAR table holds miscellanous information about the local database |
| 534 | @ -- in the form of name-value pairs. This is similar to the VAR table |
| 535 | @ -- table in the repository except that this table holds information that |
| 536 | @ -- is specific to the local check-out. |
| 537 | @ -- |
| 538 | @ -- Important Variables: |
| @@ -542,11 +542,11 @@ | |
| 542 | @ -- |
| 543 | @ CREATE TABLE vvar( |
| 544 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 545 | @ value CLOB, -- Content of the named parameter |
| 546 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 547 | @ ); |
| 548 | @ |
| 549 | @ -- Each entry in the vfile table represents a single file in the |
| 550 | @ -- current check-out. |
| 551 | @ -- |
| 552 | @ -- The file.rid field is 0 for files or folders that have been |
| 553 |
| --- src/schema.c | |
| +++ src/schema.c | |
| @@ -28,11 +28,11 @@ | |
| 28 | @ -- ~/.fossil file and that stores information about the users setup. |
| 29 | @ -- |
| 30 | @ CREATE TABLE global_config( |
| 31 | @ name TEXT PRIMARY KEY, |
| 32 | @ value TEXT |
| 33 | @ ) WITHOUT ROWID; |
| 34 | @ |
| 35 | @ -- Identifier for this file type. |
| 36 | @ -- The integer is the same as 'FSLG'. |
| 37 | @ PRAGMA application_id=252006675; |
| 38 | ; |
| @@ -138,11 +138,11 @@ | |
| 138 | @ CREATE TABLE config( |
| 139 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 140 | @ value CLOB, -- Content of the named parameter |
| 141 | @ mtime DATE, -- last modified. seconds since 1970 |
| 142 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 143 | @ ) WITHOUT ROWID; |
| 144 | @ |
| 145 | @ -- Artifacts that should not be processed are identified in the |
| 146 | @ -- "shun" table. Artifacts that are control-file forgeries or |
| 147 | @ -- spam or artifacts whose contents violate administrative policy |
| 148 | @ -- can be shunned in order to prevent them from contaminating |
| @@ -151,14 +151,14 @@ | |
| 151 | @ -- Shunned artifacts do not exist in the blob table. Hence they |
| 152 | @ -- have not artifact ID (rid) and we thus must store their full |
| 153 | @ -- UUID. |
| 154 | @ -- |
| 155 | @ CREATE TABLE shun( |
| 156 | @ uuid TEXT PRIMARY KEY,-- UUID of artifact to be shunned. Canonical form |
| 157 | @ mtime DATE, -- When added. seconds since 1970 |
| 158 | @ scom TEXT -- Optional text explaining why the shun occurred |
| 159 | @ ) WITHOUT ROWID; |
| 160 | @ |
| 161 | @ -- Artifacts that should not be pushed are stored in the "private" |
| 162 | @ -- table. Private artifacts are omitted from the "unclustered" and |
| 163 | @ -- "unsent" tables. |
| 164 | @ -- |
| @@ -193,11 +193,11 @@ | |
| 193 | @ -- |
| 194 | @ CREATE TABLE concealed( |
| 195 | @ hash TEXT PRIMARY KEY, -- The SHA1 hash of content |
| 196 | @ mtime DATE, -- Time created. Seconds since 1970 |
| 197 | @ content TEXT -- Content intended to be concealed |
| 198 | @ ) WITHOUT ROWID; |
| 199 | @ |
| 200 | @ -- The application ID helps the unix "file" command to identify the |
| 201 | @ -- database as a fossil repository. |
| 202 | @ PRAGMA application_id=252006673; |
| 203 | ; |
| @@ -528,11 +528,11 @@ | |
| 528 | ** The schema for the local FOSSIL database file found at the root |
| 529 | ** of every check-out. This database contains the complete state of |
| 530 | ** the check-out. See also the addendum in zLocalSchemaVmerge[]. |
| 531 | */ |
| 532 | const char zLocalSchema[] = |
| 533 | @ -- The VVAR table holds miscellanous information about the local checkout |
| 534 | @ -- in the form of name-value pairs. This is similar to the VAR table |
| 535 | @ -- table in the repository except that this table holds information that |
| 536 | @ -- is specific to the local check-out. |
| 537 | @ -- |
| 538 | @ -- Important Variables: |
| @@ -542,11 +542,11 @@ | |
| 542 | @ -- |
| 543 | @ CREATE TABLE vvar( |
| 544 | @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry |
| 545 | @ value CLOB, -- Content of the named parameter |
| 546 | @ CHECK( typeof(name)='text' AND length(name)>=1 ) |
| 547 | @ ) WITHOUT ROWID; |
| 548 | @ |
| 549 | @ -- Each entry in the vfile table represents a single file in the |
| 550 | @ -- current check-out. |
| 551 | @ -- |
| 552 | @ -- The file.rid field is 0 for files or folders that have been |
| 553 |
+12
-1
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -501,13 +501,24 @@ | ||
| 501 | 501 | @ behavior or to find an SQL injection opportunity or similar. This can |
| 502 | 502 | @ waste hours of CPU time and gigabytes of bandwidth on the server. A |
| 503 | 503 | @ suggested value for this setting is: |
| 504 | 504 | @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>". |
| 505 | 505 | @ (Property: robot-restrict) |
| 506 | - @ <p> | |
| 506 | + @ <br> | |
| 507 | 507 | textarea_attribute("", 2, 80, |
| 508 | 508 | "robot-restrict", "rbrestrict", "", 0); |
| 509 | + @ <br> The following comma-separated GLOB pattern allows for exceptions | |
| 510 | + @ in the maximum number of query parameters before a request is considered | |
| 511 | + @ complex. If this GLOB pattern exists and is non-empty and if it | |
| 512 | + @ matches against the pagename followed by "/" and the number of query | |
| 513 | + @ parameters, then the request is allowed through. For example, the | |
| 514 | + @ suggested pattern of "timeline/[012]" allows the /timeline page to | |
| 515 | + @ pass with up to 2 query parameters besides "name". | |
| 516 | + @ (Property: robot-restrict-qp) | |
| 517 | + @ <br> | |
| 518 | + textarea_attribute("", 2, 80, | |
| 519 | + "robot-restrict-qp", "rbrestrictqp", "", 0); | |
| 509 | 520 | |
| 510 | 521 | @ <hr> |
| 511 | 522 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 512 | 523 | @ </div></form> |
| 513 | 524 | db_end_transaction(0); |
| 514 | 525 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -501,13 +501,24 @@ | |
| 501 | @ behavior or to find an SQL injection opportunity or similar. This can |
| 502 | @ waste hours of CPU time and gigabytes of bandwidth on the server. A |
| 503 | @ suggested value for this setting is: |
| 504 | @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>". |
| 505 | @ (Property: robot-restrict) |
| 506 | @ <p> |
| 507 | textarea_attribute("", 2, 80, |
| 508 | "robot-restrict", "rbrestrict", "", 0); |
| 509 | |
| 510 | @ <hr> |
| 511 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 512 | @ </div></form> |
| 513 | db_end_transaction(0); |
| 514 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -501,13 +501,24 @@ | |
| 501 | @ behavior or to find an SQL injection opportunity or similar. This can |
| 502 | @ waste hours of CPU time and gigabytes of bandwidth on the server. A |
| 503 | @ suggested value for this setting is: |
| 504 | @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>". |
| 505 | @ (Property: robot-restrict) |
| 506 | @ <br> |
| 507 | textarea_attribute("", 2, 80, |
| 508 | "robot-restrict", "rbrestrict", "", 0); |
| 509 | @ <br> The following comma-separated GLOB pattern allows for exceptions |
| 510 | @ in the maximum number of query parameters before a request is considered |
| 511 | @ complex. If this GLOB pattern exists and is non-empty and if it |
| 512 | @ matches against the pagename followed by "/" and the number of query |
| 513 | @ parameters, then the request is allowed through. For example, the |
| 514 | @ suggested pattern of "timeline/[012]" allows the /timeline page to |
| 515 | @ pass with up to 2 query parameters besides "name". |
| 516 | @ (Property: robot-restrict-qp) |
| 517 | @ <br> |
| 518 | textarea_attribute("", 2, 80, |
| 519 | "robot-restrict-qp", "rbrestrictqp", "", 0); |
| 520 | |
| 521 | @ <hr> |
| 522 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 523 | @ </div></form> |
| 524 | db_end_transaction(0); |
| 525 |
+7
-5
| --- src/setupuser.c | ||
| +++ src/setupuser.c | ||
| @@ -115,11 +115,11 @@ | ||
| 115 | 115 | } |
| 116 | 116 | if( !bUnusedOnly ){ |
| 117 | 117 | style_submenu_element("Unused", "setup_ulist?unused"); |
| 118 | 118 | } |
| 119 | 119 | @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \ |
| 120 | - @ data-column-types='ktxTTKt' data-init-sort='2'> | |
| 120 | + @ data-column-types='ktxKTKt' data-init-sort='4'> | |
| 121 | 121 | @ <thead><tr> |
| 122 | 122 | @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\ |
| 123 | 123 | @ <th>Alerts</tr></thead> |
| 124 | 124 | @ <tbody> |
| 125 | 125 | db_multi_exec( |
| @@ -161,15 +161,16 @@ | ||
| 161 | 161 | " lower(login) AS sortkey, " |
| 162 | 162 | " CASE WHEN info LIKE '%%expires 20%%'" |
| 163 | 163 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 164 | 164 | " END AS exp," |
| 165 | 165 | "atime," |
| 166 | - " subscriber.ssub, subscriber.subscriberId" | |
| 166 | + " subscriber.ssub, subscriber.subscriberId," | |
| 167 | + " user.mtime AS sorttime" | |
| 167 | 168 | " FROM user LEFT JOIN lastAccess ON login=uname" |
| 168 | 169 | " LEFT JOIN subscriber ON login=suname" |
| 169 | 170 | " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
| 170 | - " ORDER BY sortkey", zWith/*safe-for-%s*/ | |
| 171 | + " ORDER BY sorttime DESC", zWith/*safe-for-%s*/ | |
| 171 | 172 | ); |
| 172 | 173 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 173 | 174 | while( db_step(&s)==SQLITE_ROW ){ |
| 174 | 175 | int uid = db_column_int(&s, 0); |
| 175 | 176 | const char *zLogin = db_column_text(&s, 1); |
| @@ -180,10 +181,11 @@ | ||
| 180 | 181 | const char *zExp = db_column_text(&s,6); |
| 181 | 182 | double rATime = db_column_double(&s,7); |
| 182 | 183 | char *zAge = 0; |
| 183 | 184 | const char *zSub; |
| 184 | 185 | int sid = db_column_int(&s,9); |
| 186 | + sqlite3_int64 sorttime = db_column_int64(&s, 10); | |
| 185 | 187 | if( rATime>0.0 ){ |
| 186 | 188 | zAge = human_readable_age(rNow - rATime); |
| 187 | 189 | } |
| 188 | 190 | if( bUbg ){ |
| 189 | 191 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -192,11 +194,11 @@ | ||
| 192 | 194 | } |
| 193 | 195 | @ <td data-sortkey='%h(zSortKey)'>\ |
| 194 | 196 | @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a> |
| 195 | 197 | @ <td>%h(zCap) |
| 196 | 198 | @ <td>%h(zInfo) |
| 197 | - @ <td>%h(zDate?zDate:"") | |
| 199 | + @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") | |
| 198 | 200 | @ <td>%h(zExp?zExp:"") |
| 199 | 201 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 200 | 202 | if( db_column_type(&s,8)==SQLITE_NULL ){ |
| 201 | 203 | @ <td> |
| 202 | 204 | }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ |
| @@ -1016,11 +1018,11 @@ | ||
| 1016 | 1018 | style_header("User %h", db_column_text(&q,1)); |
| 1017 | 1019 | @ <table class="label-value"> |
| 1018 | 1020 | @ <tr><th>uid:</th><td>%d(db_column_int(&q,0)) |
| 1019 | 1021 | @ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr> |
| 1020 | 1022 | @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr> |
| 1021 | - @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</th></tr> | |
| 1023 | + @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr> | |
| 1022 | 1024 | @ <tr><th valign="top">info:</th> |
| 1023 | 1025 | @ <td valign="top"><span style='white-space:pre-line;'>\ |
| 1024 | 1026 | @ %h(db_column_text(&q,5))</span></td></tr> |
| 1025 | 1027 | @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr> |
| 1026 | 1028 | if( db_column_type(&q,7)!=SQLITE_NULL ){ |
| 1027 | 1029 |
| --- src/setupuser.c | |
| +++ src/setupuser.c | |
| @@ -115,11 +115,11 @@ | |
| 115 | } |
| 116 | if( !bUnusedOnly ){ |
| 117 | style_submenu_element("Unused", "setup_ulist?unused"); |
| 118 | } |
| 119 | @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \ |
| 120 | @ data-column-types='ktxTTKt' data-init-sort='2'> |
| 121 | @ <thead><tr> |
| 122 | @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\ |
| 123 | @ <th>Alerts</tr></thead> |
| 124 | @ <tbody> |
| 125 | db_multi_exec( |
| @@ -161,15 +161,16 @@ | |
| 161 | " lower(login) AS sortkey, " |
| 162 | " CASE WHEN info LIKE '%%expires 20%%'" |
| 163 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 164 | " END AS exp," |
| 165 | "atime," |
| 166 | " subscriber.ssub, subscriber.subscriberId" |
| 167 | " FROM user LEFT JOIN lastAccess ON login=uname" |
| 168 | " LEFT JOIN subscriber ON login=suname" |
| 169 | " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
| 170 | " ORDER BY sortkey", zWith/*safe-for-%s*/ |
| 171 | ); |
| 172 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 173 | while( db_step(&s)==SQLITE_ROW ){ |
| 174 | int uid = db_column_int(&s, 0); |
| 175 | const char *zLogin = db_column_text(&s, 1); |
| @@ -180,10 +181,11 @@ | |
| 180 | const char *zExp = db_column_text(&s,6); |
| 181 | double rATime = db_column_double(&s,7); |
| 182 | char *zAge = 0; |
| 183 | const char *zSub; |
| 184 | int sid = db_column_int(&s,9); |
| 185 | if( rATime>0.0 ){ |
| 186 | zAge = human_readable_age(rNow - rATime); |
| 187 | } |
| 188 | if( bUbg ){ |
| 189 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -192,11 +194,11 @@ | |
| 192 | } |
| 193 | @ <td data-sortkey='%h(zSortKey)'>\ |
| 194 | @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a> |
| 195 | @ <td>%h(zCap) |
| 196 | @ <td>%h(zInfo) |
| 197 | @ <td>%h(zDate?zDate:"") |
| 198 | @ <td>%h(zExp?zExp:"") |
| 199 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 200 | if( db_column_type(&s,8)==SQLITE_NULL ){ |
| 201 | @ <td> |
| 202 | }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ |
| @@ -1016,11 +1018,11 @@ | |
| 1016 | style_header("User %h", db_column_text(&q,1)); |
| 1017 | @ <table class="label-value"> |
| 1018 | @ <tr><th>uid:</th><td>%d(db_column_int(&q,0)) |
| 1019 | @ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr> |
| 1020 | @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr> |
| 1021 | @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</th></tr> |
| 1022 | @ <tr><th valign="top">info:</th> |
| 1023 | @ <td valign="top"><span style='white-space:pre-line;'>\ |
| 1024 | @ %h(db_column_text(&q,5))</span></td></tr> |
| 1025 | @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr> |
| 1026 | if( db_column_type(&q,7)!=SQLITE_NULL ){ |
| 1027 |
| --- src/setupuser.c | |
| +++ src/setupuser.c | |
| @@ -115,11 +115,11 @@ | |
| 115 | } |
| 116 | if( !bUnusedOnly ){ |
| 117 | style_submenu_element("Unused", "setup_ulist?unused"); |
| 118 | } |
| 119 | @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \ |
| 120 | @ data-column-types='ktxKTKt' data-init-sort='4'> |
| 121 | @ <thead><tr> |
| 122 | @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\ |
| 123 | @ <th>Alerts</tr></thead> |
| 124 | @ <tbody> |
| 125 | db_multi_exec( |
| @@ -161,15 +161,16 @@ | |
| 161 | " lower(login) AS sortkey, " |
| 162 | " CASE WHEN info LIKE '%%expires 20%%'" |
| 163 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 164 | " END AS exp," |
| 165 | "atime," |
| 166 | " subscriber.ssub, subscriber.subscriberId," |
| 167 | " user.mtime AS sorttime" |
| 168 | " FROM user LEFT JOIN lastAccess ON login=uname" |
| 169 | " LEFT JOIN subscriber ON login=suname" |
| 170 | " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
| 171 | " ORDER BY sorttime DESC", zWith/*safe-for-%s*/ |
| 172 | ); |
| 173 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 174 | while( db_step(&s)==SQLITE_ROW ){ |
| 175 | int uid = db_column_int(&s, 0); |
| 176 | const char *zLogin = db_column_text(&s, 1); |
| @@ -180,10 +181,11 @@ | |
| 181 | const char *zExp = db_column_text(&s,6); |
| 182 | double rATime = db_column_double(&s,7); |
| 183 | char *zAge = 0; |
| 184 | const char *zSub; |
| 185 | int sid = db_column_int(&s,9); |
| 186 | sqlite3_int64 sorttime = db_column_int64(&s, 10); |
| 187 | if( rATime>0.0 ){ |
| 188 | zAge = human_readable_age(rNow - rATime); |
| 189 | } |
| 190 | if( bUbg ){ |
| 191 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -192,11 +194,11 @@ | |
| 194 | } |
| 195 | @ <td data-sortkey='%h(zSortKey)'>\ |
| 196 | @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a> |
| 197 | @ <td>%h(zCap) |
| 198 | @ <td>%h(zInfo) |
| 199 | @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") |
| 200 | @ <td>%h(zExp?zExp:"") |
| 201 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 202 | if( db_column_type(&s,8)==SQLITE_NULL ){ |
| 203 | @ <td> |
| 204 | }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ |
| @@ -1016,11 +1018,11 @@ | |
| 1018 | style_header("User %h", db_column_text(&q,1)); |
| 1019 | @ <table class="label-value"> |
| 1020 | @ <tr><th>uid:</th><td>%d(db_column_int(&q,0)) |
| 1021 | @ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr> |
| 1022 | @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr> |
| 1023 | @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr> |
| 1024 | @ <tr><th valign="top">info:</th> |
| 1025 | @ <td valign="top"><span style='white-space:pre-line;'>\ |
| 1026 | @ %h(db_column_text(&q,5))</span></td></tr> |
| 1027 | @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr> |
| 1028 | if( db_column_type(&q,7)!=SQLITE_NULL ){ |
| 1029 |
+5
-40
| --- src/sitemap.c | ||
| +++ src/sitemap.c | ||
| @@ -56,22 +56,10 @@ | ||
| 56 | 56 | int i; |
| 57 | 57 | int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */ |
| 58 | 58 | int e = atoi(PD("e","0")); |
| 59 | 59 | const char *zExtra; |
| 60 | 60 | |
| 61 | -#if 0 /* Removed 2021-01-26 */ | |
| 62 | - const struct { | |
| 63 | - const char *zTitle; | |
| 64 | - const char *zProperty; | |
| 65 | - } aExtra[] = { | |
| 66 | - { "Documentation", "sitemap-docidx" }, | |
| 67 | - { "Download", "sitemap-download" }, | |
| 68 | - { "License", "sitemap-license" }, | |
| 69 | - { "Contact", "sitemap-contact" }, | |
| 70 | - }; | |
| 71 | -#endif | |
| 72 | - | |
| 73 | 61 | login_check_credentials(); |
| 74 | 62 | if( P("popup")!=0 ){ |
| 75 | 63 | /* The "popup" query parameter |
| 76 | 64 | ** then disable anti-robot defenses */ |
| 77 | 65 | isPopup = 1; |
| @@ -87,26 +75,10 @@ | ||
| 87 | 75 | @ <ul id="sitemap" class="columns" style="column-width:20em"> |
| 88 | 76 | if( (e&1)==0 ){ |
| 89 | 77 | @ <li>%z(href("%R/home"))Home Page</a> |
| 90 | 78 | } |
| 91 | 79 | |
| 92 | -#if 0 /* Removed 2021-01-26 */ | |
| 93 | - for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){ | |
| 94 | - char *z = db_get(aExtra[i].zProperty,0); | |
| 95 | - if( z==0 || z[0]==0 ) continue; | |
| 96 | - if( !inSublist ){ | |
| 97 | - @ <ul> | |
| 98 | - inSublist = 1; | |
| 99 | - } | |
| 100 | - if( z[0]=='/' ){ | |
| 101 | - @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li> | |
| 102 | - }else{ | |
| 103 | - @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li> | |
| 104 | - } | |
| 105 | - } | |
| 106 | -#endif | |
| 107 | - | |
| 108 | 80 | zExtra = db_get("sitemap-extra",0); |
| 109 | 81 | if( zExtra && (e&2)==0 ){ |
| 110 | 82 | int rc; |
| 111 | 83 | char **azExtra = 0; |
| 112 | 84 | int *anExtra; |
| @@ -139,26 +111,18 @@ | ||
| 139 | 111 | } |
| 140 | 112 | Th_Free(g.interp, azExtra); |
| 141 | 113 | } |
| 142 | 114 | if( (e&1)!=0 ) goto end_of_sitemap; |
| 143 | 115 | |
| 144 | -#if 0 /* Removed on 2021-02-11. Make a sitemap-extra entry if you */ | |
| 145 | - /* really want this */ | |
| 146 | - if( srchFlags & SRCH_DOC ){ | |
| 147 | - if( !inSublist ){ | |
| 148 | - @ <ul> | |
| 149 | - inSublist = 1; | |
| 150 | - } | |
| 151 | - @ <li>%z(href("%R/docsrch"))Documentation Search</a></li> | |
| 152 | - } | |
| 153 | -#endif | |
| 154 | - | |
| 155 | 116 | if( inSublist ){ |
| 156 | 117 | @ </ul> |
| 157 | 118 | inSublist = 0; |
| 158 | 119 | } |
| 159 | 120 | @ </li> |
| 121 | + if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ | |
| 122 | + @ <li>%z(href("%R/ckout"))Checkout Status</a></li> | |
| 123 | + } | |
| 160 | 124 | if( g.perm.Read ){ |
| 161 | 125 | const char *zEditGlob = db_get("fileedit-glob",""); |
| 162 | 126 | @ <li>%z(href("%R/tree"))File Browser</a> |
| 163 | 127 | @ <ul> |
| 164 | 128 | @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view, |
| @@ -192,11 +156,12 @@ | ||
| 192 | 156 | } |
| 193 | 157 | if( g.perm.Chat ){ |
| 194 | 158 | @ <li>%z(href("%R/chat"))Chat</a></li> |
| 195 | 159 | } |
| 196 | 160 | if( g.perm.RdForum ){ |
| 197 | - @ <li>%z(href("%R/forum"))Forum</a> | |
| 161 | + const char *zTitle = db_get("forum-title","Forum"); | |
| 162 | + @ <li>%z(href("%R/forum"))%h(zTitle)</a> | |
| 198 | 163 | @ <ul> |
| 199 | 164 | @ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li> |
| 200 | 165 | @ </ul> |
| 201 | 166 | @ </li> |
| 202 | 167 | } |
| 203 | 168 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -56,22 +56,10 @@ | |
| 56 | int i; |
| 57 | int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */ |
| 58 | int e = atoi(PD("e","0")); |
| 59 | const char *zExtra; |
| 60 | |
| 61 | #if 0 /* Removed 2021-01-26 */ |
| 62 | const struct { |
| 63 | const char *zTitle; |
| 64 | const char *zProperty; |
| 65 | } aExtra[] = { |
| 66 | { "Documentation", "sitemap-docidx" }, |
| 67 | { "Download", "sitemap-download" }, |
| 68 | { "License", "sitemap-license" }, |
| 69 | { "Contact", "sitemap-contact" }, |
| 70 | }; |
| 71 | #endif |
| 72 | |
| 73 | login_check_credentials(); |
| 74 | if( P("popup")!=0 ){ |
| 75 | /* The "popup" query parameter |
| 76 | ** then disable anti-robot defenses */ |
| 77 | isPopup = 1; |
| @@ -87,26 +75,10 @@ | |
| 87 | @ <ul id="sitemap" class="columns" style="column-width:20em"> |
| 88 | if( (e&1)==0 ){ |
| 89 | @ <li>%z(href("%R/home"))Home Page</a> |
| 90 | } |
| 91 | |
| 92 | #if 0 /* Removed 2021-01-26 */ |
| 93 | for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){ |
| 94 | char *z = db_get(aExtra[i].zProperty,0); |
| 95 | if( z==0 || z[0]==0 ) continue; |
| 96 | if( !inSublist ){ |
| 97 | @ <ul> |
| 98 | inSublist = 1; |
| 99 | } |
| 100 | if( z[0]=='/' ){ |
| 101 | @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li> |
| 102 | }else{ |
| 103 | @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li> |
| 104 | } |
| 105 | } |
| 106 | #endif |
| 107 | |
| 108 | zExtra = db_get("sitemap-extra",0); |
| 109 | if( zExtra && (e&2)==0 ){ |
| 110 | int rc; |
| 111 | char **azExtra = 0; |
| 112 | int *anExtra; |
| @@ -139,26 +111,18 @@ | |
| 139 | } |
| 140 | Th_Free(g.interp, azExtra); |
| 141 | } |
| 142 | if( (e&1)!=0 ) goto end_of_sitemap; |
| 143 | |
| 144 | #if 0 /* Removed on 2021-02-11. Make a sitemap-extra entry if you */ |
| 145 | /* really want this */ |
| 146 | if( srchFlags & SRCH_DOC ){ |
| 147 | if( !inSublist ){ |
| 148 | @ <ul> |
| 149 | inSublist = 1; |
| 150 | } |
| 151 | @ <li>%z(href("%R/docsrch"))Documentation Search</a></li> |
| 152 | } |
| 153 | #endif |
| 154 | |
| 155 | if( inSublist ){ |
| 156 | @ </ul> |
| 157 | inSublist = 0; |
| 158 | } |
| 159 | @ </li> |
| 160 | if( g.perm.Read ){ |
| 161 | const char *zEditGlob = db_get("fileedit-glob",""); |
| 162 | @ <li>%z(href("%R/tree"))File Browser</a> |
| 163 | @ <ul> |
| 164 | @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view, |
| @@ -192,11 +156,12 @@ | |
| 192 | } |
| 193 | if( g.perm.Chat ){ |
| 194 | @ <li>%z(href("%R/chat"))Chat</a></li> |
| 195 | } |
| 196 | if( g.perm.RdForum ){ |
| 197 | @ <li>%z(href("%R/forum"))Forum</a> |
| 198 | @ <ul> |
| 199 | @ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li> |
| 200 | @ </ul> |
| 201 | @ </li> |
| 202 | } |
| 203 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -56,22 +56,10 @@ | |
| 56 | int i; |
| 57 | int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */ |
| 58 | int e = atoi(PD("e","0")); |
| 59 | const char *zExtra; |
| 60 | |
| 61 | login_check_credentials(); |
| 62 | if( P("popup")!=0 ){ |
| 63 | /* The "popup" query parameter |
| 64 | ** then disable anti-robot defenses */ |
| 65 | isPopup = 1; |
| @@ -87,26 +75,10 @@ | |
| 75 | @ <ul id="sitemap" class="columns" style="column-width:20em"> |
| 76 | if( (e&1)==0 ){ |
| 77 | @ <li>%z(href("%R/home"))Home Page</a> |
| 78 | } |
| 79 | |
| 80 | zExtra = db_get("sitemap-extra",0); |
| 81 | if( zExtra && (e&2)==0 ){ |
| 82 | int rc; |
| 83 | char **azExtra = 0; |
| 84 | int *anExtra; |
| @@ -139,26 +111,18 @@ | |
| 111 | } |
| 112 | Th_Free(g.interp, azExtra); |
| 113 | } |
| 114 | if( (e&1)!=0 ) goto end_of_sitemap; |
| 115 | |
| 116 | if( inSublist ){ |
| 117 | @ </ul> |
| 118 | inSublist = 0; |
| 119 | } |
| 120 | @ </li> |
| 121 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 122 | @ <li>%z(href("%R/ckout"))Checkout Status</a></li> |
| 123 | } |
| 124 | if( g.perm.Read ){ |
| 125 | const char *zEditGlob = db_get("fileedit-glob",""); |
| 126 | @ <li>%z(href("%R/tree"))File Browser</a> |
| 127 | @ <ul> |
| 128 | @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view, |
| @@ -192,11 +156,12 @@ | |
| 156 | } |
| 157 | if( g.perm.Chat ){ |
| 158 | @ <li>%z(href("%R/chat"))Chat</a></li> |
| 159 | } |
| 160 | if( g.perm.RdForum ){ |
| 161 | const char *zTitle = db_get("forum-title","Forum"); |
| 162 | @ <li>%z(href("%R/forum"))%h(zTitle)</a> |
| 163 | @ <ul> |
| 164 | @ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li> |
| 165 | @ </ul> |
| 166 | @ </li> |
| 167 | } |
| 168 |
+4
-4
| --- src/statrep.c | ||
| +++ src/statrep.c | ||
| @@ -290,12 +290,12 @@ | ||
| 290 | 290 | nEventsPerYear += nCount; |
| 291 | 291 | @<tr class='row%d(rowClass)'> |
| 292 | 292 | @ <td> |
| 293 | 293 | if(includeMonth){ |
| 294 | 294 | cgi_printf("<a href='%R/timeline?" |
| 295 | - "ym=%t&n=%d&y=%s", | |
| 296 | - zTimeframe, nCount, | |
| 295 | + "ym=%t&y=%s", | |
| 296 | + zTimeframe, | |
| 297 | 297 | statsReportTimelineYFlag ); |
| 298 | 298 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 299 | 299 | that was the only user who caused events. |
| 300 | 300 | */ |
| 301 | 301 | if( zUserName ){ |
| @@ -731,12 +731,12 @@ | ||
| 731 | 731 | ? (int)(100 * nCount / nMaxEvents) |
| 732 | 732 | : 0; |
| 733 | 733 | if(!nSize) nSize = 1; |
| 734 | 734 | total += nCount; |
| 735 | 735 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 736 | - cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", | |
| 737 | - zYear, zWeek, nCount, | |
| 736 | + cgi_printf("<td><a href='%R/timeline?yw=%t%s&y=%s", | |
| 737 | + zYear, zWeek, | |
| 738 | 738 | statsReportTimelineYFlag); |
| 739 | 739 | if( zUserName ){ |
| 740 | 740 | cgi_printf("&u=%t",zUserName); |
| 741 | 741 | } |
| 742 | 742 | cgi_printf("'>%s</a></td>",zWeek); |
| 743 | 743 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -290,12 +290,12 @@ | |
| 290 | nEventsPerYear += nCount; |
| 291 | @<tr class='row%d(rowClass)'> |
| 292 | @ <td> |
| 293 | if(includeMonth){ |
| 294 | cgi_printf("<a href='%R/timeline?" |
| 295 | "ym=%t&n=%d&y=%s", |
| 296 | zTimeframe, nCount, |
| 297 | statsReportTimelineYFlag ); |
| 298 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 299 | that was the only user who caused events. |
| 300 | */ |
| 301 | if( zUserName ){ |
| @@ -731,12 +731,12 @@ | |
| 731 | ? (int)(100 * nCount / nMaxEvents) |
| 732 | : 0; |
| 733 | if(!nSize) nSize = 1; |
| 734 | total += nCount; |
| 735 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 736 | cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 737 | zYear, zWeek, nCount, |
| 738 | statsReportTimelineYFlag); |
| 739 | if( zUserName ){ |
| 740 | cgi_printf("&u=%t",zUserName); |
| 741 | } |
| 742 | cgi_printf("'>%s</a></td>",zWeek); |
| 743 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -290,12 +290,12 @@ | |
| 290 | nEventsPerYear += nCount; |
| 291 | @<tr class='row%d(rowClass)'> |
| 292 | @ <td> |
| 293 | if(includeMonth){ |
| 294 | cgi_printf("<a href='%R/timeline?" |
| 295 | "ym=%t&y=%s", |
| 296 | zTimeframe, |
| 297 | statsReportTimelineYFlag ); |
| 298 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 299 | that was the only user who caused events. |
| 300 | */ |
| 301 | if( zUserName ){ |
| @@ -731,12 +731,12 @@ | |
| 731 | ? (int)(100 * nCount / nMaxEvents) |
| 732 | : 0; |
| 733 | if(!nSize) nSize = 1; |
| 734 | total += nCount; |
| 735 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 736 | cgi_printf("<td><a href='%R/timeline?yw=%t%s&y=%s", |
| 737 | zYear, zWeek, |
| 738 | statsReportTimelineYFlag); |
| 739 | if( zUserName ){ |
| 740 | cgi_printf("&u=%t",zUserName); |
| 741 | } |
| 742 | cgi_printf("'>%s</a></td>",zWeek); |
| 743 |
+2
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -821,10 +821,11 @@ | ||
| 821 | 821 | if( g.perm.Debug && P("showqp") ){ |
| 822 | 822 | @ <div class="debug"> |
| 823 | 823 | cgi_print_all(0, 0, 0); |
| 824 | 824 | @ </div> |
| 825 | 825 | } |
| 826 | + fossil_free(zTitle); | |
| 826 | 827 | } |
| 827 | 828 | |
| 828 | 829 | #if INTERFACE |
| 829 | 830 | /* Allowed parameters for style_adunit() */ |
| 830 | 831 | #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */ |
| @@ -1300,10 +1301,11 @@ | ||
| 1300 | 1301 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 1301 | 1302 | Th_Store("home", g.zTop); |
| 1302 | 1303 | image_url_var("logo"); |
| 1303 | 1304 | image_url_var("background"); |
| 1304 | 1305 | Th_Render(blob_str(&css)); |
| 1306 | + blob_reset(&css); | |
| 1305 | 1307 | |
| 1306 | 1308 | /* Tell CGI that the content returned by this page is considered cacheable */ |
| 1307 | 1309 | g.isConst = 1; |
| 1308 | 1310 | } |
| 1309 | 1311 | |
| 1310 | 1312 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -821,10 +821,11 @@ | |
| 821 | if( g.perm.Debug && P("showqp") ){ |
| 822 | @ <div class="debug"> |
| 823 | cgi_print_all(0, 0, 0); |
| 824 | @ </div> |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | #if INTERFACE |
| 829 | /* Allowed parameters for style_adunit() */ |
| 830 | #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */ |
| @@ -1300,10 +1301,11 @@ | |
| 1300 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 1301 | Th_Store("home", g.zTop); |
| 1302 | image_url_var("logo"); |
| 1303 | image_url_var("background"); |
| 1304 | Th_Render(blob_str(&css)); |
| 1305 | |
| 1306 | /* Tell CGI that the content returned by this page is considered cacheable */ |
| 1307 | g.isConst = 1; |
| 1308 | } |
| 1309 | |
| 1310 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -821,10 +821,11 @@ | |
| 821 | if( g.perm.Debug && P("showqp") ){ |
| 822 | @ <div class="debug"> |
| 823 | cgi_print_all(0, 0, 0); |
| 824 | @ </div> |
| 825 | } |
| 826 | fossil_free(zTitle); |
| 827 | } |
| 828 | |
| 829 | #if INTERFACE |
| 830 | /* Allowed parameters for style_adunit() */ |
| 831 | #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */ |
| @@ -1300,10 +1301,11 @@ | |
| 1301 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 1302 | Th_Store("home", g.zTop); |
| 1303 | image_url_var("logo"); |
| 1304 | image_url_var("background"); |
| 1305 | Th_Render(blob_str(&css)); |
| 1306 | blob_reset(&css); |
| 1307 | |
| 1308 | /* Tell CGI that the content returned by this page is considered cacheable */ |
| 1309 | g.isConst = 1; |
| 1310 | } |
| 1311 | |
| 1312 |
+474
-462
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -191,11 +191,11 @@ | ||
| 191 | 191 | void www_print_timeline( |
| 192 | 192 | Stmt *pQuery, /* Query to implement the timeline */ |
| 193 | 193 | int tmFlags, /* Flags controlling display behavior */ |
| 194 | 194 | const char *zThisUser, /* Suppress links to this user */ |
| 195 | 195 | const char *zThisTag, /* Suppress links to this tag */ |
| 196 | - const char *zLeftBranch, /* Strive to put this branch on the left margin */ | |
| 196 | + Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */ | |
| 197 | 197 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 198 | 198 | int secondRid, /* Secondary highlight (or zero) */ |
| 199 | 199 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 200 | 200 | ){ |
| 201 | 201 | int mxWikiLen; |
| @@ -813,11 +813,11 @@ | ||
| 813 | 813 | } |
| 814 | 814 | if( pendingEndTr ){ |
| 815 | 815 | @ </td></tr> |
| 816 | 816 | } |
| 817 | 817 | if( pGraph ){ |
| 818 | - graph_finish(pGraph, zLeftBranch, tmFlags); | |
| 818 | + graph_finish(pGraph, pLeftBranch, tmFlags); | |
| 819 | 819 | if( pGraph->nErr ){ |
| 820 | 820 | graph_free(pGraph); |
| 821 | 821 | pGraph = 0; |
| 822 | 822 | }else{ |
| 823 | 823 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| @@ -1290,12 +1290,13 @@ | ||
| 1290 | 1290 | const char *zChng, /* The filename GLOB list */ |
| 1291 | 1291 | Blob *pSql /* The SELECT statement under construction */ |
| 1292 | 1292 | ){ |
| 1293 | 1293 | if( zChng==0 || zChng[0]==0 ) return; |
| 1294 | 1294 | blob_append_sql(pSql," AND event.objid IN (" |
| 1295 | - "SELECT mlink.mid FROM mlink, filename" | |
| 1296 | - " WHERE mlink.fnid=filename.fnid AND %s)", | |
| 1295 | + "SELECT mlink.mid FROM mlink, filename\n" | |
| 1296 | + " WHERE mlink.fnid=filename.fnid\n" | |
| 1297 | + " AND %s)", | |
| 1297 | 1298 | glob_expr("filename.name", mprintf("\"%s\"", zChng))); |
| 1298 | 1299 | } |
| 1299 | 1300 | static void addFileGlobDescription( |
| 1300 | 1301 | const char *zChng, /* The filename GLOB list */ |
| 1301 | 1302 | Blob *pDescription /* Result description */ |
| @@ -1303,274 +1304,86 @@ | ||
| 1303 | 1304 | if( zChng==0 || zChng[0]==0 ) return; |
| 1304 | 1305 | blob_appendf(pDescription, " that include changes to files matching '%h'", |
| 1305 | 1306 | zChng); |
| 1306 | 1307 | } |
| 1307 | 1308 | |
| 1308 | -/* | |
| 1309 | -** Tag match expression type code. | |
| 1310 | -*/ | |
| 1311 | -typedef enum { | |
| 1312 | - MS_EXACT, /* Matches a single tag by exact string comparison. */ | |
| 1313 | - MS_GLOB, /* Matches tags against a list of GLOB patterns. */ | |
| 1314 | - MS_LIKE, /* Matches tags against a list of LIKE patterns. */ | |
| 1315 | - MS_REGEXP, /* Matches tags against a list of regular expressions. */ | |
| 1316 | - MS_BRLIST, /* Same as REGEXP, except the regular expression is a list | |
| 1317 | - ** of branch names */ | |
| 1318 | -} MatchStyle; | |
| 1319 | - | |
| 1320 | -/* | |
| 1321 | -** Quote a tag string by surrounding it with double quotes and preceding | |
| 1322 | -** internal double quotes and backslashes with backslashes. | |
| 1323 | -*/ | |
| 1324 | -static const char *tagQuote( | |
| 1325 | - int len, /* Maximum length of zTag, or negative for unlimited */ | |
| 1326 | - const char *zTag /* Tag string */ | |
| 1327 | -){ | |
| 1328 | - Blob blob = BLOB_INITIALIZER; | |
| 1329 | - int i, j; | |
| 1330 | - blob_zero(&blob); | |
| 1331 | - blob_append(&blob, "\"", 1); | |
| 1332 | - for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){ | |
| 1333 | - if( zTag[j]=='\"' || zTag[j]=='\\' ){ | |
| 1334 | - if( j>i ){ | |
| 1335 | - blob_append(&blob, zTag+i, j-i); | |
| 1336 | - } | |
| 1337 | - blob_append(&blob, "\\", 1); | |
| 1338 | - i = j; | |
| 1339 | - } | |
| 1340 | - } | |
| 1341 | - if( j>i ){ | |
| 1342 | - blob_append(&blob, zTag+i, j-i); | |
| 1343 | - } | |
| 1344 | - blob_append(&blob, "\"", 1); | |
| 1345 | - return blob_str(&blob); | |
| 1346 | -} | |
| 1347 | - | |
| 1348 | -/* | |
| 1349 | -** Construct the tag match SQL expression. | |
| 1350 | -** | |
| 1351 | -** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB, | |
| 1352 | -** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles. | |
| 1353 | -** | |
| 1354 | -** For MS_EXACT, the returned expression | |
| 1355 | -** checks for integer match against the tag ID which is looked up directly by | |
| 1356 | -** this function. For the other modes, the returned SQL expression performs | |
| 1357 | -** string comparisons against the tag names, so it is necessary to join against | |
| 1358 | -** the tag table to access the "tagname" column. | |
| 1359 | -** | |
| 1360 | -** Each pattern is adjusted to to start with "sym-" and be anchored at end. | |
| 1361 | -** | |
| 1362 | -** In MS_REGEXP mode, backslash can be used to protect delimiter characters. | |
| 1363 | -** The backslashes are not removed from the regular expression. | |
| 1364 | -** | |
| 1365 | -** In addition to assembling and returning an SQL expression, this function | |
| 1366 | -** makes an English-language description of the patterns being matched, suitable | |
| 1367 | -** for display in the web interface. | |
| 1368 | -** | |
| 1369 | -** If any errors arise during processing, *zError is set to an error message. | |
| 1370 | -** Otherwise it is set to NULL. | |
| 1371 | -*/ | |
| 1372 | -static const char *tagMatchExpression( | |
| 1373 | - MatchStyle matchStyle, /* Match style code */ | |
| 1374 | - const char *zTag, /* Tag name, match pattern, or pattern list */ | |
| 1375 | - const char **zDesc, /* Output expression description string */ | |
| 1376 | - const char **zError /* Output error string */ | |
| 1377 | -){ | |
| 1378 | - Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */ | |
| 1379 | - Blob desc = BLOB_INITIALIZER; /* English description of match patterns */ | |
| 1380 | - Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */ | |
| 1381 | - const char *zStart; /* Text at start of expression */ | |
| 1382 | - const char *zDelimiter; /* Text between expression terms */ | |
| 1383 | - const char *zEnd; /* Text at end of expression */ | |
| 1384 | - const char *zPrefix; /* Text before each match pattern */ | |
| 1385 | - const char *zSuffix; /* Text after each match pattern */ | |
| 1386 | - const char *zIntro; /* Text introducing pattern description */ | |
| 1387 | - const char *zPattern = 0; /* Previous quoted pattern */ | |
| 1388 | - const char *zFail = 0; /* Current failure message or NULL if okay */ | |
| 1389 | - const char *zOr = " or "; /* Text before final quoted pattern */ | |
| 1390 | - char cDel; /* Input delimiter character */ | |
| 1391 | - int i; /* Input match pattern length counter */ | |
| 1392 | - | |
| 1393 | - /* Optimize exact matches by looking up the ID in advance to create a simple | |
| 1394 | - * numeric comparison. Bypass the remainder of this function. */ | |
| 1395 | - if( matchStyle==MS_EXACT ){ | |
| 1396 | - *zDesc = tagQuote(-1, zTag); | |
| 1397 | - return mprintf("(tagid=%d)", db_int(-1, | |
| 1398 | - "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag)); | |
| 1399 | - } | |
| 1400 | - | |
| 1401 | - /* Decide pattern prefix and suffix strings according to match style. */ | |
| 1402 | - if( matchStyle==MS_GLOB ){ | |
| 1403 | - zStart = "("; | |
| 1404 | - zDelimiter = " OR "; | |
| 1405 | - zEnd = ")"; | |
| 1406 | - zPrefix = "tagname GLOB 'sym-"; | |
| 1407 | - zSuffix = "'"; | |
| 1408 | - zIntro = "glob pattern "; | |
| 1409 | - }else if( matchStyle==MS_LIKE ){ | |
| 1410 | - zStart = "("; | |
| 1411 | - zDelimiter = " OR "; | |
| 1412 | - zEnd = ")"; | |
| 1413 | - zPrefix = "tagname LIKE 'sym-"; | |
| 1414 | - zSuffix = "'"; | |
| 1415 | - zIntro = "SQL LIKE pattern "; | |
| 1416 | - }else if( matchStyle==MS_REGEXP ){ | |
| 1417 | - zStart = "(tagname REGEXP '^sym-("; | |
| 1418 | - zDelimiter = "|"; | |
| 1419 | - zEnd = ")$')"; | |
| 1420 | - zPrefix = ""; | |
| 1421 | - zSuffix = ""; | |
| 1422 | - zIntro = "regular expression "; | |
| 1423 | - }else/* if( matchStyle==MS_BRLIST )*/{ | |
| 1424 | - zStart = "tagname IN ('sym-"; | |
| 1425 | - zDelimiter = "','sym-"; | |
| 1426 | - zEnd = "')"; | |
| 1427 | - zPrefix = ""; | |
| 1428 | - zSuffix = ""; | |
| 1429 | - zIntro = ""; | |
| 1430 | - } | |
| 1431 | - | |
| 1432 | - /* Convert the list of matches into an SQL expression and text description. */ | |
| 1433 | - blob_zero(&expr); | |
| 1434 | - blob_zero(&desc); | |
| 1435 | - blob_zero(&err); | |
| 1436 | - while( 1 ){ | |
| 1437 | - /* Skip leading delimiters. */ | |
| 1438 | - for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag ); | |
| 1439 | - | |
| 1440 | - /* Next non-delimiter character determines quoting. */ | |
| 1441 | - if( !*zTag ){ | |
| 1442 | - /* Terminate loop at end of string. */ | |
| 1443 | - break; | |
| 1444 | - }else if( *zTag=='\'' || *zTag=='"' ){ | |
| 1445 | - /* If word is quoted, prepare to stop at end quote. */ | |
| 1446 | - cDel = *zTag; | |
| 1447 | - ++zTag; | |
| 1448 | - }else{ | |
| 1449 | - /* If word is not quoted, prepare to stop at delimiter. */ | |
| 1450 | - cDel = ','; | |
| 1451 | - } | |
| 1452 | - | |
| 1453 | - /* Find the next delimiter character or end of string. */ | |
| 1454 | - for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){ | |
| 1455 | - /* If delimiter is comma, also recognize spaces as delimiters. */ | |
| 1456 | - if( cDel==',' && fossil_isspace(zTag[i]) ){ | |
| 1457 | - break; | |
| 1458 | - } | |
| 1459 | - | |
| 1460 | - /* In regexp mode, ignore delimiters following backslashes. */ | |
| 1461 | - if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){ | |
| 1462 | - ++i; | |
| 1463 | - } | |
| 1464 | - } | |
| 1465 | - | |
| 1466 | - /* Check for regular expression syntax errors. */ | |
| 1467 | - if( matchStyle==MS_REGEXP ){ | |
| 1468 | - ReCompiled *regexp; | |
| 1469 | - char *zTagDup = fossil_strndup(zTag, i); | |
| 1470 | - zFail = re_compile(®exp, zTagDup, 0); | |
| 1471 | - re_free(regexp); | |
| 1472 | - fossil_free(zTagDup); | |
| 1473 | - } | |
| 1474 | - | |
| 1475 | - /* Process success and error results. */ | |
| 1476 | - if( !zFail ){ | |
| 1477 | - /* Incorporate the match word into the output expression. %q is used to | |
| 1478 | - * protect against SQL injection attacks by replacing ' with ''. */ | |
| 1479 | - blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart, | |
| 1480 | - zPrefix, i, zTag, zSuffix); | |
| 1481 | - | |
| 1482 | - /* Build up the description string. */ | |
| 1483 | - if( !blob_size(&desc) ){ | |
| 1484 | - /* First tag: start with intro followed by first quoted tag. */ | |
| 1485 | - blob_append(&desc, zIntro, -1); | |
| 1486 | - blob_append(&desc, tagQuote(i, zTag), -1); | |
| 1487 | - }else{ | |
| 1488 | - if( zPattern ){ | |
| 1489 | - /* Third and subsequent tags: append comma then previous tag. */ | |
| 1490 | - blob_append(&desc, ", ", 2); | |
| 1491 | - blob_append(&desc, zPattern, -1); | |
| 1492 | - zOr = ", or "; | |
| 1493 | - } | |
| 1494 | - | |
| 1495 | - /* Second and subsequent tags: store quoted tag for next iteration. */ | |
| 1496 | - zPattern = tagQuote(i, zTag); | |
| 1497 | - } | |
| 1498 | - }else{ | |
| 1499 | - /* On error, skip the match word and build up the error message buffer. */ | |
| 1500 | - if( !blob_size(&err) ){ | |
| 1501 | - blob_append(&err, "Error: ", 7); | |
| 1502 | - }else{ | |
| 1503 | - blob_append(&err, ", ", 2); | |
| 1504 | - } | |
| 1505 | - blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail); | |
| 1506 | - } | |
| 1507 | - | |
| 1508 | - /* Advance past all consumed input characters. */ | |
| 1509 | - zTag += i; | |
| 1510 | - if( cDel!=',' && *zTag==cDel ){ | |
| 1511 | - ++zTag; | |
| 1512 | - } | |
| 1513 | - } | |
| 1514 | - | |
| 1515 | - /* Finalize and extract the pattern description. */ | |
| 1516 | - if( zPattern ){ | |
| 1517 | - blob_append(&desc, zOr, -1); | |
| 1518 | - blob_append(&desc, zPattern, -1); | |
| 1519 | - } | |
| 1520 | - *zDesc = blob_str(&desc); | |
| 1521 | - | |
| 1522 | - /* Finalize and extract the error text. */ | |
| 1523 | - *zError = blob_size(&err) ? blob_str(&err) : 0; | |
| 1524 | - | |
| 1525 | - /* Finalize and extract the SQL expression. */ | |
| 1526 | - if( blob_size(&expr) ){ | |
| 1527 | - blob_append(&expr, zEnd, -1); | |
| 1528 | - return blob_str(&expr); | |
| 1529 | - } | |
| 1530 | - | |
| 1531 | - /* If execution reaches this point, the pattern was empty. Return NULL. */ | |
| 1532 | - return 0; | |
| 1533 | -} | |
| 1534 | - | |
| 1535 | 1309 | /* |
| 1536 | 1310 | ** Similar to fossil_expand_datetime() |
| 1537 | 1311 | ** |
| 1538 | 1312 | ** Add missing "-" characters into a date/time. Examples: |
| 1539 | 1313 | ** |
| 1540 | 1314 | ** 20190419 => 2019-04-19 |
| 1541 | 1315 | ** 201904 => 2019-04 |
| 1542 | 1316 | */ |
| 1543 | -const char *timeline_expand_datetime(const char *zIn){ | |
| 1544 | - static char zEDate[20]; | |
| 1545 | - static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; | |
| 1317 | +const char *timeline_expand_datetime(const char *zIn, int *pbZulu){ | |
| 1318 | + static char zEDate[16]; | |
| 1546 | 1319 | int n = (int)strlen(zIn); |
| 1547 | 1320 | int i, j; |
| 1548 | 1321 | |
| 1549 | - /* Only three forms allowed: | |
| 1322 | + /* These forms are recognized: | |
| 1323 | + ** | |
| 1550 | 1324 | ** (1) YYYYMMDD |
| 1551 | 1325 | ** (2) YYYYMM |
| 1552 | 1326 | ** (3) YYYYWW |
| 1553 | 1327 | */ |
| 1328 | + if( n && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){ | |
| 1329 | + n--; | |
| 1330 | + if( pbZulu ) *pbZulu = 1; | |
| 1331 | + }else{ | |
| 1332 | + if( pbZulu ) *pbZulu = 0; | |
| 1333 | + } | |
| 1554 | 1334 | if( n!=8 && n!=6 ) return zIn; |
| 1555 | 1335 | |
| 1556 | 1336 | /* Every character must be a digit */ |
| 1557 | - for(i=0; fossil_isdigit(zIn[i]); i++){} | |
| 1337 | + for(i=0; i<n && fossil_isdigit(zIn[i]); i++){} | |
| 1558 | 1338 | if( i!=n ) return zIn; |
| 1559 | 1339 | |
| 1560 | 1340 | /* Expand the date */ |
| 1561 | - for(i=j=0; zIn[i]; i++){ | |
| 1562 | - if( i>=4 && (i%2)==0 ){ | |
| 1563 | - zEDate[j++] = aPunct[i/2]; | |
| 1564 | - } | |
| 1341 | + for(i=j=0; i<n; i++){ | |
| 1342 | + if( j==4 || j==7 ) zEDate[j++] = '-'; | |
| 1565 | 1343 | zEDate[j++] = zIn[i]; |
| 1566 | 1344 | } |
| 1567 | 1345 | zEDate[j] = 0; |
| 1568 | 1346 | |
| 1569 | 1347 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | 1348 | return zEDate; |
| 1571 | 1349 | } |
| 1350 | + | |
| 1351 | +/* | |
| 1352 | +** Check to see if the argument is a date-span for the ymd= query | |
| 1353 | +** parameter. A valid date-span is of the form: | |
| 1354 | +** | |
| 1355 | +** 0123456789 123456 <-- index | |
| 1356 | +** YYYYMMDD-YYYYMMDD | |
| 1357 | +** | |
| 1358 | +** with an optional "Z" timeline modifier at the end. Return true if | |
| 1359 | +** the input is a valid date space and false if not. | |
| 1360 | +*/ | |
| 1361 | +static int timeline_is_datespan(const char *zDay){ | |
| 1362 | + size_t n = strlen(zDay); | |
| 1363 | + int i, d, m; | |
| 1364 | + | |
| 1365 | + if( n<17 || n>18 ) return 0; | |
| 1366 | + if( n==18 ){ | |
| 1367 | + if( zDay[17]!='Z' && zDay[17]!='z' ) return 0; | |
| 1368 | + n--; | |
| 1369 | + } | |
| 1370 | + if( zDay[8]!='-' ) return 0; | |
| 1371 | + for(i=0; i<17 && (fossil_isdigit(zDay[i]) || i==8); i++){} | |
| 1372 | + if( i!=17 ) return 0; | |
| 1373 | + i = atoi(zDay); | |
| 1374 | + d = i%100; | |
| 1375 | + if( d<1 || d>31 ) return 0; | |
| 1376 | + m = (i/100)%100; | |
| 1377 | + if( m<1 || m>12 ) return 0; | |
| 1378 | + i = atoi(zDay+9); | |
| 1379 | + d = i%100; | |
| 1380 | + if( d<1 || d>31 ) return 0; | |
| 1381 | + m = (i/100)%100; | |
| 1382 | + if( m<1 || m>12 ) return 0; | |
| 1383 | + return 1; | |
| 1384 | +} | |
| 1572 | 1385 | |
| 1573 | 1386 | /* |
| 1574 | 1387 | ** Find the first check-in encountered with a particular tag |
| 1575 | 1388 | ** when moving either forwards are backwards in time from a |
| 1576 | 1389 | ** particular starting point (iFrom). Return the rid of that |
| @@ -1591,10 +1404,11 @@ | ||
| 1591 | 1404 | tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd); |
| 1592 | 1405 | if( tagId==0 ){ |
| 1593 | 1406 | endId = symbolic_name_to_rid(zEnd, "ci"); |
| 1594 | 1407 | if( endId==0 ) return 0; |
| 1595 | 1408 | } |
| 1409 | + db_pause_dml_log(); | |
| 1596 | 1410 | if( bForward ){ |
| 1597 | 1411 | if( tagId ){ |
| 1598 | 1412 | db_prepare(&q, |
| 1599 | 1413 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1600 | 1414 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| @@ -1658,12 +1472,54 @@ | ||
| 1658 | 1472 | } |
| 1659 | 1473 | if( db_step(&q)==SQLITE_ROW ){ |
| 1660 | 1474 | ans = db_column_int(&q, 0); |
| 1661 | 1475 | } |
| 1662 | 1476 | db_finalize(&q); |
| 1477 | + db_unpause_dml_log(); | |
| 1663 | 1478 | return ans; |
| 1664 | 1479 | } |
| 1480 | + | |
| 1481 | +/* | |
| 1482 | +** Add to the (temp) table zTab, RID values for every check-in | |
| 1483 | +** identifier found on the zExtra string. Check-in names can be separated | |
| 1484 | +** by commas or by whitespace. | |
| 1485 | +*/ | |
| 1486 | +static void add_extra_rids(const char *zTab, const char *zExtra){ | |
| 1487 | + int ii; | |
| 1488 | + int rid; | |
| 1489 | + int cnt; | |
| 1490 | + Blob sql; | |
| 1491 | + char *zX; | |
| 1492 | + char *zToDel; | |
| 1493 | + if( zExtra==0 ) return; | |
| 1494 | + cnt = 0; | |
| 1495 | + blob_init(&sql, 0, 0); | |
| 1496 | + zX = zToDel = fossil_strdup(zExtra); | |
| 1497 | + blob_append_sql(&sql, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab); | |
| 1498 | + while( zX[0] ){ | |
| 1499 | + char c; | |
| 1500 | + if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; } | |
| 1501 | + for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){} | |
| 1502 | + c = zX[ii]; | |
| 1503 | + zX[ii] = 0; | |
| 1504 | + rid = name_to_rid(zX); | |
| 1505 | + if( rid>0 ){ | |
| 1506 | + if( (cnt%10)==4 ){ | |
| 1507 | + blob_append_sql(&sql,",\n "); | |
| 1508 | + }else if( cnt>0 ){ | |
| 1509 | + blob_append_sql(&sql,","); | |
| 1510 | + } | |
| 1511 | + blob_append_sql(&sql, "(%d)", rid); | |
| 1512 | + cnt++; | |
| 1513 | + } | |
| 1514 | + zX[ii] = c; | |
| 1515 | + zX += ii; | |
| 1516 | + } | |
| 1517 | + if( cnt ) db_exec_sql(blob_sql_text(&sql)); | |
| 1518 | + blob_reset(&sql); | |
| 1519 | + fossil_free(zToDel); | |
| 1520 | +} | |
| 1665 | 1521 | |
| 1666 | 1522 | /* |
| 1667 | 1523 | ** COMMAND: test-endpoint |
| 1668 | 1524 | ** |
| 1669 | 1525 | ** Usage: fossil test-endpoint BASE TAG ?OPTIONS? |
| @@ -1698,21 +1554,21 @@ | ||
| 1698 | 1554 | /* |
| 1699 | 1555 | ** WEBPAGE: timeline |
| 1700 | 1556 | ** |
| 1701 | 1557 | ** Query parameters: |
| 1702 | 1558 | ** |
| 1703 | -** a=TIMEORTAG Show events after TIMEORTAG | |
| 1704 | -** b=TIMEORTAG Show events before TIMEORTAG | |
| 1559 | +** a=TIMEORTAG Show events after TIMEORTAG. | |
| 1560 | +** b=TIMEORTAG Show events before TIMEORTAG. | |
| 1705 | 1561 | ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG |
| 1706 | 1562 | ** cf=FILEHASH Show events around the time of the first use of |
| 1707 | -** the file with FILEHASH | |
| 1563 | +** the file with FILEHASH. | |
| 1708 | 1564 | ** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available |
| 1709 | 1565 | ** event if TIMEORTAG is not part of the timeline. If |
| 1710 | 1566 | ** the t= or r= is used, the m event is added to the timeline |
| 1711 | 1567 | ** if it isn't there already. |
| 1712 | -** x=HASHLIST Show all check-ins in the comma-separated HASHLIST | |
| 1713 | -** in addition to check-ins specified by t= or r= | |
| 1568 | +** x=LIST Show check-ins in the comma- or space-separated LIST | |
| 1569 | +** in addition to check-ins specified by other parameters. | |
| 1714 | 1570 | ** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of |
| 1715 | 1571 | ** the timeline. Similar to m= except TIMEORTAG must |
| 1716 | 1572 | ** match a check-in that is already in the timeline. |
| 1717 | 1573 | ** sel2=TIMEORTAG Like sel1= but use the secondary highlight. |
| 1718 | 1574 | ** n=COUNT Maximum number of events. "all" for no limit |
| @@ -1732,63 +1588,71 @@ | ||
| 1732 | 1588 | ** from=CX ... shortest path from CX back to CHECKIN |
| 1733 | 1589 | ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN |
| 1734 | 1590 | ** d=CX ... from CX up to the time of CHECKIN |
| 1735 | 1591 | ** from=CX ... shortest path from CX up to CHECKIN |
| 1736 | 1592 | ** t=TAG Show only check-ins with the given TAG |
| 1737 | -** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel | |
| 1738 | -** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist | |
| 1739 | -** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist | |
| 1593 | +** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related" | |
| 1594 | +** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List" | |
| 1595 | +** rl=TAGLIST Same as 'r=TAGLIST&ms=brlist'. Mnemonic: "Related List" | |
| 1596 | +** ml=TAGLIST Same as 'tl=TAGLIST&mionly'. Mnemonic: "Merge-in List" | |
| 1597 | +** sl=TAGLIST "Sort List". Draw TAGLIST branches ordered left to right. | |
| 1740 | 1598 | ** rel Show related check-ins as well as those matching t=TAG |
| 1741 | -** mionly Limit rel to show ancestors but not descendants | |
| 1599 | +** mionly Show related parents but not related children. | |
| 1742 | 1600 | ** nowiki Do not show wiki associated with branch or tag |
| 1743 | -** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP | |
| 1601 | +** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", | |
| 1602 | +** "like", or "regexp". | |
| 1744 | 1603 | ** u=USER Only show items associated with USER |
| 1745 | 1604 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1746 | 1605 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1747 | -** x: "Classic". | |
| 1606 | +* x: "Classic". | |
| 1748 | 1607 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1749 | 1608 | ** ng No Graph. |
| 1750 | 1609 | ** ncp Omit cherrypick merges |
| 1751 | 1610 | ** nd Do not highlight the focus check-in |
| 1752 | 1611 | ** nsm Omit the submenu |
| 1753 | 1612 | ** nc Omit all graph colors other than highlights |
| 1754 | 1613 | ** v Show details of files changed |
| 1755 | 1614 | ** vfx Show complete text of forum messages |
| 1756 | -** f=CHECKIN Show family (immediate parents and children) of CHECKIN | |
| 1757 | -** from=CHECKIN Path from... | |
| 1615 | +** f=CHECKIN Family (immediate parents and children) of CHECKIN | |
| 1616 | +** from=CHECKIN Path through common ancestor from... | |
| 1758 | 1617 | ** to=CHECKIN ... to this |
| 1759 | 1618 | ** to2=CHECKIN ... backup name if to= doesn't resolve |
| 1760 | 1619 | ** shortest ... show only the shortest path |
| 1761 | 1620 | ** rel ... also show related checkins |
| 1762 | 1621 | ** bt=PRIOR ... path from CHECKIN back to PRIOR |
| 1763 | 1622 | ** ft=LATER ... path from CHECKIN forward to LATER |
| 1623 | +** me=CHECKIN Most direct path from... | |
| 1624 | +** you=CHECKIN ... to this | |
| 1625 | +** rel ... also show related checkins | |
| 1764 | 1626 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1765 | -** All qualifying check-ins are shown unless there is | |
| 1766 | -** also an n= or n1= query parameter. | |
| 1627 | +** All qualifying check-ins are shown unless there is | |
| 1628 | +** also an n= or n1= query parameter. | |
| 1767 | 1629 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1768 | -** name matches one of the comma-separate GLOBLIST | |
| 1630 | +** name matches one of the comma-separate GLOBLIST | |
| 1769 | 1631 | ** brbg Background color determined by branch name |
| 1770 | 1632 | ** ubg Background color determined by user |
| 1771 | 1633 | ** deltabg Background color red for delta manifests or green |
| 1772 | 1634 | ** for baseline manifests |
| 1773 | 1635 | ** namechng Show only check-ins that have filename changes |
| 1774 | 1636 | ** forks Show only forks and their children |
| 1775 | 1637 | ** cherrypicks Show all cherrypicks |
| 1776 | -** ym=YYYY-MM Show only events for the given year/month | |
| 1777 | -** yw=YYYY-WW Show only events for the given week of the given year | |
| 1778 | -** yw=YYYY-MM-DD Show events for the week that includes the given day | |
| 1779 | -** ymd=YYYY-MM-DD Show only events on the given day. The use "ymd=now" | |
| 1780 | -** to see all changes for the current week. | |
| 1638 | +** ym=YYYYMM Show only events for the given year/month | |
| 1639 | +** yw=YYYYWW Show only events for the given week of the given year | |
| 1640 | +** yw=YYYYMMDD Show events for the week that includes the given day | |
| 1641 | +** ymd=YYYYMMDD Show only events on the given day. The use "ymd=now" | |
| 1642 | +** to see all changes for the current week. Add "z" at end | |
| 1643 | +** to divide days at UTC instead of localtime days. | |
| 1644 | +** Use ymd=YYYYMMDD-YYYYMMDD (with optional "z") for a range. | |
| 1781 | 1645 | ** year=YYYY Show only events on the given year. The use "year=0" |
| 1782 | 1646 | ** to see all changes for the current year. |
| 1783 | 1647 | ** days=N Show events over the previous N days |
| 1784 | 1648 | ** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS, |
| 1785 | 1649 | ** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off". |
| 1786 | 1650 | ** bisect Show the check-ins that are in the current bisect |
| 1787 | 1651 | ** oldestfirst Show events oldest first. |
| 1788 | 1652 | ** showid Show RIDs |
| 1789 | -** showsql Show the SQL text | |
| 1653 | +** showsql Show the SQL used to generate the report | |
| 1790 | 1654 | ** |
| 1791 | 1655 | ** p= and d= can appear individually or together. If either p= or d= |
| 1792 | 1656 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1793 | 1657 | ** |
| 1794 | 1658 | ** If both a= and b= appear then both upper and lower bounds are honored. |
| @@ -1862,13 +1726,20 @@ | ||
| 1862 | 1726 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1863 | 1727 | char *zPlural; /* Ending for plural forms */ |
| 1864 | 1728 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1865 | 1729 | int haveParameterN; /* True if n= query parameter present */ |
| 1866 | 1730 | int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ |
| 1731 | + int showSql = PB("showsql"); /* True to show the SQL */ | |
| 1732 | + Blob allSql; /* Copy of all SQL text */ | |
| 1867 | 1733 | |
| 1734 | + login_check_credentials(); | |
| 1868 | 1735 | url_initialize(&url, "timeline"); |
| 1869 | 1736 | cgi_query_parameters_to_url(&url); |
| 1737 | + blob_init(&allSql, 0, 0); | |
| 1738 | + | |
| 1739 | + /* The "mionly" query parameter is like "rel", but shows merge-ins only */ | |
| 1740 | + if( P("mionly")!=0 ) related = 2; | |
| 1870 | 1741 | |
| 1871 | 1742 | (void)P_NoBot("ss") |
| 1872 | 1743 | /* "ss" is processed via the udc but at least one spider likes to |
| 1873 | 1744 | ** try to SQL inject via this argument, so let's catch that. */; |
| 1874 | 1745 | |
| @@ -1943,11 +1814,10 @@ | ||
| 1943 | 1814 | */ |
| 1944 | 1815 | pd_rid = name_choice("dp","dp2",&zDPName); |
| 1945 | 1816 | if( pd_rid ){ |
| 1946 | 1817 | p_rid = d_rid = pd_rid; |
| 1947 | 1818 | } |
| 1948 | - login_check_credentials(); | |
| 1949 | 1819 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) |
| 1950 | 1820 | || (bisectLocal && !g.perm.Setup) |
| 1951 | 1821 | ){ |
| 1952 | 1822 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1953 | 1823 | return; |
| @@ -1984,48 +1854,52 @@ | ||
| 1984 | 1854 | |
| 1985 | 1855 | /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for |
| 1986 | 1856 | ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */ |
| 1987 | 1857 | if( zBrName==0 && zTagName==0 ){ |
| 1988 | 1858 | const char *z; |
| 1859 | + const char *zPattern = 0; | |
| 1989 | 1860 | if( (z = P("tl"))!=0 ){ |
| 1990 | - zTagName = z; | |
| 1991 | - zMatchStyle = "brlist"; | |
| 1861 | + zPattern = zTagName = z; | |
| 1862 | + }else if( (z = P("rl"))!=0 ){ | |
| 1863 | + zPattern = zBrName = z; | |
| 1864 | + if( related==0 ) related = 1; | |
| 1865 | + }else if( (z = P("ml"))!=0 ){ | |
| 1866 | + zPattern = zBrName = z; | |
| 1867 | + if( related==0 ) related = 2; | |
| 1992 | 1868 | } |
| 1993 | - if( (z = P("rl"))!=0 ){ | |
| 1994 | - zBrName = z; | |
| 1995 | - zMatchStyle = "brlist"; | |
| 1869 | + if( zPattern!=0 && zMatchStyle==0 ){ | |
| 1870 | + /* If there was no ms= query parameter, set the match style to | |
| 1871 | + ** "glob" if the pattern appears to contain GLOB character, or | |
| 1872 | + ** "brlist" if it does not. */ | |
| 1873 | + if( strpbrk(zPattern,"*[?") ){ | |
| 1874 | + zMatchStyle = "glob"; | |
| 1875 | + }else{ | |
| 1876 | + zMatchStyle = "brlist"; | |
| 1877 | + } | |
| 1996 | 1878 | } |
| 1997 | 1879 | } |
| 1998 | 1880 | |
| 1999 | 1881 | /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */ |
| 2000 | - if( zBrName && !related ){ | |
| 1882 | + if( zBrName ){ | |
| 2001 | 1883 | cgi_delete_query_parameter("r"); |
| 2002 | 1884 | cgi_set_query_parameter("t", zBrName); (void)P("t"); |
| 2003 | 1885 | cgi_set_query_parameter("rel", "1"); |
| 2004 | 1886 | zTagName = zBrName; |
| 2005 | - related = 1; | |
| 1887 | + if( related==0 ) related = 1; | |
| 2006 | 1888 | zType = "ci"; |
| 2007 | 1889 | } |
| 2008 | 1890 | |
| 2009 | 1891 | /* Ignore empty tag query strings. */ |
| 2010 | 1892 | if( zTagName && !*zTagName ){ |
| 2011 | 1893 | zTagName = 0; |
| 2012 | 1894 | } |
| 2013 | 1895 | |
| 2014 | 1896 | /* Finish preliminary processing of tag match queries. */ |
| 1897 | + matchStyle = match_style(zMatchStyle, MS_EXACT); | |
| 2015 | 1898 | if( zTagName ){ |
| 2016 | 1899 | zType = "ci"; |
| 2017 | - /* Interpet the tag style string. */ | |
| 2018 | - if( fossil_stricmp(zMatchStyle, "glob")==0 ){ | |
| 2019 | - matchStyle = MS_GLOB; | |
| 2020 | - }else if( fossil_stricmp(zMatchStyle, "like")==0 ){ | |
| 2021 | - matchStyle = MS_LIKE; | |
| 2022 | - }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){ | |
| 2023 | - matchStyle = MS_REGEXP; | |
| 2024 | - }else if( fossil_stricmp(zMatchStyle, "brlist")==0 ){ | |
| 2025 | - matchStyle = MS_BRLIST; | |
| 2026 | - }else{ | |
| 1900 | + if( matchStyle==MS_EXACT ){ | |
| 2027 | 1901 | /* For exact maching, inhibit links to the selected tag. */ |
| 2028 | 1902 | zThisTag = zTagName; |
| 2029 | 1903 | Th_Store("current_checkin", zTagName); |
| 2030 | 1904 | } |
| 2031 | 1905 | |
| @@ -2033,11 +1907,11 @@ | ||
| 2033 | 1907 | if( advancedMenu ){ |
| 2034 | 1908 | style_submenu_checkbox("rel", "Related", 0, 0); |
| 2035 | 1909 | } |
| 2036 | 1910 | |
| 2037 | 1911 | /* Construct the tag match expression. */ |
| 2038 | - zTagSql = tagMatchExpression(matchStyle, zTagName, &zMatchDesc, &zError); | |
| 1912 | + zTagSql = match_tag_sqlexpr(matchStyle, zTagName, &zMatchDesc, &zError); | |
| 2039 | 1913 | } |
| 2040 | 1914 | |
| 2041 | 1915 | if( zMark && zMark[0]==0 ){ |
| 2042 | 1916 | if( zAfter ) zMark = zAfter; |
| 2043 | 1917 | if( zBefore ) zMark = zBefore; |
| @@ -2081,10 +1955,11 @@ | ||
| 2081 | 1955 | } |
| 2082 | 1956 | if( PB("nc") ){ |
| 2083 | 1957 | tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR); |
| 2084 | 1958 | tmFlags |= TIMELINE_NOCOLOR; |
| 2085 | 1959 | } |
| 1960 | + if( showSql ) db_append_dml_to_blob(&allSql); | |
| 2086 | 1961 | if( zUses!=0 ){ |
| 2087 | 1962 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 2088 | 1963 | if( ufid ){ |
| 2089 | 1964 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 2090 | 1965 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| @@ -2098,42 +1973,42 @@ | ||
| 2098 | 1973 | } |
| 2099 | 1974 | if( renameOnly ){ |
| 2100 | 1975 | db_multi_exec( |
| 2101 | 1976 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 2102 | 1977 | "INSERT OR IGNORE INTO rnfile" |
| 2103 | - " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" | |
| 1978 | + " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" | |
| 2104 | 1979 | ); |
| 2105 | 1980 | disableY = 1; |
| 2106 | 1981 | } |
| 2107 | 1982 | if( forkOnly ){ |
| 2108 | 1983 | db_multi_exec( |
| 2109 | 1984 | "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n" |
| 2110 | 1985 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 2111 | 1986 | " SELECT pid FROM plink\n" |
| 2112 | - " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" | |
| 2113 | - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 2114 | - " GROUP BY pid" | |
| 1987 | + " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" | |
| 1988 | + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 1989 | + " GROUP BY pid\n" | |
| 2115 | 1990 | " HAVING count(*)>1;\n" |
| 2116 | - "INSERT OR IGNORE INTO rnfork(rid)" | |
| 1991 | + "INSERT OR IGNORE INTO rnfork(rid)\n" | |
| 2117 | 1992 | " SELECT cid FROM plink\n" |
| 2118 | - " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" | |
| 2119 | - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 2120 | - " GROUP BY cid" | |
| 1993 | + " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" | |
| 1994 | + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 1995 | + " GROUP BY cid\n" | |
| 2121 | 1996 | " HAVING count(*)>1;\n", |
| 2122 | 1997 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 2123 | 1998 | ); |
| 2124 | 1999 | db_multi_exec( |
| 2125 | 2000 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 2126 | 2001 | " SELECT cid FROM plink\n" |
| 2127 | - " WHERE pid IN rnfork" | |
| 2128 | - " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" | |
| 2129 | - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 2130 | - " UNION " | |
| 2002 | + " WHERE pid IN rnfork\n" | |
| 2003 | + " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" | |
| 2004 | + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" | |
| 2005 | + " UNION\n" | |
| 2131 | 2006 | " SELECT pid FROM plink\n" |
| 2132 | - " WHERE cid IN rnfork" | |
| 2133 | - " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" | |
| 2134 | - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n", | |
| 2007 | + " WHERE cid IN rnfork\n" | |
| 2008 | + " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" | |
| 2009 | + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n", | |
| 2135 | 2010 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 2136 | 2011 | ); |
| 2137 | 2012 | tmFlags |= TIMELINE_UNHIDE; |
| 2138 | 2013 | zType = "ci"; |
| 2139 | 2014 | disableY = 1; |
| @@ -2206,77 +2081,114 @@ | ||
| 2206 | 2081 | PathNode *p = 0; |
| 2207 | 2082 | const char *zFrom = 0; |
| 2208 | 2083 | const char *zTo = 0; |
| 2209 | 2084 | Blob ins; |
| 2210 | 2085 | int nNodeOnPath = 0; |
| 2086 | + int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */ | |
| 2087 | + int earlierRid = 0, laterRid = 0; | |
| 2211 | 2088 | |
| 2212 | 2089 | if( from_rid && to_rid ){ |
| 2213 | 2090 | if( from_to_mode==0 ){ |
| 2214 | 2091 | p = path_shortest(from_rid, to_rid, noMerge, 0, 0); |
| 2215 | 2092 | }else if( from_to_mode==1 ){ |
| 2216 | 2093 | p = path_shortest(from_rid, to_rid, 0, 1, 0); |
| 2094 | + earlierRid = commonAncs = from_rid; | |
| 2095 | + laterRid = to_rid; | |
| 2217 | 2096 | }else{ |
| 2218 | 2097 | p = path_shortest(to_rid, from_rid, 0, 1, 0); |
| 2098 | + earlierRid = commonAncs = to_rid; | |
| 2099 | + laterRid = from_rid; | |
| 2219 | 2100 | } |
| 2220 | 2101 | zFrom = P("from"); |
| 2221 | 2102 | zTo = zTo2 ? zTo2 : P("to"); |
| 2222 | 2103 | }else{ |
| 2223 | - if( path_common_ancestor(me_rid, you_rid) ){ | |
| 2104 | + commonAncs = path_common_ancestor(me_rid, you_rid); | |
| 2105 | + if( commonAncs!=0 ){ | |
| 2224 | 2106 | p = path_first(); |
| 2225 | 2107 | } |
| 2226 | - zFrom = P("me"); | |
| 2227 | - zTo = P("you"); | |
| 2108 | + if( commonAncs==you_rid ){ | |
| 2109 | + zFrom = P("you"); | |
| 2110 | + zTo = P("me"); | |
| 2111 | + earlierRid = you_rid; | |
| 2112 | + laterRid = me_rid; | |
| 2113 | + }else{ | |
| 2114 | + zFrom = P("me"); | |
| 2115 | + zTo = P("you"); | |
| 2116 | + earlierRid = me_rid; | |
| 2117 | + laterRid = you_rid; | |
| 2118 | + } | |
| 2228 | 2119 | } |
| 2229 | 2120 | blob_init(&ins, 0, 0); |
| 2230 | 2121 | db_multi_exec( |
| 2231 | - "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);" | |
| 2122 | + "CREATE TEMP TABLE IF NOT EXISTS pathnode(x INTEGER PRIMARY KEY);" | |
| 2232 | 2123 | ); |
| 2233 | 2124 | if( p ){ |
| 2125 | + int cnt = 4; | |
| 2234 | 2126 | blob_init(&ins, 0, 0); |
| 2235 | 2127 | blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid); |
| 2236 | 2128 | p = p->u.pTo; |
| 2237 | 2129 | while( p ){ |
| 2238 | - blob_append_sql(&ins, ",(%d)", p->rid); | |
| 2130 | + if( cnt==8 ){ | |
| 2131 | + blob_append_sql(&ins, ",\n (%d)", p->rid); | |
| 2132 | + cnt = 0; | |
| 2133 | + }else{ | |
| 2134 | + cnt++; | |
| 2135 | + blob_append_sql(&ins, ",(%d)", p->rid); | |
| 2136 | + } | |
| 2239 | 2137 | p = p->u.pTo; |
| 2240 | 2138 | } |
| 2241 | 2139 | } |
| 2242 | 2140 | path_reset(); |
| 2243 | 2141 | db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/); |
| 2244 | 2142 | blob_reset(&ins); |
| 2245 | - if( related || P("mionly") ){ | |
| 2143 | + if( related ){ | |
| 2246 | 2144 | db_multi_exec( |
| 2247 | - "CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);" | |
| 2145 | + "CREATE TEMP TABLE IF NOT EXISTS related(x INTEGER PRIMARY KEY);" | |
| 2248 | 2146 | "INSERT OR IGNORE INTO related(x)" |
| 2249 | - " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;" | |
| 2147 | + " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;" | |
| 2250 | 2148 | ); |
| 2251 | - if( P("mionly")==0 ){ | |
| 2149 | + if( related==1 ){ | |
| 2252 | 2150 | db_multi_exec( |
| 2253 | 2151 | "INSERT OR IGNORE INTO related(x)" |
| 2254 | - " SELECT cid FROM plink WHERE pid IN pathnode;" | |
| 2152 | + " SELECT cid FROM plink WHERE pid IN pathnode;" | |
| 2255 | 2153 | ); |
| 2256 | 2154 | } |
| 2257 | 2155 | if( showCherrypicks ){ |
| 2258 | 2156 | db_multi_exec( |
| 2259 | 2157 | "INSERT OR IGNORE INTO related(x)" |
| 2260 | - " SELECT parentid FROM cherrypick WHERE childid IN pathnode;" | |
| 2158 | + " SELECT parentid FROM cherrypick WHERE childid IN pathnode;" | |
| 2261 | 2159 | ); |
| 2262 | - if( P("mionly")==0 ){ | |
| 2160 | + if( related==1 ){ | |
| 2263 | 2161 | db_multi_exec( |
| 2264 | 2162 | "INSERT OR IGNORE INTO related(x)" |
| 2265 | - " SELECT childid FROM cherrypick WHERE parentid IN pathnode;" | |
| 2163 | + " SELECT childid FROM cherrypick WHERE parentid IN pathnode;" | |
| 2266 | 2164 | ); |
| 2267 | 2165 | } |
| 2166 | + } | |
| 2167 | + if( earlierRid && laterRid && commonAncs==earlierRid ){ | |
| 2168 | + /* On a query with me=XXX, you=YYY, and rel, omit all nodes that | |
| 2169 | + ** are not ancestors of either XXX or YYY, as those nodes tend to | |
| 2170 | + ** be extraneous */ | |
| 2171 | + db_multi_exec( | |
| 2172 | + "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" | |
| 2173 | + ); | |
| 2174 | + compute_ancestors(laterRid, 0, 0, earlierRid); | |
| 2175 | + db_multi_exec( | |
| 2176 | + "DELETE FROM related WHERE x NOT IN ok;" | |
| 2177 | + ); | |
| 2268 | 2178 | } |
| 2269 | 2179 | db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related"); |
| 2270 | 2180 | } |
| 2181 | + add_extra_rids("pathnode",P("x")); | |
| 2271 | 2182 | blob_append_sql(&sql, " AND event.objid IN pathnode"); |
| 2272 | 2183 | if( zChng && zChng[0] ){ |
| 2273 | 2184 | db_multi_exec( |
| 2274 | - "DELETE FROM pathnode " | |
| 2275 | - " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename" | |
| 2276 | - " WHERE mlink.mid=x" | |
| 2277 | - " AND mlink.fnid=filename.fnid AND %s)", | |
| 2185 | + "DELETE FROM pathnode\n" | |
| 2186 | + " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename\n" | |
| 2187 | + " WHERE mlink.mid=x\n" | |
| 2188 | + " AND mlink.fnid=filename.fnid\n" | |
| 2189 | + " AND %s)", | |
| 2278 | 2190 | glob_expr("filename.name", zChng) |
| 2279 | 2191 | ); |
| 2280 | 2192 | } |
| 2281 | 2193 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2282 | 2194 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -2330,18 +2242,18 @@ | ||
| 2330 | 2242 | if( !haveParameterN ) nEntry = 10; |
| 2331 | 2243 | } |
| 2332 | 2244 | db_multi_exec( |
| 2333 | 2245 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" |
| 2334 | 2246 | ); |
| 2247 | + add_extra_rids("ok", P("x")); | |
| 2335 | 2248 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", |
| 2336 | 2249 | p_rid ? p_rid : d_rid); |
| 2337 | 2250 | zCiName = zDPName; |
| 2338 | 2251 | if( zCiName==0 ) zCiName = zUuid; |
| 2339 | 2252 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 2340 | 2253 | nd = 0; |
| 2341 | 2254 | if( d_rid ){ |
| 2342 | - Stmt s; | |
| 2343 | 2255 | double rStopTime = 9e99; |
| 2344 | 2256 | zFwdTo = P("ft"); |
| 2345 | 2257 | if( zFwdTo ){ |
| 2346 | 2258 | double rStartDate = db_double(0.0, |
| 2347 | 2259 | "SELECT mtime FROM event WHERE objid=%d", d_rid); |
| @@ -2353,27 +2265,25 @@ | ||
| 2353 | 2265 | if( !haveParameterN ) nEntry = 0; |
| 2354 | 2266 | rStopTime = db_double(9e99, |
| 2355 | 2267 | "SELECT mtime FROM event WHERE objid=%d", ridFwdTo); |
| 2356 | 2268 | } |
| 2357 | 2269 | } |
| 2358 | - db_prepare(&s, | |
| 2359 | - "WITH RECURSIVE" | |
| 2360 | - " dx(rid,mtime) AS (" | |
| 2361 | - " SELECT %d, 0" | |
| 2362 | - " UNION" | |
| 2363 | - " SELECT plink.cid, plink.mtime FROM dx, plink" | |
| 2364 | - " WHERE plink.pid=dx.rid" | |
| 2365 | - " AND (:stop>=8e99 OR plink.mtime<=:stop)" | |
| 2366 | - " ORDER BY 2" | |
| 2367 | - " )" | |
| 2270 | + if( rStopTime<9e99 ){ | |
| 2271 | + rStopTime += 5.8e-6; /* Round up by 1/2 second */ | |
| 2272 | + } | |
| 2273 | + db_multi_exec( | |
| 2274 | + "WITH RECURSIVE dx(rid,mtime) AS (\n" | |
| 2275 | + " SELECT %d, 0\n" | |
| 2276 | + " UNION\n" | |
| 2277 | + " SELECT plink.cid, plink.mtime FROM dx, plink\n" | |
| 2278 | + " WHERE plink.pid=dx.rid\n" | |
| 2279 | + " AND plink.mtime<=%.*g\n" | |
| 2280 | + " ORDER BY 2\n" | |
| 2281 | + ")\n" | |
| 2368 | 2282 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 2369 | - d_rid, nEntry<=0 ? -1 : nEntry+1 | |
| 2283 | + d_rid, rStopTime<8e99 ? 17 : 2, rStopTime, nEntry<=0 ? -1 : nEntry+1 | |
| 2370 | 2284 | ); |
| 2371 | - db_bind_double(&s, ":stop", rStopTime); | |
| 2372 | - db_step(&s); | |
| 2373 | - db_finalize(&s); | |
| 2374 | - /* compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1); */ | |
| 2375 | 2285 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2376 | 2286 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 2377 | 2287 | if( nd>0 || p_rid==0 ){ |
| 2378 | 2288 | blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 2379 | 2289 | } |
| @@ -2482,67 +2392,76 @@ | ||
| 2482 | 2392 | if( zChng && *zChng ){ |
| 2483 | 2393 | addFileGlobExclusion(zChng, &cond); |
| 2484 | 2394 | tmFlags |= TIMELINE_XMERGE; |
| 2485 | 2395 | } |
| 2486 | 2396 | if( zUses ){ |
| 2487 | - blob_append_sql(&cond, " AND event.objid IN usesfile "); | |
| 2397 | + blob_append_sql(&cond, " AND event.objid IN usesfile\n"); | |
| 2488 | 2398 | } |
| 2489 | 2399 | if( renameOnly ){ |
| 2490 | - blob_append_sql(&cond, " AND event.objid IN rnfile "); | |
| 2400 | + blob_append_sql(&cond, " AND event.objid IN rnfile\n"); | |
| 2491 | 2401 | } |
| 2492 | 2402 | if( forkOnly ){ |
| 2493 | - blob_append_sql(&cond, " AND event.objid IN rnfork "); | |
| 2403 | + blob_append_sql(&cond, " AND event.objid IN rnfork\n"); | |
| 2494 | 2404 | } |
| 2495 | 2405 | if( cpOnly && showCherrypicks ){ |
| 2496 | 2406 | db_multi_exec( |
| 2497 | 2407 | "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);" |
| 2498 | 2408 | "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;" |
| 2499 | 2409 | "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;" |
| 2500 | 2410 | ); |
| 2501 | - blob_append_sql(&cond, " AND event.objid IN cpnodes "); | |
| 2411 | + blob_append_sql(&cond, " AND event.objid IN cpnodes\n"); | |
| 2502 | 2412 | } |
| 2503 | 2413 | if( bisectLocal || zBisect!=0 ){ |
| 2504 | - blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog) "); | |
| 2414 | + blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog)\n"); | |
| 2505 | 2415 | } |
| 2506 | 2416 | if( zYearMonth ){ |
| 2507 | 2417 | char *zNext; |
| 2508 | - zYearMonth = timeline_expand_datetime(zYearMonth); | |
| 2509 | - if( strlen(zYearMonth)>7 ){ | |
| 2510 | - zYearMonth = mprintf("%.7s", zYearMonth); | |
| 2511 | - } | |
| 2418 | + int bZulu = 0; | |
| 2419 | + const char *zTZMod; | |
| 2420 | + zYearMonth = timeline_expand_datetime(zYearMonth, &bZulu); | |
| 2421 | + zYearMonth = mprintf("%.7s", zYearMonth); | |
| 2512 | 2422 | if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){ |
| 2513 | 2423 | zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');"); |
| 2514 | 2424 | } |
| 2515 | - zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','+1 month');", | |
| 2516 | - zYearMonth); | |
| 2517 | - if( db_int(0, | |
| 2518 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2519 | - " WHERE blob.rid=event.objid AND mtime>=julianday('%q-01')%s)", | |
| 2520 | - zNext, blob_sql_text(&cond)) | |
| 2521 | - ){ | |
| 2522 | - zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); | |
| 2523 | - zNewerButtonLabel = "Following month"; | |
| 2524 | - } | |
| 2525 | - fossil_free(zNext); | |
| 2526 | - zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','-1 month');", | |
| 2527 | - zYearMonth); | |
| 2528 | - if( db_int(0, | |
| 2529 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2530 | - " WHERE blob.rid=event.objid AND mtime<julianday('%q-01')%s)", | |
| 2531 | - zYearMonth, blob_sql_text(&cond)) | |
| 2532 | - ){ | |
| 2533 | - zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); | |
| 2534 | - zOlderButtonLabel = "Previous month"; | |
| 2535 | - } | |
| 2536 | - fossil_free(zNext); | |
| 2537 | - blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ", | |
| 2538 | - zYearMonth); | |
| 2539 | - nEntry = -1; | |
| 2425 | + zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; | |
| 2426 | + if( db_int(0, | |
| 2427 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2428 | + " WHERE blob.rid=event.objid" | |
| 2429 | + " AND mtime>=julianday('%q-01',%Q)%s)", | |
| 2430 | + zYearMonth, zTZMod, blob_sql_text(&cond)) | |
| 2431 | + ){ | |
| 2432 | + zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','+1 month');", | |
| 2433 | + &"Z"[!bZulu], zYearMonth); | |
| 2434 | + zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); | |
| 2435 | + zNewerButtonLabel = "Following month"; | |
| 2436 | + fossil_free(zNext); | |
| 2437 | + } | |
| 2438 | + if( db_int(0, | |
| 2439 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2440 | + " WHERE blob.rid=event.objid" | |
| 2441 | + " AND mtime<julianday('%q-01',%Q)%s)", | |
| 2442 | + zYearMonth, zTZMod, blob_sql_text(&cond)) | |
| 2443 | + ){ | |
| 2444 | + zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','-1 month');", | |
| 2445 | + &"Z"[!bZulu], zYearMonth); | |
| 2446 | + zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); | |
| 2447 | + zOlderButtonLabel = "Previous month"; | |
| 2448 | + fossil_free(zNext); | |
| 2449 | + } | |
| 2450 | + blob_append_sql(&cond, | |
| 2451 | + " AND event.mtime>=julianday('%q-01',%Q)" | |
| 2452 | + " AND event.mtime<julianday('%q-01',%Q,'+1 month')\n", | |
| 2453 | + zYearMonth, zTZMod, zYearMonth, zTZMod); | |
| 2454 | + nEntry = -1; | |
| 2455 | + /* Adjust the zYearMonth for the title */ | |
| 2456 | + zYearMonth = mprintf("%z-01%s", zYearMonth, &"Z"[!bZulu]); | |
| 2540 | 2457 | } |
| 2541 | 2458 | else if( zYearWeek ){ |
| 2542 | 2459 | char *z, *zNext; |
| 2543 | - zYearWeek = timeline_expand_datetime(zYearWeek); | |
| 2460 | + int bZulu = 0; | |
| 2461 | + const char *zTZMod; | |
| 2462 | + zYearWeek = timeline_expand_datetime(zYearWeek, &bZulu); | |
| 2544 | 2463 | z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek); |
| 2545 | 2464 | if( z && z[0] ){ |
| 2546 | 2465 | zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')", |
| 2547 | 2466 | zYearWeek); |
| 2548 | 2467 | zYearWeek = z; |
| @@ -2559,64 +2478,152 @@ | ||
| 2559 | 2478 | "SELECT date('now','-6 days','weekday 1');"); |
| 2560 | 2479 | zYearWeek = db_text(0, |
| 2561 | 2480 | "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')"); |
| 2562 | 2481 | } |
| 2563 | 2482 | } |
| 2564 | - zNext = db_text(0, "SELECT date(%Q,'+7 day');", zYearWeekStart); | |
| 2565 | - if( db_int(0, | |
| 2566 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2567 | - " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)", | |
| 2568 | - zNext, blob_sql_text(&cond)) | |
| 2569 | - ){ | |
| 2570 | - zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); | |
| 2571 | - zNewerButtonLabel = "Following week"; | |
| 2572 | - } | |
| 2573 | - fossil_free(zNext); | |
| 2574 | - zNext = db_text(0, "SELECT date(%Q,'-7 days');", zYearWeekStart); | |
| 2575 | - if( db_int(0, | |
| 2576 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2577 | - " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)", | |
| 2578 | - zYearWeekStart, blob_sql_text(&cond)) | |
| 2579 | - ){ | |
| 2580 | - zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); | |
| 2581 | - zOlderButtonLabel = "Previous week"; | |
| 2582 | - } | |
| 2583 | - fossil_free(zNext); | |
| 2584 | - blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%W',event.mtime) ", | |
| 2585 | - zYearWeek); | |
| 2586 | - nEntry = -1; | |
| 2483 | + zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; | |
| 2484 | + if( db_int(0, | |
| 2485 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2486 | + " WHERE blob.rid=event.objid" | |
| 2487 | + " AND mtime>=julianday(%Q,%Q)%s)", | |
| 2488 | + zYearWeekStart, zTZMod, blob_sql_text(&cond)) | |
| 2489 | + ){ | |
| 2490 | + zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'+7 day');", | |
| 2491 | + &"Z"[!bZulu], zYearWeekStart); | |
| 2492 | + zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); | |
| 2493 | + zNewerButtonLabel = "Following week"; | |
| 2494 | + fossil_free(zNext); | |
| 2495 | + } | |
| 2496 | + if( db_int(0, | |
| 2497 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2498 | + " WHERE blob.rid=event.objid" | |
| 2499 | + " AND mtime<julianday(%Q,%Q)%s)", | |
| 2500 | + zYearWeekStart, zTZMod, blob_sql_text(&cond)) | |
| 2501 | + ){ | |
| 2502 | + zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'-7 days');", | |
| 2503 | + &"Z"[!bZulu], zYearWeekStart); | |
| 2504 | + zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); | |
| 2505 | + zOlderButtonLabel = "Previous week"; | |
| 2506 | + fossil_free(zNext); | |
| 2507 | + } | |
| 2508 | + blob_append_sql(&cond, | |
| 2509 | + " AND event.mtime>=julianday(%Q,%Q)" | |
| 2510 | + " AND event.mtime<julianday(%Q,%Q,'+7 days')\n", | |
| 2511 | + zYearWeekStart, zTZMod, zYearWeekStart, zTZMod); | |
| 2512 | + nEntry = -1; | |
| 2513 | + if( fossil_ui_localtime() && bZulu ){ | |
| 2514 | + zYearWeekStart = mprintf("%zZ", zYearWeekStart); | |
| 2515 | + } | |
| 2516 | + } | |
| 2517 | + else if( zDay && timeline_is_datespan(zDay) ){ | |
| 2518 | + char *zNext; | |
| 2519 | + char *zStart, *zEnd; | |
| 2520 | + int nDay; | |
| 2521 | + int bZulu = 0; | |
| 2522 | + const char *zTZMod; | |
| 2523 | + zEnd = db_text(0, "SELECT date(%Q)", | |
| 2524 | + timeline_expand_datetime(zDay+9, &bZulu)); | |
| 2525 | + zStart = db_text(0, "SELECT date('%.4q-%.2q-%.2q')", | |
| 2526 | + zDay, zDay+4, zDay+6); | |
| 2527 | + nDay = db_int(0, "SELECT julianday(%Q)-julianday(%Q)", zEnd, zStart); | |
| 2528 | + if( nDay==0 ){ | |
| 2529 | + zDay = &zDay[9]; | |
| 2530 | + goto single_ymd; | |
| 2531 | + } | |
| 2532 | + if( nDay<0 ){ | |
| 2533 | + char *zTemp = zEnd; | |
| 2534 | + zEnd = zStart; | |
| 2535 | + zStart = zTemp; | |
| 2536 | + nDay = 1 - nDay; | |
| 2537 | + }else{ | |
| 2538 | + nDay += 1; | |
| 2539 | + } | |
| 2540 | + zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; | |
| 2541 | + if( nDay>0 && db_int(0, | |
| 2542 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2543 | + " WHERE blob.rid=event.objid" | |
| 2544 | + " AND mtime>=julianday(%Q,'1 day',%Q)%s)", | |
| 2545 | + zEnd, zTZMod, blob_sql_text(&cond)) | |
| 2546 | + ){ | |
| 2547 | + zNext = db_text(0, | |
| 2548 | + "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||" | |
| 2549 | + "strftime('%%Y%%m%%d%q',%Q,'%d day');", | |
| 2550 | + zStart, nDay, &"Z"[!bZulu], zEnd, nDay); | |
| 2551 | + zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2552 | + zNewerButtonLabel = mprintf("Following %d days", nDay); | |
| 2553 | + fossil_free(zNext); | |
| 2554 | + } | |
| 2555 | + if( nDay>1 && db_int(0, | |
| 2556 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2557 | + " WHERE blob.rid=event.objid" | |
| 2558 | + " AND mtime<julianday(%Q,'-1 day',%Q)%s)", | |
| 2559 | + zStart, zTZMod, blob_sql_text(&cond)) | |
| 2560 | + ){ | |
| 2561 | + zNext = db_text(0, | |
| 2562 | + "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||" | |
| 2563 | + "strftime('%%Y%%m%%d%q',%Q,'%d day');", | |
| 2564 | + zStart, -nDay, &"Z"[!bZulu], zEnd, -nDay); | |
| 2565 | + zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2566 | + zOlderButtonLabel = mprintf("Previous %d days", nDay); | |
| 2567 | + fossil_free(zNext); | |
| 2568 | + } | |
| 2569 | + blob_append_sql(&cond, | |
| 2570 | + " AND event.mtime>=julianday(%Q,%Q)" | |
| 2571 | + " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", | |
| 2572 | + zStart, zTZMod, zEnd, zTZMod); | |
| 2573 | + nEntry = -1; | |
| 2574 | + | |
| 2575 | + if( fossil_ui_localtime() && bZulu ){ | |
| 2576 | + zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd); | |
| 2577 | + }else{ | |
| 2578 | + zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd); | |
| 2579 | + } | |
| 2587 | 2580 | } |
| 2588 | 2581 | else if( zDay ){ |
| 2589 | 2582 | char *zNext; |
| 2590 | - zDay = timeline_expand_datetime(zDay); | |
| 2583 | + int bZulu = 0; | |
| 2584 | + const char *zTZMod; | |
| 2585 | + single_ymd: | |
| 2586 | + bZulu = 0; | |
| 2587 | + zDay = timeline_expand_datetime(zDay, &bZulu); | |
| 2591 | 2588 | zDay = db_text(0, "SELECT date(%Q)", zDay); |
| 2592 | 2589 | if( zDay==0 || zDay[0]==0 ){ |
| 2593 | 2590 | zDay = db_text(0, "SELECT date('now')"); |
| 2594 | 2591 | } |
| 2595 | - zNext = db_text(0, "SELECT date(%Q,'+1 day');", zDay); | |
| 2596 | - if( db_int(0, | |
| 2597 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2598 | - " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)", | |
| 2599 | - zNext, blob_sql_text(&cond)) | |
| 2600 | - ){ | |
| 2601 | - zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2602 | - zNewerButtonLabel = "Following day"; | |
| 2603 | - } | |
| 2604 | - fossil_free(zNext); | |
| 2605 | - zNext = db_text(0, "SELECT date(%Q,'-1 day');", zDay); | |
| 2606 | - if( db_int(0, | |
| 2607 | - "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2608 | - " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)", | |
| 2609 | - zDay, blob_sql_text(&cond)) | |
| 2610 | - ){ | |
| 2611 | - zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2612 | - zOlderButtonLabel = "Previous day"; | |
| 2613 | - } | |
| 2614 | - fossil_free(zNext); | |
| 2615 | - blob_append_sql(&cond, " AND %Q=date(event.mtime) ", | |
| 2616 | - zDay); | |
| 2617 | - nEntry = -1; | |
| 2592 | + zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; | |
| 2593 | + if( db_int(0, | |
| 2594 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2595 | + " WHERE blob.rid=event.objid" | |
| 2596 | + " AND mtime>=julianday(%Q,'+1 day',%Q)%s)", | |
| 2597 | + zDay, zTZMod, blob_sql_text(&cond)) | |
| 2598 | + ){ | |
| 2599 | + zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'+1 day');", | |
| 2600 | + &"Z"[!bZulu], zDay); | |
| 2601 | + zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2602 | + zNewerButtonLabel = "Following day"; | |
| 2603 | + fossil_free(zNext); | |
| 2604 | + } | |
| 2605 | + if( db_int(0, | |
| 2606 | + "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" | |
| 2607 | + " WHERE blob.rid=event.objid" | |
| 2608 | + " AND mtime<julianday(%Q,'-1 day',%Q)%s)", | |
| 2609 | + zDay, zTZMod, blob_sql_text(&cond)) | |
| 2610 | + ){ | |
| 2611 | + zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'-1 day');", | |
| 2612 | + &"Z"[!bZulu], zDay); | |
| 2613 | + zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); | |
| 2614 | + zOlderButtonLabel = "Previous day"; | |
| 2615 | + fossil_free(zNext); | |
| 2616 | + } | |
| 2617 | + blob_append_sql(&cond, | |
| 2618 | + " AND event.mtime>=julianday(%Q,%Q)" | |
| 2619 | + " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", | |
| 2620 | + zDay, zTZMod, zDay, zTZMod); | |
| 2621 | + nEntry = -1; | |
| 2622 | + if( fossil_ui_localtime() && bZulu ){ | |
| 2623 | + zDay = mprintf("%zZ", zDay); /* Add Z suffix to day for the title */ | |
| 2624 | + } | |
| 2618 | 2625 | } |
| 2619 | 2626 | else if( zNDays ){ |
| 2620 | 2627 | nDays = atoi(zNDays); |
| 2621 | 2628 | if( nDays<1 ) nDays = 1; |
| 2622 | 2629 | blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ", |
| @@ -2662,39 +2669,24 @@ | ||
| 2662 | 2669 | nEntry = -1; |
| 2663 | 2670 | } |
| 2664 | 2671 | if( zTagSql ){ |
| 2665 | 2672 | db_multi_exec( |
| 2666 | 2673 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2667 | - "INSERT OR IGNORE INTO selected_nodes" | |
| 2668 | - " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag" | |
| 2669 | - " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/ | |
| 2674 | + "INSERT OR IGNORE INTO selected_nodes\n" | |
| 2675 | + " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag\n" | |
| 2676 | + " WHERE tagtype>0\n" | |
| 2677 | + " AND %s", zTagSql/*safe-for-%s*/ | |
| 2670 | 2678 | ); |
| 2671 | 2679 | if( zMark ){ |
| 2672 | 2680 | /* If the t=release option is used with m=UUID, then also |
| 2673 | 2681 | ** include the UUID check-in in the display list */ |
| 2674 | 2682 | int ridMark = name_to_rid(zMark); |
| 2675 | 2683 | db_multi_exec( |
| 2676 | 2684 | "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark); |
| 2677 | 2685 | } |
| 2678 | - if( P("x")!=0 ){ | |
| 2679 | - char *zX = fossil_strdup(P("x")); | |
| 2680 | - int ii; | |
| 2681 | - int ridX; | |
| 2682 | - while( zX[0] ){ | |
| 2683 | - char c; | |
| 2684 | - if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; } | |
| 2685 | - for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){} | |
| 2686 | - c = zX[ii]; | |
| 2687 | - zX[ii] = 0; | |
| 2688 | - ridX = name_to_rid(zX); | |
| 2689 | - db_multi_exec( | |
| 2690 | - "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridX); | |
| 2691 | - zX[ii] = c; | |
| 2692 | - zX += ii; | |
| 2693 | - } | |
| 2694 | - } | |
| 2695 | - if( !related ){ | |
| 2686 | + add_extra_rids("selected_nodes",P("x")); | |
| 2687 | + if( related==0 ){ | |
| 2696 | 2688 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2697 | 2689 | }else{ |
| 2698 | 2690 | db_multi_exec( |
| 2699 | 2691 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| 2700 | 2692 | "INSERT INTO related_nodes SELECT rid FROM selected_nodes;" |
| @@ -2705,40 +2697,42 @@ | ||
| 2705 | 2697 | ** branch to be included in the report. These related check-ins are |
| 2706 | 2698 | ** useful in helping to visualize what has happened on a quiescent |
| 2707 | 2699 | ** branch that is infrequently merged with a much more activate branch. |
| 2708 | 2700 | */ |
| 2709 | 2701 | db_multi_exec( |
| 2710 | - "INSERT OR IGNORE INTO related_nodes" | |
| 2711 | - " SELECT pid FROM selected_nodes CROSS JOIN plink" | |
| 2712 | - " WHERE selected_nodes.rid=plink.cid;" | |
| 2702 | + "INSERT OR IGNORE INTO related_nodes\n" | |
| 2703 | + " SELECT pid FROM selected_nodes CROSS JOIN plink\n" | |
| 2704 | + " WHERE selected_nodes.rid=plink.cid;" | |
| 2713 | 2705 | ); |
| 2714 | - if( P("mionly")==0 ){ | |
| 2706 | + if( related==1 ){ | |
| 2715 | 2707 | db_multi_exec( |
| 2716 | - "INSERT OR IGNORE INTO related_nodes" | |
| 2717 | - " SELECT cid FROM selected_nodes CROSS JOIN plink" | |
| 2718 | - " WHERE selected_nodes.rid=plink.pid;" | |
| 2708 | + "INSERT OR IGNORE INTO related_nodes\n" | |
| 2709 | + " SELECT cid FROM selected_nodes CROSS JOIN plink\n" | |
| 2710 | + " WHERE selected_nodes.rid=plink.pid;" | |
| 2719 | 2711 | ); |
| 2720 | 2712 | if( showCherrypicks ){ |
| 2721 | 2713 | db_multi_exec( |
| 2722 | - "INSERT OR IGNORE INTO related_nodes" | |
| 2723 | - " SELECT childid FROM selected_nodes CROSS JOIN cherrypick" | |
| 2724 | - " WHERE selected_nodes.rid=cherrypick.parentid;" | |
| 2714 | + "INSERT OR IGNORE INTO related_nodes\n" | |
| 2715 | + " SELECT childid FROM selected_nodes CROSS JOIN cherrypick\n" | |
| 2716 | + " WHERE selected_nodes.rid=cherrypick.parentid;" | |
| 2725 | 2717 | ); |
| 2726 | 2718 | } |
| 2727 | 2719 | } |
| 2728 | 2720 | if( showCherrypicks ){ |
| 2729 | 2721 | db_multi_exec( |
| 2730 | - "INSERT OR IGNORE INTO related_nodes" | |
| 2731 | - " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick" | |
| 2732 | - " WHERE selected_nodes.rid=cherrypick.childid;" | |
| 2722 | + "INSERT OR IGNORE INTO related_nodes\n" | |
| 2723 | + " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick\n" | |
| 2724 | + " WHERE selected_nodes.rid=cherrypick.childid;" | |
| 2733 | 2725 | ); |
| 2734 | 2726 | } |
| 2735 | 2727 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 2736 | 2728 | db_multi_exec( |
| 2737 | - "DELETE FROM related_nodes WHERE rid IN " | |
| 2738 | - " (SELECT related_nodes.rid FROM related_nodes, tagxref" | |
| 2739 | - " WHERE tagid=%d AND tagtype>0 AND tagxref.rid=related_nodes.rid)", | |
| 2729 | + "DELETE FROM related_nodes\n" | |
| 2730 | + " WHERE rid IN (SELECT related_nodes.rid\n" | |
| 2731 | + " FROM related_nodes, tagxref\n" | |
| 2732 | + " WHERE tagid=%d AND tagtype>0\n" | |
| 2733 | + " AND tagxref.rid=related_nodes.rid)", | |
| 2740 | 2734 | TAG_HIDDEN |
| 2741 | 2735 | ); |
| 2742 | 2736 | } |
| 2743 | 2737 | } |
| 2744 | 2738 | } |
| @@ -2828,45 +2822,42 @@ | ||
| 2828 | 2822 | rCirca = symbolic_name_to_mtime(zCirca, &zCirca); |
| 2829 | 2823 | blob_append_sql(&sql, "%s", blob_sql_text(&cond)); |
| 2830 | 2824 | if( rAfter>0.0 ){ |
| 2831 | 2825 | if( rBefore>0.0 ){ |
| 2832 | 2826 | blob_append_sql(&sql, |
| 2833 | - " AND event.mtime>=%.17g AND event.mtime<=%.17g" | |
| 2827 | + " AND event.mtime>=%.17g AND event.mtime<=%.17g\n" | |
| 2834 | 2828 | " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND); |
| 2835 | 2829 | nEntry = -1; |
| 2836 | 2830 | }else{ |
| 2837 | 2831 | blob_append_sql(&sql, |
| 2838 | - " AND event.mtime>=%.17g ORDER BY event.mtime ASC", | |
| 2832 | + " AND event.mtime>=%.17g\n ORDER BY event.mtime ASC", | |
| 2839 | 2833 | rAfter-ONE_SECOND); |
| 2840 | 2834 | } |
| 2841 | 2835 | zCirca = 0; |
| 2842 | 2836 | url_add_parameter(&url, "c", 0); |
| 2843 | 2837 | }else if( rBefore>0.0 ){ |
| 2844 | 2838 | blob_append_sql(&sql, |
| 2845 | - " AND event.mtime<=%.17g ORDER BY event.mtime DESC", | |
| 2839 | + " AND event.mtime<=%.17g\n ORDER BY event.mtime DESC", | |
| 2846 | 2840 | rBefore+ONE_SECOND); |
| 2847 | 2841 | zCirca = 0; |
| 2848 | 2842 | url_add_parameter(&url, "c", 0); |
| 2849 | 2843 | }else if( rCirca>0.0 ){ |
| 2850 | 2844 | Blob sql2; |
| 2851 | 2845 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 2852 | 2846 | blob_append_sql(&sql2, |
| 2853 | - " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca); | |
| 2847 | + " AND event.mtime>=%f\n ORDER BY event.mtime ASC", rCirca); | |
| 2854 | 2848 | if( nEntry>0 ){ |
| 2855 | 2849 | blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2); |
| 2856 | 2850 | } |
| 2857 | - if( PB("showsql") ){ | |
| 2858 | - @ <pre>%h(blob_sql_text(&sql2))</pre> | |
| 2859 | - } | |
| 2860 | 2851 | db_multi_exec("%s", blob_sql_text(&sql2)); |
| 2861 | 2852 | if( nEntry>0 ){ |
| 2862 | 2853 | nEntry -= db_int(0,"select count(*) from timeline"); |
| 2863 | 2854 | if( nEntry<=0 ) nEntry = 1; |
| 2864 | 2855 | } |
| 2865 | 2856 | blob_reset(&sql2); |
| 2866 | 2857 | blob_append_sql(&sql, |
| 2867 | - " AND event.mtime<=%f ORDER BY event.mtime DESC", | |
| 2858 | + " AND event.mtime<=%f\n ORDER BY event.mtime DESC", | |
| 2868 | 2859 | rCirca |
| 2869 | 2860 | ); |
| 2870 | 2861 | if( zMark==0 ) zMark = zCirca; |
| 2871 | 2862 | }else{ |
| 2872 | 2863 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| @@ -2875,11 +2866,11 @@ | ||
| 2875 | 2866 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2876 | 2867 | |
| 2877 | 2868 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 2878 | 2869 | zPlural = n==1 ? "" : "s"; |
| 2879 | 2870 | if( zYearMonth ){ |
| 2880 | - blob_appendf(&desc, "%d %s%s for the month beginning %h-01", | |
| 2871 | + blob_appendf(&desc, "%d %s%s for the month beginning %h", | |
| 2881 | 2872 | n, zEType, zPlural, zYearMonth); |
| 2882 | 2873 | }else if( zYearWeek ){ |
| 2883 | 2874 | blob_appendf(&desc, "%d %s%s for week %h beginning on %h", |
| 2884 | 2875 | n, zEType, zPlural, zYearWeek, zYearWeekStart); |
| 2885 | 2876 | }else if( zDay ){ |
| @@ -3009,12 +3000,14 @@ | ||
| 3009 | 3000 | style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0); |
| 3010 | 3001 | } |
| 3011 | 3002 | } |
| 3012 | 3003 | blob_zero(&cond); |
| 3013 | 3004 | } |
| 3014 | - if( PB("showsql") ){ | |
| 3015 | - @ <pre>%h(blob_sql_text(&sql))</pre> | |
| 3005 | + if( showSql ){ | |
| 3006 | + db_append_dml_to_blob(0); | |
| 3007 | + @ <pre>%h(blob_str(&allSql))</pre> | |
| 3008 | + blob_reset(&allSql); | |
| 3016 | 3009 | } |
| 3017 | 3010 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 3018 | 3011 | style_submenu_element("Search", "%R/search?y=c"); |
| 3019 | 3012 | } |
| 3020 | 3013 | if( advancedMenu ){ |
| @@ -3068,18 +3061,36 @@ | ||
| 3068 | 3061 | if( zNewerButton ){ |
| 3069 | 3062 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3070 | 3063 | @ ↑</a> |
| 3071 | 3064 | } |
| 3072 | 3065 | cgi_check_for_malice(); |
| 3073 | - www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName, | |
| 3074 | - selectedRid, secondaryRid, 0); | |
| 3066 | + { | |
| 3067 | + Matcher *pLeftBranch; | |
| 3068 | + const char *zPattern = P("sl"); | |
| 3069 | + if( zPattern!=0 ){ | |
| 3070 | + MatchStyle ms; | |
| 3071 | + if( zMatchStyle!=0 ){ | |
| 3072 | + ms = matchStyle; | |
| 3073 | + }else{ | |
| 3074 | + ms = strpbrk(zPattern,"*[?")!=0 ? MS_GLOB : MS_BRLIST; | |
| 3075 | + } | |
| 3076 | + pLeftBranch = match_create(ms,zPattern); | |
| 3077 | + }else{ | |
| 3078 | + pLeftBranch = match_create(matchStyle, zBrName?zBrName:zTagName); | |
| 3079 | + } | |
| 3080 | + www_print_timeline(&q, tmFlags, zThisUser, zThisTag, pLeftBranch, | |
| 3081 | + selectedRid, secondaryRid, 0); | |
| 3082 | + match_free(pLeftBranch); | |
| 3083 | + } | |
| 3075 | 3084 | db_finalize(&q); |
| 3076 | 3085 | if( zOlderButton ){ |
| 3077 | 3086 | @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\ |
| 3078 | 3087 | @ ↓</a> |
| 3079 | 3088 | } |
| 3080 | 3089 | document_emit_js(/*handles pikchrs rendered above*/); |
| 3090 | + blob_reset(&sql); | |
| 3091 | + blob_reset(&desc); | |
| 3081 | 3092 | style_finish_page(); |
| 3082 | 3093 | } |
| 3083 | 3094 | |
| 3084 | 3095 | /* |
| 3085 | 3096 | ** Translate a timeline entry into the printable format by |
| @@ -3744,10 +3755,11 @@ | ||
| 3744 | 3755 | const char *zToday; |
| 3745 | 3756 | char *zStartOfProject; |
| 3746 | 3757 | int i; |
| 3747 | 3758 | Stmt q; |
| 3748 | 3759 | char *z; |
| 3760 | + int bZulu = 0; | |
| 3749 | 3761 | |
| 3750 | 3762 | login_check_credentials(); |
| 3751 | 3763 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){ |
| 3752 | 3764 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 3753 | 3765 | return; |
| @@ -3754,11 +3766,11 @@ | ||
| 3754 | 3766 | } |
| 3755 | 3767 | style_set_current_feature("timeline"); |
| 3756 | 3768 | style_header("Today In History"); |
| 3757 | 3769 | zToday = (char*)P("today"); |
| 3758 | 3770 | if( zToday ){ |
| 3759 | - zToday = timeline_expand_datetime(zToday); | |
| 3771 | + zToday = timeline_expand_datetime(zToday, &bZulu); | |
| 3760 | 3772 | if( !fossil_isdate(zToday) ) zToday = 0; |
| 3761 | 3773 | } |
| 3762 | 3774 | if( zToday==0 ){ |
| 3763 | 3775 | zToday = db_text(0, "SELECT date('now',toLocal())"); |
| 3764 | 3776 | } |
| 3765 | 3777 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -191,11 +191,11 @@ | |
| 191 | void www_print_timeline( |
| 192 | Stmt *pQuery, /* Query to implement the timeline */ |
| 193 | int tmFlags, /* Flags controlling display behavior */ |
| 194 | const char *zThisUser, /* Suppress links to this user */ |
| 195 | const char *zThisTag, /* Suppress links to this tag */ |
| 196 | const char *zLeftBranch, /* Strive to put this branch on the left margin */ |
| 197 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 198 | int secondRid, /* Secondary highlight (or zero) */ |
| 199 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 200 | ){ |
| 201 | int mxWikiLen; |
| @@ -813,11 +813,11 @@ | |
| 813 | } |
| 814 | if( pendingEndTr ){ |
| 815 | @ </td></tr> |
| 816 | } |
| 817 | if( pGraph ){ |
| 818 | graph_finish(pGraph, zLeftBranch, tmFlags); |
| 819 | if( pGraph->nErr ){ |
| 820 | graph_free(pGraph); |
| 821 | pGraph = 0; |
| 822 | }else{ |
| 823 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| @@ -1290,12 +1290,13 @@ | |
| 1290 | const char *zChng, /* The filename GLOB list */ |
| 1291 | Blob *pSql /* The SELECT statement under construction */ |
| 1292 | ){ |
| 1293 | if( zChng==0 || zChng[0]==0 ) return; |
| 1294 | blob_append_sql(pSql," AND event.objid IN (" |
| 1295 | "SELECT mlink.mid FROM mlink, filename" |
| 1296 | " WHERE mlink.fnid=filename.fnid AND %s)", |
| 1297 | glob_expr("filename.name", mprintf("\"%s\"", zChng))); |
| 1298 | } |
| 1299 | static void addFileGlobDescription( |
| 1300 | const char *zChng, /* The filename GLOB list */ |
| 1301 | Blob *pDescription /* Result description */ |
| @@ -1303,274 +1304,86 @@ | |
| 1303 | if( zChng==0 || zChng[0]==0 ) return; |
| 1304 | blob_appendf(pDescription, " that include changes to files matching '%h'", |
| 1305 | zChng); |
| 1306 | } |
| 1307 | |
| 1308 | /* |
| 1309 | ** Tag match expression type code. |
| 1310 | */ |
| 1311 | typedef enum { |
| 1312 | MS_EXACT, /* Matches a single tag by exact string comparison. */ |
| 1313 | MS_GLOB, /* Matches tags against a list of GLOB patterns. */ |
| 1314 | MS_LIKE, /* Matches tags against a list of LIKE patterns. */ |
| 1315 | MS_REGEXP, /* Matches tags against a list of regular expressions. */ |
| 1316 | MS_BRLIST, /* Same as REGEXP, except the regular expression is a list |
| 1317 | ** of branch names */ |
| 1318 | } MatchStyle; |
| 1319 | |
| 1320 | /* |
| 1321 | ** Quote a tag string by surrounding it with double quotes and preceding |
| 1322 | ** internal double quotes and backslashes with backslashes. |
| 1323 | */ |
| 1324 | static const char *tagQuote( |
| 1325 | int len, /* Maximum length of zTag, or negative for unlimited */ |
| 1326 | const char *zTag /* Tag string */ |
| 1327 | ){ |
| 1328 | Blob blob = BLOB_INITIALIZER; |
| 1329 | int i, j; |
| 1330 | blob_zero(&blob); |
| 1331 | blob_append(&blob, "\"", 1); |
| 1332 | for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){ |
| 1333 | if( zTag[j]=='\"' || zTag[j]=='\\' ){ |
| 1334 | if( j>i ){ |
| 1335 | blob_append(&blob, zTag+i, j-i); |
| 1336 | } |
| 1337 | blob_append(&blob, "\\", 1); |
| 1338 | i = j; |
| 1339 | } |
| 1340 | } |
| 1341 | if( j>i ){ |
| 1342 | blob_append(&blob, zTag+i, j-i); |
| 1343 | } |
| 1344 | blob_append(&blob, "\"", 1); |
| 1345 | return blob_str(&blob); |
| 1346 | } |
| 1347 | |
| 1348 | /* |
| 1349 | ** Construct the tag match SQL expression. |
| 1350 | ** |
| 1351 | ** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB, |
| 1352 | ** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles. |
| 1353 | ** |
| 1354 | ** For MS_EXACT, the returned expression |
| 1355 | ** checks for integer match against the tag ID which is looked up directly by |
| 1356 | ** this function. For the other modes, the returned SQL expression performs |
| 1357 | ** string comparisons against the tag names, so it is necessary to join against |
| 1358 | ** the tag table to access the "tagname" column. |
| 1359 | ** |
| 1360 | ** Each pattern is adjusted to to start with "sym-" and be anchored at end. |
| 1361 | ** |
| 1362 | ** In MS_REGEXP mode, backslash can be used to protect delimiter characters. |
| 1363 | ** The backslashes are not removed from the regular expression. |
| 1364 | ** |
| 1365 | ** In addition to assembling and returning an SQL expression, this function |
| 1366 | ** makes an English-language description of the patterns being matched, suitable |
| 1367 | ** for display in the web interface. |
| 1368 | ** |
| 1369 | ** If any errors arise during processing, *zError is set to an error message. |
| 1370 | ** Otherwise it is set to NULL. |
| 1371 | */ |
| 1372 | static const char *tagMatchExpression( |
| 1373 | MatchStyle matchStyle, /* Match style code */ |
| 1374 | const char *zTag, /* Tag name, match pattern, or pattern list */ |
| 1375 | const char **zDesc, /* Output expression description string */ |
| 1376 | const char **zError /* Output error string */ |
| 1377 | ){ |
| 1378 | Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */ |
| 1379 | Blob desc = BLOB_INITIALIZER; /* English description of match patterns */ |
| 1380 | Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */ |
| 1381 | const char *zStart; /* Text at start of expression */ |
| 1382 | const char *zDelimiter; /* Text between expression terms */ |
| 1383 | const char *zEnd; /* Text at end of expression */ |
| 1384 | const char *zPrefix; /* Text before each match pattern */ |
| 1385 | const char *zSuffix; /* Text after each match pattern */ |
| 1386 | const char *zIntro; /* Text introducing pattern description */ |
| 1387 | const char *zPattern = 0; /* Previous quoted pattern */ |
| 1388 | const char *zFail = 0; /* Current failure message or NULL if okay */ |
| 1389 | const char *zOr = " or "; /* Text before final quoted pattern */ |
| 1390 | char cDel; /* Input delimiter character */ |
| 1391 | int i; /* Input match pattern length counter */ |
| 1392 | |
| 1393 | /* Optimize exact matches by looking up the ID in advance to create a simple |
| 1394 | * numeric comparison. Bypass the remainder of this function. */ |
| 1395 | if( matchStyle==MS_EXACT ){ |
| 1396 | *zDesc = tagQuote(-1, zTag); |
| 1397 | return mprintf("(tagid=%d)", db_int(-1, |
| 1398 | "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag)); |
| 1399 | } |
| 1400 | |
| 1401 | /* Decide pattern prefix and suffix strings according to match style. */ |
| 1402 | if( matchStyle==MS_GLOB ){ |
| 1403 | zStart = "("; |
| 1404 | zDelimiter = " OR "; |
| 1405 | zEnd = ")"; |
| 1406 | zPrefix = "tagname GLOB 'sym-"; |
| 1407 | zSuffix = "'"; |
| 1408 | zIntro = "glob pattern "; |
| 1409 | }else if( matchStyle==MS_LIKE ){ |
| 1410 | zStart = "("; |
| 1411 | zDelimiter = " OR "; |
| 1412 | zEnd = ")"; |
| 1413 | zPrefix = "tagname LIKE 'sym-"; |
| 1414 | zSuffix = "'"; |
| 1415 | zIntro = "SQL LIKE pattern "; |
| 1416 | }else if( matchStyle==MS_REGEXP ){ |
| 1417 | zStart = "(tagname REGEXP '^sym-("; |
| 1418 | zDelimiter = "|"; |
| 1419 | zEnd = ")$')"; |
| 1420 | zPrefix = ""; |
| 1421 | zSuffix = ""; |
| 1422 | zIntro = "regular expression "; |
| 1423 | }else/* if( matchStyle==MS_BRLIST )*/{ |
| 1424 | zStart = "tagname IN ('sym-"; |
| 1425 | zDelimiter = "','sym-"; |
| 1426 | zEnd = "')"; |
| 1427 | zPrefix = ""; |
| 1428 | zSuffix = ""; |
| 1429 | zIntro = ""; |
| 1430 | } |
| 1431 | |
| 1432 | /* Convert the list of matches into an SQL expression and text description. */ |
| 1433 | blob_zero(&expr); |
| 1434 | blob_zero(&desc); |
| 1435 | blob_zero(&err); |
| 1436 | while( 1 ){ |
| 1437 | /* Skip leading delimiters. */ |
| 1438 | for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag ); |
| 1439 | |
| 1440 | /* Next non-delimiter character determines quoting. */ |
| 1441 | if( !*zTag ){ |
| 1442 | /* Terminate loop at end of string. */ |
| 1443 | break; |
| 1444 | }else if( *zTag=='\'' || *zTag=='"' ){ |
| 1445 | /* If word is quoted, prepare to stop at end quote. */ |
| 1446 | cDel = *zTag; |
| 1447 | ++zTag; |
| 1448 | }else{ |
| 1449 | /* If word is not quoted, prepare to stop at delimiter. */ |
| 1450 | cDel = ','; |
| 1451 | } |
| 1452 | |
| 1453 | /* Find the next delimiter character or end of string. */ |
| 1454 | for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){ |
| 1455 | /* If delimiter is comma, also recognize spaces as delimiters. */ |
| 1456 | if( cDel==',' && fossil_isspace(zTag[i]) ){ |
| 1457 | break; |
| 1458 | } |
| 1459 | |
| 1460 | /* In regexp mode, ignore delimiters following backslashes. */ |
| 1461 | if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){ |
| 1462 | ++i; |
| 1463 | } |
| 1464 | } |
| 1465 | |
| 1466 | /* Check for regular expression syntax errors. */ |
| 1467 | if( matchStyle==MS_REGEXP ){ |
| 1468 | ReCompiled *regexp; |
| 1469 | char *zTagDup = fossil_strndup(zTag, i); |
| 1470 | zFail = re_compile(®exp, zTagDup, 0); |
| 1471 | re_free(regexp); |
| 1472 | fossil_free(zTagDup); |
| 1473 | } |
| 1474 | |
| 1475 | /* Process success and error results. */ |
| 1476 | if( !zFail ){ |
| 1477 | /* Incorporate the match word into the output expression. %q is used to |
| 1478 | * protect against SQL injection attacks by replacing ' with ''. */ |
| 1479 | blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart, |
| 1480 | zPrefix, i, zTag, zSuffix); |
| 1481 | |
| 1482 | /* Build up the description string. */ |
| 1483 | if( !blob_size(&desc) ){ |
| 1484 | /* First tag: start with intro followed by first quoted tag. */ |
| 1485 | blob_append(&desc, zIntro, -1); |
| 1486 | blob_append(&desc, tagQuote(i, zTag), -1); |
| 1487 | }else{ |
| 1488 | if( zPattern ){ |
| 1489 | /* Third and subsequent tags: append comma then previous tag. */ |
| 1490 | blob_append(&desc, ", ", 2); |
| 1491 | blob_append(&desc, zPattern, -1); |
| 1492 | zOr = ", or "; |
| 1493 | } |
| 1494 | |
| 1495 | /* Second and subsequent tags: store quoted tag for next iteration. */ |
| 1496 | zPattern = tagQuote(i, zTag); |
| 1497 | } |
| 1498 | }else{ |
| 1499 | /* On error, skip the match word and build up the error message buffer. */ |
| 1500 | if( !blob_size(&err) ){ |
| 1501 | blob_append(&err, "Error: ", 7); |
| 1502 | }else{ |
| 1503 | blob_append(&err, ", ", 2); |
| 1504 | } |
| 1505 | blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail); |
| 1506 | } |
| 1507 | |
| 1508 | /* Advance past all consumed input characters. */ |
| 1509 | zTag += i; |
| 1510 | if( cDel!=',' && *zTag==cDel ){ |
| 1511 | ++zTag; |
| 1512 | } |
| 1513 | } |
| 1514 | |
| 1515 | /* Finalize and extract the pattern description. */ |
| 1516 | if( zPattern ){ |
| 1517 | blob_append(&desc, zOr, -1); |
| 1518 | blob_append(&desc, zPattern, -1); |
| 1519 | } |
| 1520 | *zDesc = blob_str(&desc); |
| 1521 | |
| 1522 | /* Finalize and extract the error text. */ |
| 1523 | *zError = blob_size(&err) ? blob_str(&err) : 0; |
| 1524 | |
| 1525 | /* Finalize and extract the SQL expression. */ |
| 1526 | if( blob_size(&expr) ){ |
| 1527 | blob_append(&expr, zEnd, -1); |
| 1528 | return blob_str(&expr); |
| 1529 | } |
| 1530 | |
| 1531 | /* If execution reaches this point, the pattern was empty. Return NULL. */ |
| 1532 | return 0; |
| 1533 | } |
| 1534 | |
| 1535 | /* |
| 1536 | ** Similar to fossil_expand_datetime() |
| 1537 | ** |
| 1538 | ** Add missing "-" characters into a date/time. Examples: |
| 1539 | ** |
| 1540 | ** 20190419 => 2019-04-19 |
| 1541 | ** 201904 => 2019-04 |
| 1542 | */ |
| 1543 | const char *timeline_expand_datetime(const char *zIn){ |
| 1544 | static char zEDate[20]; |
| 1545 | static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; |
| 1546 | int n = (int)strlen(zIn); |
| 1547 | int i, j; |
| 1548 | |
| 1549 | /* Only three forms allowed: |
| 1550 | ** (1) YYYYMMDD |
| 1551 | ** (2) YYYYMM |
| 1552 | ** (3) YYYYWW |
| 1553 | */ |
| 1554 | if( n!=8 && n!=6 ) return zIn; |
| 1555 | |
| 1556 | /* Every character must be a digit */ |
| 1557 | for(i=0; fossil_isdigit(zIn[i]); i++){} |
| 1558 | if( i!=n ) return zIn; |
| 1559 | |
| 1560 | /* Expand the date */ |
| 1561 | for(i=j=0; zIn[i]; i++){ |
| 1562 | if( i>=4 && (i%2)==0 ){ |
| 1563 | zEDate[j++] = aPunct[i/2]; |
| 1564 | } |
| 1565 | zEDate[j++] = zIn[i]; |
| 1566 | } |
| 1567 | zEDate[j] = 0; |
| 1568 | |
| 1569 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | return zEDate; |
| 1571 | } |
| 1572 | |
| 1573 | /* |
| 1574 | ** Find the first check-in encountered with a particular tag |
| 1575 | ** when moving either forwards are backwards in time from a |
| 1576 | ** particular starting point (iFrom). Return the rid of that |
| @@ -1591,10 +1404,11 @@ | |
| 1591 | tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd); |
| 1592 | if( tagId==0 ){ |
| 1593 | endId = symbolic_name_to_rid(zEnd, "ci"); |
| 1594 | if( endId==0 ) return 0; |
| 1595 | } |
| 1596 | if( bForward ){ |
| 1597 | if( tagId ){ |
| 1598 | db_prepare(&q, |
| 1599 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1600 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| @@ -1658,12 +1472,54 @@ | |
| 1658 | } |
| 1659 | if( db_step(&q)==SQLITE_ROW ){ |
| 1660 | ans = db_column_int(&q, 0); |
| 1661 | } |
| 1662 | db_finalize(&q); |
| 1663 | return ans; |
| 1664 | } |
| 1665 | |
| 1666 | /* |
| 1667 | ** COMMAND: test-endpoint |
| 1668 | ** |
| 1669 | ** Usage: fossil test-endpoint BASE TAG ?OPTIONS? |
| @@ -1698,21 +1554,21 @@ | |
| 1698 | /* |
| 1699 | ** WEBPAGE: timeline |
| 1700 | ** |
| 1701 | ** Query parameters: |
| 1702 | ** |
| 1703 | ** a=TIMEORTAG Show events after TIMEORTAG |
| 1704 | ** b=TIMEORTAG Show events before TIMEORTAG |
| 1705 | ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG |
| 1706 | ** cf=FILEHASH Show events around the time of the first use of |
| 1707 | ** the file with FILEHASH |
| 1708 | ** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available |
| 1709 | ** event if TIMEORTAG is not part of the timeline. If |
| 1710 | ** the t= or r= is used, the m event is added to the timeline |
| 1711 | ** if it isn't there already. |
| 1712 | ** x=HASHLIST Show all check-ins in the comma-separated HASHLIST |
| 1713 | ** in addition to check-ins specified by t= or r= |
| 1714 | ** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of |
| 1715 | ** the timeline. Similar to m= except TIMEORTAG must |
| 1716 | ** match a check-in that is already in the timeline. |
| 1717 | ** sel2=TIMEORTAG Like sel1= but use the secondary highlight. |
| 1718 | ** n=COUNT Maximum number of events. "all" for no limit |
| @@ -1732,63 +1588,71 @@ | |
| 1732 | ** from=CX ... shortest path from CX back to CHECKIN |
| 1733 | ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN |
| 1734 | ** d=CX ... from CX up to the time of CHECKIN |
| 1735 | ** from=CX ... shortest path from CX up to CHECKIN |
| 1736 | ** t=TAG Show only check-ins with the given TAG |
| 1737 | ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel |
| 1738 | ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist |
| 1739 | ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist |
| 1740 | ** rel Show related check-ins as well as those matching t=TAG |
| 1741 | ** mionly Limit rel to show ancestors but not descendants |
| 1742 | ** nowiki Do not show wiki associated with branch or tag |
| 1743 | ** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP |
| 1744 | ** u=USER Only show items associated with USER |
| 1745 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1746 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1747 | ** x: "Classic". |
| 1748 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1749 | ** ng No Graph. |
| 1750 | ** ncp Omit cherrypick merges |
| 1751 | ** nd Do not highlight the focus check-in |
| 1752 | ** nsm Omit the submenu |
| 1753 | ** nc Omit all graph colors other than highlights |
| 1754 | ** v Show details of files changed |
| 1755 | ** vfx Show complete text of forum messages |
| 1756 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1757 | ** from=CHECKIN Path from... |
| 1758 | ** to=CHECKIN ... to this |
| 1759 | ** to2=CHECKIN ... backup name if to= doesn't resolve |
| 1760 | ** shortest ... show only the shortest path |
| 1761 | ** rel ... also show related checkins |
| 1762 | ** bt=PRIOR ... path from CHECKIN back to PRIOR |
| 1763 | ** ft=LATER ... path from CHECKIN forward to LATER |
| 1764 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1765 | ** All qualifying check-ins are shown unless there is |
| 1766 | ** also an n= or n1= query parameter. |
| 1767 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1768 | ** name matches one of the comma-separate GLOBLIST |
| 1769 | ** brbg Background color determined by branch name |
| 1770 | ** ubg Background color determined by user |
| 1771 | ** deltabg Background color red for delta manifests or green |
| 1772 | ** for baseline manifests |
| 1773 | ** namechng Show only check-ins that have filename changes |
| 1774 | ** forks Show only forks and their children |
| 1775 | ** cherrypicks Show all cherrypicks |
| 1776 | ** ym=YYYY-MM Show only events for the given year/month |
| 1777 | ** yw=YYYY-WW Show only events for the given week of the given year |
| 1778 | ** yw=YYYY-MM-DD Show events for the week that includes the given day |
| 1779 | ** ymd=YYYY-MM-DD Show only events on the given day. The use "ymd=now" |
| 1780 | ** to see all changes for the current week. |
| 1781 | ** year=YYYY Show only events on the given year. The use "year=0" |
| 1782 | ** to see all changes for the current year. |
| 1783 | ** days=N Show events over the previous N days |
| 1784 | ** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS, |
| 1785 | ** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off". |
| 1786 | ** bisect Show the check-ins that are in the current bisect |
| 1787 | ** oldestfirst Show events oldest first. |
| 1788 | ** showid Show RIDs |
| 1789 | ** showsql Show the SQL text |
| 1790 | ** |
| 1791 | ** p= and d= can appear individually or together. If either p= or d= |
| 1792 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1793 | ** |
| 1794 | ** If both a= and b= appear then both upper and lower bounds are honored. |
| @@ -1862,13 +1726,20 @@ | |
| 1862 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1863 | char *zPlural; /* Ending for plural forms */ |
| 1864 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1865 | int haveParameterN; /* True if n= query parameter present */ |
| 1866 | int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ |
| 1867 | |
| 1868 | url_initialize(&url, "timeline"); |
| 1869 | cgi_query_parameters_to_url(&url); |
| 1870 | |
| 1871 | (void)P_NoBot("ss") |
| 1872 | /* "ss" is processed via the udc but at least one spider likes to |
| 1873 | ** try to SQL inject via this argument, so let's catch that. */; |
| 1874 | |
| @@ -1943,11 +1814,10 @@ | |
| 1943 | */ |
| 1944 | pd_rid = name_choice("dp","dp2",&zDPName); |
| 1945 | if( pd_rid ){ |
| 1946 | p_rid = d_rid = pd_rid; |
| 1947 | } |
| 1948 | login_check_credentials(); |
| 1949 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) |
| 1950 | || (bisectLocal && !g.perm.Setup) |
| 1951 | ){ |
| 1952 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1953 | return; |
| @@ -1984,48 +1854,52 @@ | |
| 1984 | |
| 1985 | /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for |
| 1986 | ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */ |
| 1987 | if( zBrName==0 && zTagName==0 ){ |
| 1988 | const char *z; |
| 1989 | if( (z = P("tl"))!=0 ){ |
| 1990 | zTagName = z; |
| 1991 | zMatchStyle = "brlist"; |
| 1992 | } |
| 1993 | if( (z = P("rl"))!=0 ){ |
| 1994 | zBrName = z; |
| 1995 | zMatchStyle = "brlist"; |
| 1996 | } |
| 1997 | } |
| 1998 | |
| 1999 | /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */ |
| 2000 | if( zBrName && !related ){ |
| 2001 | cgi_delete_query_parameter("r"); |
| 2002 | cgi_set_query_parameter("t", zBrName); (void)P("t"); |
| 2003 | cgi_set_query_parameter("rel", "1"); |
| 2004 | zTagName = zBrName; |
| 2005 | related = 1; |
| 2006 | zType = "ci"; |
| 2007 | } |
| 2008 | |
| 2009 | /* Ignore empty tag query strings. */ |
| 2010 | if( zTagName && !*zTagName ){ |
| 2011 | zTagName = 0; |
| 2012 | } |
| 2013 | |
| 2014 | /* Finish preliminary processing of tag match queries. */ |
| 2015 | if( zTagName ){ |
| 2016 | zType = "ci"; |
| 2017 | /* Interpet the tag style string. */ |
| 2018 | if( fossil_stricmp(zMatchStyle, "glob")==0 ){ |
| 2019 | matchStyle = MS_GLOB; |
| 2020 | }else if( fossil_stricmp(zMatchStyle, "like")==0 ){ |
| 2021 | matchStyle = MS_LIKE; |
| 2022 | }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){ |
| 2023 | matchStyle = MS_REGEXP; |
| 2024 | }else if( fossil_stricmp(zMatchStyle, "brlist")==0 ){ |
| 2025 | matchStyle = MS_BRLIST; |
| 2026 | }else{ |
| 2027 | /* For exact maching, inhibit links to the selected tag. */ |
| 2028 | zThisTag = zTagName; |
| 2029 | Th_Store("current_checkin", zTagName); |
| 2030 | } |
| 2031 | |
| @@ -2033,11 +1907,11 @@ | |
| 2033 | if( advancedMenu ){ |
| 2034 | style_submenu_checkbox("rel", "Related", 0, 0); |
| 2035 | } |
| 2036 | |
| 2037 | /* Construct the tag match expression. */ |
| 2038 | zTagSql = tagMatchExpression(matchStyle, zTagName, &zMatchDesc, &zError); |
| 2039 | } |
| 2040 | |
| 2041 | if( zMark && zMark[0]==0 ){ |
| 2042 | if( zAfter ) zMark = zAfter; |
| 2043 | if( zBefore ) zMark = zBefore; |
| @@ -2081,10 +1955,11 @@ | |
| 2081 | } |
| 2082 | if( PB("nc") ){ |
| 2083 | tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR); |
| 2084 | tmFlags |= TIMELINE_NOCOLOR; |
| 2085 | } |
| 2086 | if( zUses!=0 ){ |
| 2087 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 2088 | if( ufid ){ |
| 2089 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 2090 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| @@ -2098,42 +1973,42 @@ | |
| 2098 | } |
| 2099 | if( renameOnly ){ |
| 2100 | db_multi_exec( |
| 2101 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 2102 | "INSERT OR IGNORE INTO rnfile" |
| 2103 | " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" |
| 2104 | ); |
| 2105 | disableY = 1; |
| 2106 | } |
| 2107 | if( forkOnly ){ |
| 2108 | db_multi_exec( |
| 2109 | "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n" |
| 2110 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 2111 | " SELECT pid FROM plink\n" |
| 2112 | " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" |
| 2113 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 2114 | " GROUP BY pid" |
| 2115 | " HAVING count(*)>1;\n" |
| 2116 | "INSERT OR IGNORE INTO rnfork(rid)" |
| 2117 | " SELECT cid FROM plink\n" |
| 2118 | " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" |
| 2119 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 2120 | " GROUP BY cid" |
| 2121 | " HAVING count(*)>1;\n", |
| 2122 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 2123 | ); |
| 2124 | db_multi_exec( |
| 2125 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 2126 | " SELECT cid FROM plink\n" |
| 2127 | " WHERE pid IN rnfork" |
| 2128 | " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" |
| 2129 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 2130 | " UNION " |
| 2131 | " SELECT pid FROM plink\n" |
| 2132 | " WHERE cid IN rnfork" |
| 2133 | " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" |
| 2134 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n", |
| 2135 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 2136 | ); |
| 2137 | tmFlags |= TIMELINE_UNHIDE; |
| 2138 | zType = "ci"; |
| 2139 | disableY = 1; |
| @@ -2206,77 +2081,114 @@ | |
| 2206 | PathNode *p = 0; |
| 2207 | const char *zFrom = 0; |
| 2208 | const char *zTo = 0; |
| 2209 | Blob ins; |
| 2210 | int nNodeOnPath = 0; |
| 2211 | |
| 2212 | if( from_rid && to_rid ){ |
| 2213 | if( from_to_mode==0 ){ |
| 2214 | p = path_shortest(from_rid, to_rid, noMerge, 0, 0); |
| 2215 | }else if( from_to_mode==1 ){ |
| 2216 | p = path_shortest(from_rid, to_rid, 0, 1, 0); |
| 2217 | }else{ |
| 2218 | p = path_shortest(to_rid, from_rid, 0, 1, 0); |
| 2219 | } |
| 2220 | zFrom = P("from"); |
| 2221 | zTo = zTo2 ? zTo2 : P("to"); |
| 2222 | }else{ |
| 2223 | if( path_common_ancestor(me_rid, you_rid) ){ |
| 2224 | p = path_first(); |
| 2225 | } |
| 2226 | zFrom = P("me"); |
| 2227 | zTo = P("you"); |
| 2228 | } |
| 2229 | blob_init(&ins, 0, 0); |
| 2230 | db_multi_exec( |
| 2231 | "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);" |
| 2232 | ); |
| 2233 | if( p ){ |
| 2234 | blob_init(&ins, 0, 0); |
| 2235 | blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid); |
| 2236 | p = p->u.pTo; |
| 2237 | while( p ){ |
| 2238 | blob_append_sql(&ins, ",(%d)", p->rid); |
| 2239 | p = p->u.pTo; |
| 2240 | } |
| 2241 | } |
| 2242 | path_reset(); |
| 2243 | db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/); |
| 2244 | blob_reset(&ins); |
| 2245 | if( related || P("mionly") ){ |
| 2246 | db_multi_exec( |
| 2247 | "CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);" |
| 2248 | "INSERT OR IGNORE INTO related(x)" |
| 2249 | " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;" |
| 2250 | ); |
| 2251 | if( P("mionly")==0 ){ |
| 2252 | db_multi_exec( |
| 2253 | "INSERT OR IGNORE INTO related(x)" |
| 2254 | " SELECT cid FROM plink WHERE pid IN pathnode;" |
| 2255 | ); |
| 2256 | } |
| 2257 | if( showCherrypicks ){ |
| 2258 | db_multi_exec( |
| 2259 | "INSERT OR IGNORE INTO related(x)" |
| 2260 | " SELECT parentid FROM cherrypick WHERE childid IN pathnode;" |
| 2261 | ); |
| 2262 | if( P("mionly")==0 ){ |
| 2263 | db_multi_exec( |
| 2264 | "INSERT OR IGNORE INTO related(x)" |
| 2265 | " SELECT childid FROM cherrypick WHERE parentid IN pathnode;" |
| 2266 | ); |
| 2267 | } |
| 2268 | } |
| 2269 | db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related"); |
| 2270 | } |
| 2271 | blob_append_sql(&sql, " AND event.objid IN pathnode"); |
| 2272 | if( zChng && zChng[0] ){ |
| 2273 | db_multi_exec( |
| 2274 | "DELETE FROM pathnode " |
| 2275 | " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename" |
| 2276 | " WHERE mlink.mid=x" |
| 2277 | " AND mlink.fnid=filename.fnid AND %s)", |
| 2278 | glob_expr("filename.name", zChng) |
| 2279 | ); |
| 2280 | } |
| 2281 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2282 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -2330,18 +2242,18 @@ | |
| 2330 | if( !haveParameterN ) nEntry = 10; |
| 2331 | } |
| 2332 | db_multi_exec( |
| 2333 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" |
| 2334 | ); |
| 2335 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", |
| 2336 | p_rid ? p_rid : d_rid); |
| 2337 | zCiName = zDPName; |
| 2338 | if( zCiName==0 ) zCiName = zUuid; |
| 2339 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 2340 | nd = 0; |
| 2341 | if( d_rid ){ |
| 2342 | Stmt s; |
| 2343 | double rStopTime = 9e99; |
| 2344 | zFwdTo = P("ft"); |
| 2345 | if( zFwdTo ){ |
| 2346 | double rStartDate = db_double(0.0, |
| 2347 | "SELECT mtime FROM event WHERE objid=%d", d_rid); |
| @@ -2353,27 +2265,25 @@ | |
| 2353 | if( !haveParameterN ) nEntry = 0; |
| 2354 | rStopTime = db_double(9e99, |
| 2355 | "SELECT mtime FROM event WHERE objid=%d", ridFwdTo); |
| 2356 | } |
| 2357 | } |
| 2358 | db_prepare(&s, |
| 2359 | "WITH RECURSIVE" |
| 2360 | " dx(rid,mtime) AS (" |
| 2361 | " SELECT %d, 0" |
| 2362 | " UNION" |
| 2363 | " SELECT plink.cid, plink.mtime FROM dx, plink" |
| 2364 | " WHERE plink.pid=dx.rid" |
| 2365 | " AND (:stop>=8e99 OR plink.mtime<=:stop)" |
| 2366 | " ORDER BY 2" |
| 2367 | " )" |
| 2368 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 2369 | d_rid, nEntry<=0 ? -1 : nEntry+1 |
| 2370 | ); |
| 2371 | db_bind_double(&s, ":stop", rStopTime); |
| 2372 | db_step(&s); |
| 2373 | db_finalize(&s); |
| 2374 | /* compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1); */ |
| 2375 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2376 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 2377 | if( nd>0 || p_rid==0 ){ |
| 2378 | blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 2379 | } |
| @@ -2482,67 +2392,76 @@ | |
| 2482 | if( zChng && *zChng ){ |
| 2483 | addFileGlobExclusion(zChng, &cond); |
| 2484 | tmFlags |= TIMELINE_XMERGE; |
| 2485 | } |
| 2486 | if( zUses ){ |
| 2487 | blob_append_sql(&cond, " AND event.objid IN usesfile "); |
| 2488 | } |
| 2489 | if( renameOnly ){ |
| 2490 | blob_append_sql(&cond, " AND event.objid IN rnfile "); |
| 2491 | } |
| 2492 | if( forkOnly ){ |
| 2493 | blob_append_sql(&cond, " AND event.objid IN rnfork "); |
| 2494 | } |
| 2495 | if( cpOnly && showCherrypicks ){ |
| 2496 | db_multi_exec( |
| 2497 | "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);" |
| 2498 | "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;" |
| 2499 | "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;" |
| 2500 | ); |
| 2501 | blob_append_sql(&cond, " AND event.objid IN cpnodes "); |
| 2502 | } |
| 2503 | if( bisectLocal || zBisect!=0 ){ |
| 2504 | blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog) "); |
| 2505 | } |
| 2506 | if( zYearMonth ){ |
| 2507 | char *zNext; |
| 2508 | zYearMonth = timeline_expand_datetime(zYearMonth); |
| 2509 | if( strlen(zYearMonth)>7 ){ |
| 2510 | zYearMonth = mprintf("%.7s", zYearMonth); |
| 2511 | } |
| 2512 | if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){ |
| 2513 | zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');"); |
| 2514 | } |
| 2515 | zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','+1 month');", |
| 2516 | zYearMonth); |
| 2517 | if( db_int(0, |
| 2518 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2519 | " WHERE blob.rid=event.objid AND mtime>=julianday('%q-01')%s)", |
| 2520 | zNext, blob_sql_text(&cond)) |
| 2521 | ){ |
| 2522 | zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); |
| 2523 | zNewerButtonLabel = "Following month"; |
| 2524 | } |
| 2525 | fossil_free(zNext); |
| 2526 | zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','-1 month');", |
| 2527 | zYearMonth); |
| 2528 | if( db_int(0, |
| 2529 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2530 | " WHERE blob.rid=event.objid AND mtime<julianday('%q-01')%s)", |
| 2531 | zYearMonth, blob_sql_text(&cond)) |
| 2532 | ){ |
| 2533 | zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); |
| 2534 | zOlderButtonLabel = "Previous month"; |
| 2535 | } |
| 2536 | fossil_free(zNext); |
| 2537 | blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ", |
| 2538 | zYearMonth); |
| 2539 | nEntry = -1; |
| 2540 | } |
| 2541 | else if( zYearWeek ){ |
| 2542 | char *z, *zNext; |
| 2543 | zYearWeek = timeline_expand_datetime(zYearWeek); |
| 2544 | z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek); |
| 2545 | if( z && z[0] ){ |
| 2546 | zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')", |
| 2547 | zYearWeek); |
| 2548 | zYearWeek = z; |
| @@ -2559,64 +2478,152 @@ | |
| 2559 | "SELECT date('now','-6 days','weekday 1');"); |
| 2560 | zYearWeek = db_text(0, |
| 2561 | "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')"); |
| 2562 | } |
| 2563 | } |
| 2564 | zNext = db_text(0, "SELECT date(%Q,'+7 day');", zYearWeekStart); |
| 2565 | if( db_int(0, |
| 2566 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2567 | " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)", |
| 2568 | zNext, blob_sql_text(&cond)) |
| 2569 | ){ |
| 2570 | zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); |
| 2571 | zNewerButtonLabel = "Following week"; |
| 2572 | } |
| 2573 | fossil_free(zNext); |
| 2574 | zNext = db_text(0, "SELECT date(%Q,'-7 days');", zYearWeekStart); |
| 2575 | if( db_int(0, |
| 2576 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2577 | " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)", |
| 2578 | zYearWeekStart, blob_sql_text(&cond)) |
| 2579 | ){ |
| 2580 | zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); |
| 2581 | zOlderButtonLabel = "Previous week"; |
| 2582 | } |
| 2583 | fossil_free(zNext); |
| 2584 | blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%W',event.mtime) ", |
| 2585 | zYearWeek); |
| 2586 | nEntry = -1; |
| 2587 | } |
| 2588 | else if( zDay ){ |
| 2589 | char *zNext; |
| 2590 | zDay = timeline_expand_datetime(zDay); |
| 2591 | zDay = db_text(0, "SELECT date(%Q)", zDay); |
| 2592 | if( zDay==0 || zDay[0]==0 ){ |
| 2593 | zDay = db_text(0, "SELECT date('now')"); |
| 2594 | } |
| 2595 | zNext = db_text(0, "SELECT date(%Q,'+1 day');", zDay); |
| 2596 | if( db_int(0, |
| 2597 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2598 | " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)", |
| 2599 | zNext, blob_sql_text(&cond)) |
| 2600 | ){ |
| 2601 | zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2602 | zNewerButtonLabel = "Following day"; |
| 2603 | } |
| 2604 | fossil_free(zNext); |
| 2605 | zNext = db_text(0, "SELECT date(%Q,'-1 day');", zDay); |
| 2606 | if( db_int(0, |
| 2607 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2608 | " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)", |
| 2609 | zDay, blob_sql_text(&cond)) |
| 2610 | ){ |
| 2611 | zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2612 | zOlderButtonLabel = "Previous day"; |
| 2613 | } |
| 2614 | fossil_free(zNext); |
| 2615 | blob_append_sql(&cond, " AND %Q=date(event.mtime) ", |
| 2616 | zDay); |
| 2617 | nEntry = -1; |
| 2618 | } |
| 2619 | else if( zNDays ){ |
| 2620 | nDays = atoi(zNDays); |
| 2621 | if( nDays<1 ) nDays = 1; |
| 2622 | blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ", |
| @@ -2662,39 +2669,24 @@ | |
| 2662 | nEntry = -1; |
| 2663 | } |
| 2664 | if( zTagSql ){ |
| 2665 | db_multi_exec( |
| 2666 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2667 | "INSERT OR IGNORE INTO selected_nodes" |
| 2668 | " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag" |
| 2669 | " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/ |
| 2670 | ); |
| 2671 | if( zMark ){ |
| 2672 | /* If the t=release option is used with m=UUID, then also |
| 2673 | ** include the UUID check-in in the display list */ |
| 2674 | int ridMark = name_to_rid(zMark); |
| 2675 | db_multi_exec( |
| 2676 | "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark); |
| 2677 | } |
| 2678 | if( P("x")!=0 ){ |
| 2679 | char *zX = fossil_strdup(P("x")); |
| 2680 | int ii; |
| 2681 | int ridX; |
| 2682 | while( zX[0] ){ |
| 2683 | char c; |
| 2684 | if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; } |
| 2685 | for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){} |
| 2686 | c = zX[ii]; |
| 2687 | zX[ii] = 0; |
| 2688 | ridX = name_to_rid(zX); |
| 2689 | db_multi_exec( |
| 2690 | "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridX); |
| 2691 | zX[ii] = c; |
| 2692 | zX += ii; |
| 2693 | } |
| 2694 | } |
| 2695 | if( !related ){ |
| 2696 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2697 | }else{ |
| 2698 | db_multi_exec( |
| 2699 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| 2700 | "INSERT INTO related_nodes SELECT rid FROM selected_nodes;" |
| @@ -2705,40 +2697,42 @@ | |
| 2705 | ** branch to be included in the report. These related check-ins are |
| 2706 | ** useful in helping to visualize what has happened on a quiescent |
| 2707 | ** branch that is infrequently merged with a much more activate branch. |
| 2708 | */ |
| 2709 | db_multi_exec( |
| 2710 | "INSERT OR IGNORE INTO related_nodes" |
| 2711 | " SELECT pid FROM selected_nodes CROSS JOIN plink" |
| 2712 | " WHERE selected_nodes.rid=plink.cid;" |
| 2713 | ); |
| 2714 | if( P("mionly")==0 ){ |
| 2715 | db_multi_exec( |
| 2716 | "INSERT OR IGNORE INTO related_nodes" |
| 2717 | " SELECT cid FROM selected_nodes CROSS JOIN plink" |
| 2718 | " WHERE selected_nodes.rid=plink.pid;" |
| 2719 | ); |
| 2720 | if( showCherrypicks ){ |
| 2721 | db_multi_exec( |
| 2722 | "INSERT OR IGNORE INTO related_nodes" |
| 2723 | " SELECT childid FROM selected_nodes CROSS JOIN cherrypick" |
| 2724 | " WHERE selected_nodes.rid=cherrypick.parentid;" |
| 2725 | ); |
| 2726 | } |
| 2727 | } |
| 2728 | if( showCherrypicks ){ |
| 2729 | db_multi_exec( |
| 2730 | "INSERT OR IGNORE INTO related_nodes" |
| 2731 | " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick" |
| 2732 | " WHERE selected_nodes.rid=cherrypick.childid;" |
| 2733 | ); |
| 2734 | } |
| 2735 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 2736 | db_multi_exec( |
| 2737 | "DELETE FROM related_nodes WHERE rid IN " |
| 2738 | " (SELECT related_nodes.rid FROM related_nodes, tagxref" |
| 2739 | " WHERE tagid=%d AND tagtype>0 AND tagxref.rid=related_nodes.rid)", |
| 2740 | TAG_HIDDEN |
| 2741 | ); |
| 2742 | } |
| 2743 | } |
| 2744 | } |
| @@ -2828,45 +2822,42 @@ | |
| 2828 | rCirca = symbolic_name_to_mtime(zCirca, &zCirca); |
| 2829 | blob_append_sql(&sql, "%s", blob_sql_text(&cond)); |
| 2830 | if( rAfter>0.0 ){ |
| 2831 | if( rBefore>0.0 ){ |
| 2832 | blob_append_sql(&sql, |
| 2833 | " AND event.mtime>=%.17g AND event.mtime<=%.17g" |
| 2834 | " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND); |
| 2835 | nEntry = -1; |
| 2836 | }else{ |
| 2837 | blob_append_sql(&sql, |
| 2838 | " AND event.mtime>=%.17g ORDER BY event.mtime ASC", |
| 2839 | rAfter-ONE_SECOND); |
| 2840 | } |
| 2841 | zCirca = 0; |
| 2842 | url_add_parameter(&url, "c", 0); |
| 2843 | }else if( rBefore>0.0 ){ |
| 2844 | blob_append_sql(&sql, |
| 2845 | " AND event.mtime<=%.17g ORDER BY event.mtime DESC", |
| 2846 | rBefore+ONE_SECOND); |
| 2847 | zCirca = 0; |
| 2848 | url_add_parameter(&url, "c", 0); |
| 2849 | }else if( rCirca>0.0 ){ |
| 2850 | Blob sql2; |
| 2851 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 2852 | blob_append_sql(&sql2, |
| 2853 | " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca); |
| 2854 | if( nEntry>0 ){ |
| 2855 | blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2); |
| 2856 | } |
| 2857 | if( PB("showsql") ){ |
| 2858 | @ <pre>%h(blob_sql_text(&sql2))</pre> |
| 2859 | } |
| 2860 | db_multi_exec("%s", blob_sql_text(&sql2)); |
| 2861 | if( nEntry>0 ){ |
| 2862 | nEntry -= db_int(0,"select count(*) from timeline"); |
| 2863 | if( nEntry<=0 ) nEntry = 1; |
| 2864 | } |
| 2865 | blob_reset(&sql2); |
| 2866 | blob_append_sql(&sql, |
| 2867 | " AND event.mtime<=%f ORDER BY event.mtime DESC", |
| 2868 | rCirca |
| 2869 | ); |
| 2870 | if( zMark==0 ) zMark = zCirca; |
| 2871 | }else{ |
| 2872 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| @@ -2875,11 +2866,11 @@ | |
| 2875 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2876 | |
| 2877 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 2878 | zPlural = n==1 ? "" : "s"; |
| 2879 | if( zYearMonth ){ |
| 2880 | blob_appendf(&desc, "%d %s%s for the month beginning %h-01", |
| 2881 | n, zEType, zPlural, zYearMonth); |
| 2882 | }else if( zYearWeek ){ |
| 2883 | blob_appendf(&desc, "%d %s%s for week %h beginning on %h", |
| 2884 | n, zEType, zPlural, zYearWeek, zYearWeekStart); |
| 2885 | }else if( zDay ){ |
| @@ -3009,12 +3000,14 @@ | |
| 3009 | style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0); |
| 3010 | } |
| 3011 | } |
| 3012 | blob_zero(&cond); |
| 3013 | } |
| 3014 | if( PB("showsql") ){ |
| 3015 | @ <pre>%h(blob_sql_text(&sql))</pre> |
| 3016 | } |
| 3017 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 3018 | style_submenu_element("Search", "%R/search?y=c"); |
| 3019 | } |
| 3020 | if( advancedMenu ){ |
| @@ -3068,18 +3061,36 @@ | |
| 3068 | if( zNewerButton ){ |
| 3069 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3070 | @ ↑</a> |
| 3071 | } |
| 3072 | cgi_check_for_malice(); |
| 3073 | www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName, |
| 3074 | selectedRid, secondaryRid, 0); |
| 3075 | db_finalize(&q); |
| 3076 | if( zOlderButton ){ |
| 3077 | @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\ |
| 3078 | @ ↓</a> |
| 3079 | } |
| 3080 | document_emit_js(/*handles pikchrs rendered above*/); |
| 3081 | style_finish_page(); |
| 3082 | } |
| 3083 | |
| 3084 | /* |
| 3085 | ** Translate a timeline entry into the printable format by |
| @@ -3744,10 +3755,11 @@ | |
| 3744 | const char *zToday; |
| 3745 | char *zStartOfProject; |
| 3746 | int i; |
| 3747 | Stmt q; |
| 3748 | char *z; |
| 3749 | |
| 3750 | login_check_credentials(); |
| 3751 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){ |
| 3752 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 3753 | return; |
| @@ -3754,11 +3766,11 @@ | |
| 3754 | } |
| 3755 | style_set_current_feature("timeline"); |
| 3756 | style_header("Today In History"); |
| 3757 | zToday = (char*)P("today"); |
| 3758 | if( zToday ){ |
| 3759 | zToday = timeline_expand_datetime(zToday); |
| 3760 | if( !fossil_isdate(zToday) ) zToday = 0; |
| 3761 | } |
| 3762 | if( zToday==0 ){ |
| 3763 | zToday = db_text(0, "SELECT date('now',toLocal())"); |
| 3764 | } |
| 3765 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -191,11 +191,11 @@ | |
| 191 | void www_print_timeline( |
| 192 | Stmt *pQuery, /* Query to implement the timeline */ |
| 193 | int tmFlags, /* Flags controlling display behavior */ |
| 194 | const char *zThisUser, /* Suppress links to this user */ |
| 195 | const char *zThisTag, /* Suppress links to this tag */ |
| 196 | Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */ |
| 197 | int selectedRid, /* Highlight the line with this RID value or zero */ |
| 198 | int secondRid, /* Secondary highlight (or zero) */ |
| 199 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 200 | ){ |
| 201 | int mxWikiLen; |
| @@ -813,11 +813,11 @@ | |
| 813 | } |
| 814 | if( pendingEndTr ){ |
| 815 | @ </td></tr> |
| 816 | } |
| 817 | if( pGraph ){ |
| 818 | graph_finish(pGraph, pLeftBranch, tmFlags); |
| 819 | if( pGraph->nErr ){ |
| 820 | graph_free(pGraph); |
| 821 | pGraph = 0; |
| 822 | }else{ |
| 823 | @ <tr class="timelineBottom" id="btm-%d(iTableId)">\ |
| @@ -1290,12 +1290,13 @@ | |
| 1290 | const char *zChng, /* The filename GLOB list */ |
| 1291 | Blob *pSql /* The SELECT statement under construction */ |
| 1292 | ){ |
| 1293 | if( zChng==0 || zChng[0]==0 ) return; |
| 1294 | blob_append_sql(pSql," AND event.objid IN (" |
| 1295 | "SELECT mlink.mid FROM mlink, filename\n" |
| 1296 | " WHERE mlink.fnid=filename.fnid\n" |
| 1297 | " AND %s)", |
| 1298 | glob_expr("filename.name", mprintf("\"%s\"", zChng))); |
| 1299 | } |
| 1300 | static void addFileGlobDescription( |
| 1301 | const char *zChng, /* The filename GLOB list */ |
| 1302 | Blob *pDescription /* Result description */ |
| @@ -1303,274 +1304,86 @@ | |
| 1304 | if( zChng==0 || zChng[0]==0 ) return; |
| 1305 | blob_appendf(pDescription, " that include changes to files matching '%h'", |
| 1306 | zChng); |
| 1307 | } |
| 1308 | |
| 1309 | /* |
| 1310 | ** Similar to fossil_expand_datetime() |
| 1311 | ** |
| 1312 | ** Add missing "-" characters into a date/time. Examples: |
| 1313 | ** |
| 1314 | ** 20190419 => 2019-04-19 |
| 1315 | ** 201904 => 2019-04 |
| 1316 | */ |
| 1317 | const char *timeline_expand_datetime(const char *zIn, int *pbZulu){ |
| 1318 | static char zEDate[16]; |
| 1319 | int n = (int)strlen(zIn); |
| 1320 | int i, j; |
| 1321 | |
| 1322 | /* These forms are recognized: |
| 1323 | ** |
| 1324 | ** (1) YYYYMMDD |
| 1325 | ** (2) YYYYMM |
| 1326 | ** (3) YYYYWW |
| 1327 | */ |
| 1328 | if( n && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){ |
| 1329 | n--; |
| 1330 | if( pbZulu ) *pbZulu = 1; |
| 1331 | }else{ |
| 1332 | if( pbZulu ) *pbZulu = 0; |
| 1333 | } |
| 1334 | if( n!=8 && n!=6 ) return zIn; |
| 1335 | |
| 1336 | /* Every character must be a digit */ |
| 1337 | for(i=0; i<n && fossil_isdigit(zIn[i]); i++){} |
| 1338 | if( i!=n ) return zIn; |
| 1339 | |
| 1340 | /* Expand the date */ |
| 1341 | for(i=j=0; i<n; i++){ |
| 1342 | if( j==4 || j==7 ) zEDate[j++] = '-'; |
| 1343 | zEDate[j++] = zIn[i]; |
| 1344 | } |
| 1345 | zEDate[j] = 0; |
| 1346 | |
| 1347 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1348 | return zEDate; |
| 1349 | } |
| 1350 | |
| 1351 | /* |
| 1352 | ** Check to see if the argument is a date-span for the ymd= query |
| 1353 | ** parameter. A valid date-span is of the form: |
| 1354 | ** |
| 1355 | ** 0123456789 123456 <-- index |
| 1356 | ** YYYYMMDD-YYYYMMDD |
| 1357 | ** |
| 1358 | ** with an optional "Z" timeline modifier at the end. Return true if |
| 1359 | ** the input is a valid date space and false if not. |
| 1360 | */ |
| 1361 | static int timeline_is_datespan(const char *zDay){ |
| 1362 | size_t n = strlen(zDay); |
| 1363 | int i, d, m; |
| 1364 | |
| 1365 | if( n<17 || n>18 ) return 0; |
| 1366 | if( n==18 ){ |
| 1367 | if( zDay[17]!='Z' && zDay[17]!='z' ) return 0; |
| 1368 | n--; |
| 1369 | } |
| 1370 | if( zDay[8]!='-' ) return 0; |
| 1371 | for(i=0; i<17 && (fossil_isdigit(zDay[i]) || i==8); i++){} |
| 1372 | if( i!=17 ) return 0; |
| 1373 | i = atoi(zDay); |
| 1374 | d = i%100; |
| 1375 | if( d<1 || d>31 ) return 0; |
| 1376 | m = (i/100)%100; |
| 1377 | if( m<1 || m>12 ) return 0; |
| 1378 | i = atoi(zDay+9); |
| 1379 | d = i%100; |
| 1380 | if( d<1 || d>31 ) return 0; |
| 1381 | m = (i/100)%100; |
| 1382 | if( m<1 || m>12 ) return 0; |
| 1383 | return 1; |
| 1384 | } |
| 1385 | |
| 1386 | /* |
| 1387 | ** Find the first check-in encountered with a particular tag |
| 1388 | ** when moving either forwards are backwards in time from a |
| 1389 | ** particular starting point (iFrom). Return the rid of that |
| @@ -1591,10 +1404,11 @@ | |
| 1404 | tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd); |
| 1405 | if( tagId==0 ){ |
| 1406 | endId = symbolic_name_to_rid(zEnd, "ci"); |
| 1407 | if( endId==0 ) return 0; |
| 1408 | } |
| 1409 | db_pause_dml_log(); |
| 1410 | if( bForward ){ |
| 1411 | if( tagId ){ |
| 1412 | db_prepare(&q, |
| 1413 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1414 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| @@ -1658,12 +1472,54 @@ | |
| 1472 | } |
| 1473 | if( db_step(&q)==SQLITE_ROW ){ |
| 1474 | ans = db_column_int(&q, 0); |
| 1475 | } |
| 1476 | db_finalize(&q); |
| 1477 | db_unpause_dml_log(); |
| 1478 | return ans; |
| 1479 | } |
| 1480 | |
| 1481 | /* |
| 1482 | ** Add to the (temp) table zTab, RID values for every check-in |
| 1483 | ** identifier found on the zExtra string. Check-in names can be separated |
| 1484 | ** by commas or by whitespace. |
| 1485 | */ |
| 1486 | static void add_extra_rids(const char *zTab, const char *zExtra){ |
| 1487 | int ii; |
| 1488 | int rid; |
| 1489 | int cnt; |
| 1490 | Blob sql; |
| 1491 | char *zX; |
| 1492 | char *zToDel; |
| 1493 | if( zExtra==0 ) return; |
| 1494 | cnt = 0; |
| 1495 | blob_init(&sql, 0, 0); |
| 1496 | zX = zToDel = fossil_strdup(zExtra); |
| 1497 | blob_append_sql(&sql, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab); |
| 1498 | while( zX[0] ){ |
| 1499 | char c; |
| 1500 | if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; } |
| 1501 | for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){} |
| 1502 | c = zX[ii]; |
| 1503 | zX[ii] = 0; |
| 1504 | rid = name_to_rid(zX); |
| 1505 | if( rid>0 ){ |
| 1506 | if( (cnt%10)==4 ){ |
| 1507 | blob_append_sql(&sql,",\n "); |
| 1508 | }else if( cnt>0 ){ |
| 1509 | blob_append_sql(&sql,","); |
| 1510 | } |
| 1511 | blob_append_sql(&sql, "(%d)", rid); |
| 1512 | cnt++; |
| 1513 | } |
| 1514 | zX[ii] = c; |
| 1515 | zX += ii; |
| 1516 | } |
| 1517 | if( cnt ) db_exec_sql(blob_sql_text(&sql)); |
| 1518 | blob_reset(&sql); |
| 1519 | fossil_free(zToDel); |
| 1520 | } |
| 1521 | |
| 1522 | /* |
| 1523 | ** COMMAND: test-endpoint |
| 1524 | ** |
| 1525 | ** Usage: fossil test-endpoint BASE TAG ?OPTIONS? |
| @@ -1698,21 +1554,21 @@ | |
| 1554 | /* |
| 1555 | ** WEBPAGE: timeline |
| 1556 | ** |
| 1557 | ** Query parameters: |
| 1558 | ** |
| 1559 | ** a=TIMEORTAG Show events after TIMEORTAG. |
| 1560 | ** b=TIMEORTAG Show events before TIMEORTAG. |
| 1561 | ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG |
| 1562 | ** cf=FILEHASH Show events around the time of the first use of |
| 1563 | ** the file with FILEHASH. |
| 1564 | ** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available |
| 1565 | ** event if TIMEORTAG is not part of the timeline. If |
| 1566 | ** the t= or r= is used, the m event is added to the timeline |
| 1567 | ** if it isn't there already. |
| 1568 | ** x=LIST Show check-ins in the comma- or space-separated LIST |
| 1569 | ** in addition to check-ins specified by other parameters. |
| 1570 | ** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of |
| 1571 | ** the timeline. Similar to m= except TIMEORTAG must |
| 1572 | ** match a check-in that is already in the timeline. |
| 1573 | ** sel2=TIMEORTAG Like sel1= but use the secondary highlight. |
| 1574 | ** n=COUNT Maximum number of events. "all" for no limit |
| @@ -1732,63 +1588,71 @@ | |
| 1588 | ** from=CX ... shortest path from CX back to CHECKIN |
| 1589 | ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN |
| 1590 | ** d=CX ... from CX up to the time of CHECKIN |
| 1591 | ** from=CX ... shortest path from CX up to CHECKIN |
| 1592 | ** t=TAG Show only check-ins with the given TAG |
| 1593 | ** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related" |
| 1594 | ** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List" |
| 1595 | ** rl=TAGLIST Same as 'r=TAGLIST&ms=brlist'. Mnemonic: "Related List" |
| 1596 | ** ml=TAGLIST Same as 'tl=TAGLIST&mionly'. Mnemonic: "Merge-in List" |
| 1597 | ** sl=TAGLIST "Sort List". Draw TAGLIST branches ordered left to right. |
| 1598 | ** rel Show related check-ins as well as those matching t=TAG |
| 1599 | ** mionly Show related parents but not related children. |
| 1600 | ** nowiki Do not show wiki associated with branch or tag |
| 1601 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1602 | ** "like", or "regexp". |
| 1603 | ** u=USER Only show items associated with USER |
| 1604 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1605 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1606 | * x: "Classic". |
| 1607 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1608 | ** ng No Graph. |
| 1609 | ** ncp Omit cherrypick merges |
| 1610 | ** nd Do not highlight the focus check-in |
| 1611 | ** nsm Omit the submenu |
| 1612 | ** nc Omit all graph colors other than highlights |
| 1613 | ** v Show details of files changed |
| 1614 | ** vfx Show complete text of forum messages |
| 1615 | ** f=CHECKIN Family (immediate parents and children) of CHECKIN |
| 1616 | ** from=CHECKIN Path through common ancestor from... |
| 1617 | ** to=CHECKIN ... to this |
| 1618 | ** to2=CHECKIN ... backup name if to= doesn't resolve |
| 1619 | ** shortest ... show only the shortest path |
| 1620 | ** rel ... also show related checkins |
| 1621 | ** bt=PRIOR ... path from CHECKIN back to PRIOR |
| 1622 | ** ft=LATER ... path from CHECKIN forward to LATER |
| 1623 | ** me=CHECKIN Most direct path from... |
| 1624 | ** you=CHECKIN ... to this |
| 1625 | ** rel ... also show related checkins |
| 1626 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1627 | ** All qualifying check-ins are shown unless there is |
| 1628 | ** also an n= or n1= query parameter. |
| 1629 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1630 | ** name matches one of the comma-separate GLOBLIST |
| 1631 | ** brbg Background color determined by branch name |
| 1632 | ** ubg Background color determined by user |
| 1633 | ** deltabg Background color red for delta manifests or green |
| 1634 | ** for baseline manifests |
| 1635 | ** namechng Show only check-ins that have filename changes |
| 1636 | ** forks Show only forks and their children |
| 1637 | ** cherrypicks Show all cherrypicks |
| 1638 | ** ym=YYYYMM Show only events for the given year/month |
| 1639 | ** yw=YYYYWW Show only events for the given week of the given year |
| 1640 | ** yw=YYYYMMDD Show events for the week that includes the given day |
| 1641 | ** ymd=YYYYMMDD Show only events on the given day. The use "ymd=now" |
| 1642 | ** to see all changes for the current week. Add "z" at end |
| 1643 | ** to divide days at UTC instead of localtime days. |
| 1644 | ** Use ymd=YYYYMMDD-YYYYMMDD (with optional "z") for a range. |
| 1645 | ** year=YYYY Show only events on the given year. The use "year=0" |
| 1646 | ** to see all changes for the current year. |
| 1647 | ** days=N Show events over the previous N days |
| 1648 | ** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS, |
| 1649 | ** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off". |
| 1650 | ** bisect Show the check-ins that are in the current bisect |
| 1651 | ** oldestfirst Show events oldest first. |
| 1652 | ** showid Show RIDs |
| 1653 | ** showsql Show the SQL used to generate the report |
| 1654 | ** |
| 1655 | ** p= and d= can appear individually or together. If either p= or d= |
| 1656 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1657 | ** |
| 1658 | ** If both a= and b= appear then both upper and lower bounds are honored. |
| @@ -1862,13 +1726,20 @@ | |
| 1726 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1727 | char *zPlural; /* Ending for plural forms */ |
| 1728 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1729 | int haveParameterN; /* True if n= query parameter present */ |
| 1730 | int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ |
| 1731 | int showSql = PB("showsql"); /* True to show the SQL */ |
| 1732 | Blob allSql; /* Copy of all SQL text */ |
| 1733 | |
| 1734 | login_check_credentials(); |
| 1735 | url_initialize(&url, "timeline"); |
| 1736 | cgi_query_parameters_to_url(&url); |
| 1737 | blob_init(&allSql, 0, 0); |
| 1738 | |
| 1739 | /* The "mionly" query parameter is like "rel", but shows merge-ins only */ |
| 1740 | if( P("mionly")!=0 ) related = 2; |
| 1741 | |
| 1742 | (void)P_NoBot("ss") |
| 1743 | /* "ss" is processed via the udc but at least one spider likes to |
| 1744 | ** try to SQL inject via this argument, so let's catch that. */; |
| 1745 | |
| @@ -1943,11 +1814,10 @@ | |
| 1814 | */ |
| 1815 | pd_rid = name_choice("dp","dp2",&zDPName); |
| 1816 | if( pd_rid ){ |
| 1817 | p_rid = d_rid = pd_rid; |
| 1818 | } |
| 1819 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) |
| 1820 | || (bisectLocal && !g.perm.Setup) |
| 1821 | ){ |
| 1822 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1823 | return; |
| @@ -1984,48 +1854,52 @@ | |
| 1854 | |
| 1855 | /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for |
| 1856 | ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */ |
| 1857 | if( zBrName==0 && zTagName==0 ){ |
| 1858 | const char *z; |
| 1859 | const char *zPattern = 0; |
| 1860 | if( (z = P("tl"))!=0 ){ |
| 1861 | zPattern = zTagName = z; |
| 1862 | }else if( (z = P("rl"))!=0 ){ |
| 1863 | zPattern = zBrName = z; |
| 1864 | if( related==0 ) related = 1; |
| 1865 | }else if( (z = P("ml"))!=0 ){ |
| 1866 | zPattern = zBrName = z; |
| 1867 | if( related==0 ) related = 2; |
| 1868 | } |
| 1869 | if( zPattern!=0 && zMatchStyle==0 ){ |
| 1870 | /* If there was no ms= query parameter, set the match style to |
| 1871 | ** "glob" if the pattern appears to contain GLOB character, or |
| 1872 | ** "brlist" if it does not. */ |
| 1873 | if( strpbrk(zPattern,"*[?") ){ |
| 1874 | zMatchStyle = "glob"; |
| 1875 | }else{ |
| 1876 | zMatchStyle = "brlist"; |
| 1877 | } |
| 1878 | } |
| 1879 | } |
| 1880 | |
| 1881 | /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */ |
| 1882 | if( zBrName ){ |
| 1883 | cgi_delete_query_parameter("r"); |
| 1884 | cgi_set_query_parameter("t", zBrName); (void)P("t"); |
| 1885 | cgi_set_query_parameter("rel", "1"); |
| 1886 | zTagName = zBrName; |
| 1887 | if( related==0 ) related = 1; |
| 1888 | zType = "ci"; |
| 1889 | } |
| 1890 | |
| 1891 | /* Ignore empty tag query strings. */ |
| 1892 | if( zTagName && !*zTagName ){ |
| 1893 | zTagName = 0; |
| 1894 | } |
| 1895 | |
| 1896 | /* Finish preliminary processing of tag match queries. */ |
| 1897 | matchStyle = match_style(zMatchStyle, MS_EXACT); |
| 1898 | if( zTagName ){ |
| 1899 | zType = "ci"; |
| 1900 | if( matchStyle==MS_EXACT ){ |
| 1901 | /* For exact maching, inhibit links to the selected tag. */ |
| 1902 | zThisTag = zTagName; |
| 1903 | Th_Store("current_checkin", zTagName); |
| 1904 | } |
| 1905 | |
| @@ -2033,11 +1907,11 @@ | |
| 1907 | if( advancedMenu ){ |
| 1908 | style_submenu_checkbox("rel", "Related", 0, 0); |
| 1909 | } |
| 1910 | |
| 1911 | /* Construct the tag match expression. */ |
| 1912 | zTagSql = match_tag_sqlexpr(matchStyle, zTagName, &zMatchDesc, &zError); |
| 1913 | } |
| 1914 | |
| 1915 | if( zMark && zMark[0]==0 ){ |
| 1916 | if( zAfter ) zMark = zAfter; |
| 1917 | if( zBefore ) zMark = zBefore; |
| @@ -2081,10 +1955,11 @@ | |
| 1955 | } |
| 1956 | if( PB("nc") ){ |
| 1957 | tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR); |
| 1958 | tmFlags |= TIMELINE_NOCOLOR; |
| 1959 | } |
| 1960 | if( showSql ) db_append_dml_to_blob(&allSql); |
| 1961 | if( zUses!=0 ){ |
| 1962 | int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses); |
| 1963 | if( ufid ){ |
| 1964 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1965 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| @@ -2098,42 +1973,42 @@ | |
| 1973 | } |
| 1974 | if( renameOnly ){ |
| 1975 | db_multi_exec( |
| 1976 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 1977 | "INSERT OR IGNORE INTO rnfile" |
| 1978 | " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" |
| 1979 | ); |
| 1980 | disableY = 1; |
| 1981 | } |
| 1982 | if( forkOnly ){ |
| 1983 | db_multi_exec( |
| 1984 | "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n" |
| 1985 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 1986 | " SELECT pid FROM plink\n" |
| 1987 | " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" |
| 1988 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 1989 | " GROUP BY pid\n" |
| 1990 | " HAVING count(*)>1;\n" |
| 1991 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 1992 | " SELECT cid FROM plink\n" |
| 1993 | " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" |
| 1994 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 1995 | " GROUP BY cid\n" |
| 1996 | " HAVING count(*)>1;\n", |
| 1997 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 1998 | ); |
| 1999 | db_multi_exec( |
| 2000 | "INSERT OR IGNORE INTO rnfork(rid)\n" |
| 2001 | " SELECT cid FROM plink\n" |
| 2002 | " WHERE pid IN rnfork\n" |
| 2003 | " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" |
| 2004 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n" |
| 2005 | " UNION\n" |
| 2006 | " SELECT pid FROM plink\n" |
| 2007 | " WHERE cid IN rnfork\n" |
| 2008 | " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n" |
| 2009 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n", |
| 2010 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 2011 | ); |
| 2012 | tmFlags |= TIMELINE_UNHIDE; |
| 2013 | zType = "ci"; |
| 2014 | disableY = 1; |
| @@ -2206,77 +2081,114 @@ | |
| 2081 | PathNode *p = 0; |
| 2082 | const char *zFrom = 0; |
| 2083 | const char *zTo = 0; |
| 2084 | Blob ins; |
| 2085 | int nNodeOnPath = 0; |
| 2086 | int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */ |
| 2087 | int earlierRid = 0, laterRid = 0; |
| 2088 | |
| 2089 | if( from_rid && to_rid ){ |
| 2090 | if( from_to_mode==0 ){ |
| 2091 | p = path_shortest(from_rid, to_rid, noMerge, 0, 0); |
| 2092 | }else if( from_to_mode==1 ){ |
| 2093 | p = path_shortest(from_rid, to_rid, 0, 1, 0); |
| 2094 | earlierRid = commonAncs = from_rid; |
| 2095 | laterRid = to_rid; |
| 2096 | }else{ |
| 2097 | p = path_shortest(to_rid, from_rid, 0, 1, 0); |
| 2098 | earlierRid = commonAncs = to_rid; |
| 2099 | laterRid = from_rid; |
| 2100 | } |
| 2101 | zFrom = P("from"); |
| 2102 | zTo = zTo2 ? zTo2 : P("to"); |
| 2103 | }else{ |
| 2104 | commonAncs = path_common_ancestor(me_rid, you_rid); |
| 2105 | if( commonAncs!=0 ){ |
| 2106 | p = path_first(); |
| 2107 | } |
| 2108 | if( commonAncs==you_rid ){ |
| 2109 | zFrom = P("you"); |
| 2110 | zTo = P("me"); |
| 2111 | earlierRid = you_rid; |
| 2112 | laterRid = me_rid; |
| 2113 | }else{ |
| 2114 | zFrom = P("me"); |
| 2115 | zTo = P("you"); |
| 2116 | earlierRid = me_rid; |
| 2117 | laterRid = you_rid; |
| 2118 | } |
| 2119 | } |
| 2120 | blob_init(&ins, 0, 0); |
| 2121 | db_multi_exec( |
| 2122 | "CREATE TEMP TABLE IF NOT EXISTS pathnode(x INTEGER PRIMARY KEY);" |
| 2123 | ); |
| 2124 | if( p ){ |
| 2125 | int cnt = 4; |
| 2126 | blob_init(&ins, 0, 0); |
| 2127 | blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid); |
| 2128 | p = p->u.pTo; |
| 2129 | while( p ){ |
| 2130 | if( cnt==8 ){ |
| 2131 | blob_append_sql(&ins, ",\n (%d)", p->rid); |
| 2132 | cnt = 0; |
| 2133 | }else{ |
| 2134 | cnt++; |
| 2135 | blob_append_sql(&ins, ",(%d)", p->rid); |
| 2136 | } |
| 2137 | p = p->u.pTo; |
| 2138 | } |
| 2139 | } |
| 2140 | path_reset(); |
| 2141 | db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/); |
| 2142 | blob_reset(&ins); |
| 2143 | if( related ){ |
| 2144 | db_multi_exec( |
| 2145 | "CREATE TEMP TABLE IF NOT EXISTS related(x INTEGER PRIMARY KEY);" |
| 2146 | "INSERT OR IGNORE INTO related(x)" |
| 2147 | " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;" |
| 2148 | ); |
| 2149 | if( related==1 ){ |
| 2150 | db_multi_exec( |
| 2151 | "INSERT OR IGNORE INTO related(x)" |
| 2152 | " SELECT cid FROM plink WHERE pid IN pathnode;" |
| 2153 | ); |
| 2154 | } |
| 2155 | if( showCherrypicks ){ |
| 2156 | db_multi_exec( |
| 2157 | "INSERT OR IGNORE INTO related(x)" |
| 2158 | " SELECT parentid FROM cherrypick WHERE childid IN pathnode;" |
| 2159 | ); |
| 2160 | if( related==1 ){ |
| 2161 | db_multi_exec( |
| 2162 | "INSERT OR IGNORE INTO related(x)" |
| 2163 | " SELECT childid FROM cherrypick WHERE parentid IN pathnode;" |
| 2164 | ); |
| 2165 | } |
| 2166 | } |
| 2167 | if( earlierRid && laterRid && commonAncs==earlierRid ){ |
| 2168 | /* On a query with me=XXX, you=YYY, and rel, omit all nodes that |
| 2169 | ** are not ancestors of either XXX or YYY, as those nodes tend to |
| 2170 | ** be extraneous */ |
| 2171 | db_multi_exec( |
| 2172 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" |
| 2173 | ); |
| 2174 | compute_ancestors(laterRid, 0, 0, earlierRid); |
| 2175 | db_multi_exec( |
| 2176 | "DELETE FROM related WHERE x NOT IN ok;" |
| 2177 | ); |
| 2178 | } |
| 2179 | db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related"); |
| 2180 | } |
| 2181 | add_extra_rids("pathnode",P("x")); |
| 2182 | blob_append_sql(&sql, " AND event.objid IN pathnode"); |
| 2183 | if( zChng && zChng[0] ){ |
| 2184 | db_multi_exec( |
| 2185 | "DELETE FROM pathnode\n" |
| 2186 | " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename\n" |
| 2187 | " WHERE mlink.mid=x\n" |
| 2188 | " AND mlink.fnid=filename.fnid\n" |
| 2189 | " AND %s)", |
| 2190 | glob_expr("filename.name", zChng) |
| 2191 | ); |
| 2192 | } |
| 2193 | tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS; |
| 2194 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -2330,18 +2242,18 @@ | |
| 2242 | if( !haveParameterN ) nEntry = 10; |
| 2243 | } |
| 2244 | db_multi_exec( |
| 2245 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)" |
| 2246 | ); |
| 2247 | add_extra_rids("ok", P("x")); |
| 2248 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", |
| 2249 | p_rid ? p_rid : d_rid); |
| 2250 | zCiName = zDPName; |
| 2251 | if( zCiName==0 ) zCiName = zUuid; |
| 2252 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 2253 | nd = 0; |
| 2254 | if( d_rid ){ |
| 2255 | double rStopTime = 9e99; |
| 2256 | zFwdTo = P("ft"); |
| 2257 | if( zFwdTo ){ |
| 2258 | double rStartDate = db_double(0.0, |
| 2259 | "SELECT mtime FROM event WHERE objid=%d", d_rid); |
| @@ -2353,27 +2265,25 @@ | |
| 2265 | if( !haveParameterN ) nEntry = 0; |
| 2266 | rStopTime = db_double(9e99, |
| 2267 | "SELECT mtime FROM event WHERE objid=%d", ridFwdTo); |
| 2268 | } |
| 2269 | } |
| 2270 | if( rStopTime<9e99 ){ |
| 2271 | rStopTime += 5.8e-6; /* Round up by 1/2 second */ |
| 2272 | } |
| 2273 | db_multi_exec( |
| 2274 | "WITH RECURSIVE dx(rid,mtime) AS (\n" |
| 2275 | " SELECT %d, 0\n" |
| 2276 | " UNION\n" |
| 2277 | " SELECT plink.cid, plink.mtime FROM dx, plink\n" |
| 2278 | " WHERE plink.pid=dx.rid\n" |
| 2279 | " AND plink.mtime<=%.*g\n" |
| 2280 | " ORDER BY 2\n" |
| 2281 | ")\n" |
| 2282 | "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d", |
| 2283 | d_rid, rStopTime<8e99 ? 17 : 2, rStopTime, nEntry<=0 ? -1 : nEntry+1 |
| 2284 | ); |
| 2285 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 2286 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 2287 | if( nd>0 || p_rid==0 ){ |
| 2288 | blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 2289 | } |
| @@ -2482,67 +2392,76 @@ | |
| 2392 | if( zChng && *zChng ){ |
| 2393 | addFileGlobExclusion(zChng, &cond); |
| 2394 | tmFlags |= TIMELINE_XMERGE; |
| 2395 | } |
| 2396 | if( zUses ){ |
| 2397 | blob_append_sql(&cond, " AND event.objid IN usesfile\n"); |
| 2398 | } |
| 2399 | if( renameOnly ){ |
| 2400 | blob_append_sql(&cond, " AND event.objid IN rnfile\n"); |
| 2401 | } |
| 2402 | if( forkOnly ){ |
| 2403 | blob_append_sql(&cond, " AND event.objid IN rnfork\n"); |
| 2404 | } |
| 2405 | if( cpOnly && showCherrypicks ){ |
| 2406 | db_multi_exec( |
| 2407 | "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);" |
| 2408 | "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;" |
| 2409 | "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;" |
| 2410 | ); |
| 2411 | blob_append_sql(&cond, " AND event.objid IN cpnodes\n"); |
| 2412 | } |
| 2413 | if( bisectLocal || zBisect!=0 ){ |
| 2414 | blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog)\n"); |
| 2415 | } |
| 2416 | if( zYearMonth ){ |
| 2417 | char *zNext; |
| 2418 | int bZulu = 0; |
| 2419 | const char *zTZMod; |
| 2420 | zYearMonth = timeline_expand_datetime(zYearMonth, &bZulu); |
| 2421 | zYearMonth = mprintf("%.7s", zYearMonth); |
| 2422 | if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){ |
| 2423 | zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');"); |
| 2424 | } |
| 2425 | zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; |
| 2426 | if( db_int(0, |
| 2427 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2428 | " WHERE blob.rid=event.objid" |
| 2429 | " AND mtime>=julianday('%q-01',%Q)%s)", |
| 2430 | zYearMonth, zTZMod, blob_sql_text(&cond)) |
| 2431 | ){ |
| 2432 | zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','+1 month');", |
| 2433 | &"Z"[!bZulu], zYearMonth); |
| 2434 | zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); |
| 2435 | zNewerButtonLabel = "Following month"; |
| 2436 | fossil_free(zNext); |
| 2437 | } |
| 2438 | if( db_int(0, |
| 2439 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2440 | " WHERE blob.rid=event.objid" |
| 2441 | " AND mtime<julianday('%q-01',%Q)%s)", |
| 2442 | zYearMonth, zTZMod, blob_sql_text(&cond)) |
| 2443 | ){ |
| 2444 | zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','-1 month');", |
| 2445 | &"Z"[!bZulu], zYearMonth); |
| 2446 | zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0)); |
| 2447 | zOlderButtonLabel = "Previous month"; |
| 2448 | fossil_free(zNext); |
| 2449 | } |
| 2450 | blob_append_sql(&cond, |
| 2451 | " AND event.mtime>=julianday('%q-01',%Q)" |
| 2452 | " AND event.mtime<julianday('%q-01',%Q,'+1 month')\n", |
| 2453 | zYearMonth, zTZMod, zYearMonth, zTZMod); |
| 2454 | nEntry = -1; |
| 2455 | /* Adjust the zYearMonth for the title */ |
| 2456 | zYearMonth = mprintf("%z-01%s", zYearMonth, &"Z"[!bZulu]); |
| 2457 | } |
| 2458 | else if( zYearWeek ){ |
| 2459 | char *z, *zNext; |
| 2460 | int bZulu = 0; |
| 2461 | const char *zTZMod; |
| 2462 | zYearWeek = timeline_expand_datetime(zYearWeek, &bZulu); |
| 2463 | z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek); |
| 2464 | if( z && z[0] ){ |
| 2465 | zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')", |
| 2466 | zYearWeek); |
| 2467 | zYearWeek = z; |
| @@ -2559,64 +2478,152 @@ | |
| 2478 | "SELECT date('now','-6 days','weekday 1');"); |
| 2479 | zYearWeek = db_text(0, |
| 2480 | "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')"); |
| 2481 | } |
| 2482 | } |
| 2483 | zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; |
| 2484 | if( db_int(0, |
| 2485 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2486 | " WHERE blob.rid=event.objid" |
| 2487 | " AND mtime>=julianday(%Q,%Q)%s)", |
| 2488 | zYearWeekStart, zTZMod, blob_sql_text(&cond)) |
| 2489 | ){ |
| 2490 | zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'+7 day');", |
| 2491 | &"Z"[!bZulu], zYearWeekStart); |
| 2492 | zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); |
| 2493 | zNewerButtonLabel = "Following week"; |
| 2494 | fossil_free(zNext); |
| 2495 | } |
| 2496 | if( db_int(0, |
| 2497 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2498 | " WHERE blob.rid=event.objid" |
| 2499 | " AND mtime<julianday(%Q,%Q)%s)", |
| 2500 | zYearWeekStart, zTZMod, blob_sql_text(&cond)) |
| 2501 | ){ |
| 2502 | zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'-7 days');", |
| 2503 | &"Z"[!bZulu], zYearWeekStart); |
| 2504 | zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0)); |
| 2505 | zOlderButtonLabel = "Previous week"; |
| 2506 | fossil_free(zNext); |
| 2507 | } |
| 2508 | blob_append_sql(&cond, |
| 2509 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2510 | " AND event.mtime<julianday(%Q,%Q,'+7 days')\n", |
| 2511 | zYearWeekStart, zTZMod, zYearWeekStart, zTZMod); |
| 2512 | nEntry = -1; |
| 2513 | if( fossil_ui_localtime() && bZulu ){ |
| 2514 | zYearWeekStart = mprintf("%zZ", zYearWeekStart); |
| 2515 | } |
| 2516 | } |
| 2517 | else if( zDay && timeline_is_datespan(zDay) ){ |
| 2518 | char *zNext; |
| 2519 | char *zStart, *zEnd; |
| 2520 | int nDay; |
| 2521 | int bZulu = 0; |
| 2522 | const char *zTZMod; |
| 2523 | zEnd = db_text(0, "SELECT date(%Q)", |
| 2524 | timeline_expand_datetime(zDay+9, &bZulu)); |
| 2525 | zStart = db_text(0, "SELECT date('%.4q-%.2q-%.2q')", |
| 2526 | zDay, zDay+4, zDay+6); |
| 2527 | nDay = db_int(0, "SELECT julianday(%Q)-julianday(%Q)", zEnd, zStart); |
| 2528 | if( nDay==0 ){ |
| 2529 | zDay = &zDay[9]; |
| 2530 | goto single_ymd; |
| 2531 | } |
| 2532 | if( nDay<0 ){ |
| 2533 | char *zTemp = zEnd; |
| 2534 | zEnd = zStart; |
| 2535 | zStart = zTemp; |
| 2536 | nDay = 1 - nDay; |
| 2537 | }else{ |
| 2538 | nDay += 1; |
| 2539 | } |
| 2540 | zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; |
| 2541 | if( nDay>0 && db_int(0, |
| 2542 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2543 | " WHERE blob.rid=event.objid" |
| 2544 | " AND mtime>=julianday(%Q,'1 day',%Q)%s)", |
| 2545 | zEnd, zTZMod, blob_sql_text(&cond)) |
| 2546 | ){ |
| 2547 | zNext = db_text(0, |
| 2548 | "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||" |
| 2549 | "strftime('%%Y%%m%%d%q',%Q,'%d day');", |
| 2550 | zStart, nDay, &"Z"[!bZulu], zEnd, nDay); |
| 2551 | zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2552 | zNewerButtonLabel = mprintf("Following %d days", nDay); |
| 2553 | fossil_free(zNext); |
| 2554 | } |
| 2555 | if( nDay>1 && db_int(0, |
| 2556 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2557 | " WHERE blob.rid=event.objid" |
| 2558 | " AND mtime<julianday(%Q,'-1 day',%Q)%s)", |
| 2559 | zStart, zTZMod, blob_sql_text(&cond)) |
| 2560 | ){ |
| 2561 | zNext = db_text(0, |
| 2562 | "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||" |
| 2563 | "strftime('%%Y%%m%%d%q',%Q,'%d day');", |
| 2564 | zStart, -nDay, &"Z"[!bZulu], zEnd, -nDay); |
| 2565 | zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2566 | zOlderButtonLabel = mprintf("Previous %d days", nDay); |
| 2567 | fossil_free(zNext); |
| 2568 | } |
| 2569 | blob_append_sql(&cond, |
| 2570 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2571 | " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", |
| 2572 | zStart, zTZMod, zEnd, zTZMod); |
| 2573 | nEntry = -1; |
| 2574 | |
| 2575 | if( fossil_ui_localtime() && bZulu ){ |
| 2576 | zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd); |
| 2577 | }else{ |
| 2578 | zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd); |
| 2579 | } |
| 2580 | } |
| 2581 | else if( zDay ){ |
| 2582 | char *zNext; |
| 2583 | int bZulu = 0; |
| 2584 | const char *zTZMod; |
| 2585 | single_ymd: |
| 2586 | bZulu = 0; |
| 2587 | zDay = timeline_expand_datetime(zDay, &bZulu); |
| 2588 | zDay = db_text(0, "SELECT date(%Q)", zDay); |
| 2589 | if( zDay==0 || zDay[0]==0 ){ |
| 2590 | zDay = db_text(0, "SELECT date('now')"); |
| 2591 | } |
| 2592 | zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00"; |
| 2593 | if( db_int(0, |
| 2594 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2595 | " WHERE blob.rid=event.objid" |
| 2596 | " AND mtime>=julianday(%Q,'+1 day',%Q)%s)", |
| 2597 | zDay, zTZMod, blob_sql_text(&cond)) |
| 2598 | ){ |
| 2599 | zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'+1 day');", |
| 2600 | &"Z"[!bZulu], zDay); |
| 2601 | zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2602 | zNewerButtonLabel = "Following day"; |
| 2603 | fossil_free(zNext); |
| 2604 | } |
| 2605 | if( db_int(0, |
| 2606 | "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob" |
| 2607 | " WHERE blob.rid=event.objid" |
| 2608 | " AND mtime<julianday(%Q,'-1 day',%Q)%s)", |
| 2609 | zDay, zTZMod, blob_sql_text(&cond)) |
| 2610 | ){ |
| 2611 | zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'-1 day');", |
| 2612 | &"Z"[!bZulu], zDay); |
| 2613 | zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0)); |
| 2614 | zOlderButtonLabel = "Previous day"; |
| 2615 | fossil_free(zNext); |
| 2616 | } |
| 2617 | blob_append_sql(&cond, |
| 2618 | " AND event.mtime>=julianday(%Q,%Q)" |
| 2619 | " AND event.mtime<julianday(%Q,%Q,'+1 day')\n", |
| 2620 | zDay, zTZMod, zDay, zTZMod); |
| 2621 | nEntry = -1; |
| 2622 | if( fossil_ui_localtime() && bZulu ){ |
| 2623 | zDay = mprintf("%zZ", zDay); /* Add Z suffix to day for the title */ |
| 2624 | } |
| 2625 | } |
| 2626 | else if( zNDays ){ |
| 2627 | nDays = atoi(zNDays); |
| 2628 | if( nDays<1 ) nDays = 1; |
| 2629 | blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ", |
| @@ -2662,39 +2669,24 @@ | |
| 2669 | nEntry = -1; |
| 2670 | } |
| 2671 | if( zTagSql ){ |
| 2672 | db_multi_exec( |
| 2673 | "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);" |
| 2674 | "INSERT OR IGNORE INTO selected_nodes\n" |
| 2675 | " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag\n" |
| 2676 | " WHERE tagtype>0\n" |
| 2677 | " AND %s", zTagSql/*safe-for-%s*/ |
| 2678 | ); |
| 2679 | if( zMark ){ |
| 2680 | /* If the t=release option is used with m=UUID, then also |
| 2681 | ** include the UUID check-in in the display list */ |
| 2682 | int ridMark = name_to_rid(zMark); |
| 2683 | db_multi_exec( |
| 2684 | "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark); |
| 2685 | } |
| 2686 | add_extra_rids("selected_nodes",P("x")); |
| 2687 | if( related==0 ){ |
| 2688 | blob_append_sql(&cond, " AND blob.rid IN selected_nodes"); |
| 2689 | }else{ |
| 2690 | db_multi_exec( |
| 2691 | "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);" |
| 2692 | "INSERT INTO related_nodes SELECT rid FROM selected_nodes;" |
| @@ -2705,40 +2697,42 @@ | |
| 2697 | ** branch to be included in the report. These related check-ins are |
| 2698 | ** useful in helping to visualize what has happened on a quiescent |
| 2699 | ** branch that is infrequently merged with a much more activate branch. |
| 2700 | */ |
| 2701 | db_multi_exec( |
| 2702 | "INSERT OR IGNORE INTO related_nodes\n" |
| 2703 | " SELECT pid FROM selected_nodes CROSS JOIN plink\n" |
| 2704 | " WHERE selected_nodes.rid=plink.cid;" |
| 2705 | ); |
| 2706 | if( related==1 ){ |
| 2707 | db_multi_exec( |
| 2708 | "INSERT OR IGNORE INTO related_nodes\n" |
| 2709 | " SELECT cid FROM selected_nodes CROSS JOIN plink\n" |
| 2710 | " WHERE selected_nodes.rid=plink.pid;" |
| 2711 | ); |
| 2712 | if( showCherrypicks ){ |
| 2713 | db_multi_exec( |
| 2714 | "INSERT OR IGNORE INTO related_nodes\n" |
| 2715 | " SELECT childid FROM selected_nodes CROSS JOIN cherrypick\n" |
| 2716 | " WHERE selected_nodes.rid=cherrypick.parentid;" |
| 2717 | ); |
| 2718 | } |
| 2719 | } |
| 2720 | if( showCherrypicks ){ |
| 2721 | db_multi_exec( |
| 2722 | "INSERT OR IGNORE INTO related_nodes\n" |
| 2723 | " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick\n" |
| 2724 | " WHERE selected_nodes.rid=cherrypick.childid;" |
| 2725 | ); |
| 2726 | } |
| 2727 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 2728 | db_multi_exec( |
| 2729 | "DELETE FROM related_nodes\n" |
| 2730 | " WHERE rid IN (SELECT related_nodes.rid\n" |
| 2731 | " FROM related_nodes, tagxref\n" |
| 2732 | " WHERE tagid=%d AND tagtype>0\n" |
| 2733 | " AND tagxref.rid=related_nodes.rid)", |
| 2734 | TAG_HIDDEN |
| 2735 | ); |
| 2736 | } |
| 2737 | } |
| 2738 | } |
| @@ -2828,45 +2822,42 @@ | |
| 2822 | rCirca = symbolic_name_to_mtime(zCirca, &zCirca); |
| 2823 | blob_append_sql(&sql, "%s", blob_sql_text(&cond)); |
| 2824 | if( rAfter>0.0 ){ |
| 2825 | if( rBefore>0.0 ){ |
| 2826 | blob_append_sql(&sql, |
| 2827 | " AND event.mtime>=%.17g AND event.mtime<=%.17g\n" |
| 2828 | " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND); |
| 2829 | nEntry = -1; |
| 2830 | }else{ |
| 2831 | blob_append_sql(&sql, |
| 2832 | " AND event.mtime>=%.17g\n ORDER BY event.mtime ASC", |
| 2833 | rAfter-ONE_SECOND); |
| 2834 | } |
| 2835 | zCirca = 0; |
| 2836 | url_add_parameter(&url, "c", 0); |
| 2837 | }else if( rBefore>0.0 ){ |
| 2838 | blob_append_sql(&sql, |
| 2839 | " AND event.mtime<=%.17g\n ORDER BY event.mtime DESC", |
| 2840 | rBefore+ONE_SECOND); |
| 2841 | zCirca = 0; |
| 2842 | url_add_parameter(&url, "c", 0); |
| 2843 | }else if( rCirca>0.0 ){ |
| 2844 | Blob sql2; |
| 2845 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 2846 | blob_append_sql(&sql2, |
| 2847 | " AND event.mtime>=%f\n ORDER BY event.mtime ASC", rCirca); |
| 2848 | if( nEntry>0 ){ |
| 2849 | blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2); |
| 2850 | } |
| 2851 | db_multi_exec("%s", blob_sql_text(&sql2)); |
| 2852 | if( nEntry>0 ){ |
| 2853 | nEntry -= db_int(0,"select count(*) from timeline"); |
| 2854 | if( nEntry<=0 ) nEntry = 1; |
| 2855 | } |
| 2856 | blob_reset(&sql2); |
| 2857 | blob_append_sql(&sql, |
| 2858 | " AND event.mtime<=%f\n ORDER BY event.mtime DESC", |
| 2859 | rCirca |
| 2860 | ); |
| 2861 | if( zMark==0 ) zMark = zCirca; |
| 2862 | }else{ |
| 2863 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| @@ -2875,11 +2866,11 @@ | |
| 2866 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2867 | |
| 2868 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 2869 | zPlural = n==1 ? "" : "s"; |
| 2870 | if( zYearMonth ){ |
| 2871 | blob_appendf(&desc, "%d %s%s for the month beginning %h", |
| 2872 | n, zEType, zPlural, zYearMonth); |
| 2873 | }else if( zYearWeek ){ |
| 2874 | blob_appendf(&desc, "%d %s%s for week %h beginning on %h", |
| 2875 | n, zEType, zPlural, zYearWeek, zYearWeekStart); |
| 2876 | }else if( zDay ){ |
| @@ -3009,12 +3000,14 @@ | |
| 3000 | style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0); |
| 3001 | } |
| 3002 | } |
| 3003 | blob_zero(&cond); |
| 3004 | } |
| 3005 | if( showSql ){ |
| 3006 | db_append_dml_to_blob(0); |
| 3007 | @ <pre>%h(blob_str(&allSql))</pre> |
| 3008 | blob_reset(&allSql); |
| 3009 | } |
| 3010 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 3011 | style_submenu_element("Search", "%R/search?y=c"); |
| 3012 | } |
| 3013 | if( advancedMenu ){ |
| @@ -3068,18 +3061,36 @@ | |
| 3061 | if( zNewerButton ){ |
| 3062 | @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\ |
| 3063 | @ ↑</a> |
| 3064 | } |
| 3065 | cgi_check_for_malice(); |
| 3066 | { |
| 3067 | Matcher *pLeftBranch; |
| 3068 | const char *zPattern = P("sl"); |
| 3069 | if( zPattern!=0 ){ |
| 3070 | MatchStyle ms; |
| 3071 | if( zMatchStyle!=0 ){ |
| 3072 | ms = matchStyle; |
| 3073 | }else{ |
| 3074 | ms = strpbrk(zPattern,"*[?")!=0 ? MS_GLOB : MS_BRLIST; |
| 3075 | } |
| 3076 | pLeftBranch = match_create(ms,zPattern); |
| 3077 | }else{ |
| 3078 | pLeftBranch = match_create(matchStyle, zBrName?zBrName:zTagName); |
| 3079 | } |
| 3080 | www_print_timeline(&q, tmFlags, zThisUser, zThisTag, pLeftBranch, |
| 3081 | selectedRid, secondaryRid, 0); |
| 3082 | match_free(pLeftBranch); |
| 3083 | } |
| 3084 | db_finalize(&q); |
| 3085 | if( zOlderButton ){ |
| 3086 | @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\ |
| 3087 | @ ↓</a> |
| 3088 | } |
| 3089 | document_emit_js(/*handles pikchrs rendered above*/); |
| 3090 | blob_reset(&sql); |
| 3091 | blob_reset(&desc); |
| 3092 | style_finish_page(); |
| 3093 | } |
| 3094 | |
| 3095 | /* |
| 3096 | ** Translate a timeline entry into the printable format by |
| @@ -3744,10 +3755,11 @@ | |
| 3755 | const char *zToday; |
| 3756 | char *zStartOfProject; |
| 3757 | int i; |
| 3758 | Stmt q; |
| 3759 | char *z; |
| 3760 | int bZulu = 0; |
| 3761 | |
| 3762 | login_check_credentials(); |
| 3763 | if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){ |
| 3764 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 3765 | return; |
| @@ -3754,11 +3766,11 @@ | |
| 3766 | } |
| 3767 | style_set_current_feature("timeline"); |
| 3768 | style_header("Today In History"); |
| 3769 | zToday = (char*)P("today"); |
| 3770 | if( zToday ){ |
| 3771 | zToday = timeline_expand_datetime(zToday, &bZulu); |
| 3772 | if( !fossil_isdate(zToday) ) zToday = 0; |
| 3773 | } |
| 3774 | if( zToday==0 ){ |
| 3775 | zToday = db_text(0, "SELECT date('now',toLocal())"); |
| 3776 | } |
| 3777 |
+1
-1
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -781,11 +781,11 @@ | ||
| 781 | 781 | |
| 782 | 782 | zFullName = db_text(0, |
| 783 | 783 | "SELECT tkt_uuid FROM ticket" |
| 784 | 784 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 785 | 785 | if( zFullName ){ |
| 786 | - attachment_list(zFullName, "<hr><h2>Attachments:</h2><ul>"); | |
| 786 | + attachment_list(zFullName, "<h2>Attachments:</h2>", 1); | |
| 787 | 787 | } |
| 788 | 788 | |
| 789 | 789 | style_finish_page(); |
| 790 | 790 | } |
| 791 | 791 | |
| 792 | 792 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -781,11 +781,11 @@ | |
| 781 | |
| 782 | zFullName = db_text(0, |
| 783 | "SELECT tkt_uuid FROM ticket" |
| 784 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 785 | if( zFullName ){ |
| 786 | attachment_list(zFullName, "<hr><h2>Attachments:</h2><ul>"); |
| 787 | } |
| 788 | |
| 789 | style_finish_page(); |
| 790 | } |
| 791 | |
| 792 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -781,11 +781,11 @@ | |
| 781 | |
| 782 | zFullName = db_text(0, |
| 783 | "SELECT tkt_uuid FROM ticket" |
| 784 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 785 | if( zFullName ){ |
| 786 | attachment_list(zFullName, "<h2>Attachments:</h2>", 1); |
| 787 | } |
| 788 | |
| 789 | style_finish_page(); |
| 790 | } |
| 791 | |
| 792 |
+71
-13
| --- src/update.c | ||
| +++ src/update.c | ||
| @@ -132,10 +132,13 @@ | ||
| 132 | 132 | int nUpdate = 0; /* Number of changes of any kind */ |
| 133 | 133 | int bNosync = 0; /* --nosync. Omit the auto-sync */ |
| 134 | 134 | int width; /* Width of printed comment lines */ |
| 135 | 135 | Stmt mtimeXfer; /* Statement to transfer mtimes */ |
| 136 | 136 | const char *zWidth; /* Width option string value */ |
| 137 | + const char *zCurBrName; /* Current branch name */ | |
| 138 | + const char *zNewBrName; /* New branch name */ | |
| 139 | + const char *zBrChgMsg = ""; /* Message to display if branch changes */ | |
| 137 | 140 | |
| 138 | 141 | if( !internalUpdate ){ |
| 139 | 142 | undo_capture_command_line(); |
| 140 | 143 | url_proxy_options(); |
| 141 | 144 | } |
| @@ -163,10 +166,11 @@ | ||
| 163 | 166 | /* We should be done with options.. */ |
| 164 | 167 | verify_all_options(); |
| 165 | 168 | |
| 166 | 169 | db_must_be_within_tree(); |
| 167 | 170 | vid = db_lget_int("checkout", 0); |
| 171 | + zCurBrName = branch_of_rid(vid); | |
| 168 | 172 | user_select(); |
| 169 | 173 | if( !dryRunFlag && !internalUpdate && !bNosync ){ |
| 170 | 174 | if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){ |
| 171 | 175 | fossil_fatal("update abandoned due to sync failure"); |
| 172 | 176 | } |
| @@ -403,10 +407,11 @@ | ||
| 403 | 407 | " WHERE id=:idt" |
| 404 | 408 | ); |
| 405 | 409 | assert( g.zLocalRoot!=0 ); |
| 406 | 410 | assert( strlen(g.zLocalRoot)>0 ); |
| 407 | 411 | assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); |
| 412 | + merge_info_init(); | |
| 408 | 413 | while( db_step(&q)==SQLITE_ROW ){ |
| 409 | 414 | const char *zName = db_column_text(&q, 0); /* The filename from root */ |
| 410 | 415 | int idv = db_column_int(&q, 1); /* VFILE entry for current */ |
| 411 | 416 | int ridv = db_column_int(&q, 2); /* RecordID for current */ |
| 412 | 417 | int idt = db_column_int(&q, 3); /* VFILE entry for target */ |
| @@ -418,13 +423,18 @@ | ||
| 418 | 423 | int islinkt = db_column_int(&q, 9); /* Is target file is a link */ |
| 419 | 424 | int deleted = db_column_int(&q, 10); /* Marked for deletion */ |
| 420 | 425 | char *zFullPath; /* Full pathname of the file */ |
| 421 | 426 | char *zFullNewPath; /* Full pathname of dest */ |
| 422 | 427 | char nameChng; /* True if the name changed */ |
| 428 | + const char *zOp = 0; /* Type of change. */ | |
| 429 | + i64 sz = 0; /* Size of the file */ | |
| 430 | + int nc = 0; /* Number of conflicts */ | |
| 431 | + const char *zErrMsg = 0; /* Error message */ | |
| 423 | 432 | |
| 424 | 433 | zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 425 | 434 | zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); |
| 435 | + sz = file_size(zFullNewPath, ExtFILE); | |
| 426 | 436 | nameChng = fossil_strcmp(zName, zNewName); |
| 427 | 437 | nUpdate++; |
| 428 | 438 | if( deleted ){ |
| 429 | 439 | db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt); |
| 430 | 440 | } |
| @@ -432,10 +442,13 @@ | ||
| 432 | 442 | /* Conflict. This file has been added to the current check-out |
| 433 | 443 | ** but also exists in the target check-out. Use the current version. |
| 434 | 444 | */ |
| 435 | 445 | fossil_print("CONFLICT %s\n", zName); |
| 436 | 446 | nConflict++; |
| 447 | + zOp = "CONFLICT"; | |
| 448 | + nc = 1; | |
| 449 | + zErrMsg = "duplicate file"; | |
| 437 | 450 | }else if( idt>0 && idv==0 ){ |
| 438 | 451 | /* File added in the target. */ |
| 439 | 452 | if( file_isfile_or_link(zFullPath) ){ |
| 440 | 453 | /* Name of backup file with Original content */ |
| 441 | 454 | char *zOrig = file_newname(zFullPath, "original", 1); |
| @@ -444,10 +457,13 @@ | ||
| 444 | 457 | fossil_free(zOrig); |
| 445 | 458 | fossil_print("ADD %s - overwrites an unmanaged file", zName); |
| 446 | 459 | if( !dryRunFlag ) fossil_print(", original copy backed up locally"); |
| 447 | 460 | fossil_print("\n"); |
| 448 | 461 | nOverwrite++; |
| 462 | + nc = 1; | |
| 463 | + zOp = "CONFLICT"; | |
| 464 | + zErrMsg = "new file overwrites unmanaged file"; | |
| 449 | 465 | }else{ |
| 450 | 466 | fossil_print("ADD %s\n", zName); |
| 451 | 467 | } |
| 452 | 468 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 453 | 469 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| @@ -458,16 +474,18 @@ | ||
| 458 | 474 | }else{ |
| 459 | 475 | fossil_print("UPDATE %s\n", zName); |
| 460 | 476 | } |
| 461 | 477 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 462 | 478 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 479 | + zOp = "UPDATE"; | |
| 463 | 480 | }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){ |
| 464 | 481 | /* The file missing from the local check-out. Restore it to the |
| 465 | 482 | ** version that appears in the target. */ |
| 466 | 483 | fossil_print("UPDATE %s\n", zName); |
| 467 | 484 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 468 | 485 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 486 | + zOp = "UPDATE"; | |
| 469 | 487 | }else if( idt==0 && idv>0 ){ |
| 470 | 488 | if( ridv==0 ){ |
| 471 | 489 | /* Added in current check-out. Continue to hold the file as |
| 472 | 490 | ** as an addition */ |
| 473 | 491 | db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv); |
| @@ -474,10 +492,13 @@ | ||
| 474 | 492 | }else if( chnged ){ |
| 475 | 493 | /* Edited locally but deleted from the target. Do not track the |
| 476 | 494 | ** file but keep the edited version around. */ |
| 477 | 495 | fossil_print("CONFLICT %s - edited locally but deleted by update\n", |
| 478 | 496 | zName); |
| 497 | + zOp = "CONFLICT"; | |
| 498 | + zErrMsg = "edited locally but deleted by update"; | |
| 499 | + nc = 1; | |
| 479 | 500 | nConflict++; |
| 480 | 501 | }else{ |
| 481 | 502 | fossil_print("REMOVE %s\n", zName); |
| 482 | 503 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 483 | 504 | if( !dryRunFlag ){ |
| @@ -493,22 +514,23 @@ | ||
| 493 | 514 | zDir = zNext; |
| 494 | 515 | } |
| 495 | 516 | } |
| 496 | 517 | } |
| 497 | 518 | }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){ |
| 498 | - /* Merge the changes in the current tree into the target version */ | |
| 499 | - Blob r, t, v; | |
| 500 | - int rc; | |
| 501 | 519 | if( nameChng ){ |
| 502 | 520 | fossil_print("MERGE %s -> %s\n", zName, zNewName); |
| 503 | 521 | }else{ |
| 504 | 522 | fossil_print("MERGE %s\n", zName); |
| 505 | 523 | } |
| 506 | 524 | if( islinkv || islinkt ){ |
| 507 | 525 | fossil_print("***** Cannot merge symlink %s\n", zNewName); |
| 526 | + zOp = "CONFLICT"; | |
| 508 | 527 | nConflict++; |
| 509 | 528 | }else{ |
| 529 | + /* Merge the changes in the current tree into the target version */ | |
| 530 | + Blob r, t, v; | |
| 531 | + int rc; | |
| 510 | 532 | unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; |
| 511 | 533 | if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; |
| 512 | 534 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 513 | 535 | content_get(ridt, &t); |
| 514 | 536 | content_get(ridv, &v); |
| @@ -517,12 +539,17 @@ | ||
| 517 | 539 | if( !dryRunFlag ){ |
| 518 | 540 | blob_write_to_file(&r, zFullNewPath); |
| 519 | 541 | file_setexe(zFullNewPath, isexe); |
| 520 | 542 | } |
| 521 | 543 | if( rc>0 ){ |
| 544 | + nc = rc; | |
| 545 | + zOp = "CONFLICT"; | |
| 546 | + zErrMsg = "merge conflicts"; | |
| 522 | 547 | fossil_print("***** %d merge conflicts in %s\n", rc, zNewName); |
| 523 | 548 | nConflict++; |
| 549 | + }else{ | |
| 550 | + zOp = "MERGE"; | |
| 524 | 551 | } |
| 525 | 552 | }else{ |
| 526 | 553 | if( !dryRunFlag ){ |
| 527 | 554 | if( !keepMergeFlag ){ |
| 528 | 555 | /* Name of backup file with Original content */ |
| @@ -539,16 +566,19 @@ | ||
| 539 | 566 | if( !dryRunFlag ){ |
| 540 | 567 | fossil_print(", original copy backed up locally"); |
| 541 | 568 | } |
| 542 | 569 | fossil_print("\n"); |
| 543 | 570 | nConflict++; |
| 571 | + zOp = "ERROR"; | |
| 572 | + zErrMsg = "cannot merge binary file"; | |
| 573 | + nc = 1; | |
| 544 | 574 | } |
| 575 | + blob_reset(&v); | |
| 576 | + blob_reset(&t); | |
| 577 | + blob_reset(&r); | |
| 545 | 578 | } |
| 546 | 579 | if( nameChng && !dryRunFlag ) file_delete(zFullPath); |
| 547 | - blob_reset(&v); | |
| 548 | - blob_reset(&t); | |
| 549 | - blob_reset(&r); | |
| 550 | 580 | }else{ |
| 551 | 581 | nUpdate--; |
| 552 | 582 | if( chnged ){ |
| 553 | 583 | if( verboseFlag ) fossil_print("EDITED %s\n", zName); |
| 554 | 584 | }else{ |
| @@ -556,27 +586,48 @@ | ||
| 556 | 586 | db_bind_int(&mtimeXfer, ":idt", idt); |
| 557 | 587 | db_step(&mtimeXfer); |
| 558 | 588 | db_reset(&mtimeXfer); |
| 559 | 589 | if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName); |
| 560 | 590 | } |
| 591 | + } | |
| 592 | + if( zOp!=0 ){ | |
| 593 | + db_multi_exec( | |
| 594 | + "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" | |
| 595 | + "VALUES(%Q,%Q,%d,%Q,NULL,%lld,%Q,%d,%Q,%d,%Q)", | |
| 596 | + /* op */ zOp, | |
| 597 | + /* fnp */ zName, | |
| 598 | + /* ridp */ ridv, | |
| 599 | + /* fn */ zNewName, | |
| 600 | + /* sz */ sz, | |
| 601 | + /* fnm */ zName, | |
| 602 | + /* ridm */ ridt, | |
| 603 | + /* fnr */ zNewName, | |
| 604 | + /* nc */ nc, | |
| 605 | + /* msg */ zErrMsg | |
| 606 | + ); | |
| 561 | 607 | } |
| 562 | 608 | free(zFullPath); |
| 563 | 609 | free(zFullNewPath); |
| 564 | 610 | } |
| 565 | 611 | db_finalize(&q); |
| 566 | 612 | db_finalize(&mtimeXfer); |
| 567 | 613 | fossil_print("%.79c\n",'-'); |
| 614 | + zNewBrName = branch_of_rid(tid); | |
| 615 | + if( g.argc<3 && fossil_strcmp(zCurBrName, zNewBrName)!=0 ){ | |
| 616 | + zBrChgMsg = mprintf(" Branch changed from %s to %s.", | |
| 617 | + zCurBrName, zNewBrName); | |
| 618 | + } | |
| 568 | 619 | if( nUpdate==0 ){ |
| 569 | 620 | show_common_info(tid, "checkout:", 1, 0); |
| 570 | - fossil_print("%-13s None. Already up-to-date\n", "changes:"); | |
| 621 | + fossil_print("%-13s None. Already up-to-date.%s\n", "changes:", zBrChgMsg); | |
| 571 | 622 | }else{ |
| 572 | 623 | fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid), |
| 573 | 624 | db_text("", "SELECT datetime(mtime) || ' UTC' FROM event " |
| 574 | 625 | " WHERE objid=%d", vid)); |
| 575 | 626 | show_common_info(tid, "updated-to:", 1, 0); |
| 576 | - fossil_print("%-13s %d file%s modified.\n", "changes:", | |
| 577 | - nUpdate, nUpdate>1 ? "s" : ""); | |
| 627 | + fossil_print("%-13s %d file%s modified.%s\n", "changes:", | |
| 628 | + nUpdate, nUpdate>1 ? "s" : "", zBrChgMsg); | |
| 578 | 629 | } |
| 579 | 630 | |
| 580 | 631 | /* Report on conflicts |
| 581 | 632 | */ |
| 582 | 633 | if( !dryRunFlag ){ |
| @@ -802,10 +853,11 @@ | ||
| 802 | 853 | ** |
| 803 | 854 | ** If a file is reverted accidentally, it can be restored using |
| 804 | 855 | ** the "fossil undo" command. |
| 805 | 856 | ** |
| 806 | 857 | ** Options: |
| 858 | +** --noundo Do not record changes in the undo/redo log. | |
| 807 | 859 | ** -r|--revision VERSION Revert given FILE(s) back to given |
| 808 | 860 | ** VERSION |
| 809 | 861 | ** |
| 810 | 862 | ** See also: [[redo]], [[undo]], [[checkout]], [[update]] |
| 811 | 863 | */ |
| @@ -815,17 +867,19 @@ | ||
| 815 | 867 | ManifestFile *pCoFile; /* File within current check-out manifest */ |
| 816 | 868 | ManifestFile *pRvFile; /* File within revert version manifest */ |
| 817 | 869 | const char *zFile; /* Filename relative to check-out root */ |
| 818 | 870 | const char *zRevision; /* Selected revert version, NULL if current */ |
| 819 | 871 | Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */ |
| 872 | + int useUndo = 1; /* True to record changes in UNDO */ | |
| 820 | 873 | int i; |
| 821 | 874 | Stmt q; |
| 822 | 875 | int revertAll = 0; |
| 823 | 876 | int revisionOptNotSupported = 0; |
| 824 | 877 | |
| 825 | 878 | undo_capture_command_line(); |
| 826 | 879 | zRevision = find_option("revision", "r", 1); |
| 880 | + useUndo = find_option("noundo", 0, 0)==0; | |
| 827 | 881 | verify_all_options(); |
| 828 | 882 | |
| 829 | 883 | if( g.argc<2 ){ |
| 830 | 884 | usage("?OPTIONS? [FILE] ..."); |
| 831 | 885 | } |
| @@ -838,11 +892,15 @@ | ||
| 838 | 892 | /* Get manifests of revert version and (if different) current check-out. */ |
| 839 | 893 | pRvManifest = historical_manifest(zRevision); |
| 840 | 894 | pCoManifest = zRevision ? historical_manifest(0) : 0; |
| 841 | 895 | |
| 842 | 896 | db_begin_transaction(); |
| 843 | - undo_begin(); | |
| 897 | + if( useUndo ){ | |
| 898 | + undo_begin(); | |
| 899 | + }else{ | |
| 900 | + undo_reset(); | |
| 901 | + } | |
| 844 | 902 | db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);"); |
| 845 | 903 | |
| 846 | 904 | if( g.argc>2 ){ |
| 847 | 905 | for(i=2; i<g.argc; i++){ |
| 848 | 906 | Blob fname; |
| @@ -935,11 +993,11 @@ | ||
| 935 | 993 | if( !pRvFile ){ |
| 936 | 994 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", |
| 937 | 995 | zFile, zFile)==0 ){ |
| 938 | 996 | fossil_print("UNMANAGE %s\n", zFile); |
| 939 | 997 | }else{ |
| 940 | - undo_save(zFile); | |
| 998 | + if( useUndo ) undo_save(zFile); | |
| 941 | 999 | file_delete(zFull); |
| 942 | 1000 | fossil_print("DELETE %s\n", zFile); |
| 943 | 1001 | } |
| 944 | 1002 | db_multi_exec( |
| 945 | 1003 | "UPDATE OR REPLACE vfile" |
| @@ -962,11 +1020,11 @@ | ||
| 962 | 1020 | } |
| 963 | 1021 | |
| 964 | 1022 | /* Get contents of reverted-to file. */ |
| 965 | 1023 | content_get(fast_uuid_to_rid(pRvFile->zUuid), &record); |
| 966 | 1024 | |
| 967 | - undo_save(zFile); | |
| 1025 | + if( useUndo ) undo_save(zFile); | |
| 968 | 1026 | if( file_size(zFull, RepoFILE)>=0 |
| 969 | 1027 | && (rvPerm==PERM_LNK || file_islink(0)) |
| 970 | 1028 | ){ |
| 971 | 1029 | file_delete(zFull); |
| 972 | 1030 | } |
| @@ -988,12 +1046,12 @@ | ||
| 988 | 1046 | } |
| 989 | 1047 | blob_reset(&record); |
| 990 | 1048 | free(zFull); |
| 991 | 1049 | } |
| 992 | 1050 | db_finalize(&q); |
| 993 | - undo_finish(); | |
| 1051 | + if( useUndo) undo_finish(); | |
| 994 | 1052 | db_end_transaction(0); |
| 995 | 1053 | |
| 996 | 1054 | /* Deallocate parsed manifest structures. */ |
| 997 | 1055 | manifest_destroy(pRvManifest); |
| 998 | 1056 | manifest_destroy(pCoManifest); |
| 999 | 1057 | } |
| 1000 | 1058 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -132,10 +132,13 @@ | |
| 132 | int nUpdate = 0; /* Number of changes of any kind */ |
| 133 | int bNosync = 0; /* --nosync. Omit the auto-sync */ |
| 134 | int width; /* Width of printed comment lines */ |
| 135 | Stmt mtimeXfer; /* Statement to transfer mtimes */ |
| 136 | const char *zWidth; /* Width option string value */ |
| 137 | |
| 138 | if( !internalUpdate ){ |
| 139 | undo_capture_command_line(); |
| 140 | url_proxy_options(); |
| 141 | } |
| @@ -163,10 +166,11 @@ | |
| 163 | /* We should be done with options.. */ |
| 164 | verify_all_options(); |
| 165 | |
| 166 | db_must_be_within_tree(); |
| 167 | vid = db_lget_int("checkout", 0); |
| 168 | user_select(); |
| 169 | if( !dryRunFlag && !internalUpdate && !bNosync ){ |
| 170 | if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){ |
| 171 | fossil_fatal("update abandoned due to sync failure"); |
| 172 | } |
| @@ -403,10 +407,11 @@ | |
| 403 | " WHERE id=:idt" |
| 404 | ); |
| 405 | assert( g.zLocalRoot!=0 ); |
| 406 | assert( strlen(g.zLocalRoot)>0 ); |
| 407 | assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); |
| 408 | while( db_step(&q)==SQLITE_ROW ){ |
| 409 | const char *zName = db_column_text(&q, 0); /* The filename from root */ |
| 410 | int idv = db_column_int(&q, 1); /* VFILE entry for current */ |
| 411 | int ridv = db_column_int(&q, 2); /* RecordID for current */ |
| 412 | int idt = db_column_int(&q, 3); /* VFILE entry for target */ |
| @@ -418,13 +423,18 @@ | |
| 418 | int islinkt = db_column_int(&q, 9); /* Is target file is a link */ |
| 419 | int deleted = db_column_int(&q, 10); /* Marked for deletion */ |
| 420 | char *zFullPath; /* Full pathname of the file */ |
| 421 | char *zFullNewPath; /* Full pathname of dest */ |
| 422 | char nameChng; /* True if the name changed */ |
| 423 | |
| 424 | zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 425 | zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); |
| 426 | nameChng = fossil_strcmp(zName, zNewName); |
| 427 | nUpdate++; |
| 428 | if( deleted ){ |
| 429 | db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt); |
| 430 | } |
| @@ -432,10 +442,13 @@ | |
| 432 | /* Conflict. This file has been added to the current check-out |
| 433 | ** but also exists in the target check-out. Use the current version. |
| 434 | */ |
| 435 | fossil_print("CONFLICT %s\n", zName); |
| 436 | nConflict++; |
| 437 | }else if( idt>0 && idv==0 ){ |
| 438 | /* File added in the target. */ |
| 439 | if( file_isfile_or_link(zFullPath) ){ |
| 440 | /* Name of backup file with Original content */ |
| 441 | char *zOrig = file_newname(zFullPath, "original", 1); |
| @@ -444,10 +457,13 @@ | |
| 444 | fossil_free(zOrig); |
| 445 | fossil_print("ADD %s - overwrites an unmanaged file", zName); |
| 446 | if( !dryRunFlag ) fossil_print(", original copy backed up locally"); |
| 447 | fossil_print("\n"); |
| 448 | nOverwrite++; |
| 449 | }else{ |
| 450 | fossil_print("ADD %s\n", zName); |
| 451 | } |
| 452 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 453 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| @@ -458,16 +474,18 @@ | |
| 458 | }else{ |
| 459 | fossil_print("UPDATE %s\n", zName); |
| 460 | } |
| 461 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 462 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 463 | }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){ |
| 464 | /* The file missing from the local check-out. Restore it to the |
| 465 | ** version that appears in the target. */ |
| 466 | fossil_print("UPDATE %s\n", zName); |
| 467 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 468 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 469 | }else if( idt==0 && idv>0 ){ |
| 470 | if( ridv==0 ){ |
| 471 | /* Added in current check-out. Continue to hold the file as |
| 472 | ** as an addition */ |
| 473 | db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv); |
| @@ -474,10 +492,13 @@ | |
| 474 | }else if( chnged ){ |
| 475 | /* Edited locally but deleted from the target. Do not track the |
| 476 | ** file but keep the edited version around. */ |
| 477 | fossil_print("CONFLICT %s - edited locally but deleted by update\n", |
| 478 | zName); |
| 479 | nConflict++; |
| 480 | }else{ |
| 481 | fossil_print("REMOVE %s\n", zName); |
| 482 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 483 | if( !dryRunFlag ){ |
| @@ -493,22 +514,23 @@ | |
| 493 | zDir = zNext; |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){ |
| 498 | /* Merge the changes in the current tree into the target version */ |
| 499 | Blob r, t, v; |
| 500 | int rc; |
| 501 | if( nameChng ){ |
| 502 | fossil_print("MERGE %s -> %s\n", zName, zNewName); |
| 503 | }else{ |
| 504 | fossil_print("MERGE %s\n", zName); |
| 505 | } |
| 506 | if( islinkv || islinkt ){ |
| 507 | fossil_print("***** Cannot merge symlink %s\n", zNewName); |
| 508 | nConflict++; |
| 509 | }else{ |
| 510 | unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; |
| 511 | if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; |
| 512 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 513 | content_get(ridt, &t); |
| 514 | content_get(ridv, &v); |
| @@ -517,12 +539,17 @@ | |
| 517 | if( !dryRunFlag ){ |
| 518 | blob_write_to_file(&r, zFullNewPath); |
| 519 | file_setexe(zFullNewPath, isexe); |
| 520 | } |
| 521 | if( rc>0 ){ |
| 522 | fossil_print("***** %d merge conflicts in %s\n", rc, zNewName); |
| 523 | nConflict++; |
| 524 | } |
| 525 | }else{ |
| 526 | if( !dryRunFlag ){ |
| 527 | if( !keepMergeFlag ){ |
| 528 | /* Name of backup file with Original content */ |
| @@ -539,16 +566,19 @@ | |
| 539 | if( !dryRunFlag ){ |
| 540 | fossil_print(", original copy backed up locally"); |
| 541 | } |
| 542 | fossil_print("\n"); |
| 543 | nConflict++; |
| 544 | } |
| 545 | } |
| 546 | if( nameChng && !dryRunFlag ) file_delete(zFullPath); |
| 547 | blob_reset(&v); |
| 548 | blob_reset(&t); |
| 549 | blob_reset(&r); |
| 550 | }else{ |
| 551 | nUpdate--; |
| 552 | if( chnged ){ |
| 553 | if( verboseFlag ) fossil_print("EDITED %s\n", zName); |
| 554 | }else{ |
| @@ -556,27 +586,48 @@ | |
| 556 | db_bind_int(&mtimeXfer, ":idt", idt); |
| 557 | db_step(&mtimeXfer); |
| 558 | db_reset(&mtimeXfer); |
| 559 | if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName); |
| 560 | } |
| 561 | } |
| 562 | free(zFullPath); |
| 563 | free(zFullNewPath); |
| 564 | } |
| 565 | db_finalize(&q); |
| 566 | db_finalize(&mtimeXfer); |
| 567 | fossil_print("%.79c\n",'-'); |
| 568 | if( nUpdate==0 ){ |
| 569 | show_common_info(tid, "checkout:", 1, 0); |
| 570 | fossil_print("%-13s None. Already up-to-date\n", "changes:"); |
| 571 | }else{ |
| 572 | fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid), |
| 573 | db_text("", "SELECT datetime(mtime) || ' UTC' FROM event " |
| 574 | " WHERE objid=%d", vid)); |
| 575 | show_common_info(tid, "updated-to:", 1, 0); |
| 576 | fossil_print("%-13s %d file%s modified.\n", "changes:", |
| 577 | nUpdate, nUpdate>1 ? "s" : ""); |
| 578 | } |
| 579 | |
| 580 | /* Report on conflicts |
| 581 | */ |
| 582 | if( !dryRunFlag ){ |
| @@ -802,10 +853,11 @@ | |
| 802 | ** |
| 803 | ** If a file is reverted accidentally, it can be restored using |
| 804 | ** the "fossil undo" command. |
| 805 | ** |
| 806 | ** Options: |
| 807 | ** -r|--revision VERSION Revert given FILE(s) back to given |
| 808 | ** VERSION |
| 809 | ** |
| 810 | ** See also: [[redo]], [[undo]], [[checkout]], [[update]] |
| 811 | */ |
| @@ -815,17 +867,19 @@ | |
| 815 | ManifestFile *pCoFile; /* File within current check-out manifest */ |
| 816 | ManifestFile *pRvFile; /* File within revert version manifest */ |
| 817 | const char *zFile; /* Filename relative to check-out root */ |
| 818 | const char *zRevision; /* Selected revert version, NULL if current */ |
| 819 | Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */ |
| 820 | int i; |
| 821 | Stmt q; |
| 822 | int revertAll = 0; |
| 823 | int revisionOptNotSupported = 0; |
| 824 | |
| 825 | undo_capture_command_line(); |
| 826 | zRevision = find_option("revision", "r", 1); |
| 827 | verify_all_options(); |
| 828 | |
| 829 | if( g.argc<2 ){ |
| 830 | usage("?OPTIONS? [FILE] ..."); |
| 831 | } |
| @@ -838,11 +892,15 @@ | |
| 838 | /* Get manifests of revert version and (if different) current check-out. */ |
| 839 | pRvManifest = historical_manifest(zRevision); |
| 840 | pCoManifest = zRevision ? historical_manifest(0) : 0; |
| 841 | |
| 842 | db_begin_transaction(); |
| 843 | undo_begin(); |
| 844 | db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);"); |
| 845 | |
| 846 | if( g.argc>2 ){ |
| 847 | for(i=2; i<g.argc; i++){ |
| 848 | Blob fname; |
| @@ -935,11 +993,11 @@ | |
| 935 | if( !pRvFile ){ |
| 936 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", |
| 937 | zFile, zFile)==0 ){ |
| 938 | fossil_print("UNMANAGE %s\n", zFile); |
| 939 | }else{ |
| 940 | undo_save(zFile); |
| 941 | file_delete(zFull); |
| 942 | fossil_print("DELETE %s\n", zFile); |
| 943 | } |
| 944 | db_multi_exec( |
| 945 | "UPDATE OR REPLACE vfile" |
| @@ -962,11 +1020,11 @@ | |
| 962 | } |
| 963 | |
| 964 | /* Get contents of reverted-to file. */ |
| 965 | content_get(fast_uuid_to_rid(pRvFile->zUuid), &record); |
| 966 | |
| 967 | undo_save(zFile); |
| 968 | if( file_size(zFull, RepoFILE)>=0 |
| 969 | && (rvPerm==PERM_LNK || file_islink(0)) |
| 970 | ){ |
| 971 | file_delete(zFull); |
| 972 | } |
| @@ -988,12 +1046,12 @@ | |
| 988 | } |
| 989 | blob_reset(&record); |
| 990 | free(zFull); |
| 991 | } |
| 992 | db_finalize(&q); |
| 993 | undo_finish(); |
| 994 | db_end_transaction(0); |
| 995 | |
| 996 | /* Deallocate parsed manifest structures. */ |
| 997 | manifest_destroy(pRvManifest); |
| 998 | manifest_destroy(pCoManifest); |
| 999 | } |
| 1000 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -132,10 +132,13 @@ | |
| 132 | int nUpdate = 0; /* Number of changes of any kind */ |
| 133 | int bNosync = 0; /* --nosync. Omit the auto-sync */ |
| 134 | int width; /* Width of printed comment lines */ |
| 135 | Stmt mtimeXfer; /* Statement to transfer mtimes */ |
| 136 | const char *zWidth; /* Width option string value */ |
| 137 | const char *zCurBrName; /* Current branch name */ |
| 138 | const char *zNewBrName; /* New branch name */ |
| 139 | const char *zBrChgMsg = ""; /* Message to display if branch changes */ |
| 140 | |
| 141 | if( !internalUpdate ){ |
| 142 | undo_capture_command_line(); |
| 143 | url_proxy_options(); |
| 144 | } |
| @@ -163,10 +166,11 @@ | |
| 166 | /* We should be done with options.. */ |
| 167 | verify_all_options(); |
| 168 | |
| 169 | db_must_be_within_tree(); |
| 170 | vid = db_lget_int("checkout", 0); |
| 171 | zCurBrName = branch_of_rid(vid); |
| 172 | user_select(); |
| 173 | if( !dryRunFlag && !internalUpdate && !bNosync ){ |
| 174 | if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){ |
| 175 | fossil_fatal("update abandoned due to sync failure"); |
| 176 | } |
| @@ -403,10 +407,11 @@ | |
| 407 | " WHERE id=:idt" |
| 408 | ); |
| 409 | assert( g.zLocalRoot!=0 ); |
| 410 | assert( strlen(g.zLocalRoot)>0 ); |
| 411 | assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); |
| 412 | merge_info_init(); |
| 413 | while( db_step(&q)==SQLITE_ROW ){ |
| 414 | const char *zName = db_column_text(&q, 0); /* The filename from root */ |
| 415 | int idv = db_column_int(&q, 1); /* VFILE entry for current */ |
| 416 | int ridv = db_column_int(&q, 2); /* RecordID for current */ |
| 417 | int idt = db_column_int(&q, 3); /* VFILE entry for target */ |
| @@ -418,13 +423,18 @@ | |
| 423 | int islinkt = db_column_int(&q, 9); /* Is target file is a link */ |
| 424 | int deleted = db_column_int(&q, 10); /* Marked for deletion */ |
| 425 | char *zFullPath; /* Full pathname of the file */ |
| 426 | char *zFullNewPath; /* Full pathname of dest */ |
| 427 | char nameChng; /* True if the name changed */ |
| 428 | const char *zOp = 0; /* Type of change. */ |
| 429 | i64 sz = 0; /* Size of the file */ |
| 430 | int nc = 0; /* Number of conflicts */ |
| 431 | const char *zErrMsg = 0; /* Error message */ |
| 432 | |
| 433 | zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
| 434 | zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); |
| 435 | sz = file_size(zFullNewPath, ExtFILE); |
| 436 | nameChng = fossil_strcmp(zName, zNewName); |
| 437 | nUpdate++; |
| 438 | if( deleted ){ |
| 439 | db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt); |
| 440 | } |
| @@ -432,10 +442,13 @@ | |
| 442 | /* Conflict. This file has been added to the current check-out |
| 443 | ** but also exists in the target check-out. Use the current version. |
| 444 | */ |
| 445 | fossil_print("CONFLICT %s\n", zName); |
| 446 | nConflict++; |
| 447 | zOp = "CONFLICT"; |
| 448 | nc = 1; |
| 449 | zErrMsg = "duplicate file"; |
| 450 | }else if( idt>0 && idv==0 ){ |
| 451 | /* File added in the target. */ |
| 452 | if( file_isfile_or_link(zFullPath) ){ |
| 453 | /* Name of backup file with Original content */ |
| 454 | char *zOrig = file_newname(zFullPath, "original", 1); |
| @@ -444,10 +457,13 @@ | |
| 457 | fossil_free(zOrig); |
| 458 | fossil_print("ADD %s - overwrites an unmanaged file", zName); |
| 459 | if( !dryRunFlag ) fossil_print(", original copy backed up locally"); |
| 460 | fossil_print("\n"); |
| 461 | nOverwrite++; |
| 462 | nc = 1; |
| 463 | zOp = "CONFLICT"; |
| 464 | zErrMsg = "new file overwrites unmanaged file"; |
| 465 | }else{ |
| 466 | fossil_print("ADD %s\n", zName); |
| 467 | } |
| 468 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 469 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| @@ -458,16 +474,18 @@ | |
| 474 | }else{ |
| 475 | fossil_print("UPDATE %s\n", zName); |
| 476 | } |
| 477 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 478 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 479 | zOp = "UPDATE"; |
| 480 | }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){ |
| 481 | /* The file missing from the local check-out. Restore it to the |
| 482 | ** version that appears in the target. */ |
| 483 | fossil_print("UPDATE %s\n", zName); |
| 484 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 485 | if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); |
| 486 | zOp = "UPDATE"; |
| 487 | }else if( idt==0 && idv>0 ){ |
| 488 | if( ridv==0 ){ |
| 489 | /* Added in current check-out. Continue to hold the file as |
| 490 | ** as an addition */ |
| 491 | db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv); |
| @@ -474,10 +492,13 @@ | |
| 492 | }else if( chnged ){ |
| 493 | /* Edited locally but deleted from the target. Do not track the |
| 494 | ** file but keep the edited version around. */ |
| 495 | fossil_print("CONFLICT %s - edited locally but deleted by update\n", |
| 496 | zName); |
| 497 | zOp = "CONFLICT"; |
| 498 | zErrMsg = "edited locally but deleted by update"; |
| 499 | nc = 1; |
| 500 | nConflict++; |
| 501 | }else{ |
| 502 | fossil_print("REMOVE %s\n", zName); |
| 503 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 504 | if( !dryRunFlag ){ |
| @@ -493,22 +514,23 @@ | |
| 514 | zDir = zNext; |
| 515 | } |
| 516 | } |
| 517 | } |
| 518 | }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){ |
| 519 | if( nameChng ){ |
| 520 | fossil_print("MERGE %s -> %s\n", zName, zNewName); |
| 521 | }else{ |
| 522 | fossil_print("MERGE %s\n", zName); |
| 523 | } |
| 524 | if( islinkv || islinkt ){ |
| 525 | fossil_print("***** Cannot merge symlink %s\n", zNewName); |
| 526 | zOp = "CONFLICT"; |
| 527 | nConflict++; |
| 528 | }else{ |
| 529 | /* Merge the changes in the current tree into the target version */ |
| 530 | Blob r, t, v; |
| 531 | int rc; |
| 532 | unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; |
| 533 | if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; |
| 534 | if( !dryRunFlag && !internalUpdate ) undo_save(zName); |
| 535 | content_get(ridt, &t); |
| 536 | content_get(ridv, &v); |
| @@ -517,12 +539,17 @@ | |
| 539 | if( !dryRunFlag ){ |
| 540 | blob_write_to_file(&r, zFullNewPath); |
| 541 | file_setexe(zFullNewPath, isexe); |
| 542 | } |
| 543 | if( rc>0 ){ |
| 544 | nc = rc; |
| 545 | zOp = "CONFLICT"; |
| 546 | zErrMsg = "merge conflicts"; |
| 547 | fossil_print("***** %d merge conflicts in %s\n", rc, zNewName); |
| 548 | nConflict++; |
| 549 | }else{ |
| 550 | zOp = "MERGE"; |
| 551 | } |
| 552 | }else{ |
| 553 | if( !dryRunFlag ){ |
| 554 | if( !keepMergeFlag ){ |
| 555 | /* Name of backup file with Original content */ |
| @@ -539,16 +566,19 @@ | |
| 566 | if( !dryRunFlag ){ |
| 567 | fossil_print(", original copy backed up locally"); |
| 568 | } |
| 569 | fossil_print("\n"); |
| 570 | nConflict++; |
| 571 | zOp = "ERROR"; |
| 572 | zErrMsg = "cannot merge binary file"; |
| 573 | nc = 1; |
| 574 | } |
| 575 | blob_reset(&v); |
| 576 | blob_reset(&t); |
| 577 | blob_reset(&r); |
| 578 | } |
| 579 | if( nameChng && !dryRunFlag ) file_delete(zFullPath); |
| 580 | }else{ |
| 581 | nUpdate--; |
| 582 | if( chnged ){ |
| 583 | if( verboseFlag ) fossil_print("EDITED %s\n", zName); |
| 584 | }else{ |
| @@ -556,27 +586,48 @@ | |
| 586 | db_bind_int(&mtimeXfer, ":idt", idt); |
| 587 | db_step(&mtimeXfer); |
| 588 | db_reset(&mtimeXfer); |
| 589 | if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName); |
| 590 | } |
| 591 | } |
| 592 | if( zOp!=0 ){ |
| 593 | db_multi_exec( |
| 594 | "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" |
| 595 | "VALUES(%Q,%Q,%d,%Q,NULL,%lld,%Q,%d,%Q,%d,%Q)", |
| 596 | /* op */ zOp, |
| 597 | /* fnp */ zName, |
| 598 | /* ridp */ ridv, |
| 599 | /* fn */ zNewName, |
| 600 | /* sz */ sz, |
| 601 | /* fnm */ zName, |
| 602 | /* ridm */ ridt, |
| 603 | /* fnr */ zNewName, |
| 604 | /* nc */ nc, |
| 605 | /* msg */ zErrMsg |
| 606 | ); |
| 607 | } |
| 608 | free(zFullPath); |
| 609 | free(zFullNewPath); |
| 610 | } |
| 611 | db_finalize(&q); |
| 612 | db_finalize(&mtimeXfer); |
| 613 | fossil_print("%.79c\n",'-'); |
| 614 | zNewBrName = branch_of_rid(tid); |
| 615 | if( g.argc<3 && fossil_strcmp(zCurBrName, zNewBrName)!=0 ){ |
| 616 | zBrChgMsg = mprintf(" Branch changed from %s to %s.", |
| 617 | zCurBrName, zNewBrName); |
| 618 | } |
| 619 | if( nUpdate==0 ){ |
| 620 | show_common_info(tid, "checkout:", 1, 0); |
| 621 | fossil_print("%-13s None. Already up-to-date.%s\n", "changes:", zBrChgMsg); |
| 622 | }else{ |
| 623 | fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid), |
| 624 | db_text("", "SELECT datetime(mtime) || ' UTC' FROM event " |
| 625 | " WHERE objid=%d", vid)); |
| 626 | show_common_info(tid, "updated-to:", 1, 0); |
| 627 | fossil_print("%-13s %d file%s modified.%s\n", "changes:", |
| 628 | nUpdate, nUpdate>1 ? "s" : "", zBrChgMsg); |
| 629 | } |
| 630 | |
| 631 | /* Report on conflicts |
| 632 | */ |
| 633 | if( !dryRunFlag ){ |
| @@ -802,10 +853,11 @@ | |
| 853 | ** |
| 854 | ** If a file is reverted accidentally, it can be restored using |
| 855 | ** the "fossil undo" command. |
| 856 | ** |
| 857 | ** Options: |
| 858 | ** --noundo Do not record changes in the undo/redo log. |
| 859 | ** -r|--revision VERSION Revert given FILE(s) back to given |
| 860 | ** VERSION |
| 861 | ** |
| 862 | ** See also: [[redo]], [[undo]], [[checkout]], [[update]] |
| 863 | */ |
| @@ -815,17 +867,19 @@ | |
| 867 | ManifestFile *pCoFile; /* File within current check-out manifest */ |
| 868 | ManifestFile *pRvFile; /* File within revert version manifest */ |
| 869 | const char *zFile; /* Filename relative to check-out root */ |
| 870 | const char *zRevision; /* Selected revert version, NULL if current */ |
| 871 | Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */ |
| 872 | int useUndo = 1; /* True to record changes in UNDO */ |
| 873 | int i; |
| 874 | Stmt q; |
| 875 | int revertAll = 0; |
| 876 | int revisionOptNotSupported = 0; |
| 877 | |
| 878 | undo_capture_command_line(); |
| 879 | zRevision = find_option("revision", "r", 1); |
| 880 | useUndo = find_option("noundo", 0, 0)==0; |
| 881 | verify_all_options(); |
| 882 | |
| 883 | if( g.argc<2 ){ |
| 884 | usage("?OPTIONS? [FILE] ..."); |
| 885 | } |
| @@ -838,11 +892,15 @@ | |
| 892 | /* Get manifests of revert version and (if different) current check-out. */ |
| 893 | pRvManifest = historical_manifest(zRevision); |
| 894 | pCoManifest = zRevision ? historical_manifest(0) : 0; |
| 895 | |
| 896 | db_begin_transaction(); |
| 897 | if( useUndo ){ |
| 898 | undo_begin(); |
| 899 | }else{ |
| 900 | undo_reset(); |
| 901 | } |
| 902 | db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);"); |
| 903 | |
| 904 | if( g.argc>2 ){ |
| 905 | for(i=2; i<g.argc; i++){ |
| 906 | Blob fname; |
| @@ -935,11 +993,11 @@ | |
| 993 | if( !pRvFile ){ |
| 994 | if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", |
| 995 | zFile, zFile)==0 ){ |
| 996 | fossil_print("UNMANAGE %s\n", zFile); |
| 997 | }else{ |
| 998 | if( useUndo ) undo_save(zFile); |
| 999 | file_delete(zFull); |
| 1000 | fossil_print("DELETE %s\n", zFile); |
| 1001 | } |
| 1002 | db_multi_exec( |
| 1003 | "UPDATE OR REPLACE vfile" |
| @@ -962,11 +1020,11 @@ | |
| 1020 | } |
| 1021 | |
| 1022 | /* Get contents of reverted-to file. */ |
| 1023 | content_get(fast_uuid_to_rid(pRvFile->zUuid), &record); |
| 1024 | |
| 1025 | if( useUndo ) undo_save(zFile); |
| 1026 | if( file_size(zFull, RepoFILE)>=0 |
| 1027 | && (rvPerm==PERM_LNK || file_islink(0)) |
| 1028 | ){ |
| 1029 | file_delete(zFull); |
| 1030 | } |
| @@ -988,12 +1046,12 @@ | |
| 1046 | } |
| 1047 | blob_reset(&record); |
| 1048 | free(zFull); |
| 1049 | } |
| 1050 | db_finalize(&q); |
| 1051 | if( useUndo) undo_finish(); |
| 1052 | db_end_transaction(0); |
| 1053 | |
| 1054 | /* Deallocate parsed manifest structures. */ |
| 1055 | manifest_destroy(pRvManifest); |
| 1056 | manifest_destroy(pCoManifest); |
| 1057 | } |
| 1058 |
+4
-4
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -622,14 +622,14 @@ | ||
| 622 | 622 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 623 | 623 | blob_reset(&wiki); |
| 624 | 624 | } |
| 625 | 625 | manifest_destroy(pWiki); |
| 626 | 626 | if( !isPopup ){ |
| 627 | - char * zLabel = mprintf("<hr><h2><a href='%R/attachlist?name=%T'>" | |
| 628 | - "Attachments</a>:</h2><ul>", | |
| 627 | + char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>" | |
| 628 | + "Attachments</a>:</h2>", | |
| 629 | 629 | zPageName); |
| 630 | - attachment_list(zPageName, zLabel); | |
| 630 | + attachment_list(zPageName, zLabel, 1); | |
| 631 | 631 | fossil_free(zLabel); |
| 632 | 632 | document_emit_js(/*for optional pikchr support*/); |
| 633 | 633 | style_finish_page(); |
| 634 | 634 | } |
| 635 | 635 | } |
| @@ -1331,11 +1331,11 @@ | ||
| 1331 | 1331 | CX("<div id='fossil-status-bar' " |
| 1332 | 1332 | "title='Status message area. Double-click to clear them.'>" |
| 1333 | 1333 | "Status messages will go here.</div>\n" |
| 1334 | 1334 | /* will be moved into the tab container via JS */); |
| 1335 | 1335 | |
| 1336 | - CX("<div id='wikiedit-edit-status''>" | |
| 1336 | + CX("<div id='wikiedit-edit-status'>" | |
| 1337 | 1337 | "<span class='name'></span>" |
| 1338 | 1338 | "<span class='links'></span>" |
| 1339 | 1339 | "</div>"); |
| 1340 | 1340 | |
| 1341 | 1341 | /* Main tab container... */ |
| 1342 | 1342 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -622,14 +622,14 @@ | |
| 622 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 623 | blob_reset(&wiki); |
| 624 | } |
| 625 | manifest_destroy(pWiki); |
| 626 | if( !isPopup ){ |
| 627 | char * zLabel = mprintf("<hr><h2><a href='%R/attachlist?name=%T'>" |
| 628 | "Attachments</a>:</h2><ul>", |
| 629 | zPageName); |
| 630 | attachment_list(zPageName, zLabel); |
| 631 | fossil_free(zLabel); |
| 632 | document_emit_js(/*for optional pikchr support*/); |
| 633 | style_finish_page(); |
| 634 | } |
| 635 | } |
| @@ -1331,11 +1331,11 @@ | |
| 1331 | CX("<div id='fossil-status-bar' " |
| 1332 | "title='Status message area. Double-click to clear them.'>" |
| 1333 | "Status messages will go here.</div>\n" |
| 1334 | /* will be moved into the tab container via JS */); |
| 1335 | |
| 1336 | CX("<div id='wikiedit-edit-status''>" |
| 1337 | "<span class='name'></span>" |
| 1338 | "<span class='links'></span>" |
| 1339 | "</div>"); |
| 1340 | |
| 1341 | /* Main tab container... */ |
| 1342 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -622,14 +622,14 @@ | |
| 622 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 623 | blob_reset(&wiki); |
| 624 | } |
| 625 | manifest_destroy(pWiki); |
| 626 | if( !isPopup ){ |
| 627 | char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>" |
| 628 | "Attachments</a>:</h2>", |
| 629 | zPageName); |
| 630 | attachment_list(zPageName, zLabel, 1); |
| 631 | fossil_free(zLabel); |
| 632 | document_emit_js(/*for optional pikchr support*/); |
| 633 | style_finish_page(); |
| 634 | } |
| 635 | } |
| @@ -1331,11 +1331,11 @@ | |
| 1331 | CX("<div id='fossil-status-bar' " |
| 1332 | "title='Status message area. Double-click to clear them.'>" |
| 1333 | "Status messages will go here.</div>\n" |
| 1334 | /* will be moved into the tab container via JS */); |
| 1335 | |
| 1336 | CX("<div id='wikiedit-edit-status'>" |
| 1337 | "<span class='name'></span>" |
| 1338 | "<span class='links'></span>" |
| 1339 | "</div>"); |
| 1340 | |
| 1341 | /* Main tab container... */ |
| 1342 |
+68
| --- src/winfile.c | ||
| +++ src/winfile.c | ||
| @@ -451,6 +451,74 @@ | ||
| 451 | 451 | i = j; |
| 452 | 452 | } |
| 453 | 453 | fossil_free(zBuf); |
| 454 | 454 | return zRes; |
| 455 | 455 | } |
| 456 | + | |
| 457 | +/* Return the unique identifier (UID) for a file, made up of the file identifier | |
| 458 | +** (equal to "inode" for Unix-style file systems) plus the volume serial number. | |
| 459 | +** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort | |
| 460 | +** to the GetFileInformationByHandle() function on Windows XP. The result string | |
| 461 | +** is allocated by mprintf(), or NULL on failure. | |
| 462 | +*/ | |
| 463 | +char *win32_file_id( | |
| 464 | + const char *zFileName | |
| 465 | +){ | |
| 466 | + static FARPROC fnGetFileInformationByHandleEx; | |
| 467 | + static int loaded_fnGetFileInformationByHandleEx; | |
| 468 | + wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0); | |
| 469 | + HANDLE hFile; | |
| 470 | + char *zFileId = 0; | |
| 471 | + hFile = CreateFileW( | |
| 472 | + wzFileName, | |
| 473 | + 0, | |
| 474 | + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
| 475 | + NULL, | |
| 476 | + OPEN_EXISTING, | |
| 477 | + FILE_FLAG_BACKUP_SEMANTICS, | |
| 478 | + NULL); | |
| 479 | + if( hFile!=INVALID_HANDLE_VALUE ){ | |
| 480 | + BY_HANDLE_FILE_INFORMATION fi; | |
| 481 | + struct { /* FILE_ID_INFO from <winbase.h> */ | |
| 482 | + u64 VolumeSerialNumber; | |
| 483 | + unsigned char FileId[16]; | |
| 484 | + } fi2; | |
| 485 | + if( !loaded_fnGetFileInformationByHandleEx ){ | |
| 486 | + fnGetFileInformationByHandleEx = GetProcAddress( | |
| 487 | + GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); | |
| 488 | + loaded_fnGetFileInformationByHandleEx = 1; | |
| 489 | + } | |
| 490 | + if( fnGetFileInformationByHandleEx ){ | |
| 491 | + if( fnGetFileInformationByHandleEx( | |
| 492 | + hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){ | |
| 493 | + zFileId = mprintf( | |
| 494 | + "%016llx/" | |
| 495 | + "%02x%02x%02x%02x%02x%02x%02x%02x" | |
| 496 | + "%02x%02x%02x%02x%02x%02x%02x%02x", | |
| 497 | + fi2.VolumeSerialNumber, | |
| 498 | + fi2.FileId[15], fi2.FileId[14], | |
| 499 | + fi2.FileId[13], fi2.FileId[12], | |
| 500 | + fi2.FileId[11], fi2.FileId[10], | |
| 501 | + fi2.FileId[9], fi2.FileId[8], | |
| 502 | + fi2.FileId[7], fi2.FileId[6], | |
| 503 | + fi2.FileId[5], fi2.FileId[4], | |
| 504 | + fi2.FileId[3], fi2.FileId[2], | |
| 505 | + fi2.FileId[1], fi2.FileId[0]); | |
| 506 | + } | |
| 507 | + } | |
| 508 | + if( zFileId==0 ){ | |
| 509 | + if( GetFileInformationByHandle(hFile,&fi) ){ | |
| 510 | + ULARGE_INTEGER FileId = { | |
| 511 | + /*.LowPart = */ fi.nFileIndexLow, | |
| 512 | + /*.HighPart = */ fi.nFileIndexHigh | |
| 513 | + }; | |
| 514 | + zFileId = mprintf( | |
| 515 | + "%08x/%016llx", | |
| 516 | + fi.dwVolumeSerialNumber,(u64)FileId.QuadPart); | |
| 517 | + } | |
| 518 | + } | |
| 519 | + CloseHandle(hFile); | |
| 520 | + } | |
| 521 | + fossil_path_free(wzFileName); | |
| 522 | + return zFileId; | |
| 523 | +} | |
| 456 | 524 | #endif /* _WIN32 -- This code is for win32 only */ |
| 457 | 525 |
| --- src/winfile.c | |
| +++ src/winfile.c | |
| @@ -451,6 +451,74 @@ | |
| 451 | i = j; |
| 452 | } |
| 453 | fossil_free(zBuf); |
| 454 | return zRes; |
| 455 | } |
| 456 | #endif /* _WIN32 -- This code is for win32 only */ |
| 457 |
| --- src/winfile.c | |
| +++ src/winfile.c | |
| @@ -451,6 +451,74 @@ | |
| 451 | i = j; |
| 452 | } |
| 453 | fossil_free(zBuf); |
| 454 | return zRes; |
| 455 | } |
| 456 | |
| 457 | /* Return the unique identifier (UID) for a file, made up of the file identifier |
| 458 | ** (equal to "inode" for Unix-style file systems) plus the volume serial number. |
| 459 | ** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort |
| 460 | ** to the GetFileInformationByHandle() function on Windows XP. The result string |
| 461 | ** is allocated by mprintf(), or NULL on failure. |
| 462 | */ |
| 463 | char *win32_file_id( |
| 464 | const char *zFileName |
| 465 | ){ |
| 466 | static FARPROC fnGetFileInformationByHandleEx; |
| 467 | static int loaded_fnGetFileInformationByHandleEx; |
| 468 | wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0); |
| 469 | HANDLE hFile; |
| 470 | char *zFileId = 0; |
| 471 | hFile = CreateFileW( |
| 472 | wzFileName, |
| 473 | 0, |
| 474 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 475 | NULL, |
| 476 | OPEN_EXISTING, |
| 477 | FILE_FLAG_BACKUP_SEMANTICS, |
| 478 | NULL); |
| 479 | if( hFile!=INVALID_HANDLE_VALUE ){ |
| 480 | BY_HANDLE_FILE_INFORMATION fi; |
| 481 | struct { /* FILE_ID_INFO from <winbase.h> */ |
| 482 | u64 VolumeSerialNumber; |
| 483 | unsigned char FileId[16]; |
| 484 | } fi2; |
| 485 | if( !loaded_fnGetFileInformationByHandleEx ){ |
| 486 | fnGetFileInformationByHandleEx = GetProcAddress( |
| 487 | GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); |
| 488 | loaded_fnGetFileInformationByHandleEx = 1; |
| 489 | } |
| 490 | if( fnGetFileInformationByHandleEx ){ |
| 491 | if( fnGetFileInformationByHandleEx( |
| 492 | hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){ |
| 493 | zFileId = mprintf( |
| 494 | "%016llx/" |
| 495 | "%02x%02x%02x%02x%02x%02x%02x%02x" |
| 496 | "%02x%02x%02x%02x%02x%02x%02x%02x", |
| 497 | fi2.VolumeSerialNumber, |
| 498 | fi2.FileId[15], fi2.FileId[14], |
| 499 | fi2.FileId[13], fi2.FileId[12], |
| 500 | fi2.FileId[11], fi2.FileId[10], |
| 501 | fi2.FileId[9], fi2.FileId[8], |
| 502 | fi2.FileId[7], fi2.FileId[6], |
| 503 | fi2.FileId[5], fi2.FileId[4], |
| 504 | fi2.FileId[3], fi2.FileId[2], |
| 505 | fi2.FileId[1], fi2.FileId[0]); |
| 506 | } |
| 507 | } |
| 508 | if( zFileId==0 ){ |
| 509 | if( GetFileInformationByHandle(hFile,&fi) ){ |
| 510 | ULARGE_INTEGER FileId = { |
| 511 | /*.LowPart = */ fi.nFileIndexLow, |
| 512 | /*.HighPart = */ fi.nFileIndexHigh |
| 513 | }; |
| 514 | zFileId = mprintf( |
| 515 | "%08x/%016llx", |
| 516 | fi.dwVolumeSerialNumber,(u64)FileId.QuadPart); |
| 517 | } |
| 518 | } |
| 519 | CloseHandle(hFile); |
| 520 | } |
| 521 | fossil_path_free(wzFileName); |
| 522 | return zFileId; |
| 523 | } |
| 524 | #endif /* _WIN32 -- This code is for win32 only */ |
| 525 |
+55
-2
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -1041,10 +1041,43 @@ | ||
| 1041 | 1041 | } |
| 1042 | 1042 | db_finalize(&q); |
| 1043 | 1043 | if( cnt==0 ) pXfer->resync = 0; |
| 1044 | 1044 | return cnt; |
| 1045 | 1045 | } |
| 1046 | + | |
| 1047 | +/* | |
| 1048 | +** Send an igot message for every cluster artifact that is not a phantom, | |
| 1049 | +** is not shunned, is not private, and that is not in the UNCLUSTERED table. | |
| 1050 | +** Return the number of cards sent. | |
| 1051 | +*/ | |
| 1052 | +static int send_all_clusters(Xfer *pXfer){ | |
| 1053 | + Stmt q; | |
| 1054 | + int cnt = 0; | |
| 1055 | + const char *zExtra; | |
| 1056 | + if( db_table_exists("temp","onremote") ){ | |
| 1057 | + zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"; | |
| 1058 | + }else{ | |
| 1059 | + zExtra = ""; | |
| 1060 | + } | |
| 1061 | + db_prepare(&q, | |
| 1062 | + "SELECT uuid" | |
| 1063 | + " FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d" | |
| 1064 | + " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" | |
| 1065 | + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" | |
| 1066 | + " AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)" | |
| 1067 | + " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s", | |
| 1068 | + TAG_CLUSTER, zExtra /*safe-for-%s*/ | |
| 1069 | + ); | |
| 1070 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1071 | + if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n"); | |
| 1072 | + blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); | |
| 1073 | + cnt++; | |
| 1074 | + } | |
| 1075 | + db_finalize(&q); | |
| 1076 | + if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n"); | |
| 1077 | + return cnt; | |
| 1078 | +} | |
| 1046 | 1079 | |
| 1047 | 1080 | /* |
| 1048 | 1081 | ** Send an igot message for every artifact. |
| 1049 | 1082 | */ |
| 1050 | 1083 | static void send_all(Xfer *pXfer){ |
| @@ -1781,10 +1814,19 @@ | ||
| 1781 | 1814 | ** The client sends this message to the server to ask the server |
| 1782 | 1815 | ** to tell it about alternative repositories in the reply. |
| 1783 | 1816 | */ |
| 1784 | 1817 | if( blob_eq(&xfer.aToken[1], "req-links") ){ |
| 1785 | 1818 | bSendLinks = 1; |
| 1819 | + }else | |
| 1820 | + | |
| 1821 | + /* pragma req-clusters | |
| 1822 | + ** | |
| 1823 | + ** This pragma requests that the server send igot cards for every | |
| 1824 | + ** cluster artifact that it knows about. | |
| 1825 | + */ | |
| 1826 | + if( blob_eq(&xfer.aToken[1], "req-clusters") ){ | |
| 1827 | + send_all_clusters(&xfer); | |
| 1786 | 1828 | } |
| 1787 | 1829 | |
| 1788 | 1830 | }else |
| 1789 | 1831 | |
| 1790 | 1832 | /* Unknown message |
| @@ -1981,11 +2023,11 @@ | ||
| 1981 | 2023 | int nCardSent = 0; /* Number of cards sent */ |
| 1982 | 2024 | int nCardRcvd = 0; /* Number of cards received */ |
| 1983 | 2025 | int nCycle = 0; /* Number of round trips to the server */ |
| 1984 | 2026 | int size; /* Size of a config value or uvfile */ |
| 1985 | 2027 | int origConfigRcvMask; /* Original value of configRcvMask */ |
| 1986 | - int nFileRecv; /* Number of files received */ | |
| 2028 | + int nFileRecv = 0; /* Number of files received */ | |
| 1987 | 2029 | int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ |
| 1988 | 2030 | const char *zCookie; /* Server cookie */ |
| 1989 | 2031 | i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */ |
| 1990 | 2032 | i64 nSent, nRcvd; /* Bytes sent and received (after compression) */ |
| 1991 | 2033 | int cloneSeqno = 1; /* Sequence number for clones */ |
| @@ -2007,10 +2049,11 @@ | ||
| 2007 | 2049 | int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */ |
| 2008 | 2050 | int uvDoPush = 0; /* Generate uvfile messages to send to server */ |
| 2009 | 2051 | int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */ |
| 2010 | 2052 | int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */ |
| 2011 | 2053 | int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */ |
| 2054 | + int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */ | |
| 2012 | 2055 | sqlite3_int64 mtime; /* Modification time on a UV file */ |
| 2013 | 2056 | int autopushFailed = 0; /* Autopush following commit failed if true */ |
| 2014 | 2057 | const char *zCkinLock; /* Name of check-in to lock. NULL for none */ |
| 2015 | 2058 | const char *zClientId; /* A unique identifier for this check-out */ |
| 2016 | 2059 | unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */ |
| @@ -2181,15 +2224,21 @@ | ||
| 2181 | 2224 | */ |
| 2182 | 2225 | if( (syncFlags & SYNC_PULL)!=0 |
| 2183 | 2226 | || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1) |
| 2184 | 2227 | ){ |
| 2185 | 2228 | request_phantoms(&xfer, mxPhantomReq); |
| 2229 | + if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){ | |
| 2230 | + blob_appendf(&send, "pragma req-clusters\n"); | |
| 2231 | + } | |
| 2186 | 2232 | } |
| 2187 | 2233 | if( syncFlags & SYNC_PUSH ){ |
| 2188 | 2234 | send_unsent(&xfer); |
| 2189 | 2235 | nCardSent += send_unclustered(&xfer); |
| 2190 | 2236 | if( syncFlags & SYNC_PRIVATE ) send_private(&xfer); |
| 2237 | + if( nGimmeRcvd>0 && nCycle==2 ){ | |
| 2238 | + send_all_clusters(&xfer); | |
| 2239 | + } | |
| 2191 | 2240 | } |
| 2192 | 2241 | |
| 2193 | 2242 | /* Client sends configuration parameter requests. On a clone, delay sending |
| 2194 | 2243 | ** this until the second cycle since the login card might fail on |
| 2195 | 2244 | ** the first cycle. |
| @@ -2369,10 +2418,11 @@ | ||
| 2369 | 2418 | nCardSent++; |
| 2370 | 2419 | } |
| 2371 | 2420 | go = 0; |
| 2372 | 2421 | nUvGimmeSent = 0; |
| 2373 | 2422 | nUvFileRcvd = 0; |
| 2423 | + nGimmeRcvd = 0; | |
| 2374 | 2424 | nPriorArtifact = nArtifactRcvd; |
| 2375 | 2425 | |
| 2376 | 2426 | /* Process the reply that came back from the server */ |
| 2377 | 2427 | while( blob_line(&recv, &xfer.line) ){ |
| 2378 | 2428 | if( blob_buffer(&xfer.line)[0]=='#' ){ |
| @@ -2449,11 +2499,14 @@ | ||
| 2449 | 2499 | && blob_is_hname(&xfer.aToken[1]) |
| 2450 | 2500 | ){ |
| 2451 | 2501 | remote_unk(&xfer.aToken[1]); |
| 2452 | 2502 | if( syncFlags & SYNC_PUSH ){ |
| 2453 | 2503 | int rid = rid_from_uuid(&xfer.aToken[1], 0, 0); |
| 2454 | - if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0); | |
| 2504 | + if( rid ){ | |
| 2505 | + send_file(&xfer, rid, &xfer.aToken[1], 0); | |
| 2506 | + nGimmeRcvd++; | |
| 2507 | + } | |
| 2455 | 2508 | } |
| 2456 | 2509 | }else |
| 2457 | 2510 | |
| 2458 | 2511 | /* igot HASH ?PRIVATEFLAG? |
| 2459 | 2512 | ** |
| 2460 | 2513 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1041,10 +1041,43 @@ | |
| 1041 | } |
| 1042 | db_finalize(&q); |
| 1043 | if( cnt==0 ) pXfer->resync = 0; |
| 1044 | return cnt; |
| 1045 | } |
| 1046 | |
| 1047 | /* |
| 1048 | ** Send an igot message for every artifact. |
| 1049 | */ |
| 1050 | static void send_all(Xfer *pXfer){ |
| @@ -1781,10 +1814,19 @@ | |
| 1781 | ** The client sends this message to the server to ask the server |
| 1782 | ** to tell it about alternative repositories in the reply. |
| 1783 | */ |
| 1784 | if( blob_eq(&xfer.aToken[1], "req-links") ){ |
| 1785 | bSendLinks = 1; |
| 1786 | } |
| 1787 | |
| 1788 | }else |
| 1789 | |
| 1790 | /* Unknown message |
| @@ -1981,11 +2023,11 @@ | |
| 1981 | int nCardSent = 0; /* Number of cards sent */ |
| 1982 | int nCardRcvd = 0; /* Number of cards received */ |
| 1983 | int nCycle = 0; /* Number of round trips to the server */ |
| 1984 | int size; /* Size of a config value or uvfile */ |
| 1985 | int origConfigRcvMask; /* Original value of configRcvMask */ |
| 1986 | int nFileRecv; /* Number of files received */ |
| 1987 | int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ |
| 1988 | const char *zCookie; /* Server cookie */ |
| 1989 | i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */ |
| 1990 | i64 nSent, nRcvd; /* Bytes sent and received (after compression) */ |
| 1991 | int cloneSeqno = 1; /* Sequence number for clones */ |
| @@ -2007,10 +2049,11 @@ | |
| 2007 | int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */ |
| 2008 | int uvDoPush = 0; /* Generate uvfile messages to send to server */ |
| 2009 | int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */ |
| 2010 | int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */ |
| 2011 | int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */ |
| 2012 | sqlite3_int64 mtime; /* Modification time on a UV file */ |
| 2013 | int autopushFailed = 0; /* Autopush following commit failed if true */ |
| 2014 | const char *zCkinLock; /* Name of check-in to lock. NULL for none */ |
| 2015 | const char *zClientId; /* A unique identifier for this check-out */ |
| 2016 | unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */ |
| @@ -2181,15 +2224,21 @@ | |
| 2181 | */ |
| 2182 | if( (syncFlags & SYNC_PULL)!=0 |
| 2183 | || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1) |
| 2184 | ){ |
| 2185 | request_phantoms(&xfer, mxPhantomReq); |
| 2186 | } |
| 2187 | if( syncFlags & SYNC_PUSH ){ |
| 2188 | send_unsent(&xfer); |
| 2189 | nCardSent += send_unclustered(&xfer); |
| 2190 | if( syncFlags & SYNC_PRIVATE ) send_private(&xfer); |
| 2191 | } |
| 2192 | |
| 2193 | /* Client sends configuration parameter requests. On a clone, delay sending |
| 2194 | ** this until the second cycle since the login card might fail on |
| 2195 | ** the first cycle. |
| @@ -2369,10 +2418,11 @@ | |
| 2369 | nCardSent++; |
| 2370 | } |
| 2371 | go = 0; |
| 2372 | nUvGimmeSent = 0; |
| 2373 | nUvFileRcvd = 0; |
| 2374 | nPriorArtifact = nArtifactRcvd; |
| 2375 | |
| 2376 | /* Process the reply that came back from the server */ |
| 2377 | while( blob_line(&recv, &xfer.line) ){ |
| 2378 | if( blob_buffer(&xfer.line)[0]=='#' ){ |
| @@ -2449,11 +2499,14 @@ | |
| 2449 | && blob_is_hname(&xfer.aToken[1]) |
| 2450 | ){ |
| 2451 | remote_unk(&xfer.aToken[1]); |
| 2452 | if( syncFlags & SYNC_PUSH ){ |
| 2453 | int rid = rid_from_uuid(&xfer.aToken[1], 0, 0); |
| 2454 | if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0); |
| 2455 | } |
| 2456 | }else |
| 2457 | |
| 2458 | /* igot HASH ?PRIVATEFLAG? |
| 2459 | ** |
| 2460 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1041,10 +1041,43 @@ | |
| 1041 | } |
| 1042 | db_finalize(&q); |
| 1043 | if( cnt==0 ) pXfer->resync = 0; |
| 1044 | return cnt; |
| 1045 | } |
| 1046 | |
| 1047 | /* |
| 1048 | ** Send an igot message for every cluster artifact that is not a phantom, |
| 1049 | ** is not shunned, is not private, and that is not in the UNCLUSTERED table. |
| 1050 | ** Return the number of cards sent. |
| 1051 | */ |
| 1052 | static int send_all_clusters(Xfer *pXfer){ |
| 1053 | Stmt q; |
| 1054 | int cnt = 0; |
| 1055 | const char *zExtra; |
| 1056 | if( db_table_exists("temp","onremote") ){ |
| 1057 | zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"; |
| 1058 | }else{ |
| 1059 | zExtra = ""; |
| 1060 | } |
| 1061 | db_prepare(&q, |
| 1062 | "SELECT uuid" |
| 1063 | " FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d" |
| 1064 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 1065 | " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" |
| 1066 | " AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)" |
| 1067 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s", |
| 1068 | TAG_CLUSTER, zExtra /*safe-for-%s*/ |
| 1069 | ); |
| 1070 | while( db_step(&q)==SQLITE_ROW ){ |
| 1071 | if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n"); |
| 1072 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 1073 | cnt++; |
| 1074 | } |
| 1075 | db_finalize(&q); |
| 1076 | if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n"); |
| 1077 | return cnt; |
| 1078 | } |
| 1079 | |
| 1080 | /* |
| 1081 | ** Send an igot message for every artifact. |
| 1082 | */ |
| 1083 | static void send_all(Xfer *pXfer){ |
| @@ -1781,10 +1814,19 @@ | |
| 1814 | ** The client sends this message to the server to ask the server |
| 1815 | ** to tell it about alternative repositories in the reply. |
| 1816 | */ |
| 1817 | if( blob_eq(&xfer.aToken[1], "req-links") ){ |
| 1818 | bSendLinks = 1; |
| 1819 | }else |
| 1820 | |
| 1821 | /* pragma req-clusters |
| 1822 | ** |
| 1823 | ** This pragma requests that the server send igot cards for every |
| 1824 | ** cluster artifact that it knows about. |
| 1825 | */ |
| 1826 | if( blob_eq(&xfer.aToken[1], "req-clusters") ){ |
| 1827 | send_all_clusters(&xfer); |
| 1828 | } |
| 1829 | |
| 1830 | }else |
| 1831 | |
| 1832 | /* Unknown message |
| @@ -1981,11 +2023,11 @@ | |
| 2023 | int nCardSent = 0; /* Number of cards sent */ |
| 2024 | int nCardRcvd = 0; /* Number of cards received */ |
| 2025 | int nCycle = 0; /* Number of round trips to the server */ |
| 2026 | int size; /* Size of a config value or uvfile */ |
| 2027 | int origConfigRcvMask; /* Original value of configRcvMask */ |
| 2028 | int nFileRecv = 0; /* Number of files received */ |
| 2029 | int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ |
| 2030 | const char *zCookie; /* Server cookie */ |
| 2031 | i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */ |
| 2032 | i64 nSent, nRcvd; /* Bytes sent and received (after compression) */ |
| 2033 | int cloneSeqno = 1; /* Sequence number for clones */ |
| @@ -2007,10 +2049,11 @@ | |
| 2049 | int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */ |
| 2050 | int uvDoPush = 0; /* Generate uvfile messages to send to server */ |
| 2051 | int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */ |
| 2052 | int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */ |
| 2053 | int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */ |
| 2054 | int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */ |
| 2055 | sqlite3_int64 mtime; /* Modification time on a UV file */ |
| 2056 | int autopushFailed = 0; /* Autopush following commit failed if true */ |
| 2057 | const char *zCkinLock; /* Name of check-in to lock. NULL for none */ |
| 2058 | const char *zClientId; /* A unique identifier for this check-out */ |
| 2059 | unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */ |
| @@ -2181,15 +2224,21 @@ | |
| 2224 | */ |
| 2225 | if( (syncFlags & SYNC_PULL)!=0 |
| 2226 | || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1) |
| 2227 | ){ |
| 2228 | request_phantoms(&xfer, mxPhantomReq); |
| 2229 | if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){ |
| 2230 | blob_appendf(&send, "pragma req-clusters\n"); |
| 2231 | } |
| 2232 | } |
| 2233 | if( syncFlags & SYNC_PUSH ){ |
| 2234 | send_unsent(&xfer); |
| 2235 | nCardSent += send_unclustered(&xfer); |
| 2236 | if( syncFlags & SYNC_PRIVATE ) send_private(&xfer); |
| 2237 | if( nGimmeRcvd>0 && nCycle==2 ){ |
| 2238 | send_all_clusters(&xfer); |
| 2239 | } |
| 2240 | } |
| 2241 | |
| 2242 | /* Client sends configuration parameter requests. On a clone, delay sending |
| 2243 | ** this until the second cycle since the login card might fail on |
| 2244 | ** the first cycle. |
| @@ -2369,10 +2418,11 @@ | |
| 2418 | nCardSent++; |
| 2419 | } |
| 2420 | go = 0; |
| 2421 | nUvGimmeSent = 0; |
| 2422 | nUvFileRcvd = 0; |
| 2423 | nGimmeRcvd = 0; |
| 2424 | nPriorArtifact = nArtifactRcvd; |
| 2425 | |
| 2426 | /* Process the reply that came back from the server */ |
| 2427 | while( blob_line(&recv, &xfer.line) ){ |
| 2428 | if( blob_buffer(&xfer.line)[0]=='#' ){ |
| @@ -2449,11 +2499,14 @@ | |
| 2499 | && blob_is_hname(&xfer.aToken[1]) |
| 2500 | ){ |
| 2501 | remote_unk(&xfer.aToken[1]); |
| 2502 | if( syncFlags & SYNC_PUSH ){ |
| 2503 | int rid = rid_from_uuid(&xfer.aToken[1], 0, 0); |
| 2504 | if( rid ){ |
| 2505 | send_file(&xfer, rid, &xfer.aToken[1], 0); |
| 2506 | nGimmeRcvd++; |
| 2507 | } |
| 2508 | } |
| 2509 | }else |
| 2510 | |
| 2511 | /* igot HASH ?PRIVATEFLAG? |
| 2512 | ** |
| 2513 |
+25
| --- test/merge1.test | ||
| +++ test/merge1.test | ||
| @@ -75,10 +75,12 @@ | ||
| 75 | 75 | 555 - we think it well and other stuff too - 5555 |
| 76 | 76 | } |
| 77 | 77 | write_file_indented t23 { |
| 78 | 78 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 79 | 79 | 111 - This is line ONE of the demo program - 1111 |
| 80 | + ####### SUGGESTED CONFLICT RESOLUTION follows ################### | |
| 81 | + 111 - This is line ONE OF the demo program - 1111 | |
| 80 | 82 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 81 | 83 | 111 - This is line one of the demo program - 1111 |
| 82 | 84 | ======= MERGED IN content follows =============================== (line 1) |
| 83 | 85 | 111 - This is line one OF the demo program - 1111 |
| 84 | 86 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -88,10 +90,12 @@ | ||
| 88 | 90 | 555 - we think it well and other stuff too - 5555 |
| 89 | 91 | } |
| 90 | 92 | write_file_indented t32 { |
| 91 | 93 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 92 | 94 | 111 - This is line one OF the demo program - 1111 |
| 95 | + ####### SUGGESTED CONFLICT RESOLUTION follows ################### | |
| 96 | + 111 - This is line ONE OF the demo program - 1111 | |
| 93 | 97 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 94 | 98 | 111 - This is line one of the demo program - 1111 |
| 95 | 99 | ======= MERGED IN content follows =============================== (line 1) |
| 96 | 100 | 111 - This is line ONE of the demo program - 1111 |
| 97 | 101 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -159,10 +163,13 @@ | ||
| 159 | 163 | 444 - If all goes well, we will be pleased - 4444 |
| 160 | 164 | 555 - we think it well and other stuff too - 5555 |
| 161 | 165 | } |
| 162 | 166 | write_file_indented t32 { |
| 163 | 167 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 168 | + ####### SUGGESTED CONFLICT RESOLUTION follows ################### | |
| 169 | + 000 - Zero lines added to the beginning of - 0000 | |
| 170 | + 111 - This is line one of the demo program - 1111 | |
| 164 | 171 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 165 | 172 | 111 - This is line one of the demo program - 1111 |
| 166 | 173 | ======= MERGED IN content follows =============================== (line 1) |
| 167 | 174 | 000 - Zero lines added to the beginning of - 0000 |
| 168 | 175 | 111 - This is line one of the demo program - 1111 |
| @@ -305,10 +312,19 @@ | ||
| 305 | 312 | mnop 2 |
| 306 | 313 | qrst |
| 307 | 314 | uvwx |
| 308 | 315 | yzAB 2 |
| 309 | 316 | CDEF 2 |
| 317 | + GHIJ 2 | |
| 318 | + ####### SUGGESTED CONFLICT RESOLUTION follows ################### | |
| 319 | + efgh 2 | |
| 320 | + ijkl 2 | |
| 321 | + mnop 3 | |
| 322 | + qrst 3 | |
| 323 | + uvwx 3 | |
| 324 | + yzAB 3 | |
| 325 | + CDEF 2 | |
| 310 | 326 | GHIJ 2 |
| 311 | 327 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 312 | 328 | efgh |
| 313 | 329 | ijkl |
| 314 | 330 | mnop |
| @@ -372,10 +388,19 @@ | ||
| 372 | 388 | ijkl 2 |
| 373 | 389 | mnop |
| 374 | 390 | qrst |
| 375 | 391 | uvwx |
| 376 | 392 | yzAB 2 |
| 393 | + CDEF 2 | |
| 394 | + GHIJ 2 | |
| 395 | + ####### SUGGESTED CONFLICT RESOLUTION follows ################### | |
| 396 | + efgh 2 | |
| 397 | + ijkl 2 | |
| 398 | + mnop 3 | |
| 399 | + qrst 3 | |
| 400 | + uvwx 3 | |
| 401 | + yzAB 3 | |
| 377 | 402 | CDEF 2 |
| 378 | 403 | GHIJ 2 |
| 379 | 404 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 380 | 405 | efgh |
| 381 | 406 | ijkl |
| 382 | 407 |
| --- test/merge1.test | |
| +++ test/merge1.test | |
| @@ -75,10 +75,12 @@ | |
| 75 | 555 - we think it well and other stuff too - 5555 |
| 76 | } |
| 77 | write_file_indented t23 { |
| 78 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 79 | 111 - This is line ONE of the demo program - 1111 |
| 80 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 81 | 111 - This is line one of the demo program - 1111 |
| 82 | ======= MERGED IN content follows =============================== (line 1) |
| 83 | 111 - This is line one OF the demo program - 1111 |
| 84 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -88,10 +90,12 @@ | |
| 88 | 555 - we think it well and other stuff too - 5555 |
| 89 | } |
| 90 | write_file_indented t32 { |
| 91 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 92 | 111 - This is line one OF the demo program - 1111 |
| 93 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 94 | 111 - This is line one of the demo program - 1111 |
| 95 | ======= MERGED IN content follows =============================== (line 1) |
| 96 | 111 - This is line ONE of the demo program - 1111 |
| 97 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -159,10 +163,13 @@ | |
| 159 | 444 - If all goes well, we will be pleased - 4444 |
| 160 | 555 - we think it well and other stuff too - 5555 |
| 161 | } |
| 162 | write_file_indented t32 { |
| 163 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 164 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 165 | 111 - This is line one of the demo program - 1111 |
| 166 | ======= MERGED IN content follows =============================== (line 1) |
| 167 | 000 - Zero lines added to the beginning of - 0000 |
| 168 | 111 - This is line one of the demo program - 1111 |
| @@ -305,10 +312,19 @@ | |
| 305 | mnop 2 |
| 306 | qrst |
| 307 | uvwx |
| 308 | yzAB 2 |
| 309 | CDEF 2 |
| 310 | GHIJ 2 |
| 311 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 312 | efgh |
| 313 | ijkl |
| 314 | mnop |
| @@ -372,10 +388,19 @@ | |
| 372 | ijkl 2 |
| 373 | mnop |
| 374 | qrst |
| 375 | uvwx |
| 376 | yzAB 2 |
| 377 | CDEF 2 |
| 378 | GHIJ 2 |
| 379 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 380 | efgh |
| 381 | ijkl |
| 382 |
| --- test/merge1.test | |
| +++ test/merge1.test | |
| @@ -75,10 +75,12 @@ | |
| 75 | 555 - we think it well and other stuff too - 5555 |
| 76 | } |
| 77 | write_file_indented t23 { |
| 78 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 79 | 111 - This is line ONE of the demo program - 1111 |
| 80 | ####### SUGGESTED CONFLICT RESOLUTION follows ################### |
| 81 | 111 - This is line ONE OF the demo program - 1111 |
| 82 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 83 | 111 - This is line one of the demo program - 1111 |
| 84 | ======= MERGED IN content follows =============================== (line 1) |
| 85 | 111 - This is line one OF the demo program - 1111 |
| 86 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -88,10 +90,12 @@ | |
| 90 | 555 - we think it well and other stuff too - 5555 |
| 91 | } |
| 92 | write_file_indented t32 { |
| 93 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 94 | 111 - This is line one OF the demo program - 1111 |
| 95 | ####### SUGGESTED CONFLICT RESOLUTION follows ################### |
| 96 | 111 - This is line ONE OF the demo program - 1111 |
| 97 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 98 | 111 - This is line one of the demo program - 1111 |
| 99 | ======= MERGED IN content follows =============================== (line 1) |
| 100 | 111 - This is line ONE of the demo program - 1111 |
| 101 | >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
| @@ -159,10 +163,13 @@ | |
| 163 | 444 - If all goes well, we will be pleased - 4444 |
| 164 | 555 - we think it well and other stuff too - 5555 |
| 165 | } |
| 166 | write_file_indented t32 { |
| 167 | <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1) |
| 168 | ####### SUGGESTED CONFLICT RESOLUTION follows ################### |
| 169 | 000 - Zero lines added to the beginning of - 0000 |
| 170 | 111 - This is line one of the demo program - 1111 |
| 171 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1) |
| 172 | 111 - This is line one of the demo program - 1111 |
| 173 | ======= MERGED IN content follows =============================== (line 1) |
| 174 | 000 - Zero lines added to the beginning of - 0000 |
| 175 | 111 - This is line one of the demo program - 1111 |
| @@ -305,10 +312,19 @@ | |
| 312 | mnop 2 |
| 313 | qrst |
| 314 | uvwx |
| 315 | yzAB 2 |
| 316 | CDEF 2 |
| 317 | GHIJ 2 |
| 318 | ####### SUGGESTED CONFLICT RESOLUTION follows ################### |
| 319 | efgh 2 |
| 320 | ijkl 2 |
| 321 | mnop 3 |
| 322 | qrst 3 |
| 323 | uvwx 3 |
| 324 | yzAB 3 |
| 325 | CDEF 2 |
| 326 | GHIJ 2 |
| 327 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 328 | efgh |
| 329 | ijkl |
| 330 | mnop |
| @@ -372,10 +388,19 @@ | |
| 388 | ijkl 2 |
| 389 | mnop |
| 390 | qrst |
| 391 | uvwx |
| 392 | yzAB 2 |
| 393 | CDEF 2 |
| 394 | GHIJ 2 |
| 395 | ####### SUGGESTED CONFLICT RESOLUTION follows ################### |
| 396 | efgh 2 |
| 397 | ijkl 2 |
| 398 | mnop 3 |
| 399 | qrst 3 |
| 400 | uvwx 3 |
| 401 | yzAB 3 |
| 402 | CDEF 2 |
| 403 | GHIJ 2 |
| 404 | ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2) |
| 405 | efgh |
| 406 | ijkl |
| 407 |
+28
-25
| --- test/merge3.test | ||
| +++ test/merge3.test | ||
| @@ -27,10 +27,13 @@ | ||
| 27 | 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | 28 | set x [read_file t4] |
| 29 | 29 | regsub -all \ |
| 30 | 30 | {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \ |
| 31 | 31 | $x {MINE:} x |
| 32 | + regsub -all \ | |
| 33 | + {####### SUGGESTED CONFLICT RESOLUTION follows #+} \ | |
| 34 | + $x {BOT:} x | |
| 32 | 35 | regsub -all \ |
| 33 | 36 | {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \ |
| 34 | 37 | $x {COM:} x |
| 35 | 38 | regsub -all \ |
| 36 | 39 | {======= MERGED IN content follows =+ \(line \d+\)} \ |
| @@ -73,56 +76,56 @@ | ||
| 73 | 76 | } { |
| 74 | 77 | 1 2 3b 4b 5b 6 7 8 9 |
| 75 | 78 | } { |
| 76 | 79 | 1 2 3 4 5c 6 7 8 9 |
| 77 | 80 | } { |
| 78 | - 1 2 MINE: 3b 4b 5b COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9 | |
| 81 | + 1 2 MINE: 3b 4b 5b BOT: 3b 4b 5c COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9 | |
| 79 | 82 | } -expectError |
| 80 | 83 | merge-test 4 { |
| 81 | 84 | 1 2 3 4 5 6 7 8 9 |
| 82 | 85 | } { |
| 83 | 86 | 1 2 3b 4b 5b 6b 7 8 9 |
| 84 | 87 | } { |
| 85 | 88 | 1 2 3 4 5c 6 7 8 9 |
| 86 | 89 | } { |
| 87 | - 1 2 MINE: 3b 4b 5b 6b COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9 | |
| 90 | + 1 2 MINE: 3b 4b 5b 6b BOT: 3b 4b 5b 5c 6 COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9 | |
| 88 | 91 | } -expectError |
| 89 | 92 | merge-test 5 { |
| 90 | 93 | 1 2 3 4 5 6 7 8 9 |
| 91 | 94 | } { |
| 92 | 95 | 1 2 3b 4b 5b 6b 7 8 9 |
| 93 | 96 | } { |
| 94 | 97 | 1 2 3 4 5c 6c 7c 8 9 |
| 95 | 98 | } { |
| 96 | - 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9 | |
| 99 | + 1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9 | |
| 97 | 100 | } -expectError |
| 98 | 101 | merge-test 6 { |
| 99 | 102 | 1 2 3 4 5 6 7 8 9 |
| 100 | 103 | } { |
| 101 | 104 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 102 | 105 | } { |
| 103 | 106 | 1 2 3 4 5c 6c 7c 8 9 |
| 104 | 107 | } { |
| 105 | - 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9 | |
| 108 | + 1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9 | |
| 106 | 109 | } -expectError |
| 107 | 110 | merge-test 7 { |
| 108 | 111 | 1 2 3 4 5 6 7 8 9 |
| 109 | 112 | } { |
| 110 | 113 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 111 | 114 | } { |
| 112 | 115 | 1 2 3 4 5c 6c 7c 8c 9 |
| 113 | 116 | } { |
| 114 | - 1 2 MINE: 3b 4b 5b 6b 7 8b COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9 | |
| 117 | + 1 2 MINE: 3b 4b 5b 6b 7 8b BOT: 3b 4b 5b 5c 6c 7c 8c COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9 | |
| 115 | 118 | } -expectError |
| 116 | 119 | merge-test 8 { |
| 117 | 120 | 1 2 3 4 5 6 7 8 9 |
| 118 | 121 | } { |
| 119 | 122 | 1 2 3b 4b 5b 6b 7 8b 9b |
| 120 | 123 | } { |
| 121 | 124 | 1 2 3 4 5c 6c 7c 8c 9 |
| 122 | 125 | } { |
| 123 | - 1 2 MINE: 3b 4b 5b 6b 7 8b 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END | |
| 126 | + 1 2 MINE: 3b 4b 5b 6b 7 8b 9b BOT: 3b 4b 5b 5c 6c 7c 8c 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END | |
| 124 | 127 | } -expectError |
| 125 | 128 | merge-test 9 { |
| 126 | 129 | 1 2 3 4 5 6 7 8 9 |
| 127 | 130 | } { |
| 128 | 131 | 1 2 3b 4b 5 6 7 8b 9b |
| @@ -146,11 +149,11 @@ | ||
| 146 | 149 | } { |
| 147 | 150 | 1 2 3b 4b 5 6 7 8b 9b |
| 148 | 151 | } { |
| 149 | 152 | 1 2 3b 4c 5 6c 7c 8 9 |
| 150 | 153 | } { |
| 151 | - 1 2 MINE: 3b 4b COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b | |
| 154 | + 1 2 MINE: 3b 4b BOT: 3b 4c COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b | |
| 152 | 155 | } -expectError |
| 153 | 156 | merge-test 12 { |
| 154 | 157 | 1 2 3 4 5 6 7 8 9 |
| 155 | 158 | } { |
| 156 | 159 | 1 2 3b4b 5 6 7 8b 9b |
| @@ -201,20 +204,20 @@ | ||
| 201 | 204 | } { |
| 202 | 205 | 1 6 7 8 9 |
| 203 | 206 | } { |
| 204 | 207 | 1 2 3 4 9 |
| 205 | 208 | } { |
| 206 | - 1 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 | |
| 209 | + 1 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 | |
| 207 | 210 | } -expectError |
| 208 | 211 | merge-test 25 { |
| 209 | 212 | 1 2 3 4 5 6 7 8 9 |
| 210 | 213 | } { |
| 211 | 214 | 1 7 8 9 |
| 212 | 215 | } { |
| 213 | 216 | 1 2 3 9 |
| 214 | 217 | } { |
| 215 | - 1 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 | |
| 218 | + 1 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 | |
| 216 | 219 | } -expectError |
| 217 | 220 | |
| 218 | 221 | merge-test 30 { |
| 219 | 222 | 1 2 3 4 5 6 7 8 9 |
| 220 | 223 | } { |
| @@ -256,20 +259,20 @@ | ||
| 256 | 259 | } { |
| 257 | 260 | 1 2 3 4 9 |
| 258 | 261 | } { |
| 259 | 262 | 1 6 7 8 9 |
| 260 | 263 | } { |
| 261 | - 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9 | |
| 264 | + 1 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9 | |
| 262 | 265 | } -expectError |
| 263 | 266 | merge-test 35 { |
| 264 | 267 | 1 2 3 4 5 6 7 8 9 |
| 265 | 268 | } { |
| 266 | 269 | 1 2 3 9 |
| 267 | 270 | } { |
| 268 | 271 | 1 7 8 9 |
| 269 | 272 | } { |
| 270 | - 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9 | |
| 273 | + 1 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9 | |
| 271 | 274 | } -expectError |
| 272 | 275 | |
| 273 | 276 | merge-test 40 { |
| 274 | 277 | 2 3 4 5 6 7 8 |
| 275 | 278 | } { |
| @@ -311,20 +314,20 @@ | ||
| 311 | 314 | } { |
| 312 | 315 | 6 7 8 |
| 313 | 316 | } { |
| 314 | 317 | 2 3 4 |
| 315 | 318 | } { |
| 316 | - MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END | |
| 319 | + MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END | |
| 317 | 320 | } -expectError |
| 318 | 321 | merge-test 45 { |
| 319 | 322 | 2 3 4 5 6 7 8 |
| 320 | 323 | } { |
| 321 | 324 | 7 8 |
| 322 | 325 | } { |
| 323 | 326 | 2 3 |
| 324 | 327 | } { |
| 325 | - MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END | |
| 328 | + MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END | |
| 326 | 329 | } -expectError |
| 327 | 330 | |
| 328 | 331 | merge-test 50 { |
| 329 | 332 | 2 3 4 5 6 7 8 |
| 330 | 333 | } { |
| @@ -365,20 +368,20 @@ | ||
| 365 | 368 | } { |
| 366 | 369 | 2 3 4 |
| 367 | 370 | } { |
| 368 | 371 | 6 7 8 |
| 369 | 372 | } { |
| 370 | - MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END | |
| 373 | + MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END | |
| 371 | 374 | } -expectError |
| 372 | 375 | merge-test 55 { |
| 373 | 376 | 2 3 4 5 6 7 8 |
| 374 | 377 | } { |
| 375 | 378 | 2 3 |
| 376 | 379 | } { |
| 377 | 380 | 7 8 |
| 378 | 381 | } { |
| 379 | - MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END | |
| 382 | + MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END | |
| 380 | 383 | } -expectError |
| 381 | 384 | |
| 382 | 385 | merge-test 60 { |
| 383 | 386 | 1 2 3 4 5 6 7 8 9 |
| 384 | 387 | } { |
| @@ -420,20 +423,20 @@ | ||
| 420 | 423 | } { |
| 421 | 424 | 1 2b 3b 4b 5b 6 7 8 9 |
| 422 | 425 | } { |
| 423 | 426 | 1 2 3 4 9 |
| 424 | 427 | } { |
| 425 | - 1 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 | |
| 428 | + 1 MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 | |
| 426 | 429 | } -expectError |
| 427 | 430 | merge-test 65 { |
| 428 | 431 | 1 2 3 4 5 6 7 8 9 |
| 429 | 432 | } { |
| 430 | 433 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 431 | 434 | } { |
| 432 | 435 | 1 2 3 9 |
| 433 | 436 | } { |
| 434 | - 1 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 | |
| 437 | + 1 MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 | |
| 435 | 438 | } -expectError |
| 436 | 439 | |
| 437 | 440 | merge-test 70 { |
| 438 | 441 | 1 2 3 4 5 6 7 8 9 |
| 439 | 442 | } { |
| @@ -475,20 +478,20 @@ | ||
| 475 | 478 | } { |
| 476 | 479 | 1 2 3 4 9 |
| 477 | 480 | } { |
| 478 | 481 | 1 2b 3b 4b 5b 6 7 8 9 |
| 479 | 482 | } { |
| 480 | - 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9 | |
| 483 | + 1 MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9 | |
| 481 | 484 | } -expectError |
| 482 | 485 | merge-test 75 { |
| 483 | 486 | 1 2 3 4 5 6 7 8 9 |
| 484 | 487 | } { |
| 485 | 488 | 1 2 3 9 |
| 486 | 489 | } { |
| 487 | 490 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 488 | 491 | } { |
| 489 | - 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9 | |
| 492 | + 1 MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9 | |
| 490 | 493 | } -expectError |
| 491 | 494 | |
| 492 | 495 | merge-test 80 { |
| 493 | 496 | 2 3 4 5 6 7 8 |
| 494 | 497 | } { |
| @@ -530,20 +533,20 @@ | ||
| 530 | 533 | } { |
| 531 | 534 | 2b 3b 4b 5b 6 7 8 |
| 532 | 535 | } { |
| 533 | 536 | 2 3 4 |
| 534 | 537 | } { |
| 535 | - MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END | |
| 538 | + MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END | |
| 536 | 539 | } -expectError |
| 537 | 540 | merge-test 85 { |
| 538 | 541 | 2 3 4 5 6 7 8 |
| 539 | 542 | } { |
| 540 | 543 | 2b 3b 4b 5b 6b 7 8 |
| 541 | 544 | } { |
| 542 | 545 | 2 3 |
| 543 | 546 | } { |
| 544 | - MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END | |
| 547 | + MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END | |
| 545 | 548 | } -expectError |
| 546 | 549 | |
| 547 | 550 | merge-test 90 { |
| 548 | 551 | 2 3 4 5 6 7 8 |
| 549 | 552 | } { |
| @@ -585,20 +588,20 @@ | ||
| 585 | 588 | } { |
| 586 | 589 | 2 3 4 |
| 587 | 590 | } { |
| 588 | 591 | 2b 3b 4b 5b 6 7 8 |
| 589 | 592 | } { |
| 590 | - MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END | |
| 593 | + MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END | |
| 591 | 594 | } -expectError |
| 592 | 595 | merge-test 95 { |
| 593 | 596 | 2 3 4 5 6 7 8 |
| 594 | 597 | } { |
| 595 | 598 | 2 3 |
| 596 | 599 | } { |
| 597 | 600 | 2b 3b 4b 5b 6b 7 8 |
| 598 | 601 | } { |
| 599 | - MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END | |
| 602 | + MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END | |
| 600 | 603 | } -expectError |
| 601 | 604 | |
| 602 | 605 | merge-test 100 { |
| 603 | 606 | 1 2 3 4 5 6 7 8 9 |
| 604 | 607 | } { |
| @@ -631,20 +634,20 @@ | ||
| 631 | 634 | } { |
| 632 | 635 | 1 2 3 4 5 7 8 9b |
| 633 | 636 | } { |
| 634 | 637 | 1 2 3 4 5 7 8 9b a b c d e |
| 635 | 638 | } { |
| 636 | - 1 2 3 4 5 7 8 MINE: 9b COM: 9 YOURS: 9b a b c d e END | |
| 639 | + 1 2 3 4 5 7 8 MINE: 9b BOT: 9b a b c d e COM: 9 YOURS: 9b a b c d e END | |
| 637 | 640 | } -expectError |
| 638 | 641 | merge-test 104 { |
| 639 | 642 | 1 2 3 4 5 6 7 8 9 |
| 640 | 643 | } { |
| 641 | 644 | 1 2 3 4 5 7 8 9b a b c d e |
| 642 | 645 | } { |
| 643 | 646 | 1 2 3 4 5 7 8 9b |
| 644 | 647 | } { |
| 645 | - 1 2 3 4 5 7 8 MINE: 9b a b c d e COM: 9 YOURS: 9b END | |
| 648 | + 1 2 3 4 5 7 8 MINE: 9b a b c d e BOT: 9b COM: 9 YOURS: 9b END | |
| 646 | 649 | } -expectError |
| 647 | 650 | |
| 648 | 651 | ############################################################################### |
| 649 | 652 | |
| 650 | 653 | test_cleanup |
| 651 | 654 |
| --- test/merge3.test | |
| +++ test/merge3.test | |
| @@ -27,10 +27,13 @@ | |
| 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | set x [read_file t4] |
| 29 | regsub -all \ |
| 30 | {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \ |
| 31 | $x {MINE:} x |
| 32 | regsub -all \ |
| 33 | {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \ |
| 34 | $x {COM:} x |
| 35 | regsub -all \ |
| 36 | {======= MERGED IN content follows =+ \(line \d+\)} \ |
| @@ -73,56 +76,56 @@ | |
| 73 | } { |
| 74 | 1 2 3b 4b 5b 6 7 8 9 |
| 75 | } { |
| 76 | 1 2 3 4 5c 6 7 8 9 |
| 77 | } { |
| 78 | 1 2 MINE: 3b 4b 5b COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9 |
| 79 | } -expectError |
| 80 | merge-test 4 { |
| 81 | 1 2 3 4 5 6 7 8 9 |
| 82 | } { |
| 83 | 1 2 3b 4b 5b 6b 7 8 9 |
| 84 | } { |
| 85 | 1 2 3 4 5c 6 7 8 9 |
| 86 | } { |
| 87 | 1 2 MINE: 3b 4b 5b 6b COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9 |
| 88 | } -expectError |
| 89 | merge-test 5 { |
| 90 | 1 2 3 4 5 6 7 8 9 |
| 91 | } { |
| 92 | 1 2 3b 4b 5b 6b 7 8 9 |
| 93 | } { |
| 94 | 1 2 3 4 5c 6c 7c 8 9 |
| 95 | } { |
| 96 | 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9 |
| 97 | } -expectError |
| 98 | merge-test 6 { |
| 99 | 1 2 3 4 5 6 7 8 9 |
| 100 | } { |
| 101 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 102 | } { |
| 103 | 1 2 3 4 5c 6c 7c 8 9 |
| 104 | } { |
| 105 | 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9 |
| 106 | } -expectError |
| 107 | merge-test 7 { |
| 108 | 1 2 3 4 5 6 7 8 9 |
| 109 | } { |
| 110 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 111 | } { |
| 112 | 1 2 3 4 5c 6c 7c 8c 9 |
| 113 | } { |
| 114 | 1 2 MINE: 3b 4b 5b 6b 7 8b COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9 |
| 115 | } -expectError |
| 116 | merge-test 8 { |
| 117 | 1 2 3 4 5 6 7 8 9 |
| 118 | } { |
| 119 | 1 2 3b 4b 5b 6b 7 8b 9b |
| 120 | } { |
| 121 | 1 2 3 4 5c 6c 7c 8c 9 |
| 122 | } { |
| 123 | 1 2 MINE: 3b 4b 5b 6b 7 8b 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END |
| 124 | } -expectError |
| 125 | merge-test 9 { |
| 126 | 1 2 3 4 5 6 7 8 9 |
| 127 | } { |
| 128 | 1 2 3b 4b 5 6 7 8b 9b |
| @@ -146,11 +149,11 @@ | |
| 146 | } { |
| 147 | 1 2 3b 4b 5 6 7 8b 9b |
| 148 | } { |
| 149 | 1 2 3b 4c 5 6c 7c 8 9 |
| 150 | } { |
| 151 | 1 2 MINE: 3b 4b COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b |
| 152 | } -expectError |
| 153 | merge-test 12 { |
| 154 | 1 2 3 4 5 6 7 8 9 |
| 155 | } { |
| 156 | 1 2 3b4b 5 6 7 8b 9b |
| @@ -201,20 +204,20 @@ | |
| 201 | } { |
| 202 | 1 6 7 8 9 |
| 203 | } { |
| 204 | 1 2 3 4 9 |
| 205 | } { |
| 206 | 1 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 |
| 207 | } -expectError |
| 208 | merge-test 25 { |
| 209 | 1 2 3 4 5 6 7 8 9 |
| 210 | } { |
| 211 | 1 7 8 9 |
| 212 | } { |
| 213 | 1 2 3 9 |
| 214 | } { |
| 215 | 1 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 |
| 216 | } -expectError |
| 217 | |
| 218 | merge-test 30 { |
| 219 | 1 2 3 4 5 6 7 8 9 |
| 220 | } { |
| @@ -256,20 +259,20 @@ | |
| 256 | } { |
| 257 | 1 2 3 4 9 |
| 258 | } { |
| 259 | 1 6 7 8 9 |
| 260 | } { |
| 261 | 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9 |
| 262 | } -expectError |
| 263 | merge-test 35 { |
| 264 | 1 2 3 4 5 6 7 8 9 |
| 265 | } { |
| 266 | 1 2 3 9 |
| 267 | } { |
| 268 | 1 7 8 9 |
| 269 | } { |
| 270 | 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9 |
| 271 | } -expectError |
| 272 | |
| 273 | merge-test 40 { |
| 274 | 2 3 4 5 6 7 8 |
| 275 | } { |
| @@ -311,20 +314,20 @@ | |
| 311 | } { |
| 312 | 6 7 8 |
| 313 | } { |
| 314 | 2 3 4 |
| 315 | } { |
| 316 | MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END |
| 317 | } -expectError |
| 318 | merge-test 45 { |
| 319 | 2 3 4 5 6 7 8 |
| 320 | } { |
| 321 | 7 8 |
| 322 | } { |
| 323 | 2 3 |
| 324 | } { |
| 325 | MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END |
| 326 | } -expectError |
| 327 | |
| 328 | merge-test 50 { |
| 329 | 2 3 4 5 6 7 8 |
| 330 | } { |
| @@ -365,20 +368,20 @@ | |
| 365 | } { |
| 366 | 2 3 4 |
| 367 | } { |
| 368 | 6 7 8 |
| 369 | } { |
| 370 | MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END |
| 371 | } -expectError |
| 372 | merge-test 55 { |
| 373 | 2 3 4 5 6 7 8 |
| 374 | } { |
| 375 | 2 3 |
| 376 | } { |
| 377 | 7 8 |
| 378 | } { |
| 379 | MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END |
| 380 | } -expectError |
| 381 | |
| 382 | merge-test 60 { |
| 383 | 1 2 3 4 5 6 7 8 9 |
| 384 | } { |
| @@ -420,20 +423,20 @@ | |
| 420 | } { |
| 421 | 1 2b 3b 4b 5b 6 7 8 9 |
| 422 | } { |
| 423 | 1 2 3 4 9 |
| 424 | } { |
| 425 | 1 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 |
| 426 | } -expectError |
| 427 | merge-test 65 { |
| 428 | 1 2 3 4 5 6 7 8 9 |
| 429 | } { |
| 430 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 431 | } { |
| 432 | 1 2 3 9 |
| 433 | } { |
| 434 | 1 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 |
| 435 | } -expectError |
| 436 | |
| 437 | merge-test 70 { |
| 438 | 1 2 3 4 5 6 7 8 9 |
| 439 | } { |
| @@ -475,20 +478,20 @@ | |
| 475 | } { |
| 476 | 1 2 3 4 9 |
| 477 | } { |
| 478 | 1 2b 3b 4b 5b 6 7 8 9 |
| 479 | } { |
| 480 | 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9 |
| 481 | } -expectError |
| 482 | merge-test 75 { |
| 483 | 1 2 3 4 5 6 7 8 9 |
| 484 | } { |
| 485 | 1 2 3 9 |
| 486 | } { |
| 487 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 488 | } { |
| 489 | 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9 |
| 490 | } -expectError |
| 491 | |
| 492 | merge-test 80 { |
| 493 | 2 3 4 5 6 7 8 |
| 494 | } { |
| @@ -530,20 +533,20 @@ | |
| 530 | } { |
| 531 | 2b 3b 4b 5b 6 7 8 |
| 532 | } { |
| 533 | 2 3 4 |
| 534 | } { |
| 535 | MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END |
| 536 | } -expectError |
| 537 | merge-test 85 { |
| 538 | 2 3 4 5 6 7 8 |
| 539 | } { |
| 540 | 2b 3b 4b 5b 6b 7 8 |
| 541 | } { |
| 542 | 2 3 |
| 543 | } { |
| 544 | MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END |
| 545 | } -expectError |
| 546 | |
| 547 | merge-test 90 { |
| 548 | 2 3 4 5 6 7 8 |
| 549 | } { |
| @@ -585,20 +588,20 @@ | |
| 585 | } { |
| 586 | 2 3 4 |
| 587 | } { |
| 588 | 2b 3b 4b 5b 6 7 8 |
| 589 | } { |
| 590 | MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END |
| 591 | } -expectError |
| 592 | merge-test 95 { |
| 593 | 2 3 4 5 6 7 8 |
| 594 | } { |
| 595 | 2 3 |
| 596 | } { |
| 597 | 2b 3b 4b 5b 6b 7 8 |
| 598 | } { |
| 599 | MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END |
| 600 | } -expectError |
| 601 | |
| 602 | merge-test 100 { |
| 603 | 1 2 3 4 5 6 7 8 9 |
| 604 | } { |
| @@ -631,20 +634,20 @@ | |
| 631 | } { |
| 632 | 1 2 3 4 5 7 8 9b |
| 633 | } { |
| 634 | 1 2 3 4 5 7 8 9b a b c d e |
| 635 | } { |
| 636 | 1 2 3 4 5 7 8 MINE: 9b COM: 9 YOURS: 9b a b c d e END |
| 637 | } -expectError |
| 638 | merge-test 104 { |
| 639 | 1 2 3 4 5 6 7 8 9 |
| 640 | } { |
| 641 | 1 2 3 4 5 7 8 9b a b c d e |
| 642 | } { |
| 643 | 1 2 3 4 5 7 8 9b |
| 644 | } { |
| 645 | 1 2 3 4 5 7 8 MINE: 9b a b c d e COM: 9 YOURS: 9b END |
| 646 | } -expectError |
| 647 | |
| 648 | ############################################################################### |
| 649 | |
| 650 | test_cleanup |
| 651 |
| --- test/merge3.test | |
| +++ test/merge3.test | |
| @@ -27,10 +27,13 @@ | |
| 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | set x [read_file t4] |
| 29 | regsub -all \ |
| 30 | {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \ |
| 31 | $x {MINE:} x |
| 32 | regsub -all \ |
| 33 | {####### SUGGESTED CONFLICT RESOLUTION follows #+} \ |
| 34 | $x {BOT:} x |
| 35 | regsub -all \ |
| 36 | {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \ |
| 37 | $x {COM:} x |
| 38 | regsub -all \ |
| 39 | {======= MERGED IN content follows =+ \(line \d+\)} \ |
| @@ -73,56 +76,56 @@ | |
| 76 | } { |
| 77 | 1 2 3b 4b 5b 6 7 8 9 |
| 78 | } { |
| 79 | 1 2 3 4 5c 6 7 8 9 |
| 80 | } { |
| 81 | 1 2 MINE: 3b 4b 5b BOT: 3b 4b 5c COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9 |
| 82 | } -expectError |
| 83 | merge-test 4 { |
| 84 | 1 2 3 4 5 6 7 8 9 |
| 85 | } { |
| 86 | 1 2 3b 4b 5b 6b 7 8 9 |
| 87 | } { |
| 88 | 1 2 3 4 5c 6 7 8 9 |
| 89 | } { |
| 90 | 1 2 MINE: 3b 4b 5b 6b BOT: 3b 4b 5b 5c 6 COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9 |
| 91 | } -expectError |
| 92 | merge-test 5 { |
| 93 | 1 2 3 4 5 6 7 8 9 |
| 94 | } { |
| 95 | 1 2 3b 4b 5b 6b 7 8 9 |
| 96 | } { |
| 97 | 1 2 3 4 5c 6c 7c 8 9 |
| 98 | } { |
| 99 | 1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9 |
| 100 | } -expectError |
| 101 | merge-test 6 { |
| 102 | 1 2 3 4 5 6 7 8 9 |
| 103 | } { |
| 104 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 105 | } { |
| 106 | 1 2 3 4 5c 6c 7c 8 9 |
| 107 | } { |
| 108 | 1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9 |
| 109 | } -expectError |
| 110 | merge-test 7 { |
| 111 | 1 2 3 4 5 6 7 8 9 |
| 112 | } { |
| 113 | 1 2 3b 4b 5b 6b 7 8b 9 |
| 114 | } { |
| 115 | 1 2 3 4 5c 6c 7c 8c 9 |
| 116 | } { |
| 117 | 1 2 MINE: 3b 4b 5b 6b 7 8b BOT: 3b 4b 5b 5c 6c 7c 8c COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9 |
| 118 | } -expectError |
| 119 | merge-test 8 { |
| 120 | 1 2 3 4 5 6 7 8 9 |
| 121 | } { |
| 122 | 1 2 3b 4b 5b 6b 7 8b 9b |
| 123 | } { |
| 124 | 1 2 3 4 5c 6c 7c 8c 9 |
| 125 | } { |
| 126 | 1 2 MINE: 3b 4b 5b 6b 7 8b 9b BOT: 3b 4b 5b 5c 6c 7c 8c 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END |
| 127 | } -expectError |
| 128 | merge-test 9 { |
| 129 | 1 2 3 4 5 6 7 8 9 |
| 130 | } { |
| 131 | 1 2 3b 4b 5 6 7 8b 9b |
| @@ -146,11 +149,11 @@ | |
| 149 | } { |
| 150 | 1 2 3b 4b 5 6 7 8b 9b |
| 151 | } { |
| 152 | 1 2 3b 4c 5 6c 7c 8 9 |
| 153 | } { |
| 154 | 1 2 MINE: 3b 4b BOT: 3b 4c COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b |
| 155 | } -expectError |
| 156 | merge-test 12 { |
| 157 | 1 2 3 4 5 6 7 8 9 |
| 158 | } { |
| 159 | 1 2 3b4b 5 6 7 8b 9b |
| @@ -201,20 +204,20 @@ | |
| 204 | } { |
| 205 | 1 6 7 8 9 |
| 206 | } { |
| 207 | 1 2 3 4 9 |
| 208 | } { |
| 209 | 1 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 |
| 210 | } -expectError |
| 211 | merge-test 25 { |
| 212 | 1 2 3 4 5 6 7 8 9 |
| 213 | } { |
| 214 | 1 7 8 9 |
| 215 | } { |
| 216 | 1 2 3 9 |
| 217 | } { |
| 218 | 1 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 |
| 219 | } -expectError |
| 220 | |
| 221 | merge-test 30 { |
| 222 | 1 2 3 4 5 6 7 8 9 |
| 223 | } { |
| @@ -256,20 +259,20 @@ | |
| 259 | } { |
| 260 | 1 2 3 4 9 |
| 261 | } { |
| 262 | 1 6 7 8 9 |
| 263 | } { |
| 264 | 1 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9 |
| 265 | } -expectError |
| 266 | merge-test 35 { |
| 267 | 1 2 3 4 5 6 7 8 9 |
| 268 | } { |
| 269 | 1 2 3 9 |
| 270 | } { |
| 271 | 1 7 8 9 |
| 272 | } { |
| 273 | 1 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9 |
| 274 | } -expectError |
| 275 | |
| 276 | merge-test 40 { |
| 277 | 2 3 4 5 6 7 8 |
| 278 | } { |
| @@ -311,20 +314,20 @@ | |
| 314 | } { |
| 315 | 6 7 8 |
| 316 | } { |
| 317 | 2 3 4 |
| 318 | } { |
| 319 | MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END |
| 320 | } -expectError |
| 321 | merge-test 45 { |
| 322 | 2 3 4 5 6 7 8 |
| 323 | } { |
| 324 | 7 8 |
| 325 | } { |
| 326 | 2 3 |
| 327 | } { |
| 328 | MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END |
| 329 | } -expectError |
| 330 | |
| 331 | merge-test 50 { |
| 332 | 2 3 4 5 6 7 8 |
| 333 | } { |
| @@ -365,20 +368,20 @@ | |
| 368 | } { |
| 369 | 2 3 4 |
| 370 | } { |
| 371 | 6 7 8 |
| 372 | } { |
| 373 | MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END |
| 374 | } -expectError |
| 375 | merge-test 55 { |
| 376 | 2 3 4 5 6 7 8 |
| 377 | } { |
| 378 | 2 3 |
| 379 | } { |
| 380 | 7 8 |
| 381 | } { |
| 382 | MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END |
| 383 | } -expectError |
| 384 | |
| 385 | merge-test 60 { |
| 386 | 1 2 3 4 5 6 7 8 9 |
| 387 | } { |
| @@ -420,20 +423,20 @@ | |
| 423 | } { |
| 424 | 1 2b 3b 4b 5b 6 7 8 9 |
| 425 | } { |
| 426 | 1 2 3 4 9 |
| 427 | } { |
| 428 | 1 MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9 |
| 429 | } -expectError |
| 430 | merge-test 65 { |
| 431 | 1 2 3 4 5 6 7 8 9 |
| 432 | } { |
| 433 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 434 | } { |
| 435 | 1 2 3 9 |
| 436 | } { |
| 437 | 1 MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9 |
| 438 | } -expectError |
| 439 | |
| 440 | merge-test 70 { |
| 441 | 1 2 3 4 5 6 7 8 9 |
| 442 | } { |
| @@ -475,20 +478,20 @@ | |
| 478 | } { |
| 479 | 1 2 3 4 9 |
| 480 | } { |
| 481 | 1 2b 3b 4b 5b 6 7 8 9 |
| 482 | } { |
| 483 | 1 MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9 |
| 484 | } -expectError |
| 485 | merge-test 75 { |
| 486 | 1 2 3 4 5 6 7 8 9 |
| 487 | } { |
| 488 | 1 2 3 9 |
| 489 | } { |
| 490 | 1 2b 3b 4b 5b 6b 7 8 9 |
| 491 | } { |
| 492 | 1 MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9 |
| 493 | } -expectError |
| 494 | |
| 495 | merge-test 80 { |
| 496 | 2 3 4 5 6 7 8 |
| 497 | } { |
| @@ -530,20 +533,20 @@ | |
| 533 | } { |
| 534 | 2b 3b 4b 5b 6 7 8 |
| 535 | } { |
| 536 | 2 3 4 |
| 537 | } { |
| 538 | MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END |
| 539 | } -expectError |
| 540 | merge-test 85 { |
| 541 | 2 3 4 5 6 7 8 |
| 542 | } { |
| 543 | 2b 3b 4b 5b 6b 7 8 |
| 544 | } { |
| 545 | 2 3 |
| 546 | } { |
| 547 | MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END |
| 548 | } -expectError |
| 549 | |
| 550 | merge-test 90 { |
| 551 | 2 3 4 5 6 7 8 |
| 552 | } { |
| @@ -585,20 +588,20 @@ | |
| 588 | } { |
| 589 | 2 3 4 |
| 590 | } { |
| 591 | 2b 3b 4b 5b 6 7 8 |
| 592 | } { |
| 593 | MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END |
| 594 | } -expectError |
| 595 | merge-test 95 { |
| 596 | 2 3 4 5 6 7 8 |
| 597 | } { |
| 598 | 2 3 |
| 599 | } { |
| 600 | 2b 3b 4b 5b 6b 7 8 |
| 601 | } { |
| 602 | MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END |
| 603 | } -expectError |
| 604 | |
| 605 | merge-test 100 { |
| 606 | 1 2 3 4 5 6 7 8 9 |
| 607 | } { |
| @@ -631,20 +634,20 @@ | |
| 634 | } { |
| 635 | 1 2 3 4 5 7 8 9b |
| 636 | } { |
| 637 | 1 2 3 4 5 7 8 9b a b c d e |
| 638 | } { |
| 639 | 1 2 3 4 5 7 8 MINE: 9b BOT: 9b a b c d e COM: 9 YOURS: 9b a b c d e END |
| 640 | } -expectError |
| 641 | merge-test 104 { |
| 642 | 1 2 3 4 5 6 7 8 9 |
| 643 | } { |
| 644 | 1 2 3 4 5 7 8 9b a b c d e |
| 645 | } { |
| 646 | 1 2 3 4 5 7 8 9b |
| 647 | } { |
| 648 | 1 2 3 4 5 7 8 MINE: 9b a b c d e BOT: 9b COM: 9 YOURS: 9b END |
| 649 | } -expectError |
| 650 | |
| 651 | ############################################################################### |
| 652 | |
| 653 | test_cleanup |
| 654 |
+6
-4
| --- test/merge4.test | ||
| +++ test/merge4.test | ||
| @@ -26,15 +26,17 @@ | ||
| 26 | 26 | write_file t3 [join [string trim $v2] \n]\n |
| 27 | 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | 28 | fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args |
| 29 | 29 | set x [read_file t4] |
| 30 | 30 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x |
| 31 | + regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $x {#} x | |
| 31 | 32 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x |
| 32 | 33 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x |
| 33 | 34 | set x [split [string trim $x] \n] |
| 34 | 35 | set y [read_file t5] |
| 35 | 36 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y |
| 37 | + regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $y {#} y | |
| 36 | 38 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y |
| 37 | 39 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y |
| 38 | 40 | set y [split [string trim $y] \n] |
| 39 | 41 | set result1 [string trim $result1] |
| 40 | 42 | if {$x!=$result1} { |
| @@ -58,13 +60,13 @@ | ||
| 58 | 60 | } { |
| 59 | 61 | 1 2b 3b 4b 5 6b 7b 8b 9 |
| 60 | 62 | } { |
| 61 | 63 | 1 2 3 4c 5c 6c 7 8 9 |
| 62 | 64 | } { |
| 63 | - 1 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < 9 | |
| 65 | + 1 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < 9 | |
| 64 | 66 | } { |
| 65 | - 1 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < 9 | |
| 67 | + 1 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < 9 | |
| 66 | 68 | } -expectError |
| 67 | 69 | merge-test 1001 { |
| 68 | 70 | 1 2 3 4 5 6 7 8 9 |
| 69 | 71 | } { |
| 70 | 72 | 1 2b 3b 4 5 6 7b 8b 9 |
| @@ -80,13 +82,13 @@ | ||
| 80 | 82 | } { |
| 81 | 83 | 2b 3b 4b 5 6b 7b 8b |
| 82 | 84 | } { |
| 83 | 85 | 2 3 4c 5c 6c 7 8 |
| 84 | 86 | } { |
| 85 | - > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < | |
| 87 | + > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < | |
| 86 | 88 | } { |
| 87 | - > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < | |
| 89 | + > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < | |
| 88 | 90 | } -expectError |
| 89 | 91 | merge-test 1003 { |
| 90 | 92 | 2 3 4 5 6 7 8 |
| 91 | 93 | } { |
| 92 | 94 | 2b 3b 4 5 6 7b 8b |
| 93 | 95 |
| --- test/merge4.test | |
| +++ test/merge4.test | |
| @@ -26,15 +26,17 @@ | |
| 26 | write_file t3 [join [string trim $v2] \n]\n |
| 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args |
| 29 | set x [read_file t4] |
| 30 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x |
| 31 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x |
| 32 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x |
| 33 | set x [split [string trim $x] \n] |
| 34 | set y [read_file t5] |
| 35 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y |
| 36 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y |
| 37 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y |
| 38 | set y [split [string trim $y] \n] |
| 39 | set result1 [string trim $result1] |
| 40 | if {$x!=$result1} { |
| @@ -58,13 +60,13 @@ | |
| 58 | } { |
| 59 | 1 2b 3b 4b 5 6b 7b 8b 9 |
| 60 | } { |
| 61 | 1 2 3 4c 5c 6c 7 8 9 |
| 62 | } { |
| 63 | 1 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < 9 |
| 64 | } { |
| 65 | 1 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < 9 |
| 66 | } -expectError |
| 67 | merge-test 1001 { |
| 68 | 1 2 3 4 5 6 7 8 9 |
| 69 | } { |
| 70 | 1 2b 3b 4 5 6 7b 8b 9 |
| @@ -80,13 +82,13 @@ | |
| 80 | } { |
| 81 | 2b 3b 4b 5 6b 7b 8b |
| 82 | } { |
| 83 | 2 3 4c 5c 6c 7 8 |
| 84 | } { |
| 85 | > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < |
| 86 | } { |
| 87 | > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < |
| 88 | } -expectError |
| 89 | merge-test 1003 { |
| 90 | 2 3 4 5 6 7 8 |
| 91 | } { |
| 92 | 2b 3b 4 5 6 7b 8b |
| 93 |
| --- test/merge4.test | |
| +++ test/merge4.test | |
| @@ -26,15 +26,17 @@ | |
| 26 | write_file t3 [join [string trim $v2] \n]\n |
| 27 | fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args |
| 28 | fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args |
| 29 | set x [read_file t4] |
| 30 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x |
| 31 | regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $x {#} x |
| 32 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x |
| 33 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x |
| 34 | set x [split [string trim $x] \n] |
| 35 | set y [read_file t5] |
| 36 | regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y |
| 37 | regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $y {#} y |
| 38 | regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y |
| 39 | regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y |
| 40 | set y [split [string trim $y] \n] |
| 41 | set result1 [string trim $result1] |
| 42 | if {$x!=$result1} { |
| @@ -58,13 +60,13 @@ | |
| 60 | } { |
| 61 | 1 2b 3b 4b 5 6b 7b 8b 9 |
| 62 | } { |
| 63 | 1 2 3 4c 5c 6c 7 8 9 |
| 64 | } { |
| 65 | 1 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < 9 |
| 66 | } { |
| 67 | 1 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < 9 |
| 68 | } -expectError |
| 69 | merge-test 1001 { |
| 70 | 1 2 3 4 5 6 7 8 9 |
| 71 | } { |
| 72 | 1 2b 3b 4 5 6 7b 8b 9 |
| @@ -80,13 +82,13 @@ | |
| 82 | } { |
| 83 | 2b 3b 4b 5 6b 7b 8b |
| 84 | } { |
| 85 | 2 3 4c 5c 6c 7 8 |
| 86 | } { |
| 87 | > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < |
| 88 | } { |
| 89 | > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < |
| 90 | } -expectError |
| 91 | merge-test 1003 { |
| 92 | 2 3 4 5 6 7 8 |
| 93 | } { |
| 94 | 2b 3b 4 5 6 7b 8b |
| 95 |
+2
| --- test/tester.tcl | ||
| +++ test/tester.tcl | ||
| @@ -332,10 +332,11 @@ | ||
| 332 | 332 | encoding-glob \ |
| 333 | 333 | exec-rel-paths \ |
| 334 | 334 | fileedit-glob \ |
| 335 | 335 | forbid-delta-manifests \ |
| 336 | 336 | forum-close-policy \ |
| 337 | + forum-title \ | |
| 337 | 338 | gdiff-command \ |
| 338 | 339 | gmerge-command \ |
| 339 | 340 | hash-digits \ |
| 340 | 341 | hooks \ |
| 341 | 342 | http-port \ |
| @@ -372,10 +373,11 @@ | ||
| 372 | 373 | ssl-identity \ |
| 373 | 374 | tclsh \ |
| 374 | 375 | th1-setup \ |
| 375 | 376 | th1-uri-regexp \ |
| 376 | 377 | ticket-default-report \ |
| 378 | + timeline-utc \ | |
| 377 | 379 | user-color-map \ |
| 378 | 380 | uv-sync \ |
| 379 | 381 | web-browser] |
| 380 | 382 | |
| 381 | 383 | fossil test-th-eval "hasfeature legacyMvRm" |
| 382 | 384 |
| --- test/tester.tcl | |
| +++ test/tester.tcl | |
| @@ -332,10 +332,11 @@ | |
| 332 | encoding-glob \ |
| 333 | exec-rel-paths \ |
| 334 | fileedit-glob \ |
| 335 | forbid-delta-manifests \ |
| 336 | forum-close-policy \ |
| 337 | gdiff-command \ |
| 338 | gmerge-command \ |
| 339 | hash-digits \ |
| 340 | hooks \ |
| 341 | http-port \ |
| @@ -372,10 +373,11 @@ | |
| 372 | ssl-identity \ |
| 373 | tclsh \ |
| 374 | th1-setup \ |
| 375 | th1-uri-regexp \ |
| 376 | ticket-default-report \ |
| 377 | user-color-map \ |
| 378 | uv-sync \ |
| 379 | web-browser] |
| 380 | |
| 381 | fossil test-th-eval "hasfeature legacyMvRm" |
| 382 |
| --- test/tester.tcl | |
| +++ test/tester.tcl | |
| @@ -332,10 +332,11 @@ | |
| 332 | encoding-glob \ |
| 333 | exec-rel-paths \ |
| 334 | fileedit-glob \ |
| 335 | forbid-delta-manifests \ |
| 336 | forum-close-policy \ |
| 337 | forum-title \ |
| 338 | gdiff-command \ |
| 339 | gmerge-command \ |
| 340 | hash-digits \ |
| 341 | hooks \ |
| 342 | http-port \ |
| @@ -372,10 +373,11 @@ | |
| 373 | ssl-identity \ |
| 374 | tclsh \ |
| 375 | th1-setup \ |
| 376 | th1-uri-regexp \ |
| 377 | ticket-default-report \ |
| 378 | timeline-utc \ |
| 379 | user-color-map \ |
| 380 | uv-sync \ |
| 381 | web-browser] |
| 382 | |
| 383 | fossil test-th-eval "hasfeature legacyMvRm" |
| 384 |
+1
-1
| --- test/update.test | ||
| +++ test/update.test | ||
| @@ -57,11 +57,11 @@ | ||
| 57 | 57 | |
| 58 | 58 | ############################################################################### |
| 59 | 59 | |
| 60 | 60 | fossil update --verbose |
| 61 | 61 | test update-already-up-to-date { |
| 62 | - [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date$} $RESULT] | |
| 62 | + [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date.$} $RESULT] | |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | # Remaining tests are carried out in the order update_cmd() performs checks. |
| 66 | 66 | # |
| 67 | 67 | # Common approach for tests below: |
| 68 | 68 |
| --- test/update.test | |
| +++ test/update.test | |
| @@ -57,11 +57,11 @@ | |
| 57 | |
| 58 | ############################################################################### |
| 59 | |
| 60 | fossil update --verbose |
| 61 | test update-already-up-to-date { |
| 62 | [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date$} $RESULT] |
| 63 | } |
| 64 | |
| 65 | # Remaining tests are carried out in the order update_cmd() performs checks. |
| 66 | # |
| 67 | # Common approach for tests below: |
| 68 |
| --- test/update.test | |
| +++ test/update.test | |
| @@ -57,11 +57,11 @@ | |
| 57 | |
| 58 | ############################################################################### |
| 59 | |
| 60 | fossil update --verbose |
| 61 | test update-already-up-to-date { |
| 62 | [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date.$} $RESULT] |
| 63 | } |
| 64 | |
| 65 | # Remaining tests are carried out in the order update_cmd() performs checks. |
| 66 | # |
| 67 | # Common approach for tests below: |
| 68 |
+3
-1
| --- tools/makemake.tcl | ||
| +++ tools/makemake.tcl | ||
| @@ -132,10 +132,11 @@ | ||
| 132 | 132 | lookslike |
| 133 | 133 | main |
| 134 | 134 | manifest |
| 135 | 135 | markdown |
| 136 | 136 | markdown_html |
| 137 | + match | |
| 137 | 138 | md5 |
| 138 | 139 | merge |
| 139 | 140 | merge3 |
| 140 | 141 | moderate |
| 141 | 142 | name |
| @@ -209,10 +210,11 @@ | ||
| 209 | 210 | # Additional resource files that get built into the executable. |
| 210 | 211 | # These paths are all resolved from the src/ directory, so must |
| 211 | 212 | # be relative to that. |
| 212 | 213 | set extra_files { |
| 213 | 214 | diff.tcl |
| 215 | + merge.tcl | |
| 214 | 216 | markdown.md |
| 215 | 217 | wiki.wiki |
| 216 | 218 | *.js |
| 217 | 219 | default.css |
| 218 | 220 | style.*.css |
| @@ -445,11 +447,11 @@ | ||
| 445 | 447 | |
| 446 | 448 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 447 | 449 | # to 1. If it is set to 0, then there is no need to build or link |
| 448 | 450 | # the linenoise.o object. |
| 449 | 451 | LINENOISE_DEF.0 = |
| 450 | -LINENOISE_DEF.1 = -DHAVE_LINENOISE | |
| 452 | +LINENOISE_DEF.1 = -DHAVE_LINENOISE=2 | |
| 451 | 453 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 452 | 454 | LINENOISE_OBJ.0 = |
| 453 | 455 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 454 | 456 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 455 | 457 | |
| 456 | 458 |
| --- tools/makemake.tcl | |
| +++ tools/makemake.tcl | |
| @@ -132,10 +132,11 @@ | |
| 132 | lookslike |
| 133 | main |
| 134 | manifest |
| 135 | markdown |
| 136 | markdown_html |
| 137 | md5 |
| 138 | merge |
| 139 | merge3 |
| 140 | moderate |
| 141 | name |
| @@ -209,10 +210,11 @@ | |
| 209 | # Additional resource files that get built into the executable. |
| 210 | # These paths are all resolved from the src/ directory, so must |
| 211 | # be relative to that. |
| 212 | set extra_files { |
| 213 | diff.tcl |
| 214 | markdown.md |
| 215 | wiki.wiki |
| 216 | *.js |
| 217 | default.css |
| 218 | style.*.css |
| @@ -445,11 +447,11 @@ | |
| 445 | |
| 446 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 447 | # to 1. If it is set to 0, then there is no need to build or link |
| 448 | # the linenoise.o object. |
| 449 | LINENOISE_DEF.0 = |
| 450 | LINENOISE_DEF.1 = -DHAVE_LINENOISE |
| 451 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 452 | LINENOISE_OBJ.0 = |
| 453 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 454 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 455 | |
| 456 |
| --- tools/makemake.tcl | |
| +++ tools/makemake.tcl | |
| @@ -132,10 +132,11 @@ | |
| 132 | lookslike |
| 133 | main |
| 134 | manifest |
| 135 | markdown |
| 136 | markdown_html |
| 137 | match |
| 138 | md5 |
| 139 | merge |
| 140 | merge3 |
| 141 | moderate |
| 142 | name |
| @@ -209,10 +210,11 @@ | |
| 210 | # Additional resource files that get built into the executable. |
| 211 | # These paths are all resolved from the src/ directory, so must |
| 212 | # be relative to that. |
| 213 | set extra_files { |
| 214 | diff.tcl |
| 215 | merge.tcl |
| 216 | markdown.md |
| 217 | wiki.wiki |
| 218 | *.js |
| 219 | default.css |
| 220 | style.*.css |
| @@ -445,11 +447,11 @@ | |
| 447 | |
| 448 | # The USE_LINENOISE variable may be undefined, set to 0, or set |
| 449 | # to 1. If it is set to 0, then there is no need to build or link |
| 450 | # the linenoise.o object. |
| 451 | LINENOISE_DEF.0 = |
| 452 | LINENOISE_DEF.1 = -DHAVE_LINENOISE=2 |
| 453 | LINENOISE_DEF. = $(LINENOISE_DEF.0) |
| 454 | LINENOISE_OBJ.0 = |
| 455 | LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o |
| 456 | LINENOISE_OBJ. = $(LINENOISE_OBJ.0) |
| 457 | |
| 458 |
+1
-1
| --- tools/man_page_command_list.tcl | ||
| +++ tools/man_page_command_list.tcl | ||
| @@ -10,11 +10,11 @@ | ||
| 10 | 10 | set file [lindex $argv 0] |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | # Get list of common commands. |
| 14 | 14 | set commands [exec fossil help] |
| 15 | -regsub -nocase {.*?\ncommon commands:.*\n} $commands {} commands | |
| 15 | +regsub -nocase {.*?\nfrequently used commands:.*\n} $commands {} commands | |
| 16 | 16 | regsub -nocase {\nthis is fossil version.*} $commands {} commands |
| 17 | 17 | regsub -all {\s+} $commands " " commands |
| 18 | 18 | set commands [lsort $commands] |
| 19 | 19 | |
| 20 | 20 | # Compute number of rows. |
| 21 | 21 |
| --- tools/man_page_command_list.tcl | |
| +++ tools/man_page_command_list.tcl | |
| @@ -10,11 +10,11 @@ | |
| 10 | set file [lindex $argv 0] |
| 11 | } |
| 12 | |
| 13 | # Get list of common commands. |
| 14 | set commands [exec fossil help] |
| 15 | regsub -nocase {.*?\ncommon commands:.*\n} $commands {} commands |
| 16 | regsub -nocase {\nthis is fossil version.*} $commands {} commands |
| 17 | regsub -all {\s+} $commands " " commands |
| 18 | set commands [lsort $commands] |
| 19 | |
| 20 | # Compute number of rows. |
| 21 |
| --- tools/man_page_command_list.tcl | |
| +++ tools/man_page_command_list.tcl | |
| @@ -10,11 +10,11 @@ | |
| 10 | set file [lindex $argv 0] |
| 11 | } |
| 12 | |
| 13 | # Get list of common commands. |
| 14 | set commands [exec fossil help] |
| 15 | regsub -nocase {.*?\nfrequently used commands:.*\n} $commands {} commands |
| 16 | regsub -nocase {\nthis is fossil version.*} $commands {} commands |
| 17 | regsub -all {\s+} $commands " " commands |
| 18 | set commands [lsort $commands] |
| 19 | |
| 20 | # Compute number of rows. |
| 21 |
+16
-5
| --- tools/translate.c | ||
| +++ tools/translate.c | ||
| @@ -78,10 +78,21 @@ | ||
| 78 | 78 | |
| 79 | 79 | /* |
| 80 | 80 | ** Name of files being processed |
| 81 | 81 | */ |
| 82 | 82 | static const char *zInFile = "(stdin)"; |
| 83 | + | |
| 84 | +/* | |
| 85 | +** The `fossil_isspace()' function copied from the Fossil source code. | |
| 86 | +** Some MSVC runtime library versions of `isspace()' break with an `assert()' if | |
| 87 | +** the input is smaller than -1 or greater than 255 in debug builds, due to sign | |
| 88 | +** extension when promoting `signed char' to `int' for non-ASCII characters. Use | |
| 89 | +** an `isspace()' replacement instead of explicit type casts to `unsigned char'. | |
| 90 | +*/ | |
| 91 | +int fossil_isspace(char c){ | |
| 92 | + return c==' ' || (c<='\r' && c>='\t'); | |
| 93 | +} | |
| 83 | 94 | |
| 84 | 95 | /* |
| 85 | 96 | ** Terminate an active cgi_printf() or free string |
| 86 | 97 | */ |
| 87 | 98 | static void end_block(FILE *out){ |
| @@ -106,21 +117,21 @@ | ||
| 106 | 117 | char zOut[4000]; /* The input line translated into appropriate output */ |
| 107 | 118 | |
| 108 | 119 | c1 = c2 = '-'; |
| 109 | 120 | while( fgets(zLine, sizeof(zLine), in) ){ |
| 110 | 121 | lineNo++; |
| 111 | - for(i=0; zLine[i] && isspace(zLine[i]); i++){} | |
| 122 | + for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){} | |
| 112 | 123 | if( zLine[i]!='@' ){ |
| 113 | 124 | if( inPrint || inStr ) end_block(out); |
| 114 | 125 | fprintf(out,"%s",zLine); |
| 115 | 126 | /* 0123456789 12345 */ |
| 116 | 127 | if( strncmp(zLine, "/* @-comment: ", 14)==0 ){ |
| 117 | 128 | c1 = zLine[14]; |
| 118 | 129 | c2 = zLine[15]; |
| 119 | 130 | } |
| 120 | 131 | i += strlen(&zLine[i]); |
| 121 | - while( i>0 && isspace(zLine[i-1]) ){ i--; } | |
| 132 | + while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; } | |
| 122 | 133 | lastWasEq = i>0 && zLine[i-1]=='='; |
| 123 | 134 | lastWasComma = i>0 && zLine[i-1]==','; |
| 124 | 135 | }else if( lastWasEq || lastWasComma){ |
| 125 | 136 | /* If the last non-whitespace character before the first @ was |
| 126 | 137 | ** an "="(var init/set) or a ","(const definition in list) then |
| @@ -129,11 +140,11 @@ | ||
| 129 | 140 | ** and end of line. |
| 130 | 141 | */ |
| 131 | 142 | int indent, omitline; |
| 132 | 143 | char *zNewline = "\\n"; |
| 133 | 144 | i++; |
| 134 | - if( isspace(zLine[i]) ){ i++; } | |
| 145 | + if( fossil_isspace(zLine[i]) ){ i++; } | |
| 135 | 146 | indent = i - 2; |
| 136 | 147 | if( indent<0 ) indent = 0; |
| 137 | 148 | omitline = 0; |
| 138 | 149 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 139 | 150 | if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){ |
| @@ -147,11 +158,11 @@ | ||
| 147 | 158 | break; |
| 148 | 159 | } |
| 149 | 160 | if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; } |
| 150 | 161 | zOut[j++] = zLine[i]; |
| 151 | 162 | } |
| 152 | - if( zNewline[0] ) while( j>0 && isspace(zOut[j-1]) ){ j--; } | |
| 163 | + if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; } | |
| 153 | 164 | zOut[j] = 0; |
| 154 | 165 | if( j<=0 && omitline ){ |
| 155 | 166 | fprintf(out,"\n"); |
| 156 | 167 | }else{ |
| 157 | 168 | fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline); |
| @@ -171,11 +182,11 @@ | ||
| 171 | 182 | int indent; |
| 172 | 183 | int nC; |
| 173 | 184 | int nParam; |
| 174 | 185 | char c; |
| 175 | 186 | i++; |
| 176 | - if( isspace(zLine[i]) ){ i++; } | |
| 187 | + if( fossil_isspace(zLine[i]) ){ i++; } | |
| 177 | 188 | indent = i; |
| 178 | 189 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 179 | 190 | if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r' |
| 180 | 191 | || zLine[i+1]=='\n') ){ |
| 181 | 192 | zNewline = ""; |
| 182 | 193 |
| --- tools/translate.c | |
| +++ tools/translate.c | |
| @@ -78,10 +78,21 @@ | |
| 78 | |
| 79 | /* |
| 80 | ** Name of files being processed |
| 81 | */ |
| 82 | static const char *zInFile = "(stdin)"; |
| 83 | |
| 84 | /* |
| 85 | ** Terminate an active cgi_printf() or free string |
| 86 | */ |
| 87 | static void end_block(FILE *out){ |
| @@ -106,21 +117,21 @@ | |
| 106 | char zOut[4000]; /* The input line translated into appropriate output */ |
| 107 | |
| 108 | c1 = c2 = '-'; |
| 109 | while( fgets(zLine, sizeof(zLine), in) ){ |
| 110 | lineNo++; |
| 111 | for(i=0; zLine[i] && isspace(zLine[i]); i++){} |
| 112 | if( zLine[i]!='@' ){ |
| 113 | if( inPrint || inStr ) end_block(out); |
| 114 | fprintf(out,"%s",zLine); |
| 115 | /* 0123456789 12345 */ |
| 116 | if( strncmp(zLine, "/* @-comment: ", 14)==0 ){ |
| 117 | c1 = zLine[14]; |
| 118 | c2 = zLine[15]; |
| 119 | } |
| 120 | i += strlen(&zLine[i]); |
| 121 | while( i>0 && isspace(zLine[i-1]) ){ i--; } |
| 122 | lastWasEq = i>0 && zLine[i-1]=='='; |
| 123 | lastWasComma = i>0 && zLine[i-1]==','; |
| 124 | }else if( lastWasEq || lastWasComma){ |
| 125 | /* If the last non-whitespace character before the first @ was |
| 126 | ** an "="(var init/set) or a ","(const definition in list) then |
| @@ -129,11 +140,11 @@ | |
| 129 | ** and end of line. |
| 130 | */ |
| 131 | int indent, omitline; |
| 132 | char *zNewline = "\\n"; |
| 133 | i++; |
| 134 | if( isspace(zLine[i]) ){ i++; } |
| 135 | indent = i - 2; |
| 136 | if( indent<0 ) indent = 0; |
| 137 | omitline = 0; |
| 138 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 139 | if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){ |
| @@ -147,11 +158,11 @@ | |
| 147 | break; |
| 148 | } |
| 149 | if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; } |
| 150 | zOut[j++] = zLine[i]; |
| 151 | } |
| 152 | if( zNewline[0] ) while( j>0 && isspace(zOut[j-1]) ){ j--; } |
| 153 | zOut[j] = 0; |
| 154 | if( j<=0 && omitline ){ |
| 155 | fprintf(out,"\n"); |
| 156 | }else{ |
| 157 | fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline); |
| @@ -171,11 +182,11 @@ | |
| 171 | int indent; |
| 172 | int nC; |
| 173 | int nParam; |
| 174 | char c; |
| 175 | i++; |
| 176 | if( isspace(zLine[i]) ){ i++; } |
| 177 | indent = i; |
| 178 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 179 | if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r' |
| 180 | || zLine[i+1]=='\n') ){ |
| 181 | zNewline = ""; |
| 182 |
| --- tools/translate.c | |
| +++ tools/translate.c | |
| @@ -78,10 +78,21 @@ | |
| 78 | |
| 79 | /* |
| 80 | ** Name of files being processed |
| 81 | */ |
| 82 | static const char *zInFile = "(stdin)"; |
| 83 | |
| 84 | /* |
| 85 | ** The `fossil_isspace()' function copied from the Fossil source code. |
| 86 | ** Some MSVC runtime library versions of `isspace()' break with an `assert()' if |
| 87 | ** the input is smaller than -1 or greater than 255 in debug builds, due to sign |
| 88 | ** extension when promoting `signed char' to `int' for non-ASCII characters. Use |
| 89 | ** an `isspace()' replacement instead of explicit type casts to `unsigned char'. |
| 90 | */ |
| 91 | int fossil_isspace(char c){ |
| 92 | return c==' ' || (c<='\r' && c>='\t'); |
| 93 | } |
| 94 | |
| 95 | /* |
| 96 | ** Terminate an active cgi_printf() or free string |
| 97 | */ |
| 98 | static void end_block(FILE *out){ |
| @@ -106,21 +117,21 @@ | |
| 117 | char zOut[4000]; /* The input line translated into appropriate output */ |
| 118 | |
| 119 | c1 = c2 = '-'; |
| 120 | while( fgets(zLine, sizeof(zLine), in) ){ |
| 121 | lineNo++; |
| 122 | for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){} |
| 123 | if( zLine[i]!='@' ){ |
| 124 | if( inPrint || inStr ) end_block(out); |
| 125 | fprintf(out,"%s",zLine); |
| 126 | /* 0123456789 12345 */ |
| 127 | if( strncmp(zLine, "/* @-comment: ", 14)==0 ){ |
| 128 | c1 = zLine[14]; |
| 129 | c2 = zLine[15]; |
| 130 | } |
| 131 | i += strlen(&zLine[i]); |
| 132 | while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; } |
| 133 | lastWasEq = i>0 && zLine[i-1]=='='; |
| 134 | lastWasComma = i>0 && zLine[i-1]==','; |
| 135 | }else if( lastWasEq || lastWasComma){ |
| 136 | /* If the last non-whitespace character before the first @ was |
| 137 | ** an "="(var init/set) or a ","(const definition in list) then |
| @@ -129,11 +140,11 @@ | |
| 140 | ** and end of line. |
| 141 | */ |
| 142 | int indent, omitline; |
| 143 | char *zNewline = "\\n"; |
| 144 | i++; |
| 145 | if( fossil_isspace(zLine[i]) ){ i++; } |
| 146 | indent = i - 2; |
| 147 | if( indent<0 ) indent = 0; |
| 148 | omitline = 0; |
| 149 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 150 | if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){ |
| @@ -147,11 +158,11 @@ | |
| 158 | break; |
| 159 | } |
| 160 | if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; } |
| 161 | zOut[j++] = zLine[i]; |
| 162 | } |
| 163 | if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; } |
| 164 | zOut[j] = 0; |
| 165 | if( j<=0 && omitline ){ |
| 166 | fprintf(out,"\n"); |
| 167 | }else{ |
| 168 | fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline); |
| @@ -171,11 +182,11 @@ | |
| 182 | int indent; |
| 183 | int nC; |
| 184 | int nParam; |
| 185 | char c; |
| 186 | i++; |
| 187 | if( fossil_isspace(zLine[i]) ){ i++; } |
| 188 | indent = i; |
| 189 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| 190 | if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r' |
| 191 | || zLine[i+1]=='\n') ){ |
| 192 | zNewline = ""; |
| 193 |
+10
-4
| --- win/Makefile.dmc | ||
| +++ win/Makefile.dmc | ||
| @@ -32,13 +32,13 @@ | ||
| 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_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | 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 md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c | |
| 37 | +SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c | |
| 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)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O | |
| 39 | +OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O | |
| 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 md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ | |
| 58 | + +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ | |
| 59 | 59 | +echo fossil >> $@ |
| 60 | 60 | +echo fossil >> $@ |
| 61 | 61 | +echo $(LIBS) >> $@ |
| 62 | 62 | +echo. >> $@ |
| 63 | 63 | +echo fossil >> $@ |
| @@ -635,10 +635,16 @@ | ||
| 635 | 635 | $(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h |
| 636 | 636 | $(TCC) -o$@ -c markdown_html_.c |
| 637 | 637 | |
| 638 | 638 | markdown_html_.c : $(SRCDIR)\markdown_html.c |
| 639 | 639 | +translate$E $** > $@ |
| 640 | + | |
| 641 | +$(OBJDIR)\match$O : match_.c match.h | |
| 642 | + $(TCC) -o$@ -c match_.c | |
| 643 | + | |
| 644 | +match_.c : $(SRCDIR)\match.c | |
| 645 | + +translate$E $** > $@ | |
| 640 | 646 | |
| 641 | 647 | $(OBJDIR)\md5$O : md5_.c md5.h |
| 642 | 648 | $(TCC) -o$@ -c md5_.c |
| 643 | 649 | |
| 644 | 650 | md5_.c : $(SRCDIR)\md5.c |
| @@ -1009,7 +1015,7 @@ | ||
| 1009 | 1015 | |
| 1010 | 1016 | zip_.c : $(SRCDIR)\zip.c |
| 1011 | 1017 | +translate$E $** > $@ |
| 1012 | 1018 | |
| 1013 | 1019 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1014 | - +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h | |
| 1020 | + +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h | |
| 1015 | 1021 | @copy /Y nul: headers |
| 1016 | 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_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | |
| 35 | PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000 |
| 36 | |
| 37 | SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c |
| 38 | |
| 39 | OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O |
| 40 | |
| 41 | |
| 42 | RC=$(DMDIR)\bin\rcc |
| 43 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 44 | |
| @@ -53,11 +53,11 @@ | |
| 53 | |
| 54 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 55 | $(RC) $(RCFLAGS) -o$@ $** |
| 56 | |
| 57 | $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res |
| 58 | +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ |
| 59 | +echo fossil >> $@ |
| 60 | +echo fossil >> $@ |
| 61 | +echo $(LIBS) >> $@ |
| 62 | +echo. >> $@ |
| 63 | +echo fossil >> $@ |
| @@ -635,10 +635,16 @@ | |
| 635 | $(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h |
| 636 | $(TCC) -o$@ -c markdown_html_.c |
| 637 | |
| 638 | markdown_html_.c : $(SRCDIR)\markdown_html.c |
| 639 | +translate$E $** > $@ |
| 640 | |
| 641 | $(OBJDIR)\md5$O : md5_.c md5.h |
| 642 | $(TCC) -o$@ -c md5_.c |
| 643 | |
| 644 | md5_.c : $(SRCDIR)\md5.c |
| @@ -1009,7 +1015,7 @@ | |
| 1009 | |
| 1010 | zip_.c : $(SRCDIR)\zip.c |
| 1011 | +translate$E $** > $@ |
| 1012 | |
| 1013 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1014 | +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h |
| 1015 | @copy /Y nul: headers |
| 1016 |
| --- win/Makefile.dmc | |
| +++ win/Makefile.dmc | |
| @@ -32,13 +32,13 @@ | |
| 32 | |
| 33 | SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 34 | |
| 35 | PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000 |
| 36 | |
| 37 | SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c |
| 38 | |
| 39 | OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O |
| 40 | |
| 41 | |
| 42 | RC=$(DMDIR)\bin\rcc |
| 43 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 44 | |
| @@ -53,11 +53,11 @@ | |
| 53 | |
| 54 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 55 | $(RC) $(RCFLAGS) -o$@ $** |
| 56 | |
| 57 | $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res |
| 58 | +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@ |
| 59 | +echo fossil >> $@ |
| 60 | +echo fossil >> $@ |
| 61 | +echo $(LIBS) >> $@ |
| 62 | +echo. >> $@ |
| 63 | +echo fossil >> $@ |
| @@ -635,10 +635,16 @@ | |
| 635 | $(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h |
| 636 | $(TCC) -o$@ -c markdown_html_.c |
| 637 | |
| 638 | markdown_html_.c : $(SRCDIR)\markdown_html.c |
| 639 | +translate$E $** > $@ |
| 640 | |
| 641 | $(OBJDIR)\match$O : match_.c match.h |
| 642 | $(TCC) -o$@ -c match_.c |
| 643 | |
| 644 | match_.c : $(SRCDIR)\match.c |
| 645 | +translate$E $** > $@ |
| 646 | |
| 647 | $(OBJDIR)\md5$O : md5_.c md5.h |
| 648 | $(TCC) -o$@ -c md5_.c |
| 649 | |
| 650 | md5_.c : $(SRCDIR)\md5.c |
| @@ -1009,7 +1015,7 @@ | |
| 1015 | |
| 1016 | zip_.c : $(SRCDIR)\zip.c |
| 1017 | +translate$E $** > $@ |
| 1018 | |
| 1019 | headers: makeheaders$E page_index.h builtin_data.h VERSION.h |
| 1020 | +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h |
| 1021 | @copy /Y nul: headers |
| 1022 |
+13
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -485,10 +485,11 @@ | ||
| 485 | 485 | $(SRCDIR)/lookslike.c \ |
| 486 | 486 | $(SRCDIR)/main.c \ |
| 487 | 487 | $(SRCDIR)/manifest.c \ |
| 488 | 488 | $(SRCDIR)/markdown.c \ |
| 489 | 489 | $(SRCDIR)/markdown_html.c \ |
| 490 | + $(SRCDIR)/match.c \ | |
| 490 | 491 | $(SRCDIR)/md5.c \ |
| 491 | 492 | $(SRCDIR)/merge.c \ |
| 492 | 493 | $(SRCDIR)/merge3.c \ |
| 493 | 494 | $(SRCDIR)/moderate.c \ |
| 494 | 495 | $(SRCDIR)/name.c \ |
| @@ -634,10 +635,11 @@ | ||
| 634 | 635 | $(SRCDIR)/hbmenu.js \ |
| 635 | 636 | $(SRCDIR)/href.js \ |
| 636 | 637 | $(SRCDIR)/login.js \ |
| 637 | 638 | $(SRCDIR)/markdown.md \ |
| 638 | 639 | $(SRCDIR)/menu.js \ |
| 640 | + $(SRCDIR)/merge.tcl \ | |
| 639 | 641 | $(SRCDIR)/scroll.js \ |
| 640 | 642 | $(SRCDIR)/skin.js \ |
| 641 | 643 | $(SRCDIR)/sorttable.js \ |
| 642 | 644 | $(SRCDIR)/sounds/0.wav \ |
| 643 | 645 | $(SRCDIR)/sounds/1.wav \ |
| @@ -749,10 +751,11 @@ | ||
| 749 | 751 | $(OBJDIR)/lookslike_.c \ |
| 750 | 752 | $(OBJDIR)/main_.c \ |
| 751 | 753 | $(OBJDIR)/manifest_.c \ |
| 752 | 754 | $(OBJDIR)/markdown_.c \ |
| 753 | 755 | $(OBJDIR)/markdown_html_.c \ |
| 756 | + $(OBJDIR)/match_.c \ | |
| 754 | 757 | $(OBJDIR)/md5_.c \ |
| 755 | 758 | $(OBJDIR)/merge_.c \ |
| 756 | 759 | $(OBJDIR)/merge3_.c \ |
| 757 | 760 | $(OBJDIR)/moderate_.c \ |
| 758 | 761 | $(OBJDIR)/name_.c \ |
| @@ -898,10 +901,11 @@ | ||
| 898 | 901 | $(OBJDIR)/lookslike.o \ |
| 899 | 902 | $(OBJDIR)/main.o \ |
| 900 | 903 | $(OBJDIR)/manifest.o \ |
| 901 | 904 | $(OBJDIR)/markdown.o \ |
| 902 | 905 | $(OBJDIR)/markdown_html.o \ |
| 906 | + $(OBJDIR)/match.o \ | |
| 903 | 907 | $(OBJDIR)/md5.o \ |
| 904 | 908 | $(OBJDIR)/merge.o \ |
| 905 | 909 | $(OBJDIR)/merge3.o \ |
| 906 | 910 | $(OBJDIR)/moderate.o \ |
| 907 | 911 | $(OBJDIR)/name.o \ |
| @@ -1251,10 +1255,11 @@ | ||
| 1251 | 1255 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 1252 | 1256 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 1253 | 1257 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 1254 | 1258 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 1255 | 1259 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 1260 | + $(OBJDIR)/match_.c:$(OBJDIR)/match.h \ | |
| 1256 | 1261 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 1257 | 1262 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 1258 | 1263 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 1259 | 1264 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 1260 | 1265 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -2002,10 +2007,18 @@ | ||
| 2002 | 2007 | |
| 2003 | 2008 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 2004 | 2009 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 2005 | 2010 | |
| 2006 | 2011 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 2012 | + | |
| 2013 | +$(OBJDIR)/match_.c: $(SRCDIR)/match.c $(TRANSLATE) | |
| 2014 | + $(TRANSLATE) $(SRCDIR)/match.c >$@ | |
| 2015 | + | |
| 2016 | +$(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h | |
| 2017 | + $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c | |
| 2018 | + | |
| 2019 | +$(OBJDIR)/match.h: $(OBJDIR)/headers | |
| 2007 | 2020 | |
| 2008 | 2021 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE) |
| 2009 | 2022 | $(TRANSLATE) $(SRCDIR)/md5.c >$@ |
| 2010 | 2023 | |
| 2011 | 2024 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 2012 | 2025 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -485,10 +485,11 @@ | |
| 485 | $(SRCDIR)/lookslike.c \ |
| 486 | $(SRCDIR)/main.c \ |
| 487 | $(SRCDIR)/manifest.c \ |
| 488 | $(SRCDIR)/markdown.c \ |
| 489 | $(SRCDIR)/markdown_html.c \ |
| 490 | $(SRCDIR)/md5.c \ |
| 491 | $(SRCDIR)/merge.c \ |
| 492 | $(SRCDIR)/merge3.c \ |
| 493 | $(SRCDIR)/moderate.c \ |
| 494 | $(SRCDIR)/name.c \ |
| @@ -634,10 +635,11 @@ | |
| 634 | $(SRCDIR)/hbmenu.js \ |
| 635 | $(SRCDIR)/href.js \ |
| 636 | $(SRCDIR)/login.js \ |
| 637 | $(SRCDIR)/markdown.md \ |
| 638 | $(SRCDIR)/menu.js \ |
| 639 | $(SRCDIR)/scroll.js \ |
| 640 | $(SRCDIR)/skin.js \ |
| 641 | $(SRCDIR)/sorttable.js \ |
| 642 | $(SRCDIR)/sounds/0.wav \ |
| 643 | $(SRCDIR)/sounds/1.wav \ |
| @@ -749,10 +751,11 @@ | |
| 749 | $(OBJDIR)/lookslike_.c \ |
| 750 | $(OBJDIR)/main_.c \ |
| 751 | $(OBJDIR)/manifest_.c \ |
| 752 | $(OBJDIR)/markdown_.c \ |
| 753 | $(OBJDIR)/markdown_html_.c \ |
| 754 | $(OBJDIR)/md5_.c \ |
| 755 | $(OBJDIR)/merge_.c \ |
| 756 | $(OBJDIR)/merge3_.c \ |
| 757 | $(OBJDIR)/moderate_.c \ |
| 758 | $(OBJDIR)/name_.c \ |
| @@ -898,10 +901,11 @@ | |
| 898 | $(OBJDIR)/lookslike.o \ |
| 899 | $(OBJDIR)/main.o \ |
| 900 | $(OBJDIR)/manifest.o \ |
| 901 | $(OBJDIR)/markdown.o \ |
| 902 | $(OBJDIR)/markdown_html.o \ |
| 903 | $(OBJDIR)/md5.o \ |
| 904 | $(OBJDIR)/merge.o \ |
| 905 | $(OBJDIR)/merge3.o \ |
| 906 | $(OBJDIR)/moderate.o \ |
| 907 | $(OBJDIR)/name.o \ |
| @@ -1251,10 +1255,11 @@ | |
| 1251 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 1252 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 1253 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 1254 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 1255 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 1256 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 1257 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 1258 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 1259 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 1260 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -2002,10 +2007,18 @@ | |
| 2002 | |
| 2003 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 2004 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 2005 | |
| 2006 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 2007 | |
| 2008 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE) |
| 2009 | $(TRANSLATE) $(SRCDIR)/md5.c >$@ |
| 2010 | |
| 2011 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 2012 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -485,10 +485,11 @@ | |
| 485 | $(SRCDIR)/lookslike.c \ |
| 486 | $(SRCDIR)/main.c \ |
| 487 | $(SRCDIR)/manifest.c \ |
| 488 | $(SRCDIR)/markdown.c \ |
| 489 | $(SRCDIR)/markdown_html.c \ |
| 490 | $(SRCDIR)/match.c \ |
| 491 | $(SRCDIR)/md5.c \ |
| 492 | $(SRCDIR)/merge.c \ |
| 493 | $(SRCDIR)/merge3.c \ |
| 494 | $(SRCDIR)/moderate.c \ |
| 495 | $(SRCDIR)/name.c \ |
| @@ -634,10 +635,11 @@ | |
| 635 | $(SRCDIR)/hbmenu.js \ |
| 636 | $(SRCDIR)/href.js \ |
| 637 | $(SRCDIR)/login.js \ |
| 638 | $(SRCDIR)/markdown.md \ |
| 639 | $(SRCDIR)/menu.js \ |
| 640 | $(SRCDIR)/merge.tcl \ |
| 641 | $(SRCDIR)/scroll.js \ |
| 642 | $(SRCDIR)/skin.js \ |
| 643 | $(SRCDIR)/sorttable.js \ |
| 644 | $(SRCDIR)/sounds/0.wav \ |
| 645 | $(SRCDIR)/sounds/1.wav \ |
| @@ -749,10 +751,11 @@ | |
| 751 | $(OBJDIR)/lookslike_.c \ |
| 752 | $(OBJDIR)/main_.c \ |
| 753 | $(OBJDIR)/manifest_.c \ |
| 754 | $(OBJDIR)/markdown_.c \ |
| 755 | $(OBJDIR)/markdown_html_.c \ |
| 756 | $(OBJDIR)/match_.c \ |
| 757 | $(OBJDIR)/md5_.c \ |
| 758 | $(OBJDIR)/merge_.c \ |
| 759 | $(OBJDIR)/merge3_.c \ |
| 760 | $(OBJDIR)/moderate_.c \ |
| 761 | $(OBJDIR)/name_.c \ |
| @@ -898,10 +901,11 @@ | |
| 901 | $(OBJDIR)/lookslike.o \ |
| 902 | $(OBJDIR)/main.o \ |
| 903 | $(OBJDIR)/manifest.o \ |
| 904 | $(OBJDIR)/markdown.o \ |
| 905 | $(OBJDIR)/markdown_html.o \ |
| 906 | $(OBJDIR)/match.o \ |
| 907 | $(OBJDIR)/md5.o \ |
| 908 | $(OBJDIR)/merge.o \ |
| 909 | $(OBJDIR)/merge3.o \ |
| 910 | $(OBJDIR)/moderate.o \ |
| 911 | $(OBJDIR)/name.o \ |
| @@ -1251,10 +1255,11 @@ | |
| 1255 | $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ |
| 1256 | $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ |
| 1257 | $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ |
| 1258 | $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ |
| 1259 | $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ |
| 1260 | $(OBJDIR)/match_.c:$(OBJDIR)/match.h \ |
| 1261 | $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ |
| 1262 | $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ |
| 1263 | $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ |
| 1264 | $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ |
| 1265 | $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ |
| @@ -2002,10 +2007,18 @@ | |
| 2007 | |
| 2008 | $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h |
| 2009 | $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c |
| 2010 | |
| 2011 | $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers |
| 2012 | |
| 2013 | $(OBJDIR)/match_.c: $(SRCDIR)/match.c $(TRANSLATE) |
| 2014 | $(TRANSLATE) $(SRCDIR)/match.c >$@ |
| 2015 | |
| 2016 | $(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h |
| 2017 | $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c |
| 2018 | |
| 2019 | $(OBJDIR)/match.h: $(OBJDIR)/headers |
| 2020 | |
| 2021 | $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE) |
| 2022 | $(TRANSLATE) $(SRCDIR)/md5.c >$@ |
| 2023 | |
| 2024 | $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h |
| 2025 |
+12
| --- win/Makefile.msc | ||
| +++ win/Makefile.msc | ||
| @@ -443,10 +443,11 @@ | ||
| 443 | 443 | "$(OX)\lookslike_.c" \ |
| 444 | 444 | "$(OX)\main_.c" \ |
| 445 | 445 | "$(OX)\manifest_.c" \ |
| 446 | 446 | "$(OX)\markdown_.c" \ |
| 447 | 447 | "$(OX)\markdown_html_.c" \ |
| 448 | + "$(OX)\match_.c" \ | |
| 448 | 449 | "$(OX)\md5_.c" \ |
| 449 | 450 | "$(OX)\merge_.c" \ |
| 450 | 451 | "$(OX)\merge3_.c" \ |
| 451 | 452 | "$(OX)\moderate_.c" \ |
| 452 | 453 | "$(OX)\name_.c" \ |
| @@ -592,10 +593,11 @@ | ||
| 592 | 593 | "$(SRCDIR)\hbmenu.js" \ |
| 593 | 594 | "$(SRCDIR)\href.js" \ |
| 594 | 595 | "$(SRCDIR)\login.js" \ |
| 595 | 596 | "$(SRCDIR)\markdown.md" \ |
| 596 | 597 | "$(SRCDIR)\menu.js" \ |
| 598 | + "$(SRCDIR)\merge.tcl" \ | |
| 597 | 599 | "$(SRCDIR)\scroll.js" \ |
| 598 | 600 | "$(SRCDIR)\skin.js" \ |
| 599 | 601 | "$(SRCDIR)\sorttable.js" \ |
| 600 | 602 | "$(SRCDIR)\sounds\0.wav" \ |
| 601 | 603 | "$(SRCDIR)\sounds\1.wav" \ |
| @@ -707,10 +709,11 @@ | ||
| 707 | 709 | "$(OX)\lookslike$O" \ |
| 708 | 710 | "$(OX)\main$O" \ |
| 709 | 711 | "$(OX)\manifest$O" \ |
| 710 | 712 | "$(OX)\markdown$O" \ |
| 711 | 713 | "$(OX)\markdown_html$O" \ |
| 714 | + "$(OX)\match$O" \ | |
| 712 | 715 | "$(OX)\md5$O" \ |
| 713 | 716 | "$(OX)\merge$O" \ |
| 714 | 717 | "$(OX)\merge3$O" \ |
| 715 | 718 | "$(OX)\moderate$O" \ |
| 716 | 719 | "$(OX)\name$O" \ |
| @@ -956,10 +959,11 @@ | ||
| 956 | 959 | echo "$(OX)\lookslike.obj" >> $@ |
| 957 | 960 | echo "$(OX)\main.obj" >> $@ |
| 958 | 961 | echo "$(OX)\manifest.obj" >> $@ |
| 959 | 962 | echo "$(OX)\markdown.obj" >> $@ |
| 960 | 963 | echo "$(OX)\markdown_html.obj" >> $@ |
| 964 | + echo "$(OX)\match.obj" >> $@ | |
| 961 | 965 | echo "$(OX)\md5.obj" >> $@ |
| 962 | 966 | echo "$(OX)\merge.obj" >> $@ |
| 963 | 967 | echo "$(OX)\merge3.obj" >> $@ |
| 964 | 968 | echo "$(OX)\moderate.obj" >> $@ |
| 965 | 969 | echo "$(OX)\name.obj" >> $@ |
| @@ -1222,10 +1226,11 @@ | ||
| 1222 | 1226 | echo "$(SRCDIR)\hbmenu.js" >> $@ |
| 1223 | 1227 | echo "$(SRCDIR)\href.js" >> $@ |
| 1224 | 1228 | echo "$(SRCDIR)\login.js" >> $@ |
| 1225 | 1229 | echo "$(SRCDIR)\markdown.md" >> $@ |
| 1226 | 1230 | echo "$(SRCDIR)\menu.js" >> $@ |
| 1231 | + echo "$(SRCDIR)\merge.tcl" >> $@ | |
| 1227 | 1232 | echo "$(SRCDIR)\scroll.js" >> $@ |
| 1228 | 1233 | echo "$(SRCDIR)\skin.js" >> $@ |
| 1229 | 1234 | echo "$(SRCDIR)\sorttable.js" >> $@ |
| 1230 | 1235 | echo "$(SRCDIR)\sounds/0.wav" >> $@ |
| 1231 | 1236 | echo "$(SRCDIR)\sounds/1.wav" >> $@ |
| @@ -1760,10 +1765,16 @@ | ||
| 1760 | 1765 | "$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h" |
| 1761 | 1766 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c" |
| 1762 | 1767 | |
| 1763 | 1768 | "$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c" |
| 1764 | 1769 | "$(OBJDIR)\translate$E" $** > $@ |
| 1770 | + | |
| 1771 | +"$(OX)\match$O" : "$(OX)\match_.c" "$(OX)\match.h" | |
| 1772 | + $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\match_.c" | |
| 1773 | + | |
| 1774 | +"$(OX)\match_.c" : "$(SRCDIR)\match.c" | |
| 1775 | + "$(OBJDIR)\translate$E" $** > $@ | |
| 1765 | 1776 | |
| 1766 | 1777 | "$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h" |
| 1767 | 1778 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c" |
| 1768 | 1779 | |
| 1769 | 1780 | "$(OX)\md5_.c" : "$(SRCDIR)\md5.c" |
| @@ -2222,10 +2233,11 @@ | ||
| 2222 | 2233 | "$(OX)\lookslike_.c":"$(OX)\lookslike.h" \ |
| 2223 | 2234 | "$(OX)\main_.c":"$(OX)\main.h" \ |
| 2224 | 2235 | "$(OX)\manifest_.c":"$(OX)\manifest.h" \ |
| 2225 | 2236 | "$(OX)\markdown_.c":"$(OX)\markdown.h" \ |
| 2226 | 2237 | "$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \ |
| 2238 | + "$(OX)\match_.c":"$(OX)\match.h" \ | |
| 2227 | 2239 | "$(OX)\md5_.c":"$(OX)\md5.h" \ |
| 2228 | 2240 | "$(OX)\merge_.c":"$(OX)\merge.h" \ |
| 2229 | 2241 | "$(OX)\merge3_.c":"$(OX)\merge3.h" \ |
| 2230 | 2242 | "$(OX)\moderate_.c":"$(OX)\moderate.h" \ |
| 2231 | 2243 | "$(OX)\name_.c":"$(OX)\name.h" \ |
| 2232 | 2244 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -443,10 +443,11 @@ | |
| 443 | "$(OX)\lookslike_.c" \ |
| 444 | "$(OX)\main_.c" \ |
| 445 | "$(OX)\manifest_.c" \ |
| 446 | "$(OX)\markdown_.c" \ |
| 447 | "$(OX)\markdown_html_.c" \ |
| 448 | "$(OX)\md5_.c" \ |
| 449 | "$(OX)\merge_.c" \ |
| 450 | "$(OX)\merge3_.c" \ |
| 451 | "$(OX)\moderate_.c" \ |
| 452 | "$(OX)\name_.c" \ |
| @@ -592,10 +593,11 @@ | |
| 592 | "$(SRCDIR)\hbmenu.js" \ |
| 593 | "$(SRCDIR)\href.js" \ |
| 594 | "$(SRCDIR)\login.js" \ |
| 595 | "$(SRCDIR)\markdown.md" \ |
| 596 | "$(SRCDIR)\menu.js" \ |
| 597 | "$(SRCDIR)\scroll.js" \ |
| 598 | "$(SRCDIR)\skin.js" \ |
| 599 | "$(SRCDIR)\sorttable.js" \ |
| 600 | "$(SRCDIR)\sounds\0.wav" \ |
| 601 | "$(SRCDIR)\sounds\1.wav" \ |
| @@ -707,10 +709,11 @@ | |
| 707 | "$(OX)\lookslike$O" \ |
| 708 | "$(OX)\main$O" \ |
| 709 | "$(OX)\manifest$O" \ |
| 710 | "$(OX)\markdown$O" \ |
| 711 | "$(OX)\markdown_html$O" \ |
| 712 | "$(OX)\md5$O" \ |
| 713 | "$(OX)\merge$O" \ |
| 714 | "$(OX)\merge3$O" \ |
| 715 | "$(OX)\moderate$O" \ |
| 716 | "$(OX)\name$O" \ |
| @@ -956,10 +959,11 @@ | |
| 956 | echo "$(OX)\lookslike.obj" >> $@ |
| 957 | echo "$(OX)\main.obj" >> $@ |
| 958 | echo "$(OX)\manifest.obj" >> $@ |
| 959 | echo "$(OX)\markdown.obj" >> $@ |
| 960 | echo "$(OX)\markdown_html.obj" >> $@ |
| 961 | echo "$(OX)\md5.obj" >> $@ |
| 962 | echo "$(OX)\merge.obj" >> $@ |
| 963 | echo "$(OX)\merge3.obj" >> $@ |
| 964 | echo "$(OX)\moderate.obj" >> $@ |
| 965 | echo "$(OX)\name.obj" >> $@ |
| @@ -1222,10 +1226,11 @@ | |
| 1222 | echo "$(SRCDIR)\hbmenu.js" >> $@ |
| 1223 | echo "$(SRCDIR)\href.js" >> $@ |
| 1224 | echo "$(SRCDIR)\login.js" >> $@ |
| 1225 | echo "$(SRCDIR)\markdown.md" >> $@ |
| 1226 | echo "$(SRCDIR)\menu.js" >> $@ |
| 1227 | echo "$(SRCDIR)\scroll.js" >> $@ |
| 1228 | echo "$(SRCDIR)\skin.js" >> $@ |
| 1229 | echo "$(SRCDIR)\sorttable.js" >> $@ |
| 1230 | echo "$(SRCDIR)\sounds/0.wav" >> $@ |
| 1231 | echo "$(SRCDIR)\sounds/1.wav" >> $@ |
| @@ -1760,10 +1765,16 @@ | |
| 1760 | "$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h" |
| 1761 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c" |
| 1762 | |
| 1763 | "$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c" |
| 1764 | "$(OBJDIR)\translate$E" $** > $@ |
| 1765 | |
| 1766 | "$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h" |
| 1767 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c" |
| 1768 | |
| 1769 | "$(OX)\md5_.c" : "$(SRCDIR)\md5.c" |
| @@ -2222,10 +2233,11 @@ | |
| 2222 | "$(OX)\lookslike_.c":"$(OX)\lookslike.h" \ |
| 2223 | "$(OX)\main_.c":"$(OX)\main.h" \ |
| 2224 | "$(OX)\manifest_.c":"$(OX)\manifest.h" \ |
| 2225 | "$(OX)\markdown_.c":"$(OX)\markdown.h" \ |
| 2226 | "$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \ |
| 2227 | "$(OX)\md5_.c":"$(OX)\md5.h" \ |
| 2228 | "$(OX)\merge_.c":"$(OX)\merge.h" \ |
| 2229 | "$(OX)\merge3_.c":"$(OX)\merge3.h" \ |
| 2230 | "$(OX)\moderate_.c":"$(OX)\moderate.h" \ |
| 2231 | "$(OX)\name_.c":"$(OX)\name.h" \ |
| 2232 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -443,10 +443,11 @@ | |
| 443 | "$(OX)\lookslike_.c" \ |
| 444 | "$(OX)\main_.c" \ |
| 445 | "$(OX)\manifest_.c" \ |
| 446 | "$(OX)\markdown_.c" \ |
| 447 | "$(OX)\markdown_html_.c" \ |
| 448 | "$(OX)\match_.c" \ |
| 449 | "$(OX)\md5_.c" \ |
| 450 | "$(OX)\merge_.c" \ |
| 451 | "$(OX)\merge3_.c" \ |
| 452 | "$(OX)\moderate_.c" \ |
| 453 | "$(OX)\name_.c" \ |
| @@ -592,10 +593,11 @@ | |
| 593 | "$(SRCDIR)\hbmenu.js" \ |
| 594 | "$(SRCDIR)\href.js" \ |
| 595 | "$(SRCDIR)\login.js" \ |
| 596 | "$(SRCDIR)\markdown.md" \ |
| 597 | "$(SRCDIR)\menu.js" \ |
| 598 | "$(SRCDIR)\merge.tcl" \ |
| 599 | "$(SRCDIR)\scroll.js" \ |
| 600 | "$(SRCDIR)\skin.js" \ |
| 601 | "$(SRCDIR)\sorttable.js" \ |
| 602 | "$(SRCDIR)\sounds\0.wav" \ |
| 603 | "$(SRCDIR)\sounds\1.wav" \ |
| @@ -707,10 +709,11 @@ | |
| 709 | "$(OX)\lookslike$O" \ |
| 710 | "$(OX)\main$O" \ |
| 711 | "$(OX)\manifest$O" \ |
| 712 | "$(OX)\markdown$O" \ |
| 713 | "$(OX)\markdown_html$O" \ |
| 714 | "$(OX)\match$O" \ |
| 715 | "$(OX)\md5$O" \ |
| 716 | "$(OX)\merge$O" \ |
| 717 | "$(OX)\merge3$O" \ |
| 718 | "$(OX)\moderate$O" \ |
| 719 | "$(OX)\name$O" \ |
| @@ -956,10 +959,11 @@ | |
| 959 | echo "$(OX)\lookslike.obj" >> $@ |
| 960 | echo "$(OX)\main.obj" >> $@ |
| 961 | echo "$(OX)\manifest.obj" >> $@ |
| 962 | echo "$(OX)\markdown.obj" >> $@ |
| 963 | echo "$(OX)\markdown_html.obj" >> $@ |
| 964 | echo "$(OX)\match.obj" >> $@ |
| 965 | echo "$(OX)\md5.obj" >> $@ |
| 966 | echo "$(OX)\merge.obj" >> $@ |
| 967 | echo "$(OX)\merge3.obj" >> $@ |
| 968 | echo "$(OX)\moderate.obj" >> $@ |
| 969 | echo "$(OX)\name.obj" >> $@ |
| @@ -1222,10 +1226,11 @@ | |
| 1226 | echo "$(SRCDIR)\hbmenu.js" >> $@ |
| 1227 | echo "$(SRCDIR)\href.js" >> $@ |
| 1228 | echo "$(SRCDIR)\login.js" >> $@ |
| 1229 | echo "$(SRCDIR)\markdown.md" >> $@ |
| 1230 | echo "$(SRCDIR)\menu.js" >> $@ |
| 1231 | echo "$(SRCDIR)\merge.tcl" >> $@ |
| 1232 | echo "$(SRCDIR)\scroll.js" >> $@ |
| 1233 | echo "$(SRCDIR)\skin.js" >> $@ |
| 1234 | echo "$(SRCDIR)\sorttable.js" >> $@ |
| 1235 | echo "$(SRCDIR)\sounds/0.wav" >> $@ |
| 1236 | echo "$(SRCDIR)\sounds/1.wav" >> $@ |
| @@ -1760,10 +1765,16 @@ | |
| 1765 | "$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h" |
| 1766 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c" |
| 1767 | |
| 1768 | "$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c" |
| 1769 | "$(OBJDIR)\translate$E" $** > $@ |
| 1770 | |
| 1771 | "$(OX)\match$O" : "$(OX)\match_.c" "$(OX)\match.h" |
| 1772 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\match_.c" |
| 1773 | |
| 1774 | "$(OX)\match_.c" : "$(SRCDIR)\match.c" |
| 1775 | "$(OBJDIR)\translate$E" $** > $@ |
| 1776 | |
| 1777 | "$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h" |
| 1778 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c" |
| 1779 | |
| 1780 | "$(OX)\md5_.c" : "$(SRCDIR)\md5.c" |
| @@ -2222,10 +2233,11 @@ | |
| 2233 | "$(OX)\lookslike_.c":"$(OX)\lookslike.h" \ |
| 2234 | "$(OX)\main_.c":"$(OX)\main.h" \ |
| 2235 | "$(OX)\manifest_.c":"$(OX)\manifest.h" \ |
| 2236 | "$(OX)\markdown_.c":"$(OX)\markdown.h" \ |
| 2237 | "$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \ |
| 2238 | "$(OX)\match_.c":"$(OX)\match.h" \ |
| 2239 | "$(OX)\md5_.c":"$(OX)\md5.h" \ |
| 2240 | "$(OX)\merge_.c":"$(OX)\merge.h" \ |
| 2241 | "$(OX)\merge3_.c":"$(OX)\merge3.h" \ |
| 2242 | "$(OX)\moderate_.c":"$(OX)\moderate.h" \ |
| 2243 | "$(OX)\name_.c":"$(OX)\name.h" \ |
| 2244 |
+2
-2
| --- www/alerts.md | ||
| +++ www/alerts.md | ||
| @@ -311,11 +311,11 @@ | ||
| 311 | 311 | ## Advanced Email Setups |
| 312 | 312 | |
| 313 | 313 | Fossil offers several methods of sending email: |
| 314 | 314 | |
| 315 | 315 | 1. Pipe the email message text into a command. |
| 316 | - 2. Store email messages as entries in a SQLite database. | |
| 316 | + 2. Store email messages as entries in an SQLite database. | |
| 317 | 317 | 3. Store email messages as individual files in a directory. |
| 318 | 318 | 4. Send emails to an SMTP relay. |
| 319 | 319 | 5. Send emails directly to the recipients via SMTP. |
| 320 | 320 | |
| 321 | 321 | This wide range of options allows Fossil to talk to pretty much any |
| @@ -390,11 +390,11 @@ | ||
| 390 | 390 | currently uses this method rather than [the pipe method](#pipe) because |
| 391 | 391 | it is running inside of a restrictive [chroot jail][cj] which is unable |
| 392 | 392 | to hand off messages to the local MTA directly. |
| 393 | 393 | |
| 394 | 394 | When you configure a Fossil server this way, it adds outgoing email |
| 395 | -messages to a SQLite database file. A separate daemon process can then | |
| 395 | +messages to an SQLite database file. A separate daemon process can then | |
| 396 | 396 | extract those messages for further disposition. |
| 397 | 397 | |
| 398 | 398 | Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl) |
| 399 | 399 | used on `fossil-scm.org`: it is just a short Tcl script that |
| 400 | 400 | continuously monitors this database for new messages and hands any that |
| 401 | 401 |
| --- www/alerts.md | |
| +++ www/alerts.md | |
| @@ -311,11 +311,11 @@ | |
| 311 | ## Advanced Email Setups |
| 312 | |
| 313 | Fossil offers several methods of sending email: |
| 314 | |
| 315 | 1. Pipe the email message text into a command. |
| 316 | 2. Store email messages as entries in a SQLite database. |
| 317 | 3. Store email messages as individual files in a directory. |
| 318 | 4. Send emails to an SMTP relay. |
| 319 | 5. Send emails directly to the recipients via SMTP. |
| 320 | |
| 321 | This wide range of options allows Fossil to talk to pretty much any |
| @@ -390,11 +390,11 @@ | |
| 390 | currently uses this method rather than [the pipe method](#pipe) because |
| 391 | it is running inside of a restrictive [chroot jail][cj] which is unable |
| 392 | to hand off messages to the local MTA directly. |
| 393 | |
| 394 | When you configure a Fossil server this way, it adds outgoing email |
| 395 | messages to a SQLite database file. A separate daemon process can then |
| 396 | extract those messages for further disposition. |
| 397 | |
| 398 | Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl) |
| 399 | used on `fossil-scm.org`: it is just a short Tcl script that |
| 400 | continuously monitors this database for new messages and hands any that |
| 401 |
| --- www/alerts.md | |
| +++ www/alerts.md | |
| @@ -311,11 +311,11 @@ | |
| 311 | ## Advanced Email Setups |
| 312 | |
| 313 | Fossil offers several methods of sending email: |
| 314 | |
| 315 | 1. Pipe the email message text into a command. |
| 316 | 2. Store email messages as entries in an SQLite database. |
| 317 | 3. Store email messages as individual files in a directory. |
| 318 | 4. Send emails to an SMTP relay. |
| 319 | 5. Send emails directly to the recipients via SMTP. |
| 320 | |
| 321 | This wide range of options allows Fossil to talk to pretty much any |
| @@ -390,11 +390,11 @@ | |
| 390 | currently uses this method rather than [the pipe method](#pipe) because |
| 391 | it is running inside of a restrictive [chroot jail][cj] which is unable |
| 392 | to hand off messages to the local MTA directly. |
| 393 | |
| 394 | When you configure a Fossil server this way, it adds outgoing email |
| 395 | messages to an SQLite database file. A separate daemon process can then |
| 396 | extract those messages for further disposition. |
| 397 | |
| 398 | Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl) |
| 399 | used on `fossil-scm.org`: it is just a short Tcl script that |
| 400 | continuously monitors this database for new messages and hands any that |
| 401 |
+1
-1
| --- www/caps/ref.html | ||
| +++ www/caps/ref.html | ||
| @@ -252,11 +252,11 @@ | ||
| 252 | 252 | Create new ticket report formats. Note that although this allows |
| 253 | 253 | the user to provide SQL code to be run in the server’s context, |
| 254 | 254 | and this capability is given to the untrusted “anonymous” user |
| 255 | 255 | category by default, this is a safe capability to give to users |
| 256 | 256 | because it is internally restricted to read-only queries on the |
| 257 | - tickets table only. (This restriction is done with a SQLite | |
| 257 | + tickets table only. (This restriction is done with an SQLite | |
| 258 | 258 | authorization hook, not by any method so weak as SQL text |
| 259 | 259 | filtering.) Mnemonic: new <b>t</b>icket report. |
| 260 | 260 | </td> |
| 261 | 261 | </tr> |
| 262 | 262 | |
| 263 | 263 |
| --- www/caps/ref.html | |
| +++ www/caps/ref.html | |
| @@ -252,11 +252,11 @@ | |
| 252 | Create new ticket report formats. Note that although this allows |
| 253 | the user to provide SQL code to be run in the server’s context, |
| 254 | and this capability is given to the untrusted “anonymous” user |
| 255 | category by default, this is a safe capability to give to users |
| 256 | because it is internally restricted to read-only queries on the |
| 257 | tickets table only. (This restriction is done with a SQLite |
| 258 | authorization hook, not by any method so weak as SQL text |
| 259 | filtering.) Mnemonic: new <b>t</b>icket report. |
| 260 | </td> |
| 261 | </tr> |
| 262 | |
| 263 |
| --- www/caps/ref.html | |
| +++ www/caps/ref.html | |
| @@ -252,11 +252,11 @@ | |
| 252 | Create new ticket report formats. Note that although this allows |
| 253 | the user to provide SQL code to be run in the server’s context, |
| 254 | and this capability is given to the untrusted “anonymous” user |
| 255 | category by default, this is a safe capability to give to users |
| 256 | because it is internally restricted to read-only queries on the |
| 257 | tickets table only. (This restriction is done with an SQLite |
| 258 | authorization hook, not by any method so weak as SQL text |
| 259 | filtering.) Mnemonic: new <b>t</b>icket report. |
| 260 | </td> |
| 261 | </tr> |
| 262 | |
| 263 |
+51
-1
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,8 +1,55 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | -<h2 id='v2_25'>Changes for version 2.25 (pending)</h2> | |
| 3 | +<h2 id='v2_26'>Changes for version 2.26 (pending)</h2> | |
| 4 | + | |
| 5 | + * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that | |
| 6 | + it optionally accepts a directory name as its argument, and uses files | |
| 7 | + under that directory as the baseline for the diff. | |
| 8 | + * Added the [/help?cmd=/ckout|/ckout web page] to provide information | |
| 9 | + about pending changes in a working check-out | |
| 10 | + * The [/help?cmd=ui|fossil ui] command defaults to using the | |
| 11 | + [/help?cmd=/ckout|/ckout page] as its start page. Or, if the | |
| 12 | + "--from PATH" option is present, the default start page becomes | |
| 13 | + "/ckout?exbase=PATH". | |
| 14 | + * Added the [/help?cmd=merge-info|fossil merge-info] command and especially | |
| 15 | + the --tk option to that command, to provide analysis of the most recent | |
| 16 | + merge or update operation. | |
| 17 | + * Issue a warning if a user tries to commit on a check-in where the | |
| 18 | + branch has been changed. | |
| 19 | + * When a merge conflict occurs, a new section is added to the conflict | |
| 20 | + text that shows Fossil's suggested resolution to the conflict. | |
| 21 | + * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show | |
| 22 | + diffs of multiple files. | |
| 23 | + * Enhancements to the [/help?cmd=/timeline|/timeline page]: | |
| 24 | + <ol type="a"> | |
| 25 | + <li> Added the "ml=" ("Merge-in List") query parameter that works | |
| 26 | + like "rl=" ("Related List") but adds "mionly" style related | |
| 27 | + check-ins instead of the full "rel" style. | |
| 28 | + <li> For "tl=", "rl=", and "ml=", the order of the branches in the | |
| 29 | + graph now tries to match the order of the branches named in | |
| 30 | + the list. | |
| 31 | + <li> The "ms=" ("Match Style") query parameter is honored for | |
| 32 | + "tl=", "rl=", and "ml=". | |
| 33 | + <li> New query parameter "sl=BRANCHLIST" ("Sort List") strives to | |
| 34 | + put branches in the specified order in the graph. This | |
| 35 | + overrides any "tl=" or similar ordering. | |
| 36 | + <li> In the various "from=","to=" query formats, if the one of the | |
| 37 | + end points is an ancestor of the other, then the "rel" modifier | |
| 38 | + omits check-ins that are not ancestors of the newer endpoint. | |
| 39 | + <li> For "tl=" and similar query parameters, if the pattern contains | |
| 40 | + GLOB characters, then the matching style ("ms=") is set to GLOB | |
| 41 | + automatically and the "ms=" query parameter can be omitted. | |
| 42 | + </ol> | |
| 43 | + * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis | |
| 44 | + and debugging | |
| 45 | + * Fix a bug in [/help?cmd=patch|fossil patch create] that causes | |
| 46 | + [/help?cmd=revert|fossil revert] operations that happened on individual | |
| 47 | + files after a [/help?cmd=merge|fossil merge] to be omitted from the | |
| 48 | + patch. | |
| 49 | + | |
| 50 | +<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> | |
| 4 | 51 | |
| 5 | 52 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 6 | 53 | that have non-ASCII filenames |
| 7 | 54 | * Add the [/help?cmd=tree|fossil tree] command. |
| 8 | 55 | * On case-insensitive filesystems, store files using the filesystem's |
| @@ -11,17 +58,20 @@ | ||
| 11 | 58 | which is more familiar to Git users. Retain the legacy name for |
| 12 | 59 | compatibility. |
| 13 | 60 | * Add new query parameters to the [/help?cmd=/timeline|/timeline page]: |
| 14 | 61 | d2=, p2=, and dp2=. |
| 15 | 62 | * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values. |
| 63 | + * Add the -b|--brief option to the [/help?cmd=status|fossil status] command. | |
| 16 | 64 | * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page]. |
| 17 | 65 | * Add history search to the [/help?cmd=/chat|/chat page]. |
| 18 | 66 | * Add Unix socket support to the [/help?cmd=server|server command]. |
| 19 | 67 | * On Windows, use the root certificates managed by the operating system |
| 20 | 68 | (requires OpenSSL 3.2.0 or greater). |
| 21 | 69 | * Take into account zero-width and double-width unicode characters when |
| 22 | 70 | formatting the command-line timeline. |
| 71 | + * Update the built-in SQLite to version 3.47.0. Precompiled binaries are | |
| 72 | + linked against OpenSSL 3.4.0. | |
| 23 | 73 | * Numerous minor fixes and additions. |
| 24 | 74 | |
| 25 | 75 | |
| 26 | 76 | <h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2> |
| 27 | 77 | |
| 28 | 78 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,8 +1,55 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_25'>Changes for version 2.25 (pending)</h2> |
| 4 | |
| 5 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 6 | that have non-ASCII filenames |
| 7 | * Add the [/help?cmd=tree|fossil tree] command. |
| 8 | * On case-insensitive filesystems, store files using the filesystem's |
| @@ -11,17 +58,20 @@ | |
| 11 | which is more familiar to Git users. Retain the legacy name for |
| 12 | compatibility. |
| 13 | * Add new query parameters to the [/help?cmd=/timeline|/timeline page]: |
| 14 | d2=, p2=, and dp2=. |
| 15 | * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values. |
| 16 | * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page]. |
| 17 | * Add history search to the [/help?cmd=/chat|/chat page]. |
| 18 | * Add Unix socket support to the [/help?cmd=server|server command]. |
| 19 | * On Windows, use the root certificates managed by the operating system |
| 20 | (requires OpenSSL 3.2.0 or greater). |
| 21 | * Take into account zero-width and double-width unicode characters when |
| 22 | formatting the command-line timeline. |
| 23 | * Numerous minor fixes and additions. |
| 24 | |
| 25 | |
| 26 | <h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2> |
| 27 | |
| 28 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,8 +1,55 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_26'>Changes for version 2.26 (pending)</h2> |
| 4 | |
| 5 | * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that |
| 6 | it optionally accepts a directory name as its argument, and uses files |
| 7 | under that directory as the baseline for the diff. |
| 8 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 9 | about pending changes in a working check-out |
| 10 | * The [/help?cmd=ui|fossil ui] command defaults to using the |
| 11 | [/help?cmd=/ckout|/ckout page] as its start page. Or, if the |
| 12 | "--from PATH" option is present, the default start page becomes |
| 13 | "/ckout?exbase=PATH". |
| 14 | * Added the [/help?cmd=merge-info|fossil merge-info] command and especially |
| 15 | the --tk option to that command, to provide analysis of the most recent |
| 16 | merge or update operation. |
| 17 | * Issue a warning if a user tries to commit on a check-in where the |
| 18 | branch has been changed. |
| 19 | * When a merge conflict occurs, a new section is added to the conflict |
| 20 | text that shows Fossil's suggested resolution to the conflict. |
| 21 | * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 22 | diffs of multiple files. |
| 23 | * Enhancements to the [/help?cmd=/timeline|/timeline page]: |
| 24 | <ol type="a"> |
| 25 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 26 | like "rl=" ("Related List") but adds "mionly" style related |
| 27 | check-ins instead of the full "rel" style. |
| 28 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| 29 | graph now tries to match the order of the branches named in |
| 30 | the list. |
| 31 | <li> The "ms=" ("Match Style") query parameter is honored for |
| 32 | "tl=", "rl=", and "ml=". |
| 33 | <li> New query parameter "sl=BRANCHLIST" ("Sort List") strives to |
| 34 | put branches in the specified order in the graph. This |
| 35 | overrides any "tl=" or similar ordering. |
| 36 | <li> In the various "from=","to=" query formats, if the one of the |
| 37 | end points is an ancestor of the other, then the "rel" modifier |
| 38 | omits check-ins that are not ancestors of the newer endpoint. |
| 39 | <li> For "tl=" and similar query parameters, if the pattern contains |
| 40 | GLOB characters, then the matching style ("ms=") is set to GLOB |
| 41 | automatically and the "ms=" query parameter can be omitted. |
| 42 | </ol> |
| 43 | * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 44 | and debugging |
| 45 | * Fix a bug in [/help?cmd=patch|fossil patch create] that causes |
| 46 | [/help?cmd=revert|fossil revert] operations that happened on individual |
| 47 | files after a [/help?cmd=merge|fossil merge] to be omitted from the |
| 48 | patch. |
| 49 | |
| 50 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 51 | |
| 52 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 53 | that have non-ASCII filenames |
| 54 | * Add the [/help?cmd=tree|fossil tree] command. |
| 55 | * On case-insensitive filesystems, store files using the filesystem's |
| @@ -11,17 +58,20 @@ | |
| 58 | which is more familiar to Git users. Retain the legacy name for |
| 59 | compatibility. |
| 60 | * Add new query parameters to the [/help?cmd=/timeline|/timeline page]: |
| 61 | d2=, p2=, and dp2=. |
| 62 | * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values. |
| 63 | * Add the -b|--brief option to the [/help?cmd=status|fossil status] command. |
| 64 | * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page]. |
| 65 | * Add history search to the [/help?cmd=/chat|/chat page]. |
| 66 | * Add Unix socket support to the [/help?cmd=server|server command]. |
| 67 | * On Windows, use the root certificates managed by the operating system |
| 68 | (requires OpenSSL 3.2.0 or greater). |
| 69 | * Take into account zero-width and double-width unicode characters when |
| 70 | formatting the command-line timeline. |
| 71 | * Update the built-in SQLite to version 3.47.0. Precompiled binaries are |
| 72 | linked against OpenSSL 3.4.0. |
| 73 | * Numerous minor fixes and additions. |
| 74 | |
| 75 | |
| 76 | <h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2> |
| 77 | |
| 78 |
+1
-1
| --- www/fossil-is-not-relational.md | ||
| +++ www/fossil-is-not-relational.md | ||
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | metadata. |
| 132 | 132 | |
| 133 | 133 | - Raw file content of versioned files. These data are external to |
| 134 | 134 | artifacts, which refer to them by their hashes. How they are stored |
| 135 | 135 | is not the concern of the data model, but (spoiler alert!) Fossil |
| 136 | - stores them in an sqlite database, one record per distinct hash, in | |
| 136 | + stores them in an SQLite database, one record per distinct hash, in | |
| 137 | 137 | its `blob` table (which we will cover more very soon). |
| 138 | 138 | |
| 139 | 139 | Non-SCM-relevant state includes: |
| 140 | 140 | |
| 141 | 141 | - Fossil's list of users and their metadata (permissions, email |
| 142 | 142 |
| --- www/fossil-is-not-relational.md | |
| +++ www/fossil-is-not-relational.md | |
| @@ -131,11 +131,11 @@ | |
| 131 | metadata. |
| 132 | |
| 133 | - Raw file content of versioned files. These data are external to |
| 134 | artifacts, which refer to them by their hashes. How they are stored |
| 135 | is not the concern of the data model, but (spoiler alert!) Fossil |
| 136 | stores them in an sqlite database, one record per distinct hash, in |
| 137 | its `blob` table (which we will cover more very soon). |
| 138 | |
| 139 | Non-SCM-relevant state includes: |
| 140 | |
| 141 | - Fossil's list of users and their metadata (permissions, email |
| 142 |
| --- www/fossil-is-not-relational.md | |
| +++ www/fossil-is-not-relational.md | |
| @@ -131,11 +131,11 @@ | |
| 131 | metadata. |
| 132 | |
| 133 | - Raw file content of versioned files. These data are external to |
| 134 | artifacts, which refer to them by their hashes. How they are stored |
| 135 | is not the concern of the data model, but (spoiler alert!) Fossil |
| 136 | stores them in an SQLite database, one record per distinct hash, in |
| 137 | its `blob` table (which we will cover more very soon). |
| 138 | |
| 139 | Non-SCM-relevant state includes: |
| 140 | |
| 141 | - Fossil's list of users and their metadata (permissions, email |
| 142 |
+1
-1
| --- www/fossil-v-git.wiki | ||
| +++ www/fossil-v-git.wiki | ||
| @@ -596,11 +596,11 @@ | ||
| 596 | 596 | |
| 597 | 597 | Because Git commingles the repository data with the initial checkout of |
| 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 | -Fossil doesn't work that way. A Fossil repository is a SQLite database | |
| 601 | +Fossil doesn't work that way. A Fossil repository is an SQLite database | |
| 602 | 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | 603 | [/help?cmd=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 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -596,11 +596,11 @@ | |
| 596 | |
| 597 | Because Git commingles the repository data with the initial checkout of |
| 598 | that repository, the default mode of operation in Git is to stick to that |
| 599 | single work/repo tree, even when that's a shortsighted way of working. |
| 600 | |
| 601 | Fossil doesn't work that way. A Fossil repository is a SQLite database |
| 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | [/help?cmd=open | open] a Fossil repository any number of times into |
| 604 | any number of working directories. A common usage pattern is to have one |
| 605 | working directory per active working branch, so that switching branches |
| 606 | is done with a <tt>cd</tt> command rather than by checking out the |
| 607 |
| --- www/fossil-v-git.wiki | |
| +++ www/fossil-v-git.wiki | |
| @@ -596,11 +596,11 @@ | |
| 596 | |
| 597 | Because Git commingles the repository data with the initial checkout of |
| 598 | that repository, the default mode of operation in Git is to stick to that |
| 599 | single work/repo tree, even when that's a shortsighted way of working. |
| 600 | |
| 601 | Fossil doesn't work that way. A Fossil repository is an SQLite database |
| 602 | file which is normally stored outside the working checkout directory. You can |
| 603 | [/help?cmd=open | open] a Fossil repository any number of times into |
| 604 | any number of working directories. A common usage pattern is to have one |
| 605 | working directory per active working branch, so that switching branches |
| 606 | is done with a <tt>cd</tt> command rather than by checking out the |
| 607 |
+2
-2
| --- www/gitusers.md | ||
| +++ www/gitusers.md | ||
| @@ -56,11 +56,11 @@ | ||
| 56 | 56 | |
| 57 | 57 | |
| 58 | 58 | |
| 59 | 59 | #### <a id="cwork" name="scw"></a> Checkout Workflows |
| 60 | 60 | |
| 61 | -A Fossil repository is a SQLite database storing the entire history of a | |
| 61 | +A Fossil repository is an SQLite database storing the entire history of a | |
| 62 | 62 | project. It is not normally stored inside the working tree. |
| 63 | 63 | A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory |
| 64 | 64 | that contains a snapshot of your project that you are currently working |
| 65 | 65 | on, extracted for you from the repository database file by the `fossil` |
| 66 | 66 | program. |
| @@ -148,11 +148,11 @@ | ||
| 148 | 148 | option, it won’t let you close the check-out with uncommitted changes to |
| 149 | 149 | those managed files. |
| 150 | 150 | |
| 151 | 151 | The `close` command also refuses to run without `--force` when you have |
| 152 | 152 | certain other precious per-checkout data that Fossil stores in the |
| 153 | -`.fslckout` file at the root of a check-out directory. This is a SQLite | |
| 153 | +`.fslckout` file at the root of a check-out directory. This is an SQLite | |
| 154 | 154 | database that keeps track of local state such as what version you have |
| 155 | 155 | checked out, the contents of the [stash] for that working directory, the |
| 156 | 156 | [undo] buffers, per-checkout [settings][set], and so forth. The stash |
| 157 | 157 | and undo buffers are considered precious uncommitted changes, |
| 158 | 158 | so you have to force Fossil to discard these as part of closing the |
| 159 | 159 |
| --- www/gitusers.md | |
| +++ www/gitusers.md | |
| @@ -56,11 +56,11 @@ | |
| 56 | |
| 57 | |
| 58 | |
| 59 | #### <a id="cwork" name="scw"></a> Checkout Workflows |
| 60 | |
| 61 | A Fossil repository is a SQLite database storing the entire history of a |
| 62 | project. It is not normally stored inside the working tree. |
| 63 | A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory |
| 64 | that contains a snapshot of your project that you are currently working |
| 65 | on, extracted for you from the repository database file by the `fossil` |
| 66 | program. |
| @@ -148,11 +148,11 @@ | |
| 148 | option, it won’t let you close the check-out with uncommitted changes to |
| 149 | those managed files. |
| 150 | |
| 151 | The `close` command also refuses to run without `--force` when you have |
| 152 | certain other precious per-checkout data that Fossil stores in the |
| 153 | `.fslckout` file at the root of a check-out directory. This is a SQLite |
| 154 | database that keeps track of local state such as what version you have |
| 155 | checked out, the contents of the [stash] for that working directory, the |
| 156 | [undo] buffers, per-checkout [settings][set], and so forth. The stash |
| 157 | and undo buffers are considered precious uncommitted changes, |
| 158 | so you have to force Fossil to discard these as part of closing the |
| 159 |
| --- www/gitusers.md | |
| +++ www/gitusers.md | |
| @@ -56,11 +56,11 @@ | |
| 56 | |
| 57 | |
| 58 | |
| 59 | #### <a id="cwork" name="scw"></a> Checkout Workflows |
| 60 | |
| 61 | A Fossil repository is an SQLite database storing the entire history of a |
| 62 | project. It is not normally stored inside the working tree. |
| 63 | A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory |
| 64 | that contains a snapshot of your project that you are currently working |
| 65 | on, extracted for you from the repository database file by the `fossil` |
| 66 | program. |
| @@ -148,11 +148,11 @@ | |
| 148 | option, it won’t let you close the check-out with uncommitted changes to |
| 149 | those managed files. |
| 150 | |
| 151 | The `close` command also refuses to run without `--force` when you have |
| 152 | certain other precious per-checkout data that Fossil stores in the |
| 153 | `.fslckout` file at the root of a check-out directory. This is an SQLite |
| 154 | database that keeps track of local state such as what version you have |
| 155 | checked out, the contents of the [stash] for that working directory, the |
| 156 | [undo] buffers, per-checkout [settings][set], and so forth. The stash |
| 157 | and undo buffers are considered precious uncommitted changes, |
| 158 | so you have to force Fossil to discard these as part of closing the |
| 159 |
+1
-1
| --- www/glossary.md | ||
| +++ www/glossary.md | ||
| @@ -348,11 +348,11 @@ | ||
| 348 | 348 | |
| 349 | 349 | * In the same way that one cannot extract files from a zip archive |
| 350 | 350 | without having a copy of that zip file, one cannot make check-outs |
| 351 | 351 | without access to the repository file or a clone thereof. |
| 352 | 352 | |
| 353 | -* Because a Fossil repository is a SQLite database file, the same | |
| 353 | +* Because a Fossil repository is an SQLite database file, the same | |
| 354 | 354 | rules for avoiding data corruption apply to it. In particular, it is |
| 355 | 355 | [nearly a hard requirement][h2cflp] that the repository clone be on |
| 356 | 356 | the same machine as the one where you make check-outs and the |
| 357 | 357 | subsequent check-ins. |
| 358 | 358 | |
| 359 | 359 |
| --- www/glossary.md | |
| +++ www/glossary.md | |
| @@ -348,11 +348,11 @@ | |
| 348 | |
| 349 | * In the same way that one cannot extract files from a zip archive |
| 350 | without having a copy of that zip file, one cannot make check-outs |
| 351 | without access to the repository file or a clone thereof. |
| 352 | |
| 353 | * Because a Fossil repository is a SQLite database file, the same |
| 354 | rules for avoiding data corruption apply to it. In particular, it is |
| 355 | [nearly a hard requirement][h2cflp] that the repository clone be on |
| 356 | the same machine as the one where you make check-outs and the |
| 357 | subsequent check-ins. |
| 358 | |
| 359 |
| --- www/glossary.md | |
| +++ www/glossary.md | |
| @@ -348,11 +348,11 @@ | |
| 348 | |
| 349 | * In the same way that one cannot extract files from a zip archive |
| 350 | without having a copy of that zip file, one cannot make check-outs |
| 351 | without access to the repository file or a clone thereof. |
| 352 | |
| 353 | * Because a Fossil repository is an SQLite database file, the same |
| 354 | rules for avoiding data corruption apply to it. In particular, it is |
| 355 | [nearly a hard requirement][h2cflp] that the repository clone be on |
| 356 | the same machine as the one where you make check-outs and the |
| 357 | subsequent check-ins. |
| 358 | |
| 359 |
+4
-4
| --- www/index.wiki | ||
| +++ www/index.wiki | ||
| @@ -84,16 +84,16 @@ | ||
| 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.24 ([/timeline?c=version-2.24|2024-04-23])</h3> | |
| 89 | +<h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3> | |
| 90 | 90 | |
| 91 | 91 | * [/uv/download.html|Download] |
| 92 | - * [./changes.wiki#v2_24|Change Summary] | |
| 93 | - * [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24] | |
| 94 | - * [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release] | |
| 92 | + * [./changes.wiki#v2_25|Change Summary] | |
| 93 | + * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25] | |
| 94 | + * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release] | |
| 95 | 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.24 ([/timeline?c=version-2.24|2024-04-23])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_24|Change Summary] |
| 93 | * [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24] |
| 94 | * [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -84,16 +84,16 @@ | |
| 84 | the repository are consistent prior to each commit. |
| 85 | |
| 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | |
| 88 | <hr> |
| 89 | <h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_25|Change Summary] |
| 93 | * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25] |
| 94 | * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
+1
-1
| --- www/qandc.wiki | ||
| +++ www/qandc.wiki | ||
| @@ -142,11 +142,11 @@ | ||
| 142 | 142 | fossil supports disconnected operation. |
| 143 | 143 | |
| 144 | 144 | As for bloat: Fossil is a single self-contained executable. |
| 145 | 145 | You do not need any other packages |
| 146 | 146 | (diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache, |
| 147 | -sqlite, and so forth) | |
| 147 | +SQLite, and so forth) | |
| 148 | 148 | in order to run fossil. Fossil runs just fine in a chroot jail all |
| 149 | 149 | by itself. And the self-contained fossil |
| 150 | 150 | executable is much less than 1MB in size. (Update 2015-01-12: Fossil has |
| 151 | 151 | grown in the years since the previous sentence was written but is still |
| 152 | 152 | much less than 2MB according to "size" when compiled using -Os on x64 Linux.) |
| 153 | 153 |
| --- www/qandc.wiki | |
| +++ www/qandc.wiki | |
| @@ -142,11 +142,11 @@ | |
| 142 | fossil supports disconnected operation. |
| 143 | |
| 144 | As for bloat: Fossil is a single self-contained executable. |
| 145 | You do not need any other packages |
| 146 | (diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache, |
| 147 | sqlite, and so forth) |
| 148 | in order to run fossil. Fossil runs just fine in a chroot jail all |
| 149 | by itself. And the self-contained fossil |
| 150 | executable is much less than 1MB in size. (Update 2015-01-12: Fossil has |
| 151 | grown in the years since the previous sentence was written but is still |
| 152 | much less than 2MB according to "size" when compiled using -Os on x64 Linux.) |
| 153 |
| --- www/qandc.wiki | |
| +++ www/qandc.wiki | |
| @@ -142,11 +142,11 @@ | |
| 142 | fossil supports disconnected operation. |
| 143 | |
| 144 | As for bloat: Fossil is a single self-contained executable. |
| 145 | You do not need any other packages |
| 146 | (diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache, |
| 147 | SQLite, and so forth) |
| 148 | in order to run fossil. Fossil runs just fine in a chroot jail all |
| 149 | by itself. And the self-contained fossil |
| 150 | executable is much less than 1MB in size. (Update 2015-01-12: Fossil has |
| 151 | grown in the years since the previous sentence was written but is still |
| 152 | much less than 2MB according to "size" when compiled using -Os on x64 Linux.) |
| 153 |
+197
-88
| --- www/ssl-server.md | ||
| +++ www/ssl-server.md | ||
| @@ -12,76 +12,107 @@ | ||
| 12 | 12 | |
| 13 | 13 | [0]: ./ssl.wiki |
| 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 | -this has been fixed. Commands like | |
| 17 | +Fossil servers are now able to converse directly over TLS. Commands like | |
| 18 | 18 | |
| 19 | 19 | * "[fossil server](/help?cmd=server)" |
| 20 | 20 | * "[fossil ui](/help?cmd=ui)", and |
| 21 | 21 | * "[fossil http](/help?cmd=http)" |
| 22 | 22 | |
| 23 | -now all handle server-mode SSL/TLS encryption natively. It is now possible | |
| 24 | -to run a secure Fossil server without having to put Fossil behind an encrypting | |
| 25 | -web server or reverse proxy. Hence, it is now possible to stand up a complete | |
| 26 | -Fossil project website on an inexpensive VPS with no added software other than | |
| 27 | -Fossil itself and something like [certbot](https://certbot.eff.org) for | |
| 28 | -obtaining a CA-signed certificate. | |
| 29 | - | |
| 30 | -## Usage | |
| 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 | |
| 31 | 27 | |
| 32 | 28 | To put any of the Fossil server commands into SSL/TLS mode, simply |
| 33 | -add the "--cert" command-line option. | |
| 29 | +add the "`--cert`" command-line option: | |
| 34 | 30 | |
| 35 | 31 | fossil ui --cert unsafe-builtin |
| 36 | 32 | |
| 37 | -The --cert option is what tells Fossil to use TLS encryption. | |
| 38 | -Normally, the argument to --cert is the name of a file containing | |
| 39 | -the certificate (the "fullchain.pem" file) for the website. In this | |
| 40 | -example, the magic name "unsafe-builtin" is used, which causes Fossil | |
| 41 | -to use a self-signed cert rather than a real cert obtained from a | |
| 42 | -[Certificate Authority](https://en.wikipedia.org/wiki/Certificate_authority) | |
| 43 | -or "CA". As the name implies, this self-signed cert is not secure and | |
| 44 | -should only be used for testing. Your web-browser will complain bitterly | |
| 45 | -and will refuse to display the pages using the "unsafe-builtin" cert. | |
| 46 | -Firefox will allow you to click an "I know the risks" button and continue. | |
| 47 | -Other web browsers will stubornly refuse to display the page, under the theory | |
| 48 | -that weak encryption is worse than no encryption at all. Continue reading | |
| 49 | -to see how to solve this. | |
| 50 | - | |
| 51 | -## About Certs | |
| 52 | - | |
| 53 | -Certs are based on public-key or asymmetric cryptography. To create a cert, | |
| 54 | -you first create a new "key pair" consisting of a public key and a private key. | |
| 55 | -The public key can be freely shared with the world, but you must keep the | |
| 56 | -private key secret. If anyone gains access to your private key then he will be | |
| 57 | -able to impersonate you and break into your system. | |
| 58 | - | |
| 59 | -To obtain a cert, you send your public key and the name of the domain you | |
| 60 | -want to protect to a certificate authority. The CA then digitally signs | |
| 61 | -the combination of those two things using their own private key and sends | |
| 62 | -the signed combination back to you. The CA's digital signature of your | |
| 63 | -public key and domain name is the cert. | |
| 64 | - | |
| 65 | -SSL/TLS servers need two things in order to prove their identity to clients: | |
| 66 | - | |
| 67 | - 1. The cert that was signed by a CA | |
| 68 | - 2. The private key | |
| 69 | - | |
| 70 | -The SSL/TLS servers send the cert to each client, so that the client | |
| 71 | -can verify it. But the private key is kept strictly private and is never | |
| 72 | -shared with anyone. | |
| 73 | - | |
| 74 | -## How To Tell Fossil About Your Cert And Private Key | |
| 75 | - | |
| 76 | -If you do not have your own cert and private key, you can ask Fossil | |
| 33 | +Here, we are passing the magic name "unsafe-builtin" to cause Fossil to | |
| 34 | +use a [hard-coded self-signed cert][hcssc] rather than one obtained from | |
| 35 | +a recognized [Certificate Authority][CA], or "CA". | |
| 36 | + | |
| 37 | +As the name implies, this self-signed cert is _not secure_ and should | |
| 38 | +only be used for testing. Your web browser is likely to complain | |
| 39 | +bitterly about it and will refuse to display the pages using the | |
| 40 | +"unsafe-builtin" cert until you placate it. The complexity of the | |
| 41 | +ceremony demanded depends on how paranoid your browser’s creators have | |
| 42 | +decided to be. It may require as little as clicking a single big "I know | |
| 43 | +the risks" type of button, or it may require a sequence be several | |
| 44 | +clicks designed to discourage the “yes, yes, just let me do the thing” | |
| 45 | +crowd lest they run themselves into trouble by disregarding well-meant | |
| 46 | +warnings. | |
| 47 | + | |
| 48 | +Our purpose here is to show you an alternate path that will avoid the | |
| 49 | +issue entirely, not weigh in on which browser handles self-signed | |
| 50 | +certificates best. | |
| 51 | + | |
| 52 | +[CA]: https://en.wikipedia.org/wiki/Certificate_authority | |
| 53 | +[hcssc]: /info/c2a7b14c3f541edb96?ln=89-116 | |
| 54 | + | |
| 55 | +## <a id="about"></a>About Certs | |
| 56 | + | |
| 57 | +The X.509 certificate system used by browsers to secure TLS connections | |
| 58 | +is based on asymmetric public-key cryptography. The methods for | |
| 59 | +obtaining one vary widely, with a resulting tradeoff we may summarize as | |
| 60 | +trustworthiness versus convenience, the latter characteristic falling as | |
| 61 | +the former rises.(^No strict correlation exists. CAs have invented | |
| 62 | +highly inconvenient certification schemes that offer little additional | |
| 63 | +real-world trustworthiness. Extreme cases along this axis may be fairly | |
| 64 | +characterized as [security theater][st]. We focus in this document on | |
| 65 | +well-balanced trade-offs between decreasing convenience and useful | |
| 66 | +levels of trustworthiness gained thereby.) | |
| 67 | + | |
| 68 | +The self-signed method demonstrated above offers approximately zero | |
| 69 | +trustworthiness, though not zero _value_ since it does still provide | |
| 70 | +connection encryption. | |
| 71 | + | |
| 72 | +More trustworthy methods are necessarily less convenient. One such is to | |
| 73 | +send your public key and the name of the domain you want to protect to a | |
| 74 | +recognized CA, which then performs one or more tests to convince itself | |
| 75 | +that the requester is in control of that domain. If the CA’s tests all | |
| 76 | +pass, it produces an X.509 certificate bound to that domain, which | |
| 77 | +includes assorted other information under the CA’s digital signature | |
| 78 | +attesting to the validity of the document’s contents. The result is sent | |
| 79 | +back to the requester, which may then use it to transitively attest to | |
| 80 | +these tests’ success: presuming one cannot fake the type of signature | |
| 81 | +used, the document must have been signed by the trusted, recognized CA. | |
| 82 | + | |
| 83 | +There is one element of the assorted information included with a | |
| 84 | +certificate that is neither supplied by the requester nor rubber-stamped | |
| 85 | +on it in passing by the CA. It also generates a one-time key pair and | |
| 86 | +stores the public half in the certificate. The cryptosystem this keypair | |
| 87 | +is intended to work with varies both by the CA and by time, as older | |
| 88 | +systems become obsolete. Details aside, the CA then puts this matching | |
| 89 | +private half of the key in a separate file, often encrypted under a | |
| 90 | +separate cryptosystem for security. | |
| 91 | + | |
| 92 | +SSL/TLS servers need both resulting halves to make these attestations, | |
| 93 | +but they send only the public half to the client when establishing the | |
| 94 | +connection. The client then makes its own checks to determine whether it | |
| 95 | +trusts the attestations being made. | |
| 96 | + | |
| 97 | +A properly written and administered server never releases the private | |
| 98 | +key to anyone. Ideally, it goes directly from the CA to the requesting | |
| 99 | +server and never moves from there; then when it expires, the server | |
| 100 | +deletes it permanently. | |
| 101 | + | |
| 102 | +[st]: https://en.wikipedia.org/wiki/Security_theater | |
| 103 | + | |
| 104 | +## <a id="startup"></a>How To Tell Fossil About Your Cert And Private Key | |
| 105 | + | |
| 106 | +As we saw [above](#usage), | |
| 107 | +if you do not have your own cert and private key, you can ask Fossil | |
| 77 | 108 | to use "unsafe-builtin", which is a self-signed cert that is built into |
| 78 | -Fossil. This is wildly insecure, since the private key is not really private - | |
| 79 | -it is [in plain sight](/info/c2a7b14c3f541edb96?ln=89-116) in the Fossil | |
| 109 | +Fossil. This is wildly insecure, since the private key is not really private; | |
| 110 | +it is [in plain sight][hcssc] in the Fossil | |
| 80 | 111 | source tree for anybody to read. <b>Never add the private key that is |
| 81 | 112 | built into Fossil to your OS's trust store</b> as doing so will severely |
| 82 | -compromise your computer. The built-in cert is only useful for testing. | |
| 113 | +compromise your computer.[^ssattack] This built-in cert is only useful for testing. | |
| 83 | 114 | If you want actual security, you will need to come up with your own private |
| 84 | 115 | key and cert. |
| 85 | 116 | |
| 86 | 117 | Fossil wants to read certs and public keys in the |
| 87 | 118 | [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail). |
| @@ -98,66 +129,142 @@ | ||
| 98 | 129 | *base-64 encoding of the certificate* |
| 99 | 130 | -----END CERTIFICATE----- |
| 100 | 131 | |
| 101 | 132 | In both formats, text outside of the delimiters is ignored. That means |
| 102 | 133 | that if you have a PEM-formatted private key and a separate PEM-formatted |
| 103 | -certificate, you can concatenate the two into a single file and the | |
| 134 | +certificate, you can concatenate the two into a single file, and the | |
| 104 | 135 | individual components will still be easily accessible. |
| 105 | 136 | |
| 106 | -If you have a single file that holds both your private key and your | |
| 137 | +### <a id="cat"></a>Separate or Concatenated? | |
| 138 | + | |
| 139 | +Given a single concatenated file that holds both your private key and your | |
| 107 | 140 | cert, you can hand it off to the "[fossil server](/help?cmd=server)" |
| 108 | -command using the --cert option. Like this: | |
| 141 | +command using the `--cert` option, like this: | |
| 109 | 142 | |
| 110 | 143 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 111 | 144 | |
| 112 | 145 | The command above is sufficient to run a fully-encrypted web site for |
| 113 | 146 | the "myproject.fossil" Fossil repository. This command must be run as |
| 114 | 147 | root, since it wants to listen on TCP port 443, and only root processes are |
| 115 | 148 | allowed to do that. This is safe, however, since before reading any |
| 116 | -information off of the wire, Fossil will put itself inside a chroot jail | |
| 117 | -at /home/www and drop all root privileges. | |
| 118 | - | |
| 119 | -### Keeping The Cert And Private Key In Separate Files | |
| 120 | - | |
| 121 | -If you do not want to combine your cert and private key into a single | |
| 122 | -big PEM file, you can keep them separate using the --pkey option to | |
| 123 | -Fossil. | |
| 149 | +information off of the wire, Fossil will [put itself inside a chroot | |
| 150 | +jail](./chroot.md) at `/home/www` and drop all root privileges. | |
| 151 | + | |
| 152 | +This method of combining your cert and private key into a single big PEM | |
| 153 | +file carries risks, one of which is that the system administrator must | |
| 154 | +make both halves readable by the user running the Fossil server. Given | |
| 155 | +the chroot jail feature, a more secure scheme separates the halves so | |
| 156 | +that only root can read the private half, which then means that when | |
| 157 | +Fossil drops its root privileges, it becomes unable to access the | |
| 158 | +private key on disk. Fossil’s `server` feature includes the `--pkey` | |
| 159 | +option to allow for that use case: | |
| 124 | 160 | |
| 125 | 161 | fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil |
| 126 | 162 | |
| 127 | -## The ACME Protocol | |
| 163 | +[^ssattack]: ^How, you ask? Because the keys are known, they can be used | |
| 164 | + to provide signed certificates for **any** other domain. One foolish | |
| 165 | + enough to tell their OS’s TLS mechanisms to trust the signing | |
| 166 | + certificate is implicitly handing over all TLS encryption controls | |
| 167 | + to any attacker that knows they did this. Don’t do it. | |
| 168 | + | |
| 169 | +### <a id="chain"></a>Chains and Links | |
| 170 | + | |
| 171 | +The file name “`fullchain.pem`” used above is a reference to a term of | |
| 172 | +art within this world of TLS protocols and their associated X.509 | |
| 173 | +certificates. Within the simplistic scheme originally envisioned by the | |
| 174 | +creators of SSL — the predecessor to TLS — we were all expected to agree | |
| 175 | +on a single set of CA root authorities, and we would all agree to get | |
| 176 | +our certificates from one of them. The real world is more complicated: | |
| 177 | + | |
| 178 | +* The closest we have to universal acceptance of CAs is via the | |
| 179 | + [CA/Browser Forum][CAB], and even within its select membership there | |
| 180 | + is continual argument over which roots are trustworthy. (Hashing | |
| 181 | + that out is arguably this group’s key purpose.) | |
| 182 | + | |
| 183 | +* CAB’s decision regarding trustworthiness may not match that of any | |
| 184 | + given system’s administrator. There are solid, defensible reasons to | |
| 185 | + prune back the stock CA root set included with your browser, then to | |
| 186 | + augment it with ones CAB _doesn’t_ trust. | |
| 187 | + | |
| 188 | +* TLS isn’t limited to use between web browsers and public Internet | |
| 189 | + sites. Several common use cases preclude use of the process CAB | |
| 190 | + envisions, with servers able to contact Internet-based CA roots as | |
| 191 | + part of proving their identity. Different use cases demand different | |
| 192 | + CA root authority stores. | |
| 193 | + | |
| 194 | + The most common of these divergent cases are servers behind strict | |
| 195 | + firewalls and edge devices that never interact with the public | |
| 196 | + Internet. This class ranges from cheap home IoT devices to the | |
| 197 | + internal equipment managed by IT for a massive global corporation. | |
| 198 | + | |
| 199 | +Your private Fossil server is liable to fall into that last category. | |
| 200 | +This may then require that you generate a more complicated “chain” of | |
| 201 | +certificates for Fossil to use here, without which the client may not be | |
| 202 | +able to get back to a CA root it trusts. This is true regardless of | |
| 203 | +whether that client is another copy of Fossil or a web browser | |
| 204 | +traversing Fossil’s web UI, though that fact complicates matters by | |
| 205 | +allowing for multiple classes of client, each of which may have their | |
| 206 | +own rules for modifying the stock certificate scheme. | |
| 207 | + | |
| 208 | +This is distressingly common, in fact: Fossil links to OpenSSL to | |
| 209 | +provide its TLS support, but there is a good chance that your browser | |
| 210 | +uses another TLS implementation entirely. They may or may not agree on a | |
| 211 | +single CA root store. | |
| 212 | + | |
| 213 | +How you accommodate all this complexity varies by the CA and other | |
| 214 | +details. As but one example, Firefox’s “View Certificate” feature offers | |
| 215 | +_two_ ways to download a given web site’s certificate: the cert alone or | |
| 216 | +the “chain” leading back to the root. Depending on the use case, the | |
| 217 | +standalone certificate might suffice, or you might need some type of | |
| 218 | +cert chain. Complicating this is that the last link in the chain may be | |
| 219 | +left off when it is for a mutually trusted CA root, implicitly | |
| 220 | +completing the chain. | |
| 221 | + | |
| 222 | +[CAB]: https://en.wikipedia.org/wiki/CA/Browser_Forum | |
| 223 | + | |
| 224 | +## <a id="acme"></a>The ACME Protocol | |
| 225 | + | |
| 226 | +The [ACME Protocol][2] simplifies all this by automating the process of | |
| 227 | +proving to a recognized public CA that you are in control of a given | |
| 228 | +website. Without this proof, no valid CA will issue a cert for that | |
| 229 | +domain, as that allows fraudulent impersonation. | |
| 128 | 230 | |
| 129 | -The [ACME Protocol][2] is used to prove to a CA that you control a | |
| 130 | -website. CAs require proof that you control a domain before they | |
| 131 | -will issue a cert for that domain. The usual means of dealing | |
| 132 | -with ACME is to run the separate [certbot](https://certbot.eff.org) tool. | |
| 231 | +The primary implementation of ACME is [certbot], a product of the Let’s | |
| 232 | +Encrypt organization. | |
| 133 | 233 | Here is, in a nutshell, what certbot will do to obtain your cert: |
| 134 | 234 | |
| 135 | - 1. Certbot sends your "signing request" (the document that contains | |
| 235 | + 1. It sends your "signing request" (the document that contains | |
| 136 | 236 | your public key and your domain name) to the CA. |
| 137 | 237 | |
| 138 | - 2. After receiving the signing request, the CA needs to verify that you | |
| 139 | - control the domain of the cert. To do this (or, one common | |
| 140 | - way of doing this, at least) the CA sends a secret token back to | |
| 141 | - certbot through a secure backchannel, and instructs certbot to make | |
| 142 | - that token accessible on the (unencrypted, ordinary "http:") web site | |
| 143 | - for the domain in a particular file under the ".well-known" subdirectory. | |
| 144 | - | |
| 145 | - 3. Certbot puts the token where the CA requested it, then notifies the | |
| 146 | - CA that it is there. | |
| 147 | - | |
| 148 | - 4. The CA accesses the token to confirm that you do indeed control the | |
| 149 | - website. It then creates the cert and sends it back to certbot. | |
| 150 | - | |
| 151 | - 5. Certbot stores your cert and deletes the ".well-known" token. | |
| 238 | + 2. After receiving the signing request, the CA needs to verify that | |
| 239 | + you control the domain of the cert. One of several methods certbot has | |
| 240 | + for accomplishing this is to create a secret token and place it at | |
| 241 | + a well-known location, then tell the CA about it over ACME. | |
| 242 | + | |
| 243 | + 3. The CA then tries pulling that token, which if successful proves | |
| 244 | + that the requester is able to create arbitrary data on the server, | |
| 245 | + implicitly proving control over that server. This must be done | |
| 246 | + over the unencrypted HTTP protocol since TLS isn’t working yet. | |
| 247 | + | |
| 248 | + 4. If satisfied by this proof of control, the CA then creates the | |
| 249 | + keypair described above and bakes the public half into the | |
| 250 | + certificate it signs. It then sends this and the private half of | |
| 251 | + the key back to certbot. | |
| 252 | + | |
| 253 | + 5. Certbot stores these halves separately for the reasons sketched | |
| 254 | + out above. | |
| 255 | + | |
| 256 | + 6. It then deletes the secret one-time-use token it used to prove | |
| 257 | + domain control. ACME’s design precludes replay attacks. | |
| 152 | 258 | |
| 153 | 259 | In order for all of this to happen, certbot needs to be able to create |
| 154 | -a subdirectory named ".well-known", within a directory you specify, and | |
| 260 | +a subdirectory named ".well-known", within a directory you specify, | |
| 155 | 261 | then populate that subdirectory with a token file of some kind. To support |
| 156 | 262 | this, the "[fossil server](/help?cmd=server)" and |
| 157 | 263 | "[fossil http](/help?cmd=http)" commands have the --acme option. |
| 158 | -When the --acme option is specified and Fossil sees a URL where the path | |
| 264 | + | |
| 265 | +When specified, Fossil sees a URL where the path | |
| 159 | 266 | begins with ".well-known", then instead of doing its normal processing, it |
| 160 | 267 | looks for a file with that pathname and returns it to the client. If |
| 161 | 268 | the "server" or "http" command is referencing a single Fossil repository, |
| 162 | 269 | then the ".well-known" sub-directory should be in the same directory as |
| 163 | 270 | the repository file. If the "server" or "http" command are run against |
| @@ -172,9 +279,11 @@ | ||
| 172 | 279 | Then you create your public/private key pair and run certbot, giving it |
| 173 | 280 | a --webroot of /home/www. Certbot will create the sub-directory |
| 174 | 281 | named "/home/www/.well-known" and put token files there, which the CA |
| 175 | 282 | will verify. Then certbot will store your new cert in a particular file. |
| 176 | 283 | |
| 177 | -Once certbot has obtained your cert, then you can concatenate that | |
| 178 | -cert with your private key and run Fossil in SSL/TLS mode as shown above. | |
| 284 | +Once certbot has obtained your cert, you may either pass the two halves | |
| 285 | +to Fossil separately using the `--pkey` and `--cert` options described | |
| 286 | +above, or you may concatenate them and pass that via `--cert` alone. | |
| 179 | 287 | |
| 180 | 288 | [2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment |
| 289 | +[certbot]: https://certbot.eff.org | |
| 181 | 290 |
| --- www/ssl-server.md | |
| +++ www/ssl-server.md | |
| @@ -12,76 +12,107 @@ | |
| 12 | |
| 13 | [0]: ./ssl.wiki |
| 14 | [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13 |
| 15 | |
| 16 | Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13), |
| 17 | this has been fixed. Commands like |
| 18 | |
| 19 | * "[fossil server](/help?cmd=server)" |
| 20 | * "[fossil ui](/help?cmd=ui)", and |
| 21 | * "[fossil http](/help?cmd=http)" |
| 22 | |
| 23 | now all handle server-mode SSL/TLS encryption natively. It is now possible |
| 24 | to run a secure Fossil server without having to put Fossil behind an encrypting |
| 25 | web server or reverse proxy. Hence, it is now possible to stand up a complete |
| 26 | Fossil project website on an inexpensive VPS with no added software other than |
| 27 | Fossil itself and something like [certbot](https://certbot.eff.org) for |
| 28 | obtaining a CA-signed certificate. |
| 29 | |
| 30 | ## Usage |
| 31 | |
| 32 | To put any of the Fossil server commands into SSL/TLS mode, simply |
| 33 | add the "--cert" command-line option. |
| 34 | |
| 35 | fossil ui --cert unsafe-builtin |
| 36 | |
| 37 | The --cert option is what tells Fossil to use TLS encryption. |
| 38 | Normally, the argument to --cert is the name of a file containing |
| 39 | the certificate (the "fullchain.pem" file) for the website. In this |
| 40 | example, the magic name "unsafe-builtin" is used, which causes Fossil |
| 41 | to use a self-signed cert rather than a real cert obtained from a |
| 42 | [Certificate Authority](https://en.wikipedia.org/wiki/Certificate_authority) |
| 43 | or "CA". As the name implies, this self-signed cert is not secure and |
| 44 | should only be used for testing. Your web-browser will complain bitterly |
| 45 | and will refuse to display the pages using the "unsafe-builtin" cert. |
| 46 | Firefox will allow you to click an "I know the risks" button and continue. |
| 47 | Other web browsers will stubornly refuse to display the page, under the theory |
| 48 | that weak encryption is worse than no encryption at all. Continue reading |
| 49 | to see how to solve this. |
| 50 | |
| 51 | ## About Certs |
| 52 | |
| 53 | Certs are based on public-key or asymmetric cryptography. To create a cert, |
| 54 | you first create a new "key pair" consisting of a public key and a private key. |
| 55 | The public key can be freely shared with the world, but you must keep the |
| 56 | private key secret. If anyone gains access to your private key then he will be |
| 57 | able to impersonate you and break into your system. |
| 58 | |
| 59 | To obtain a cert, you send your public key and the name of the domain you |
| 60 | want to protect to a certificate authority. The CA then digitally signs |
| 61 | the combination of those two things using their own private key and sends |
| 62 | the signed combination back to you. The CA's digital signature of your |
| 63 | public key and domain name is the cert. |
| 64 | |
| 65 | SSL/TLS servers need two things in order to prove their identity to clients: |
| 66 | |
| 67 | 1. The cert that was signed by a CA |
| 68 | 2. The private key |
| 69 | |
| 70 | The SSL/TLS servers send the cert to each client, so that the client |
| 71 | can verify it. But the private key is kept strictly private and is never |
| 72 | shared with anyone. |
| 73 | |
| 74 | ## How To Tell Fossil About Your Cert And Private Key |
| 75 | |
| 76 | If you do not have your own cert and private key, you can ask Fossil |
| 77 | to use "unsafe-builtin", which is a self-signed cert that is built into |
| 78 | Fossil. This is wildly insecure, since the private key is not really private - |
| 79 | it is [in plain sight](/info/c2a7b14c3f541edb96?ln=89-116) in the Fossil |
| 80 | source tree for anybody to read. <b>Never add the private key that is |
| 81 | built into Fossil to your OS's trust store</b> as doing so will severely |
| 82 | compromise your computer. The built-in cert is only useful for testing. |
| 83 | If you want actual security, you will need to come up with your own private |
| 84 | key and cert. |
| 85 | |
| 86 | Fossil wants to read certs and public keys in the |
| 87 | [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail). |
| @@ -98,66 +129,142 @@ | |
| 98 | *base-64 encoding of the certificate* |
| 99 | -----END CERTIFICATE----- |
| 100 | |
| 101 | In both formats, text outside of the delimiters is ignored. That means |
| 102 | that if you have a PEM-formatted private key and a separate PEM-formatted |
| 103 | certificate, you can concatenate the two into a single file and the |
| 104 | individual components will still be easily accessible. |
| 105 | |
| 106 | If you have a single file that holds both your private key and your |
| 107 | cert, you can hand it off to the "[fossil server](/help?cmd=server)" |
| 108 | command using the --cert option. Like this: |
| 109 | |
| 110 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 111 | |
| 112 | The command above is sufficient to run a fully-encrypted web site for |
| 113 | the "myproject.fossil" Fossil repository. This command must be run as |
| 114 | root, since it wants to listen on TCP port 443, and only root processes are |
| 115 | allowed to do that. This is safe, however, since before reading any |
| 116 | information off of the wire, Fossil will put itself inside a chroot jail |
| 117 | at /home/www and drop all root privileges. |
| 118 | |
| 119 | ### Keeping The Cert And Private Key In Separate Files |
| 120 | |
| 121 | If you do not want to combine your cert and private key into a single |
| 122 | big PEM file, you can keep them separate using the --pkey option to |
| 123 | Fossil. |
| 124 | |
| 125 | fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil |
| 126 | |
| 127 | ## The ACME Protocol |
| 128 | |
| 129 | The [ACME Protocol][2] is used to prove to a CA that you control a |
| 130 | website. CAs require proof that you control a domain before they |
| 131 | will issue a cert for that domain. The usual means of dealing |
| 132 | with ACME is to run the separate [certbot](https://certbot.eff.org) tool. |
| 133 | Here is, in a nutshell, what certbot will do to obtain your cert: |
| 134 | |
| 135 | 1. Certbot sends your "signing request" (the document that contains |
| 136 | your public key and your domain name) to the CA. |
| 137 | |
| 138 | 2. After receiving the signing request, the CA needs to verify that you |
| 139 | control the domain of the cert. To do this (or, one common |
| 140 | way of doing this, at least) the CA sends a secret token back to |
| 141 | certbot through a secure backchannel, and instructs certbot to make |
| 142 | that token accessible on the (unencrypted, ordinary "http:") web site |
| 143 | for the domain in a particular file under the ".well-known" subdirectory. |
| 144 | |
| 145 | 3. Certbot puts the token where the CA requested it, then notifies the |
| 146 | CA that it is there. |
| 147 | |
| 148 | 4. The CA accesses the token to confirm that you do indeed control the |
| 149 | website. It then creates the cert and sends it back to certbot. |
| 150 | |
| 151 | 5. Certbot stores your cert and deletes the ".well-known" token. |
| 152 | |
| 153 | In order for all of this to happen, certbot needs to be able to create |
| 154 | a subdirectory named ".well-known", within a directory you specify, and |
| 155 | then populate that subdirectory with a token file of some kind. To support |
| 156 | this, the "[fossil server](/help?cmd=server)" and |
| 157 | "[fossil http](/help?cmd=http)" commands have the --acme option. |
| 158 | When the --acme option is specified and Fossil sees a URL where the path |
| 159 | begins with ".well-known", then instead of doing its normal processing, it |
| 160 | looks for a file with that pathname and returns it to the client. If |
| 161 | the "server" or "http" command is referencing a single Fossil repository, |
| 162 | then the ".well-known" sub-directory should be in the same directory as |
| 163 | the repository file. If the "server" or "http" command are run against |
| @@ -172,9 +279,11 @@ | |
| 172 | Then you create your public/private key pair and run certbot, giving it |
| 173 | a --webroot of /home/www. Certbot will create the sub-directory |
| 174 | named "/home/www/.well-known" and put token files there, which the CA |
| 175 | will verify. Then certbot will store your new cert in a particular file. |
| 176 | |
| 177 | Once certbot has obtained your cert, then you can concatenate that |
| 178 | cert with your private key and run Fossil in SSL/TLS mode as shown above. |
| 179 | |
| 180 | [2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment |
| 181 |
| --- www/ssl-server.md | |
| +++ www/ssl-server.md | |
| @@ -12,76 +12,107 @@ | |
| 12 | |
| 13 | [0]: ./ssl.wiki |
| 14 | [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13 |
| 15 | |
| 16 | Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13), |
| 17 | Fossil servers are now able to converse directly over TLS. Commands like |
| 18 | |
| 19 | * "[fossil server](/help?cmd=server)" |
| 20 | * "[fossil ui](/help?cmd=ui)", and |
| 21 | * "[fossil http](/help?cmd=http)" |
| 22 | |
| 23 | may now handle the encryption natively when suitably configured, without |
| 24 | requiring a third-party proxy layer. |
| 25 | |
| 26 | ## <a id="usage"></a>Usage |
| 27 | |
| 28 | To put any of the Fossil server commands into SSL/TLS mode, simply |
| 29 | add the "`--cert`" command-line option: |
| 30 | |
| 31 | fossil ui --cert unsafe-builtin |
| 32 | |
| 33 | Here, we are passing the magic name "unsafe-builtin" to cause Fossil to |
| 34 | use a [hard-coded self-signed cert][hcssc] rather than one obtained from |
| 35 | a recognized [Certificate Authority][CA], or "CA". |
| 36 | |
| 37 | As the name implies, this self-signed cert is _not secure_ and should |
| 38 | only be used for testing. Your web browser is likely to complain |
| 39 | bitterly about it and will refuse to display the pages using the |
| 40 | "unsafe-builtin" cert until you placate it. The complexity of the |
| 41 | ceremony demanded depends on how paranoid your browser’s creators have |
| 42 | decided to be. It may require as little as clicking a single big "I know |
| 43 | the risks" type of button, or it may require a sequence be several |
| 44 | clicks designed to discourage the “yes, yes, just let me do the thing” |
| 45 | crowd lest they run themselves into trouble by disregarding well-meant |
| 46 | warnings. |
| 47 | |
| 48 | Our purpose here is to show you an alternate path that will avoid the |
| 49 | issue entirely, not weigh in on which browser handles self-signed |
| 50 | certificates best. |
| 51 | |
| 52 | [CA]: https://en.wikipedia.org/wiki/Certificate_authority |
| 53 | [hcssc]: /info/c2a7b14c3f541edb96?ln=89-116 |
| 54 | |
| 55 | ## <a id="about"></a>About Certs |
| 56 | |
| 57 | The X.509 certificate system used by browsers to secure TLS connections |
| 58 | is based on asymmetric public-key cryptography. The methods for |
| 59 | obtaining one vary widely, with a resulting tradeoff we may summarize as |
| 60 | trustworthiness versus convenience, the latter characteristic falling as |
| 61 | the former rises.(^No strict correlation exists. CAs have invented |
| 62 | highly inconvenient certification schemes that offer little additional |
| 63 | real-world trustworthiness. Extreme cases along this axis may be fairly |
| 64 | characterized as [security theater][st]. We focus in this document on |
| 65 | well-balanced trade-offs between decreasing convenience and useful |
| 66 | levels of trustworthiness gained thereby.) |
| 67 | |
| 68 | The self-signed method demonstrated above offers approximately zero |
| 69 | trustworthiness, though not zero _value_ since it does still provide |
| 70 | connection encryption. |
| 71 | |
| 72 | More trustworthy methods are necessarily less convenient. One such is to |
| 73 | send your public key and the name of the domain you want to protect to a |
| 74 | recognized CA, which then performs one or more tests to convince itself |
| 75 | that the requester is in control of that domain. If the CA’s tests all |
| 76 | pass, it produces an X.509 certificate bound to that domain, which |
| 77 | includes assorted other information under the CA’s digital signature |
| 78 | attesting to the validity of the document’s contents. The result is sent |
| 79 | back to the requester, which may then use it to transitively attest to |
| 80 | these tests’ success: presuming one cannot fake the type of signature |
| 81 | used, the document must have been signed by the trusted, recognized CA. |
| 82 | |
| 83 | There is one element of the assorted information included with a |
| 84 | certificate that is neither supplied by the requester nor rubber-stamped |
| 85 | on it in passing by the CA. It also generates a one-time key pair and |
| 86 | stores the public half in the certificate. The cryptosystem this keypair |
| 87 | is intended to work with varies both by the CA and by time, as older |
| 88 | systems become obsolete. Details aside, the CA then puts this matching |
| 89 | private half of the key in a separate file, often encrypted under a |
| 90 | separate cryptosystem for security. |
| 91 | |
| 92 | SSL/TLS servers need both resulting halves to make these attestations, |
| 93 | but they send only the public half to the client when establishing the |
| 94 | connection. The client then makes its own checks to determine whether it |
| 95 | trusts the attestations being made. |
| 96 | |
| 97 | A properly written and administered server never releases the private |
| 98 | key to anyone. Ideally, it goes directly from the CA to the requesting |
| 99 | server and never moves from there; then when it expires, the server |
| 100 | deletes it permanently. |
| 101 | |
| 102 | [st]: https://en.wikipedia.org/wiki/Security_theater |
| 103 | |
| 104 | ## <a id="startup"></a>How To Tell Fossil About Your Cert And Private Key |
| 105 | |
| 106 | As we saw [above](#usage), |
| 107 | if you do not have your own cert and private key, you can ask Fossil |
| 108 | to use "unsafe-builtin", which is a self-signed cert that is built into |
| 109 | Fossil. This is wildly insecure, since the private key is not really private; |
| 110 | it is [in plain sight][hcssc] in the Fossil |
| 111 | source tree for anybody to read. <b>Never add the private key that is |
| 112 | built into Fossil to your OS's trust store</b> as doing so will severely |
| 113 | compromise your computer.[^ssattack] This built-in cert is only useful for testing. |
| 114 | If you want actual security, you will need to come up with your own private |
| 115 | key and cert. |
| 116 | |
| 117 | Fossil wants to read certs and public keys in the |
| 118 | [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail). |
| @@ -98,66 +129,142 @@ | |
| 129 | *base-64 encoding of the certificate* |
| 130 | -----END CERTIFICATE----- |
| 131 | |
| 132 | In both formats, text outside of the delimiters is ignored. That means |
| 133 | that if you have a PEM-formatted private key and a separate PEM-formatted |
| 134 | certificate, you can concatenate the two into a single file, and the |
| 135 | individual components will still be easily accessible. |
| 136 | |
| 137 | ### <a id="cat"></a>Separate or Concatenated? |
| 138 | |
| 139 | Given a single concatenated file that holds both your private key and your |
| 140 | cert, you can hand it off to the "[fossil server](/help?cmd=server)" |
| 141 | command using the `--cert` option, like this: |
| 142 | |
| 143 | fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil |
| 144 | |
| 145 | The command above is sufficient to run a fully-encrypted web site for |
| 146 | the "myproject.fossil" Fossil repository. This command must be run as |
| 147 | root, since it wants to listen on TCP port 443, and only root processes are |
| 148 | allowed to do that. This is safe, however, since before reading any |
| 149 | information off of the wire, Fossil will [put itself inside a chroot |
| 150 | jail](./chroot.md) at `/home/www` and drop all root privileges. |
| 151 | |
| 152 | This method of combining your cert and private key into a single big PEM |
| 153 | file carries risks, one of which is that the system administrator must |
| 154 | make both halves readable by the user running the Fossil server. Given |
| 155 | the chroot jail feature, a more secure scheme separates the halves so |
| 156 | that only root can read the private half, which then means that when |
| 157 | Fossil drops its root privileges, it becomes unable to access the |
| 158 | private key on disk. Fossil’s `server` feature includes the `--pkey` |
| 159 | option to allow for that use case: |
| 160 | |
| 161 | fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil |
| 162 | |
| 163 | [^ssattack]: ^How, you ask? Because the keys are known, they can be used |
| 164 | to provide signed certificates for **any** other domain. One foolish |
| 165 | enough to tell their OS’s TLS mechanisms to trust the signing |
| 166 | certificate is implicitly handing over all TLS encryption controls |
| 167 | to any attacker that knows they did this. Don’t do it. |
| 168 | |
| 169 | ### <a id="chain"></a>Chains and Links |
| 170 | |
| 171 | The file name “`fullchain.pem`” used above is a reference to a term of |
| 172 | art within this world of TLS protocols and their associated X.509 |
| 173 | certificates. Within the simplistic scheme originally envisioned by the |
| 174 | creators of SSL — the predecessor to TLS — we were all expected to agree |
| 175 | on a single set of CA root authorities, and we would all agree to get |
| 176 | our certificates from one of them. The real world is more complicated: |
| 177 | |
| 178 | * The closest we have to universal acceptance of CAs is via the |
| 179 | [CA/Browser Forum][CAB], and even within its select membership there |
| 180 | is continual argument over which roots are trustworthy. (Hashing |
| 181 | that out is arguably this group’s key purpose.) |
| 182 | |
| 183 | * CAB’s decision regarding trustworthiness may not match that of any |
| 184 | given system’s administrator. There are solid, defensible reasons to |
| 185 | prune back the stock CA root set included with your browser, then to |
| 186 | augment it with ones CAB _doesn’t_ trust. |
| 187 | |
| 188 | * TLS isn’t limited to use between web browsers and public Internet |
| 189 | sites. Several common use cases preclude use of the process CAB |
| 190 | envisions, with servers able to contact Internet-based CA roots as |
| 191 | part of proving their identity. Different use cases demand different |
| 192 | CA root authority stores. |
| 193 | |
| 194 | The most common of these divergent cases are servers behind strict |
| 195 | firewalls and edge devices that never interact with the public |
| 196 | Internet. This class ranges from cheap home IoT devices to the |
| 197 | internal equipment managed by IT for a massive global corporation. |
| 198 | |
| 199 | Your private Fossil server is liable to fall into that last category. |
| 200 | This may then require that you generate a more complicated “chain” of |
| 201 | certificates for Fossil to use here, without which the client may not be |
| 202 | able to get back to a CA root it trusts. This is true regardless of |
| 203 | whether that client is another copy of Fossil or a web browser |
| 204 | traversing Fossil’s web UI, though that fact complicates matters by |
| 205 | allowing for multiple classes of client, each of which may have their |
| 206 | own rules for modifying the stock certificate scheme. |
| 207 | |
| 208 | This is distressingly common, in fact: Fossil links to OpenSSL to |
| 209 | provide its TLS support, but there is a good chance that your browser |
| 210 | uses another TLS implementation entirely. They may or may not agree on a |
| 211 | single CA root store. |
| 212 | |
| 213 | How you accommodate all this complexity varies by the CA and other |
| 214 | details. As but one example, Firefox’s “View Certificate” feature offers |
| 215 | _two_ ways to download a given web site’s certificate: the cert alone or |
| 216 | the “chain” leading back to the root. Depending on the use case, the |
| 217 | standalone certificate might suffice, or you might need some type of |
| 218 | cert chain. Complicating this is that the last link in the chain may be |
| 219 | left off when it is for a mutually trusted CA root, implicitly |
| 220 | completing the chain. |
| 221 | |
| 222 | [CAB]: https://en.wikipedia.org/wiki/CA/Browser_Forum |
| 223 | |
| 224 | ## <a id="acme"></a>The ACME Protocol |
| 225 | |
| 226 | The [ACME Protocol][2] simplifies all this by automating the process of |
| 227 | proving to a recognized public CA that you are in control of a given |
| 228 | website. Without this proof, no valid CA will issue a cert for that |
| 229 | domain, as that allows fraudulent impersonation. |
| 230 | |
| 231 | The primary implementation of ACME is [certbot], a product of the Let’s |
| 232 | Encrypt organization. |
| 233 | Here is, in a nutshell, what certbot will do to obtain your cert: |
| 234 | |
| 235 | 1. It sends your "signing request" (the document that contains |
| 236 | your public key and your domain name) to the CA. |
| 237 | |
| 238 | 2. After receiving the signing request, the CA needs to verify that |
| 239 | you control the domain of the cert. One of several methods certbot has |
| 240 | for accomplishing this is to create a secret token and place it at |
| 241 | a well-known location, then tell the CA about it over ACME. |
| 242 | |
| 243 | 3. The CA then tries pulling that token, which if successful proves |
| 244 | that the requester is able to create arbitrary data on the server, |
| 245 | implicitly proving control over that server. This must be done |
| 246 | over the unencrypted HTTP protocol since TLS isn’t working yet. |
| 247 | |
| 248 | 4. If satisfied by this proof of control, the CA then creates the |
| 249 | keypair described above and bakes the public half into the |
| 250 | certificate it signs. It then sends this and the private half of |
| 251 | the key back to certbot. |
| 252 | |
| 253 | 5. Certbot stores these halves separately for the reasons sketched |
| 254 | out above. |
| 255 | |
| 256 | 6. It then deletes the secret one-time-use token it used to prove |
| 257 | domain control. ACME’s design precludes replay attacks. |
| 258 | |
| 259 | In order for all of this to happen, certbot needs to be able to create |
| 260 | a subdirectory named ".well-known", within a directory you specify, |
| 261 | then populate that subdirectory with a token file of some kind. To support |
| 262 | this, the "[fossil server](/help?cmd=server)" and |
| 263 | "[fossil http](/help?cmd=http)" commands have the --acme option. |
| 264 | |
| 265 | When specified, Fossil sees a URL where the path |
| 266 | begins with ".well-known", then instead of doing its normal processing, it |
| 267 | looks for a file with that pathname and returns it to the client. If |
| 268 | the "server" or "http" command is referencing a single Fossil repository, |
| 269 | then the ".well-known" sub-directory should be in the same directory as |
| 270 | the repository file. If the "server" or "http" command are run against |
| @@ -172,9 +279,11 @@ | |
| 279 | Then you create your public/private key pair and run certbot, giving it |
| 280 | a --webroot of /home/www. Certbot will create the sub-directory |
| 281 | named "/home/www/.well-known" and put token files there, which the CA |
| 282 | will verify. Then certbot will store your new cert in a particular file. |
| 283 | |
| 284 | Once certbot has obtained your cert, you may either pass the two halves |
| 285 | to Fossil separately using the `--pkey` and `--cert` options described |
| 286 | above, or you may concatenate them and pass that via `--cert` alone. |
| 287 | |
| 288 | [2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment |
| 289 | [certbot]: https://certbot.eff.org |
| 290 |
+8
| --- www/sync.wiki | ||
| +++ www/sync.wiki | ||
| @@ -789,10 +789,18 @@ | ||
| 789 | 789 | a successful commit. This instructs the server to release |
| 790 | 790 | any lock on any check-in previously held by that client. |
| 791 | 791 | The ci-unlock pragma helps to avoid false-positive lock warnings |
| 792 | 792 | that might arise if a check-in is aborted and then restarted |
| 793 | 793 | on a branch. |
| 794 | + | |
| 795 | +<li><b>req-clusters</b> A client sends the "req-clusters" pragma | |
| 796 | +to the server to ask the server to reply with "igot" cards for | |
| 797 | +every [./fileformat.wiki#cluster|cluster artifact] that it holds. The | |
| 798 | +client typically does this when it thinks that it might be attempting | |
| 799 | +to pull a long chain of cluster artifacts. Sending the artifacts | |
| 800 | +all at once can dramatically reduce the number of round trip | |
| 801 | +messages needed to complete the synchronization. | |
| 794 | 802 | </ol> |
| 795 | 803 | |
| 796 | 804 | <h3 id="comment">3.12 Comment Cards</h3> |
| 797 | 805 | |
| 798 | 806 | Any card that begins with "#" (ASCII 0x23) is a comment card and |
| 799 | 807 |
| --- www/sync.wiki | |
| +++ www/sync.wiki | |
| @@ -789,10 +789,18 @@ | |
| 789 | a successful commit. This instructs the server to release |
| 790 | any lock on any check-in previously held by that client. |
| 791 | The ci-unlock pragma helps to avoid false-positive lock warnings |
| 792 | that might arise if a check-in is aborted and then restarted |
| 793 | on a branch. |
| 794 | </ol> |
| 795 | |
| 796 | <h3 id="comment">3.12 Comment Cards</h3> |
| 797 | |
| 798 | Any card that begins with "#" (ASCII 0x23) is a comment card and |
| 799 |
| --- www/sync.wiki | |
| +++ www/sync.wiki | |
| @@ -789,10 +789,18 @@ | |
| 789 | a successful commit. This instructs the server to release |
| 790 | any lock on any check-in previously held by that client. |
| 791 | The ci-unlock pragma helps to avoid false-positive lock warnings |
| 792 | that might arise if a check-in is aborted and then restarted |
| 793 | on a branch. |
| 794 | |
| 795 | <li><b>req-clusters</b> A client sends the "req-clusters" pragma |
| 796 | to the server to ask the server to reply with "igot" cards for |
| 797 | every [./fileformat.wiki#cluster|cluster artifact] that it holds. The |
| 798 | client typically does this when it thinks that it might be attempting |
| 799 | to pull a long chain of cluster artifacts. Sending the artifacts |
| 800 | all at once can dramatically reduce the number of round trip |
| 801 | messages needed to complete the synchronization. |
| 802 | </ol> |
| 803 | |
| 804 | <h3 id="comment">3.12 Comment Cards</h3> |
| 805 | |
| 806 | Any card that begins with "#" (ASCII 0x23) is a comment card and |
| 807 |