Fossil SCM

Merge from trunk.

dg 2015-02-03 20:46 dg-skin-reset merge
Commit fc0b5efb8dcd6245b4fd58d45587c1f9e1adb3c2
+1 -1
--- auto.def
+++ auto.def
@@ -68,11 +68,11 @@
6868
6969
find_internal_sqlite
7070
}
7171
7272
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__}
7474
}
7575
7676
if {[opt-bool fossil-debug]} {
7777
define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
7878
msg-result "Debugging support enabled"
7979
--- 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
--- skins/black_and_white/header.txt
+++ skins/black_and_white/header.txt
@@ -34,11 +34,11 @@
3434
if {[hascap o]} {
3535
html "<a href='$home/brlist'>Branches</a>\n"
3636
html "<a href='$home/taglist'>Tags</a>\n"
3737
}
3838
if {[hascap r]} {
39
- html "<a href='$home/reportlist'>Tickets</a>\n"
39
+ html "<a href='$home/ticket'>Tickets</a>\n"
4040
}
4141
if {[hascap j]} {
4242
html "<a href='$home/wiki'>Wiki</a>\n"
4343
}
4444
if {[hascap s]} {
4545
--- 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
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -33,11 +33,11 @@
3333
if {[hascap o]} {
3434
html "<a href='$home/brlist'>Branches</a>\n"
3535
html "<a href='$home/taglist'>Tags</a>\n"
3636
}
3737
if {[hascap r]} {
38
- html "<a href='$home/reportlist'>Tickets</a>\n"
38
+ html "<a href='$home/ticket'>Tickets</a>\n"
3939
}
4040
if {[hascap j]} {
4141
html "<a href='$home/wiki'>Wiki</a>\n"
4242
}
4343
if {[hascap s]} {
4444
--- 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
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -187,11 +187,11 @@
187187
188188
/* format for the user list table on the user setup page */
189189
table.usetupUserList {
190190
outline-style: double;
191191
outline-width: 1px;
192
- border-color: white;
192
+ outline-color: white;
193193
padding: 10px;
194194
}
195195
196196
/* color for capabilities, inherited by reader */
197197
span.ueditInheritReader {
198198
--- 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
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -114,11 +114,11 @@
114114
if {[hascap o]} {
115115
html "<a href='$home/brlist'>Branches</a>\n"
116116
html "<a href='$home/taglist'>Tags</a>\n"
117117
}
118118
if {[hascap r]} {
119
- html "<a href='$home/reportlist'>Tickets</a>\n"
119
+ html "<a href='$home/ticket'>Tickets</a>\n"
120120
}
121121
if {[hascap j]} {
122122
html "<a href='$home/wiki'>Wiki</a>\n"
123123
}
124124
if {[hascap s]} {
125125
--- 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
--- skins/enhanced1/header.txt
+++ skins/enhanced1/header.txt
@@ -114,11 +114,11 @@
114114
if {[hascap o]} {
115115
html "<a href='$home/brlist'>Branches</a>\n"
116116
html "<a href='$home/taglist'>Tags</a>\n"
117117
}
118118
if {[hascap r]} {
119
- html "<a href='$home/reportlist'>Tickets</a>\n"
119
+ html "<a href='$home/ticket'>Tickets</a>\n"
120120
}
121121
if {[hascap j]} {
122122
html "<a href='$home/wiki'>Wiki</a>\n"
123123
}
124124
if {[hascap s]} {
125125
--- 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
--- skins/khaki/header.txt
+++ skins/khaki/header.txt
@@ -32,11 +32,11 @@
3232
if {[hascap o]} {
3333
html "<a href='$home/brlist'>Branches</a>\n"
3434
html "<a href='$home/taglist'>Tags</a>\n"
3535
}
3636
if {[hascap r]} {
37
- html "<a href='$home/reportlist'>Tickets</a>\n"
37
+ html "<a href='$home/ticket'>Tickets</a>\n"
3838
}
3939
if {[hascap j]} {
4040
html "<a href='$home/wiki'>Wiki</a>\n"
4141
}
4242
if {[hascap s]} {
4343
--- 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
--- skins/plain_gray/header.txt
+++ skins/plain_gray/header.txt
@@ -30,11 +30,11 @@
3030
if {[hascap o]} {
3131
html "<a href='$home/brlist'>Branches</a>\n"
3232
html "<a href='$home/taglist'>Tags</a>\n"
3333
}
3434
if {[hascap r]} {
35
- html "<a href='$home/reportlist'>Tickets</a>\n"
35
+ html "<a href='$home/ticket'>Tickets</a>\n"
3636
}
3737
if {[hascap j]} {
3838
html "<a href='$home/wiki'>Wiki</a>\n"
3939
}
4040
if {[hascap s]} {
4141
--- 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
--- skins/rounded1/header.txt
+++ skins/rounded1/header.txt
@@ -34,11 +34,11 @@
3434
if {[hascap o]} {
3535
html "<a href='$home/brlist'>Branches</a>\n"
3636
html "<a href='$home/taglist'>Tags</a>\n"
3737
}
3838
if {[hascap r]} {
39
- html "<a href='$home/reportlist'>Tickets</a>\n"
39
+ html "<a href='$home/ticket'>Tickets</a>\n"
4040
}
4141
if {[hascap j]} {
4242
html "<a href='$home/wiki'>Wiki</a>\n"
4343
}
4444
if {[hascap s]} {
4545
--- 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
--- src/allrepo.c
+++ src/allrepo.c
@@ -229,15 +229,20 @@
229229
collect_argument(&extra, "vacuum",0);
230230
collect_argument(&extra, "deanalyze",0);
231231
collect_argument(&extra, "analyze",0);
232232
collect_argument(&extra, "wal",0);
233233
collect_argument(&extra, "stats",0);
234
+ collect_argument(&extra, "index",0);
235
+ collect_argument(&extra, "no-index",0);
234236
}else if( strncmp(zCmd, "setting", n)==0 ){
235237
zCmd = "setting -R";
236238
collect_argv(&extra, 3);
237239
}else if( strncmp(zCmd, "unset", n)==0 ){
238240
zCmd = "unset -R";
241
+ collect_argv(&extra, 3);
242
+ }else if( strncmp(zCmd, "fts-config", n)==0 ){
243
+ zCmd = "fts-config -R";
239244
collect_argv(&extra, 3);
240245
}else if( strncmp(zCmd, "sync", n)==0 ){
241246
zCmd = "sync -autourl -R";
242247
collect_argument(&extra, "verbose","v");
243248
}else if( strncmp(zCmd, "test-integrity", n)==0 ){
244249
--- 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 @@
138138
139139
if( g.argc < 4 ){
140140
usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
141141
}
142142
db_open_config(0);
143
- if( file_size(g.argv[3])>0 ){
143
+ if( -1 != file_size(g.argv[3]) ){
144144
fossil_fatal("file already exists: %s", g.argv[3]);
145145
}
146146
147147
url_parse(g.argv[2], urlFlags);
148148
if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
149149
--- 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
+139 -88
--- src/db.c
+++ src/db.c
@@ -547,12 +547,13 @@
547547
va_end(ap);
548548
z = blob_str(&sql);
549549
while( rc==SQLITE_OK && z[0] ){
550550
pStmt = 0;
551551
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 ){
554555
db.nPrepare++;
555556
while( sqlite3_step(pStmt)==SQLITE_ROW ){}
556557
rc = sqlite3_finalize(pStmt);
557558
if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
558559
}
@@ -1197,11 +1198,11 @@
11971198
*/
11981199
if( !db_table_has_column("repository","mlink","isaux") ){
11991200
db_begin_transaction();
12001201
db_multi_exec(
12011202
"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;",
12031204
db_name("repository"), db_name("repository")
12041205
);
12051206
db_end_transaction(0);
12061207
}
12071208
}
@@ -1380,15 +1381,10 @@
13801381
while( db.pAllStmt ){
13811382
db_finalize(db.pAllStmt);
13821383
}
13831384
db_end_transaction(1);
13841385
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
- }
13901386
db_close_config();
13911387
13921388
/* If the localdb (the check-out database) is open and if it has
13931389
** a lot of unused free space, then VACUUM it as we shut down.
13941390
*/
@@ -1399,12 +1395,18 @@
13991395
db_multi_exec("VACUUM;");
14001396
}
14011397
}
14021398
14031399
if( g.db ){
1400
+ int rc;
14041401
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
+ }
14061408
g.db = 0;
14071409
g.zMainDbType = 0;
14081410
}
14091411
g.repositoryOpen = 0;
14101412
g.localOpen = 0;
@@ -1520,12 +1522,12 @@
15201522
int i;
15211523
const char *zSep = "";
15221524
15231525
blob_zero(&x);
15241526
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);
15271529
zSep = ",";
15281530
}
15291531
blob_append_sql(&x, ")");
15301532
return blob_sql_text(&x);
15311533
}
@@ -1682,10 +1684,15 @@
16821684
verify_all_options();
16831685
16841686
if( g.argc!=3 ){
16851687
usage("REPOSITORY-NAME");
16861688
}
1689
+
1690
+ if( -1 != file_size(g.argv[2]) ){
1691
+ fossil_fatal("file already exists: %s", g.argv[2]);
1692
+ }
1693
+
16871694
db_create_repository(g.argv[2]);
16881695
db_open_repository(g.argv[2]);
16891696
db_open_config(0);
16901697
if( zTemplate ) db_attach(zTemplate, "settingSrc");
16911698
db_begin_transaction();
@@ -1913,16 +1920,21 @@
19131920
g.zConfigDbType = zTempDbType;
19141921
}
19151922
}
19161923
19171924
/*
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.
19221934
*/
1923
-char *db_get_do_versionable(const char *zName, char *zNonVersionedSetting){
1935
+char *db_get_versioned(const char *zName, char *zNonVersionedSetting){
19241936
char *zVersionedSetting = 0;
19251937
int noWarn = 0;
19261938
struct _cacheEntry {
19271939
struct _cacheEntry *next;
19281940
const char *zName, *zValue;
@@ -1993,37 +2005,38 @@
19932005
19942006
19952007
/*
19962008
** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
19972009
** 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.
19982015
*/
19992016
char *db_get(const char *zName, char *zDefault){
20002017
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);
20102019
if( g.repositoryOpen ){
20112020
z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
20122021
}
20132022
if( z==0 && g.zConfigDbName ){
20142023
db_swap_connections();
20152024
z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
20162025
db_swap_connections();
20172026
}
2018
- if( ctrlSetting!=0 && ctrlSetting->versionable ){
2027
+ if( pSetting!=0 && pSetting->versionable ){
20192028
/* This is a versionable setting, try and get the info from a
20202029
** checked out file */
2021
- z = db_get_do_versionable(zName, z);
2030
+ z = db_get_versioned(zName, z);
20222031
}
20232032
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
+ }
20252038
}
20262039
return z;
20272040
}
20282041
char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){
20292042
char *z = 0;
@@ -2285,88 +2298,91 @@
22852298
g.argc = 2;
22862299
info_cmd();
22872300
}
22882301
22892302
/*
2290
-** Print the value of a setting named zName
2303
+** Print the current value of a setting identified by the pSetting
2304
+** pointer.
22912305
*/
2292
-static void print_setting(
2293
- const struct stControlSettings *ctrlSetting,
2294
- int localOpen
2295
-){
2306
+static void print_setting(const Setting *pSetting){
22962307
Stmt q;
22972308
if( g.repositoryOpen ){
22982309
db_prepare(&q,
22992310
"SELECT '(local)', value FROM config WHERE name=%Q"
23002311
" UNION ALL "
23012312
"SELECT '(global)', value FROM global_config WHERE name=%Q",
2302
- ctrlSetting->name, ctrlSetting->name
2313
+ pSetting->name, pSetting->name
23032314
);
23042315
}else{
23052316
db_prepare(&q,
23062317
"SELECT '(global)', value FROM global_config WHERE name=%Q",
2307
- ctrlSetting->name
2318
+ pSetting->name
23082319
);
23092320
}
23102321
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),
23122323
db_column_text(&q, 1));
23132324
}else{
2314
- fossil_print("%-20s\n", ctrlSetting->name);
2325
+ fossil_print("%-20s\n", pSetting->name);
23152326
}
2316
- if( ctrlSetting->versionable && localOpen ){
2327
+ if( pSetting->versionable && g.localOpen ){
23172328
/* Check to see if this is overridden by a versionable settings file */
23182329
Blob versionedPathname;
23192330
blob_zero(&versionedPathname);
23202331
blob_appendf(&versionedPathname, "%s/.fossil-settings/%s",
2321
- g.zLocalRoot, ctrlSetting->name);
2332
+ g.zLocalRoot, pSetting->name);
23222333
if( file_size(blob_str(&versionedPathname))>=0 ){
23232334
fossil_print(" (overridden by contents of file .fossil-settings/%s)\n",
2324
- ctrlSetting->name);
2335
+ pSetting->name);
23252336
}
23262337
}
23272338
db_finalize(&q);
23282339
}
23292340
23302341
2342
+#if INTERFACE
23312343
/*
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.
23342348
** If var is 0, the settings name is used.
2349
+**
23352350
** width is the length for the edit field on the behavior page, 0
23362351
** is used for on/off checkboxes.
2352
+**
23372353
** The behaviour page doesn't use a special layout. It lists all
23382354
** set-commands and displays the 'set'-help as info.
23392355
*/
2340
-#if INTERFACE
2341
-struct stControlSettings {
2356
+struct Setting {
23422357
const char *name; /* Name of the setting */
23432358
const char *var; /* Internal variable name used by db_set() */
23442359
int width; /* Width of display. 0 for boolean values. */
23452360
int versionable; /* Is this setting versionable? */
23462361
int forceTextArea; /* Force using a text area for display? */
23472362
const char *def; /* Default value */
23482363
};
23492364
#endif /* INTERFACE */
2350
-struct stControlSettings const ctrlSettings[] = {
2365
+
2366
+const Setting aSetting[] = {
23512367
{ "access-log", 0, 0, 0, 0, "off" },
23522368
{ "admin-log", 0, 0, 0, 0, "off" },
23532369
{ "allow-symlinks", 0, 0, 1, 0, "off" },
23542370
{ "auto-captcha", "autocaptcha", 0, 0, 0, "on" },
23552371
{ "auto-hyperlink", 0, 0, 0, 0, "on", },
23562372
{ "auto-shun", 0, 0, 0, 0, "on" },
23572373
{ "autosync", 0, 0, 0, 0, "on" },
23582374
{ "autosync-tries", 0, 16, 0, 0, "1" },
23592375
{ "binary-glob", 0, 40, 1, 0, "" },
2360
- { "clearsign", 0, 0, 0, 0, "off" },
23612376
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \
23622377
defined(__APPLE__)
23632378
{ "case-sensitive", 0, 0, 0, 0, "off" },
23642379
#else
23652380
{ "case-sensitive", 0, 0, 0, 0, "on" },
23662381
#endif
23672382
{ "clean-glob", 0, 40, 1, 0, "" },
2383
+ { "clearsign", 0, 0, 0, 0, "off" },
23682384
{ "crnl-glob", 0, 40, 1, 0, "" },
23692385
{ "default-perms", 0, 16, 0, 0, "u" },
23702386
{ "diff-binary", 0, 0, 0, 0, "on" },
23712387
{ "diff-command", 0, 40, 0, 0, "" },
23722388
{ "dont-push", 0, 0, 0, 0, "off" },
@@ -2407,10 +2423,41 @@
24072423
{ "th1-uri-regexp", 0, 40, 1, 0, "" },
24082424
{ "web-browser", 0, 32, 0, 0, "" },
24092425
{ "white-foreground", 0, 0, 0, 0, "off" },
24102426
{ 0,0,0,0,0,0 }
24112427
};
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
+}
24122459
24132460
/*
24142461
** COMMAND: settings
24152462
** COMMAND: unset*
24162463
**
@@ -2658,61 +2705,65 @@
26582705
globalFlag = 1;
26592706
}
26602707
if( unsetFlag && g.argc!=3 ){
26612708
usage("PROPERTY ?-global?");
26622709
}
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
+
26632722
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]);
26672725
}
26682726
}else if( g.argc==3 || g.argc==4 ){
26692727
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 ){
26762731
fossil_fatal("no such setting: %s", zName);
26772732
}
2678
- isManifest = fossil_strcmp(ctrlSettings[i].name, "manifest")==0;
2679
- if( isManifest && globalFlag ){
2733
+ if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
26802734
fossil_fatal("cannot set 'manifest' globally");
26812735
}
26822736
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
+ }
27142765
}
27152766
}else{
27162767
usage("?PROPERTY? ?VALUE? ?-global?");
27172768
}
27182769
}
27192770
--- 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 @@
6565
}
6666
}
6767
if( i>=n ){
6868
return 0; /* Plain text */
6969
}
70
- for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
70
+ for(i=0; i<ArraySize(aMime); i++){
7171
if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){
7272
return aMime[i].zMimetype;
7373
}
7474
}
7575
return "unknown/unknown";
7676
}
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
+};
77293
78294
/*
79295
** Guess the mime-type of a document based on its name.
80296
*/
81297
const char *mimetype_from_name(const char *zName){
@@ -83,226 +299,17 @@
83299
int i;
84300
int first, last;
85301
int len;
86302
char zSuffix[20];
87303
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
- };
297304
298305
#ifdef FOSSIL_DEBUG
299306
/* This is test code to make sure the table above is in the correct
300307
** order
301308
*/
302309
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++){
304311
if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
305312
fossil_fatal("mimetypes out of sequence: %s before %s",
306313
aMime[i-1].zSuffix, aMime[i].zSuffix);
307314
}
308315
}
@@ -317,11 +324,11 @@
317324
len = strlen(z);
318325
if( len<sizeof(zSuffix)-1 ){
319326
sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z);
320327
for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]);
321328
first = 0;
322
- last = sizeof(aMime)/sizeof(aMime[0]) - 1;
329
+ last = ArraySize(aMime) - 1;
323330
while( first<=last ){
324331
int c;
325332
i = (first+last)/2;
326333
c = fossil_strcmp(zSuffix, aMime[i].zSuffix);
327334
if( c==0 ) return aMime[i].zMimetype;
@@ -350,10 +357,134 @@
350357
int i;
351358
for(i=2; i<g.argc; i++){
352359
fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
353360
}
354361
}
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
+}
355486
356487
/*
357488
** WEBPAGE: doc
358489
** URL: /doc?name=CHECKIN/FILE
359490
** URL: /doc/CHECKIN/FILE
@@ -375,97 +506,80 @@
375506
** The "ckout" CHECKIN is intended for development - to provide a mechanism
376507
** for looking at what a file will look like using the /doc webpage after
377508
** it gets checked in.
378509
**
379510
** 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.
380516
*/
381517
void doc_page(void){
382518
const char *zName; /* Argument to the /doc page */
383
- const char *zOrigName; /* Original document name */
519
+ const char *zOrigName = "?"; /* Original document name */
384520
const char *zMime; /* Document MIME type */
385
- char *zCheckin; /* The checkin holding the document */
521
+ char *zCheckin = "tip"; /* The checkin holding the document */
386522
int vid = 0; /* Artifact of checkin */
387523
int rid = 0; /* Artifact of file */
388524
int i; /* Loop counter */
389525
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
+ };
391531
392532
login_check_credentials();
393533
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 ){
426572
rid = 1; /* Fake RID just to get the loop to end */
427573
}
428574
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;
467581
blob_to_utf8_no_bom(&filebody, 0);
468582
469583
/* The file is now contained in the filebody blob. Deliver the
470584
** file to the user
471585
*/
@@ -477,11 +591,11 @@
477591
Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
478592
" FROM blob WHERE rid=%d", vid));
479593
Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
480594
" WHERE objid=%d AND type='ci'", vid));
481595
if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
482
- Blob title, tail;
596
+ Blob tail;
483597
style_adunit_config(ADUNIT_RIGHT_OK);
484598
if( wiki_find_title(&filebody, &title, &tail) ){
485599
style_header("%s", blob_str(&title));
486600
wiki_convert(&tail, 0, WIKI_BUTTONS);
487601
}else{
@@ -488,26 +602,32 @@
488602
style_header("Documentation");
489603
wiki_convert(&filebody, 0, WIKI_BUTTONS);
490604
}
491605
style_footer();
492606
}else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
493
- Blob title = BLOB_INITIALIZER;
494607
Blob tail = BLOB_INITIALIZER;
495608
markdown_to_html(&filebody, &title, &tail);
496609
if( blob_size(&title)>0 ){
497610
style_header("%s", blob_str(&title));
498611
}else{
499
- style_header("%s", nMiss?"Not Found":"Documentation");
612
+ style_header("%s", nMiss>=ArraySize(azSuffix)?
613
+ "Not Found" : "Documentation");
500614
}
501615
blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
502616
style_footer();
503617
}else if( fossil_strcmp(zMime, "text/plain")==0 ){
504618
style_header("Documentation");
505619
@ <blockquote><pre>
506620
@ %h(blob_str(&filebody))
507621
@ </pre></blockquote>
508622
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();
509629
#ifdef FOSSIL_ENABLE_TH1_DOCS
510630
}else if( db_get_boolean("th1-docs", 0) &&
511631
fossil_strcmp(zMime, "application/x-th1")==0 ){
512632
style_header("%h", zName);
513633
Th_Render(blob_str(&filebody));
@@ -515,11 +635,12 @@
515635
#endif
516636
}else{
517637
cgi_set_content_type(zMime);
518638
cgi_set_content(&filebody);
519639
}
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);
521642
return;
522643
523644
/* Jump here when unable to locate the document */
524645
doc_not_found:
525646
db_end_transaction(0);
@@ -528,10 +649,11 @@
528649
@ <p>Document %h(zOrigName) not found
529650
if( fossil_strcmp(zCheckin,"ckout")!=0 ){
530651
@ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
531652
}
532653
style_footer();
654
+ db_end_transaction(0);
533655
return;
534656
}
535657
536658
/*
537659
** The default logo.
@@ -653,5 +775,18 @@
653775
}
654776
cgi_set_content_type(zMime);
655777
cgi_set_content(&bgimg);
656778
g.isConst = 1;
657779
}
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
+}
658793
--- 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 @@
4444
const char *zSep = "(";
4545
int nTerm = 0;
4646
int i;
4747
int cTerm;
4848
49
- if( zGlobList==0 || zGlobList[0]==0 ) return "0";
49
+ if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0");
5050
blob_zero(&expr);
5151
while( zGlobList[0] ){
5252
while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){
5353
zGlobList++; /* Skip leading commas, spaces, and newlines */
5454
}
@@ -73,11 +73,11 @@
7373
}
7474
if( nTerm ){
7575
blob_appendf(&expr, ")");
7676
return blob_str(&expr);
7777
}else{
78
- return "0";
78
+ return fossil_strdup("0");
7979
}
8080
}
8181
8282
#if INTERFACE
8383
/*
8484
--- 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 @@
449449
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
450450
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
451451
-DSQLITE_THREADSAFE=0 \
452452
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
453453
-DSQLITE_OMIT_DEPRECATED \
454
- -DSQLITE_ENABLE_EXPLAIN_COMMENTS
454
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
455
+ -DSQLITE_ENABLE_FTS4
455456
456457
# Setup the options used to compile the included SQLite shell.
457458
SHELL_OPTIONS = -Dmain=sqlite3_shell \
458459
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
459460
-DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
460461
--- 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
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -157,10 +157,11 @@
157157
-DSQLITE_ENABLE_LOCKING_STYLE=0
158158
-DSQLITE_THREADSAFE=0
159159
-DSQLITE_DEFAULT_FILE_FORMAT=4
160160
-DSQLITE_OMIT_DEPRECATED
161161
-DSQLITE_ENABLE_EXPLAIN_COMMENTS
162
+ -DSQLITE_ENABLE_FTS4
162163
}
163164
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
164165
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4
165166
#lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI
166167
#lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096
167168
--- 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
--- src/manifest.c
+++ src/manifest.c
@@ -1829,10 +1829,11 @@
18291829
for(i=0; i<p->nFile; i++){
18301830
add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0,
18311831
isPublic, 1, manifest_file_mperm(&p->aFile[i]));
18321832
}
18331833
}
1834
+ search_doc_touch('c', rid, 0);
18341835
db_multi_exec(
18351836
"REPLACE INTO event(type,mtime,objid,user,comment,"
18361837
"bgcolor,euser,ecomment,omtime)"
18371838
"VALUES('ci',"
18381839
" coalesce("
@@ -1934,10 +1935,11 @@
19341935
if( nWiki>0 ){
19351936
zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);
19361937
}else{
19371938
zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);
19381939
}
1940
+ search_doc_touch('w',rid,p->zWikiTitle);
19391941
db_multi_exec(
19401942
"REPLACE INTO event(type,mtime,objid,user,comment,"
19411943
" bgcolor,euser,ecomment)"
19421944
"VALUES('w',%.17g,%d,%Q,%Q,"
19431945
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
@@ -1986,10 +1988,11 @@
19861988
}
19871989
}
19881990
if( subsequent ){
19891991
content_deltify(rid, subsequent, 0);
19901992
}else{
1993
+ search_doc_touch('e',rid,0);
19911994
db_multi_exec(
19921995
"REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
19931996
"VALUES('e',%.17g,%d,%d,%Q,%Q,"
19941997
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
19951998
p->rEventDate, rid, tagid, p->zUser, p->zComment,
19961999
--- 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
--- src/rebuild.c
+++ src/rebuild.c
@@ -533,10 +533,12 @@
533533
** --vacuum Run VACUUM on the database after rebuilding
534534
** --deanalyze Remove ANALYZE tables from the database
535535
** --analyze Run ANALYZE on the database after rebuilding
536536
** --wal Set Write-Ahead-Log journalling mode on the database
537537
** --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
538540
**
539541
** See also: deconstruct, reconstruct
540542
*/
541543
void rebuild_database(void){
542544
int forceFlag;
@@ -550,10 +552,11 @@
550552
int runVacuum;
551553
int runDeanalyze;
552554
int runAnalyze;
553555
int runCompress;
554556
int showStats;
557
+ int runReindex;
555558
556559
omitVerify = find_option("noverify",0,0)!=0;
557560
forceFlag = find_option("force","f",0)!=0;
558561
randomizeFlag = find_option("randomize", 0, 0)!=0;
559562
doClustering = find_option("cluster", 0, 0)!=0;
@@ -580,15 +583,19 @@
580583
usage("?REPOSITORY-FILENAME?");
581584
}
582585
db_close(1);
583586
db_open_repository(g.zRepositoryName);
584587
}
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;
585591
586592
/* We should be done with options.. */
587593
verify_all_options();
588594
589595
db_begin_transaction();
596
+ search_drop_index();
590597
ttyOutput = 1;
591598
errCnt = rebuild_db(randomizeFlag, 1, doClustering);
592599
reconstruct_private_table();
593600
db_multi_exec(
594601
"REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());"
@@ -634,10 +641,11 @@
634641
}
635642
if( activateWal ){
636643
db_multi_exec("PRAGMA journal_mode=WAL;");
637644
}
638645
}
646
+ if( runReindex ) search_rebuild_index();
639647
if( showStats ){
640648
static const struct { int idx; const char *zLabel; } aStat[] = {
641649
{ CFTYPE_ANY, "Artifacts:" },
642650
{ CFTYPE_MANIFEST, "Manifests:" },
643651
{ CFTYPE_CLUSTER, "Clusters:" },
644652
--- 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
--- src/report.c
+++ src/report.c
@@ -29,10 +29,12 @@
2929
# define SQLITE_RECURSIVE 33
3030
#endif
3131
3232
/*
3333
** WEBPAGE: /reportlist
34
+**
35
+** Main menu for Tickets.
3436
*/
3537
void view_list(void){
3638
const char *zScript;
3739
Blob ril; /* Report Item List */
3840
Stmt q;
@@ -40,10 +42,11 @@
4042
int cnt = 0;
4143
4244
login_check_credentials();
4345
if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }
4446
style_header("Ticket Main Menu");
47
+ ticket_standard_submenu(T_ALL_BUT(T_REPLIST));
4548
if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
4649
zScript = ticket_reportlist_code();
4750
if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
4851
4952
blob_zero(&ril);
5053
--- 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 @@
4545
char *zPattern; /* The search pattern */
4646
char *zMarkBegin; /* Start of a match */
4747
char *zMarkEnd; /* End of a match */
4848
char *zMarkGap; /* A gap between two matches */
4949
unsigned fSrchFlg; /* Flags */
50
+ int iScore; /* Score of the last match attempt */
51
+ Blob snip; /* Snippet for the most recent match */
5052
};
5153
5254
#define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */
53
-#define SRCHFLG_SCORE 0x02 /* Prepend the score to each snippet */
5455
#define SRCHFLG_STATIC 0x04 /* The static gSearch object */
5556
5657
#endif
5758
5859
/*
@@ -92,10 +93,11 @@
9293
if( p ){
9394
fossil_free(p->zPattern);
9495
fossil_free(p->zMarkBegin);
9596
fossil_free(p->zMarkEnd);
9697
fossil_free(p->zMarkGap);
98
+ if( p->iScore ) blob_reset(&p->snip);
9799
memset(p, 0, sizeof(*p));
98100
if( p!=&gSearch ) fossil_free(p);
99101
}
100102
}
101103
@@ -123,10 +125,11 @@
123125
p->zPattern = z = mprintf("%s", zPattern);
124126
p->zMarkBegin = mprintf("%s", zMarkBegin);
125127
p->zMarkEnd = mprintf("%s", zMarkEnd);
126128
p->zMarkGap = mprintf("%s", zMarkGap);
127129
p->fSrchFlg = fSrchFlg;
130
+ blob_init(&p->snip, 0, 0);
128131
while( *z && p->nTerm<SEARCH_MAX_TERM ){
129132
while( *z && !ISALNUM(*z) ){ z++; }
130133
if( *z==0 ) break;
131134
p->a[p->nTerm].z = z;
132135
for(i=1; ISALNUM(z[i]); i++){}
@@ -156,24 +159,26 @@
156159
}
157160
}
158161
159162
/*
160163
** 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.
163169
**
164170
** Scoring:
165171
** * All terms must match at least once or the score is zero
166172
** * One point for each matching term
167173
** * Extra points if consecutive words of the pattern are consecutive
168174
** in the document
169175
*/
170
-static int search_score(
176
+static int search_match(
171177
Search *p, /* Search pattern and flags */
172178
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 */
175180
){
176181
int score; /* Final score */
177182
int i; /* Offset into current document */
178183
int ii; /* Loop counter */
179184
int j; /* Loop over search terms */
@@ -217,26 +222,26 @@
217222
}
218223
break;
219224
}
220225
}
221226
while( ISALNUM(zDoc[i]) ){ i++; }
227
+ if( zDoc[i]==0 ) break;
222228
}
223229
}
224230
225231
/* Finished search all documents.
226232
** Every term must be seen or else the score is zero
227233
*/
228234
score = 1;
229235
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;
231239
232240
233241
/* Prepare a snippet that describes the matching text.
234242
*/
235
- blob_init(pSnip, 0, 0);
236
- if( p->fSrchFlg & SRCHFLG_SCORE ) blob_appendf(pSnip, "%08x", score);
237
-
238243
while(1){
239244
int iOfst;
240245
int iTail;
241246
int iBest;
242247
for(ii=0; ii<p->nTerm && anMatch[ii]==0; ii++){}
@@ -271,11 +276,11 @@
271276
if( iOfst<0 ) iOfst = 0;
272277
while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--;
273278
while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++;
274279
for(ii=0; ii<CTX && zDoc[iTail]; ii++, iTail++){}
275280
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);
277282
wantGap = zDoc[iTail]!=0;
278283
zDoc += iOfst;
279284
iTail -= iOfst;
280285
281286
/* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */
@@ -284,53 +289,51 @@
284289
for(j=0; j<p->nTerm; j++){
285290
int n = p->a[j].n;
286291
if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0
287292
&& (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*')
288293
){
289
- snippet_text_append(p, pSnip, zDoc, i);
294
+ snippet_text_append(p, &p->snip, zDoc, i);
290295
zDoc += i;
291296
iTail -= i;
292
- blob_append(pSnip, p->zMarkBegin, -1);
297
+ blob_append(&p->snip, p->zMarkBegin, -1);
293298
if( p->a[j].z[n]=='*' ){
294299
while( ISALNUM(zDoc[n]) ) n++;
295300
}
296
- snippet_text_append(p, pSnip, zDoc, n);
301
+ snippet_text_append(p, &p->snip, zDoc, n);
297302
zDoc += n;
298303
iTail -= n;
299
- blob_append(pSnip, p->zMarkEnd, -1);
304
+ blob_append(&p->snip, p->zMarkEnd, -1);
300305
i = -1;
301306
break;
302307
} /* end-if */
303308
} /* end for(j) */
304309
if( j<p->nTerm ){
305310
while( ISALNUM(zDoc[i]) && i<iTail ){ i++; }
306311
}
307312
} /* end for(i) */
308
- snippet_text_append(p, pSnip, zDoc, iTail);
313
+ snippet_text_append(p, &p->snip, zDoc, iTail);
309314
}
310
- if( wantGap ) blob_append(pSnip, p->zMarkGap, -1);
315
+ if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1);
311316
return score;
312317
}
313318
314319
/*
315
-** COMMAND: test-snippet
320
+** COMMAND: test-match
316321
**
317
-** Usage: fossil test-snippet SEARCHSTRING FILE1 FILE2 ...
322
+** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
318323
*/
319
-void test_snippet_cmd(void){
324
+void test_match_cmd(void){
320325
Search *p;
321326
int i;
322327
Blob x;
323
- Blob snip;
324328
int score;
325329
char *zDoc;
326330
int flg = 0;
327331
char *zBegin = (char*)find_option("begin",0,1);
328332
char *zEnd = (char*)find_option("end",0,1);
329333
char *zGap = (char*)find_option("gap",0,1);
330334
if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML;
331
- if( find_option("score",0,0)!=0 ) flg |= SRCHFLG_SCORE;
332335
if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC;
333336
verify_all_options();
334337
if( g.argc<4 ) usage("SEARCHSTRING FILE1...");
335338
if( zBegin==0 ) zBegin = "[[";
336339
if( zEnd==0 ) zEnd = "]]";
@@ -337,18 +340,18 @@
337340
if( zGap==0 ) zGap = " ... ";
338341
p = search_init(g.argv[2], zBegin, zEnd, zGap, flg);
339342
for(i=3; i<g.argc; i++){
340343
blob_read_from_file(&x, g.argv[i]);
341344
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);
344347
blob_reset(&x);
345348
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), '=');
348350
}
349351
}
352
+ search_end(p);
350353
}
351354
352355
/*
353356
** An SQL function to initialize the global search pattern:
354357
**
@@ -360,12 +363,12 @@
360363
sqlite3_context *context,
361364
int argc,
362365
sqlite3_value **argv
363366
){
364367
const char *zPattern = 0;
365
- const char *zBegin = "<b>";
366
- const char *zEnd = "</b>";
368
+ const char *zBegin = "<mark>";
369
+ const char *zEnd = "</mark>";
367370
const char *zGap = " ... ";
368371
unsigned int flg = SRCHFLG_HTML;
369372
switch( argc ){
370373
default:
371374
flg = (unsigned int)sqlite3_value_int(argv[4]);
@@ -384,50 +387,98 @@
384387
search_end(&gSearch);
385388
}
386389
}
387390
388391
/*
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.
391411
*/
392412
static void search_score_sqlfunc(
393413
sqlite3_context *context,
394414
int argc,
395415
sqlite3_value **argv
396416
){
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);
415458
}
416459
417460
/*
418461
** Register the "score()" SQL function to score its input text
419462
** using the given Search object. Once this function is registered,
420463
** do not delete the Search object.
421464
*/
422465
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,
426471
search_score_sqlfunc, 0, 0);
472
+ sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0,
473
+ search_snippet_sqlfunc, 0, 0);
427474
sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0,
428475
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);
429480
}
430481
431482
/*
432483
** Testing the search function.
433484
**
@@ -501,67 +552,930 @@
501552
db_prepare(&q, "%s", blob_sql_text(&sql));
502553
blob_reset(&sql);
503554
print_timeline(&q, nLimit, width, 0);
504555
db_finalize(&q);
505556
}
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 "&lt;". 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], "&lt;", 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
+}
506932
507933
/*
508934
** WEBPAGE: /search
509935
**
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.
523938
*/
524939
void search_page(void){
525
- const char *zPattern = PD("s","");
526
- Stmt q;
940
+ unsigned srchFlags = SRCH_ALL;
941
+ const char *zOnly = P("only");
527942
528943
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
+ }
530950
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");
566952
style_footer();
567953
}
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
+}
5681482
--- 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 "&lt;". 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], "&lt;", 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 @@
7070
@ <p class="generalError"><b>Configuration Error:</b> Please add
7171
@ <tt>&lt;base href="$secureurl/$current_page"&gt;</tt> after
7272
@ <tt>&lt;head&gt;</tt> in the <a href="setup_header">HTML header</a>!</p>
7373
}
7474
75
- @ <table border="0" cellspacing="7">
75
+ @ <table border="0" cellspacing="3">
7676
setup_menu_entry("Users", "setup_ulist",
7777
"Grant privileges to individual users.");
7878
setup_menu_entry("Access", "setup_access",
7979
"Control access settings.");
8080
setup_menu_entry("Configuration", "setup_config",
@@ -86,10 +86,12 @@
8686
setup_menu_entry("Login-Group", "setup_login_group",
8787
"Manage single sign-on between this repository and others"
8888
" on the same server");
8989
setup_menu_entry("Tickets", "tktsetup",
9090
"Configure the trouble-ticketing system for this repository");
91
+ setup_menu_entry("Search","srchsetup",
92
+ "Configure the built-in search engine");
9193
setup_menu_entry("Transfers", "xfersetup",
9294
"Configure the transfer system for this repository");
9395
setup_menu_entry("Skins", "setup_skin",
9496
"Select from a menu of prepackaged \"skins\" for the web interface");
9597
setup_menu_entry("CSS", "setup_editcss",
@@ -1354,11 +1356,11 @@
13541356
13551357
/*
13561358
** WEBPAGE: setup_settings
13571359
*/
13581360
void setup_settings(void){
1359
- struct stControlSettings const *pSet;
1361
+ Setting const *pSet;
13601362
13611363
login_check_credentials();
13621364
if( !g.perm.Setup ){
13631365
login_needed();
13641366
}
@@ -1375,14 +1377,14 @@
13751377
@ See the "fossil help setting" output below for further information on
13761378
@ the meaning of each setting.</p><hr />
13771379
@ <form action="%s(g.zTop)/setup_settings" method="post"><div>
13781380
@ <table border="0"><tr><td valign="top">
13791381
login_insert_csrf_secret();
1380
- for(pSet=ctrlSettings; pSet->name!=0; pSet++){
1382
+ for(pSet=aSetting; pSet->name!=0; pSet++){
13811383
if( pSet->width==0 ){
13821384
int hasVersionableValue = pSet->versionable &&
1383
- (db_get_do_versionable(pSet->name, NULL)!=0);
1385
+ (db_get_versioned(pSet->name, NULL)!=0);
13841386
onoff_attribute(pSet->name, pSet->name,
13851387
pSet->var!=0 ? pSet->var : pSet->name,
13861388
is_truth(pSet->def), hasVersionableValue);
13871389
if( pSet->versionable ){
13881390
@ (v)<br />
@@ -1391,31 +1393,31 @@
13911393
}
13921394
}
13931395
}
13941396
@ <br /><input type="submit" name="submit" value="Apply Changes" />
13951397
@ </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++){
13971399
if( pSet->width!=0 && !pSet->versionable && !pSet->forceTextArea ){
13981400
entry_attribute(pSet->name, /*pSet->width*/ 25, pSet->name,
13991401
pSet->var!=0 ? pSet->var : pSet->name,
14001402
(char*)pSet->def, 0);
14011403
@ <br />
14021404
}
14031405
}
1404
- for(pSet=ctrlSettings; pSet->name!=0; pSet++){
1406
+ for(pSet=aSetting; pSet->name!=0; pSet++){
14051407
if( pSet->width!=0 && !pSet->versionable && pSet->forceTextArea ){
14061408
@<b>%s(pSet->name)</b><br />
14071409
textarea_attribute("", /*rows*/ 3, /*cols*/ 50, pSet->name,
14081410
pSet->var!=0 ? pSet->var : pSet->name,
14091411
(char*)pSet->def, 0);
14101412
@ <br />
14111413
}
14121414
}
14131415
@ </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++){
14151417
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;
14171419
@<b>%s(pSet->name)</b> (v)<br />
14181420
textarea_attribute("", /*rows*/ 3, /*cols*/ 20, pSet->name,
14191421
pSet->var!=0 ? pSet->var : pSet->name,
14201422
(char*)pSet->def, hasVersionableValue);
14211423
@<br />
@@ -2155,5 +2157,77 @@
21552157
if(limit>0 && counter<limit){
21562158
@ <div>%d(counter) entries shown.</div>
21572159
}
21582160
style_footer();
21592161
}
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
+}
21602234
--- src/setup.c
+++ src/setup.c
@@ -70,11 +70,11 @@
70 @ <p class="generalError"><b>Configuration Error:</b> Please add
71 @ <tt>&lt;base href="$secureurl/$current_page"&gt;</tt> after
72 @ <tt>&lt;head&gt;</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>&lt;base href="$secureurl/$current_page"&gt;</tt> after
72 @ <tt>&lt;head&gt;</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 @@
11
/******************************************************************************
22
** 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
44
** single large file, the entire code can be compiled as a single translation
55
** unit. This allows many compilers to do optimizations that would not be
66
** possible if the files were compiled separately. Performance improvements
77
** of 5% or more are commonly seen when SQLite is compiled as a single
88
** translation unit.
@@ -276,13 +276,13 @@
276276
**
277277
** See also: [sqlite3_libversion()],
278278
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
279279
** [sqlite_version()] and [sqlite_source_id()].
280280
*/
281
-#define SQLITE_VERSION "3.8.8.1"
281
+#define SQLITE_VERSION "3.8.8.2"
282282
#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"
284284
285285
/*
286286
** CAPI3REF: Run-Time Library Version Numbers
287287
** KEYWORDS: sqlite3_version, sqlite3_sourceid
288288
**
@@ -19870,21 +19870,10 @@
1987019870
# define SQLITE_WIN32_VOLATILE
1987119871
#else
1987219872
# define SQLITE_WIN32_VOLATILE volatile
1987319873
#endif
1987419874
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
-
1988619875
#endif /* _OS_WIN_H_ */
1988719876
1988819877
/************** End of os_win.h **********************************************/
1988919878
/************** Continuing where we left off in mutex_w32.c ******************/
1989019879
#endif
@@ -22444,11 +22433,11 @@
2244422433
#endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */
2244522434
/******************************** End Unix Pthreads *************************/
2244622435
2244722436
2244822437
/********************************* Win32 Threads ****************************/
22449
-#if SQLITE_OS_WIN_THREADS
22438
+#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_THREADSAFE>0
2245022439
2245122440
#define SQLITE_THREADS_IMPLEMENTED 1 /* Prevent the single-thread code below */
2245222441
#include <process.h>
2245322442
2245422443
/* A running thread */
@@ -22537,11 +22526,11 @@
2253722526
if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult;
2253822527
sqlite3_free(p);
2253922528
return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR;
2254022529
}
2254122530
22542
-#endif /* SQLITE_OS_WIN_THREADS */
22531
+#endif /* SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT */
2254322532
/******************************** End Win32 Threads *************************/
2254422533
2254522534
2254622535
/********************************* Single-Threaded **************************/
2254722536
#ifndef SQLITE_THREADS_IMPLEMENTED
@@ -50206,11 +50195,11 @@
5020650195
int (*xBusy)(void*), /* Function to call when busy */
5020750196
void *pBusyArg, /* Context argument for xBusyHandler */
5020850197
int sync_flags, /* Flags for OsSync() (or 0) */
5020950198
u8 *zBuf /* Temporary buffer to use */
5021050199
){
50211
- int rc; /* Return code */
50200
+ int rc = SQLITE_OK; /* Return code */
5021250201
int szPage; /* Database page-size */
5021350202
WalIterator *pIter = 0; /* Wal iterator context */
5021450203
u32 iDbpage = 0; /* Next database page to write */
5021550204
u32 iFrame = 0; /* Wal frame containing data for iDbpage */
5021650205
u32 mxSafeFrame; /* Max frame that can be backfilled */
@@ -50220,108 +50209,111 @@
5022050209
5022150210
szPage = walPagesize(pWal);
5022250211
testcase( szPage<=32768 );
5022350212
testcase( szPage>=65536 );
5022450213
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
+ }
5032350315
}
5032450316
5032550317
/* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the
5032650318
** entire wal file has been copied into the database file, then block
5032750319
** until all readers have finished using the wal file. This ensures that
@@ -50332,11 +50324,11 @@
5033250324
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
5033350325
rc = SQLITE_BUSY;
5033450326
}else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
5033550327
u32 salt1;
5033650328
sqlite3_randomness(4, &salt1);
50337
- assert( mxSafeFrame==pWal->hdr.mxFrame );
50329
+ assert( pInfo->nBackfill==pWal->hdr.mxFrame );
5033850330
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
5033950331
if( rc==SQLITE_OK ){
5034050332
if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){
5034150333
/* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as
5034250334
** SQLITE_CHECKPOINT_RESTART with the addition that it also
@@ -128378,10 +128370,11 @@
128378128370
}
128379128371
if( iDb<0 ){
128380128372
rc = SQLITE_ERROR;
128381128373
sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb);
128382128374
}else{
128375
+ db->busyHandler.nBusy = 0;
128383128376
rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt);
128384128377
sqlite3Error(db, rc);
128385128378
}
128386128379
rc = sqlite3ApiExit(db, rc);
128387128380
sqlite3_mutex_leave(db->mutex);
128388128381
--- 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 @@
105105
**
106106
** See also: [sqlite3_libversion()],
107107
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
108108
** [sqlite_version()] and [sqlite_source_id()].
109109
*/
110
-#define SQLITE_VERSION "3.8.8.1"
110
+#define SQLITE_VERSION "3.8.8.2"
111111
#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"
113113
114114
/*
115115
** CAPI3REF: Run-Time Library Version Numbers
116116
** KEYWORDS: sqlite3_version, sqlite3_sourceid
117117
**
118118
--- 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 @@
11251125
},
11261126
{ "th.sort.desc:after",
11271127
"Descending sort column marker",
11281128
@ content: '\2191';
11291129
},
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
+ },
11301143
{ 0,
11311144
0,
11321145
0
11331146
}
11341147
};
@@ -1149,23 +1162,47 @@
11491162
);
11501163
}
11511164
}
11521165
}
11531166
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
+
11541190
/*
11551191
** WEBPAGE: style.css
11561192
*/
11571193
void page_style_css(void){
11581194
Blob css;
11591195
int i;
11601196
11611197
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);
11631199
11641200
/* add special missing definitions */
11651201
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) ){
11671204
blob_appendf(&css, "/* %s */\n%s {\n%s}\n",
11681205
cssDefaultList[i].comment,
11691206
cssDefaultList[i].elementClass,
11701207
cssDefaultList[i].value);
11711208
}
11721209
--- 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 @@
313313
314314
fossil_free(zTag);
315315
getAllTicketFields();
316316
if( haveTicket==0 ) return;
317317
tktid = db_int(0, "SELECT tkt_id FROM ticket WHERE tkt_uuid=%Q", zTktUuid);
318
+ search_doc_touch('t', tktid, 0);
318319
if( haveTicketChng ){
319320
db_multi_exec("DELETE FROM ticketchng WHERE tkt_id=%d;", tktid);
320321
}
321322
db_multi_exec("DELETE FROM ticket WHERE tkt_id=%d", tktid);
322323
tktid = 0;
@@ -691,10 +692,11 @@
691692
if( !g.perm.NewTkt ){ login_needed(); return; }
692693
if( P("cancel") ){
693694
cgi_redirect("home");
694695
}
695696
style_header("New Ticket");
697
+ ticket_standard_submenu(T_ALL_BUT(T_NEW));
696698
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW<br />\n", -1);
697699
ticket_init();
698700
initializeVariablesFromCGI();
699701
getAllTicketFields();
700702
initializeVariablesFromDb();
@@ -1380,5 +1382,60 @@
13801382
(eCmd==set?"set":"add"),zTktUuid);
13811383
}
13821384
}
13831385
}
13841386
}
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
+}
13851442
--- 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 @@
389389
void url_enable_proxy(const char *zMsg){
390390
const char *zProxy;
391391
zProxy = zProxyOpt;
392392
if( zProxy==0 ){
393393
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) ){
395395
zProxy = fossil_getenv("http_proxy");
396396
}
397397
}
398398
if( zProxy && zProxy[0] && !is_false(zProxy)
399399
&& !g.url.isSsh && !g.url.isFile ){
400400
--- 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 @@
198198
if( localUser ){
199199
return 0;
200200
}
201201
return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1;
202202
}
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
+}
203299
204300
/*
205301
** WEBPAGE: wiki
206302
** URL: /wiki?name=PAGENAME
207303
*/
@@ -208,10 +304,11 @@
208304
void wiki_page(void){
209305
char *zTag;
210306
int rid = 0;
211307
int isSandbox;
212308
char *zUuid;
309
+ unsigned submenuFlags = W_ALL;
213310
Blob wiki;
214311
Manifest *pWiki = 0;
215312
const char *zPageName;
216313
const char *zMimetype = 0;
217314
char *zBody = mprintf("%s","<i>Empty Page</i>");
@@ -218,63 +315,35 @@
218315
219316
login_check_credentials();
220317
if( !g.perm.RdWiki ){ login_needed(); return; }
221318
zPageName = P("name");
222319
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
- @ &nbsp; <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
+ }
260325
return;
261326
}
262327
if( check_name(zPageName) ) return;
263328
isSandbox = is_sandbox(zPageName);
264329
if( isSandbox ){
330
+ submenuFlags &= ~W_SANDBOX;
265331
zBody = db_get("sandbox",zBody);
266332
zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
267333
rid = 0;
268334
}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
+ }
276345
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
277346
if( pWiki ){
278347
zBody = pWiki->zWiki;
279348
zMimetype = pWiki->zMimetype;
280349
}
@@ -314,10 +383,11 @@
314383
g.zTop, zPageName);
315384
}
316385
}
317386
style_set_current_page("%T?name=%T", g.zPath, zPageName);
318387
style_header("%s", zPageName);
388
+ wiki_standard_submenu(submenuFlags);
319389
blob_init(&wiki, zBody, -1);
320390
wiki_render_by_mimetype(&wiki, zMimetype);
321391
blob_reset(&wiki);
322392
attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>");
323393
manifest_destroy(pWiki);
@@ -570,10 +640,11 @@
570640
}else{
571641
cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype);
572642
}
573643
}
574644
style_header("Create A New Wiki Page");
645
+ wiki_standard_submenu(W_ALL_BUT(W_NEW));
575646
@ <p>Rules for wiki page names:</p>
576647
well_formed_wiki_name_rules();
577648
form_begin(0, "%R/wikinew");
578649
@ <p>Name of new wiki page:
579650
@ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
@@ -846,11 +917,12 @@
846917
*/
847918
void wiki_prepare_page_list( Stmt * pStmt ){
848919
db_prepare(pStmt,
849920
"SELECT"
850921
" 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"
852924
" FROM tag WHERE tagname GLOB 'wiki-*'"
853925
" ORDER BY lower(tagname) /*sort*/"
854926
);
855927
}
856928
/*
@@ -870,10 +942,11 @@
870942
if( showAll ){
871943
style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
872944
}else{
873945
style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
874946
}
947
+ wiki_standard_submenu(W_ALL_BUT(W_LIST));
875948
@ <ul>
876949
wiki_prepare_page_list(&q);
877950
while( db_step(&q)==SQLITE_ROW ){
878951
const char *zName = db_column_text(&q, 0);
879952
int size = db_column_int(&q, 1);
@@ -935,11 +1008,12 @@
9351008
@ </ol>
9361009
@ <p>We call the first five rules above "wiki" formatting rules. The
9371010
@ last two rules are the HTML formatting rule.</p>
9381011
@ <h2>Formatting Rule Details</h2>
9391012
@ <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
9411015
@ a paragraph break. Centered or right-justified paragraphs are not
9421016
@ supported by wiki markup, but you can do these things if you need them
9431017
@ using HTML.</p></li>
9441018
@ <li> <p><span class="wikiruleHead">Bullet Lists</span>.
9451019
@ A bullet list item is a line that begins with a single "*" character
@@ -960,11 +1034,12 @@
9601034
@ Text within square brackets ("[...]") becomes a hyperlink. The
9611035
@ target can be a wiki page name, the artifact ID of a check-in or ticket,
9621036
@ the name of an image, or a URL. By default, the target is displayed
9631037
@ as the text of the hyperlink. But you can specify alternative text
9641038
@ 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
9661041
@ you have added the necessary "&lt;a name='anchor-name'&gt;&lt;/a&gt;"
9671042
@ tag to your wiki page.</p></li>
9681043
@ <li> <p><span class="wikiruleHead">HTML</span>.
9691044
@ The following standard HTML elements may be used:
9701045
show_allowed_wiki_markup();
@@ -1166,11 +1241,12 @@
11661241
}else if( strncmp(g.argv[2],"delete",n)==0 ){
11671242
if( g.argc!=5 ){
11681243
usage("delete PAGENAME");
11691244
}
11701245
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 )){
11721248
Stmt q;
11731249
db_prepare(&q,
11741250
"SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
11751251
" ORDER BY lower(tagname) /*sort*/"
11761252
);
11771253
--- 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 @ &nbsp; <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 "&lt;a name='anchor-name'&gt;&lt;/a&gt;"
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 "&lt;a name='anchor-name'&gt;&lt;/a&gt;"
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
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2092,9 +2092,81 @@
20922092
for(i=2; i<g.argc; i++){
20932093
blob_read_from_file(&in, g.argv[i]);
20942094
blob_zero(&out);
20952095
htmlTidy(blob_str(&in), &out);
20962096
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);
20972169
fossil_puts(blob_str(&out), 0);
20982170
blob_reset(&out);
20992171
}
21002172
}
21012173
--- 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
--- win/Makefile.PellesCGMake
+++ win/Makefile.PellesCGMake
@@ -83,11 +83,11 @@
8383
8484
# define the SQLite files, which need special flags on compile
8585
SQLITESRC=sqlite3.c
8686
ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf))
8787
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
8989
9090
# define the SQLite shell files, which need special flags on compile
9191
SQLITESHELLSRC=shell.c
9292
ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf))
9393
SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj))
9494
--- 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
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -24,11 +24,11 @@
2424
CFLAGS = -o
2525
BCC = $(DMDIR)\bin\dmc $(CFLAGS)
2626
TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
2727
LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32
2828
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
3030
3131
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
3232
3333
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
3434
3535
--- 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
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -2024,10 +2024,11 @@
20242024
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
20252025
-DSQLITE_THREADSAFE=0 \
20262026
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
20272027
-DSQLITE_OMIT_DEPRECATED \
20282028
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
2029
+ -DSQLITE_ENABLE_FTS4 \
20292030
-DSQLITE_WIN32_NO_ANSI \
20302031
-D_HAVE__MINGW_H \
20312032
-DSQLITE_USE_MALLOC_H \
20322033
-DSQLITE_USE_MSIZE
20332034
20342035
--- 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
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -189,10 +189,11 @@
189189
/DSQLITE_ENABLE_LOCKING_STYLE=0 \
190190
/DSQLITE_THREADSAFE=0 \
191191
/DSQLITE_DEFAULT_FILE_FORMAT=4 \
192192
/DSQLITE_OMIT_DEPRECATED \
193193
/DSQLITE_ENABLE_EXPLAIN_COMMENTS \
194
+ /DSQLITE_ENABLE_FTS4 \
194195
/DSQLITE_WIN32_NO_ANSI
195196
196197
SHELL_OPTIONS = /Dmain=sqlite3_shell \
197198
/DSQLITE_OMIT_LOAD_EXTENSION=1 \
198199
/DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
199200
--- 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
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,27 @@
11
<title>Change Log</title>
22
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
+
323
<h2>Changes For Version 1.30 (2015-01-19)</h2>
424
* Added the [/help?cmd=bundle|fossil bundle] command.
525
* Added the [/help?cmd=purge|fossil purge] command.
626
* Added the [/help?cmd=publish|fossil publish] command.
727
* Added the [/help?cmd=unpublished|fossil unpublished] command.
828
--- 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 @@
2323
<li> [/timeline | Recent changes]
2424
<li> [./faq.wiki | FAQ]
2525
<li> [./hacker-howto.wiki | Hacker How-To]
2626
<li> [./changes.wiki | Change Log]
2727
<li> [./hints.wiki | Tip &amp; Hints]
28
-<li> [./permutedindex.wiki | Documentation Index]
28
+<li> [./permutedindex.html | Documentation Index]
2929
<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
3030
<li> Mailing list
3131
<ul>
3232
<li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up]
3333
<li> [http://www.mail-archive.com/[email protected] | archives]
3434
--- 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 &amp; 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 &amp; 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
--- www/makefile.wiki
+++ www/makefile.wiki
@@ -224,21 +224,22 @@
224224
225225
Some files require special C-preprocessor macro definitions.
226226
When compiling sqlite.c, the following macros are recommended:
227227
228228
* -DSQLITE_OMIT_LOAD_EXTENSION=1
229
+ * -DSQLITE_ENABLE_FTS4=1
229230
* -DSQLITE_ENABLE_LOCKING_STYLE=0
230231
* -DSQLITE_THREADSAFE=0
231232
* -DSQLITE_DEFAULT_FILE_FORMAT=4
232233
* -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1
233234
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.
240241
241242
When compiling the shell.c source file, these macros are required:
242243
243244
* -Dmain=sqlite3_main
244245
* -DSQLITE_OMIT_LOAD_EXTENSION=1
245246
--- 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
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -26,11 +26,11 @@
2626
<div class="mainmenu">
2727
<a href='/fossil/doc/trunk/www/index.wiki'>Home</a>
2828
<a href='/fossil/timeline?y=ci'>Timeline</a>
2929
<a href='/download.html'>Download</a>
3030
<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>
3232
<a href='/fossil/brlist'>Branches</a>
3333
<a href='/fossil/taglist'>Tags</a>
3434
<a href='/fossil/reportlist'>Tickets</a>
3535
</div>
3636
<div class="content">
3737
--- 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 @@
11
#!/bin/sh
22
#
33
# Run this TCL script to generate a WIKI page that contains a
44
# permuted index of the various documentation files.
55
#
6
-# tclsh mkindex.tcl >permutedindex.wiki
6
+# tclsh mkindex.tcl >permutedindex.html
77
#
88
99
set doclist {
1010
adding_code.wiki {Adding New Features To Fossil}
1111
adding_code.wiki {Hacking Fossil}
@@ -77,26 +77,34 @@
7777
lappend permindex [list "$suffix &mdash; $prefix" $file]
7878
}
7979
}
8080
}
8181
set permindex [lsort -dict -index 0 $permindex]
82
-set out [open permutedindex.wiki w]
82
+set out [open permutedindex.html w]
8383
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'>"
8586
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>
8693
<h2>Primary Documents:</h2>
8794
<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>
94102
</ul>
95103
<a name="pindex"></a>
96104
<h2>Permuted Index:</h2>
97105
<ul>}
98106
foreach entry $permindex {
99107
foreach {title file} $entry break
100108
puts $out "<li><a href=\"$file\">$title</a></li>"
101109
}
102
-puts $out "</ul>"
110
+puts $out "</ul></div>"
103111
104112
ADDED www/permutedindex.html
105113
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 &mdash; $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 &mdash; $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
--- 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>
63
(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>
74
</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>
85
</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 @@
107
</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>
118
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>
129
</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
--- 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 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -387,9 +387,9 @@
387387
388388
<h2>More Hints</h2>
389389
390390
<p>A [/help | complete list of commands] is available, as is the
391391
[./hints.wiki|helpful hints] document. See the
392
- [./permutedindex.wiki#pindex|permuted index] for additional
392
+ [./permutedindex.html#pindex|permuted index] for additional
393393
documentation.
394394
395395
<p>Explore and have fun!</p>
396396
--- 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

Keyboard Shortcuts

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