Fossil SCM
Merge from trunk.
Commit
fc0b5efb8dcd6245b4fd58d45587c1f9e1adb3c2
Parent
cab404b2f499117…
41 files changed
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+5
+1
-1
+139
-88
+424
-289
+2
-2
+2
-1
+1
+3
+8
+3
+1016
-102
+82
-8
+109
-116
+2
-2
+39
-2
+57
+1
-1
+124
-48
+72
+1
-1
+1
-1
+1
+1
+20
+1
-1
+7
-6
+1
-1
+18
-10
+3
-6
+10
-13
+1
-1
~
auto.def
~
skins/black_and_white/header.txt
~
skins/default/header.txt
~
skins/eagle/css.txt
~
skins/eagle/header.txt
~
skins/enhanced1/header.txt
~
skins/khaki/header.txt
~
skins/plain_gray/header.txt
~
skins/rounded1/header.txt
~
src/allrepo.c
~
src/clone.c
~
src/db.c
~
src/doc.c
~
src/glob.c
~
src/main.mk
~
src/makemake.tcl
~
src/manifest.c
~
src/rebuild.c
~
src/report.c
~
src/search.c
~
src/setup.c
~
src/sqlite3.c
~
src/sqlite3.h
~
src/style.c
~
src/tkt.c
~
src/url.c
~
src/wiki.c
~
src/wikiformat.c
~
win/Makefile.PellesCGMake
~
win/Makefile.dmc
~
win/Makefile.mingw
~
win/Makefile.msc
~
www/changes.wiki
~
www/index.wiki
~
www/makefile.wiki
~
www/mkdownload.tcl
~
www/mkindex.tcl
~
www/permutedindex.html
+
www/permutedindex.html
-
www/permutedindex.wiki
~
www/quickstart.wiki
M
auto.def
+1
-1
| --- auto.def | ||
| +++ auto.def | ||
| @@ -68,11 +68,11 @@ | ||
| 68 | 68 | |
| 69 | 69 | find_internal_sqlite |
| 70 | 70 | } |
| 71 | 71 | |
| 72 | 72 | if {[string match *-solaris* [get-define host]]} { |
| 73 | - define-append EXTRA_CFLAGS -D_XOPEN_SOURCE=500 | |
| 73 | + define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__} | |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | if {[opt-bool fossil-debug]} { |
| 77 | 77 | define-append EXTRA_CFLAGS -DFOSSIL_DEBUG |
| 78 | 78 | msg-result "Debugging support enabled" |
| 79 | 79 |
| --- auto.def | |
| +++ auto.def | |
| @@ -68,11 +68,11 @@ | |
| 68 | |
| 69 | find_internal_sqlite |
| 70 | } |
| 71 | |
| 72 | if {[string match *-solaris* [get-define host]]} { |
| 73 | define-append EXTRA_CFLAGS -D_XOPEN_SOURCE=500 |
| 74 | } |
| 75 | |
| 76 | if {[opt-bool fossil-debug]} { |
| 77 | define-append EXTRA_CFLAGS -DFOSSIL_DEBUG |
| 78 | msg-result "Debugging support enabled" |
| 79 |
| --- auto.def | |
| +++ auto.def | |
| @@ -68,11 +68,11 @@ | |
| 68 | |
| 69 | find_internal_sqlite |
| 70 | } |
| 71 | |
| 72 | if {[string match *-solaris* [get-define host]]} { |
| 73 | define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__} |
| 74 | } |
| 75 | |
| 76 | if {[opt-bool fossil-debug]} { |
| 77 | define-append EXTRA_CFLAGS -DFOSSIL_DEBUG |
| 78 | msg-result "Debugging support enabled" |
| 79 |
+1
-1
| --- skins/black_and_white/header.txt | ||
| +++ skins/black_and_white/header.txt | ||
| @@ -34,11 +34,11 @@ | ||
| 34 | 34 | if {[hascap o]} { |
| 35 | 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | 37 | } |
| 38 | 38 | if {[hascap r]} { |
| 39 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 39 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 40 | 40 | } |
| 41 | 41 | if {[hascap j]} { |
| 42 | 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | 43 | } |
| 44 | 44 | if {[hascap s]} { |
| 45 | 45 |
| --- skins/black_and_white/header.txt | |
| +++ skins/black_and_white/header.txt | |
| @@ -34,11 +34,11 @@ | |
| 34 | if {[hascap o]} { |
| 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | } |
| 38 | if {[hascap r]} { |
| 39 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 40 | } |
| 41 | if {[hascap j]} { |
| 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | } |
| 44 | if {[hascap s]} { |
| 45 |
| --- skins/black_and_white/header.txt | |
| +++ skins/black_and_white/header.txt | |
| @@ -34,11 +34,11 @@ | |
| 34 | if {[hascap o]} { |
| 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | } |
| 38 | if {[hascap r]} { |
| 39 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 40 | } |
| 41 | if {[hascap j]} { |
| 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | } |
| 44 | if {[hascap s]} { |
| 45 |
+1
-1
| --- skins/default/header.txt | ||
| +++ skins/default/header.txt | ||
| @@ -33,11 +33,11 @@ | ||
| 33 | 33 | if {[hascap o]} { |
| 34 | 34 | html "<a href='$home/brlist'>Branches</a>\n" |
| 35 | 35 | html "<a href='$home/taglist'>Tags</a>\n" |
| 36 | 36 | } |
| 37 | 37 | if {[hascap r]} { |
| 38 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 38 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 39 | 39 | } |
| 40 | 40 | if {[hascap j]} { |
| 41 | 41 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 42 | 42 | } |
| 43 | 43 | if {[hascap s]} { |
| 44 | 44 |
| --- skins/default/header.txt | |
| +++ skins/default/header.txt | |
| @@ -33,11 +33,11 @@ | |
| 33 | if {[hascap o]} { |
| 34 | html "<a href='$home/brlist'>Branches</a>\n" |
| 35 | html "<a href='$home/taglist'>Tags</a>\n" |
| 36 | } |
| 37 | if {[hascap r]} { |
| 38 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 39 | } |
| 40 | if {[hascap j]} { |
| 41 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 42 | } |
| 43 | if {[hascap s]} { |
| 44 |
| --- skins/default/header.txt | |
| +++ skins/default/header.txt | |
| @@ -33,11 +33,11 @@ | |
| 33 | if {[hascap o]} { |
| 34 | html "<a href='$home/brlist'>Branches</a>\n" |
| 35 | html "<a href='$home/taglist'>Tags</a>\n" |
| 36 | } |
| 37 | if {[hascap r]} { |
| 38 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 39 | } |
| 40 | if {[hascap j]} { |
| 41 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 42 | } |
| 43 | if {[hascap s]} { |
| 44 |
+1
-1
| --- skins/eagle/css.txt | ||
| +++ skins/eagle/css.txt | ||
| @@ -187,11 +187,11 @@ | ||
| 187 | 187 | |
| 188 | 188 | /* format for the user list table on the user setup page */ |
| 189 | 189 | table.usetupUserList { |
| 190 | 190 | outline-style: double; |
| 191 | 191 | outline-width: 1px; |
| 192 | - border-color: white; | |
| 192 | + outline-color: white; | |
| 193 | 193 | padding: 10px; |
| 194 | 194 | } |
| 195 | 195 | |
| 196 | 196 | /* color for capabilities, inherited by reader */ |
| 197 | 197 | span.ueditInheritReader { |
| 198 | 198 |
| --- skins/eagle/css.txt | |
| +++ skins/eagle/css.txt | |
| @@ -187,11 +187,11 @@ | |
| 187 | |
| 188 | /* format for the user list table on the user setup page */ |
| 189 | table.usetupUserList { |
| 190 | outline-style: double; |
| 191 | outline-width: 1px; |
| 192 | border-color: white; |
| 193 | padding: 10px; |
| 194 | } |
| 195 | |
| 196 | /* color for capabilities, inherited by reader */ |
| 197 | span.ueditInheritReader { |
| 198 |
| --- skins/eagle/css.txt | |
| +++ skins/eagle/css.txt | |
| @@ -187,11 +187,11 @@ | |
| 187 | |
| 188 | /* format for the user list table on the user setup page */ |
| 189 | table.usetupUserList { |
| 190 | outline-style: double; |
| 191 | outline-width: 1px; |
| 192 | outline-color: white; |
| 193 | padding: 10px; |
| 194 | } |
| 195 | |
| 196 | /* color for capabilities, inherited by reader */ |
| 197 | span.ueditInheritReader { |
| 198 |
+1
-1
| --- skins/eagle/header.txt | ||
| +++ skins/eagle/header.txt | ||
| @@ -114,11 +114,11 @@ | ||
| 114 | 114 | if {[hascap o]} { |
| 115 | 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | 117 | } |
| 118 | 118 | if {[hascap r]} { |
| 119 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 119 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 120 | 120 | } |
| 121 | 121 | if {[hascap j]} { |
| 122 | 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | 123 | } |
| 124 | 124 | if {[hascap s]} { |
| 125 | 125 |
| --- skins/eagle/header.txt | |
| +++ skins/eagle/header.txt | |
| @@ -114,11 +114,11 @@ | |
| 114 | if {[hascap o]} { |
| 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | } |
| 118 | if {[hascap r]} { |
| 119 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 120 | } |
| 121 | if {[hascap j]} { |
| 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | } |
| 124 | if {[hascap s]} { |
| 125 |
| --- skins/eagle/header.txt | |
| +++ skins/eagle/header.txt | |
| @@ -114,11 +114,11 @@ | |
| 114 | if {[hascap o]} { |
| 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | } |
| 118 | if {[hascap r]} { |
| 119 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 120 | } |
| 121 | if {[hascap j]} { |
| 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | } |
| 124 | if {[hascap s]} { |
| 125 |
+1
-1
| --- skins/enhanced1/header.txt | ||
| +++ skins/enhanced1/header.txt | ||
| @@ -114,11 +114,11 @@ | ||
| 114 | 114 | if {[hascap o]} { |
| 115 | 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | 117 | } |
| 118 | 118 | if {[hascap r]} { |
| 119 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 119 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 120 | 120 | } |
| 121 | 121 | if {[hascap j]} { |
| 122 | 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | 123 | } |
| 124 | 124 | if {[hascap s]} { |
| 125 | 125 |
| --- skins/enhanced1/header.txt | |
| +++ skins/enhanced1/header.txt | |
| @@ -114,11 +114,11 @@ | |
| 114 | if {[hascap o]} { |
| 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | } |
| 118 | if {[hascap r]} { |
| 119 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 120 | } |
| 121 | if {[hascap j]} { |
| 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | } |
| 124 | if {[hascap s]} { |
| 125 |
| --- skins/enhanced1/header.txt | |
| +++ skins/enhanced1/header.txt | |
| @@ -114,11 +114,11 @@ | |
| 114 | if {[hascap o]} { |
| 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | } |
| 118 | if {[hascap r]} { |
| 119 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 120 | } |
| 121 | if {[hascap j]} { |
| 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | } |
| 124 | if {[hascap s]} { |
| 125 |
+1
-1
| --- skins/khaki/header.txt | ||
| +++ skins/khaki/header.txt | ||
| @@ -32,11 +32,11 @@ | ||
| 32 | 32 | if {[hascap o]} { |
| 33 | 33 | html "<a href='$home/brlist'>Branches</a>\n" |
| 34 | 34 | html "<a href='$home/taglist'>Tags</a>\n" |
| 35 | 35 | } |
| 36 | 36 | if {[hascap r]} { |
| 37 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 37 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 38 | 38 | } |
| 39 | 39 | if {[hascap j]} { |
| 40 | 40 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 41 | 41 | } |
| 42 | 42 | if {[hascap s]} { |
| 43 | 43 |
| --- skins/khaki/header.txt | |
| +++ skins/khaki/header.txt | |
| @@ -32,11 +32,11 @@ | |
| 32 | if {[hascap o]} { |
| 33 | html "<a href='$home/brlist'>Branches</a>\n" |
| 34 | html "<a href='$home/taglist'>Tags</a>\n" |
| 35 | } |
| 36 | if {[hascap r]} { |
| 37 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 38 | } |
| 39 | if {[hascap j]} { |
| 40 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 41 | } |
| 42 | if {[hascap s]} { |
| 43 |
| --- skins/khaki/header.txt | |
| +++ skins/khaki/header.txt | |
| @@ -32,11 +32,11 @@ | |
| 32 | if {[hascap o]} { |
| 33 | html "<a href='$home/brlist'>Branches</a>\n" |
| 34 | html "<a href='$home/taglist'>Tags</a>\n" |
| 35 | } |
| 36 | if {[hascap r]} { |
| 37 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 38 | } |
| 39 | if {[hascap j]} { |
| 40 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 41 | } |
| 42 | if {[hascap s]} { |
| 43 |
+1
-1
| --- skins/plain_gray/header.txt | ||
| +++ skins/plain_gray/header.txt | ||
| @@ -30,11 +30,11 @@ | ||
| 30 | 30 | if {[hascap o]} { |
| 31 | 31 | html "<a href='$home/brlist'>Branches</a>\n" |
| 32 | 32 | html "<a href='$home/taglist'>Tags</a>\n" |
| 33 | 33 | } |
| 34 | 34 | if {[hascap r]} { |
| 35 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 35 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 36 | 36 | } |
| 37 | 37 | if {[hascap j]} { |
| 38 | 38 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 39 | 39 | } |
| 40 | 40 | if {[hascap s]} { |
| 41 | 41 |
| --- skins/plain_gray/header.txt | |
| +++ skins/plain_gray/header.txt | |
| @@ -30,11 +30,11 @@ | |
| 30 | if {[hascap o]} { |
| 31 | html "<a href='$home/brlist'>Branches</a>\n" |
| 32 | html "<a href='$home/taglist'>Tags</a>\n" |
| 33 | } |
| 34 | if {[hascap r]} { |
| 35 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 36 | } |
| 37 | if {[hascap j]} { |
| 38 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 39 | } |
| 40 | if {[hascap s]} { |
| 41 |
| --- skins/plain_gray/header.txt | |
| +++ skins/plain_gray/header.txt | |
| @@ -30,11 +30,11 @@ | |
| 30 | if {[hascap o]} { |
| 31 | html "<a href='$home/brlist'>Branches</a>\n" |
| 32 | html "<a href='$home/taglist'>Tags</a>\n" |
| 33 | } |
| 34 | if {[hascap r]} { |
| 35 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 36 | } |
| 37 | if {[hascap j]} { |
| 38 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 39 | } |
| 40 | if {[hascap s]} { |
| 41 |
+1
-1
| --- skins/rounded1/header.txt | ||
| +++ skins/rounded1/header.txt | ||
| @@ -34,11 +34,11 @@ | ||
| 34 | 34 | if {[hascap o]} { |
| 35 | 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | 37 | } |
| 38 | 38 | if {[hascap r]} { |
| 39 | - html "<a href='$home/reportlist'>Tickets</a>\n" | |
| 39 | + html "<a href='$home/ticket'>Tickets</a>\n" | |
| 40 | 40 | } |
| 41 | 41 | if {[hascap j]} { |
| 42 | 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | 43 | } |
| 44 | 44 | if {[hascap s]} { |
| 45 | 45 |
| --- skins/rounded1/header.txt | |
| +++ skins/rounded1/header.txt | |
| @@ -34,11 +34,11 @@ | |
| 34 | if {[hascap o]} { |
| 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | } |
| 38 | if {[hascap r]} { |
| 39 | html "<a href='$home/reportlist'>Tickets</a>\n" |
| 40 | } |
| 41 | if {[hascap j]} { |
| 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | } |
| 44 | if {[hascap s]} { |
| 45 |
| --- skins/rounded1/header.txt | |
| +++ skins/rounded1/header.txt | |
| @@ -34,11 +34,11 @@ | |
| 34 | if {[hascap o]} { |
| 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | } |
| 38 | if {[hascap r]} { |
| 39 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 40 | } |
| 41 | if {[hascap j]} { |
| 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | } |
| 44 | if {[hascap s]} { |
| 45 |
+5
| --- src/allrepo.c | ||
| +++ src/allrepo.c | ||
| @@ -229,15 +229,20 @@ | ||
| 229 | 229 | collect_argument(&extra, "vacuum",0); |
| 230 | 230 | collect_argument(&extra, "deanalyze",0); |
| 231 | 231 | collect_argument(&extra, "analyze",0); |
| 232 | 232 | collect_argument(&extra, "wal",0); |
| 233 | 233 | collect_argument(&extra, "stats",0); |
| 234 | + collect_argument(&extra, "index",0); | |
| 235 | + collect_argument(&extra, "no-index",0); | |
| 234 | 236 | }else if( strncmp(zCmd, "setting", n)==0 ){ |
| 235 | 237 | zCmd = "setting -R"; |
| 236 | 238 | collect_argv(&extra, 3); |
| 237 | 239 | }else if( strncmp(zCmd, "unset", n)==0 ){ |
| 238 | 240 | zCmd = "unset -R"; |
| 241 | + collect_argv(&extra, 3); | |
| 242 | + }else if( strncmp(zCmd, "fts-config", n)==0 ){ | |
| 243 | + zCmd = "fts-config -R"; | |
| 239 | 244 | collect_argv(&extra, 3); |
| 240 | 245 | }else if( strncmp(zCmd, "sync", n)==0 ){ |
| 241 | 246 | zCmd = "sync -autourl -R"; |
| 242 | 247 | collect_argument(&extra, "verbose","v"); |
| 243 | 248 | }else if( strncmp(zCmd, "test-integrity", n)==0 ){ |
| 244 | 249 |
| --- src/allrepo.c | |
| +++ src/allrepo.c | |
| @@ -229,15 +229,20 @@ | |
| 229 | collect_argument(&extra, "vacuum",0); |
| 230 | collect_argument(&extra, "deanalyze",0); |
| 231 | collect_argument(&extra, "analyze",0); |
| 232 | collect_argument(&extra, "wal",0); |
| 233 | collect_argument(&extra, "stats",0); |
| 234 | }else if( strncmp(zCmd, "setting", n)==0 ){ |
| 235 | zCmd = "setting -R"; |
| 236 | collect_argv(&extra, 3); |
| 237 | }else if( strncmp(zCmd, "unset", n)==0 ){ |
| 238 | zCmd = "unset -R"; |
| 239 | collect_argv(&extra, 3); |
| 240 | }else if( strncmp(zCmd, "sync", n)==0 ){ |
| 241 | zCmd = "sync -autourl -R"; |
| 242 | collect_argument(&extra, "verbose","v"); |
| 243 | }else if( strncmp(zCmd, "test-integrity", n)==0 ){ |
| 244 |
| --- src/allrepo.c | |
| +++ src/allrepo.c | |
| @@ -229,15 +229,20 @@ | |
| 229 | collect_argument(&extra, "vacuum",0); |
| 230 | collect_argument(&extra, "deanalyze",0); |
| 231 | collect_argument(&extra, "analyze",0); |
| 232 | collect_argument(&extra, "wal",0); |
| 233 | collect_argument(&extra, "stats",0); |
| 234 | collect_argument(&extra, "index",0); |
| 235 | collect_argument(&extra, "no-index",0); |
| 236 | }else if( strncmp(zCmd, "setting", n)==0 ){ |
| 237 | zCmd = "setting -R"; |
| 238 | collect_argv(&extra, 3); |
| 239 | }else if( strncmp(zCmd, "unset", n)==0 ){ |
| 240 | zCmd = "unset -R"; |
| 241 | collect_argv(&extra, 3); |
| 242 | }else if( strncmp(zCmd, "fts-config", n)==0 ){ |
| 243 | zCmd = "fts-config -R"; |
| 244 | collect_argv(&extra, 3); |
| 245 | }else if( strncmp(zCmd, "sync", n)==0 ){ |
| 246 | zCmd = "sync -autourl -R"; |
| 247 | collect_argument(&extra, "verbose","v"); |
| 248 | }else if( strncmp(zCmd, "test-integrity", n)==0 ){ |
| 249 |
+1
-1
| --- src/clone.c | ||
| +++ src/clone.c | ||
| @@ -138,11 +138,11 @@ | ||
| 138 | 138 | |
| 139 | 139 | if( g.argc < 4 ){ |
| 140 | 140 | usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); |
| 141 | 141 | } |
| 142 | 142 | db_open_config(0); |
| 143 | - if( file_size(g.argv[3])>0 ){ | |
| 143 | + if( -1 != file_size(g.argv[3]) ){ | |
| 144 | 144 | fossil_fatal("file already exists: %s", g.argv[3]); |
| 145 | 145 | } |
| 146 | 146 | |
| 147 | 147 | url_parse(g.argv[2], urlFlags); |
| 148 | 148 | if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; |
| 149 | 149 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -138,11 +138,11 @@ | |
| 138 | |
| 139 | if( g.argc < 4 ){ |
| 140 | usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); |
| 141 | } |
| 142 | db_open_config(0); |
| 143 | if( file_size(g.argv[3])>0 ){ |
| 144 | fossil_fatal("file already exists: %s", g.argv[3]); |
| 145 | } |
| 146 | |
| 147 | url_parse(g.argv[2], urlFlags); |
| 148 | if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; |
| 149 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -138,11 +138,11 @@ | |
| 138 | |
| 139 | if( g.argc < 4 ){ |
| 140 | usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); |
| 141 | } |
| 142 | db_open_config(0); |
| 143 | if( -1 != file_size(g.argv[3]) ){ |
| 144 | fossil_fatal("file already exists: %s", g.argv[3]); |
| 145 | } |
| 146 | |
| 147 | url_parse(g.argv[2], urlFlags); |
| 148 | if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; |
| 149 |
M
src/db.c
+139
-88
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -547,12 +547,13 @@ | ||
| 547 | 547 | va_end(ap); |
| 548 | 548 | z = blob_str(&sql); |
| 549 | 549 | while( rc==SQLITE_OK && z[0] ){ |
| 550 | 550 | pStmt = 0; |
| 551 | 551 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 552 | - if( rc!=SQLITE_OK ) break; | |
| 553 | - if( pStmt ){ | |
| 552 | + if( rc ){ | |
| 553 | + db_err("%s: {%s}", sqlite3_errmsg(g.db), z); | |
| 554 | + }else if( pStmt ){ | |
| 554 | 555 | db.nPrepare++; |
| 555 | 556 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 556 | 557 | rc = sqlite3_finalize(pStmt); |
| 557 | 558 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 558 | 559 | } |
| @@ -1197,11 +1198,11 @@ | ||
| 1197 | 1198 | */ |
| 1198 | 1199 | if( !db_table_has_column("repository","mlink","isaux") ){ |
| 1199 | 1200 | db_begin_transaction(); |
| 1200 | 1201 | db_multi_exec( |
| 1201 | 1202 | "ALTER TABLE %s.mlink ADD COLUMN pmid INTEGER DEFAULT 0;" |
| 1202 | - "ALTER TABLE %s.mlink ADD COLUMN isaux INTEGER DEFAULT 0;", | |
| 1203 | + "ALTER TABLE %s.mlink ADD COLUMN isaux BOOLEAN DEFAULT 0;", | |
| 1203 | 1204 | db_name("repository"), db_name("repository") |
| 1204 | 1205 | ); |
| 1205 | 1206 | db_end_transaction(0); |
| 1206 | 1207 | } |
| 1207 | 1208 | } |
| @@ -1380,15 +1381,10 @@ | ||
| 1380 | 1381 | while( db.pAllStmt ){ |
| 1381 | 1382 | db_finalize(db.pAllStmt); |
| 1382 | 1383 | } |
| 1383 | 1384 | db_end_transaction(1); |
| 1384 | 1385 | pStmt = 0; |
| 1385 | - if( reportErrors ){ | |
| 1386 | - while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ | |
| 1387 | - fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); | |
| 1388 | - } | |
| 1389 | - } | |
| 1390 | 1386 | db_close_config(); |
| 1391 | 1387 | |
| 1392 | 1388 | /* If the localdb (the check-out database) is open and if it has |
| 1393 | 1389 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1394 | 1390 | */ |
| @@ -1399,12 +1395,18 @@ | ||
| 1399 | 1395 | db_multi_exec("VACUUM;"); |
| 1400 | 1396 | } |
| 1401 | 1397 | } |
| 1402 | 1398 | |
| 1403 | 1399 | if( g.db ){ |
| 1400 | + int rc; | |
| 1404 | 1401 | sqlite3_wal_checkpoint(g.db, 0); |
| 1405 | - sqlite3_close(g.db); | |
| 1402 | + rc = sqlite3_close(g.db); | |
| 1403 | + if( rc==SQLITE_BUSY && reportErrors ){ | |
| 1404 | + while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ | |
| 1405 | + fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); | |
| 1406 | + } | |
| 1407 | + } | |
| 1406 | 1408 | g.db = 0; |
| 1407 | 1409 | g.zMainDbType = 0; |
| 1408 | 1410 | } |
| 1409 | 1411 | g.repositoryOpen = 0; |
| 1410 | 1412 | g.localOpen = 0; |
| @@ -1520,12 +1522,12 @@ | ||
| 1520 | 1522 | int i; |
| 1521 | 1523 | const char *zSep = ""; |
| 1522 | 1524 | |
| 1523 | 1525 | blob_zero(&x); |
| 1524 | 1526 | blob_append_sql(&x, "("); |
| 1525 | - for(i=0; ctrlSettings[i].name; i++){ | |
| 1526 | - blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, ctrlSettings[i].name); | |
| 1527 | + for(i=0; aSetting[i].name; i++){ | |
| 1528 | + blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, aSetting[i].name); | |
| 1527 | 1529 | zSep = ","; |
| 1528 | 1530 | } |
| 1529 | 1531 | blob_append_sql(&x, ")"); |
| 1530 | 1532 | return blob_sql_text(&x); |
| 1531 | 1533 | } |
| @@ -1682,10 +1684,15 @@ | ||
| 1682 | 1684 | verify_all_options(); |
| 1683 | 1685 | |
| 1684 | 1686 | if( g.argc!=3 ){ |
| 1685 | 1687 | usage("REPOSITORY-NAME"); |
| 1686 | 1688 | } |
| 1689 | + | |
| 1690 | + if( -1 != file_size(g.argv[2]) ){ | |
| 1691 | + fossil_fatal("file already exists: %s", g.argv[2]); | |
| 1692 | + } | |
| 1693 | + | |
| 1687 | 1694 | db_create_repository(g.argv[2]); |
| 1688 | 1695 | db_open_repository(g.argv[2]); |
| 1689 | 1696 | db_open_config(0); |
| 1690 | 1697 | if( zTemplate ) db_attach(zTemplate, "settingSrc"); |
| 1691 | 1698 | db_begin_transaction(); |
| @@ -1913,16 +1920,21 @@ | ||
| 1913 | 1920 | g.zConfigDbType = zTempDbType; |
| 1914 | 1921 | } |
| 1915 | 1922 | } |
| 1916 | 1923 | |
| 1917 | 1924 | /* |
| 1918 | -** Logic for reading potentially versioned settings from | |
| 1919 | -** .fossil-settings/<name> , and emits warnings if necessary. | |
| 1920 | -** Returns the non-versioned value without modification if there is no | |
| 1921 | -** versioned value. | |
| 1925 | +** Try to read a versioned setting string from .fossil-settings/<name>. | |
| 1926 | +** | |
| 1927 | +** Return the text of the string if it is found. Return NULL if not | |
| 1928 | +** found. | |
| 1929 | +** | |
| 1930 | +** If the zNonVersionedSetting parameter is not NULL then it holds the | |
| 1931 | +** non-versioned value for this setting. If both a versioned and ad | |
| 1932 | +** non-versioned value exist and are not equal, then a warning message | |
| 1933 | +** might be generated. | |
| 1922 | 1934 | */ |
| 1923 | -char *db_get_do_versionable(const char *zName, char *zNonVersionedSetting){ | |
| 1935 | +char *db_get_versioned(const char *zName, char *zNonVersionedSetting){ | |
| 1924 | 1936 | char *zVersionedSetting = 0; |
| 1925 | 1937 | int noWarn = 0; |
| 1926 | 1938 | struct _cacheEntry { |
| 1927 | 1939 | struct _cacheEntry *next; |
| 1928 | 1940 | const char *zName, *zValue; |
| @@ -1993,37 +2005,38 @@ | ||
| 1993 | 2005 | |
| 1994 | 2006 | |
| 1995 | 2007 | /* |
| 1996 | 2008 | ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the |
| 1997 | 2009 | ** repository and local databases. |
| 2010 | +** | |
| 2011 | +** If no such variable exists, return zDefault. Or, if zName is the name | |
| 2012 | +** of a setting, then the zDefault is ignored and the default value of the | |
| 2013 | +** setting is returned instead. If zName is a versioned setting, then | |
| 2014 | +** versioned value takes priority. | |
| 1998 | 2015 | */ |
| 1999 | 2016 | char *db_get(const char *zName, char *zDefault){ |
| 2000 | 2017 | char *z = 0; |
| 2001 | - int i; | |
| 2002 | - const struct stControlSettings *ctrlSetting = 0; | |
| 2003 | - /* Is this a setting? */ | |
| 2004 | - for(i=0; ctrlSettings[i].name; i++){ | |
| 2005 | - if( strcmp(ctrlSettings[i].name, zName)==0 ){ | |
| 2006 | - ctrlSetting = &(ctrlSettings[i]); | |
| 2007 | - break; | |
| 2008 | - } | |
| 2009 | - } | |
| 2018 | + const Setting *pSetting = db_find_setting(zName, 0); | |
| 2010 | 2019 | if( g.repositoryOpen ){ |
| 2011 | 2020 | z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName); |
| 2012 | 2021 | } |
| 2013 | 2022 | if( z==0 && g.zConfigDbName ){ |
| 2014 | 2023 | db_swap_connections(); |
| 2015 | 2024 | z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName); |
| 2016 | 2025 | db_swap_connections(); |
| 2017 | 2026 | } |
| 2018 | - if( ctrlSetting!=0 && ctrlSetting->versionable ){ | |
| 2027 | + if( pSetting!=0 && pSetting->versionable ){ | |
| 2019 | 2028 | /* This is a versionable setting, try and get the info from a |
| 2020 | 2029 | ** checked out file */ |
| 2021 | - z = db_get_do_versionable(zName, z); | |
| 2030 | + z = db_get_versioned(zName, z); | |
| 2022 | 2031 | } |
| 2023 | 2032 | if( z==0 ){ |
| 2024 | - z = zDefault; | |
| 2033 | + if( zDefault==0 && pSetting && pSetting->def[0] ){ | |
| 2034 | + z = fossil_strdup(pSetting->def); | |
| 2035 | + }else{ | |
| 2036 | + z = zDefault; | |
| 2037 | + } | |
| 2025 | 2038 | } |
| 2026 | 2039 | return z; |
| 2027 | 2040 | } |
| 2028 | 2041 | char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){ |
| 2029 | 2042 | char *z = 0; |
| @@ -2285,88 +2298,91 @@ | ||
| 2285 | 2298 | g.argc = 2; |
| 2286 | 2299 | info_cmd(); |
| 2287 | 2300 | } |
| 2288 | 2301 | |
| 2289 | 2302 | /* |
| 2290 | -** Print the value of a setting named zName | |
| 2303 | +** Print the current value of a setting identified by the pSetting | |
| 2304 | +** pointer. | |
| 2291 | 2305 | */ |
| 2292 | -static void print_setting( | |
| 2293 | - const struct stControlSettings *ctrlSetting, | |
| 2294 | - int localOpen | |
| 2295 | -){ | |
| 2306 | +static void print_setting(const Setting *pSetting){ | |
| 2296 | 2307 | Stmt q; |
| 2297 | 2308 | if( g.repositoryOpen ){ |
| 2298 | 2309 | db_prepare(&q, |
| 2299 | 2310 | "SELECT '(local)', value FROM config WHERE name=%Q" |
| 2300 | 2311 | " UNION ALL " |
| 2301 | 2312 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2302 | - ctrlSetting->name, ctrlSetting->name | |
| 2313 | + pSetting->name, pSetting->name | |
| 2303 | 2314 | ); |
| 2304 | 2315 | }else{ |
| 2305 | 2316 | db_prepare(&q, |
| 2306 | 2317 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2307 | - ctrlSetting->name | |
| 2318 | + pSetting->name | |
| 2308 | 2319 | ); |
| 2309 | 2320 | } |
| 2310 | 2321 | if( db_step(&q)==SQLITE_ROW ){ |
| 2311 | - fossil_print("%-20s %-8s %s\n", ctrlSetting->name, db_column_text(&q, 0), | |
| 2322 | + fossil_print("%-20s %-8s %s\n", pSetting->name, db_column_text(&q, 0), | |
| 2312 | 2323 | db_column_text(&q, 1)); |
| 2313 | 2324 | }else{ |
| 2314 | - fossil_print("%-20s\n", ctrlSetting->name); | |
| 2325 | + fossil_print("%-20s\n", pSetting->name); | |
| 2315 | 2326 | } |
| 2316 | - if( ctrlSetting->versionable && localOpen ){ | |
| 2327 | + if( pSetting->versionable && g.localOpen ){ | |
| 2317 | 2328 | /* Check to see if this is overridden by a versionable settings file */ |
| 2318 | 2329 | Blob versionedPathname; |
| 2319 | 2330 | blob_zero(&versionedPathname); |
| 2320 | 2331 | blob_appendf(&versionedPathname, "%s/.fossil-settings/%s", |
| 2321 | - g.zLocalRoot, ctrlSetting->name); | |
| 2332 | + g.zLocalRoot, pSetting->name); | |
| 2322 | 2333 | if( file_size(blob_str(&versionedPathname))>=0 ){ |
| 2323 | 2334 | fossil_print(" (overridden by contents of file .fossil-settings/%s)\n", |
| 2324 | - ctrlSetting->name); | |
| 2335 | + pSetting->name); | |
| 2325 | 2336 | } |
| 2326 | 2337 | } |
| 2327 | 2338 | db_finalize(&q); |
| 2328 | 2339 | } |
| 2329 | 2340 | |
| 2330 | 2341 | |
| 2342 | +#if INTERFACE | |
| 2331 | 2343 | /* |
| 2332 | -** define all settings, which can be controlled via the set/unset | |
| 2333 | -** command. var is the name of the internal configuration name for db_(un)set. | |
| 2344 | +** Define all settings, which can be controlled via the set/unset | |
| 2345 | +** command. | |
| 2346 | +** | |
| 2347 | +** var is the name of the internal configuration name for db_(un)set. | |
| 2334 | 2348 | ** If var is 0, the settings name is used. |
| 2349 | +** | |
| 2335 | 2350 | ** width is the length for the edit field on the behavior page, 0 |
| 2336 | 2351 | ** is used for on/off checkboxes. |
| 2352 | +** | |
| 2337 | 2353 | ** The behaviour page doesn't use a special layout. It lists all |
| 2338 | 2354 | ** set-commands and displays the 'set'-help as info. |
| 2339 | 2355 | */ |
| 2340 | -#if INTERFACE | |
| 2341 | -struct stControlSettings { | |
| 2356 | +struct Setting { | |
| 2342 | 2357 | const char *name; /* Name of the setting */ |
| 2343 | 2358 | const char *var; /* Internal variable name used by db_set() */ |
| 2344 | 2359 | int width; /* Width of display. 0 for boolean values. */ |
| 2345 | 2360 | int versionable; /* Is this setting versionable? */ |
| 2346 | 2361 | int forceTextArea; /* Force using a text area for display? */ |
| 2347 | 2362 | const char *def; /* Default value */ |
| 2348 | 2363 | }; |
| 2349 | 2364 | #endif /* INTERFACE */ |
| 2350 | -struct stControlSettings const ctrlSettings[] = { | |
| 2365 | + | |
| 2366 | +const Setting aSetting[] = { | |
| 2351 | 2367 | { "access-log", 0, 0, 0, 0, "off" }, |
| 2352 | 2368 | { "admin-log", 0, 0, 0, 0, "off" }, |
| 2353 | 2369 | { "allow-symlinks", 0, 0, 1, 0, "off" }, |
| 2354 | 2370 | { "auto-captcha", "autocaptcha", 0, 0, 0, "on" }, |
| 2355 | 2371 | { "auto-hyperlink", 0, 0, 0, 0, "on", }, |
| 2356 | 2372 | { "auto-shun", 0, 0, 0, 0, "on" }, |
| 2357 | 2373 | { "autosync", 0, 0, 0, 0, "on" }, |
| 2358 | 2374 | { "autosync-tries", 0, 16, 0, 0, "1" }, |
| 2359 | 2375 | { "binary-glob", 0, 40, 1, 0, "" }, |
| 2360 | - { "clearsign", 0, 0, 0, 0, "off" }, | |
| 2361 | 2376 | #if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \ |
| 2362 | 2377 | defined(__APPLE__) |
| 2363 | 2378 | { "case-sensitive", 0, 0, 0, 0, "off" }, |
| 2364 | 2379 | #else |
| 2365 | 2380 | { "case-sensitive", 0, 0, 0, 0, "on" }, |
| 2366 | 2381 | #endif |
| 2367 | 2382 | { "clean-glob", 0, 40, 1, 0, "" }, |
| 2383 | + { "clearsign", 0, 0, 0, 0, "off" }, | |
| 2368 | 2384 | { "crnl-glob", 0, 40, 1, 0, "" }, |
| 2369 | 2385 | { "default-perms", 0, 16, 0, 0, "u" }, |
| 2370 | 2386 | { "diff-binary", 0, 0, 0, 0, "on" }, |
| 2371 | 2387 | { "diff-command", 0, 40, 0, 0, "" }, |
| 2372 | 2388 | { "dont-push", 0, 0, 0, 0, "off" }, |
| @@ -2407,10 +2423,41 @@ | ||
| 2407 | 2423 | { "th1-uri-regexp", 0, 40, 1, 0, "" }, |
| 2408 | 2424 | { "web-browser", 0, 32, 0, 0, "" }, |
| 2409 | 2425 | { "white-foreground", 0, 0, 0, 0, "off" }, |
| 2410 | 2426 | { 0,0,0,0,0,0 } |
| 2411 | 2427 | }; |
| 2428 | + | |
| 2429 | +/* | |
| 2430 | +** Look up a control setting by its name. Return a pointer to the Setting | |
| 2431 | +** object, or NULL if there is no such setting. | |
| 2432 | +** | |
| 2433 | +** If allowPrefix is true, then the Setting returned is the first one for | |
| 2434 | +** which zName is a prefix of the Setting name. | |
| 2435 | +*/ | |
| 2436 | +const Setting *db_find_setting(const char *zName, int allowPrefix){ | |
| 2437 | + int lwr, mid, upr, c; | |
| 2438 | + int n = (int)strlen(zName) + !allowPrefix; | |
| 2439 | + lwr = 0; | |
| 2440 | + upr = ArraySize(aSetting)-2; | |
| 2441 | + while( upr>=lwr ){ | |
| 2442 | + mid = (upr+lwr)/2; | |
| 2443 | + c = fossil_strncmp(zName, aSetting[mid].name, n); | |
| 2444 | + if( c<0 ){ | |
| 2445 | + upr = mid - 1; | |
| 2446 | + }else if( c>0 ){ | |
| 2447 | + lwr = mid + 1; | |
| 2448 | + }else{ | |
| 2449 | + if( allowPrefix ){ | |
| 2450 | + while( mid>lwr && fossil_strncmp(zName, aSetting[mid-1].name, n)==0 ){ | |
| 2451 | + mid--; | |
| 2452 | + } | |
| 2453 | + } | |
| 2454 | + return &aSetting[mid]; | |
| 2455 | + } | |
| 2456 | + } | |
| 2457 | + return 0; | |
| 2458 | +} | |
| 2412 | 2459 | |
| 2413 | 2460 | /* |
| 2414 | 2461 | ** COMMAND: settings |
| 2415 | 2462 | ** COMMAND: unset* |
| 2416 | 2463 | ** |
| @@ -2658,61 +2705,65 @@ | ||
| 2658 | 2705 | globalFlag = 1; |
| 2659 | 2706 | } |
| 2660 | 2707 | if( unsetFlag && g.argc!=3 ){ |
| 2661 | 2708 | usage("PROPERTY ?-global?"); |
| 2662 | 2709 | } |
| 2710 | + | |
| 2711 | + /* Verify that the aSetting[] entries are in sorted order. This is | |
| 2712 | + ** necessary for the binary search in db_find_setting() to work correctly. | |
| 2713 | + */ | |
| 2714 | + for(i=1; aSetting[i].name; i++){ | |
| 2715 | + if( fossil_strcmp(aSetting[i-1].name, aSetting[i].name)>=0 ){ | |
| 2716 | + fossil_panic("Internal Error: aSetting[] entries for \"%s\"" | |
| 2717 | + " and \"%s\" are out of order.", | |
| 2718 | + aSetting[i-1].name, aSetting[i].name); | |
| 2719 | + } | |
| 2720 | + } | |
| 2721 | + | |
| 2663 | 2722 | if( g.argc==2 ){ |
| 2664 | - int openLocal = db_open_local(0); | |
| 2665 | - for(i=0; ctrlSettings[i].name; i++){ | |
| 2666 | - print_setting(&ctrlSettings[i], openLocal); | |
| 2723 | + for(i=0; aSetting[i].name; i++){ | |
| 2724 | + print_setting(&aSetting[i]); | |
| 2667 | 2725 | } |
| 2668 | 2726 | }else if( g.argc==3 || g.argc==4 ){ |
| 2669 | 2727 | const char *zName = g.argv[2]; |
| 2670 | - int isManifest; | |
| 2671 | - int n = strlen(zName); | |
| 2672 | - for(i=0; ctrlSettings[i].name; i++){ | |
| 2673 | - if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; | |
| 2674 | - } | |
| 2675 | - if( !ctrlSettings[i].name ){ | |
| 2728 | + int n = (int)strlen(zName); | |
| 2729 | + const Setting *pSetting = db_find_setting(zName, 1); | |
| 2730 | + if( pSetting==0 ){ | |
| 2676 | 2731 | fossil_fatal("no such setting: %s", zName); |
| 2677 | 2732 | } |
| 2678 | - isManifest = fossil_strcmp(ctrlSettings[i].name, "manifest")==0; | |
| 2679 | - if( isManifest && globalFlag ){ | |
| 2733 | + if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ | |
| 2680 | 2734 | fossil_fatal("cannot set 'manifest' globally"); |
| 2681 | 2735 | } |
| 2682 | 2736 | if( unsetFlag || g.argc==4 ){ |
| 2683 | - if( ctrlSettings[i+1].name | |
| 2684 | - && strncmp(ctrlSettings[i+1].name, zName, n)==0 | |
| 2685 | - && ctrlSettings[i].name[n]!=0 | |
| 2686 | - ){ | |
| 2687 | - fossil_print("ambiguous property prefix: %s\nMatching properties:\n", | |
| 2688 | - zName); | |
| 2689 | - while( ctrlSettings[i].name | |
| 2690 | - && strncmp(ctrlSettings[i].name, zName, n)==0 | |
| 2691 | - ){ | |
| 2692 | - fossil_print("%s\n", ctrlSettings[i].name); | |
| 2693 | - i++; | |
| 2694 | - } | |
| 2695 | - fossil_exit(1); | |
| 2696 | - }else{ | |
| 2697 | - if( unsetFlag ){ | |
| 2698 | - db_unset(ctrlSettings[i].name, globalFlag); | |
| 2699 | - }else{ | |
| 2700 | - db_set(ctrlSettings[i].name, g.argv[3], globalFlag); | |
| 2701 | - } | |
| 2702 | - } | |
| 2703 | - }else{ | |
| 2704 | - isManifest = 0; | |
| 2705 | - while( ctrlSettings[i].name | |
| 2706 | - && strncmp(ctrlSettings[i].name, zName, n)==0 | |
| 2707 | - ){ | |
| 2708 | - print_setting(&ctrlSettings[i], db_open_local(0)); | |
| 2709 | - i++; | |
| 2710 | - } | |
| 2711 | - } | |
| 2712 | - if( isManifest && g.localOpen ){ | |
| 2713 | - manifest_to_disk(db_lget_int("checkout", 0)); | |
| 2737 | + int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; | |
| 2738 | + if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){ | |
| 2739 | + Blob x; | |
| 2740 | + int i; | |
| 2741 | + blob_init(&x,0,0); | |
| 2742 | + for(i=0; pSetting[i].name; i++){ | |
| 2743 | + if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; | |
| 2744 | + blob_appendf(&x, " %s", pSetting[i].name); | |
| 2745 | + } | |
| 2746 | + fossil_fatal("ambiguous setting \"%s\" - might be:%s", | |
| 2747 | + zName, blob_str(&x)); | |
| 2748 | + } | |
| 2749 | + if( globalFlag && isManifest ){ | |
| 2750 | + fossil_fatal("cannot set 'manifest' globally"); | |
| 2751 | + } | |
| 2752 | + if( unsetFlag ){ | |
| 2753 | + db_unset(pSetting->name, globalFlag); | |
| 2754 | + }else{ | |
| 2755 | + db_set(pSetting->name, g.argv[3], globalFlag); | |
| 2756 | + } | |
| 2757 | + if( isManifest && g.localOpen ){ | |
| 2758 | + manifest_to_disk(db_lget_int("checkout", 0)); | |
| 2759 | + } | |
| 2760 | + }else{ | |
| 2761 | + while( pSetting->name && fossil_strncmp(pSetting->name,zName,n)==0 ){ | |
| 2762 | + print_setting(pSetting); | |
| 2763 | + pSetting++; | |
| 2764 | + } | |
| 2714 | 2765 | } |
| 2715 | 2766 | }else{ |
| 2716 | 2767 | usage("?PROPERTY? ?VALUE? ?-global?"); |
| 2717 | 2768 | } |
| 2718 | 2769 | } |
| 2719 | 2770 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -547,12 +547,13 @@ | |
| 547 | va_end(ap); |
| 548 | z = blob_str(&sql); |
| 549 | while( rc==SQLITE_OK && z[0] ){ |
| 550 | pStmt = 0; |
| 551 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 552 | if( rc!=SQLITE_OK ) break; |
| 553 | if( pStmt ){ |
| 554 | db.nPrepare++; |
| 555 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 556 | rc = sqlite3_finalize(pStmt); |
| 557 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 558 | } |
| @@ -1197,11 +1198,11 @@ | |
| 1197 | */ |
| 1198 | if( !db_table_has_column("repository","mlink","isaux") ){ |
| 1199 | db_begin_transaction(); |
| 1200 | db_multi_exec( |
| 1201 | "ALTER TABLE %s.mlink ADD COLUMN pmid INTEGER DEFAULT 0;" |
| 1202 | "ALTER TABLE %s.mlink ADD COLUMN isaux INTEGER DEFAULT 0;", |
| 1203 | db_name("repository"), db_name("repository") |
| 1204 | ); |
| 1205 | db_end_transaction(0); |
| 1206 | } |
| 1207 | } |
| @@ -1380,15 +1381,10 @@ | |
| 1380 | while( db.pAllStmt ){ |
| 1381 | db_finalize(db.pAllStmt); |
| 1382 | } |
| 1383 | db_end_transaction(1); |
| 1384 | pStmt = 0; |
| 1385 | if( reportErrors ){ |
| 1386 | while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ |
| 1387 | fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); |
| 1388 | } |
| 1389 | } |
| 1390 | db_close_config(); |
| 1391 | |
| 1392 | /* If the localdb (the check-out database) is open and if it has |
| 1393 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1394 | */ |
| @@ -1399,12 +1395,18 @@ | |
| 1399 | db_multi_exec("VACUUM;"); |
| 1400 | } |
| 1401 | } |
| 1402 | |
| 1403 | if( g.db ){ |
| 1404 | sqlite3_wal_checkpoint(g.db, 0); |
| 1405 | sqlite3_close(g.db); |
| 1406 | g.db = 0; |
| 1407 | g.zMainDbType = 0; |
| 1408 | } |
| 1409 | g.repositoryOpen = 0; |
| 1410 | g.localOpen = 0; |
| @@ -1520,12 +1522,12 @@ | |
| 1520 | int i; |
| 1521 | const char *zSep = ""; |
| 1522 | |
| 1523 | blob_zero(&x); |
| 1524 | blob_append_sql(&x, "("); |
| 1525 | for(i=0; ctrlSettings[i].name; i++){ |
| 1526 | blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, ctrlSettings[i].name); |
| 1527 | zSep = ","; |
| 1528 | } |
| 1529 | blob_append_sql(&x, ")"); |
| 1530 | return blob_sql_text(&x); |
| 1531 | } |
| @@ -1682,10 +1684,15 @@ | |
| 1682 | verify_all_options(); |
| 1683 | |
| 1684 | if( g.argc!=3 ){ |
| 1685 | usage("REPOSITORY-NAME"); |
| 1686 | } |
| 1687 | db_create_repository(g.argv[2]); |
| 1688 | db_open_repository(g.argv[2]); |
| 1689 | db_open_config(0); |
| 1690 | if( zTemplate ) db_attach(zTemplate, "settingSrc"); |
| 1691 | db_begin_transaction(); |
| @@ -1913,16 +1920,21 @@ | |
| 1913 | g.zConfigDbType = zTempDbType; |
| 1914 | } |
| 1915 | } |
| 1916 | |
| 1917 | /* |
| 1918 | ** Logic for reading potentially versioned settings from |
| 1919 | ** .fossil-settings/<name> , and emits warnings if necessary. |
| 1920 | ** Returns the non-versioned value without modification if there is no |
| 1921 | ** versioned value. |
| 1922 | */ |
| 1923 | char *db_get_do_versionable(const char *zName, char *zNonVersionedSetting){ |
| 1924 | char *zVersionedSetting = 0; |
| 1925 | int noWarn = 0; |
| 1926 | struct _cacheEntry { |
| 1927 | struct _cacheEntry *next; |
| 1928 | const char *zName, *zValue; |
| @@ -1993,37 +2005,38 @@ | |
| 1993 | |
| 1994 | |
| 1995 | /* |
| 1996 | ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the |
| 1997 | ** repository and local databases. |
| 1998 | */ |
| 1999 | char *db_get(const char *zName, char *zDefault){ |
| 2000 | char *z = 0; |
| 2001 | int i; |
| 2002 | const struct stControlSettings *ctrlSetting = 0; |
| 2003 | /* Is this a setting? */ |
| 2004 | for(i=0; ctrlSettings[i].name; i++){ |
| 2005 | if( strcmp(ctrlSettings[i].name, zName)==0 ){ |
| 2006 | ctrlSetting = &(ctrlSettings[i]); |
| 2007 | break; |
| 2008 | } |
| 2009 | } |
| 2010 | if( g.repositoryOpen ){ |
| 2011 | z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName); |
| 2012 | } |
| 2013 | if( z==0 && g.zConfigDbName ){ |
| 2014 | db_swap_connections(); |
| 2015 | z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName); |
| 2016 | db_swap_connections(); |
| 2017 | } |
| 2018 | if( ctrlSetting!=0 && ctrlSetting->versionable ){ |
| 2019 | /* This is a versionable setting, try and get the info from a |
| 2020 | ** checked out file */ |
| 2021 | z = db_get_do_versionable(zName, z); |
| 2022 | } |
| 2023 | if( z==0 ){ |
| 2024 | z = zDefault; |
| 2025 | } |
| 2026 | return z; |
| 2027 | } |
| 2028 | char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){ |
| 2029 | char *z = 0; |
| @@ -2285,88 +2298,91 @@ | |
| 2285 | g.argc = 2; |
| 2286 | info_cmd(); |
| 2287 | } |
| 2288 | |
| 2289 | /* |
| 2290 | ** Print the value of a setting named zName |
| 2291 | */ |
| 2292 | static void print_setting( |
| 2293 | const struct stControlSettings *ctrlSetting, |
| 2294 | int localOpen |
| 2295 | ){ |
| 2296 | Stmt q; |
| 2297 | if( g.repositoryOpen ){ |
| 2298 | db_prepare(&q, |
| 2299 | "SELECT '(local)', value FROM config WHERE name=%Q" |
| 2300 | " UNION ALL " |
| 2301 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2302 | ctrlSetting->name, ctrlSetting->name |
| 2303 | ); |
| 2304 | }else{ |
| 2305 | db_prepare(&q, |
| 2306 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2307 | ctrlSetting->name |
| 2308 | ); |
| 2309 | } |
| 2310 | if( db_step(&q)==SQLITE_ROW ){ |
| 2311 | fossil_print("%-20s %-8s %s\n", ctrlSetting->name, db_column_text(&q, 0), |
| 2312 | db_column_text(&q, 1)); |
| 2313 | }else{ |
| 2314 | fossil_print("%-20s\n", ctrlSetting->name); |
| 2315 | } |
| 2316 | if( ctrlSetting->versionable && localOpen ){ |
| 2317 | /* Check to see if this is overridden by a versionable settings file */ |
| 2318 | Blob versionedPathname; |
| 2319 | blob_zero(&versionedPathname); |
| 2320 | blob_appendf(&versionedPathname, "%s/.fossil-settings/%s", |
| 2321 | g.zLocalRoot, ctrlSetting->name); |
| 2322 | if( file_size(blob_str(&versionedPathname))>=0 ){ |
| 2323 | fossil_print(" (overridden by contents of file .fossil-settings/%s)\n", |
| 2324 | ctrlSetting->name); |
| 2325 | } |
| 2326 | } |
| 2327 | db_finalize(&q); |
| 2328 | } |
| 2329 | |
| 2330 | |
| 2331 | /* |
| 2332 | ** define all settings, which can be controlled via the set/unset |
| 2333 | ** command. var is the name of the internal configuration name for db_(un)set. |
| 2334 | ** If var is 0, the settings name is used. |
| 2335 | ** width is the length for the edit field on the behavior page, 0 |
| 2336 | ** is used for on/off checkboxes. |
| 2337 | ** The behaviour page doesn't use a special layout. It lists all |
| 2338 | ** set-commands and displays the 'set'-help as info. |
| 2339 | */ |
| 2340 | #if INTERFACE |
| 2341 | struct stControlSettings { |
| 2342 | const char *name; /* Name of the setting */ |
| 2343 | const char *var; /* Internal variable name used by db_set() */ |
| 2344 | int width; /* Width of display. 0 for boolean values. */ |
| 2345 | int versionable; /* Is this setting versionable? */ |
| 2346 | int forceTextArea; /* Force using a text area for display? */ |
| 2347 | const char *def; /* Default value */ |
| 2348 | }; |
| 2349 | #endif /* INTERFACE */ |
| 2350 | struct stControlSettings const ctrlSettings[] = { |
| 2351 | { "access-log", 0, 0, 0, 0, "off" }, |
| 2352 | { "admin-log", 0, 0, 0, 0, "off" }, |
| 2353 | { "allow-symlinks", 0, 0, 1, 0, "off" }, |
| 2354 | { "auto-captcha", "autocaptcha", 0, 0, 0, "on" }, |
| 2355 | { "auto-hyperlink", 0, 0, 0, 0, "on", }, |
| 2356 | { "auto-shun", 0, 0, 0, 0, "on" }, |
| 2357 | { "autosync", 0, 0, 0, 0, "on" }, |
| 2358 | { "autosync-tries", 0, 16, 0, 0, "1" }, |
| 2359 | { "binary-glob", 0, 40, 1, 0, "" }, |
| 2360 | { "clearsign", 0, 0, 0, 0, "off" }, |
| 2361 | #if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \ |
| 2362 | defined(__APPLE__) |
| 2363 | { "case-sensitive", 0, 0, 0, 0, "off" }, |
| 2364 | #else |
| 2365 | { "case-sensitive", 0, 0, 0, 0, "on" }, |
| 2366 | #endif |
| 2367 | { "clean-glob", 0, 40, 1, 0, "" }, |
| 2368 | { "crnl-glob", 0, 40, 1, 0, "" }, |
| 2369 | { "default-perms", 0, 16, 0, 0, "u" }, |
| 2370 | { "diff-binary", 0, 0, 0, 0, "on" }, |
| 2371 | { "diff-command", 0, 40, 0, 0, "" }, |
| 2372 | { "dont-push", 0, 0, 0, 0, "off" }, |
| @@ -2407,10 +2423,41 @@ | |
| 2407 | { "th1-uri-regexp", 0, 40, 1, 0, "" }, |
| 2408 | { "web-browser", 0, 32, 0, 0, "" }, |
| 2409 | { "white-foreground", 0, 0, 0, 0, "off" }, |
| 2410 | { 0,0,0,0,0,0 } |
| 2411 | }; |
| 2412 | |
| 2413 | /* |
| 2414 | ** COMMAND: settings |
| 2415 | ** COMMAND: unset* |
| 2416 | ** |
| @@ -2658,61 +2705,65 @@ | |
| 2658 | globalFlag = 1; |
| 2659 | } |
| 2660 | if( unsetFlag && g.argc!=3 ){ |
| 2661 | usage("PROPERTY ?-global?"); |
| 2662 | } |
| 2663 | if( g.argc==2 ){ |
| 2664 | int openLocal = db_open_local(0); |
| 2665 | for(i=0; ctrlSettings[i].name; i++){ |
| 2666 | print_setting(&ctrlSettings[i], openLocal); |
| 2667 | } |
| 2668 | }else if( g.argc==3 || g.argc==4 ){ |
| 2669 | const char *zName = g.argv[2]; |
| 2670 | int isManifest; |
| 2671 | int n = strlen(zName); |
| 2672 | for(i=0; ctrlSettings[i].name; i++){ |
| 2673 | if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; |
| 2674 | } |
| 2675 | if( !ctrlSettings[i].name ){ |
| 2676 | fossil_fatal("no such setting: %s", zName); |
| 2677 | } |
| 2678 | isManifest = fossil_strcmp(ctrlSettings[i].name, "manifest")==0; |
| 2679 | if( isManifest && globalFlag ){ |
| 2680 | fossil_fatal("cannot set 'manifest' globally"); |
| 2681 | } |
| 2682 | if( unsetFlag || g.argc==4 ){ |
| 2683 | if( ctrlSettings[i+1].name |
| 2684 | && strncmp(ctrlSettings[i+1].name, zName, n)==0 |
| 2685 | && ctrlSettings[i].name[n]!=0 |
| 2686 | ){ |
| 2687 | fossil_print("ambiguous property prefix: %s\nMatching properties:\n", |
| 2688 | zName); |
| 2689 | while( ctrlSettings[i].name |
| 2690 | && strncmp(ctrlSettings[i].name, zName, n)==0 |
| 2691 | ){ |
| 2692 | fossil_print("%s\n", ctrlSettings[i].name); |
| 2693 | i++; |
| 2694 | } |
| 2695 | fossil_exit(1); |
| 2696 | }else{ |
| 2697 | if( unsetFlag ){ |
| 2698 | db_unset(ctrlSettings[i].name, globalFlag); |
| 2699 | }else{ |
| 2700 | db_set(ctrlSettings[i].name, g.argv[3], globalFlag); |
| 2701 | } |
| 2702 | } |
| 2703 | }else{ |
| 2704 | isManifest = 0; |
| 2705 | while( ctrlSettings[i].name |
| 2706 | && strncmp(ctrlSettings[i].name, zName, n)==0 |
| 2707 | ){ |
| 2708 | print_setting(&ctrlSettings[i], db_open_local(0)); |
| 2709 | i++; |
| 2710 | } |
| 2711 | } |
| 2712 | if( isManifest && g.localOpen ){ |
| 2713 | manifest_to_disk(db_lget_int("checkout", 0)); |
| 2714 | } |
| 2715 | }else{ |
| 2716 | usage("?PROPERTY? ?VALUE? ?-global?"); |
| 2717 | } |
| 2718 | } |
| 2719 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -547,12 +547,13 @@ | |
| 547 | va_end(ap); |
| 548 | z = blob_str(&sql); |
| 549 | while( rc==SQLITE_OK && z[0] ){ |
| 550 | pStmt = 0; |
| 551 | rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd); |
| 552 | if( rc ){ |
| 553 | db_err("%s: {%s}", sqlite3_errmsg(g.db), z); |
| 554 | }else if( pStmt ){ |
| 555 | db.nPrepare++; |
| 556 | while( sqlite3_step(pStmt)==SQLITE_ROW ){} |
| 557 | rc = sqlite3_finalize(pStmt); |
| 558 | if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z); |
| 559 | } |
| @@ -1197,11 +1198,11 @@ | |
| 1198 | */ |
| 1199 | if( !db_table_has_column("repository","mlink","isaux") ){ |
| 1200 | db_begin_transaction(); |
| 1201 | db_multi_exec( |
| 1202 | "ALTER TABLE %s.mlink ADD COLUMN pmid INTEGER DEFAULT 0;" |
| 1203 | "ALTER TABLE %s.mlink ADD COLUMN isaux BOOLEAN DEFAULT 0;", |
| 1204 | db_name("repository"), db_name("repository") |
| 1205 | ); |
| 1206 | db_end_transaction(0); |
| 1207 | } |
| 1208 | } |
| @@ -1380,15 +1381,10 @@ | |
| 1381 | while( db.pAllStmt ){ |
| 1382 | db_finalize(db.pAllStmt); |
| 1383 | } |
| 1384 | db_end_transaction(1); |
| 1385 | pStmt = 0; |
| 1386 | db_close_config(); |
| 1387 | |
| 1388 | /* If the localdb (the check-out database) is open and if it has |
| 1389 | ** a lot of unused free space, then VACUUM it as we shut down. |
| 1390 | */ |
| @@ -1399,12 +1395,18 @@ | |
| 1395 | db_multi_exec("VACUUM;"); |
| 1396 | } |
| 1397 | } |
| 1398 | |
| 1399 | if( g.db ){ |
| 1400 | int rc; |
| 1401 | sqlite3_wal_checkpoint(g.db, 0); |
| 1402 | rc = sqlite3_close(g.db); |
| 1403 | if( rc==SQLITE_BUSY && reportErrors ){ |
| 1404 | while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){ |
| 1405 | fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt)); |
| 1406 | } |
| 1407 | } |
| 1408 | g.db = 0; |
| 1409 | g.zMainDbType = 0; |
| 1410 | } |
| 1411 | g.repositoryOpen = 0; |
| 1412 | g.localOpen = 0; |
| @@ -1520,12 +1522,12 @@ | |
| 1522 | int i; |
| 1523 | const char *zSep = ""; |
| 1524 | |
| 1525 | blob_zero(&x); |
| 1526 | blob_append_sql(&x, "("); |
| 1527 | for(i=0; aSetting[i].name; i++){ |
| 1528 | blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, aSetting[i].name); |
| 1529 | zSep = ","; |
| 1530 | } |
| 1531 | blob_append_sql(&x, ")"); |
| 1532 | return blob_sql_text(&x); |
| 1533 | } |
| @@ -1682,10 +1684,15 @@ | |
| 1684 | verify_all_options(); |
| 1685 | |
| 1686 | if( g.argc!=3 ){ |
| 1687 | usage("REPOSITORY-NAME"); |
| 1688 | } |
| 1689 | |
| 1690 | if( -1 != file_size(g.argv[2]) ){ |
| 1691 | fossil_fatal("file already exists: %s", g.argv[2]); |
| 1692 | } |
| 1693 | |
| 1694 | db_create_repository(g.argv[2]); |
| 1695 | db_open_repository(g.argv[2]); |
| 1696 | db_open_config(0); |
| 1697 | if( zTemplate ) db_attach(zTemplate, "settingSrc"); |
| 1698 | db_begin_transaction(); |
| @@ -1913,16 +1920,21 @@ | |
| 1920 | g.zConfigDbType = zTempDbType; |
| 1921 | } |
| 1922 | } |
| 1923 | |
| 1924 | /* |
| 1925 | ** Try to read a versioned setting string from .fossil-settings/<name>. |
| 1926 | ** |
| 1927 | ** Return the text of the string if it is found. Return NULL if not |
| 1928 | ** found. |
| 1929 | ** |
| 1930 | ** If the zNonVersionedSetting parameter is not NULL then it holds the |
| 1931 | ** non-versioned value for this setting. If both a versioned and ad |
| 1932 | ** non-versioned value exist and are not equal, then a warning message |
| 1933 | ** might be generated. |
| 1934 | */ |
| 1935 | char *db_get_versioned(const char *zName, char *zNonVersionedSetting){ |
| 1936 | char *zVersionedSetting = 0; |
| 1937 | int noWarn = 0; |
| 1938 | struct _cacheEntry { |
| 1939 | struct _cacheEntry *next; |
| 1940 | const char *zName, *zValue; |
| @@ -1993,37 +2005,38 @@ | |
| 2005 | |
| 2006 | |
| 2007 | /* |
| 2008 | ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the |
| 2009 | ** repository and local databases. |
| 2010 | ** |
| 2011 | ** If no such variable exists, return zDefault. Or, if zName is the name |
| 2012 | ** of a setting, then the zDefault is ignored and the default value of the |
| 2013 | ** setting is returned instead. If zName is a versioned setting, then |
| 2014 | ** versioned value takes priority. |
| 2015 | */ |
| 2016 | char *db_get(const char *zName, char *zDefault){ |
| 2017 | char *z = 0; |
| 2018 | const Setting *pSetting = db_find_setting(zName, 0); |
| 2019 | if( g.repositoryOpen ){ |
| 2020 | z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName); |
| 2021 | } |
| 2022 | if( z==0 && g.zConfigDbName ){ |
| 2023 | db_swap_connections(); |
| 2024 | z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName); |
| 2025 | db_swap_connections(); |
| 2026 | } |
| 2027 | if( pSetting!=0 && pSetting->versionable ){ |
| 2028 | /* This is a versionable setting, try and get the info from a |
| 2029 | ** checked out file */ |
| 2030 | z = db_get_versioned(zName, z); |
| 2031 | } |
| 2032 | if( z==0 ){ |
| 2033 | if( zDefault==0 && pSetting && pSetting->def[0] ){ |
| 2034 | z = fossil_strdup(pSetting->def); |
| 2035 | }else{ |
| 2036 | z = zDefault; |
| 2037 | } |
| 2038 | } |
| 2039 | return z; |
| 2040 | } |
| 2041 | char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){ |
| 2042 | char *z = 0; |
| @@ -2285,88 +2298,91 @@ | |
| 2298 | g.argc = 2; |
| 2299 | info_cmd(); |
| 2300 | } |
| 2301 | |
| 2302 | /* |
| 2303 | ** Print the current value of a setting identified by the pSetting |
| 2304 | ** pointer. |
| 2305 | */ |
| 2306 | static void print_setting(const Setting *pSetting){ |
| 2307 | Stmt q; |
| 2308 | if( g.repositoryOpen ){ |
| 2309 | db_prepare(&q, |
| 2310 | "SELECT '(local)', value FROM config WHERE name=%Q" |
| 2311 | " UNION ALL " |
| 2312 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2313 | pSetting->name, pSetting->name |
| 2314 | ); |
| 2315 | }else{ |
| 2316 | db_prepare(&q, |
| 2317 | "SELECT '(global)', value FROM global_config WHERE name=%Q", |
| 2318 | pSetting->name |
| 2319 | ); |
| 2320 | } |
| 2321 | if( db_step(&q)==SQLITE_ROW ){ |
| 2322 | fossil_print("%-20s %-8s %s\n", pSetting->name, db_column_text(&q, 0), |
| 2323 | db_column_text(&q, 1)); |
| 2324 | }else{ |
| 2325 | fossil_print("%-20s\n", pSetting->name); |
| 2326 | } |
| 2327 | if( pSetting->versionable && g.localOpen ){ |
| 2328 | /* Check to see if this is overridden by a versionable settings file */ |
| 2329 | Blob versionedPathname; |
| 2330 | blob_zero(&versionedPathname); |
| 2331 | blob_appendf(&versionedPathname, "%s/.fossil-settings/%s", |
| 2332 | g.zLocalRoot, pSetting->name); |
| 2333 | if( file_size(blob_str(&versionedPathname))>=0 ){ |
| 2334 | fossil_print(" (overridden by contents of file .fossil-settings/%s)\n", |
| 2335 | pSetting->name); |
| 2336 | } |
| 2337 | } |
| 2338 | db_finalize(&q); |
| 2339 | } |
| 2340 | |
| 2341 | |
| 2342 | #if INTERFACE |
| 2343 | /* |
| 2344 | ** Define all settings, which can be controlled via the set/unset |
| 2345 | ** command. |
| 2346 | ** |
| 2347 | ** var is the name of the internal configuration name for db_(un)set. |
| 2348 | ** If var is 0, the settings name is used. |
| 2349 | ** |
| 2350 | ** width is the length for the edit field on the behavior page, 0 |
| 2351 | ** is used for on/off checkboxes. |
| 2352 | ** |
| 2353 | ** The behaviour page doesn't use a special layout. It lists all |
| 2354 | ** set-commands and displays the 'set'-help as info. |
| 2355 | */ |
| 2356 | struct Setting { |
| 2357 | const char *name; /* Name of the setting */ |
| 2358 | const char *var; /* Internal variable name used by db_set() */ |
| 2359 | int width; /* Width of display. 0 for boolean values. */ |
| 2360 | int versionable; /* Is this setting versionable? */ |
| 2361 | int forceTextArea; /* Force using a text area for display? */ |
| 2362 | const char *def; /* Default value */ |
| 2363 | }; |
| 2364 | #endif /* INTERFACE */ |
| 2365 | |
| 2366 | const Setting aSetting[] = { |
| 2367 | { "access-log", 0, 0, 0, 0, "off" }, |
| 2368 | { "admin-log", 0, 0, 0, 0, "off" }, |
| 2369 | { "allow-symlinks", 0, 0, 1, 0, "off" }, |
| 2370 | { "auto-captcha", "autocaptcha", 0, 0, 0, "on" }, |
| 2371 | { "auto-hyperlink", 0, 0, 0, 0, "on", }, |
| 2372 | { "auto-shun", 0, 0, 0, 0, "on" }, |
| 2373 | { "autosync", 0, 0, 0, 0, "on" }, |
| 2374 | { "autosync-tries", 0, 16, 0, 0, "1" }, |
| 2375 | { "binary-glob", 0, 40, 1, 0, "" }, |
| 2376 | #if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \ |
| 2377 | defined(__APPLE__) |
| 2378 | { "case-sensitive", 0, 0, 0, 0, "off" }, |
| 2379 | #else |
| 2380 | { "case-sensitive", 0, 0, 0, 0, "on" }, |
| 2381 | #endif |
| 2382 | { "clean-glob", 0, 40, 1, 0, "" }, |
| 2383 | { "clearsign", 0, 0, 0, 0, "off" }, |
| 2384 | { "crnl-glob", 0, 40, 1, 0, "" }, |
| 2385 | { "default-perms", 0, 16, 0, 0, "u" }, |
| 2386 | { "diff-binary", 0, 0, 0, 0, "on" }, |
| 2387 | { "diff-command", 0, 40, 0, 0, "" }, |
| 2388 | { "dont-push", 0, 0, 0, 0, "off" }, |
| @@ -2407,10 +2423,41 @@ | |
| 2423 | { "th1-uri-regexp", 0, 40, 1, 0, "" }, |
| 2424 | { "web-browser", 0, 32, 0, 0, "" }, |
| 2425 | { "white-foreground", 0, 0, 0, 0, "off" }, |
| 2426 | { 0,0,0,0,0,0 } |
| 2427 | }; |
| 2428 | |
| 2429 | /* |
| 2430 | ** Look up a control setting by its name. Return a pointer to the Setting |
| 2431 | ** object, or NULL if there is no such setting. |
| 2432 | ** |
| 2433 | ** If allowPrefix is true, then the Setting returned is the first one for |
| 2434 | ** which zName is a prefix of the Setting name. |
| 2435 | */ |
| 2436 | const Setting *db_find_setting(const char *zName, int allowPrefix){ |
| 2437 | int lwr, mid, upr, c; |
| 2438 | int n = (int)strlen(zName) + !allowPrefix; |
| 2439 | lwr = 0; |
| 2440 | upr = ArraySize(aSetting)-2; |
| 2441 | while( upr>=lwr ){ |
| 2442 | mid = (upr+lwr)/2; |
| 2443 | c = fossil_strncmp(zName, aSetting[mid].name, n); |
| 2444 | if( c<0 ){ |
| 2445 | upr = mid - 1; |
| 2446 | }else if( c>0 ){ |
| 2447 | lwr = mid + 1; |
| 2448 | }else{ |
| 2449 | if( allowPrefix ){ |
| 2450 | while( mid>lwr && fossil_strncmp(zName, aSetting[mid-1].name, n)==0 ){ |
| 2451 | mid--; |
| 2452 | } |
| 2453 | } |
| 2454 | return &aSetting[mid]; |
| 2455 | } |
| 2456 | } |
| 2457 | return 0; |
| 2458 | } |
| 2459 | |
| 2460 | /* |
| 2461 | ** COMMAND: settings |
| 2462 | ** COMMAND: unset* |
| 2463 | ** |
| @@ -2658,61 +2705,65 @@ | |
| 2705 | globalFlag = 1; |
| 2706 | } |
| 2707 | if( unsetFlag && g.argc!=3 ){ |
| 2708 | usage("PROPERTY ?-global?"); |
| 2709 | } |
| 2710 | |
| 2711 | /* Verify that the aSetting[] entries are in sorted order. This is |
| 2712 | ** necessary for the binary search in db_find_setting() to work correctly. |
| 2713 | */ |
| 2714 | for(i=1; aSetting[i].name; i++){ |
| 2715 | if( fossil_strcmp(aSetting[i-1].name, aSetting[i].name)>=0 ){ |
| 2716 | fossil_panic("Internal Error: aSetting[] entries for \"%s\"" |
| 2717 | " and \"%s\" are out of order.", |
| 2718 | aSetting[i-1].name, aSetting[i].name); |
| 2719 | } |
| 2720 | } |
| 2721 | |
| 2722 | if( g.argc==2 ){ |
| 2723 | for(i=0; aSetting[i].name; i++){ |
| 2724 | print_setting(&aSetting[i]); |
| 2725 | } |
| 2726 | }else if( g.argc==3 || g.argc==4 ){ |
| 2727 | const char *zName = g.argv[2]; |
| 2728 | int n = (int)strlen(zName); |
| 2729 | const Setting *pSetting = db_find_setting(zName, 1); |
| 2730 | if( pSetting==0 ){ |
| 2731 | fossil_fatal("no such setting: %s", zName); |
| 2732 | } |
| 2733 | if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ |
| 2734 | fossil_fatal("cannot set 'manifest' globally"); |
| 2735 | } |
| 2736 | if( unsetFlag || g.argc==4 ){ |
| 2737 | int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; |
| 2738 | if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){ |
| 2739 | Blob x; |
| 2740 | int i; |
| 2741 | blob_init(&x,0,0); |
| 2742 | for(i=0; pSetting[i].name; i++){ |
| 2743 | if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; |
| 2744 | blob_appendf(&x, " %s", pSetting[i].name); |
| 2745 | } |
| 2746 | fossil_fatal("ambiguous setting \"%s\" - might be:%s", |
| 2747 | zName, blob_str(&x)); |
| 2748 | } |
| 2749 | if( globalFlag && isManifest ){ |
| 2750 | fossil_fatal("cannot set 'manifest' globally"); |
| 2751 | } |
| 2752 | if( unsetFlag ){ |
| 2753 | db_unset(pSetting->name, globalFlag); |
| 2754 | }else{ |
| 2755 | db_set(pSetting->name, g.argv[3], globalFlag); |
| 2756 | } |
| 2757 | if( isManifest && g.localOpen ){ |
| 2758 | manifest_to_disk(db_lget_int("checkout", 0)); |
| 2759 | } |
| 2760 | }else{ |
| 2761 | while( pSetting->name && fossil_strncmp(pSetting->name,zName,n)==0 ){ |
| 2762 | print_setting(pSetting); |
| 2763 | pSetting++; |
| 2764 | } |
| 2765 | } |
| 2766 | }else{ |
| 2767 | usage("?PROPERTY? ?VALUE? ?-global?"); |
| 2768 | } |
| 2769 | } |
| 2770 |
+424
-289
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -65,17 +65,233 @@ | ||
| 65 | 65 | } |
| 66 | 66 | } |
| 67 | 67 | if( i>=n ){ |
| 68 | 68 | return 0; /* Plain text */ |
| 69 | 69 | } |
| 70 | - for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){ | |
| 70 | + for(i=0; i<ArraySize(aMime); i++){ | |
| 71 | 71 | if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){ |
| 72 | 72 | return aMime[i].zMimetype; |
| 73 | 73 | } |
| 74 | 74 | } |
| 75 | 75 | return "unknown/unknown"; |
| 76 | 76 | } |
| 77 | + | |
| 78 | +/* A table of mimetypes based on file suffixes. | |
| 79 | +** Suffixes must be in sorted order so that we can do a binary | |
| 80 | +** search to find the mime-type | |
| 81 | +*/ | |
| 82 | +static const struct { | |
| 83 | + const char *zSuffix; /* The file suffix */ | |
| 84 | + int size; /* Length of the suffix */ | |
| 85 | + const char *zMimetype; /* The corresponding mimetype */ | |
| 86 | +} aMime[] = { | |
| 87 | + { "ai", 2, "application/postscript" }, | |
| 88 | + { "aif", 3, "audio/x-aiff" }, | |
| 89 | + { "aifc", 4, "audio/x-aiff" }, | |
| 90 | + { "aiff", 4, "audio/x-aiff" }, | |
| 91 | + { "arj", 3, "application/x-arj-compressed" }, | |
| 92 | + { "asc", 3, "text/plain" }, | |
| 93 | + { "asf", 3, "video/x-ms-asf" }, | |
| 94 | + { "asx", 3, "video/x-ms-asx" }, | |
| 95 | + { "au", 2, "audio/ulaw" }, | |
| 96 | + { "avi", 3, "video/x-msvideo" }, | |
| 97 | + { "bat", 3, "application/x-msdos-program" }, | |
| 98 | + { "bcpio", 5, "application/x-bcpio" }, | |
| 99 | + { "bin", 3, "application/octet-stream" }, | |
| 100 | + { "c", 1, "text/plain" }, | |
| 101 | + { "cc", 2, "text/plain" }, | |
| 102 | + { "ccad", 4, "application/clariscad" }, | |
| 103 | + { "cdf", 3, "application/x-netcdf" }, | |
| 104 | + { "class", 5, "application/octet-stream" }, | |
| 105 | + { "cod", 3, "application/vnd.rim.cod" }, | |
| 106 | + { "com", 3, "application/x-msdos-program" }, | |
| 107 | + { "cpio", 4, "application/x-cpio" }, | |
| 108 | + { "cpt", 3, "application/mac-compactpro" }, | |
| 109 | + { "csh", 3, "application/x-csh" }, | |
| 110 | + { "css", 3, "text/css" }, | |
| 111 | + { "dcr", 3, "application/x-director" }, | |
| 112 | + { "deb", 3, "application/x-debian-package" }, | |
| 113 | + { "dir", 3, "application/x-director" }, | |
| 114 | + { "dl", 2, "video/dl" }, | |
| 115 | + { "dms", 3, "application/octet-stream" }, | |
| 116 | + { "doc", 3, "application/msword" }, | |
| 117 | + { "docx", 4, "application/vnd.openxmlformats-" | |
| 118 | + "officedocument.wordprocessingml.document"}, | |
| 119 | + { "dot", 3, "application/msword" }, | |
| 120 | + { "dotx", 4, "application/vnd.openxmlformats-" | |
| 121 | + "officedocument.wordprocessingml.template"}, | |
| 122 | + { "drw", 3, "application/drafting" }, | |
| 123 | + { "dvi", 3, "application/x-dvi" }, | |
| 124 | + { "dwg", 3, "application/acad" }, | |
| 125 | + { "dxf", 3, "application/dxf" }, | |
| 126 | + { "dxr", 3, "application/x-director" }, | |
| 127 | + { "eps", 3, "application/postscript" }, | |
| 128 | + { "etx", 3, "text/x-setext" }, | |
| 129 | + { "exe", 3, "application/octet-stream" }, | |
| 130 | + { "ez", 2, "application/andrew-inset" }, | |
| 131 | + { "f", 1, "text/plain" }, | |
| 132 | + { "f90", 3, "text/plain" }, | |
| 133 | + { "fli", 3, "video/fli" }, | |
| 134 | + { "flv", 3, "video/flv" }, | |
| 135 | + { "gif", 3, "image/gif" }, | |
| 136 | + { "gl", 2, "video/gl" }, | |
| 137 | + { "gtar", 4, "application/x-gtar" }, | |
| 138 | + { "gz", 2, "application/x-gzip" }, | |
| 139 | + { "h", 1, "text/plain" }, | |
| 140 | + { "hdf", 3, "application/x-hdf" }, | |
| 141 | + { "hh", 2, "text/plain" }, | |
| 142 | + { "hqx", 3, "application/mac-binhex40" }, | |
| 143 | + { "htm", 3, "text/html" }, | |
| 144 | + { "html", 4, "text/html" }, | |
| 145 | + { "ice", 3, "x-conference/x-cooltalk" }, | |
| 146 | + { "ief", 3, "image/ief" }, | |
| 147 | + { "iges", 4, "model/iges" }, | |
| 148 | + { "igs", 3, "model/iges" }, | |
| 149 | + { "ips", 3, "application/x-ipscript" }, | |
| 150 | + { "ipx", 3, "application/x-ipix" }, | |
| 151 | + { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, | |
| 152 | + { "jar", 3, "application/java-archive" }, | |
| 153 | + { "jpe", 3, "image/jpeg" }, | |
| 154 | + { "jpeg", 4, "image/jpeg" }, | |
| 155 | + { "jpg", 3, "image/jpeg" }, | |
| 156 | + { "js", 2, "application/x-javascript" }, | |
| 157 | + { "kar", 3, "audio/midi" }, | |
| 158 | + { "latex", 5, "application/x-latex" }, | |
| 159 | + { "lha", 3, "application/octet-stream" }, | |
| 160 | + { "lsp", 3, "application/x-lisp" }, | |
| 161 | + { "lzh", 3, "application/octet-stream" }, | |
| 162 | + { "m", 1, "text/plain" }, | |
| 163 | + { "m3u", 3, "audio/x-mpegurl" }, | |
| 164 | + { "man", 3, "application/x-troff-man" }, | |
| 165 | + { "markdown", 8, "text/x-markdown" }, | |
| 166 | + { "md", 2, "text/x-markdown" }, | |
| 167 | + { "me", 2, "application/x-troff-me" }, | |
| 168 | + { "mesh", 4, "model/mesh" }, | |
| 169 | + { "mid", 3, "audio/midi" }, | |
| 170 | + { "midi", 4, "audio/midi" }, | |
| 171 | + { "mif", 3, "application/x-mif" }, | |
| 172 | + { "mime", 4, "www/mime" }, | |
| 173 | + { "mkd", 3, "text/x-markdown" }, | |
| 174 | + { "mov", 3, "video/quicktime" }, | |
| 175 | + { "movie", 5, "video/x-sgi-movie" }, | |
| 176 | + { "mp2", 3, "audio/mpeg" }, | |
| 177 | + { "mp3", 3, "audio/mpeg" }, | |
| 178 | + { "mp4", 3, "video/mp4" }, | |
| 179 | + { "mpe", 3, "video/mpeg" }, | |
| 180 | + { "mpeg", 4, "video/mpeg" }, | |
| 181 | + { "mpg", 3, "video/mpeg" }, | |
| 182 | + { "mpga", 4, "audio/mpeg" }, | |
| 183 | + { "ms", 2, "application/x-troff-ms" }, | |
| 184 | + { "msh", 3, "model/mesh" }, | |
| 185 | + { "nc", 2, "application/x-netcdf" }, | |
| 186 | + { "oda", 3, "application/oda" }, | |
| 187 | + { "ogg", 3, "application/ogg" }, | |
| 188 | + { "ogm", 3, "application/ogg" }, | |
| 189 | + { "pbm", 3, "image/x-portable-bitmap" }, | |
| 190 | + { "pdb", 3, "chemical/x-pdb" }, | |
| 191 | + { "pdf", 3, "application/pdf" }, | |
| 192 | + { "pgm", 3, "image/x-portable-graymap" }, | |
| 193 | + { "pgn", 3, "application/x-chess-pgn" }, | |
| 194 | + { "pgp", 3, "application/pgp" }, | |
| 195 | + { "pl", 2, "application/x-perl" }, | |
| 196 | + { "pm", 2, "application/x-perl" }, | |
| 197 | + { "png", 3, "image/png" }, | |
| 198 | + { "pnm", 3, "image/x-portable-anymap" }, | |
| 199 | + { "pot", 3, "application/mspowerpoint" }, | |
| 200 | + { "potx", 4, "application/vnd.openxmlformats-" | |
| 201 | + "officedocument.presentationml.template"}, | |
| 202 | + { "ppm", 3, "image/x-portable-pixmap" }, | |
| 203 | + { "pps", 3, "application/mspowerpoint" }, | |
| 204 | + { "ppsx", 4, "application/vnd.openxmlformats-" | |
| 205 | + "officedocument.presentationml.slideshow"}, | |
| 206 | + { "ppt", 3, "application/mspowerpoint" }, | |
| 207 | + { "pptx", 4, "application/vnd.openxmlformats-" | |
| 208 | + "officedocument.presentationml.presentation"}, | |
| 209 | + { "ppz", 3, "application/mspowerpoint" }, | |
| 210 | + { "pre", 3, "application/x-freelance" }, | |
| 211 | + { "prt", 3, "application/pro_eng" }, | |
| 212 | + { "ps", 2, "application/postscript" }, | |
| 213 | + { "qt", 2, "video/quicktime" }, | |
| 214 | + { "ra", 2, "audio/x-realaudio" }, | |
| 215 | + { "ram", 3, "audio/x-pn-realaudio" }, | |
| 216 | + { "rar", 3, "application/x-rar-compressed" }, | |
| 217 | + { "ras", 3, "image/cmu-raster" }, | |
| 218 | + { "rgb", 3, "image/x-rgb" }, | |
| 219 | + { "rm", 2, "audio/x-pn-realaudio" }, | |
| 220 | + { "roff", 4, "application/x-troff" }, | |
| 221 | + { "rpm", 3, "audio/x-pn-realaudio-plugin" }, | |
| 222 | + { "rtf", 3, "text/rtf" }, | |
| 223 | + { "rtx", 3, "text/richtext" }, | |
| 224 | + { "scm", 3, "application/x-lotusscreencam" }, | |
| 225 | + { "set", 3, "application/set" }, | |
| 226 | + { "sgm", 3, "text/sgml" }, | |
| 227 | + { "sgml", 4, "text/sgml" }, | |
| 228 | + { "sh", 2, "application/x-sh" }, | |
| 229 | + { "shar", 4, "application/x-shar" }, | |
| 230 | + { "silo", 4, "model/mesh" }, | |
| 231 | + { "sit", 3, "application/x-stuffit" }, | |
| 232 | + { "skd", 3, "application/x-koan" }, | |
| 233 | + { "skm", 3, "application/x-koan" }, | |
| 234 | + { "skp", 3, "application/x-koan" }, | |
| 235 | + { "skt", 3, "application/x-koan" }, | |
| 236 | + { "smi", 3, "application/smil" }, | |
| 237 | + { "smil", 4, "application/smil" }, | |
| 238 | + { "snd", 3, "audio/basic" }, | |
| 239 | + { "sol", 3, "application/solids" }, | |
| 240 | + { "spl", 3, "application/x-futuresplash" }, | |
| 241 | + { "src", 3, "application/x-wais-source" }, | |
| 242 | + { "step", 4, "application/STEP" }, | |
| 243 | + { "stl", 3, "application/SLA" }, | |
| 244 | + { "stp", 3, "application/STEP" }, | |
| 245 | + { "sv4cpio", 7, "application/x-sv4cpio" }, | |
| 246 | + { "sv4crc", 6, "application/x-sv4crc" }, | |
| 247 | + { "svg", 3, "image/svg+xml" }, | |
| 248 | + { "swf", 3, "application/x-shockwave-flash" }, | |
| 249 | + { "t", 1, "application/x-troff" }, | |
| 250 | + { "tar", 3, "application/x-tar" }, | |
| 251 | + { "tcl", 3, "application/x-tcl" }, | |
| 252 | + { "tex", 3, "application/x-tex" }, | |
| 253 | + { "texi", 4, "application/x-texinfo" }, | |
| 254 | + { "texinfo", 7, "application/x-texinfo" }, | |
| 255 | + { "tgz", 3, "application/x-tar-gz" }, | |
| 256 | + { "th1", 3, "application/x-th1" }, | |
| 257 | + { "tif", 3, "image/tiff" }, | |
| 258 | + { "tiff", 4, "image/tiff" }, | |
| 259 | + { "tr", 2, "application/x-troff" }, | |
| 260 | + { "tsi", 3, "audio/TSP-audio" }, | |
| 261 | + { "tsp", 3, "application/dsptype" }, | |
| 262 | + { "tsv", 3, "text/tab-separated-values" }, | |
| 263 | + { "txt", 3, "text/plain" }, | |
| 264 | + { "unv", 3, "application/i-deas" }, | |
| 265 | + { "ustar", 5, "application/x-ustar" }, | |
| 266 | + { "vcd", 3, "application/x-cdlink" }, | |
| 267 | + { "vda", 3, "application/vda" }, | |
| 268 | + { "viv", 3, "video/vnd.vivo" }, | |
| 269 | + { "vivo", 4, "video/vnd.vivo" }, | |
| 270 | + { "vrml", 4, "model/vrml" }, | |
| 271 | + { "wav", 3, "audio/x-wav" }, | |
| 272 | + { "wax", 3, "audio/x-ms-wax" }, | |
| 273 | + { "wiki", 4, "text/x-fossil-wiki" }, | |
| 274 | + { "wma", 3, "audio/x-ms-wma" }, | |
| 275 | + { "wmv", 3, "video/x-ms-wmv" }, | |
| 276 | + { "wmx", 3, "video/x-ms-wmx" }, | |
| 277 | + { "wrl", 3, "model/vrml" }, | |
| 278 | + { "wvx", 3, "video/x-ms-wvx" }, | |
| 279 | + { "xbm", 3, "image/x-xbitmap" }, | |
| 280 | + { "xlc", 3, "application/vnd.ms-excel" }, | |
| 281 | + { "xll", 3, "application/vnd.ms-excel" }, | |
| 282 | + { "xlm", 3, "application/vnd.ms-excel" }, | |
| 283 | + { "xls", 3, "application/vnd.ms-excel" }, | |
| 284 | + { "xlsx", 4, "application/vnd.openxmlformats-" | |
| 285 | + "officedocument.spreadsheetml.sheet"}, | |
| 286 | + { "xlw", 3, "application/vnd.ms-excel" }, | |
| 287 | + { "xml", 3, "text/xml" }, | |
| 288 | + { "xpm", 3, "image/x-xpixmap" }, | |
| 289 | + { "xwd", 3, "image/x-xwindowdump" }, | |
| 290 | + { "xyz", 3, "chemical/x-pdb" }, | |
| 291 | + { "zip", 3, "application/zip" }, | |
| 292 | +}; | |
| 77 | 293 | |
| 78 | 294 | /* |
| 79 | 295 | ** Guess the mime-type of a document based on its name. |
| 80 | 296 | */ |
| 81 | 297 | const char *mimetype_from_name(const char *zName){ |
| @@ -83,226 +299,17 @@ | ||
| 83 | 299 | int i; |
| 84 | 300 | int first, last; |
| 85 | 301 | int len; |
| 86 | 302 | char zSuffix[20]; |
| 87 | 303 | |
| 88 | - /* A table of mimetypes based on file suffixes. | |
| 89 | - ** Suffixes must be in sorted order so that we can do a binary | |
| 90 | - ** search to find the mime-type | |
| 91 | - */ | |
| 92 | - static const struct { | |
| 93 | - const char *zSuffix; /* The file suffix */ | |
| 94 | - int size; /* Length of the suffix */ | |
| 95 | - const char *zMimetype; /* The corresponding mimetype */ | |
| 96 | - } aMime[] = { | |
| 97 | - { "ai", 2, "application/postscript" }, | |
| 98 | - { "aif", 3, "audio/x-aiff" }, | |
| 99 | - { "aifc", 4, "audio/x-aiff" }, | |
| 100 | - { "aiff", 4, "audio/x-aiff" }, | |
| 101 | - { "arj", 3, "application/x-arj-compressed" }, | |
| 102 | - { "asc", 3, "text/plain" }, | |
| 103 | - { "asf", 3, "video/x-ms-asf" }, | |
| 104 | - { "asx", 3, "video/x-ms-asx" }, | |
| 105 | - { "au", 2, "audio/ulaw" }, | |
| 106 | - { "avi", 3, "video/x-msvideo" }, | |
| 107 | - { "bat", 3, "application/x-msdos-program" }, | |
| 108 | - { "bcpio", 5, "application/x-bcpio" }, | |
| 109 | - { "bin", 3, "application/octet-stream" }, | |
| 110 | - { "c", 1, "text/plain" }, | |
| 111 | - { "cc", 2, "text/plain" }, | |
| 112 | - { "ccad", 4, "application/clariscad" }, | |
| 113 | - { "cdf", 3, "application/x-netcdf" }, | |
| 114 | - { "class", 5, "application/octet-stream" }, | |
| 115 | - { "cod", 3, "application/vnd.rim.cod" }, | |
| 116 | - { "com", 3, "application/x-msdos-program" }, | |
| 117 | - { "cpio", 4, "application/x-cpio" }, | |
| 118 | - { "cpt", 3, "application/mac-compactpro" }, | |
| 119 | - { "csh", 3, "application/x-csh" }, | |
| 120 | - { "css", 3, "text/css" }, | |
| 121 | - { "dcr", 3, "application/x-director" }, | |
| 122 | - { "deb", 3, "application/x-debian-package" }, | |
| 123 | - { "dir", 3, "application/x-director" }, | |
| 124 | - { "dl", 2, "video/dl" }, | |
| 125 | - { "dms", 3, "application/octet-stream" }, | |
| 126 | - { "doc", 3, "application/msword" }, | |
| 127 | - { "docx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, | |
| 128 | - { "dot", 3, "application/msword" }, | |
| 129 | - { "dotx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, | |
| 130 | - { "drw", 3, "application/drafting" }, | |
| 131 | - { "dvi", 3, "application/x-dvi" }, | |
| 132 | - { "dwg", 3, "application/acad" }, | |
| 133 | - { "dxf", 3, "application/dxf" }, | |
| 134 | - { "dxr", 3, "application/x-director" }, | |
| 135 | - { "eps", 3, "application/postscript" }, | |
| 136 | - { "etx", 3, "text/x-setext" }, | |
| 137 | - { "exe", 3, "application/octet-stream" }, | |
| 138 | - { "ez", 2, "application/andrew-inset" }, | |
| 139 | - { "f", 1, "text/plain" }, | |
| 140 | - { "f90", 3, "text/plain" }, | |
| 141 | - { "fli", 3, "video/fli" }, | |
| 142 | - { "flv", 3, "video/flv" }, | |
| 143 | - { "gif", 3, "image/gif" }, | |
| 144 | - { "gl", 2, "video/gl" }, | |
| 145 | - { "gtar", 4, "application/x-gtar" }, | |
| 146 | - { "gz", 2, "application/x-gzip" }, | |
| 147 | - { "h", 1, "text/plain" }, | |
| 148 | - { "hdf", 3, "application/x-hdf" }, | |
| 149 | - { "hh", 2, "text/plain" }, | |
| 150 | - { "hqx", 3, "application/mac-binhex40" }, | |
| 151 | - { "htm", 3, "text/html" }, | |
| 152 | - { "html", 4, "text/html" }, | |
| 153 | - { "ice", 3, "x-conference/x-cooltalk" }, | |
| 154 | - { "ief", 3, "image/ief" }, | |
| 155 | - { "iges", 4, "model/iges" }, | |
| 156 | - { "igs", 3, "model/iges" }, | |
| 157 | - { "ips", 3, "application/x-ipscript" }, | |
| 158 | - { "ipx", 3, "application/x-ipix" }, | |
| 159 | - { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, | |
| 160 | - { "jar", 3, "application/java-archive" }, | |
| 161 | - { "jpe", 3, "image/jpeg" }, | |
| 162 | - { "jpeg", 4, "image/jpeg" }, | |
| 163 | - { "jpg", 3, "image/jpeg" }, | |
| 164 | - { "js", 2, "application/x-javascript" }, | |
| 165 | - { "kar", 3, "audio/midi" }, | |
| 166 | - { "latex", 5, "application/x-latex" }, | |
| 167 | - { "lha", 3, "application/octet-stream" }, | |
| 168 | - { "lsp", 3, "application/x-lisp" }, | |
| 169 | - { "lzh", 3, "application/octet-stream" }, | |
| 170 | - { "m", 1, "text/plain" }, | |
| 171 | - { "m3u", 3, "audio/x-mpegurl" }, | |
| 172 | - { "man", 3, "application/x-troff-man" }, | |
| 173 | - { "markdown", 8, "text/x-markdown" }, | |
| 174 | - { "md", 2, "text/x-markdown" }, | |
| 175 | - { "me", 2, "application/x-troff-me" }, | |
| 176 | - { "mesh", 4, "model/mesh" }, | |
| 177 | - { "mid", 3, "audio/midi" }, | |
| 178 | - { "midi", 4, "audio/midi" }, | |
| 179 | - { "mif", 3, "application/x-mif" }, | |
| 180 | - { "mime", 4, "www/mime" }, | |
| 181 | - { "mkd", 3, "text/x-markdown" }, | |
| 182 | - { "mov", 3, "video/quicktime" }, | |
| 183 | - { "movie", 5, "video/x-sgi-movie" }, | |
| 184 | - { "mp2", 3, "audio/mpeg" }, | |
| 185 | - { "mp3", 3, "audio/mpeg" }, | |
| 186 | - { "mp4", 3, "video/mp4" }, | |
| 187 | - { "mpe", 3, "video/mpeg" }, | |
| 188 | - { "mpeg", 4, "video/mpeg" }, | |
| 189 | - { "mpg", 3, "video/mpeg" }, | |
| 190 | - { "mpga", 4, "audio/mpeg" }, | |
| 191 | - { "ms", 2, "application/x-troff-ms" }, | |
| 192 | - { "msh", 3, "model/mesh" }, | |
| 193 | - { "nc", 2, "application/x-netcdf" }, | |
| 194 | - { "oda", 3, "application/oda" }, | |
| 195 | - { "ogg", 3, "application/ogg" }, | |
| 196 | - { "ogm", 3, "application/ogg" }, | |
| 197 | - { "pbm", 3, "image/x-portable-bitmap" }, | |
| 198 | - { "pdb", 3, "chemical/x-pdb" }, | |
| 199 | - { "pdf", 3, "application/pdf" }, | |
| 200 | - { "pgm", 3, "image/x-portable-graymap" }, | |
| 201 | - { "pgn", 3, "application/x-chess-pgn" }, | |
| 202 | - { "pgp", 3, "application/pgp" }, | |
| 203 | - { "pl", 2, "application/x-perl" }, | |
| 204 | - { "pm", 2, "application/x-perl" }, | |
| 205 | - { "png", 3, "image/png" }, | |
| 206 | - { "pnm", 3, "image/x-portable-anymap" }, | |
| 207 | - { "pot", 3, "application/mspowerpoint" }, | |
| 208 | - { "potx", 4, "application/vnd.openxmlformats-officedocument.presentationml.template"}, | |
| 209 | - { "ppm", 3, "image/x-portable-pixmap" }, | |
| 210 | - { "pps", 3, "application/mspowerpoint" }, | |
| 211 | - { "ppsx", 4, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, | |
| 212 | - { "ppt", 3, "application/mspowerpoint" }, | |
| 213 | - { "pptx", 4, "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, | |
| 214 | - { "ppz", 3, "application/mspowerpoint" }, | |
| 215 | - { "pre", 3, "application/x-freelance" }, | |
| 216 | - { "prt", 3, "application/pro_eng" }, | |
| 217 | - { "ps", 2, "application/postscript" }, | |
| 218 | - { "qt", 2, "video/quicktime" }, | |
| 219 | - { "ra", 2, "audio/x-realaudio" }, | |
| 220 | - { "ram", 3, "audio/x-pn-realaudio" }, | |
| 221 | - { "rar", 3, "application/x-rar-compressed" }, | |
| 222 | - { "ras", 3, "image/cmu-raster" }, | |
| 223 | - { "rgb", 3, "image/x-rgb" }, | |
| 224 | - { "rm", 2, "audio/x-pn-realaudio" }, | |
| 225 | - { "roff", 4, "application/x-troff" }, | |
| 226 | - { "rpm", 3, "audio/x-pn-realaudio-plugin" }, | |
| 227 | - { "rtf", 3, "text/rtf" }, | |
| 228 | - { "rtx", 3, "text/richtext" }, | |
| 229 | - { "scm", 3, "application/x-lotusscreencam" }, | |
| 230 | - { "set", 3, "application/set" }, | |
| 231 | - { "sgm", 3, "text/sgml" }, | |
| 232 | - { "sgml", 4, "text/sgml" }, | |
| 233 | - { "sh", 2, "application/x-sh" }, | |
| 234 | - { "shar", 4, "application/x-shar" }, | |
| 235 | - { "silo", 4, "model/mesh" }, | |
| 236 | - { "sit", 3, "application/x-stuffit" }, | |
| 237 | - { "skd", 3, "application/x-koan" }, | |
| 238 | - { "skm", 3, "application/x-koan" }, | |
| 239 | - { "skp", 3, "application/x-koan" }, | |
| 240 | - { "skt", 3, "application/x-koan" }, | |
| 241 | - { "smi", 3, "application/smil" }, | |
| 242 | - { "smil", 4, "application/smil" }, | |
| 243 | - { "snd", 3, "audio/basic" }, | |
| 244 | - { "sol", 3, "application/solids" }, | |
| 245 | - { "spl", 3, "application/x-futuresplash" }, | |
| 246 | - { "src", 3, "application/x-wais-source" }, | |
| 247 | - { "step", 4, "application/STEP" }, | |
| 248 | - { "stl", 3, "application/SLA" }, | |
| 249 | - { "stp", 3, "application/STEP" }, | |
| 250 | - { "sv4cpio", 7, "application/x-sv4cpio" }, | |
| 251 | - { "sv4crc", 6, "application/x-sv4crc" }, | |
| 252 | - { "svg", 3, "image/svg+xml" }, | |
| 253 | - { "swf", 3, "application/x-shockwave-flash" }, | |
| 254 | - { "t", 1, "application/x-troff" }, | |
| 255 | - { "tar", 3, "application/x-tar" }, | |
| 256 | - { "tcl", 3, "application/x-tcl" }, | |
| 257 | - { "tex", 3, "application/x-tex" }, | |
| 258 | - { "texi", 4, "application/x-texinfo" }, | |
| 259 | - { "texinfo", 7, "application/x-texinfo" }, | |
| 260 | - { "tgz", 3, "application/x-tar-gz" }, | |
| 261 | - { "th1", 3, "application/x-th1" }, | |
| 262 | - { "tif", 3, "image/tiff" }, | |
| 263 | - { "tiff", 4, "image/tiff" }, | |
| 264 | - { "tr", 2, "application/x-troff" }, | |
| 265 | - { "tsi", 3, "audio/TSP-audio" }, | |
| 266 | - { "tsp", 3, "application/dsptype" }, | |
| 267 | - { "tsv", 3, "text/tab-separated-values" }, | |
| 268 | - { "txt", 3, "text/plain" }, | |
| 269 | - { "unv", 3, "application/i-deas" }, | |
| 270 | - { "ustar", 5, "application/x-ustar" }, | |
| 271 | - { "vcd", 3, "application/x-cdlink" }, | |
| 272 | - { "vda", 3, "application/vda" }, | |
| 273 | - { "viv", 3, "video/vnd.vivo" }, | |
| 274 | - { "vivo", 4, "video/vnd.vivo" }, | |
| 275 | - { "vrml", 4, "model/vrml" }, | |
| 276 | - { "wav", 3, "audio/x-wav" }, | |
| 277 | - { "wax", 3, "audio/x-ms-wax" }, | |
| 278 | - { "wiki", 4, "text/x-fossil-wiki" }, | |
| 279 | - { "wma", 3, "audio/x-ms-wma" }, | |
| 280 | - { "wmv", 3, "video/x-ms-wmv" }, | |
| 281 | - { "wmx", 3, "video/x-ms-wmx" }, | |
| 282 | - { "wrl", 3, "model/vrml" }, | |
| 283 | - { "wvx", 3, "video/x-ms-wvx" }, | |
| 284 | - { "xbm", 3, "image/x-xbitmap" }, | |
| 285 | - { "xlc", 3, "application/vnd.ms-excel" }, | |
| 286 | - { "xll", 3, "application/vnd.ms-excel" }, | |
| 287 | - { "xlm", 3, "application/vnd.ms-excel" }, | |
| 288 | - { "xls", 3, "application/vnd.ms-excel" }, | |
| 289 | - { "xlsx", 4, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, | |
| 290 | - { "xlw", 3, "application/vnd.ms-excel" }, | |
| 291 | - { "xml", 3, "text/xml" }, | |
| 292 | - { "xpm", 3, "image/x-xpixmap" }, | |
| 293 | - { "xwd", 3, "image/x-xwindowdump" }, | |
| 294 | - { "xyz", 3, "chemical/x-pdb" }, | |
| 295 | - { "zip", 3, "application/zip" }, | |
| 296 | - }; | |
| 297 | 304 | |
| 298 | 305 | #ifdef FOSSIL_DEBUG |
| 299 | 306 | /* This is test code to make sure the table above is in the correct |
| 300 | 307 | ** order |
| 301 | 308 | */ |
| 302 | 309 | if( fossil_strcmp(zName, "mimetype-test")==0 ){ |
| 303 | - for(i=1; i<sizeof(aMime)/sizeof(aMime[0]); i++){ | |
| 310 | + for(i=1; i<ArraySize(aMime); i++){ | |
| 304 | 311 | if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ |
| 305 | 312 | fossil_fatal("mimetypes out of sequence: %s before %s", |
| 306 | 313 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 307 | 314 | } |
| 308 | 315 | } |
| @@ -317,11 +324,11 @@ | ||
| 317 | 324 | len = strlen(z); |
| 318 | 325 | if( len<sizeof(zSuffix)-1 ){ |
| 319 | 326 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 320 | 327 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 321 | 328 | first = 0; |
| 322 | - last = sizeof(aMime)/sizeof(aMime[0]) - 1; | |
| 329 | + last = ArraySize(aMime) - 1; | |
| 323 | 330 | while( first<=last ){ |
| 324 | 331 | int c; |
| 325 | 332 | i = (first+last)/2; |
| 326 | 333 | c = fossil_strcmp(zSuffix, aMime[i].zSuffix); |
| 327 | 334 | if( c==0 ) return aMime[i].zMimetype; |
| @@ -350,10 +357,134 @@ | ||
| 350 | 357 | int i; |
| 351 | 358 | for(i=2; i<g.argc; i++){ |
| 352 | 359 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 353 | 360 | } |
| 354 | 361 | } |
| 362 | + | |
| 363 | +/* | |
| 364 | +** WEBPAGE: mimetype_list | |
| 365 | +** | |
| 366 | +** Show the built-in table used to guess embedded document mimetypes | |
| 367 | +** from file suffixes. | |
| 368 | +*/ | |
| 369 | +void mimetype_list_page(void){ | |
| 370 | + int i; | |
| 371 | + style_header("Mimetype List"); | |
| 372 | + @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename | |
| 373 | + @ suffixes and the following table to guess at the appropriate mimetype | |
| 374 | + @ for each document.</p> | |
| 375 | + @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> | |
| 376 | + @ <thead> | |
| 377 | + @ <tr><th>Suffix<th>Mimetype | |
| 378 | + @ </thead> | |
| 379 | + @ <tbody> | |
| 380 | + for(i=0; i<ArraySize(aMime); i++){ | |
| 381 | + @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> | |
| 382 | + } | |
| 383 | + @ </tbody></table> | |
| 384 | + output_table_sorting_javascript("mimeTable","tt",1); | |
| 385 | + style_footer(); | |
| 386 | +} | |
| 387 | + | |
| 388 | +/* | |
| 389 | +** Check to see if the file in the pContent blob is "embedded HTML". Return | |
| 390 | +** true if it is, and fill pTitle with the document title. | |
| 391 | +** | |
| 392 | +** An "embedded HTML" file is HTML that lacks a header and a footer. The | |
| 393 | +** standard Fossil header is prepended and the standard Fossil footer is | |
| 394 | +** appended. Otherwise, the file is displayed without change. | |
| 395 | +** | |
| 396 | +** Embedded HTML must be contained in a <div class='fossil-doc'> element. | |
| 397 | +** If that <div> also contains a data-title attribute, then the | |
| 398 | +** value of that attribute is extracted into pTitle and becomes the title | |
| 399 | +** of the document. | |
| 400 | +*/ | |
| 401 | +int doc_is_embedded_html(Blob *pContent, Blob *pTitle){ | |
| 402 | + const char *zIn = blob_str(pContent); | |
| 403 | + const char *zAttr; | |
| 404 | + const char *zValue; | |
| 405 | + int nAttr, nValue; | |
| 406 | + int seenClass = 0; | |
| 407 | + int seenTitle = 0; | |
| 408 | + | |
| 409 | + while( fossil_isspace(zIn[0]) ) zIn++; | |
| 410 | + if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0; | |
| 411 | + zIn += 4; | |
| 412 | + while( zIn[0] ){ | |
| 413 | + if( fossil_isspace(zIn[0]) ) zIn++; | |
| 414 | + if( zIn[0]=='>' ) return 0; | |
| 415 | + zAttr = zIn; | |
| 416 | + while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++; | |
| 417 | + nAttr = (int)(zIn - zAttr); | |
| 418 | + while( fossil_isspace(zIn[0]) ) zIn++; | |
| 419 | + if( zIn[0]!='=' ) continue; | |
| 420 | + zIn++; | |
| 421 | + while( fossil_isspace(zIn[0]) ) zIn++; | |
| 422 | + if( zIn[0]=='"' || zIn[0]=='\'' ){ | |
| 423 | + char cDelim = zIn[0]; | |
| 424 | + zIn++; | |
| 425 | + zValue = zIn; | |
| 426 | + while( zIn[0] && zIn[0]!=cDelim ) zIn++; | |
| 427 | + if( zIn[0]==0 ) return 0; | |
| 428 | + nValue = (int)(zIn - zValue); | |
| 429 | + zIn++; | |
| 430 | + }else{ | |
| 431 | + zValue = zIn; | |
| 432 | + while( zIn[0]!=0 && zIn[0]!='>' && zIn[0]!='/' | |
| 433 | + && !fossil_isspace(zIn[0]) ) zIn++; | |
| 434 | + if( zIn[0]==0 ) return 0; | |
| 435 | + nValue = (int)(zIn - zValue); | |
| 436 | + } | |
| 437 | + if( nAttr==5 && fossil_strnicmp(zAttr,"class",5)==0 ){ | |
| 438 | + if( nValue!=10 || fossil_strnicmp(zValue,"fossil-doc",10)!=0 ) return 0; | |
| 439 | + seenClass = 1; | |
| 440 | + if( seenTitle ) return 1; | |
| 441 | + } | |
| 442 | + if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){ | |
| 443 | + blob_append(pTitle, zValue, nValue); | |
| 444 | + seenTitle = 1; | |
| 445 | + if( seenClass ) return 1; | |
| 446 | + } | |
| 447 | + } | |
| 448 | + return seenClass; | |
| 449 | +} | |
| 450 | + | |
| 451 | +/* | |
| 452 | +** Look for a file named zName in the checkin with RID=vid. Load the content | |
| 453 | +** of that file into pContent and return the RID for the file. Or return 0 | |
| 454 | +** if the file is not found or could not be loaded. | |
| 455 | +*/ | |
| 456 | +int doc_load_content(int vid, const char *zName, Blob *pContent){ | |
| 457 | + int rid; /* The RID of the file being loaded */ | |
| 458 | + if( !db_table_exists("repository","vcache") ){ | |
| 459 | + db_multi_exec( | |
| 460 | + "CREATE TABLE IF NOT EXISTS vcache(\n" | |
| 461 | + " vid INTEGER, -- checkin ID\n" | |
| 462 | + " fname TEXT, -- filename\n" | |
| 463 | + " rid INTEGER, -- artifact ID\n" | |
| 464 | + " PRIMARY KEY(vid,fname)\n" | |
| 465 | + ") WITHOUT ROWID" | |
| 466 | + ); | |
| 467 | + } | |
| 468 | + if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ | |
| 469 | + db_multi_exec( | |
| 470 | + "DELETE FROM vcache;\n" | |
| 471 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;\n" | |
| 472 | + "INSERT INTO vcache(vid,fname,rid)" | |
| 473 | + " SELECT checkinID, filename, blob.rid FROM foci, blob" | |
| 474 | + " WHERE blob.uuid=foci.uuid" | |
| 475 | + " AND foci.checkinID=%d;", | |
| 476 | + vid | |
| 477 | + ); | |
| 478 | + } | |
| 479 | + rid = db_int(0, "SELECT rid FROM vcache" | |
| 480 | + " WHERE vid=%d AND fname=%Q", vid, zName); | |
| 481 | + if( rid && content_get(rid, pContent)==0 ){ | |
| 482 | + rid = 0; | |
| 483 | + } | |
| 484 | + return rid; | |
| 485 | +} | |
| 355 | 486 | |
| 356 | 487 | /* |
| 357 | 488 | ** WEBPAGE: doc |
| 358 | 489 | ** URL: /doc?name=CHECKIN/FILE |
| 359 | 490 | ** URL: /doc/CHECKIN/FILE |
| @@ -375,97 +506,80 @@ | ||
| 375 | 506 | ** The "ckout" CHECKIN is intended for development - to provide a mechanism |
| 376 | 507 | ** for looking at what a file will look like using the /doc webpage after |
| 377 | 508 | ** it gets checked in. |
| 378 | 509 | ** |
| 379 | 510 | ** The file extension is used to decide how to render the file. |
| 511 | +** | |
| 512 | +** If FILE ends in "/" then names "FILE/index.html", "FILE/index.wiki", | |
| 513 | +** and "FILE/index.md" are in that order. If none of those are found, | |
| 514 | +** then FILE is completely replaced by "404.md" and tried. If that is | |
| 515 | +** not found, then a default 404 screen is generated. | |
| 380 | 516 | */ |
| 381 | 517 | void doc_page(void){ |
| 382 | 518 | const char *zName; /* Argument to the /doc page */ |
| 383 | - const char *zOrigName; /* Original document name */ | |
| 519 | + const char *zOrigName = "?"; /* Original document name */ | |
| 384 | 520 | const char *zMime; /* Document MIME type */ |
| 385 | - char *zCheckin; /* The checkin holding the document */ | |
| 521 | + char *zCheckin = "tip"; /* The checkin holding the document */ | |
| 386 | 522 | int vid = 0; /* Artifact of checkin */ |
| 387 | 523 | int rid = 0; /* Artifact of file */ |
| 388 | 524 | int i; /* Loop counter */ |
| 389 | 525 | Blob filebody; /* Content of the documentation file */ |
| 390 | - int nMiss = 0; /* Failed attempts to find the document */ | |
| 526 | + Blob title; /* Document title */ | |
| 527 | + int nMiss = (-1); /* Failed attempts to find the document */ | |
| 528 | + static const char *const azSuffix[] = { | |
| 529 | + "index.html", "index.wiki", "index.md" | |
| 530 | + }; | |
| 391 | 531 | |
| 392 | 532 | login_check_credentials(); |
| 393 | 533 | if( !g.perm.Read ){ login_needed(); return; } |
| 394 | - zName = PD("name", "tip/index.wiki"); | |
| 395 | - for(i=0; zName[i] && zName[i]!='/'; i++){} | |
| 396 | - zCheckin = mprintf("%.*s", i, zName); | |
| 397 | - if( zName[i]==0 ){ | |
| 398 | - zName = "index.wiki"; | |
| 399 | - }else{ | |
| 400 | - zName += i; | |
| 401 | - } | |
| 402 | - while( zName[0]=='/' ){ zName++; } | |
| 403 | - g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); | |
| 404 | - zOrigName = zName; | |
| 405 | - if( !file_is_simple_pathname(zName, 1) ){ | |
| 406 | - if( sqlite3_strglob("*/", zName)==0 ){ | |
| 407 | - zOrigName = zName = mprintf("%sindex.wiki", zName); | |
| 408 | - if( !file_is_simple_pathname(zName, 1) ){ | |
| 409 | - goto doc_not_found; | |
| 410 | - } | |
| 411 | - }else{ | |
| 412 | - goto doc_not_found; | |
| 413 | - } | |
| 414 | - } | |
| 415 | - if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){ | |
| 416 | - sqlite3_snprintf(sizeof(zCheckin), zCheckin, "tip"); | |
| 417 | - } | |
| 418 | - if( fossil_strcmp(zCheckin,"ckout")==0 ){ | |
| 419 | - /* Read from the local checkout */ | |
| 420 | - char *zFullpath; | |
| 421 | - db_must_be_within_tree(); | |
| 422 | - while( rid==0 && nMiss<2 ){ | |
| 423 | - zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); | |
| 424 | - if( file_isfile(zFullpath) | |
| 425 | - && blob_read_from_file(&filebody, zFullpath)<0 ){ | |
| 534 | + blob_init(&title, 0, 0); | |
| 535 | + db_begin_transaction(); | |
| 536 | + while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ | |
| 537 | + zName = PD("name", "tip/index.wiki"); | |
| 538 | + for(i=0; zName[i] && zName[i]!='/'; i++){} | |
| 539 | + zCheckin = mprintf("%.*s", i, zName); | |
| 540 | + if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){ | |
| 541 | + zCheckin = "tip"; | |
| 542 | + } | |
| 543 | + if( nMiss==ArraySize(azSuffix) ){ | |
| 544 | + zName = "404.md"; | |
| 545 | + }else if( zName[i]==0 ){ | |
| 546 | + assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); | |
| 547 | + zName = azSuffix[nMiss]; | |
| 548 | + }else{ | |
| 549 | + zName += i; | |
| 550 | + } | |
| 551 | + while( zName[0]=='/' ){ zName++; } | |
| 552 | + g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); | |
| 553 | + if( nMiss==0 ) zOrigName = zName; | |
| 554 | + if( !file_is_simple_pathname(zName, 1) ){ | |
| 555 | + if( sqlite3_strglob("*/", zName)==0 ){ | |
| 556 | + assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); | |
| 557 | + zName = mprintf("%s%s", zName, azSuffix[nMiss]); | |
| 558 | + if( !file_is_simple_pathname(zName, 1) ){ | |
| 559 | + goto doc_not_found; | |
| 560 | + } | |
| 561 | + }else{ | |
| 562 | + goto doc_not_found; | |
| 563 | + } | |
| 564 | + } | |
| 565 | + if( fossil_strcmp(zCheckin,"ckout")==0 ){ | |
| 566 | + /* Read from the local checkout */ | |
| 567 | + char *zFullpath; | |
| 568 | + db_must_be_within_tree(); | |
| 569 | + zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); | |
| 570 | + if( file_isfile(zFullpath) | |
| 571 | + && blob_read_from_file(&filebody, zFullpath)>0 ){ | |
| 426 | 572 | rid = 1; /* Fake RID just to get the loop to end */ |
| 427 | 573 | } |
| 428 | 574 | fossil_free(zFullpath); |
| 429 | - if( rid ) break; | |
| 430 | - nMiss++; | |
| 431 | - zName = "404.md"; | |
| 432 | - } | |
| 433 | - }else{ | |
| 434 | - db_begin_transaction(); | |
| 435 | - vid = name_to_typed_rid(zCheckin, "ci"); | |
| 436 | - db_multi_exec( | |
| 437 | - "CREATE TABLE IF NOT EXISTS vcache(\n" | |
| 438 | - " vid INTEGER, -- checkin ID\n" | |
| 439 | - " fname TEXT, -- filename\n" | |
| 440 | - " rid INTEGER, -- artifact ID\n" | |
| 441 | - " PRIMARY KEY(vid,fname)\n" | |
| 442 | - ") WITHOUT ROWID" | |
| 443 | - ); | |
| 444 | - if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ | |
| 445 | - db_multi_exec( | |
| 446 | - "DELETE FROM vcache;\n" | |
| 447 | - "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;\n" | |
| 448 | - "INSERT INTO vcache(vid,fname,rid)" | |
| 449 | - " SELECT checkinID, filename, blob.rid FROM foci, blob" | |
| 450 | - " WHERE blob.uuid=foci.uuid" | |
| 451 | - " AND foci.checkinID=%d;", | |
| 452 | - vid | |
| 453 | - ); | |
| 454 | - } | |
| 455 | - while( rid==0 && nMiss<2 ){ | |
| 456 | - rid = db_int(0, "SELECT rid FROM vcache" | |
| 457 | - " WHERE vid=%d AND fname=%Q", vid, zName); | |
| 458 | - if( rid ) break; | |
| 459 | - nMiss++; | |
| 460 | - zName = "404.md"; | |
| 461 | - } | |
| 462 | - if( rid==0 || content_get(rid, &filebody)==0 ){ | |
| 463 | - goto doc_not_found; | |
| 464 | - } | |
| 465 | - db_end_transaction(0); | |
| 466 | - } | |
| 575 | + }else{ | |
| 576 | + vid = name_to_typed_rid(zCheckin, "ci"); | |
| 577 | + rid = doc_load_content(vid, zName, &filebody); | |
| 578 | + } | |
| 579 | + } | |
| 580 | + if( rid==0 ) goto doc_not_found; | |
| 467 | 581 | blob_to_utf8_no_bom(&filebody, 0); |
| 468 | 582 | |
| 469 | 583 | /* The file is now contained in the filebody blob. Deliver the |
| 470 | 584 | ** file to the user |
| 471 | 585 | */ |
| @@ -477,11 +591,11 @@ | ||
| 477 | 591 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 478 | 592 | " FROM blob WHERE rid=%d", vid)); |
| 479 | 593 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 480 | 594 | " WHERE objid=%d AND type='ci'", vid)); |
| 481 | 595 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 482 | - Blob title, tail; | |
| 596 | + Blob tail; | |
| 483 | 597 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 484 | 598 | if( wiki_find_title(&filebody, &title, &tail) ){ |
| 485 | 599 | style_header("%s", blob_str(&title)); |
| 486 | 600 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 487 | 601 | }else{ |
| @@ -488,26 +602,32 @@ | ||
| 488 | 602 | style_header("Documentation"); |
| 489 | 603 | wiki_convert(&filebody, 0, WIKI_BUTTONS); |
| 490 | 604 | } |
| 491 | 605 | style_footer(); |
| 492 | 606 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 493 | - Blob title = BLOB_INITIALIZER; | |
| 494 | 607 | Blob tail = BLOB_INITIALIZER; |
| 495 | 608 | markdown_to_html(&filebody, &title, &tail); |
| 496 | 609 | if( blob_size(&title)>0 ){ |
| 497 | 610 | style_header("%s", blob_str(&title)); |
| 498 | 611 | }else{ |
| 499 | - style_header("%s", nMiss?"Not Found":"Documentation"); | |
| 612 | + style_header("%s", nMiss>=ArraySize(azSuffix)? | |
| 613 | + "Not Found" : "Documentation"); | |
| 500 | 614 | } |
| 501 | 615 | blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail)); |
| 502 | 616 | style_footer(); |
| 503 | 617 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 504 | 618 | style_header("Documentation"); |
| 505 | 619 | @ <blockquote><pre> |
| 506 | 620 | @ %h(blob_str(&filebody)) |
| 507 | 621 | @ </pre></blockquote> |
| 508 | 622 | style_footer(); |
| 623 | + }else if( fossil_strcmp(zMime, "text/html")==0 | |
| 624 | + && doc_is_embedded_html(&filebody, &title) ){ | |
| 625 | + if( blob_size(&title)==0 ) blob_append(&title,zName,-1); | |
| 626 | + style_header("%s", blob_str(&title)); | |
| 627 | + blob_append(cgi_output_blob(), blob_buffer(&filebody),blob_size(&filebody)); | |
| 628 | + style_footer(); | |
| 509 | 629 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 510 | 630 | }else if( db_get_boolean("th1-docs", 0) && |
| 511 | 631 | fossil_strcmp(zMime, "application/x-th1")==0 ){ |
| 512 | 632 | style_header("%h", zName); |
| 513 | 633 | Th_Render(blob_str(&filebody)); |
| @@ -515,11 +635,12 @@ | ||
| 515 | 635 | #endif |
| 516 | 636 | }else{ |
| 517 | 637 | cgi_set_content_type(zMime); |
| 518 | 638 | cgi_set_content(&filebody); |
| 519 | 639 | } |
| 520 | - if( nMiss ) cgi_set_status(404, "Not Found"); | |
| 640 | + if( nMiss>=ArraySize(azSuffix) ) cgi_set_status(404, "Not Found"); | |
| 641 | + db_end_transaction(0); | |
| 521 | 642 | return; |
| 522 | 643 | |
| 523 | 644 | /* Jump here when unable to locate the document */ |
| 524 | 645 | doc_not_found: |
| 525 | 646 | db_end_transaction(0); |
| @@ -528,10 +649,11 @@ | ||
| 528 | 649 | @ <p>Document %h(zOrigName) not found |
| 529 | 650 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 530 | 651 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 531 | 652 | } |
| 532 | 653 | style_footer(); |
| 654 | + db_end_transaction(0); | |
| 533 | 655 | return; |
| 534 | 656 | } |
| 535 | 657 | |
| 536 | 658 | /* |
| 537 | 659 | ** The default logo. |
| @@ -653,5 +775,18 @@ | ||
| 653 | 775 | } |
| 654 | 776 | cgi_set_content_type(zMime); |
| 655 | 777 | cgi_set_content(&bgimg); |
| 656 | 778 | g.isConst = 1; |
| 657 | 779 | } |
| 780 | + | |
| 781 | + | |
| 782 | +/* | |
| 783 | +** WEBPAGE: /docsrch | |
| 784 | +** | |
| 785 | +** Search for documents that match a user-supplied pattern. | |
| 786 | +*/ | |
| 787 | +void doc_search_page(void){ | |
| 788 | + login_check_credentials(); | |
| 789 | + style_header("Document Search"); | |
| 790 | + search_screen(SRCH_DOC, "docsrch"); | |
| 791 | + style_footer(); | |
| 792 | +} | |
| 658 | 793 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -65,17 +65,233 @@ | |
| 65 | } |
| 66 | } |
| 67 | if( i>=n ){ |
| 68 | return 0; /* Plain text */ |
| 69 | } |
| 70 | for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){ |
| 71 | if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){ |
| 72 | return aMime[i].zMimetype; |
| 73 | } |
| 74 | } |
| 75 | return "unknown/unknown"; |
| 76 | } |
| 77 | |
| 78 | /* |
| 79 | ** Guess the mime-type of a document based on its name. |
| 80 | */ |
| 81 | const char *mimetype_from_name(const char *zName){ |
| @@ -83,226 +299,17 @@ | |
| 83 | int i; |
| 84 | int first, last; |
| 85 | int len; |
| 86 | char zSuffix[20]; |
| 87 | |
| 88 | /* A table of mimetypes based on file suffixes. |
| 89 | ** Suffixes must be in sorted order so that we can do a binary |
| 90 | ** search to find the mime-type |
| 91 | */ |
| 92 | static const struct { |
| 93 | const char *zSuffix; /* The file suffix */ |
| 94 | int size; /* Length of the suffix */ |
| 95 | const char *zMimetype; /* The corresponding mimetype */ |
| 96 | } aMime[] = { |
| 97 | { "ai", 2, "application/postscript" }, |
| 98 | { "aif", 3, "audio/x-aiff" }, |
| 99 | { "aifc", 4, "audio/x-aiff" }, |
| 100 | { "aiff", 4, "audio/x-aiff" }, |
| 101 | { "arj", 3, "application/x-arj-compressed" }, |
| 102 | { "asc", 3, "text/plain" }, |
| 103 | { "asf", 3, "video/x-ms-asf" }, |
| 104 | { "asx", 3, "video/x-ms-asx" }, |
| 105 | { "au", 2, "audio/ulaw" }, |
| 106 | { "avi", 3, "video/x-msvideo" }, |
| 107 | { "bat", 3, "application/x-msdos-program" }, |
| 108 | { "bcpio", 5, "application/x-bcpio" }, |
| 109 | { "bin", 3, "application/octet-stream" }, |
| 110 | { "c", 1, "text/plain" }, |
| 111 | { "cc", 2, "text/plain" }, |
| 112 | { "ccad", 4, "application/clariscad" }, |
| 113 | { "cdf", 3, "application/x-netcdf" }, |
| 114 | { "class", 5, "application/octet-stream" }, |
| 115 | { "cod", 3, "application/vnd.rim.cod" }, |
| 116 | { "com", 3, "application/x-msdos-program" }, |
| 117 | { "cpio", 4, "application/x-cpio" }, |
| 118 | { "cpt", 3, "application/mac-compactpro" }, |
| 119 | { "csh", 3, "application/x-csh" }, |
| 120 | { "css", 3, "text/css" }, |
| 121 | { "dcr", 3, "application/x-director" }, |
| 122 | { "deb", 3, "application/x-debian-package" }, |
| 123 | { "dir", 3, "application/x-director" }, |
| 124 | { "dl", 2, "video/dl" }, |
| 125 | { "dms", 3, "application/octet-stream" }, |
| 126 | { "doc", 3, "application/msword" }, |
| 127 | { "docx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, |
| 128 | { "dot", 3, "application/msword" }, |
| 129 | { "dotx", 4, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, |
| 130 | { "drw", 3, "application/drafting" }, |
| 131 | { "dvi", 3, "application/x-dvi" }, |
| 132 | { "dwg", 3, "application/acad" }, |
| 133 | { "dxf", 3, "application/dxf" }, |
| 134 | { "dxr", 3, "application/x-director" }, |
| 135 | { "eps", 3, "application/postscript" }, |
| 136 | { "etx", 3, "text/x-setext" }, |
| 137 | { "exe", 3, "application/octet-stream" }, |
| 138 | { "ez", 2, "application/andrew-inset" }, |
| 139 | { "f", 1, "text/plain" }, |
| 140 | { "f90", 3, "text/plain" }, |
| 141 | { "fli", 3, "video/fli" }, |
| 142 | { "flv", 3, "video/flv" }, |
| 143 | { "gif", 3, "image/gif" }, |
| 144 | { "gl", 2, "video/gl" }, |
| 145 | { "gtar", 4, "application/x-gtar" }, |
| 146 | { "gz", 2, "application/x-gzip" }, |
| 147 | { "h", 1, "text/plain" }, |
| 148 | { "hdf", 3, "application/x-hdf" }, |
| 149 | { "hh", 2, "text/plain" }, |
| 150 | { "hqx", 3, "application/mac-binhex40" }, |
| 151 | { "htm", 3, "text/html" }, |
| 152 | { "html", 4, "text/html" }, |
| 153 | { "ice", 3, "x-conference/x-cooltalk" }, |
| 154 | { "ief", 3, "image/ief" }, |
| 155 | { "iges", 4, "model/iges" }, |
| 156 | { "igs", 3, "model/iges" }, |
| 157 | { "ips", 3, "application/x-ipscript" }, |
| 158 | { "ipx", 3, "application/x-ipix" }, |
| 159 | { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, |
| 160 | { "jar", 3, "application/java-archive" }, |
| 161 | { "jpe", 3, "image/jpeg" }, |
| 162 | { "jpeg", 4, "image/jpeg" }, |
| 163 | { "jpg", 3, "image/jpeg" }, |
| 164 | { "js", 2, "application/x-javascript" }, |
| 165 | { "kar", 3, "audio/midi" }, |
| 166 | { "latex", 5, "application/x-latex" }, |
| 167 | { "lha", 3, "application/octet-stream" }, |
| 168 | { "lsp", 3, "application/x-lisp" }, |
| 169 | { "lzh", 3, "application/octet-stream" }, |
| 170 | { "m", 1, "text/plain" }, |
| 171 | { "m3u", 3, "audio/x-mpegurl" }, |
| 172 | { "man", 3, "application/x-troff-man" }, |
| 173 | { "markdown", 8, "text/x-markdown" }, |
| 174 | { "md", 2, "text/x-markdown" }, |
| 175 | { "me", 2, "application/x-troff-me" }, |
| 176 | { "mesh", 4, "model/mesh" }, |
| 177 | { "mid", 3, "audio/midi" }, |
| 178 | { "midi", 4, "audio/midi" }, |
| 179 | { "mif", 3, "application/x-mif" }, |
| 180 | { "mime", 4, "www/mime" }, |
| 181 | { "mkd", 3, "text/x-markdown" }, |
| 182 | { "mov", 3, "video/quicktime" }, |
| 183 | { "movie", 5, "video/x-sgi-movie" }, |
| 184 | { "mp2", 3, "audio/mpeg" }, |
| 185 | { "mp3", 3, "audio/mpeg" }, |
| 186 | { "mp4", 3, "video/mp4" }, |
| 187 | { "mpe", 3, "video/mpeg" }, |
| 188 | { "mpeg", 4, "video/mpeg" }, |
| 189 | { "mpg", 3, "video/mpeg" }, |
| 190 | { "mpga", 4, "audio/mpeg" }, |
| 191 | { "ms", 2, "application/x-troff-ms" }, |
| 192 | { "msh", 3, "model/mesh" }, |
| 193 | { "nc", 2, "application/x-netcdf" }, |
| 194 | { "oda", 3, "application/oda" }, |
| 195 | { "ogg", 3, "application/ogg" }, |
| 196 | { "ogm", 3, "application/ogg" }, |
| 197 | { "pbm", 3, "image/x-portable-bitmap" }, |
| 198 | { "pdb", 3, "chemical/x-pdb" }, |
| 199 | { "pdf", 3, "application/pdf" }, |
| 200 | { "pgm", 3, "image/x-portable-graymap" }, |
| 201 | { "pgn", 3, "application/x-chess-pgn" }, |
| 202 | { "pgp", 3, "application/pgp" }, |
| 203 | { "pl", 2, "application/x-perl" }, |
| 204 | { "pm", 2, "application/x-perl" }, |
| 205 | { "png", 3, "image/png" }, |
| 206 | { "pnm", 3, "image/x-portable-anymap" }, |
| 207 | { "pot", 3, "application/mspowerpoint" }, |
| 208 | { "potx", 4, "application/vnd.openxmlformats-officedocument.presentationml.template"}, |
| 209 | { "ppm", 3, "image/x-portable-pixmap" }, |
| 210 | { "pps", 3, "application/mspowerpoint" }, |
| 211 | { "ppsx", 4, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, |
| 212 | { "ppt", 3, "application/mspowerpoint" }, |
| 213 | { "pptx", 4, "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, |
| 214 | { "ppz", 3, "application/mspowerpoint" }, |
| 215 | { "pre", 3, "application/x-freelance" }, |
| 216 | { "prt", 3, "application/pro_eng" }, |
| 217 | { "ps", 2, "application/postscript" }, |
| 218 | { "qt", 2, "video/quicktime" }, |
| 219 | { "ra", 2, "audio/x-realaudio" }, |
| 220 | { "ram", 3, "audio/x-pn-realaudio" }, |
| 221 | { "rar", 3, "application/x-rar-compressed" }, |
| 222 | { "ras", 3, "image/cmu-raster" }, |
| 223 | { "rgb", 3, "image/x-rgb" }, |
| 224 | { "rm", 2, "audio/x-pn-realaudio" }, |
| 225 | { "roff", 4, "application/x-troff" }, |
| 226 | { "rpm", 3, "audio/x-pn-realaudio-plugin" }, |
| 227 | { "rtf", 3, "text/rtf" }, |
| 228 | { "rtx", 3, "text/richtext" }, |
| 229 | { "scm", 3, "application/x-lotusscreencam" }, |
| 230 | { "set", 3, "application/set" }, |
| 231 | { "sgm", 3, "text/sgml" }, |
| 232 | { "sgml", 4, "text/sgml" }, |
| 233 | { "sh", 2, "application/x-sh" }, |
| 234 | { "shar", 4, "application/x-shar" }, |
| 235 | { "silo", 4, "model/mesh" }, |
| 236 | { "sit", 3, "application/x-stuffit" }, |
| 237 | { "skd", 3, "application/x-koan" }, |
| 238 | { "skm", 3, "application/x-koan" }, |
| 239 | { "skp", 3, "application/x-koan" }, |
| 240 | { "skt", 3, "application/x-koan" }, |
| 241 | { "smi", 3, "application/smil" }, |
| 242 | { "smil", 4, "application/smil" }, |
| 243 | { "snd", 3, "audio/basic" }, |
| 244 | { "sol", 3, "application/solids" }, |
| 245 | { "spl", 3, "application/x-futuresplash" }, |
| 246 | { "src", 3, "application/x-wais-source" }, |
| 247 | { "step", 4, "application/STEP" }, |
| 248 | { "stl", 3, "application/SLA" }, |
| 249 | { "stp", 3, "application/STEP" }, |
| 250 | { "sv4cpio", 7, "application/x-sv4cpio" }, |
| 251 | { "sv4crc", 6, "application/x-sv4crc" }, |
| 252 | { "svg", 3, "image/svg+xml" }, |
| 253 | { "swf", 3, "application/x-shockwave-flash" }, |
| 254 | { "t", 1, "application/x-troff" }, |
| 255 | { "tar", 3, "application/x-tar" }, |
| 256 | { "tcl", 3, "application/x-tcl" }, |
| 257 | { "tex", 3, "application/x-tex" }, |
| 258 | { "texi", 4, "application/x-texinfo" }, |
| 259 | { "texinfo", 7, "application/x-texinfo" }, |
| 260 | { "tgz", 3, "application/x-tar-gz" }, |
| 261 | { "th1", 3, "application/x-th1" }, |
| 262 | { "tif", 3, "image/tiff" }, |
| 263 | { "tiff", 4, "image/tiff" }, |
| 264 | { "tr", 2, "application/x-troff" }, |
| 265 | { "tsi", 3, "audio/TSP-audio" }, |
| 266 | { "tsp", 3, "application/dsptype" }, |
| 267 | { "tsv", 3, "text/tab-separated-values" }, |
| 268 | { "txt", 3, "text/plain" }, |
| 269 | { "unv", 3, "application/i-deas" }, |
| 270 | { "ustar", 5, "application/x-ustar" }, |
| 271 | { "vcd", 3, "application/x-cdlink" }, |
| 272 | { "vda", 3, "application/vda" }, |
| 273 | { "viv", 3, "video/vnd.vivo" }, |
| 274 | { "vivo", 4, "video/vnd.vivo" }, |
| 275 | { "vrml", 4, "model/vrml" }, |
| 276 | { "wav", 3, "audio/x-wav" }, |
| 277 | { "wax", 3, "audio/x-ms-wax" }, |
| 278 | { "wiki", 4, "text/x-fossil-wiki" }, |
| 279 | { "wma", 3, "audio/x-ms-wma" }, |
| 280 | { "wmv", 3, "video/x-ms-wmv" }, |
| 281 | { "wmx", 3, "video/x-ms-wmx" }, |
| 282 | { "wrl", 3, "model/vrml" }, |
| 283 | { "wvx", 3, "video/x-ms-wvx" }, |
| 284 | { "xbm", 3, "image/x-xbitmap" }, |
| 285 | { "xlc", 3, "application/vnd.ms-excel" }, |
| 286 | { "xll", 3, "application/vnd.ms-excel" }, |
| 287 | { "xlm", 3, "application/vnd.ms-excel" }, |
| 288 | { "xls", 3, "application/vnd.ms-excel" }, |
| 289 | { "xlsx", 4, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, |
| 290 | { "xlw", 3, "application/vnd.ms-excel" }, |
| 291 | { "xml", 3, "text/xml" }, |
| 292 | { "xpm", 3, "image/x-xpixmap" }, |
| 293 | { "xwd", 3, "image/x-xwindowdump" }, |
| 294 | { "xyz", 3, "chemical/x-pdb" }, |
| 295 | { "zip", 3, "application/zip" }, |
| 296 | }; |
| 297 | |
| 298 | #ifdef FOSSIL_DEBUG |
| 299 | /* This is test code to make sure the table above is in the correct |
| 300 | ** order |
| 301 | */ |
| 302 | if( fossil_strcmp(zName, "mimetype-test")==0 ){ |
| 303 | for(i=1; i<sizeof(aMime)/sizeof(aMime[0]); i++){ |
| 304 | if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ |
| 305 | fossil_fatal("mimetypes out of sequence: %s before %s", |
| 306 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 307 | } |
| 308 | } |
| @@ -317,11 +324,11 @@ | |
| 317 | len = strlen(z); |
| 318 | if( len<sizeof(zSuffix)-1 ){ |
| 319 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 320 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 321 | first = 0; |
| 322 | last = sizeof(aMime)/sizeof(aMime[0]) - 1; |
| 323 | while( first<=last ){ |
| 324 | int c; |
| 325 | i = (first+last)/2; |
| 326 | c = fossil_strcmp(zSuffix, aMime[i].zSuffix); |
| 327 | if( c==0 ) return aMime[i].zMimetype; |
| @@ -350,10 +357,134 @@ | |
| 350 | int i; |
| 351 | for(i=2; i<g.argc; i++){ |
| 352 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | /* |
| 357 | ** WEBPAGE: doc |
| 358 | ** URL: /doc?name=CHECKIN/FILE |
| 359 | ** URL: /doc/CHECKIN/FILE |
| @@ -375,97 +506,80 @@ | |
| 375 | ** The "ckout" CHECKIN is intended for development - to provide a mechanism |
| 376 | ** for looking at what a file will look like using the /doc webpage after |
| 377 | ** it gets checked in. |
| 378 | ** |
| 379 | ** The file extension is used to decide how to render the file. |
| 380 | */ |
| 381 | void doc_page(void){ |
| 382 | const char *zName; /* Argument to the /doc page */ |
| 383 | const char *zOrigName; /* Original document name */ |
| 384 | const char *zMime; /* Document MIME type */ |
| 385 | char *zCheckin; /* The checkin holding the document */ |
| 386 | int vid = 0; /* Artifact of checkin */ |
| 387 | int rid = 0; /* Artifact of file */ |
| 388 | int i; /* Loop counter */ |
| 389 | Blob filebody; /* Content of the documentation file */ |
| 390 | int nMiss = 0; /* Failed attempts to find the document */ |
| 391 | |
| 392 | login_check_credentials(); |
| 393 | if( !g.perm.Read ){ login_needed(); return; } |
| 394 | zName = PD("name", "tip/index.wiki"); |
| 395 | for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 396 | zCheckin = mprintf("%.*s", i, zName); |
| 397 | if( zName[i]==0 ){ |
| 398 | zName = "index.wiki"; |
| 399 | }else{ |
| 400 | zName += i; |
| 401 | } |
| 402 | while( zName[0]=='/' ){ zName++; } |
| 403 | g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); |
| 404 | zOrigName = zName; |
| 405 | if( !file_is_simple_pathname(zName, 1) ){ |
| 406 | if( sqlite3_strglob("*/", zName)==0 ){ |
| 407 | zOrigName = zName = mprintf("%sindex.wiki", zName); |
| 408 | if( !file_is_simple_pathname(zName, 1) ){ |
| 409 | goto doc_not_found; |
| 410 | } |
| 411 | }else{ |
| 412 | goto doc_not_found; |
| 413 | } |
| 414 | } |
| 415 | if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){ |
| 416 | sqlite3_snprintf(sizeof(zCheckin), zCheckin, "tip"); |
| 417 | } |
| 418 | if( fossil_strcmp(zCheckin,"ckout")==0 ){ |
| 419 | /* Read from the local checkout */ |
| 420 | char *zFullpath; |
| 421 | db_must_be_within_tree(); |
| 422 | while( rid==0 && nMiss<2 ){ |
| 423 | zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 424 | if( file_isfile(zFullpath) |
| 425 | && blob_read_from_file(&filebody, zFullpath)<0 ){ |
| 426 | rid = 1; /* Fake RID just to get the loop to end */ |
| 427 | } |
| 428 | fossil_free(zFullpath); |
| 429 | if( rid ) break; |
| 430 | nMiss++; |
| 431 | zName = "404.md"; |
| 432 | } |
| 433 | }else{ |
| 434 | db_begin_transaction(); |
| 435 | vid = name_to_typed_rid(zCheckin, "ci"); |
| 436 | db_multi_exec( |
| 437 | "CREATE TABLE IF NOT EXISTS vcache(\n" |
| 438 | " vid INTEGER, -- checkin ID\n" |
| 439 | " fname TEXT, -- filename\n" |
| 440 | " rid INTEGER, -- artifact ID\n" |
| 441 | " PRIMARY KEY(vid,fname)\n" |
| 442 | ") WITHOUT ROWID" |
| 443 | ); |
| 444 | if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ |
| 445 | db_multi_exec( |
| 446 | "DELETE FROM vcache;\n" |
| 447 | "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;\n" |
| 448 | "INSERT INTO vcache(vid,fname,rid)" |
| 449 | " SELECT checkinID, filename, blob.rid FROM foci, blob" |
| 450 | " WHERE blob.uuid=foci.uuid" |
| 451 | " AND foci.checkinID=%d;", |
| 452 | vid |
| 453 | ); |
| 454 | } |
| 455 | while( rid==0 && nMiss<2 ){ |
| 456 | rid = db_int(0, "SELECT rid FROM vcache" |
| 457 | " WHERE vid=%d AND fname=%Q", vid, zName); |
| 458 | if( rid ) break; |
| 459 | nMiss++; |
| 460 | zName = "404.md"; |
| 461 | } |
| 462 | if( rid==0 || content_get(rid, &filebody)==0 ){ |
| 463 | goto doc_not_found; |
| 464 | } |
| 465 | db_end_transaction(0); |
| 466 | } |
| 467 | blob_to_utf8_no_bom(&filebody, 0); |
| 468 | |
| 469 | /* The file is now contained in the filebody blob. Deliver the |
| 470 | ** file to the user |
| 471 | */ |
| @@ -477,11 +591,11 @@ | |
| 477 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 478 | " FROM blob WHERE rid=%d", vid)); |
| 479 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 480 | " WHERE objid=%d AND type='ci'", vid)); |
| 481 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 482 | Blob title, tail; |
| 483 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 484 | if( wiki_find_title(&filebody, &title, &tail) ){ |
| 485 | style_header("%s", blob_str(&title)); |
| 486 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 487 | }else{ |
| @@ -488,26 +602,32 @@ | |
| 488 | style_header("Documentation"); |
| 489 | wiki_convert(&filebody, 0, WIKI_BUTTONS); |
| 490 | } |
| 491 | style_footer(); |
| 492 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 493 | Blob title = BLOB_INITIALIZER; |
| 494 | Blob tail = BLOB_INITIALIZER; |
| 495 | markdown_to_html(&filebody, &title, &tail); |
| 496 | if( blob_size(&title)>0 ){ |
| 497 | style_header("%s", blob_str(&title)); |
| 498 | }else{ |
| 499 | style_header("%s", nMiss?"Not Found":"Documentation"); |
| 500 | } |
| 501 | blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail)); |
| 502 | style_footer(); |
| 503 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 504 | style_header("Documentation"); |
| 505 | @ <blockquote><pre> |
| 506 | @ %h(blob_str(&filebody)) |
| 507 | @ </pre></blockquote> |
| 508 | style_footer(); |
| 509 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 510 | }else if( db_get_boolean("th1-docs", 0) && |
| 511 | fossil_strcmp(zMime, "application/x-th1")==0 ){ |
| 512 | style_header("%h", zName); |
| 513 | Th_Render(blob_str(&filebody)); |
| @@ -515,11 +635,12 @@ | |
| 515 | #endif |
| 516 | }else{ |
| 517 | cgi_set_content_type(zMime); |
| 518 | cgi_set_content(&filebody); |
| 519 | } |
| 520 | if( nMiss ) cgi_set_status(404, "Not Found"); |
| 521 | return; |
| 522 | |
| 523 | /* Jump here when unable to locate the document */ |
| 524 | doc_not_found: |
| 525 | db_end_transaction(0); |
| @@ -528,10 +649,11 @@ | |
| 528 | @ <p>Document %h(zOrigName) not found |
| 529 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 530 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 531 | } |
| 532 | style_footer(); |
| 533 | return; |
| 534 | } |
| 535 | |
| 536 | /* |
| 537 | ** The default logo. |
| @@ -653,5 +775,18 @@ | |
| 653 | } |
| 654 | cgi_set_content_type(zMime); |
| 655 | cgi_set_content(&bgimg); |
| 656 | g.isConst = 1; |
| 657 | } |
| 658 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -65,17 +65,233 @@ | |
| 65 | } |
| 66 | } |
| 67 | if( i>=n ){ |
| 68 | return 0; /* Plain text */ |
| 69 | } |
| 70 | for(i=0; i<ArraySize(aMime); i++){ |
| 71 | if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){ |
| 72 | return aMime[i].zMimetype; |
| 73 | } |
| 74 | } |
| 75 | return "unknown/unknown"; |
| 76 | } |
| 77 | |
| 78 | /* A table of mimetypes based on file suffixes. |
| 79 | ** Suffixes must be in sorted order so that we can do a binary |
| 80 | ** search to find the mime-type |
| 81 | */ |
| 82 | static const struct { |
| 83 | const char *zSuffix; /* The file suffix */ |
| 84 | int size; /* Length of the suffix */ |
| 85 | const char *zMimetype; /* The corresponding mimetype */ |
| 86 | } aMime[] = { |
| 87 | { "ai", 2, "application/postscript" }, |
| 88 | { "aif", 3, "audio/x-aiff" }, |
| 89 | { "aifc", 4, "audio/x-aiff" }, |
| 90 | { "aiff", 4, "audio/x-aiff" }, |
| 91 | { "arj", 3, "application/x-arj-compressed" }, |
| 92 | { "asc", 3, "text/plain" }, |
| 93 | { "asf", 3, "video/x-ms-asf" }, |
| 94 | { "asx", 3, "video/x-ms-asx" }, |
| 95 | { "au", 2, "audio/ulaw" }, |
| 96 | { "avi", 3, "video/x-msvideo" }, |
| 97 | { "bat", 3, "application/x-msdos-program" }, |
| 98 | { "bcpio", 5, "application/x-bcpio" }, |
| 99 | { "bin", 3, "application/octet-stream" }, |
| 100 | { "c", 1, "text/plain" }, |
| 101 | { "cc", 2, "text/plain" }, |
| 102 | { "ccad", 4, "application/clariscad" }, |
| 103 | { "cdf", 3, "application/x-netcdf" }, |
| 104 | { "class", 5, "application/octet-stream" }, |
| 105 | { "cod", 3, "application/vnd.rim.cod" }, |
| 106 | { "com", 3, "application/x-msdos-program" }, |
| 107 | { "cpio", 4, "application/x-cpio" }, |
| 108 | { "cpt", 3, "application/mac-compactpro" }, |
| 109 | { "csh", 3, "application/x-csh" }, |
| 110 | { "css", 3, "text/css" }, |
| 111 | { "dcr", 3, "application/x-director" }, |
| 112 | { "deb", 3, "application/x-debian-package" }, |
| 113 | { "dir", 3, "application/x-director" }, |
| 114 | { "dl", 2, "video/dl" }, |
| 115 | { "dms", 3, "application/octet-stream" }, |
| 116 | { "doc", 3, "application/msword" }, |
| 117 | { "docx", 4, "application/vnd.openxmlformats-" |
| 118 | "officedocument.wordprocessingml.document"}, |
| 119 | { "dot", 3, "application/msword" }, |
| 120 | { "dotx", 4, "application/vnd.openxmlformats-" |
| 121 | "officedocument.wordprocessingml.template"}, |
| 122 | { "drw", 3, "application/drafting" }, |
| 123 | { "dvi", 3, "application/x-dvi" }, |
| 124 | { "dwg", 3, "application/acad" }, |
| 125 | { "dxf", 3, "application/dxf" }, |
| 126 | { "dxr", 3, "application/x-director" }, |
| 127 | { "eps", 3, "application/postscript" }, |
| 128 | { "etx", 3, "text/x-setext" }, |
| 129 | { "exe", 3, "application/octet-stream" }, |
| 130 | { "ez", 2, "application/andrew-inset" }, |
| 131 | { "f", 1, "text/plain" }, |
| 132 | { "f90", 3, "text/plain" }, |
| 133 | { "fli", 3, "video/fli" }, |
| 134 | { "flv", 3, "video/flv" }, |
| 135 | { "gif", 3, "image/gif" }, |
| 136 | { "gl", 2, "video/gl" }, |
| 137 | { "gtar", 4, "application/x-gtar" }, |
| 138 | { "gz", 2, "application/x-gzip" }, |
| 139 | { "h", 1, "text/plain" }, |
| 140 | { "hdf", 3, "application/x-hdf" }, |
| 141 | { "hh", 2, "text/plain" }, |
| 142 | { "hqx", 3, "application/mac-binhex40" }, |
| 143 | { "htm", 3, "text/html" }, |
| 144 | { "html", 4, "text/html" }, |
| 145 | { "ice", 3, "x-conference/x-cooltalk" }, |
| 146 | { "ief", 3, "image/ief" }, |
| 147 | { "iges", 4, "model/iges" }, |
| 148 | { "igs", 3, "model/iges" }, |
| 149 | { "ips", 3, "application/x-ipscript" }, |
| 150 | { "ipx", 3, "application/x-ipix" }, |
| 151 | { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, |
| 152 | { "jar", 3, "application/java-archive" }, |
| 153 | { "jpe", 3, "image/jpeg" }, |
| 154 | { "jpeg", 4, "image/jpeg" }, |
| 155 | { "jpg", 3, "image/jpeg" }, |
| 156 | { "js", 2, "application/x-javascript" }, |
| 157 | { "kar", 3, "audio/midi" }, |
| 158 | { "latex", 5, "application/x-latex" }, |
| 159 | { "lha", 3, "application/octet-stream" }, |
| 160 | { "lsp", 3, "application/x-lisp" }, |
| 161 | { "lzh", 3, "application/octet-stream" }, |
| 162 | { "m", 1, "text/plain" }, |
| 163 | { "m3u", 3, "audio/x-mpegurl" }, |
| 164 | { "man", 3, "application/x-troff-man" }, |
| 165 | { "markdown", 8, "text/x-markdown" }, |
| 166 | { "md", 2, "text/x-markdown" }, |
| 167 | { "me", 2, "application/x-troff-me" }, |
| 168 | { "mesh", 4, "model/mesh" }, |
| 169 | { "mid", 3, "audio/midi" }, |
| 170 | { "midi", 4, "audio/midi" }, |
| 171 | { "mif", 3, "application/x-mif" }, |
| 172 | { "mime", 4, "www/mime" }, |
| 173 | { "mkd", 3, "text/x-markdown" }, |
| 174 | { "mov", 3, "video/quicktime" }, |
| 175 | { "movie", 5, "video/x-sgi-movie" }, |
| 176 | { "mp2", 3, "audio/mpeg" }, |
| 177 | { "mp3", 3, "audio/mpeg" }, |
| 178 | { "mp4", 3, "video/mp4" }, |
| 179 | { "mpe", 3, "video/mpeg" }, |
| 180 | { "mpeg", 4, "video/mpeg" }, |
| 181 | { "mpg", 3, "video/mpeg" }, |
| 182 | { "mpga", 4, "audio/mpeg" }, |
| 183 | { "ms", 2, "application/x-troff-ms" }, |
| 184 | { "msh", 3, "model/mesh" }, |
| 185 | { "nc", 2, "application/x-netcdf" }, |
| 186 | { "oda", 3, "application/oda" }, |
| 187 | { "ogg", 3, "application/ogg" }, |
| 188 | { "ogm", 3, "application/ogg" }, |
| 189 | { "pbm", 3, "image/x-portable-bitmap" }, |
| 190 | { "pdb", 3, "chemical/x-pdb" }, |
| 191 | { "pdf", 3, "application/pdf" }, |
| 192 | { "pgm", 3, "image/x-portable-graymap" }, |
| 193 | { "pgn", 3, "application/x-chess-pgn" }, |
| 194 | { "pgp", 3, "application/pgp" }, |
| 195 | { "pl", 2, "application/x-perl" }, |
| 196 | { "pm", 2, "application/x-perl" }, |
| 197 | { "png", 3, "image/png" }, |
| 198 | { "pnm", 3, "image/x-portable-anymap" }, |
| 199 | { "pot", 3, "application/mspowerpoint" }, |
| 200 | { "potx", 4, "application/vnd.openxmlformats-" |
| 201 | "officedocument.presentationml.template"}, |
| 202 | { "ppm", 3, "image/x-portable-pixmap" }, |
| 203 | { "pps", 3, "application/mspowerpoint" }, |
| 204 | { "ppsx", 4, "application/vnd.openxmlformats-" |
| 205 | "officedocument.presentationml.slideshow"}, |
| 206 | { "ppt", 3, "application/mspowerpoint" }, |
| 207 | { "pptx", 4, "application/vnd.openxmlformats-" |
| 208 | "officedocument.presentationml.presentation"}, |
| 209 | { "ppz", 3, "application/mspowerpoint" }, |
| 210 | { "pre", 3, "application/x-freelance" }, |
| 211 | { "prt", 3, "application/pro_eng" }, |
| 212 | { "ps", 2, "application/postscript" }, |
| 213 | { "qt", 2, "video/quicktime" }, |
| 214 | { "ra", 2, "audio/x-realaudio" }, |
| 215 | { "ram", 3, "audio/x-pn-realaudio" }, |
| 216 | { "rar", 3, "application/x-rar-compressed" }, |
| 217 | { "ras", 3, "image/cmu-raster" }, |
| 218 | { "rgb", 3, "image/x-rgb" }, |
| 219 | { "rm", 2, "audio/x-pn-realaudio" }, |
| 220 | { "roff", 4, "application/x-troff" }, |
| 221 | { "rpm", 3, "audio/x-pn-realaudio-plugin" }, |
| 222 | { "rtf", 3, "text/rtf" }, |
| 223 | { "rtx", 3, "text/richtext" }, |
| 224 | { "scm", 3, "application/x-lotusscreencam" }, |
| 225 | { "set", 3, "application/set" }, |
| 226 | { "sgm", 3, "text/sgml" }, |
| 227 | { "sgml", 4, "text/sgml" }, |
| 228 | { "sh", 2, "application/x-sh" }, |
| 229 | { "shar", 4, "application/x-shar" }, |
| 230 | { "silo", 4, "model/mesh" }, |
| 231 | { "sit", 3, "application/x-stuffit" }, |
| 232 | { "skd", 3, "application/x-koan" }, |
| 233 | { "skm", 3, "application/x-koan" }, |
| 234 | { "skp", 3, "application/x-koan" }, |
| 235 | { "skt", 3, "application/x-koan" }, |
| 236 | { "smi", 3, "application/smil" }, |
| 237 | { "smil", 4, "application/smil" }, |
| 238 | { "snd", 3, "audio/basic" }, |
| 239 | { "sol", 3, "application/solids" }, |
| 240 | { "spl", 3, "application/x-futuresplash" }, |
| 241 | { "src", 3, "application/x-wais-source" }, |
| 242 | { "step", 4, "application/STEP" }, |
| 243 | { "stl", 3, "application/SLA" }, |
| 244 | { "stp", 3, "application/STEP" }, |
| 245 | { "sv4cpio", 7, "application/x-sv4cpio" }, |
| 246 | { "sv4crc", 6, "application/x-sv4crc" }, |
| 247 | { "svg", 3, "image/svg+xml" }, |
| 248 | { "swf", 3, "application/x-shockwave-flash" }, |
| 249 | { "t", 1, "application/x-troff" }, |
| 250 | { "tar", 3, "application/x-tar" }, |
| 251 | { "tcl", 3, "application/x-tcl" }, |
| 252 | { "tex", 3, "application/x-tex" }, |
| 253 | { "texi", 4, "application/x-texinfo" }, |
| 254 | { "texinfo", 7, "application/x-texinfo" }, |
| 255 | { "tgz", 3, "application/x-tar-gz" }, |
| 256 | { "th1", 3, "application/x-th1" }, |
| 257 | { "tif", 3, "image/tiff" }, |
| 258 | { "tiff", 4, "image/tiff" }, |
| 259 | { "tr", 2, "application/x-troff" }, |
| 260 | { "tsi", 3, "audio/TSP-audio" }, |
| 261 | { "tsp", 3, "application/dsptype" }, |
| 262 | { "tsv", 3, "text/tab-separated-values" }, |
| 263 | { "txt", 3, "text/plain" }, |
| 264 | { "unv", 3, "application/i-deas" }, |
| 265 | { "ustar", 5, "application/x-ustar" }, |
| 266 | { "vcd", 3, "application/x-cdlink" }, |
| 267 | { "vda", 3, "application/vda" }, |
| 268 | { "viv", 3, "video/vnd.vivo" }, |
| 269 | { "vivo", 4, "video/vnd.vivo" }, |
| 270 | { "vrml", 4, "model/vrml" }, |
| 271 | { "wav", 3, "audio/x-wav" }, |
| 272 | { "wax", 3, "audio/x-ms-wax" }, |
| 273 | { "wiki", 4, "text/x-fossil-wiki" }, |
| 274 | { "wma", 3, "audio/x-ms-wma" }, |
| 275 | { "wmv", 3, "video/x-ms-wmv" }, |
| 276 | { "wmx", 3, "video/x-ms-wmx" }, |
| 277 | { "wrl", 3, "model/vrml" }, |
| 278 | { "wvx", 3, "video/x-ms-wvx" }, |
| 279 | { "xbm", 3, "image/x-xbitmap" }, |
| 280 | { "xlc", 3, "application/vnd.ms-excel" }, |
| 281 | { "xll", 3, "application/vnd.ms-excel" }, |
| 282 | { "xlm", 3, "application/vnd.ms-excel" }, |
| 283 | { "xls", 3, "application/vnd.ms-excel" }, |
| 284 | { "xlsx", 4, "application/vnd.openxmlformats-" |
| 285 | "officedocument.spreadsheetml.sheet"}, |
| 286 | { "xlw", 3, "application/vnd.ms-excel" }, |
| 287 | { "xml", 3, "text/xml" }, |
| 288 | { "xpm", 3, "image/x-xpixmap" }, |
| 289 | { "xwd", 3, "image/x-xwindowdump" }, |
| 290 | { "xyz", 3, "chemical/x-pdb" }, |
| 291 | { "zip", 3, "application/zip" }, |
| 292 | }; |
| 293 | |
| 294 | /* |
| 295 | ** Guess the mime-type of a document based on its name. |
| 296 | */ |
| 297 | const char *mimetype_from_name(const char *zName){ |
| @@ -83,226 +299,17 @@ | |
| 299 | int i; |
| 300 | int first, last; |
| 301 | int len; |
| 302 | char zSuffix[20]; |
| 303 | |
| 304 | |
| 305 | #ifdef FOSSIL_DEBUG |
| 306 | /* This is test code to make sure the table above is in the correct |
| 307 | ** order |
| 308 | */ |
| 309 | if( fossil_strcmp(zName, "mimetype-test")==0 ){ |
| 310 | for(i=1; i<ArraySize(aMime); i++){ |
| 311 | if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ |
| 312 | fossil_fatal("mimetypes out of sequence: %s before %s", |
| 313 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 314 | } |
| 315 | } |
| @@ -317,11 +324,11 @@ | |
| 324 | len = strlen(z); |
| 325 | if( len<sizeof(zSuffix)-1 ){ |
| 326 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 327 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 328 | first = 0; |
| 329 | last = ArraySize(aMime) - 1; |
| 330 | while( first<=last ){ |
| 331 | int c; |
| 332 | i = (first+last)/2; |
| 333 | c = fossil_strcmp(zSuffix, aMime[i].zSuffix); |
| 334 | if( c==0 ) return aMime[i].zMimetype; |
| @@ -350,10 +357,134 @@ | |
| 357 | int i; |
| 358 | for(i=2; i<g.argc; i++){ |
| 359 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | /* |
| 364 | ** WEBPAGE: mimetype_list |
| 365 | ** |
| 366 | ** Show the built-in table used to guess embedded document mimetypes |
| 367 | ** from file suffixes. |
| 368 | */ |
| 369 | void mimetype_list_page(void){ |
| 370 | int i; |
| 371 | style_header("Mimetype List"); |
| 372 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 373 | @ suffixes and the following table to guess at the appropriate mimetype |
| 374 | @ for each document.</p> |
| 375 | @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> |
| 376 | @ <thead> |
| 377 | @ <tr><th>Suffix<th>Mimetype |
| 378 | @ </thead> |
| 379 | @ <tbody> |
| 380 | for(i=0; i<ArraySize(aMime); i++){ |
| 381 | @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 382 | } |
| 383 | @ </tbody></table> |
| 384 | output_table_sorting_javascript("mimeTable","tt",1); |
| 385 | style_footer(); |
| 386 | } |
| 387 | |
| 388 | /* |
| 389 | ** Check to see if the file in the pContent blob is "embedded HTML". Return |
| 390 | ** true if it is, and fill pTitle with the document title. |
| 391 | ** |
| 392 | ** An "embedded HTML" file is HTML that lacks a header and a footer. The |
| 393 | ** standard Fossil header is prepended and the standard Fossil footer is |
| 394 | ** appended. Otherwise, the file is displayed without change. |
| 395 | ** |
| 396 | ** Embedded HTML must be contained in a <div class='fossil-doc'> element. |
| 397 | ** If that <div> also contains a data-title attribute, then the |
| 398 | ** value of that attribute is extracted into pTitle and becomes the title |
| 399 | ** of the document. |
| 400 | */ |
| 401 | int doc_is_embedded_html(Blob *pContent, Blob *pTitle){ |
| 402 | const char *zIn = blob_str(pContent); |
| 403 | const char *zAttr; |
| 404 | const char *zValue; |
| 405 | int nAttr, nValue; |
| 406 | int seenClass = 0; |
| 407 | int seenTitle = 0; |
| 408 | |
| 409 | while( fossil_isspace(zIn[0]) ) zIn++; |
| 410 | if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0; |
| 411 | zIn += 4; |
| 412 | while( zIn[0] ){ |
| 413 | if( fossil_isspace(zIn[0]) ) zIn++; |
| 414 | if( zIn[0]=='>' ) return 0; |
| 415 | zAttr = zIn; |
| 416 | while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++; |
| 417 | nAttr = (int)(zIn - zAttr); |
| 418 | while( fossil_isspace(zIn[0]) ) zIn++; |
| 419 | if( zIn[0]!='=' ) continue; |
| 420 | zIn++; |
| 421 | while( fossil_isspace(zIn[0]) ) zIn++; |
| 422 | if( zIn[0]=='"' || zIn[0]=='\'' ){ |
| 423 | char cDelim = zIn[0]; |
| 424 | zIn++; |
| 425 | zValue = zIn; |
| 426 | while( zIn[0] && zIn[0]!=cDelim ) zIn++; |
| 427 | if( zIn[0]==0 ) return 0; |
| 428 | nValue = (int)(zIn - zValue); |
| 429 | zIn++; |
| 430 | }else{ |
| 431 | zValue = zIn; |
| 432 | while( zIn[0]!=0 && zIn[0]!='>' && zIn[0]!='/' |
| 433 | && !fossil_isspace(zIn[0]) ) zIn++; |
| 434 | if( zIn[0]==0 ) return 0; |
| 435 | nValue = (int)(zIn - zValue); |
| 436 | } |
| 437 | if( nAttr==5 && fossil_strnicmp(zAttr,"class",5)==0 ){ |
| 438 | if( nValue!=10 || fossil_strnicmp(zValue,"fossil-doc",10)!=0 ) return 0; |
| 439 | seenClass = 1; |
| 440 | if( seenTitle ) return 1; |
| 441 | } |
| 442 | if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){ |
| 443 | blob_append(pTitle, zValue, nValue); |
| 444 | seenTitle = 1; |
| 445 | if( seenClass ) return 1; |
| 446 | } |
| 447 | } |
| 448 | return seenClass; |
| 449 | } |
| 450 | |
| 451 | /* |
| 452 | ** Look for a file named zName in the checkin with RID=vid. Load the content |
| 453 | ** of that file into pContent and return the RID for the file. Or return 0 |
| 454 | ** if the file is not found or could not be loaded. |
| 455 | */ |
| 456 | int doc_load_content(int vid, const char *zName, Blob *pContent){ |
| 457 | int rid; /* The RID of the file being loaded */ |
| 458 | if( !db_table_exists("repository","vcache") ){ |
| 459 | db_multi_exec( |
| 460 | "CREATE TABLE IF NOT EXISTS vcache(\n" |
| 461 | " vid INTEGER, -- checkin ID\n" |
| 462 | " fname TEXT, -- filename\n" |
| 463 | " rid INTEGER, -- artifact ID\n" |
| 464 | " PRIMARY KEY(vid,fname)\n" |
| 465 | ") WITHOUT ROWID" |
| 466 | ); |
| 467 | } |
| 468 | if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ |
| 469 | db_multi_exec( |
| 470 | "DELETE FROM vcache;\n" |
| 471 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;\n" |
| 472 | "INSERT INTO vcache(vid,fname,rid)" |
| 473 | " SELECT checkinID, filename, blob.rid FROM foci, blob" |
| 474 | " WHERE blob.uuid=foci.uuid" |
| 475 | " AND foci.checkinID=%d;", |
| 476 | vid |
| 477 | ); |
| 478 | } |
| 479 | rid = db_int(0, "SELECT rid FROM vcache" |
| 480 | " WHERE vid=%d AND fname=%Q", vid, zName); |
| 481 | if( rid && content_get(rid, pContent)==0 ){ |
| 482 | rid = 0; |
| 483 | } |
| 484 | return rid; |
| 485 | } |
| 486 | |
| 487 | /* |
| 488 | ** WEBPAGE: doc |
| 489 | ** URL: /doc?name=CHECKIN/FILE |
| 490 | ** URL: /doc/CHECKIN/FILE |
| @@ -375,97 +506,80 @@ | |
| 506 | ** The "ckout" CHECKIN is intended for development - to provide a mechanism |
| 507 | ** for looking at what a file will look like using the /doc webpage after |
| 508 | ** it gets checked in. |
| 509 | ** |
| 510 | ** The file extension is used to decide how to render the file. |
| 511 | ** |
| 512 | ** If FILE ends in "/" then names "FILE/index.html", "FILE/index.wiki", |
| 513 | ** and "FILE/index.md" are in that order. If none of those are found, |
| 514 | ** then FILE is completely replaced by "404.md" and tried. If that is |
| 515 | ** not found, then a default 404 screen is generated. |
| 516 | */ |
| 517 | void doc_page(void){ |
| 518 | const char *zName; /* Argument to the /doc page */ |
| 519 | const char *zOrigName = "?"; /* Original document name */ |
| 520 | const char *zMime; /* Document MIME type */ |
| 521 | char *zCheckin = "tip"; /* The checkin holding the document */ |
| 522 | int vid = 0; /* Artifact of checkin */ |
| 523 | int rid = 0; /* Artifact of file */ |
| 524 | int i; /* Loop counter */ |
| 525 | Blob filebody; /* Content of the documentation file */ |
| 526 | Blob title; /* Document title */ |
| 527 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 528 | static const char *const azSuffix[] = { |
| 529 | "index.html", "index.wiki", "index.md" |
| 530 | }; |
| 531 | |
| 532 | login_check_credentials(); |
| 533 | if( !g.perm.Read ){ login_needed(); return; } |
| 534 | blob_init(&title, 0, 0); |
| 535 | db_begin_transaction(); |
| 536 | while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ |
| 537 | zName = PD("name", "tip/index.wiki"); |
| 538 | for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 539 | zCheckin = mprintf("%.*s", i, zName); |
| 540 | if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){ |
| 541 | zCheckin = "tip"; |
| 542 | } |
| 543 | if( nMiss==ArraySize(azSuffix) ){ |
| 544 | zName = "404.md"; |
| 545 | }else if( zName[i]==0 ){ |
| 546 | assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); |
| 547 | zName = azSuffix[nMiss]; |
| 548 | }else{ |
| 549 | zName += i; |
| 550 | } |
| 551 | while( zName[0]=='/' ){ zName++; } |
| 552 | g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); |
| 553 | if( nMiss==0 ) zOrigName = zName; |
| 554 | if( !file_is_simple_pathname(zName, 1) ){ |
| 555 | if( sqlite3_strglob("*/", zName)==0 ){ |
| 556 | assert( nMiss>=0 && nMiss<ArraySize(azSuffix) ); |
| 557 | zName = mprintf("%s%s", zName, azSuffix[nMiss]); |
| 558 | if( !file_is_simple_pathname(zName, 1) ){ |
| 559 | goto doc_not_found; |
| 560 | } |
| 561 | }else{ |
| 562 | goto doc_not_found; |
| 563 | } |
| 564 | } |
| 565 | if( fossil_strcmp(zCheckin,"ckout")==0 ){ |
| 566 | /* Read from the local checkout */ |
| 567 | char *zFullpath; |
| 568 | db_must_be_within_tree(); |
| 569 | zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 570 | if( file_isfile(zFullpath) |
| 571 | && blob_read_from_file(&filebody, zFullpath)>0 ){ |
| 572 | rid = 1; /* Fake RID just to get the loop to end */ |
| 573 | } |
| 574 | fossil_free(zFullpath); |
| 575 | }else{ |
| 576 | vid = name_to_typed_rid(zCheckin, "ci"); |
| 577 | rid = doc_load_content(vid, zName, &filebody); |
| 578 | } |
| 579 | } |
| 580 | if( rid==0 ) goto doc_not_found; |
| 581 | blob_to_utf8_no_bom(&filebody, 0); |
| 582 | |
| 583 | /* The file is now contained in the filebody blob. Deliver the |
| 584 | ** file to the user |
| 585 | */ |
| @@ -477,11 +591,11 @@ | |
| 591 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 592 | " FROM blob WHERE rid=%d", vid)); |
| 593 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 594 | " WHERE objid=%d AND type='ci'", vid)); |
| 595 | if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){ |
| 596 | Blob tail; |
| 597 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 598 | if( wiki_find_title(&filebody, &title, &tail) ){ |
| 599 | style_header("%s", blob_str(&title)); |
| 600 | wiki_convert(&tail, 0, WIKI_BUTTONS); |
| 601 | }else{ |
| @@ -488,26 +602,32 @@ | |
| 602 | style_header("Documentation"); |
| 603 | wiki_convert(&filebody, 0, WIKI_BUTTONS); |
| 604 | } |
| 605 | style_footer(); |
| 606 | }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){ |
| 607 | Blob tail = BLOB_INITIALIZER; |
| 608 | markdown_to_html(&filebody, &title, &tail); |
| 609 | if( blob_size(&title)>0 ){ |
| 610 | style_header("%s", blob_str(&title)); |
| 611 | }else{ |
| 612 | style_header("%s", nMiss>=ArraySize(azSuffix)? |
| 613 | "Not Found" : "Documentation"); |
| 614 | } |
| 615 | blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail)); |
| 616 | style_footer(); |
| 617 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 618 | style_header("Documentation"); |
| 619 | @ <blockquote><pre> |
| 620 | @ %h(blob_str(&filebody)) |
| 621 | @ </pre></blockquote> |
| 622 | style_footer(); |
| 623 | }else if( fossil_strcmp(zMime, "text/html")==0 |
| 624 | && doc_is_embedded_html(&filebody, &title) ){ |
| 625 | if( blob_size(&title)==0 ) blob_append(&title,zName,-1); |
| 626 | style_header("%s", blob_str(&title)); |
| 627 | blob_append(cgi_output_blob(), blob_buffer(&filebody),blob_size(&filebody)); |
| 628 | style_footer(); |
| 629 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 630 | }else if( db_get_boolean("th1-docs", 0) && |
| 631 | fossil_strcmp(zMime, "application/x-th1")==0 ){ |
| 632 | style_header("%h", zName); |
| 633 | Th_Render(blob_str(&filebody)); |
| @@ -515,11 +635,12 @@ | |
| 635 | #endif |
| 636 | }else{ |
| 637 | cgi_set_content_type(zMime); |
| 638 | cgi_set_content(&filebody); |
| 639 | } |
| 640 | if( nMiss>=ArraySize(azSuffix) ) cgi_set_status(404, "Not Found"); |
| 641 | db_end_transaction(0); |
| 642 | return; |
| 643 | |
| 644 | /* Jump here when unable to locate the document */ |
| 645 | doc_not_found: |
| 646 | db_end_transaction(0); |
| @@ -528,10 +649,11 @@ | |
| 649 | @ <p>Document %h(zOrigName) not found |
| 650 | if( fossil_strcmp(zCheckin,"ckout")!=0 ){ |
| 651 | @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a> |
| 652 | } |
| 653 | style_footer(); |
| 654 | db_end_transaction(0); |
| 655 | return; |
| 656 | } |
| 657 | |
| 658 | /* |
| 659 | ** The default logo. |
| @@ -653,5 +775,18 @@ | |
| 775 | } |
| 776 | cgi_set_content_type(zMime); |
| 777 | cgi_set_content(&bgimg); |
| 778 | g.isConst = 1; |
| 779 | } |
| 780 | |
| 781 | |
| 782 | /* |
| 783 | ** WEBPAGE: /docsrch |
| 784 | ** |
| 785 | ** Search for documents that match a user-supplied pattern. |
| 786 | */ |
| 787 | void doc_search_page(void){ |
| 788 | login_check_credentials(); |
| 789 | style_header("Document Search"); |
| 790 | search_screen(SRCH_DOC, "docsrch"); |
| 791 | style_footer(); |
| 792 | } |
| 793 |
+2
-2
| --- src/glob.c | ||
| +++ src/glob.c | ||
| @@ -44,11 +44,11 @@ | ||
| 44 | 44 | const char *zSep = "("; |
| 45 | 45 | int nTerm = 0; |
| 46 | 46 | int i; |
| 47 | 47 | int cTerm; |
| 48 | 48 | |
| 49 | - if( zGlobList==0 || zGlobList[0]==0 ) return "0"; | |
| 49 | + if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0"); | |
| 50 | 50 | blob_zero(&expr); |
| 51 | 51 | while( zGlobList[0] ){ |
| 52 | 52 | while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ |
| 53 | 53 | zGlobList++; /* Skip leading commas, spaces, and newlines */ |
| 54 | 54 | } |
| @@ -73,11 +73,11 @@ | ||
| 73 | 73 | } |
| 74 | 74 | if( nTerm ){ |
| 75 | 75 | blob_appendf(&expr, ")"); |
| 76 | 76 | return blob_str(&expr); |
| 77 | 77 | }else{ |
| 78 | - return "0"; | |
| 78 | + return fossil_strdup("0"); | |
| 79 | 79 | } |
| 80 | 80 | } |
| 81 | 81 | |
| 82 | 82 | #if INTERFACE |
| 83 | 83 | /* |
| 84 | 84 |
| --- src/glob.c | |
| +++ src/glob.c | |
| @@ -44,11 +44,11 @@ | |
| 44 | const char *zSep = "("; |
| 45 | int nTerm = 0; |
| 46 | int i; |
| 47 | int cTerm; |
| 48 | |
| 49 | if( zGlobList==0 || zGlobList[0]==0 ) return "0"; |
| 50 | blob_zero(&expr); |
| 51 | while( zGlobList[0] ){ |
| 52 | while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ |
| 53 | zGlobList++; /* Skip leading commas, spaces, and newlines */ |
| 54 | } |
| @@ -73,11 +73,11 @@ | |
| 73 | } |
| 74 | if( nTerm ){ |
| 75 | blob_appendf(&expr, ")"); |
| 76 | return blob_str(&expr); |
| 77 | }else{ |
| 78 | return "0"; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | #if INTERFACE |
| 83 | /* |
| 84 |
| --- src/glob.c | |
| +++ src/glob.c | |
| @@ -44,11 +44,11 @@ | |
| 44 | const char *zSep = "("; |
| 45 | int nTerm = 0; |
| 46 | int i; |
| 47 | int cTerm; |
| 48 | |
| 49 | if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0"); |
| 50 | blob_zero(&expr); |
| 51 | while( zGlobList[0] ){ |
| 52 | while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ |
| 53 | zGlobList++; /* Skip leading commas, spaces, and newlines */ |
| 54 | } |
| @@ -73,11 +73,11 @@ | |
| 73 | } |
| 74 | if( nTerm ){ |
| 75 | blob_appendf(&expr, ")"); |
| 76 | return blob_str(&expr); |
| 77 | }else{ |
| 78 | return fossil_strdup("0"); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | #if INTERFACE |
| 83 | /* |
| 84 |
+2
-1
| --- src/main.mk | ||
| +++ src/main.mk | ||
| @@ -449,11 +449,12 @@ | ||
| 449 | 449 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 450 | 450 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 451 | 451 | -DSQLITE_THREADSAFE=0 \ |
| 452 | 452 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 453 | 453 | -DSQLITE_OMIT_DEPRECATED \ |
| 454 | - -DSQLITE_ENABLE_EXPLAIN_COMMENTS | |
| 454 | + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ | |
| 455 | + -DSQLITE_ENABLE_FTS4 | |
| 455 | 456 | |
| 456 | 457 | # Setup the options used to compile the included SQLite shell. |
| 457 | 458 | SHELL_OPTIONS = -Dmain=sqlite3_shell \ |
| 458 | 459 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 459 | 460 | -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 460 | 461 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -449,11 +449,12 @@ | |
| 449 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 450 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 451 | -DSQLITE_THREADSAFE=0 \ |
| 452 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 453 | -DSQLITE_OMIT_DEPRECATED \ |
| 454 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS |
| 455 | |
| 456 | # Setup the options used to compile the included SQLite shell. |
| 457 | SHELL_OPTIONS = -Dmain=sqlite3_shell \ |
| 458 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 459 | -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 460 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -449,11 +449,12 @@ | |
| 449 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 450 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 451 | -DSQLITE_THREADSAFE=0 \ |
| 452 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 453 | -DSQLITE_OMIT_DEPRECATED \ |
| 454 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 455 | -DSQLITE_ENABLE_FTS4 |
| 456 | |
| 457 | # Setup the options used to compile the included SQLite shell. |
| 458 | SHELL_OPTIONS = -Dmain=sqlite3_shell \ |
| 459 | -DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 460 | -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 461 |
+1
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -157,10 +157,11 @@ | ||
| 157 | 157 | -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 158 | 158 | -DSQLITE_THREADSAFE=0 |
| 159 | 159 | -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 160 | 160 | -DSQLITE_OMIT_DEPRECATED |
| 161 | 161 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS |
| 162 | + -DSQLITE_ENABLE_FTS4 | |
| 162 | 163 | } |
| 163 | 164 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1 |
| 164 | 165 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4 |
| 165 | 166 | #lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI |
| 166 | 167 | #lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096 |
| 167 | 168 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -157,10 +157,11 @@ | |
| 157 | -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 158 | -DSQLITE_THREADSAFE=0 |
| 159 | -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 160 | -DSQLITE_OMIT_DEPRECATED |
| 161 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS |
| 162 | } |
| 163 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1 |
| 164 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4 |
| 165 | #lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI |
| 166 | #lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096 |
| 167 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -157,10 +157,11 @@ | |
| 157 | -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 158 | -DSQLITE_THREADSAFE=0 |
| 159 | -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 160 | -DSQLITE_OMIT_DEPRECATED |
| 161 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS |
| 162 | -DSQLITE_ENABLE_FTS4 |
| 163 | } |
| 164 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1 |
| 165 | #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4 |
| 166 | #lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI |
| 167 | #lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096 |
| 168 |
+3
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -1829,10 +1829,11 @@ | ||
| 1829 | 1829 | for(i=0; i<p->nFile; i++){ |
| 1830 | 1830 | add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0, |
| 1831 | 1831 | isPublic, 1, manifest_file_mperm(&p->aFile[i])); |
| 1832 | 1832 | } |
| 1833 | 1833 | } |
| 1834 | + search_doc_touch('c', rid, 0); | |
| 1834 | 1835 | db_multi_exec( |
| 1835 | 1836 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1836 | 1837 | "bgcolor,euser,ecomment,omtime)" |
| 1837 | 1838 | "VALUES('ci'," |
| 1838 | 1839 | " coalesce(" |
| @@ -1934,10 +1935,11 @@ | ||
| 1934 | 1935 | if( nWiki>0 ){ |
| 1935 | 1936 | zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); |
| 1936 | 1937 | }else{ |
| 1937 | 1938 | zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); |
| 1938 | 1939 | } |
| 1940 | + search_doc_touch('w',rid,p->zWikiTitle); | |
| 1939 | 1941 | db_multi_exec( |
| 1940 | 1942 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1941 | 1943 | " bgcolor,euser,ecomment)" |
| 1942 | 1944 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1943 | 1945 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1986,10 +1988,11 @@ | ||
| 1986 | 1988 | } |
| 1987 | 1989 | } |
| 1988 | 1990 | if( subsequent ){ |
| 1989 | 1991 | content_deltify(rid, subsequent, 0); |
| 1990 | 1992 | }else{ |
| 1993 | + search_doc_touch('e',rid,0); | |
| 1991 | 1994 | db_multi_exec( |
| 1992 | 1995 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1993 | 1996 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1994 | 1997 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1995 | 1998 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 1996 | 1999 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -1829,10 +1829,11 @@ | |
| 1829 | for(i=0; i<p->nFile; i++){ |
| 1830 | add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0, |
| 1831 | isPublic, 1, manifest_file_mperm(&p->aFile[i])); |
| 1832 | } |
| 1833 | } |
| 1834 | db_multi_exec( |
| 1835 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1836 | "bgcolor,euser,ecomment,omtime)" |
| 1837 | "VALUES('ci'," |
| 1838 | " coalesce(" |
| @@ -1934,10 +1935,11 @@ | |
| 1934 | if( nWiki>0 ){ |
| 1935 | zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); |
| 1936 | }else{ |
| 1937 | zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); |
| 1938 | } |
| 1939 | db_multi_exec( |
| 1940 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1941 | " bgcolor,euser,ecomment)" |
| 1942 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1943 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1986,10 +1988,11 @@ | |
| 1986 | } |
| 1987 | } |
| 1988 | if( subsequent ){ |
| 1989 | content_deltify(rid, subsequent, 0); |
| 1990 | }else{ |
| 1991 | db_multi_exec( |
| 1992 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1993 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1994 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1995 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 1996 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -1829,10 +1829,11 @@ | |
| 1829 | for(i=0; i<p->nFile; i++){ |
| 1830 | add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0, |
| 1831 | isPublic, 1, manifest_file_mperm(&p->aFile[i])); |
| 1832 | } |
| 1833 | } |
| 1834 | search_doc_touch('c', rid, 0); |
| 1835 | db_multi_exec( |
| 1836 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1837 | "bgcolor,euser,ecomment,omtime)" |
| 1838 | "VALUES('ci'," |
| 1839 | " coalesce(" |
| @@ -1934,10 +1935,11 @@ | |
| 1935 | if( nWiki>0 ){ |
| 1936 | zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); |
| 1937 | }else{ |
| 1938 | zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); |
| 1939 | } |
| 1940 | search_doc_touch('w',rid,p->zWikiTitle); |
| 1941 | db_multi_exec( |
| 1942 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1943 | " bgcolor,euser,ecomment)" |
| 1944 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1945 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1986,10 +1988,11 @@ | |
| 1988 | } |
| 1989 | } |
| 1990 | if( subsequent ){ |
| 1991 | content_deltify(rid, subsequent, 0); |
| 1992 | }else{ |
| 1993 | search_doc_touch('e',rid,0); |
| 1994 | db_multi_exec( |
| 1995 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1996 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1997 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1998 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 1999 |
+8
| --- src/rebuild.c | ||
| +++ src/rebuild.c | ||
| @@ -533,10 +533,12 @@ | ||
| 533 | 533 | ** --vacuum Run VACUUM on the database after rebuilding |
| 534 | 534 | ** --deanalyze Remove ANALYZE tables from the database |
| 535 | 535 | ** --analyze Run ANALYZE on the database after rebuilding |
| 536 | 536 | ** --wal Set Write-Ahead-Log journalling mode on the database |
| 537 | 537 | ** --stats Show artifact statistics after rebuilding |
| 538 | +** --index Always add in the full-text search index | |
| 539 | +** --no-index Always omit the full-text search index | |
| 538 | 540 | ** |
| 539 | 541 | ** See also: deconstruct, reconstruct |
| 540 | 542 | */ |
| 541 | 543 | void rebuild_database(void){ |
| 542 | 544 | int forceFlag; |
| @@ -550,10 +552,11 @@ | ||
| 550 | 552 | int runVacuum; |
| 551 | 553 | int runDeanalyze; |
| 552 | 554 | int runAnalyze; |
| 553 | 555 | int runCompress; |
| 554 | 556 | int showStats; |
| 557 | + int runReindex; | |
| 555 | 558 | |
| 556 | 559 | omitVerify = find_option("noverify",0,0)!=0; |
| 557 | 560 | forceFlag = find_option("force","f",0)!=0; |
| 558 | 561 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 559 | 562 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -580,15 +583,19 @@ | ||
| 580 | 583 | usage("?REPOSITORY-FILENAME?"); |
| 581 | 584 | } |
| 582 | 585 | db_close(1); |
| 583 | 586 | db_open_repository(g.zRepositoryName); |
| 584 | 587 | } |
| 588 | + runReindex = search_index_exists(); | |
| 589 | + if( find_option("index",0,0)!=0 ) runReindex = 1; | |
| 590 | + if( find_option("no-index",0,0)!=0 ) runReindex = 0; | |
| 585 | 591 | |
| 586 | 592 | /* We should be done with options.. */ |
| 587 | 593 | verify_all_options(); |
| 588 | 594 | |
| 589 | 595 | db_begin_transaction(); |
| 596 | + search_drop_index(); | |
| 590 | 597 | ttyOutput = 1; |
| 591 | 598 | errCnt = rebuild_db(randomizeFlag, 1, doClustering); |
| 592 | 599 | reconstruct_private_table(); |
| 593 | 600 | db_multi_exec( |
| 594 | 601 | "REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());" |
| @@ -634,10 +641,11 @@ | ||
| 634 | 641 | } |
| 635 | 642 | if( activateWal ){ |
| 636 | 643 | db_multi_exec("PRAGMA journal_mode=WAL;"); |
| 637 | 644 | } |
| 638 | 645 | } |
| 646 | + if( runReindex ) search_rebuild_index(); | |
| 639 | 647 | if( showStats ){ |
| 640 | 648 | static const struct { int idx; const char *zLabel; } aStat[] = { |
| 641 | 649 | { CFTYPE_ANY, "Artifacts:" }, |
| 642 | 650 | { CFTYPE_MANIFEST, "Manifests:" }, |
| 643 | 651 | { CFTYPE_CLUSTER, "Clusters:" }, |
| 644 | 652 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -533,10 +533,12 @@ | |
| 533 | ** --vacuum Run VACUUM on the database after rebuilding |
| 534 | ** --deanalyze Remove ANALYZE tables from the database |
| 535 | ** --analyze Run ANALYZE on the database after rebuilding |
| 536 | ** --wal Set Write-Ahead-Log journalling mode on the database |
| 537 | ** --stats Show artifact statistics after rebuilding |
| 538 | ** |
| 539 | ** See also: deconstruct, reconstruct |
| 540 | */ |
| 541 | void rebuild_database(void){ |
| 542 | int forceFlag; |
| @@ -550,10 +552,11 @@ | |
| 550 | int runVacuum; |
| 551 | int runDeanalyze; |
| 552 | int runAnalyze; |
| 553 | int runCompress; |
| 554 | int showStats; |
| 555 | |
| 556 | omitVerify = find_option("noverify",0,0)!=0; |
| 557 | forceFlag = find_option("force","f",0)!=0; |
| 558 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 559 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -580,15 +583,19 @@ | |
| 580 | usage("?REPOSITORY-FILENAME?"); |
| 581 | } |
| 582 | db_close(1); |
| 583 | db_open_repository(g.zRepositoryName); |
| 584 | } |
| 585 | |
| 586 | /* We should be done with options.. */ |
| 587 | verify_all_options(); |
| 588 | |
| 589 | db_begin_transaction(); |
| 590 | ttyOutput = 1; |
| 591 | errCnt = rebuild_db(randomizeFlag, 1, doClustering); |
| 592 | reconstruct_private_table(); |
| 593 | db_multi_exec( |
| 594 | "REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());" |
| @@ -634,10 +641,11 @@ | |
| 634 | } |
| 635 | if( activateWal ){ |
| 636 | db_multi_exec("PRAGMA journal_mode=WAL;"); |
| 637 | } |
| 638 | } |
| 639 | if( showStats ){ |
| 640 | static const struct { int idx; const char *zLabel; } aStat[] = { |
| 641 | { CFTYPE_ANY, "Artifacts:" }, |
| 642 | { CFTYPE_MANIFEST, "Manifests:" }, |
| 643 | { CFTYPE_CLUSTER, "Clusters:" }, |
| 644 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -533,10 +533,12 @@ | |
| 533 | ** --vacuum Run VACUUM on the database after rebuilding |
| 534 | ** --deanalyze Remove ANALYZE tables from the database |
| 535 | ** --analyze Run ANALYZE on the database after rebuilding |
| 536 | ** --wal Set Write-Ahead-Log journalling mode on the database |
| 537 | ** --stats Show artifact statistics after rebuilding |
| 538 | ** --index Always add in the full-text search index |
| 539 | ** --no-index Always omit the full-text search index |
| 540 | ** |
| 541 | ** See also: deconstruct, reconstruct |
| 542 | */ |
| 543 | void rebuild_database(void){ |
| 544 | int forceFlag; |
| @@ -550,10 +552,11 @@ | |
| 552 | int runVacuum; |
| 553 | int runDeanalyze; |
| 554 | int runAnalyze; |
| 555 | int runCompress; |
| 556 | int showStats; |
| 557 | int runReindex; |
| 558 | |
| 559 | omitVerify = find_option("noverify",0,0)!=0; |
| 560 | forceFlag = find_option("force","f",0)!=0; |
| 561 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 562 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -580,15 +583,19 @@ | |
| 583 | usage("?REPOSITORY-FILENAME?"); |
| 584 | } |
| 585 | db_close(1); |
| 586 | db_open_repository(g.zRepositoryName); |
| 587 | } |
| 588 | runReindex = search_index_exists(); |
| 589 | if( find_option("index",0,0)!=0 ) runReindex = 1; |
| 590 | if( find_option("no-index",0,0)!=0 ) runReindex = 0; |
| 591 | |
| 592 | /* We should be done with options.. */ |
| 593 | verify_all_options(); |
| 594 | |
| 595 | db_begin_transaction(); |
| 596 | search_drop_index(); |
| 597 | ttyOutput = 1; |
| 598 | errCnt = rebuild_db(randomizeFlag, 1, doClustering); |
| 599 | reconstruct_private_table(); |
| 600 | db_multi_exec( |
| 601 | "REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());" |
| @@ -634,10 +641,11 @@ | |
| 641 | } |
| 642 | if( activateWal ){ |
| 643 | db_multi_exec("PRAGMA journal_mode=WAL;"); |
| 644 | } |
| 645 | } |
| 646 | if( runReindex ) search_rebuild_index(); |
| 647 | if( showStats ){ |
| 648 | static const struct { int idx; const char *zLabel; } aStat[] = { |
| 649 | { CFTYPE_ANY, "Artifacts:" }, |
| 650 | { CFTYPE_MANIFEST, "Manifests:" }, |
| 651 | { CFTYPE_CLUSTER, "Clusters:" }, |
| 652 |
+3
| --- src/report.c | ||
| +++ src/report.c | ||
| @@ -29,10 +29,12 @@ | ||
| 29 | 29 | # define SQLITE_RECURSIVE 33 |
| 30 | 30 | #endif |
| 31 | 31 | |
| 32 | 32 | /* |
| 33 | 33 | ** WEBPAGE: /reportlist |
| 34 | +** | |
| 35 | +** Main menu for Tickets. | |
| 34 | 36 | */ |
| 35 | 37 | void view_list(void){ |
| 36 | 38 | const char *zScript; |
| 37 | 39 | Blob ril; /* Report Item List */ |
| 38 | 40 | Stmt q; |
| @@ -40,10 +42,11 @@ | ||
| 40 | 42 | int cnt = 0; |
| 41 | 43 | |
| 42 | 44 | login_check_credentials(); |
| 43 | 45 | if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } |
| 44 | 46 | style_header("Ticket Main Menu"); |
| 47 | + ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); | |
| 45 | 48 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 46 | 49 | zScript = ticket_reportlist_code(); |
| 47 | 50 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| 48 | 51 | |
| 49 | 52 | blob_zero(&ril); |
| 50 | 53 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -29,10 +29,12 @@ | |
| 29 | # define SQLITE_RECURSIVE 33 |
| 30 | #endif |
| 31 | |
| 32 | /* |
| 33 | ** WEBPAGE: /reportlist |
| 34 | */ |
| 35 | void view_list(void){ |
| 36 | const char *zScript; |
| 37 | Blob ril; /* Report Item List */ |
| 38 | Stmt q; |
| @@ -40,10 +42,11 @@ | |
| 40 | int cnt = 0; |
| 41 | |
| 42 | login_check_credentials(); |
| 43 | if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } |
| 44 | style_header("Ticket Main Menu"); |
| 45 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 46 | zScript = ticket_reportlist_code(); |
| 47 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| 48 | |
| 49 | blob_zero(&ril); |
| 50 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -29,10 +29,12 @@ | |
| 29 | # define SQLITE_RECURSIVE 33 |
| 30 | #endif |
| 31 | |
| 32 | /* |
| 33 | ** WEBPAGE: /reportlist |
| 34 | ** |
| 35 | ** Main menu for Tickets. |
| 36 | */ |
| 37 | void view_list(void){ |
| 38 | const char *zScript; |
| 39 | Blob ril; /* Report Item List */ |
| 40 | Stmt q; |
| @@ -40,10 +42,11 @@ | |
| 42 | int cnt = 0; |
| 43 | |
| 44 | login_check_credentials(); |
| 45 | if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } |
| 46 | style_header("Ticket Main Menu"); |
| 47 | ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); |
| 48 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 49 | zScript = ticket_reportlist_code(); |
| 50 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| 51 | |
| 52 | blob_zero(&ril); |
| 53 |
+1016
-102
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -45,14 +45,15 @@ | ||
| 45 | 45 | char *zPattern; /* The search pattern */ |
| 46 | 46 | char *zMarkBegin; /* Start of a match */ |
| 47 | 47 | char *zMarkEnd; /* End of a match */ |
| 48 | 48 | char *zMarkGap; /* A gap between two matches */ |
| 49 | 49 | unsigned fSrchFlg; /* Flags */ |
| 50 | + int iScore; /* Score of the last match attempt */ | |
| 51 | + Blob snip; /* Snippet for the most recent match */ | |
| 50 | 52 | }; |
| 51 | 53 | |
| 52 | 54 | #define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */ |
| 53 | -#define SRCHFLG_SCORE 0x02 /* Prepend the score to each snippet */ | |
| 54 | 55 | #define SRCHFLG_STATIC 0x04 /* The static gSearch object */ |
| 55 | 56 | |
| 56 | 57 | #endif |
| 57 | 58 | |
| 58 | 59 | /* |
| @@ -92,10 +93,11 @@ | ||
| 92 | 93 | if( p ){ |
| 93 | 94 | fossil_free(p->zPattern); |
| 94 | 95 | fossil_free(p->zMarkBegin); |
| 95 | 96 | fossil_free(p->zMarkEnd); |
| 96 | 97 | fossil_free(p->zMarkGap); |
| 98 | + if( p->iScore ) blob_reset(&p->snip); | |
| 97 | 99 | memset(p, 0, sizeof(*p)); |
| 98 | 100 | if( p!=&gSearch ) fossil_free(p); |
| 99 | 101 | } |
| 100 | 102 | } |
| 101 | 103 | |
| @@ -123,10 +125,11 @@ | ||
| 123 | 125 | p->zPattern = z = mprintf("%s", zPattern); |
| 124 | 126 | p->zMarkBegin = mprintf("%s", zMarkBegin); |
| 125 | 127 | p->zMarkEnd = mprintf("%s", zMarkEnd); |
| 126 | 128 | p->zMarkGap = mprintf("%s", zMarkGap); |
| 127 | 129 | p->fSrchFlg = fSrchFlg; |
| 130 | + blob_init(&p->snip, 0, 0); | |
| 128 | 131 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 129 | 132 | while( *z && !ISALNUM(*z) ){ z++; } |
| 130 | 133 | if( *z==0 ) break; |
| 131 | 134 | p->a[p->nTerm].z = z; |
| 132 | 135 | for(i=1; ISALNUM(z[i]); i++){} |
| @@ -156,24 +159,26 @@ | ||
| 156 | 159 | } |
| 157 | 160 | } |
| 158 | 161 | |
| 159 | 162 | /* |
| 160 | 163 | ** Compare a search pattern against one or more input strings which |
| 161 | -** collectively comprise a document. Return a match score. Optionally | |
| 162 | -** also return a "snippet". | |
| 164 | +** collectively comprise a document. Return a match score. Any | |
| 165 | +** postive value means there was a match. Zero means that one or | |
| 166 | +** more terms are missing. | |
| 167 | +** | |
| 168 | +** The score and a snippet are record for future use. | |
| 163 | 169 | ** |
| 164 | 170 | ** Scoring: |
| 165 | 171 | ** * All terms must match at least once or the score is zero |
| 166 | 172 | ** * One point for each matching term |
| 167 | 173 | ** * Extra points if consecutive words of the pattern are consecutive |
| 168 | 174 | ** in the document |
| 169 | 175 | */ |
| 170 | -static int search_score( | |
| 176 | +static int search_match( | |
| 171 | 177 | Search *p, /* Search pattern and flags */ |
| 172 | 178 | int nDoc, /* Number of strings in this document */ |
| 173 | - const char **azDoc, /* Text of each string */ | |
| 174 | - Blob *pSnip /* If not NULL: Write a snippet here */ | |
| 179 | + const char **azDoc /* Text of each string */ | |
| 175 | 180 | ){ |
| 176 | 181 | int score; /* Final score */ |
| 177 | 182 | int i; /* Offset into current document */ |
| 178 | 183 | int ii; /* Loop counter */ |
| 179 | 184 | int j; /* Loop over search terms */ |
| @@ -217,26 +222,26 @@ | ||
| 217 | 222 | } |
| 218 | 223 | break; |
| 219 | 224 | } |
| 220 | 225 | } |
| 221 | 226 | while( ISALNUM(zDoc[i]) ){ i++; } |
| 227 | + if( zDoc[i]==0 ) break; | |
| 222 | 228 | } |
| 223 | 229 | } |
| 224 | 230 | |
| 225 | 231 | /* Finished search all documents. |
| 226 | 232 | ** Every term must be seen or else the score is zero |
| 227 | 233 | */ |
| 228 | 234 | score = 1; |
| 229 | 235 | for(j=0; j<p->nTerm; j++) score *= anMatch[j]; |
| 230 | - if( score==0 || pSnip==0 ) return score; | |
| 236 | + blob_reset(&p->snip); | |
| 237 | + p->iScore = score; | |
| 238 | + if( score==0 ) return score; | |
| 231 | 239 | |
| 232 | 240 | |
| 233 | 241 | /* Prepare a snippet that describes the matching text. |
| 234 | 242 | */ |
| 235 | - blob_init(pSnip, 0, 0); | |
| 236 | - if( p->fSrchFlg & SRCHFLG_SCORE ) blob_appendf(pSnip, "%08x", score); | |
| 237 | - | |
| 238 | 243 | while(1){ |
| 239 | 244 | int iOfst; |
| 240 | 245 | int iTail; |
| 241 | 246 | int iBest; |
| 242 | 247 | for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){} |
| @@ -271,11 +276,11 @@ | ||
| 271 | 276 | if( iOfst<0 ) iOfst = 0; |
| 272 | 277 | while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--; |
| 273 | 278 | while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++; |
| 274 | 279 | for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){} |
| 275 | 280 | while( ISALNUM(zDoc[iTail]) ) iTail++; |
| 276 | - if( iOfst>0 || wantGap ) blob_append(pSnip, p->zMarkGap, -1); | |
| 281 | + if( iOfst>0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1); | |
| 277 | 282 | wantGap = zDoc[iTail]!=0; |
| 278 | 283 | zDoc += iOfst; |
| 279 | 284 | iTail -= iOfst; |
| 280 | 285 | |
| 281 | 286 | /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */ |
| @@ -284,53 +289,51 @@ | ||
| 284 | 289 | for(j=0; j<p->nTerm; j++){ |
| 285 | 290 | int n = p->a[j].n; |
| 286 | 291 | if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 |
| 287 | 292 | && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') |
| 288 | 293 | ){ |
| 289 | - snippet_text_append(p, pSnip, zDoc, i); | |
| 294 | + snippet_text_append(p, &p->snip, zDoc, i); | |
| 290 | 295 | zDoc += i; |
| 291 | 296 | iTail -= i; |
| 292 | - blob_append(pSnip, p->zMarkBegin, -1); | |
| 297 | + blob_append(&p->snip, p->zMarkBegin, -1); | |
| 293 | 298 | if( p->a[j].z[n]=='*' ){ |
| 294 | 299 | while( ISALNUM(zDoc[n]) ) n++; |
| 295 | 300 | } |
| 296 | - snippet_text_append(p, pSnip, zDoc, n); | |
| 301 | + snippet_text_append(p, &p->snip, zDoc, n); | |
| 297 | 302 | zDoc += n; |
| 298 | 303 | iTail -= n; |
| 299 | - blob_append(pSnip, p->zMarkEnd, -1); | |
| 304 | + blob_append(&p->snip, p->zMarkEnd, -1); | |
| 300 | 305 | i = -1; |
| 301 | 306 | break; |
| 302 | 307 | } /* end-if */ |
| 303 | 308 | } /* end for(j) */ |
| 304 | 309 | if( j<p->nTerm ){ |
| 305 | 310 | while( ISALNUM(zDoc[i]) && i<iTail ){ i++; } |
| 306 | 311 | } |
| 307 | 312 | } /* end for(i) */ |
| 308 | - snippet_text_append(p, pSnip, zDoc, iTail); | |
| 313 | + snippet_text_append(p, &p->snip, zDoc, iTail); | |
| 309 | 314 | } |
| 310 | - if( wantGap ) blob_append(pSnip, p->zMarkGap, -1); | |
| 315 | + if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1); | |
| 311 | 316 | return score; |
| 312 | 317 | } |
| 313 | 318 | |
| 314 | 319 | /* |
| 315 | -** COMMAND: test-snippet | |
| 320 | +** COMMAND: test-match | |
| 316 | 321 | ** |
| 317 | -** Usage: fossil test-snippet SEARCHSTRING FILE1 FILE2 ... | |
| 322 | +** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ... | |
| 318 | 323 | */ |
| 319 | -void test_snippet_cmd(void){ | |
| 324 | +void test_match_cmd(void){ | |
| 320 | 325 | Search *p; |
| 321 | 326 | int i; |
| 322 | 327 | Blob x; |
| 323 | - Blob snip; | |
| 324 | 328 | int score; |
| 325 | 329 | char *zDoc; |
| 326 | 330 | int flg = 0; |
| 327 | 331 | char *zBegin = (char*)find_option("begin",0,1); |
| 328 | 332 | char *zEnd = (char*)find_option("end",0,1); |
| 329 | 333 | char *zGap = (char*)find_option("gap",0,1); |
| 330 | 334 | if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML; |
| 331 | - if( find_option("score",0,0)!=0 ) flg |= SRCHFLG_SCORE; | |
| 332 | 335 | if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC; |
| 333 | 336 | verify_all_options(); |
| 334 | 337 | if( g.argc<4 ) usage("SEARCHSTRING FILE1..."); |
| 335 | 338 | if( zBegin==0 ) zBegin = "[["; |
| 336 | 339 | if( zEnd==0 ) zEnd = "]]"; |
| @@ -337,18 +340,18 @@ | ||
| 337 | 340 | if( zGap==0 ) zGap = " ... "; |
| 338 | 341 | p = search_init(g.argv[2], zBegin, zEnd, zGap, flg); |
| 339 | 342 | for(i=3; i<g.argc; i++){ |
| 340 | 343 | blob_read_from_file(&x, g.argv[i]); |
| 341 | 344 | zDoc = blob_str(&x); |
| 342 | - score = search_score(p, 1, (const char**)&zDoc, &snip); | |
| 343 | - fossil_print("%s: %d\n", g.argv[i], score); | |
| 345 | + score = search_match(p, 1, (const char**)&zDoc); | |
| 346 | + fossil_print("%s: %d\n", g.argv[i], p->iScore); | |
| 344 | 347 | blob_reset(&x); |
| 345 | 348 | if( score ){ |
| 346 | - fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&snip), '='); | |
| 347 | - blob_reset(&snip); | |
| 349 | + fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '='); | |
| 348 | 350 | } |
| 349 | 351 | } |
| 352 | + search_end(p); | |
| 350 | 353 | } |
| 351 | 354 | |
| 352 | 355 | /* |
| 353 | 356 | ** An SQL function to initialize the global search pattern: |
| 354 | 357 | ** |
| @@ -360,12 +363,12 @@ | ||
| 360 | 363 | sqlite3_context *context, |
| 361 | 364 | int argc, |
| 362 | 365 | sqlite3_value **argv |
| 363 | 366 | ){ |
| 364 | 367 | const char *zPattern = 0; |
| 365 | - const char *zBegin = "<b>"; | |
| 366 | - const char *zEnd = "</b>"; | |
| 368 | + const char *zBegin = "<mark>"; | |
| 369 | + const char *zEnd = "</mark>"; | |
| 367 | 370 | const char *zGap = " ... "; |
| 368 | 371 | unsigned int flg = SRCHFLG_HTML; |
| 369 | 372 | switch( argc ){ |
| 370 | 373 | default: |
| 371 | 374 | flg = (unsigned int)sqlite3_value_int(argv[4]); |
| @@ -384,50 +387,98 @@ | ||
| 384 | 387 | search_end(&gSearch); |
| 385 | 388 | } |
| 386 | 389 | } |
| 387 | 390 | |
| 388 | 391 | /* |
| 389 | -** This is an SQLite function that scores its input using | |
| 390 | -** the pattern from the previous call to search_init(). | |
| 392 | +** Try to match the input text against the search parameters set up | |
| 393 | +** by the previous search_init() call. Remember the results globally. | |
| 394 | +** Return non-zero on a match and zero on a miss. | |
| 395 | +*/ | |
| 396 | +static void search_match_sqlfunc( | |
| 397 | + sqlite3_context *context, | |
| 398 | + int argc, | |
| 399 | + sqlite3_value **argv | |
| 400 | +){ | |
| 401 | + const char *zSText = (const char*)sqlite3_value_text(argv[0]); | |
| 402 | + int rc; | |
| 403 | + if( zSText==0 ) return; | |
| 404 | + rc = search_match(&gSearch, 1, &zSText); | |
| 405 | + sqlite3_result_int(context, rc); | |
| 406 | +} | |
| 407 | + | |
| 408 | +/* | |
| 409 | +** These SQL functions return the results of the last | |
| 410 | +** call to the search_match() SQL function. | |
| 391 | 411 | */ |
| 392 | 412 | static void search_score_sqlfunc( |
| 393 | 413 | sqlite3_context *context, |
| 394 | 414 | int argc, |
| 395 | 415 | sqlite3_value **argv |
| 396 | 416 | ){ |
| 397 | - int isSnippet = sqlite3_user_data(context)!=0; | |
| 398 | - const char **azDoc; | |
| 399 | - int score; | |
| 400 | - int i; | |
| 401 | - Blob snip; | |
| 402 | - | |
| 403 | - if( gSearch.nTerm==0 ) return; | |
| 404 | - azDoc = fossil_malloc( sizeof(const char*)*(argc+1) ); | |
| 405 | - for(i=0; i<argc; i++) azDoc[i] = (const char*)sqlite3_value_text(argv[i]); | |
| 406 | - score = search_score(&gSearch, argc, azDoc, isSnippet ? &snip : 0); | |
| 407 | - fossil_free((void *)azDoc); | |
| 408 | - if( isSnippet ){ | |
| 409 | - if( score ){ | |
| 410 | - sqlite3_result_text(context, blob_materialize(&snip), -1, fossil_free); | |
| 411 | - } | |
| 412 | - }else{ | |
| 413 | - sqlite3_result_int(context, score); | |
| 414 | - } | |
| 417 | + sqlite3_result_int(context, gSearch.iScore); | |
| 418 | +} | |
| 419 | +static void search_snippet_sqlfunc( | |
| 420 | + sqlite3_context *context, | |
| 421 | + int argc, | |
| 422 | + sqlite3_value **argv | |
| 423 | +){ | |
| 424 | + if( blob_size(&gSearch.snip)>0 ){ | |
| 425 | + sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free); | |
| 426 | + blob_init(&gSearch.snip, 0, 0); | |
| 427 | + } | |
| 428 | +} | |
| 429 | + | |
| 430 | +/* | |
| 431 | +** This is an SQLite function that computes the searchable text. | |
| 432 | +** It is a wrapper around the search_stext() routine. See the | |
| 433 | +** search_stext() routine for further detail. | |
| 434 | +*/ | |
| 435 | +static void search_stext_sqlfunc( | |
| 436 | + sqlite3_context *context, | |
| 437 | + int argc, | |
| 438 | + sqlite3_value **argv | |
| 439 | +){ | |
| 440 | + Blob txt; | |
| 441 | + const char *zType = (const char*)sqlite3_value_text(argv[0]); | |
| 442 | + int rid = sqlite3_value_int(argv[1]); | |
| 443 | + const char *zName = (const char*)sqlite3_value_text(argv[2]); | |
| 444 | + search_stext(zType[0], rid, zName, &txt); | |
| 445 | + sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); | |
| 446 | +} | |
| 447 | + | |
| 448 | +/* | |
| 449 | +** Encode a string for use as a query parameter in a URL | |
| 450 | +*/ | |
| 451 | +static void search_urlencode_sqlfunc( | |
| 452 | + sqlite3_context *context, | |
| 453 | + int argc, | |
| 454 | + sqlite3_value **argv | |
| 455 | +){ | |
| 456 | + char *z = mprintf("%T",sqlite3_value_text(argv[0])); | |
| 457 | + sqlite3_result_text(context, z, -1, fossil_free); | |
| 415 | 458 | } |
| 416 | 459 | |
| 417 | 460 | /* |
| 418 | 461 | ** Register the "score()" SQL function to score its input text |
| 419 | 462 | ** using the given Search object. Once this function is registered, |
| 420 | 463 | ** do not delete the Search object. |
| 421 | 464 | */ |
| 422 | 465 | void search_sql_setup(sqlite3 *db){ |
| 423 | - sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, | |
| 424 | - search_score_sqlfunc, 0, 0); | |
| 425 | - sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, | |
| 466 | + static int once = 0; | |
| 467 | + if( once++ ) return; | |
| 468 | + sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0, | |
| 469 | + search_match_sqlfunc, 0, 0); | |
| 470 | + sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, | |
| 426 | 471 | search_score_sqlfunc, 0, 0); |
| 472 | + sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, | |
| 473 | + search_snippet_sqlfunc, 0, 0); | |
| 427 | 474 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 428 | 475 | search_init_sqlfunc, 0, 0); |
| 476 | + sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, | |
| 477 | + search_stext_sqlfunc, 0, 0); | |
| 478 | + sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, | |
| 479 | + search_urlencode_sqlfunc, 0, 0); | |
| 429 | 480 | } |
| 430 | 481 | |
| 431 | 482 | /* |
| 432 | 483 | ** Testing the search function. |
| 433 | 484 | ** |
| @@ -501,67 +552,930 @@ | ||
| 501 | 552 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 502 | 553 | blob_reset(&sql); |
| 503 | 554 | print_timeline(&q, nLimit, width, 0); |
| 504 | 555 | db_finalize(&q); |
| 505 | 556 | } |
| 557 | + | |
| 558 | +#if INTERFACE | |
| 559 | +/* What to search for */ | |
| 560 | +#define SRCH_CKIN 0x0001 /* Search over check-in comments */ | |
| 561 | +#define SRCH_DOC 0x0002 /* Search over embedded documents */ | |
| 562 | +#define SRCH_TKT 0x0004 /* Search over tickets */ | |
| 563 | +#define SRCH_WIKI 0x0008 /* Search over wiki */ | |
| 564 | +#define SRCH_ALL 0x000f /* Search over everything */ | |
| 565 | +#endif | |
| 566 | + | |
| 567 | +/* | |
| 568 | +** Remove bits from srchFlags which are disallowed by either the | |
| 569 | +** current server configuration or by user permissions. | |
| 570 | +*/ | |
| 571 | +unsigned int search_restrict(unsigned int srchFlags){ | |
| 572 | + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); | |
| 573 | + if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); | |
| 574 | + if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); | |
| 575 | + if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){ | |
| 576 | + srchFlags &= ~SRCH_CKIN; | |
| 577 | + } | |
| 578 | + if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){ | |
| 579 | + srchFlags &= ~SRCH_DOC; | |
| 580 | + } | |
| 581 | + if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){ | |
| 582 | + srchFlags &= ~SRCH_TKT; | |
| 583 | + } | |
| 584 | + if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){ | |
| 585 | + srchFlags &= ~SRCH_WIKI; | |
| 586 | + } | |
| 587 | + return srchFlags; | |
| 588 | +} | |
| 589 | + | |
| 590 | +/* | |
| 591 | +** When this routine is called, there already exists a table | |
| 592 | +** | |
| 593 | +** x(label,url,score,date,snip). | |
| 594 | +** | |
| 595 | +** And the srchFlags parameter has been validated. This routine | |
| 596 | +** fills the X table with search results using a full-text scan. | |
| 597 | +** | |
| 598 | +** The companion indexed scan routine is search_indexed(). | |
| 599 | +*/ | |
| 600 | +static void search_fullscan( | |
| 601 | + const char *zPattern, /* The query pattern */ | |
| 602 | + unsigned int srchFlags /* What to search over */ | |
| 603 | +){ | |
| 604 | + search_init(zPattern, "<mark>", "</mark>", " ... ", | |
| 605 | + SRCHFLG_STATIC|SRCHFLG_HTML); | |
| 606 | + if( (srchFlags & SRCH_DOC)!=0 ){ | |
| 607 | + char *zDocGlob = db_get("doc-glob",""); | |
| 608 | + char *zDocBr = db_get("doc-branch","trunk"); | |
| 609 | + if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ | |
| 610 | + db_multi_exec( | |
| 611 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 612 | + ); | |
| 613 | + db_multi_exec( | |
| 614 | + "INSERT INTO x(label,url,score,date,snip)" | |
| 615 | + " SELECT printf('Document: %%s',foci.filename)," | |
| 616 | + " printf('%R/doc/%T/%%s',foci.filename)," | |
| 617 | + " search_score()," | |
| 618 | + " (SELECT datetime(event.mtime) FROM event" | |
| 619 | + " WHERE objid=symbolic_name_to_rid('trunk'))," | |
| 620 | + " search_snippet()" | |
| 621 | + " FROM foci CROSS JOIN blob" | |
| 622 | + " WHERE checkinID=symbolic_name_to_rid('trunk')" | |
| 623 | + " AND blob.uuid=foci.uuid" | |
| 624 | + " AND search_match(stext('d',blob.rid,foci.filename))" | |
| 625 | + " AND %z", | |
| 626 | + zDocBr, glob_expr("foci.filename", zDocGlob) | |
| 627 | + ); | |
| 628 | + } | |
| 629 | + } | |
| 630 | + if( (srchFlags & SRCH_WIKI)!=0 ){ | |
| 631 | + db_multi_exec( | |
| 632 | + "WITH wiki(name,rid,mtime) AS (" | |
| 633 | + " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" | |
| 634 | + " FROM tag, tagxref" | |
| 635 | + " WHERE tag.tagname GLOB 'wiki-*'" | |
| 636 | + " AND tagxref.tagid=tag.tagid" | |
| 637 | + " GROUP BY 1" | |
| 638 | + ")" | |
| 639 | + "INSERT INTO x(label,url,score,date,snip)" | |
| 640 | + " SELECT printf('Wiki: %%s',name)," | |
| 641 | + " printf('%R/wiki?name=%%s',urlencode(name))," | |
| 642 | + " search_score()," | |
| 643 | + " datetime(mtime)," | |
| 644 | + " search_snippet()" | |
| 645 | + " FROM wiki" | |
| 646 | + " WHERE search_match(stext('w',rid,name));" | |
| 647 | + ); | |
| 648 | + } | |
| 649 | + if( (srchFlags & SRCH_CKIN)!=0 ){ | |
| 650 | + db_multi_exec( | |
| 651 | + "WITH ckin(uuid,rid,mtime) AS (" | |
| 652 | + " SELECT blob.uuid, event.objid, event.mtime" | |
| 653 | + " FROM event, blob" | |
| 654 | + " WHERE event.type='ci'" | |
| 655 | + " AND blob.rid=event.objid" | |
| 656 | + ")" | |
| 657 | + "INSERT INTO x(label,url,score,date,snip)" | |
| 658 | + " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," | |
| 659 | + " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," | |
| 660 | + " search_score()," | |
| 661 | + " datetime(mtime)," | |
| 662 | + " search_snippet()" | |
| 663 | + " FROM ckin" | |
| 664 | + " WHERE search_match(stext('c',rid,NULL));" | |
| 665 | + ); | |
| 666 | + } | |
| 667 | + if( (srchFlags & SRCH_TKT)!=0 ){ | |
| 668 | + db_multi_exec( | |
| 669 | + "INSERT INTO x(label,url,score, date,snip)" | |
| 670 | + " SELECT printf('Ticket [%%.17s] on %%s'," | |
| 671 | + "tkt_uuid,datetime(tkt_mtime))," | |
| 672 | + " printf('%R/tktview/%%.20s',tkt_uuid)," | |
| 673 | + " search_score()," | |
| 674 | + " datetime(tkt_mtime)," | |
| 675 | + " search_snippet()" | |
| 676 | + " FROM ticket" | |
| 677 | + " WHERE search_match(stext('t',tkt_id,NULL));" | |
| 678 | + ); | |
| 679 | + } | |
| 680 | +} | |
| 681 | + | |
| 682 | +/* | |
| 683 | +** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). | |
| 684 | +*/ | |
| 685 | +static void search_rank_sqlfunc( | |
| 686 | + sqlite3_context *context, | |
| 687 | + int argc, | |
| 688 | + sqlite3_value **argv | |
| 689 | +){ | |
| 690 | + const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); | |
| 691 | + int nVal = sqlite3_value_bytes(argv[0])/4; | |
| 692 | + int nTerm; /* Number of search terms in the query */ | |
| 693 | + int i; /* Loop counter */ | |
| 694 | + double r = 1.0; /* Score */ | |
| 695 | + | |
| 696 | + if( nVal<6 ) return; | |
| 697 | + if( aVal[1]!=1 ) return; | |
| 698 | + nTerm = aVal[0]; | |
| 699 | + r *= 1<<((30*(aVal[2]-1))/nTerm); | |
| 700 | + for(i=1; i<=nTerm; i++){ | |
| 701 | + int hits_this_row = aVal[3*i]; | |
| 702 | + int hits_all_rows = aVal[3*i+1]; | |
| 703 | + int rows_with_hit = aVal[3*i+2]; | |
| 704 | + double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; | |
| 705 | + r *= hits_this_row/avg_hits_per_row; | |
| 706 | + } | |
| 707 | +#define SEARCH_DEBUG_RANK 0 | |
| 708 | +#if SEARCH_DEBUG_RANK | |
| 709 | + { | |
| 710 | + Blob x; | |
| 711 | + blob_init(&x,0,0); | |
| 712 | + blob_appendf(&x,"%08x", (int)r); | |
| 713 | + for(i=0; i<nVal; i++){ | |
| 714 | + blob_appendf(&x," %d", aVal[i]); | |
| 715 | + } | |
| 716 | + blob_appendf(&x," r=%g", r); | |
| 717 | + sqlite3_result_text(context, blob_str(&x), -1, fossil_free); | |
| 718 | + } | |
| 719 | +#else | |
| 720 | + sqlite3_result_double(context, r); | |
| 721 | +#endif | |
| 722 | +} | |
| 723 | + | |
| 724 | +/* | |
| 725 | +** When this routine is called, there already exists a table | |
| 726 | +** | |
| 727 | +** x(label,url,score,date,snip). | |
| 728 | +** | |
| 729 | +** And the srchFlags parameter has been validated. This routine | |
| 730 | +** fills the X table with search results using a index scan. | |
| 731 | +** | |
| 732 | +** The companion full-text scan routine is search_fullscan(). | |
| 733 | +*/ | |
| 734 | +static void search_indexed( | |
| 735 | + const char *zPattern, /* The query pattern */ | |
| 736 | + unsigned int srchFlags /* What to search over */ | |
| 737 | +){ | |
| 738 | + Blob sql; | |
| 739 | + if( srchFlags==0 ) return; | |
| 740 | + sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, | |
| 741 | + search_rank_sqlfunc, 0, 0); | |
| 742 | + blob_init(&sql, 0, 0); | |
| 743 | + blob_appendf(&sql, | |
| 744 | + "INSERT INTO x(label,url,score,date,snip) " | |
| 745 | + " SELECT ftsdocs.label," | |
| 746 | + " ftsdocs.url," | |
| 747 | + " rank(matchinfo(ftsidx,'pcsx'))," | |
| 748 | + " datetime(ftsdocs.mtime)," | |
| 749 | + " snippet(ftsidx,'<mark>','</mark>',' ... ')" | |
| 750 | + " FROM ftsidx CROSS JOIN ftsdocs" | |
| 751 | + " WHERE ftsidx MATCH %Q" | |
| 752 | + " AND ftsdocs.rowid=ftsidx.docid", | |
| 753 | + zPattern | |
| 754 | + ); | |
| 755 | + if( srchFlags!=SRCH_ALL ){ | |
| 756 | + const char *zSep = " AND ("; | |
| 757 | + static const struct { unsigned m; char c; } aMask[] = { | |
| 758 | + { SRCH_CKIN, 'c' }, | |
| 759 | + { SRCH_DOC, 'd' }, | |
| 760 | + { SRCH_TKT, 't' }, | |
| 761 | + { SRCH_WIKI, 'w' }, | |
| 762 | + }; | |
| 763 | + int i; | |
| 764 | + for(i=0; i<ArraySize(aMask); i++){ | |
| 765 | + if( srchFlags & aMask[i].m ){ | |
| 766 | + blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c); | |
| 767 | + zSep = " OR "; | |
| 768 | + } | |
| 769 | + } | |
| 770 | + blob_append(&sql,")",1); | |
| 771 | + } | |
| 772 | + db_multi_exec("%s",blob_str(&sql)/*safe-for-%s*/); | |
| 773 | +#if SEARCH_DEBUG_RANK | |
| 774 | + db_multi_exec("UPDATE x SET label=printf('%%s (score=%%s)',label,score)"); | |
| 775 | +#endif | |
| 776 | +} | |
| 777 | + | |
| 778 | +/* | |
| 779 | +** If z[] is of the form "<mark>TEXT</mark>" where TEXT contains | |
| 780 | +** no white-space or punctuation, then return the length of the mark. | |
| 781 | +** If | |
| 782 | +*/ | |
| 783 | +static int isSnippetMark(const char *z){ | |
| 784 | + int n; | |
| 785 | + if( strncmp(z,"<mark>",6)!=0 ) return 0; | |
| 786 | + n = 6; | |
| 787 | + while( fossil_isalnum(z[n]) ) n++; | |
| 788 | + if( strncmp(&z[n],"</mark>",7)!=0 ) return 0; | |
| 789 | + return n+7; | |
| 790 | +} | |
| 791 | + | |
| 792 | +/* | |
| 793 | +** Return a copy of zSnip (in memory obtained from fossil_malloc()) that | |
| 794 | +** has all "<" characters, other than those on <mark> and </mark>, | |
| 795 | +** converted into "<". This is similar to htmlize() except that | |
| 796 | +** <mark> and </mark> are preserved. | |
| 797 | +*/ | |
| 798 | +static char *cleanSnippet(const char *zSnip){ | |
| 799 | + int i; | |
| 800 | + int n = 0; | |
| 801 | + char *z; | |
| 802 | + for(i=0; zSnip[i]; i++) if( zSnip[i]=='<' ) n++; | |
| 803 | + z = fossil_malloc( i+n*4+1 ); | |
| 804 | + i = 0; | |
| 805 | + while( zSnip[0] ){ | |
| 806 | + if( zSnip[0]=='<' ){ | |
| 807 | + n = isSnippetMark(zSnip); | |
| 808 | + if( n ){ | |
| 809 | + memcpy(&z[i], zSnip, n); | |
| 810 | + zSnip += n; | |
| 811 | + i += n; | |
| 812 | + continue; | |
| 813 | + }else{ | |
| 814 | + memcpy(&z[i], "<", 4); | |
| 815 | + i += 4; | |
| 816 | + zSnip++; | |
| 817 | + } | |
| 818 | + }else{ | |
| 819 | + z[i++] = zSnip[0]; | |
| 820 | + zSnip++; | |
| 821 | + } | |
| 822 | + } | |
| 823 | + z[i] = 0; | |
| 824 | + return z; | |
| 825 | +} | |
| 826 | + | |
| 827 | + | |
| 828 | +/* | |
| 829 | +** This routine generates web-page output for a search operation. | |
| 830 | +** Other web-pages can invoke this routine to add search results | |
| 831 | +** in the middle of the page. | |
| 832 | +** | |
| 833 | +** Return the number of rows. | |
| 834 | +*/ | |
| 835 | +int search_run_and_output( | |
| 836 | + const char *zPattern, /* The query pattern */ | |
| 837 | + unsigned int srchFlags /* What to search over */ | |
| 838 | +){ | |
| 839 | + Stmt q; | |
| 840 | + int nRow = 0; | |
| 841 | + | |
| 842 | + srchFlags = search_restrict(srchFlags); | |
| 843 | + if( srchFlags==0 ) return 0; | |
| 844 | + search_sql_setup(g.db); | |
| 845 | + add_content_sql_commands(g.db); | |
| 846 | + db_multi_exec( | |
| 847 | + "CREATE TEMP TABLE x(label,url,score,date,snip);" | |
| 848 | + ); | |
| 849 | + if( !search_index_exists() ){ | |
| 850 | + search_fullscan(zPattern, srchFlags); | |
| 851 | + }else{ | |
| 852 | + search_update_index(srchFlags); | |
| 853 | + search_indexed(zPattern, srchFlags); | |
| 854 | + } | |
| 855 | + db_prepare(&q, "SELECT url, snip, label" | |
| 856 | + " FROM x" | |
| 857 | + " ORDER BY score DESC, date DESC;"); | |
| 858 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 859 | + const char *zUrl = db_column_text(&q, 0); | |
| 860 | + const char *zSnippet = db_column_text(&q, 1); | |
| 861 | + const char *zLabel = db_column_text(&q, 2); | |
| 862 | + if( nRow==0 ){ | |
| 863 | + @ <ol> | |
| 864 | + } | |
| 865 | + nRow++; | |
| 866 | + @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> | |
| 867 | + @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> | |
| 868 | + } | |
| 869 | + db_finalize(&q); | |
| 870 | + if( nRow ){ | |
| 871 | + @ </ol> | |
| 872 | + } | |
| 873 | + return nRow; | |
| 874 | +} | |
| 875 | + | |
| 876 | +/* | |
| 877 | +** Generate some HTML for doing search. At a minimum include the | |
| 878 | +** Search-Text entry form. If the "s" query parameter is present, also | |
| 879 | +** show search results. | |
| 880 | +** | |
| 881 | +** The srchFlags parameter is used to customize some of the text of the | |
| 882 | +** form and the results. srchFlags should be either a single search | |
| 883 | +** category or all categories. Any srchFlags with two or more bits set | |
| 884 | +** is treated like SRCH_ALL for display purposes. | |
| 885 | +** | |
| 886 | +** The entry box is shown disabled if srchFlags is 0. | |
| 887 | +*/ | |
| 888 | +void search_screen(unsigned srchFlags, const char *zAction){ | |
| 889 | + const char *zType = 0; | |
| 890 | + const char *zClass = 0; | |
| 891 | + const char *zDisable; | |
| 892 | + const char *zPattern; | |
| 893 | + switch( srchFlags ){ | |
| 894 | + case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; | |
| 895 | + case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; | |
| 896 | + case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; | |
| 897 | + case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; | |
| 898 | + } | |
| 899 | + srchFlags = search_restrict(srchFlags); | |
| 900 | + if( srchFlags==0 ){ | |
| 901 | + zDisable = " disabled"; | |
| 902 | + zPattern = ""; | |
| 903 | + }else{ | |
| 904 | + zDisable = ""; | |
| 905 | + zPattern = PD("s",""); | |
| 906 | + } | |
| 907 | + @ <form method='GET' action='%s(zAction)'> | |
| 908 | + if( zClass ){ | |
| 909 | + @ <div class='searchForm searchForm%s(zClass)'> | |
| 910 | + }else{ | |
| 911 | + @ <div class='searchForm'> | |
| 912 | + } | |
| 913 | + @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable)> | |
| 914 | + @ <input type="submit" value="Search%s(zType)"%s(zDisable)> | |
| 915 | + if( srchFlags==0 ){ | |
| 916 | + @ <p class="generalError">Search is disabled</p> | |
| 917 | + } | |
| 918 | + @ </div></form> | |
| 919 | + while( fossil_isspace(zPattern[0]) ) zPattern++; | |
| 920 | + if( zPattern[0] ){ | |
| 921 | + if( zClass ){ | |
| 922 | + @ <div class='searchResult searchResult%s(zClass)'> | |
| 923 | + }else{ | |
| 924 | + @ <div class='searchResult'> | |
| 925 | + } | |
| 926 | + if( search_run_and_output(zPattern, srchFlags)==0 ){ | |
| 927 | + @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> | |
| 928 | + } | |
| 929 | + @ </div> | |
| 930 | + } | |
| 931 | +} | |
| 506 | 932 | |
| 507 | 933 | /* |
| 508 | 934 | ** WEBPAGE: /search |
| 509 | 935 | ** |
| 510 | -** This is an EXPERIMENTAL page for doing search across a repository. | |
| 511 | -** | |
| 512 | -** The current implementation does a full text search over embedded | |
| 513 | -** documentation files on the tip of the "trunk" branch. Only files | |
| 514 | -** ending in ".wiki", ".md", ".html", and ".txt" are searched. | |
| 515 | -** | |
| 516 | -** The entire text is scanned. There is no full-text index. This is | |
| 517 | -** experimental. We may change to using a full-text index depending | |
| 518 | -** on performance. | |
| 519 | -** | |
| 520 | -** Other pending enhancements: | |
| 521 | -** * Search tickets | |
| 522 | -** * Search wiki | |
| 936 | +** Search for check-in comments, documents, tickets, or wiki that | |
| 937 | +** match a user-supplied pattern. | |
| 523 | 938 | */ |
| 524 | 939 | void search_page(void){ |
| 525 | - const char *zPattern = PD("s",""); | |
| 526 | - Stmt q; | |
| 940 | + unsigned srchFlags = SRCH_ALL; | |
| 941 | + const char *zOnly = P("only"); | |
| 527 | 942 | |
| 528 | 943 | login_check_credentials(); |
| 529 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 944 | + if( zOnly ){ | |
| 945 | + if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN; | |
| 946 | + if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC; | |
| 947 | + if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT; | |
| 948 | + if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI; | |
| 949 | + } | |
| 530 | 950 | style_header("Search"); |
| 531 | - @ <form method="GET" action="search"><center> | |
| 532 | - @ <input type="text" name="s" size="40" value="%h(zPattern)"> | |
| 533 | - @ <input type="submit" value="Search"> | |
| 534 | - @ </center></form> | |
| 535 | - while( fossil_isspace(zPattern[0]) ) zPattern++; | |
| 536 | - if( zPattern[0] ){ | |
| 537 | - search_sql_setup(g.db); | |
| 538 | - add_content_sql_commands(g.db); | |
| 539 | - search_init(zPattern, "<b>", "</b>", " ... ", | |
| 540 | - SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); | |
| 541 | - db_multi_exec( | |
| 542 | - "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;" | |
| 543 | - "CREATE TEMP TABLE x(fn TEXT,url TEXT,snip TEXT);" | |
| 544 | - "INSERT INTO x(fn,url,snip)" | |
| 545 | - " SELECT filename, printf('%R/doc/trunk/%%s',filename)," | |
| 546 | - " snippet(content(uuid))" | |
| 547 | - " FROM foci" | |
| 548 | - " WHERE checkinID=symbolic_name_to_rid('trunk')" | |
| 549 | - " AND (filename GLOB '*.wiki' OR" | |
| 550 | - " filename GLOB '*.md' OR" | |
| 551 | - " filename GLOB '*.txt' OR" | |
| 552 | - " filename GLOB '*.html');" | |
| 553 | - ); | |
| 554 | - db_prepare(&q, "SELECT url, substr(snip,8)" | |
| 555 | - " FROM x WHERE snip IS NOT NULL" | |
| 556 | - " ORDER BY substr(snip,1,8) DESC, fn;"); | |
| 557 | - @ <ol> | |
| 558 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 559 | - const char *zUrl = db_column_text(&q, 0); | |
| 560 | - const char *zSnippet = db_column_text(&q, 1); | |
| 561 | - @ <li><p>%s(href("%s",zUrl))%h(zUrl)</a><br>%s(zSnippet)</li> | |
| 562 | - } | |
| 563 | - db_finalize(&q); | |
| 564 | - @ </ol> | |
| 565 | - } | |
| 951 | + search_screen(srchFlags, "search"); | |
| 566 | 952 | style_footer(); |
| 567 | 953 | } |
| 954 | + | |
| 955 | + | |
| 956 | +/* | |
| 957 | +** This is a helper function for search_stext(). Writing into pOut | |
| 958 | +** the search text obtained from pIn according to zMimetype. | |
| 959 | +*/ | |
| 960 | +static void get_stext_by_mimetype( | |
| 961 | + Blob *pIn, | |
| 962 | + const char *zMimetype, | |
| 963 | + Blob *pOut | |
| 964 | +){ | |
| 965 | + Blob html, title; | |
| 966 | + blob_init(&html, 0, 0); | |
| 967 | + blob_init(&title, 0, 0); | |
| 968 | + if( zMimetype==0 ) zMimetype = "text/plain"; | |
| 969 | + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ | |
| 970 | + wiki_convert(pIn, &html, 0); | |
| 971 | + html_to_plaintext(blob_str(&html), pOut); | |
| 972 | + }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ | |
| 973 | + markdown_to_html(pIn, &title, &html); | |
| 974 | + html_to_plaintext(blob_str(&html), pOut); | |
| 975 | + }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ | |
| 976 | + html_to_plaintext(blob_str(pIn), pOut); | |
| 977 | + }else{ | |
| 978 | + *pOut = *pIn; | |
| 979 | + blob_init(pIn, 0, 0); | |
| 980 | + } | |
| 981 | + blob_reset(&html); | |
| 982 | + blob_reset(&title); | |
| 983 | +} | |
| 984 | + | |
| 985 | +/* | |
| 986 | +** Query pQuery is pointing at a single row of output. Append a text | |
| 987 | +** representation of every text-compatible column to pAccum. | |
| 988 | +*/ | |
| 989 | +static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){ | |
| 990 | + int n = db_column_count(pQuery); | |
| 991 | + int i; | |
| 992 | + for(i=0; i<n; i++){ | |
| 993 | + const char *zColName = db_column_name(pQuery,i); | |
| 994 | + if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue; | |
| 995 | + if( fossil_stricmp(zColName,"mimetype")==0 ) continue; | |
| 996 | + switch( db_column_type(pQuery,i) ){ | |
| 997 | + case SQLITE_INTEGER: | |
| 998 | + case SQLITE_FLOAT: | |
| 999 | + case SQLITE_TEXT: | |
| 1000 | + blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); | |
| 1001 | + } | |
| 1002 | + } | |
| 1003 | +} | |
| 1004 | + | |
| 1005 | + | |
| 1006 | +/* | |
| 1007 | +** Return "search text" - a reduced version of a document appropriate for | |
| 1008 | +** full text search and/or for constructing a search result snippet. | |
| 1009 | +** | |
| 1010 | +** cType: d Embedded documentation | |
| 1011 | +** w Wiki page | |
| 1012 | +** c Check-in comment | |
| 1013 | +** t Ticket text | |
| 1014 | +** | |
| 1015 | +** rid The RID of an artifact that defines the object | |
| 1016 | +** being searched. | |
| 1017 | +** | |
| 1018 | +** zName Name of the object being searched. | |
| 1019 | +*/ | |
| 1020 | +void search_stext( | |
| 1021 | + char cType, /* Type of document */ | |
| 1022 | + int rid, /* BLOB.RID or TAG.TAGID value for document */ | |
| 1023 | + const char *zName, /* Auxiliary information */ | |
| 1024 | + Blob *pOut /* OUT: Initialize to the search text */ | |
| 1025 | +){ | |
| 1026 | + blob_init(pOut, 0, 0); | |
| 1027 | + switch( cType ){ | |
| 1028 | + case 'd': { /* Documents */ | |
| 1029 | + Blob doc; | |
| 1030 | + content_get(rid, &doc); | |
| 1031 | + blob_to_utf8_no_bom(&doc, 0); | |
| 1032 | + get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); | |
| 1033 | + blob_reset(&doc); | |
| 1034 | + break; | |
| 1035 | + } | |
| 1036 | + case 'w': { /* Wiki */ | |
| 1037 | + Manifest *pWiki = manifest_get(rid, CFTYPE_WIKI,0); | |
| 1038 | + Blob wiki; | |
| 1039 | + if( pWiki==0 ) break; | |
| 1040 | + blob_init(&wiki, pWiki->zWiki, -1); | |
| 1041 | + get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), | |
| 1042 | + pOut); | |
| 1043 | + blob_reset(&wiki); | |
| 1044 | + manifest_destroy(pWiki); | |
| 1045 | + break; | |
| 1046 | + } | |
| 1047 | + case 'c': { /* Check-in Comments */ | |
| 1048 | + static Stmt q; | |
| 1049 | + db_static_prepare(&q, | |
| 1050 | + "SELECT coalesce(ecomment,comment)" | |
| 1051 | + " ||' (user: '||coalesce(euser,user,'?')" | |
| 1052 | + " ||', tags: '||" | |
| 1053 | + " (SELECT group_concat(substr(tag.tagname,5),',')" | |
| 1054 | + " FROM tag, tagxref" | |
| 1055 | + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" | |
| 1056 | + " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" | |
| 1057 | + " ||')'" | |
| 1058 | + " FROM event WHERE objid=:x AND type='ci'"); | |
| 1059 | + db_bind_int(&q, ":x", rid); | |
| 1060 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 1061 | + db_column_blob(&q, 0, pOut); | |
| 1062 | + blob_append(pOut, "\n", 1); | |
| 1063 | + } | |
| 1064 | + db_reset(&q); | |
| 1065 | + break; | |
| 1066 | + } | |
| 1067 | + case 't': { /* Tickets */ | |
| 1068 | + static Stmt q1; | |
| 1069 | + Blob raw; | |
| 1070 | + db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); | |
| 1071 | + blob_init(&raw,0,0); | |
| 1072 | + db_bind_int(&q1, ":rid", rid); | |
| 1073 | + if( db_step(&q1)==SQLITE_ROW ){ | |
| 1074 | + append_all_ticket_fields(&raw, &q1); | |
| 1075 | + } | |
| 1076 | + db_reset(&q1); | |
| 1077 | + if( db_table_exists("repository","ticketchng") ){ | |
| 1078 | + static Stmt q2; | |
| 1079 | + db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid" | |
| 1080 | + " ORDER BY tkt_mtime"); | |
| 1081 | + db_bind_int(&q2, ":rid", rid); | |
| 1082 | + while( db_step(&q2)==SQLITE_ROW ){ | |
| 1083 | + append_all_ticket_fields(&raw, &q2); | |
| 1084 | + } | |
| 1085 | + db_reset(&q2); | |
| 1086 | + } | |
| 1087 | + html_to_plaintext(blob_str(&raw), pOut); | |
| 1088 | + blob_reset(&raw); | |
| 1089 | + break; | |
| 1090 | + } | |
| 1091 | + } | |
| 1092 | +} | |
| 1093 | + | |
| 1094 | +/* | |
| 1095 | +** COMMAND: test-search-stext | |
| 1096 | +** | |
| 1097 | +** Usage: fossil test-search-stext TYPE ARG1 ARG2 | |
| 1098 | +*/ | |
| 1099 | +void test_search_stext(void){ | |
| 1100 | + Blob out; | |
| 1101 | + db_find_and_open_repository(0,0); | |
| 1102 | + if( g.argc!=5 ) usage("TYPE RID NAME"); | |
| 1103 | + search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); | |
| 1104 | + fossil_print("%s\n",blob_str(&out)); | |
| 1105 | + blob_reset(&out); | |
| 1106 | +} | |
| 1107 | + | |
| 1108 | +/* The schema for the full-text index | |
| 1109 | +*/ | |
| 1110 | +static const char zFtsSchema[] = | |
| 1111 | +@ -- One entry for each possible search result | |
| 1112 | +@ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( | |
| 1113 | +@ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid | |
| 1114 | +@ type CHAR(1), -- Type of document | |
| 1115 | +@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document | |
| 1116 | +@ name TEXT, -- Additional document description | |
| 1117 | +@ idxed BOOLEAN, -- True if currently in the index | |
| 1118 | +@ label TEXT, -- Label to print on search results | |
| 1119 | +@ url TEXT, -- URL to access this document | |
| 1120 | +@ mtime DATE, -- Date when document created | |
| 1121 | +@ UNIQUE(type,rid) | |
| 1122 | +@ ); | |
| 1123 | +@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; | |
| 1124 | +@ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; | |
| 1125 | +@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS | |
| 1126 | +@ SELECT rowid, type, rid, name, idxed, label, url, mtime, | |
| 1127 | +@ stext(type,rid,name) AS 'stext' | |
| 1128 | +@ FROM ftsdocs; | |
| 1129 | +@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx | |
| 1130 | +@ USING fts4(content="ftscontent", stext); | |
| 1131 | +; | |
| 1132 | +static const char zFtsDrop[] = | |
| 1133 | +@ DROP TABLE IF EXISTS "%w".ftsidx; | |
| 1134 | +@ DROP VIEW IF EXISTS "%w".ftscontent; | |
| 1135 | +@ DROP TABLE IF EXISTS "%w".ftsdocs; | |
| 1136 | +; | |
| 1137 | + | |
| 1138 | +/* | |
| 1139 | +** Create or drop the tables associated with a full-text index. | |
| 1140 | +*/ | |
| 1141 | +static int searchIdxExists = -1; | |
| 1142 | +void search_create_index(void){ | |
| 1143 | + const char *zDb = db_name("repository"); | |
| 1144 | + search_sql_setup(g.db); | |
| 1145 | + db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/, | |
| 1146 | + zDb, zDb, zDb, zDb, zDb); | |
| 1147 | + searchIdxExists = 1; | |
| 1148 | +} | |
| 1149 | +void search_drop_index(void){ | |
| 1150 | + const char *zDb = db_name("repository"); | |
| 1151 | + db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); | |
| 1152 | + searchIdxExists = 0; | |
| 1153 | +} | |
| 1154 | + | |
| 1155 | +/* | |
| 1156 | +** Return true if the full-text search index exists | |
| 1157 | +*/ | |
| 1158 | +int search_index_exists(void){ | |
| 1159 | + if( searchIdxExists<0 ){ | |
| 1160 | + searchIdxExists = db_table_exists("repository","ftsdocs"); | |
| 1161 | + } | |
| 1162 | + return searchIdxExists; | |
| 1163 | +} | |
| 1164 | + | |
| 1165 | +/* | |
| 1166 | +** Fill the FTSDOCS table with unindexed entries for everything | |
| 1167 | +** in the repository. This uses INSERT OR IGNORE so entries already | |
| 1168 | +** in FTSDOCS are unchanged. | |
| 1169 | +*/ | |
| 1170 | +void search_fill_index(void){ | |
| 1171 | + if( !search_index_exists() ) return; | |
| 1172 | + search_sql_setup(g.db); | |
| 1173 | + db_multi_exec( | |
| 1174 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" | |
| 1175 | + " SELECT 'c', objid, 0 FROM event WHERE type='ci';" | |
| 1176 | + ); | |
| 1177 | + db_multi_exec( | |
| 1178 | + "WITH latest_wiki(rid,name,mtime) AS (" | |
| 1179 | + " SELECT tagxref.rid, substr(tag.tagname,6), max(tagxref.mtime)" | |
| 1180 | + " FROM tag, tagxref" | |
| 1181 | + " WHERE tag.tagname GLOB 'wiki-*'" | |
| 1182 | + " AND tagxref.tagid=tag.tagid" | |
| 1183 | + " AND tagxref.value>0" | |
| 1184 | + " GROUP BY 2" | |
| 1185 | + ") INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" | |
| 1186 | + " SELECT 'w', rid, name, 0 FROM latest_wiki;" | |
| 1187 | + ); | |
| 1188 | + db_multi_exec( | |
| 1189 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" | |
| 1190 | + " SELECT 't', tkt_id, 0 FROM ticket;" | |
| 1191 | + ); | |
| 1192 | +} | |
| 1193 | + | |
| 1194 | +/* | |
| 1195 | +** The document described by cType,rid,zName is about to be added or | |
| 1196 | +** updated. If the document has already been indexed, then unindex it | |
| 1197 | +** now while we still have access to the old content. Add the document | |
| 1198 | +** to the queue of documents that need to be indexed or reindexed. | |
| 1199 | +*/ | |
| 1200 | +void search_doc_touch(char cType, int rid, const char *zName){ | |
| 1201 | + if( search_index_exists() ){ | |
| 1202 | + char zType[2]; | |
| 1203 | + zType[0] = cType; | |
| 1204 | + zType[1] = 0; | |
| 1205 | + search_sql_setup(g.db); | |
| 1206 | + db_multi_exec( | |
| 1207 | + "DELETE FROM ftsidx WHERE docid IN" | |
| 1208 | + " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", | |
| 1209 | + zType, rid | |
| 1210 | + ); | |
| 1211 | + db_multi_exec( | |
| 1212 | + "REPLACE INTO ftsdocs(type,rid,name,idxed)" | |
| 1213 | + " VALUES(%Q,%d,%Q,0)", | |
| 1214 | + zType, rid, zName | |
| 1215 | + ); | |
| 1216 | + if( cType=='w' ){ | |
| 1217 | + db_multi_exec( | |
| 1218 | + "DELETE FROM ftsidx WHERE docid IN" | |
| 1219 | + " (SELECT rowid FROM ftsdocs WHERE type='w' AND name=%Q AND idxed)", | |
| 1220 | + zName | |
| 1221 | + ); | |
| 1222 | + db_multi_exec( | |
| 1223 | + "DELETE FROM ftsdocs WHERE type='w' AND name=%Q AND rid!=%d", | |
| 1224 | + zName, rid | |
| 1225 | + ); | |
| 1226 | + } | |
| 1227 | + } | |
| 1228 | +} | |
| 1229 | + | |
| 1230 | +/* | |
| 1231 | +** If the doc-glob and doc-br settings are valid for document search | |
| 1232 | +** and if the latest check-in on doc-br is in the unindexed set of | |
| 1233 | +** check-ins, then update all 'd' entries in FTSDOCS that have | |
| 1234 | +** changed. | |
| 1235 | +*/ | |
| 1236 | +static void search_update_doc_index(void){ | |
| 1237 | + const char *zDocBr = db_get("doc-branch","trunk"); | |
| 1238 | + int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; | |
| 1239 | + double rTime; | |
| 1240 | + char *zBrUuid; | |
| 1241 | + if( ckid==0 ) return; | |
| 1242 | + if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" | |
| 1243 | + " AND NOT idxed", ckid) ) return; | |
| 1244 | + | |
| 1245 | + /* If we get this far, it means that changes to 'd' entries are | |
| 1246 | + ** required. */ | |
| 1247 | + rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); | |
| 1248 | + zBrUuid = db_text("","SELECT substr(uuid,1,20) FROM blob WHERE rid=%d",ckid); | |
| 1249 | + db_multi_exec( | |
| 1250 | + "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" | |
| 1251 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 1252 | + "INSERT OR IGNORE INTO current_docs(rid, name)" | |
| 1253 | + " SELECT blob.rid, foci.filename FROM foci, blob" | |
| 1254 | + " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" | |
| 1255 | + " AND %z", | |
| 1256 | + ckid, glob_expr("foci.filename", db_get("doc-glob","")) | |
| 1257 | + ); | |
| 1258 | + db_multi_exec( | |
| 1259 | + "DELETE FROM ftsidx WHERE docid IN" | |
| 1260 | + " (SELECT rowid FROM ftsdocs WHERE type='d'" | |
| 1261 | + " AND rid NOT IN (SELECT rid FROM current_docs))" | |
| 1262 | + ); | |
| 1263 | + db_multi_exec( | |
| 1264 | + "DELETE FROM ftsdocs WHERE type='d'" | |
| 1265 | + " AND rid NOT IN (SELECT rid FROM current_docs)" | |
| 1266 | + ); | |
| 1267 | + db_multi_exec( | |
| 1268 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" | |
| 1269 | + " SELECT 'd', rid, name, 0," | |
| 1270 | + " printf('Document: %%s',name)," | |
| 1271 | + " printf('/doc/%q/%%s',urlencode(name))," | |
| 1272 | + " %.17g" | |
| 1273 | + " FROM current_docs", | |
| 1274 | + zBrUuid, rTime | |
| 1275 | + ); | |
| 1276 | + db_multi_exec( | |
| 1277 | + "INSERT INTO ftsidx(docid,stext)" | |
| 1278 | + " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed" | |
| 1279 | + ); | |
| 1280 | + db_multi_exec( | |
| 1281 | + "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" | |
| 1282 | + ); | |
| 1283 | +} | |
| 1284 | + | |
| 1285 | +/* | |
| 1286 | +** Deal with all of the unindexed 'c' terms in FTSDOCS | |
| 1287 | +*/ | |
| 1288 | +static void search_update_checkin_index(void){ | |
| 1289 | + db_multi_exec( | |
| 1290 | + "INSERT INTO ftsidx(docid,stext)" | |
| 1291 | + " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs" | |
| 1292 | + " WHERE type='c' AND NOT idxed;" | |
| 1293 | + ); | |
| 1294 | + db_multi_exec( | |
| 1295 | + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" | |
| 1296 | + " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," | |
| 1297 | + " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime))," | |
| 1298 | + " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid)," | |
| 1299 | + " event.mtime" | |
| 1300 | + " FROM ftsdocs, event, blob" | |
| 1301 | + " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" | |
| 1302 | + " AND event.objid=ftsdocs.rid" | |
| 1303 | + " AND blob.rid=ftsdocs.rid" | |
| 1304 | + ); | |
| 1305 | +} | |
| 1306 | + | |
| 1307 | +/* | |
| 1308 | +** Deal with all of the unindexed 't' terms in FTSDOCS | |
| 1309 | +*/ | |
| 1310 | +static void search_update_ticket_index(void){ | |
| 1311 | + db_multi_exec( | |
| 1312 | + "INSERT INTO ftsidx(docid,stext)" | |
| 1313 | + " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs" | |
| 1314 | + " WHERE type='t' AND NOT idxed;" | |
| 1315 | + ); | |
| 1316 | + if( db_changes()==0 ) return; | |
| 1317 | + db_multi_exec( | |
| 1318 | + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" | |
| 1319 | + " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," | |
| 1320 | + " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime))," | |
| 1321 | + " printf('/tktview/%%.20s',tkt_uuid)," | |
| 1322 | + " tkt_mtime" | |
| 1323 | + " FROM ftsdocs, ticket" | |
| 1324 | + " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" | |
| 1325 | + " AND ticket.tkt_id=ftsdocs.rid" | |
| 1326 | + ); | |
| 1327 | +} | |
| 1328 | + | |
| 1329 | +/* | |
| 1330 | +** Deal with all of the unindexed 'w' terms in FTSDOCS | |
| 1331 | +*/ | |
| 1332 | +static void search_update_wiki_index(void){ | |
| 1333 | + db_multi_exec( | |
| 1334 | + "INSERT INTO ftsidx(docid,stext)" | |
| 1335 | + " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs" | |
| 1336 | + " WHERE type='w' AND NOT idxed;" | |
| 1337 | + ); | |
| 1338 | + if( db_changes()==0 ) return; | |
| 1339 | + db_multi_exec( | |
| 1340 | + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" | |
| 1341 | + " SELECT ftsdocs.rowid, 1, 'w', ftsdocs.rid, ftsdocs.name," | |
| 1342 | + " 'Wiki: '||ftsdocs.name," | |
| 1343 | + " '/wiki?name='||urlencode(ftsdocs.name)," | |
| 1344 | + " tagxref.mtime" | |
| 1345 | + " FROM ftsdocs, tagxref" | |
| 1346 | + " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed" | |
| 1347 | + " AND tagxref.rid=ftsdocs.rid" | |
| 1348 | + ); | |
| 1349 | +} | |
| 1350 | + | |
| 1351 | +/* | |
| 1352 | +** Deal with all of the unindexed entries in the FTSDOCS table - that | |
| 1353 | +** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the | |
| 1354 | +** index. | |
| 1355 | +*/ | |
| 1356 | +void search_update_index(unsigned int srchFlags){ | |
| 1357 | + if( !search_index_exists() ) return; | |
| 1358 | + if( !db_exists("SELECT 1 FROM ftsdocs WHERE NOT idxed") ) return; | |
| 1359 | + search_sql_setup(g.db); | |
| 1360 | + if( srchFlags & (SRCH_CKIN|SRCH_DOC) ){ | |
| 1361 | + search_update_doc_index(); | |
| 1362 | + search_update_checkin_index(); | |
| 1363 | + } | |
| 1364 | + if( srchFlags & SRCH_TKT ){ | |
| 1365 | + search_update_ticket_index(); | |
| 1366 | + } | |
| 1367 | + if( srchFlags & SRCH_WIKI ){ | |
| 1368 | + search_update_wiki_index(); | |
| 1369 | + } | |
| 1370 | +} | |
| 1371 | + | |
| 1372 | +/* | |
| 1373 | +** Construct, prepopulate, and then update the full-text index. | |
| 1374 | +*/ | |
| 1375 | +void search_rebuild_index(void){ | |
| 1376 | + fossil_print("rebuilding the search index..."); | |
| 1377 | + fflush(stdout); | |
| 1378 | + search_create_index(); | |
| 1379 | + search_fill_index(); | |
| 1380 | + search_update_index(search_restrict(SRCH_ALL)); | |
| 1381 | + fossil_print(" done\n"); | |
| 1382 | +} | |
| 1383 | + | |
| 1384 | +/* | |
| 1385 | +** COMMAND: fts-config* | |
| 1386 | +** | |
| 1387 | +** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? | |
| 1388 | +** | |
| 1389 | +** The "fossil fts-config" command configures the full-text search capabilities | |
| 1390 | +** of the repository. Subcommands: | |
| 1391 | +** | |
| 1392 | +** reindex Rebuild the search index. Create it if it does | |
| 1393 | +** not already exist | |
| 1394 | +** | |
| 1395 | +** index (on|off) Turn the search index on or off | |
| 1396 | +** | |
| 1397 | +** enable cdtw Enable various kinds of search. c=Check-ins, | |
| 1398 | +** d=Documents, t=Tickets, w=Wiki. | |
| 1399 | +** | |
| 1400 | +** disable cdtw Disable versious kinds of search | |
| 1401 | +** | |
| 1402 | +** The current search settings are displayed after any changes are applied. | |
| 1403 | +** Run this command with no arguments to simply see the settings. | |
| 1404 | +*/ | |
| 1405 | +void test_fts_cmd(void){ | |
| 1406 | + static const struct { int iCmd; const char *z; } aCmd[] = { | |
| 1407 | + { 1, "reindex" }, | |
| 1408 | + { 2, "index" }, | |
| 1409 | + { 3, "disable" }, | |
| 1410 | + { 4, "enable" }, | |
| 1411 | + }; | |
| 1412 | + static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { | |
| 1413 | + { "search-ckin", "check-in search:", "c" }, | |
| 1414 | + { "search-doc", "document search:", "d" }, | |
| 1415 | + { "search-tkt", "ticket search:", "t" }, | |
| 1416 | + { "search-wiki", "wiki search:", "w" }, | |
| 1417 | + }; | |
| 1418 | + char *zSubCmd; | |
| 1419 | + int i, j, n; | |
| 1420 | + int iCmd = 0; | |
| 1421 | + int iAction = 0; | |
| 1422 | + db_find_and_open_repository(0, 0); | |
| 1423 | + if( g.argc>2 ){ | |
| 1424 | + zSubCmd = g.argv[2]; | |
| 1425 | + n = (int)strlen(zSubCmd); | |
| 1426 | + for(i=0; i<ArraySize(aCmd); i++){ | |
| 1427 | + if( fossil_strncmp(aCmd[i].z, zSubCmd, n)==0 ) break; | |
| 1428 | + } | |
| 1429 | + if( i>=ArraySize(aCmd) ){ | |
| 1430 | + Blob all; | |
| 1431 | + blob_init(&all,0,0); | |
| 1432 | + for(i=0; i<ArraySize(aCmd); i++) blob_appendf(&all, " %s", aCmd[i].z); | |
| 1433 | + fossil_fatal("unknown \"%s\" - should be on of:%s", | |
| 1434 | + zSubCmd, blob_str(&all)); | |
| 1435 | + return; | |
| 1436 | + } | |
| 1437 | + iCmd = aCmd[i].iCmd; | |
| 1438 | + } | |
| 1439 | + if( iCmd==1 ){ | |
| 1440 | + iAction = 2; | |
| 1441 | + } | |
| 1442 | + if( iCmd==2 ){ | |
| 1443 | + if( g.argc<3 ) usage("index (on|off)"); | |
| 1444 | + iAction = 1 + is_truth(g.argv[3]); | |
| 1445 | + } | |
| 1446 | + db_begin_transaction(); | |
| 1447 | + | |
| 1448 | + /* Adjust search settings */ | |
| 1449 | + if( iCmd==3 || iCmd==4 ){ | |
| 1450 | + const char *zCtrl; | |
| 1451 | + if( g.argc<4 ) usage("enable STRING"); | |
| 1452 | + zCtrl = g.argv[3]; | |
| 1453 | + for(j=0; j<ArraySize(aSetng); j++){ | |
| 1454 | + if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){ | |
| 1455 | + db_set_int(aSetng[j].zSetting, iCmd-3, 0); | |
| 1456 | + } | |
| 1457 | + } | |
| 1458 | + } | |
| 1459 | + | |
| 1460 | + /* destroy or rebuild the index, if requested */ | |
| 1461 | + if( iAction>=1 ){ | |
| 1462 | + search_drop_index(); | |
| 1463 | + } | |
| 1464 | + if( iAction>=2 ){ | |
| 1465 | + search_rebuild_index(); | |
| 1466 | + } | |
| 1467 | + | |
| 1468 | + /* Always show the status before ending */ | |
| 1469 | + for(i=0; i<ArraySize(aSetng); i++){ | |
| 1470 | + fossil_print("%-16s %s\n", aSetng[i].zName, | |
| 1471 | + db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); | |
| 1472 | + } | |
| 1473 | + if( search_index_exists() ){ | |
| 1474 | + fossil_print("%-16s enabled\n", "full-text index:"); | |
| 1475 | + fossil_print("%-16s %d\n", "documents:", | |
| 1476 | + db_int(0, "SELECT count(*) FROM ftsdocs")); | |
| 1477 | + }else{ | |
| 1478 | + fossil_print("%-16s disabled\n", "full-text index:"); | |
| 1479 | + } | |
| 1480 | + db_end_transaction(0); | |
| 1481 | +} | |
| 568 | 1482 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -45,14 +45,15 @@ | |
| 45 | char *zPattern; /* The search pattern */ |
| 46 | char *zMarkBegin; /* Start of a match */ |
| 47 | char *zMarkEnd; /* End of a match */ |
| 48 | char *zMarkGap; /* A gap between two matches */ |
| 49 | unsigned fSrchFlg; /* Flags */ |
| 50 | }; |
| 51 | |
| 52 | #define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */ |
| 53 | #define SRCHFLG_SCORE 0x02 /* Prepend the score to each snippet */ |
| 54 | #define SRCHFLG_STATIC 0x04 /* The static gSearch object */ |
| 55 | |
| 56 | #endif |
| 57 | |
| 58 | /* |
| @@ -92,10 +93,11 @@ | |
| 92 | if( p ){ |
| 93 | fossil_free(p->zPattern); |
| 94 | fossil_free(p->zMarkBegin); |
| 95 | fossil_free(p->zMarkEnd); |
| 96 | fossil_free(p->zMarkGap); |
| 97 | memset(p, 0, sizeof(*p)); |
| 98 | if( p!=&gSearch ) fossil_free(p); |
| 99 | } |
| 100 | } |
| 101 | |
| @@ -123,10 +125,11 @@ | |
| 123 | p->zPattern = z = mprintf("%s", zPattern); |
| 124 | p->zMarkBegin = mprintf("%s", zMarkBegin); |
| 125 | p->zMarkEnd = mprintf("%s", zMarkEnd); |
| 126 | p->zMarkGap = mprintf("%s", zMarkGap); |
| 127 | p->fSrchFlg = fSrchFlg; |
| 128 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 129 | while( *z && !ISALNUM(*z) ){ z++; } |
| 130 | if( *z==0 ) break; |
| 131 | p->a[p->nTerm].z = z; |
| 132 | for(i=1; ISALNUM(z[i]); i++){} |
| @@ -156,24 +159,26 @@ | |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /* |
| 160 | ** Compare a search pattern against one or more input strings which |
| 161 | ** collectively comprise a document. Return a match score. Optionally |
| 162 | ** also return a "snippet". |
| 163 | ** |
| 164 | ** Scoring: |
| 165 | ** * All terms must match at least once or the score is zero |
| 166 | ** * One point for each matching term |
| 167 | ** * Extra points if consecutive words of the pattern are consecutive |
| 168 | ** in the document |
| 169 | */ |
| 170 | static int search_score( |
| 171 | Search *p, /* Search pattern and flags */ |
| 172 | int nDoc, /* Number of strings in this document */ |
| 173 | const char **azDoc, /* Text of each string */ |
| 174 | Blob *pSnip /* If not NULL: Write a snippet here */ |
| 175 | ){ |
| 176 | int score; /* Final score */ |
| 177 | int i; /* Offset into current document */ |
| 178 | int ii; /* Loop counter */ |
| 179 | int j; /* Loop over search terms */ |
| @@ -217,26 +222,26 @@ | |
| 217 | } |
| 218 | break; |
| 219 | } |
| 220 | } |
| 221 | while( ISALNUM(zDoc[i]) ){ i++; } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | /* Finished search all documents. |
| 226 | ** Every term must be seen or else the score is zero |
| 227 | */ |
| 228 | score = 1; |
| 229 | for(j=0; j<p->nTerm; j++) score *= anMatch[j]; |
| 230 | if( score==0 || pSnip==0 ) return score; |
| 231 | |
| 232 | |
| 233 | /* Prepare a snippet that describes the matching text. |
| 234 | */ |
| 235 | blob_init(pSnip, 0, 0); |
| 236 | if( p->fSrchFlg & SRCHFLG_SCORE ) blob_appendf(pSnip, "%08x", score); |
| 237 | |
| 238 | while(1){ |
| 239 | int iOfst; |
| 240 | int iTail; |
| 241 | int iBest; |
| 242 | for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){} |
| @@ -271,11 +276,11 @@ | |
| 271 | if( iOfst<0 ) iOfst = 0; |
| 272 | while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--; |
| 273 | while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++; |
| 274 | for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){} |
| 275 | while( ISALNUM(zDoc[iTail]) ) iTail++; |
| 276 | if( iOfst>0 || wantGap ) blob_append(pSnip, p->zMarkGap, -1); |
| 277 | wantGap = zDoc[iTail]!=0; |
| 278 | zDoc += iOfst; |
| 279 | iTail -= iOfst; |
| 280 | |
| 281 | /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */ |
| @@ -284,53 +289,51 @@ | |
| 284 | for(j=0; j<p->nTerm; j++){ |
| 285 | int n = p->a[j].n; |
| 286 | if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 |
| 287 | && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') |
| 288 | ){ |
| 289 | snippet_text_append(p, pSnip, zDoc, i); |
| 290 | zDoc += i; |
| 291 | iTail -= i; |
| 292 | blob_append(pSnip, p->zMarkBegin, -1); |
| 293 | if( p->a[j].z[n]=='*' ){ |
| 294 | while( ISALNUM(zDoc[n]) ) n++; |
| 295 | } |
| 296 | snippet_text_append(p, pSnip, zDoc, n); |
| 297 | zDoc += n; |
| 298 | iTail -= n; |
| 299 | blob_append(pSnip, p->zMarkEnd, -1); |
| 300 | i = -1; |
| 301 | break; |
| 302 | } /* end-if */ |
| 303 | } /* end for(j) */ |
| 304 | if( j<p->nTerm ){ |
| 305 | while( ISALNUM(zDoc[i]) && i<iTail ){ i++; } |
| 306 | } |
| 307 | } /* end for(i) */ |
| 308 | snippet_text_append(p, pSnip, zDoc, iTail); |
| 309 | } |
| 310 | if( wantGap ) blob_append(pSnip, p->zMarkGap, -1); |
| 311 | return score; |
| 312 | } |
| 313 | |
| 314 | /* |
| 315 | ** COMMAND: test-snippet |
| 316 | ** |
| 317 | ** Usage: fossil test-snippet SEARCHSTRING FILE1 FILE2 ... |
| 318 | */ |
| 319 | void test_snippet_cmd(void){ |
| 320 | Search *p; |
| 321 | int i; |
| 322 | Blob x; |
| 323 | Blob snip; |
| 324 | int score; |
| 325 | char *zDoc; |
| 326 | int flg = 0; |
| 327 | char *zBegin = (char*)find_option("begin",0,1); |
| 328 | char *zEnd = (char*)find_option("end",0,1); |
| 329 | char *zGap = (char*)find_option("gap",0,1); |
| 330 | if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML; |
| 331 | if( find_option("score",0,0)!=0 ) flg |= SRCHFLG_SCORE; |
| 332 | if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC; |
| 333 | verify_all_options(); |
| 334 | if( g.argc<4 ) usage("SEARCHSTRING FILE1..."); |
| 335 | if( zBegin==0 ) zBegin = "[["; |
| 336 | if( zEnd==0 ) zEnd = "]]"; |
| @@ -337,18 +340,18 @@ | |
| 337 | if( zGap==0 ) zGap = " ... "; |
| 338 | p = search_init(g.argv[2], zBegin, zEnd, zGap, flg); |
| 339 | for(i=3; i<g.argc; i++){ |
| 340 | blob_read_from_file(&x, g.argv[i]); |
| 341 | zDoc = blob_str(&x); |
| 342 | score = search_score(p, 1, (const char**)&zDoc, &snip); |
| 343 | fossil_print("%s: %d\n", g.argv[i], score); |
| 344 | blob_reset(&x); |
| 345 | if( score ){ |
| 346 | fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&snip), '='); |
| 347 | blob_reset(&snip); |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | /* |
| 353 | ** An SQL function to initialize the global search pattern: |
| 354 | ** |
| @@ -360,12 +363,12 @@ | |
| 360 | sqlite3_context *context, |
| 361 | int argc, |
| 362 | sqlite3_value **argv |
| 363 | ){ |
| 364 | const char *zPattern = 0; |
| 365 | const char *zBegin = "<b>"; |
| 366 | const char *zEnd = "</b>"; |
| 367 | const char *zGap = " ... "; |
| 368 | unsigned int flg = SRCHFLG_HTML; |
| 369 | switch( argc ){ |
| 370 | default: |
| 371 | flg = (unsigned int)sqlite3_value_int(argv[4]); |
| @@ -384,50 +387,98 @@ | |
| 384 | search_end(&gSearch); |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | /* |
| 389 | ** This is an SQLite function that scores its input using |
| 390 | ** the pattern from the previous call to search_init(). |
| 391 | */ |
| 392 | static void search_score_sqlfunc( |
| 393 | sqlite3_context *context, |
| 394 | int argc, |
| 395 | sqlite3_value **argv |
| 396 | ){ |
| 397 | int isSnippet = sqlite3_user_data(context)!=0; |
| 398 | const char **azDoc; |
| 399 | int score; |
| 400 | int i; |
| 401 | Blob snip; |
| 402 | |
| 403 | if( gSearch.nTerm==0 ) return; |
| 404 | azDoc = fossil_malloc( sizeof(const char*)*(argc+1) ); |
| 405 | for(i=0; i<argc; i++) azDoc[i] = (const char*)sqlite3_value_text(argv[i]); |
| 406 | score = search_score(&gSearch, argc, azDoc, isSnippet ? &snip : 0); |
| 407 | fossil_free((void *)azDoc); |
| 408 | if( isSnippet ){ |
| 409 | if( score ){ |
| 410 | sqlite3_result_text(context, blob_materialize(&snip), -1, fossil_free); |
| 411 | } |
| 412 | }else{ |
| 413 | sqlite3_result_int(context, score); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | /* |
| 418 | ** Register the "score()" SQL function to score its input text |
| 419 | ** using the given Search object. Once this function is registered, |
| 420 | ** do not delete the Search object. |
| 421 | */ |
| 422 | void search_sql_setup(sqlite3 *db){ |
| 423 | sqlite3_create_function(db, "score", -1, SQLITE_UTF8, 0, |
| 424 | search_score_sqlfunc, 0, 0); |
| 425 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 426 | search_score_sqlfunc, 0, 0); |
| 427 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 428 | search_init_sqlfunc, 0, 0); |
| 429 | } |
| 430 | |
| 431 | /* |
| 432 | ** Testing the search function. |
| 433 | ** |
| @@ -501,67 +552,930 @@ | |
| 501 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 502 | blob_reset(&sql); |
| 503 | print_timeline(&q, nLimit, width, 0); |
| 504 | db_finalize(&q); |
| 505 | } |
| 506 | |
| 507 | /* |
| 508 | ** WEBPAGE: /search |
| 509 | ** |
| 510 | ** This is an EXPERIMENTAL page for doing search across a repository. |
| 511 | ** |
| 512 | ** The current implementation does a full text search over embedded |
| 513 | ** documentation files on the tip of the "trunk" branch. Only files |
| 514 | ** ending in ".wiki", ".md", ".html", and ".txt" are searched. |
| 515 | ** |
| 516 | ** The entire text is scanned. There is no full-text index. This is |
| 517 | ** experimental. We may change to using a full-text index depending |
| 518 | ** on performance. |
| 519 | ** |
| 520 | ** Other pending enhancements: |
| 521 | ** * Search tickets |
| 522 | ** * Search wiki |
| 523 | */ |
| 524 | void search_page(void){ |
| 525 | const char *zPattern = PD("s",""); |
| 526 | Stmt q; |
| 527 | |
| 528 | login_check_credentials(); |
| 529 | if( !g.perm.Read ){ login_needed(); return; } |
| 530 | style_header("Search"); |
| 531 | @ <form method="GET" action="search"><center> |
| 532 | @ <input type="text" name="s" size="40" value="%h(zPattern)"> |
| 533 | @ <input type="submit" value="Search"> |
| 534 | @ </center></form> |
| 535 | while( fossil_isspace(zPattern[0]) ) zPattern++; |
| 536 | if( zPattern[0] ){ |
| 537 | search_sql_setup(g.db); |
| 538 | add_content_sql_commands(g.db); |
| 539 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 540 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 541 | db_multi_exec( |
| 542 | "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;" |
| 543 | "CREATE TEMP TABLE x(fn TEXT,url TEXT,snip TEXT);" |
| 544 | "INSERT INTO x(fn,url,snip)" |
| 545 | " SELECT filename, printf('%R/doc/trunk/%%s',filename)," |
| 546 | " snippet(content(uuid))" |
| 547 | " FROM foci" |
| 548 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 549 | " AND (filename GLOB '*.wiki' OR" |
| 550 | " filename GLOB '*.md' OR" |
| 551 | " filename GLOB '*.txt' OR" |
| 552 | " filename GLOB '*.html');" |
| 553 | ); |
| 554 | db_prepare(&q, "SELECT url, substr(snip,8)" |
| 555 | " FROM x WHERE snip IS NOT NULL" |
| 556 | " ORDER BY substr(snip,1,8) DESC, fn;"); |
| 557 | @ <ol> |
| 558 | while( db_step(&q)==SQLITE_ROW ){ |
| 559 | const char *zUrl = db_column_text(&q, 0); |
| 560 | const char *zSnippet = db_column_text(&q, 1); |
| 561 | @ <li><p>%s(href("%s",zUrl))%h(zUrl)</a><br>%s(zSnippet)</li> |
| 562 | } |
| 563 | db_finalize(&q); |
| 564 | @ </ol> |
| 565 | } |
| 566 | style_footer(); |
| 567 | } |
| 568 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -45,14 +45,15 @@ | |
| 45 | char *zPattern; /* The search pattern */ |
| 46 | char *zMarkBegin; /* Start of a match */ |
| 47 | char *zMarkEnd; /* End of a match */ |
| 48 | char *zMarkGap; /* A gap between two matches */ |
| 49 | unsigned fSrchFlg; /* Flags */ |
| 50 | int iScore; /* Score of the last match attempt */ |
| 51 | Blob snip; /* Snippet for the most recent match */ |
| 52 | }; |
| 53 | |
| 54 | #define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */ |
| 55 | #define SRCHFLG_STATIC 0x04 /* The static gSearch object */ |
| 56 | |
| 57 | #endif |
| 58 | |
| 59 | /* |
| @@ -92,10 +93,11 @@ | |
| 93 | if( p ){ |
| 94 | fossil_free(p->zPattern); |
| 95 | fossil_free(p->zMarkBegin); |
| 96 | fossil_free(p->zMarkEnd); |
| 97 | fossil_free(p->zMarkGap); |
| 98 | if( p->iScore ) blob_reset(&p->snip); |
| 99 | memset(p, 0, sizeof(*p)); |
| 100 | if( p!=&gSearch ) fossil_free(p); |
| 101 | } |
| 102 | } |
| 103 | |
| @@ -123,10 +125,11 @@ | |
| 125 | p->zPattern = z = mprintf("%s", zPattern); |
| 126 | p->zMarkBegin = mprintf("%s", zMarkBegin); |
| 127 | p->zMarkEnd = mprintf("%s", zMarkEnd); |
| 128 | p->zMarkGap = mprintf("%s", zMarkGap); |
| 129 | p->fSrchFlg = fSrchFlg; |
| 130 | blob_init(&p->snip, 0, 0); |
| 131 | while( *z && p->nTerm<SEARCH_MAX_TERM ){ |
| 132 | while( *z && !ISALNUM(*z) ){ z++; } |
| 133 | if( *z==0 ) break; |
| 134 | p->a[p->nTerm].z = z; |
| 135 | for(i=1; ISALNUM(z[i]); i++){} |
| @@ -156,24 +159,26 @@ | |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /* |
| 163 | ** Compare a search pattern against one or more input strings which |
| 164 | ** collectively comprise a document. Return a match score. Any |
| 165 | ** postive value means there was a match. Zero means that one or |
| 166 | ** more terms are missing. |
| 167 | ** |
| 168 | ** The score and a snippet are record for future use. |
| 169 | ** |
| 170 | ** Scoring: |
| 171 | ** * All terms must match at least once or the score is zero |
| 172 | ** * One point for each matching term |
| 173 | ** * Extra points if consecutive words of the pattern are consecutive |
| 174 | ** in the document |
| 175 | */ |
| 176 | static int search_match( |
| 177 | Search *p, /* Search pattern and flags */ |
| 178 | int nDoc, /* Number of strings in this document */ |
| 179 | const char **azDoc /* Text of each string */ |
| 180 | ){ |
| 181 | int score; /* Final score */ |
| 182 | int i; /* Offset into current document */ |
| 183 | int ii; /* Loop counter */ |
| 184 | int j; /* Loop over search terms */ |
| @@ -217,26 +222,26 @@ | |
| 222 | } |
| 223 | break; |
| 224 | } |
| 225 | } |
| 226 | while( ISALNUM(zDoc[i]) ){ i++; } |
| 227 | if( zDoc[i]==0 ) break; |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | /* Finished search all documents. |
| 232 | ** Every term must be seen or else the score is zero |
| 233 | */ |
| 234 | score = 1; |
| 235 | for(j=0; j<p->nTerm; j++) score *= anMatch[j]; |
| 236 | blob_reset(&p->snip); |
| 237 | p->iScore = score; |
| 238 | if( score==0 ) return score; |
| 239 | |
| 240 | |
| 241 | /* Prepare a snippet that describes the matching text. |
| 242 | */ |
| 243 | while(1){ |
| 244 | int iOfst; |
| 245 | int iTail; |
| 246 | int iBest; |
| 247 | for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){} |
| @@ -271,11 +276,11 @@ | |
| 276 | if( iOfst<0 ) iOfst = 0; |
| 277 | while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--; |
| 278 | while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++; |
| 279 | for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){} |
| 280 | while( ISALNUM(zDoc[iTail]) ) iTail++; |
| 281 | if( iOfst>0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1); |
| 282 | wantGap = zDoc[iTail]!=0; |
| 283 | zDoc += iOfst; |
| 284 | iTail -= iOfst; |
| 285 | |
| 286 | /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */ |
| @@ -284,53 +289,51 @@ | |
| 289 | for(j=0; j<p->nTerm; j++){ |
| 290 | int n = p->a[j].n; |
| 291 | if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 |
| 292 | && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') |
| 293 | ){ |
| 294 | snippet_text_append(p, &p->snip, zDoc, i); |
| 295 | zDoc += i; |
| 296 | iTail -= i; |
| 297 | blob_append(&p->snip, p->zMarkBegin, -1); |
| 298 | if( p->a[j].z[n]=='*' ){ |
| 299 | while( ISALNUM(zDoc[n]) ) n++; |
| 300 | } |
| 301 | snippet_text_append(p, &p->snip, zDoc, n); |
| 302 | zDoc += n; |
| 303 | iTail -= n; |
| 304 | blob_append(&p->snip, p->zMarkEnd, -1); |
| 305 | i = -1; |
| 306 | break; |
| 307 | } /* end-if */ |
| 308 | } /* end for(j) */ |
| 309 | if( j<p->nTerm ){ |
| 310 | while( ISALNUM(zDoc[i]) && i<iTail ){ i++; } |
| 311 | } |
| 312 | } /* end for(i) */ |
| 313 | snippet_text_append(p, &p->snip, zDoc, iTail); |
| 314 | } |
| 315 | if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1); |
| 316 | return score; |
| 317 | } |
| 318 | |
| 319 | /* |
| 320 | ** COMMAND: test-match |
| 321 | ** |
| 322 | ** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ... |
| 323 | */ |
| 324 | void test_match_cmd(void){ |
| 325 | Search *p; |
| 326 | int i; |
| 327 | Blob x; |
| 328 | int score; |
| 329 | char *zDoc; |
| 330 | int flg = 0; |
| 331 | char *zBegin = (char*)find_option("begin",0,1); |
| 332 | char *zEnd = (char*)find_option("end",0,1); |
| 333 | char *zGap = (char*)find_option("gap",0,1); |
| 334 | if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML; |
| 335 | if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC; |
| 336 | verify_all_options(); |
| 337 | if( g.argc<4 ) usage("SEARCHSTRING FILE1..."); |
| 338 | if( zBegin==0 ) zBegin = "[["; |
| 339 | if( zEnd==0 ) zEnd = "]]"; |
| @@ -337,18 +340,18 @@ | |
| 340 | if( zGap==0 ) zGap = " ... "; |
| 341 | p = search_init(g.argv[2], zBegin, zEnd, zGap, flg); |
| 342 | for(i=3; i<g.argc; i++){ |
| 343 | blob_read_from_file(&x, g.argv[i]); |
| 344 | zDoc = blob_str(&x); |
| 345 | score = search_match(p, 1, (const char**)&zDoc); |
| 346 | fossil_print("%s: %d\n", g.argv[i], p->iScore); |
| 347 | blob_reset(&x); |
| 348 | if( score ){ |
| 349 | fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '='); |
| 350 | } |
| 351 | } |
| 352 | search_end(p); |
| 353 | } |
| 354 | |
| 355 | /* |
| 356 | ** An SQL function to initialize the global search pattern: |
| 357 | ** |
| @@ -360,12 +363,12 @@ | |
| 363 | sqlite3_context *context, |
| 364 | int argc, |
| 365 | sqlite3_value **argv |
| 366 | ){ |
| 367 | const char *zPattern = 0; |
| 368 | const char *zBegin = "<mark>"; |
| 369 | const char *zEnd = "</mark>"; |
| 370 | const char *zGap = " ... "; |
| 371 | unsigned int flg = SRCHFLG_HTML; |
| 372 | switch( argc ){ |
| 373 | default: |
| 374 | flg = (unsigned int)sqlite3_value_int(argv[4]); |
| @@ -384,50 +387,98 @@ | |
| 387 | search_end(&gSearch); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | ** Try to match the input text against the search parameters set up |
| 393 | ** by the previous search_init() call. Remember the results globally. |
| 394 | ** Return non-zero on a match and zero on a miss. |
| 395 | */ |
| 396 | static void search_match_sqlfunc( |
| 397 | sqlite3_context *context, |
| 398 | int argc, |
| 399 | sqlite3_value **argv |
| 400 | ){ |
| 401 | const char *zSText = (const char*)sqlite3_value_text(argv[0]); |
| 402 | int rc; |
| 403 | if( zSText==0 ) return; |
| 404 | rc = search_match(&gSearch, 1, &zSText); |
| 405 | sqlite3_result_int(context, rc); |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | ** These SQL functions return the results of the last |
| 410 | ** call to the search_match() SQL function. |
| 411 | */ |
| 412 | static void search_score_sqlfunc( |
| 413 | sqlite3_context *context, |
| 414 | int argc, |
| 415 | sqlite3_value **argv |
| 416 | ){ |
| 417 | sqlite3_result_int(context, gSearch.iScore); |
| 418 | } |
| 419 | static void search_snippet_sqlfunc( |
| 420 | sqlite3_context *context, |
| 421 | int argc, |
| 422 | sqlite3_value **argv |
| 423 | ){ |
| 424 | if( blob_size(&gSearch.snip)>0 ){ |
| 425 | sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free); |
| 426 | blob_init(&gSearch.snip, 0, 0); |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | /* |
| 431 | ** This is an SQLite function that computes the searchable text. |
| 432 | ** It is a wrapper around the search_stext() routine. See the |
| 433 | ** search_stext() routine for further detail. |
| 434 | */ |
| 435 | static void search_stext_sqlfunc( |
| 436 | sqlite3_context *context, |
| 437 | int argc, |
| 438 | sqlite3_value **argv |
| 439 | ){ |
| 440 | Blob txt; |
| 441 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 442 | int rid = sqlite3_value_int(argv[1]); |
| 443 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 444 | search_stext(zType[0], rid, zName, &txt); |
| 445 | sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); |
| 446 | } |
| 447 | |
| 448 | /* |
| 449 | ** Encode a string for use as a query parameter in a URL |
| 450 | */ |
| 451 | static void search_urlencode_sqlfunc( |
| 452 | sqlite3_context *context, |
| 453 | int argc, |
| 454 | sqlite3_value **argv |
| 455 | ){ |
| 456 | char *z = mprintf("%T",sqlite3_value_text(argv[0])); |
| 457 | sqlite3_result_text(context, z, -1, fossil_free); |
| 458 | } |
| 459 | |
| 460 | /* |
| 461 | ** Register the "score()" SQL function to score its input text |
| 462 | ** using the given Search object. Once this function is registered, |
| 463 | ** do not delete the Search object. |
| 464 | */ |
| 465 | void search_sql_setup(sqlite3 *db){ |
| 466 | static int once = 0; |
| 467 | if( once++ ) return; |
| 468 | sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0, |
| 469 | search_match_sqlfunc, 0, 0); |
| 470 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 471 | search_score_sqlfunc, 0, 0); |
| 472 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 473 | search_snippet_sqlfunc, 0, 0); |
| 474 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 475 | search_init_sqlfunc, 0, 0); |
| 476 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 477 | search_stext_sqlfunc, 0, 0); |
| 478 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 479 | search_urlencode_sqlfunc, 0, 0); |
| 480 | } |
| 481 | |
| 482 | /* |
| 483 | ** Testing the search function. |
| 484 | ** |
| @@ -501,67 +552,930 @@ | |
| 552 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 553 | blob_reset(&sql); |
| 554 | print_timeline(&q, nLimit, width, 0); |
| 555 | db_finalize(&q); |
| 556 | } |
| 557 | |
| 558 | #if INTERFACE |
| 559 | /* What to search for */ |
| 560 | #define SRCH_CKIN 0x0001 /* Search over check-in comments */ |
| 561 | #define SRCH_DOC 0x0002 /* Search over embedded documents */ |
| 562 | #define SRCH_TKT 0x0004 /* Search over tickets */ |
| 563 | #define SRCH_WIKI 0x0008 /* Search over wiki */ |
| 564 | #define SRCH_ALL 0x000f /* Search over everything */ |
| 565 | #endif |
| 566 | |
| 567 | /* |
| 568 | ** Remove bits from srchFlags which are disallowed by either the |
| 569 | ** current server configuration or by user permissions. |
| 570 | */ |
| 571 | unsigned int search_restrict(unsigned int srchFlags){ |
| 572 | if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); |
| 573 | if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); |
| 574 | if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); |
| 575 | if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){ |
| 576 | srchFlags &= ~SRCH_CKIN; |
| 577 | } |
| 578 | if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){ |
| 579 | srchFlags &= ~SRCH_DOC; |
| 580 | } |
| 581 | if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){ |
| 582 | srchFlags &= ~SRCH_TKT; |
| 583 | } |
| 584 | if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){ |
| 585 | srchFlags &= ~SRCH_WIKI; |
| 586 | } |
| 587 | return srchFlags; |
| 588 | } |
| 589 | |
| 590 | /* |
| 591 | ** When this routine is called, there already exists a table |
| 592 | ** |
| 593 | ** x(label,url,score,date,snip). |
| 594 | ** |
| 595 | ** And the srchFlags parameter has been validated. This routine |
| 596 | ** fills the X table with search results using a full-text scan. |
| 597 | ** |
| 598 | ** The companion indexed scan routine is search_indexed(). |
| 599 | */ |
| 600 | static void search_fullscan( |
| 601 | const char *zPattern, /* The query pattern */ |
| 602 | unsigned int srchFlags /* What to search over */ |
| 603 | ){ |
| 604 | search_init(zPattern, "<mark>", "</mark>", " ... ", |
| 605 | SRCHFLG_STATIC|SRCHFLG_HTML); |
| 606 | if( (srchFlags & SRCH_DOC)!=0 ){ |
| 607 | char *zDocGlob = db_get("doc-glob",""); |
| 608 | char *zDocBr = db_get("doc-branch","trunk"); |
| 609 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 610 | db_multi_exec( |
| 611 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 612 | ); |
| 613 | db_multi_exec( |
| 614 | "INSERT INTO x(label,url,score,date,snip)" |
| 615 | " SELECT printf('Document: %%s',foci.filename)," |
| 616 | " printf('%R/doc/%T/%%s',foci.filename)," |
| 617 | " search_score()," |
| 618 | " (SELECT datetime(event.mtime) FROM event" |
| 619 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 620 | " search_snippet()" |
| 621 | " FROM foci CROSS JOIN blob" |
| 622 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 623 | " AND blob.uuid=foci.uuid" |
| 624 | " AND search_match(stext('d',blob.rid,foci.filename))" |
| 625 | " AND %z", |
| 626 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 627 | ); |
| 628 | } |
| 629 | } |
| 630 | if( (srchFlags & SRCH_WIKI)!=0 ){ |
| 631 | db_multi_exec( |
| 632 | "WITH wiki(name,rid,mtime) AS (" |
| 633 | " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" |
| 634 | " FROM tag, tagxref" |
| 635 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 636 | " AND tagxref.tagid=tag.tagid" |
| 637 | " GROUP BY 1" |
| 638 | ")" |
| 639 | "INSERT INTO x(label,url,score,date,snip)" |
| 640 | " SELECT printf('Wiki: %%s',name)," |
| 641 | " printf('%R/wiki?name=%%s',urlencode(name))," |
| 642 | " search_score()," |
| 643 | " datetime(mtime)," |
| 644 | " search_snippet()" |
| 645 | " FROM wiki" |
| 646 | " WHERE search_match(stext('w',rid,name));" |
| 647 | ); |
| 648 | } |
| 649 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 650 | db_multi_exec( |
| 651 | "WITH ckin(uuid,rid,mtime) AS (" |
| 652 | " SELECT blob.uuid, event.objid, event.mtime" |
| 653 | " FROM event, blob" |
| 654 | " WHERE event.type='ci'" |
| 655 | " AND blob.rid=event.objid" |
| 656 | ")" |
| 657 | "INSERT INTO x(label,url,score,date,snip)" |
| 658 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 659 | " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 660 | " search_score()," |
| 661 | " datetime(mtime)," |
| 662 | " search_snippet()" |
| 663 | " FROM ckin" |
| 664 | " WHERE search_match(stext('c',rid,NULL));" |
| 665 | ); |
| 666 | } |
| 667 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 668 | db_multi_exec( |
| 669 | "INSERT INTO x(label,url,score, date,snip)" |
| 670 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 671 | "tkt_uuid,datetime(tkt_mtime))," |
| 672 | " printf('%R/tktview/%%.20s',tkt_uuid)," |
| 673 | " search_score()," |
| 674 | " datetime(tkt_mtime)," |
| 675 | " search_snippet()" |
| 676 | " FROM ticket" |
| 677 | " WHERE search_match(stext('t',tkt_id,NULL));" |
| 678 | ); |
| 679 | } |
| 680 | } |
| 681 | |
| 682 | /* |
| 683 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 684 | */ |
| 685 | static void search_rank_sqlfunc( |
| 686 | sqlite3_context *context, |
| 687 | int argc, |
| 688 | sqlite3_value **argv |
| 689 | ){ |
| 690 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 691 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 692 | int nTerm; /* Number of search terms in the query */ |
| 693 | int i; /* Loop counter */ |
| 694 | double r = 1.0; /* Score */ |
| 695 | |
| 696 | if( nVal<6 ) return; |
| 697 | if( aVal[1]!=1 ) return; |
| 698 | nTerm = aVal[0]; |
| 699 | r *= 1<<((30*(aVal[2]-1))/nTerm); |
| 700 | for(i=1; i<=nTerm; i++){ |
| 701 | int hits_this_row = aVal[3*i]; |
| 702 | int hits_all_rows = aVal[3*i+1]; |
| 703 | int rows_with_hit = aVal[3*i+2]; |
| 704 | double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; |
| 705 | r *= hits_this_row/avg_hits_per_row; |
| 706 | } |
| 707 | #define SEARCH_DEBUG_RANK 0 |
| 708 | #if SEARCH_DEBUG_RANK |
| 709 | { |
| 710 | Blob x; |
| 711 | blob_init(&x,0,0); |
| 712 | blob_appendf(&x,"%08x", (int)r); |
| 713 | for(i=0; i<nVal; i++){ |
| 714 | blob_appendf(&x," %d", aVal[i]); |
| 715 | } |
| 716 | blob_appendf(&x," r=%g", r); |
| 717 | sqlite3_result_text(context, blob_str(&x), -1, fossil_free); |
| 718 | } |
| 719 | #else |
| 720 | sqlite3_result_double(context, r); |
| 721 | #endif |
| 722 | } |
| 723 | |
| 724 | /* |
| 725 | ** When this routine is called, there already exists a table |
| 726 | ** |
| 727 | ** x(label,url,score,date,snip). |
| 728 | ** |
| 729 | ** And the srchFlags parameter has been validated. This routine |
| 730 | ** fills the X table with search results using a index scan. |
| 731 | ** |
| 732 | ** The companion full-text scan routine is search_fullscan(). |
| 733 | */ |
| 734 | static void search_indexed( |
| 735 | const char *zPattern, /* The query pattern */ |
| 736 | unsigned int srchFlags /* What to search over */ |
| 737 | ){ |
| 738 | Blob sql; |
| 739 | if( srchFlags==0 ) return; |
| 740 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 741 | search_rank_sqlfunc, 0, 0); |
| 742 | blob_init(&sql, 0, 0); |
| 743 | blob_appendf(&sql, |
| 744 | "INSERT INTO x(label,url,score,date,snip) " |
| 745 | " SELECT ftsdocs.label," |
| 746 | " ftsdocs.url," |
| 747 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 748 | " datetime(ftsdocs.mtime)," |
| 749 | " snippet(ftsidx,'<mark>','</mark>',' ... ')" |
| 750 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 751 | " WHERE ftsidx MATCH %Q" |
| 752 | " AND ftsdocs.rowid=ftsidx.docid", |
| 753 | zPattern |
| 754 | ); |
| 755 | if( srchFlags!=SRCH_ALL ){ |
| 756 | const char *zSep = " AND ("; |
| 757 | static const struct { unsigned m; char c; } aMask[] = { |
| 758 | { SRCH_CKIN, 'c' }, |
| 759 | { SRCH_DOC, 'd' }, |
| 760 | { SRCH_TKT, 't' }, |
| 761 | { SRCH_WIKI, 'w' }, |
| 762 | }; |
| 763 | int i; |
| 764 | for(i=0; i<ArraySize(aMask); i++){ |
| 765 | if( srchFlags & aMask[i].m ){ |
| 766 | blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c); |
| 767 | zSep = " OR "; |
| 768 | } |
| 769 | } |
| 770 | blob_append(&sql,")",1); |
| 771 | } |
| 772 | db_multi_exec("%s",blob_str(&sql)/*safe-for-%s*/); |
| 773 | #if SEARCH_DEBUG_RANK |
| 774 | db_multi_exec("UPDATE x SET label=printf('%%s (score=%%s)',label,score)"); |
| 775 | #endif |
| 776 | } |
| 777 | |
| 778 | /* |
| 779 | ** If z[] is of the form "<mark>TEXT</mark>" where TEXT contains |
| 780 | ** no white-space or punctuation, then return the length of the mark. |
| 781 | ** If |
| 782 | */ |
| 783 | static int isSnippetMark(const char *z){ |
| 784 | int n; |
| 785 | if( strncmp(z,"<mark>",6)!=0 ) return 0; |
| 786 | n = 6; |
| 787 | while( fossil_isalnum(z[n]) ) n++; |
| 788 | if( strncmp(&z[n],"</mark>",7)!=0 ) return 0; |
| 789 | return n+7; |
| 790 | } |
| 791 | |
| 792 | /* |
| 793 | ** Return a copy of zSnip (in memory obtained from fossil_malloc()) that |
| 794 | ** has all "<" characters, other than those on <mark> and </mark>, |
| 795 | ** converted into "<". This is similar to htmlize() except that |
| 796 | ** <mark> and </mark> are preserved. |
| 797 | */ |
| 798 | static char *cleanSnippet(const char *zSnip){ |
| 799 | int i; |
| 800 | int n = 0; |
| 801 | char *z; |
| 802 | for(i=0; zSnip[i]; i++) if( zSnip[i]=='<' ) n++; |
| 803 | z = fossil_malloc( i+n*4+1 ); |
| 804 | i = 0; |
| 805 | while( zSnip[0] ){ |
| 806 | if( zSnip[0]=='<' ){ |
| 807 | n = isSnippetMark(zSnip); |
| 808 | if( n ){ |
| 809 | memcpy(&z[i], zSnip, n); |
| 810 | zSnip += n; |
| 811 | i += n; |
| 812 | continue; |
| 813 | }else{ |
| 814 | memcpy(&z[i], "<", 4); |
| 815 | i += 4; |
| 816 | zSnip++; |
| 817 | } |
| 818 | }else{ |
| 819 | z[i++] = zSnip[0]; |
| 820 | zSnip++; |
| 821 | } |
| 822 | } |
| 823 | z[i] = 0; |
| 824 | return z; |
| 825 | } |
| 826 | |
| 827 | |
| 828 | /* |
| 829 | ** This routine generates web-page output for a search operation. |
| 830 | ** Other web-pages can invoke this routine to add search results |
| 831 | ** in the middle of the page. |
| 832 | ** |
| 833 | ** Return the number of rows. |
| 834 | */ |
| 835 | int search_run_and_output( |
| 836 | const char *zPattern, /* The query pattern */ |
| 837 | unsigned int srchFlags /* What to search over */ |
| 838 | ){ |
| 839 | Stmt q; |
| 840 | int nRow = 0; |
| 841 | |
| 842 | srchFlags = search_restrict(srchFlags); |
| 843 | if( srchFlags==0 ) return 0; |
| 844 | search_sql_setup(g.db); |
| 845 | add_content_sql_commands(g.db); |
| 846 | db_multi_exec( |
| 847 | "CREATE TEMP TABLE x(label,url,score,date,snip);" |
| 848 | ); |
| 849 | if( !search_index_exists() ){ |
| 850 | search_fullscan(zPattern, srchFlags); |
| 851 | }else{ |
| 852 | search_update_index(srchFlags); |
| 853 | search_indexed(zPattern, srchFlags); |
| 854 | } |
| 855 | db_prepare(&q, "SELECT url, snip, label" |
| 856 | " FROM x" |
| 857 | " ORDER BY score DESC, date DESC;"); |
| 858 | while( db_step(&q)==SQLITE_ROW ){ |
| 859 | const char *zUrl = db_column_text(&q, 0); |
| 860 | const char *zSnippet = db_column_text(&q, 1); |
| 861 | const char *zLabel = db_column_text(&q, 2); |
| 862 | if( nRow==0 ){ |
| 863 | @ <ol> |
| 864 | } |
| 865 | nRow++; |
| 866 | @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> |
| 867 | @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> |
| 868 | } |
| 869 | db_finalize(&q); |
| 870 | if( nRow ){ |
| 871 | @ </ol> |
| 872 | } |
| 873 | return nRow; |
| 874 | } |
| 875 | |
| 876 | /* |
| 877 | ** Generate some HTML for doing search. At a minimum include the |
| 878 | ** Search-Text entry form. If the "s" query parameter is present, also |
| 879 | ** show search results. |
| 880 | ** |
| 881 | ** The srchFlags parameter is used to customize some of the text of the |
| 882 | ** form and the results. srchFlags should be either a single search |
| 883 | ** category or all categories. Any srchFlags with two or more bits set |
| 884 | ** is treated like SRCH_ALL for display purposes. |
| 885 | ** |
| 886 | ** The entry box is shown disabled if srchFlags is 0. |
| 887 | */ |
| 888 | void search_screen(unsigned srchFlags, const char *zAction){ |
| 889 | const char *zType = 0; |
| 890 | const char *zClass = 0; |
| 891 | const char *zDisable; |
| 892 | const char *zPattern; |
| 893 | switch( srchFlags ){ |
| 894 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 895 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 896 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| 897 | case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; |
| 898 | } |
| 899 | srchFlags = search_restrict(srchFlags); |
| 900 | if( srchFlags==0 ){ |
| 901 | zDisable = " disabled"; |
| 902 | zPattern = ""; |
| 903 | }else{ |
| 904 | zDisable = ""; |
| 905 | zPattern = PD("s",""); |
| 906 | } |
| 907 | @ <form method='GET' action='%s(zAction)'> |
| 908 | if( zClass ){ |
| 909 | @ <div class='searchForm searchForm%s(zClass)'> |
| 910 | }else{ |
| 911 | @ <div class='searchForm'> |
| 912 | } |
| 913 | @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable)> |
| 914 | @ <input type="submit" value="Search%s(zType)"%s(zDisable)> |
| 915 | if( srchFlags==0 ){ |
| 916 | @ <p class="generalError">Search is disabled</p> |
| 917 | } |
| 918 | @ </div></form> |
| 919 | while( fossil_isspace(zPattern[0]) ) zPattern++; |
| 920 | if( zPattern[0] ){ |
| 921 | if( zClass ){ |
| 922 | @ <div class='searchResult searchResult%s(zClass)'> |
| 923 | }else{ |
| 924 | @ <div class='searchResult'> |
| 925 | } |
| 926 | if( search_run_and_output(zPattern, srchFlags)==0 ){ |
| 927 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 928 | } |
| 929 | @ </div> |
| 930 | } |
| 931 | } |
| 932 | |
| 933 | /* |
| 934 | ** WEBPAGE: /search |
| 935 | ** |
| 936 | ** Search for check-in comments, documents, tickets, or wiki that |
| 937 | ** match a user-supplied pattern. |
| 938 | */ |
| 939 | void search_page(void){ |
| 940 | unsigned srchFlags = SRCH_ALL; |
| 941 | const char *zOnly = P("only"); |
| 942 | |
| 943 | login_check_credentials(); |
| 944 | if( zOnly ){ |
| 945 | if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN; |
| 946 | if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC; |
| 947 | if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT; |
| 948 | if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI; |
| 949 | } |
| 950 | style_header("Search"); |
| 951 | search_screen(srchFlags, "search"); |
| 952 | style_footer(); |
| 953 | } |
| 954 | |
| 955 | |
| 956 | /* |
| 957 | ** This is a helper function for search_stext(). Writing into pOut |
| 958 | ** the search text obtained from pIn according to zMimetype. |
| 959 | */ |
| 960 | static void get_stext_by_mimetype( |
| 961 | Blob *pIn, |
| 962 | const char *zMimetype, |
| 963 | Blob *pOut |
| 964 | ){ |
| 965 | Blob html, title; |
| 966 | blob_init(&html, 0, 0); |
| 967 | blob_init(&title, 0, 0); |
| 968 | if( zMimetype==0 ) zMimetype = "text/plain"; |
| 969 | if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ |
| 970 | wiki_convert(pIn, &html, 0); |
| 971 | html_to_plaintext(blob_str(&html), pOut); |
| 972 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 973 | markdown_to_html(pIn, &title, &html); |
| 974 | html_to_plaintext(blob_str(&html), pOut); |
| 975 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 976 | html_to_plaintext(blob_str(pIn), pOut); |
| 977 | }else{ |
| 978 | *pOut = *pIn; |
| 979 | blob_init(pIn, 0, 0); |
| 980 | } |
| 981 | blob_reset(&html); |
| 982 | blob_reset(&title); |
| 983 | } |
| 984 | |
| 985 | /* |
| 986 | ** Query pQuery is pointing at a single row of output. Append a text |
| 987 | ** representation of every text-compatible column to pAccum. |
| 988 | */ |
| 989 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){ |
| 990 | int n = db_column_count(pQuery); |
| 991 | int i; |
| 992 | for(i=0; i<n; i++){ |
| 993 | const char *zColName = db_column_name(pQuery,i); |
| 994 | if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue; |
| 995 | if( fossil_stricmp(zColName,"mimetype")==0 ) continue; |
| 996 | switch( db_column_type(pQuery,i) ){ |
| 997 | case SQLITE_INTEGER: |
| 998 | case SQLITE_FLOAT: |
| 999 | case SQLITE_TEXT: |
| 1000 | blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); |
| 1001 | } |
| 1002 | } |
| 1003 | } |
| 1004 | |
| 1005 | |
| 1006 | /* |
| 1007 | ** Return "search text" - a reduced version of a document appropriate for |
| 1008 | ** full text search and/or for constructing a search result snippet. |
| 1009 | ** |
| 1010 | ** cType: d Embedded documentation |
| 1011 | ** w Wiki page |
| 1012 | ** c Check-in comment |
| 1013 | ** t Ticket text |
| 1014 | ** |
| 1015 | ** rid The RID of an artifact that defines the object |
| 1016 | ** being searched. |
| 1017 | ** |
| 1018 | ** zName Name of the object being searched. |
| 1019 | */ |
| 1020 | void search_stext( |
| 1021 | char cType, /* Type of document */ |
| 1022 | int rid, /* BLOB.RID or TAG.TAGID value for document */ |
| 1023 | const char *zName, /* Auxiliary information */ |
| 1024 | Blob *pOut /* OUT: Initialize to the search text */ |
| 1025 | ){ |
| 1026 | blob_init(pOut, 0, 0); |
| 1027 | switch( cType ){ |
| 1028 | case 'd': { /* Documents */ |
| 1029 | Blob doc; |
| 1030 | content_get(rid, &doc); |
| 1031 | blob_to_utf8_no_bom(&doc, 0); |
| 1032 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 1033 | blob_reset(&doc); |
| 1034 | break; |
| 1035 | } |
| 1036 | case 'w': { /* Wiki */ |
| 1037 | Manifest *pWiki = manifest_get(rid, CFTYPE_WIKI,0); |
| 1038 | Blob wiki; |
| 1039 | if( pWiki==0 ) break; |
| 1040 | blob_init(&wiki, pWiki->zWiki, -1); |
| 1041 | get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), |
| 1042 | pOut); |
| 1043 | blob_reset(&wiki); |
| 1044 | manifest_destroy(pWiki); |
| 1045 | break; |
| 1046 | } |
| 1047 | case 'c': { /* Check-in Comments */ |
| 1048 | static Stmt q; |
| 1049 | db_static_prepare(&q, |
| 1050 | "SELECT coalesce(ecomment,comment)" |
| 1051 | " ||' (user: '||coalesce(euser,user,'?')" |
| 1052 | " ||', tags: '||" |
| 1053 | " (SELECT group_concat(substr(tag.tagname,5),',')" |
| 1054 | " FROM tag, tagxref" |
| 1055 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 1056 | " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" |
| 1057 | " ||')'" |
| 1058 | " FROM event WHERE objid=:x AND type='ci'"); |
| 1059 | db_bind_int(&q, ":x", rid); |
| 1060 | if( db_step(&q)==SQLITE_ROW ){ |
| 1061 | db_column_blob(&q, 0, pOut); |
| 1062 | blob_append(pOut, "\n", 1); |
| 1063 | } |
| 1064 | db_reset(&q); |
| 1065 | break; |
| 1066 | } |
| 1067 | case 't': { /* Tickets */ |
| 1068 | static Stmt q1; |
| 1069 | Blob raw; |
| 1070 | db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); |
| 1071 | blob_init(&raw,0,0); |
| 1072 | db_bind_int(&q1, ":rid", rid); |
| 1073 | if( db_step(&q1)==SQLITE_ROW ){ |
| 1074 | append_all_ticket_fields(&raw, &q1); |
| 1075 | } |
| 1076 | db_reset(&q1); |
| 1077 | if( db_table_exists("repository","ticketchng") ){ |
| 1078 | static Stmt q2; |
| 1079 | db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid" |
| 1080 | " ORDER BY tkt_mtime"); |
| 1081 | db_bind_int(&q2, ":rid", rid); |
| 1082 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1083 | append_all_ticket_fields(&raw, &q2); |
| 1084 | } |
| 1085 | db_reset(&q2); |
| 1086 | } |
| 1087 | html_to_plaintext(blob_str(&raw), pOut); |
| 1088 | blob_reset(&raw); |
| 1089 | break; |
| 1090 | } |
| 1091 | } |
| 1092 | } |
| 1093 | |
| 1094 | /* |
| 1095 | ** COMMAND: test-search-stext |
| 1096 | ** |
| 1097 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 1098 | */ |
| 1099 | void test_search_stext(void){ |
| 1100 | Blob out; |
| 1101 | db_find_and_open_repository(0,0); |
| 1102 | if( g.argc!=5 ) usage("TYPE RID NAME"); |
| 1103 | search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); |
| 1104 | fossil_print("%s\n",blob_str(&out)); |
| 1105 | blob_reset(&out); |
| 1106 | } |
| 1107 | |
| 1108 | /* The schema for the full-text index |
| 1109 | */ |
| 1110 | static const char zFtsSchema[] = |
| 1111 | @ -- One entry for each possible search result |
| 1112 | @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs( |
| 1113 | @ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid |
| 1114 | @ type CHAR(1), -- Type of document |
| 1115 | @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document |
| 1116 | @ name TEXT, -- Additional document description |
| 1117 | @ idxed BOOLEAN, -- True if currently in the index |
| 1118 | @ label TEXT, -- Label to print on search results |
| 1119 | @ url TEXT, -- URL to access this document |
| 1120 | @ mtime DATE, -- Date when document created |
| 1121 | @ UNIQUE(type,rid) |
| 1122 | @ ); |
| 1123 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 1124 | @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; |
| 1125 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 1126 | @ SELECT rowid, type, rid, name, idxed, label, url, mtime, |
| 1127 | @ stext(type,rid,name) AS 'stext' |
| 1128 | @ FROM ftsdocs; |
| 1129 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 1130 | @ USING fts4(content="ftscontent", stext); |
| 1131 | ; |
| 1132 | static const char zFtsDrop[] = |
| 1133 | @ DROP TABLE IF EXISTS "%w".ftsidx; |
| 1134 | @ DROP VIEW IF EXISTS "%w".ftscontent; |
| 1135 | @ DROP TABLE IF EXISTS "%w".ftsdocs; |
| 1136 | ; |
| 1137 | |
| 1138 | /* |
| 1139 | ** Create or drop the tables associated with a full-text index. |
| 1140 | */ |
| 1141 | static int searchIdxExists = -1; |
| 1142 | void search_create_index(void){ |
| 1143 | const char *zDb = db_name("repository"); |
| 1144 | search_sql_setup(g.db); |
| 1145 | db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/, |
| 1146 | zDb, zDb, zDb, zDb, zDb); |
| 1147 | searchIdxExists = 1; |
| 1148 | } |
| 1149 | void search_drop_index(void){ |
| 1150 | const char *zDb = db_name("repository"); |
| 1151 | db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); |
| 1152 | searchIdxExists = 0; |
| 1153 | } |
| 1154 | |
| 1155 | /* |
| 1156 | ** Return true if the full-text search index exists |
| 1157 | */ |
| 1158 | int search_index_exists(void){ |
| 1159 | if( searchIdxExists<0 ){ |
| 1160 | searchIdxExists = db_table_exists("repository","ftsdocs"); |
| 1161 | } |
| 1162 | return searchIdxExists; |
| 1163 | } |
| 1164 | |
| 1165 | /* |
| 1166 | ** Fill the FTSDOCS table with unindexed entries for everything |
| 1167 | ** in the repository. This uses INSERT OR IGNORE so entries already |
| 1168 | ** in FTSDOCS are unchanged. |
| 1169 | */ |
| 1170 | void search_fill_index(void){ |
| 1171 | if( !search_index_exists() ) return; |
| 1172 | search_sql_setup(g.db); |
| 1173 | db_multi_exec( |
| 1174 | "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" |
| 1175 | " SELECT 'c', objid, 0 FROM event WHERE type='ci';" |
| 1176 | ); |
| 1177 | db_multi_exec( |
| 1178 | "WITH latest_wiki(rid,name,mtime) AS (" |
| 1179 | " SELECT tagxref.rid, substr(tag.tagname,6), max(tagxref.mtime)" |
| 1180 | " FROM tag, tagxref" |
| 1181 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 1182 | " AND tagxref.tagid=tag.tagid" |
| 1183 | " AND tagxref.value>0" |
| 1184 | " GROUP BY 2" |
| 1185 | ") INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" |
| 1186 | " SELECT 'w', rid, name, 0 FROM latest_wiki;" |
| 1187 | ); |
| 1188 | db_multi_exec( |
| 1189 | "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" |
| 1190 | " SELECT 't', tkt_id, 0 FROM ticket;" |
| 1191 | ); |
| 1192 | } |
| 1193 | |
| 1194 | /* |
| 1195 | ** The document described by cType,rid,zName is about to be added or |
| 1196 | ** updated. If the document has already been indexed, then unindex it |
| 1197 | ** now while we still have access to the old content. Add the document |
| 1198 | ** to the queue of documents that need to be indexed or reindexed. |
| 1199 | */ |
| 1200 | void search_doc_touch(char cType, int rid, const char *zName){ |
| 1201 | if( search_index_exists() ){ |
| 1202 | char zType[2]; |
| 1203 | zType[0] = cType; |
| 1204 | zType[1] = 0; |
| 1205 | search_sql_setup(g.db); |
| 1206 | db_multi_exec( |
| 1207 | "DELETE FROM ftsidx WHERE docid IN" |
| 1208 | " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", |
| 1209 | zType, rid |
| 1210 | ); |
| 1211 | db_multi_exec( |
| 1212 | "REPLACE INTO ftsdocs(type,rid,name,idxed)" |
| 1213 | " VALUES(%Q,%d,%Q,0)", |
| 1214 | zType, rid, zName |
| 1215 | ); |
| 1216 | if( cType=='w' ){ |
| 1217 | db_multi_exec( |
| 1218 | "DELETE FROM ftsidx WHERE docid IN" |
| 1219 | " (SELECT rowid FROM ftsdocs WHERE type='w' AND name=%Q AND idxed)", |
| 1220 | zName |
| 1221 | ); |
| 1222 | db_multi_exec( |
| 1223 | "DELETE FROM ftsdocs WHERE type='w' AND name=%Q AND rid!=%d", |
| 1224 | zName, rid |
| 1225 | ); |
| 1226 | } |
| 1227 | } |
| 1228 | } |
| 1229 | |
| 1230 | /* |
| 1231 | ** If the doc-glob and doc-br settings are valid for document search |
| 1232 | ** and if the latest check-in on doc-br is in the unindexed set of |
| 1233 | ** check-ins, then update all 'd' entries in FTSDOCS that have |
| 1234 | ** changed. |
| 1235 | */ |
| 1236 | static void search_update_doc_index(void){ |
| 1237 | const char *zDocBr = db_get("doc-branch","trunk"); |
| 1238 | int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; |
| 1239 | double rTime; |
| 1240 | char *zBrUuid; |
| 1241 | if( ckid==0 ) return; |
| 1242 | if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" |
| 1243 | " AND NOT idxed", ckid) ) return; |
| 1244 | |
| 1245 | /* If we get this far, it means that changes to 'd' entries are |
| 1246 | ** required. */ |
| 1247 | rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); |
| 1248 | zBrUuid = db_text("","SELECT substr(uuid,1,20) FROM blob WHERE rid=%d",ckid); |
| 1249 | db_multi_exec( |
| 1250 | "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" |
| 1251 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 1252 | "INSERT OR IGNORE INTO current_docs(rid, name)" |
| 1253 | " SELECT blob.rid, foci.filename FROM foci, blob" |
| 1254 | " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" |
| 1255 | " AND %z", |
| 1256 | ckid, glob_expr("foci.filename", db_get("doc-glob","")) |
| 1257 | ); |
| 1258 | db_multi_exec( |
| 1259 | "DELETE FROM ftsidx WHERE docid IN" |
| 1260 | " (SELECT rowid FROM ftsdocs WHERE type='d'" |
| 1261 | " AND rid NOT IN (SELECT rid FROM current_docs))" |
| 1262 | ); |
| 1263 | db_multi_exec( |
| 1264 | "DELETE FROM ftsdocs WHERE type='d'" |
| 1265 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1266 | ); |
| 1267 | db_multi_exec( |
| 1268 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" |
| 1269 | " SELECT 'd', rid, name, 0," |
| 1270 | " printf('Document: %%s',name)," |
| 1271 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1272 | " %.17g" |
| 1273 | " FROM current_docs", |
| 1274 | zBrUuid, rTime |
| 1275 | ); |
| 1276 | db_multi_exec( |
| 1277 | "INSERT INTO ftsidx(docid,stext)" |
| 1278 | " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed" |
| 1279 | ); |
| 1280 | db_multi_exec( |
| 1281 | "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" |
| 1282 | ); |
| 1283 | } |
| 1284 | |
| 1285 | /* |
| 1286 | ** Deal with all of the unindexed 'c' terms in FTSDOCS |
| 1287 | */ |
| 1288 | static void search_update_checkin_index(void){ |
| 1289 | db_multi_exec( |
| 1290 | "INSERT INTO ftsidx(docid,stext)" |
| 1291 | " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs" |
| 1292 | " WHERE type='c' AND NOT idxed;" |
| 1293 | ); |
| 1294 | db_multi_exec( |
| 1295 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1296 | " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," |
| 1297 | " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime))," |
| 1298 | " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid)," |
| 1299 | " event.mtime" |
| 1300 | " FROM ftsdocs, event, blob" |
| 1301 | " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" |
| 1302 | " AND event.objid=ftsdocs.rid" |
| 1303 | " AND blob.rid=ftsdocs.rid" |
| 1304 | ); |
| 1305 | } |
| 1306 | |
| 1307 | /* |
| 1308 | ** Deal with all of the unindexed 't' terms in FTSDOCS |
| 1309 | */ |
| 1310 | static void search_update_ticket_index(void){ |
| 1311 | db_multi_exec( |
| 1312 | "INSERT INTO ftsidx(docid,stext)" |
| 1313 | " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs" |
| 1314 | " WHERE type='t' AND NOT idxed;" |
| 1315 | ); |
| 1316 | if( db_changes()==0 ) return; |
| 1317 | db_multi_exec( |
| 1318 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1319 | " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," |
| 1320 | " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime))," |
| 1321 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 1322 | " tkt_mtime" |
| 1323 | " FROM ftsdocs, ticket" |
| 1324 | " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" |
| 1325 | " AND ticket.tkt_id=ftsdocs.rid" |
| 1326 | ); |
| 1327 | } |
| 1328 | |
| 1329 | /* |
| 1330 | ** Deal with all of the unindexed 'w' terms in FTSDOCS |
| 1331 | */ |
| 1332 | static void search_update_wiki_index(void){ |
| 1333 | db_multi_exec( |
| 1334 | "INSERT INTO ftsidx(docid,stext)" |
| 1335 | " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs" |
| 1336 | " WHERE type='w' AND NOT idxed;" |
| 1337 | ); |
| 1338 | if( db_changes()==0 ) return; |
| 1339 | db_multi_exec( |
| 1340 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1341 | " SELECT ftsdocs.rowid, 1, 'w', ftsdocs.rid, ftsdocs.name," |
| 1342 | " 'Wiki: '||ftsdocs.name," |
| 1343 | " '/wiki?name='||urlencode(ftsdocs.name)," |
| 1344 | " tagxref.mtime" |
| 1345 | " FROM ftsdocs, tagxref" |
| 1346 | " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed" |
| 1347 | " AND tagxref.rid=ftsdocs.rid" |
| 1348 | ); |
| 1349 | } |
| 1350 | |
| 1351 | /* |
| 1352 | ** Deal with all of the unindexed entries in the FTSDOCS table - that |
| 1353 | ** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the |
| 1354 | ** index. |
| 1355 | */ |
| 1356 | void search_update_index(unsigned int srchFlags){ |
| 1357 | if( !search_index_exists() ) return; |
| 1358 | if( !db_exists("SELECT 1 FROM ftsdocs WHERE NOT idxed") ) return; |
| 1359 | search_sql_setup(g.db); |
| 1360 | if( srchFlags & (SRCH_CKIN|SRCH_DOC) ){ |
| 1361 | search_update_doc_index(); |
| 1362 | search_update_checkin_index(); |
| 1363 | } |
| 1364 | if( srchFlags & SRCH_TKT ){ |
| 1365 | search_update_ticket_index(); |
| 1366 | } |
| 1367 | if( srchFlags & SRCH_WIKI ){ |
| 1368 | search_update_wiki_index(); |
| 1369 | } |
| 1370 | } |
| 1371 | |
| 1372 | /* |
| 1373 | ** Construct, prepopulate, and then update the full-text index. |
| 1374 | */ |
| 1375 | void search_rebuild_index(void){ |
| 1376 | fossil_print("rebuilding the search index..."); |
| 1377 | fflush(stdout); |
| 1378 | search_create_index(); |
| 1379 | search_fill_index(); |
| 1380 | search_update_index(search_restrict(SRCH_ALL)); |
| 1381 | fossil_print(" done\n"); |
| 1382 | } |
| 1383 | |
| 1384 | /* |
| 1385 | ** COMMAND: fts-config* |
| 1386 | ** |
| 1387 | ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? |
| 1388 | ** |
| 1389 | ** The "fossil fts-config" command configures the full-text search capabilities |
| 1390 | ** of the repository. Subcommands: |
| 1391 | ** |
| 1392 | ** reindex Rebuild the search index. Create it if it does |
| 1393 | ** not already exist |
| 1394 | ** |
| 1395 | ** index (on|off) Turn the search index on or off |
| 1396 | ** |
| 1397 | ** enable cdtw Enable various kinds of search. c=Check-ins, |
| 1398 | ** d=Documents, t=Tickets, w=Wiki. |
| 1399 | ** |
| 1400 | ** disable cdtw Disable versious kinds of search |
| 1401 | ** |
| 1402 | ** The current search settings are displayed after any changes are applied. |
| 1403 | ** Run this command with no arguments to simply see the settings. |
| 1404 | */ |
| 1405 | void test_fts_cmd(void){ |
| 1406 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1407 | { 1, "reindex" }, |
| 1408 | { 2, "index" }, |
| 1409 | { 3, "disable" }, |
| 1410 | { 4, "enable" }, |
| 1411 | }; |
| 1412 | static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { |
| 1413 | { "search-ckin", "check-in search:", "c" }, |
| 1414 | { "search-doc", "document search:", "d" }, |
| 1415 | { "search-tkt", "ticket search:", "t" }, |
| 1416 | { "search-wiki", "wiki search:", "w" }, |
| 1417 | }; |
| 1418 | char *zSubCmd; |
| 1419 | int i, j, n; |
| 1420 | int iCmd = 0; |
| 1421 | int iAction = 0; |
| 1422 | db_find_and_open_repository(0, 0); |
| 1423 | if( g.argc>2 ){ |
| 1424 | zSubCmd = g.argv[2]; |
| 1425 | n = (int)strlen(zSubCmd); |
| 1426 | for(i=0; i<ArraySize(aCmd); i++){ |
| 1427 | if( fossil_strncmp(aCmd[i].z, zSubCmd, n)==0 ) break; |
| 1428 | } |
| 1429 | if( i>=ArraySize(aCmd) ){ |
| 1430 | Blob all; |
| 1431 | blob_init(&all,0,0); |
| 1432 | for(i=0; i<ArraySize(aCmd); i++) blob_appendf(&all, " %s", aCmd[i].z); |
| 1433 | fossil_fatal("unknown \"%s\" - should be on of:%s", |
| 1434 | zSubCmd, blob_str(&all)); |
| 1435 | return; |
| 1436 | } |
| 1437 | iCmd = aCmd[i].iCmd; |
| 1438 | } |
| 1439 | if( iCmd==1 ){ |
| 1440 | iAction = 2; |
| 1441 | } |
| 1442 | if( iCmd==2 ){ |
| 1443 | if( g.argc<3 ) usage("index (on|off)"); |
| 1444 | iAction = 1 + is_truth(g.argv[3]); |
| 1445 | } |
| 1446 | db_begin_transaction(); |
| 1447 | |
| 1448 | /* Adjust search settings */ |
| 1449 | if( iCmd==3 || iCmd==4 ){ |
| 1450 | const char *zCtrl; |
| 1451 | if( g.argc<4 ) usage("enable STRING"); |
| 1452 | zCtrl = g.argv[3]; |
| 1453 | for(j=0; j<ArraySize(aSetng); j++){ |
| 1454 | if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){ |
| 1455 | db_set_int(aSetng[j].zSetting, iCmd-3, 0); |
| 1456 | } |
| 1457 | } |
| 1458 | } |
| 1459 | |
| 1460 | /* destroy or rebuild the index, if requested */ |
| 1461 | if( iAction>=1 ){ |
| 1462 | search_drop_index(); |
| 1463 | } |
| 1464 | if( iAction>=2 ){ |
| 1465 | search_rebuild_index(); |
| 1466 | } |
| 1467 | |
| 1468 | /* Always show the status before ending */ |
| 1469 | for(i=0; i<ArraySize(aSetng); i++){ |
| 1470 | fossil_print("%-16s %s\n", aSetng[i].zName, |
| 1471 | db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); |
| 1472 | } |
| 1473 | if( search_index_exists() ){ |
| 1474 | fossil_print("%-16s enabled\n", "full-text index:"); |
| 1475 | fossil_print("%-16s %d\n", "documents:", |
| 1476 | db_int(0, "SELECT count(*) FROM ftsdocs")); |
| 1477 | }else{ |
| 1478 | fossil_print("%-16s disabled\n", "full-text index:"); |
| 1479 | } |
| 1480 | db_end_transaction(0); |
| 1481 | } |
| 1482 |
+82
-8
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -70,11 +70,11 @@ | ||
| 70 | 70 | @ <p class="generalError"><b>Configuration Error:</b> Please add |
| 71 | 71 | @ <tt><base href="$secureurl/$current_page"></tt> after |
| 72 | 72 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | - @ <table border="0" cellspacing="7"> | |
| 75 | + @ <table border="0" cellspacing="3"> | |
| 76 | 76 | setup_menu_entry("Users", "setup_ulist", |
| 77 | 77 | "Grant privileges to individual users."); |
| 78 | 78 | setup_menu_entry("Access", "setup_access", |
| 79 | 79 | "Control access settings."); |
| 80 | 80 | setup_menu_entry("Configuration", "setup_config", |
| @@ -86,10 +86,12 @@ | ||
| 86 | 86 | setup_menu_entry("Login-Group", "setup_login_group", |
| 87 | 87 | "Manage single sign-on between this repository and others" |
| 88 | 88 | " on the same server"); |
| 89 | 89 | setup_menu_entry("Tickets", "tktsetup", |
| 90 | 90 | "Configure the trouble-ticketing system for this repository"); |
| 91 | + setup_menu_entry("Search","srchsetup", | |
| 92 | + "Configure the built-in search engine"); | |
| 91 | 93 | setup_menu_entry("Transfers", "xfersetup", |
| 92 | 94 | "Configure the transfer system for this repository"); |
| 93 | 95 | setup_menu_entry("Skins", "setup_skin", |
| 94 | 96 | "Select from a menu of prepackaged \"skins\" for the web interface"); |
| 95 | 97 | setup_menu_entry("CSS", "setup_editcss", |
| @@ -1354,11 +1356,11 @@ | ||
| 1354 | 1356 | |
| 1355 | 1357 | /* |
| 1356 | 1358 | ** WEBPAGE: setup_settings |
| 1357 | 1359 | */ |
| 1358 | 1360 | void setup_settings(void){ |
| 1359 | - struct stControlSettings const *pSet; | |
| 1361 | + Setting const *pSet; | |
| 1360 | 1362 | |
| 1361 | 1363 | login_check_credentials(); |
| 1362 | 1364 | if( !g.perm.Setup ){ |
| 1363 | 1365 | login_needed(); |
| 1364 | 1366 | } |
| @@ -1375,14 +1377,14 @@ | ||
| 1375 | 1377 | @ See the "fossil help setting" output below for further information on |
| 1376 | 1378 | @ the meaning of each setting.</p><hr /> |
| 1377 | 1379 | @ <form action="%s(g.zTop)/setup_settings" method="post"><div> |
| 1378 | 1380 | @ <table border="0"><tr><td valign="top"> |
| 1379 | 1381 | login_insert_csrf_secret(); |
| 1380 | - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ | |
| 1382 | + for(pSet=aSetting; pSet->name!=0; pSet++){ | |
| 1381 | 1383 | if( pSet->width==0 ){ |
| 1382 | 1384 | int hasVersionableValue = pSet->versionable && |
| 1383 | - (db_get_do_versionable(pSet->name, NULL)!=0); | |
| 1385 | + (db_get_versioned(pSet->name, NULL)!=0); | |
| 1384 | 1386 | onoff_attribute(pSet->name, pSet->name, |
| 1385 | 1387 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1386 | 1388 | is_truth(pSet->def), hasVersionableValue); |
| 1387 | 1389 | if( pSet->versionable ){ |
| 1388 | 1390 | @ (v)<br /> |
| @@ -1391,31 +1393,31 @@ | ||
| 1391 | 1393 | } |
| 1392 | 1394 | } |
| 1393 | 1395 | } |
| 1394 | 1396 | @ <br /><input type="submit" name="submit" value="Apply Changes" /> |
| 1395 | 1397 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1396 | - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ | |
| 1398 | + for(pSet=aSetting; pSet->name!=0; pSet++){ | |
| 1397 | 1399 | if( pSet->width!=0 && !pSet->versionable && !pSet->forceTextArea ){ |
| 1398 | 1400 | entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name, |
| 1399 | 1401 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1400 | 1402 | (char*)pSet->def, 0); |
| 1401 | 1403 | @ <br /> |
| 1402 | 1404 | } |
| 1403 | 1405 | } |
| 1404 | - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ | |
| 1406 | + for(pSet=aSetting; pSet->name!=0; pSet++){ | |
| 1405 | 1407 | if( pSet->width!=0 && !pSet->versionable && pSet->forceTextArea ){ |
| 1406 | 1408 | @<b>%s(pSet->name)</b><br /> |
| 1407 | 1409 | textarea_attribute("", /*rows*/ 3, /*cols*/ 50, pSet->name, |
| 1408 | 1410 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1409 | 1411 | (char*)pSet->def, 0); |
| 1410 | 1412 | @ <br /> |
| 1411 | 1413 | } |
| 1412 | 1414 | } |
| 1413 | 1415 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1414 | - for(pSet=ctrlSettings; pSet->name!=0; pSet++){ | |
| 1416 | + for(pSet=aSetting; pSet->name!=0; pSet++){ | |
| 1415 | 1417 | if( pSet->width!=0 && pSet->versionable ){ |
| 1416 | - int hasVersionableValue = db_get_do_versionable(pSet->name, NULL)!=0; | |
| 1418 | + int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0; | |
| 1417 | 1419 | @<b>%s(pSet->name)</b> (v)<br /> |
| 1418 | 1420 | textarea_attribute("", /*rows*/ 3, /*cols*/ 20, pSet->name, |
| 1419 | 1421 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1420 | 1422 | (char*)pSet->def, hasVersionableValue); |
| 1421 | 1423 | @<br /> |
| @@ -2155,5 +2157,77 @@ | ||
| 2155 | 2157 | if(limit>0 && counter<limit){ |
| 2156 | 2158 | @ <div>%d(counter) entries shown.</div> |
| 2157 | 2159 | } |
| 2158 | 2160 | style_footer(); |
| 2159 | 2161 | } |
| 2162 | + | |
| 2163 | +/* | |
| 2164 | +** WEBPAGE: srchsetup | |
| 2165 | +** | |
| 2166 | +** Configure the search engine. | |
| 2167 | +*/ | |
| 2168 | +void page_srchsetup(){ | |
| 2169 | + login_check_credentials(); | |
| 2170 | + if( !g.perm.Setup && !g.perm.Admin ){ | |
| 2171 | + login_needed(); | |
| 2172 | + } | |
| 2173 | + style_header("Search Configuration"); | |
| 2174 | + @ <form action="%s(g.zTop)/srchsetup" method="post"><div> | |
| 2175 | + login_insert_csrf_secret(); | |
| 2176 | + @ <div style="text-align:center;font-weight:bold;"> | |
| 2177 | + @ Server-specific settings that affect the | |
| 2178 | + @ <a href="%R/search">/search</a> webpage. | |
| 2179 | + @ </div> | |
| 2180 | + @ <hr /> | |
| 2181 | + textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0); | |
| 2182 | + @ <p>The "Document Glob List" is a comma- or newline-separated list | |
| 2183 | + @ of GLOB expressions that identify all documents within the source | |
| 2184 | + @ tree that are to be searched when "Document Search" is enabled. | |
| 2185 | + @ Some examples: | |
| 2186 | + @ <table border=0 cellpadding=2 align=center> | |
| 2187 | + @ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;"> | |
| 2188 | + @ <td>Search all wiki, HTML, Markdown, and Text files</tr> | |
| 2189 | + @ <tr><td>doc/*.md,*/README.txt,README.txt<td> | |
| 2190 | + @ <td>Search all Markdown files in the doc/ subfolder and all README.txt | |
| 2191 | + @ files.</tr> | |
| 2192 | + @ <tr><td>*<td><td>Search all checked-in files</tr> | |
| 2193 | + @ <tr><td><i>(blank)</i><td> | |
| 2194 | + @ <td>Search nothing. (Disables document search).</tr> | |
| 2195 | + @ </table> | |
| 2196 | + @ <hr /> | |
| 2197 | + entry_attribute("Document Branch", 20, "doc-branch", "db", "trunk", 0); | |
| 2198 | + @ <p>When searching documents, use the versions of the files found at the | |
| 2199 | + @ type of the "Document Branch" branch. Recommended value: "trunk". | |
| 2200 | + @ Document search is disabled if blank. | |
| 2201 | + @ <hr/> | |
| 2202 | + onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); | |
| 2203 | + @ <br> | |
| 2204 | + onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); | |
| 2205 | + @ <br> | |
| 2206 | + onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); | |
| 2207 | + @ <br> | |
| 2208 | + onoff_attribute("Search Wiki","search-wiki", "sw", 0, 0); | |
| 2209 | + @ <hr/> | |
| 2210 | + @ <p><input type="submit" name="submit" value="Apply Changes" /></p> | |
| 2211 | + @ <hr/> | |
| 2212 | + if( P("fts0") ){ | |
| 2213 | + search_drop_index(); | |
| 2214 | + }else if( P("fts1") ){ | |
| 2215 | + search_drop_index(); | |
| 2216 | + search_create_index(); | |
| 2217 | + search_fill_index(); | |
| 2218 | + search_update_index(search_restrict(SRCH_ALL)); | |
| 2219 | + } | |
| 2220 | + if( search_index_exists() ){ | |
| 2221 | + @ <p>Currently using an SQLite FTS4 search index. This makes search | |
| 2222 | + @ run faster, especially on large repositories, but takes up space.</p> | |
| 2223 | + @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index"> | |
| 2224 | + @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index"> | |
| 2225 | + }else{ | |
| 2226 | + @ <p>The SQLite FTS4 search index is disabled. All searching will be | |
| 2227 | + @ a full-text scan. This usually works fine, but can be slow for | |
| 2228 | + @ larger repositories.</p> | |
| 2229 | + @ <p><input type="submit" name="fts1" value="Create A Full-Text Index"> | |
| 2230 | + } | |
| 2231 | + @ </div></form> | |
| 2232 | + style_footer(); | |
| 2233 | +} | |
| 2160 | 2234 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -70,11 +70,11 @@ | |
| 70 | @ <p class="generalError"><b>Configuration Error:</b> Please add |
| 71 | @ <tt><base href="$secureurl/$current_page"></tt> after |
| 72 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 73 | } |
| 74 | |
| 75 | @ <table border="0" cellspacing="7"> |
| 76 | setup_menu_entry("Users", "setup_ulist", |
| 77 | "Grant privileges to individual users."); |
| 78 | setup_menu_entry("Access", "setup_access", |
| 79 | "Control access settings."); |
| 80 | setup_menu_entry("Configuration", "setup_config", |
| @@ -86,10 +86,12 @@ | |
| 86 | setup_menu_entry("Login-Group", "setup_login_group", |
| 87 | "Manage single sign-on between this repository and others" |
| 88 | " on the same server"); |
| 89 | setup_menu_entry("Tickets", "tktsetup", |
| 90 | "Configure the trouble-ticketing system for this repository"); |
| 91 | setup_menu_entry("Transfers", "xfersetup", |
| 92 | "Configure the transfer system for this repository"); |
| 93 | setup_menu_entry("Skins", "setup_skin", |
| 94 | "Select from a menu of prepackaged \"skins\" for the web interface"); |
| 95 | setup_menu_entry("CSS", "setup_editcss", |
| @@ -1354,11 +1356,11 @@ | |
| 1354 | |
| 1355 | /* |
| 1356 | ** WEBPAGE: setup_settings |
| 1357 | */ |
| 1358 | void setup_settings(void){ |
| 1359 | struct stControlSettings const *pSet; |
| 1360 | |
| 1361 | login_check_credentials(); |
| 1362 | if( !g.perm.Setup ){ |
| 1363 | login_needed(); |
| 1364 | } |
| @@ -1375,14 +1377,14 @@ | |
| 1375 | @ See the "fossil help setting" output below for further information on |
| 1376 | @ the meaning of each setting.</p><hr /> |
| 1377 | @ <form action="%s(g.zTop)/setup_settings" method="post"><div> |
| 1378 | @ <table border="0"><tr><td valign="top"> |
| 1379 | login_insert_csrf_secret(); |
| 1380 | for(pSet=ctrlSettings; pSet->name!=0; pSet++){ |
| 1381 | if( pSet->width==0 ){ |
| 1382 | int hasVersionableValue = pSet->versionable && |
| 1383 | (db_get_do_versionable(pSet->name, NULL)!=0); |
| 1384 | onoff_attribute(pSet->name, pSet->name, |
| 1385 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1386 | is_truth(pSet->def), hasVersionableValue); |
| 1387 | if( pSet->versionable ){ |
| 1388 | @ (v)<br /> |
| @@ -1391,31 +1393,31 @@ | |
| 1391 | } |
| 1392 | } |
| 1393 | } |
| 1394 | @ <br /><input type="submit" name="submit" value="Apply Changes" /> |
| 1395 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1396 | for(pSet=ctrlSettings; pSet->name!=0; pSet++){ |
| 1397 | if( pSet->width!=0 && !pSet->versionable && !pSet->forceTextArea ){ |
| 1398 | entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name, |
| 1399 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1400 | (char*)pSet->def, 0); |
| 1401 | @ <br /> |
| 1402 | } |
| 1403 | } |
| 1404 | for(pSet=ctrlSettings; pSet->name!=0; pSet++){ |
| 1405 | if( pSet->width!=0 && !pSet->versionable && pSet->forceTextArea ){ |
| 1406 | @<b>%s(pSet->name)</b><br /> |
| 1407 | textarea_attribute("", /*rows*/ 3, /*cols*/ 50, pSet->name, |
| 1408 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1409 | (char*)pSet->def, 0); |
| 1410 | @ <br /> |
| 1411 | } |
| 1412 | } |
| 1413 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1414 | for(pSet=ctrlSettings; pSet->name!=0; pSet++){ |
| 1415 | if( pSet->width!=0 && pSet->versionable ){ |
| 1416 | int hasVersionableValue = db_get_do_versionable(pSet->name, NULL)!=0; |
| 1417 | @<b>%s(pSet->name)</b> (v)<br /> |
| 1418 | textarea_attribute("", /*rows*/ 3, /*cols*/ 20, pSet->name, |
| 1419 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1420 | (char*)pSet->def, hasVersionableValue); |
| 1421 | @<br /> |
| @@ -2155,5 +2157,77 @@ | |
| 2155 | if(limit>0 && counter<limit){ |
| 2156 | @ <div>%d(counter) entries shown.</div> |
| 2157 | } |
| 2158 | style_footer(); |
| 2159 | } |
| 2160 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -70,11 +70,11 @@ | |
| 70 | @ <p class="generalError"><b>Configuration Error:</b> Please add |
| 71 | @ <tt><base href="$secureurl/$current_page"></tt> after |
| 72 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 73 | } |
| 74 | |
| 75 | @ <table border="0" cellspacing="3"> |
| 76 | setup_menu_entry("Users", "setup_ulist", |
| 77 | "Grant privileges to individual users."); |
| 78 | setup_menu_entry("Access", "setup_access", |
| 79 | "Control access settings."); |
| 80 | setup_menu_entry("Configuration", "setup_config", |
| @@ -86,10 +86,12 @@ | |
| 86 | setup_menu_entry("Login-Group", "setup_login_group", |
| 87 | "Manage single sign-on between this repository and others" |
| 88 | " on the same server"); |
| 89 | setup_menu_entry("Tickets", "tktsetup", |
| 90 | "Configure the trouble-ticketing system for this repository"); |
| 91 | setup_menu_entry("Search","srchsetup", |
| 92 | "Configure the built-in search engine"); |
| 93 | setup_menu_entry("Transfers", "xfersetup", |
| 94 | "Configure the transfer system for this repository"); |
| 95 | setup_menu_entry("Skins", "setup_skin", |
| 96 | "Select from a menu of prepackaged \"skins\" for the web interface"); |
| 97 | setup_menu_entry("CSS", "setup_editcss", |
| @@ -1354,11 +1356,11 @@ | |
| 1356 | |
| 1357 | /* |
| 1358 | ** WEBPAGE: setup_settings |
| 1359 | */ |
| 1360 | void setup_settings(void){ |
| 1361 | Setting const *pSet; |
| 1362 | |
| 1363 | login_check_credentials(); |
| 1364 | if( !g.perm.Setup ){ |
| 1365 | login_needed(); |
| 1366 | } |
| @@ -1375,14 +1377,14 @@ | |
| 1377 | @ See the "fossil help setting" output below for further information on |
| 1378 | @ the meaning of each setting.</p><hr /> |
| 1379 | @ <form action="%s(g.zTop)/setup_settings" method="post"><div> |
| 1380 | @ <table border="0"><tr><td valign="top"> |
| 1381 | login_insert_csrf_secret(); |
| 1382 | for(pSet=aSetting; pSet->name!=0; pSet++){ |
| 1383 | if( pSet->width==0 ){ |
| 1384 | int hasVersionableValue = pSet->versionable && |
| 1385 | (db_get_versioned(pSet->name, NULL)!=0); |
| 1386 | onoff_attribute(pSet->name, pSet->name, |
| 1387 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1388 | is_truth(pSet->def), hasVersionableValue); |
| 1389 | if( pSet->versionable ){ |
| 1390 | @ (v)<br /> |
| @@ -1391,31 +1393,31 @@ | |
| 1393 | } |
| 1394 | } |
| 1395 | } |
| 1396 | @ <br /><input type="submit" name="submit" value="Apply Changes" /> |
| 1397 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1398 | for(pSet=aSetting; pSet->name!=0; pSet++){ |
| 1399 | if( pSet->width!=0 && !pSet->versionable && !pSet->forceTextArea ){ |
| 1400 | entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name, |
| 1401 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1402 | (char*)pSet->def, 0); |
| 1403 | @ <br /> |
| 1404 | } |
| 1405 | } |
| 1406 | for(pSet=aSetting; pSet->name!=0; pSet++){ |
| 1407 | if( pSet->width!=0 && !pSet->versionable && pSet->forceTextArea ){ |
| 1408 | @<b>%s(pSet->name)</b><br /> |
| 1409 | textarea_attribute("", /*rows*/ 3, /*cols*/ 50, pSet->name, |
| 1410 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1411 | (char*)pSet->def, 0); |
| 1412 | @ <br /> |
| 1413 | } |
| 1414 | } |
| 1415 | @ </td><td style="width:50px;"></td><td valign="top"> |
| 1416 | for(pSet=aSetting; pSet->name!=0; pSet++){ |
| 1417 | if( pSet->width!=0 && pSet->versionable ){ |
| 1418 | int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0; |
| 1419 | @<b>%s(pSet->name)</b> (v)<br /> |
| 1420 | textarea_attribute("", /*rows*/ 3, /*cols*/ 20, pSet->name, |
| 1421 | pSet->var!=0 ? pSet->var : pSet->name, |
| 1422 | (char*)pSet->def, hasVersionableValue); |
| 1423 | @<br /> |
| @@ -2155,5 +2157,77 @@ | |
| 2157 | if(limit>0 && counter<limit){ |
| 2158 | @ <div>%d(counter) entries shown.</div> |
| 2159 | } |
| 2160 | style_footer(); |
| 2161 | } |
| 2162 | |
| 2163 | /* |
| 2164 | ** WEBPAGE: srchsetup |
| 2165 | ** |
| 2166 | ** Configure the search engine. |
| 2167 | */ |
| 2168 | void page_srchsetup(){ |
| 2169 | login_check_credentials(); |
| 2170 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2171 | login_needed(); |
| 2172 | } |
| 2173 | style_header("Search Configuration"); |
| 2174 | @ <form action="%s(g.zTop)/srchsetup" method="post"><div> |
| 2175 | login_insert_csrf_secret(); |
| 2176 | @ <div style="text-align:center;font-weight:bold;"> |
| 2177 | @ Server-specific settings that affect the |
| 2178 | @ <a href="%R/search">/search</a> webpage. |
| 2179 | @ </div> |
| 2180 | @ <hr /> |
| 2181 | textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0); |
| 2182 | @ <p>The "Document Glob List" is a comma- or newline-separated list |
| 2183 | @ of GLOB expressions that identify all documents within the source |
| 2184 | @ tree that are to be searched when "Document Search" is enabled. |
| 2185 | @ Some examples: |
| 2186 | @ <table border=0 cellpadding=2 align=center> |
| 2187 | @ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;"> |
| 2188 | @ <td>Search all wiki, HTML, Markdown, and Text files</tr> |
| 2189 | @ <tr><td>doc/*.md,*/README.txt,README.txt<td> |
| 2190 | @ <td>Search all Markdown files in the doc/ subfolder and all README.txt |
| 2191 | @ files.</tr> |
| 2192 | @ <tr><td>*<td><td>Search all checked-in files</tr> |
| 2193 | @ <tr><td><i>(blank)</i><td> |
| 2194 | @ <td>Search nothing. (Disables document search).</tr> |
| 2195 | @ </table> |
| 2196 | @ <hr /> |
| 2197 | entry_attribute("Document Branch", 20, "doc-branch", "db", "trunk", 0); |
| 2198 | @ <p>When searching documents, use the versions of the files found at the |
| 2199 | @ type of the "Document Branch" branch. Recommended value: "trunk". |
| 2200 | @ Document search is disabled if blank. |
| 2201 | @ <hr/> |
| 2202 | onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); |
| 2203 | @ <br> |
| 2204 | onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); |
| 2205 | @ <br> |
| 2206 | onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); |
| 2207 | @ <br> |
| 2208 | onoff_attribute("Search Wiki","search-wiki", "sw", 0, 0); |
| 2209 | @ <hr/> |
| 2210 | @ <p><input type="submit" name="submit" value="Apply Changes" /></p> |
| 2211 | @ <hr/> |
| 2212 | if( P("fts0") ){ |
| 2213 | search_drop_index(); |
| 2214 | }else if( P("fts1") ){ |
| 2215 | search_drop_index(); |
| 2216 | search_create_index(); |
| 2217 | search_fill_index(); |
| 2218 | search_update_index(search_restrict(SRCH_ALL)); |
| 2219 | } |
| 2220 | if( search_index_exists() ){ |
| 2221 | @ <p>Currently using an SQLite FTS4 search index. This makes search |
| 2222 | @ run faster, especially on large repositories, but takes up space.</p> |
| 2223 | @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index"> |
| 2224 | @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index"> |
| 2225 | }else{ |
| 2226 | @ <p>The SQLite FTS4 search index is disabled. All searching will be |
| 2227 | @ a full-text scan. This usually works fine, but can be slow for |
| 2228 | @ larger repositories.</p> |
| 2229 | @ <p><input type="submit" name="fts1" value="Create A Full-Text Index"> |
| 2230 | } |
| 2231 | @ </div></form> |
| 2232 | style_footer(); |
| 2233 | } |
| 2234 |
+109
-116
| --- src/sqlite3.c | ||
| +++ src/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.8.8.1. By combining all the individual C code files into this | |
| 3 | +** version 3.8.8.2. 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. |
| @@ -276,13 +276,13 @@ | ||
| 276 | 276 | ** |
| 277 | 277 | ** See also: [sqlite3_libversion()], |
| 278 | 278 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 279 | 279 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 280 | 280 | */ |
| 281 | -#define SQLITE_VERSION "3.8.8.1" | |
| 281 | +#define SQLITE_VERSION "3.8.8.2" | |
| 282 | 282 | #define SQLITE_VERSION_NUMBER 3008008 |
| 283 | -#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" | |
| 283 | +#define SQLITE_SOURCE_ID "2015-01-30 14:30:45 7757fc721220e136620a89c9d28247f28bbbc098" | |
| 284 | 284 | |
| 285 | 285 | /* |
| 286 | 286 | ** CAPI3REF: Run-Time Library Version Numbers |
| 287 | 287 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 288 | 288 | ** |
| @@ -19870,21 +19870,10 @@ | ||
| 19870 | 19870 | # define SQLITE_WIN32_VOLATILE |
| 19871 | 19871 | #else |
| 19872 | 19872 | # define SQLITE_WIN32_VOLATILE volatile |
| 19873 | 19873 | #endif |
| 19874 | 19874 | |
| 19875 | -/* | |
| 19876 | -** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() | |
| 19877 | -** functions are not available (e.g. those not using MSVC, Cygwin, etc). | |
| 19878 | -*/ | |
| 19879 | -#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ | |
| 19880 | - SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) | |
| 19881 | -# define SQLITE_OS_WIN_THREADS 1 | |
| 19882 | -#else | |
| 19883 | -# define SQLITE_OS_WIN_THREADS 0 | |
| 19884 | -#endif | |
| 19885 | - | |
| 19886 | 19875 | #endif /* _OS_WIN_H_ */ |
| 19887 | 19876 | |
| 19888 | 19877 | /************** End of os_win.h **********************************************/ |
| 19889 | 19878 | /************** Continuing where we left off in mutex_w32.c ******************/ |
| 19890 | 19879 | #endif |
| @@ -22444,11 +22433,11 @@ | ||
| 22444 | 22433 | #endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ |
| 22445 | 22434 | /******************************** End Unix Pthreads *************************/ |
| 22446 | 22435 | |
| 22447 | 22436 | |
| 22448 | 22437 | /********************************* Win32 Threads ****************************/ |
| 22449 | -#if SQLITE_OS_WIN_THREADS | |
| 22438 | +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_THREADSAFE>0 | |
| 22450 | 22439 | |
| 22451 | 22440 | #define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ |
| 22452 | 22441 | #include <process.h> |
| 22453 | 22442 | |
| 22454 | 22443 | /* A running thread */ |
| @@ -22537,11 +22526,11 @@ | ||
| 22537 | 22526 | if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; |
| 22538 | 22527 | sqlite3_free(p); |
| 22539 | 22528 | return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR; |
| 22540 | 22529 | } |
| 22541 | 22530 | |
| 22542 | -#endif /* SQLITE_OS_WIN_THREADS */ | |
| 22531 | +#endif /* SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT */ | |
| 22543 | 22532 | /******************************** End Win32 Threads *************************/ |
| 22544 | 22533 | |
| 22545 | 22534 | |
| 22546 | 22535 | /********************************* Single-Threaded **************************/ |
| 22547 | 22536 | #ifndef SQLITE_THREADS_IMPLEMENTED |
| @@ -50206,11 +50195,11 @@ | ||
| 50206 | 50195 | int (*xBusy)(void*), /* Function to call when busy */ |
| 50207 | 50196 | void *pBusyArg, /* Context argument for xBusyHandler */ |
| 50208 | 50197 | int sync_flags, /* Flags for OsSync() (or 0) */ |
| 50209 | 50198 | u8 *zBuf /* Temporary buffer to use */ |
| 50210 | 50199 | ){ |
| 50211 | - int rc; /* Return code */ | |
| 50200 | + int rc = SQLITE_OK; /* Return code */ | |
| 50212 | 50201 | int szPage; /* Database page-size */ |
| 50213 | 50202 | WalIterator *pIter = 0; /* Wal iterator context */ |
| 50214 | 50203 | u32 iDbpage = 0; /* Next database page to write */ |
| 50215 | 50204 | u32 iFrame = 0; /* Wal frame containing data for iDbpage */ |
| 50216 | 50205 | u32 mxSafeFrame; /* Max frame that can be backfilled */ |
| @@ -50220,108 +50209,111 @@ | ||
| 50220 | 50209 | |
| 50221 | 50210 | szPage = walPagesize(pWal); |
| 50222 | 50211 | testcase( szPage<=32768 ); |
| 50223 | 50212 | testcase( szPage>=65536 ); |
| 50224 | 50213 | pInfo = walCkptInfo(pWal); |
| 50225 | - if( pInfo->nBackfill>=pWal->hdr.mxFrame ) return SQLITE_OK; | |
| 50226 | - | |
| 50227 | - /* Allocate the iterator */ | |
| 50228 | - rc = walIteratorInit(pWal, &pIter); | |
| 50229 | - if( rc!=SQLITE_OK ){ | |
| 50230 | - return rc; | |
| 50231 | - } | |
| 50232 | - assert( pIter ); | |
| 50233 | - | |
| 50234 | - /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked | |
| 50235 | - ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ | |
| 50236 | - assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); | |
| 50237 | - | |
| 50238 | - /* Compute in mxSafeFrame the index of the last frame of the WAL that is | |
| 50239 | - ** safe to write into the database. Frames beyond mxSafeFrame might | |
| 50240 | - ** overwrite database pages that are in use by active readers and thus | |
| 50241 | - ** cannot be backfilled from the WAL. | |
| 50242 | - */ | |
| 50243 | - mxSafeFrame = pWal->hdr.mxFrame; | |
| 50244 | - mxPage = pWal->hdr.nPage; | |
| 50245 | - for(i=1; i<WAL_NREADER; i++){ | |
| 50246 | - u32 y = pInfo->aReadMark[i]; | |
| 50247 | - if( mxSafeFrame>y ){ | |
| 50248 | - assert( y<=pWal->hdr.mxFrame ); | |
| 50249 | - rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); | |
| 50250 | - if( rc==SQLITE_OK ){ | |
| 50251 | - pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); | |
| 50252 | - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 50253 | - }else if( rc==SQLITE_BUSY ){ | |
| 50254 | - mxSafeFrame = y; | |
| 50255 | - xBusy = 0; | |
| 50256 | - }else{ | |
| 50257 | - goto walcheckpoint_out; | |
| 50258 | - } | |
| 50259 | - } | |
| 50260 | - } | |
| 50261 | - | |
| 50262 | - if( pInfo->nBackfill<mxSafeFrame | |
| 50263 | - && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0), 1))==SQLITE_OK | |
| 50264 | - ){ | |
| 50265 | - i64 nSize; /* Current size of database file */ | |
| 50266 | - u32 nBackfill = pInfo->nBackfill; | |
| 50267 | - | |
| 50268 | - /* Sync the WAL to disk */ | |
| 50269 | - if( sync_flags ){ | |
| 50270 | - rc = sqlite3OsSync(pWal->pWalFd, sync_flags); | |
| 50271 | - } | |
| 50272 | - | |
| 50273 | - /* If the database may grow as a result of this checkpoint, hint | |
| 50274 | - ** about the eventual size of the db file to the VFS layer. | |
| 50275 | - */ | |
| 50276 | - if( rc==SQLITE_OK ){ | |
| 50277 | - i64 nReq = ((i64)mxPage * szPage); | |
| 50278 | - rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); | |
| 50279 | - if( rc==SQLITE_OK && nSize<nReq ){ | |
| 50280 | - sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); | |
| 50281 | - } | |
| 50282 | - } | |
| 50283 | - | |
| 50284 | - | |
| 50285 | - /* Iterate through the contents of the WAL, copying data to the db file. */ | |
| 50286 | - while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ | |
| 50287 | - i64 iOffset; | |
| 50288 | - assert( walFramePgno(pWal, iFrame)==iDbpage ); | |
| 50289 | - if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ) continue; | |
| 50290 | - iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; | |
| 50291 | - /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ | |
| 50292 | - rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); | |
| 50293 | - if( rc!=SQLITE_OK ) break; | |
| 50294 | - iOffset = (iDbpage-1)*(i64)szPage; | |
| 50295 | - testcase( IS_BIG_INT(iOffset) ); | |
| 50296 | - rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); | |
| 50297 | - if( rc!=SQLITE_OK ) break; | |
| 50298 | - } | |
| 50299 | - | |
| 50300 | - /* If work was actually accomplished... */ | |
| 50301 | - if( rc==SQLITE_OK ){ | |
| 50302 | - if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ | |
| 50303 | - i64 szDb = pWal->hdr.nPage*(i64)szPage; | |
| 50304 | - testcase( IS_BIG_INT(szDb) ); | |
| 50305 | - rc = sqlite3OsTruncate(pWal->pDbFd, szDb); | |
| 50306 | - if( rc==SQLITE_OK && sync_flags ){ | |
| 50307 | - rc = sqlite3OsSync(pWal->pDbFd, sync_flags); | |
| 50308 | - } | |
| 50309 | - } | |
| 50310 | - if( rc==SQLITE_OK ){ | |
| 50311 | - pInfo->nBackfill = mxSafeFrame; | |
| 50312 | - } | |
| 50313 | - } | |
| 50314 | - | |
| 50315 | - /* Release the reader lock held while backfilling */ | |
| 50316 | - walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); | |
| 50317 | - } | |
| 50318 | - | |
| 50319 | - if( rc==SQLITE_BUSY ){ | |
| 50320 | - /* Reset the return code so as not to report a checkpoint failure | |
| 50321 | - ** just because there are active readers. */ | |
| 50322 | - rc = SQLITE_OK; | |
| 50214 | + if( pInfo->nBackfill<pWal->hdr.mxFrame ){ | |
| 50215 | + | |
| 50216 | + /* Allocate the iterator */ | |
| 50217 | + rc = walIteratorInit(pWal, &pIter); | |
| 50218 | + if( rc!=SQLITE_OK ){ | |
| 50219 | + return rc; | |
| 50220 | + } | |
| 50221 | + assert( pIter ); | |
| 50222 | + | |
| 50223 | + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked | |
| 50224 | + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ | |
| 50225 | + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); | |
| 50226 | + | |
| 50227 | + /* Compute in mxSafeFrame the index of the last frame of the WAL that is | |
| 50228 | + ** safe to write into the database. Frames beyond mxSafeFrame might | |
| 50229 | + ** overwrite database pages that are in use by active readers and thus | |
| 50230 | + ** cannot be backfilled from the WAL. | |
| 50231 | + */ | |
| 50232 | + mxSafeFrame = pWal->hdr.mxFrame; | |
| 50233 | + mxPage = pWal->hdr.nPage; | |
| 50234 | + for(i=1; i<WAL_NREADER; i++){ | |
| 50235 | + u32 y = pInfo->aReadMark[i]; | |
| 50236 | + if( mxSafeFrame>y ){ | |
| 50237 | + assert( y<=pWal->hdr.mxFrame ); | |
| 50238 | + rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); | |
| 50239 | + if( rc==SQLITE_OK ){ | |
| 50240 | + pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); | |
| 50241 | + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); | |
| 50242 | + }else if( rc==SQLITE_BUSY ){ | |
| 50243 | + mxSafeFrame = y; | |
| 50244 | + xBusy = 0; | |
| 50245 | + }else{ | |
| 50246 | + goto walcheckpoint_out; | |
| 50247 | + } | |
| 50248 | + } | |
| 50249 | + } | |
| 50250 | + | |
| 50251 | + if( pInfo->nBackfill<mxSafeFrame | |
| 50252 | + && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK | |
| 50253 | + ){ | |
| 50254 | + i64 nSize; /* Current size of database file */ | |
| 50255 | + u32 nBackfill = pInfo->nBackfill; | |
| 50256 | + | |
| 50257 | + /* Sync the WAL to disk */ | |
| 50258 | + if( sync_flags ){ | |
| 50259 | + rc = sqlite3OsSync(pWal->pWalFd, sync_flags); | |
| 50260 | + } | |
| 50261 | + | |
| 50262 | + /* If the database may grow as a result of this checkpoint, hint | |
| 50263 | + ** about the eventual size of the db file to the VFS layer. | |
| 50264 | + */ | |
| 50265 | + if( rc==SQLITE_OK ){ | |
| 50266 | + i64 nReq = ((i64)mxPage * szPage); | |
| 50267 | + rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); | |
| 50268 | + if( rc==SQLITE_OK && nSize<nReq ){ | |
| 50269 | + sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); | |
| 50270 | + } | |
| 50271 | + } | |
| 50272 | + | |
| 50273 | + | |
| 50274 | + /* Iterate through the contents of the WAL, copying data to the db file */ | |
| 50275 | + while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ | |
| 50276 | + i64 iOffset; | |
| 50277 | + assert( walFramePgno(pWal, iFrame)==iDbpage ); | |
| 50278 | + if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ | |
| 50279 | + continue; | |
| 50280 | + } | |
| 50281 | + iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; | |
| 50282 | + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ | |
| 50283 | + rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); | |
| 50284 | + if( rc!=SQLITE_OK ) break; | |
| 50285 | + iOffset = (iDbpage-1)*(i64)szPage; | |
| 50286 | + testcase( IS_BIG_INT(iOffset) ); | |
| 50287 | + rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); | |
| 50288 | + if( rc!=SQLITE_OK ) break; | |
| 50289 | + } | |
| 50290 | + | |
| 50291 | + /* If work was actually accomplished... */ | |
| 50292 | + if( rc==SQLITE_OK ){ | |
| 50293 | + if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ | |
| 50294 | + i64 szDb = pWal->hdr.nPage*(i64)szPage; | |
| 50295 | + testcase( IS_BIG_INT(szDb) ); | |
| 50296 | + rc = sqlite3OsTruncate(pWal->pDbFd, szDb); | |
| 50297 | + if( rc==SQLITE_OK && sync_flags ){ | |
| 50298 | + rc = sqlite3OsSync(pWal->pDbFd, sync_flags); | |
| 50299 | + } | |
| 50300 | + } | |
| 50301 | + if( rc==SQLITE_OK ){ | |
| 50302 | + pInfo->nBackfill = mxSafeFrame; | |
| 50303 | + } | |
| 50304 | + } | |
| 50305 | + | |
| 50306 | + /* Release the reader lock held while backfilling */ | |
| 50307 | + walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); | |
| 50308 | + } | |
| 50309 | + | |
| 50310 | + if( rc==SQLITE_BUSY ){ | |
| 50311 | + /* Reset the return code so as not to report a checkpoint failure | |
| 50312 | + ** just because there are active readers. */ | |
| 50313 | + rc = SQLITE_OK; | |
| 50314 | + } | |
| 50323 | 50315 | } |
| 50324 | 50316 | |
| 50325 | 50317 | /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the |
| 50326 | 50318 | ** entire wal file has been copied into the database file, then block |
| 50327 | 50319 | ** until all readers have finished using the wal file. This ensures that |
| @@ -50332,11 +50324,11 @@ | ||
| 50332 | 50324 | if( pInfo->nBackfill<pWal->hdr.mxFrame ){ |
| 50333 | 50325 | rc = SQLITE_BUSY; |
| 50334 | 50326 | }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ |
| 50335 | 50327 | u32 salt1; |
| 50336 | 50328 | sqlite3_randomness(4, &salt1); |
| 50337 | - assert( mxSafeFrame==pWal->hdr.mxFrame ); | |
| 50329 | + assert( pInfo->nBackfill==pWal->hdr.mxFrame ); | |
| 50338 | 50330 | rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); |
| 50339 | 50331 | if( rc==SQLITE_OK ){ |
| 50340 | 50332 | if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ |
| 50341 | 50333 | /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as |
| 50342 | 50334 | ** SQLITE_CHECKPOINT_RESTART with the addition that it also |
| @@ -128378,10 +128370,11 @@ | ||
| 128378 | 128370 | } |
| 128379 | 128371 | if( iDb<0 ){ |
| 128380 | 128372 | rc = SQLITE_ERROR; |
| 128381 | 128373 | sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb); |
| 128382 | 128374 | }else{ |
| 128375 | + db->busyHandler.nBusy = 0; | |
| 128383 | 128376 | rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt); |
| 128384 | 128377 | sqlite3Error(db, rc); |
| 128385 | 128378 | } |
| 128386 | 128379 | rc = sqlite3ApiExit(db, rc); |
| 128387 | 128380 | sqlite3_mutex_leave(db->mutex); |
| 128388 | 128381 |
| --- src/sqlite3.c | |
| +++ src/sqlite3.c | |
| @@ -1,8 +1,8 @@ | |
| 1 | /****************************************************************************** |
| 2 | ** This file is an amalgamation of many separate C source files from SQLite |
| 3 | ** version 3.8.8.1. 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. |
| @@ -276,13 +276,13 @@ | |
| 276 | ** |
| 277 | ** See also: [sqlite3_libversion()], |
| 278 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 279 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 280 | */ |
| 281 | #define SQLITE_VERSION "3.8.8.1" |
| 282 | #define SQLITE_VERSION_NUMBER 3008008 |
| 283 | #define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" |
| 284 | |
| 285 | /* |
| 286 | ** CAPI3REF: Run-Time Library Version Numbers |
| 287 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 288 | ** |
| @@ -19870,21 +19870,10 @@ | |
| 19870 | # define SQLITE_WIN32_VOLATILE |
| 19871 | #else |
| 19872 | # define SQLITE_WIN32_VOLATILE volatile |
| 19873 | #endif |
| 19874 | |
| 19875 | /* |
| 19876 | ** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() |
| 19877 | ** functions are not available (e.g. those not using MSVC, Cygwin, etc). |
| 19878 | */ |
| 19879 | #if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ |
| 19880 | SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) |
| 19881 | # define SQLITE_OS_WIN_THREADS 1 |
| 19882 | #else |
| 19883 | # define SQLITE_OS_WIN_THREADS 0 |
| 19884 | #endif |
| 19885 | |
| 19886 | #endif /* _OS_WIN_H_ */ |
| 19887 | |
| 19888 | /************** End of os_win.h **********************************************/ |
| 19889 | /************** Continuing where we left off in mutex_w32.c ******************/ |
| 19890 | #endif |
| @@ -22444,11 +22433,11 @@ | |
| 22444 | #endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ |
| 22445 | /******************************** End Unix Pthreads *************************/ |
| 22446 | |
| 22447 | |
| 22448 | /********************************* Win32 Threads ****************************/ |
| 22449 | #if SQLITE_OS_WIN_THREADS |
| 22450 | |
| 22451 | #define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ |
| 22452 | #include <process.h> |
| 22453 | |
| 22454 | /* A running thread */ |
| @@ -22537,11 +22526,11 @@ | |
| 22537 | if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; |
| 22538 | sqlite3_free(p); |
| 22539 | return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR; |
| 22540 | } |
| 22541 | |
| 22542 | #endif /* SQLITE_OS_WIN_THREADS */ |
| 22543 | /******************************** End Win32 Threads *************************/ |
| 22544 | |
| 22545 | |
| 22546 | /********************************* Single-Threaded **************************/ |
| 22547 | #ifndef SQLITE_THREADS_IMPLEMENTED |
| @@ -50206,11 +50195,11 @@ | |
| 50206 | int (*xBusy)(void*), /* Function to call when busy */ |
| 50207 | void *pBusyArg, /* Context argument for xBusyHandler */ |
| 50208 | int sync_flags, /* Flags for OsSync() (or 0) */ |
| 50209 | u8 *zBuf /* Temporary buffer to use */ |
| 50210 | ){ |
| 50211 | int rc; /* Return code */ |
| 50212 | int szPage; /* Database page-size */ |
| 50213 | WalIterator *pIter = 0; /* Wal iterator context */ |
| 50214 | u32 iDbpage = 0; /* Next database page to write */ |
| 50215 | u32 iFrame = 0; /* Wal frame containing data for iDbpage */ |
| 50216 | u32 mxSafeFrame; /* Max frame that can be backfilled */ |
| @@ -50220,108 +50209,111 @@ | |
| 50220 | |
| 50221 | szPage = walPagesize(pWal); |
| 50222 | testcase( szPage<=32768 ); |
| 50223 | testcase( szPage>=65536 ); |
| 50224 | pInfo = walCkptInfo(pWal); |
| 50225 | if( pInfo->nBackfill>=pWal->hdr.mxFrame ) return SQLITE_OK; |
| 50226 | |
| 50227 | /* Allocate the iterator */ |
| 50228 | rc = walIteratorInit(pWal, &pIter); |
| 50229 | if( rc!=SQLITE_OK ){ |
| 50230 | return rc; |
| 50231 | } |
| 50232 | assert( pIter ); |
| 50233 | |
| 50234 | /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked |
| 50235 | ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ |
| 50236 | assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); |
| 50237 | |
| 50238 | /* Compute in mxSafeFrame the index of the last frame of the WAL that is |
| 50239 | ** safe to write into the database. Frames beyond mxSafeFrame might |
| 50240 | ** overwrite database pages that are in use by active readers and thus |
| 50241 | ** cannot be backfilled from the WAL. |
| 50242 | */ |
| 50243 | mxSafeFrame = pWal->hdr.mxFrame; |
| 50244 | mxPage = pWal->hdr.nPage; |
| 50245 | for(i=1; i<WAL_NREADER; i++){ |
| 50246 | u32 y = pInfo->aReadMark[i]; |
| 50247 | if( mxSafeFrame>y ){ |
| 50248 | assert( y<=pWal->hdr.mxFrame ); |
| 50249 | rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); |
| 50250 | if( rc==SQLITE_OK ){ |
| 50251 | pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); |
| 50252 | walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 50253 | }else if( rc==SQLITE_BUSY ){ |
| 50254 | mxSafeFrame = y; |
| 50255 | xBusy = 0; |
| 50256 | }else{ |
| 50257 | goto walcheckpoint_out; |
| 50258 | } |
| 50259 | } |
| 50260 | } |
| 50261 | |
| 50262 | if( pInfo->nBackfill<mxSafeFrame |
| 50263 | && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0), 1))==SQLITE_OK |
| 50264 | ){ |
| 50265 | i64 nSize; /* Current size of database file */ |
| 50266 | u32 nBackfill = pInfo->nBackfill; |
| 50267 | |
| 50268 | /* Sync the WAL to disk */ |
| 50269 | if( sync_flags ){ |
| 50270 | rc = sqlite3OsSync(pWal->pWalFd, sync_flags); |
| 50271 | } |
| 50272 | |
| 50273 | /* If the database may grow as a result of this checkpoint, hint |
| 50274 | ** about the eventual size of the db file to the VFS layer. |
| 50275 | */ |
| 50276 | if( rc==SQLITE_OK ){ |
| 50277 | i64 nReq = ((i64)mxPage * szPage); |
| 50278 | rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); |
| 50279 | if( rc==SQLITE_OK && nSize<nReq ){ |
| 50280 | sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); |
| 50281 | } |
| 50282 | } |
| 50283 | |
| 50284 | |
| 50285 | /* Iterate through the contents of the WAL, copying data to the db file. */ |
| 50286 | while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ |
| 50287 | i64 iOffset; |
| 50288 | assert( walFramePgno(pWal, iFrame)==iDbpage ); |
| 50289 | if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ) continue; |
| 50290 | iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; |
| 50291 | /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ |
| 50292 | rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); |
| 50293 | if( rc!=SQLITE_OK ) break; |
| 50294 | iOffset = (iDbpage-1)*(i64)szPage; |
| 50295 | testcase( IS_BIG_INT(iOffset) ); |
| 50296 | rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); |
| 50297 | if( rc!=SQLITE_OK ) break; |
| 50298 | } |
| 50299 | |
| 50300 | /* If work was actually accomplished... */ |
| 50301 | if( rc==SQLITE_OK ){ |
| 50302 | if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ |
| 50303 | i64 szDb = pWal->hdr.nPage*(i64)szPage; |
| 50304 | testcase( IS_BIG_INT(szDb) ); |
| 50305 | rc = sqlite3OsTruncate(pWal->pDbFd, szDb); |
| 50306 | if( rc==SQLITE_OK && sync_flags ){ |
| 50307 | rc = sqlite3OsSync(pWal->pDbFd, sync_flags); |
| 50308 | } |
| 50309 | } |
| 50310 | if( rc==SQLITE_OK ){ |
| 50311 | pInfo->nBackfill = mxSafeFrame; |
| 50312 | } |
| 50313 | } |
| 50314 | |
| 50315 | /* Release the reader lock held while backfilling */ |
| 50316 | walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); |
| 50317 | } |
| 50318 | |
| 50319 | if( rc==SQLITE_BUSY ){ |
| 50320 | /* Reset the return code so as not to report a checkpoint failure |
| 50321 | ** just because there are active readers. */ |
| 50322 | rc = SQLITE_OK; |
| 50323 | } |
| 50324 | |
| 50325 | /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the |
| 50326 | ** entire wal file has been copied into the database file, then block |
| 50327 | ** until all readers have finished using the wal file. This ensures that |
| @@ -50332,11 +50324,11 @@ | |
| 50332 | if( pInfo->nBackfill<pWal->hdr.mxFrame ){ |
| 50333 | rc = SQLITE_BUSY; |
| 50334 | }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ |
| 50335 | u32 salt1; |
| 50336 | sqlite3_randomness(4, &salt1); |
| 50337 | assert( mxSafeFrame==pWal->hdr.mxFrame ); |
| 50338 | rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); |
| 50339 | if( rc==SQLITE_OK ){ |
| 50340 | if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ |
| 50341 | /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as |
| 50342 | ** SQLITE_CHECKPOINT_RESTART with the addition that it also |
| @@ -128378,10 +128370,11 @@ | |
| 128378 | } |
| 128379 | if( iDb<0 ){ |
| 128380 | rc = SQLITE_ERROR; |
| 128381 | sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb); |
| 128382 | }else{ |
| 128383 | rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt); |
| 128384 | sqlite3Error(db, rc); |
| 128385 | } |
| 128386 | rc = sqlite3ApiExit(db, rc); |
| 128387 | sqlite3_mutex_leave(db->mutex); |
| 128388 |
| --- src/sqlite3.c | |
| +++ src/sqlite3.c | |
| @@ -1,8 +1,8 @@ | |
| 1 | /****************************************************************************** |
| 2 | ** This file is an amalgamation of many separate C source files from SQLite |
| 3 | ** version 3.8.8.2. 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. |
| @@ -276,13 +276,13 @@ | |
| 276 | ** |
| 277 | ** See also: [sqlite3_libversion()], |
| 278 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 279 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 280 | */ |
| 281 | #define SQLITE_VERSION "3.8.8.2" |
| 282 | #define SQLITE_VERSION_NUMBER 3008008 |
| 283 | #define SQLITE_SOURCE_ID "2015-01-30 14:30:45 7757fc721220e136620a89c9d28247f28bbbc098" |
| 284 | |
| 285 | /* |
| 286 | ** CAPI3REF: Run-Time Library Version Numbers |
| 287 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 288 | ** |
| @@ -19870,21 +19870,10 @@ | |
| 19870 | # define SQLITE_WIN32_VOLATILE |
| 19871 | #else |
| 19872 | # define SQLITE_WIN32_VOLATILE volatile |
| 19873 | #endif |
| 19874 | |
| 19875 | #endif /* _OS_WIN_H_ */ |
| 19876 | |
| 19877 | /************** End of os_win.h **********************************************/ |
| 19878 | /************** Continuing where we left off in mutex_w32.c ******************/ |
| 19879 | #endif |
| @@ -22444,11 +22433,11 @@ | |
| 22433 | #endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ |
| 22434 | /******************************** End Unix Pthreads *************************/ |
| 22435 | |
| 22436 | |
| 22437 | /********************************* Win32 Threads ****************************/ |
| 22438 | #if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_THREADSAFE>0 |
| 22439 | |
| 22440 | #define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */ |
| 22441 | #include <process.h> |
| 22442 | |
| 22443 | /* A running thread */ |
| @@ -22537,11 +22526,11 @@ | |
| 22526 | if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; |
| 22527 | sqlite3_free(p); |
| 22528 | return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR; |
| 22529 | } |
| 22530 | |
| 22531 | #endif /* SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT */ |
| 22532 | /******************************** End Win32 Threads *************************/ |
| 22533 | |
| 22534 | |
| 22535 | /********************************* Single-Threaded **************************/ |
| 22536 | #ifndef SQLITE_THREADS_IMPLEMENTED |
| @@ -50206,11 +50195,11 @@ | |
| 50195 | int (*xBusy)(void*), /* Function to call when busy */ |
| 50196 | void *pBusyArg, /* Context argument for xBusyHandler */ |
| 50197 | int sync_flags, /* Flags for OsSync() (or 0) */ |
| 50198 | u8 *zBuf /* Temporary buffer to use */ |
| 50199 | ){ |
| 50200 | int rc = SQLITE_OK; /* Return code */ |
| 50201 | int szPage; /* Database page-size */ |
| 50202 | WalIterator *pIter = 0; /* Wal iterator context */ |
| 50203 | u32 iDbpage = 0; /* Next database page to write */ |
| 50204 | u32 iFrame = 0; /* Wal frame containing data for iDbpage */ |
| 50205 | u32 mxSafeFrame; /* Max frame that can be backfilled */ |
| @@ -50220,108 +50209,111 @@ | |
| 50209 | |
| 50210 | szPage = walPagesize(pWal); |
| 50211 | testcase( szPage<=32768 ); |
| 50212 | testcase( szPage>=65536 ); |
| 50213 | pInfo = walCkptInfo(pWal); |
| 50214 | if( pInfo->nBackfill<pWal->hdr.mxFrame ){ |
| 50215 | |
| 50216 | /* Allocate the iterator */ |
| 50217 | rc = walIteratorInit(pWal, &pIter); |
| 50218 | if( rc!=SQLITE_OK ){ |
| 50219 | return rc; |
| 50220 | } |
| 50221 | assert( pIter ); |
| 50222 | |
| 50223 | /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked |
| 50224 | ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ |
| 50225 | assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); |
| 50226 | |
| 50227 | /* Compute in mxSafeFrame the index of the last frame of the WAL that is |
| 50228 | ** safe to write into the database. Frames beyond mxSafeFrame might |
| 50229 | ** overwrite database pages that are in use by active readers and thus |
| 50230 | ** cannot be backfilled from the WAL. |
| 50231 | */ |
| 50232 | mxSafeFrame = pWal->hdr.mxFrame; |
| 50233 | mxPage = pWal->hdr.nPage; |
| 50234 | for(i=1; i<WAL_NREADER; i++){ |
| 50235 | u32 y = pInfo->aReadMark[i]; |
| 50236 | if( mxSafeFrame>y ){ |
| 50237 | assert( y<=pWal->hdr.mxFrame ); |
| 50238 | rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); |
| 50239 | if( rc==SQLITE_OK ){ |
| 50240 | pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); |
| 50241 | walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); |
| 50242 | }else if( rc==SQLITE_BUSY ){ |
| 50243 | mxSafeFrame = y; |
| 50244 | xBusy = 0; |
| 50245 | }else{ |
| 50246 | goto walcheckpoint_out; |
| 50247 | } |
| 50248 | } |
| 50249 | } |
| 50250 | |
| 50251 | if( pInfo->nBackfill<mxSafeFrame |
| 50252 | && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK |
| 50253 | ){ |
| 50254 | i64 nSize; /* Current size of database file */ |
| 50255 | u32 nBackfill = pInfo->nBackfill; |
| 50256 | |
| 50257 | /* Sync the WAL to disk */ |
| 50258 | if( sync_flags ){ |
| 50259 | rc = sqlite3OsSync(pWal->pWalFd, sync_flags); |
| 50260 | } |
| 50261 | |
| 50262 | /* If the database may grow as a result of this checkpoint, hint |
| 50263 | ** about the eventual size of the db file to the VFS layer. |
| 50264 | */ |
| 50265 | if( rc==SQLITE_OK ){ |
| 50266 | i64 nReq = ((i64)mxPage * szPage); |
| 50267 | rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); |
| 50268 | if( rc==SQLITE_OK && nSize<nReq ){ |
| 50269 | sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); |
| 50270 | } |
| 50271 | } |
| 50272 | |
| 50273 | |
| 50274 | /* Iterate through the contents of the WAL, copying data to the db file */ |
| 50275 | while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ |
| 50276 | i64 iOffset; |
| 50277 | assert( walFramePgno(pWal, iFrame)==iDbpage ); |
| 50278 | if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ |
| 50279 | continue; |
| 50280 | } |
| 50281 | iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; |
| 50282 | /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ |
| 50283 | rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); |
| 50284 | if( rc!=SQLITE_OK ) break; |
| 50285 | iOffset = (iDbpage-1)*(i64)szPage; |
| 50286 | testcase( IS_BIG_INT(iOffset) ); |
| 50287 | rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); |
| 50288 | if( rc!=SQLITE_OK ) break; |
| 50289 | } |
| 50290 | |
| 50291 | /* If work was actually accomplished... */ |
| 50292 | if( rc==SQLITE_OK ){ |
| 50293 | if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ |
| 50294 | i64 szDb = pWal->hdr.nPage*(i64)szPage; |
| 50295 | testcase( IS_BIG_INT(szDb) ); |
| 50296 | rc = sqlite3OsTruncate(pWal->pDbFd, szDb); |
| 50297 | if( rc==SQLITE_OK && sync_flags ){ |
| 50298 | rc = sqlite3OsSync(pWal->pDbFd, sync_flags); |
| 50299 | } |
| 50300 | } |
| 50301 | if( rc==SQLITE_OK ){ |
| 50302 | pInfo->nBackfill = mxSafeFrame; |
| 50303 | } |
| 50304 | } |
| 50305 | |
| 50306 | /* Release the reader lock held while backfilling */ |
| 50307 | walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1); |
| 50308 | } |
| 50309 | |
| 50310 | if( rc==SQLITE_BUSY ){ |
| 50311 | /* Reset the return code so as not to report a checkpoint failure |
| 50312 | ** just because there are active readers. */ |
| 50313 | rc = SQLITE_OK; |
| 50314 | } |
| 50315 | } |
| 50316 | |
| 50317 | /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the |
| 50318 | ** entire wal file has been copied into the database file, then block |
| 50319 | ** until all readers have finished using the wal file. This ensures that |
| @@ -50332,11 +50324,11 @@ | |
| 50324 | if( pInfo->nBackfill<pWal->hdr.mxFrame ){ |
| 50325 | rc = SQLITE_BUSY; |
| 50326 | }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ |
| 50327 | u32 salt1; |
| 50328 | sqlite3_randomness(4, &salt1); |
| 50329 | assert( pInfo->nBackfill==pWal->hdr.mxFrame ); |
| 50330 | rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); |
| 50331 | if( rc==SQLITE_OK ){ |
| 50332 | if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ |
| 50333 | /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as |
| 50334 | ** SQLITE_CHECKPOINT_RESTART with the addition that it also |
| @@ -128378,10 +128370,11 @@ | |
| 128370 | } |
| 128371 | if( iDb<0 ){ |
| 128372 | rc = SQLITE_ERROR; |
| 128373 | sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb); |
| 128374 | }else{ |
| 128375 | db->busyHandler.nBusy = 0; |
| 128376 | rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt); |
| 128377 | sqlite3Error(db, rc); |
| 128378 | } |
| 128379 | rc = sqlite3ApiExit(db, rc); |
| 128380 | sqlite3_mutex_leave(db->mutex); |
| 128381 |
+2
-2
| --- src/sqlite3.h | ||
| +++ src/sqlite3.h | ||
| @@ -105,13 +105,13 @@ | ||
| 105 | 105 | ** |
| 106 | 106 | ** See also: [sqlite3_libversion()], |
| 107 | 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | 109 | */ |
| 110 | -#define SQLITE_VERSION "3.8.8.1" | |
| 110 | +#define SQLITE_VERSION "3.8.8.2" | |
| 111 | 111 | #define SQLITE_VERSION_NUMBER 3008008 |
| 112 | -#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" | |
| 112 | +#define SQLITE_SOURCE_ID "2015-01-30 14:30:45 7757fc721220e136620a89c9d28247f28bbbc098" | |
| 113 | 113 | |
| 114 | 114 | /* |
| 115 | 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | 117 | ** |
| 118 | 118 |
| --- src/sqlite3.h | |
| +++ src/sqlite3.h | |
| @@ -105,13 +105,13 @@ | |
| 105 | ** |
| 106 | ** See also: [sqlite3_libversion()], |
| 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | */ |
| 110 | #define SQLITE_VERSION "3.8.8.1" |
| 111 | #define SQLITE_VERSION_NUMBER 3008008 |
| 112 | #define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" |
| 113 | |
| 114 | /* |
| 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | ** |
| 118 |
| --- src/sqlite3.h | |
| +++ src/sqlite3.h | |
| @@ -105,13 +105,13 @@ | |
| 105 | ** |
| 106 | ** See also: [sqlite3_libversion()], |
| 107 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 108 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 109 | */ |
| 110 | #define SQLITE_VERSION "3.8.8.2" |
| 111 | #define SQLITE_VERSION_NUMBER 3008008 |
| 112 | #define SQLITE_SOURCE_ID "2015-01-30 14:30:45 7757fc721220e136620a89c9d28247f28bbbc098" |
| 113 | |
| 114 | /* |
| 115 | ** CAPI3REF: Run-Time Library Version Numbers |
| 116 | ** KEYWORDS: sqlite3_version, sqlite3_sourceid |
| 117 | ** |
| 118 |
+39
-2
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -1125,10 +1125,23 @@ | ||
| 1125 | 1125 | }, |
| 1126 | 1126 | { "th.sort.desc:after", |
| 1127 | 1127 | "Descending sort column marker", |
| 1128 | 1128 | @ content: '\2191'; |
| 1129 | 1129 | }, |
| 1130 | + { "span.snippet>mark", | |
| 1131 | + "Search markup", | |
| 1132 | + @ background-color: inherit; | |
| 1133 | + @ font-weight: bold; | |
| 1134 | + }, | |
| 1135 | + { "div.searchForm", | |
| 1136 | + "Container for the search terms entry box", | |
| 1137 | + @ text-align: center; | |
| 1138 | + }, | |
| 1139 | + { "p.searchEmpty", | |
| 1140 | + "Message explaining that there are no search results", | |
| 1141 | + @ font-style: italic; | |
| 1142 | + }, | |
| 1130 | 1143 | { 0, |
| 1131 | 1144 | 0, |
| 1132 | 1145 | 0 |
| 1133 | 1146 | } |
| 1134 | 1147 | }; |
| @@ -1149,23 +1162,47 @@ | ||
| 1149 | 1162 | ); |
| 1150 | 1163 | } |
| 1151 | 1164 | } |
| 1152 | 1165 | } |
| 1153 | 1166 | |
| 1167 | +/* | |
| 1168 | +** Search string zHaystack for zNeedle. zNeedle must be an isolated | |
| 1169 | +** word with space or punctuation on either size. | |
| 1170 | +** | |
| 1171 | +** Return true if found. Return false if not found | |
| 1172 | +*/ | |
| 1173 | +static int containsString(const char *zHaystack, const char *zNeedle){ | |
| 1174 | + char *z; | |
| 1175 | + int n; | |
| 1176 | + | |
| 1177 | + while( zHaystack[0] ){ | |
| 1178 | + z = strstr(zHaystack, zNeedle); | |
| 1179 | + if( z==0 ) return 0; | |
| 1180 | + n = (int)strlen(zNeedle); | |
| 1181 | + if( (z==zHaystack || !fossil_isalnum(z[-1])) && !fossil_isalnum(z[n]) ){ | |
| 1182 | + return 1; | |
| 1183 | + } | |
| 1184 | + zHaystack = z + n; | |
| 1185 | + } | |
| 1186 | + return 0; | |
| 1187 | +} | |
| 1188 | + | |
| 1189 | + | |
| 1154 | 1190 | /* |
| 1155 | 1191 | ** WEBPAGE: style.css |
| 1156 | 1192 | */ |
| 1157 | 1193 | void page_style_css(void){ |
| 1158 | 1194 | Blob css; |
| 1159 | 1195 | int i; |
| 1160 | 1196 | |
| 1161 | 1197 | cgi_set_content_type("text/css"); |
| 1162 | - blob_init(&css, db_get("css",(char*)builtin_text("skins/default/css.txt")), -1); | |
| 1198 | + blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1); | |
| 1163 | 1199 | |
| 1164 | 1200 | /* add special missing definitions */ |
| 1165 | 1201 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1166 | - if( strstr(blob_str(&css), cssDefaultList[i].elementClass)==0 ){ | |
| 1202 | + char *z = blob_str(&css); | |
| 1203 | + if( !containsString(z, cssDefaultList[i].elementClass) ){ | |
| 1167 | 1204 | blob_appendf(&css, "/* %s */\n%s {\n%s}\n", |
| 1168 | 1205 | cssDefaultList[i].comment, |
| 1169 | 1206 | cssDefaultList[i].elementClass, |
| 1170 | 1207 | cssDefaultList[i].value); |
| 1171 | 1208 | } |
| 1172 | 1209 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -1125,10 +1125,23 @@ | |
| 1125 | }, |
| 1126 | { "th.sort.desc:after", |
| 1127 | "Descending sort column marker", |
| 1128 | @ content: '\2191'; |
| 1129 | }, |
| 1130 | { 0, |
| 1131 | 0, |
| 1132 | 0 |
| 1133 | } |
| 1134 | }; |
| @@ -1149,23 +1162,47 @@ | |
| 1149 | ); |
| 1150 | } |
| 1151 | } |
| 1152 | } |
| 1153 | |
| 1154 | /* |
| 1155 | ** WEBPAGE: style.css |
| 1156 | */ |
| 1157 | void page_style_css(void){ |
| 1158 | Blob css; |
| 1159 | int i; |
| 1160 | |
| 1161 | cgi_set_content_type("text/css"); |
| 1162 | blob_init(&css, db_get("css",(char*)builtin_text("skins/default/css.txt")), -1); |
| 1163 | |
| 1164 | /* add special missing definitions */ |
| 1165 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1166 | if( strstr(blob_str(&css), cssDefaultList[i].elementClass)==0 ){ |
| 1167 | blob_appendf(&css, "/* %s */\n%s {\n%s}\n", |
| 1168 | cssDefaultList[i].comment, |
| 1169 | cssDefaultList[i].elementClass, |
| 1170 | cssDefaultList[i].value); |
| 1171 | } |
| 1172 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -1125,10 +1125,23 @@ | |
| 1125 | }, |
| 1126 | { "th.sort.desc:after", |
| 1127 | "Descending sort column marker", |
| 1128 | @ content: '\2191'; |
| 1129 | }, |
| 1130 | { "span.snippet>mark", |
| 1131 | "Search markup", |
| 1132 | @ background-color: inherit; |
| 1133 | @ font-weight: bold; |
| 1134 | }, |
| 1135 | { "div.searchForm", |
| 1136 | "Container for the search terms entry box", |
| 1137 | @ text-align: center; |
| 1138 | }, |
| 1139 | { "p.searchEmpty", |
| 1140 | "Message explaining that there are no search results", |
| 1141 | @ font-style: italic; |
| 1142 | }, |
| 1143 | { 0, |
| 1144 | 0, |
| 1145 | 0 |
| 1146 | } |
| 1147 | }; |
| @@ -1149,23 +1162,47 @@ | |
| 1162 | ); |
| 1163 | } |
| 1164 | } |
| 1165 | } |
| 1166 | |
| 1167 | /* |
| 1168 | ** Search string zHaystack for zNeedle. zNeedle must be an isolated |
| 1169 | ** word with space or punctuation on either size. |
| 1170 | ** |
| 1171 | ** Return true if found. Return false if not found |
| 1172 | */ |
| 1173 | static int containsString(const char *zHaystack, const char *zNeedle){ |
| 1174 | char *z; |
| 1175 | int n; |
| 1176 | |
| 1177 | while( zHaystack[0] ){ |
| 1178 | z = strstr(zHaystack, zNeedle); |
| 1179 | if( z==0 ) return 0; |
| 1180 | n = (int)strlen(zNeedle); |
| 1181 | if( (z==zHaystack || !fossil_isalnum(z[-1])) && !fossil_isalnum(z[n]) ){ |
| 1182 | return 1; |
| 1183 | } |
| 1184 | zHaystack = z + n; |
| 1185 | } |
| 1186 | return 0; |
| 1187 | } |
| 1188 | |
| 1189 | |
| 1190 | /* |
| 1191 | ** WEBPAGE: style.css |
| 1192 | */ |
| 1193 | void page_style_css(void){ |
| 1194 | Blob css; |
| 1195 | int i; |
| 1196 | |
| 1197 | cgi_set_content_type("text/css"); |
| 1198 | blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1); |
| 1199 | |
| 1200 | /* add special missing definitions */ |
| 1201 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1202 | char *z = blob_str(&css); |
| 1203 | if( !containsString(z, cssDefaultList[i].elementClass) ){ |
| 1204 | blob_appendf(&css, "/* %s */\n%s {\n%s}\n", |
| 1205 | cssDefaultList[i].comment, |
| 1206 | cssDefaultList[i].elementClass, |
| 1207 | cssDefaultList[i].value); |
| 1208 | } |
| 1209 |
+57
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -313,10 +313,11 @@ | ||
| 313 | 313 | |
| 314 | 314 | fossil_free(zTag); |
| 315 | 315 | getAllTicketFields(); |
| 316 | 316 | if( haveTicket==0 ) return; |
| 317 | 317 | tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid); |
| 318 | + search_doc_touch('t', tktid, 0); | |
| 318 | 319 | if( haveTicketChng ){ |
| 319 | 320 | db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid); |
| 320 | 321 | } |
| 321 | 322 | db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid); |
| 322 | 323 | tktid = 0; |
| @@ -691,10 +692,11 @@ | ||
| 691 | 692 | if( !g.perm.NewTkt ){ login_needed(); return; } |
| 692 | 693 | if( P("cancel") ){ |
| 693 | 694 | cgi_redirect("home"); |
| 694 | 695 | } |
| 695 | 696 | style_header("New Ticket"); |
| 697 | + ticket_standard_submenu(T_ALL_BUT(T_NEW)); | |
| 696 | 698 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); |
| 697 | 699 | ticket_init(); |
| 698 | 700 | initializeVariablesFromCGI(); |
| 699 | 701 | getAllTicketFields(); |
| 700 | 702 | initializeVariablesFromDb(); |
| @@ -1380,5 +1382,60 @@ | ||
| 1380 | 1382 | (eCmd==set?"set":"add"),zTktUuid); |
| 1381 | 1383 | } |
| 1382 | 1384 | } |
| 1383 | 1385 | } |
| 1384 | 1386 | } |
| 1387 | + | |
| 1388 | + | |
| 1389 | +#if INTERFACE | |
| 1390 | +/* Standard submenu items for wiki pages */ | |
| 1391 | +#define T_SRCH 0x00001 | |
| 1392 | +#define T_REPLIST 0x00002 | |
| 1393 | +#define T_NEW 0x00004 | |
| 1394 | +#define T_ALL 0x00007 | |
| 1395 | +#define T_ALL_BUT(x) (T_ALL&~(x)) | |
| 1396 | +#endif | |
| 1397 | + | |
| 1398 | +/* | |
| 1399 | +** Add some standard submenu elements for ticket screens. | |
| 1400 | +*/ | |
| 1401 | +void ticket_standard_submenu(unsigned int ok){ | |
| 1402 | + if( (ok & T_SRCH)!=0 && search_restrict(SRCH_TKT)!=0 ){ | |
| 1403 | + style_submenu_element("Search","Search","%R/tktsrch"); | |
| 1404 | + } | |
| 1405 | + if( (ok & T_REPLIST)!=0 ){ | |
| 1406 | + style_submenu_element("Reports","Reports","%R/reportlist"); | |
| 1407 | + } | |
| 1408 | + if( (ok & T_NEW)!=0 && g.perm.NewTkt ){ | |
| 1409 | + style_submenu_element("New","New","%R/tktnew"); | |
| 1410 | + } | |
| 1411 | +} | |
| 1412 | + | |
| 1413 | +/* | |
| 1414 | +** WEBPAGE: ticket | |
| 1415 | +** | |
| 1416 | +** This is intended to be the primary "Ticket" page. Render as | |
| 1417 | +** either ticket-search (if search is enabled) or as the | |
| 1418 | +** /reportlist page (if ticket search is disabled). | |
| 1419 | +*/ | |
| 1420 | +void tkt_home_page(void){ | |
| 1421 | + login_check_credentials(); | |
| 1422 | + if( search_restrict(SRCH_TKT)!=0 ){ | |
| 1423 | + tkt_srchpage(); | |
| 1424 | + }else{ | |
| 1425 | + view_list(); | |
| 1426 | + } | |
| 1427 | +} | |
| 1428 | + | |
| 1429 | +/* | |
| 1430 | +** WEBPAGE: tktsrch | |
| 1431 | +** Usage: /tktsrch?s=PATTERN | |
| 1432 | +** | |
| 1433 | +** Full-text search of all current tickets | |
| 1434 | +*/ | |
| 1435 | +void tkt_srchpage(void){ | |
| 1436 | + login_check_credentials(); | |
| 1437 | + style_header("Ticket Search"); | |
| 1438 | + ticket_standard_submenu(T_ALL_BUT(T_SRCH)); | |
| 1439 | + search_screen(SRCH_TKT, "tktsrch"); | |
| 1440 | + style_footer(); | |
| 1441 | +} | |
| 1385 | 1442 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -313,10 +313,11 @@ | |
| 313 | |
| 314 | fossil_free(zTag); |
| 315 | getAllTicketFields(); |
| 316 | if( haveTicket==0 ) return; |
| 317 | tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid); |
| 318 | if( haveTicketChng ){ |
| 319 | db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid); |
| 320 | } |
| 321 | db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid); |
| 322 | tktid = 0; |
| @@ -691,10 +692,11 @@ | |
| 691 | if( !g.perm.NewTkt ){ login_needed(); return; } |
| 692 | if( P("cancel") ){ |
| 693 | cgi_redirect("home"); |
| 694 | } |
| 695 | style_header("New Ticket"); |
| 696 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); |
| 697 | ticket_init(); |
| 698 | initializeVariablesFromCGI(); |
| 699 | getAllTicketFields(); |
| 700 | initializeVariablesFromDb(); |
| @@ -1380,5 +1382,60 @@ | |
| 1380 | (eCmd==set?"set":"add"),zTktUuid); |
| 1381 | } |
| 1382 | } |
| 1383 | } |
| 1384 | } |
| 1385 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -313,10 +313,11 @@ | |
| 313 | |
| 314 | fossil_free(zTag); |
| 315 | getAllTicketFields(); |
| 316 | if( haveTicket==0 ) return; |
| 317 | tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid); |
| 318 | search_doc_touch('t', tktid, 0); |
| 319 | if( haveTicketChng ){ |
| 320 | db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid); |
| 321 | } |
| 322 | db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid); |
| 323 | tktid = 0; |
| @@ -691,10 +692,11 @@ | |
| 692 | if( !g.perm.NewTkt ){ login_needed(); return; } |
| 693 | if( P("cancel") ){ |
| 694 | cgi_redirect("home"); |
| 695 | } |
| 696 | style_header("New Ticket"); |
| 697 | ticket_standard_submenu(T_ALL_BUT(T_NEW)); |
| 698 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1); |
| 699 | ticket_init(); |
| 700 | initializeVariablesFromCGI(); |
| 701 | getAllTicketFields(); |
| 702 | initializeVariablesFromDb(); |
| @@ -1380,5 +1382,60 @@ | |
| 1382 | (eCmd==set?"set":"add"),zTktUuid); |
| 1383 | } |
| 1384 | } |
| 1385 | } |
| 1386 | } |
| 1387 | |
| 1388 | |
| 1389 | #if INTERFACE |
| 1390 | /* Standard submenu items for wiki pages */ |
| 1391 | #define T_SRCH 0x00001 |
| 1392 | #define T_REPLIST 0x00002 |
| 1393 | #define T_NEW 0x00004 |
| 1394 | #define T_ALL 0x00007 |
| 1395 | #define T_ALL_BUT(x) (T_ALL&~(x)) |
| 1396 | #endif |
| 1397 | |
| 1398 | /* |
| 1399 | ** Add some standard submenu elements for ticket screens. |
| 1400 | */ |
| 1401 | void ticket_standard_submenu(unsigned int ok){ |
| 1402 | if( (ok & T_SRCH)!=0 && search_restrict(SRCH_TKT)!=0 ){ |
| 1403 | style_submenu_element("Search","Search","%R/tktsrch"); |
| 1404 | } |
| 1405 | if( (ok & T_REPLIST)!=0 ){ |
| 1406 | style_submenu_element("Reports","Reports","%R/reportlist"); |
| 1407 | } |
| 1408 | if( (ok & T_NEW)!=0 && g.perm.NewTkt ){ |
| 1409 | style_submenu_element("New","New","%R/tktnew"); |
| 1410 | } |
| 1411 | } |
| 1412 | |
| 1413 | /* |
| 1414 | ** WEBPAGE: ticket |
| 1415 | ** |
| 1416 | ** This is intended to be the primary "Ticket" page. Render as |
| 1417 | ** either ticket-search (if search is enabled) or as the |
| 1418 | ** /reportlist page (if ticket search is disabled). |
| 1419 | */ |
| 1420 | void tkt_home_page(void){ |
| 1421 | login_check_credentials(); |
| 1422 | if( search_restrict(SRCH_TKT)!=0 ){ |
| 1423 | tkt_srchpage(); |
| 1424 | }else{ |
| 1425 | view_list(); |
| 1426 | } |
| 1427 | } |
| 1428 | |
| 1429 | /* |
| 1430 | ** WEBPAGE: tktsrch |
| 1431 | ** Usage: /tktsrch?s=PATTERN |
| 1432 | ** |
| 1433 | ** Full-text search of all current tickets |
| 1434 | */ |
| 1435 | void tkt_srchpage(void){ |
| 1436 | login_check_credentials(); |
| 1437 | style_header("Ticket Search"); |
| 1438 | ticket_standard_submenu(T_ALL_BUT(T_SRCH)); |
| 1439 | search_screen(SRCH_TKT, "tktsrch"); |
| 1440 | style_footer(); |
| 1441 | } |
| 1442 |
+1
-1
| --- src/url.c | ||
| +++ src/url.c | ||
| @@ -389,11 +389,11 @@ | ||
| 389 | 389 | void url_enable_proxy(const char *zMsg){ |
| 390 | 390 | const char *zProxy; |
| 391 | 391 | zProxy = zProxyOpt; |
| 392 | 392 | if( zProxy==0 ){ |
| 393 | 393 | zProxy = db_get("proxy", 0); |
| 394 | - if( zProxy==0 || zProxy[0]==0 || is_truth(zProxy) ){ | |
| 394 | + if( zProxy==0 || zProxy[0]==0 || is_false(zProxy) ){ | |
| 395 | 395 | zProxy = fossil_getenv("http_proxy"); |
| 396 | 396 | } |
| 397 | 397 | } |
| 398 | 398 | if( zProxy && zProxy[0] && !is_false(zProxy) |
| 399 | 399 | && !g.url.isSsh && !g.url.isFile ){ |
| 400 | 400 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -389,11 +389,11 @@ | |
| 389 | void url_enable_proxy(const char *zMsg){ |
| 390 | const char *zProxy; |
| 391 | zProxy = zProxyOpt; |
| 392 | if( zProxy==0 ){ |
| 393 | zProxy = db_get("proxy", 0); |
| 394 | if( zProxy==0 || zProxy[0]==0 || is_truth(zProxy) ){ |
| 395 | zProxy = fossil_getenv("http_proxy"); |
| 396 | } |
| 397 | } |
| 398 | if( zProxy && zProxy[0] && !is_false(zProxy) |
| 399 | && !g.url.isSsh && !g.url.isFile ){ |
| 400 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -389,11 +389,11 @@ | |
| 389 | void url_enable_proxy(const char *zMsg){ |
| 390 | const char *zProxy; |
| 391 | zProxy = zProxyOpt; |
| 392 | if( zProxy==0 ){ |
| 393 | zProxy = db_get("proxy", 0); |
| 394 | if( zProxy==0 || zProxy[0]==0 || is_false(zProxy) ){ |
| 395 | zProxy = fossil_getenv("http_proxy"); |
| 396 | } |
| 397 | } |
| 398 | if( zProxy && zProxy[0] && !is_false(zProxy) |
| 399 | && !g.url.isSsh && !g.url.isFile ){ |
| 400 |
+124
-48
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -198,10 +198,106 @@ | ||
| 198 | 198 | if( localUser ){ |
| 199 | 199 | return 0; |
| 200 | 200 | } |
| 201 | 201 | return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1; |
| 202 | 202 | } |
| 203 | + | |
| 204 | +/* Standard submenu items for wiki pages */ | |
| 205 | +#define W_SRCH 0x00001 | |
| 206 | +#define W_LIST 0x00002 | |
| 207 | +#define W_HELP 0x00004 | |
| 208 | +#define W_NEW 0x00008 | |
| 209 | +#define W_BLOG 0x00010 | |
| 210 | +#define W_SANDBOX 0x00020 | |
| 211 | +#define W_ALL 0x0001f | |
| 212 | +#define W_ALL_BUT(x) (W_ALL&~(x)) | |
| 213 | + | |
| 214 | +/* | |
| 215 | +** Add some standard submenu elements for wiki screens. | |
| 216 | +*/ | |
| 217 | +static void wiki_standard_submenu(unsigned int ok){ | |
| 218 | + if( (ok & W_SRCH)!=0 && search_restrict(SRCH_WIKI)!=0 ){ | |
| 219 | + style_submenu_element("Search","Search","%R/wikisrch"); | |
| 220 | + } | |
| 221 | + if( (ok & W_LIST)!=0 ){ | |
| 222 | + style_submenu_element("List","List","%R/wcontent"); | |
| 223 | + } | |
| 224 | + if( (ok & W_HELP)!=0 ){ | |
| 225 | + style_submenu_element("Help","Help","%R/wikihelp"); | |
| 226 | + } | |
| 227 | + if( (ok & W_NEW)!=0 && g.perm.NewWiki ){ | |
| 228 | + style_submenu_element("New","New","%R/wikinew"); | |
| 229 | + } | |
| 230 | +#if 0 | |
| 231 | + if( (ok & W_BLOG)!=0 | |
| 232 | +#endif | |
| 233 | + if( (ok & W_SANDBOX)!=0 ){ | |
| 234 | + style_submenu_element("Sandbox", "Sandbox", "/wiki?name=Sandbox"); | |
| 235 | + } | |
| 236 | +} | |
| 237 | + | |
| 238 | +/* | |
| 239 | +** WEBPAGE: wikihelp | |
| 240 | +** A generic landing page for wiki. | |
| 241 | +*/ | |
| 242 | +void wiki_helppage(void){ | |
| 243 | + login_check_credentials(); | |
| 244 | + if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 245 | + style_header("Wiki Help"); | |
| 246 | + wiki_standard_submenu(W_ALL_BUT(W_HELP)); | |
| 247 | + @ <h2>Wiki Links</h2> | |
| 248 | + @ <ul> | |
| 249 | + { char *zWikiHomePageName = db_get("index-page",0); | |
| 250 | + if( zWikiHomePageName ){ | |
| 251 | + @ <li> %z(href("%R%s",zWikiHomePageName)) | |
| 252 | + @ %h(zWikiHomePageName)</a> wiki home page.</li> | |
| 253 | + } | |
| 254 | + } | |
| 255 | + { char *zHomePageName = db_get("project-name",0); | |
| 256 | + if( zHomePageName ){ | |
| 257 | + @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) | |
| 258 | + @ %h(zHomePageName)</a> project home page.</li> | |
| 259 | + } | |
| 260 | + } | |
| 261 | + @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> | |
| 262 | + @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for | |
| 263 | + @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> | |
| 264 | + @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> | |
| 265 | + @ to experiment.</li> | |
| 266 | + if( g.perm.NewWiki ){ | |
| 267 | + @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> | |
| 268 | + if( g.perm.Write ){ | |
| 269 | + @ <li> Create a %z(href("%R/eventedit"))new blog entry</a>.</li> | |
| 270 | + } | |
| 271 | + } | |
| 272 | + @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> | |
| 273 | + @ available on this server.</li> | |
| 274 | + if( g.perm.ModWiki ){ | |
| 275 | + @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> | |
| 276 | + } | |
| 277 | + if( search_restrict(SRCH_WIKI)!=0 ){ | |
| 278 | + @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key | |
| 279 | + @ words</li> | |
| 280 | + } | |
| 281 | + @ </ul> | |
| 282 | + style_footer(); | |
| 283 | + return; | |
| 284 | +} | |
| 285 | + | |
| 286 | +/* | |
| 287 | +** WEBPAGE: wikisrch | |
| 288 | +** Usage: /wikisrch?s=PATTERN | |
| 289 | +** | |
| 290 | +** Full-text search of all current wiki text | |
| 291 | +*/ | |
| 292 | +void wiki_srchpage(void){ | |
| 293 | + login_check_credentials(); | |
| 294 | + style_header("Wiki Search"); | |
| 295 | + wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); | |
| 296 | + search_screen(SRCH_WIKI, "wikisrch"); | |
| 297 | + style_footer(); | |
| 298 | +} | |
| 203 | 299 | |
| 204 | 300 | /* |
| 205 | 301 | ** WEBPAGE: wiki |
| 206 | 302 | ** URL: /wiki?name=PAGENAME |
| 207 | 303 | */ |
| @@ -208,10 +304,11 @@ | ||
| 208 | 304 | void wiki_page(void){ |
| 209 | 305 | char *zTag; |
| 210 | 306 | int rid = 0; |
| 211 | 307 | int isSandbox; |
| 212 | 308 | char *zUuid; |
| 309 | + unsigned submenuFlags = W_ALL; | |
| 213 | 310 | Blob wiki; |
| 214 | 311 | Manifest *pWiki = 0; |
| 215 | 312 | const char *zPageName; |
| 216 | 313 | const char *zMimetype = 0; |
| 217 | 314 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| @@ -218,63 +315,35 @@ | ||
| 218 | 315 | |
| 219 | 316 | login_check_credentials(); |
| 220 | 317 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 221 | 318 | zPageName = P("name"); |
| 222 | 319 | if( zPageName==0 ){ |
| 223 | - style_header("Wiki"); | |
| 224 | - @ <ul> | |
| 225 | - { char *zWikiHomePageName = db_get("index-page",0); | |
| 226 | - if( zWikiHomePageName ){ | |
| 227 | - @ <li> %z(href("%R%s",zWikiHomePageName)) | |
| 228 | - @ %h(zWikiHomePageName)</a> wiki home page.</li> | |
| 229 | - } | |
| 230 | - } | |
| 231 | - { char *zHomePageName = db_get("project-name",0); | |
| 232 | - if( zHomePageName ){ | |
| 233 | - @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) | |
| 234 | - @ %h(zHomePageName)</a> project home page.</li> | |
| 235 | - } | |
| 236 | - } | |
| 237 | - @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> | |
| 238 | - @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for | |
| 239 | - @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> | |
| 240 | - @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> | |
| 241 | - @ to experiment.</li> | |
| 242 | - if( g.perm.NewWiki ){ | |
| 243 | - @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> | |
| 244 | - if( g.perm.Write ){ | |
| 245 | - @ <li> Create a %z(href("%R/eventedit"))new event</a>.</li> | |
| 246 | - } | |
| 247 | - } | |
| 248 | - @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> | |
| 249 | - @ available on this server.</li> | |
| 250 | - if( g.perm.ModWiki ){ | |
| 251 | - @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> | |
| 252 | - } | |
| 253 | - @ <li> | |
| 254 | - form_begin(0, "%R/wfind"); | |
| 255 | - @ <div>Search wiki titles: <input type="text" name="title"/> | |
| 256 | - @ <input type="submit" /></div></form> | |
| 257 | - @ </li> | |
| 258 | - @ </ul> | |
| 259 | - style_footer(); | |
| 320 | + if( search_restrict(SRCH_WIKI)!=0 ){ | |
| 321 | + wiki_srchpage(); | |
| 322 | + }else{ | |
| 323 | + wiki_helppage(); | |
| 324 | + } | |
| 260 | 325 | return; |
| 261 | 326 | } |
| 262 | 327 | if( check_name(zPageName) ) return; |
| 263 | 328 | isSandbox = is_sandbox(zPageName); |
| 264 | 329 | if( isSandbox ){ |
| 330 | + submenuFlags &= ~W_SANDBOX; | |
| 265 | 331 | zBody = db_get("sandbox",zBody); |
| 266 | 332 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 267 | 333 | rid = 0; |
| 268 | 334 | }else{ |
| 269 | - zTag = mprintf("wiki-%s", zPageName); | |
| 270 | - rid = db_int(0, | |
| 271 | - "SELECT rid FROM tagxref" | |
| 272 | - " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" | |
| 273 | - " ORDER BY mtime DESC", zTag | |
| 274 | - ); | |
| 275 | - free(zTag); | |
| 335 | + const char *zUuid = P("id"); | |
| 336 | + if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ | |
| 337 | + zTag = mprintf("wiki-%s", zPageName); | |
| 338 | + rid = db_int(0, | |
| 339 | + "SELECT rid FROM tagxref" | |
| 340 | + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" | |
| 341 | + " ORDER BY mtime DESC", zTag | |
| 342 | + ); | |
| 343 | + free(zTag); | |
| 344 | + } | |
| 276 | 345 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 277 | 346 | if( pWiki ){ |
| 278 | 347 | zBody = pWiki->zWiki; |
| 279 | 348 | zMimetype = pWiki->zMimetype; |
| 280 | 349 | } |
| @@ -314,10 +383,11 @@ | ||
| 314 | 383 | g.zTop, zPageName); |
| 315 | 384 | } |
| 316 | 385 | } |
| 317 | 386 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 318 | 387 | style_header("%s", zPageName); |
| 388 | + wiki_standard_submenu(submenuFlags); | |
| 319 | 389 | blob_init(&wiki, zBody, -1); |
| 320 | 390 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 321 | 391 | blob_reset(&wiki); |
| 322 | 392 | attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>"); |
| 323 | 393 | manifest_destroy(pWiki); |
| @@ -570,10 +640,11 @@ | ||
| 570 | 640 | }else{ |
| 571 | 641 | cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); |
| 572 | 642 | } |
| 573 | 643 | } |
| 574 | 644 | style_header("Create A New Wiki Page"); |
| 645 | + wiki_standard_submenu(W_ALL_BUT(W_NEW)); | |
| 575 | 646 | @ <p>Rules for wiki page names:</p> |
| 576 | 647 | well_formed_wiki_name_rules(); |
| 577 | 648 | form_begin(0, "%R/wikinew"); |
| 578 | 649 | @ <p>Name of new wiki page: |
| 579 | 650 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| @@ -846,11 +917,12 @@ | ||
| 846 | 917 | */ |
| 847 | 918 | void wiki_prepare_page_list( Stmt * pStmt ){ |
| 848 | 919 | db_prepare(pStmt, |
| 849 | 920 | "SELECT" |
| 850 | 921 | " substr(tagname, 6) as name," |
| 851 | - " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref" | |
| 922 | + " (SELECT value FROM tagxref WHERE tagid=tag.tagid" | |
| 923 | + " ORDER BY mtime DESC) as tagXref" | |
| 852 | 924 | " FROM tag WHERE tagname GLOB 'wiki-*'" |
| 853 | 925 | " ORDER BY lower(tagname) /*sort*/" |
| 854 | 926 | ); |
| 855 | 927 | } |
| 856 | 928 | /* |
| @@ -870,10 +942,11 @@ | ||
| 870 | 942 | if( showAll ){ |
| 871 | 943 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 872 | 944 | }else{ |
| 873 | 945 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| 874 | 946 | } |
| 947 | + wiki_standard_submenu(W_ALL_BUT(W_LIST)); | |
| 875 | 948 | @ <ul> |
| 876 | 949 | wiki_prepare_page_list(&q); |
| 877 | 950 | while( db_step(&q)==SQLITE_ROW ){ |
| 878 | 951 | const char *zName = db_column_text(&q, 0); |
| 879 | 952 | int size = db_column_int(&q, 1); |
| @@ -935,11 +1008,12 @@ | ||
| 935 | 1008 | @ </ol> |
| 936 | 1009 | @ <p>We call the first five rules above "wiki" formatting rules. The |
| 937 | 1010 | @ last two rules are the HTML formatting rule.</p> |
| 938 | 1011 | @ <h2>Formatting Rule Details</h2> |
| 939 | 1012 | @ <ol> |
| 940 | - @ <li> <p><span class="wikiruleHead">Paragraphs</span>. Any sequence of one or more blank lines forms | |
| 1013 | + @ <li> <p><span class="wikiruleHead">Paragraphs</span>. | |
| 1014 | + @ Any sequence of one or more blank lines forms | |
| 941 | 1015 | @ a paragraph break. Centered or right-justified paragraphs are not |
| 942 | 1016 | @ supported by wiki markup, but you can do these things if you need them |
| 943 | 1017 | @ using HTML.</p></li> |
| 944 | 1018 | @ <li> <p><span class="wikiruleHead">Bullet Lists</span>. |
| 945 | 1019 | @ A bullet list item is a line that begins with a single "*" character |
| @@ -960,11 +1034,12 @@ | ||
| 960 | 1034 | @ Text within square brackets ("[...]") becomes a hyperlink. The |
| 961 | 1035 | @ target can be a wiki page name, the artifact ID of a check-in or ticket, |
| 962 | 1036 | @ the name of an image, or a URL. By default, the target is displayed |
| 963 | 1037 | @ as the text of the hyperlink. But you can specify alternative text |
| 964 | 1038 | @ after the target name separated by a "|" character.</p> |
| 965 | - @ <p>You can also link to internal anchor names using [#anchor-name], providing | |
| 1039 | + @ <p>You can also link to internal anchor names using [#anchor-name], | |
| 1040 | + @ providing | |
| 966 | 1041 | @ you have added the necessary "<a name='anchor-name'></a>" |
| 967 | 1042 | @ tag to your wiki page.</p></li> |
| 968 | 1043 | @ <li> <p><span class="wikiruleHead">HTML</span>. |
| 969 | 1044 | @ The following standard HTML elements may be used: |
| 970 | 1045 | show_allowed_wiki_markup(); |
| @@ -1166,11 +1241,12 @@ | ||
| 1166 | 1241 | }else if( strncmp(g.argv[2],"delete",n)==0 ){ |
| 1167 | 1242 | if( g.argc!=5 ){ |
| 1168 | 1243 | usage("delete PAGENAME"); |
| 1169 | 1244 | } |
| 1170 | 1245 | fossil_fatal("delete not yet implemented."); |
| 1171 | - }else if(( strncmp(g.argv[2],"list",n)==0 ) || ( strncmp(g.argv[2],"ls",n)==0 )){ | |
| 1246 | + }else if(( strncmp(g.argv[2],"list",n)==0 ) | |
| 1247 | + || ( strncmp(g.argv[2],"ls",n)==0 )){ | |
| 1172 | 1248 | Stmt q; |
| 1173 | 1249 | db_prepare(&q, |
| 1174 | 1250 | "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'" |
| 1175 | 1251 | " ORDER BY lower(tagname) /*sort*/" |
| 1176 | 1252 | ); |
| 1177 | 1253 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -198,10 +198,106 @@ | |
| 198 | if( localUser ){ |
| 199 | return 0; |
| 200 | } |
| 201 | return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1; |
| 202 | } |
| 203 | |
| 204 | /* |
| 205 | ** WEBPAGE: wiki |
| 206 | ** URL: /wiki?name=PAGENAME |
| 207 | */ |
| @@ -208,10 +304,11 @@ | |
| 208 | void wiki_page(void){ |
| 209 | char *zTag; |
| 210 | int rid = 0; |
| 211 | int isSandbox; |
| 212 | char *zUuid; |
| 213 | Blob wiki; |
| 214 | Manifest *pWiki = 0; |
| 215 | const char *zPageName; |
| 216 | const char *zMimetype = 0; |
| 217 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| @@ -218,63 +315,35 @@ | |
| 218 | |
| 219 | login_check_credentials(); |
| 220 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 221 | zPageName = P("name"); |
| 222 | if( zPageName==0 ){ |
| 223 | style_header("Wiki"); |
| 224 | @ <ul> |
| 225 | { char *zWikiHomePageName = db_get("index-page",0); |
| 226 | if( zWikiHomePageName ){ |
| 227 | @ <li> %z(href("%R%s",zWikiHomePageName)) |
| 228 | @ %h(zWikiHomePageName)</a> wiki home page.</li> |
| 229 | } |
| 230 | } |
| 231 | { char *zHomePageName = db_get("project-name",0); |
| 232 | if( zHomePageName ){ |
| 233 | @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) |
| 234 | @ %h(zHomePageName)</a> project home page.</li> |
| 235 | } |
| 236 | } |
| 237 | @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
| 238 | @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
| 239 | @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
| 240 | @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> |
| 241 | @ to experiment.</li> |
| 242 | if( g.perm.NewWiki ){ |
| 243 | @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> |
| 244 | if( g.perm.Write ){ |
| 245 | @ <li> Create a %z(href("%R/eventedit"))new event</a>.</li> |
| 246 | } |
| 247 | } |
| 248 | @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
| 249 | @ available on this server.</li> |
| 250 | if( g.perm.ModWiki ){ |
| 251 | @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
| 252 | } |
| 253 | @ <li> |
| 254 | form_begin(0, "%R/wfind"); |
| 255 | @ <div>Search wiki titles: <input type="text" name="title"/> |
| 256 | @ <input type="submit" /></div></form> |
| 257 | @ </li> |
| 258 | @ </ul> |
| 259 | style_footer(); |
| 260 | return; |
| 261 | } |
| 262 | if( check_name(zPageName) ) return; |
| 263 | isSandbox = is_sandbox(zPageName); |
| 264 | if( isSandbox ){ |
| 265 | zBody = db_get("sandbox",zBody); |
| 266 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 267 | rid = 0; |
| 268 | }else{ |
| 269 | zTag = mprintf("wiki-%s", zPageName); |
| 270 | rid = db_int(0, |
| 271 | "SELECT rid FROM tagxref" |
| 272 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 273 | " ORDER BY mtime DESC", zTag |
| 274 | ); |
| 275 | free(zTag); |
| 276 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 277 | if( pWiki ){ |
| 278 | zBody = pWiki->zWiki; |
| 279 | zMimetype = pWiki->zMimetype; |
| 280 | } |
| @@ -314,10 +383,11 @@ | |
| 314 | g.zTop, zPageName); |
| 315 | } |
| 316 | } |
| 317 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 318 | style_header("%s", zPageName); |
| 319 | blob_init(&wiki, zBody, -1); |
| 320 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 321 | blob_reset(&wiki); |
| 322 | attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>"); |
| 323 | manifest_destroy(pWiki); |
| @@ -570,10 +640,11 @@ | |
| 570 | }else{ |
| 571 | cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); |
| 572 | } |
| 573 | } |
| 574 | style_header("Create A New Wiki Page"); |
| 575 | @ <p>Rules for wiki page names:</p> |
| 576 | well_formed_wiki_name_rules(); |
| 577 | form_begin(0, "%R/wikinew"); |
| 578 | @ <p>Name of new wiki page: |
| 579 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| @@ -846,11 +917,12 @@ | |
| 846 | */ |
| 847 | void wiki_prepare_page_list( Stmt * pStmt ){ |
| 848 | db_prepare(pStmt, |
| 849 | "SELECT" |
| 850 | " substr(tagname, 6) as name," |
| 851 | " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref" |
| 852 | " FROM tag WHERE tagname GLOB 'wiki-*'" |
| 853 | " ORDER BY lower(tagname) /*sort*/" |
| 854 | ); |
| 855 | } |
| 856 | /* |
| @@ -870,10 +942,11 @@ | |
| 870 | if( showAll ){ |
| 871 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 872 | }else{ |
| 873 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| 874 | } |
| 875 | @ <ul> |
| 876 | wiki_prepare_page_list(&q); |
| 877 | while( db_step(&q)==SQLITE_ROW ){ |
| 878 | const char *zName = db_column_text(&q, 0); |
| 879 | int size = db_column_int(&q, 1); |
| @@ -935,11 +1008,12 @@ | |
| 935 | @ </ol> |
| 936 | @ <p>We call the first five rules above "wiki" formatting rules. The |
| 937 | @ last two rules are the HTML formatting rule.</p> |
| 938 | @ <h2>Formatting Rule Details</h2> |
| 939 | @ <ol> |
| 940 | @ <li> <p><span class="wikiruleHead">Paragraphs</span>. Any sequence of one or more blank lines forms |
| 941 | @ a paragraph break. Centered or right-justified paragraphs are not |
| 942 | @ supported by wiki markup, but you can do these things if you need them |
| 943 | @ using HTML.</p></li> |
| 944 | @ <li> <p><span class="wikiruleHead">Bullet Lists</span>. |
| 945 | @ A bullet list item is a line that begins with a single "*" character |
| @@ -960,11 +1034,12 @@ | |
| 960 | @ Text within square brackets ("[...]") becomes a hyperlink. The |
| 961 | @ target can be a wiki page name, the artifact ID of a check-in or ticket, |
| 962 | @ the name of an image, or a URL. By default, the target is displayed |
| 963 | @ as the text of the hyperlink. But you can specify alternative text |
| 964 | @ after the target name separated by a "|" character.</p> |
| 965 | @ <p>You can also link to internal anchor names using [#anchor-name], providing |
| 966 | @ you have added the necessary "<a name='anchor-name'></a>" |
| 967 | @ tag to your wiki page.</p></li> |
| 968 | @ <li> <p><span class="wikiruleHead">HTML</span>. |
| 969 | @ The following standard HTML elements may be used: |
| 970 | show_allowed_wiki_markup(); |
| @@ -1166,11 +1241,12 @@ | |
| 1166 | }else if( strncmp(g.argv[2],"delete",n)==0 ){ |
| 1167 | if( g.argc!=5 ){ |
| 1168 | usage("delete PAGENAME"); |
| 1169 | } |
| 1170 | fossil_fatal("delete not yet implemented."); |
| 1171 | }else if(( strncmp(g.argv[2],"list",n)==0 ) || ( strncmp(g.argv[2],"ls",n)==0 )){ |
| 1172 | Stmt q; |
| 1173 | db_prepare(&q, |
| 1174 | "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'" |
| 1175 | " ORDER BY lower(tagname) /*sort*/" |
| 1176 | ); |
| 1177 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -198,10 +198,106 @@ | |
| 198 | if( localUser ){ |
| 199 | return 0; |
| 200 | } |
| 201 | return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1; |
| 202 | } |
| 203 | |
| 204 | /* Standard submenu items for wiki pages */ |
| 205 | #define W_SRCH 0x00001 |
| 206 | #define W_LIST 0x00002 |
| 207 | #define W_HELP 0x00004 |
| 208 | #define W_NEW 0x00008 |
| 209 | #define W_BLOG 0x00010 |
| 210 | #define W_SANDBOX 0x00020 |
| 211 | #define W_ALL 0x0001f |
| 212 | #define W_ALL_BUT(x) (W_ALL&~(x)) |
| 213 | |
| 214 | /* |
| 215 | ** Add some standard submenu elements for wiki screens. |
| 216 | */ |
| 217 | static void wiki_standard_submenu(unsigned int ok){ |
| 218 | if( (ok & W_SRCH)!=0 && search_restrict(SRCH_WIKI)!=0 ){ |
| 219 | style_submenu_element("Search","Search","%R/wikisrch"); |
| 220 | } |
| 221 | if( (ok & W_LIST)!=0 ){ |
| 222 | style_submenu_element("List","List","%R/wcontent"); |
| 223 | } |
| 224 | if( (ok & W_HELP)!=0 ){ |
| 225 | style_submenu_element("Help","Help","%R/wikihelp"); |
| 226 | } |
| 227 | if( (ok & W_NEW)!=0 && g.perm.NewWiki ){ |
| 228 | style_submenu_element("New","New","%R/wikinew"); |
| 229 | } |
| 230 | #if 0 |
| 231 | if( (ok & W_BLOG)!=0 |
| 232 | #endif |
| 233 | if( (ok & W_SANDBOX)!=0 ){ |
| 234 | style_submenu_element("Sandbox", "Sandbox", "/wiki?name=Sandbox"); |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | /* |
| 239 | ** WEBPAGE: wikihelp |
| 240 | ** A generic landing page for wiki. |
| 241 | */ |
| 242 | void wiki_helppage(void){ |
| 243 | login_check_credentials(); |
| 244 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 245 | style_header("Wiki Help"); |
| 246 | wiki_standard_submenu(W_ALL_BUT(W_HELP)); |
| 247 | @ <h2>Wiki Links</h2> |
| 248 | @ <ul> |
| 249 | { char *zWikiHomePageName = db_get("index-page",0); |
| 250 | if( zWikiHomePageName ){ |
| 251 | @ <li> %z(href("%R%s",zWikiHomePageName)) |
| 252 | @ %h(zWikiHomePageName)</a> wiki home page.</li> |
| 253 | } |
| 254 | } |
| 255 | { char *zHomePageName = db_get("project-name",0); |
| 256 | if( zHomePageName ){ |
| 257 | @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) |
| 258 | @ %h(zHomePageName)</a> project home page.</li> |
| 259 | } |
| 260 | } |
| 261 | @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
| 262 | @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
| 263 | @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
| 264 | @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> |
| 265 | @ to experiment.</li> |
| 266 | if( g.perm.NewWiki ){ |
| 267 | @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> |
| 268 | if( g.perm.Write ){ |
| 269 | @ <li> Create a %z(href("%R/eventedit"))new blog entry</a>.</li> |
| 270 | } |
| 271 | } |
| 272 | @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
| 273 | @ available on this server.</li> |
| 274 | if( g.perm.ModWiki ){ |
| 275 | @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
| 276 | } |
| 277 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 278 | @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key |
| 279 | @ words</li> |
| 280 | } |
| 281 | @ </ul> |
| 282 | style_footer(); |
| 283 | return; |
| 284 | } |
| 285 | |
| 286 | /* |
| 287 | ** WEBPAGE: wikisrch |
| 288 | ** Usage: /wikisrch?s=PATTERN |
| 289 | ** |
| 290 | ** Full-text search of all current wiki text |
| 291 | */ |
| 292 | void wiki_srchpage(void){ |
| 293 | login_check_credentials(); |
| 294 | style_header("Wiki Search"); |
| 295 | wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); |
| 296 | search_screen(SRCH_WIKI, "wikisrch"); |
| 297 | style_footer(); |
| 298 | } |
| 299 | |
| 300 | /* |
| 301 | ** WEBPAGE: wiki |
| 302 | ** URL: /wiki?name=PAGENAME |
| 303 | */ |
| @@ -208,10 +304,11 @@ | |
| 304 | void wiki_page(void){ |
| 305 | char *zTag; |
| 306 | int rid = 0; |
| 307 | int isSandbox; |
| 308 | char *zUuid; |
| 309 | unsigned submenuFlags = W_ALL; |
| 310 | Blob wiki; |
| 311 | Manifest *pWiki = 0; |
| 312 | const char *zPageName; |
| 313 | const char *zMimetype = 0; |
| 314 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| @@ -218,63 +315,35 @@ | |
| 315 | |
| 316 | login_check_credentials(); |
| 317 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 318 | zPageName = P("name"); |
| 319 | if( zPageName==0 ){ |
| 320 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 321 | wiki_srchpage(); |
| 322 | }else{ |
| 323 | wiki_helppage(); |
| 324 | } |
| 325 | return; |
| 326 | } |
| 327 | if( check_name(zPageName) ) return; |
| 328 | isSandbox = is_sandbox(zPageName); |
| 329 | if( isSandbox ){ |
| 330 | submenuFlags &= ~W_SANDBOX; |
| 331 | zBody = db_get("sandbox",zBody); |
| 332 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 333 | rid = 0; |
| 334 | }else{ |
| 335 | const char *zUuid = P("id"); |
| 336 | if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ |
| 337 | zTag = mprintf("wiki-%s", zPageName); |
| 338 | rid = db_int(0, |
| 339 | "SELECT rid FROM tagxref" |
| 340 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 341 | " ORDER BY mtime DESC", zTag |
| 342 | ); |
| 343 | free(zTag); |
| 344 | } |
| 345 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 346 | if( pWiki ){ |
| 347 | zBody = pWiki->zWiki; |
| 348 | zMimetype = pWiki->zMimetype; |
| 349 | } |
| @@ -314,10 +383,11 @@ | |
| 383 | g.zTop, zPageName); |
| 384 | } |
| 385 | } |
| 386 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 387 | style_header("%s", zPageName); |
| 388 | wiki_standard_submenu(submenuFlags); |
| 389 | blob_init(&wiki, zBody, -1); |
| 390 | wiki_render_by_mimetype(&wiki, zMimetype); |
| 391 | blob_reset(&wiki); |
| 392 | attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>"); |
| 393 | manifest_destroy(pWiki); |
| @@ -570,10 +640,11 @@ | |
| 640 | }else{ |
| 641 | cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); |
| 642 | } |
| 643 | } |
| 644 | style_header("Create A New Wiki Page"); |
| 645 | wiki_standard_submenu(W_ALL_BUT(W_NEW)); |
| 646 | @ <p>Rules for wiki page names:</p> |
| 647 | well_formed_wiki_name_rules(); |
| 648 | form_begin(0, "%R/wikinew"); |
| 649 | @ <p>Name of new wiki page: |
| 650 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| @@ -846,11 +917,12 @@ | |
| 917 | */ |
| 918 | void wiki_prepare_page_list( Stmt * pStmt ){ |
| 919 | db_prepare(pStmt, |
| 920 | "SELECT" |
| 921 | " substr(tagname, 6) as name," |
| 922 | " (SELECT value FROM tagxref WHERE tagid=tag.tagid" |
| 923 | " ORDER BY mtime DESC) as tagXref" |
| 924 | " FROM tag WHERE tagname GLOB 'wiki-*'" |
| 925 | " ORDER BY lower(tagname) /*sort*/" |
| 926 | ); |
| 927 | } |
| 928 | /* |
| @@ -870,10 +942,11 @@ | |
| 942 | if( showAll ){ |
| 943 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 944 | }else{ |
| 945 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| 946 | } |
| 947 | wiki_standard_submenu(W_ALL_BUT(W_LIST)); |
| 948 | @ <ul> |
| 949 | wiki_prepare_page_list(&q); |
| 950 | while( db_step(&q)==SQLITE_ROW ){ |
| 951 | const char *zName = db_column_text(&q, 0); |
| 952 | int size = db_column_int(&q, 1); |
| @@ -935,11 +1008,12 @@ | |
| 1008 | @ </ol> |
| 1009 | @ <p>We call the first five rules above "wiki" formatting rules. The |
| 1010 | @ last two rules are the HTML formatting rule.</p> |
| 1011 | @ <h2>Formatting Rule Details</h2> |
| 1012 | @ <ol> |
| 1013 | @ <li> <p><span class="wikiruleHead">Paragraphs</span>. |
| 1014 | @ Any sequence of one or more blank lines forms |
| 1015 | @ a paragraph break. Centered or right-justified paragraphs are not |
| 1016 | @ supported by wiki markup, but you can do these things if you need them |
| 1017 | @ using HTML.</p></li> |
| 1018 | @ <li> <p><span class="wikiruleHead">Bullet Lists</span>. |
| 1019 | @ A bullet list item is a line that begins with a single "*" character |
| @@ -960,11 +1034,12 @@ | |
| 1034 | @ Text within square brackets ("[...]") becomes a hyperlink. The |
| 1035 | @ target can be a wiki page name, the artifact ID of a check-in or ticket, |
| 1036 | @ the name of an image, or a URL. By default, the target is displayed |
| 1037 | @ as the text of the hyperlink. But you can specify alternative text |
| 1038 | @ after the target name separated by a "|" character.</p> |
| 1039 | @ <p>You can also link to internal anchor names using [#anchor-name], |
| 1040 | @ providing |
| 1041 | @ you have added the necessary "<a name='anchor-name'></a>" |
| 1042 | @ tag to your wiki page.</p></li> |
| 1043 | @ <li> <p><span class="wikiruleHead">HTML</span>. |
| 1044 | @ The following standard HTML elements may be used: |
| 1045 | show_allowed_wiki_markup(); |
| @@ -1166,11 +1241,12 @@ | |
| 1241 | }else if( strncmp(g.argv[2],"delete",n)==0 ){ |
| 1242 | if( g.argc!=5 ){ |
| 1243 | usage("delete PAGENAME"); |
| 1244 | } |
| 1245 | fossil_fatal("delete not yet implemented."); |
| 1246 | }else if(( strncmp(g.argv[2],"list",n)==0 ) |
| 1247 | || ( strncmp(g.argv[2],"ls",n)==0 )){ |
| 1248 | Stmt q; |
| 1249 | db_prepare(&q, |
| 1250 | "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'" |
| 1251 | " ORDER BY lower(tagname) /*sort*/" |
| 1252 | ); |
| 1253 |
+72
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -2092,9 +2092,81 @@ | ||
| 2092 | 2092 | for(i=2; i<g.argc; i++){ |
| 2093 | 2093 | blob_read_from_file(&in, g.argv[i]); |
| 2094 | 2094 | blob_zero(&out); |
| 2095 | 2095 | htmlTidy(blob_str(&in), &out); |
| 2096 | 2096 | blob_reset(&in); |
| 2097 | + fossil_puts(blob_str(&out), 0); | |
| 2098 | + blob_reset(&out); | |
| 2099 | + } | |
| 2100 | +} | |
| 2101 | + | |
| 2102 | +/* | |
| 2103 | +** Remove all HTML markup from the input text. The output written into | |
| 2104 | +** pOut is pure text. | |
| 2105 | +*/ | |
| 2106 | +void html_to_plaintext(const char *zIn, Blob *pOut){ | |
| 2107 | + int n; | |
| 2108 | + int i, j; | |
| 2109 | + int nNL = 0; /* Number of \n characters at the end of pOut */ | |
| 2110 | + int nWS = 0; /* True if pOut ends with whitespace */ | |
| 2111 | + while( zIn[0] ){ | |
| 2112 | + n = nextHtmlToken(zIn); | |
| 2113 | + if( zIn[0]=='<' && n>1 ){ | |
| 2114 | + int isCloseTag; | |
| 2115 | + int eTag; | |
| 2116 | + int eType; | |
| 2117 | + char zTag[32]; | |
| 2118 | + isCloseTag = zIn[1]=='/'; | |
| 2119 | + for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ | |
| 2120 | + zTag[i] = fossil_tolower(zIn[j]); | |
| 2121 | + } | |
| 2122 | + zTag[i] = 0; | |
| 2123 | + eTag = findTag(zTag); | |
| 2124 | + eType = aMarkup[eTag].iType; | |
| 2125 | + if( eTag==MARKUP_INVALID && fossil_strnicmp(zIn,"<style",6)==0 ){ | |
| 2126 | + zIn += n; | |
| 2127 | + while( zIn[0] ){ | |
| 2128 | + n = nextHtmlToken(zIn); | |
| 2129 | + if( fossil_strnicmp(zIn, "</style",7)==0 ) break; | |
| 2130 | + zIn += n; | |
| 2131 | + } | |
| 2132 | + if( zIn[0]=='<' ) zIn += n; | |
| 2133 | + continue; | |
| 2134 | + } | |
| 2135 | + if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ | |
| 2136 | + if( nNL==0 ){ | |
| 2137 | + blob_append(pOut, "\n", 1); | |
| 2138 | + nNL++; | |
| 2139 | + } | |
| 2140 | + nWS = 1; | |
| 2141 | + } | |
| 2142 | + }else if( fossil_isspace(zIn[0]) ){ | |
| 2143 | + for(i=nNL=0; i<n; i++) if( zIn[i]=='\n' ) nNL++; | |
| 2144 | + if( !nWS ){ | |
| 2145 | + blob_append(pOut, nNL ? "\n" : " ", 1); | |
| 2146 | + nWS = 1; | |
| 2147 | + } | |
| 2148 | + }else{ | |
| 2149 | + blob_append(pOut, zIn, n); | |
| 2150 | + nNL = nWS = 0; | |
| 2151 | + } | |
| 2152 | + zIn += n; | |
| 2153 | + } | |
| 2154 | + if( nNL==0 ) blob_append(pOut, "\n", 1); | |
| 2155 | +} | |
| 2156 | + | |
| 2157 | +/* | |
| 2158 | +** COMMAND: test-html-to-text | |
| 2159 | +*/ | |
| 2160 | +void test_html_to_text(void){ | |
| 2161 | + Blob in, out; | |
| 2162 | + int i; | |
| 2163 | + | |
| 2164 | + for(i=2; i<g.argc; i++){ | |
| 2165 | + blob_read_from_file(&in, g.argv[i]); | |
| 2166 | + blob_zero(&out); | |
| 2167 | + html_to_plaintext(blob_str(&in), &out); | |
| 2168 | + blob_reset(&in); | |
| 2097 | 2169 | fossil_puts(blob_str(&out), 0); |
| 2098 | 2170 | blob_reset(&out); |
| 2099 | 2171 | } |
| 2100 | 2172 | } |
| 2101 | 2173 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -2092,9 +2092,81 @@ | |
| 2092 | for(i=2; i<g.argc; i++){ |
| 2093 | blob_read_from_file(&in, g.argv[i]); |
| 2094 | blob_zero(&out); |
| 2095 | htmlTidy(blob_str(&in), &out); |
| 2096 | blob_reset(&in); |
| 2097 | fossil_puts(blob_str(&out), 0); |
| 2098 | blob_reset(&out); |
| 2099 | } |
| 2100 | } |
| 2101 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -2092,9 +2092,81 @@ | |
| 2092 | for(i=2; i<g.argc; i++){ |
| 2093 | blob_read_from_file(&in, g.argv[i]); |
| 2094 | blob_zero(&out); |
| 2095 | htmlTidy(blob_str(&in), &out); |
| 2096 | blob_reset(&in); |
| 2097 | fossil_puts(blob_str(&out), 0); |
| 2098 | blob_reset(&out); |
| 2099 | } |
| 2100 | } |
| 2101 | |
| 2102 | /* |
| 2103 | ** Remove all HTML markup from the input text. The output written into |
| 2104 | ** pOut is pure text. |
| 2105 | */ |
| 2106 | void html_to_plaintext(const char *zIn, Blob *pOut){ |
| 2107 | int n; |
| 2108 | int i, j; |
| 2109 | int nNL = 0; /* Number of \n characters at the end of pOut */ |
| 2110 | int nWS = 0; /* True if pOut ends with whitespace */ |
| 2111 | while( zIn[0] ){ |
| 2112 | n = nextHtmlToken(zIn); |
| 2113 | if( zIn[0]=='<' && n>1 ){ |
| 2114 | int isCloseTag; |
| 2115 | int eTag; |
| 2116 | int eType; |
| 2117 | char zTag[32]; |
| 2118 | isCloseTag = zIn[1]=='/'; |
| 2119 | for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ |
| 2120 | zTag[i] = fossil_tolower(zIn[j]); |
| 2121 | } |
| 2122 | zTag[i] = 0; |
| 2123 | eTag = findTag(zTag); |
| 2124 | eType = aMarkup[eTag].iType; |
| 2125 | if( eTag==MARKUP_INVALID && fossil_strnicmp(zIn,"<style",6)==0 ){ |
| 2126 | zIn += n; |
| 2127 | while( zIn[0] ){ |
| 2128 | n = nextHtmlToken(zIn); |
| 2129 | if( fossil_strnicmp(zIn, "</style",7)==0 ) break; |
| 2130 | zIn += n; |
| 2131 | } |
| 2132 | if( zIn[0]=='<' ) zIn += n; |
| 2133 | continue; |
| 2134 | } |
| 2135 | if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ |
| 2136 | if( nNL==0 ){ |
| 2137 | blob_append(pOut, "\n", 1); |
| 2138 | nNL++; |
| 2139 | } |
| 2140 | nWS = 1; |
| 2141 | } |
| 2142 | }else if( fossil_isspace(zIn[0]) ){ |
| 2143 | for(i=nNL=0; i<n; i++) if( zIn[i]=='\n' ) nNL++; |
| 2144 | if( !nWS ){ |
| 2145 | blob_append(pOut, nNL ? "\n" : " ", 1); |
| 2146 | nWS = 1; |
| 2147 | } |
| 2148 | }else{ |
| 2149 | blob_append(pOut, zIn, n); |
| 2150 | nNL = nWS = 0; |
| 2151 | } |
| 2152 | zIn += n; |
| 2153 | } |
| 2154 | if( nNL==0 ) blob_append(pOut, "\n", 1); |
| 2155 | } |
| 2156 | |
| 2157 | /* |
| 2158 | ** COMMAND: test-html-to-text |
| 2159 | */ |
| 2160 | void test_html_to_text(void){ |
| 2161 | Blob in, out; |
| 2162 | int i; |
| 2163 | |
| 2164 | for(i=2; i<g.argc; i++){ |
| 2165 | blob_read_from_file(&in, g.argv[i]); |
| 2166 | blob_zero(&out); |
| 2167 | html_to_plaintext(blob_str(&in), &out); |
| 2168 | blob_reset(&in); |
| 2169 | fossil_puts(blob_str(&out), 0); |
| 2170 | blob_reset(&out); |
| 2171 | } |
| 2172 | } |
| 2173 |
+1
-1
| --- win/Makefile.PellesCGMake | ||
| +++ win/Makefile.PellesCGMake | ||
| @@ -83,11 +83,11 @@ | ||
| 83 | 83 | |
| 84 | 84 | # define the SQLite files, which need special flags on compile |
| 85 | 85 | SQLITESRC=sqlite3.c |
| 86 | 86 | ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) |
| 87 | 87 | SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) |
| 88 | -SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_WIN32_NO_ANSI | |
| 88 | +SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_WIN32_NO_ANSI | |
| 89 | 89 | |
| 90 | 90 | # define the SQLite shell files, which need special flags on compile |
| 91 | 91 | SQLITESHELLSRC=shell.c |
| 92 | 92 | ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) |
| 93 | 93 | SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) |
| 94 | 94 |
| --- win/Makefile.PellesCGMake | |
| +++ win/Makefile.PellesCGMake | |
| @@ -83,11 +83,11 @@ | |
| 83 | |
| 84 | # define the SQLite files, which need special flags on compile |
| 85 | SQLITESRC=sqlite3.c |
| 86 | ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) |
| 87 | SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) |
| 88 | SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_WIN32_NO_ANSI |
| 89 | |
| 90 | # define the SQLite shell files, which need special flags on compile |
| 91 | SQLITESHELLSRC=shell.c |
| 92 | ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) |
| 93 | SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) |
| 94 |
| --- win/Makefile.PellesCGMake | |
| +++ win/Makefile.PellesCGMake | |
| @@ -83,11 +83,11 @@ | |
| 83 | |
| 84 | # define the SQLite files, which need special flags on compile |
| 85 | SQLITESRC=sqlite3.c |
| 86 | ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) |
| 87 | SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) |
| 88 | SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_WIN32_NO_ANSI |
| 89 | |
| 90 | # define the SQLite shell files, which need special flags on compile |
| 91 | SQLITESHELLSRC=shell.c |
| 92 | ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) |
| 93 | SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) |
| 94 |
+1
-1
| --- win/Makefile.dmc | ||
| +++ win/Makefile.dmc | ||
| @@ -24,11 +24,11 @@ | ||
| 24 | 24 | CFLAGS = -o |
| 25 | 25 | BCC = $(DMDIR)\bin\dmc $(CFLAGS) |
| 26 | 26 | TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) |
| 27 | 27 | LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 |
| 28 | 28 | |
| 29 | -SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS | |
| 29 | +SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 | |
| 30 | 30 | |
| 31 | 31 | SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 32 | 32 | |
| 33 | 33 | SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.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 path_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c |
| 34 | 34 | |
| 35 | 35 |
| --- win/Makefile.dmc | |
| +++ win/Makefile.dmc | |
| @@ -24,11 +24,11 @@ | |
| 24 | CFLAGS = -o |
| 25 | BCC = $(DMDIR)\bin\dmc $(CFLAGS) |
| 26 | TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) |
| 27 | LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 |
| 28 | |
| 29 | SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS |
| 30 | |
| 31 | SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 32 | |
| 33 | SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.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 path_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c |
| 34 | |
| 35 |
| --- win/Makefile.dmc | |
| +++ win/Makefile.dmc | |
| @@ -24,11 +24,11 @@ | |
| 24 | CFLAGS = -o |
| 25 | BCC = $(DMDIR)\bin\dmc $(CFLAGS) |
| 26 | TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) |
| 27 | LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 |
| 28 | |
| 29 | SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 |
| 30 | |
| 31 | SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen |
| 32 | |
| 33 | SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.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 path_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c |
| 34 | |
| 35 |
+1
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -2024,10 +2024,11 @@ | ||
| 2024 | 2024 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 2025 | 2025 | -DSQLITE_THREADSAFE=0 \ |
| 2026 | 2026 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 2027 | 2027 | -DSQLITE_OMIT_DEPRECATED \ |
| 2028 | 2028 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 2029 | + -DSQLITE_ENABLE_FTS4 \ | |
| 2029 | 2030 | -DSQLITE_WIN32_NO_ANSI \ |
| 2030 | 2031 | -D_HAVE__MINGW_H \ |
| 2031 | 2032 | -DSQLITE_USE_MALLOC_H \ |
| 2032 | 2033 | -DSQLITE_USE_MSIZE |
| 2033 | 2034 | |
| 2034 | 2035 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -2024,10 +2024,11 @@ | |
| 2024 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 2025 | -DSQLITE_THREADSAFE=0 \ |
| 2026 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 2027 | -DSQLITE_OMIT_DEPRECATED \ |
| 2028 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 2029 | -DSQLITE_WIN32_NO_ANSI \ |
| 2030 | -D_HAVE__MINGW_H \ |
| 2031 | -DSQLITE_USE_MALLOC_H \ |
| 2032 | -DSQLITE_USE_MSIZE |
| 2033 | |
| 2034 |
| --- win/Makefile.mingw | |
| +++ win/Makefile.mingw | |
| @@ -2024,10 +2024,11 @@ | |
| 2024 | -DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 2025 | -DSQLITE_THREADSAFE=0 \ |
| 2026 | -DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 2027 | -DSQLITE_OMIT_DEPRECATED \ |
| 2028 | -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 2029 | -DSQLITE_ENABLE_FTS4 \ |
| 2030 | -DSQLITE_WIN32_NO_ANSI \ |
| 2031 | -D_HAVE__MINGW_H \ |
| 2032 | -DSQLITE_USE_MALLOC_H \ |
| 2033 | -DSQLITE_USE_MSIZE |
| 2034 | |
| 2035 |
+1
| --- win/Makefile.msc | ||
| +++ win/Makefile.msc | ||
| @@ -189,10 +189,11 @@ | ||
| 189 | 189 | /DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 190 | 190 | /DSQLITE_THREADSAFE=0 \ |
| 191 | 191 | /DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 192 | 192 | /DSQLITE_OMIT_DEPRECATED \ |
| 193 | 193 | /DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 194 | + /DSQLITE_ENABLE_FTS4 \ | |
| 194 | 195 | /DSQLITE_WIN32_NO_ANSI |
| 195 | 196 | |
| 196 | 197 | SHELL_OPTIONS = /Dmain=sqlite3_shell \ |
| 197 | 198 | /DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 198 | 199 | /DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 199 | 200 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -189,10 +189,11 @@ | |
| 189 | /DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 190 | /DSQLITE_THREADSAFE=0 \ |
| 191 | /DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 192 | /DSQLITE_OMIT_DEPRECATED \ |
| 193 | /DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 194 | /DSQLITE_WIN32_NO_ANSI |
| 195 | |
| 196 | SHELL_OPTIONS = /Dmain=sqlite3_shell \ |
| 197 | /DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 198 | /DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 199 |
| --- win/Makefile.msc | |
| +++ win/Makefile.msc | |
| @@ -189,10 +189,11 @@ | |
| 189 | /DSQLITE_ENABLE_LOCKING_STYLE=0 \ |
| 190 | /DSQLITE_THREADSAFE=0 \ |
| 191 | /DSQLITE_DEFAULT_FILE_FORMAT=4 \ |
| 192 | /DSQLITE_OMIT_DEPRECATED \ |
| 193 | /DSQLITE_ENABLE_EXPLAIN_COMMENTS \ |
| 194 | /DSQLITE_ENABLE_FTS4 \ |
| 195 | /DSQLITE_WIN32_NO_ANSI |
| 196 | |
| 197 | SHELL_OPTIONS = /Dmain=sqlite3_shell \ |
| 198 | /DSQLITE_OMIT_LOAD_EXTENSION=1 \ |
| 199 | /DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ |
| 200 |
+20
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,7 +1,27 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | +<h2>Changes For Version 1.31 (2015-??-??)</h2> | |
| 4 | + * Add direct Subversion import support to | |
| 5 | + [/help?cmd=import|fossil import]. (??) | |
| 6 | + * Add IPv6 support to [/help?cmd=sync|fossil sync] and | |
| 7 | + [/help?cmd=clone|fossil clone] | |
| 8 | + * Add more skins such as "San Francisco Modern" and "Eagle". | |
| 9 | + * Update SQLite to version 3.8.9. (??) | |
| 10 | + * Improve the display of the history of changes to a single | |
| 11 | + file. A [/help?cmd=rebuild|fossil rebuild] is needed for that. | |
| 12 | + * Enable ZIP and Tarball downloads for user "nobody" by default. | |
| 13 | + * Make the now() SQL function available in the | |
| 14 | + [/help?cmd=sqlite|fossil sqlite] command. | |
| 15 | + * Improve table sorting in various pages. | |
| 16 | + * Add support for setting environment variables from within CGI script | |
| 17 | + files. | |
| 18 | + * Enhance the Ad-Unit processing to allow a choice of two different | |
| 19 | + ad-units. | |
| 20 | + * During shutdown, check to see if the check-out database (".fslckout") | |
| 21 | + contains a lot of free space, and if it does, VACUUM it. | |
| 22 | + | |
| 3 | 23 | <h2>Changes For Version 1.30 (2015-01-19)</h2> |
| 4 | 24 | * Added the [/help?cmd=bundle|fossil bundle] command. |
| 5 | 25 | * Added the [/help?cmd=purge|fossil purge] command. |
| 6 | 26 | * Added the [/help?cmd=publish|fossil publish] command. |
| 7 | 27 | * Added the [/help?cmd=unpublished|fossil unpublished] command. |
| 8 | 28 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,7 +1,27 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2>Changes For Version 1.30 (2015-01-19)</h2> |
| 4 | * Added the [/help?cmd=bundle|fossil bundle] command. |
| 5 | * Added the [/help?cmd=purge|fossil purge] command. |
| 6 | * Added the [/help?cmd=publish|fossil publish] command. |
| 7 | * Added the [/help?cmd=unpublished|fossil unpublished] command. |
| 8 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,7 +1,27 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2>Changes For Version 1.31 (2015-??-??)</h2> |
| 4 | * Add direct Subversion import support to |
| 5 | [/help?cmd=import|fossil import]. (??) |
| 6 | * Add IPv6 support to [/help?cmd=sync|fossil sync] and |
| 7 | [/help?cmd=clone|fossil clone] |
| 8 | * Add more skins such as "San Francisco Modern" and "Eagle". |
| 9 | * Update SQLite to version 3.8.9. (??) |
| 10 | * Improve the display of the history of changes to a single |
| 11 | file. A [/help?cmd=rebuild|fossil rebuild] is needed for that. |
| 12 | * Enable ZIP and Tarball downloads for user "nobody" by default. |
| 13 | * Make the now() SQL function available in the |
| 14 | [/help?cmd=sqlite|fossil sqlite] command. |
| 15 | * Improve table sorting in various pages. |
| 16 | * Add support for setting environment variables from within CGI script |
| 17 | files. |
| 18 | * Enhance the Ad-Unit processing to allow a choice of two different |
| 19 | ad-units. |
| 20 | * During shutdown, check to see if the check-out database (".fslckout") |
| 21 | contains a lot of free space, and if it does, VACUUM it. |
| 22 | |
| 23 | <h2>Changes For Version 1.30 (2015-01-19)</h2> |
| 24 | * Added the [/help?cmd=bundle|fossil bundle] command. |
| 25 | * Added the [/help?cmd=purge|fossil purge] command. |
| 26 | * Added the [/help?cmd=publish|fossil publish] command. |
| 27 | * Added the [/help?cmd=unpublished|fossil unpublished] command. |
| 28 |
+1
-1
| --- www/index.wiki | ||
| +++ www/index.wiki | ||
| @@ -23,11 +23,11 @@ | ||
| 23 | 23 | <li> [/timeline | Recent changes] |
| 24 | 24 | <li> [./faq.wiki | FAQ] |
| 25 | 25 | <li> [./hacker-howto.wiki | Hacker How-To] |
| 26 | 26 | <li> [./changes.wiki | Change Log] |
| 27 | 27 | <li> [./hints.wiki | Tip & Hints] |
| 28 | -<li> [./permutedindex.wiki | Documentation Index] | |
| 28 | +<li> [./permutedindex.html | Documentation Index] | |
| 29 | 29 | <li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book] |
| 30 | 30 | <li> Mailing list |
| 31 | 31 | <ul> |
| 32 | 32 | <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up] |
| 33 | 33 | <li> [http://www.mail-archive.com/[email protected] | archives] |
| 34 | 34 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -23,11 +23,11 @@ | |
| 23 | <li> [/timeline | Recent changes] |
| 24 | <li> [./faq.wiki | FAQ] |
| 25 | <li> [./hacker-howto.wiki | Hacker How-To] |
| 26 | <li> [./changes.wiki | Change Log] |
| 27 | <li> [./hints.wiki | Tip & Hints] |
| 28 | <li> [./permutedindex.wiki | Documentation Index] |
| 29 | <li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book] |
| 30 | <li> Mailing list |
| 31 | <ul> |
| 32 | <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up] |
| 33 | <li> [http://www.mail-archive.com/[email protected] | archives] |
| 34 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -23,11 +23,11 @@ | |
| 23 | <li> [/timeline | Recent changes] |
| 24 | <li> [./faq.wiki | FAQ] |
| 25 | <li> [./hacker-howto.wiki | Hacker How-To] |
| 26 | <li> [./changes.wiki | Change Log] |
| 27 | <li> [./hints.wiki | Tip & Hints] |
| 28 | <li> [./permutedindex.html | Documentation Index] |
| 29 | <li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book] |
| 30 | <li> Mailing list |
| 31 | <ul> |
| 32 | <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up] |
| 33 | <li> [http://www.mail-archive.com/[email protected] | archives] |
| 34 |
+7
-6
| --- www/makefile.wiki | ||
| +++ www/makefile.wiki | ||
| @@ -224,21 +224,22 @@ | ||
| 224 | 224 | |
| 225 | 225 | Some files require special C-preprocessor macro definitions. |
| 226 | 226 | When compiling sqlite.c, the following macros are recommended: |
| 227 | 227 | |
| 228 | 228 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 229 | + * -DSQLITE_ENABLE_FTS4=1 | |
| 229 | 230 | * -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 230 | 231 | * -DSQLITE_THREADSAFE=0 |
| 231 | 232 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 232 | 233 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 233 | 234 | |
| 234 | -The first symbol definition above is required; the others | |
| 235 | -are merely recommended. Extension loading is omitted | |
| 236 | -as a security measure. Fossil is single-threaded so mutexing is disabled | |
| 237 | -in SQLite as a performance enhancement. The SQLITE_ENABLE_EXPLAIN_COMMENTS | |
| 238 | -option makes the output of "EXPLAIN" queries in the | |
| 239 | -"[/help?cmd=sqlite3|fossil sql]" command much more readable. | |
| 235 | +The first two symbol definitions above are required; the others are merely | |
| 236 | +recommended. Extension loading is omitted as a security measure. FTS4 is | |
| 237 | +needed for the search feature. Fossil is single-threaded so mutexing is | |
| 238 | +disabled in SQLite as a performance enhancement. The | |
| 239 | +SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries | |
| 240 | +in the "[/help?cmd=sqlite3|fossil sql]" command much more readable. | |
| 240 | 241 | |
| 241 | 242 | When compiling the shell.c source file, these macros are required: |
| 242 | 243 | |
| 243 | 244 | * -Dmain=sqlite3_main |
| 244 | 245 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 245 | 246 |
| --- www/makefile.wiki | |
| +++ www/makefile.wiki | |
| @@ -224,21 +224,22 @@ | |
| 224 | |
| 225 | Some files require special C-preprocessor macro definitions. |
| 226 | When compiling sqlite.c, the following macros are recommended: |
| 227 | |
| 228 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 229 | * -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 230 | * -DSQLITE_THREADSAFE=0 |
| 231 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 232 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 233 | |
| 234 | The first symbol definition above is required; the others |
| 235 | are merely recommended. Extension loading is omitted |
| 236 | as a security measure. Fossil is single-threaded so mutexing is disabled |
| 237 | in SQLite as a performance enhancement. The SQLITE_ENABLE_EXPLAIN_COMMENTS |
| 238 | option makes the output of "EXPLAIN" queries in the |
| 239 | "[/help?cmd=sqlite3|fossil sql]" command much more readable. |
| 240 | |
| 241 | When compiling the shell.c source file, these macros are required: |
| 242 | |
| 243 | * -Dmain=sqlite3_main |
| 244 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 245 |
| --- www/makefile.wiki | |
| +++ www/makefile.wiki | |
| @@ -224,21 +224,22 @@ | |
| 224 | |
| 225 | Some files require special C-preprocessor macro definitions. |
| 226 | When compiling sqlite.c, the following macros are recommended: |
| 227 | |
| 228 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 229 | * -DSQLITE_ENABLE_FTS4=1 |
| 230 | * -DSQLITE_ENABLE_LOCKING_STYLE=0 |
| 231 | * -DSQLITE_THREADSAFE=0 |
| 232 | * -DSQLITE_DEFAULT_FILE_FORMAT=4 |
| 233 | * -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1 |
| 234 | |
| 235 | The first two symbol definitions above are required; the others are merely |
| 236 | recommended. Extension loading is omitted as a security measure. FTS4 is |
| 237 | needed for the search feature. Fossil is single-threaded so mutexing is |
| 238 | disabled in SQLite as a performance enhancement. The |
| 239 | SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries |
| 240 | in the "[/help?cmd=sqlite3|fossil sql]" command much more readable. |
| 241 | |
| 242 | When compiling the shell.c source file, these macros are required: |
| 243 | |
| 244 | * -Dmain=sqlite3_main |
| 245 | * -DSQLITE_OMIT_LOAD_EXTENSION=1 |
| 246 |
+1
-1
| --- www/mkdownload.tcl | ||
| +++ www/mkdownload.tcl | ||
| @@ -26,11 +26,11 @@ | ||
| 26 | 26 | <div class="mainmenu"> |
| 27 | 27 | <a href='/fossil/doc/trunk/www/index.wiki'>Home</a> |
| 28 | 28 | <a href='/fossil/timeline?y=ci'>Timeline</a> |
| 29 | 29 | <a href='/download.html'>Download</a> |
| 30 | 30 | <a href='/fossil/dir?ci=trunk'>Code</a> |
| 31 | -<a href='/fossil/doc/trunk/www/permutedindex.wiki'>Documentation</a> | |
| 31 | +<a href='/fossil/doc/trunk/www/permutedindex.html'>Documentation</a> | |
| 32 | 32 | <a href='/fossil/brlist'>Branches</a> |
| 33 | 33 | <a href='/fossil/taglist'>Tags</a> |
| 34 | 34 | <a href='/fossil/reportlist'>Tickets</a> |
| 35 | 35 | </div> |
| 36 | 36 | <div class="content"> |
| 37 | 37 |
| --- www/mkdownload.tcl | |
| +++ www/mkdownload.tcl | |
| @@ -26,11 +26,11 @@ | |
| 26 | <div class="mainmenu"> |
| 27 | <a href='/fossil/doc/trunk/www/index.wiki'>Home</a> |
| 28 | <a href='/fossil/timeline?y=ci'>Timeline</a> |
| 29 | <a href='/download.html'>Download</a> |
| 30 | <a href='/fossil/dir?ci=trunk'>Code</a> |
| 31 | <a href='/fossil/doc/trunk/www/permutedindex.wiki'>Documentation</a> |
| 32 | <a href='/fossil/brlist'>Branches</a> |
| 33 | <a href='/fossil/taglist'>Tags</a> |
| 34 | <a href='/fossil/reportlist'>Tickets</a> |
| 35 | </div> |
| 36 | <div class="content"> |
| 37 |
| --- www/mkdownload.tcl | |
| +++ www/mkdownload.tcl | |
| @@ -26,11 +26,11 @@ | |
| 26 | <div class="mainmenu"> |
| 27 | <a href='/fossil/doc/trunk/www/index.wiki'>Home</a> |
| 28 | <a href='/fossil/timeline?y=ci'>Timeline</a> |
| 29 | <a href='/download.html'>Download</a> |
| 30 | <a href='/fossil/dir?ci=trunk'>Code</a> |
| 31 | <a href='/fossil/doc/trunk/www/permutedindex.html'>Documentation</a> |
| 32 | <a href='/fossil/brlist'>Branches</a> |
| 33 | <a href='/fossil/taglist'>Tags</a> |
| 34 | <a href='/fossil/reportlist'>Tickets</a> |
| 35 | </div> |
| 36 | <div class="content"> |
| 37 |
+18
-10
| --- www/mkindex.tcl | ||
| +++ www/mkindex.tcl | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | #!/bin/sh |
| 2 | 2 | # |
| 3 | 3 | # Run this TCL script to generate a WIKI page that contains a |
| 4 | 4 | # permuted index of the various documentation files. |
| 5 | 5 | # |
| 6 | -# tclsh mkindex.tcl >permutedindex.wiki | |
| 6 | +# tclsh mkindex.tcl >permutedindex.html | |
| 7 | 7 | # |
| 8 | 8 | |
| 9 | 9 | set doclist { |
| 10 | 10 | adding_code.wiki {Adding New Features To Fossil} |
| 11 | 11 | adding_code.wiki {Hacking Fossil} |
| @@ -77,26 +77,34 @@ | ||
| 77 | 77 | lappend permindex [list "$suffix — $prefix" $file] |
| 78 | 78 | } |
| 79 | 79 | } |
| 80 | 80 | } |
| 81 | 81 | set permindex [lsort -dict -index 0 $permindex] |
| 82 | -set out [open permutedindex.wiki w] | |
| 82 | +set out [open permutedindex.html w] | |
| 83 | 83 | fconfigure $out -encoding utf-8 -translation lf |
| 84 | -puts $out "<title>Index Of Fossil Documentation</title>" | |
| 84 | +puts $out \ | |
| 85 | +"<div class='fossil-doc' data-title='Index Of Fossil Documentation'>" | |
| 85 | 86 | puts $out { |
| 87 | +<center> | |
| 88 | +<form action='../../../docsrch' method='GET'> | |
| 89 | +<input type="text" name="s" size="40"> | |
| 90 | +<input type="submit" value="Search Docs"> | |
| 91 | +</form> | |
| 92 | +</center> | |
| 86 | 93 | <h2>Primary Documents:</h2> |
| 87 | 94 | <ul> |
| 88 | -<li> [./quickstart.wiki | Quick-start Guide] | |
| 89 | -<li> [./faq.wiki | FAQ] | |
| 90 | -<li> [./build.wiki | Compiling and installing Fossil] | |
| 91 | -<li> [../COPYRIGHT-BSD2.txt | License] | |
| 92 | -<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book] | |
| 93 | -<li> [/help | Command-line help] | |
| 95 | +<li> <a href='quickstart.wiki'>Quick-start Guide</a> | |
| 96 | +<li> <a href='faq.wiki'>FAQ</a> | |
| 97 | +<li> <a href='build.wiki'>Compiling and installing Fossil</a> | |
| 98 | +<li> <a href='COPYRIGHT-BSD2.txt'>License</a> | |
| 99 | +<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's | |
| 100 | +book</a> | |
| 101 | +<li> <a href='../../help'>Command-line help</a> | |
| 94 | 102 | </ul> |
| 95 | 103 | <a name="pindex"></a> |
| 96 | 104 | <h2>Permuted Index:</h2> |
| 97 | 105 | <ul>} |
| 98 | 106 | foreach entry $permindex { |
| 99 | 107 | foreach {title file} $entry break |
| 100 | 108 | puts $out "<li><a href=\"$file\">$title</a></li>" |
| 101 | 109 | } |
| 102 | -puts $out "</ul>" | |
| 110 | +puts $out "</ul></div>" | |
| 103 | 111 | |
| 104 | 112 | ADDED www/permutedindex.html |
| 105 | 113 | DELETED www/permutedindex.wiki |
| --- www/mkindex.tcl | |
| +++ www/mkindex.tcl | |
| @@ -1,11 +1,11 @@ | |
| 1 | #!/bin/sh |
| 2 | # |
| 3 | # Run this TCL script to generate a WIKI page that contains a |
| 4 | # permuted index of the various documentation files. |
| 5 | # |
| 6 | # tclsh mkindex.tcl >permutedindex.wiki |
| 7 | # |
| 8 | |
| 9 | set doclist { |
| 10 | adding_code.wiki {Adding New Features To Fossil} |
| 11 | adding_code.wiki {Hacking Fossil} |
| @@ -77,26 +77,34 @@ | |
| 77 | lappend permindex [list "$suffix — $prefix" $file] |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | set permindex [lsort -dict -index 0 $permindex] |
| 82 | set out [open permutedindex.wiki w] |
| 83 | fconfigure $out -encoding utf-8 -translation lf |
| 84 | puts $out "<title>Index Of Fossil Documentation</title>" |
| 85 | puts $out { |
| 86 | <h2>Primary Documents:</h2> |
| 87 | <ul> |
| 88 | <li> [./quickstart.wiki | Quick-start Guide] |
| 89 | <li> [./faq.wiki | FAQ] |
| 90 | <li> [./build.wiki | Compiling and installing Fossil] |
| 91 | <li> [../COPYRIGHT-BSD2.txt | License] |
| 92 | <li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book] |
| 93 | <li> [/help | Command-line help] |
| 94 | </ul> |
| 95 | <a name="pindex"></a> |
| 96 | <h2>Permuted Index:</h2> |
| 97 | <ul>} |
| 98 | foreach entry $permindex { |
| 99 | foreach {title file} $entry break |
| 100 | puts $out "<li><a href=\"$file\">$title</a></li>" |
| 101 | } |
| 102 | puts $out "</ul>" |
| 103 | |
| 104 | DDED www/permutedindex.html |
| 105 | ELETED www/permutedindex.wiki |
| --- www/mkindex.tcl | |
| +++ www/mkindex.tcl | |
| @@ -1,11 +1,11 @@ | |
| 1 | #!/bin/sh |
| 2 | # |
| 3 | # Run this TCL script to generate a WIKI page that contains a |
| 4 | # permuted index of the various documentation files. |
| 5 | # |
| 6 | # tclsh mkindex.tcl >permutedindex.html |
| 7 | # |
| 8 | |
| 9 | set doclist { |
| 10 | adding_code.wiki {Adding New Features To Fossil} |
| 11 | adding_code.wiki {Hacking Fossil} |
| @@ -77,26 +77,34 @@ | |
| 77 | lappend permindex [list "$suffix — $prefix" $file] |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | set permindex [lsort -dict -index 0 $permindex] |
| 82 | set out [open permutedindex.html w] |
| 83 | fconfigure $out -encoding utf-8 -translation lf |
| 84 | puts $out \ |
| 85 | "<div class='fossil-doc' data-title='Index Of Fossil Documentation'>" |
| 86 | puts $out { |
| 87 | <center> |
| 88 | <form action='../../../docsrch' method='GET'> |
| 89 | <input type="text" name="s" size="40"> |
| 90 | <input type="submit" value="Search Docs"> |
| 91 | </form> |
| 92 | </center> |
| 93 | <h2>Primary Documents:</h2> |
| 94 | <ul> |
| 95 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 96 | <li> <a href='faq.wiki'>FAQ</a> |
| 97 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 98 | <li> <a href='COPYRIGHT-BSD2.txt'>License</a> |
| 99 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 100 | book</a> |
| 101 | <li> <a href='../../help'>Command-line help</a> |
| 102 | </ul> |
| 103 | <a name="pindex"></a> |
| 104 | <h2>Permuted Index:</h2> |
| 105 | <ul>} |
| 106 | foreach entry $permindex { |
| 107 | foreach {title file} $entry break |
| 108 | puts $out "<li><a href=\"$file\">$title</a></li>" |
| 109 | } |
| 110 | puts $out "</ul></div>" |
| 111 | |
| 112 | DDED www/permutedindex.html |
| 113 | ELETED www/permutedindex.wiki |
+3
-6
| --- a/www/permutedindex.html | ||
| +++ b/www/permutedindex.html | ||
| @@ -1,8 +1,5 @@ | ||
| 1 | -<title>p'>Site Mapname GLOBLicCommanp'>Site Mmap'>Site Ma[./quickstart.wiki | Quick-start Guide] | |
| 2 | -<li> [./faq.wiki | FAQ] | |
| 3 | -<li> [./build.wiki | g ruleswiki_rules'>Wikirmatting] | |
| 4 | -<li> [../COPYRIGHTmand-line md_ru | Jim Schimpf's book] | |
| 5 | -<li> nd-line h2>Canonical Index:</h2> | |
| 1 | +p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting rulcenter> | |
| 2 | +<formte Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswikrmatting ruleswiki_rules'>Wiki formatting ru/center ruiki formatting rulessitema/centeremap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wikirmatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>e Mapname GLOBLOB nLicCommand-line h2>Canonical Index:</h2> | |
| 6 | 3 | (lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 7 | 4 | </ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> |
| 8 | 5 | </ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| @@ -10,4 +7,4 @@ | ||
| 10 | 7 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> |
| 11 | 8 | u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 12 | 9 | </ul></div> |
| 13 | -dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS | |
| 10 | +dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
| --- a/www/permutedindex.html | |
| +++ b/www/permutedindex.html | |
| @@ -1,8 +1,5 @@ | |
| 1 | <title>p'>Site Mapname GLOBLicCommanp'>Site Mmap'>Site Ma[./quickstart.wiki | Quick-start Guide] |
| 2 | <li> [./faq.wiki | FAQ] |
| 3 | <li> [./build.wiki | g ruleswiki_rules'>Wikirmatting] |
| 4 | <li> [../COPYRIGHTmand-line md_ru | Jim Schimpf's book] |
| 5 | <li> nd-line h2>Canonical Index:</h2> |
| 6 | (lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 7 | </ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> |
| 8 | </ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| @@ -10,4 +7,4 @@ | |
| 10 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> |
| 11 | u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 12 | </ul></div> |
| 13 | dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
| --- a/www/permutedindex.html | |
| +++ b/www/permutedindex.html | |
| @@ -1,8 +1,5 @@ | |
| 1 | p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting rulcenter> |
| 2 | <formte Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswikrmatting ruleswiki_rules'>Wiki formatting ru/center ruiki formatting rulessitema/centeremap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wikirmatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>e Mapname GLOBLOB nLicCommand-line h2>Canonical Index:</h2> |
| 3 | (lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 4 | </ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> |
| 5 | </ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| @@ -10,4 +7,4 @@ | |
| 7 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> |
| 8 | u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 9 | </ul></div> |
| 10 | dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
+10
| --- a/www/permutedindex.html | ||
| +++ b/www/permutedindex.html | ||
| @@ -0,0 +1,10 @@ | ||
| 1 | +p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting rulcenter> | |
| 2 | +<formte Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswikrmatting ruleswiki_rules'>Wiki formatting ru/center ruiki formatting rulessitema/centeremap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wikirmatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>e Mapname GLOBLOB nLicCommand-line h2>Canonical Index:</h2> | |
| 3 | +(lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 4 | +</ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> | |
| 5 | +</ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 6 | +</ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wikiline webpage-ex.Guideould UseWhlessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi L-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 7 | +</ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> | |
| 8 | +u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 9 | +</ul></div> | |
| 10 | +dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
| --- a/www/permutedindex.html | |
| +++ b/www/permutedindex.html | |
| @@ -0,0 +1,10 @@ | |
| --- a/www/permutedindex.html | |
| +++ b/www/permutedindex.html | |
| @@ -0,0 +1,10 @@ | |
| 1 | p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting rulcenter> |
| 2 | <formte Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswikrmatting ruleswiki_rules'>Wiki formatting ru/center ruiki formatting rulessitema/centeremap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wikirmatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>e Mapname GLOBLOB nLicCommand-line h2>Canonical Index:</h2> |
| 3 | (lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 4 | </ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> |
| 5 | </ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 6 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wikiline webpage-ex.Guideould UseWhlessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi L-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 7 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> |
| 8 | u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 9 | </ul></div> |
| 10 | dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
D
www/permutedindex.wiki
-13
| --- a/www/permutedindex.wiki | ||
| +++ b/www/permutedindex.wiki | ||
| @@ -1,13 +0,0 @@ | ||
| 1 | -<title>p'>Site Mapname GLOBLicCommanp'>Site Mmap'>Site Ma[./quickstart.wiki | Quick-start Guide] | |
| 2 | -<li> [./faq.wiki | FAQ] | |
| 3 | -<li> [./build.wiki | g ruleswiki_rules'>Wikirmatting] | |
| 4 | -<li> [../COPYRIGHTmand-line md_ru | Jim Schimpf's book] | |
| 5 | -<li> nd-line h2>Canonical Index:</h2> | |
| 6 | -(lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 7 | -</ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> | |
| 8 | -</ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 9 | -</ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wikiline webpage-ex.Guideould UseWhlessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi L-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 10 | -</ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> | |
| 11 | -u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> | |
| 12 | -</ul></div> | |
| 13 | -dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
| --- a/www/permutedindex.wiki | |
| +++ b/www/permutedindex.wiki | |
| @@ -1,13 +0,0 @@ | |
| 1 | <title>p'>Site Mapname GLOBLicCommanp'>Site Mmap'>Site Ma[./quickstart.wiki | Quick-start Guide] |
| 2 | <li> [./faq.wiki | FAQ] |
| 3 | <li> [./build.wiki | g ruleswiki_rules'>Wikirmatting] |
| 4 | <li> [../COPYRIGHTmand-line md_ru | Jim Schimpf's book] |
| 5 | <li> nd-line h2>Canonical Index:</h2> |
| 6 | (lketsicket LicCommand-line md_rul LicCommand-name GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikia><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 7 | </ul>_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Shd_rulesdatep'>ncep md_rulesould UseWhy</a></li> |
| 8 | </ul></div>iinutes.wiki"wn formatting rules md_rulesdatepiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a><cCommand-line md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formaUse<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 9 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f p'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wikiline webpage-ex.Guideould UseWhlessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCS_ticketicket ticketsicket LicCommand-line md_rul LicCommand-lin Customizing thints.wiki">TipdatA dding_code.wiki">Adding >Site Mapname GLOBLicCp'>Site Ma"><b>Wiki In Pag.wiki"><b>Wiki In PagabfitVersion NameCheck-in Afossil-v-git.wiki">Versus webui.wiki">Web Interface GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi L-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wiki"><b>Wiki In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 10 | </ul></div>iinutes.wiki">wn formatting rules md_rulesdatep'>ncep md_rulesdatep'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLicCommand-line md_rules'>Markdown f /ul></div> |
| 11 | u Should UseWhy</a><'>Site Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBLOB nLi LicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting ruleste Mapname GLOBLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> Commandqu./../../help"> CommandquoQuot In Pagaboutcgi.wiki"> CGIYou Should UseWhy</a></li> |
| 12 | </ul></div> |
| 13 | dates And Usage Hin 5 LicCommand-line LicCommand-line md_rulesdatep'>Site Maprkdown formattin Taggrul GLOBLOB nLicCommand-line md_rules'>Markdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Maprkdown formatting ruleswiki_rules'>Wiki formatting rulessitemap'>Site Mapname GLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../help"> CommandquoQuotes:Why You Should Use<b>Wikiwikitheory.wikLOBMarkdThe Fossil DVCCheck-in And Version Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCSion Namesheckin.wiki">Check-in Checklistheckiniki"><b>Unversioned FilefCheck-innversioned Filefivemin SSL withenvantibot.wiki">against opts.md">Variables and LicCommand-line md_ruChild Projectst.wiki">Versus webui.wiki">Web Interface LicCommand-line md_rul LicCommand-line webpage-ex.md"><b>Webpage Example../../../htheory1mmand-l DVCS |
| --- a/www/permutedindex.wiki | |
| +++ b/www/permutedindex.wiki | |
| @@ -1,13 +0,0 @@ | |
+1
-1
| --- www/quickstart.wiki | ||
| +++ www/quickstart.wiki | ||
| @@ -387,9 +387,9 @@ | ||
| 387 | 387 | |
| 388 | 388 | <h2>More Hints</h2> |
| 389 | 389 | |
| 390 | 390 | <p>A [/help | complete list of commands] is available, as is the |
| 391 | 391 | [./hints.wiki|helpful hints] document. See the |
| 392 | - [./permutedindex.wiki#pindex|permuted index] for additional | |
| 392 | + [./permutedindex.html#pindex|permuted index] for additional | |
| 393 | 393 | documentation. |
| 394 | 394 | |
| 395 | 395 | <p>Explore and have fun!</p> |
| 396 | 396 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -387,9 +387,9 @@ | |
| 387 | |
| 388 | <h2>More Hints</h2> |
| 389 | |
| 390 | <p>A [/help | complete list of commands] is available, as is the |
| 391 | [./hints.wiki|helpful hints] document. See the |
| 392 | [./permutedindex.wiki#pindex|permuted index] for additional |
| 393 | documentation. |
| 394 | |
| 395 | <p>Explore and have fun!</p> |
| 396 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -387,9 +387,9 @@ | |
| 387 | |
| 388 | <h2>More Hints</h2> |
| 389 | |
| 390 | <p>A [/help | complete list of commands] is available, as is the |
| 391 | [./hints.wiki|helpful hints] document. See the |
| 392 | [./permutedindex.html#pindex|permuted index] for additional |
| 393 | documentation. |
| 394 | |
| 395 | <p>Explore and have fun!</p> |
| 396 |