Fossil SCM

Merge updates from trunk.

mistachkin 2015-02-24 00:51 unifiedStart merge
Commit ae8f6f2b7aa851e8156e628258964e923f45d7d9
96 files changed +17 +17 -1 +3 +4 -4 +3 +4 -4 +3 +4 -4 +3 +4 -4 +22 -11 +19 -51 +3 +4 -4 +3 +4 -4 +3 -1 +4 -4 +49 -13 +34 -22 +1 -1 +5 -5 +18 -18 +2 -2 +2 +3 -3 +10 -9 +1 -1 +12 -12 +1 -1 +19 -7 +156 -92 +9 -7 +51 -49 +51 -49 +154 -108 +145 -52 +2 -1 +1 +13 -13 +16 -10 +5 -2 +4 -1 +85 -10 +85 -10 +2 -2 +39 -6 +7 -4 +292 -80 +43 -26 +120 -5 +10 -7 +24 -12 +120 -9 +5 -4 +10 -10 +15 -10 +2 -2 +12 -1 +120 -17 +74 -50 +22 -13 +11 -8 +1 -1 +29 -22 +71 -15 +3 +4 -2 +12 -1 +10 -10 +21 -21 +1 -1 +1 -1 +1 +2 +1 +1 -1 +48 -15 +25 -64 +5 +22 -8 +30 -30 +30 -28 +56 -11 +1 +51 -75 +11 +23 -24 +3 -1 +10 -1 +9 -2 +1 -1 +2 +60 +2 -2 +6 -2
~ fossil.1 ~ skins/README.md ~ skins/black_and_white/css.txt ~ skins/black_and_white/header.txt ~ skins/default/css.txt ~ skins/default/header.txt ~ skins/eagle/css.txt ~ skins/eagle/header.txt ~ skins/enhanced1/css.txt ~ skins/enhanced1/header.txt ~ skins/etienne1/css.txt ~ skins/etienne1/header.txt ~ skins/khaki/css.txt ~ skins/khaki/header.txt ~ skins/plain_gray/css.txt ~ skins/plain_gray/header.txt ~ skins/rounded1/css.txt ~ skins/rounded1/header.txt ~ src/allrepo.c ~ src/attach.c ~ src/blob.c ~ src/branch.c ~ src/browse.c ~ src/cache.c ~ src/cgi.c ~ src/checkin.c ~ src/db.c ~ src/descendants.c ~ src/diff.c ~ src/diffcmd.c ~ src/doc.c ~ src/event.c ~ src/finfo.c ~ src/info.c ~ src/info.c ~ src/login.c ~ src/main.c ~ src/main.mk ~ src/makemake.tcl ~ src/manifest.c ~ src/markdown.c ~ src/markdown_html.c ~ src/moderate.c ~ src/name.c ~ src/name.c ~ src/path.c ~ src/printf.c ~ src/report.c ~ src/search.c ~ src/setup.c ~ src/shell.c ~ src/shun.c ~ src/sitemap.c ~ src/skins.c ~ src/stat.c ~ src/statrep.c ~ src/style.c ~ src/tag.c ~ src/tar.c ~ src/th_main.c ~ src/timeline.c ~ src/tkt.c ~ src/tktsetup.c ~ src/user.c ~ src/wiki.c ~ src/wikiformat.c ~ src/winhttp.c ~ src/xfersetup.c ~ src/zip.c ~ test/th1-tcl.test ~ test/th1.test ~ win/Makefile.PellesCGMake ~ win/Makefile.dmc ~ win/Makefile.mingw ~ win/Makefile.mingw.mistachkin ~ win/Makefile.msc ~ www/branching.wiki ~ www/changes.wiki ~ www/concepts.wiki ~ www/customskin.md ~ www/embeddeddoc.wiki ~ www/event.wiki ~ www/fileformat.wiki ~ www/fossil-v-git.wiki ~ www/hacker-howto.wiki ~ www/index.wiki ~ www/inout.wiki ~ www/mkdownload.tcl ~ www/mkindex.tcl ~ www/permutedindex.html ~ www/quotes.wiki ~ www/selfcheck.wiki ~ www/th1.md ~ www/webpage-ex.md ~ www/webui.wiki ~ www/wikitheory.wiki
+17
--- a/fossil.1
+++ b/fossil.1
@@ -0,0 +1,17 @@
1
+rsioned
2
+.br
3
+biseFebruary 2015wiki, ticket tracker, CGI/httphttp&and rsioned
4
+.br
5
+bisect In
6
+over seven years of operation, no work has ever been lost after
7
+having been commihangesgdiffddremove clean helpisect ranchundlefinfo open sqlite3February 2015wiki, rsiraise stashlean import tashone info purge ll commit init synciff jsonag
8
+.br
9
+bisect exportmote-url timeline
10
+.br
11
+blame iseFebruary 2015wiki, t icrsioi
12
+.br
13
+branchmv rm undousefsrss
14
+.br
15
+cat gdiff praisepdate
16
+.br
17
+changeshelp qlite3
--- a/fossil.1
+++ b/fossil.1
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/fossil.1
+++ b/fossil.1
@@ -0,0 +1,17 @@
1 rsioned
2 .br
3 biseFebruary 2015wiki, ticket tracker, CGI/httphttp&and rsioned
4 .br
5 bisect In
6 over seven years of operation, no work has ever been lost after
7 having been commihangesgdiffddremove clean helpisect ranchundlefinfo open sqlite3February 2015wiki, rsiraise stashlean import tashone info purge ll commit init synciff jsonag
8 .br
9 bisect exportmote-url timeline
10 .br
11 blame iseFebruary 2015wiki, t icrsioi
12 .br
13 branchmv rm undousefsrss
14 .br
15 cat gdiff praisepdate
16 .br
17 changeshelp qlite3
+17 -1
--- skins/README.md
+++ skins/README.md
@@ -1,11 +1,11 @@
11
Built-in Skins
22
==============
33
44
Each subdirectory under this folder describes a built-in "skin".
55
There are three files in each subdirectory for the CSS, the header,
6
-and the footer for the skin.
6
+and the footer for that skin.
77
88
To improve an existing built-in skin, simply edit the appropriate
99
files and recompile.
1010
1111
To add a new skin:
@@ -23,5 +23,21 @@
2323
2424
4. Edit the BuiltinSkin[] array near the top of the src/skins.c source
2525
file so that it describes and references the "newskin" skin.
2626
2727
5. Type "make" to rebuild.
28
+
29
+Development Hints
30
+-----------------
31
+
32
+One way to develop a new skin is to copy the baseline files (css.txt,
33
+footer.txt, and header.txt) into a working directory $WORKDIR then
34
+launch Fossil with a command-line option "--skin $WORKDIR". Example:
35
+
36
+ cp -r skins/default newskin
37
+ fossil ui --skin ./newskin
38
+
39
+When the argument to --skin contains one or more '/' characters, the
40
+appropriate skin files are read from disk from the directory specified.
41
+So after launching fossil as shown above, you can edit the newskin/css.txt,
42
+newskin/header.txt, and newskin/footer.txt files using your favorite
43
+text editor, then press Reload on your browser to see immediate results.
2844
--- skins/README.md
+++ skins/README.md
@@ -1,11 +1,11 @@
1 Built-in Skins
2 ==============
3
4 Each subdirectory under this folder describes a built-in "skin".
5 There are three files in each subdirectory for the CSS, the header,
6 and the footer for the skin.
7
8 To improve an existing built-in skin, simply edit the appropriate
9 files and recompile.
10
11 To add a new skin:
@@ -23,5 +23,21 @@
23
24 4. Edit the BuiltinSkin[] array near the top of the src/skins.c source
25 file so that it describes and references the "newskin" skin.
26
27 5. Type "make" to rebuild.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
--- skins/README.md
+++ skins/README.md
@@ -1,11 +1,11 @@
1 Built-in Skins
2 ==============
3
4 Each subdirectory under this folder describes a built-in "skin".
5 There are three files in each subdirectory for the CSS, the header,
6 and the footer for that skin.
7
8 To improve an existing built-in skin, simply edit the appropriate
9 files and recompile.
10
11 To add a new skin:
@@ -23,5 +23,21 @@
23
24 4. Edit the BuiltinSkin[] array near the top of the src/skins.c source
25 file so that it describes and references the "newskin" skin.
26
27 5. Type "make" to rebuild.
28
29 Development Hints
30 -----------------
31
32 One way to develop a new skin is to copy the baseline files (css.txt,
33 footer.txt, and header.txt) into a working directory $WORKDIR then
34 launch Fossil with a command-line option "--skin $WORKDIR". Example:
35
36 cp -r skins/default newskin
37 fossil ui --skin ./newskin
38
39 When the argument to --skin contains one or more '/' characters, the
40 appropriate skin files are read from disk from the directory specified.
41 So after launching fossil as shown above, you can edit the newskin/css.txt,
42 newskin/header.txt, and newskin/footer.txt files using your favorite
43 text editor, then press Reload on your browser to see immediate results.
44
--- skins/black_and_white/css.txt
+++ skins/black_and_white/css.txt
@@ -3,10 +3,13 @@
33
margin:0px 0px 0px 0px;
44
padding:0px;
55
font-family:verdana, arial, helvetica, "sans serif";
66
color:#333;
77
background-color:white;
8
+ -moz-text-size-adjust: none;
9
+ -webkit-text-size-adjust: none;
10
+ -mx-text-size-adjust: none;
811
}
912
1013
/* consistent colours */
1114
h2 {
1215
color: #333;
1316
--- skins/black_and_white/css.txt
+++ skins/black_and_white/css.txt
@@ -3,10 +3,13 @@
3 margin:0px 0px 0px 0px;
4 padding:0px;
5 font-family:verdana, arial, helvetica, "sans serif";
6 color:#333;
7 background-color:white;
 
 
 
8 }
9
10 /* consistent colours */
11 h2 {
12 color: #333;
13
--- skins/black_and_white/css.txt
+++ skins/black_and_white/css.txt
@@ -3,10 +3,13 @@
3 margin:0px 0px 0px 0px;
4 padding:0px;
5 font-family:verdana, arial, helvetica, "sans serif";
6 color:#333;
7 background-color:white;
8 -moz-text-size-adjust: none;
9 -webkit-text-size-adjust: none;
10 -mx-text-size-adjust: none;
11 }
12
13 /* consistent colours */
14 h2 {
15 color: #333;
16
--- skins/black_and_white/header.txt
+++ skins/black_and_white/header.txt
@@ -26,21 +26,21 @@
2626
<th1>
2727
html "<a href='$home$index_page'>Home</a>\n"
2828
if {[anycap jor]} {
2929
html "<a href='$home/timeline'>Timeline</a>\n"
3030
}
31
-if {[hascap oh]} {
31
+if {[anoncap oh]} {
3232
html "<a href='$home/tree?ci=tip'>Files</a>\n"
3333
}
34
-if {[hascap o]} {
34
+if {[anoncap o]} {
3535
html "<a href='$home/brlist'>Branches</a>\n"
3636
html "<a href='$home/taglist'>Tags</a>\n"
3737
}
38
-if {[hascap r]} {
38
+if {[anoncap r]} {
3939
html "<a href='$home/ticket'>Tickets</a>\n"
4040
}
41
-if {[hascap j]} {
41
+if {[anoncap j]} {
4242
html "<a href='$home/wiki'>Wiki</a>\n"
4343
}
4444
if {[hascap s]} {
4545
html "<a href='$home/setup'>Admin</a>\n"
4646
} elseif {[hascap a]} {
4747
--- skins/black_and_white/header.txt
+++ skins/black_and_white/header.txt
@@ -26,21 +26,21 @@
26 <th1>
27 html "<a href='$home$index_page'>Home</a>\n"
28 if {[anycap jor]} {
29 html "<a href='$home/timeline'>Timeline</a>\n"
30 }
31 if {[hascap oh]} {
32 html "<a href='$home/tree?ci=tip'>Files</a>\n"
33 }
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 html "<a href='$home/setup'>Admin</a>\n"
46 } elseif {[hascap a]} {
47
--- skins/black_and_white/header.txt
+++ skins/black_and_white/header.txt
@@ -26,21 +26,21 @@
26 <th1>
27 html "<a href='$home$index_page'>Home</a>\n"
28 if {[anycap jor]} {
29 html "<a href='$home/timeline'>Timeline</a>\n"
30 }
31 if {[anoncap oh]} {
32 html "<a href='$home/tree?ci=tip'>Files</a>\n"
33 }
34 if {[anoncap o]} {
35 html "<a href='$home/brlist'>Branches</a>\n"
36 html "<a href='$home/taglist'>Tags</a>\n"
37 }
38 if {[anoncap r]} {
39 html "<a href='$home/ticket'>Tickets</a>\n"
40 }
41 if {[anoncap j]} {
42 html "<a href='$home/wiki'>Wiki</a>\n"
43 }
44 if {[hascap s]} {
45 html "<a href='$home/setup'>Admin</a>\n"
46 } elseif {[hascap a]} {
47
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -2,10 +2,13 @@
22
body {
33
margin: 0ex 1ex;
44
padding: 0px;
55
background-color: white;
66
font-family: sans-serif;
7
+ -moz-text-size-adjust: none;
8
+ -webkit-text-size-adjust: none;
9
+ -mx-text-size-adjust: none;
710
}
811
912
/* The project logo in the upper left-hand corner of each page */
1013
div.logo {
1114
display: table-cell;
1215
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
 
 
 
7 }
8
9 /* The project logo in the upper left-hand corner of each page */
10 div.logo {
11 display: table-cell;
12
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
7 -moz-text-size-adjust: none;
8 -webkit-text-size-adjust: none;
9 -mx-text-size-adjust: none;
10 }
11
12 /* The project logo in the upper left-hand corner of each page */
13 div.logo {
14 display: table-cell;
15
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -25,21 +25,21 @@
2525
<th1>
2626
html "<a href='$home$index_page'>Home</a>\n"
2727
if {[anycap jor]} {
2828
html "<a href='$home/timeline'>Timeline</a>\n"
2929
}
30
-if {[hascap oh]} {
30
+if {[anoncap oh]} {
3131
html "<a href='$home/tree?ci=tip'>Files</a>\n"
3232
}
33
-if {[hascap o]} {
33
+if {[anoncap o]} {
3434
html "<a href='$home/brlist'>Branches</a>\n"
3535
html "<a href='$home/taglist'>Tags</a>\n"
3636
}
37
-if {[hascap r]} {
37
+if {[anoncap r]} {
3838
html "<a href='$home/ticket'>Tickets</a>\n"
3939
}
40
-if {[hascap j]} {
40
+if {[anoncap j]} {
4141
html "<a href='$home/wiki'>Wiki</a>\n"
4242
}
4343
if {[hascap s]} {
4444
html "<a href='$home/setup'>Admin</a>\n"
4545
} elseif {[hascap a]} {
4646
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -25,21 +25,21 @@
25 <th1>
26 html "<a href='$home$index_page'>Home</a>\n"
27 if {[anycap jor]} {
28 html "<a href='$home/timeline'>Timeline</a>\n"
29 }
30 if {[hascap oh]} {
31 html "<a href='$home/tree?ci=tip'>Files</a>\n"
32 }
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 html "<a href='$home/setup'>Admin</a>\n"
45 } elseif {[hascap a]} {
46
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -25,21 +25,21 @@
25 <th1>
26 html "<a href='$home$index_page'>Home</a>\n"
27 if {[anycap jor]} {
28 html "<a href='$home/timeline'>Timeline</a>\n"
29 }
30 if {[anoncap oh]} {
31 html "<a href='$home/tree?ci=tip'>Files</a>\n"
32 }
33 if {[anoncap o]} {
34 html "<a href='$home/brlist'>Branches</a>\n"
35 html "<a href='$home/taglist'>Tags</a>\n"
36 }
37 if {[anoncap r]} {
38 html "<a href='$home/ticket'>Tickets</a>\n"
39 }
40 if {[anoncap j]} {
41 html "<a href='$home/wiki'>Wiki</a>\n"
42 }
43 if {[hascap s]} {
44 html "<a href='$home/setup'>Admin</a>\n"
45 } elseif {[hascap a]} {
46
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -3,10 +3,13 @@
33
margin: 0ex 1ex;
44
padding: 0px;
55
background-color: #485D7B;
66
font-family: sans-serif;
77
color: white;
8
+ -moz-text-size-adjust: none;
9
+ -webkit-text-size-adjust: none;
10
+ -mx-text-size-adjust: none;
811
}
912
1013
/* The project logo in the upper left-hand corner of each page */
1114
div.logo {
1215
display: table-cell;
1316
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -3,10 +3,13 @@
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: #485D7B;
6 font-family: sans-serif;
7 color: white;
 
 
 
8 }
9
10 /* The project logo in the upper left-hand corner of each page */
11 div.logo {
12 display: table-cell;
13
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -3,10 +3,13 @@
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: #485D7B;
6 font-family: sans-serif;
7 color: white;
8 -moz-text-size-adjust: none;
9 -webkit-text-size-adjust: none;
10 -mx-text-size-adjust: none;
11 }
12
13 /* The project logo in the upper left-hand corner of each page */
14 div.logo {
15 display: table-cell;
16
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -106,21 +106,21 @@
106106
html "<a href='$home$index_page'>Home</a>\n"
107107
html "<a href='$home/help'>Help</a>\n"
108108
if {[anycap jor]} {
109109
html "<a href='$home/timeline'>Timeline</a>\n"
110110
}
111
-if {[hascap oh]} {
111
+if {[anoncap oh]} {
112112
html "<a href='$home/tree?ci=tip'>Files</a>\n"
113113
}
114
-if {[hascap o]} {
114
+if {[anoncap o]} {
115115
html "<a href='$home/brlist'>Branches</a>\n"
116116
html "<a href='$home/taglist'>Tags</a>\n"
117117
}
118
-if {[hascap r]} {
118
+if {[anoncap r]} {
119119
html "<a href='$home/ticket'>Tickets</a>\n"
120120
}
121
-if {[hascap j]} {
121
+if {[anoncap j]} {
122122
html "<a href='$home/wiki'>Wiki</a>\n"
123123
}
124124
if {[hascap s]} {
125125
html "<a href='$home/setup'>Admin</a>\n"
126126
} elseif {[hascap a]} {
127127
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -106,21 +106,21 @@
106 html "<a href='$home$index_page'>Home</a>\n"
107 html "<a href='$home/help'>Help</a>\n"
108 if {[anycap jor]} {
109 html "<a href='$home/timeline'>Timeline</a>\n"
110 }
111 if {[hascap oh]} {
112 html "<a href='$home/tree?ci=tip'>Files</a>\n"
113 }
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 html "<a href='$home/setup'>Admin</a>\n"
126 } elseif {[hascap a]} {
127
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -106,21 +106,21 @@
106 html "<a href='$home$index_page'>Home</a>\n"
107 html "<a href='$home/help'>Help</a>\n"
108 if {[anycap jor]} {
109 html "<a href='$home/timeline'>Timeline</a>\n"
110 }
111 if {[anoncap oh]} {
112 html "<a href='$home/tree?ci=tip'>Files</a>\n"
113 }
114 if {[anoncap o]} {
115 html "<a href='$home/brlist'>Branches</a>\n"
116 html "<a href='$home/taglist'>Tags</a>\n"
117 }
118 if {[anoncap r]} {
119 html "<a href='$home/ticket'>Tickets</a>\n"
120 }
121 if {[anoncap j]} {
122 html "<a href='$home/wiki'>Wiki</a>\n"
123 }
124 if {[hascap s]} {
125 html "<a href='$home/setup'>Admin</a>\n"
126 } elseif {[hascap a]} {
127
--- skins/enhanced1/css.txt
+++ skins/enhanced1/css.txt
@@ -2,10 +2,13 @@
22
body {
33
margin: 0ex 1ex;
44
padding: 0px;
55
background-color: white;
66
font-family: sans-serif;
7
+ -moz-text-size-adjust: none;
8
+ -webkit-text-size-adjust: none;
9
+ -mx-text-size-adjust: none;
710
}
811
912
/* The project logo in the upper left-hand corner of each page */
1013
div.logo {
1114
display: table-cell;
1215
--- skins/enhanced1/css.txt
+++ skins/enhanced1/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
 
 
 
7 }
8
9 /* The project logo in the upper left-hand corner of each page */
10 div.logo {
11 display: table-cell;
12
--- skins/enhanced1/css.txt
+++ skins/enhanced1/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
7 -moz-text-size-adjust: none;
8 -webkit-text-size-adjust: none;
9 -mx-text-size-adjust: none;
10 }
11
12 /* The project logo in the upper left-hand corner of each page */
13 div.logo {
14 display: table-cell;
15
--- skins/enhanced1/header.txt
+++ skins/enhanced1/header.txt
@@ -106,21 +106,21 @@
106106
html "<a href='$home$index_page'>Home</a>\n"
107107
html "<a href='$home/help'>Help</a>\n"
108108
if {[anycap jor]} {
109109
html "<a href='$home/timeline'>Timeline</a>\n"
110110
}
111
-if {[hascap oh]} {
111
+if {[anoncap oh]} {
112112
html "<a href='$home/tree?ci=tip'>Files</a>\n"
113113
}
114
-if {[hascap o]} {
114
+if {[anoncap o]} {
115115
html "<a href='$home/brlist'>Branches</a>\n"
116116
html "<a href='$home/taglist'>Tags</a>\n"
117117
}
118
-if {[hascap r]} {
118
+if {[anoncap r]} {
119119
html "<a href='$home/ticket'>Tickets</a>\n"
120120
}
121
-if {[hascap j]} {
121
+if {[anoncap j]} {
122122
html "<a href='$home/wiki'>Wiki</a>\n"
123123
}
124124
if {[hascap s]} {
125125
html "<a href='$home/setup'>Admin</a>\n"
126126
} elseif {[hascap a]} {
127127
--- skins/enhanced1/header.txt
+++ skins/enhanced1/header.txt
@@ -106,21 +106,21 @@
106 html "<a href='$home$index_page'>Home</a>\n"
107 html "<a href='$home/help'>Help</a>\n"
108 if {[anycap jor]} {
109 html "<a href='$home/timeline'>Timeline</a>\n"
110 }
111 if {[hascap oh]} {
112 html "<a href='$home/tree?ci=tip'>Files</a>\n"
113 }
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 html "<a href='$home/setup'>Admin</a>\n"
126 } elseif {[hascap a]} {
127
--- skins/enhanced1/header.txt
+++ skins/enhanced1/header.txt
@@ -106,21 +106,21 @@
106 html "<a href='$home$index_page'>Home</a>\n"
107 html "<a href='$home/help'>Help</a>\n"
108 if {[anycap jor]} {
109 html "<a href='$home/timeline'>Timeline</a>\n"
110 }
111 if {[anoncap oh]} {
112 html "<a href='$home/tree?ci=tip'>Files</a>\n"
113 }
114 if {[anoncap o]} {
115 html "<a href='$home/brlist'>Branches</a>\n"
116 html "<a href='$home/taglist'>Tags</a>\n"
117 }
118 if {[anoncap r]} {
119 html "<a href='$home/ticket'>Tickets</a>\n"
120 }
121 if {[anoncap j]} {
122 html "<a href='$home/wiki'>Wiki</a>\n"
123 }
124 if {[hascap s]} {
125 html "<a href='$home/setup'>Admin</a>\n"
126 } elseif {[hascap a]} {
127
--- skins/etienne1/css.txt
+++ skins/etienne1/css.txt
@@ -1,10 +1,14 @@
11
body {
22
margin: 0 auto;
3
- width: 960px;
3
+ min-width: 800px;
4
+ padding: 0px 20px;
45
font-family: sans-serif;
56
font-size:14pt;
7
+ -moz-text-size-adjust: none;
8
+ -webkit-text-size-adjust: none;
9
+ -mx-text-size-adjust: none;
610
}
711
812
a {
913
color: #4183C4;
1014
text-decoration: none;
@@ -13,11 +17,11 @@
1317
color: #4183C4;
1418
text-decoration: underline;
1519
}
1620
1721
hr {
18
- border: 0px;
22
+ color: #eee;
1923
}
2024
2125
.title {
2226
color: #4183C4;
2327
float:left;
@@ -100,30 +104,29 @@
100104
padding: 10px;
101105
border-bottom: 1px solid #ccc;
102106
}
103107
104108
.submenu a {
105
- padding: 10px;
109
+ padding: 10px 11px;
106110
text-decoration:none;
107111
color: #777;
108112
}
109113
110114
.submenu a:hover {
115
+ padding: 6px 10px;
111116
border: 1px solid #ccc;
112
- border-bottom: 1px solid #fff;
113
- border-top-left-radius: 5px;
114
- border-top-right-radius: 5px;
117
+ border-radius: 5px;
118
+ color: #000;
115119
}
116120
117121
.content {
118122
padding-top: 10px;
119123
font-size:.8em;
120124
color: #444;
121125
}
122126
123
-.udiff, .sbsdiff,
124
-.content blockquote {
127
+.udiff, .sbsdiff {
125128
font-size: .85em !important;
126129
overflow: auto;
127130
border: 1px solid #ccc;
128131
border-radius: 5px;
129132
}
@@ -164,18 +167,19 @@
164167
.report thead+tbody tr:hover {
165168
background-color: #f5f9fc !important;
166169
}
167170
168171
td.tktDspLabel {
169
- max-width: 70px;
172
+ width: 70px;
170173
text-align: right;
174
+ overflow: hidden;
171175
}
172176
td.tktDspValue {
173
- max-width: 800px;
174177
text-align: left;
175178
vertical-align: top;
176
- background-color: #f5f9fc;
179
+ background-color: #f8f8f8;
180
+ border: 1px solid #ccc;
177181
}
178182
td.tktDspValue pre {
179183
white-space: pre-wrap;
180184
}
181185
@@ -183,6 +187,13 @@
183187
border-top: 1px solid #ccc;
184188
padding: 10px;
185189
font-size:.7em;
186190
margin-top: 10px;
187191
color: #ccc;
192
+}
193
+div.timelineDate {
194
+ font-weight: bold;
195
+ white-space: nowrap;
196
+}
197
+span.submenuctrl, span.submenuctrl input, select.submenuctrl {
198
+ color: #777;
188199
}
189200
--- skins/etienne1/css.txt
+++ skins/etienne1/css.txt
@@ -1,10 +1,14 @@
1 body {
2 margin: 0 auto;
3 width: 960px;
 
4 font-family: sans-serif;
5 font-size:14pt;
 
 
 
6 }
7
8 a {
9 color: #4183C4;
10 text-decoration: none;
@@ -13,11 +17,11 @@
13 color: #4183C4;
14 text-decoration: underline;
15 }
16
17 hr {
18 border: 0px;
19 }
20
21 .title {
22 color: #4183C4;
23 float:left;
@@ -100,30 +104,29 @@
100 padding: 10px;
101 border-bottom: 1px solid #ccc;
102 }
103
104 .submenu a {
105 padding: 10px;
106 text-decoration:none;
107 color: #777;
108 }
109
110 .submenu a:hover {
 
111 border: 1px solid #ccc;
112 border-bottom: 1px solid #fff;
113 border-top-left-radius: 5px;
114 border-top-right-radius: 5px;
115 }
116
117 .content {
118 padding-top: 10px;
119 font-size:.8em;
120 color: #444;
121 }
122
123 .udiff, .sbsdiff,
124 .content blockquote {
125 font-size: .85em !important;
126 overflow: auto;
127 border: 1px solid #ccc;
128 border-radius: 5px;
129 }
@@ -164,18 +167,19 @@
164 .report thead+tbody tr:hover {
165 background-color: #f5f9fc !important;
166 }
167
168 td.tktDspLabel {
169 max-width: 70px;
170 text-align: right;
 
171 }
172 td.tktDspValue {
173 max-width: 800px;
174 text-align: left;
175 vertical-align: top;
176 background-color: #f5f9fc;
 
177 }
178 td.tktDspValue pre {
179 white-space: pre-wrap;
180 }
181
@@ -183,6 +187,13 @@
183 border-top: 1px solid #ccc;
184 padding: 10px;
185 font-size:.7em;
186 margin-top: 10px;
187 color: #ccc;
 
 
 
 
 
 
 
188 }
189
--- skins/etienne1/css.txt
+++ skins/etienne1/css.txt
@@ -1,10 +1,14 @@
1 body {
2 margin: 0 auto;
3 min-width: 800px;
4 padding: 0px 20px;
5 font-family: sans-serif;
6 font-size:14pt;
7 -moz-text-size-adjust: none;
8 -webkit-text-size-adjust: none;
9 -mx-text-size-adjust: none;
10 }
11
12 a {
13 color: #4183C4;
14 text-decoration: none;
@@ -13,11 +17,11 @@
17 color: #4183C4;
18 text-decoration: underline;
19 }
20
21 hr {
22 color: #eee;
23 }
24
25 .title {
26 color: #4183C4;
27 float:left;
@@ -100,30 +104,29 @@
104 padding: 10px;
105 border-bottom: 1px solid #ccc;
106 }
107
108 .submenu a {
109 padding: 10px 11px;
110 text-decoration:none;
111 color: #777;
112 }
113
114 .submenu a:hover {
115 padding: 6px 10px;
116 border: 1px solid #ccc;
117 border-radius: 5px;
118 color: #000;
 
119 }
120
121 .content {
122 padding-top: 10px;
123 font-size:.8em;
124 color: #444;
125 }
126
127 .udiff, .sbsdiff {
 
128 font-size: .85em !important;
129 overflow: auto;
130 border: 1px solid #ccc;
131 border-radius: 5px;
132 }
@@ -164,18 +167,19 @@
167 .report thead+tbody tr:hover {
168 background-color: #f5f9fc !important;
169 }
170
171 td.tktDspLabel {
172 width: 70px;
173 text-align: right;
174 overflow: hidden;
175 }
176 td.tktDspValue {
 
177 text-align: left;
178 vertical-align: top;
179 background-color: #f8f8f8;
180 border: 1px solid #ccc;
181 }
182 td.tktDspValue pre {
183 white-space: pre-wrap;
184 }
185
@@ -183,6 +187,13 @@
187 border-top: 1px solid #ccc;
188 padding: 10px;
189 font-size:.7em;
190 margin-top: 10px;
191 color: #ccc;
192 }
193 div.timelineDate {
194 font-weight: bold;
195 white-space: nowrap;
196 }
197 span.submenuctrl, span.submenuctrl input, select.submenuctrl {
198 color: #777;
199 }
200
--- skins/etienne1/header.txt
+++ skins/etienne1/header.txt
@@ -19,70 +19,38 @@
1919
}
2020
</th1></div>
2121
</div>
2222
2323
<div class="mainmenu">
24
- <th1>
25
-proc isin {val lst} {
26
- set tot [llength $lst]
27
- for {set i 0} {$i < $tot} {set i [expr {$i + 1}]} {
28
- set cur [lindex $lst $i]
29
- if {$val eq $cur} {
30
- return 0
31
- }
32
- }
33
- return 1
34
-}
35
-
36
-proc menulink {pagename url name} {
37
- upvar current_page current
38
- upvar home home
39
-
40
- set compsetup [string compare [string range $current 0 4] setup]
41
- set comphome [string compare [string range $current 0 3] home]
42
- set comptag [string compare $current tagtimeline]
43
- set compbr [string compare $current brtimeline]
44
- set compdir [isin $current "artifact ci finfo hexdump"]
45
- set comptl [string compare $current info]
46
- set comptkt [isin $current "modreq rptedit tktnew rptsql rptview"]
47
-
48
- html "<a href='$home$url'"
49
-
50
- if {$pagename eq $current
51
- || ($pagename eq "home" && $comphome == 0)
52
- || ($pagename eq "setup" && $compsetup == 0)
53
- || ($pagename eq "taglist" && $comptag == 0)
54
- || ($pagename eq "dir" && $compdir == 0)
55
- || ($pagename eq "timeline" && $comptl == 0)
56
- || ($pagename eq "ticket" && $comptkt == 0)
57
- || ($pagename eq "brlist" && $compbr == 0)
58
- } {
59
- html " class='active' "
60
- }
61
-
62
- html ">$name</a>"
63
-}
64
-
65
-menulink "home" $index_page Home
66
-
24
+<th1>
25
+proc menulink {url name} {
26
+ upvar current_page current
27
+ upvar home home
28
+ if {[string range $url 0 [string length $current]] eq "/$current"} {
29
+ html "<a href='$home$url' class='active'>$name</a>\n"
30
+ } else {
31
+ html "<a href='$home$url'>$name</a>\n"
32
+ }
33
+}
34
+menulink $index_page Home
6735
if {[anycap jor]} {
68
- menulink "timeline" "/timeline" Timeline
36
+ menulink /timeline Timeline
6937
}
7038
if {[hascap oh]} {
71
- menulink "dir" "/dir?ci=tip" Files
39
+ menulink /dir?ci=tip Files
7240
}
7341
if {[hascap o]} {
74
- menulink "brlist" "/brlist" Branches
75
- menulink "taglist" "/taglist" Tags
42
+ menulink /brlist Branches
43
+ menulink /taglist Tags
7644
}
7745
if {[hascap r]} {
78
- menulink "ticket" "/ticket" Tickets
46
+ menulink /ticket Tickets
7947
}
8048
if {[hascap j]} {
81
- menulink "wiki" "/wiki" Wiki
49
+ menulink /wiki Wiki
8250
}
8351
if {[hascap s]} {
84
- menulink "setup" "/setup" Admin
52
+ menulink /setup Admin
8553
} elseif {[hascap a]} {
86
- menulink "setup_ulist" "/setup_ulist" Users
54
+ menulink /setup_ulist Users
8755
}
8856
</th1></div>
8957
--- skins/etienne1/header.txt
+++ skins/etienne1/header.txt
@@ -19,70 +19,38 @@
19 }
20 </th1></div>
21 </div>
22
23 <div class="mainmenu">
24 <th1>
25 proc isin {val lst} {
26 set tot [llength $lst]
27 for {set i 0} {$i < $tot} {set i [expr {$i + 1}]} {
28 set cur [lindex $lst $i]
29 if {$val eq $cur} {
30 return 0
31 }
32 }
33 return 1
34 }
35
36 proc menulink {pagename url name} {
37 upvar current_page current
38 upvar home home
39
40 set compsetup [string compare [string range $current 0 4] setup]
41 set comphome [string compare [string range $current 0 3] home]
42 set comptag [string compare $current tagtimeline]
43 set compbr [string compare $current brtimeline]
44 set compdir [isin $current "artifact ci finfo hexdump"]
45 set comptl [string compare $current info]
46 set comptkt [isin $current "modreq rptedit tktnew rptsql rptview"]
47
48 html "<a href='$home$url'"
49
50 if {$pagename eq $current
51 || ($pagename eq "home" && $comphome == 0)
52 || ($pagename eq "setup" && $compsetup == 0)
53 || ($pagename eq "taglist" && $comptag == 0)
54 || ($pagename eq "dir" && $compdir == 0)
55 || ($pagename eq "timeline" && $comptl == 0)
56 || ($pagename eq "ticket" && $comptkt == 0)
57 || ($pagename eq "brlist" && $compbr == 0)
58 } {
59 html " class='active' "
60 }
61
62 html ">$name</a>"
63 }
64
65 menulink "home" $index_page Home
66
67 if {[anycap jor]} {
68 menulink "timeline" "/timeline" Timeline
69 }
70 if {[hascap oh]} {
71 menulink "dir" "/dir?ci=tip" Files
72 }
73 if {[hascap o]} {
74 menulink "brlist" "/brlist" Branches
75 menulink "taglist" "/taglist" Tags
76 }
77 if {[hascap r]} {
78 menulink "ticket" "/ticket" Tickets
79 }
80 if {[hascap j]} {
81 menulink "wiki" "/wiki" Wiki
82 }
83 if {[hascap s]} {
84 menulink "setup" "/setup" Admin
85 } elseif {[hascap a]} {
86 menulink "setup_ulist" "/setup_ulist" Users
87 }
88 </th1></div>
89
--- skins/etienne1/header.txt
+++ skins/etienne1/header.txt
@@ -19,70 +19,38 @@
19 }
20 </th1></div>
21 </div>
22
23 <div class="mainmenu">
24 <th1>
25 proc menulink {url name} {
26 upvar current_page current
27 upvar home home
28 if {[string range $url 0 [string length $current]] eq "/$current"} {
29 html "<a href='$home$url' class='active'>$name</a>\n"
30 } else {
31 html "<a href='$home$url'>$name</a>\n"
32 }
33 }
34 menulink $index_page Home
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35 if {[anycap jor]} {
36 menulink /timeline Timeline
37 }
38 if {[hascap oh]} {
39 menulink /dir?ci=tip Files
40 }
41 if {[hascap o]} {
42 menulink /brlist Branches
43 menulink /taglist Tags
44 }
45 if {[hascap r]} {
46 menulink /ticket Tickets
47 }
48 if {[hascap j]} {
49 menulink /wiki Wiki
50 }
51 if {[hascap s]} {
52 menulink /setup Admin
53 } elseif {[hascap a]} {
54 menulink /setup_ulist Users
55 }
56 </th1></div>
57
--- skins/khaki/css.txt
+++ skins/khaki/css.txt
@@ -2,10 +2,13 @@
22
body {
33
margin: 0ex 0ex;
44
padding: 0px;
55
background-color: #fef3bc;
66
font-family: sans-serif;
7
+ -moz-text-size-adjust: none;
8
+ -webkit-text-size-adjust: none;
9
+ -mx-text-size-adjust: none;
710
}
811
912
/* The project logo in the upper left-hand corner of each page */
1013
div.logo {
1114
display: inline;
1215
--- skins/khaki/css.txt
+++ skins/khaki/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 0ex;
4 padding: 0px;
5 background-color: #fef3bc;
6 font-family: sans-serif;
 
 
 
7 }
8
9 /* The project logo in the upper left-hand corner of each page */
10 div.logo {
11 display: inline;
12
--- skins/khaki/css.txt
+++ skins/khaki/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 0ex;
4 padding: 0px;
5 background-color: #fef3bc;
6 font-family: sans-serif;
7 -moz-text-size-adjust: none;
8 -webkit-text-size-adjust: none;
9 -mx-text-size-adjust: none;
10 }
11
12 /* The project logo in the upper left-hand corner of each page */
13 div.logo {
14 display: inline;
15
--- skins/khaki/header.txt
+++ skins/khaki/header.txt
@@ -24,21 +24,21 @@
2424
<th1>
2525
html "<a href='$home$index_page'>Home</a>\n"
2626
if {[anycap jor]} {
2727
html "<a href='$home/timeline'>Timeline</a>\n"
2828
}
29
-if {[hascap oh]} {
29
+if {[anoncap oh]} {
3030
html "<a href='$home/tree?ci=tip'>Files</a>\n"
3131
}
32
-if {[hascap o]} {
32
+if {[anoncap o]} {
3333
html "<a href='$home/brlist'>Branches</a>\n"
3434
html "<a href='$home/taglist'>Tags</a>\n"
3535
}
36
-if {[hascap r]} {
36
+if {[anoncap r]} {
3737
html "<a href='$home/ticket'>Tickets</a>\n"
3838
}
39
-if {[hascap j]} {
39
+if {[anoncap j]} {
4040
html "<a href='$home/wiki'>Wiki</a>\n"
4141
}
4242
if {[hascap s]} {
4343
html "<a href='$home/setup'>Admin</a>\n"
4444
} elseif {[hascap a]} {
4545
--- skins/khaki/header.txt
+++ skins/khaki/header.txt
@@ -24,21 +24,21 @@
24 <th1>
25 html "<a href='$home$index_page'>Home</a>\n"
26 if {[anycap jor]} {
27 html "<a href='$home/timeline'>Timeline</a>\n"
28 }
29 if {[hascap oh]} {
30 html "<a href='$home/tree?ci=tip'>Files</a>\n"
31 }
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 html "<a href='$home/setup'>Admin</a>\n"
44 } elseif {[hascap a]} {
45
--- skins/khaki/header.txt
+++ skins/khaki/header.txt
@@ -24,21 +24,21 @@
24 <th1>
25 html "<a href='$home$index_page'>Home</a>\n"
26 if {[anycap jor]} {
27 html "<a href='$home/timeline'>Timeline</a>\n"
28 }
29 if {[anoncap oh]} {
30 html "<a href='$home/tree?ci=tip'>Files</a>\n"
31 }
32 if {[anoncap o]} {
33 html "<a href='$home/brlist'>Branches</a>\n"
34 html "<a href='$home/taglist'>Tags</a>\n"
35 }
36 if {[anoncap r]} {
37 html "<a href='$home/ticket'>Tickets</a>\n"
38 }
39 if {[anoncap j]} {
40 html "<a href='$home/wiki'>Wiki</a>\n"
41 }
42 if {[hascap s]} {
43 html "<a href='$home/setup'>Admin</a>\n"
44 } elseif {[hascap a]} {
45
--- skins/plain_gray/css.txt
+++ skins/plain_gray/css.txt
@@ -2,10 +2,13 @@
22
body {
33
margin: 0ex 1ex;
44
padding: 0px;
55
background-color: white;
66
font-family: sans-serif;
7
+ -moz-text-size-adjust: none;
8
+ -webkit-text-size-adjust: none;
9
+ -mx-text-size-adjust: none;
710
}
811
912
/* The project logo in the upper left-hand corner of each page */
1013
div.logo {
1114
display: table-row;
1215
--- skins/plain_gray/css.txt
+++ skins/plain_gray/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
 
 
 
7 }
8
9 /* The project logo in the upper left-hand corner of each page */
10 div.logo {
11 display: table-row;
12
--- skins/plain_gray/css.txt
+++ skins/plain_gray/css.txt
@@ -2,10 +2,13 @@
2 body {
3 margin: 0ex 1ex;
4 padding: 0px;
5 background-color: white;
6 font-family: sans-serif;
7 -moz-text-size-adjust: none;
8 -webkit-text-size-adjust: none;
9 -mx-text-size-adjust: none;
10 }
11
12 /* The project logo in the upper left-hand corner of each page */
13 div.logo {
14 display: table-row;
15
--- skins/plain_gray/header.txt
+++ skins/plain_gray/header.txt
@@ -22,21 +22,21 @@
2222
<th1>
2323
html "<a href='$home$index_page'>Home</a>\n"
2424
if {[anycap jor]} {
2525
html "<a href='$home/timeline'>Timeline</a>\n"
2626
}
27
-if {[hascap oh]} {
27
+if {[anoncap oh]} {
2828
html "<a href='$home/tree?ci=tip'>Files</a>\n"
2929
}
30
-if {[hascap o]} {
30
+if {[anoncap o]} {
3131
html "<a href='$home/brlist'>Branches</a>\n"
3232
html "<a href='$home/taglist'>Tags</a>\n"
3333
}
34
-if {[hascap r]} {
34
+if {[anoncap r]} {
3535
html "<a href='$home/ticket'>Tickets</a>\n"
3636
}
37
-if {[hascap j]} {
37
+if {[anoncap j]} {
3838
html "<a href='$home/wiki'>Wiki</a>\n"
3939
}
4040
if {[hascap s]} {
4141
html "<a href='$home/setup'>Admin</a>\n"
4242
} elseif {[hascap a]} {
4343
--- skins/plain_gray/header.txt
+++ skins/plain_gray/header.txt
@@ -22,21 +22,21 @@
22 <th1>
23 html "<a href='$home$index_page'>Home</a>\n"
24 if {[anycap jor]} {
25 html "<a href='$home/timeline'>Timeline</a>\n"
26 }
27 if {[hascap oh]} {
28 html "<a href='$home/tree?ci=tip'>Files</a>\n"
29 }
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 html "<a href='$home/setup'>Admin</a>\n"
42 } elseif {[hascap a]} {
43
--- skins/plain_gray/header.txt
+++ skins/plain_gray/header.txt
@@ -22,21 +22,21 @@
22 <th1>
23 html "<a href='$home$index_page'>Home</a>\n"
24 if {[anycap jor]} {
25 html "<a href='$home/timeline'>Timeline</a>\n"
26 }
27 if {[anoncap oh]} {
28 html "<a href='$home/tree?ci=tip'>Files</a>\n"
29 }
30 if {[anoncap o]} {
31 html "<a href='$home/brlist'>Branches</a>\n"
32 html "<a href='$home/taglist'>Tags</a>\n"
33 }
34 if {[anoncap r]} {
35 html "<a href='$home/ticket'>Tickets</a>\n"
36 }
37 if {[anoncap j]} {
38 html "<a href='$home/wiki'>Wiki</a>\n"
39 }
40 if {[hascap s]} {
41 html "<a href='$home/setup'>Admin</a>\n"
42 } elseif {[hascap a]} {
43
--- skins/rounded1/css.txt
+++ skins/rounded1/css.txt
@@ -7,10 +7,13 @@
77
padding: 0px;
88
background-color: white;
99
color: #333;
1010
font-family: Verdana, sans-serif;
1111
font-size: 0.8em;
12
+ -moz-text-size-adjust: none;
13
+ -webkit-text-size-adjust: none;
14
+ -mx-text-size-adjust: none;
1215
}
1316
1417
/* The project logo in the upper left-hand corner of each page */
1518
div.logo {
1619
display: table-cell;
@@ -181,15 +184,14 @@
181184
cursor: pointer;
182185
}
183186
184187
table.report tr td {
185188
padding: 3px 5px;
186
- cursor: pointer;
187189
}
188190
189191
textarea {
190192
font-size: 1em;
191193
}
192194
193195
.fullsize-text {
194196
font-size: 1.25em;
195197
}
196198
--- skins/rounded1/css.txt
+++ skins/rounded1/css.txt
@@ -7,10 +7,13 @@
7 padding: 0px;
8 background-color: white;
9 color: #333;
10 font-family: Verdana, sans-serif;
11 font-size: 0.8em;
 
 
 
12 }
13
14 /* The project logo in the upper left-hand corner of each page */
15 div.logo {
16 display: table-cell;
@@ -181,15 +184,14 @@
181 cursor: pointer;
182 }
183
184 table.report tr td {
185 padding: 3px 5px;
186 cursor: pointer;
187 }
188
189 textarea {
190 font-size: 1em;
191 }
192
193 .fullsize-text {
194 font-size: 1.25em;
195 }
196
--- skins/rounded1/css.txt
+++ skins/rounded1/css.txt
@@ -7,10 +7,13 @@
7 padding: 0px;
8 background-color: white;
9 color: #333;
10 font-family: Verdana, sans-serif;
11 font-size: 0.8em;
12 -moz-text-size-adjust: none;
13 -webkit-text-size-adjust: none;
14 -mx-text-size-adjust: none;
15 }
16
17 /* The project logo in the upper left-hand corner of each page */
18 div.logo {
19 display: table-cell;
@@ -181,15 +184,14 @@
184 cursor: pointer;
185 }
186
187 table.report tr td {
188 padding: 3px 5px;
 
189 }
190
191 textarea {
192 font-size: 1em;
193 }
194
195 .fullsize-text {
196 font-size: 1.25em;
197 }
198
--- skins/rounded1/header.txt
+++ skins/rounded1/header.txt
@@ -26,21 +26,21 @@
2626
<th1>
2727
html "<a href='$home$index_page'>Home</a>\n"
2828
if {[anycap jor]} {
2929
html "<a href='$home/timeline'>Timeline</a>\n"
3030
}
31
-if {[hascap oh]} {
31
+if {[anoncap oh]} {
3232
html "<a href='$home/tree?ci=tip'>Files</a>\n"
3333
}
34
-if {[hascap o]} {
34
+if {[anoncap o]} {
3535
html "<a href='$home/brlist'>Branches</a>\n"
3636
html "<a href='$home/taglist'>Tags</a>\n"
3737
}
38
-if {[hascap r]} {
38
+if {[anoncap r]} {
3939
html "<a href='$home/ticket'>Tickets</a>\n"
4040
}
41
-if {[hascap j]} {
41
+if {[anoncap j]} {
4242
html "<a href='$home/wiki'>Wiki</a>\n"
4343
}
4444
if {[hascap s]} {
4545
html "<a href='$home/setup'>Admin</a>\n"
4646
} elseif {[hascap a]} {
4747
--- skins/rounded1/header.txt
+++ skins/rounded1/header.txt
@@ -26,21 +26,21 @@
26 <th1>
27 html "<a href='$home$index_page'>Home</a>\n"
28 if {[anycap jor]} {
29 html "<a href='$home/timeline'>Timeline</a>\n"
30 }
31 if {[hascap oh]} {
32 html "<a href='$home/tree?ci=tip'>Files</a>\n"
33 }
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 html "<a href='$home/setup'>Admin</a>\n"
46 } elseif {[hascap a]} {
47
--- skins/rounded1/header.txt
+++ skins/rounded1/header.txt
@@ -26,21 +26,21 @@
26 <th1>
27 html "<a href='$home$index_page'>Home</a>\n"
28 if {[anycap jor]} {
29 html "<a href='$home/timeline'>Timeline</a>\n"
30 }
31 if {[anoncap oh]} {
32 html "<a href='$home/tree?ci=tip'>Files</a>\n"
33 }
34 if {[anoncap o]} {
35 html "<a href='$home/brlist'>Branches</a>\n"
36 html "<a href='$home/taglist'>Tags</a>\n"
37 }
38 if {[anoncap r]} {
39 html "<a href='$home/ticket'>Tickets</a>\n"
40 }
41 if {[anoncap j]} {
42 html "<a href='$home/wiki'>Wiki</a>\n"
43 }
44 if {[hascap s]} {
45 html "<a href='$home/setup'>Admin</a>\n"
46 } elseif {[hascap a]} {
47
+49 -13
--- src/allrepo.c
+++ src/allrepo.c
@@ -105,20 +105,12 @@
105105
**
106106
** extras Shows "extra" files from all local checkouts. The command
107107
** line options supported by the extra command itself, if any
108108
** are present, are passed along verbatim.
109109
**
110
-** ignore Arguments are repositories that should be ignored by
111
-** subsequent clean, extras, list, pull, push, rebuild, and
112
-** sync operations. The -c|--ckout option causes the listed
113
-** local checkouts to be ignored instead.
114
-**
115110
** info Run the "info" command on all repositories.
116111
**
117
-** list | ls Display the location of all repositories. The -c|--ckout
118
-** option causes all local checkouts to be listed instead.
119
-**
120112
** pull Run a "pull" operation on all repositories. Only the
121113
** --verbose option is supported.
122114
**
123115
** push Run a "push" on all repositories. Only the --verbose
124116
** option is supported.
@@ -134,10 +126,25 @@
134126
** setting Run the "setting", "set", or "unset" commands on all
135127
** set repositories. These command are particularly useful in
136128
** unset conjunction with the "max-loadavg" setting which cannot
137129
** otherwise be set globally.
138130
**
131
+** In addition, the following maintenance operations are supported:
132
+**
133
+** add Add all the repositories named to the set of repositories
134
+** tracked by Fossil. Normally Fossil is able to keep up with
135
+** this list by itself, but sometime it can benefit from this
136
+** hint if you rename repositories.
137
+**
138
+** ignore Arguments are repositories that should be ignored by
139
+** subsequent clean, extras, list, pull, push, rebuild, and
140
+** sync operations. The -c|--ckout option causes the listed
141
+** local checkouts to be ignored instead.
142
+**
143
+** list | ls Display the location of all repositories. The -c|--ckout
144
+** option causes all local checkouts to be listed instead.
145
+**
139146
** Repositories are automatically added to the set of known repositories
140147
** when one of the following commands are run against the repository:
141148
** clone, info, pull, push, or sync. Even previously ignored repositories
142149
** are added back to the list of repositories by these commands.
143150
**
@@ -259,26 +266,55 @@
259266
useCheckouts = 1;
260267
stopOnError = 0;
261268
quiet = 1;
262269
}else if( strncmp(zCmd, "ignore", n)==0 ){
263270
int j;
271
+ Blob fn = BLOB_INITIALIZER;
272
+ Blob sql = BLOB_INITIALIZER;
264273
useCheckouts = find_option("ckout","c",0)!=0;
265274
verify_all_options();
266275
db_begin_transaction();
267
- for(j=3; j<g.argc; j++){
268
- Blob sql;
269
- blob_zero(&sql);
276
+ for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){
277
+ file_canonical_name(g.argv[j], &fn, 0);
270278
blob_append_sql(&sql,
271279
"DELETE FROM global_config WHERE name GLOB '%s:%q'",
272
- useCheckouts?"ckout":"repo", g.argv[j]
280
+ useCheckouts?"ckout":"repo", blob_str(&fn)
273281
);
274282
if( dryRunFlag ){
275283
fossil_print("%s\n", blob_sql_text(&sql));
276284
}else{
277285
db_multi_exec("%s", blob_sql_text(&sql));
278286
}
279
- blob_reset(&sql);
287
+ }
288
+ db_end_transaction(0);
289
+ return;
290
+ }else if( strncmp(zCmd, "add", n)==0 ){
291
+ int j;
292
+ Blob fn = BLOB_INITIALIZER;
293
+ Blob sql = BLOB_INITIALIZER;
294
+ verify_all_options();
295
+ db_begin_transaction();
296
+ for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){
297
+ sqlite3 *db;
298
+ int rc;
299
+ const char *z;
300
+ file_canonical_name(g.argv[j], &fn, 0);
301
+ z = blob_str(&fn);
302
+ if( !file_isfile(z) ) continue;
303
+ rc = sqlite3_open(z, &db);
304
+ if( rc!=SQLITE_OK ){ sqlite3_close(db); continue; }
305
+ rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0);
306
+ sqlite3_close(db);
307
+ if( rc!=SQLITE_OK ) continue;
308
+ blob_append_sql(&sql,
309
+ "INSERT INTO global_config(name,value)VALUES('repo:%q',1)", z
310
+ );
311
+ if( dryRunFlag ){
312
+ fossil_print("%s\n", blob_sql_text(&sql));
313
+ }else{
314
+ db_multi_exec("%s", blob_sql_text(&sql));
315
+ }
280316
}
281317
db_end_transaction(0);
282318
return;
283319
}else if( strncmp(zCmd, "info", n)==0 ){
284320
zCmd = "info";
285321
--- src/allrepo.c
+++ src/allrepo.c
@@ -105,20 +105,12 @@
105 **
106 ** extras Shows "extra" files from all local checkouts. The command
107 ** line options supported by the extra command itself, if any
108 ** are present, are passed along verbatim.
109 **
110 ** ignore Arguments are repositories that should be ignored by
111 ** subsequent clean, extras, list, pull, push, rebuild, and
112 ** sync operations. The -c|--ckout option causes the listed
113 ** local checkouts to be ignored instead.
114 **
115 ** info Run the "info" command on all repositories.
116 **
117 ** list | ls Display the location of all repositories. The -c|--ckout
118 ** option causes all local checkouts to be listed instead.
119 **
120 ** pull Run a "pull" operation on all repositories. Only the
121 ** --verbose option is supported.
122 **
123 ** push Run a "push" on all repositories. Only the --verbose
124 ** option is supported.
@@ -134,10 +126,25 @@
134 ** setting Run the "setting", "set", or "unset" commands on all
135 ** set repositories. These command are particularly useful in
136 ** unset conjunction with the "max-loadavg" setting which cannot
137 ** otherwise be set globally.
138 **
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139 ** Repositories are automatically added to the set of known repositories
140 ** when one of the following commands are run against the repository:
141 ** clone, info, pull, push, or sync. Even previously ignored repositories
142 ** are added back to the list of repositories by these commands.
143 **
@@ -259,26 +266,55 @@
259 useCheckouts = 1;
260 stopOnError = 0;
261 quiet = 1;
262 }else if( strncmp(zCmd, "ignore", n)==0 ){
263 int j;
 
 
264 useCheckouts = find_option("ckout","c",0)!=0;
265 verify_all_options();
266 db_begin_transaction();
267 for(j=3; j<g.argc; j++){
268 Blob sql;
269 blob_zero(&sql);
270 blob_append_sql(&sql,
271 "DELETE FROM global_config WHERE name GLOB '%s:%q'",
272 useCheckouts?"ckout":"repo", g.argv[j]
273 );
274 if( dryRunFlag ){
275 fossil_print("%s\n", blob_sql_text(&sql));
276 }else{
277 db_multi_exec("%s", blob_sql_text(&sql));
278 }
279 blob_reset(&sql);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280 }
281 db_end_transaction(0);
282 return;
283 }else if( strncmp(zCmd, "info", n)==0 ){
284 zCmd = "info";
285
--- src/allrepo.c
+++ src/allrepo.c
@@ -105,20 +105,12 @@
105 **
106 ** extras Shows "extra" files from all local checkouts. The command
107 ** line options supported by the extra command itself, if any
108 ** are present, are passed along verbatim.
109 **
 
 
 
 
 
110 ** info Run the "info" command on all repositories.
111 **
 
 
 
112 ** pull Run a "pull" operation on all repositories. Only the
113 ** --verbose option is supported.
114 **
115 ** push Run a "push" on all repositories. Only the --verbose
116 ** option is supported.
@@ -134,10 +126,25 @@
126 ** setting Run the "setting", "set", or "unset" commands on all
127 ** set repositories. These command are particularly useful in
128 ** unset conjunction with the "max-loadavg" setting which cannot
129 ** otherwise be set globally.
130 **
131 ** In addition, the following maintenance operations are supported:
132 **
133 ** add Add all the repositories named to the set of repositories
134 ** tracked by Fossil. Normally Fossil is able to keep up with
135 ** this list by itself, but sometime it can benefit from this
136 ** hint if you rename repositories.
137 **
138 ** ignore Arguments are repositories that should be ignored by
139 ** subsequent clean, extras, list, pull, push, rebuild, and
140 ** sync operations. The -c|--ckout option causes the listed
141 ** local checkouts to be ignored instead.
142 **
143 ** list | ls Display the location of all repositories. The -c|--ckout
144 ** option causes all local checkouts to be listed instead.
145 **
146 ** Repositories are automatically added to the set of known repositories
147 ** when one of the following commands are run against the repository:
148 ** clone, info, pull, push, or sync. Even previously ignored repositories
149 ** are added back to the list of repositories by these commands.
150 **
@@ -259,26 +266,55 @@
266 useCheckouts = 1;
267 stopOnError = 0;
268 quiet = 1;
269 }else if( strncmp(zCmd, "ignore", n)==0 ){
270 int j;
271 Blob fn = BLOB_INITIALIZER;
272 Blob sql = BLOB_INITIALIZER;
273 useCheckouts = find_option("ckout","c",0)!=0;
274 verify_all_options();
275 db_begin_transaction();
276 for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){
277 file_canonical_name(g.argv[j], &fn, 0);
 
278 blob_append_sql(&sql,
279 "DELETE FROM global_config WHERE name GLOB '%s:%q'",
280 useCheckouts?"ckout":"repo", blob_str(&fn)
281 );
282 if( dryRunFlag ){
283 fossil_print("%s\n", blob_sql_text(&sql));
284 }else{
285 db_multi_exec("%s", blob_sql_text(&sql));
286 }
287 }
288 db_end_transaction(0);
289 return;
290 }else if( strncmp(zCmd, "add", n)==0 ){
291 int j;
292 Blob fn = BLOB_INITIALIZER;
293 Blob sql = BLOB_INITIALIZER;
294 verify_all_options();
295 db_begin_transaction();
296 for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){
297 sqlite3 *db;
298 int rc;
299 const char *z;
300 file_canonical_name(g.argv[j], &fn, 0);
301 z = blob_str(&fn);
302 if( !file_isfile(z) ) continue;
303 rc = sqlite3_open(z, &db);
304 if( rc!=SQLITE_OK ){ sqlite3_close(db); continue; }
305 rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0);
306 sqlite3_close(db);
307 if( rc!=SQLITE_OK ) continue;
308 blob_append_sql(&sql,
309 "INSERT INTO global_config(name,value)VALUES('repo:%q',1)", z
310 );
311 if( dryRunFlag ){
312 fossil_print("%s\n", blob_sql_text(&sql));
313 }else{
314 db_multi_exec("%s", blob_sql_text(&sql));
315 }
316 }
317 db_end_transaction(0);
318 return;
319 }else if( strncmp(zCmd, "info", n)==0 ){
320 zCmd = "info";
321
+34 -22
--- src/attach.c
+++ src/attach.c
@@ -48,19 +48,22 @@
4848
" (SELECT uuid FROM blob WHERE rid=attachid), attachid"
4949
" FROM attachment",
5050
timeline_utc()
5151
);
5252
if( zPage ){
53
- if( g.perm.RdWiki==0 ) login_needed();
53
+ if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
5454
style_header("Attachments To %h", zPage);
5555
blob_append_sql(&sql, " WHERE target=%Q", zPage);
5656
}else if( zTkt ){
57
- if( g.perm.RdTkt==0 ) login_needed();
57
+ if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
5858
style_header("Attachments To Ticket %S", zTkt);
5959
blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
6060
}else{
61
- if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed();
61
+ if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
62
+ login_needed(g.anon.RdTkt || g.anon.RdWiki);
63
+ return;
64
+ }
6265
style_header("All Attachments");
6366
}
6467
blob_append_sql(&sql, " ORDER BY mtime DESC");
6568
db_prepare(&q, "%s", blob_sql_text(&sql));
6669
@ <ol>
@@ -86,11 +89,11 @@
8689
zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
8790
}else{
8891
zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
8992
}
9093
@ <li><p>
91
- @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a>
94
+ @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
9295
if( moderation_pending(attachid) ){
9396
@ <span class="modpending">*** Awaiting Moderator Approval ***</span>
9497
}
9598
@ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
9699
@ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
@@ -103,14 +106,14 @@
103106
zSrc = "Deleted from";
104107
}else {
105108
zSrc = "Added to";
106109
}
107110
if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
108
- @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
111
+ @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
109112
@ %S(zTarget)</a>
110113
}else{
111
- @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
114
+ @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
112115
@ %h(zTarget)</a>
113116
}
114117
}else{
115118
if( zSrc==0 || zSrc[0]==0 ){
116119
@ Deleted
@@ -150,14 +153,14 @@
150153
151154
if( zPage && zTkt ) zTkt = 0;
152155
if( zFile==0 ) fossil_redirect_home();
153156
login_check_credentials();
154157
if( zPage ){
155
- if( g.perm.RdWiki==0 ) login_needed();
158
+ if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
156159
zTarget = zPage;
157160
}else if( zTkt ){
158
- if( g.perm.RdTkt==0 ) login_needed();
161
+ if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
159162
zTarget = zTkt;
160163
}else{
161164
fossil_redirect_home();
162165
}
163166
if( attachid>0 ){
@@ -243,27 +246,33 @@
243246
if( P("cancel") ) cgi_redirect(zFrom);
244247
if( zPage && zTkt ) fossil_redirect_home();
245248
if( zPage==0 && zTkt==0 ) fossil_redirect_home();
246249
login_check_credentials();
247250
if( zPage ){
248
- if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed();
251
+ if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
252
+ login_needed(g.anon.ApndWiki && g.anon.Attach);
253
+ return;
254
+ }
249255
if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
250256
fossil_redirect_home();
251257
}
252258
zTarget = zPage;
253
- zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>",
254
- g.zTop, zPage, zPage);
259
+ zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
260
+ zPage, zPage);
255261
}else{
256
- if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
262
+ if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
263
+ login_needed(g.anon.ApndTkt && g.anon.Attach);
264
+ return;
265
+ }
257266
if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
258267
zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
259268
" WHERE tagname GLOB 'tkt-%q*'", zTkt);
260269
if( zTkt==0 ) fossil_redirect_home();
261270
}
262271
zTarget = zTkt;
263
- zTargetType = mprintf("Ticket <a href=\"%s/tktview/%s\">%S</a>",
264
- g.zTop, zTkt, zTkt);
272
+ zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
273
+ zTkt, zTkt);
265274
}
266275
if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
267276
if( P("cancel") ){
268277
cgi_redirect(zFrom);
269278
}
@@ -369,11 +378,14 @@
369378
Blob attach; /* Content of the attachment */
370379
int fShowContent = 0;
371380
const char *zLn = P("ln");
372381
373382
login_check_credentials();
374
- if( !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; }
383
+ if( !g.perm.RdTkt && !g.perm.RdWiki ){
384
+ login_needed(g.anon.RdTkt || g.anon.RdWiki);
385
+ return;
386
+ }
375387
rid = name_to_rid_www("name");
376388
if( rid==0 ){ fossil_redirect_home(); }
377389
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
378390
#if 0
379391
/* Shunning here needs to get both the attachment control artifact and
@@ -399,17 +411,17 @@
399411
fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
400412
if( validate16(zTarget, strlen(zTarget))
401413
&& db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
402414
){
403415
zTktUuid = zTarget;
404
- if( !g.perm.RdTkt ){ login_needed(); return; }
416
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
405417
if( g.perm.WrTkt ){
406418
style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
407419
}
408420
}else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
409421
zWikiName = zTarget;
410
- if( !g.perm.RdWiki ){ login_needed(); return; }
422
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
411423
if( g.perm.WrWiki ){
412424
style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
413425
}
414426
}
415427
zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
@@ -443,11 +455,11 @@
443455
}
444456
445457
if( P("del")
446458
&& ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
447459
){
448
- form_begin(0, "%R/ainfo/%s", zUuid);
460
+ form_begin(0, "%R/ainfo/%!S", zUuid);
449461
@ <p>Confirm you want to delete the attachment shown below.
450462
@ <input type="submit" name="confirm" value="Confirm">
451463
@ </form>
452464
}
453465
@@ -456,11 +468,11 @@
456468
(zWikiName && g.perm.ModWiki);
457469
if( isModerator && (zModAction = P("modaction"))!=0 ){
458470
if( strcmp(zModAction,"delete")==0 ){
459471
moderation_disapprove(rid);
460472
if( zTktUuid ){
461
- cgi_redirectf("%R/tktview/%s", zTktUuid);
473
+ cgi_redirectf("%R/tktview/%!S", zTktUuid);
462474
}else{
463475
cgi_redirectf("%R/wiki?name=%t", zWikiName);
464476
}
465477
return;
466478
}
@@ -477,11 +489,11 @@
477489
}
478490
479491
@ <div class="section">Overview</div>
480492
@ <p><table class="label-value">
481493
@ <tr><th>Artifact&nbsp;ID:</th>
482
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
494
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
483495
if( g.perm.Setup ){
484496
@ (%d(rid))
485497
}
486498
modPending = moderation_pending(rid);
487499
if( modPending ){
@@ -579,17 +591,17 @@
579591
if( cnt==0 ){
580592
@ %s(zHeader)
581593
}
582594
cnt++;
583595
@ <li>
584
- @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a>
596
+ @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
585597
@ added by %h(zDispUser) on
586598
hyperlink_to_date(zDate, ".");
587
- @ [%z(href("%R/ainfo/%s",zUuid))details</a>]
599
+ @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
588600
@ </li>
589601
}
590602
if( cnt ){
591603
@ </ul>
592604
}
593605
db_finalize(&q);
594606
595607
}
596608
--- src/attach.c
+++ src/attach.c
@@ -48,19 +48,22 @@
48 " (SELECT uuid FROM blob WHERE rid=attachid), attachid"
49 " FROM attachment",
50 timeline_utc()
51 );
52 if( zPage ){
53 if( g.perm.RdWiki==0 ) login_needed();
54 style_header("Attachments To %h", zPage);
55 blob_append_sql(&sql, " WHERE target=%Q", zPage);
56 }else if( zTkt ){
57 if( g.perm.RdTkt==0 ) login_needed();
58 style_header("Attachments To Ticket %S", zTkt);
59 blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
60 }else{
61 if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed();
 
 
 
62 style_header("All Attachments");
63 }
64 blob_append_sql(&sql, " ORDER BY mtime DESC");
65 db_prepare(&q, "%s", blob_sql_text(&sql));
66 @ <ol>
@@ -86,11 +89,11 @@
86 zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
87 }else{
88 zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
89 }
90 @ <li><p>
91 @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a>
92 if( moderation_pending(attachid) ){
93 @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
94 }
95 @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
96 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
@@ -103,14 +106,14 @@
103 zSrc = "Deleted from";
104 }else {
105 zSrc = "Added to";
106 }
107 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
108 @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
109 @ %S(zTarget)</a>
110 }else{
111 @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
112 @ %h(zTarget)</a>
113 }
114 }else{
115 if( zSrc==0 || zSrc[0]==0 ){
116 @ Deleted
@@ -150,14 +153,14 @@
150
151 if( zPage && zTkt ) zTkt = 0;
152 if( zFile==0 ) fossil_redirect_home();
153 login_check_credentials();
154 if( zPage ){
155 if( g.perm.RdWiki==0 ) login_needed();
156 zTarget = zPage;
157 }else if( zTkt ){
158 if( g.perm.RdTkt==0 ) login_needed();
159 zTarget = zTkt;
160 }else{
161 fossil_redirect_home();
162 }
163 if( attachid>0 ){
@@ -243,27 +246,33 @@
243 if( P("cancel") ) cgi_redirect(zFrom);
244 if( zPage && zTkt ) fossil_redirect_home();
245 if( zPage==0 && zTkt==0 ) fossil_redirect_home();
246 login_check_credentials();
247 if( zPage ){
248 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed();
 
 
 
249 if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
250 fossil_redirect_home();
251 }
252 zTarget = zPage;
253 zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>",
254 g.zTop, zPage, zPage);
255 }else{
256 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
 
 
 
257 if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
258 zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
259 " WHERE tagname GLOB 'tkt-%q*'", zTkt);
260 if( zTkt==0 ) fossil_redirect_home();
261 }
262 zTarget = zTkt;
263 zTargetType = mprintf("Ticket <a href=\"%s/tktview/%s\">%S</a>",
264 g.zTop, zTkt, zTkt);
265 }
266 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
267 if( P("cancel") ){
268 cgi_redirect(zFrom);
269 }
@@ -369,11 +378,14 @@
369 Blob attach; /* Content of the attachment */
370 int fShowContent = 0;
371 const char *zLn = P("ln");
372
373 login_check_credentials();
374 if( !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; }
 
 
 
375 rid = name_to_rid_www("name");
376 if( rid==0 ){ fossil_redirect_home(); }
377 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
378 #if 0
379 /* Shunning here needs to get both the attachment control artifact and
@@ -399,17 +411,17 @@
399 fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
400 if( validate16(zTarget, strlen(zTarget))
401 && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
402 ){
403 zTktUuid = zTarget;
404 if( !g.perm.RdTkt ){ login_needed(); return; }
405 if( g.perm.WrTkt ){
406 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
407 }
408 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
409 zWikiName = zTarget;
410 if( !g.perm.RdWiki ){ login_needed(); return; }
411 if( g.perm.WrWiki ){
412 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
413 }
414 }
415 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
@@ -443,11 +455,11 @@
443 }
444
445 if( P("del")
446 && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
447 ){
448 form_begin(0, "%R/ainfo/%s", zUuid);
449 @ <p>Confirm you want to delete the attachment shown below.
450 @ <input type="submit" name="confirm" value="Confirm">
451 @ </form>
452 }
453
@@ -456,11 +468,11 @@
456 (zWikiName && g.perm.ModWiki);
457 if( isModerator && (zModAction = P("modaction"))!=0 ){
458 if( strcmp(zModAction,"delete")==0 ){
459 moderation_disapprove(rid);
460 if( zTktUuid ){
461 cgi_redirectf("%R/tktview/%s", zTktUuid);
462 }else{
463 cgi_redirectf("%R/wiki?name=%t", zWikiName);
464 }
465 return;
466 }
@@ -477,11 +489,11 @@
477 }
478
479 @ <div class="section">Overview</div>
480 @ <p><table class="label-value">
481 @ <tr><th>Artifact&nbsp;ID:</th>
482 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
483 if( g.perm.Setup ){
484 @ (%d(rid))
485 }
486 modPending = moderation_pending(rid);
487 if( modPending ){
@@ -579,17 +591,17 @@
579 if( cnt==0 ){
580 @ %s(zHeader)
581 }
582 cnt++;
583 @ <li>
584 @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a>
585 @ added by %h(zDispUser) on
586 hyperlink_to_date(zDate, ".");
587 @ [%z(href("%R/ainfo/%s",zUuid))details</a>]
588 @ </li>
589 }
590 if( cnt ){
591 @ </ul>
592 }
593 db_finalize(&q);
594
595 }
596
--- src/attach.c
+++ src/attach.c
@@ -48,19 +48,22 @@
48 " (SELECT uuid FROM blob WHERE rid=attachid), attachid"
49 " FROM attachment",
50 timeline_utc()
51 );
52 if( zPage ){
53 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
54 style_header("Attachments To %h", zPage);
55 blob_append_sql(&sql, " WHERE target=%Q", zPage);
56 }else if( zTkt ){
57 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
58 style_header("Attachments To Ticket %S", zTkt);
59 blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
60 }else{
61 if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
62 login_needed(g.anon.RdTkt || g.anon.RdWiki);
63 return;
64 }
65 style_header("All Attachments");
66 }
67 blob_append_sql(&sql, " ORDER BY mtime DESC");
68 db_prepare(&q, "%s", blob_sql_text(&sql));
69 @ <ol>
@@ -86,11 +89,11 @@
89 zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
90 }else{
91 zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
92 }
93 @ <li><p>
94 @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
95 if( moderation_pending(attachid) ){
96 @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
97 }
98 @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
99 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
@@ -103,14 +106,14 @@
106 zSrc = "Deleted from";
107 }else {
108 zSrc = "Added to";
109 }
110 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
111 @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
112 @ %S(zTarget)</a>
113 }else{
114 @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
115 @ %h(zTarget)</a>
116 }
117 }else{
118 if( zSrc==0 || zSrc[0]==0 ){
119 @ Deleted
@@ -150,14 +153,14 @@
153
154 if( zPage && zTkt ) zTkt = 0;
155 if( zFile==0 ) fossil_redirect_home();
156 login_check_credentials();
157 if( zPage ){
158 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
159 zTarget = zPage;
160 }else if( zTkt ){
161 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
162 zTarget = zTkt;
163 }else{
164 fossil_redirect_home();
165 }
166 if( attachid>0 ){
@@ -243,27 +246,33 @@
246 if( P("cancel") ) cgi_redirect(zFrom);
247 if( zPage && zTkt ) fossil_redirect_home();
248 if( zPage==0 && zTkt==0 ) fossil_redirect_home();
249 login_check_credentials();
250 if( zPage ){
251 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
252 login_needed(g.anon.ApndWiki && g.anon.Attach);
253 return;
254 }
255 if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
256 fossil_redirect_home();
257 }
258 zTarget = zPage;
259 zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
260 zPage, zPage);
261 }else{
262 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
263 login_needed(g.anon.ApndTkt && g.anon.Attach);
264 return;
265 }
266 if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
267 zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
268 " WHERE tagname GLOB 'tkt-%q*'", zTkt);
269 if( zTkt==0 ) fossil_redirect_home();
270 }
271 zTarget = zTkt;
272 zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
273 zTkt, zTkt);
274 }
275 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
276 if( P("cancel") ){
277 cgi_redirect(zFrom);
278 }
@@ -369,11 +378,14 @@
378 Blob attach; /* Content of the attachment */
379 int fShowContent = 0;
380 const char *zLn = P("ln");
381
382 login_check_credentials();
383 if( !g.perm.RdTkt && !g.perm.RdWiki ){
384 login_needed(g.anon.RdTkt || g.anon.RdWiki);
385 return;
386 }
387 rid = name_to_rid_www("name");
388 if( rid==0 ){ fossil_redirect_home(); }
389 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
390 #if 0
391 /* Shunning here needs to get both the attachment control artifact and
@@ -399,17 +411,17 @@
411 fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
412 if( validate16(zTarget, strlen(zTarget))
413 && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
414 ){
415 zTktUuid = zTarget;
416 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
417 if( g.perm.WrTkt ){
418 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
419 }
420 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
421 zWikiName = zTarget;
422 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
423 if( g.perm.WrWiki ){
424 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
425 }
426 }
427 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
@@ -443,11 +455,11 @@
455 }
456
457 if( P("del")
458 && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
459 ){
460 form_begin(0, "%R/ainfo/%!S", zUuid);
461 @ <p>Confirm you want to delete the attachment shown below.
462 @ <input type="submit" name="confirm" value="Confirm">
463 @ </form>
464 }
465
@@ -456,11 +468,11 @@
468 (zWikiName && g.perm.ModWiki);
469 if( isModerator && (zModAction = P("modaction"))!=0 ){
470 if( strcmp(zModAction,"delete")==0 ){
471 moderation_disapprove(rid);
472 if( zTktUuid ){
473 cgi_redirectf("%R/tktview/%!S", zTktUuid);
474 }else{
475 cgi_redirectf("%R/wiki?name=%t", zWikiName);
476 }
477 return;
478 }
@@ -477,11 +489,11 @@
489 }
490
491 @ <div class="section">Overview</div>
492 @ <p><table class="label-value">
493 @ <tr><th>Artifact&nbsp;ID:</th>
494 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
495 if( g.perm.Setup ){
496 @ (%d(rid))
497 }
498 modPending = moderation_pending(rid);
499 if( modPending ){
@@ -579,17 +591,17 @@
591 if( cnt==0 ){
592 @ %s(zHeader)
593 }
594 cnt++;
595 @ <li>
596 @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
597 @ added by %h(zDispUser) on
598 hyperlink_to_date(zDate, ".");
599 @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
600 @ </li>
601 }
602 if( cnt ){
603 @ </ul>
604 }
605 db_finalize(&q);
606
607 }
608
+1 -1
--- src/blob.c
+++ src/blob.c
@@ -753,11 +753,11 @@
753753
** Initialize a blob to be the content of a file. If the filename
754754
** is blank or "-" then read from standard input.
755755
**
756756
** Any prior content of the blob is discarded, not freed.
757757
**
758
-** Return the number of bytes read. Calls fossil_fatal() error (i.e.
758
+** Return the number of bytes read. Calls fossil_fatal() on error (i.e.
759759
** it exit()s and does not return).
760760
*/
761761
int blob_read_from_file(Blob *pBlob, const char *zFilename){
762762
int size, got;
763763
FILE *in;
764764
--- src/blob.c
+++ src/blob.c
@@ -753,11 +753,11 @@
753 ** Initialize a blob to be the content of a file. If the filename
754 ** is blank or "-" then read from standard input.
755 **
756 ** Any prior content of the blob is discarded, not freed.
757 **
758 ** Return the number of bytes read. Calls fossil_fatal() error (i.e.
759 ** it exit()s and does not return).
760 */
761 int blob_read_from_file(Blob *pBlob, const char *zFilename){
762 int size, got;
763 FILE *in;
764
--- src/blob.c
+++ src/blob.c
@@ -753,11 +753,11 @@
753 ** Initialize a blob to be the content of a file. If the filename
754 ** is blank or "-" then read from standard input.
755 **
756 ** Any prior content of the blob is discarded, not freed.
757 **
758 ** Return the number of bytes read. Calls fossil_fatal() on error (i.e.
759 ** it exit()s and does not return).
760 */
761 int blob_read_from_file(Blob *pBlob, const char *zFilename){
762 int size, got;
763 FILE *in;
764
+5 -5
--- src/branch.c
+++ src/branch.c
@@ -159,11 +159,11 @@
159159
fossil_fatal("%s\n", g.zErrMsg);
160160
}
161161
assert( blob_is_reset(&branch) );
162162
content_deltify(rootid, brid, 0);
163163
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164
- fossil_print("New branch: %s\n", zUuid);
164
+ fossil_print("New branch: %S\n", zUuid);
165165
if( g.argc==3 ){
166166
fossil_print(
167167
"\n"
168168
"Note: the local check-out has not been updated to the new\n"
169169
" branch. To begin working on the new branch, do this:\n"
@@ -339,11 +339,11 @@
339339
*/
340340
static void new_brlist_page(void){
341341
Stmt q;
342342
double rNow;
343343
login_check_credentials();
344
- if( !g.perm.Read ){ login_needed(); return; }
344
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
345345
style_header("Branches");
346346
style_adunit_config(ADUNIT_RIGHT_OK);
347347
login_anonymous_available();
348348
349349
db_prepare(&q, brlistQuery/*works-like:""*/);
@@ -372,11 +372,11 @@
372372
@ <td>%d(nCkin)</td>
373373
fossil_free(zAge);
374374
@ <td>%s(isClosed?"closed":"")</td>
375375
if( zMergeTo ){
376376
@ <td>merged into
377
- @ %z(href("%R/timeline?f=%s",zLastCkin))%h(zMergeTo)</a></td>
377
+ @ %z(href("%R/timeline?f=%!S",zLastCkin))%h(zMergeTo)</a></td>
378378
}else{
379379
@ <td></td>
380380
}
381381
@ </tr>
382382
}
@@ -408,11 +408,11 @@
408408
if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){
409409
new_brlist_page();
410410
return;
411411
}
412412
login_check_credentials();
413
- if( !g.perm.Read ){ login_needed(); return; }
413
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
414414
if( colorTest ){
415415
showClosed = 0;
416416
showAll = 1;
417417
}
418418
if( showAll ) brFlags = BRL_BOTH;
@@ -515,11 +515,11 @@
515515
*/
516516
void brtimeline_page(void){
517517
Stmt q;
518518
519519
login_check_credentials();
520
- if( !g.perm.Read ){ login_needed(); return; }
520
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
521521
522522
style_header("Branches");
523523
style_submenu_element("List", "List", "brlist");
524524
login_anonymous_available();
525525
@ <h2>The initial check-in for each branch:</h2>
526526
--- src/branch.c
+++ src/branch.c
@@ -159,11 +159,11 @@
159 fossil_fatal("%s\n", g.zErrMsg);
160 }
161 assert( blob_is_reset(&branch) );
162 content_deltify(rootid, brid, 0);
163 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164 fossil_print("New branch: %s\n", zUuid);
165 if( g.argc==3 ){
166 fossil_print(
167 "\n"
168 "Note: the local check-out has not been updated to the new\n"
169 " branch. To begin working on the new branch, do this:\n"
@@ -339,11 +339,11 @@
339 */
340 static void new_brlist_page(void){
341 Stmt q;
342 double rNow;
343 login_check_credentials();
344 if( !g.perm.Read ){ login_needed(); return; }
345 style_header("Branches");
346 style_adunit_config(ADUNIT_RIGHT_OK);
347 login_anonymous_available();
348
349 db_prepare(&q, brlistQuery/*works-like:""*/);
@@ -372,11 +372,11 @@
372 @ <td>%d(nCkin)</td>
373 fossil_free(zAge);
374 @ <td>%s(isClosed?"closed":"")</td>
375 if( zMergeTo ){
376 @ <td>merged into
377 @ %z(href("%R/timeline?f=%s",zLastCkin))%h(zMergeTo)</a></td>
378 }else{
379 @ <td></td>
380 }
381 @ </tr>
382 }
@@ -408,11 +408,11 @@
408 if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){
409 new_brlist_page();
410 return;
411 }
412 login_check_credentials();
413 if( !g.perm.Read ){ login_needed(); return; }
414 if( colorTest ){
415 showClosed = 0;
416 showAll = 1;
417 }
418 if( showAll ) brFlags = BRL_BOTH;
@@ -515,11 +515,11 @@
515 */
516 void brtimeline_page(void){
517 Stmt q;
518
519 login_check_credentials();
520 if( !g.perm.Read ){ login_needed(); return; }
521
522 style_header("Branches");
523 style_submenu_element("List", "List", "brlist");
524 login_anonymous_available();
525 @ <h2>The initial check-in for each branch:</h2>
526
--- src/branch.c
+++ src/branch.c
@@ -159,11 +159,11 @@
159 fossil_fatal("%s\n", g.zErrMsg);
160 }
161 assert( blob_is_reset(&branch) );
162 content_deltify(rootid, brid, 0);
163 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164 fossil_print("New branch: %S\n", zUuid);
165 if( g.argc==3 ){
166 fossil_print(
167 "\n"
168 "Note: the local check-out has not been updated to the new\n"
169 " branch. To begin working on the new branch, do this:\n"
@@ -339,11 +339,11 @@
339 */
340 static void new_brlist_page(void){
341 Stmt q;
342 double rNow;
343 login_check_credentials();
344 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
345 style_header("Branches");
346 style_adunit_config(ADUNIT_RIGHT_OK);
347 login_anonymous_available();
348
349 db_prepare(&q, brlistQuery/*works-like:""*/);
@@ -372,11 +372,11 @@
372 @ <td>%d(nCkin)</td>
373 fossil_free(zAge);
374 @ <td>%s(isClosed?"closed":"")</td>
375 if( zMergeTo ){
376 @ <td>merged into
377 @ %z(href("%R/timeline?f=%!S",zLastCkin))%h(zMergeTo)</a></td>
378 }else{
379 @ <td></td>
380 }
381 @ </tr>
382 }
@@ -408,11 +408,11 @@
408 if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){
409 new_brlist_page();
410 return;
411 }
412 login_check_credentials();
413 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
414 if( colorTest ){
415 showClosed = 0;
416 showAll = 1;
417 }
418 if( showAll ) brFlags = BRL_BOTH;
@@ -515,11 +515,11 @@
515 */
516 void brtimeline_page(void){
517 Stmt q;
518
519 login_check_credentials();
520 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
521
522 style_header("Branches");
523 style_submenu_element("List", "List", "brlist");
524 login_anonymous_available();
525 @ <h2>The initial check-in for each branch:</h2>
526
+18 -18
--- src/browse.c
+++ src/browse.c
@@ -85,11 +85,11 @@
8585
8686
for(i=0; zPath[i]; i=j){
8787
for(j=i; zPath[j] && zPath[j]!='/'; j++){}
8888
if( zPath[j] && g.perm.Hyperlink ){
8989
if( zCI ){
90
- char *zLink = href("%R/%s?name=%#T%s&ci=%s", zURI, j, zPath, zREx, zCI);
90
+ char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI);
9191
blob_appendf(pOut, "%s%z%#h</a>",
9292
zSep, zLink, j-i, &zPath[i]);
9393
}else{
9494
char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
9595
blob_appendf(pOut, "%s%z%#h</a>",
@@ -130,11 +130,11 @@
130130
int linkTip = 1;
131131
HQuery sURI;
132132
133133
if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
134134
login_check_credentials();
135
- if( !g.perm.Read ){ login_needed(); return; }
135
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
136136
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
137137
style_header("File List");
138138
style_adunit_config(ADUNIT_RIGHT_OK);
139139
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
140140
pathelementFunc, 0, 0);
@@ -179,15 +179,15 @@
179179
if( linkTip ){
180180
style_submenu_element("Tip", "Tip", "%s",
181181
url_render(&sURI, "ci", "tip", 0, 0));
182182
}
183183
if( zCI ){
184
- @ <h2>Files of check-in [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>]
184
+ @ <h2>Files of check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>]
185185
@ %s(blob_str(&dirname))</h2>
186
- zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
186
+ zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix);
187187
if( nD==0 ){
188
- style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
188
+ style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%!S",
189189
zUuid);
190190
}
191191
}else{
192192
@ <h2>The union of all files from all check-ins
193193
@ %s(blob_str(&dirname))</h2>
@@ -281,11 +281,11 @@
281281
@ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
282282
}else{
283283
const char *zLink;
284284
if( zCI ){
285285
const char *zUuid = db_column_text(&q, 1);
286
- zLink = href("%R/artifact/%s",zUuid);
286
+ zLink = href("%R/artifact/%!S",zUuid);
287287
}else{
288288
zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
289289
}
290290
@ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
291291
}
@@ -542,11 +542,11 @@
542542
char *zProjectName = db_get("project-name", 0);
543543
544544
if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
545545
memset(&sTree, 0, sizeof(sTree));
546546
login_check_credentials();
547
- if( !g.perm.Read ){ login_needed(); return; }
547
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
548548
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
549549
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
550550
pathelementFunc, 0, 0);
551551
url_initialize(&sURI, "tree");
552552
cgi_query_parameters_to_url(&sURI);
@@ -693,11 +693,11 @@
693693
if( zCI ){
694694
@ <h2>%s(zObjType) from
695695
if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
696696
@ "%h(zCI)"
697697
}
698
- @ [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
698
+ @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
699699
}else{
700700
int n = db_int(0, "SELECT count(*) FROM plink");
701701
@ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
702702
}
703703
if( useMtime ){
@@ -755,11 +755,11 @@
755755
nDir++;
756756
}else if( !showDirOnly ){
757757
const char *zFileClass = fileext_class(p->zName);
758758
char *zLink;
759759
if( zCI ){
760
- zLink = href("%R/artifact/%.16s",p->zUuid);
760
+ zLink = href("%R/artifact/%!S",p->zUuid);
761761
}else{
762762
zLink = href("%R/finfo?name=%T",p->zFullName);
763763
}
764764
@ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
765765
@ %z(zLink)%h(p->zName)</a>
@@ -905,11 +905,11 @@
905905
@ AND filename.name=foci.filename
906906
@ AND blob.uuid=foci.uuid
907907
@ AND mlink.fid=blob.rid
908908
@ AND mlink.fid!=mlink.pid
909909
@ AND mlink.mid IN (SELECT x FROM ckin)
910
-@ AND event.objid=mlink.mid
910
+@ AND event.objid=mlink.mid
911911
@ ORDER BY event.mtime ASC;
912912
;
913913
914914
/*
915915
** Look at all file containing in the version "vid". Construct a
@@ -1002,11 +1002,11 @@
10021002
const char *zNow; /* Time of checkin */
10031003
int showId = PB("showid");
10041004
Stmt q1, q2;
10051005
double baseTime;
10061006
login_check_credentials();
1007
- if( !g.perm.Read ){ login_needed(); return; }
1007
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
10081008
zName = P("name");
10091009
if( zName==0 ) zName = "tip";
10101010
rid = symbolic_name_to_rid(zName, "ci");
10111011
if( rid==0 ){
10121012
fossil_fatal("not a valid check-in: %s", zName);
@@ -1022,18 +1022,18 @@
10221022
zGlob = P("glob");
10231023
compute_fileage(rid,zGlob);
10241024
db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
10251025
10261026
@ <h2>Files in
1027
- @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a>
1027
+ @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
10281028
if( zGlob && zGlob[0] ){
10291029
@ that match "%h(zGlob)" and
10301030
}
10311031
@ ordered by check-in time</h2>
10321032
@
10331033
@ <p>Times are relative to the checkin time for
1034
- @ %z(href("%R/ci/%s",zUuid))[%S(zUuid)]</a> which is
1034
+ @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> which is
10351035
@ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
10361036
@
10371037
@ <div class='fileage'><table>
10381038
@ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
10391039
db_prepare(&q1,
@@ -1069,30 +1069,30 @@
10691069
while( db_step(&q2)==SQLITE_ROW ){
10701070
const char *zFUuid = db_column_text(&q2,0);
10711071
const char *zFile = db_column_text(&q2,1);
10721072
int fid = db_column_int(&q2,2);
10731073
if( showId ){
1074
- @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a> (%d(fid))<br>
1074
+ @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br>
10751075
}else{
1076
- @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a><br>
1076
+ @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br>
10771077
}
10781078
}
10791079
db_reset(&q2);
10801080
@ </td>
10811081
@ <td>
1082
- @ %z(href("%R/info/%s",zUuid))[%S(zUuid)]</a>
1082
+ @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
10831083
if( showId ){
10841084
@ (%d(mid))
10851085
}
10861086
@ %W(zComment) (user:
1087
- @ %z(href("%R/timeline?u=%t&c=%t&nd&n=200",zUser,zUuid))%h(zUser)</a>,
1087
+ @ %z(href("%R/timeline?u=%t&c=%!S&nd&n=200",zUser,zUuid))%h(zUser)</a>,
10881088
@ branch:
1089
- @ %z(href("%R/timeline?r=%t&c=%t&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
1089
+ @ %z(href("%R/timeline?r=%t&c=%!S&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
10901090
@ </td></tr>
10911091
@
10921092
fossil_free(zAge);
10931093
}
10941094
@ </table></div>
10951095
db_finalize(&q1);
10961096
db_finalize(&q2);
10971097
style_footer();
10981098
}
10991099
--- src/browse.c
+++ src/browse.c
@@ -85,11 +85,11 @@
85
86 for(i=0; zPath[i]; i=j){
87 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
88 if( zPath[j] && g.perm.Hyperlink ){
89 if( zCI ){
90 char *zLink = href("%R/%s?name=%#T%s&ci=%s", zURI, j, zPath, zREx, zCI);
91 blob_appendf(pOut, "%s%z%#h</a>",
92 zSep, zLink, j-i, &zPath[i]);
93 }else{
94 char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
95 blob_appendf(pOut, "%s%z%#h</a>",
@@ -130,11 +130,11 @@
130 int linkTip = 1;
131 HQuery sURI;
132
133 if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
134 login_check_credentials();
135 if( !g.perm.Read ){ login_needed(); return; }
136 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
137 style_header("File List");
138 style_adunit_config(ADUNIT_RIGHT_OK);
139 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
140 pathelementFunc, 0, 0);
@@ -179,15 +179,15 @@
179 if( linkTip ){
180 style_submenu_element("Tip", "Tip", "%s",
181 url_render(&sURI, "ci", "tip", 0, 0));
182 }
183 if( zCI ){
184 @ <h2>Files of check-in [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>]
185 @ %s(blob_str(&dirname))</h2>
186 zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
187 if( nD==0 ){
188 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
189 zUuid);
190 }
191 }else{
192 @ <h2>The union of all files from all check-ins
193 @ %s(blob_str(&dirname))</h2>
@@ -281,11 +281,11 @@
281 @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
282 }else{
283 const char *zLink;
284 if( zCI ){
285 const char *zUuid = db_column_text(&q, 1);
286 zLink = href("%R/artifact/%s",zUuid);
287 }else{
288 zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
289 }
290 @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
291 }
@@ -542,11 +542,11 @@
542 char *zProjectName = db_get("project-name", 0);
543
544 if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
545 memset(&sTree, 0, sizeof(sTree));
546 login_check_credentials();
547 if( !g.perm.Read ){ login_needed(); return; }
548 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
549 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
550 pathelementFunc, 0, 0);
551 url_initialize(&sURI, "tree");
552 cgi_query_parameters_to_url(&sURI);
@@ -693,11 +693,11 @@
693 if( zCI ){
694 @ <h2>%s(zObjType) from
695 if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
696 @ "%h(zCI)"
697 }
698 @ [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
699 }else{
700 int n = db_int(0, "SELECT count(*) FROM plink");
701 @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
702 }
703 if( useMtime ){
@@ -755,11 +755,11 @@
755 nDir++;
756 }else if( !showDirOnly ){
757 const char *zFileClass = fileext_class(p->zName);
758 char *zLink;
759 if( zCI ){
760 zLink = href("%R/artifact/%.16s",p->zUuid);
761 }else{
762 zLink = href("%R/finfo?name=%T",p->zFullName);
763 }
764 @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
765 @ %z(zLink)%h(p->zName)</a>
@@ -905,11 +905,11 @@
905 @ AND filename.name=foci.filename
906 @ AND blob.uuid=foci.uuid
907 @ AND mlink.fid=blob.rid
908 @ AND mlink.fid!=mlink.pid
909 @ AND mlink.mid IN (SELECT x FROM ckin)
910 @ AND event.objid=mlink.mid
911 @ ORDER BY event.mtime ASC;
912 ;
913
914 /*
915 ** Look at all file containing in the version "vid". Construct a
@@ -1002,11 +1002,11 @@
1002 const char *zNow; /* Time of checkin */
1003 int showId = PB("showid");
1004 Stmt q1, q2;
1005 double baseTime;
1006 login_check_credentials();
1007 if( !g.perm.Read ){ login_needed(); return; }
1008 zName = P("name");
1009 if( zName==0 ) zName = "tip";
1010 rid = symbolic_name_to_rid(zName, "ci");
1011 if( rid==0 ){
1012 fossil_fatal("not a valid check-in: %s", zName);
@@ -1022,18 +1022,18 @@
1022 zGlob = P("glob");
1023 compute_fileage(rid,zGlob);
1024 db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
1025
1026 @ <h2>Files in
1027 @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a>
1028 if( zGlob && zGlob[0] ){
1029 @ that match "%h(zGlob)" and
1030 }
1031 @ ordered by check-in time</h2>
1032 @
1033 @ <p>Times are relative to the checkin time for
1034 @ %z(href("%R/ci/%s",zUuid))[%S(zUuid)]</a> which is
1035 @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
1036 @
1037 @ <div class='fileage'><table>
1038 @ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
1039 db_prepare(&q1,
@@ -1069,30 +1069,30 @@
1069 while( db_step(&q2)==SQLITE_ROW ){
1070 const char *zFUuid = db_column_text(&q2,0);
1071 const char *zFile = db_column_text(&q2,1);
1072 int fid = db_column_int(&q2,2);
1073 if( showId ){
1074 @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a> (%d(fid))<br>
1075 }else{
1076 @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a><br>
1077 }
1078 }
1079 db_reset(&q2);
1080 @ </td>
1081 @ <td>
1082 @ %z(href("%R/info/%s",zUuid))[%S(zUuid)]</a>
1083 if( showId ){
1084 @ (%d(mid))
1085 }
1086 @ %W(zComment) (user:
1087 @ %z(href("%R/timeline?u=%t&c=%t&nd&n=200",zUser,zUuid))%h(zUser)</a>,
1088 @ branch:
1089 @ %z(href("%R/timeline?r=%t&c=%t&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
1090 @ </td></tr>
1091 @
1092 fossil_free(zAge);
1093 }
1094 @ </table></div>
1095 db_finalize(&q1);
1096 db_finalize(&q2);
1097 style_footer();
1098 }
1099
--- src/browse.c
+++ src/browse.c
@@ -85,11 +85,11 @@
85
86 for(i=0; zPath[i]; i=j){
87 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
88 if( zPath[j] && g.perm.Hyperlink ){
89 if( zCI ){
90 char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI);
91 blob_appendf(pOut, "%s%z%#h</a>",
92 zSep, zLink, j-i, &zPath[i]);
93 }else{
94 char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
95 blob_appendf(pOut, "%s%z%#h</a>",
@@ -130,11 +130,11 @@
130 int linkTip = 1;
131 HQuery sURI;
132
133 if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
134 login_check_credentials();
135 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
136 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
137 style_header("File List");
138 style_adunit_config(ADUNIT_RIGHT_OK);
139 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
140 pathelementFunc, 0, 0);
@@ -179,15 +179,15 @@
179 if( linkTip ){
180 style_submenu_element("Tip", "Tip", "%s",
181 url_render(&sURI, "ci", "tip", 0, 0));
182 }
183 if( zCI ){
184 @ <h2>Files of check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>]
185 @ %s(blob_str(&dirname))</h2>
186 zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix);
187 if( nD==0 ){
188 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%!S",
189 zUuid);
190 }
191 }else{
192 @ <h2>The union of all files from all check-ins
193 @ %s(blob_str(&dirname))</h2>
@@ -281,11 +281,11 @@
281 @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
282 }else{
283 const char *zLink;
284 if( zCI ){
285 const char *zUuid = db_column_text(&q, 1);
286 zLink = href("%R/artifact/%!S",zUuid);
287 }else{
288 zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
289 }
290 @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
291 }
@@ -542,11 +542,11 @@
542 char *zProjectName = db_get("project-name", 0);
543
544 if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
545 memset(&sTree, 0, sizeof(sTree));
546 login_check_credentials();
547 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
548 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
549 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
550 pathelementFunc, 0, 0);
551 url_initialize(&sURI, "tree");
552 cgi_query_parameters_to_url(&sURI);
@@ -693,11 +693,11 @@
693 if( zCI ){
694 @ <h2>%s(zObjType) from
695 if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
696 @ "%h(zCI)"
697 }
698 @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
699 }else{
700 int n = db_int(0, "SELECT count(*) FROM plink");
701 @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
702 }
703 if( useMtime ){
@@ -755,11 +755,11 @@
755 nDir++;
756 }else if( !showDirOnly ){
757 const char *zFileClass = fileext_class(p->zName);
758 char *zLink;
759 if( zCI ){
760 zLink = href("%R/artifact/%!S",p->zUuid);
761 }else{
762 zLink = href("%R/finfo?name=%T",p->zFullName);
763 }
764 @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
765 @ %z(zLink)%h(p->zName)</a>
@@ -905,11 +905,11 @@
905 @ AND filename.name=foci.filename
906 @ AND blob.uuid=foci.uuid
907 @ AND mlink.fid=blob.rid
908 @ AND mlink.fid!=mlink.pid
909 @ AND mlink.mid IN (SELECT x FROM ckin)
910 @ AND event.objid=mlink.mid
911 @ ORDER BY event.mtime ASC;
912 ;
913
914 /*
915 ** Look at all file containing in the version "vid". Construct a
@@ -1002,11 +1002,11 @@
1002 const char *zNow; /* Time of checkin */
1003 int showId = PB("showid");
1004 Stmt q1, q2;
1005 double baseTime;
1006 login_check_credentials();
1007 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1008 zName = P("name");
1009 if( zName==0 ) zName = "tip";
1010 rid = symbolic_name_to_rid(zName, "ci");
1011 if( rid==0 ){
1012 fossil_fatal("not a valid check-in: %s", zName);
@@ -1022,18 +1022,18 @@
1022 zGlob = P("glob");
1023 compute_fileage(rid,zGlob);
1024 db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
1025
1026 @ <h2>Files in
1027 @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
1028 if( zGlob && zGlob[0] ){
1029 @ that match "%h(zGlob)" and
1030 }
1031 @ ordered by check-in time</h2>
1032 @
1033 @ <p>Times are relative to the checkin time for
1034 @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> which is
1035 @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
1036 @
1037 @ <div class='fileage'><table>
1038 @ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
1039 db_prepare(&q1,
@@ -1069,30 +1069,30 @@
1069 while( db_step(&q2)==SQLITE_ROW ){
1070 const char *zFUuid = db_column_text(&q2,0);
1071 const char *zFile = db_column_text(&q2,1);
1072 int fid = db_column_int(&q2,2);
1073 if( showId ){
1074 @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br>
1075 }else{
1076 @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br>
1077 }
1078 }
1079 db_reset(&q2);
1080 @ </td>
1081 @ <td>
1082 @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
1083 if( showId ){
1084 @ (%d(mid))
1085 }
1086 @ %W(zComment) (user:
1087 @ %z(href("%R/timeline?u=%t&c=%!S&nd&n=200",zUser,zUuid))%h(zUser)</a>,
1088 @ branch:
1089 @ %z(href("%R/timeline?r=%t&c=%!S&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
1090 @ </td></tr>
1091 @
1092 fossil_free(zAge);
1093 }
1094 @ </table></div>
1095 db_finalize(&q1);
1096 db_finalize(&q2);
1097 style_footer();
1098 }
1099
+2 -2
--- src/cache.c
+++ src/cache.c
@@ -330,11 +330,11 @@
330330
sqlite3 *db;
331331
sqlite3_stmt *pStmt;
332332
char zBuf[100];
333333
334334
login_check_credentials();
335
- if( !g.perm.Setup ){ login_needed(); return; }
335
+ if( !g.perm.Setup ){ login_needed(0); return; }
336336
style_header("Web Cache Status");
337337
db = cacheOpen(0);
338338
if( db==0 ){
339339
@ The web-page cache is disabled for this repository
340340
}else{
@@ -378,11 +378,11 @@
378378
void cache_getpage(void){
379379
const char *zKey;
380380
Blob content;
381381
382382
login_check_credentials();
383
- if( !g.perm.Setup ){ login_needed(); return; }
383
+ if( !g.perm.Setup ){ login_needed(0); return; }
384384
zKey = PD("key","");
385385
blob_zero(&content);
386386
if( cache_read(&content, zKey)==0 ){
387387
style_header("Cache Download Error");
388388
@ The cache does not contain any entry with this key: "%h(zKey)"
389389
--- src/cache.c
+++ src/cache.c
@@ -330,11 +330,11 @@
330 sqlite3 *db;
331 sqlite3_stmt *pStmt;
332 char zBuf[100];
333
334 login_check_credentials();
335 if( !g.perm.Setup ){ login_needed(); return; }
336 style_header("Web Cache Status");
337 db = cacheOpen(0);
338 if( db==0 ){
339 @ The web-page cache is disabled for this repository
340 }else{
@@ -378,11 +378,11 @@
378 void cache_getpage(void){
379 const char *zKey;
380 Blob content;
381
382 login_check_credentials();
383 if( !g.perm.Setup ){ login_needed(); return; }
384 zKey = PD("key","");
385 blob_zero(&content);
386 if( cache_read(&content, zKey)==0 ){
387 style_header("Cache Download Error");
388 @ The cache does not contain any entry with this key: "%h(zKey)"
389
--- src/cache.c
+++ src/cache.c
@@ -330,11 +330,11 @@
330 sqlite3 *db;
331 sqlite3_stmt *pStmt;
332 char zBuf[100];
333
334 login_check_credentials();
335 if( !g.perm.Setup ){ login_needed(0); return; }
336 style_header("Web Cache Status");
337 db = cacheOpen(0);
338 if( db==0 ){
339 @ The web-page cache is disabled for this repository
340 }else{
@@ -378,11 +378,11 @@
378 void cache_getpage(void){
379 const char *zKey;
380 Blob content;
381
382 login_check_credentials();
383 if( !g.perm.Setup ){ login_needed(0); return; }
384 zKey = PD("key","");
385 blob_zero(&content);
386 if( cache_read(&content, zKey)==0 ){
387 style_header("Cache Download Error");
388 @ The cache does not contain any entry with this key: "%h(zKey)"
389
+2
--- src/cgi.c
+++ src/cgi.c
@@ -465,10 +465,11 @@
465465
if( g.fHttpTrace ){
466466
fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
467467
}
468468
aParamQP[nUsedQP].seq = seqQP++;
469469
aParamQP[nUsedQP].isQP = isQP;
470
+ aParamQP[nUsedQP].cTag = 0;
470471
nUsedQP++;
471472
sortQP = 1;
472473
}
473474
474475
/*
@@ -1680,10 +1681,11 @@
16801681
*/
16811682
#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
16821683
#define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */
16831684
#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */
16841685
#define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */
1686
+#define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */
16851687
16861688
#endif /* INTERFACE */
16871689
16881690
/*
16891691
** Maximum number of child processes that we can have running
16901692
--- src/cgi.c
+++ src/cgi.c
@@ -465,10 +465,11 @@
465 if( g.fHttpTrace ){
466 fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
467 }
468 aParamQP[nUsedQP].seq = seqQP++;
469 aParamQP[nUsedQP].isQP = isQP;
 
470 nUsedQP++;
471 sortQP = 1;
472 }
473
474 /*
@@ -1680,10 +1681,11 @@
1680 */
1681 #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
1682 #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */
1683 #define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */
1684 #define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */
 
1685
1686 #endif /* INTERFACE */
1687
1688 /*
1689 ** Maximum number of child processes that we can have running
1690
--- src/cgi.c
+++ src/cgi.c
@@ -465,10 +465,11 @@
465 if( g.fHttpTrace ){
466 fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
467 }
468 aParamQP[nUsedQP].seq = seqQP++;
469 aParamQP[nUsedQP].isQP = isQP;
470 aParamQP[nUsedQP].cTag = 0;
471 nUsedQP++;
472 sortQP = 1;
473 }
474
475 /*
@@ -1680,10 +1681,11 @@
1681 */
1682 #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
1683 #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */
1684 #define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */
1685 #define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */
1686 #define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */
1687
1688 #endif /* INTERFACE */
1689
1690 /*
1691 ** Maximum number of child processes that we can have running
1692
+3 -3
--- src/checkin.c
+++ src/checkin.c
@@ -1936,18 +1936,18 @@
19361936
db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
19371937
" WHERE id=-4");
19381938
while( db_step(&q)==SQLITE_ROW ){
19391939
const char *zIntegrateUuid = db_column_text(&q, 0);
19401940
if( is_a_leaf(db_column_int(&q, 1)) ){
1941
- fossil_print("Closed: %s\n", zIntegrateUuid);
1941
+ fossil_print("Closed: %S\n", zIntegrateUuid);
19421942
}else{
1943
- fossil_print("Not_Closed: %s (not a leaf any more)\n", zIntegrateUuid);
1943
+ fossil_print("Not_Closed: %S (not a leaf any more)\n", zIntegrateUuid);
19441944
}
19451945
}
19461946
db_finalize(&q);
19471947
1948
- fossil_print("New_Version: %s\n", zUuid);
1948
+ fossil_print("New_Version: %S\n", zUuid);
19491949
if( outputManifest ){
19501950
zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
19511951
blob_zero(&muuid);
19521952
blob_appendf(&muuid, "%s\n", zUuid);
19531953
blob_write_to_file(&muuid, zManifestFile);
19541954
--- src/checkin.c
+++ src/checkin.c
@@ -1936,18 +1936,18 @@
1936 db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
1937 " WHERE id=-4");
1938 while( db_step(&q)==SQLITE_ROW ){
1939 const char *zIntegrateUuid = db_column_text(&q, 0);
1940 if( is_a_leaf(db_column_int(&q, 1)) ){
1941 fossil_print("Closed: %s\n", zIntegrateUuid);
1942 }else{
1943 fossil_print("Not_Closed: %s (not a leaf any more)\n", zIntegrateUuid);
1944 }
1945 }
1946 db_finalize(&q);
1947
1948 fossil_print("New_Version: %s\n", zUuid);
1949 if( outputManifest ){
1950 zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
1951 blob_zero(&muuid);
1952 blob_appendf(&muuid, "%s\n", zUuid);
1953 blob_write_to_file(&muuid, zManifestFile);
1954
--- src/checkin.c
+++ src/checkin.c
@@ -1936,18 +1936,18 @@
1936 db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
1937 " WHERE id=-4");
1938 while( db_step(&q)==SQLITE_ROW ){
1939 const char *zIntegrateUuid = db_column_text(&q, 0);
1940 if( is_a_leaf(db_column_int(&q, 1)) ){
1941 fossil_print("Closed: %S\n", zIntegrateUuid);
1942 }else{
1943 fossil_print("Not_Closed: %S (not a leaf any more)\n", zIntegrateUuid);
1944 }
1945 }
1946 db_finalize(&q);
1947
1948 fossil_print("New_Version: %S\n", zUuid);
1949 if( outputManifest ){
1950 zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
1951 blob_zero(&muuid);
1952 blob_appendf(&muuid, "%s\n", zUuid);
1953 blob_write_to_file(&muuid, zManifestFile);
1954
+10 -9
--- src/db.c
+++ src/db.c
@@ -65,14 +65,10 @@
6565
*/
6666
static void db_err(const char *zFormat, ...){
6767
va_list ap;
6868
char *z;
6969
int rc = 1;
70
- static const char zRebuildMsg[] =
71
- "If you have recently updated your fossil executable, you might\n"
72
- "need to run \"fossil all rebuild\" to bring the repository\n"
73
- "schemas up to date.\n";
7470
va_start(ap, zFormat);
7571
z = vmprintf(zFormat, ap);
7672
va_end(ap);
7773
#ifdef FOSSIL_ENABLE_JSON
7874
if( g.json.isJsonMode ){
@@ -88,15 +84,14 @@
8884
@ error Database\serror:\s%F(z)
8985
cgi_reply();
9086
}
9187
else if( g.cgiOutput ){
9288
g.cgiOutput = 0;
93
- cgi_printf("<h1>Database Error</h1>\n"
94
- "<pre>%h</pre>\n<p>%s</p>\n", z, zRebuildMsg);
89
+ cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z);
9590
cgi_reply();
9691
}else{
97
- fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg);
92
+ fprintf(stderr, "%s: %s\n", g.argv[0], z);
9893
}
9994
free(z);
10095
db_force_rollback();
10196
fossil_exit(rc);
10297
}
@@ -164,14 +159,16 @@
164159
if( rollbackFlag ) db.doRollback = 1;
165160
db.nBegin--;
166161
if( db.nBegin==0 ){
167162
int i;
168163
if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
164
+ i = 0;
169165
while( db.nBeforeCommit ){
170166
db.nBeforeCommit--;
171
- sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
172
- sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
167
+ sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
168
+ sqlite3_free(db.azBeforeCommit[i]);
169
+ i++;
173170
}
174171
leaf_do_pending_checks();
175172
}
176173
for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
177174
db.doRollback |= db.aHook[i].xHook();
@@ -2389,10 +2386,11 @@
23892386
{ "editor", 0, 32, 0, 0, "" },
23902387
{ "empty-dirs", 0, 40, 1, 0, "" },
23912388
{ "encoding-glob", 0, 40, 1, 0, "" },
23922389
{ "gdiff-command", 0, 40, 0, 0, "gdiff" },
23932390
{ "gmerge-command", 0, 40, 0, 0, "" },
2391
+ { "hash-digits", 0, 5, 0, 0, "10" },
23942392
{ "http-port", 0, 16, 0, 0, "8080" },
23952393
{ "https-login", 0, 0, 0, 0, "off" },
23962394
{ "ignore-glob", 0, 40, 1, 0, "" },
23972395
{ "keep-glob", 0, 40, 1, 0, "" },
23982396
{ "localauth", 0, 0, 0, 0, "off" },
@@ -2565,10 +2563,13 @@
25652563
** gmerge-command A graphical merge conflict resolver command operating
25662564
** on four files.
25672565
** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
25682566
** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
25692567
** Ex: meld "%baseline" "%original" "%merge" "%output"
2568
+**
2569
+** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570
+** display. (Default: 10; Minimum: 6)
25702571
**
25712572
** http-port The TCP/IP port number to use by the "server"
25722573
** and "ui" commands. Default: 8080
25732574
**
25742575
** https-login Send login credentials using HTTPS instead of HTTP
25752576
--- src/db.c
+++ src/db.c
@@ -65,14 +65,10 @@
65 */
66 static void db_err(const char *zFormat, ...){
67 va_list ap;
68 char *z;
69 int rc = 1;
70 static const char zRebuildMsg[] =
71 "If you have recently updated your fossil executable, you might\n"
72 "need to run \"fossil all rebuild\" to bring the repository\n"
73 "schemas up to date.\n";
74 va_start(ap, zFormat);
75 z = vmprintf(zFormat, ap);
76 va_end(ap);
77 #ifdef FOSSIL_ENABLE_JSON
78 if( g.json.isJsonMode ){
@@ -88,15 +84,14 @@
88 @ error Database\serror:\s%F(z)
89 cgi_reply();
90 }
91 else if( g.cgiOutput ){
92 g.cgiOutput = 0;
93 cgi_printf("<h1>Database Error</h1>\n"
94 "<pre>%h</pre>\n<p>%s</p>\n", z, zRebuildMsg);
95 cgi_reply();
96 }else{
97 fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg);
98 }
99 free(z);
100 db_force_rollback();
101 fossil_exit(rc);
102 }
@@ -164,14 +159,16 @@
164 if( rollbackFlag ) db.doRollback = 1;
165 db.nBegin--;
166 if( db.nBegin==0 ){
167 int i;
168 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
 
169 while( db.nBeforeCommit ){
170 db.nBeforeCommit--;
171 sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
172 sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
 
173 }
174 leaf_do_pending_checks();
175 }
176 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
177 db.doRollback |= db.aHook[i].xHook();
@@ -2389,10 +2386,11 @@
2389 { "editor", 0, 32, 0, 0, "" },
2390 { "empty-dirs", 0, 40, 1, 0, "" },
2391 { "encoding-glob", 0, 40, 1, 0, "" },
2392 { "gdiff-command", 0, 40, 0, 0, "gdiff" },
2393 { "gmerge-command", 0, 40, 0, 0, "" },
 
2394 { "http-port", 0, 16, 0, 0, "8080" },
2395 { "https-login", 0, 0, 0, 0, "off" },
2396 { "ignore-glob", 0, 40, 1, 0, "" },
2397 { "keep-glob", 0, 40, 1, 0, "" },
2398 { "localauth", 0, 0, 0, 0, "off" },
@@ -2565,10 +2563,13 @@
2565 ** gmerge-command A graphical merge conflict resolver command operating
2566 ** on four files.
2567 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2568 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2569 ** Ex: meld "%baseline" "%original" "%merge" "%output"
 
 
 
2570 **
2571 ** http-port The TCP/IP port number to use by the "server"
2572 ** and "ui" commands. Default: 8080
2573 **
2574 ** https-login Send login credentials using HTTPS instead of HTTP
2575
--- src/db.c
+++ src/db.c
@@ -65,14 +65,10 @@
65 */
66 static void db_err(const char *zFormat, ...){
67 va_list ap;
68 char *z;
69 int rc = 1;
 
 
 
 
70 va_start(ap, zFormat);
71 z = vmprintf(zFormat, ap);
72 va_end(ap);
73 #ifdef FOSSIL_ENABLE_JSON
74 if( g.json.isJsonMode ){
@@ -88,15 +84,14 @@
84 @ error Database\serror:\s%F(z)
85 cgi_reply();
86 }
87 else if( g.cgiOutput ){
88 g.cgiOutput = 0;
89 cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z);
 
90 cgi_reply();
91 }else{
92 fprintf(stderr, "%s: %s\n", g.argv[0], z);
93 }
94 free(z);
95 db_force_rollback();
96 fossil_exit(rc);
97 }
@@ -164,14 +159,16 @@
159 if( rollbackFlag ) db.doRollback = 1;
160 db.nBegin--;
161 if( db.nBegin==0 ){
162 int i;
163 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
164 i = 0;
165 while( db.nBeforeCommit ){
166 db.nBeforeCommit--;
167 sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
168 sqlite3_free(db.azBeforeCommit[i]);
169 i++;
170 }
171 leaf_do_pending_checks();
172 }
173 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
174 db.doRollback |= db.aHook[i].xHook();
@@ -2389,10 +2386,11 @@
2386 { "editor", 0, 32, 0, 0, "" },
2387 { "empty-dirs", 0, 40, 1, 0, "" },
2388 { "encoding-glob", 0, 40, 1, 0, "" },
2389 { "gdiff-command", 0, 40, 0, 0, "gdiff" },
2390 { "gmerge-command", 0, 40, 0, 0, "" },
2391 { "hash-digits", 0, 5, 0, 0, "10" },
2392 { "http-port", 0, 16, 0, 0, "8080" },
2393 { "https-login", 0, 0, 0, 0, "off" },
2394 { "ignore-glob", 0, 40, 1, 0, "" },
2395 { "keep-glob", 0, 40, 1, 0, "" },
2396 { "localauth", 0, 0, 0, 0, "off" },
@@ -2565,10 +2563,13 @@
2563 ** gmerge-command A graphical merge conflict resolver command operating
2564 ** on four files.
2565 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2566 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2567 ** Ex: meld "%baseline" "%original" "%merge" "%output"
2568 **
2569 ** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570 ** display. (Default: 10; Minimum: 6)
2571 **
2572 ** http-port The TCP/IP port number to use by the "server"
2573 ** and "ui" commands. Default: 8080
2574 **
2575 ** https-login Send login credentials using HTTPS instead of HTTP
2576
--- src/descendants.c
+++ src/descendants.c
@@ -439,11 +439,11 @@
439439
Stmt q;
440440
int showAll = P("all")!=0;
441441
int showClosed = P("closed")!=0;
442442
443443
login_check_credentials();
444
- if( !g.perm.Read ){ login_needed(); return; }
444
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
445445
446446
if( !showAll ){
447447
style_submenu_element("All", "All", "leaves?all");
448448
}
449449
if( !showClosed ){
450450
--- src/descendants.c
+++ src/descendants.c
@@ -439,11 +439,11 @@
439 Stmt q;
440 int showAll = P("all")!=0;
441 int showClosed = P("closed")!=0;
442
443 login_check_credentials();
444 if( !g.perm.Read ){ login_needed(); return; }
445
446 if( !showAll ){
447 style_submenu_element("All", "All", "leaves?all");
448 }
449 if( !showClosed ){
450
--- src/descendants.c
+++ src/descendants.c
@@ -439,11 +439,11 @@
439 Stmt q;
440 int showAll = P("all")!=0;
441 int showClosed = P("closed")!=0;
442
443 login_check_credentials();
444 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
445
446 if( !showAll ){
447 style_submenu_element("All", "All", "leaves?all");
448 }
449 if( !showClosed ){
450
+12 -12
--- src/diff.c
+++ src/diff.c
@@ -2234,11 +2234,11 @@
22342234
int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */
22352235
22362236
/* Gather query parameters */
22372237
showLog = atoi(PD("log","1"));
22382238
login_check_credentials();
2239
- if( !g.perm.Read ){ login_needed(); return; }
2239
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
22402240
if( exclude_spiders("annotate") ) return;
22412241
load_control();
22422242
mid = name_to_typed_rid(PD("checkin","0"),"ci");
22432243
zFilename = P("filename");
22442244
fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
@@ -2294,11 +2294,11 @@
22942294
}
22952295
if( iLimit>20 ){
22962296
style_submenu_element("20 Ancestors", "20 Ancestors",
22972297
"%s", url_render(&url, "limit", "20", 0, 0));
22982298
}
2299
- if( db_get_boolean("white-foreground", 0) ){
2299
+ if( skin_white_foreground() ){
23002300
clr1 = 0xa04040;
23012301
clr2 = 0x4059a0;
23022302
}else{
23032303
clr1 = 0xffb5b5; /* Recent changes: red (hot) */
23042304
clr2 = 0xb5e0ff; /* Older changes: blue (cold) */
@@ -2307,17 +2307,17 @@
23072307
clr = gradient_color(clr1, clr2, ann.nVers-1, i);
23082308
ann.aVers[i].zBgColor = mprintf("#%06x", clr);
23092309
}
23102310
23112311
if( showLog ){
2312
- char *zLink = href("%R/finfo?name=%t&ci=%s",zFilename,zCI);
2312
+ char *zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI);
23132313
@ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
23142314
@ <ol>
23152315
for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
23162316
@ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
2317
- @ check-in %z(href("%R/info/%s",p->zMUuid))%S(p->zMUuid)</a>
2318
- @ artifact %z(href("%R/artifact/%s",p->zFUuid))%S(p->zFUuid)</a>
2317
+ @ check-in %z(href("%R/info/%!S",p->zMUuid))%S(p->zMUuid)</a>
2318
+ @ artifact %z(href("%R/artifact/%!S",p->zFUuid))%S(p->zFUuid)</a>
23192319
@ </span>
23202320
#if 0
23212321
if( i>0 ){
23222322
char *zLink = xhref("target='infowindow'",
23232323
"%R/fdiff?v1=%S&v2=%S&sbs=1",
@@ -2335,17 +2335,17 @@
23352335
@ </ol>
23362336
@ <hr>
23372337
}
23382338
if( !ann.bLimit ){
23392339
@ <h2>Origin for each line in
2340
- @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
2341
- @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
2340
+ @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
2341
+ @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
23422342
iLimit = ann.nVers+10;
23432343
}else{
23442344
@ <h2>Lines added by the %d(iLimit) most recent ancestors of
2345
- @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
2346
- @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
2345
+ @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
2346
+ @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
23472347
}
23482348
@ <pre>
23492349
for(i=0; i<ann.nOrig; i++){
23502350
int iVers = ann.aOrig[i].iVers;
23512351
char *z = (char*)ann.aOrig[i].z;
@@ -2355,11 +2355,11 @@
23552355
if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;
23562356
23572357
if( bBlame ){
23582358
if( iVers>=0 ){
23592359
struct AnnVers *p = ann.aVers+iVers;
2360
- char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
2360
+ char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
23612361
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
23622362
"<span style='background-color:%s'>"
23632363
"%s%.10s</a> %s</span> %13.13s:",
23642364
p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
23652365
fossil_free(zLink);
@@ -2367,11 +2367,11 @@
23672367
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
23682368
}
23692369
}else{
23702370
if( iVers>=0 ){
23712371
struct AnnVers *p = ann.aVers+iVers;
2372
- char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
2372
+ char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
23732373
sqlite3_snprintf(sizeof(zPrefix), zPrefix,
23742374
"<span style='background-color:%s'>"
23752375
"%s%.10s</a> %s</span> %4d:",
23762376
p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
23772377
fossil_free(zLink);
@@ -2434,11 +2434,11 @@
24342434
if( find_option("ignore-all-space","w",0)!=0 ){
24352435
annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
24362436
}
24372437
fileVers = find_option("filevers",0,0)!=0;
24382438
db_must_be_within_tree();
2439
-
2439
+
24402440
/* We should be done with options.. */
24412441
verify_all_options();
24422442
24432443
if( g.argc<3 ) {
24442444
usage("FILENAME");
24452445
--- src/diff.c
+++ src/diff.c
@@ -2234,11 +2234,11 @@
2234 int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */
2235
2236 /* Gather query parameters */
2237 showLog = atoi(PD("log","1"));
2238 login_check_credentials();
2239 if( !g.perm.Read ){ login_needed(); return; }
2240 if( exclude_spiders("annotate") ) return;
2241 load_control();
2242 mid = name_to_typed_rid(PD("checkin","0"),"ci");
2243 zFilename = P("filename");
2244 fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
@@ -2294,11 +2294,11 @@
2294 }
2295 if( iLimit>20 ){
2296 style_submenu_element("20 Ancestors", "20 Ancestors",
2297 "%s", url_render(&url, "limit", "20", 0, 0));
2298 }
2299 if( db_get_boolean("white-foreground", 0) ){
2300 clr1 = 0xa04040;
2301 clr2 = 0x4059a0;
2302 }else{
2303 clr1 = 0xffb5b5; /* Recent changes: red (hot) */
2304 clr2 = 0xb5e0ff; /* Older changes: blue (cold) */
@@ -2307,17 +2307,17 @@
2307 clr = gradient_color(clr1, clr2, ann.nVers-1, i);
2308 ann.aVers[i].zBgColor = mprintf("#%06x", clr);
2309 }
2310
2311 if( showLog ){
2312 char *zLink = href("%R/finfo?name=%t&ci=%s",zFilename,zCI);
2313 @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
2314 @ <ol>
2315 for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
2316 @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
2317 @ check-in %z(href("%R/info/%s",p->zMUuid))%S(p->zMUuid)</a>
2318 @ artifact %z(href("%R/artifact/%s",p->zFUuid))%S(p->zFUuid)</a>
2319 @ </span>
2320 #if 0
2321 if( i>0 ){
2322 char *zLink = xhref("target='infowindow'",
2323 "%R/fdiff?v1=%S&v2=%S&sbs=1",
@@ -2335,17 +2335,17 @@
2335 @ </ol>
2336 @ <hr>
2337 }
2338 if( !ann.bLimit ){
2339 @ <h2>Origin for each line in
2340 @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
2341 @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
2342 iLimit = ann.nVers+10;
2343 }else{
2344 @ <h2>Lines added by the %d(iLimit) most recent ancestors of
2345 @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
2346 @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
2347 }
2348 @ <pre>
2349 for(i=0; i<ann.nOrig; i++){
2350 int iVers = ann.aOrig[i].iVers;
2351 char *z = (char*)ann.aOrig[i].z;
@@ -2355,11 +2355,11 @@
2355 if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;
2356
2357 if( bBlame ){
2358 if( iVers>=0 ){
2359 struct AnnVers *p = ann.aVers+iVers;
2360 char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
2361 sqlite3_snprintf(sizeof(zPrefix), zPrefix,
2362 "<span style='background-color:%s'>"
2363 "%s%.10s</a> %s</span> %13.13s:",
2364 p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
2365 fossil_free(zLink);
@@ -2367,11 +2367,11 @@
2367 sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
2368 }
2369 }else{
2370 if( iVers>=0 ){
2371 struct AnnVers *p = ann.aVers+iVers;
2372 char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
2373 sqlite3_snprintf(sizeof(zPrefix), zPrefix,
2374 "<span style='background-color:%s'>"
2375 "%s%.10s</a> %s</span> %4d:",
2376 p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
2377 fossil_free(zLink);
@@ -2434,11 +2434,11 @@
2434 if( find_option("ignore-all-space","w",0)!=0 ){
2435 annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
2436 }
2437 fileVers = find_option("filevers",0,0)!=0;
2438 db_must_be_within_tree();
2439
2440 /* We should be done with options.. */
2441 verify_all_options();
2442
2443 if( g.argc<3 ) {
2444 usage("FILENAME");
2445
--- src/diff.c
+++ src/diff.c
@@ -2234,11 +2234,11 @@
2234 int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */
2235
2236 /* Gather query parameters */
2237 showLog = atoi(PD("log","1"));
2238 login_check_credentials();
2239 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2240 if( exclude_spiders("annotate") ) return;
2241 load_control();
2242 mid = name_to_typed_rid(PD("checkin","0"),"ci");
2243 zFilename = P("filename");
2244 fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
@@ -2294,11 +2294,11 @@
2294 }
2295 if( iLimit>20 ){
2296 style_submenu_element("20 Ancestors", "20 Ancestors",
2297 "%s", url_render(&url, "limit", "20", 0, 0));
2298 }
2299 if( skin_white_foreground() ){
2300 clr1 = 0xa04040;
2301 clr2 = 0x4059a0;
2302 }else{
2303 clr1 = 0xffb5b5; /* Recent changes: red (hot) */
2304 clr2 = 0xb5e0ff; /* Older changes: blue (cold) */
@@ -2307,17 +2307,17 @@
2307 clr = gradient_color(clr1, clr2, ann.nVers-1, i);
2308 ann.aVers[i].zBgColor = mprintf("#%06x", clr);
2309 }
2310
2311 if( showLog ){
2312 char *zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI);
2313 @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
2314 @ <ol>
2315 for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
2316 @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
2317 @ check-in %z(href("%R/info/%!S",p->zMUuid))%S(p->zMUuid)</a>
2318 @ artifact %z(href("%R/artifact/%!S",p->zFUuid))%S(p->zFUuid)</a>
2319 @ </span>
2320 #if 0
2321 if( i>0 ){
2322 char *zLink = xhref("target='infowindow'",
2323 "%R/fdiff?v1=%S&v2=%S&sbs=1",
@@ -2335,17 +2335,17 @@
2335 @ </ol>
2336 @ <hr>
2337 }
2338 if( !ann.bLimit ){
2339 @ <h2>Origin for each line in
2340 @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
2341 @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
2342 iLimit = ann.nVers+10;
2343 }else{
2344 @ <h2>Lines added by the %d(iLimit) most recent ancestors of
2345 @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
2346 @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
2347 }
2348 @ <pre>
2349 for(i=0; i<ann.nOrig; i++){
2350 int iVers = ann.aOrig[i].iVers;
2351 char *z = (char*)ann.aOrig[i].z;
@@ -2355,11 +2355,11 @@
2355 if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;
2356
2357 if( bBlame ){
2358 if( iVers>=0 ){
2359 struct AnnVers *p = ann.aVers+iVers;
2360 char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
2361 sqlite3_snprintf(sizeof(zPrefix), zPrefix,
2362 "<span style='background-color:%s'>"
2363 "%s%.10s</a> %s</span> %13.13s:",
2364 p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
2365 fossil_free(zLink);
@@ -2367,11 +2367,11 @@
2367 sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
2368 }
2369 }else{
2370 if( iVers>=0 ){
2371 struct AnnVers *p = ann.aVers+iVers;
2372 char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
2373 sqlite3_snprintf(sizeof(zPrefix), zPrefix,
2374 "<span style='background-color:%s'>"
2375 "%s%.10s</a> %s</span> %4d:",
2376 p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
2377 fossil_free(zLink);
@@ -2434,11 +2434,11 @@
2434 if( find_option("ignore-all-space","w",0)!=0 ){
2435 annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
2436 }
2437 fileVers = find_option("filevers",0,0)!=0;
2438 db_must_be_within_tree();
2439
2440 /* We should be done with options.. */
2441 verify_all_options();
2442
2443 if( g.argc<3 ) {
2444 usage("FILENAME");
2445
+1 -1
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -841,11 +841,11 @@
841841
*/
842842
void vpatch_page(void){
843843
const char *zFrom = P("from");
844844
const char *zTo = P("to");
845845
login_check_credentials();
846
- if( !g.perm.Read ){ login_needed(); return; }
846
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
847847
if( zFrom==0 || zTo==0 ) fossil_redirect_home();
848848
849849
cgi_set_content_type("text/plain");
850850
diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE);
851851
}
852852
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -841,11 +841,11 @@
841 */
842 void vpatch_page(void){
843 const char *zFrom = P("from");
844 const char *zTo = P("to");
845 login_check_credentials();
846 if( !g.perm.Read ){ login_needed(); return; }
847 if( zFrom==0 || zTo==0 ) fossil_redirect_home();
848
849 cgi_set_content_type("text/plain");
850 diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE);
851 }
852
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -841,11 +841,11 @@
841 */
842 void vpatch_page(void){
843 const char *zFrom = P("from");
844 const char *zTo = P("to");
845 login_check_credentials();
846 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
847 if( zFrom==0 || zTo==0 ) fossil_redirect_home();
848
849 cgi_set_content_type("text/plain");
850 diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE);
851 }
852
+19 -7
--- src/doc.c
+++ src/doc.c
@@ -106,10 +106,11 @@
106106
{ "com", 3, "application/x-msdos-program" },
107107
{ "cpio", 4, "application/x-cpio" },
108108
{ "cpt", 3, "application/mac-compactpro" },
109109
{ "csh", 3, "application/x-csh" },
110110
{ "css", 3, "text/css" },
111
+ { "csv", 3, "text/csv" },
111112
{ "dcr", 3, "application/x-director" },
112113
{ "deb", 3, "application/x-debian-package" },
113114
{ "dir", 3, "application/x-director" },
114115
{ "dl", 2, "video/dl" },
115116
{ "dms", 3, "application/octet-stream" },
@@ -291,10 +292,24 @@
291292
{ "xpm", 3, "image/x-xpixmap" },
292293
{ "xwd", 3, "image/x-xwindowdump" },
293294
{ "xyz", 3, "chemical/x-pdb" },
294295
{ "zip", 3, "application/zip" },
295296
};
297
+
298
+/*
299
+** Verify that all entries in the aMime[] table are in sorted order.
300
+** Abort with a fatal error if any is out-of-order.
301
+*/
302
+static void mimetype_verify(void){
303
+ int i;
304
+ for(i=1; i<ArraySize(aMime); i++){
305
+ if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
306
+ fossil_fatal("mimetypes out of sequence: %s before %s",
307
+ aMime[i-1].zSuffix, aMime[i].zSuffix);
308
+ }
309
+ }
310
+}
296311
297312
/*
298313
** Guess the mime-type of a document based on its name.
299314
*/
300315
const char *mimetype_from_name(const char *zName){
@@ -308,16 +323,11 @@
308323
#ifdef FOSSIL_DEBUG
309324
/* This is test code to make sure the table above is in the correct
310325
** order
311326
*/
312327
if( fossil_strcmp(zName, "mimetype-test")==0 ){
313
- for(i=1; i<ArraySize(aMime); i++){
314
- if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
315
- fossil_fatal("mimetypes out of sequence: %s before %s",
316
- aMime[i-1].zSuffix, aMime[i].zSuffix);
317
- }
318
- }
328
+ mimetype_verify();
319329
return "ok";
320330
}
321331
#endif
322332
323333
z = zName;
@@ -356,10 +366,11 @@
356366
** filename is special and verifies the integrity of the mimetype table.
357367
** It should return "ok".
358368
*/
359369
void mimetype_test_cmd(void){
360370
int i;
371
+ mimetype_verify();
361372
for(i=2; i<g.argc; i++){
362373
fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
363374
}
364375
}
365376
@@ -369,10 +380,11 @@
369380
** Show the built-in table used to guess embedded document mimetypes
370381
** from file suffixes.
371382
*/
372383
void mimetype_list_page(void){
373384
int i;
385
+ mimetype_verify();
374386
style_header("Mimetype List");
375387
@ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
376388
@ suffixes and the following table to guess at the appropriate mimetype
377389
@ for each document.</p>
378390
@ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
@@ -531,11 +543,11 @@
531543
static const char *const azSuffix[] = {
532544
"index.html", "index.wiki", "index.md"
533545
};
534546
535547
login_check_credentials();
536
- if( !g.perm.Read ){ login_needed(); return; }
548
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
537549
blob_init(&title, 0, 0);
538550
db_begin_transaction();
539551
while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
540552
zName = PD("name", "tip/index.wiki");
541553
for(i=0; zName[i] && zName[i]!='/'; i++){}
542554
--- src/doc.c
+++ src/doc.c
@@ -106,10 +106,11 @@
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" },
@@ -291,10 +292,24 @@
291 { "xpm", 3, "image/x-xpixmap" },
292 { "xwd", 3, "image/x-xwindowdump" },
293 { "xyz", 3, "chemical/x-pdb" },
294 { "zip", 3, "application/zip" },
295 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
297 /*
298 ** Guess the mime-type of a document based on its name.
299 */
300 const char *mimetype_from_name(const char *zName){
@@ -308,16 +323,11 @@
308 #ifdef FOSSIL_DEBUG
309 /* This is test code to make sure the table above is in the correct
310 ** order
311 */
312 if( fossil_strcmp(zName, "mimetype-test")==0 ){
313 for(i=1; i<ArraySize(aMime); i++){
314 if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
315 fossil_fatal("mimetypes out of sequence: %s before %s",
316 aMime[i-1].zSuffix, aMime[i].zSuffix);
317 }
318 }
319 return "ok";
320 }
321 #endif
322
323 z = zName;
@@ -356,10 +366,11 @@
356 ** filename is special and verifies the integrity of the mimetype table.
357 ** It should return "ok".
358 */
359 void mimetype_test_cmd(void){
360 int i;
 
361 for(i=2; i<g.argc; i++){
362 fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
363 }
364 }
365
@@ -369,10 +380,11 @@
369 ** Show the built-in table used to guess embedded document mimetypes
370 ** from file suffixes.
371 */
372 void mimetype_list_page(void){
373 int i;
 
374 style_header("Mimetype List");
375 @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
376 @ suffixes and the following table to guess at the appropriate mimetype
377 @ for each document.</p>
378 @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
@@ -531,11 +543,11 @@
531 static const char *const azSuffix[] = {
532 "index.html", "index.wiki", "index.md"
533 };
534
535 login_check_credentials();
536 if( !g.perm.Read ){ login_needed(); return; }
537 blob_init(&title, 0, 0);
538 db_begin_transaction();
539 while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
540 zName = PD("name", "tip/index.wiki");
541 for(i=0; zName[i] && zName[i]!='/'; i++){}
542
--- src/doc.c
+++ src/doc.c
@@ -106,10 +106,11 @@
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 { "csv", 3, "text/csv" },
112 { "dcr", 3, "application/x-director" },
113 { "deb", 3, "application/x-debian-package" },
114 { "dir", 3, "application/x-director" },
115 { "dl", 2, "video/dl" },
116 { "dms", 3, "application/octet-stream" },
@@ -291,10 +292,24 @@
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 /*
299 ** Verify that all entries in the aMime[] table are in sorted order.
300 ** Abort with a fatal error if any is out-of-order.
301 */
302 static void mimetype_verify(void){
303 int i;
304 for(i=1; i<ArraySize(aMime); i++){
305 if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
306 fossil_fatal("mimetypes out of sequence: %s before %s",
307 aMime[i-1].zSuffix, aMime[i].zSuffix);
308 }
309 }
310 }
311
312 /*
313 ** Guess the mime-type of a document based on its name.
314 */
315 const char *mimetype_from_name(const char *zName){
@@ -308,16 +323,11 @@
323 #ifdef FOSSIL_DEBUG
324 /* This is test code to make sure the table above is in the correct
325 ** order
326 */
327 if( fossil_strcmp(zName, "mimetype-test")==0 ){
328 mimetype_verify();
 
 
 
 
 
329 return "ok";
330 }
331 #endif
332
333 z = zName;
@@ -356,10 +366,11 @@
366 ** filename is special and verifies the integrity of the mimetype table.
367 ** It should return "ok".
368 */
369 void mimetype_test_cmd(void){
370 int i;
371 mimetype_verify();
372 for(i=2; i<g.argc; i++){
373 fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
374 }
375 }
376
@@ -369,10 +380,11 @@
380 ** Show the built-in table used to guess embedded document mimetypes
381 ** from file suffixes.
382 */
383 void mimetype_list_page(void){
384 int i;
385 mimetype_verify();
386 style_header("Mimetype List");
387 @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
388 @ suffixes and the following table to guess at the appropriate mimetype
389 @ for each document.</p>
390 @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
@@ -531,11 +543,11 @@
543 static const char *const azSuffix[] = {
544 "index.html", "index.wiki", "index.md"
545 };
546
547 login_check_credentials();
548 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
549 blob_init(&title, 0, 0);
550 db_begin_transaction();
551 while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
552 zName = PD("name", "tip/index.wiki");
553 for(i=0; zName[i] && zName[i]!='/'; i++){}
554
+156 -92
--- src/event.c
+++ src/event.c
@@ -15,78 +15,90 @@
1515
**
1616
*******************************************************************************
1717
**
1818
** This file contains code to do formatting of event messages:
1919
**
20
+** Technical Notes
2021
** Milestones
2122
** Blog posts
2223
** New articles
2324
** Process checkpoints
2425
** Announcements
26
+**
27
+** Do not confuse "event" artifacts with the "event" table in the
28
+** repository database. An "event" artifact is a technical-note: a
29
+** wiki- or blog-like essay that appears on the timeline. The "event"
30
+** table records all entries on the timeline, including tech-notes.
31
+**
32
+** (2015-02-14): Changing the name to "tech-note" most everywhere.
2533
*/
2634
#include "config.h"
2735
#include <assert.h>
2836
#include <ctype.h>
2937
#include "event.h"
3038
3139
/*
32
-** Output a hyperlink to an event given its tagid.
40
+** Output a hyperlink to an technote given its tagid.
3341
*/
3442
void hyperlink_to_event_tagid(int tagid){
35
- char *zEventId;
36
- zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
43
+ char *zId;
44
+ zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
3745
tagid);
38
- @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>]
39
- free(zEventId);
46
+ @ [%z(href("%R/technote/%s",zId))%S(zId)</a>]
47
+ free(zId);
4048
}
4149
4250
/*
51
+** WEBPAGE: technote
4352
** WEBPAGE: event
44
-** URL: /event
53
+**
54
+** Display a "technical note" or "tech-note" (formerly called an "event").
55
+**
4556
** PARAMETERS:
4657
**
47
-** name=EVENTID // Identify the event to display EVENTID must be complete
48
-** aid=ARTIFACTID // Which specific version of the event. Optional.
49
-** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional.
58
+** name=ID // Identify the tech-note to display. ID must be complete
59
+** aid=ARTIFACTID // Which specific version of the tech-note. Optional.
60
+** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional.
5061
**
5162
** Display an existing event identified by EVENTID
5263
*/
5364
void event_page(void){
5465
int rid = 0; /* rid of the event artifact */
5566
char *zUuid; /* UUID corresponding to rid */
56
- const char *zEventId; /* Event identifier */
67
+ const char *zId; /* Event identifier */
5768
const char *zVerbose; /* Value of verbose option */
58
- char *zETime; /* Time of the event */
69
+ char *zETime; /* Time of the tech-note */
5970
char *zATime; /* Time the artifact was created */
6071
int specRid; /* rid specified by aid= parameter */
61
- int prevRid, nextRid; /* Previous or next edits of this event */
62
- Manifest *pEvent; /* Parsed event artifact */
63
- Blob fullbody; /* Complete content of the event body */
64
- Blob title; /* Title extracted from the event body */
72
+ int prevRid, nextRid; /* Previous or next edits of this tech-note */
73
+ Manifest *pTNote; /* Parsed technote artifact */
74
+ Blob fullbody; /* Complete content of the technote body */
75
+ Blob title; /* Title extracted from the technote body */
6576
Blob tail; /* Event body that comes after the title */
66
- Stmt q1; /* Query to search for the event */
77
+ Stmt q1; /* Query to search for the technote */
6778
int verboseFlag; /* True to show details */
79
+ const char *zMimetype = 0; /* Mimetype of the document */
6880
6981
70
- /* wiki-read privilege is needed in order to read events.
82
+ /* wiki-read privilege is needed in order to read tech-notes.
7183
*/
7284
login_check_credentials();
7385
if( !g.perm.RdWiki ){
74
- login_needed();
86
+ login_needed(g.anon.RdWiki);
7587
return;
7688
}
7789
78
- zEventId = P("name");
79
- if( zEventId==0 ){ fossil_redirect_home(); return; }
90
+ zId = P("name");
91
+ if( zId==0 ){ fossil_redirect_home(); return; }
8092
zUuid = (char*)P("aid");
8193
specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0;
8294
rid = nextRid = prevRid = 0;
8395
db_prepare(&q1,
8496
"SELECT rid FROM tagxref"
8597
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')"
8698
" ORDER BY mtime DESC",
87
- zEventId
99
+ zId
88100
);
89101
while( db_step(&q1)==SQLITE_ROW ){
90102
nextRid = rid;
91103
rid = db_column_int(&q1, 0);
92104
if( specRid==0 || specRid==rid ){
@@ -96,12 +108,12 @@
96108
break;
97109
}
98110
}
99111
db_finalize(&q1);
100112
if( rid==0 || (specRid!=0 && specRid!=rid) ){
101
- style_header("No Such Event");
102
- @ Cannot locate specified event
113
+ style_header("No Such Tech-Note");
114
+ @ Cannot locate a technical note called <b>%h(zId)</b>.
103115
style_footer();
104116
return;
105117
}
106118
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
107119
zVerbose = P("v");
@@ -113,156 +125,194 @@
113125
}
114126
verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
115127
116128
/* Extract the event content.
117129
*/
118
- pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
119
- if( pEvent==0 ){
120
- fossil_fatal("Object #%d is not an event", rid);
130
+ pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
131
+ if( pTNote==0 ){
132
+ fossil_fatal("Object #%d is not a tech-note", rid);
121133
}
122
- blob_init(&fullbody, pEvent->zWiki, -1);
123
- if( wiki_find_title(&fullbody, &title, &tail) ){
124
- style_header("%s", blob_str(&title));
134
+ zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype));
135
+ blob_init(&fullbody, pTNote->zWiki, -1);
136
+ blob_init(&title, 0, 0);
137
+ blob_init(&tail, 0, 0);
138
+ if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
139
+ if( !wiki_find_title(&fullbody, &title, &tail) ){
140
+ blob_appendf(&title, "Tech-note %S", zId);
141
+ tail = fullbody;
142
+ }
143
+ }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
144
+ markdown_to_html(&fullbody, &title, &tail);
145
+ if( blob_size(&title)==0 ){
146
+ blob_appendf(&title, "Tech-note %S", zId);
147
+ }
125148
}else{
126
- style_header("Event %S", zEventId);
149
+ blob_appendf(&title, "Tech-note %S", zId);
127150
tail = fullbody;
128151
}
152
+ style_header("%s", blob_str(&title));
129153
if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
130
- style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
131
- g.zTop, zEventId);
154
+ style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
132155
}
133
- zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
134
- style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId);
156
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157
+ style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
135158
if( g.perm.Hyperlink ){
136159
if( verboseFlag ){
137
- style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s",
138
- zEventId, zUuid);
160
+ style_submenu_element("Plain", 0,
161
+ "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
162
+ zId, zUuid);
139163
if( nextRid ){
140164
char *zNext;
141165
zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
142
- style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v",
143
- zEventId, zNext);
166
+ style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v",
167
+ zId, zNext);
144168
free(zNext);
145169
}
146170
if( prevRid ){
147171
char *zPrev;
148172
zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
149
- style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v",
150
- zEventId, zPrev);
173
+ style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v",
174
+ zId, zPrev);
151175
free(zPrev);
152176
}
153177
}else{
154
- style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v",
155
- zEventId, zUuid);
178
+ style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v",
179
+ zId, zUuid);
156180
}
157181
}
158182
159183
if( verboseFlag && g.perm.Hyperlink ){
160184
int i;
161185
const char *zClr = 0;
162186
Blob comment;
163187
164
- zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
165
- @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at
188
+ zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate);
189
+ @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at
166190
@ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
167
- @ entered by user <b>%h(pEvent->zUser)</b> on
191
+ @ entered by user <b>%h(pTNote->zUser)</b> on
168192
@ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
169193
@ <blockquote>
170
- for(i=0; i<pEvent->nTag; i++){
171
- if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
172
- zClr = pEvent->aTag[i].zValue;
194
+ for(i=0; i<pTNote->nTag; i++){
195
+ if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){
196
+ zClr = pTNote->aTag[i].zValue;
173197
}
174198
}
175199
if( zClr && zClr[0]==0 ) zClr = 0;
176200
if( zClr ){
177201
@ <div style="background-color: %h(zClr);">
178202
}else{
179203
@ <div>
180204
}
181
- blob_init(&comment, pEvent->zComment, -1);
205
+ blob_init(&comment, pTNote->zComment, -1);
182206
wiki_convert(&comment, 0, WIKI_INLINE);
183207
blob_reset(&comment);
184208
@ </div>
185209
@ </blockquote><hr />
186210
}
187211
188
- wiki_convert(&tail, 0, 0);
212
+ if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
213
+ wiki_convert(&fullbody, 0, 0);
214
+ }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
215
+ cgi_append_content(blob_buffer(&tail), blob_size(&tail));
216
+ }else{
217
+ @ <pre>
218
+ @ %h(blob_str(&fullbody))
219
+ @ </pre>
220
+ }
189221
style_footer();
190
- manifest_destroy(pEvent);
222
+ manifest_destroy(pTNote);
191223
}
192224
193225
/*
226
+** WEBPAGE: technoteedit
194227
** WEBPAGE: eventedit
195
-** URL: /eventedit?name=EVENTID
228
+**
229
+** Revise or create a technical note (formerly called an 'event').
230
+**
231
+** Parameters:
196232
**
197
-** Edit an event. If name is omitted, create a new event.
233
+** name=ID Hex hash ID of the tech-note. If omitted, a new
234
+** tech-note is created.
198235
*/
199236
void eventedit_page(void){
200237
char *zTag;
201238
int rid = 0;
202239
Blob event;
203
- const char *zEventId;
240
+ const char *zId;
204241
int n;
205242
const char *z;
206243
char *zBody = (char*)P("w");
207244
char *zETime = (char*)P("t");
208245
const char *zComment = P("c");
209246
const char *zTags = P("g");
210247
const char *zClr;
248
+ const char *zMimetype = P("mimetype");
249
+ int isNew = 0;
211250
212251
if( zBody ){
213252
zBody = mprintf("%s", zBody);
214253
}
215254
login_check_credentials();
216
- zEventId = P("name");
217
- if( zEventId==0 ){
218
- zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))");
255
+ zId = P("name");
256
+ if( zId==0 ){
257
+ zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
258
+ isNew = 1;
219259
}else{
220
- int nEventId = strlen(zEventId);
221
- if( nEventId!=40 || !validate16(zEventId, 40) ){
260
+ int nId = strlen(zId);
261
+ if( !validate16(zId, nId) ){
222262
fossil_redirect_home();
223263
return;
224264
}
225265
}
226
- zTag = mprintf("event-%s", zEventId);
266
+ zTag = mprintf("event-%s", zId);
227267
rid = db_int(0,
228268
"SELECT rid FROM tagxref"
229
- " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
269
+ " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')"
230270
" ORDER BY mtime DESC", zTag
231271
);
272
+ if( rid && strlen(zId)<40 ){
273
+ zId = db_text(0,
274
+ "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB '%q*'",
275
+ zTag
276
+ );
277
+ }
232278
free(zTag);
233279
234280
/* Need both check-in and wiki-write or wiki-create privileges in order
235281
** to edit/create an event.
236282
*/
237283
if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
238
- login_needed();
284
+ login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
239285
return;
240286
}
241287
242288
/* Figure out the color */
243289
if( rid ){
244
- zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
290
+ zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
245291
}else{
246292
zClr = "";
293
+ isNew = 1;
247294
}
248295
zClr = PD("clr",zClr);
249296
if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
250297
251298
252299
/* If editing an existing event, extract the key fields to use as
253300
** a starting point for the edit.
254301
*/
255
- if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
256
- Manifest *pEvent;
257
- pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
258
- if( pEvent && pEvent->type==CFTYPE_EVENT ){
259
- if( zBody==0 ) zBody = pEvent->zWiki;
302
+ if( rid
303
+ && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0)
304
+ ){
305
+ Manifest *pTNote;
306
+ pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
307
+ if( pTNote && pTNote->type==CFTYPE_EVENT ){
308
+ if( zBody==0 ) zBody = pTNote->zWiki;
260309
if( zETime==0 ){
261
- zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
310
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
262311
}
263
- if( zComment==0 ) zComment = pEvent->zComment;
312
+ if( zComment==0 ) zComment = pTNote->zComment;
313
+ if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
264314
}
265315
if( zTags==0 ){
266316
zTags = db_text(0,
267317
"SELECT group_concat(substr(tagname,5),', ')"
268318
" FROM tagxref, tag"
@@ -276,11 +326,11 @@
276326
zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
277327
if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
278328
char *zDate;
279329
Blob cksum;
280330
int nrid, n;
281
- blob_zero(&event);
331
+ blob_init(&event, 0, 0);
282332
db_begin_transaction();
283333
login_verify_csrf_secret();
284334
while( fossil_isspace(zComment[0]) ) zComment++;
285335
n = strlen(zComment);
286336
while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -289,17 +339,20 @@
289339
}
290340
zDate = date_in_standard_format("now");
291341
blob_appendf(&event, "D %s\n", zDate);
292342
free(zDate);
293343
zETime[10] = 'T';
294
- blob_appendf(&event, "E %s %s\n", zETime, zEventId);
344
+ blob_appendf(&event, "E %s %s\n", zETime, zId);
295345
zETime[10] = ' ';
296346
if( rid ){
297347
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
298348
blob_appendf(&event, "P %s\n", zUuid);
299349
free(zUuid);
300350
}
351
+ if( zMimetype && zMimetype[0] ){
352
+ blob_appendf(&event, "N %s\n", zMimetype);
353
+ }
301354
if( zClr && zClr[0] ){
302355
blob_appendf(&event, "T +bgcolor * %F\n", zClr);
303356
}
304357
if( zTags && zTags[0] ){
305358
Blob tags, one;
@@ -346,26 +399,37 @@
346399
md5sum_blob(&event, &cksum);
347400
blob_appendf(&event, "Z %b\n", &cksum);
348401
blob_reset(&cksum);
349402
nrid = content_put(&event);
350403
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
351
- manifest_crosslink(nrid, &event, MC_NONE);
404
+ if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405
+ db_end_transaction(1);
406
+ style_header("Error");
407
+ @ Internal error: Fossil tried to make an invalid artifact for
408
+ @ the edited technode.
409
+ style_footer();
410
+ return;
411
+ }
352412
assert( blob_is_reset(&event) );
353413
content_deltify(rid, nrid, 0);
354414
db_end_transaction(0);
355
- cgi_redirectf("event?name=%T", zEventId);
415
+ cgi_redirectf("technote?name=%T", zId);
356416
}
357417
if( P("cancel")!=0 ){
358
- cgi_redirectf("event?name=%T", zEventId);
418
+ cgi_redirectf("technote?name=%T", zId);
359419
return;
360420
}
361421
if( zBody==0 ){
362
- zBody = mprintf("<i>Event Text</i>");
422
+ zBody = mprintf("Insert new content here...");
363423
}
364
- style_header("Edit Event %S", zEventId);
424
+ if( isNew ){
425
+ style_header("New Tech-note %S", zId);
426
+ }else{
427
+ style_header("Edit Tech-note %S", zId);
428
+ }
365429
if( P("preview")!=0 ){
366
- Blob title, tail, com;
430
+ Blob com;
367431
@ <p><b>Timeline comment preview:</b></p>
368432
@ <blockquote>
369433
@ <table border="0">
370434
if( zClr && zClr[0] ){
371435
@ <tr><td style="background-color: %h(zClr);">
@@ -377,55 +441,55 @@
377441
wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
378442
@ </td></tr></table>
379443
@ </blockquote>
380444
@ <p><b>Page content preview:</b><p>
381445
@ <blockquote>
382
- blob_zero(&event);
446
+ blob_init(&event, 0, 0);
383447
blob_append(&event, zBody, -1);
384
- if( wiki_find_title(&event, &title, &tail) ){
385
- @ <h2 align="center">%h(blob_str(&title))</h2>
386
- wiki_convert(&tail, 0, 0);
387
- }else{
388
- wiki_convert(&event, 0, 0);
389
- }
448
+ wiki_render_by_mimetype(&event, zMimetype);
390449
@ </blockquote><hr />
391450
blob_reset(&event);
392451
}
393452
for(n=2, z=zBody; z[0]; z++){
394453
if( z[0]=='\n' ) n++;
395454
}
396455
if( n<20 ) n = 20;
397456
if( n>40 ) n = 40;
398
- @ <form method="post" action="%s(g.zTop)/eventedit"><div>
457
+ @ <form method="post" action="%R/technoteedit"><div>
399458
login_insert_csrf_secret();
400
- @ <input type="hidden" name="name" value="%h(zEventId)" />
459
+ @ <input type="hidden" name="name" value="%h(zId)" />
401460
@ <table border="0" cellspacing="10">
402461
403
- @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
462
+ @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
404463
@ <td valign="top">
405464
@ <input type="text" name="t" size="25" value="%h(zETime)" />
406465
@ </td></tr>
407466
408
- @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
467
+ @ <tr><th align="right" valign="top">Timeline Comment:</th>
409468
@ <td valign="top">
410
- @ <textarea name="c" class="eventedit" cols="80"
469
+ @ <textarea name="c" class="technoteedit" cols="80"
411470
@ rows="3" wrap="virtual">%h(zComment)</textarea>
412471
@ </td></tr>
413472
414
- @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
473
+ @ <tr><th align="right" valign="top">Timeline Background Color:</th>
415474
@ <td valign="top">
416475
render_color_chooser(0, zClr, 0, "clr", "cclr");
417476
@ </td></tr>
418477
419478
@ <tr><th align="right" valign="top">Tags:</th>
420479
@ <td valign="top">
421480
@ <input type="text" name="g" size="40" value="%h(zTags)" />
422481
@ </td></tr>
482
+
483
+ @ <tr><th align="right" valign="top">Markup Style:</th>
484
+ @ <td valign="top">
485
+ mimetype_option_menu(zMimetype);
486
+ @ </td></tr>
423487
424488
@ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
425489
@ <td valign="top">
426
- @ <textarea name="w" class="eventedit" cols="80"
490
+ @ <textarea name="w" class="technoteedit" cols="80"
427491
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
428492
@ </td></tr>
429493
430494
@ <tr><td colspan="2">
431495
@ <input type="submit" name="preview" value="Preview Your Changes" />
432496
--- src/event.c
+++ src/event.c
@@ -15,78 +15,90 @@
15 **
16 *******************************************************************************
17 **
18 ** This file contains code to do formatting of event messages:
19 **
 
20 ** Milestones
21 ** Blog posts
22 ** New articles
23 ** Process checkpoints
24 ** Announcements
 
 
 
 
 
 
 
25 */
26 #include "config.h"
27 #include <assert.h>
28 #include <ctype.h>
29 #include "event.h"
30
31 /*
32 ** Output a hyperlink to an event given its tagid.
33 */
34 void hyperlink_to_event_tagid(int tagid){
35 char *zEventId;
36 zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
37 tagid);
38 @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>]
39 free(zEventId);
40 }
41
42 /*
 
43 ** WEBPAGE: event
44 ** URL: /event
 
 
45 ** PARAMETERS:
46 **
47 ** name=EVENTID // Identify the event to display EVENTID must be complete
48 ** aid=ARTIFACTID // Which specific version of the event. Optional.
49 ** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional.
50 **
51 ** Display an existing event identified by EVENTID
52 */
53 void event_page(void){
54 int rid = 0; /* rid of the event artifact */
55 char *zUuid; /* UUID corresponding to rid */
56 const char *zEventId; /* Event identifier */
57 const char *zVerbose; /* Value of verbose option */
58 char *zETime; /* Time of the event */
59 char *zATime; /* Time the artifact was created */
60 int specRid; /* rid specified by aid= parameter */
61 int prevRid, nextRid; /* Previous or next edits of this event */
62 Manifest *pEvent; /* Parsed event artifact */
63 Blob fullbody; /* Complete content of the event body */
64 Blob title; /* Title extracted from the event body */
65 Blob tail; /* Event body that comes after the title */
66 Stmt q1; /* Query to search for the event */
67 int verboseFlag; /* True to show details */
 
68
69
70 /* wiki-read privilege is needed in order to read events.
71 */
72 login_check_credentials();
73 if( !g.perm.RdWiki ){
74 login_needed();
75 return;
76 }
77
78 zEventId = P("name");
79 if( zEventId==0 ){ fossil_redirect_home(); return; }
80 zUuid = (char*)P("aid");
81 specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0;
82 rid = nextRid = prevRid = 0;
83 db_prepare(&q1,
84 "SELECT rid FROM tagxref"
85 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')"
86 " ORDER BY mtime DESC",
87 zEventId
88 );
89 while( db_step(&q1)==SQLITE_ROW ){
90 nextRid = rid;
91 rid = db_column_int(&q1, 0);
92 if( specRid==0 || specRid==rid ){
@@ -96,12 +108,12 @@
96 break;
97 }
98 }
99 db_finalize(&q1);
100 if( rid==0 || (specRid!=0 && specRid!=rid) ){
101 style_header("No Such Event");
102 @ Cannot locate specified event
103 style_footer();
104 return;
105 }
106 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
107 zVerbose = P("v");
@@ -113,156 +125,194 @@
113 }
114 verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
115
116 /* Extract the event content.
117 */
118 pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
119 if( pEvent==0 ){
120 fossil_fatal("Object #%d is not an event", rid);
121 }
122 blob_init(&fullbody, pEvent->zWiki, -1);
123 if( wiki_find_title(&fullbody, &title, &tail) ){
124 style_header("%s", blob_str(&title));
 
 
 
 
 
 
 
 
 
 
 
125 }else{
126 style_header("Event %S", zEventId);
127 tail = fullbody;
128 }
 
129 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
130 style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
131 g.zTop, zEventId);
132 }
133 zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
134 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId);
135 if( g.perm.Hyperlink ){
136 if( verboseFlag ){
137 style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s",
138 zEventId, zUuid);
 
139 if( nextRid ){
140 char *zNext;
141 zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
142 style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v",
143 zEventId, zNext);
144 free(zNext);
145 }
146 if( prevRid ){
147 char *zPrev;
148 zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
149 style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v",
150 zEventId, zPrev);
151 free(zPrev);
152 }
153 }else{
154 style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v",
155 zEventId, zUuid);
156 }
157 }
158
159 if( verboseFlag && g.perm.Hyperlink ){
160 int i;
161 const char *zClr = 0;
162 Blob comment;
163
164 zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
165 @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at
166 @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
167 @ entered by user <b>%h(pEvent->zUser)</b> on
168 @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
169 @ <blockquote>
170 for(i=0; i<pEvent->nTag; i++){
171 if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
172 zClr = pEvent->aTag[i].zValue;
173 }
174 }
175 if( zClr && zClr[0]==0 ) zClr = 0;
176 if( zClr ){
177 @ <div style="background-color: %h(zClr);">
178 }else{
179 @ <div>
180 }
181 blob_init(&comment, pEvent->zComment, -1);
182 wiki_convert(&comment, 0, WIKI_INLINE);
183 blob_reset(&comment);
184 @ </div>
185 @ </blockquote><hr />
186 }
187
188 wiki_convert(&tail, 0, 0);
 
 
 
 
 
 
 
 
189 style_footer();
190 manifest_destroy(pEvent);
191 }
192
193 /*
 
194 ** WEBPAGE: eventedit
195 ** URL: /eventedit?name=EVENTID
 
 
 
196 **
197 ** Edit an event. If name is omitted, create a new event.
 
198 */
199 void eventedit_page(void){
200 char *zTag;
201 int rid = 0;
202 Blob event;
203 const char *zEventId;
204 int n;
205 const char *z;
206 char *zBody = (char*)P("w");
207 char *zETime = (char*)P("t");
208 const char *zComment = P("c");
209 const char *zTags = P("g");
210 const char *zClr;
 
 
211
212 if( zBody ){
213 zBody = mprintf("%s", zBody);
214 }
215 login_check_credentials();
216 zEventId = P("name");
217 if( zEventId==0 ){
218 zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))");
 
219 }else{
220 int nEventId = strlen(zEventId);
221 if( nEventId!=40 || !validate16(zEventId, 40) ){
222 fossil_redirect_home();
223 return;
224 }
225 }
226 zTag = mprintf("event-%s", zEventId);
227 rid = db_int(0,
228 "SELECT rid FROM tagxref"
229 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
230 " ORDER BY mtime DESC", zTag
231 );
 
 
 
 
 
 
232 free(zTag);
233
234 /* Need both check-in and wiki-write or wiki-create privileges in order
235 ** to edit/create an event.
236 */
237 if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
238 login_needed();
239 return;
240 }
241
242 /* Figure out the color */
243 if( rid ){
244 zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
245 }else{
246 zClr = "";
 
247 }
248 zClr = PD("clr",zClr);
249 if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
250
251
252 /* If editing an existing event, extract the key fields to use as
253 ** a starting point for the edit.
254 */
255 if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
256 Manifest *pEvent;
257 pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
258 if( pEvent && pEvent->type==CFTYPE_EVENT ){
259 if( zBody==0 ) zBody = pEvent->zWiki;
 
 
260 if( zETime==0 ){
261 zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
262 }
263 if( zComment==0 ) zComment = pEvent->zComment;
 
264 }
265 if( zTags==0 ){
266 zTags = db_text(0,
267 "SELECT group_concat(substr(tagname,5),', ')"
268 " FROM tagxref, tag"
@@ -276,11 +326,11 @@
276 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
277 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
278 char *zDate;
279 Blob cksum;
280 int nrid, n;
281 blob_zero(&event);
282 db_begin_transaction();
283 login_verify_csrf_secret();
284 while( fossil_isspace(zComment[0]) ) zComment++;
285 n = strlen(zComment);
286 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -289,17 +339,20 @@
289 }
290 zDate = date_in_standard_format("now");
291 blob_appendf(&event, "D %s\n", zDate);
292 free(zDate);
293 zETime[10] = 'T';
294 blob_appendf(&event, "E %s %s\n", zETime, zEventId);
295 zETime[10] = ' ';
296 if( rid ){
297 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
298 blob_appendf(&event, "P %s\n", zUuid);
299 free(zUuid);
300 }
 
 
 
301 if( zClr && zClr[0] ){
302 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
303 }
304 if( zTags && zTags[0] ){
305 Blob tags, one;
@@ -346,26 +399,37 @@
346 md5sum_blob(&event, &cksum);
347 blob_appendf(&event, "Z %b\n", &cksum);
348 blob_reset(&cksum);
349 nrid = content_put(&event);
350 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
351 manifest_crosslink(nrid, &event, MC_NONE);
 
 
 
 
 
 
 
352 assert( blob_is_reset(&event) );
353 content_deltify(rid, nrid, 0);
354 db_end_transaction(0);
355 cgi_redirectf("event?name=%T", zEventId);
356 }
357 if( P("cancel")!=0 ){
358 cgi_redirectf("event?name=%T", zEventId);
359 return;
360 }
361 if( zBody==0 ){
362 zBody = mprintf("<i>Event Text</i>");
363 }
364 style_header("Edit Event %S", zEventId);
 
 
 
 
365 if( P("preview")!=0 ){
366 Blob title, tail, com;
367 @ <p><b>Timeline comment preview:</b></p>
368 @ <blockquote>
369 @ <table border="0">
370 if( zClr && zClr[0] ){
371 @ <tr><td style="background-color: %h(zClr);">
@@ -377,55 +441,55 @@
377 wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
378 @ </td></tr></table>
379 @ </blockquote>
380 @ <p><b>Page content preview:</b><p>
381 @ <blockquote>
382 blob_zero(&event);
383 blob_append(&event, zBody, -1);
384 if( wiki_find_title(&event, &title, &tail) ){
385 @ <h2 align="center">%h(blob_str(&title))</h2>
386 wiki_convert(&tail, 0, 0);
387 }else{
388 wiki_convert(&event, 0, 0);
389 }
390 @ </blockquote><hr />
391 blob_reset(&event);
392 }
393 for(n=2, z=zBody; z[0]; z++){
394 if( z[0]=='\n' ) n++;
395 }
396 if( n<20 ) n = 20;
397 if( n>40 ) n = 40;
398 @ <form method="post" action="%s(g.zTop)/eventedit"><div>
399 login_insert_csrf_secret();
400 @ <input type="hidden" name="name" value="%h(zEventId)" />
401 @ <table border="0" cellspacing="10">
402
403 @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
404 @ <td valign="top">
405 @ <input type="text" name="t" size="25" value="%h(zETime)" />
406 @ </td></tr>
407
408 @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
409 @ <td valign="top">
410 @ <textarea name="c" class="eventedit" cols="80"
411 @ rows="3" wrap="virtual">%h(zComment)</textarea>
412 @ </td></tr>
413
414 @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
415 @ <td valign="top">
416 render_color_chooser(0, zClr, 0, "clr", "cclr");
417 @ </td></tr>
418
419 @ <tr><th align="right" valign="top">Tags:</th>
420 @ <td valign="top">
421 @ <input type="text" name="g" size="40" value="%h(zTags)" />
422 @ </td></tr>
 
 
 
 
 
423
424 @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
425 @ <td valign="top">
426 @ <textarea name="w" class="eventedit" cols="80"
427 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
428 @ </td></tr>
429
430 @ <tr><td colspan="2">
431 @ <input type="submit" name="preview" value="Preview Your Changes" />
432
--- src/event.c
+++ src/event.c
@@ -15,78 +15,90 @@
15 **
16 *******************************************************************************
17 **
18 ** This file contains code to do formatting of event messages:
19 **
20 ** Technical Notes
21 ** Milestones
22 ** Blog posts
23 ** New articles
24 ** Process checkpoints
25 ** Announcements
26 **
27 ** Do not confuse "event" artifacts with the "event" table in the
28 ** repository database. An "event" artifact is a technical-note: a
29 ** wiki- or blog-like essay that appears on the timeline. The "event"
30 ** table records all entries on the timeline, including tech-notes.
31 **
32 ** (2015-02-14): Changing the name to "tech-note" most everywhere.
33 */
34 #include "config.h"
35 #include <assert.h>
36 #include <ctype.h>
37 #include "event.h"
38
39 /*
40 ** Output a hyperlink to an technote given its tagid.
41 */
42 void hyperlink_to_event_tagid(int tagid){
43 char *zId;
44 zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
45 tagid);
46 @ [%z(href("%R/technote/%s",zId))%S(zId)</a>]
47 free(zId);
48 }
49
50 /*
51 ** WEBPAGE: technote
52 ** WEBPAGE: event
53 **
54 ** Display a "technical note" or "tech-note" (formerly called an "event").
55 **
56 ** PARAMETERS:
57 **
58 ** name=ID // Identify the tech-note to display. ID must be complete
59 ** aid=ARTIFACTID // Which specific version of the tech-note. Optional.
60 ** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional.
61 **
62 ** Display an existing event identified by EVENTID
63 */
64 void event_page(void){
65 int rid = 0; /* rid of the event artifact */
66 char *zUuid; /* UUID corresponding to rid */
67 const char *zId; /* Event identifier */
68 const char *zVerbose; /* Value of verbose option */
69 char *zETime; /* Time of the tech-note */
70 char *zATime; /* Time the artifact was created */
71 int specRid; /* rid specified by aid= parameter */
72 int prevRid, nextRid; /* Previous or next edits of this tech-note */
73 Manifest *pTNote; /* Parsed technote artifact */
74 Blob fullbody; /* Complete content of the technote body */
75 Blob title; /* Title extracted from the technote body */
76 Blob tail; /* Event body that comes after the title */
77 Stmt q1; /* Query to search for the technote */
78 int verboseFlag; /* True to show details */
79 const char *zMimetype = 0; /* Mimetype of the document */
80
81
82 /* wiki-read privilege is needed in order to read tech-notes.
83 */
84 login_check_credentials();
85 if( !g.perm.RdWiki ){
86 login_needed(g.anon.RdWiki);
87 return;
88 }
89
90 zId = P("name");
91 if( zId==0 ){ fossil_redirect_home(); return; }
92 zUuid = (char*)P("aid");
93 specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0;
94 rid = nextRid = prevRid = 0;
95 db_prepare(&q1,
96 "SELECT rid FROM tagxref"
97 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')"
98 " ORDER BY mtime DESC",
99 zId
100 );
101 while( db_step(&q1)==SQLITE_ROW ){
102 nextRid = rid;
103 rid = db_column_int(&q1, 0);
104 if( specRid==0 || specRid==rid ){
@@ -96,12 +108,12 @@
108 break;
109 }
110 }
111 db_finalize(&q1);
112 if( rid==0 || (specRid!=0 && specRid!=rid) ){
113 style_header("No Such Tech-Note");
114 @ Cannot locate a technical note called <b>%h(zId)</b>.
115 style_footer();
116 return;
117 }
118 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
119 zVerbose = P("v");
@@ -113,156 +125,194 @@
125 }
126 verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
127
128 /* Extract the event content.
129 */
130 pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
131 if( pTNote==0 ){
132 fossil_fatal("Object #%d is not a tech-note", rid);
133 }
134 zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype));
135 blob_init(&fullbody, pTNote->zWiki, -1);
136 blob_init(&title, 0, 0);
137 blob_init(&tail, 0, 0);
138 if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
139 if( !wiki_find_title(&fullbody, &title, &tail) ){
140 blob_appendf(&title, "Tech-note %S", zId);
141 tail = fullbody;
142 }
143 }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
144 markdown_to_html(&fullbody, &title, &tail);
145 if( blob_size(&title)==0 ){
146 blob_appendf(&title, "Tech-note %S", zId);
147 }
148 }else{
149 blob_appendf(&title, "Tech-note %S", zId);
150 tail = fullbody;
151 }
152 style_header("%s", blob_str(&title));
153 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
154 style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
 
155 }
156 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
158 if( g.perm.Hyperlink ){
159 if( verboseFlag ){
160 style_submenu_element("Plain", 0,
161 "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
162 zId, zUuid);
163 if( nextRid ){
164 char *zNext;
165 zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
166 style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v",
167 zId, zNext);
168 free(zNext);
169 }
170 if( prevRid ){
171 char *zPrev;
172 zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
173 style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v",
174 zId, zPrev);
175 free(zPrev);
176 }
177 }else{
178 style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v",
179 zId, zUuid);
180 }
181 }
182
183 if( verboseFlag && g.perm.Hyperlink ){
184 int i;
185 const char *zClr = 0;
186 Blob comment;
187
188 zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate);
189 @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at
190 @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
191 @ entered by user <b>%h(pTNote->zUser)</b> on
192 @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
193 @ <blockquote>
194 for(i=0; i<pTNote->nTag; i++){
195 if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){
196 zClr = pTNote->aTag[i].zValue;
197 }
198 }
199 if( zClr && zClr[0]==0 ) zClr = 0;
200 if( zClr ){
201 @ <div style="background-color: %h(zClr);">
202 }else{
203 @ <div>
204 }
205 blob_init(&comment, pTNote->zComment, -1);
206 wiki_convert(&comment, 0, WIKI_INLINE);
207 blob_reset(&comment);
208 @ </div>
209 @ </blockquote><hr />
210 }
211
212 if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
213 wiki_convert(&fullbody, 0, 0);
214 }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
215 cgi_append_content(blob_buffer(&tail), blob_size(&tail));
216 }else{
217 @ <pre>
218 @ %h(blob_str(&fullbody))
219 @ </pre>
220 }
221 style_footer();
222 manifest_destroy(pTNote);
223 }
224
225 /*
226 ** WEBPAGE: technoteedit
227 ** WEBPAGE: eventedit
228 **
229 ** Revise or create a technical note (formerly called an 'event').
230 **
231 ** Parameters:
232 **
233 ** name=ID Hex hash ID of the tech-note. If omitted, a new
234 ** tech-note is created.
235 */
236 void eventedit_page(void){
237 char *zTag;
238 int rid = 0;
239 Blob event;
240 const char *zId;
241 int n;
242 const char *z;
243 char *zBody = (char*)P("w");
244 char *zETime = (char*)P("t");
245 const char *zComment = P("c");
246 const char *zTags = P("g");
247 const char *zClr;
248 const char *zMimetype = P("mimetype");
249 int isNew = 0;
250
251 if( zBody ){
252 zBody = mprintf("%s", zBody);
253 }
254 login_check_credentials();
255 zId = P("name");
256 if( zId==0 ){
257 zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
258 isNew = 1;
259 }else{
260 int nId = strlen(zId);
261 if( !validate16(zId, nId) ){
262 fossil_redirect_home();
263 return;
264 }
265 }
266 zTag = mprintf("event-%s", zId);
267 rid = db_int(0,
268 "SELECT rid FROM tagxref"
269 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')"
270 " ORDER BY mtime DESC", zTag
271 );
272 if( rid && strlen(zId)<40 ){
273 zId = db_text(0,
274 "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB '%q*'",
275 zTag
276 );
277 }
278 free(zTag);
279
280 /* Need both check-in and wiki-write or wiki-create privileges in order
281 ** to edit/create an event.
282 */
283 if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
284 login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
285 return;
286 }
287
288 /* Figure out the color */
289 if( rid ){
290 zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
291 }else{
292 zClr = "";
293 isNew = 1;
294 }
295 zClr = PD("clr",zClr);
296 if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
297
298
299 /* If editing an existing event, extract the key fields to use as
300 ** a starting point for the edit.
301 */
302 if( rid
303 && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0)
304 ){
305 Manifest *pTNote;
306 pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
307 if( pTNote && pTNote->type==CFTYPE_EVENT ){
308 if( zBody==0 ) zBody = pTNote->zWiki;
309 if( zETime==0 ){
310 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
311 }
312 if( zComment==0 ) zComment = pTNote->zComment;
313 if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
314 }
315 if( zTags==0 ){
316 zTags = db_text(0,
317 "SELECT group_concat(substr(tagname,5),', ')"
318 " FROM tagxref, tag"
@@ -276,11 +326,11 @@
326 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
327 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
328 char *zDate;
329 Blob cksum;
330 int nrid, n;
331 blob_init(&event, 0, 0);
332 db_begin_transaction();
333 login_verify_csrf_secret();
334 while( fossil_isspace(zComment[0]) ) zComment++;
335 n = strlen(zComment);
336 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -289,17 +339,20 @@
339 }
340 zDate = date_in_standard_format("now");
341 blob_appendf(&event, "D %s\n", zDate);
342 free(zDate);
343 zETime[10] = 'T';
344 blob_appendf(&event, "E %s %s\n", zETime, zId);
345 zETime[10] = ' ';
346 if( rid ){
347 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
348 blob_appendf(&event, "P %s\n", zUuid);
349 free(zUuid);
350 }
351 if( zMimetype && zMimetype[0] ){
352 blob_appendf(&event, "N %s\n", zMimetype);
353 }
354 if( zClr && zClr[0] ){
355 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
356 }
357 if( zTags && zTags[0] ){
358 Blob tags, one;
@@ -346,26 +399,37 @@
399 md5sum_blob(&event, &cksum);
400 blob_appendf(&event, "Z %b\n", &cksum);
401 blob_reset(&cksum);
402 nrid = content_put(&event);
403 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
404 if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405 db_end_transaction(1);
406 style_header("Error");
407 @ Internal error: Fossil tried to make an invalid artifact for
408 @ the edited technode.
409 style_footer();
410 return;
411 }
412 assert( blob_is_reset(&event) );
413 content_deltify(rid, nrid, 0);
414 db_end_transaction(0);
415 cgi_redirectf("technote?name=%T", zId);
416 }
417 if( P("cancel")!=0 ){
418 cgi_redirectf("technote?name=%T", zId);
419 return;
420 }
421 if( zBody==0 ){
422 zBody = mprintf("Insert new content here...");
423 }
424 if( isNew ){
425 style_header("New Tech-note %S", zId);
426 }else{
427 style_header("Edit Tech-note %S", zId);
428 }
429 if( P("preview")!=0 ){
430 Blob com;
431 @ <p><b>Timeline comment preview:</b></p>
432 @ <blockquote>
433 @ <table border="0">
434 if( zClr && zClr[0] ){
435 @ <tr><td style="background-color: %h(zClr);">
@@ -377,55 +441,55 @@
441 wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
442 @ </td></tr></table>
443 @ </blockquote>
444 @ <p><b>Page content preview:</b><p>
445 @ <blockquote>
446 blob_init(&event, 0, 0);
447 blob_append(&event, zBody, -1);
448 wiki_render_by_mimetype(&event, zMimetype);
 
 
 
 
 
449 @ </blockquote><hr />
450 blob_reset(&event);
451 }
452 for(n=2, z=zBody; z[0]; z++){
453 if( z[0]=='\n' ) n++;
454 }
455 if( n<20 ) n = 20;
456 if( n>40 ) n = 40;
457 @ <form method="post" action="%R/technoteedit"><div>
458 login_insert_csrf_secret();
459 @ <input type="hidden" name="name" value="%h(zId)" />
460 @ <table border="0" cellspacing="10">
461
462 @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
463 @ <td valign="top">
464 @ <input type="text" name="t" size="25" value="%h(zETime)" />
465 @ </td></tr>
466
467 @ <tr><th align="right" valign="top">Timeline Comment:</th>
468 @ <td valign="top">
469 @ <textarea name="c" class="technoteedit" cols="80"
470 @ rows="3" wrap="virtual">%h(zComment)</textarea>
471 @ </td></tr>
472
473 @ <tr><th align="right" valign="top">Timeline Background Color:</th>
474 @ <td valign="top">
475 render_color_chooser(0, zClr, 0, "clr", "cclr");
476 @ </td></tr>
477
478 @ <tr><th align="right" valign="top">Tags:</th>
479 @ <td valign="top">
480 @ <input type="text" name="g" size="40" value="%h(zTags)" />
481 @ </td></tr>
482
483 @ <tr><th align="right" valign="top">Markup Style:</th>
484 @ <td valign="top">
485 mimetype_option_menu(zMimetype);
486 @ </td></tr>
487
488 @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
489 @ <td valign="top">
490 @ <textarea name="w" class="technoteedit" cols="80"
491 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
492 @ </td></tr>
493
494 @ <tr><td colspan="2">
495 @ <input type="submit" name="preview" value="Preview Your Changes" />
496
+9 -7
--- src/finfo.c
+++ src/finfo.c
@@ -215,11 +215,11 @@
215215
zCiUuid, zCom, zUser, zFileUuid, zBr);
216216
comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags);
217217
fossil_free(zOut);
218218
}else{
219219
blob_reset(&line);
220
- blob_appendf(&line, "%.10s ", zCiUuid);
220
+ blob_appendf(&line, "%S ", zCiUuid);
221221
blob_appendf(&line, "%.10s ", zDate);
222222
blob_appendf(&line, "%8.8s ", zUser);
223223
blob_appendf(&line, "%8.8s ", zBr);
224224
blob_appendf(&line,"%-39.39s", zCom );
225225
comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags);
@@ -305,11 +305,11 @@
305305
int uBg = P("ubg")!=0;
306306
int fDebug = atoi(PD("debug","0"));
307307
int fShowId = P("showid")!=0;
308308
309309
login_check_credentials();
310
- if( !g.perm.Read ){ login_needed(); return; }
310
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
311311
style_header("File History");
312312
login_anonymous_available();
313313
url_initialize(&url, "finfo");
314314
if( brBg ) url_add_parameter(&url, "brbg", 0);
315315
if( uBg ) url_add_parameter(&url, "ubg", 0);
@@ -383,11 +383,11 @@
383383
}
384384
blob_reset(&sql);
385385
blob_zero(&title);
386386
if( baseCheckin ){
387387
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
388
- char *zLink = href("%R/info/%s", zUuid);
388
+ char *zLink = href("%R/info/%!S", zUuid);
389389
blob_appendf(&title, "Ancestors of file ");
390390
hyperlinked_path(zFilename, &title, zUuid, "tree", "");
391391
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
392392
blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
393393
if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
@@ -469,11 +469,11 @@
469469
char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
470470
pfnid);
471471
@ <b>Renamed</b> from
472472
@ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
473473
}
474
- @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a>
474
+ @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a>
475475
if( fShowId ){
476476
@ (%d(frid))
477477
}
478478
@ part of check-in
479479
}else{
@@ -503,25 +503,27 @@
503503
const char *z = zFilename;
504504
@ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
505505
@ [annotate]</a>
506506
@ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
507507
@ [blame]</a>
508
- @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins&nbsp;using]</a>
508
+ @ %z(href("%R/timeline?n=200&uf=%!S",zUuid))[checkins&nbsp;using]</a>
509509
if( fpid ){
510
- @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
510
+ @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a>
511511
}
512512
}
513513
if( fDebug & FINFO_DEBUG_MLINK ){
514514
int ii;
515
+ char *zAncLink;
515516
@ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
516517
if( nParent>0 ){
517518
@ parents=%d(aParent[0])
518519
for(ii=1; ii<nParent; ii++){
519520
@ %d(aParent[ii])
520521
}
521522
}
522
- @ %z(href("%R/finfo?name=%T&ci=%s&debug=1",zFilename,zCkin))[ancestry]</a>
523
+ zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin);
524
+ @ %z(zAncLink)[ancestry]</a>
523525
}
524526
tag_private_status(frid);
525527
@ </td></tr>
526528
}
527529
db_finalize(&q);
528530
--- src/finfo.c
+++ src/finfo.c
@@ -215,11 +215,11 @@
215 zCiUuid, zCom, zUser, zFileUuid, zBr);
216 comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags);
217 fossil_free(zOut);
218 }else{
219 blob_reset(&line);
220 blob_appendf(&line, "%.10s ", zCiUuid);
221 blob_appendf(&line, "%.10s ", zDate);
222 blob_appendf(&line, "%8.8s ", zUser);
223 blob_appendf(&line, "%8.8s ", zBr);
224 blob_appendf(&line,"%-39.39s", zCom );
225 comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags);
@@ -305,11 +305,11 @@
305 int uBg = P("ubg")!=0;
306 int fDebug = atoi(PD("debug","0"));
307 int fShowId = P("showid")!=0;
308
309 login_check_credentials();
310 if( !g.perm.Read ){ login_needed(); return; }
311 style_header("File History");
312 login_anonymous_available();
313 url_initialize(&url, "finfo");
314 if( brBg ) url_add_parameter(&url, "brbg", 0);
315 if( uBg ) url_add_parameter(&url, "ubg", 0);
@@ -383,11 +383,11 @@
383 }
384 blob_reset(&sql);
385 blob_zero(&title);
386 if( baseCheckin ){
387 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
388 char *zLink = href("%R/info/%s", zUuid);
389 blob_appendf(&title, "Ancestors of file ");
390 hyperlinked_path(zFilename, &title, zUuid, "tree", "");
391 if( fShowId ) blob_appendf(&title, " (%d)", fnid);
392 blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
393 if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
@@ -469,11 +469,11 @@
469 char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
470 pfnid);
471 @ <b>Renamed</b> from
472 @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
473 }
474 @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a>
475 if( fShowId ){
476 @ (%d(frid))
477 }
478 @ part of check-in
479 }else{
@@ -503,25 +503,27 @@
503 const char *z = zFilename;
504 @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
505 @ [annotate]</a>
506 @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
507 @ [blame]</a>
508 @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins&nbsp;using]</a>
509 if( fpid ){
510 @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
511 }
512 }
513 if( fDebug & FINFO_DEBUG_MLINK ){
514 int ii;
 
515 @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
516 if( nParent>0 ){
517 @ parents=%d(aParent[0])
518 for(ii=1; ii<nParent; ii++){
519 @ %d(aParent[ii])
520 }
521 }
522 @ %z(href("%R/finfo?name=%T&ci=%s&debug=1",zFilename,zCkin))[ancestry]</a>
 
523 }
524 tag_private_status(frid);
525 @ </td></tr>
526 }
527 db_finalize(&q);
528
--- src/finfo.c
+++ src/finfo.c
@@ -215,11 +215,11 @@
215 zCiUuid, zCom, zUser, zFileUuid, zBr);
216 comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags);
217 fossil_free(zOut);
218 }else{
219 blob_reset(&line);
220 blob_appendf(&line, "%S ", zCiUuid);
221 blob_appendf(&line, "%.10s ", zDate);
222 blob_appendf(&line, "%8.8s ", zUser);
223 blob_appendf(&line, "%8.8s ", zBr);
224 blob_appendf(&line,"%-39.39s", zCom );
225 comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags);
@@ -305,11 +305,11 @@
305 int uBg = P("ubg")!=0;
306 int fDebug = atoi(PD("debug","0"));
307 int fShowId = P("showid")!=0;
308
309 login_check_credentials();
310 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
311 style_header("File History");
312 login_anonymous_available();
313 url_initialize(&url, "finfo");
314 if( brBg ) url_add_parameter(&url, "brbg", 0);
315 if( uBg ) url_add_parameter(&url, "ubg", 0);
@@ -383,11 +383,11 @@
383 }
384 blob_reset(&sql);
385 blob_zero(&title);
386 if( baseCheckin ){
387 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
388 char *zLink = href("%R/info/%!S", zUuid);
389 blob_appendf(&title, "Ancestors of file ");
390 hyperlinked_path(zFilename, &title, zUuid, "tree", "");
391 if( fShowId ) blob_appendf(&title, " (%d)", fnid);
392 blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
393 if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
@@ -469,11 +469,11 @@
469 char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
470 pfnid);
471 @ <b>Renamed</b> from
472 @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
473 }
474 @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a>
475 if( fShowId ){
476 @ (%d(frid))
477 }
478 @ part of check-in
479 }else{
@@ -503,25 +503,27 @@
503 const char *z = zFilename;
504 @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
505 @ [annotate]</a>
506 @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
507 @ [blame]</a>
508 @ %z(href("%R/timeline?n=200&uf=%!S",zUuid))[checkins&nbsp;using]</a>
509 if( fpid ){
510 @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a>
511 }
512 }
513 if( fDebug & FINFO_DEBUG_MLINK ){
514 int ii;
515 char *zAncLink;
516 @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
517 if( nParent>0 ){
518 @ parents=%d(aParent[0])
519 for(ii=1; ii<nParent; ii++){
520 @ %d(aParent[ii])
521 }
522 }
523 zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin);
524 @ %z(zAncLink)[ancestry]</a>
525 }
526 tag_private_status(frid);
527 @ </td></tr>
528 }
529 db_finalize(&q);
530
+51 -49
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403403
}
404404
}else{
405405
if( zOld && zNew ){
406406
if( fossil_strcmp(zOld, zNew)!=0 ){
407407
@ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408
- @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
409
- @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>.
408
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
409
+ @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
410410
}else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411411
@ <p>Name change
412412
@ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413413
@ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414414
}else{
415415
@ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416416
@ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417417
}
418418
}else if( zOld ){
419
- @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
420
- @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
419
+ @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
420
+ @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
421421
}else{
422422
@ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423
- @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
423
+ @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
424424
}
425425
if( diffFlags ){
426426
append_diff(zOld, zNew, diffFlags, pRe);
427427
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428428
@ &nbsp;&nbsp;
429
- @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
429
+ @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a>
430430
}
431431
}
432432
}
433433
434434
/*
@@ -527,11 +527,11 @@
527527
const char *zW; /* URL param for ignoring whitespace */
528528
const char *zPage = "vinfo"; /* Page that shows diffs */
529529
const char *zPageHide = "ci"; /* Page that hides diffs */
530530
531531
login_check_credentials();
532
- if( !g.perm.Read ){ login_needed(); return; }
532
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
533533
zName = P("name");
534534
rid = name_to_rid_www("name");
535535
if( rid==0 ){
536536
style_header("Check-in Information Error");
537537
@ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635635
if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636636
zPJ[jj] = '_';
637637
}
638638
}
639639
@ <tr><th>Timelines:</th><td>
640
- @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
640
+ @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
641641
if( zParent ){
642
- @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
642
+ @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
643643
}
644644
if( !isLeaf ){
645
- @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
645
+ @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
646646
}
647647
if( zParent && !isLeaf ){
648
- @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
648
+ @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
649649
}
650650
db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651651
" WHERE rid=%d AND tagtype>0 "
652652
" AND tag.tagid=tagxref.tagid "
653653
" AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657657
}
658658
db_finalize(&q2);
659659
660660
661661
/* The Download: line */
662
- if( g.perm.Zip ){
662
+ if( g.anon.Zip ){
663663
char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664664
zPJ, zUuid, zUuid);
665665
@ </td></tr>
666666
@ <tr><th>Downloads:</th><td>
667667
@ %z(href("%s",zUrl))Tarball</a>
668
- @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
668
+ @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid))
669669
@ ZIP archive</a>
670670
fossil_free(zUrl);
671671
}
672672
@ </td></tr>
673673
@ <tr><th>Other&nbsp;Links:</th>
674674
@ <td>
675
- @ %z(href("%R/tree?ci=%S",zUuid))files</a>
676
- @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
677
- @ | %z(href("%R/tree?nofiles&type=tree&ci=%S",zUuid))folders</a>
678
- @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
679
- @ | %z(href("%R/vdiff?from=pbranch:%S&to=%S",zUuid,zUuid))
675
+ @ %z(href("%R/tree?ci=%!S",zUuid))files</a>
676
+ @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
677
+ @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
678
+ @ | %z(href("%R/artifact/%!S",zUuid))manifest</a>
679
+ @ | %z(href("%R/vdiff?from=pbranch:%!S&to=%!S",zUuid,zUuid))
680680
@ branch diff</a>
681
- if( g.perm.Write ){
682
- @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
681
+ if( g.anon.Write ){
682
+ @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
683683
}
684684
@ </td>
685685
@ </tr>
686686
blob_reset(&projName);
687687
}
@@ -725,11 +725,11 @@
725725
@ Show&nbsp;Unified&nbsp;Diffs</a>
726726
@ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727727
@ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728728
}
729729
if( zParent ){
730
- @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
730
+ @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
731731
@ Patch</a>
732732
}
733733
@</div>
734734
if( pRe ){
735735
@ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775775
Blob wiki;
776776
int modPending;
777777
const char *zModAction;
778778
779779
login_check_credentials();
780
- if( !g.perm.RdWiki ){ login_needed(); return; }
780
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
781781
rid = name_to_rid_www("name");
782782
if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783783
style_header("Wiki Page Information Error");
784784
@ No such object: %h(P("name"))
785785
style_footer();
@@ -815,11 +815,11 @@
815815
pWiki->zWikiTitle);
816816
login_anonymous_available();
817817
@ <div class="section">Overview</div>
818818
@ <p><table class="label-value">
819819
@ <tr><th>Artifact&nbsp;ID:</th>
820
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
820
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
821821
if( g.perm.Setup ){
822822
@ (%d(rid))
823823
}
824824
modPending = moderation_pending(rid);
825825
if( modPending ){
@@ -829,16 +829,19 @@
829829
@ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830830
@ <tr><th>Date:</th><td>
831831
hyperlink_to_date(zDate, "</td></tr>");
832832
@ <tr><th>Original&nbsp;User:</th><td>
833833
hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
834
+ if( pWiki->zMimetype ){
835
+ @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr>
836
+ }
834837
if( pWiki->nParent>0 ){
835838
int i;
836839
@ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
837840
for(i=0; i<pWiki->nParent; i++){
838841
char *zParent = pWiki->azParent[i];
839
- @ %z(href("info/%s",zParent))%s(zParent)</a>
842
+ @ %z(href("info/%!S",zParent))%s(zParent)</a>
840843
}
841844
@ </td></tr>
842845
}
843846
@ </table>
844847
@@ -856,11 +859,11 @@
856859
}
857860
858861
859862
@ <div class="section">Content</div>
860863
blob_init(&wiki, pWiki->zWiki, -1);
861
- wiki_convert(&wiki, 0, 0);
864
+ wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
862865
blob_reset(&wiki);
863866
manifest_destroy(pWiki);
864867
style_footer();
865868
}
866869
@@ -993,11 +996,11 @@
993996
const char *zW;
994997
const char *zVerbose;
995998
const char *zGlob;
996999
ReCompiled *pRe = 0;
9971000
login_check_credentials();
998
- if( !g.perm.Read ){ login_needed(); return; }
1001
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
9991002
login_anonymous_available();
10001003
zRe = P("regex");
10011004
if( zRe ) re_compile(&pRe, zRe, 0);
10021005
zBranch = P("branch");
10031006
if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
12081211
int mPerm = db_column_int(&q, 5);
12091212
const char *zBr = db_column_text(&q, 6);
12101213
int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
12111214
if( sameFilename && !showDetail ){
12121215
if( cnt==1 ){
1213
- @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1216
+ @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
12141217
}
12151218
cnt++;
12161219
continue;
12171220
}
12181221
if( !sameFilename ){
@@ -1251,14 +1254,14 @@
12511254
@ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
12521255
}
12531256
@ &mdash; %!w(zCom) (user:
12541257
hyperlink_to_user(zUser,zDate,")");
12551258
if( g.perm.Hyperlink ){
1256
- @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1257
- @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1259
+ @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1260
+ @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
12581261
@ [annotate]</a>
1259
- @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1262
+ @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
12601263
@ [blame]</a>
12611264
}
12621265
cnt++;
12631266
if( pDownloadName && blob_size(pDownloadName)==0 ){
12641267
blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
13381341
}
13391342
@ - %!w(zCom) by
13401343
hyperlink_to_user(zUser,zDate," on");
13411344
hyperlink_to_date(zDate, ".");
13421345
if( pDownloadName && blob_size(pDownloadName)==0 ){
1343
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1346
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13441347
}
13451348
tag_private_status(rid);
13461349
cnt++;
13471350
}
13481351
db_finalize(&q);
@@ -1365,17 +1368,17 @@
13651368
}else{
13661369
@ Attachment "%h(zFilename)" to
13671370
}
13681371
objType |= OBJTYPE_ATTACHMENT;
13691372
if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1370
- if( g.perm.Hyperlink && g.perm.RdTkt ){
1371
- @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1373
+ if( g.perm.Hyperlink && g.anon.RdTkt ){
1374
+ @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
13721375
}else{
13731376
@ ticket [%S(zTarget)]
13741377
}
13751378
}else{
1376
- if( g.perm.Hyperlink && g.perm.RdWiki ){
1379
+ if( g.perm.Hyperlink && g.anon.RdWiki ){
13771380
@ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
13781381
}else{
13791382
@ wiki page [%h(zTarget)]
13801383
}
13811384
}
@@ -1390,11 +1393,11 @@
13901393
}
13911394
db_finalize(&q);
13921395
if( cnt==0 ){
13931396
@ Control artifact.
13941397
if( pDownloadName && blob_size(pDownloadName)==0 ){
1395
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1398
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13961399
}
13971400
tag_private_status(rid);
13981401
}
13991402
return objType;
14001403
}
@@ -1423,11 +1426,11 @@
14231426
ReCompiled *pRe = 0;
14241427
u64 diffFlags;
14251428
u32 objdescFlags = 0;
14261429
14271430
login_check_credentials();
1428
- if( !g.perm.Read ){ login_needed(); return; }
1431
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
14291432
v1 = name_to_rid_www("v1");
14301433
v2 = name_to_rid_www("v2");
14311434
if( v1==0 || v2==0 ) fossil_redirect_home();
14321435
zRe = P("regex");
14331436
if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
14741477
g.zTop, P("v1"), P("v2"), zW);
14751478
}
14761479
14771480
if( P("smhdr")!=0 ){
14781481
@ <h2>Differences From Artifact
1479
- @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1480
- @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1482
+ @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1483
+ @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
14811484
}else{
14821485
@ <h2>Differences From
1483
- @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1486
+ @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
14841487
object_description(v1, objdescFlags, 0);
1485
- @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1488
+ @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
14861489
object_description(v2, objdescFlags, 0);
14871490
}
14881491
if( pRe ){
14891492
@ <b>Only differences that match regular expression "%h(zRe)"
14901493
@ are shown.</b>
@@ -1508,11 +1511,11 @@
15081511
const char *zMime;
15091512
Blob content;
15101513
15111514
rid = name_to_rid_www("name");
15121515
login_check_credentials();
1513
- if( !g.perm.Read ){ login_needed(); return; }
1516
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
15141517
if( rid==0 ) fossil_redirect_home();
15151518
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
15161519
if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
15171520
g.isConst = 1;
15181521
}
@@ -1605,11 +1608,11 @@
16051608
char *zUuid;
16061609
u32 objdescFlags = 0;
16071610
16081611
rid = name_to_rid_www("name");
16091612
login_check_credentials();
1610
- if( !g.perm.Read ){ login_needed(); return; }
1613
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
16111614
if( rid==0 ) fossil_redirect_home();
16121615
if( g.perm.Admin ){
16131616
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
16141617
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
16151618
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
17911794
if( rid==0 ){
17921795
rid = name_to_rid_www("name");
17931796
}
17941797
17951798
login_check_credentials();
1796
- if( !g.perm.Read ){ login_needed(); return; }
1799
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
17971800
if( rid==0 ) fossil_redirect_home();
17981801
if( g.perm.Admin ){
17991802
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
18001803
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
18011804
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
19061909
Manifest *pTktChng;
19071910
int modPending;
19081911
const char *zModAction;
19091912
char *zTktTitle;
19101913
login_check_credentials();
1911
- if( !g.perm.RdTkt ){ login_needed(); return; }
1914
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
19121915
rid = name_to_rid_www("name");
19131916
if( rid==0 ){ fossil_redirect_home(); }
19141917
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
19151918
if( g.perm.Admin ){
19161919
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
19611964
}
19621965
19631966
@ <div class="section">Overview</div>
19641967
@ <p><table class="label-value">
19651968
@ <tr><th>Artifact&nbsp;ID:</th>
1966
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1969
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
19671970
if( g.perm.Setup ){
19681971
@ (%d(rid))
19691972
}
19701973
modPending = moderation_pending(rid);
19711974
if( modPending ){
@@ -2278,11 +2281,11 @@
22782281
Blob comment;
22792282
char *zBranchName = 0;
22802283
Stmt q;
22812284
22822285
login_check_credentials();
2283
- if( !g.perm.Write ){ login_needed(); return; }
2286
+ if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
22842287
rid = name_to_typed_rid(P("r"), "ci");
22852288
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
22862289
zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
22872290
" FROM event WHERE objid=%d", rid);
22882291
if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
24802483
@ </blockquote>
24812484
@ <hr />
24822485
blob_reset(&suffix);
24832486
}
24842487
@ <p>Make changes to attributes of check-in
2485
- @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2488
+ @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
24862489
form_begin(0, "%R/ci_edit");
24872490
login_insert_csrf_secret();
24882491
@ <div><input type="hidden" name="r" value="%s(zUuid)" />
24892492
@ <table border="0" cellspacing="10">
24902493
@@ -2598,12 +2601,11 @@
25982601
@ <tr><th align="right" valign="top">Branch Closure:</th>
25992602
@ <td valign="top">
26002603
@ <label><input type="checkbox" name="close"%s(zCloseFlag) />
26012604
@ Mark branch
26022605
@ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2603
- @ as "closed" so that its leafs no longer appear on the "leaves" page
2604
- @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2606
+ @ as "closed".</label>
26052607
@ </td></tr>
26062608
}
26072609
}
26082610
if( zBranchName ) fossil_free(zBranchName);
26092611
26102612
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403 }
404 }else{
405 if( zOld && zNew ){
406 if( fossil_strcmp(zOld, zNew)!=0 ){
407 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408 @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
409 @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>.
410 }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411 @ <p>Name change
412 @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413 @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414 }else{
415 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416 @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417 }
418 }else if( zOld ){
419 @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
420 @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
421 }else{
422 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423 @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
424 }
425 if( diffFlags ){
426 append_diff(zOld, zNew, diffFlags, pRe);
427 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428 @ &nbsp;&nbsp;
429 @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
430 }
431 }
432 }
433
434 /*
@@ -527,11 +527,11 @@
527 const char *zW; /* URL param for ignoring whitespace */
528 const char *zPage = "vinfo"; /* Page that shows diffs */
529 const char *zPageHide = "ci"; /* Page that hides diffs */
530
531 login_check_credentials();
532 if( !g.perm.Read ){ login_needed(); return; }
533 zName = P("name");
534 rid = name_to_rid_www("name");
535 if( rid==0 ){
536 style_header("Check-in Information Error");
537 @ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635 if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636 zPJ[jj] = '_';
637 }
638 }
639 @ <tr><th>Timelines:</th><td>
640 @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
641 if( zParent ){
642 @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
643 }
644 if( !isLeaf ){
645 @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
646 }
647 if( zParent && !isLeaf ){
648 @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
649 }
650 db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651 " WHERE rid=%d AND tagtype>0 "
652 " AND tag.tagid=tagxref.tagid "
653 " AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657 }
658 db_finalize(&q2);
659
660
661 /* The Download: line */
662 if( g.perm.Zip ){
663 char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664 zPJ, zUuid, zUuid);
665 @ </td></tr>
666 @ <tr><th>Downloads:</th><td>
667 @ %z(href("%s",zUrl))Tarball</a>
668 @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
669 @ ZIP archive</a>
670 fossil_free(zUrl);
671 }
672 @ </td></tr>
673 @ <tr><th>Other&nbsp;Links:</th>
674 @ <td>
675 @ %z(href("%R/tree?ci=%S",zUuid))files</a>
676 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
677 @ | %z(href("%R/tree?nofiles&type=tree&ci=%S",zUuid))folders</a>
678 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
679 @ | %z(href("%R/vdiff?from=pbranch:%S&to=%S",zUuid,zUuid))
680 @ branch diff</a>
681 if( g.perm.Write ){
682 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
683 }
684 @ </td>
685 @ </tr>
686 blob_reset(&projName);
687 }
@@ -725,11 +725,11 @@
725 @ Show&nbsp;Unified&nbsp;Diffs</a>
726 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728 }
729 if( zParent ){
730 @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
731 @ Patch</a>
732 }
733 @</div>
734 if( pRe ){
735 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775 Blob wiki;
776 int modPending;
777 const char *zModAction;
778
779 login_check_credentials();
780 if( !g.perm.RdWiki ){ login_needed(); return; }
781 rid = name_to_rid_www("name");
782 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783 style_header("Wiki Page Information Error");
784 @ No such object: %h(P("name"))
785 style_footer();
@@ -815,11 +815,11 @@
815 pWiki->zWikiTitle);
816 login_anonymous_available();
817 @ <div class="section">Overview</div>
818 @ <p><table class="label-value">
819 @ <tr><th>Artifact&nbsp;ID:</th>
820 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
821 if( g.perm.Setup ){
822 @ (%d(rid))
823 }
824 modPending = moderation_pending(rid);
825 if( modPending ){
@@ -829,16 +829,19 @@
829 @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830 @ <tr><th>Date:</th><td>
831 hyperlink_to_date(zDate, "</td></tr>");
832 @ <tr><th>Original&nbsp;User:</th><td>
833 hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
 
 
 
834 if( pWiki->nParent>0 ){
835 int i;
836 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
837 for(i=0; i<pWiki->nParent; i++){
838 char *zParent = pWiki->azParent[i];
839 @ %z(href("info/%s",zParent))%s(zParent)</a>
840 }
841 @ </td></tr>
842 }
843 @ </table>
844
@@ -856,11 +859,11 @@
856 }
857
858
859 @ <div class="section">Content</div>
860 blob_init(&wiki, pWiki->zWiki, -1);
861 wiki_convert(&wiki, 0, 0);
862 blob_reset(&wiki);
863 manifest_destroy(pWiki);
864 style_footer();
865 }
866
@@ -993,11 +996,11 @@
993 const char *zW;
994 const char *zVerbose;
995 const char *zGlob;
996 ReCompiled *pRe = 0;
997 login_check_credentials();
998 if( !g.perm.Read ){ login_needed(); return; }
999 login_anonymous_available();
1000 zRe = P("regex");
1001 if( zRe ) re_compile(&pRe, zRe, 0);
1002 zBranch = P("branch");
1003 if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
1208 int mPerm = db_column_int(&q, 5);
1209 const char *zBr = db_column_text(&q, 6);
1210 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1211 if( sameFilename && !showDetail ){
1212 if( cnt==1 ){
1213 @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1214 }
1215 cnt++;
1216 continue;
1217 }
1218 if( !sameFilename ){
@@ -1251,14 +1254,14 @@
1251 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1252 }
1253 @ &mdash; %!w(zCom) (user:
1254 hyperlink_to_user(zUser,zDate,")");
1255 if( g.perm.Hyperlink ){
1256 @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1257 @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1258 @ [annotate]</a>
1259 @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1260 @ [blame]</a>
1261 }
1262 cnt++;
1263 if( pDownloadName && blob_size(pDownloadName)==0 ){
1264 blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
1338 }
1339 @ - %!w(zCom) by
1340 hyperlink_to_user(zUser,zDate," on");
1341 hyperlink_to_date(zDate, ".");
1342 if( pDownloadName && blob_size(pDownloadName)==0 ){
1343 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1344 }
1345 tag_private_status(rid);
1346 cnt++;
1347 }
1348 db_finalize(&q);
@@ -1365,17 +1368,17 @@
1365 }else{
1366 @ Attachment "%h(zFilename)" to
1367 }
1368 objType |= OBJTYPE_ATTACHMENT;
1369 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1370 if( g.perm.Hyperlink && g.perm.RdTkt ){
1371 @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1372 }else{
1373 @ ticket [%S(zTarget)]
1374 }
1375 }else{
1376 if( g.perm.Hyperlink && g.perm.RdWiki ){
1377 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1378 }else{
1379 @ wiki page [%h(zTarget)]
1380 }
1381 }
@@ -1390,11 +1393,11 @@
1390 }
1391 db_finalize(&q);
1392 if( cnt==0 ){
1393 @ Control artifact.
1394 if( pDownloadName && blob_size(pDownloadName)==0 ){
1395 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1396 }
1397 tag_private_status(rid);
1398 }
1399 return objType;
1400 }
@@ -1423,11 +1426,11 @@
1423 ReCompiled *pRe = 0;
1424 u64 diffFlags;
1425 u32 objdescFlags = 0;
1426
1427 login_check_credentials();
1428 if( !g.perm.Read ){ login_needed(); return; }
1429 v1 = name_to_rid_www("v1");
1430 v2 = name_to_rid_www("v2");
1431 if( v1==0 || v2==0 ) fossil_redirect_home();
1432 zRe = P("regex");
1433 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
1474 g.zTop, P("v1"), P("v2"), zW);
1475 }
1476
1477 if( P("smhdr")!=0 ){
1478 @ <h2>Differences From Artifact
1479 @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1480 @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1481 }else{
1482 @ <h2>Differences From
1483 @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1484 object_description(v1, objdescFlags, 0);
1485 @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1486 object_description(v2, objdescFlags, 0);
1487 }
1488 if( pRe ){
1489 @ <b>Only differences that match regular expression "%h(zRe)"
1490 @ are shown.</b>
@@ -1508,11 +1511,11 @@
1508 const char *zMime;
1509 Blob content;
1510
1511 rid = name_to_rid_www("name");
1512 login_check_credentials();
1513 if( !g.perm.Read ){ login_needed(); return; }
1514 if( rid==0 ) fossil_redirect_home();
1515 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1516 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1517 g.isConst = 1;
1518 }
@@ -1605,11 +1608,11 @@
1605 char *zUuid;
1606 u32 objdescFlags = 0;
1607
1608 rid = name_to_rid_www("name");
1609 login_check_credentials();
1610 if( !g.perm.Read ){ login_needed(); return; }
1611 if( rid==0 ) fossil_redirect_home();
1612 if( g.perm.Admin ){
1613 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1614 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1615 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
1791 if( rid==0 ){
1792 rid = name_to_rid_www("name");
1793 }
1794
1795 login_check_credentials();
1796 if( !g.perm.Read ){ login_needed(); return; }
1797 if( rid==0 ) fossil_redirect_home();
1798 if( g.perm.Admin ){
1799 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1800 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1801 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
1906 Manifest *pTktChng;
1907 int modPending;
1908 const char *zModAction;
1909 char *zTktTitle;
1910 login_check_credentials();
1911 if( !g.perm.RdTkt ){ login_needed(); return; }
1912 rid = name_to_rid_www("name");
1913 if( rid==0 ){ fossil_redirect_home(); }
1914 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1915 if( g.perm.Admin ){
1916 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
1961 }
1962
1963 @ <div class="section">Overview</div>
1964 @ <p><table class="label-value">
1965 @ <tr><th>Artifact&nbsp;ID:</th>
1966 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1967 if( g.perm.Setup ){
1968 @ (%d(rid))
1969 }
1970 modPending = moderation_pending(rid);
1971 if( modPending ){
@@ -2278,11 +2281,11 @@
2278 Blob comment;
2279 char *zBranchName = 0;
2280 Stmt q;
2281
2282 login_check_credentials();
2283 if( !g.perm.Write ){ login_needed(); return; }
2284 rid = name_to_typed_rid(P("r"), "ci");
2285 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2286 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2287 " FROM event WHERE objid=%d", rid);
2288 if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
2480 @ </blockquote>
2481 @ <hr />
2482 blob_reset(&suffix);
2483 }
2484 @ <p>Make changes to attributes of check-in
2485 @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2486 form_begin(0, "%R/ci_edit");
2487 login_insert_csrf_secret();
2488 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2489 @ <table border="0" cellspacing="10">
2490
@@ -2598,12 +2601,11 @@
2598 @ <tr><th align="right" valign="top">Branch Closure:</th>
2599 @ <td valign="top">
2600 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2601 @ Mark branch
2602 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2603 @ as "closed" so that its leafs no longer appear on the "leaves" page
2604 @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2605 @ </td></tr>
2606 }
2607 }
2608 if( zBranchName ) fossil_free(zBranchName);
2609
2610
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403 }
404 }else{
405 if( zOld && zNew ){
406 if( fossil_strcmp(zOld, zNew)!=0 ){
407 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
409 @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
410 }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411 @ <p>Name change
412 @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413 @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414 }else{
415 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416 @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417 }
418 }else if( zOld ){
419 @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
420 @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
421 }else{
422 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423 @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
424 }
425 if( diffFlags ){
426 append_diff(zOld, zNew, diffFlags, pRe);
427 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428 @ &nbsp;&nbsp;
429 @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a>
430 }
431 }
432 }
433
434 /*
@@ -527,11 +527,11 @@
527 const char *zW; /* URL param for ignoring whitespace */
528 const char *zPage = "vinfo"; /* Page that shows diffs */
529 const char *zPageHide = "ci"; /* Page that hides diffs */
530
531 login_check_credentials();
532 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
533 zName = P("name");
534 rid = name_to_rid_www("name");
535 if( rid==0 ){
536 style_header("Check-in Information Error");
537 @ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635 if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636 zPJ[jj] = '_';
637 }
638 }
639 @ <tr><th>Timelines:</th><td>
640 @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
641 if( zParent ){
642 @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
643 }
644 if( !isLeaf ){
645 @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
646 }
647 if( zParent && !isLeaf ){
648 @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
649 }
650 db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651 " WHERE rid=%d AND tagtype>0 "
652 " AND tag.tagid=tagxref.tagid "
653 " AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657 }
658 db_finalize(&q2);
659
660
661 /* The Download: line */
662 if( g.anon.Zip ){
663 char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664 zPJ, zUuid, zUuid);
665 @ </td></tr>
666 @ <tr><th>Downloads:</th><td>
667 @ %z(href("%s",zUrl))Tarball</a>
668 @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid))
669 @ ZIP archive</a>
670 fossil_free(zUrl);
671 }
672 @ </td></tr>
673 @ <tr><th>Other&nbsp;Links:</th>
674 @ <td>
675 @ %z(href("%R/tree?ci=%!S",zUuid))files</a>
676 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
677 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
678 @ | %z(href("%R/artifact/%!S",zUuid))manifest</a>
679 @ | %z(href("%R/vdiff?from=pbranch:%!S&to=%!S",zUuid,zUuid))
680 @ branch diff</a>
681 if( g.anon.Write ){
682 @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
683 }
684 @ </td>
685 @ </tr>
686 blob_reset(&projName);
687 }
@@ -725,11 +725,11 @@
725 @ Show&nbsp;Unified&nbsp;Diffs</a>
726 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728 }
729 if( zParent ){
730 @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
731 @ Patch</a>
732 }
733 @</div>
734 if( pRe ){
735 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775 Blob wiki;
776 int modPending;
777 const char *zModAction;
778
779 login_check_credentials();
780 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
781 rid = name_to_rid_www("name");
782 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783 style_header("Wiki Page Information Error");
784 @ No such object: %h(P("name"))
785 style_footer();
@@ -815,11 +815,11 @@
815 pWiki->zWikiTitle);
816 login_anonymous_available();
817 @ <div class="section">Overview</div>
818 @ <p><table class="label-value">
819 @ <tr><th>Artifact&nbsp;ID:</th>
820 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
821 if( g.perm.Setup ){
822 @ (%d(rid))
823 }
824 modPending = moderation_pending(rid);
825 if( modPending ){
@@ -829,16 +829,19 @@
829 @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830 @ <tr><th>Date:</th><td>
831 hyperlink_to_date(zDate, "</td></tr>");
832 @ <tr><th>Original&nbsp;User:</th><td>
833 hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
834 if( pWiki->zMimetype ){
835 @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr>
836 }
837 if( pWiki->nParent>0 ){
838 int i;
839 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
840 for(i=0; i<pWiki->nParent; i++){
841 char *zParent = pWiki->azParent[i];
842 @ %z(href("info/%!S",zParent))%s(zParent)</a>
843 }
844 @ </td></tr>
845 }
846 @ </table>
847
@@ -856,11 +859,11 @@
859 }
860
861
862 @ <div class="section">Content</div>
863 blob_init(&wiki, pWiki->zWiki, -1);
864 wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
865 blob_reset(&wiki);
866 manifest_destroy(pWiki);
867 style_footer();
868 }
869
@@ -993,11 +996,11 @@
996 const char *zW;
997 const char *zVerbose;
998 const char *zGlob;
999 ReCompiled *pRe = 0;
1000 login_check_credentials();
1001 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1002 login_anonymous_available();
1003 zRe = P("regex");
1004 if( zRe ) re_compile(&pRe, zRe, 0);
1005 zBranch = P("branch");
1006 if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
1211 int mPerm = db_column_int(&q, 5);
1212 const char *zBr = db_column_text(&q, 6);
1213 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1214 if( sameFilename && !showDetail ){
1215 if( cnt==1 ){
1216 @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
1217 }
1218 cnt++;
1219 continue;
1220 }
1221 if( !sameFilename ){
@@ -1251,14 +1254,14 @@
1254 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1255 }
1256 @ &mdash; %!w(zCom) (user:
1257 hyperlink_to_user(zUser,zDate,")");
1258 if( g.perm.Hyperlink ){
1259 @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1260 @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
1261 @ [annotate]</a>
1262 @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
1263 @ [blame]</a>
1264 }
1265 cnt++;
1266 if( pDownloadName && blob_size(pDownloadName)==0 ){
1267 blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
1341 }
1342 @ - %!w(zCom) by
1343 hyperlink_to_user(zUser,zDate," on");
1344 hyperlink_to_date(zDate, ".");
1345 if( pDownloadName && blob_size(pDownloadName)==0 ){
1346 blob_appendf(pDownloadName, "%S.txt", zUuid);
1347 }
1348 tag_private_status(rid);
1349 cnt++;
1350 }
1351 db_finalize(&q);
@@ -1365,17 +1368,17 @@
1368 }else{
1369 @ Attachment "%h(zFilename)" to
1370 }
1371 objType |= OBJTYPE_ATTACHMENT;
1372 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1373 if( g.perm.Hyperlink && g.anon.RdTkt ){
1374 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1375 }else{
1376 @ ticket [%S(zTarget)]
1377 }
1378 }else{
1379 if( g.perm.Hyperlink && g.anon.RdWiki ){
1380 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1381 }else{
1382 @ wiki page [%h(zTarget)]
1383 }
1384 }
@@ -1390,11 +1393,11 @@
1393 }
1394 db_finalize(&q);
1395 if( cnt==0 ){
1396 @ Control artifact.
1397 if( pDownloadName && blob_size(pDownloadName)==0 ){
1398 blob_appendf(pDownloadName, "%S.txt", zUuid);
1399 }
1400 tag_private_status(rid);
1401 }
1402 return objType;
1403 }
@@ -1423,11 +1426,11 @@
1426 ReCompiled *pRe = 0;
1427 u64 diffFlags;
1428 u32 objdescFlags = 0;
1429
1430 login_check_credentials();
1431 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1432 v1 = name_to_rid_www("v1");
1433 v2 = name_to_rid_www("v2");
1434 if( v1==0 || v2==0 ) fossil_redirect_home();
1435 zRe = P("regex");
1436 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
1477 g.zTop, P("v1"), P("v2"), zW);
1478 }
1479
1480 if( P("smhdr")!=0 ){
1481 @ <h2>Differences From Artifact
1482 @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1483 @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
1484 }else{
1485 @ <h2>Differences From
1486 @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
1487 object_description(v1, objdescFlags, 0);
1488 @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
1489 object_description(v2, objdescFlags, 0);
1490 }
1491 if( pRe ){
1492 @ <b>Only differences that match regular expression "%h(zRe)"
1493 @ are shown.</b>
@@ -1508,11 +1511,11 @@
1511 const char *zMime;
1512 Blob content;
1513
1514 rid = name_to_rid_www("name");
1515 login_check_credentials();
1516 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1517 if( rid==0 ) fossil_redirect_home();
1518 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1519 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1520 g.isConst = 1;
1521 }
@@ -1605,11 +1608,11 @@
1608 char *zUuid;
1609 u32 objdescFlags = 0;
1610
1611 rid = name_to_rid_www("name");
1612 login_check_credentials();
1613 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1614 if( rid==0 ) fossil_redirect_home();
1615 if( g.perm.Admin ){
1616 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1617 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1618 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
1794 if( rid==0 ){
1795 rid = name_to_rid_www("name");
1796 }
1797
1798 login_check_credentials();
1799 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1800 if( rid==0 ) fossil_redirect_home();
1801 if( g.perm.Admin ){
1802 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1803 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1804 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
1909 Manifest *pTktChng;
1910 int modPending;
1911 const char *zModAction;
1912 char *zTktTitle;
1913 login_check_credentials();
1914 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
1915 rid = name_to_rid_www("name");
1916 if( rid==0 ){ fossil_redirect_home(); }
1917 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1918 if( g.perm.Admin ){
1919 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
1964 }
1965
1966 @ <div class="section">Overview</div>
1967 @ <p><table class="label-value">
1968 @ <tr><th>Artifact&nbsp;ID:</th>
1969 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
1970 if( g.perm.Setup ){
1971 @ (%d(rid))
1972 }
1973 modPending = moderation_pending(rid);
1974 if( modPending ){
@@ -2278,11 +2281,11 @@
2281 Blob comment;
2282 char *zBranchName = 0;
2283 Stmt q;
2284
2285 login_check_credentials();
2286 if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
2287 rid = name_to_typed_rid(P("r"), "ci");
2288 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2289 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2290 " FROM event WHERE objid=%d", rid);
2291 if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
2483 @ </blockquote>
2484 @ <hr />
2485 blob_reset(&suffix);
2486 }
2487 @ <p>Make changes to attributes of check-in
2488 @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
2489 form_begin(0, "%R/ci_edit");
2490 login_insert_csrf_secret();
2491 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2492 @ <table border="0" cellspacing="10">
2493
@@ -2598,12 +2601,11 @@
2601 @ <tr><th align="right" valign="top">Branch Closure:</th>
2602 @ <td valign="top">
2603 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2604 @ Mark branch
2605 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2606 @ as "closed".</label>
 
2607 @ </td></tr>
2608 }
2609 }
2610 if( zBranchName ) fossil_free(zBranchName);
2611
2612
+51 -49
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403403
}
404404
}else{
405405
if( zOld && zNew ){
406406
if( fossil_strcmp(zOld, zNew)!=0 ){
407407
@ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408
- @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
409
- @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>.
408
+ @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
409
+ @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
410410
}else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411411
@ <p>Name change
412412
@ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413413
@ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414414
}else{
415415
@ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416416
@ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417417
}
418418
}else if( zOld ){
419
- @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
420
- @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
419
+ @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
420
+ @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
421421
}else{
422422
@ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423
- @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
423
+ @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
424424
}
425425
if( diffFlags ){
426426
append_diff(zOld, zNew, diffFlags, pRe);
427427
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428428
@ &nbsp;&nbsp;
429
- @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
429
+ @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a>
430430
}
431431
}
432432
}
433433
434434
/*
@@ -527,11 +527,11 @@
527527
const char *zW; /* URL param for ignoring whitespace */
528528
const char *zPage = "vinfo"; /* Page that shows diffs */
529529
const char *zPageHide = "ci"; /* Page that hides diffs */
530530
531531
login_check_credentials();
532
- if( !g.perm.Read ){ login_needed(); return; }
532
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
533533
zName = P("name");
534534
rid = name_to_rid_www("name");
535535
if( rid==0 ){
536536
style_header("Check-in Information Error");
537537
@ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635635
if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636636
zPJ[jj] = '_';
637637
}
638638
}
639639
@ <tr><th>Timelines:</th><td>
640
- @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
640
+ @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
641641
if( zParent ){
642
- @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
642
+ @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
643643
}
644644
if( !isLeaf ){
645
- @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
645
+ @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
646646
}
647647
if( zParent && !isLeaf ){
648
- @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
648
+ @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
649649
}
650650
db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651651
" WHERE rid=%d AND tagtype>0 "
652652
" AND tag.tagid=tagxref.tagid "
653653
" AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657657
}
658658
db_finalize(&q2);
659659
660660
661661
/* The Download: line */
662
- if( g.perm.Zip ){
662
+ if( g.anon.Zip ){
663663
char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664664
zPJ, zUuid, zUuid);
665665
@ </td></tr>
666666
@ <tr><th>Downloads:</th><td>
667667
@ %z(href("%s",zUrl))Tarball</a>
668
- @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
668
+ @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid))
669669
@ ZIP archive</a>
670670
fossil_free(zUrl);
671671
}
672672
@ </td></tr>
673673
@ <tr><th>Other&nbsp;Links:</th>
674674
@ <td>
675
- @ %z(href("%R/tree?ci=%S",zUuid))files</a>
676
- @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
677
- @ | %z(href("%R/tree?nofiles&type=tree&ci=%S",zUuid))folders</a>
678
- @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
679
- @ | %z(href("%R/vdiff?from=pbranch:%S&to=%S",zUuid,zUuid))
675
+ @ %z(href("%R/tree?ci=%!S",zUuid))files</a>
676
+ @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
677
+ @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
678
+ @ | %z(href("%R/artifact/%!S",zUuid))manifest</a>
679
+ @ | %z(href("%R/vdiff?from=pbranch:%!S&to=%!S",zUuid,zUuid))
680680
@ branch diff</a>
681
- if( g.perm.Write ){
682
- @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
681
+ if( g.anon.Write ){
682
+ @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
683683
}
684684
@ </td>
685685
@ </tr>
686686
blob_reset(&projName);
687687
}
@@ -725,11 +725,11 @@
725725
@ Show&nbsp;Unified&nbsp;Diffs</a>
726726
@ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727727
@ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728728
}
729729
if( zParent ){
730
- @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
730
+ @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
731731
@ Patch</a>
732732
}
733733
@</div>
734734
if( pRe ){
735735
@ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775775
Blob wiki;
776776
int modPending;
777777
const char *zModAction;
778778
779779
login_check_credentials();
780
- if( !g.perm.RdWiki ){ login_needed(); return; }
780
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
781781
rid = name_to_rid_www("name");
782782
if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783783
style_header("Wiki Page Information Error");
784784
@ No such object: %h(P("name"))
785785
style_footer();
@@ -815,11 +815,11 @@
815815
pWiki->zWikiTitle);
816816
login_anonymous_available();
817817
@ <div class="section">Overview</div>
818818
@ <p><table class="label-value">
819819
@ <tr><th>Artifact&nbsp;ID:</th>
820
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
820
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
821821
if( g.perm.Setup ){
822822
@ (%d(rid))
823823
}
824824
modPending = moderation_pending(rid);
825825
if( modPending ){
@@ -829,16 +829,19 @@
829829
@ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830830
@ <tr><th>Date:</th><td>
831831
hyperlink_to_date(zDate, "</td></tr>");
832832
@ <tr><th>Original&nbsp;User:</th><td>
833833
hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
834
+ if( pWiki->zMimetype ){
835
+ @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr>
836
+ }
834837
if( pWiki->nParent>0 ){
835838
int i;
836839
@ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
837840
for(i=0; i<pWiki->nParent; i++){
838841
char *zParent = pWiki->azParent[i];
839
- @ %z(href("info/%s",zParent))%s(zParent)</a>
842
+ @ %z(href("info/%!S",zParent))%s(zParent)</a>
840843
}
841844
@ </td></tr>
842845
}
843846
@ </table>
844847
@@ -856,11 +859,11 @@
856859
}
857860
858861
859862
@ <div class="section">Content</div>
860863
blob_init(&wiki, pWiki->zWiki, -1);
861
- wiki_convert(&wiki, 0, 0);
864
+ wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
862865
blob_reset(&wiki);
863866
manifest_destroy(pWiki);
864867
style_footer();
865868
}
866869
@@ -993,11 +996,11 @@
993996
const char *zW;
994997
const char *zVerbose;
995998
const char *zGlob;
996999
ReCompiled *pRe = 0;
9971000
login_check_credentials();
998
- if( !g.perm.Read ){ login_needed(); return; }
1001
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
9991002
login_anonymous_available();
10001003
zRe = P("regex");
10011004
if( zRe ) re_compile(&pRe, zRe, 0);
10021005
zBranch = P("branch");
10031006
if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
12081211
int mPerm = db_column_int(&q, 5);
12091212
const char *zBr = db_column_text(&q, 6);
12101213
int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
12111214
if( sameFilename && !showDetail ){
12121215
if( cnt==1 ){
1213
- @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1216
+ @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
12141217
}
12151218
cnt++;
12161219
continue;
12171220
}
12181221
if( !sameFilename ){
@@ -1251,14 +1254,14 @@
12511254
@ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
12521255
}
12531256
@ &mdash; %!w(zCom) (user:
12541257
hyperlink_to_user(zUser,zDate,")");
12551258
if( g.perm.Hyperlink ){
1256
- @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1257
- @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1259
+ @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1260
+ @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
12581261
@ [annotate]</a>
1259
- @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1262
+ @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
12601263
@ [blame]</a>
12611264
}
12621265
cnt++;
12631266
if( pDownloadName && blob_size(pDownloadName)==0 ){
12641267
blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
13381341
}
13391342
@ - %!w(zCom) by
13401343
hyperlink_to_user(zUser,zDate," on");
13411344
hyperlink_to_date(zDate, ".");
13421345
if( pDownloadName && blob_size(pDownloadName)==0 ){
1343
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1346
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13441347
}
13451348
tag_private_status(rid);
13461349
cnt++;
13471350
}
13481351
db_finalize(&q);
@@ -1365,17 +1368,17 @@
13651368
}else{
13661369
@ Attachment "%h(zFilename)" to
13671370
}
13681371
objType |= OBJTYPE_ATTACHMENT;
13691372
if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1370
- if( g.perm.Hyperlink && g.perm.RdTkt ){
1371
- @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1373
+ if( g.perm.Hyperlink && g.anon.RdTkt ){
1374
+ @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
13721375
}else{
13731376
@ ticket [%S(zTarget)]
13741377
}
13751378
}else{
1376
- if( g.perm.Hyperlink && g.perm.RdWiki ){
1379
+ if( g.perm.Hyperlink && g.anon.RdWiki ){
13771380
@ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
13781381
}else{
13791382
@ wiki page [%h(zTarget)]
13801383
}
13811384
}
@@ -1390,11 +1393,11 @@
13901393
}
13911394
db_finalize(&q);
13921395
if( cnt==0 ){
13931396
@ Control artifact.
13941397
if( pDownloadName && blob_size(pDownloadName)==0 ){
1395
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1398
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13961399
}
13971400
tag_private_status(rid);
13981401
}
13991402
return objType;
14001403
}
@@ -1423,11 +1426,11 @@
14231426
ReCompiled *pRe = 0;
14241427
u64 diffFlags;
14251428
u32 objdescFlags = 0;
14261429
14271430
login_check_credentials();
1428
- if( !g.perm.Read ){ login_needed(); return; }
1431
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
14291432
v1 = name_to_rid_www("v1");
14301433
v2 = name_to_rid_www("v2");
14311434
if( v1==0 || v2==0 ) fossil_redirect_home();
14321435
zRe = P("regex");
14331436
if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
14741477
g.zTop, P("v1"), P("v2"), zW);
14751478
}
14761479
14771480
if( P("smhdr")!=0 ){
14781481
@ <h2>Differences From Artifact
1479
- @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1480
- @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1482
+ @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1483
+ @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
14811484
}else{
14821485
@ <h2>Differences From
1483
- @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1486
+ @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
14841487
object_description(v1, objdescFlags, 0);
1485
- @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1488
+ @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
14861489
object_description(v2, objdescFlags, 0);
14871490
}
14881491
if( pRe ){
14891492
@ <b>Only differences that match regular expression "%h(zRe)"
14901493
@ are shown.</b>
@@ -1508,11 +1511,11 @@
15081511
const char *zMime;
15091512
Blob content;
15101513
15111514
rid = name_to_rid_www("name");
15121515
login_check_credentials();
1513
- if( !g.perm.Read ){ login_needed(); return; }
1516
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
15141517
if( rid==0 ) fossil_redirect_home();
15151518
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
15161519
if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
15171520
g.isConst = 1;
15181521
}
@@ -1605,11 +1608,11 @@
16051608
char *zUuid;
16061609
u32 objdescFlags = 0;
16071610
16081611
rid = name_to_rid_www("name");
16091612
login_check_credentials();
1610
- if( !g.perm.Read ){ login_needed(); return; }
1613
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
16111614
if( rid==0 ) fossil_redirect_home();
16121615
if( g.perm.Admin ){
16131616
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
16141617
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
16151618
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
17911794
if( rid==0 ){
17921795
rid = name_to_rid_www("name");
17931796
}
17941797
17951798
login_check_credentials();
1796
- if( !g.perm.Read ){ login_needed(); return; }
1799
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
17971800
if( rid==0 ) fossil_redirect_home();
17981801
if( g.perm.Admin ){
17991802
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
18001803
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
18011804
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
19061909
Manifest *pTktChng;
19071910
int modPending;
19081911
const char *zModAction;
19091912
char *zTktTitle;
19101913
login_check_credentials();
1911
- if( !g.perm.RdTkt ){ login_needed(); return; }
1914
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
19121915
rid = name_to_rid_www("name");
19131916
if( rid==0 ){ fossil_redirect_home(); }
19141917
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
19151918
if( g.perm.Admin ){
19161919
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
19611964
}
19621965
19631966
@ <div class="section">Overview</div>
19641967
@ <p><table class="label-value">
19651968
@ <tr><th>Artifact&nbsp;ID:</th>
1966
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1969
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
19671970
if( g.perm.Setup ){
19681971
@ (%d(rid))
19691972
}
19701973
modPending = moderation_pending(rid);
19711974
if( modPending ){
@@ -2278,11 +2281,11 @@
22782281
Blob comment;
22792282
char *zBranchName = 0;
22802283
Stmt q;
22812284
22822285
login_check_credentials();
2283
- if( !g.perm.Write ){ login_needed(); return; }
2286
+ if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
22842287
rid = name_to_typed_rid(P("r"), "ci");
22852288
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
22862289
zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
22872290
" FROM event WHERE objid=%d", rid);
22882291
if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
24802483
@ </blockquote>
24812484
@ <hr />
24822485
blob_reset(&suffix);
24832486
}
24842487
@ <p>Make changes to attributes of check-in
2485
- @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2488
+ @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
24862489
form_begin(0, "%R/ci_edit");
24872490
login_insert_csrf_secret();
24882491
@ <div><input type="hidden" name="r" value="%s(zUuid)" />
24892492
@ <table border="0" cellspacing="10">
24902493
@@ -2598,12 +2601,11 @@
25982601
@ <tr><th align="right" valign="top">Branch Closure:</th>
25992602
@ <td valign="top">
26002603
@ <label><input type="checkbox" name="close"%s(zCloseFlag) />
26012604
@ Mark branch
26022605
@ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2603
- @ as "closed" so that its leafs no longer appear on the "leaves" page
2604
- @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2606
+ @ as "closed".</label>
26052607
@ </td></tr>
26062608
}
26072609
}
26082610
if( zBranchName ) fossil_free(zBranchName);
26092611
26102612
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403 }
404 }else{
405 if( zOld && zNew ){
406 if( fossil_strcmp(zOld, zNew)!=0 ){
407 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408 @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
409 @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>.
410 }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411 @ <p>Name change
412 @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413 @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414 }else{
415 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416 @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417 }
418 }else if( zOld ){
419 @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
420 @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
421 }else{
422 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423 @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
424 }
425 if( diffFlags ){
426 append_diff(zOld, zNew, diffFlags, pRe);
427 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428 @ &nbsp;&nbsp;
429 @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
430 }
431 }
432 }
433
434 /*
@@ -527,11 +527,11 @@
527 const char *zW; /* URL param for ignoring whitespace */
528 const char *zPage = "vinfo"; /* Page that shows diffs */
529 const char *zPageHide = "ci"; /* Page that hides diffs */
530
531 login_check_credentials();
532 if( !g.perm.Read ){ login_needed(); return; }
533 zName = P("name");
534 rid = name_to_rid_www("name");
535 if( rid==0 ){
536 style_header("Check-in Information Error");
537 @ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635 if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636 zPJ[jj] = '_';
637 }
638 }
639 @ <tr><th>Timelines:</th><td>
640 @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
641 if( zParent ){
642 @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
643 }
644 if( !isLeaf ){
645 @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
646 }
647 if( zParent && !isLeaf ){
648 @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
649 }
650 db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651 " WHERE rid=%d AND tagtype>0 "
652 " AND tag.tagid=tagxref.tagid "
653 " AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657 }
658 db_finalize(&q2);
659
660
661 /* The Download: line */
662 if( g.perm.Zip ){
663 char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664 zPJ, zUuid, zUuid);
665 @ </td></tr>
666 @ <tr><th>Downloads:</th><td>
667 @ %z(href("%s",zUrl))Tarball</a>
668 @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
669 @ ZIP archive</a>
670 fossil_free(zUrl);
671 }
672 @ </td></tr>
673 @ <tr><th>Other&nbsp;Links:</th>
674 @ <td>
675 @ %z(href("%R/tree?ci=%S",zUuid))files</a>
676 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
677 @ | %z(href("%R/tree?nofiles&type=tree&ci=%S",zUuid))folders</a>
678 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
679 @ | %z(href("%R/vdiff?from=pbranch:%S&to=%S",zUuid,zUuid))
680 @ branch diff</a>
681 if( g.perm.Write ){
682 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
683 }
684 @ </td>
685 @ </tr>
686 blob_reset(&projName);
687 }
@@ -725,11 +725,11 @@
725 @ Show&nbsp;Unified&nbsp;Diffs</a>
726 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728 }
729 if( zParent ){
730 @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
731 @ Patch</a>
732 }
733 @</div>
734 if( pRe ){
735 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775 Blob wiki;
776 int modPending;
777 const char *zModAction;
778
779 login_check_credentials();
780 if( !g.perm.RdWiki ){ login_needed(); return; }
781 rid = name_to_rid_www("name");
782 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783 style_header("Wiki Page Information Error");
784 @ No such object: %h(P("name"))
785 style_footer();
@@ -815,11 +815,11 @@
815 pWiki->zWikiTitle);
816 login_anonymous_available();
817 @ <div class="section">Overview</div>
818 @ <p><table class="label-value">
819 @ <tr><th>Artifact&nbsp;ID:</th>
820 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
821 if( g.perm.Setup ){
822 @ (%d(rid))
823 }
824 modPending = moderation_pending(rid);
825 if( modPending ){
@@ -829,16 +829,19 @@
829 @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830 @ <tr><th>Date:</th><td>
831 hyperlink_to_date(zDate, "</td></tr>");
832 @ <tr><th>Original&nbsp;User:</th><td>
833 hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
 
 
 
834 if( pWiki->nParent>0 ){
835 int i;
836 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
837 for(i=0; i<pWiki->nParent; i++){
838 char *zParent = pWiki->azParent[i];
839 @ %z(href("info/%s",zParent))%s(zParent)</a>
840 }
841 @ </td></tr>
842 }
843 @ </table>
844
@@ -856,11 +859,11 @@
856 }
857
858
859 @ <div class="section">Content</div>
860 blob_init(&wiki, pWiki->zWiki, -1);
861 wiki_convert(&wiki, 0, 0);
862 blob_reset(&wiki);
863 manifest_destroy(pWiki);
864 style_footer();
865 }
866
@@ -993,11 +996,11 @@
993 const char *zW;
994 const char *zVerbose;
995 const char *zGlob;
996 ReCompiled *pRe = 0;
997 login_check_credentials();
998 if( !g.perm.Read ){ login_needed(); return; }
999 login_anonymous_available();
1000 zRe = P("regex");
1001 if( zRe ) re_compile(&pRe, zRe, 0);
1002 zBranch = P("branch");
1003 if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
1208 int mPerm = db_column_int(&q, 5);
1209 const char *zBr = db_column_text(&q, 6);
1210 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1211 if( sameFilename && !showDetail ){
1212 if( cnt==1 ){
1213 @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1214 }
1215 cnt++;
1216 continue;
1217 }
1218 if( !sameFilename ){
@@ -1251,14 +1254,14 @@
1251 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1252 }
1253 @ &mdash; %!w(zCom) (user:
1254 hyperlink_to_user(zUser,zDate,")");
1255 if( g.perm.Hyperlink ){
1256 @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1257 @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1258 @ [annotate]</a>
1259 @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1260 @ [blame]</a>
1261 }
1262 cnt++;
1263 if( pDownloadName && blob_size(pDownloadName)==0 ){
1264 blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
1338 }
1339 @ - %!w(zCom) by
1340 hyperlink_to_user(zUser,zDate," on");
1341 hyperlink_to_date(zDate, ".");
1342 if( pDownloadName && blob_size(pDownloadName)==0 ){
1343 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1344 }
1345 tag_private_status(rid);
1346 cnt++;
1347 }
1348 db_finalize(&q);
@@ -1365,17 +1368,17 @@
1365 }else{
1366 @ Attachment "%h(zFilename)" to
1367 }
1368 objType |= OBJTYPE_ATTACHMENT;
1369 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1370 if( g.perm.Hyperlink && g.perm.RdTkt ){
1371 @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1372 }else{
1373 @ ticket [%S(zTarget)]
1374 }
1375 }else{
1376 if( g.perm.Hyperlink && g.perm.RdWiki ){
1377 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1378 }else{
1379 @ wiki page [%h(zTarget)]
1380 }
1381 }
@@ -1390,11 +1393,11 @@
1390 }
1391 db_finalize(&q);
1392 if( cnt==0 ){
1393 @ Control artifact.
1394 if( pDownloadName && blob_size(pDownloadName)==0 ){
1395 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1396 }
1397 tag_private_status(rid);
1398 }
1399 return objType;
1400 }
@@ -1423,11 +1426,11 @@
1423 ReCompiled *pRe = 0;
1424 u64 diffFlags;
1425 u32 objdescFlags = 0;
1426
1427 login_check_credentials();
1428 if( !g.perm.Read ){ login_needed(); return; }
1429 v1 = name_to_rid_www("v1");
1430 v2 = name_to_rid_www("v2");
1431 if( v1==0 || v2==0 ) fossil_redirect_home();
1432 zRe = P("regex");
1433 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
1474 g.zTop, P("v1"), P("v2"), zW);
1475 }
1476
1477 if( P("smhdr")!=0 ){
1478 @ <h2>Differences From Artifact
1479 @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1480 @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1481 }else{
1482 @ <h2>Differences From
1483 @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1484 object_description(v1, objdescFlags, 0);
1485 @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1486 object_description(v2, objdescFlags, 0);
1487 }
1488 if( pRe ){
1489 @ <b>Only differences that match regular expression "%h(zRe)"
1490 @ are shown.</b>
@@ -1508,11 +1511,11 @@
1508 const char *zMime;
1509 Blob content;
1510
1511 rid = name_to_rid_www("name");
1512 login_check_credentials();
1513 if( !g.perm.Read ){ login_needed(); return; }
1514 if( rid==0 ) fossil_redirect_home();
1515 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1516 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1517 g.isConst = 1;
1518 }
@@ -1605,11 +1608,11 @@
1605 char *zUuid;
1606 u32 objdescFlags = 0;
1607
1608 rid = name_to_rid_www("name");
1609 login_check_credentials();
1610 if( !g.perm.Read ){ login_needed(); return; }
1611 if( rid==0 ) fossil_redirect_home();
1612 if( g.perm.Admin ){
1613 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1614 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1615 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
1791 if( rid==0 ){
1792 rid = name_to_rid_www("name");
1793 }
1794
1795 login_check_credentials();
1796 if( !g.perm.Read ){ login_needed(); return; }
1797 if( rid==0 ) fossil_redirect_home();
1798 if( g.perm.Admin ){
1799 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1800 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1801 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
1906 Manifest *pTktChng;
1907 int modPending;
1908 const char *zModAction;
1909 char *zTktTitle;
1910 login_check_credentials();
1911 if( !g.perm.RdTkt ){ login_needed(); return; }
1912 rid = name_to_rid_www("name");
1913 if( rid==0 ){ fossil_redirect_home(); }
1914 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1915 if( g.perm.Admin ){
1916 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
1961 }
1962
1963 @ <div class="section">Overview</div>
1964 @ <p><table class="label-value">
1965 @ <tr><th>Artifact&nbsp;ID:</th>
1966 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1967 if( g.perm.Setup ){
1968 @ (%d(rid))
1969 }
1970 modPending = moderation_pending(rid);
1971 if( modPending ){
@@ -2278,11 +2281,11 @@
2278 Blob comment;
2279 char *zBranchName = 0;
2280 Stmt q;
2281
2282 login_check_credentials();
2283 if( !g.perm.Write ){ login_needed(); return; }
2284 rid = name_to_typed_rid(P("r"), "ci");
2285 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2286 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2287 " FROM event WHERE objid=%d", rid);
2288 if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
2480 @ </blockquote>
2481 @ <hr />
2482 blob_reset(&suffix);
2483 }
2484 @ <p>Make changes to attributes of check-in
2485 @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2486 form_begin(0, "%R/ci_edit");
2487 login_insert_csrf_secret();
2488 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2489 @ <table border="0" cellspacing="10">
2490
@@ -2598,12 +2601,11 @@
2598 @ <tr><th align="right" valign="top">Branch Closure:</th>
2599 @ <td valign="top">
2600 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2601 @ Mark branch
2602 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2603 @ as "closed" so that its leafs no longer appear on the "leaves" page
2604 @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2605 @ </td></tr>
2606 }
2607 }
2608 if( zBranchName ) fossil_free(zBranchName);
2609
2610
--- src/info.c
+++ src/info.c
@@ -403,32 +403,32 @@
403 }
404 }else{
405 if( zOld && zNew ){
406 if( fossil_strcmp(zOld, zNew)!=0 ){
407 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
408 @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
409 @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
410 }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
411 @ <p>Name change
412 @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
413 @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
414 }else{
415 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
416 @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
417 }
418 }else if( zOld ){
419 @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
420 @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
421 }else{
422 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
423 @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
424 }
425 if( diffFlags ){
426 append_diff(zOld, zNew, diffFlags, pRe);
427 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
428 @ &nbsp;&nbsp;
429 @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a>
430 }
431 }
432 }
433
434 /*
@@ -527,11 +527,11 @@
527 const char *zW; /* URL param for ignoring whitespace */
528 const char *zPage = "vinfo"; /* Page that shows diffs */
529 const char *zPageHide = "ci"; /* Page that hides diffs */
530
531 login_check_credentials();
532 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
533 zName = P("name");
534 rid = name_to_rid_www("name");
535 if( rid==0 ){
536 style_header("Check-in Information Error");
537 @ No such object: %h(g.argv[2])
@@ -635,19 +635,19 @@
635 if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
636 zPJ[jj] = '_';
637 }
638 }
639 @ <tr><th>Timelines:</th><td>
640 @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
641 if( zParent ){
642 @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
643 }
644 if( !isLeaf ){
645 @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
646 }
647 if( zParent && !isLeaf ){
648 @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
649 }
650 db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
651 " WHERE rid=%d AND tagtype>0 "
652 " AND tag.tagid=tagxref.tagid "
653 " AND +tag.tagname GLOB 'sym-*'", rid);
@@ -657,31 +657,31 @@
657 }
658 db_finalize(&q2);
659
660
661 /* The Download: line */
662 if( g.anon.Zip ){
663 char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
664 zPJ, zUuid, zUuid);
665 @ </td></tr>
666 @ <tr><th>Downloads:</th><td>
667 @ %z(href("%s",zUrl))Tarball</a>
668 @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid))
669 @ ZIP archive</a>
670 fossil_free(zUrl);
671 }
672 @ </td></tr>
673 @ <tr><th>Other&nbsp;Links:</th>
674 @ <td>
675 @ %z(href("%R/tree?ci=%!S",zUuid))files</a>
676 @ | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
677 @ | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
678 @ | %z(href("%R/artifact/%!S",zUuid))manifest</a>
679 @ | %z(href("%R/vdiff?from=pbranch:%!S&to=%!S",zUuid,zUuid))
680 @ branch diff</a>
681 if( g.anon.Write ){
682 @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
683 }
684 @ </td>
685 @ </tr>
686 blob_reset(&projName);
687 }
@@ -725,11 +725,11 @@
725 @ Show&nbsp;Unified&nbsp;Diffs</a>
726 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
727 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
728 }
729 if( zParent ){
730 @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
731 @ Patch</a>
732 }
733 @</div>
734 if( pRe ){
735 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -775,11 +775,11 @@
775 Blob wiki;
776 int modPending;
777 const char *zModAction;
778
779 login_check_credentials();
780 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
781 rid = name_to_rid_www("name");
782 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
783 style_header("Wiki Page Information Error");
784 @ No such object: %h(P("name"))
785 style_footer();
@@ -815,11 +815,11 @@
815 pWiki->zWikiTitle);
816 login_anonymous_available();
817 @ <div class="section">Overview</div>
818 @ <p><table class="label-value">
819 @ <tr><th>Artifact&nbsp;ID:</th>
820 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
821 if( g.perm.Setup ){
822 @ (%d(rid))
823 }
824 modPending = moderation_pending(rid);
825 if( modPending ){
@@ -829,16 +829,19 @@
829 @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
830 @ <tr><th>Date:</th><td>
831 hyperlink_to_date(zDate, "</td></tr>");
832 @ <tr><th>Original&nbsp;User:</th><td>
833 hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
834 if( pWiki->zMimetype ){
835 @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr>
836 }
837 if( pWiki->nParent>0 ){
838 int i;
839 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
840 for(i=0; i<pWiki->nParent; i++){
841 char *zParent = pWiki->azParent[i];
842 @ %z(href("info/%!S",zParent))%s(zParent)</a>
843 }
844 @ </td></tr>
845 }
846 @ </table>
847
@@ -856,11 +859,11 @@
859 }
860
861
862 @ <div class="section">Content</div>
863 blob_init(&wiki, pWiki->zWiki, -1);
864 wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
865 blob_reset(&wiki);
866 manifest_destroy(pWiki);
867 style_footer();
868 }
869
@@ -993,11 +996,11 @@
996 const char *zW;
997 const char *zVerbose;
998 const char *zGlob;
999 ReCompiled *pRe = 0;
1000 login_check_credentials();
1001 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1002 login_anonymous_available();
1003 zRe = P("regex");
1004 if( zRe ) re_compile(&pRe, zRe, 0);
1005 zBranch = P("branch");
1006 if( zBranch && zBranch[0] ){
@@ -1208,11 +1211,11 @@
1211 int mPerm = db_column_int(&q, 5);
1212 const char *zBr = db_column_text(&q, 6);
1213 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1214 if( sameFilename && !showDetail ){
1215 if( cnt==1 ){
1216 @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
1217 }
1218 cnt++;
1219 continue;
1220 }
1221 if( !sameFilename ){
@@ -1251,14 +1254,14 @@
1254 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1255 }
1256 @ &mdash; %!w(zCom) (user:
1257 hyperlink_to_user(zUser,zDate,")");
1258 if( g.perm.Hyperlink ){
1259 @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1260 @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
1261 @ [annotate]</a>
1262 @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
1263 @ [blame]</a>
1264 }
1265 cnt++;
1266 if( pDownloadName && blob_size(pDownloadName)==0 ){
1267 blob_append(pDownloadName, zName, -1);
@@ -1338,11 +1341,11 @@
1341 }
1342 @ - %!w(zCom) by
1343 hyperlink_to_user(zUser,zDate," on");
1344 hyperlink_to_date(zDate, ".");
1345 if( pDownloadName && blob_size(pDownloadName)==0 ){
1346 blob_appendf(pDownloadName, "%S.txt", zUuid);
1347 }
1348 tag_private_status(rid);
1349 cnt++;
1350 }
1351 db_finalize(&q);
@@ -1365,17 +1368,17 @@
1368 }else{
1369 @ Attachment "%h(zFilename)" to
1370 }
1371 objType |= OBJTYPE_ATTACHMENT;
1372 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1373 if( g.perm.Hyperlink && g.anon.RdTkt ){
1374 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1375 }else{
1376 @ ticket [%S(zTarget)]
1377 }
1378 }else{
1379 if( g.perm.Hyperlink && g.anon.RdWiki ){
1380 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1381 }else{
1382 @ wiki page [%h(zTarget)]
1383 }
1384 }
@@ -1390,11 +1393,11 @@
1393 }
1394 db_finalize(&q);
1395 if( cnt==0 ){
1396 @ Control artifact.
1397 if( pDownloadName && blob_size(pDownloadName)==0 ){
1398 blob_appendf(pDownloadName, "%S.txt", zUuid);
1399 }
1400 tag_private_status(rid);
1401 }
1402 return objType;
1403 }
@@ -1423,11 +1426,11 @@
1426 ReCompiled *pRe = 0;
1427 u64 diffFlags;
1428 u32 objdescFlags = 0;
1429
1430 login_check_credentials();
1431 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1432 v1 = name_to_rid_www("v1");
1433 v2 = name_to_rid_www("v2");
1434 if( v1==0 || v2==0 ) fossil_redirect_home();
1435 zRe = P("regex");
1436 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1474,17 +1477,17 @@
1477 g.zTop, P("v1"), P("v2"), zW);
1478 }
1479
1480 if( P("smhdr")!=0 ){
1481 @ <h2>Differences From Artifact
1482 @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1483 @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
1484 }else{
1485 @ <h2>Differences From
1486 @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
1487 object_description(v1, objdescFlags, 0);
1488 @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
1489 object_description(v2, objdescFlags, 0);
1490 }
1491 if( pRe ){
1492 @ <b>Only differences that match regular expression "%h(zRe)"
1493 @ are shown.</b>
@@ -1508,11 +1511,11 @@
1511 const char *zMime;
1512 Blob content;
1513
1514 rid = name_to_rid_www("name");
1515 login_check_credentials();
1516 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1517 if( rid==0 ) fossil_redirect_home();
1518 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1519 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1520 g.isConst = 1;
1521 }
@@ -1605,11 +1608,11 @@
1608 char *zUuid;
1609 u32 objdescFlags = 0;
1610
1611 rid = name_to_rid_www("name");
1612 login_check_credentials();
1613 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1614 if( rid==0 ) fossil_redirect_home();
1615 if( g.perm.Admin ){
1616 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1617 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1618 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1791,11 +1794,11 @@
1794 if( rid==0 ){
1795 rid = name_to_rid_www("name");
1796 }
1797
1798 login_check_credentials();
1799 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1800 if( rid==0 ) fossil_redirect_home();
1801 if( g.perm.Admin ){
1802 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1803 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1804 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1906,11 +1909,11 @@
1909 Manifest *pTktChng;
1910 int modPending;
1911 const char *zModAction;
1912 char *zTktTitle;
1913 login_check_credentials();
1914 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
1915 rid = name_to_rid_www("name");
1916 if( rid==0 ){ fossil_redirect_home(); }
1917 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1918 if( g.perm.Admin ){
1919 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1961,11 +1964,11 @@
1964 }
1965
1966 @ <div class="section">Overview</div>
1967 @ <p><table class="label-value">
1968 @ <tr><th>Artifact&nbsp;ID:</th>
1969 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
1970 if( g.perm.Setup ){
1971 @ (%d(rid))
1972 }
1973 modPending = moderation_pending(rid);
1974 if( modPending ){
@@ -2278,11 +2281,11 @@
2281 Blob comment;
2282 char *zBranchName = 0;
2283 Stmt q;
2284
2285 login_check_credentials();
2286 if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
2287 rid = name_to_typed_rid(P("r"), "ci");
2288 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2289 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2290 " FROM event WHERE objid=%d", rid);
2291 if( zComment==0 ) fossil_redirect_home();
@@ -2480,11 +2483,11 @@
2483 @ </blockquote>
2484 @ <hr />
2485 blob_reset(&suffix);
2486 }
2487 @ <p>Make changes to attributes of check-in
2488 @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
2489 form_begin(0, "%R/ci_edit");
2490 login_insert_csrf_secret();
2491 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2492 @ <table border="0" cellspacing="10">
2493
@@ -2598,12 +2601,11 @@
2601 @ <tr><th align="right" valign="top">Branch Closure:</th>
2602 @ <td valign="top">
2603 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2604 @ Mark branch
2605 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2606 @ as "closed".</label>
 
2607 @ </td></tr>
2608 }
2609 }
2610 if( zBranchName ) fossil_free(zBranchName);
2611
2612
+154 -108
--- src/login.c
+++ src/login.c
@@ -352,16 +352,12 @@
352352
login_cookie_path(), -86400);
353353
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
354354
" cexpire=0 WHERE uid=%d"
355355
" AND login NOT IN ('anonymous','nobody',"
356356
" 'developer','reader')", g.userUid);
357
- cgi_replace_parameter(cookie, NULL)
358
- /* At the time of this writing, cgi_replace_parameter() was
359
- ** "NULL-value-safe", and I'm hoping the NULL doesn't cause any
360
- ** downstream problems here. We could alternately use "" here.
361
- */
362
- ;
357
+ cgi_replace_parameter(cookie, NULL);
358
+ cgi_replace_parameter("anon", NULL);
363359
}
364360
}
365361
366362
/*
367363
** Return true if the prefix of zStr matches zPattern. Return false if
@@ -451,27 +447,46 @@
451447
}
452448
sqlite3_result_int(context, rc);
453449
}
454450
455451
/*
456
-** WEBPAGE: login
457
-** WEBPAGE: logout
458
-** WEBPAGE: my
459
-**
460
-** Generate the login page.
461
-**
452
+** Return true if the current page was reached by a redirect from the /login
453
+** page.
454
+*/
455
+int referred_from_login(void){
456
+ const char *zReferer = P("HTTP_REFERER");
457
+ char *zPattern;
458
+ int rc;
459
+ if( zReferer==0 ) return 0;
460
+ zPattern = mprintf("%s/login*", g.zBaseURL);
461
+ rc = sqlite3_strglob(zPattern, zReferer)==0;
462
+ fossil_free(zPattern);
463
+ return rc;
464
+}
465
+
466
+/*
462467
** There used to be a page named "my" that was designed to show information
463468
** about a specific user. The "my" page was linked from the "Logged in as USER"
464469
** line on the title bar. The "my" page was never completed so it is now
465470
** removed. Use this page as a placeholder in older installations.
471
+**
472
+** WEBPAGE: login
473
+** WEBPAGE: logout
474
+** WEBPAGE: my
475
+**
476
+** The login/logout page. Parameters:
477
+**
478
+** g=URL Jump back to this URL after login completes
479
+** anon The g=URL is not accessible by "nobody" but is
480
+** accessible by "anonymous"
466481
*/
467482
void login_page(void){
468483
const char *zUsername, *zPasswd;
469484
const char *zNew1, *zNew2;
470485
const char *zAnonPw = 0;
471486
const char *zGoto = P("g");
472
- int anonFlag;
487
+ int anonFlag; /* Login as "anonymous" would be useful */
473488
char *zErrMsg = "";
474489
int uid; /* User id logged in user */
475490
char *zSha1Pw;
476491
const char *zIpAddr; /* IP address of requestor */
477492
const char *zReferer;
@@ -489,15 +504,20 @@
489504
}
490505
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
491506
constant_time_cmp_function, 0, 0);
492507
zUsername = P("u");
493508
zPasswd = P("p");
494
- anonFlag = P("anon")!=0;
495
- if( P("out")!=0 ){
509
+ anonFlag = g.zLogin==0 && PB("anon");
510
+
511
+ /* Handle log-out requests */
512
+ if( P("out") ){
496513
login_clear_login_data();
497514
redirect_to_g();
515
+ return;
498516
}
517
+
518
+ /* Deal with password-change requests */
499519
if( g.perm.Password && zPasswd
500520
&& (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
501521
){
502522
/* The user requests a password change */
503523
zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
@@ -577,19 +597,40 @@
577597
}
578598
}
579599
style_header("Login/Logout");
580600
style_adunit_config(ADUNIT_OFF);
581601
@ %s(zErrMsg)
582
- if( zGoto && P("anon")==0 ){
583
- @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
602
+ if( zGoto ){
603
+ char *zAbbrev = fossil_strdup(zGoto);
604
+ int i;
605
+ for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
606
+ zAbbrev[i] = 0;
607
+ if( g.zLogin ){
608
+ @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
609
+ @ to access <b>%h(zAbbrev)</b>.
610
+ }else if( anonFlag ){
611
+ @ <p>Login as <b>anonymous</b> or any named user
612
+ @ to access page <b>%h(zAbbrev)</b>.
613
+ }else{
614
+ @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
615
+ }
584616
}
585617
form_begin(0, "%R/login");
586618
if( zGoto ){
587619
@ <input type="hidden" name="g" value="%h(zGoto)" />
588620
}else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
589621
@ <input type="hidden" name="g" value="%h(zReferer)" />
590622
}
623
+ if( anonFlag ){
624
+ @ <input type="hidden" name="anon" value="1" />
625
+ }
626
+ if( g.zLogin ){
627
+ @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
628
+ @ <input type="submit" name="out" value="Logout"></p>
629
+ @ <hr />
630
+ @ <p>Change user:
631
+ }
591632
@ <table class="login_out">
592633
@ <tr>
593634
@ <td class="login_out_label">User ID:</td>
594635
if( anonFlag ){
595636
@ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
@@ -599,11 +640,11 @@
599640
@ </tr>
600641
@ <tr>
601642
@ <td class="login_out_label">Password:</td>
602643
@ <td><input type="password" id="p" name="p" value="" size="30" /></td>
603644
@ </tr>
604
- if( g.zLogin==0 ){
645
+ if( g.zLogin==0 && (anonFlag || zGoto==0) ){
605646
zAnonPw = db_text(0, "SELECT pw FROM user"
606647
" WHERE login='anonymous'"
607648
" AND cap!=''");
608649
}
609650
@ <tr>
@@ -624,23 +665,14 @@
624665
@ form.action = "%h(zSSL)/login";
625666
@ }
626667
}
627668
@ }
628669
@ </script>
629
- if( g.zLogin==0 ){
630
- @ <p>Enter
631
- }else{
632
- @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
633
- @ <p>To change your login to a different user, enter
634
- }
635
- @ your user-id and password at the left and press the
636
- @ "Login" button. Your user name will be stored in a browser cookie.
637
- @ You must configure your web browser to accept cookies in order for
638
- @ the login to take.</p>
670
+ @ <p>Pressing the Login button grants permission to store a cookie.</p>
639671
if( db_get_boolean("self-register", 0) ){
640672
@ <p>If you do not have an account, you can
641
- @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
673
+ @ <a href="%R/register?g=%T(P("G"))">create one</a>.
642674
}
643675
if( zAnonPw ){
644676
unsigned int uSeed = captcha_seed();
645677
const char *zDecoded = captcha_decode(uSeed);
646678
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
@@ -657,22 +689,14 @@
657689
@ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
658690
}
659691
@ </div>
660692
free(zCaptcha);
661693
}
662
- if( g.zLogin ){
663
- @ <hr />
664
- @ <p>To log off the system (and delete your login cookie)
665
- @ press the following button:<br />
666
- @ <input type="submit" name="out" value="Logout" /></p>
667
- }
668694
@ </form>
669695
if( g.perm.Password ){
670696
@ <hr />
671
- @ <p>To change your password, enter your old password and your
672
- @ new password twice below then press the "Change Password"
673
- @ button.</p>
697
+ @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
674698
form_begin(0, "%R/login");
675699
@ <table>
676700
@ <tr><td class="login_out_label">Old Password:</td>
677701
@ <td><input type="password" name="p" size="30" /></td></tr>
678702
@ <tr><td class="login_out_label">New Password:</td>
@@ -811,10 +835,11 @@
811835
** variables appropriately.
812836
**
813837
** g.userUid Database USER.UID value. Might be -1 for "nobody"
814838
** g.zLogin Database USER.LOGIN value. NULL for user "nobody"
815839
** g.perm Permissions granted to this user
840
+** g.anon Permissions that would be available to anonymous
816841
** g.isHuman True if the user is human, not a spider or robot
817842
**
818843
*/
819844
void login_check_credentials(void){
820845
int uid = 0; /* User id */
@@ -1002,23 +1027,32 @@
10021027
** Memory of settings
10031028
*/
10041029
static int login_anon_once = 1;
10051030
10061031
/*
1007
-** Add the default privileges of users "nobody" and "anonymous" as appropriate
1008
-** for the user g.zLogin.
1032
+** Add to g.perm the default privileges of users "nobody" and/or "anonymous"
1033
+** as appropriate for the user g.zLogin.
1034
+**
1035
+** This routine also sets up g.anon to be either a copy of g.perm for
1036
+** all logged in uses, or the privileges that would be available to "anonymous"
1037
+** if g.zLogin==0 (meaning that the user is "nobody").
10091038
*/
10101039
void login_set_anon_nobody_capabilities(void){
1011
- if( g.zLogin && login_anon_once ){
1040
+ if( login_anon_once ){
10121041
const char *zCap;
1013
- /* All logged-in users inherit privileges from "nobody" */
1042
+ /* All users get privileges from "nobody" */
10141043
zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
10151044
login_set_capabilities(zCap, 0);
1016
- if( fossil_strcmp(g.zLogin, "nobody")!=0 ){
1045
+ zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
1046
+ if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){
10171047
/* All logged-in users inherit privileges from "anonymous" */
1018
- zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
10191048
login_set_capabilities(zCap, 0);
1049
+ g.anon = g.perm;
1050
+ }else{
1051
+ /* Record the privileges of anonymous in g.anon */
1052
+ g.anon = g.perm;
1053
+ login_set_capabilities(zCap, LOGIN_ANON);
10201054
}
10211055
login_anon_once = 0;
10221056
}
10231057
}
10241058
@@ -1025,55 +1059,57 @@
10251059
/*
10261060
** Flags passed into the 2nd argument of login_set/replace_capabilities().
10271061
*/
10281062
#if INTERFACE
10291063
#define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */
1064
+#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */
10301065
#endif
10311066
10321067
/*
1033
-** Adds all capability flags in zCap to g.perm.
1068
+** Adds all capability flags in zCap to g.perm or g.anon.
10341069
*/
10351070
void login_set_capabilities(const char *zCap, unsigned flags){
10361071
int i;
1072
+ FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
10371073
if(NULL==zCap){
10381074
return;
10391075
}
10401076
for(i=0; zCap[i]; i++){
10411077
switch( zCap[i] ){
1042
- case 's': g.perm.Setup = 1; /* Fall thru into Admin */
1043
- case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
1044
- g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
1045
- g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone =
1046
- g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
1047
- g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt =
1048
- g.perm.ModWiki = g.perm.ModTkt = 1;
1078
+ case 's': p->Setup = 1; /* Fall thru into Admin */
1079
+ case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
1080
+ p->RdWiki = p->WrWiki = p->NewWiki =
1081
+ p->ApndWiki = p->Hyperlink = p->Clone =
1082
+ p->NewTkt = p->Password = p->RdAddr =
1083
+ p->TktFmt = p->Attach = p->ApndTkt =
1084
+ p->ModWiki = p->ModTkt = 1;
10491085
/* Fall thru into Read/Write */
1050
- case 'i': g.perm.Read = g.perm.Write = 1; break;
1051
- case 'o': g.perm.Read = 1; break;
1052
- case 'z': g.perm.Zip = 1; break;
1053
-
1054
- case 'd': g.perm.Delete = 1; break;
1055
- case 'h': g.perm.Hyperlink = 1; break;
1056
- case 'g': g.perm.Clone = 1; break;
1057
- case 'p': g.perm.Password = 1; break;
1058
-
1059
- case 'j': g.perm.RdWiki = 1; break;
1060
- case 'k': g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1; break;
1061
- case 'm': g.perm.ApndWiki = 1; break;
1062
- case 'f': g.perm.NewWiki = 1; break;
1063
- case 'l': g.perm.ModWiki = 1; break;
1064
-
1065
- case 'e': g.perm.RdAddr = 1; break;
1066
- case 'r': g.perm.RdTkt = 1; break;
1067
- case 'n': g.perm.NewTkt = 1; break;
1068
- case 'w': g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt =
1069
- g.perm.ApndTkt = 1; break;
1070
- case 'c': g.perm.ApndTkt = 1; break;
1071
- case 'q': g.perm.ModTkt = 1; break;
1072
- case 't': g.perm.TktFmt = 1; break;
1073
- case 'b': g.perm.Attach = 1; break;
1074
- case 'x': g.perm.Private = 1; break;
1086
+ case 'i': p->Read = p->Write = 1; break;
1087
+ case 'o': p->Read = 1; break;
1088
+ case 'z': p->Zip = 1; break;
1089
+
1090
+ case 'd': p->Delete = 1; break;
1091
+ case 'h': p->Hyperlink = 1; break;
1092
+ case 'g': p->Clone = 1; break;
1093
+ case 'p': p->Password = 1; break;
1094
+
1095
+ case 'j': p->RdWiki = 1; break;
1096
+ case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break;
1097
+ case 'm': p->ApndWiki = 1; break;
1098
+ case 'f': p->NewWiki = 1; break;
1099
+ case 'l': p->ModWiki = 1; break;
1100
+
1101
+ case 'e': p->RdAddr = 1; break;
1102
+ case 'r': p->RdTkt = 1; break;
1103
+ case 'n': p->NewTkt = 1; break;
1104
+ case 'w': p->WrTkt = p->RdTkt = p->NewTkt =
1105
+ p->ApndTkt = 1; break;
1106
+ case 'c': p->ApndTkt = 1; break;
1107
+ case 'q': p->ModTkt = 1; break;
1108
+ case 't': p->TktFmt = 1; break;
1109
+ case 'b': p->Attach = 1; break;
1110
+ case 'x': p->Private = 1; break;
10751111
10761112
/* The "u" privileges is a little different. It recursively
10771113
** inherits all privileges of the user named "reader" */
10781114
case 'u': {
10791115
if( (flags & LOGIN_IGNORE_UV)==0 ){
@@ -1110,42 +1146,43 @@
11101146
/*
11111147
** If the current login lacks any of the capabilities listed in
11121148
** the input, then return 0. If all capabilities are present, then
11131149
** return 1.
11141150
*/
1115
-int login_has_capability(const char *zCap, int nCap){
1151
+int login_has_capability(const char *zCap, int nCap, u32 flgs){
11161152
int i;
11171153
int rc = 1;
1154
+ FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
11181155
if( nCap<0 ) nCap = strlen(zCap);
11191156
for(i=0; i<nCap && rc && zCap[i]; i++){
11201157
switch( zCap[i] ){
1121
- case 'a': rc = g.perm.Admin; break;
1122
- case 'b': rc = g.perm.Attach; break;
1123
- case 'c': rc = g.perm.ApndTkt; break;
1124
- case 'd': rc = g.perm.Delete; break;
1125
- case 'e': rc = g.perm.RdAddr; break;
1126
- case 'f': rc = g.perm.NewWiki; break;
1127
- case 'g': rc = g.perm.Clone; break;
1128
- case 'h': rc = g.perm.Hyperlink; break;
1129
- case 'i': rc = g.perm.Write; break;
1130
- case 'j': rc = g.perm.RdWiki; break;
1131
- case 'k': rc = g.perm.WrWiki; break;
1132
- case 'l': rc = g.perm.ModWiki; break;
1133
- case 'm': rc = g.perm.ApndWiki; break;
1134
- case 'n': rc = g.perm.NewTkt; break;
1135
- case 'o': rc = g.perm.Read; break;
1136
- case 'p': rc = g.perm.Password; break;
1137
- case 'q': rc = g.perm.ModTkt; break;
1138
- case 'r': rc = g.perm.RdTkt; break;
1139
- case 's': rc = g.perm.Setup; break;
1140
- case 't': rc = g.perm.TktFmt; break;
1158
+ case 'a': rc = p->Admin; break;
1159
+ case 'b': rc = p->Attach; break;
1160
+ case 'c': rc = p->ApndTkt; break;
1161
+ case 'd': rc = p->Delete; break;
1162
+ case 'e': rc = p->RdAddr; break;
1163
+ case 'f': rc = p->NewWiki; break;
1164
+ case 'g': rc = p->Clone; break;
1165
+ case 'h': rc = p->Hyperlink; break;
1166
+ case 'i': rc = p->Write; break;
1167
+ case 'j': rc = p->RdWiki; break;
1168
+ case 'k': rc = p->WrWiki; break;
1169
+ case 'l': rc = p->ModWiki; break;
1170
+ case 'm': rc = p->ApndWiki; break;
1171
+ case 'n': rc = p->NewTkt; break;
1172
+ case 'o': rc = p->Read; break;
1173
+ case 'p': rc = p->Password; break;
1174
+ case 'q': rc = p->ModTkt; break;
1175
+ case 'r': rc = p->RdTkt; break;
1176
+ case 's': rc = p->Setup; break;
1177
+ case 't': rc = p->TktFmt; break;
11411178
/* case 'u': READER */
11421179
/* case 'v': DEVELOPER */
1143
- case 'w': rc = g.perm.WrTkt; break;
1144
- case 'x': rc = g.perm.Private; break;
1180
+ case 'w': rc = p->WrTkt; break;
1181
+ case 'x': rc = p->Private; break;
11451182
/* case 'y': */
1146
- case 'z': rc = g.perm.Zip; break;
1183
+ case 'z': rc = p->Zip; break;
11471184
default: rc = 0; break;
11481185
}
11491186
}
11501187
return rc;
11511188
}
@@ -1195,11 +1232,11 @@
11951232
11961233
/*
11971234
** Call this routine when the credential check fails. It causes
11981235
** a redirect to the "login" page.
11991236
*/
1200
-void login_needed(void){
1237
+void login_needed(int anonOk){
12011238
#ifdef FOSSIL_ENABLE_JSON
12021239
if(g.json.isJsonMode){
12031240
json_err( FSL_JSON_E_DENIED, NULL, 1 );
12041241
fossil_exit(0);
12051242
/* NOTREACHED */
@@ -1206,11 +1243,23 @@
12061243
assert(0);
12071244
}else
12081245
#endif /* FOSSIL_ENABLE_JSON */
12091246
{
12101247
const char *zUrl = PD("REQUEST_URI", "index");
1211
- cgi_redirect(mprintf("login?g=%T", zUrl));
1248
+ const char *zQS = P("QUERY_STRING");
1249
+ Blob redir;
1250
+ blob_init(&redir, 0, 0);
1251
+ if( login_wants_https_redirect() ){
1252
+ blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrl);
1253
+ }else{
1254
+ blob_appendf(&redir, "%R/login?g=%T", zUrl);
1255
+ }
1256
+ if( anonOk ) blob_append(&redir, "&anon", 5);
1257
+ if( zQS && zQS[0] ){
1258
+ blob_appendf(&redir, "&%s", zQS);
1259
+ }
1260
+ cgi_redirect(blob_str(&redir));
12121261
/* NOTREACHED */
12131262
assert(0);
12141263
}
12151264
}
12161265
@@ -1219,17 +1268,14 @@
12191268
** the anonymous user has Hyperlink permission, then paint a mesage
12201269
** to inform the user that much more information is available by
12211270
** logging in as anonymous.
12221271
*/
12231272
void login_anonymous_available(void){
1224
- if( !g.perm.Hyperlink &&
1225
- db_exists("SELECT 1 FROM user"
1226
- " WHERE login='anonymous'"
1227
- " AND cap LIKE '%%h%%'") ){
1273
+ if( !g.perm.Hyperlink && g.anon.Hyperlink ){
12281274
const char *zUrl = PD("REQUEST_URI", "index");
12291275
@ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
1230
- @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
1276
+ @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
12311277
@ to enable hyperlinks.</p>
12321278
}
12331279
}
12341280
12351281
/*
12361282
--- src/login.c
+++ src/login.c
@@ -352,16 +352,12 @@
352 login_cookie_path(), -86400);
353 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
354 " cexpire=0 WHERE uid=%d"
355 " AND login NOT IN ('anonymous','nobody',"
356 " 'developer','reader')", g.userUid);
357 cgi_replace_parameter(cookie, NULL)
358 /* At the time of this writing, cgi_replace_parameter() was
359 ** "NULL-value-safe", and I'm hoping the NULL doesn't cause any
360 ** downstream problems here. We could alternately use "" here.
361 */
362 ;
363 }
364 }
365
366 /*
367 ** Return true if the prefix of zStr matches zPattern. Return false if
@@ -451,27 +447,46 @@
451 }
452 sqlite3_result_int(context, rc);
453 }
454
455 /*
456 ** WEBPAGE: login
457 ** WEBPAGE: logout
458 ** WEBPAGE: my
459 **
460 ** Generate the login page.
461 **
 
 
 
 
 
 
 
 
 
462 ** There used to be a page named "my" that was designed to show information
463 ** about a specific user. The "my" page was linked from the "Logged in as USER"
464 ** line on the title bar. The "my" page was never completed so it is now
465 ** removed. Use this page as a placeholder in older installations.
 
 
 
 
 
 
 
 
 
 
466 */
467 void login_page(void){
468 const char *zUsername, *zPasswd;
469 const char *zNew1, *zNew2;
470 const char *zAnonPw = 0;
471 const char *zGoto = P("g");
472 int anonFlag;
473 char *zErrMsg = "";
474 int uid; /* User id logged in user */
475 char *zSha1Pw;
476 const char *zIpAddr; /* IP address of requestor */
477 const char *zReferer;
@@ -489,15 +504,20 @@
489 }
490 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
491 constant_time_cmp_function, 0, 0);
492 zUsername = P("u");
493 zPasswd = P("p");
494 anonFlag = P("anon")!=0;
495 if( P("out")!=0 ){
 
 
496 login_clear_login_data();
497 redirect_to_g();
 
498 }
 
 
499 if( g.perm.Password && zPasswd
500 && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
501 ){
502 /* The user requests a password change */
503 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
@@ -577,19 +597,40 @@
577 }
578 }
579 style_header("Login/Logout");
580 style_adunit_config(ADUNIT_OFF);
581 @ %s(zErrMsg)
582 if( zGoto && P("anon")==0 ){
583 @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
 
 
 
 
 
 
 
 
 
 
 
 
584 }
585 form_begin(0, "%R/login");
586 if( zGoto ){
587 @ <input type="hidden" name="g" value="%h(zGoto)" />
588 }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
589 @ <input type="hidden" name="g" value="%h(zReferer)" />
590 }
 
 
 
 
 
 
 
 
 
591 @ <table class="login_out">
592 @ <tr>
593 @ <td class="login_out_label">User ID:</td>
594 if( anonFlag ){
595 @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
@@ -599,11 +640,11 @@
599 @ </tr>
600 @ <tr>
601 @ <td class="login_out_label">Password:</td>
602 @ <td><input type="password" id="p" name="p" value="" size="30" /></td>
603 @ </tr>
604 if( g.zLogin==0 ){
605 zAnonPw = db_text(0, "SELECT pw FROM user"
606 " WHERE login='anonymous'"
607 " AND cap!=''");
608 }
609 @ <tr>
@@ -624,23 +665,14 @@
624 @ form.action = "%h(zSSL)/login";
625 @ }
626 }
627 @ }
628 @ </script>
629 if( g.zLogin==0 ){
630 @ <p>Enter
631 }else{
632 @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
633 @ <p>To change your login to a different user, enter
634 }
635 @ your user-id and password at the left and press the
636 @ "Login" button. Your user name will be stored in a browser cookie.
637 @ You must configure your web browser to accept cookies in order for
638 @ the login to take.</p>
639 if( db_get_boolean("self-register", 0) ){
640 @ <p>If you do not have an account, you can
641 @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
642 }
643 if( zAnonPw ){
644 unsigned int uSeed = captcha_seed();
645 const char *zDecoded = captcha_decode(uSeed);
646 int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
@@ -657,22 +689,14 @@
657 @ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
658 }
659 @ </div>
660 free(zCaptcha);
661 }
662 if( g.zLogin ){
663 @ <hr />
664 @ <p>To log off the system (and delete your login cookie)
665 @ press the following button:<br />
666 @ <input type="submit" name="out" value="Logout" /></p>
667 }
668 @ </form>
669 if( g.perm.Password ){
670 @ <hr />
671 @ <p>To change your password, enter your old password and your
672 @ new password twice below then press the "Change Password"
673 @ button.</p>
674 form_begin(0, "%R/login");
675 @ <table>
676 @ <tr><td class="login_out_label">Old Password:</td>
677 @ <td><input type="password" name="p" size="30" /></td></tr>
678 @ <tr><td class="login_out_label">New Password:</td>
@@ -811,10 +835,11 @@
811 ** variables appropriately.
812 **
813 ** g.userUid Database USER.UID value. Might be -1 for "nobody"
814 ** g.zLogin Database USER.LOGIN value. NULL for user "nobody"
815 ** g.perm Permissions granted to this user
 
816 ** g.isHuman True if the user is human, not a spider or robot
817 **
818 */
819 void login_check_credentials(void){
820 int uid = 0; /* User id */
@@ -1002,23 +1027,32 @@
1002 ** Memory of settings
1003 */
1004 static int login_anon_once = 1;
1005
1006 /*
1007 ** Add the default privileges of users "nobody" and "anonymous" as appropriate
1008 ** for the user g.zLogin.
 
 
 
 
1009 */
1010 void login_set_anon_nobody_capabilities(void){
1011 if( g.zLogin && login_anon_once ){
1012 const char *zCap;
1013 /* All logged-in users inherit privileges from "nobody" */
1014 zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
1015 login_set_capabilities(zCap, 0);
1016 if( fossil_strcmp(g.zLogin, "nobody")!=0 ){
 
1017 /* All logged-in users inherit privileges from "anonymous" */
1018 zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
1019 login_set_capabilities(zCap, 0);
 
 
 
 
 
1020 }
1021 login_anon_once = 0;
1022 }
1023 }
1024
@@ -1025,55 +1059,57 @@
1025 /*
1026 ** Flags passed into the 2nd argument of login_set/replace_capabilities().
1027 */
1028 #if INTERFACE
1029 #define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */
 
1030 #endif
1031
1032 /*
1033 ** Adds all capability flags in zCap to g.perm.
1034 */
1035 void login_set_capabilities(const char *zCap, unsigned flags){
1036 int i;
 
1037 if(NULL==zCap){
1038 return;
1039 }
1040 for(i=0; zCap[i]; i++){
1041 switch( zCap[i] ){
1042 case 's': g.perm.Setup = 1; /* Fall thru into Admin */
1043 case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
1044 g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
1045 g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone =
1046 g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
1047 g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt =
1048 g.perm.ModWiki = g.perm.ModTkt = 1;
1049 /* Fall thru into Read/Write */
1050 case 'i': g.perm.Read = g.perm.Write = 1; break;
1051 case 'o': g.perm.Read = 1; break;
1052 case 'z': g.perm.Zip = 1; break;
1053
1054 case 'd': g.perm.Delete = 1; break;
1055 case 'h': g.perm.Hyperlink = 1; break;
1056 case 'g': g.perm.Clone = 1; break;
1057 case 'p': g.perm.Password = 1; break;
1058
1059 case 'j': g.perm.RdWiki = 1; break;
1060 case 'k': g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1; break;
1061 case 'm': g.perm.ApndWiki = 1; break;
1062 case 'f': g.perm.NewWiki = 1; break;
1063 case 'l': g.perm.ModWiki = 1; break;
1064
1065 case 'e': g.perm.RdAddr = 1; break;
1066 case 'r': g.perm.RdTkt = 1; break;
1067 case 'n': g.perm.NewTkt = 1; break;
1068 case 'w': g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt =
1069 g.perm.ApndTkt = 1; break;
1070 case 'c': g.perm.ApndTkt = 1; break;
1071 case 'q': g.perm.ModTkt = 1; break;
1072 case 't': g.perm.TktFmt = 1; break;
1073 case 'b': g.perm.Attach = 1; break;
1074 case 'x': g.perm.Private = 1; break;
1075
1076 /* The "u" privileges is a little different. It recursively
1077 ** inherits all privileges of the user named "reader" */
1078 case 'u': {
1079 if( (flags & LOGIN_IGNORE_UV)==0 ){
@@ -1110,42 +1146,43 @@
1110 /*
1111 ** If the current login lacks any of the capabilities listed in
1112 ** the input, then return 0. If all capabilities are present, then
1113 ** return 1.
1114 */
1115 int login_has_capability(const char *zCap, int nCap){
1116 int i;
1117 int rc = 1;
 
1118 if( nCap<0 ) nCap = strlen(zCap);
1119 for(i=0; i<nCap && rc && zCap[i]; i++){
1120 switch( zCap[i] ){
1121 case 'a': rc = g.perm.Admin; break;
1122 case 'b': rc = g.perm.Attach; break;
1123 case 'c': rc = g.perm.ApndTkt; break;
1124 case 'd': rc = g.perm.Delete; break;
1125 case 'e': rc = g.perm.RdAddr; break;
1126 case 'f': rc = g.perm.NewWiki; break;
1127 case 'g': rc = g.perm.Clone; break;
1128 case 'h': rc = g.perm.Hyperlink; break;
1129 case 'i': rc = g.perm.Write; break;
1130 case 'j': rc = g.perm.RdWiki; break;
1131 case 'k': rc = g.perm.WrWiki; break;
1132 case 'l': rc = g.perm.ModWiki; break;
1133 case 'm': rc = g.perm.ApndWiki; break;
1134 case 'n': rc = g.perm.NewTkt; break;
1135 case 'o': rc = g.perm.Read; break;
1136 case 'p': rc = g.perm.Password; break;
1137 case 'q': rc = g.perm.ModTkt; break;
1138 case 'r': rc = g.perm.RdTkt; break;
1139 case 's': rc = g.perm.Setup; break;
1140 case 't': rc = g.perm.TktFmt; break;
1141 /* case 'u': READER */
1142 /* case 'v': DEVELOPER */
1143 case 'w': rc = g.perm.WrTkt; break;
1144 case 'x': rc = g.perm.Private; break;
1145 /* case 'y': */
1146 case 'z': rc = g.perm.Zip; break;
1147 default: rc = 0; break;
1148 }
1149 }
1150 return rc;
1151 }
@@ -1195,11 +1232,11 @@
1195
1196 /*
1197 ** Call this routine when the credential check fails. It causes
1198 ** a redirect to the "login" page.
1199 */
1200 void login_needed(void){
1201 #ifdef FOSSIL_ENABLE_JSON
1202 if(g.json.isJsonMode){
1203 json_err( FSL_JSON_E_DENIED, NULL, 1 );
1204 fossil_exit(0);
1205 /* NOTREACHED */
@@ -1206,11 +1243,23 @@
1206 assert(0);
1207 }else
1208 #endif /* FOSSIL_ENABLE_JSON */
1209 {
1210 const char *zUrl = PD("REQUEST_URI", "index");
1211 cgi_redirect(mprintf("login?g=%T", zUrl));
 
 
 
 
 
 
 
 
 
 
 
 
1212 /* NOTREACHED */
1213 assert(0);
1214 }
1215 }
1216
@@ -1219,17 +1268,14 @@
1219 ** the anonymous user has Hyperlink permission, then paint a mesage
1220 ** to inform the user that much more information is available by
1221 ** logging in as anonymous.
1222 */
1223 void login_anonymous_available(void){
1224 if( !g.perm.Hyperlink &&
1225 db_exists("SELECT 1 FROM user"
1226 " WHERE login='anonymous'"
1227 " AND cap LIKE '%%h%%'") ){
1228 const char *zUrl = PD("REQUEST_URI", "index");
1229 @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
1230 @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
1231 @ to enable hyperlinks.</p>
1232 }
1233 }
1234
1235 /*
1236
--- src/login.c
+++ src/login.c
@@ -352,16 +352,12 @@
352 login_cookie_path(), -86400);
353 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
354 " cexpire=0 WHERE uid=%d"
355 " AND login NOT IN ('anonymous','nobody',"
356 " 'developer','reader')", g.userUid);
357 cgi_replace_parameter(cookie, NULL);
358 cgi_replace_parameter("anon", NULL);
 
 
 
 
359 }
360 }
361
362 /*
363 ** Return true if the prefix of zStr matches zPattern. Return false if
@@ -451,27 +447,46 @@
447 }
448 sqlite3_result_int(context, rc);
449 }
450
451 /*
452 ** Return true if the current page was reached by a redirect from the /login
453 ** page.
454 */
455 int referred_from_login(void){
456 const char *zReferer = P("HTTP_REFERER");
457 char *zPattern;
458 int rc;
459 if( zReferer==0 ) return 0;
460 zPattern = mprintf("%s/login*", g.zBaseURL);
461 rc = sqlite3_strglob(zPattern, zReferer)==0;
462 fossil_free(zPattern);
463 return rc;
464 }
465
466 /*
467 ** There used to be a page named "my" that was designed to show information
468 ** about a specific user. The "my" page was linked from the "Logged in as USER"
469 ** line on the title bar. The "my" page was never completed so it is now
470 ** removed. Use this page as a placeholder in older installations.
471 **
472 ** WEBPAGE: login
473 ** WEBPAGE: logout
474 ** WEBPAGE: my
475 **
476 ** The login/logout page. Parameters:
477 **
478 ** g=URL Jump back to this URL after login completes
479 ** anon The g=URL is not accessible by "nobody" but is
480 ** accessible by "anonymous"
481 */
482 void login_page(void){
483 const char *zUsername, *zPasswd;
484 const char *zNew1, *zNew2;
485 const char *zAnonPw = 0;
486 const char *zGoto = P("g");
487 int anonFlag; /* Login as "anonymous" would be useful */
488 char *zErrMsg = "";
489 int uid; /* User id logged in user */
490 char *zSha1Pw;
491 const char *zIpAddr; /* IP address of requestor */
492 const char *zReferer;
@@ -489,15 +504,20 @@
504 }
505 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
506 constant_time_cmp_function, 0, 0);
507 zUsername = P("u");
508 zPasswd = P("p");
509 anonFlag = g.zLogin==0 && PB("anon");
510
511 /* Handle log-out requests */
512 if( P("out") ){
513 login_clear_login_data();
514 redirect_to_g();
515 return;
516 }
517
518 /* Deal with password-change requests */
519 if( g.perm.Password && zPasswd
520 && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
521 ){
522 /* The user requests a password change */
523 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
@@ -577,19 +597,40 @@
597 }
598 }
599 style_header("Login/Logout");
600 style_adunit_config(ADUNIT_OFF);
601 @ %s(zErrMsg)
602 if( zGoto ){
603 char *zAbbrev = fossil_strdup(zGoto);
604 int i;
605 for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
606 zAbbrev[i] = 0;
607 if( g.zLogin ){
608 @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
609 @ to access <b>%h(zAbbrev)</b>.
610 }else if( anonFlag ){
611 @ <p>Login as <b>anonymous</b> or any named user
612 @ to access page <b>%h(zAbbrev)</b>.
613 }else{
614 @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
615 }
616 }
617 form_begin(0, "%R/login");
618 if( zGoto ){
619 @ <input type="hidden" name="g" value="%h(zGoto)" />
620 }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
621 @ <input type="hidden" name="g" value="%h(zReferer)" />
622 }
623 if( anonFlag ){
624 @ <input type="hidden" name="anon" value="1" />
625 }
626 if( g.zLogin ){
627 @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
628 @ <input type="submit" name="out" value="Logout"></p>
629 @ <hr />
630 @ <p>Change user:
631 }
632 @ <table class="login_out">
633 @ <tr>
634 @ <td class="login_out_label">User ID:</td>
635 if( anonFlag ){
636 @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
@@ -599,11 +640,11 @@
640 @ </tr>
641 @ <tr>
642 @ <td class="login_out_label">Password:</td>
643 @ <td><input type="password" id="p" name="p" value="" size="30" /></td>
644 @ </tr>
645 if( g.zLogin==0 && (anonFlag || zGoto==0) ){
646 zAnonPw = db_text(0, "SELECT pw FROM user"
647 " WHERE login='anonymous'"
648 " AND cap!=''");
649 }
650 @ <tr>
@@ -624,23 +665,14 @@
665 @ form.action = "%h(zSSL)/login";
666 @ }
667 }
668 @ }
669 @ </script>
670 @ <p>Pressing the Login button grants permission to store a cookie.</p>
 
 
 
 
 
 
 
 
 
671 if( db_get_boolean("self-register", 0) ){
672 @ <p>If you do not have an account, you can
673 @ <a href="%R/register?g=%T(P("G"))">create one</a>.
674 }
675 if( zAnonPw ){
676 unsigned int uSeed = captcha_seed();
677 const char *zDecoded = captcha_decode(uSeed);
678 int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
@@ -657,22 +689,14 @@
689 @ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
690 }
691 @ </div>
692 free(zCaptcha);
693 }
 
 
 
 
 
 
694 @ </form>
695 if( g.perm.Password ){
696 @ <hr />
697 @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
 
 
698 form_begin(0, "%R/login");
699 @ <table>
700 @ <tr><td class="login_out_label">Old Password:</td>
701 @ <td><input type="password" name="p" size="30" /></td></tr>
702 @ <tr><td class="login_out_label">New Password:</td>
@@ -811,10 +835,11 @@
835 ** variables appropriately.
836 **
837 ** g.userUid Database USER.UID value. Might be -1 for "nobody"
838 ** g.zLogin Database USER.LOGIN value. NULL for user "nobody"
839 ** g.perm Permissions granted to this user
840 ** g.anon Permissions that would be available to anonymous
841 ** g.isHuman True if the user is human, not a spider or robot
842 **
843 */
844 void login_check_credentials(void){
845 int uid = 0; /* User id */
@@ -1002,23 +1027,32 @@
1027 ** Memory of settings
1028 */
1029 static int login_anon_once = 1;
1030
1031 /*
1032 ** Add to g.perm the default privileges of users "nobody" and/or "anonymous"
1033 ** as appropriate for the user g.zLogin.
1034 **
1035 ** This routine also sets up g.anon to be either a copy of g.perm for
1036 ** all logged in uses, or the privileges that would be available to "anonymous"
1037 ** if g.zLogin==0 (meaning that the user is "nobody").
1038 */
1039 void login_set_anon_nobody_capabilities(void){
1040 if( login_anon_once ){
1041 const char *zCap;
1042 /* All users get privileges from "nobody" */
1043 zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
1044 login_set_capabilities(zCap, 0);
1045 zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
1046 if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){
1047 /* All logged-in users inherit privileges from "anonymous" */
 
1048 login_set_capabilities(zCap, 0);
1049 g.anon = g.perm;
1050 }else{
1051 /* Record the privileges of anonymous in g.anon */
1052 g.anon = g.perm;
1053 login_set_capabilities(zCap, LOGIN_ANON);
1054 }
1055 login_anon_once = 0;
1056 }
1057 }
1058
@@ -1025,55 +1059,57 @@
1059 /*
1060 ** Flags passed into the 2nd argument of login_set/replace_capabilities().
1061 */
1062 #if INTERFACE
1063 #define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */
1064 #define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */
1065 #endif
1066
1067 /*
1068 ** Adds all capability flags in zCap to g.perm or g.anon.
1069 */
1070 void login_set_capabilities(const char *zCap, unsigned flags){
1071 int i;
1072 FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
1073 if(NULL==zCap){
1074 return;
1075 }
1076 for(i=0; zCap[i]; i++){
1077 switch( zCap[i] ){
1078 case 's': p->Setup = 1; /* Fall thru into Admin */
1079 case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
1080 p->RdWiki = p->WrWiki = p->NewWiki =
1081 p->ApndWiki = p->Hyperlink = p->Clone =
1082 p->NewTkt = p->Password = p->RdAddr =
1083 p->TktFmt = p->Attach = p->ApndTkt =
1084 p->ModWiki = p->ModTkt = 1;
1085 /* Fall thru into Read/Write */
1086 case 'i': p->Read = p->Write = 1; break;
1087 case 'o': p->Read = 1; break;
1088 case 'z': p->Zip = 1; break;
1089
1090 case 'd': p->Delete = 1; break;
1091 case 'h': p->Hyperlink = 1; break;
1092 case 'g': p->Clone = 1; break;
1093 case 'p': p->Password = 1; break;
1094
1095 case 'j': p->RdWiki = 1; break;
1096 case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break;
1097 case 'm': p->ApndWiki = 1; break;
1098 case 'f': p->NewWiki = 1; break;
1099 case 'l': p->ModWiki = 1; break;
1100
1101 case 'e': p->RdAddr = 1; break;
1102 case 'r': p->RdTkt = 1; break;
1103 case 'n': p->NewTkt = 1; break;
1104 case 'w': p->WrTkt = p->RdTkt = p->NewTkt =
1105 p->ApndTkt = 1; break;
1106 case 'c': p->ApndTkt = 1; break;
1107 case 'q': p->ModTkt = 1; break;
1108 case 't': p->TktFmt = 1; break;
1109 case 'b': p->Attach = 1; break;
1110 case 'x': p->Private = 1; break;
1111
1112 /* The "u" privileges is a little different. It recursively
1113 ** inherits all privileges of the user named "reader" */
1114 case 'u': {
1115 if( (flags & LOGIN_IGNORE_UV)==0 ){
@@ -1110,42 +1146,43 @@
1146 /*
1147 ** If the current login lacks any of the capabilities listed in
1148 ** the input, then return 0. If all capabilities are present, then
1149 ** return 1.
1150 */
1151 int login_has_capability(const char *zCap, int nCap, u32 flgs){
1152 int i;
1153 int rc = 1;
1154 FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
1155 if( nCap<0 ) nCap = strlen(zCap);
1156 for(i=0; i<nCap && rc && zCap[i]; i++){
1157 switch( zCap[i] ){
1158 case 'a': rc = p->Admin; break;
1159 case 'b': rc = p->Attach; break;
1160 case 'c': rc = p->ApndTkt; break;
1161 case 'd': rc = p->Delete; break;
1162 case 'e': rc = p->RdAddr; break;
1163 case 'f': rc = p->NewWiki; break;
1164 case 'g': rc = p->Clone; break;
1165 case 'h': rc = p->Hyperlink; break;
1166 case 'i': rc = p->Write; break;
1167 case 'j': rc = p->RdWiki; break;
1168 case 'k': rc = p->WrWiki; break;
1169 case 'l': rc = p->ModWiki; break;
1170 case 'm': rc = p->ApndWiki; break;
1171 case 'n': rc = p->NewTkt; break;
1172 case 'o': rc = p->Read; break;
1173 case 'p': rc = p->Password; break;
1174 case 'q': rc = p->ModTkt; break;
1175 case 'r': rc = p->RdTkt; break;
1176 case 's': rc = p->Setup; break;
1177 case 't': rc = p->TktFmt; break;
1178 /* case 'u': READER */
1179 /* case 'v': DEVELOPER */
1180 case 'w': rc = p->WrTkt; break;
1181 case 'x': rc = p->Private; break;
1182 /* case 'y': */
1183 case 'z': rc = p->Zip; break;
1184 default: rc = 0; break;
1185 }
1186 }
1187 return rc;
1188 }
@@ -1195,11 +1232,11 @@
1232
1233 /*
1234 ** Call this routine when the credential check fails. It causes
1235 ** a redirect to the "login" page.
1236 */
1237 void login_needed(int anonOk){
1238 #ifdef FOSSIL_ENABLE_JSON
1239 if(g.json.isJsonMode){
1240 json_err( FSL_JSON_E_DENIED, NULL, 1 );
1241 fossil_exit(0);
1242 /* NOTREACHED */
@@ -1206,11 +1243,23 @@
1243 assert(0);
1244 }else
1245 #endif /* FOSSIL_ENABLE_JSON */
1246 {
1247 const char *zUrl = PD("REQUEST_URI", "index");
1248 const char *zQS = P("QUERY_STRING");
1249 Blob redir;
1250 blob_init(&redir, 0, 0);
1251 if( login_wants_https_redirect() ){
1252 blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrl);
1253 }else{
1254 blob_appendf(&redir, "%R/login?g=%T", zUrl);
1255 }
1256 if( anonOk ) blob_append(&redir, "&anon", 5);
1257 if( zQS && zQS[0] ){
1258 blob_appendf(&redir, "&%s", zQS);
1259 }
1260 cgi_redirect(blob_str(&redir));
1261 /* NOTREACHED */
1262 assert(0);
1263 }
1264 }
1265
@@ -1219,17 +1268,14 @@
1268 ** the anonymous user has Hyperlink permission, then paint a mesage
1269 ** to inform the user that much more information is available by
1270 ** logging in as anonymous.
1271 */
1272 void login_anonymous_available(void){
1273 if( !g.perm.Hyperlink && g.anon.Hyperlink ){
 
 
 
1274 const char *zUrl = PD("REQUEST_URI", "index");
1275 @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
1276 @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
1277 @ to enable hyperlinks.</p>
1278 }
1279 }
1280
1281 /*
1282
+145 -52
--- src/main.c
+++ src/main.c
@@ -193,12 +193,17 @@
193193
/* Information used to populate the RCVFROM table */
194194
int rcvid; /* The rcvid. 0 if not yet defined. */
195195
char *zIpAddr; /* The remote IP address */
196196
char *zNonce; /* The nonce used for login */
197197
198
- /* permissions used by the server */
198
+ /* permissions available to current user */
199199
struct FossilUserPerms perm;
200
+
201
+ /* permissions available to current user or to "anonymous".
202
+ ** This is the logical union of perm permissions above with
203
+ ** the value that perm would take if g.zLogin were "anonymous". */
204
+ struct FossilUserPerms anon;
200205
201206
#ifdef FOSSIL_ENABLE_TCL
202207
/* all Tcl related context necessary for integration */
203208
struct TclContext tcl;
204209
#endif
@@ -1173,11 +1178,11 @@
11731178
const char *z = aCommand[i].zName;
11741179
if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
11751180
if( j==0 ){
11761181
@ <td valign="top"><ul>
11771182
}
1178
- @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1183
+ @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
11791184
j++;
11801185
if( j>=n ){
11811186
@ </ul></td>
11821187
j = 0;
11831188
}
@@ -1201,11 +1206,11 @@
12011206
if( '/'!=*z ) continue;
12021207
if( j==0 ){
12031208
@ <td valign="top"><ul>
12041209
}
12051210
if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1206
- @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z+1)</a></li>
1211
+ @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
12071212
}else{
12081213
@ <li>%s(z+1)</li>
12091214
}
12101215
j++;
12111216
if( j>=n ){
@@ -1231,11 +1236,11 @@
12311236
if( strncmp(z,"test",4)!=0 ) continue;
12321237
if( j==0 ){
12331238
@ <td valign="top"><ul>
12341239
}
12351240
if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1236
- @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1241
+ @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
12371242
}else{
12381243
@ <li>%s(z)</li>
12391244
}
12401245
j++;
12411246
if( j>=n ){
@@ -1352,12 +1357,15 @@
13521357
** zRepo might be a directory itself. In that case chroot into
13531358
** the directory zRepo.
13541359
**
13551360
** Assume the user-id and group-id of the repository, or if zRepo
13561361
** is a directory, of that directory.
1362
+**
1363
+** The noJail flag means that the chroot jail is not entered. But
1364
+** privileges are still lowered to that of the the user-id and group-id.
13571365
*/
1358
-static char *enter_chroot_jail(char *zRepo){
1366
+static char *enter_chroot_jail(char *zRepo, int noJail){
13591367
#if !defined(_WIN32)
13601368
if( getuid()==0 ){
13611369
int i;
13621370
struct stat sStat;
13631371
Blob dir;
@@ -1366,26 +1374,28 @@
13661374
db_close(1);
13671375
}
13681376
13691377
file_canonical_name(zRepo, &dir, 0);
13701378
zDir = blob_str(&dir);
1371
- if( file_isdir(zDir)==1 ){
1372
- if( file_chdir(zDir, 1) ){
1373
- fossil_fatal("unable to chroot into %s", zDir);
1374
- }
1375
- zRepo = "/";
1376
- }else{
1377
- for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1378
- if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1379
- if( i>0 ){
1380
- zDir[i] = 0;
1379
+ if( !noJail ){
1380
+ if( file_isdir(zDir)==1 ){
13811381
if( file_chdir(zDir, 1) ){
13821382
fossil_fatal("unable to chroot into %s", zDir);
13831383
}
1384
- zDir[i] = '/';
1384
+ zRepo = "/";
1385
+ }else{
1386
+ for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1387
+ if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1388
+ if( i>0 ){
1389
+ zDir[i] = 0;
1390
+ if( file_chdir(zDir, 1) ){
1391
+ fossil_fatal("unable to chroot into %s", zDir);
1392
+ }
1393
+ zDir[i] = '/';
1394
+ }
1395
+ zRepo = &zDir[i];
13851396
}
1386
- zRepo = &zDir[i];
13871397
}
13881398
if( stat(zRepo, &sStat)!=0 ){
13891399
fossil_fatal("cannot stat() repository: %s", zRepo);
13901400
}
13911401
i = setgid(sStat.st_gid);
@@ -1398,10 +1408,48 @@
13981408
}
13991409
}
14001410
#endif
14011411
return zRepo;
14021412
}
1413
+
1414
+/*
1415
+** Generate a web-page that lists all repositories located under the
1416
+** g.zRepositoryName directory and return non-zero.
1417
+**
1418
+** Or, if no repositories can be located beneath g.zRepositoryName,
1419
+** return 0.
1420
+*/
1421
+static int repo_list_page(void){
1422
+ Blob base;
1423
+ int n = 0;
1424
+
1425
+ assert( g.db==0 );
1426
+ blob_init(&base, g.zRepositoryName, -1);
1427
+ sqlite3_open(":memory:", &g.db);
1428
+ db_multi_exec("CREATE TABLE sfile(x TEXT);");
1429
+ db_multi_exec("CREATE TABLE vfile(pathname);");
1430
+ vfile_scan(&base, blob_size(&base), 0, 0, 0);
1431
+ db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*.fossil'");
1432
+ n = db_int(0, "SELECT count(*) FROM sfile");
1433
+ if( n>0 ){
1434
+ Stmt q;
1435
+ @ <h1>Available Repositories:</h1>
1436
+ @ <ol>
1437
+ db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'"
1438
+ " FROM sfile ORDER BY x COLLATE nocase;");
1439
+ while( db_step(&q)==SQLITE_ROW ){
1440
+ const char *zName = db_column_text(&q, 0);
1441
+ const char *zUrl = db_column_text(&q, 1);
1442
+ @ <li><a href="%h(zUrl)">%h(zName)</a></li>
1443
+ }
1444
+ @ </ol>
1445
+ cgi_reply();
1446
+ }
1447
+ sqlite3_close(g.db);
1448
+ g.db = 0;
1449
+ return n;
1450
+}
14031451
14041452
/*
14051453
** Preconditions:
14061454
**
14071455
** * Environment variables are set up according to the CGI standard.
@@ -1421,11 +1469,15 @@
14211469
** $prefix can be determined from its suffix, then the file $prefix is
14221470
** returned as static text.
14231471
**
14241472
** If no suitable webpage is found, try to redirect to zNotFound.
14251473
*/
1426
-static void process_one_web_page(const char *zNotFound, Glob *pFileGlob){
1474
+static void process_one_web_page(
1475
+ const char *zNotFound, /* Redirect here on a 404 if not NULL */
1476
+ Glob *pFileGlob, /* Deliver static files matching */
1477
+ int allowRepoList /* Send repo list for "/" URL */
1478
+){
14271479
const char *zPathInfo;
14281480
char *zPath = NULL;
14291481
int idx;
14301482
int i;
14311483
@@ -1496,10 +1548,14 @@
14961548
14971549
if( szFile<1024 ){
14981550
set_base_url(0);
14991551
if( zNotFound ){
15001552
cgi_redirect(zNotFound);
1553
+ }else if( strcmp(zPathInfo,"/")==0
1554
+ && allowRepoList
1555
+ && repo_list_page() ){
1556
+ /* Will return a list of repositories */
15011557
}else{
15021558
#ifdef FOSSIL_ENABLE_JSON
15031559
if(g.json.isJsonMode){
15041560
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
15051561
return;
@@ -1795,10 +1851,11 @@
17951851
const char *zFile;
17961852
const char *zNotFound = 0;
17971853
char **azRedirect = 0; /* List of repositories to redirect to */
17981854
int nRedirect = 0; /* Number of entries in azRedirect */
17991855
Glob *pFileGlob = 0; /* Pattern for files */
1856
+ int allowRepoList = 0; /* Allow lists of repository files */
18001857
Blob config, line, key, value, value2;
18011858
if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
18021859
zFile = g.argv[2];
18031860
}else{
18041861
zFile = g.argv[1];
@@ -1852,10 +1909,19 @@
18521909
** Grant "administrator" privileges to users connecting with HTTP
18531910
** from IP address 127.0.0.1. Do not bother checking credentials.
18541911
*/
18551912
g.useLocalauth = 1;
18561913
continue;
1914
+ }
1915
+ if( blob_eq(&key, "repolist") ){
1916
+ /* repolist
1917
+ **
1918
+ ** If using "directory:" and the URL is "/" then generate a page
1919
+ ** showing a list of available repositories.
1920
+ */
1921
+ allowRepoList = 1;
1922
+ continue;
18571923
}
18581924
if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
18591925
&& blob_token(&line, &value2) ){
18601926
/* See the header comment on the redirect_web_page() function
18611927
** above for details. */
@@ -1921,20 +1987,32 @@
19211987
*/
19221988
cgi_setenv("HOME", blob_str(&value));
19231989
blob_reset(&value);
19241990
continue;
19251991
}
1992
+ if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
1993
+ /* skin: LABEL
1994
+ **
1995
+ ** Use one of the built-in skins defined by LABEL. LABEL is the
1996
+ ** name of the subdirectory under the skins/ directory that holds
1997
+ ** the elements of the built-in skin. If LABEL does not match,
1998
+ ** this directive is a silent no-op.
1999
+ */
2000
+ skin_use_alternative(blob_str(&value));
2001
+ blob_reset(&value);
2002
+ continue;
2003
+ }
19262004
}
19272005
blob_reset(&config);
19282006
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
19292007
cgi_panic("Unable to find or open the project repository");
19302008
}
19312009
cgi_init();
19322010
if( nRedirect ){
19332011
redirect_web_page(nRedirect, azRedirect);
19342012
}else{
1935
- process_one_web_page(zNotFound, pFileGlob);
2013
+ process_one_web_page(zNotFound, pFileGlob, allowRepoList);
19362014
}
19372015
}
19382016
19392017
/*
19402018
** If g.argv[arg] exists then it is either the name of a repository
@@ -1948,24 +2026,17 @@
19482026
** Open the repository to be served if it is known. If g.argv[arg] is
19492027
** a directory full of repositories, then set g.zRepositoryName to
19502028
** the name of that directory and the specific repository will be
19512029
** opened later by process_one_web_page() based on the content of
19522030
** the PATH_INFO variable.
1953
-**
1954
-** If disallowDir is set, then the directory full of repositories method
1955
-** is disallowed.
19562031
*/
1957
-static void find_server_repository(int disallowDir, int arg){
2032
+static void find_server_repository(int arg){
19582033
if( g.argc<=arg ){
19592034
db_must_be_within_tree();
19602035
}else if( file_isdir(g.argv[arg])==1 ){
1961
- if( disallowDir ){
1962
- fossil_fatal("\"%s\" is a directory, not a repository file", g.argv[arg]);
1963
- }else{
1964
- g.zRepositoryName = mprintf("%s", g.argv[arg]);
1965
- file_simplify_name(g.zRepositoryName, -1, 0);
1966
- }
2036
+ g.zRepositoryName = mprintf("%s", g.argv[arg]);
2037
+ file_simplify_name(g.zRepositoryName, -1, 0);
19672038
}else{
19682039
db_open_repository(g.argv[arg]);
19692040
}
19702041
}
19712042
@@ -2006,18 +2077,21 @@
20062077
** If the --localauth option is given, then automatic login is performed
20072078
** for requests coming from localhost, if the "localauth" setting is not
20082079
** enabled.
20092080
**
20102081
** Options:
2082
+** --baseurl URL base URL (useful with reverse proxies)
2083
+** --files GLOB comma-separate glob patterns for static file to serve
20112084
** --localauth enable automatic login for local connections
20122085
** --host NAME specify hostname of the server
20132086
** --https signal a request coming in via https
2087
+** --nojail drop root privilege but do not enter the chroot jail
20142088
** --nossl signal that no SSL connections are available
20152089
** --notfound URL use URL as "HTTP 404, object not found" page.
2016
-** --files GLOB comma-separate glob patterns for static file to serve
2017
-** --baseurl URL base URL (useful with reverse proxies)
2090
+** --repolist If REPOSITORY is directory, URL "/" lists all repos
20182091
** --scgi Interpret input as SCGI rather than HTTP
2092
+** --skin LABEL Use override skin LABEL
20192093
**
20202094
** See also: cgi, server, winsrv
20212095
*/
20222096
void cmd_http(void){
20232097
const char *zIpAddr = 0;
@@ -2024,10 +2098,12 @@
20242098
const char *zNotFound;
20252099
const char *zHost;
20262100
const char *zAltBase;
20272101
const char *zFileGlob;
20282102
int useSCGI;
2103
+ int noJail;
2104
+ int allowRepoList;
20292105
20302106
/* The winhttp module passes the --files option as --files-urlenc with
20312107
** the argument being URL encoded, to avoid wildcard expansion in the
20322108
** shell. This option is for internal use and is undocumented.
20332109
*/
@@ -2037,11 +2113,14 @@
20372113
dehttpize(z);
20382114
zFileGlob = z;
20392115
}else{
20402116
zFileGlob = find_option("files",0,1);
20412117
}
2118
+ skin_override();
20422119
zNotFound = find_option("notfound", 0, 1);
2120
+ noJail = find_option("nojail",0,0)!=0;
2121
+ allowRepoList = find_option("repolist",0,0)!=0;
20432122
g.useLocalauth = find_option("localauth", 0, 0)!=0;
20442123
g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
20452124
useSCGI = find_option("scgi", 0, 0)!=0;
20462125
zAltBase = find_option("baseurl", 0, 1);
20472126
if( zAltBase ) set_base_url(zAltBase);
@@ -2062,41 +2141,41 @@
20622141
g.fullHttpReply = 1;
20632142
if( g.argc>=5 ){
20642143
g.httpIn = fossil_fopen(g.argv[2], "rb");
20652144
g.httpOut = fossil_fopen(g.argv[3], "wb");
20662145
zIpAddr = g.argv[4];
2067
- find_server_repository(0, 5);
2146
+ find_server_repository(5);
20682147
}else{
20692148
g.httpIn = stdin;
20702149
g.httpOut = stdout;
2071
- find_server_repository(0, 2);
2150
+ find_server_repository(2);
20722151
}
20732152
if( zIpAddr==0 ){
20742153
zIpAddr = cgi_ssh_remote_addr(0);
20752154
if( zIpAddr && zIpAddr[0] ){
20762155
g.fSshClient |= CGI_SSH_CLIENT;
20772156
}
20782157
}
2079
- g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2158
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
20802159
if( useSCGI ){
20812160
cgi_handle_scgi_request();
20822161
}else if( g.fSshClient & CGI_SSH_CLIENT ){
20832162
ssh_request_loop(zIpAddr, glob_create(zFileGlob));
20842163
}else{
20852164
cgi_handle_http_request(zIpAddr);
20862165
}
2087
- process_one_web_page(zNotFound, glob_create(zFileGlob));
2166
+ process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
20882167
}
20892168
20902169
/*
20912170
** Process all requests in a single SSH connection if possible.
20922171
*/
20932172
void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
20942173
blob_zero(&g.cgiIn);
20952174
do{
20962175
cgi_handle_ssh_http_request(zIpAddr);
2097
- process_one_web_page(0, FileGlob);
2176
+ process_one_web_page(0, FileGlob, 0);
20982177
blob_reset(&g.cgiIn);
20992178
} while ( g.fSshClient & CGI_SSH_FOSSIL ||
21002179
g.fSshClient & CGI_SSH_COMPAT );
21012180
}
21022181
@@ -2113,21 +2192,21 @@
21132192
Th_InitTraceLog();
21142193
login_set_capabilities("sx", 0);
21152194
g.useLocalauth = 1;
21162195
g.httpIn = stdin;
21172196
g.httpOut = stdout;
2118
- find_server_repository(0, 2);
2197
+ find_server_repository(2);
21192198
g.cgiOutput = 1;
21202199
g.fullHttpReply = 1;
21212200
zIpAddr = cgi_ssh_remote_addr(0);
21222201
if( zIpAddr && zIpAddr[0] ){
21232202
g.fSshClient |= CGI_SSH_CLIENT;
21242203
ssh_request_loop(zIpAddr, 0);
21252204
}else{
21262205
cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
21272206
cgi_handle_http_request(0);
2128
- process_one_web_page(0, 0);
2207
+ process_one_web_page(0, 0, 0);
21292208
}
21302209
}
21312210
21322211
#if !defined(_WIN32)
21332212
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
@@ -2184,26 +2263,29 @@
21842263
** "*.fossil*" will be served as static content. With the "ui" command,
21852264
** the REPOSITORY can only be a directory if the --notfound option is
21862265
** also present.
21872266
**
21882267
** By default, the "ui" command provides full administrative access without
2189
-** having to log in. This can be disabled by setting turning off the
2190
-** "localauth" setting. Automatic login for the "server" command is available
2191
-** if the --localauth option is present and the "localauth" setting is off
2192
-** and the connection is from localhost. The optional REPOSITORY argument
2193
-** to "ui" may be a directory and will function as "server" if and only if
2194
-** the --notfound option is used.
2268
+** having to log in. This can be disabled by turning off the "localauth"
2269
+** setting. Automatic login for the "server" command is available if the
2270
+** --localauth option is present and the "localauth" setting is off and the
2271
+** connection is from localhost. The "ui" command also enables --repolist
2272
+** by default.
21952273
**
21962274
** Options:
2275
+** --baseurl URL Use URL as the base (useful for reverse proxies)
2276
+** --files GLOBLIST Comma-separated list of glob patterns for static files
21972277
** --localauth enable automatic login for requests from localhost
21982278
** --localhost listen on 127.0.0.1 only (always true for "ui")
2279
+** --nojail Drop root privileges but do not enter the chroot jail
2280
+** --notfound URL Redirect
21992281
** -P|--port TCPPORT listen to request on port TCPPORT
22002282
** --th-trace trace TH1 execution (for debugging purposes)
2201
-** --baseurl URL Use URL as the base (useful for reverse proxies)
2202
-** --notfound URL Redirect
2203
-** --files GLOBLIST Comma-separated list of glob patterns for static files
2283
+** --repolist If REPOSITORY is dir, URL "/" lists repos.
22042284
** --scgi Accept SCGI rather than HTTP
2285
+** --skin LABEL Use override skin LABEL
2286
+
22052287
**
22062288
** See also: cgi, http, winsrv
22072289
*/
22082290
void cmd_webserver(void){
22092291
int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2211,10 +2293,14 @@
22112293
const char *zBrowser; /* Name of web browser program */
22122294
char *zBrowserCmd = 0; /* Command to launch the web browser */
22132295
int isUiCmd; /* True if command is "ui", not "server' */
22142296
const char *zNotFound; /* The --notfound option or NULL */
22152297
int flags = 0; /* Server flags */
2298
+#if !defined(_WIN32)
2299
+ int noJail; /* Do not enter the chroot jail */
2300
+#endif
2301
+ int allowRepoList; /* List repositories on URL "/" */
22162302
const char *zAltBase; /* Argument to the --baseurl option */
22172303
const char *zFileGlob; /* Static content must match this */
22182304
char *zIpAddr = 0; /* Bind to this IP address */
22192305
22202306
#if defined(_WIN32)
@@ -2228,14 +2314,19 @@
22282314
dehttpize(z);
22292315
zFileGlob = z;
22302316
}else{
22312317
zFileGlob = find_option("files",0,1);
22322318
}
2319
+ skin_override();
2320
+#if !defined(_WIN32)
2321
+ noJail = find_option("nojail",0,0)!=0;
2322
+#endif
22332323
g.useLocalauth = find_option("localauth", 0, 0)!=0;
22342324
Th_InitTraceLog();
22352325
zPort = find_option("port", "P", 1);
22362326
zNotFound = find_option("notfound", 0, 1);
2327
+ allowRepoList = find_option("repolist",0,0)!=0;
22372328
zAltBase = find_option("baseurl", 0, 1);
22382329
if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
22392330
if( zAltBase ){
22402331
set_base_url(zAltBase);
22412332
}
@@ -2247,14 +2338,15 @@
22472338
verify_all_options();
22482339
22492340
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
22502341
isUiCmd = g.argv[1][0]=='u';
22512342
if( isUiCmd ){
2252
- flags |= HTTP_SERVER_LOCALHOST;
2343
+ flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
22532344
g.useLocalauth = 1;
2345
+ allowRepoList = 1;
22542346
}
2255
- find_server_repository(isUiCmd && zNotFound==0, 2);
2347
+ find_server_repository(2);
22562348
if( zPort ){
22572349
int i;
22582350
for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
22592351
if( i>0 ){
22602352
zIpAddr = mprintf("%.*s", i, zPort);
@@ -2304,20 +2396,21 @@
23042396
g.httpOut = stdout;
23052397
if( g.fHttpTrace || g.fSqlTrace ){
23062398
fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
23072399
}
23082400
g.cgiOutput = 1;
2309
- find_server_repository(isUiCmd && zNotFound==0, 2);
2310
- g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2401
+ find_server_repository(2);
2402
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
23112403
if( flags & HTTP_SERVER_SCGI ){
23122404
cgi_handle_scgi_request();
23132405
}else{
23142406
cgi_handle_http_request(0);
23152407
}
2316
- process_one_web_page(zNotFound, glob_create(zFileGlob));
2408
+ process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
23172409
#else
23182410
/* Win32 implementation */
2411
+ (void)allowRepoList; /* Suppress warning */
23192412
if( isUiCmd ){
23202413
zBrowser = db_get("web-browser", "start");
23212414
if( zIpAddr ){
23222415
zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr);
23232416
}else{
23242417
--- src/main.c
+++ src/main.c
@@ -193,12 +193,17 @@
193 /* Information used to populate the RCVFROM table */
194 int rcvid; /* The rcvid. 0 if not yet defined. */
195 char *zIpAddr; /* The remote IP address */
196 char *zNonce; /* The nonce used for login */
197
198 /* permissions used by the server */
199 struct FossilUserPerms perm;
 
 
 
 
 
200
201 #ifdef FOSSIL_ENABLE_TCL
202 /* all Tcl related context necessary for integration */
203 struct TclContext tcl;
204 #endif
@@ -1173,11 +1178,11 @@
1173 const char *z = aCommand[i].zName;
1174 if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
1175 if( j==0 ){
1176 @ <td valign="top"><ul>
1177 }
1178 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1179 j++;
1180 if( j>=n ){
1181 @ </ul></td>
1182 j = 0;
1183 }
@@ -1201,11 +1206,11 @@
1201 if( '/'!=*z ) continue;
1202 if( j==0 ){
1203 @ <td valign="top"><ul>
1204 }
1205 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1206 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z+1)</a></li>
1207 }else{
1208 @ <li>%s(z+1)</li>
1209 }
1210 j++;
1211 if( j>=n ){
@@ -1231,11 +1236,11 @@
1231 if( strncmp(z,"test",4)!=0 ) continue;
1232 if( j==0 ){
1233 @ <td valign="top"><ul>
1234 }
1235 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1236 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1237 }else{
1238 @ <li>%s(z)</li>
1239 }
1240 j++;
1241 if( j>=n ){
@@ -1352,12 +1357,15 @@
1352 ** zRepo might be a directory itself. In that case chroot into
1353 ** the directory zRepo.
1354 **
1355 ** Assume the user-id and group-id of the repository, or if zRepo
1356 ** is a directory, of that directory.
 
 
 
1357 */
1358 static char *enter_chroot_jail(char *zRepo){
1359 #if !defined(_WIN32)
1360 if( getuid()==0 ){
1361 int i;
1362 struct stat sStat;
1363 Blob dir;
@@ -1366,26 +1374,28 @@
1366 db_close(1);
1367 }
1368
1369 file_canonical_name(zRepo, &dir, 0);
1370 zDir = blob_str(&dir);
1371 if( file_isdir(zDir)==1 ){
1372 if( file_chdir(zDir, 1) ){
1373 fossil_fatal("unable to chroot into %s", zDir);
1374 }
1375 zRepo = "/";
1376 }else{
1377 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1378 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1379 if( i>0 ){
1380 zDir[i] = 0;
1381 if( file_chdir(zDir, 1) ){
1382 fossil_fatal("unable to chroot into %s", zDir);
1383 }
1384 zDir[i] = '/';
 
 
 
 
 
 
 
 
 
 
 
1385 }
1386 zRepo = &zDir[i];
1387 }
1388 if( stat(zRepo, &sStat)!=0 ){
1389 fossil_fatal("cannot stat() repository: %s", zRepo);
1390 }
1391 i = setgid(sStat.st_gid);
@@ -1398,10 +1408,48 @@
1398 }
1399 }
1400 #endif
1401 return zRepo;
1402 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1403
1404 /*
1405 ** Preconditions:
1406 **
1407 ** * Environment variables are set up according to the CGI standard.
@@ -1421,11 +1469,15 @@
1421 ** $prefix can be determined from its suffix, then the file $prefix is
1422 ** returned as static text.
1423 **
1424 ** If no suitable webpage is found, try to redirect to zNotFound.
1425 */
1426 static void process_one_web_page(const char *zNotFound, Glob *pFileGlob){
 
 
 
 
1427 const char *zPathInfo;
1428 char *zPath = NULL;
1429 int idx;
1430 int i;
1431
@@ -1496,10 +1548,14 @@
1496
1497 if( szFile<1024 ){
1498 set_base_url(0);
1499 if( zNotFound ){
1500 cgi_redirect(zNotFound);
 
 
 
 
1501 }else{
1502 #ifdef FOSSIL_ENABLE_JSON
1503 if(g.json.isJsonMode){
1504 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1505 return;
@@ -1795,10 +1851,11 @@
1795 const char *zFile;
1796 const char *zNotFound = 0;
1797 char **azRedirect = 0; /* List of repositories to redirect to */
1798 int nRedirect = 0; /* Number of entries in azRedirect */
1799 Glob *pFileGlob = 0; /* Pattern for files */
 
1800 Blob config, line, key, value, value2;
1801 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
1802 zFile = g.argv[2];
1803 }else{
1804 zFile = g.argv[1];
@@ -1852,10 +1909,19 @@
1852 ** Grant "administrator" privileges to users connecting with HTTP
1853 ** from IP address 127.0.0.1. Do not bother checking credentials.
1854 */
1855 g.useLocalauth = 1;
1856 continue;
 
 
 
 
 
 
 
 
 
1857 }
1858 if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
1859 && blob_token(&line, &value2) ){
1860 /* See the header comment on the redirect_web_page() function
1861 ** above for details. */
@@ -1921,20 +1987,32 @@
1921 */
1922 cgi_setenv("HOME", blob_str(&value));
1923 blob_reset(&value);
1924 continue;
1925 }
 
 
 
 
 
 
 
 
 
 
 
 
1926 }
1927 blob_reset(&config);
1928 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
1929 cgi_panic("Unable to find or open the project repository");
1930 }
1931 cgi_init();
1932 if( nRedirect ){
1933 redirect_web_page(nRedirect, azRedirect);
1934 }else{
1935 process_one_web_page(zNotFound, pFileGlob);
1936 }
1937 }
1938
1939 /*
1940 ** If g.argv[arg] exists then it is either the name of a repository
@@ -1948,24 +2026,17 @@
1948 ** Open the repository to be served if it is known. If g.argv[arg] is
1949 ** a directory full of repositories, then set g.zRepositoryName to
1950 ** the name of that directory and the specific repository will be
1951 ** opened later by process_one_web_page() based on the content of
1952 ** the PATH_INFO variable.
1953 **
1954 ** If disallowDir is set, then the directory full of repositories method
1955 ** is disallowed.
1956 */
1957 static void find_server_repository(int disallowDir, int arg){
1958 if( g.argc<=arg ){
1959 db_must_be_within_tree();
1960 }else if( file_isdir(g.argv[arg])==1 ){
1961 if( disallowDir ){
1962 fossil_fatal("\"%s\" is a directory, not a repository file", g.argv[arg]);
1963 }else{
1964 g.zRepositoryName = mprintf("%s", g.argv[arg]);
1965 file_simplify_name(g.zRepositoryName, -1, 0);
1966 }
1967 }else{
1968 db_open_repository(g.argv[arg]);
1969 }
1970 }
1971
@@ -2006,18 +2077,21 @@
2006 ** If the --localauth option is given, then automatic login is performed
2007 ** for requests coming from localhost, if the "localauth" setting is not
2008 ** enabled.
2009 **
2010 ** Options:
 
 
2011 ** --localauth enable automatic login for local connections
2012 ** --host NAME specify hostname of the server
2013 ** --https signal a request coming in via https
 
2014 ** --nossl signal that no SSL connections are available
2015 ** --notfound URL use URL as "HTTP 404, object not found" page.
2016 ** --files GLOB comma-separate glob patterns for static file to serve
2017 ** --baseurl URL base URL (useful with reverse proxies)
2018 ** --scgi Interpret input as SCGI rather than HTTP
 
2019 **
2020 ** See also: cgi, server, winsrv
2021 */
2022 void cmd_http(void){
2023 const char *zIpAddr = 0;
@@ -2024,10 +2098,12 @@
2024 const char *zNotFound;
2025 const char *zHost;
2026 const char *zAltBase;
2027 const char *zFileGlob;
2028 int useSCGI;
 
 
2029
2030 /* The winhttp module passes the --files option as --files-urlenc with
2031 ** the argument being URL encoded, to avoid wildcard expansion in the
2032 ** shell. This option is for internal use and is undocumented.
2033 */
@@ -2037,11 +2113,14 @@
2037 dehttpize(z);
2038 zFileGlob = z;
2039 }else{
2040 zFileGlob = find_option("files",0,1);
2041 }
 
2042 zNotFound = find_option("notfound", 0, 1);
 
 
2043 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2044 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2045 useSCGI = find_option("scgi", 0, 0)!=0;
2046 zAltBase = find_option("baseurl", 0, 1);
2047 if( zAltBase ) set_base_url(zAltBase);
@@ -2062,41 +2141,41 @@
2062 g.fullHttpReply = 1;
2063 if( g.argc>=5 ){
2064 g.httpIn = fossil_fopen(g.argv[2], "rb");
2065 g.httpOut = fossil_fopen(g.argv[3], "wb");
2066 zIpAddr = g.argv[4];
2067 find_server_repository(0, 5);
2068 }else{
2069 g.httpIn = stdin;
2070 g.httpOut = stdout;
2071 find_server_repository(0, 2);
2072 }
2073 if( zIpAddr==0 ){
2074 zIpAddr = cgi_ssh_remote_addr(0);
2075 if( zIpAddr && zIpAddr[0] ){
2076 g.fSshClient |= CGI_SSH_CLIENT;
2077 }
2078 }
2079 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2080 if( useSCGI ){
2081 cgi_handle_scgi_request();
2082 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2083 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2084 }else{
2085 cgi_handle_http_request(zIpAddr);
2086 }
2087 process_one_web_page(zNotFound, glob_create(zFileGlob));
2088 }
2089
2090 /*
2091 ** Process all requests in a single SSH connection if possible.
2092 */
2093 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
2094 blob_zero(&g.cgiIn);
2095 do{
2096 cgi_handle_ssh_http_request(zIpAddr);
2097 process_one_web_page(0, FileGlob);
2098 blob_reset(&g.cgiIn);
2099 } while ( g.fSshClient & CGI_SSH_FOSSIL ||
2100 g.fSshClient & CGI_SSH_COMPAT );
2101 }
2102
@@ -2113,21 +2192,21 @@
2113 Th_InitTraceLog();
2114 login_set_capabilities("sx", 0);
2115 g.useLocalauth = 1;
2116 g.httpIn = stdin;
2117 g.httpOut = stdout;
2118 find_server_repository(0, 2);
2119 g.cgiOutput = 1;
2120 g.fullHttpReply = 1;
2121 zIpAddr = cgi_ssh_remote_addr(0);
2122 if( zIpAddr && zIpAddr[0] ){
2123 g.fSshClient |= CGI_SSH_CLIENT;
2124 ssh_request_loop(zIpAddr, 0);
2125 }else{
2126 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
2127 cgi_handle_http_request(0);
2128 process_one_web_page(0, 0);
2129 }
2130 }
2131
2132 #if !defined(_WIN32)
2133 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
@@ -2184,26 +2263,29 @@
2184 ** "*.fossil*" will be served as static content. With the "ui" command,
2185 ** the REPOSITORY can only be a directory if the --notfound option is
2186 ** also present.
2187 **
2188 ** By default, the "ui" command provides full administrative access without
2189 ** having to log in. This can be disabled by setting turning off the
2190 ** "localauth" setting. Automatic login for the "server" command is available
2191 ** if the --localauth option is present and the "localauth" setting is off
2192 ** and the connection is from localhost. The optional REPOSITORY argument
2193 ** to "ui" may be a directory and will function as "server" if and only if
2194 ** the --notfound option is used.
2195 **
2196 ** Options:
 
 
2197 ** --localauth enable automatic login for requests from localhost
2198 ** --localhost listen on 127.0.0.1 only (always true for "ui")
 
 
2199 ** -P|--port TCPPORT listen to request on port TCPPORT
2200 ** --th-trace trace TH1 execution (for debugging purposes)
2201 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2202 ** --notfound URL Redirect
2203 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2204 ** --scgi Accept SCGI rather than HTTP
 
 
2205 **
2206 ** See also: cgi, http, winsrv
2207 */
2208 void cmd_webserver(void){
2209 int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2211,10 +2293,14 @@
2211 const char *zBrowser; /* Name of web browser program */
2212 char *zBrowserCmd = 0; /* Command to launch the web browser */
2213 int isUiCmd; /* True if command is "ui", not "server' */
2214 const char *zNotFound; /* The --notfound option or NULL */
2215 int flags = 0; /* Server flags */
 
 
 
 
2216 const char *zAltBase; /* Argument to the --baseurl option */
2217 const char *zFileGlob; /* Static content must match this */
2218 char *zIpAddr = 0; /* Bind to this IP address */
2219
2220 #if defined(_WIN32)
@@ -2228,14 +2314,19 @@
2228 dehttpize(z);
2229 zFileGlob = z;
2230 }else{
2231 zFileGlob = find_option("files",0,1);
2232 }
 
 
 
 
2233 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2234 Th_InitTraceLog();
2235 zPort = find_option("port", "P", 1);
2236 zNotFound = find_option("notfound", 0, 1);
 
2237 zAltBase = find_option("baseurl", 0, 1);
2238 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
2239 if( zAltBase ){
2240 set_base_url(zAltBase);
2241 }
@@ -2247,14 +2338,15 @@
2247 verify_all_options();
2248
2249 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
2250 isUiCmd = g.argv[1][0]=='u';
2251 if( isUiCmd ){
2252 flags |= HTTP_SERVER_LOCALHOST;
2253 g.useLocalauth = 1;
 
2254 }
2255 find_server_repository(isUiCmd && zNotFound==0, 2);
2256 if( zPort ){
2257 int i;
2258 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
2259 if( i>0 ){
2260 zIpAddr = mprintf("%.*s", i, zPort);
@@ -2304,20 +2396,21 @@
2304 g.httpOut = stdout;
2305 if( g.fHttpTrace || g.fSqlTrace ){
2306 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2307 }
2308 g.cgiOutput = 1;
2309 find_server_repository(isUiCmd && zNotFound==0, 2);
2310 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2311 if( flags & HTTP_SERVER_SCGI ){
2312 cgi_handle_scgi_request();
2313 }else{
2314 cgi_handle_http_request(0);
2315 }
2316 process_one_web_page(zNotFound, glob_create(zFileGlob));
2317 #else
2318 /* Win32 implementation */
 
2319 if( isUiCmd ){
2320 zBrowser = db_get("web-browser", "start");
2321 if( zIpAddr ){
2322 zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr);
2323 }else{
2324
--- src/main.c
+++ src/main.c
@@ -193,12 +193,17 @@
193 /* Information used to populate the RCVFROM table */
194 int rcvid; /* The rcvid. 0 if not yet defined. */
195 char *zIpAddr; /* The remote IP address */
196 char *zNonce; /* The nonce used for login */
197
198 /* permissions available to current user */
199 struct FossilUserPerms perm;
200
201 /* permissions available to current user or to "anonymous".
202 ** This is the logical union of perm permissions above with
203 ** the value that perm would take if g.zLogin were "anonymous". */
204 struct FossilUserPerms anon;
205
206 #ifdef FOSSIL_ENABLE_TCL
207 /* all Tcl related context necessary for integration */
208 struct TclContext tcl;
209 #endif
@@ -1173,11 +1178,11 @@
1178 const char *z = aCommand[i].zName;
1179 if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
1180 if( j==0 ){
1181 @ <td valign="top"><ul>
1182 }
1183 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
1184 j++;
1185 if( j>=n ){
1186 @ </ul></td>
1187 j = 0;
1188 }
@@ -1201,11 +1206,11 @@
1206 if( '/'!=*z ) continue;
1207 if( j==0 ){
1208 @ <td valign="top"><ul>
1209 }
1210 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1211 @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
1212 }else{
1213 @ <li>%s(z+1)</li>
1214 }
1215 j++;
1216 if( j>=n ){
@@ -1231,11 +1236,11 @@
1236 if( strncmp(z,"test",4)!=0 ) continue;
1237 if( j==0 ){
1238 @ <td valign="top"><ul>
1239 }
1240 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1241 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
1242 }else{
1243 @ <li>%s(z)</li>
1244 }
1245 j++;
1246 if( j>=n ){
@@ -1352,12 +1357,15 @@
1357 ** zRepo might be a directory itself. In that case chroot into
1358 ** the directory zRepo.
1359 **
1360 ** Assume the user-id and group-id of the repository, or if zRepo
1361 ** is a directory, of that directory.
1362 **
1363 ** The noJail flag means that the chroot jail is not entered. But
1364 ** privileges are still lowered to that of the the user-id and group-id.
1365 */
1366 static char *enter_chroot_jail(char *zRepo, int noJail){
1367 #if !defined(_WIN32)
1368 if( getuid()==0 ){
1369 int i;
1370 struct stat sStat;
1371 Blob dir;
@@ -1366,26 +1374,28 @@
1374 db_close(1);
1375 }
1376
1377 file_canonical_name(zRepo, &dir, 0);
1378 zDir = blob_str(&dir);
1379 if( !noJail ){
1380 if( file_isdir(zDir)==1 ){
 
 
 
 
 
 
 
 
1381 if( file_chdir(zDir, 1) ){
1382 fossil_fatal("unable to chroot into %s", zDir);
1383 }
1384 zRepo = "/";
1385 }else{
1386 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1387 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1388 if( i>0 ){
1389 zDir[i] = 0;
1390 if( file_chdir(zDir, 1) ){
1391 fossil_fatal("unable to chroot into %s", zDir);
1392 }
1393 zDir[i] = '/';
1394 }
1395 zRepo = &zDir[i];
1396 }
 
1397 }
1398 if( stat(zRepo, &sStat)!=0 ){
1399 fossil_fatal("cannot stat() repository: %s", zRepo);
1400 }
1401 i = setgid(sStat.st_gid);
@@ -1398,10 +1408,48 @@
1408 }
1409 }
1410 #endif
1411 return zRepo;
1412 }
1413
1414 /*
1415 ** Generate a web-page that lists all repositories located under the
1416 ** g.zRepositoryName directory and return non-zero.
1417 **
1418 ** Or, if no repositories can be located beneath g.zRepositoryName,
1419 ** return 0.
1420 */
1421 static int repo_list_page(void){
1422 Blob base;
1423 int n = 0;
1424
1425 assert( g.db==0 );
1426 blob_init(&base, g.zRepositoryName, -1);
1427 sqlite3_open(":memory:", &g.db);
1428 db_multi_exec("CREATE TABLE sfile(x TEXT);");
1429 db_multi_exec("CREATE TABLE vfile(pathname);");
1430 vfile_scan(&base, blob_size(&base), 0, 0, 0);
1431 db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*.fossil'");
1432 n = db_int(0, "SELECT count(*) FROM sfile");
1433 if( n>0 ){
1434 Stmt q;
1435 @ <h1>Available Repositories:</h1>
1436 @ <ol>
1437 db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'"
1438 " FROM sfile ORDER BY x COLLATE nocase;");
1439 while( db_step(&q)==SQLITE_ROW ){
1440 const char *zName = db_column_text(&q, 0);
1441 const char *zUrl = db_column_text(&q, 1);
1442 @ <li><a href="%h(zUrl)">%h(zName)</a></li>
1443 }
1444 @ </ol>
1445 cgi_reply();
1446 }
1447 sqlite3_close(g.db);
1448 g.db = 0;
1449 return n;
1450 }
1451
1452 /*
1453 ** Preconditions:
1454 **
1455 ** * Environment variables are set up according to the CGI standard.
@@ -1421,11 +1469,15 @@
1469 ** $prefix can be determined from its suffix, then the file $prefix is
1470 ** returned as static text.
1471 **
1472 ** If no suitable webpage is found, try to redirect to zNotFound.
1473 */
1474 static void process_one_web_page(
1475 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1476 Glob *pFileGlob, /* Deliver static files matching */
1477 int allowRepoList /* Send repo list for "/" URL */
1478 ){
1479 const char *zPathInfo;
1480 char *zPath = NULL;
1481 int idx;
1482 int i;
1483
@@ -1496,10 +1548,14 @@
1548
1549 if( szFile<1024 ){
1550 set_base_url(0);
1551 if( zNotFound ){
1552 cgi_redirect(zNotFound);
1553 }else if( strcmp(zPathInfo,"/")==0
1554 && allowRepoList
1555 && repo_list_page() ){
1556 /* Will return a list of repositories */
1557 }else{
1558 #ifdef FOSSIL_ENABLE_JSON
1559 if(g.json.isJsonMode){
1560 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1561 return;
@@ -1795,10 +1851,11 @@
1851 const char *zFile;
1852 const char *zNotFound = 0;
1853 char **azRedirect = 0; /* List of repositories to redirect to */
1854 int nRedirect = 0; /* Number of entries in azRedirect */
1855 Glob *pFileGlob = 0; /* Pattern for files */
1856 int allowRepoList = 0; /* Allow lists of repository files */
1857 Blob config, line, key, value, value2;
1858 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
1859 zFile = g.argv[2];
1860 }else{
1861 zFile = g.argv[1];
@@ -1852,10 +1909,19 @@
1909 ** Grant "administrator" privileges to users connecting with HTTP
1910 ** from IP address 127.0.0.1. Do not bother checking credentials.
1911 */
1912 g.useLocalauth = 1;
1913 continue;
1914 }
1915 if( blob_eq(&key, "repolist") ){
1916 /* repolist
1917 **
1918 ** If using "directory:" and the URL is "/" then generate a page
1919 ** showing a list of available repositories.
1920 */
1921 allowRepoList = 1;
1922 continue;
1923 }
1924 if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
1925 && blob_token(&line, &value2) ){
1926 /* See the header comment on the redirect_web_page() function
1927 ** above for details. */
@@ -1921,20 +1987,32 @@
1987 */
1988 cgi_setenv("HOME", blob_str(&value));
1989 blob_reset(&value);
1990 continue;
1991 }
1992 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
1993 /* skin: LABEL
1994 **
1995 ** Use one of the built-in skins defined by LABEL. LABEL is the
1996 ** name of the subdirectory under the skins/ directory that holds
1997 ** the elements of the built-in skin. If LABEL does not match,
1998 ** this directive is a silent no-op.
1999 */
2000 skin_use_alternative(blob_str(&value));
2001 blob_reset(&value);
2002 continue;
2003 }
2004 }
2005 blob_reset(&config);
2006 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2007 cgi_panic("Unable to find or open the project repository");
2008 }
2009 cgi_init();
2010 if( nRedirect ){
2011 redirect_web_page(nRedirect, azRedirect);
2012 }else{
2013 process_one_web_page(zNotFound, pFileGlob, allowRepoList);
2014 }
2015 }
2016
2017 /*
2018 ** If g.argv[arg] exists then it is either the name of a repository
@@ -1948,24 +2026,17 @@
2026 ** Open the repository to be served if it is known. If g.argv[arg] is
2027 ** a directory full of repositories, then set g.zRepositoryName to
2028 ** the name of that directory and the specific repository will be
2029 ** opened later by process_one_web_page() based on the content of
2030 ** the PATH_INFO variable.
 
 
 
2031 */
2032 static void find_server_repository(int arg){
2033 if( g.argc<=arg ){
2034 db_must_be_within_tree();
2035 }else if( file_isdir(g.argv[arg])==1 ){
2036 g.zRepositoryName = mprintf("%s", g.argv[arg]);
2037 file_simplify_name(g.zRepositoryName, -1, 0);
 
 
 
 
2038 }else{
2039 db_open_repository(g.argv[arg]);
2040 }
2041 }
2042
@@ -2006,18 +2077,21 @@
2077 ** If the --localauth option is given, then automatic login is performed
2078 ** for requests coming from localhost, if the "localauth" setting is not
2079 ** enabled.
2080 **
2081 ** Options:
2082 ** --baseurl URL base URL (useful with reverse proxies)
2083 ** --files GLOB comma-separate glob patterns for static file to serve
2084 ** --localauth enable automatic login for local connections
2085 ** --host NAME specify hostname of the server
2086 ** --https signal a request coming in via https
2087 ** --nojail drop root privilege but do not enter the chroot jail
2088 ** --nossl signal that no SSL connections are available
2089 ** --notfound URL use URL as "HTTP 404, object not found" page.
2090 ** --repolist If REPOSITORY is directory, URL "/" lists all repos
 
2091 ** --scgi Interpret input as SCGI rather than HTTP
2092 ** --skin LABEL Use override skin LABEL
2093 **
2094 ** See also: cgi, server, winsrv
2095 */
2096 void cmd_http(void){
2097 const char *zIpAddr = 0;
@@ -2024,10 +2098,12 @@
2098 const char *zNotFound;
2099 const char *zHost;
2100 const char *zAltBase;
2101 const char *zFileGlob;
2102 int useSCGI;
2103 int noJail;
2104 int allowRepoList;
2105
2106 /* The winhttp module passes the --files option as --files-urlenc with
2107 ** the argument being URL encoded, to avoid wildcard expansion in the
2108 ** shell. This option is for internal use and is undocumented.
2109 */
@@ -2037,11 +2113,14 @@
2113 dehttpize(z);
2114 zFileGlob = z;
2115 }else{
2116 zFileGlob = find_option("files",0,1);
2117 }
2118 skin_override();
2119 zNotFound = find_option("notfound", 0, 1);
2120 noJail = find_option("nojail",0,0)!=0;
2121 allowRepoList = find_option("repolist",0,0)!=0;
2122 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2123 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2124 useSCGI = find_option("scgi", 0, 0)!=0;
2125 zAltBase = find_option("baseurl", 0, 1);
2126 if( zAltBase ) set_base_url(zAltBase);
@@ -2062,41 +2141,41 @@
2141 g.fullHttpReply = 1;
2142 if( g.argc>=5 ){
2143 g.httpIn = fossil_fopen(g.argv[2], "rb");
2144 g.httpOut = fossil_fopen(g.argv[3], "wb");
2145 zIpAddr = g.argv[4];
2146 find_server_repository(5);
2147 }else{
2148 g.httpIn = stdin;
2149 g.httpOut = stdout;
2150 find_server_repository(2);
2151 }
2152 if( zIpAddr==0 ){
2153 zIpAddr = cgi_ssh_remote_addr(0);
2154 if( zIpAddr && zIpAddr[0] ){
2155 g.fSshClient |= CGI_SSH_CLIENT;
2156 }
2157 }
2158 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2159 if( useSCGI ){
2160 cgi_handle_scgi_request();
2161 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2162 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2163 }else{
2164 cgi_handle_http_request(zIpAddr);
2165 }
2166 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2167 }
2168
2169 /*
2170 ** Process all requests in a single SSH connection if possible.
2171 */
2172 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
2173 blob_zero(&g.cgiIn);
2174 do{
2175 cgi_handle_ssh_http_request(zIpAddr);
2176 process_one_web_page(0, FileGlob, 0);
2177 blob_reset(&g.cgiIn);
2178 } while ( g.fSshClient & CGI_SSH_FOSSIL ||
2179 g.fSshClient & CGI_SSH_COMPAT );
2180 }
2181
@@ -2113,21 +2192,21 @@
2192 Th_InitTraceLog();
2193 login_set_capabilities("sx", 0);
2194 g.useLocalauth = 1;
2195 g.httpIn = stdin;
2196 g.httpOut = stdout;
2197 find_server_repository(2);
2198 g.cgiOutput = 1;
2199 g.fullHttpReply = 1;
2200 zIpAddr = cgi_ssh_remote_addr(0);
2201 if( zIpAddr && zIpAddr[0] ){
2202 g.fSshClient |= CGI_SSH_CLIENT;
2203 ssh_request_loop(zIpAddr, 0);
2204 }else{
2205 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
2206 cgi_handle_http_request(0);
2207 process_one_web_page(0, 0, 0);
2208 }
2209 }
2210
2211 #if !defined(_WIN32)
2212 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
@@ -2184,26 +2263,29 @@
2263 ** "*.fossil*" will be served as static content. With the "ui" command,
2264 ** the REPOSITORY can only be a directory if the --notfound option is
2265 ** also present.
2266 **
2267 ** By default, the "ui" command provides full administrative access without
2268 ** having to log in. This can be disabled by turning off the "localauth"
2269 ** setting. Automatic login for the "server" command is available if the
2270 ** --localauth option is present and the "localauth" setting is off and the
2271 ** connection is from localhost. The "ui" command also enables --repolist
2272 ** by default.
 
2273 **
2274 ** Options:
2275 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2276 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2277 ** --localauth enable automatic login for requests from localhost
2278 ** --localhost listen on 127.0.0.1 only (always true for "ui")
2279 ** --nojail Drop root privileges but do not enter the chroot jail
2280 ** --notfound URL Redirect
2281 ** -P|--port TCPPORT listen to request on port TCPPORT
2282 ** --th-trace trace TH1 execution (for debugging purposes)
2283 ** --repolist If REPOSITORY is dir, URL "/" lists repos.
 
 
2284 ** --scgi Accept SCGI rather than HTTP
2285 ** --skin LABEL Use override skin LABEL
2286
2287 **
2288 ** See also: cgi, http, winsrv
2289 */
2290 void cmd_webserver(void){
2291 int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2211,10 +2293,14 @@
2293 const char *zBrowser; /* Name of web browser program */
2294 char *zBrowserCmd = 0; /* Command to launch the web browser */
2295 int isUiCmd; /* True if command is "ui", not "server' */
2296 const char *zNotFound; /* The --notfound option or NULL */
2297 int flags = 0; /* Server flags */
2298 #if !defined(_WIN32)
2299 int noJail; /* Do not enter the chroot jail */
2300 #endif
2301 int allowRepoList; /* List repositories on URL "/" */
2302 const char *zAltBase; /* Argument to the --baseurl option */
2303 const char *zFileGlob; /* Static content must match this */
2304 char *zIpAddr = 0; /* Bind to this IP address */
2305
2306 #if defined(_WIN32)
@@ -2228,14 +2314,19 @@
2314 dehttpize(z);
2315 zFileGlob = z;
2316 }else{
2317 zFileGlob = find_option("files",0,1);
2318 }
2319 skin_override();
2320 #if !defined(_WIN32)
2321 noJail = find_option("nojail",0,0)!=0;
2322 #endif
2323 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2324 Th_InitTraceLog();
2325 zPort = find_option("port", "P", 1);
2326 zNotFound = find_option("notfound", 0, 1);
2327 allowRepoList = find_option("repolist",0,0)!=0;
2328 zAltBase = find_option("baseurl", 0, 1);
2329 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
2330 if( zAltBase ){
2331 set_base_url(zAltBase);
2332 }
@@ -2247,14 +2338,15 @@
2338 verify_all_options();
2339
2340 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
2341 isUiCmd = g.argv[1][0]=='u';
2342 if( isUiCmd ){
2343 flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
2344 g.useLocalauth = 1;
2345 allowRepoList = 1;
2346 }
2347 find_server_repository(2);
2348 if( zPort ){
2349 int i;
2350 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
2351 if( i>0 ){
2352 zIpAddr = mprintf("%.*s", i, zPort);
@@ -2304,20 +2396,21 @@
2396 g.httpOut = stdout;
2397 if( g.fHttpTrace || g.fSqlTrace ){
2398 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2399 }
2400 g.cgiOutput = 1;
2401 find_server_repository(2);
2402 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2403 if( flags & HTTP_SERVER_SCGI ){
2404 cgi_handle_scgi_request();
2405 }else{
2406 cgi_handle_http_request(0);
2407 }
2408 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2409 #else
2410 /* Win32 implementation */
2411 (void)allowRepoList; /* Suppress warning */
2412 if( isUiCmd ){
2413 zBrowser = db_get("web-browser", "start");
2414 if( zIpAddr ){
2415 zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr);
2416 }else{
2417
+2 -1
--- src/main.mk
+++ src/main.mk
@@ -450,11 +450,12 @@
450450
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
451451
-DSQLITE_THREADSAFE=0 \
452452
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
453453
-DSQLITE_OMIT_DEPRECATED \
454454
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
455
- -DSQLITE_ENABLE_FTS4
455
+ -DSQLITE_ENABLE_FTS4 \
456
+ -DSQLITE_ENABLE_FTS3_PARENTHESIS
456457
457458
# Setup the options used to compile the included SQLite shell.
458459
SHELL_OPTIONS = -Dmain=sqlite3_shell \
459460
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
460461
-DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
461462
--- src/main.mk
+++ src/main.mk
@@ -450,11 +450,12 @@
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/main.mk
+++ src/main.mk
@@ -450,11 +450,12 @@
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 -DSQLITE_ENABLE_FTS3_PARENTHESIS
457
458 # Setup the options used to compile the included SQLite shell.
459 SHELL_OPTIONS = -Dmain=sqlite3_shell \
460 -DSQLITE_OMIT_LOAD_EXTENSION=1 \
461 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
462
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -158,10 +158,11 @@
158158
-DSQLITE_THREADSAFE=0
159159
-DSQLITE_DEFAULT_FILE_FORMAT=4
160160
-DSQLITE_OMIT_DEPRECATED
161161
-DSQLITE_ENABLE_EXPLAIN_COMMENTS
162162
-DSQLITE_ENABLE_FTS4
163
+ -DSQLITE_ENABLE_FTS3_PARENTHESIS
163164
}
164165
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
165166
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4
166167
#lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI
167168
#lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096
168169
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -158,10 +158,11 @@
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/makemake.tcl
+++ src/makemake.tcl
@@ -158,10 +158,11 @@
158 -DSQLITE_THREADSAFE=0
159 -DSQLITE_DEFAULT_FILE_FORMAT=4
160 -DSQLITE_OMIT_DEPRECATED
161 -DSQLITE_ENABLE_EXPLAIN_COMMENTS
162 -DSQLITE_ENABLE_FTS4
163 -DSQLITE_ENABLE_FTS3_PARENTHESIS
164 }
165 #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
166 #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4
167 #lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI
168 #lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096
169
+13 -13
--- src/manifest.c
+++ src/manifest.c
@@ -1621,40 +1621,40 @@
16211621
if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
16221622
zNewStatus = pManifest->aField[i].zValue;
16231623
}
16241624
}
16251625
if( zNewStatus ){
1626
- blob_appendf(&comment, "%h ticket [%s|%S]: <i>%h</i>",
1626
+ blob_appendf(&comment, "%h ticket [%!S|%S]: <i>%h</i>",
16271627
zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
16281628
);
16291629
if( pManifest->nField>1 ){
16301630
blob_appendf(&comment, " plus %d other change%s",
16311631
pManifest->nField-1, pManifest->nField==2 ? "" : "s");
16321632
}
1633
- blob_appendf(&brief, "%h ticket [%s|%S].",
1633
+ blob_appendf(&brief, "%h ticket [%!S|%S].",
16341634
zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid);
16351635
}else{
16361636
zNewStatus = db_text("unknown",
16371637
"SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q",
16381638
zStatusColumn, pManifest->zTicketUuid
16391639
);
1640
- blob_appendf(&comment, "Ticket [%s|%S] <i>%h</i> status still %h with "
1640
+ blob_appendf(&comment, "Ticket [%!S|%S] <i>%h</i> status still %h with "
16411641
"%d other change%s",
16421642
pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus,
16431643
pManifest->nField, pManifest->nField==1 ? "" : "s"
16441644
);
16451645
fossil_free(zNewStatus);
1646
- blob_appendf(&brief, "Ticket [%s|%S]: %d change%s",
1646
+ blob_appendf(&brief, "Ticket [%!S|%S]: %d change%s",
16471647
pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField,
16481648
pManifest->nField==1 ? "" : "s"
16491649
);
16501650
}
16511651
}else{
1652
- blob_appendf(&comment, "New ticket [%s|%S] <i>%h</i>.",
1652
+ blob_appendf(&comment, "New ticket [%!S|%S] <i>%h</i>.",
16531653
pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
16541654
);
1655
- blob_appendf(&brief, "New ticket [%s|%S].", pManifest->zTicketUuid,
1655
+ blob_appendf(&brief, "New ticket [%!S|%S].", pManifest->zTicketUuid,
16561656
pManifest->zTicketUuid);
16571657
}
16581658
fossil_free(zTitle);
16591659
db_multi_exec(
16601660
"REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
@@ -1756,11 +1756,11 @@
17561756
if( (p = manifest_cache_find(rid))!=0 ){
17571757
blob_reset(pContent);
17581758
}else if( (p = manifest_parse(pContent, rid, 0))==0 ){
17591759
assert( blob_is_reset(pContent) || pContent==0 );
17601760
if( (flags & MC_NO_ERRORS)==0 ){
1761
- fossil_error(1, "syntax error in manifest [%s]",
1761
+ fossil_error(1, "syntax error in manifest [%S]",
17621762
db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
17631763
}
17641764
return 0;
17651765
}
17661766
if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
@@ -1771,11 +1771,11 @@
17711771
}
17721772
if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
17731773
manifest_destroy(p);
17741774
assert( blob_is_reset(pContent) );
17751775
if( (flags & MC_NO_ERRORS)==0 ){
1776
- fossil_error(1, "cannot fetch baseline for manifest [%s]",
1776
+ fossil_error(1, "cannot fetch baseline for manifest [%S]",
17771777
db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
17781778
}
17791779
return 0;
17801780
}
17811781
db_begin_transaction();
@@ -2031,23 +2031,23 @@
20312031
p->zAttachTarget, p->zAttachName
20322032
);
20332033
if( 'w' == attachToType ){
20342034
if( isAdd ){
20352035
zComment = mprintf(
2036
- "Add attachment [/artifact/%s|%h] to wiki page [%h]",
2036
+ "Add attachment [/artifact/%!S|%h] to wiki page [%h]",
20372037
p->zAttachSrc, p->zAttachName, p->zAttachTarget);
20382038
}else{
20392039
zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
20402040
p->zAttachName, p->zAttachTarget);
20412041
}
20422042
}else{
20432043
if( isAdd ){
20442044
zComment = mprintf(
2045
- "Add attachment [/artifact/%s|%h] to ticket [%s|%S]",
2045
+ "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
20462046
p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
20472047
}else{
2048
- zComment = mprintf("Delete attachment \"%h\" from ticket [%s|%S]",
2048
+ zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
20492049
p->zAttachName, p->zAttachTarget, p->zAttachTarget);
20502050
}
20512051
}
20522052
db_multi_exec(
20532053
"REPLACE INTO event(type,mtime,objid,user,comment)"
@@ -2072,11 +2072,11 @@
20722072
for(i=0; i<p->nTag; i++){
20732073
zTagUuid = p->aTag[i].zUuid;
20742074
if( !zTagUuid ) continue;
20752075
if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
20762076
blob_appendf(&comment,
2077
- " Edit [%s|%S]:",
2077
+ " Edit [%!S|%S]:",
20782078
zTagUuid, zTagUuid);
20792079
branchMove = 0;
20802080
if( permitHooks && db_exists("SELECT 1 FROM event, blob"
20812081
" WHERE event.type='ci' AND event.objid=blob.rid"
20822082
" AND blob.uuid=%Q", zTagUuid) ){
@@ -2086,11 +2086,11 @@
20862086
}
20872087
zName = p->aTag[i].zName;
20882088
zValue = p->aTag[i].zValue;
20892089
if( strcmp(zName, "*branch")==0 ){
20902090
blob_appendf(&comment,
2091
- " Move to branch [/timeline?r=%h&nd&dp=%s&unhide | %h].",
2091
+ " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].",
20922092
zValue, zTagUuid, zValue);
20932093
branchMove = 1;
20942094
continue;
20952095
}else if( strcmp(zName, "*bgcolor")==0 ){
20962096
blob_appendf(&comment,
20972097
--- src/manifest.c
+++ src/manifest.c
@@ -1621,40 +1621,40 @@
1621 if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
1622 zNewStatus = pManifest->aField[i].zValue;
1623 }
1624 }
1625 if( zNewStatus ){
1626 blob_appendf(&comment, "%h ticket [%s|%S]: <i>%h</i>",
1627 zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
1628 );
1629 if( pManifest->nField>1 ){
1630 blob_appendf(&comment, " plus %d other change%s",
1631 pManifest->nField-1, pManifest->nField==2 ? "" : "s");
1632 }
1633 blob_appendf(&brief, "%h ticket [%s|%S].",
1634 zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid);
1635 }else{
1636 zNewStatus = db_text("unknown",
1637 "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q",
1638 zStatusColumn, pManifest->zTicketUuid
1639 );
1640 blob_appendf(&comment, "Ticket [%s|%S] <i>%h</i> status still %h with "
1641 "%d other change%s",
1642 pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus,
1643 pManifest->nField, pManifest->nField==1 ? "" : "s"
1644 );
1645 fossil_free(zNewStatus);
1646 blob_appendf(&brief, "Ticket [%s|%S]: %d change%s",
1647 pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField,
1648 pManifest->nField==1 ? "" : "s"
1649 );
1650 }
1651 }else{
1652 blob_appendf(&comment, "New ticket [%s|%S] <i>%h</i>.",
1653 pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
1654 );
1655 blob_appendf(&brief, "New ticket [%s|%S].", pManifest->zTicketUuid,
1656 pManifest->zTicketUuid);
1657 }
1658 fossil_free(zTitle);
1659 db_multi_exec(
1660 "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
@@ -1756,11 +1756,11 @@
1756 if( (p = manifest_cache_find(rid))!=0 ){
1757 blob_reset(pContent);
1758 }else if( (p = manifest_parse(pContent, rid, 0))==0 ){
1759 assert( blob_is_reset(pContent) || pContent==0 );
1760 if( (flags & MC_NO_ERRORS)==0 ){
1761 fossil_error(1, "syntax error in manifest [%s]",
1762 db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
1763 }
1764 return 0;
1765 }
1766 if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
@@ -1771,11 +1771,11 @@
1771 }
1772 if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
1773 manifest_destroy(p);
1774 assert( blob_is_reset(pContent) );
1775 if( (flags & MC_NO_ERRORS)==0 ){
1776 fossil_error(1, "cannot fetch baseline for manifest [%s]",
1777 db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
1778 }
1779 return 0;
1780 }
1781 db_begin_transaction();
@@ -2031,23 +2031,23 @@
2031 p->zAttachTarget, p->zAttachName
2032 );
2033 if( 'w' == attachToType ){
2034 if( isAdd ){
2035 zComment = mprintf(
2036 "Add attachment [/artifact/%s|%h] to wiki page [%h]",
2037 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2038 }else{
2039 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2040 p->zAttachName, p->zAttachTarget);
2041 }
2042 }else{
2043 if( isAdd ){
2044 zComment = mprintf(
2045 "Add attachment [/artifact/%s|%h] to ticket [%s|%S]",
2046 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2047 }else{
2048 zComment = mprintf("Delete attachment \"%h\" from ticket [%s|%S]",
2049 p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2050 }
2051 }
2052 db_multi_exec(
2053 "REPLACE INTO event(type,mtime,objid,user,comment)"
@@ -2072,11 +2072,11 @@
2072 for(i=0; i<p->nTag; i++){
2073 zTagUuid = p->aTag[i].zUuid;
2074 if( !zTagUuid ) continue;
2075 if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
2076 blob_appendf(&comment,
2077 " Edit [%s|%S]:",
2078 zTagUuid, zTagUuid);
2079 branchMove = 0;
2080 if( permitHooks && db_exists("SELECT 1 FROM event, blob"
2081 " WHERE event.type='ci' AND event.objid=blob.rid"
2082 " AND blob.uuid=%Q", zTagUuid) ){
@@ -2086,11 +2086,11 @@
2086 }
2087 zName = p->aTag[i].zName;
2088 zValue = p->aTag[i].zValue;
2089 if( strcmp(zName, "*branch")==0 ){
2090 blob_appendf(&comment,
2091 " Move to branch [/timeline?r=%h&nd&dp=%s&unhide | %h].",
2092 zValue, zTagUuid, zValue);
2093 branchMove = 1;
2094 continue;
2095 }else if( strcmp(zName, "*bgcolor")==0 ){
2096 blob_appendf(&comment,
2097
--- src/manifest.c
+++ src/manifest.c
@@ -1621,40 +1621,40 @@
1621 if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
1622 zNewStatus = pManifest->aField[i].zValue;
1623 }
1624 }
1625 if( zNewStatus ){
1626 blob_appendf(&comment, "%h ticket [%!S|%S]: <i>%h</i>",
1627 zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
1628 );
1629 if( pManifest->nField>1 ){
1630 blob_appendf(&comment, " plus %d other change%s",
1631 pManifest->nField-1, pManifest->nField==2 ? "" : "s");
1632 }
1633 blob_appendf(&brief, "%h ticket [%!S|%S].",
1634 zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid);
1635 }else{
1636 zNewStatus = db_text("unknown",
1637 "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q",
1638 zStatusColumn, pManifest->zTicketUuid
1639 );
1640 blob_appendf(&comment, "Ticket [%!S|%S] <i>%h</i> status still %h with "
1641 "%d other change%s",
1642 pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus,
1643 pManifest->nField, pManifest->nField==1 ? "" : "s"
1644 );
1645 fossil_free(zNewStatus);
1646 blob_appendf(&brief, "Ticket [%!S|%S]: %d change%s",
1647 pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField,
1648 pManifest->nField==1 ? "" : "s"
1649 );
1650 }
1651 }else{
1652 blob_appendf(&comment, "New ticket [%!S|%S] <i>%h</i>.",
1653 pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
1654 );
1655 blob_appendf(&brief, "New ticket [%!S|%S].", pManifest->zTicketUuid,
1656 pManifest->zTicketUuid);
1657 }
1658 fossil_free(zTitle);
1659 db_multi_exec(
1660 "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
@@ -1756,11 +1756,11 @@
1756 if( (p = manifest_cache_find(rid))!=0 ){
1757 blob_reset(pContent);
1758 }else if( (p = manifest_parse(pContent, rid, 0))==0 ){
1759 assert( blob_is_reset(pContent) || pContent==0 );
1760 if( (flags & MC_NO_ERRORS)==0 ){
1761 fossil_error(1, "syntax error in manifest [%S]",
1762 db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
1763 }
1764 return 0;
1765 }
1766 if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
@@ -1771,11 +1771,11 @@
1771 }
1772 if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
1773 manifest_destroy(p);
1774 assert( blob_is_reset(pContent) );
1775 if( (flags & MC_NO_ERRORS)==0 ){
1776 fossil_error(1, "cannot fetch baseline for manifest [%S]",
1777 db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid));
1778 }
1779 return 0;
1780 }
1781 db_begin_transaction();
@@ -2031,23 +2031,23 @@
2031 p->zAttachTarget, p->zAttachName
2032 );
2033 if( 'w' == attachToType ){
2034 if( isAdd ){
2035 zComment = mprintf(
2036 "Add attachment [/artifact/%!S|%h] to wiki page [%h]",
2037 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2038 }else{
2039 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2040 p->zAttachName, p->zAttachTarget);
2041 }
2042 }else{
2043 if( isAdd ){
2044 zComment = mprintf(
2045 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2046 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2047 }else{
2048 zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
2049 p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2050 }
2051 }
2052 db_multi_exec(
2053 "REPLACE INTO event(type,mtime,objid,user,comment)"
@@ -2072,11 +2072,11 @@
2072 for(i=0; i<p->nTag; i++){
2073 zTagUuid = p->aTag[i].zUuid;
2074 if( !zTagUuid ) continue;
2075 if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
2076 blob_appendf(&comment,
2077 " Edit [%!S|%S]:",
2078 zTagUuid, zTagUuid);
2079 branchMove = 0;
2080 if( permitHooks && db_exists("SELECT 1 FROM event, blob"
2081 " WHERE event.type='ci' AND event.objid=blob.rid"
2082 " AND blob.uuid=%Q", zTagUuid) ){
@@ -2086,11 +2086,11 @@
2086 }
2087 zName = p->aTag[i].zName;
2088 zValue = p->aTag[i].zValue;
2089 if( strcmp(zName, "*branch")==0 ){
2090 blob_appendf(&comment,
2091 " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].",
2092 zValue, zTagUuid, zValue);
2093 branchMove = 1;
2094 continue;
2095 }else if( strcmp(zName, "*bgcolor")==0 ){
2096 blob_appendf(&comment,
2097
+16 -10
--- src/markdown.c
+++ src/markdown.c
@@ -842,11 +842,13 @@
842842
return end;
843843
}
844844
}
845845
846846
847
-/* get_link_inline -- extract inline-style link and title from parenthesed data*/
847
+/* get_link_inline -- extract inline-style link and title from
848
+** parenthesed data
849
+*/
848850
static int get_link_inline(
849851
struct Blob *link,
850852
struct Blob *title,
851853
char *data,
852854
size_t size
@@ -1522,11 +1524,11 @@
15221524
if( !inter ){
15231525
if( rndr->make.listitem ){
15241526
rndr->make.listitem(ob, work, *flags, rndr->make.opaque);
15251527
}
15261528
if( work!=&fallback ) release_work_buffer(rndr, work);
1527
- blob_zero(&fallback);
1529
+ blob_reset(&fallback);
15281530
return beg;
15291531
}
15301532
15311533
/* render of li contents */
15321534
if( has_inside_empty ) *flags |= MKD_LI_BLOCK;
@@ -1558,11 +1560,11 @@
15581560
if( rndr->make.listitem ){
15591561
rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
15601562
}
15611563
release_work_buffer(rndr, inter);
15621564
if( work!=&fallback ) release_work_buffer(rndr, work);
1563
- blob_zero(&fallback);
1565
+ blob_reset(&fallback);
15641566
return beg;
15651567
}
15661568
15671569
15681570
/* parse_list -- parsing ordered or unordered list block */
@@ -1584,11 +1586,11 @@
15841586
if( !j || (flags & MKD_LI_END) ) break;
15851587
}
15861588
15871589
if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque);
15881590
if( work!=&fallback ) release_work_buffer(rndr, work);
1589
- blob_zero(&fallback);
1591
+ blob_reset(&fallback);
15901592
return i;
15911593
}
15921594
15931595
15941596
/* parse_atxheader -- parsing of atx-style headers */
@@ -1631,11 +1633,15 @@
16311633
}
16321634
16331635
16341636
/* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
16351637
/* returns the length on match, 0 otherwise */
1636
-static size_t htmlblock_end(const struct html_tag *tag, const char *data, size_t size){
1638
+static size_t htmlblock_end(
1639
+ const struct html_tag *tag,
1640
+ const char *data,
1641
+ size_t size
1642
+){
16371643
size_t i, w;
16381644
16391645
/* assuming data[0]=='<' && data[1]=='/' already tested */
16401646
16411647
/* checking tag is a match */
@@ -2224,17 +2230,17 @@
22242230
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
22252231
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
22262232
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
22272233
22282234
/* clean-up */
2229
- blob_zero(&text);
2235
+ blob_reset(&text);
22302236
lr = (struct link_ref *)blob_buffer(&rndr.refs);
22312237
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
22322238
for(i=0; i<end; i++){
2233
- blob_zero(&lr[i].id);
2234
- blob_zero(&lr[i].link);
2235
- blob_zero(&lr[i].title);
2239
+ blob_reset(&lr[i].id);
2240
+ blob_reset(&lr[i].link);
2241
+ blob_reset(&lr[i].title);
22362242
}
2237
- blob_zero(&rndr.refs);
2243
+ blob_reset(&rndr.refs);
22382244
blobarray_zero(rndr.work, rndr.make.max_work_stack);
22392245
fossil_free(rndr.work);
22402246
}
22412247
--- src/markdown.c
+++ src/markdown.c
@@ -842,11 +842,13 @@
842 return end;
843 }
844 }
845
846
847 /* get_link_inline -- extract inline-style link and title from parenthesed data*/
 
 
848 static int get_link_inline(
849 struct Blob *link,
850 struct Blob *title,
851 char *data,
852 size_t size
@@ -1522,11 +1524,11 @@
1522 if( !inter ){
1523 if( rndr->make.listitem ){
1524 rndr->make.listitem(ob, work, *flags, rndr->make.opaque);
1525 }
1526 if( work!=&fallback ) release_work_buffer(rndr, work);
1527 blob_zero(&fallback);
1528 return beg;
1529 }
1530
1531 /* render of li contents */
1532 if( has_inside_empty ) *flags |= MKD_LI_BLOCK;
@@ -1558,11 +1560,11 @@
1558 if( rndr->make.listitem ){
1559 rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
1560 }
1561 release_work_buffer(rndr, inter);
1562 if( work!=&fallback ) release_work_buffer(rndr, work);
1563 blob_zero(&fallback);
1564 return beg;
1565 }
1566
1567
1568 /* parse_list -- parsing ordered or unordered list block */
@@ -1584,11 +1586,11 @@
1584 if( !j || (flags & MKD_LI_END) ) break;
1585 }
1586
1587 if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque);
1588 if( work!=&fallback ) release_work_buffer(rndr, work);
1589 blob_zero(&fallback);
1590 return i;
1591 }
1592
1593
1594 /* parse_atxheader -- parsing of atx-style headers */
@@ -1631,11 +1633,15 @@
1631 }
1632
1633
1634 /* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
1635 /* returns the length on match, 0 otherwise */
1636 static size_t htmlblock_end(const struct html_tag *tag, const char *data, size_t size){
 
 
 
 
1637 size_t i, w;
1638
1639 /* assuming data[0]=='<' && data[1]=='/' already tested */
1640
1641 /* checking tag is a match */
@@ -2224,17 +2230,17 @@
2224 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2225 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2226 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2227
2228 /* clean-up */
2229 blob_zero(&text);
2230 lr = (struct link_ref *)blob_buffer(&rndr.refs);
2231 end = blob_size(&rndr.refs)/sizeof(struct link_ref);
2232 for(i=0; i<end; i++){
2233 blob_zero(&lr[i].id);
2234 blob_zero(&lr[i].link);
2235 blob_zero(&lr[i].title);
2236 }
2237 blob_zero(&rndr.refs);
2238 blobarray_zero(rndr.work, rndr.make.max_work_stack);
2239 fossil_free(rndr.work);
2240 }
2241
--- src/markdown.c
+++ src/markdown.c
@@ -842,11 +842,13 @@
842 return end;
843 }
844 }
845
846
847 /* get_link_inline -- extract inline-style link and title from
848 ** parenthesed data
849 */
850 static int get_link_inline(
851 struct Blob *link,
852 struct Blob *title,
853 char *data,
854 size_t size
@@ -1522,11 +1524,11 @@
1524 if( !inter ){
1525 if( rndr->make.listitem ){
1526 rndr->make.listitem(ob, work, *flags, rndr->make.opaque);
1527 }
1528 if( work!=&fallback ) release_work_buffer(rndr, work);
1529 blob_reset(&fallback);
1530 return beg;
1531 }
1532
1533 /* render of li contents */
1534 if( has_inside_empty ) *flags |= MKD_LI_BLOCK;
@@ -1558,11 +1560,11 @@
1560 if( rndr->make.listitem ){
1561 rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
1562 }
1563 release_work_buffer(rndr, inter);
1564 if( work!=&fallback ) release_work_buffer(rndr, work);
1565 blob_reset(&fallback);
1566 return beg;
1567 }
1568
1569
1570 /* parse_list -- parsing ordered or unordered list block */
@@ -1584,11 +1586,11 @@
1586 if( !j || (flags & MKD_LI_END) ) break;
1587 }
1588
1589 if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque);
1590 if( work!=&fallback ) release_work_buffer(rndr, work);
1591 blob_reset(&fallback);
1592 return i;
1593 }
1594
1595
1596 /* parse_atxheader -- parsing of atx-style headers */
@@ -1631,11 +1633,15 @@
1633 }
1634
1635
1636 /* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
1637 /* returns the length on match, 0 otherwise */
1638 static size_t htmlblock_end(
1639 const struct html_tag *tag,
1640 const char *data,
1641 size_t size
1642 ){
1643 size_t i, w;
1644
1645 /* assuming data[0]=='<' && data[1]=='/' already tested */
1646
1647 /* checking tag is a match */
@@ -2224,17 +2230,17 @@
2230 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2231 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2232 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2233
2234 /* clean-up */
2235 blob_reset(&text);
2236 lr = (struct link_ref *)blob_buffer(&rndr.refs);
2237 end = blob_size(&rndr.refs)/sizeof(struct link_ref);
2238 for(i=0; i<end; i++){
2239 blob_reset(&lr[i].id);
2240 blob_reset(&lr[i].link);
2241 blob_reset(&lr[i].title);
2242 }
2243 blob_reset(&rndr.refs);
2244 blobarray_zero(rndr.work, rndr.make.max_work_stack);
2245 fossil_free(rndr.work);
2246 }
2247
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -82,14 +82,18 @@
8282
}
8383
}
8484
8585
8686
/* HTML block tags */
87
+
88
+/* Size of the prolog: "<div class='markdown'>\n" */
89
+#define PROLOG_SIZE 23
8790
8891
static void html_prolog(struct Blob *ob, void *opaque){
8992
INTER_BLOCK(ob);
9093
BLOB_APPEND_LITTERAL(ob, "<div class=\"markdown\">\n");
94
+ assert( blob_size(ob)==PROLOG_SIZE );
9195
}
9296
9397
static void html_epilog(struct Blob *ob, void *opaque){
9498
INTER_BLOCK(ob);
9599
BLOB_APPEND_LITTERAL(ob, "</div>\n");
@@ -126,13 +130,12 @@
126130
void *opaque
127131
){
128132
struct Blob *title = opaque;
129133
/* The first header at the beginning of a text is considered as
130134
* a title and not output. */
131
- if( blob_size(ob)==0 && blob_size(title)==0 ){
135
+ if( blob_size(ob)<=PROLOG_SIZE && blob_size(title)==0 ){
132136
BLOB_APPEND_BLOB(title, text);
133
- return;
134137
}
135138
INTER_BLOCK(ob);
136139
blob_appendf(ob, "<h%d>", level);
137140
BLOB_APPEND_BLOB(ob, text);
138141
blob_appendf(ob, "</h%d>", level);
139142
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -82,14 +82,18 @@
82 }
83 }
84
85
86 /* HTML block tags */
 
 
 
87
88 static void html_prolog(struct Blob *ob, void *opaque){
89 INTER_BLOCK(ob);
90 BLOB_APPEND_LITTERAL(ob, "<div class=\"markdown\">\n");
 
91 }
92
93 static void html_epilog(struct Blob *ob, void *opaque){
94 INTER_BLOCK(ob);
95 BLOB_APPEND_LITTERAL(ob, "</div>\n");
@@ -126,13 +130,12 @@
126 void *opaque
127 ){
128 struct Blob *title = opaque;
129 /* The first header at the beginning of a text is considered as
130 * a title and not output. */
131 if( blob_size(ob)==0 && blob_size(title)==0 ){
132 BLOB_APPEND_BLOB(title, text);
133 return;
134 }
135 INTER_BLOCK(ob);
136 blob_appendf(ob, "<h%d>", level);
137 BLOB_APPEND_BLOB(ob, text);
138 blob_appendf(ob, "</h%d>", level);
139
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -82,14 +82,18 @@
82 }
83 }
84
85
86 /* HTML block tags */
87
88 /* Size of the prolog: "<div class='markdown'>\n" */
89 #define PROLOG_SIZE 23
90
91 static void html_prolog(struct Blob *ob, void *opaque){
92 INTER_BLOCK(ob);
93 BLOB_APPEND_LITTERAL(ob, "<div class=\"markdown\">\n");
94 assert( blob_size(ob)==PROLOG_SIZE );
95 }
96
97 static void html_epilog(struct Blob *ob, void *opaque){
98 INTER_BLOCK(ob);
99 BLOB_APPEND_LITTERAL(ob, "</div>\n");
@@ -126,13 +130,12 @@
130 void *opaque
131 ){
132 struct Blob *title = opaque;
133 /* The first header at the beginning of a text is considered as
134 * a title and not output. */
135 if( blob_size(ob)<=PROLOG_SIZE && blob_size(title)==0 ){
136 BLOB_APPEND_BLOB(title, text);
 
137 }
138 INTER_BLOCK(ob);
139 blob_appendf(ob, "<h%d>", level);
140 BLOB_APPEND_BLOB(ob, text);
141 blob_appendf(ob, "</h%d>", level);
142
+4 -1
--- src/moderate.c
+++ src/moderate.c
@@ -144,11 +144,14 @@
144144
void modreq_page(void){
145145
Blob sql;
146146
Stmt q;
147147
148148
login_check_credentials();
149
- if( !g.perm.RdWiki && !g.perm.RdTkt ){ login_needed(); return; }
149
+ if( !g.perm.RdWiki && !g.perm.RdTkt ){
150
+ login_needed(g.anon.RdWiki && g.anon.RdTkt);
151
+ return;
152
+ }
150153
style_header("Pending Moderation Requests");
151154
@ <h2>All Pending Moderation Requests</h2>
152155
if( moderation_table_exists() ){
153156
blob_init(&sql, timeline_query_for_www(), -1);
154157
blob_append_sql(&sql,
155158
--- src/moderate.c
+++ src/moderate.c
@@ -144,11 +144,14 @@
144 void modreq_page(void){
145 Blob sql;
146 Stmt q;
147
148 login_check_credentials();
149 if( !g.perm.RdWiki && !g.perm.RdTkt ){ login_needed(); return; }
 
 
 
150 style_header("Pending Moderation Requests");
151 @ <h2>All Pending Moderation Requests</h2>
152 if( moderation_table_exists() ){
153 blob_init(&sql, timeline_query_for_www(), -1);
154 blob_append_sql(&sql,
155
--- src/moderate.c
+++ src/moderate.c
@@ -144,11 +144,14 @@
144 void modreq_page(void){
145 Blob sql;
146 Stmt q;
147
148 login_check_credentials();
149 if( !g.perm.RdWiki && !g.perm.RdTkt ){
150 login_needed(g.anon.RdWiki && g.anon.RdTkt);
151 return;
152 }
153 style_header("Pending Moderation Requests");
154 @ <h2>All Pending Moderation Requests</h2>
155 if( moderation_table_exists() ){
156 blob_init(&sql, timeline_query_for_www(), -1);
157 blob_append_sql(&sql,
158
+85 -10
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114114
** If zType is NULL or "" or "*" then any type of artifact will serve.
115115
** If zType is "br" then find the first check-in of the named branch
116116
** rather than the last.
117117
** zType is "ci" in most use cases since we are usually searching for
118118
** a check-in.
119
+**
120
+** Note that the input zTag for types "t" and "e" is the SHA1 hash of
121
+** the ticket-change or event-change artifact, not the randomly generated
122
+** hexadecimal identifier assigned to tickets and events. Those identifiers
123
+** live in a separate namespace.
119124
*/
120125
int symbolic_name_to_rid(const char *zTag, const char *zType){
121126
int vid;
122127
int rid = 0;
123128
int nTag;
@@ -463,11 +468,11 @@
463468
canonical16(z, strlen(z));
464469
db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
465470
while( db_step(&q)==SQLITE_ROW ){
466471
const char *zUuid = db_column_text(&q, 0);
467472
int rid = db_column_int(&q, 1);
468
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
473
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
469474
@ %s(zUuid)</a> -
470475
object_description(rid, 0, 0);
471476
@ </p></li>
472477
}
473478
db_finalize(&q);
@@ -480,11 +485,11 @@
480485
" ORDER BY tkt_ctime DESC", z);
481486
while( db_step(&q)==SQLITE_ROW ){
482487
int rid = db_column_int(&q, 0);
483488
const char *zUuid = db_column_text(&q, 1);
484489
const char *zTitle = db_column_text(&q, 2);
485
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
490
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
486491
@ %s(zUuid)</a> -
487492
@ <ul></ul>
488493
@ Ticket
489494
hyperlink_to_uuid(zUuid);
490495
@ - %s(zTitle).
@@ -500,11 +505,11 @@
500505
" FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
501506
" AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
502507
while( db_step(&q)==SQLITE_ROW ){
503508
int rid = db_column_int(&q, 0);
504509
const char* zUuid = db_column_text(&q, 1);
505
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
510
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
506511
@ %s(zUuid)</a> -
507512
@ <ul><li>
508513
object_description(rid, 0, 0);
509514
@ </li></ul>
510515
@ </p></li>
@@ -796,18 +801,18 @@
796801
}
797802
798803
/*
799804
** Schema for the description table
800805
*/
801
-static const char zDescTab[] =
806
+static const char zDescTab[] =
802807
@ CREATE TEMP TABLE IF NOT EXISTS description(
803
-@ rid INTEGER PRIMARY KEY, -- RID of the object
804
-@ uuid TEXT, -- SHA1 hash of the object
805
-@ ctime DATETIME, -- Time of creation
808
+@ rid INTEGER PRIMARY KEY, -- RID of the object
809
+@ uuid TEXT, -- SHA1 hash of the object
810
+@ ctime DATETIME, -- Time of creation
806811
@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
807812
@ type TEXT, -- file, checkin, wiki, ticket, etc.
808
-@ summary TEXT, -- Summary comment for the object
813
+@ summary TEXT, -- Summary comment for the object
809814
@ detail TEXT -- filename, checkin comment, etc
810815
@ );
811816
;
812817
813818
/*
@@ -1001,11 +1006,11 @@
10011006
int n = atoi(PD("n","5000"));
10021007
int mx = db_int(0, "SELECT max(rid) FROM blob");
10031008
char *zRange;
10041009
10051010
login_check_credentials();
1006
- if( !g.perm.Read ){ login_needed(); return; }
1011
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
10071012
style_header("List Of Artifacts");
10081013
if( mx>n && P("s")==0 ){
10091014
int i;
10101015
@ <p>Select a range of artifacts to view:</p>
10111016
@ <ul>
@@ -1030,11 +1035,11 @@
10301035
int rid = db_column_int(&q,0);
10311036
const char *zUuid = db_column_text(&q, 1);
10321037
const char *zDesc = db_column_text(&q, 2);
10331038
int isPriv = db_column_int(&q,2);
10341039
@ <tr><td align="right">%d(rid)</td>
1035
- @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1040
+ @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
10361041
@ <td align="left">%h(zDesc)</td>
10371042
if( isPriv ){
10381043
@ <td>(unpublished)</td>
10391044
}
10401045
@ </tr>
@@ -1142,5 +1147,75 @@
11421147
11431148
fossil_free(branchName);
11441149
fossil_free(parentBranchName);
11451150
return 0;
11461151
}
1152
+
1153
+/* Maximum number of collision examples to remember */
1154
+#define MAX_COLLIDE 25
1155
+
1156
+/*
1157
+** WEBPAGE: hash-collisions
1158
+**
1159
+** Show the number of hash collisions for hash prefixes of various lengths.
1160
+*/
1161
+void hash_collisions_webpage(void){
1162
+ int i, j, kk;
1163
+ int nHash = 0;
1164
+ Stmt q;
1165
+ char zPrev[UUID_SIZE+1];
1166
+ struct {
1167
+ int cnt;
1168
+ char *azHit[MAX_COLLIDE];
1169
+ char z[UUID_SIZE+1];
1170
+ } aCollide[UUID_SIZE+1];
1171
+ login_check_credentials();
1172
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1173
+ memset(aCollide, 0, sizeof(aCollide));
1174
+ memset(zPrev, 0, sizeof(zPrev));
1175
+ db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1176
+ while( db_step(&q)==SQLITE_ROW ){
1177
+ const char *zUuid = db_column_text(&q,0);
1178
+ int n = db_column_bytes(&q,0);
1179
+ int i;
1180
+ nHash++;
1181
+ for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1182
+ if( i>0 && i<=UUID_SIZE ){
1183
+ if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1184
+ aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1185
+ }
1186
+ aCollide[i].cnt++;
1187
+ if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1188
+ }
1189
+ memcpy(zPrev, zUuid, n+1);
1190
+ }
1191
+ db_finalize(&q);
1192
+ style_header("SHA1 Prefix Collisions");
1193
+ style_submenu_element("Activity Reports", 0, "reports");
1194
+ style_submenu_element("Stats", 0, "stat");
1195
+ @ <table border=1><thead>
1196
+ @ <tr><th>Length<th>Instances<th>First Instance</tr>
1197
+ @ </thead><tbody>
1198
+ for(i=1; i<=UUID_SIZE; i++){
1199
+ if( aCollide[i].cnt==0 ) continue;
1200
+ @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1201
+ }
1202
+ @ </tbody></table>
1203
+ @ <p>Total number of hashes: %d(nHash)</p>
1204
+ kk = 0;
1205
+ for(i=UUID_SIZE; i>=4; i--){
1206
+ if( aCollide[i].cnt==0 ) continue;
1207
+ if( aCollide[i].cnt>200 ) break;
1208
+ kk += aCollide[i].cnt;
1209
+ if( aCollide[i].cnt<25 ){
1210
+ @ <p>Collisions of length %d(i):
1211
+ }else{
1212
+ @ <p>First 25 collisions of length %d(i):
1213
+ }
1214
+ for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1215
+ char *zId = aCollide[i].azHit[j];
1216
+ if( zId==0 ) continue;
1217
+ @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1218
+ }
1219
+ }
1220
+ style_footer();
1221
+}
11471222
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114 ** If zType is NULL or "" or "*" then any type of artifact will serve.
115 ** If zType is "br" then find the first check-in of the named branch
116 ** rather than the last.
117 ** zType is "ci" in most use cases since we are usually searching for
118 ** a check-in.
 
 
 
 
 
119 */
120 int symbolic_name_to_rid(const char *zTag, const char *zType){
121 int vid;
122 int rid = 0;
123 int nTag;
@@ -463,11 +468,11 @@
463 canonical16(z, strlen(z));
464 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
465 while( db_step(&q)==SQLITE_ROW ){
466 const char *zUuid = db_column_text(&q, 0);
467 int rid = db_column_int(&q, 1);
468 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
469 @ %s(zUuid)</a> -
470 object_description(rid, 0, 0);
471 @ </p></li>
472 }
473 db_finalize(&q);
@@ -480,11 +485,11 @@
480 " ORDER BY tkt_ctime DESC", z);
481 while( db_step(&q)==SQLITE_ROW ){
482 int rid = db_column_int(&q, 0);
483 const char *zUuid = db_column_text(&q, 1);
484 const char *zTitle = db_column_text(&q, 2);
485 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
486 @ %s(zUuid)</a> -
487 @ <ul></ul>
488 @ Ticket
489 hyperlink_to_uuid(zUuid);
490 @ - %s(zTitle).
@@ -500,11 +505,11 @@
500 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
501 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
502 while( db_step(&q)==SQLITE_ROW ){
503 int rid = db_column_int(&q, 0);
504 const char* zUuid = db_column_text(&q, 1);
505 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
506 @ %s(zUuid)</a> -
507 @ <ul><li>
508 object_description(rid, 0, 0);
509 @ </li></ul>
510 @ </p></li>
@@ -796,18 +801,18 @@
796 }
797
798 /*
799 ** Schema for the description table
800 */
801 static const char zDescTab[] =
802 @ CREATE TEMP TABLE IF NOT EXISTS description(
803 @ rid INTEGER PRIMARY KEY, -- RID of the object
804 @ uuid TEXT, -- SHA1 hash of the object
805 @ ctime DATETIME, -- Time of creation
806 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
807 @ type TEXT, -- file, checkin, wiki, ticket, etc.
808 @ summary TEXT, -- Summary comment for the object
809 @ detail TEXT -- filename, checkin comment, etc
810 @ );
811 ;
812
813 /*
@@ -1001,11 +1006,11 @@
1001 int n = atoi(PD("n","5000"));
1002 int mx = db_int(0, "SELECT max(rid) FROM blob");
1003 char *zRange;
1004
1005 login_check_credentials();
1006 if( !g.perm.Read ){ login_needed(); return; }
1007 style_header("List Of Artifacts");
1008 if( mx>n && P("s")==0 ){
1009 int i;
1010 @ <p>Select a range of artifacts to view:</p>
1011 @ <ul>
@@ -1030,11 +1035,11 @@
1030 int rid = db_column_int(&q,0);
1031 const char *zUuid = db_column_text(&q, 1);
1032 const char *zDesc = db_column_text(&q, 2);
1033 int isPriv = db_column_int(&q,2);
1034 @ <tr><td align="right">%d(rid)</td>
1035 @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1036 @ <td align="left">%h(zDesc)</td>
1037 if( isPriv ){
1038 @ <td>(unpublished)</td>
1039 }
1040 @ </tr>
@@ -1142,5 +1147,75 @@
1142
1143 fossil_free(branchName);
1144 fossil_free(parentBranchName);
1145 return 0;
1146 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114 ** If zType is NULL or "" or "*" then any type of artifact will serve.
115 ** If zType is "br" then find the first check-in of the named branch
116 ** rather than the last.
117 ** zType is "ci" in most use cases since we are usually searching for
118 ** a check-in.
119 **
120 ** Note that the input zTag for types "t" and "e" is the SHA1 hash of
121 ** the ticket-change or event-change artifact, not the randomly generated
122 ** hexadecimal identifier assigned to tickets and events. Those identifiers
123 ** live in a separate namespace.
124 */
125 int symbolic_name_to_rid(const char *zTag, const char *zType){
126 int vid;
127 int rid = 0;
128 int nTag;
@@ -463,11 +468,11 @@
468 canonical16(z, strlen(z));
469 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
470 while( db_step(&q)==SQLITE_ROW ){
471 const char *zUuid = db_column_text(&q, 0);
472 int rid = db_column_int(&q, 1);
473 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
474 @ %s(zUuid)</a> -
475 object_description(rid, 0, 0);
476 @ </p></li>
477 }
478 db_finalize(&q);
@@ -480,11 +485,11 @@
485 " ORDER BY tkt_ctime DESC", z);
486 while( db_step(&q)==SQLITE_ROW ){
487 int rid = db_column_int(&q, 0);
488 const char *zUuid = db_column_text(&q, 1);
489 const char *zTitle = db_column_text(&q, 2);
490 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
491 @ %s(zUuid)</a> -
492 @ <ul></ul>
493 @ Ticket
494 hyperlink_to_uuid(zUuid);
495 @ - %s(zTitle).
@@ -500,11 +505,11 @@
505 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
506 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
507 while( db_step(&q)==SQLITE_ROW ){
508 int rid = db_column_int(&q, 0);
509 const char* zUuid = db_column_text(&q, 1);
510 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
511 @ %s(zUuid)</a> -
512 @ <ul><li>
513 object_description(rid, 0, 0);
514 @ </li></ul>
515 @ </p></li>
@@ -796,18 +801,18 @@
801 }
802
803 /*
804 ** Schema for the description table
805 */
806 static const char zDescTab[] =
807 @ CREATE TEMP TABLE IF NOT EXISTS description(
808 @ rid INTEGER PRIMARY KEY, -- RID of the object
809 @ uuid TEXT, -- SHA1 hash of the object
810 @ ctime DATETIME, -- Time of creation
811 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
812 @ type TEXT, -- file, checkin, wiki, ticket, etc.
813 @ summary TEXT, -- Summary comment for the object
814 @ detail TEXT -- filename, checkin comment, etc
815 @ );
816 ;
817
818 /*
@@ -1001,11 +1006,11 @@
1006 int n = atoi(PD("n","5000"));
1007 int mx = db_int(0, "SELECT max(rid) FROM blob");
1008 char *zRange;
1009
1010 login_check_credentials();
1011 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1012 style_header("List Of Artifacts");
1013 if( mx>n && P("s")==0 ){
1014 int i;
1015 @ <p>Select a range of artifacts to view:</p>
1016 @ <ul>
@@ -1030,11 +1035,11 @@
1035 int rid = db_column_int(&q,0);
1036 const char *zUuid = db_column_text(&q, 1);
1037 const char *zDesc = db_column_text(&q, 2);
1038 int isPriv = db_column_int(&q,2);
1039 @ <tr><td align="right">%d(rid)</td>
1040 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
1041 @ <td align="left">%h(zDesc)</td>
1042 if( isPriv ){
1043 @ <td>(unpublished)</td>
1044 }
1045 @ </tr>
@@ -1142,5 +1147,75 @@
1147
1148 fossil_free(branchName);
1149 fossil_free(parentBranchName);
1150 return 0;
1151 }
1152
1153 /* Maximum number of collision examples to remember */
1154 #define MAX_COLLIDE 25
1155
1156 /*
1157 ** WEBPAGE: hash-collisions
1158 **
1159 ** Show the number of hash collisions for hash prefixes of various lengths.
1160 */
1161 void hash_collisions_webpage(void){
1162 int i, j, kk;
1163 int nHash = 0;
1164 Stmt q;
1165 char zPrev[UUID_SIZE+1];
1166 struct {
1167 int cnt;
1168 char *azHit[MAX_COLLIDE];
1169 char z[UUID_SIZE+1];
1170 } aCollide[UUID_SIZE+1];
1171 login_check_credentials();
1172 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1173 memset(aCollide, 0, sizeof(aCollide));
1174 memset(zPrev, 0, sizeof(zPrev));
1175 db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1176 while( db_step(&q)==SQLITE_ROW ){
1177 const char *zUuid = db_column_text(&q,0);
1178 int n = db_column_bytes(&q,0);
1179 int i;
1180 nHash++;
1181 for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1182 if( i>0 && i<=UUID_SIZE ){
1183 if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1184 aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1185 }
1186 aCollide[i].cnt++;
1187 if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1188 }
1189 memcpy(zPrev, zUuid, n+1);
1190 }
1191 db_finalize(&q);
1192 style_header("SHA1 Prefix Collisions");
1193 style_submenu_element("Activity Reports", 0, "reports");
1194 style_submenu_element("Stats", 0, "stat");
1195 @ <table border=1><thead>
1196 @ <tr><th>Length<th>Instances<th>First Instance</tr>
1197 @ </thead><tbody>
1198 for(i=1; i<=UUID_SIZE; i++){
1199 if( aCollide[i].cnt==0 ) continue;
1200 @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1201 }
1202 @ </tbody></table>
1203 @ <p>Total number of hashes: %d(nHash)</p>
1204 kk = 0;
1205 for(i=UUID_SIZE; i>=4; i--){
1206 if( aCollide[i].cnt==0 ) continue;
1207 if( aCollide[i].cnt>200 ) break;
1208 kk += aCollide[i].cnt;
1209 if( aCollide[i].cnt<25 ){
1210 @ <p>Collisions of length %d(i):
1211 }else{
1212 @ <p>First 25 collisions of length %d(i):
1213 }
1214 for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1215 char *zId = aCollide[i].azHit[j];
1216 if( zId==0 ) continue;
1217 @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1218 }
1219 }
1220 style_footer();
1221 }
1222
+85 -10
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114114
** If zType is NULL or "" or "*" then any type of artifact will serve.
115115
** If zType is "br" then find the first check-in of the named branch
116116
** rather than the last.
117117
** zType is "ci" in most use cases since we are usually searching for
118118
** a check-in.
119
+**
120
+** Note that the input zTag for types "t" and "e" is the SHA1 hash of
121
+** the ticket-change or event-change artifact, not the randomly generated
122
+** hexadecimal identifier assigned to tickets and events. Those identifiers
123
+** live in a separate namespace.
119124
*/
120125
int symbolic_name_to_rid(const char *zTag, const char *zType){
121126
int vid;
122127
int rid = 0;
123128
int nTag;
@@ -463,11 +468,11 @@
463468
canonical16(z, strlen(z));
464469
db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
465470
while( db_step(&q)==SQLITE_ROW ){
466471
const char *zUuid = db_column_text(&q, 0);
467472
int rid = db_column_int(&q, 1);
468
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
473
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
469474
@ %s(zUuid)</a> -
470475
object_description(rid, 0, 0);
471476
@ </p></li>
472477
}
473478
db_finalize(&q);
@@ -480,11 +485,11 @@
480485
" ORDER BY tkt_ctime DESC", z);
481486
while( db_step(&q)==SQLITE_ROW ){
482487
int rid = db_column_int(&q, 0);
483488
const char *zUuid = db_column_text(&q, 1);
484489
const char *zTitle = db_column_text(&q, 2);
485
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
490
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
486491
@ %s(zUuid)</a> -
487492
@ <ul></ul>
488493
@ Ticket
489494
hyperlink_to_uuid(zUuid);
490495
@ - %s(zTitle).
@@ -500,11 +505,11 @@
500505
" FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
501506
" AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
502507
while( db_step(&q)==SQLITE_ROW ){
503508
int rid = db_column_int(&q, 0);
504509
const char* zUuid = db_column_text(&q, 1);
505
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
510
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
506511
@ %s(zUuid)</a> -
507512
@ <ul><li>
508513
object_description(rid, 0, 0);
509514
@ </li></ul>
510515
@ </p></li>
@@ -796,18 +801,18 @@
796801
}
797802
798803
/*
799804
** Schema for the description table
800805
*/
801
-static const char zDescTab[] =
806
+static const char zDescTab[] =
802807
@ CREATE TEMP TABLE IF NOT EXISTS description(
803
-@ rid INTEGER PRIMARY KEY, -- RID of the object
804
-@ uuid TEXT, -- SHA1 hash of the object
805
-@ ctime DATETIME, -- Time of creation
808
+@ rid INTEGER PRIMARY KEY, -- RID of the object
809
+@ uuid TEXT, -- SHA1 hash of the object
810
+@ ctime DATETIME, -- Time of creation
806811
@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
807812
@ type TEXT, -- file, checkin, wiki, ticket, etc.
808
-@ summary TEXT, -- Summary comment for the object
813
+@ summary TEXT, -- Summary comment for the object
809814
@ detail TEXT -- filename, checkin comment, etc
810815
@ );
811816
;
812817
813818
/*
@@ -1001,11 +1006,11 @@
10011006
int n = atoi(PD("n","5000"));
10021007
int mx = db_int(0, "SELECT max(rid) FROM blob");
10031008
char *zRange;
10041009
10051010
login_check_credentials();
1006
- if( !g.perm.Read ){ login_needed(); return; }
1011
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
10071012
style_header("List Of Artifacts");
10081013
if( mx>n && P("s")==0 ){
10091014
int i;
10101015
@ <p>Select a range of artifacts to view:</p>
10111016
@ <ul>
@@ -1030,11 +1035,11 @@
10301035
int rid = db_column_int(&q,0);
10311036
const char *zUuid = db_column_text(&q, 1);
10321037
const char *zDesc = db_column_text(&q, 2);
10331038
int isPriv = db_column_int(&q,2);
10341039
@ <tr><td align="right">%d(rid)</td>
1035
- @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1040
+ @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
10361041
@ <td align="left">%h(zDesc)</td>
10371042
if( isPriv ){
10381043
@ <td>(unpublished)</td>
10391044
}
10401045
@ </tr>
@@ -1142,5 +1147,75 @@
11421147
11431148
fossil_free(branchName);
11441149
fossil_free(parentBranchName);
11451150
return 0;
11461151
}
1152
+
1153
+/* Maximum number of collision examples to remember */
1154
+#define MAX_COLLIDE 25
1155
+
1156
+/*
1157
+** WEBPAGE: hash-collisions
1158
+**
1159
+** Show the number of hash collisions for hash prefixes of various lengths.
1160
+*/
1161
+void hash_collisions_webpage(void){
1162
+ int i, j, kk;
1163
+ int nHash = 0;
1164
+ Stmt q;
1165
+ char zPrev[UUID_SIZE+1];
1166
+ struct {
1167
+ int cnt;
1168
+ char *azHit[MAX_COLLIDE];
1169
+ char z[UUID_SIZE+1];
1170
+ } aCollide[UUID_SIZE+1];
1171
+ login_check_credentials();
1172
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1173
+ memset(aCollide, 0, sizeof(aCollide));
1174
+ memset(zPrev, 0, sizeof(zPrev));
1175
+ db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1176
+ while( db_step(&q)==SQLITE_ROW ){
1177
+ const char *zUuid = db_column_text(&q,0);
1178
+ int n = db_column_bytes(&q,0);
1179
+ int i;
1180
+ nHash++;
1181
+ for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1182
+ if( i>0 && i<=UUID_SIZE ){
1183
+ if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1184
+ aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1185
+ }
1186
+ aCollide[i].cnt++;
1187
+ if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1188
+ }
1189
+ memcpy(zPrev, zUuid, n+1);
1190
+ }
1191
+ db_finalize(&q);
1192
+ style_header("SHA1 Prefix Collisions");
1193
+ style_submenu_element("Activity Reports", 0, "reports");
1194
+ style_submenu_element("Stats", 0, "stat");
1195
+ @ <table border=1><thead>
1196
+ @ <tr><th>Length<th>Instances<th>First Instance</tr>
1197
+ @ </thead><tbody>
1198
+ for(i=1; i<=UUID_SIZE; i++){
1199
+ if( aCollide[i].cnt==0 ) continue;
1200
+ @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1201
+ }
1202
+ @ </tbody></table>
1203
+ @ <p>Total number of hashes: %d(nHash)</p>
1204
+ kk = 0;
1205
+ for(i=UUID_SIZE; i>=4; i--){
1206
+ if( aCollide[i].cnt==0 ) continue;
1207
+ if( aCollide[i].cnt>200 ) break;
1208
+ kk += aCollide[i].cnt;
1209
+ if( aCollide[i].cnt<25 ){
1210
+ @ <p>Collisions of length %d(i):
1211
+ }else{
1212
+ @ <p>First 25 collisions of length %d(i):
1213
+ }
1214
+ for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1215
+ char *zId = aCollide[i].azHit[j];
1216
+ if( zId==0 ) continue;
1217
+ @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1218
+ }
1219
+ }
1220
+ style_footer();
1221
+}
11471222
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114 ** If zType is NULL or "" or "*" then any type of artifact will serve.
115 ** If zType is "br" then find the first check-in of the named branch
116 ** rather than the last.
117 ** zType is "ci" in most use cases since we are usually searching for
118 ** a check-in.
 
 
 
 
 
119 */
120 int symbolic_name_to_rid(const char *zTag, const char *zType){
121 int vid;
122 int rid = 0;
123 int nTag;
@@ -463,11 +468,11 @@
463 canonical16(z, strlen(z));
464 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
465 while( db_step(&q)==SQLITE_ROW ){
466 const char *zUuid = db_column_text(&q, 0);
467 int rid = db_column_int(&q, 1);
468 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
469 @ %s(zUuid)</a> -
470 object_description(rid, 0, 0);
471 @ </p></li>
472 }
473 db_finalize(&q);
@@ -480,11 +485,11 @@
480 " ORDER BY tkt_ctime DESC", z);
481 while( db_step(&q)==SQLITE_ROW ){
482 int rid = db_column_int(&q, 0);
483 const char *zUuid = db_column_text(&q, 1);
484 const char *zTitle = db_column_text(&q, 2);
485 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
486 @ %s(zUuid)</a> -
487 @ <ul></ul>
488 @ Ticket
489 hyperlink_to_uuid(zUuid);
490 @ - %s(zTitle).
@@ -500,11 +505,11 @@
500 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
501 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
502 while( db_step(&q)==SQLITE_ROW ){
503 int rid = db_column_int(&q, 0);
504 const char* zUuid = db_column_text(&q, 1);
505 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
506 @ %s(zUuid)</a> -
507 @ <ul><li>
508 object_description(rid, 0, 0);
509 @ </li></ul>
510 @ </p></li>
@@ -796,18 +801,18 @@
796 }
797
798 /*
799 ** Schema for the description table
800 */
801 static const char zDescTab[] =
802 @ CREATE TEMP TABLE IF NOT EXISTS description(
803 @ rid INTEGER PRIMARY KEY, -- RID of the object
804 @ uuid TEXT, -- SHA1 hash of the object
805 @ ctime DATETIME, -- Time of creation
806 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
807 @ type TEXT, -- file, checkin, wiki, ticket, etc.
808 @ summary TEXT, -- Summary comment for the object
809 @ detail TEXT -- filename, checkin comment, etc
810 @ );
811 ;
812
813 /*
@@ -1001,11 +1006,11 @@
1001 int n = atoi(PD("n","5000"));
1002 int mx = db_int(0, "SELECT max(rid) FROM blob");
1003 char *zRange;
1004
1005 login_check_credentials();
1006 if( !g.perm.Read ){ login_needed(); return; }
1007 style_header("List Of Artifacts");
1008 if( mx>n && P("s")==0 ){
1009 int i;
1010 @ <p>Select a range of artifacts to view:</p>
1011 @ <ul>
@@ -1030,11 +1035,11 @@
1030 int rid = db_column_int(&q,0);
1031 const char *zUuid = db_column_text(&q, 1);
1032 const char *zDesc = db_column_text(&q, 2);
1033 int isPriv = db_column_int(&q,2);
1034 @ <tr><td align="right">%d(rid)</td>
1035 @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1036 @ <td align="left">%h(zDesc)</td>
1037 if( isPriv ){
1038 @ <td>(unpublished)</td>
1039 }
1040 @ </tr>
@@ -1142,5 +1147,75 @@
1142
1143 fossil_free(branchName);
1144 fossil_free(parentBranchName);
1145 return 0;
1146 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1147
--- src/name.c
+++ src/name.c
@@ -114,10 +114,15 @@
114 ** If zType is NULL or "" or "*" then any type of artifact will serve.
115 ** If zType is "br" then find the first check-in of the named branch
116 ** rather than the last.
117 ** zType is "ci" in most use cases since we are usually searching for
118 ** a check-in.
119 **
120 ** Note that the input zTag for types "t" and "e" is the SHA1 hash of
121 ** the ticket-change or event-change artifact, not the randomly generated
122 ** hexadecimal identifier assigned to tickets and events. Those identifiers
123 ** live in a separate namespace.
124 */
125 int symbolic_name_to_rid(const char *zTag, const char *zType){
126 int vid;
127 int rid = 0;
128 int nTag;
@@ -463,11 +468,11 @@
468 canonical16(z, strlen(z));
469 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
470 while( db_step(&q)==SQLITE_ROW ){
471 const char *zUuid = db_column_text(&q, 0);
472 int rid = db_column_int(&q, 1);
473 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
474 @ %s(zUuid)</a> -
475 object_description(rid, 0, 0);
476 @ </p></li>
477 }
478 db_finalize(&q);
@@ -480,11 +485,11 @@
485 " ORDER BY tkt_ctime DESC", z);
486 while( db_step(&q)==SQLITE_ROW ){
487 int rid = db_column_int(&q, 0);
488 const char *zUuid = db_column_text(&q, 1);
489 const char *zTitle = db_column_text(&q, 2);
490 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
491 @ %s(zUuid)</a> -
492 @ <ul></ul>
493 @ Ticket
494 hyperlink_to_uuid(zUuid);
495 @ - %s(zTitle).
@@ -500,11 +505,11 @@
505 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
506 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
507 while( db_step(&q)==SQLITE_ROW ){
508 int rid = db_column_int(&q, 0);
509 const char* zUuid = db_column_text(&q, 1);
510 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
511 @ %s(zUuid)</a> -
512 @ <ul><li>
513 object_description(rid, 0, 0);
514 @ </li></ul>
515 @ </p></li>
@@ -796,18 +801,18 @@
801 }
802
803 /*
804 ** Schema for the description table
805 */
806 static const char zDescTab[] =
807 @ CREATE TEMP TABLE IF NOT EXISTS description(
808 @ rid INTEGER PRIMARY KEY, -- RID of the object
809 @ uuid TEXT, -- SHA1 hash of the object
810 @ ctime DATETIME, -- Time of creation
811 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
812 @ type TEXT, -- file, checkin, wiki, ticket, etc.
813 @ summary TEXT, -- Summary comment for the object
814 @ detail TEXT -- filename, checkin comment, etc
815 @ );
816 ;
817
818 /*
@@ -1001,11 +1006,11 @@
1006 int n = atoi(PD("n","5000"));
1007 int mx = db_int(0, "SELECT max(rid) FROM blob");
1008 char *zRange;
1009
1010 login_check_credentials();
1011 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1012 style_header("List Of Artifacts");
1013 if( mx>n && P("s")==0 ){
1014 int i;
1015 @ <p>Select a range of artifacts to view:</p>
1016 @ <ul>
@@ -1030,11 +1035,11 @@
1035 int rid = db_column_int(&q,0);
1036 const char *zUuid = db_column_text(&q, 1);
1037 const char *zDesc = db_column_text(&q, 2);
1038 int isPriv = db_column_int(&q,2);
1039 @ <tr><td align="right">%d(rid)</td>
1040 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
1041 @ <td align="left">%h(zDesc)</td>
1042 if( isPriv ){
1043 @ <td>(unpublished)</td>
1044 }
1045 @ </tr>
@@ -1142,5 +1147,75 @@
1147
1148 fossil_free(branchName);
1149 fossil_free(parentBranchName);
1150 return 0;
1151 }
1152
1153 /* Maximum number of collision examples to remember */
1154 #define MAX_COLLIDE 25
1155
1156 /*
1157 ** WEBPAGE: hash-collisions
1158 **
1159 ** Show the number of hash collisions for hash prefixes of various lengths.
1160 */
1161 void hash_collisions_webpage(void){
1162 int i, j, kk;
1163 int nHash = 0;
1164 Stmt q;
1165 char zPrev[UUID_SIZE+1];
1166 struct {
1167 int cnt;
1168 char *azHit[MAX_COLLIDE];
1169 char z[UUID_SIZE+1];
1170 } aCollide[UUID_SIZE+1];
1171 login_check_credentials();
1172 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1173 memset(aCollide, 0, sizeof(aCollide));
1174 memset(zPrev, 0, sizeof(zPrev));
1175 db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1176 while( db_step(&q)==SQLITE_ROW ){
1177 const char *zUuid = db_column_text(&q,0);
1178 int n = db_column_bytes(&q,0);
1179 int i;
1180 nHash++;
1181 for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1182 if( i>0 && i<=UUID_SIZE ){
1183 if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1184 aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1185 }
1186 aCollide[i].cnt++;
1187 if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1188 }
1189 memcpy(zPrev, zUuid, n+1);
1190 }
1191 db_finalize(&q);
1192 style_header("SHA1 Prefix Collisions");
1193 style_submenu_element("Activity Reports", 0, "reports");
1194 style_submenu_element("Stats", 0, "stat");
1195 @ <table border=1><thead>
1196 @ <tr><th>Length<th>Instances<th>First Instance</tr>
1197 @ </thead><tbody>
1198 for(i=1; i<=UUID_SIZE; i++){
1199 if( aCollide[i].cnt==0 ) continue;
1200 @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1201 }
1202 @ </tbody></table>
1203 @ <p>Total number of hashes: %d(nHash)</p>
1204 kk = 0;
1205 for(i=UUID_SIZE; i>=4; i--){
1206 if( aCollide[i].cnt==0 ) continue;
1207 if( aCollide[i].cnt>200 ) break;
1208 kk += aCollide[i].cnt;
1209 if( aCollide[i].cnt<25 ){
1210 @ <p>Collisions of length %d(i):
1211 }else{
1212 @ <p>First 25 collisions of length %d(i):
1213 }
1214 for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1215 char *zId = aCollide[i].azHit[j];
1216 if( zId==0 ) continue;
1217 @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1218 }
1219 }
1220 style_footer();
1221 }
1222
+2 -2
--- src/path.c
+++ src/path.c
@@ -543,11 +543,11 @@
543543
*/
544544
void test_rename_list_page(void){
545545
Stmt q;
546546
547547
login_check_credentials();
548
- if( !g.perm.Read ){ login_needed(); return; }
548
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
549549
style_header("List Of File Name Changes");
550550
@ <h3>NB: Experimental Page</h3>
551551
@ <table border="1" width="100%%">
552552
@ <tr><th>Date &amp; Time</th>
553553
@ <th>Old Name</th>
@@ -561,11 +561,11 @@
561561
const char *zUuid = db_column_text(&q, 3);
562562
@ <tr>
563563
@ <td>%z(href("%R/timeline?c=%t",zDate))%s(zDate)</a></td>
564564
@ <td>%z(href("%R/finfo?name=%t",zOld))%h(zOld)</a></td>
565565
@ <td>%z(href("%R/finfo?name=%t",zNew))%h(zNew)</a></td>
566
- @ <td>%z(href("%R/info/%s",zUuid))%S(zUuid)</a></td></tr>
566
+ @ <td>%z(href("%R/info/%!S",zUuid))%S(zUuid)</a></td></tr>
567567
}
568568
@ </table>
569569
db_finalize(&q);
570570
style_footer();
571571
}
572572
--- src/path.c
+++ src/path.c
@@ -543,11 +543,11 @@
543 */
544 void test_rename_list_page(void){
545 Stmt q;
546
547 login_check_credentials();
548 if( !g.perm.Read ){ login_needed(); return; }
549 style_header("List Of File Name Changes");
550 @ <h3>NB: Experimental Page</h3>
551 @ <table border="1" width="100%%">
552 @ <tr><th>Date &amp; Time</th>
553 @ <th>Old Name</th>
@@ -561,11 +561,11 @@
561 const char *zUuid = db_column_text(&q, 3);
562 @ <tr>
563 @ <td>%z(href("%R/timeline?c=%t",zDate))%s(zDate)</a></td>
564 @ <td>%z(href("%R/finfo?name=%t",zOld))%h(zOld)</a></td>
565 @ <td>%z(href("%R/finfo?name=%t",zNew))%h(zNew)</a></td>
566 @ <td>%z(href("%R/info/%s",zUuid))%S(zUuid)</a></td></tr>
567 }
568 @ </table>
569 db_finalize(&q);
570 style_footer();
571 }
572
--- src/path.c
+++ src/path.c
@@ -543,11 +543,11 @@
543 */
544 void test_rename_list_page(void){
545 Stmt q;
546
547 login_check_credentials();
548 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
549 style_header("List Of File Name Changes");
550 @ <h3>NB: Experimental Page</h3>
551 @ <table border="1" width="100%%">
552 @ <tr><th>Date &amp; Time</th>
553 @ <th>Old Name</th>
@@ -561,11 +561,11 @@
561 const char *zUuid = db_column_text(&q, 3);
562 @ <tr>
563 @ <td>%z(href("%R/timeline?c=%t",zDate))%s(zDate)</a></td>
564 @ <td>%z(href("%R/finfo?name=%t",zOld))%h(zOld)</a></td>
565 @ <td>%z(href("%R/finfo?name=%t",zNew))%h(zNew)</a></td>
566 @ <td>%z(href("%R/info/%!S",zUuid))%S(zUuid)</a></td></tr>
567 }
568 @ </table>
569 db_finalize(&q);
570 style_footer();
571 }
572
+39 -6
--- src/printf.c
+++ src/printf.c
@@ -23,10 +23,48 @@
2323
#if defined(_WIN32)
2424
# include <io.h>
2525
# include <fcntl.h>
2626
#endif
2727
#include <time.h>
28
+
29
+/* Two custom conversions are used to show a prefix of SHA1 hashes:
30
+**
31
+** %!S Prefix of a length appropriate for URLs
32
+** %S Prefix of a length appropriate for human display
33
+**
34
+** The following macros help determine those lengths. FOSSIL_HASH_DIGITS
35
+** is the default number of digits to display to humans. This value can
36
+** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL
37
+** is the minimum number of digits to be used in URLs. The number used
38
+** will always be at least 6 more than the number used for human output,
39
+** or 40 if the number of digits in human output is 34 or more.
40
+*/
41
+#ifndef FOSSIL_HASH_DIGITS
42
+# define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */
43
+#endif
44
+#ifndef FOSSIL_HASH_DIGITS_URL
45
+# define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */
46
+#endif
47
+
48
+/*
49
+** Return the number of SHA1 hash digits to display. The number is for
50
+** human output if the bForUrl is false and is destined for a URL if
51
+** bForUrl is false.
52
+*/
53
+static int hashDigits(int bForUrl){
54
+ static int nDigitHuman = 0;
55
+ static int nDigitUrl = 0;
56
+ if( nDigitHuman==0 ){
57
+ nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
58
+ if( nDigitHuman < 6 ) nDigitHuman = 6;
59
+ if( nDigitHuman > 40 ) nDigitHuman = 40;
60
+ nDigitUrl = nDigitHuman + 6;
61
+ if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL;
62
+ if( nDigitUrl > 40 ) nDigitUrl = 40;
63
+ }
64
+ return bForUrl ? nDigitUrl : nDigitHuman;
65
+}
2866
2967
/*
3068
** Conversion types fall into various categories as defined by the
3169
** following enumeration.
3270
*/
@@ -620,16 +658,11 @@
620658
if( bufpt==0 ){
621659
bufpt = "";
622660
}else if( xtype==etDYNSTRING ){
623661
zExtra = bufpt;
624662
}else if( xtype==etSTRINGID ){
625
- precision = 0;
626
- while( bufpt[precision]>='0' && bufpt[precision]<='9' ){
627
- precision++;
628
- }
629
- if( bufpt[precision]!=0 ) precision++;
630
- if( precision<10 ) precision=10;
663
+ precision = hashDigits(flag_altform2);
631664
}
632665
length = StrNLen32(bufpt, limit);
633666
if( precision>=0 && precision<length ) length = precision;
634667
break;
635668
}
636669
--- src/printf.c
+++ src/printf.c
@@ -23,10 +23,48 @@
23 #if defined(_WIN32)
24 # include <io.h>
25 # include <fcntl.h>
26 #endif
27 #include <time.h>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
29 /*
30 ** Conversion types fall into various categories as defined by the
31 ** following enumeration.
32 */
@@ -620,16 +658,11 @@
620 if( bufpt==0 ){
621 bufpt = "";
622 }else if( xtype==etDYNSTRING ){
623 zExtra = bufpt;
624 }else if( xtype==etSTRINGID ){
625 precision = 0;
626 while( bufpt[precision]>='0' && bufpt[precision]<='9' ){
627 precision++;
628 }
629 if( bufpt[precision]!=0 ) precision++;
630 if( precision<10 ) precision=10;
631 }
632 length = StrNLen32(bufpt, limit);
633 if( precision>=0 && precision<length ) length = precision;
634 break;
635 }
636
--- src/printf.c
+++ src/printf.c
@@ -23,10 +23,48 @@
23 #if defined(_WIN32)
24 # include <io.h>
25 # include <fcntl.h>
26 #endif
27 #include <time.h>
28
29 /* Two custom conversions are used to show a prefix of SHA1 hashes:
30 **
31 ** %!S Prefix of a length appropriate for URLs
32 ** %S Prefix of a length appropriate for human display
33 **
34 ** The following macros help determine those lengths. FOSSIL_HASH_DIGITS
35 ** is the default number of digits to display to humans. This value can
36 ** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL
37 ** is the minimum number of digits to be used in URLs. The number used
38 ** will always be at least 6 more than the number used for human output,
39 ** or 40 if the number of digits in human output is 34 or more.
40 */
41 #ifndef FOSSIL_HASH_DIGITS
42 # define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */
43 #endif
44 #ifndef FOSSIL_HASH_DIGITS_URL
45 # define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */
46 #endif
47
48 /*
49 ** Return the number of SHA1 hash digits to display. The number is for
50 ** human output if the bForUrl is false and is destined for a URL if
51 ** bForUrl is false.
52 */
53 static int hashDigits(int bForUrl){
54 static int nDigitHuman = 0;
55 static int nDigitUrl = 0;
56 if( nDigitHuman==0 ){
57 nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
58 if( nDigitHuman < 6 ) nDigitHuman = 6;
59 if( nDigitHuman > 40 ) nDigitHuman = 40;
60 nDigitUrl = nDigitHuman + 6;
61 if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL;
62 if( nDigitUrl > 40 ) nDigitUrl = 40;
63 }
64 return bForUrl ? nDigitUrl : nDigitHuman;
65 }
66
67 /*
68 ** Conversion types fall into various categories as defined by the
69 ** following enumeration.
70 */
@@ -620,16 +658,11 @@
658 if( bufpt==0 ){
659 bufpt = "";
660 }else if( xtype==etDYNSTRING ){
661 zExtra = bufpt;
662 }else if( xtype==etSTRINGID ){
663 precision = hashDigits(flag_altform2);
 
 
 
 
 
664 }
665 length = StrNLen32(bufpt, limit);
666 if( precision>=0 && precision<length ) length = precision;
667 break;
668 }
669
+7 -4
--- src/report.c
+++ src/report.c
@@ -40,11 +40,14 @@
4040
Stmt q;
4141
int rn = 0;
4242
int cnt = 0;
4343
4444
login_check_credentials();
45
- if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }
45
+ if( !g.perm.RdTkt && !g.perm.NewTkt ){
46
+ login_needed(g.anon.RdTkt || g.anon.NewTkt);
47
+ return;
48
+ }
4649
style_header("Ticket Main Menu");
4750
ticket_standard_submenu(T_ALL_BUT(T_REPLIST));
4851
if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
4952
zScript = ticket_reportlist_code();
5053
if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
@@ -293,11 +296,11 @@
293296
const char *zClrKey;
294297
Stmt q;
295298
296299
login_check_credentials();
297300
if( !g.perm.TktFmt ){
298
- login_needed();
301
+ login_needed(g.anon.TktFmt);
299302
return;
300303
}
301304
rn = atoi(PD("rn","0"));
302305
db_prepare(&q, "SELECT title, sqlcode, owner, cols "
303306
"FROM reportfmt WHERE rn=%d",rn);
@@ -343,11 +346,11 @@
343346
char *zSQL;
344347
char *zErr = 0;
345348
346349
login_check_credentials();
347350
if( !g.perm.TktFmt ){
348
- login_needed();
351
+ login_needed(g.anon.TktFmt);
349352
return;
350353
}
351354
/*view_add_functions(0);*/
352355
rn = atoi(PD("rn","0"));
353356
zTitle = P("t");
@@ -1078,11 +1081,11 @@
10781081
Stmt q;
10791082
char *zErr1 = 0;
10801083
char *zErr2 = 0;
10811084
10821085
login_check_credentials();
1083
- if( !g.perm.RdTkt ){ login_needed(); return; }
1086
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
10841087
rn = atoi(PD("rn","0"));
10851088
if( rn==0 ){
10861089
cgi_redirect("reportlist");
10871090
return;
10881091
}
10891092
--- src/report.c
+++ src/report.c
@@ -40,11 +40,14 @@
40 Stmt q;
41 int rn = 0;
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);
@@ -293,11 +296,11 @@
293 const char *zClrKey;
294 Stmt q;
295
296 login_check_credentials();
297 if( !g.perm.TktFmt ){
298 login_needed();
299 return;
300 }
301 rn = atoi(PD("rn","0"));
302 db_prepare(&q, "SELECT title, sqlcode, owner, cols "
303 "FROM reportfmt WHERE rn=%d",rn);
@@ -343,11 +346,11 @@
343 char *zSQL;
344 char *zErr = 0;
345
346 login_check_credentials();
347 if( !g.perm.TktFmt ){
348 login_needed();
349 return;
350 }
351 /*view_add_functions(0);*/
352 rn = atoi(PD("rn","0"));
353 zTitle = P("t");
@@ -1078,11 +1081,11 @@
1078 Stmt q;
1079 char *zErr1 = 0;
1080 char *zErr2 = 0;
1081
1082 login_check_credentials();
1083 if( !g.perm.RdTkt ){ login_needed(); return; }
1084 rn = atoi(PD("rn","0"));
1085 if( rn==0 ){
1086 cgi_redirect("reportlist");
1087 return;
1088 }
1089
--- src/report.c
+++ src/report.c
@@ -40,11 +40,14 @@
40 Stmt q;
41 int rn = 0;
42 int cnt = 0;
43
44 login_check_credentials();
45 if( !g.perm.RdTkt && !g.perm.NewTkt ){
46 login_needed(g.anon.RdTkt || g.anon.NewTkt);
47 return;
48 }
49 style_header("Ticket Main Menu");
50 ticket_standard_submenu(T_ALL_BUT(T_REPLIST));
51 if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
52 zScript = ticket_reportlist_code();
53 if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
@@ -293,11 +296,11 @@
296 const char *zClrKey;
297 Stmt q;
298
299 login_check_credentials();
300 if( !g.perm.TktFmt ){
301 login_needed(g.anon.TktFmt);
302 return;
303 }
304 rn = atoi(PD("rn","0"));
305 db_prepare(&q, "SELECT title, sqlcode, owner, cols "
306 "FROM reportfmt WHERE rn=%d",rn);
@@ -343,11 +346,11 @@
346 char *zSQL;
347 char *zErr = 0;
348
349 login_check_credentials();
350 if( !g.perm.TktFmt ){
351 login_needed(g.anon.TktFmt);
352 return;
353 }
354 /*view_add_functions(0);*/
355 rn = atoi(PD("rn","0"));
356 zTitle = P("t");
@@ -1078,11 +1081,11 @@
1081 Stmt q;
1082 char *zErr1 = 0;
1083 char *zErr2 = 0;
1084
1085 login_check_credentials();
1086 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
1087 rn = atoi(PD("rn","0"));
1088 if( rn==0 ){
1089 cgi_redirect("reportlist");
1090 return;
1091 }
1092
+292 -80
--- src/search.c
+++ src/search.c
@@ -213,11 +213,11 @@
213213
aiLastDoc[j] = iDoc;
214214
aiLastOfst[j] = i;
215215
for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){}
216216
for(ii=0; ii<k; ii++){
217217
if( anMatch[j-ii]<k ){
218
- anMatch[j-ii] = k;
218
+ anMatch[j-ii] = k*(nDoc-iDoc);
219219
aiBestDoc[j-ii] = aiLastDoc[j-ii];
220220
aiBestOfst[j-ii] = aiLastOfst[j-ii];
221221
}
222222
}
223223
break;
@@ -396,14 +396,18 @@
396396
static void search_match_sqlfunc(
397397
sqlite3_context *context,
398398
int argc,
399399
sqlite3_value **argv
400400
){
401
- const char *zSText = (const char*)sqlite3_value_text(argv[0]);
401
+ const char *azDoc[5];
402
+ int nDoc;
402403
int rc;
403
- if( zSText==0 ) return;
404
- rc = search_match(&gSearch, 1, &zSText);
404
+ for(nDoc=0; nDoc<ArraySize(azDoc) && nDoc<argc; nDoc++){
405
+ azDoc[nDoc] = (const char*)sqlite3_value_text(argv[nDoc]);
406
+ if( azDoc[nDoc]==0 ) azDoc[nDoc] = "";
407
+ }
408
+ rc = search_match(&gSearch, nDoc, azDoc);
405409
sqlite3_result_int(context, rc);
406410
}
407411
408412
/*
409413
** These SQL functions return the results of the last
@@ -435,16 +439,43 @@
435439
static void search_stext_sqlfunc(
436440
sqlite3_context *context,
437441
int argc,
438442
sqlite3_value **argv
439443
){
440
- Blob txt;
444
+ const char *zType = (const char*)sqlite3_value_text(argv[0]);
445
+ int rid = sqlite3_value_int(argv[1]);
446
+ const char *zName = (const char*)sqlite3_value_text(argv[2]);
447
+ sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1,
448
+ SQLITE_TRANSIENT);
449
+}
450
+static void search_title_sqlfunc(
451
+ sqlite3_context *context,
452
+ int argc,
453
+ sqlite3_value **argv
454
+){
455
+ const char *zType = (const char*)sqlite3_value_text(argv[0]);
456
+ int rid = sqlite3_value_int(argv[1]);
457
+ const char *zName = (const char*)sqlite3_value_text(argv[2]);
458
+ int nHdr;
459
+ char *z = search_stext_cached(zType[0], rid, zName, &nHdr);
460
+ if( nHdr || zType[0]!='d' ){
461
+ sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT);
462
+ }else{
463
+ sqlite3_result_value(context, argv[2]);
464
+ }
465
+}
466
+static void search_body_sqlfunc(
467
+ sqlite3_context *context,
468
+ int argc,
469
+ sqlite3_value **argv
470
+){
441471
const char *zType = (const char*)sqlite3_value_text(argv[0]);
442472
int rid = sqlite3_value_int(argv[1]);
443473
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);
474
+ int nHdr;
475
+ char *z = search_stext_cached(zType[0], rid, zName, &nHdr);
476
+ sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT);
446477
}
447478
448479
/*
449480
** Encode a string for use as a query parameter in a URL
450481
*/
@@ -463,20 +494,24 @@
463494
** do not delete the Search object.
464495
*/
465496
void search_sql_setup(sqlite3 *db){
466497
static int once = 0;
467498
if( once++ ) return;
468
- sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0,
499
+ sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0,
469500
search_match_sqlfunc, 0, 0);
470501
sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0,
471502
search_score_sqlfunc, 0, 0);
472503
sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0,
473504
search_snippet_sqlfunc, 0, 0);
474505
sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0,
475506
search_init_sqlfunc, 0, 0);
476507
sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0,
477508
search_stext_sqlfunc, 0, 0);
509
+ sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0,
510
+ search_title_sqlfunc, 0, 0);
511
+ sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0,
512
+ search_body_sqlfunc, 0, 0);
478513
sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0,
479514
search_urlencode_sqlfunc, 0, 0);
480515
}
481516
482517
/*
@@ -616,21 +651,23 @@
616651
if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
617652
db_multi_exec(
618653
"CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
619654
);
620655
db_multi_exec(
621
- "INSERT INTO x(label,url,score,date,snip)"
622
- " SELECT printf('Document: %%s',foci.filename),"
656
+ "INSERT INTO x(label,url,score,id,date,snip)"
657
+ " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename)),"
623658
" printf('/doc/%T/%%s',foci.filename),"
624659
" search_score(),"
660
+ " 'd'||blob.rid,"
625661
" (SELECT datetime(event.mtime) FROM event"
626662
" WHERE objid=symbolic_name_to_rid('trunk')),"
627663
" search_snippet()"
628664
" FROM foci CROSS JOIN blob"
629665
" WHERE checkinID=symbolic_name_to_rid('trunk')"
630666
" AND blob.uuid=foci.uuid"
631
- " AND search_match(stext('d',blob.rid,foci.filename))"
667
+ " AND search_match(title('d',blob.rid,foci.filename),"
668
+ " body('d',blob.rid,foci.filename))"
632669
" AND %z",
633670
zDocBr, glob_expr("foci.filename", zDocGlob)
634671
);
635672
}
636673
}
@@ -641,18 +678,19 @@
641678
" FROM tag, tagxref"
642679
" WHERE tag.tagname GLOB 'wiki-*'"
643680
" AND tagxref.tagid=tag.tagid"
644681
" GROUP BY 1"
645682
")"
646
- "INSERT INTO x(label,url,score,date,snip)"
683
+ "INSERT INTO x(label,url,score,id,date,snip)"
647684
" SELECT printf('Wiki: %%s',name),"
648685
" printf('/wiki?name=%%s',urlencode(name)),"
649686
" search_score(),"
687
+ " 'w'||rid,"
650688
" datetime(mtime),"
651689
" search_snippet()"
652690
" FROM wiki"
653
- " WHERE search_match(stext('w',rid,name));"
691
+ " WHERE search_match(title('w',rid,name),body('w',rid,name));"
654692
);
655693
}
656694
if( (srchFlags & SRCH_CKIN)!=0 ){
657695
db_multi_exec(
658696
"WITH ckin(uuid,rid,mtime) AS ("
@@ -659,34 +697,45 @@
659697
" SELECT blob.uuid, event.objid, event.mtime"
660698
" FROM event, blob"
661699
" WHERE event.type='ci'"
662700
" AND blob.rid=event.objid"
663701
")"
664
- "INSERT INTO x(label,url,score,date,snip)"
702
+ "INSERT INTO x(label,url,score,id,date,snip)"
665703
" SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
666704
" printf('/timeline?c=%%s&n=8&y=ci',uuid),"
667705
" search_score(),"
706
+ " 'c'||rid,"
668707
" datetime(mtime),"
669708
" search_snippet()"
670709
" FROM ckin"
671
- " WHERE search_match(stext('c',rid,NULL));"
710
+ " WHERE search_match('',body('c',rid,NULL));"
672711
);
673712
}
674713
if( (srchFlags & SRCH_TKT)!=0 ){
675714
db_multi_exec(
676
- "INSERT INTO x(label,url,score, date,snip)"
677
- " SELECT printf('Ticket [%%.17s] on %%s',"
678
- "tkt_uuid,datetime(tkt_mtime)),"
715
+ "INSERT INTO x(label,url,score,id,date,snip)"
716
+ " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL),"
717
+ "datetime(tkt_mtime)),"
679718
" printf('/tktview/%%.20s',tkt_uuid),"
680719
" search_score(),"
720
+ " 't'||tkt_id,"
681721
" datetime(tkt_mtime),"
682722
" search_snippet()"
683723
" FROM ticket"
684
- " WHERE search_match(stext('t',tkt_id,NULL));"
724
+ " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));"
685725
);
686726
}
687727
}
728
+
729
+/*
730
+** Number of significant bits in a u32
731
+*/
732
+static int nbits(u32 x){
733
+ int n = 0;
734
+ while( x ){ n++; x >>= 1; }
735
+ return n;
736
+}
688737
689738
/*
690739
** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')).
691740
*/
692741
static void search_rank_sqlfunc(
@@ -694,24 +743,45 @@
694743
int argc,
695744
sqlite3_value **argv
696745
){
697746
const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]);
698747
int nVal = sqlite3_value_bytes(argv[0])/4;
748
+ int nCol; /* Number of columns in the index */
699749
int nTerm; /* Number of search terms in the query */
700
- int i; /* Loop counter */
701
- double r = 1.0; /* Score */
750
+ int i, j; /* Loop counter */
751
+ double r = 0.0; /* Score */
752
+ const unsigned *aX, *aS;
702753
703
- if( nVal<6 ) return;
704
- if( aVal[1]!=1 ) return;
754
+ if( nVal<2 ) return;
705755
nTerm = aVal[0];
706
- r *= 1<<((30*(aVal[2]-1))/nTerm);
707
- for(i=1; i<=nTerm; i++){
708
- int hits_this_row = aVal[3*i];
709
- int hits_all_rows = aVal[3*i+1];
710
- int rows_with_hit = aVal[3*i+2];
711
- double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit;
712
- r *= hits_this_row/avg_hits_per_row;
756
+ nCol = aVal[1];
757
+ if( nVal<2+3*nCol*nTerm+nCol ) return;
758
+ aS = aVal+2;
759
+ aX = aS+nCol;
760
+ for(j=0; j<nCol; j++){
761
+ double x;
762
+ if( aS[j]>0 ){
763
+ x = 0.0;
764
+ for(i=0; i<nTerm; i++){
765
+ int hits_this_row;
766
+ int hits_all_rows;
767
+ int rows_with_hit;
768
+ double avg_hits_per_row;
769
+
770
+ hits_this_row = aX[j + i*nCol*3];
771
+ if( hits_this_row==0 )continue;
772
+ hits_all_rows = aX[j + i*nCol*3 + 1];
773
+ rows_with_hit = aX[j + i*nCol*3 + 2];
774
+ if( rows_with_hit==0 ) continue;
775
+ avg_hits_per_row = hits_all_rows/(double)rows_with_hit;
776
+ x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit));
777
+ }
778
+ x *= (1<<((30*(aS[j]-1))/nTerm));
779
+ }else{
780
+ x = 0.0;
781
+ }
782
+ r = r*10.0 + x;
713783
}
714784
#define SEARCH_DEBUG_RANK 0
715785
#if SEARCH_DEBUG_RANK
716786
{
717787
Blob x;
@@ -746,14 +816,15 @@
746816
if( srchFlags==0 ) return;
747817
sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0,
748818
search_rank_sqlfunc, 0, 0);
749819
blob_init(&sql, 0, 0);
750820
blob_appendf(&sql,
751
- "INSERT INTO x(label,url,score,date,snip) "
821
+ "INSERT INTO x(label,url,score,id,date,snip) "
752822
" SELECT ftsdocs.label,"
753823
" ftsdocs.url,"
754824
" rank(matchinfo(ftsidx,'pcsx')),"
825
+ " ftsdocs.type || ftsdocs.rid,"
755826
" datetime(ftsdocs.mtime),"
756827
" snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
757828
" FROM ftsidx CROSS JOIN ftsdocs"
758829
" WHERE ftsidx MATCH %Q"
759830
" AND ftsdocs.rowid=ftsidx.docid",
@@ -838,29 +909,30 @@
838909
**
839910
** Return the number of rows.
840911
*/
841912
int search_run_and_output(
842913
const char *zPattern, /* The query pattern */
843
- unsigned int srchFlags /* What to search over */
914
+ unsigned int srchFlags, /* What to search over */
915
+ int fDebug /* Extra debugging output */
844916
){
845917
Stmt q;
846918
int nRow = 0;
847919
848920
srchFlags = search_restrict(srchFlags);
849921
if( srchFlags==0 ) return 0;
850922
search_sql_setup(g.db);
851923
add_content_sql_commands(g.db);
852924
db_multi_exec(
853
- "CREATE TEMP TABLE x(label,url,score,date,snip);"
925
+ "CREATE TEMP TABLE x(label,url,score,id,date,snip);"
854926
);
855927
if( !search_index_exists() ){
856928
search_fullscan(zPattern, srchFlags);
857929
}else{
858930
search_update_index(srchFlags);
859931
search_indexed(zPattern, srchFlags);
860932
}
861
- db_prepare(&q, "SELECT url, snip, label"
933
+ db_prepare(&q, "SELECT url, snip, label, score, id"
862934
" FROM x"
863935
" ORDER BY score DESC, date DESC;");
864936
while( db_step(&q)==SQLITE_ROW ){
865937
const char *zUrl = db_column_text(&q, 0);
866938
const char *zSnippet = db_column_text(&q, 1);
@@ -867,12 +939,15 @@
867939
const char *zLabel = db_column_text(&q, 2);
868940
if( nRow==0 ){
869941
@ <ol>
870942
}
871943
nRow++;
872
- @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br>
873
- @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li>
944
+ @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a>
945
+ if( fDebug ){
946
+ @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4)))
947
+ }
948
+ @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li>
874949
}
875950
db_finalize(&q);
876951
if( nRow ){
877952
@ </ol>
878953
}
@@ -900,10 +975,11 @@
900975
const char *zType = 0;
901976
const char *zClass = 0;
902977
const char *zDisable1;
903978
const char *zDisable2;
904979
const char *zPattern;
980
+ int fDebug = PB("debug");
905981
srchFlags = search_restrict(srchFlags);
906982
switch( srchFlags ){
907983
case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
908984
case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
909985
case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
@@ -947,10 +1023,13 @@
9471023
cgi_printf(">%s</option>\n", aY[i].zNm);
9481024
}
9491025
@ </select>
9501026
srchFlags = newFlags;
9511027
}
1028
+ if( fDebug ){
1029
+ @ <input type="hidden" name="debug" value="1">
1030
+ }
9521031
@ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
9531032
if( srchFlags==0 ){
9541033
@ <p class="generalError">Search is disabled</p>
9551034
}
9561035
@ </div></form>
@@ -959,11 +1038,11 @@
9591038
if( zClass ){
9601039
@ <div class='searchResult searchResult%s(zClass)'>
9611040
}else{
9621041
@ <div class='searchResult'>
9631042
}
964
- if( search_run_and_output(zPattern, srchFlags)==0 ){
1043
+ if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
9651044
@ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
9661045
}
9671046
@ </div>
9681047
}
9691048
}
@@ -983,10 +1062,14 @@
9831062
9841063
9851064
/*
9861065
** This is a helper function for search_stext(). Writing into pOut
9871066
** the search text obtained from pIn according to zMimetype.
1067
+**
1068
+** The title of the document is the first line of text. All subsequent
1069
+** lines are the body. If the document has no title, the first line
1070
+** is blank.
9881071
*/
9891072
static void get_stext_by_mimetype(
9901073
Blob *pIn,
9911074
const char *zMimetype,
9921075
Blob *pOut
@@ -994,41 +1077,74 @@
9941077
Blob html, title;
9951078
blob_init(&html, 0, 0);
9961079
blob_init(&title, 0, 0);
9971080
if( zMimetype==0 ) zMimetype = "text/plain";
9981081
if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
999
- wiki_convert(pIn, &html, 0);
1082
+ Blob tail;
1083
+ blob_init(&tail, 0, 0);
1084
+ if( wiki_find_title(pIn, &title, &tail) ){
1085
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1086
+ wiki_convert(&tail, &html, 0);
1087
+ blob_reset(&tail);
1088
+ }else{
1089
+ blob_append(pOut, "\n", 1);
1090
+ wiki_convert(pIn, &html, 0);
1091
+ }
10001092
html_to_plaintext(blob_str(&html), pOut);
10011093
}else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
10021094
markdown_to_html(pIn, &title, &html);
1095
+ if( blob_size(&title) ){
1096
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1097
+ }else{
1098
+ blob_append(pOut, "\n", 1);
1099
+ }
10031100
html_to_plaintext(blob_str(&html), pOut);
10041101
}else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1102
+ if( doc_is_embedded_html(pIn, &title) ){
1103
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1104
+ }
10051105
html_to_plaintext(blob_str(pIn), pOut);
10061106
}else{
1007
- *pOut = *pIn;
1008
- blob_init(pIn, 0, 0);
1107
+ blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
10091108
}
10101109
blob_reset(&html);
10111110
blob_reset(&title);
10121111
}
10131112
10141113
/*
10151114
** Query pQuery is pointing at a single row of output. Append a text
10161115
** representation of every text-compatible column to pAccum.
10171116
*/
1018
-static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){
1117
+static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){
10191118
int n = db_column_count(pQuery);
10201119
int i;
1120
+ const char *zMime = 0;
1121
+ if( iTitle>=0 && iTitle<n ){
1122
+ if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){
1123
+ blob_append(pAccum, db_column_text(pQuery,iTitle), -1);
1124
+ }
1125
+ blob_append(pAccum, "\n", 1);
1126
+ }
10211127
for(i=0; i<n; i++){
10221128
const char *zColName = db_column_name(pQuery,i);
1129
+ int eType = db_column_type(pQuery,i);
1130
+ if( i==iTitle ) continue;
10231131
if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue;
1024
- if( fossil_stricmp(zColName,"mimetype")==0 ) continue;
1025
- switch( db_column_type(pQuery,i) ){
1026
- case SQLITE_INTEGER:
1027
- case SQLITE_FLOAT:
1028
- case SQLITE_TEXT:
1029
- blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i));
1132
+ if( fossil_strnicmp(zColName,"private_",8)==0 ) continue;
1133
+ if( eType==SQLITE_BLOB || eType==SQLITE_NULL ) continue;
1134
+ if( fossil_stricmp(zColName,"mimetype")==0 ){
1135
+ zMime = db_column_text(pQuery,i);
1136
+ if( fossil_strcmp(zMime,"text/plain")==0 ) zMime = 0;
1137
+ }else if( zMime==0 || eType!=SQLITE_TEXT ){
1138
+ blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i));
1139
+ }else{
1140
+ Blob txt;
1141
+ blob_init(&txt, db_column_text(pQuery,i), -1);
1142
+ blob_appendf(pAccum, "%s: ", zColName);
1143
+ get_stext_by_mimetype(&txt, zMime, pAccum);
1144
+ blob_append(pAccum, " |", 2);
1145
+ blob_reset(&txt);
10301146
}
10311147
}
10321148
}
10331149
10341150
@@ -1054,11 +1170,11 @@
10541170
){
10551171
blob_init(pOut, 0, 0);
10561172
switch( cType ){
10571173
case 'd': { /* Documents */
10581174
Blob doc;
1059
- content_get(rid, &doc);
1175
+ content_get(rid, &doc);
10601176
blob_to_utf8_no_bom(&doc, 0);
10611177
get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
10621178
blob_reset(&doc);
10631179
break;
10641180
}
@@ -1073,10 +1189,11 @@
10731189
manifest_destroy(pWiki);
10741190
break;
10751191
}
10761192
case 'c': { /* Check-in Comments */
10771193
static Stmt q;
1194
+ static int isPlainText = -1;
10781195
db_static_prepare(&q,
10791196
"SELECT coalesce(ecomment,comment)"
10801197
" ||' (user: '||coalesce(euser,user,'?')"
10811198
" ||', tags: '||"
10821199
" (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,44 +1200,99 @@
10831200
" FROM tag, tagxref"
10841201
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
10851202
" AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
10861203
" ||')'"
10871204
" FROM event WHERE objid=:x AND type='ci'");
1205
+ if( isPlainText<0 ){
1206
+ isPlainText = db_get_boolean("timeline-plaintext",0);
1207
+ }
10881208
db_bind_int(&q, ":x", rid);
10891209
if( db_step(&q)==SQLITE_ROW ){
1090
- db_column_blob(&q, 0, pOut);
10911210
blob_append(pOut, "\n", 1);
1211
+ if( isPlainText ){
1212
+ db_column_blob(&q, 0, pOut);
1213
+ }else{
1214
+ Blob x;
1215
+ blob_init(&x,0,0);
1216
+ db_column_blob(&q, 0, &x);
1217
+ get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut);
1218
+ blob_reset(&x);
1219
+ }
10921220
}
10931221
db_reset(&q);
10941222
break;
10951223
}
10961224
case 't': { /* Tickets */
10971225
static Stmt q1;
1098
- Blob raw;
1226
+ static int iTitle = -1;
10991227
db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
1100
- blob_init(&raw,0,0);
11011228
db_bind_int(&q1, ":rid", rid);
11021229
if( db_step(&q1)==SQLITE_ROW ){
1103
- append_all_ticket_fields(&raw, &q1);
1230
+ if( iTitle<0 ){
1231
+ int n = db_column_count(&q1);
1232
+ for(iTitle=0; iTitle<n; iTitle++){
1233
+ if( fossil_stricmp(db_column_name(&q1,iTitle),"title")==0 ) break;
1234
+ }
1235
+ }
1236
+ append_all_ticket_fields(pOut, &q1, iTitle);
11041237
}
11051238
db_reset(&q1);
11061239
if( db_table_exists("repository","ticketchng") ){
11071240
static Stmt q2;
11081241
db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid"
11091242
" ORDER BY tkt_mtime");
11101243
db_bind_int(&q2, ":rid", rid);
11111244
while( db_step(&q2)==SQLITE_ROW ){
1112
- append_all_ticket_fields(&raw, &q2);
1245
+ append_all_ticket_fields(pOut, &q2, -1);
11131246
}
11141247
db_reset(&q2);
11151248
}
1116
- html_to_plaintext(blob_str(&raw), pOut);
1117
- blob_reset(&raw);
11181249
break;
11191250
}
11201251
}
11211252
}
1253
+
1254
+/*
1255
+** This routine is a wrapper around search_stext().
1256
+**
1257
+** This routine looks up the search text, stores it in an internal
1258
+** buffer, and returns a pointer to the text. Subsequent requests
1259
+** for the same document return the same pointer. The returned pointer
1260
+** is valid until the next invocation of this routine. Call this routine
1261
+** with an eType of 0 to clear the cache.
1262
+*/
1263
+char *search_stext_cached(
1264
+ char cType, /* Type of document */
1265
+ int rid, /* BLOB.RID or TAG.TAGID value for document */
1266
+ const char *zName, /* Auxiliary information */
1267
+ int *pnTitle /* OUT: length of title in bytes excluding \n */
1268
+){
1269
+ static struct {
1270
+ Blob stext; /* Cached search text */
1271
+ char cType; /* The type */
1272
+ int rid; /* The RID */
1273
+ int nTitle; /* Number of bytes in title */
1274
+ } cache;
1275
+ int i;
1276
+ char *z;
1277
+ if( cType!=cache.cType || rid!=cache.rid ){
1278
+ if( cache.rid>0 ){
1279
+ blob_reset(&cache.stext);
1280
+ }else{
1281
+ blob_init(&cache.stext,0,0);
1282
+ }
1283
+ cache.cType = cType;
1284
+ cache.rid = rid;
1285
+ if( cType==0 ) return 0;
1286
+ search_stext(cType, rid, zName, &cache.stext);
1287
+ z = blob_str(&cache.stext);
1288
+ for(i=0; z[i] && z[i]!='\n'; i++){}
1289
+ cache.nTitle = i;
1290
+ }
1291
+ if( pnTitle ) *pnTitle = cache.nTitle;
1292
+ return blob_str(&cache.stext);
1293
+}
11221294
11231295
/*
11241296
** COMMAND: test-search-stext
11251297
**
11261298
** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1131,10 +1303,30 @@
11311303
if( g.argc!=5 ) usage("TYPE RID NAME");
11321304
search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
11331305
fossil_print("%s\n",blob_str(&out));
11341306
blob_reset(&out);
11351307
}
1308
+
1309
+/*
1310
+** COMMAND: test-convert-stext
1311
+**
1312
+** Usage: fossil test-convert-stext FILE MIMETYPE
1313
+**
1314
+** Read the content of FILE and convert it to stext according to MIMETYPE.
1315
+** Send the result to standard output.
1316
+*/
1317
+void test_convert_stext(void){
1318
+ Blob in, out;
1319
+ db_find_and_open_repository(0,0);
1320
+ if( g.argc!=4 ) usage("FILENAME MIMETYPE");
1321
+ blob_read_from_file(&in, g.argv[2]);
1322
+ blob_init(&out, 0, 0);
1323
+ get_stext_by_mimetype(&in, g.argv[3], &out);
1324
+ fossil_print("%s\n",blob_str(&out));
1325
+ blob_reset(&in);
1326
+ blob_reset(&out);
1327
+}
11361328
11371329
/* The schema for the full-text index
11381330
*/
11391331
static const char zFtsSchema[] =
11401332
@ -- One entry for each possible search result
@@ -1145,20 +1337,21 @@
11451337
@ name TEXT, -- Additional document description
11461338
@ idxed BOOLEAN, -- True if currently in the index
11471339
@ label TEXT, -- Label to print on search results
11481340
@ url TEXT, -- URL to access this document
11491341
@ mtime DATE, -- Date when document created
1342
+@ bx TEXT, -- Temporary "body" content cache
11501343
@ UNIQUE(type,rid)
11511344
@ );
11521345
@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
11531346
@ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
11541347
@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
11551348
@ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1156
-@ stext(type,rid,name) AS 'stext'
1349
+@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body'
11571350
@ FROM ftsdocs;
11581351
@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1159
-@ USING fts4(content="ftscontent", stext);
1352
+@ USING fts4(content="ftscontent", title, body%s);
11601353
;
11611354
static const char zFtsDrop[] =
11621355
@ DROP TABLE IF EXISTS "%w".ftsidx;
11631356
@ DROP VIEW IF EXISTS "%w".ftscontent;
11641357
@ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1168,13 +1361,15 @@
11681361
** Create or drop the tables associated with a full-text index.
11691362
*/
11701363
static int searchIdxExists = -1;
11711364
void search_create_index(void){
11721365
const char *zDb = db_name("repository");
1366
+ int useStemmer = db_get_boolean("search-stemmer",0);
1367
+ const char *zExtra = useStemmer ? ",tokenize=porter" : "";
11731368
search_sql_setup(g.db);
1174
- db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/,
1175
- zDb, zDb, zDb, zDb, zDb);
1369
+ db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/,
1370
+ zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/);
11761371
searchIdxExists = 1;
11771372
}
11781373
void search_drop_index(void){
11791374
const char *zDb = db_name("repository");
11801375
db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1292,34 +1487,39 @@
12921487
db_multi_exec(
12931488
"DELETE FROM ftsdocs WHERE type='d'"
12941489
" AND rid NOT IN (SELECT rid FROM current_docs)"
12951490
);
12961491
db_multi_exec(
1297
- "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)"
1492
+ "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)"
12981493
" SELECT 'd', rid, name, 0,"
1299
- " printf('Document: %%s',name),"
1494
+ " title('d',rid,name),"
1495
+ " body('d',rid,name),"
13001496
" printf('/doc/%q/%%s',urlencode(name)),"
13011497
" %.17g"
13021498
" FROM current_docs",
13031499
zBrUuid, rTime
13041500
);
13051501
db_multi_exec(
1306
- "INSERT INTO ftsidx(docid,stext)"
1307
- " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
1502
+ "INSERT INTO ftsidx(docid,title,body)"
1503
+ " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed"
13081504
);
13091505
db_multi_exec(
1310
- "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
1506
+ "UPDATE ftsdocs SET"
1507
+ " idxed=1,"
1508
+ " bx=NULL,"
1509
+ " label='Document: '||label"
1510
+ " WHERE type='d' AND NOT idxed"
13111511
);
13121512
}
13131513
13141514
/*
13151515
** Deal with all of the unindexed 'c' terms in FTSDOCS
13161516
*/
13171517
static void search_update_checkin_index(void){
13181518
db_multi_exec(
1319
- "INSERT INTO ftsidx(docid,stext)"
1320
- " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs"
1519
+ "INSERT INTO ftsidx(docid,title,body)"
1520
+ " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs"
13211521
" WHERE type='c' AND NOT idxed;"
13221522
);
13231523
db_multi_exec(
13241524
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
13251525
" SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
@@ -1336,19 +1536,20 @@
13361536
/*
13371537
** Deal with all of the unindexed 't' terms in FTSDOCS
13381538
*/
13391539
static void search_update_ticket_index(void){
13401540
db_multi_exec(
1341
- "INSERT INTO ftsidx(docid,stext)"
1342
- " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs"
1541
+ "INSERT INTO ftsidx(docid,title,body)"
1542
+ " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs"
13431543
" WHERE type='t' AND NOT idxed;"
13441544
);
13451545
if( db_changes()==0 ) return;
13461546
db_multi_exec(
13471547
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
13481548
" SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
1349
- " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime)),"
1549
+ " printf('Ticket: %%s (%%s)',title('t',tkt_id,null),"
1550
+ " datetime(tkt_mtime)),"
13501551
" printf('/tktview/%%.20s',tkt_uuid),"
13511552
" tkt_mtime"
13521553
" FROM ftsdocs, ticket"
13531554
" WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
13541555
" AND ticket.tkt_id=ftsdocs.rid"
@@ -1358,12 +1559,12 @@
13581559
/*
13591560
** Deal with all of the unindexed 'w' terms in FTSDOCS
13601561
*/
13611562
static void search_update_wiki_index(void){
13621563
db_multi_exec(
1363
- "INSERT INTO ftsidx(docid,stext)"
1364
- " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs"
1564
+ "INSERT INTO ftsidx(docid,title,body)"
1565
+ " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs"
13651566
" WHERE type='w' AND NOT idxed;"
13661567
);
13671568
if( db_changes()==0 ) return;
13681569
db_multi_exec(
13691570
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1416,19 +1617,22 @@
14161617
** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
14171618
**
14181619
** The "fossil fts-config" command configures the full-text search capabilities
14191620
** of the repository. Subcommands:
14201621
**
1421
-** reindex Rebuild the search index. Create it if it does
1422
-** not already exist
1622
+** reindex Rebuild the search index. This is a no-op if
1623
+** index search is disabled
14231624
**
14241625
** index (on|off) Turn the search index on or off
14251626
**
14261627
** enable cdtw Enable various kinds of search. c=Check-ins,
14271628
** d=Documents, t=Tickets, w=Wiki.
14281629
**
14291630
** disable cdtw Disable versious kinds of search
1631
+**
1632
+** stemmer (on|off) Turn the Porter stemmer on or off for indexed
1633
+** search. (Unindexed search is never stemmed.)
14301634
**
14311635
** The current search settings are displayed after any changes are applied.
14321636
** Run this command with no arguments to simply see the settings.
14331637
*/
14341638
void test_fts_cmd(void){
@@ -1435,18 +1639,19 @@
14351639
static const struct { int iCmd; const char *z; } aCmd[] = {
14361640
{ 1, "reindex" },
14371641
{ 2, "index" },
14381642
{ 3, "disable" },
14391643
{ 4, "enable" },
1644
+ { 5, "stemmer" },
14401645
};
14411646
static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
1442
- { "search-ckin", "check-in search:", "c" },
1443
- { "search-doc", "document search:", "d" },
1444
- { "search-tkt", "ticket search:", "t" },
1445
- { "search-wiki", "wiki search:", "w" },
1647
+ { "search-ckin", "check-in search:", "c" },
1648
+ { "search-doc", "document search:", "d" },
1649
+ { "search-tkt", "ticket search:", "t" },
1650
+ { "search-wiki", "wiki search:", "w" },
14461651
};
1447
- char *zSubCmd;
1652
+ char *zSubCmd = 0;
14481653
int i, j, n;
14491654
int iCmd = 0;
14501655
int iAction = 0;
14511656
db_find_and_open_repository(0, 0);
14521657
if( g.argc>2 ){
@@ -1464,11 +1669,11 @@
14641669
return;
14651670
}
14661671
iCmd = aCmd[i].iCmd;
14671672
}
14681673
if( iCmd==1 ){
1469
- iAction = 2;
1674
+ if( search_index_exists() ) iAction = 2;
14701675
}
14711676
if( iCmd==2 ){
14721677
if( g.argc<3 ) usage("index (on|off)");
14731678
iAction = 1 + is_truth(g.argv[3]);
14741679
}
@@ -1475,18 +1680,23 @@
14751680
db_begin_transaction();
14761681
14771682
/* Adjust search settings */
14781683
if( iCmd==3 || iCmd==4 ){
14791684
const char *zCtrl;
1480
- if( g.argc<4 ) usage("enable STRING");
1685
+ if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd));
14811686
zCtrl = g.argv[3];
14821687
for(j=0; j<ArraySize(aSetng); j++){
14831688
if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
14841689
db_set_int(aSetng[j].zSetting, iCmd-3, 0);
14851690
}
14861691
}
14871692
}
1693
+ if( iCmd==5 ){
1694
+ if( g.argc<4 ) usage("porter ON/OFF");
1695
+ db_set_int("search-stemmer", is_truth(g.argv[3]), 0);
1696
+ }
1697
+
14881698
14891699
/* destroy or rebuild the index, if requested */
14901700
if( iAction>=1 ){
14911701
search_drop_index();
14921702
}
@@ -1497,14 +1707,16 @@
14971707
/* Always show the status before ending */
14981708
for(i=0; i<ArraySize(aSetng); i++){
14991709
fossil_print("%-16s %s\n", aSetng[i].zName,
15001710
db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
15011711
}
1712
+ fossil_print("%-16s %s\n", "Porter stemmer:",
1713
+ db_get_boolean("search-stemmer",0) ? "on" : "off");
15021714
if( search_index_exists() ){
15031715
fossil_print("%-16s enabled\n", "full-text index:");
15041716
fossil_print("%-16s %d\n", "documents:",
15051717
db_int(0, "SELECT count(*) FROM ftsdocs"));
15061718
}else{
15071719
fossil_print("%-16s disabled\n", "full-text index:");
15081720
}
15091721
db_end_transaction(0);
15101722
}
15111723
--- src/search.c
+++ src/search.c
@@ -213,11 +213,11 @@
213 aiLastDoc[j] = iDoc;
214 aiLastOfst[j] = i;
215 for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){}
216 for(ii=0; ii<k; ii++){
217 if( anMatch[j-ii]<k ){
218 anMatch[j-ii] = k;
219 aiBestDoc[j-ii] = aiLastDoc[j-ii];
220 aiBestOfst[j-ii] = aiLastOfst[j-ii];
221 }
222 }
223 break;
@@ -396,14 +396,18 @@
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
@@ -435,16 +439,43 @@
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 */
@@ -463,20 +494,24 @@
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 /*
@@ -616,21 +651,23 @@
616 if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
617 db_multi_exec(
618 "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
619 );
620 db_multi_exec(
621 "INSERT INTO x(label,url,score,date,snip)"
622 " SELECT printf('Document: %%s',foci.filename),"
623 " printf('/doc/%T/%%s',foci.filename),"
624 " search_score(),"
 
625 " (SELECT datetime(event.mtime) FROM event"
626 " WHERE objid=symbolic_name_to_rid('trunk')),"
627 " search_snippet()"
628 " FROM foci CROSS JOIN blob"
629 " WHERE checkinID=symbolic_name_to_rid('trunk')"
630 " AND blob.uuid=foci.uuid"
631 " AND search_match(stext('d',blob.rid,foci.filename))"
 
632 " AND %z",
633 zDocBr, glob_expr("foci.filename", zDocGlob)
634 );
635 }
636 }
@@ -641,18 +678,19 @@
641 " FROM tag, tagxref"
642 " WHERE tag.tagname GLOB 'wiki-*'"
643 " AND tagxref.tagid=tag.tagid"
644 " GROUP BY 1"
645 ")"
646 "INSERT INTO x(label,url,score,date,snip)"
647 " SELECT printf('Wiki: %%s',name),"
648 " printf('/wiki?name=%%s',urlencode(name)),"
649 " search_score(),"
 
650 " datetime(mtime),"
651 " search_snippet()"
652 " FROM wiki"
653 " WHERE search_match(stext('w',rid,name));"
654 );
655 }
656 if( (srchFlags & SRCH_CKIN)!=0 ){
657 db_multi_exec(
658 "WITH ckin(uuid,rid,mtime) AS ("
@@ -659,34 +697,45 @@
659 " SELECT blob.uuid, event.objid, event.mtime"
660 " FROM event, blob"
661 " WHERE event.type='ci'"
662 " AND blob.rid=event.objid"
663 ")"
664 "INSERT INTO x(label,url,score,date,snip)"
665 " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
666 " printf('/timeline?c=%%s&n=8&y=ci',uuid),"
667 " search_score(),"
 
668 " datetime(mtime),"
669 " search_snippet()"
670 " FROM ckin"
671 " WHERE search_match(stext('c',rid,NULL));"
672 );
673 }
674 if( (srchFlags & SRCH_TKT)!=0 ){
675 db_multi_exec(
676 "INSERT INTO x(label,url,score, date,snip)"
677 " SELECT printf('Ticket [%%.17s] on %%s',"
678 "tkt_uuid,datetime(tkt_mtime)),"
679 " printf('/tktview/%%.20s',tkt_uuid),"
680 " search_score(),"
 
681 " datetime(tkt_mtime),"
682 " search_snippet()"
683 " FROM ticket"
684 " WHERE search_match(stext('t',tkt_id,NULL));"
685 );
686 }
687 }
 
 
 
 
 
 
 
 
 
688
689 /*
690 ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')).
691 */
692 static void search_rank_sqlfunc(
@@ -694,24 +743,45 @@
694 int argc,
695 sqlite3_value **argv
696 ){
697 const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]);
698 int nVal = sqlite3_value_bytes(argv[0])/4;
 
699 int nTerm; /* Number of search terms in the query */
700 int i; /* Loop counter */
701 double r = 1.0; /* Score */
 
702
703 if( nVal<6 ) return;
704 if( aVal[1]!=1 ) return;
705 nTerm = aVal[0];
706 r *= 1<<((30*(aVal[2]-1))/nTerm);
707 for(i=1; i<=nTerm; i++){
708 int hits_this_row = aVal[3*i];
709 int hits_all_rows = aVal[3*i+1];
710 int rows_with_hit = aVal[3*i+2];
711 double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit;
712 r *= hits_this_row/avg_hits_per_row;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713 }
714 #define SEARCH_DEBUG_RANK 0
715 #if SEARCH_DEBUG_RANK
716 {
717 Blob x;
@@ -746,14 +816,15 @@
746 if( srchFlags==0 ) return;
747 sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0,
748 search_rank_sqlfunc, 0, 0);
749 blob_init(&sql, 0, 0);
750 blob_appendf(&sql,
751 "INSERT INTO x(label,url,score,date,snip) "
752 " SELECT ftsdocs.label,"
753 " ftsdocs.url,"
754 " rank(matchinfo(ftsidx,'pcsx')),"
 
755 " datetime(ftsdocs.mtime),"
756 " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
757 " FROM ftsidx CROSS JOIN ftsdocs"
758 " WHERE ftsidx MATCH %Q"
759 " AND ftsdocs.rowid=ftsidx.docid",
@@ -838,29 +909,30 @@
838 **
839 ** Return the number of rows.
840 */
841 int search_run_and_output(
842 const char *zPattern, /* The query pattern */
843 unsigned int srchFlags /* What to search over */
 
844 ){
845 Stmt q;
846 int nRow = 0;
847
848 srchFlags = search_restrict(srchFlags);
849 if( srchFlags==0 ) return 0;
850 search_sql_setup(g.db);
851 add_content_sql_commands(g.db);
852 db_multi_exec(
853 "CREATE TEMP TABLE x(label,url,score,date,snip);"
854 );
855 if( !search_index_exists() ){
856 search_fullscan(zPattern, srchFlags);
857 }else{
858 search_update_index(srchFlags);
859 search_indexed(zPattern, srchFlags);
860 }
861 db_prepare(&q, "SELECT url, snip, label"
862 " FROM x"
863 " ORDER BY score DESC, date DESC;");
864 while( db_step(&q)==SQLITE_ROW ){
865 const char *zUrl = db_column_text(&q, 0);
866 const char *zSnippet = db_column_text(&q, 1);
@@ -867,12 +939,15 @@
867 const char *zLabel = db_column_text(&q, 2);
868 if( nRow==0 ){
869 @ <ol>
870 }
871 nRow++;
872 @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br>
873 @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li>
 
 
 
874 }
875 db_finalize(&q);
876 if( nRow ){
877 @ </ol>
878 }
@@ -900,10 +975,11 @@
900 const char *zType = 0;
901 const char *zClass = 0;
902 const char *zDisable1;
903 const char *zDisable2;
904 const char *zPattern;
 
905 srchFlags = search_restrict(srchFlags);
906 switch( srchFlags ){
907 case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
908 case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
909 case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
@@ -947,10 +1023,13 @@
947 cgi_printf(">%s</option>\n", aY[i].zNm);
948 }
949 @ </select>
950 srchFlags = newFlags;
951 }
 
 
 
952 @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
953 if( srchFlags==0 ){
954 @ <p class="generalError">Search is disabled</p>
955 }
956 @ </div></form>
@@ -959,11 +1038,11 @@
959 if( zClass ){
960 @ <div class='searchResult searchResult%s(zClass)'>
961 }else{
962 @ <div class='searchResult'>
963 }
964 if( search_run_and_output(zPattern, srchFlags)==0 ){
965 @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
966 }
967 @ </div>
968 }
969 }
@@ -983,10 +1062,14 @@
983
984
985 /*
986 ** This is a helper function for search_stext(). Writing into pOut
987 ** the search text obtained from pIn according to zMimetype.
 
 
 
 
988 */
989 static void get_stext_by_mimetype(
990 Blob *pIn,
991 const char *zMimetype,
992 Blob *pOut
@@ -994,41 +1077,74 @@
994 Blob html, title;
995 blob_init(&html, 0, 0);
996 blob_init(&title, 0, 0);
997 if( zMimetype==0 ) zMimetype = "text/plain";
998 if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
999 wiki_convert(pIn, &html, 0);
 
 
 
 
 
 
 
 
 
1000 html_to_plaintext(blob_str(&html), pOut);
1001 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1002 markdown_to_html(pIn, &title, &html);
 
 
 
 
 
1003 html_to_plaintext(blob_str(&html), pOut);
1004 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
 
 
 
1005 html_to_plaintext(blob_str(pIn), pOut);
1006 }else{
1007 *pOut = *pIn;
1008 blob_init(pIn, 0, 0);
1009 }
1010 blob_reset(&html);
1011 blob_reset(&title);
1012 }
1013
1014 /*
1015 ** Query pQuery is pointing at a single row of output. Append a text
1016 ** representation of every text-compatible column to pAccum.
1017 */
1018 static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){
1019 int n = db_column_count(pQuery);
1020 int i;
 
 
 
 
 
 
 
1021 for(i=0; i<n; i++){
1022 const char *zColName = db_column_name(pQuery,i);
 
 
1023 if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue;
1024 if( fossil_stricmp(zColName,"mimetype")==0 ) continue;
1025 switch( db_column_type(pQuery,i) ){
1026 case SQLITE_INTEGER:
1027 case SQLITE_FLOAT:
1028 case SQLITE_TEXT:
1029 blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i));
 
 
 
 
 
 
 
 
1030 }
1031 }
1032 }
1033
1034
@@ -1054,11 +1170,11 @@
1054 ){
1055 blob_init(pOut, 0, 0);
1056 switch( cType ){
1057 case 'd': { /* Documents */
1058 Blob doc;
1059 content_get(rid, &doc);
1060 blob_to_utf8_no_bom(&doc, 0);
1061 get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
1062 blob_reset(&doc);
1063 break;
1064 }
@@ -1073,10 +1189,11 @@
1073 manifest_destroy(pWiki);
1074 break;
1075 }
1076 case 'c': { /* Check-in Comments */
1077 static Stmt q;
 
1078 db_static_prepare(&q,
1079 "SELECT coalesce(ecomment,comment)"
1080 " ||' (user: '||coalesce(euser,user,'?')"
1081 " ||', tags: '||"
1082 " (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,44 +1200,99 @@
1083 " FROM tag, tagxref"
1084 " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
1085 " AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
1086 " ||')'"
1087 " FROM event WHERE objid=:x AND type='ci'");
 
 
 
1088 db_bind_int(&q, ":x", rid);
1089 if( db_step(&q)==SQLITE_ROW ){
1090 db_column_blob(&q, 0, pOut);
1091 blob_append(pOut, "\n", 1);
 
 
 
 
 
 
 
 
 
1092 }
1093 db_reset(&q);
1094 break;
1095 }
1096 case 't': { /* Tickets */
1097 static Stmt q1;
1098 Blob raw;
1099 db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
1100 blob_init(&raw,0,0);
1101 db_bind_int(&q1, ":rid", rid);
1102 if( db_step(&q1)==SQLITE_ROW ){
1103 append_all_ticket_fields(&raw, &q1);
 
 
 
 
 
 
1104 }
1105 db_reset(&q1);
1106 if( db_table_exists("repository","ticketchng") ){
1107 static Stmt q2;
1108 db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid"
1109 " ORDER BY tkt_mtime");
1110 db_bind_int(&q2, ":rid", rid);
1111 while( db_step(&q2)==SQLITE_ROW ){
1112 append_all_ticket_fields(&raw, &q2);
1113 }
1114 db_reset(&q2);
1115 }
1116 html_to_plaintext(blob_str(&raw), pOut);
1117 blob_reset(&raw);
1118 break;
1119 }
1120 }
1121 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
1123 /*
1124 ** COMMAND: test-search-stext
1125 **
1126 ** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1131,10 +1303,30 @@
1131 if( g.argc!=5 ) usage("TYPE RID NAME");
1132 search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
1133 fossil_print("%s\n",blob_str(&out));
1134 blob_reset(&out);
1135 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
1137 /* The schema for the full-text index
1138 */
1139 static const char zFtsSchema[] =
1140 @ -- One entry for each possible search result
@@ -1145,20 +1337,21 @@
1145 @ name TEXT, -- Additional document description
1146 @ idxed BOOLEAN, -- True if currently in the index
1147 @ label TEXT, -- Label to print on search results
1148 @ url TEXT, -- URL to access this document
1149 @ mtime DATE, -- Date when document created
 
1150 @ UNIQUE(type,rid)
1151 @ );
1152 @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
1153 @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
1154 @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
1155 @ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1156 @ stext(type,rid,name) AS 'stext'
1157 @ FROM ftsdocs;
1158 @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1159 @ USING fts4(content="ftscontent", stext);
1160 ;
1161 static const char zFtsDrop[] =
1162 @ DROP TABLE IF EXISTS "%w".ftsidx;
1163 @ DROP VIEW IF EXISTS "%w".ftscontent;
1164 @ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1168,13 +1361,15 @@
1168 ** Create or drop the tables associated with a full-text index.
1169 */
1170 static int searchIdxExists = -1;
1171 void search_create_index(void){
1172 const char *zDb = db_name("repository");
 
 
1173 search_sql_setup(g.db);
1174 db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/,
1175 zDb, zDb, zDb, zDb, zDb);
1176 searchIdxExists = 1;
1177 }
1178 void search_drop_index(void){
1179 const char *zDb = db_name("repository");
1180 db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1292,34 +1487,39 @@
1292 db_multi_exec(
1293 "DELETE FROM ftsdocs WHERE type='d'"
1294 " AND rid NOT IN (SELECT rid FROM current_docs)"
1295 );
1296 db_multi_exec(
1297 "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)"
1298 " SELECT 'd', rid, name, 0,"
1299 " printf('Document: %%s',name),"
 
1300 " printf('/doc/%q/%%s',urlencode(name)),"
1301 " %.17g"
1302 " FROM current_docs",
1303 zBrUuid, rTime
1304 );
1305 db_multi_exec(
1306 "INSERT INTO ftsidx(docid,stext)"
1307 " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
1308 );
1309 db_multi_exec(
1310 "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
 
 
 
 
1311 );
1312 }
1313
1314 /*
1315 ** Deal with all of the unindexed 'c' terms in FTSDOCS
1316 */
1317 static void search_update_checkin_index(void){
1318 db_multi_exec(
1319 "INSERT INTO ftsidx(docid,stext)"
1320 " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs"
1321 " WHERE type='c' AND NOT idxed;"
1322 );
1323 db_multi_exec(
1324 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1325 " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
@@ -1336,19 +1536,20 @@
1336 /*
1337 ** Deal with all of the unindexed 't' terms in FTSDOCS
1338 */
1339 static void search_update_ticket_index(void){
1340 db_multi_exec(
1341 "INSERT INTO ftsidx(docid,stext)"
1342 " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs"
1343 " WHERE type='t' AND NOT idxed;"
1344 );
1345 if( db_changes()==0 ) return;
1346 db_multi_exec(
1347 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1348 " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
1349 " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime)),"
 
1350 " printf('/tktview/%%.20s',tkt_uuid),"
1351 " tkt_mtime"
1352 " FROM ftsdocs, ticket"
1353 " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
1354 " AND ticket.tkt_id=ftsdocs.rid"
@@ -1358,12 +1559,12 @@
1358 /*
1359 ** Deal with all of the unindexed 'w' terms in FTSDOCS
1360 */
1361 static void search_update_wiki_index(void){
1362 db_multi_exec(
1363 "INSERT INTO ftsidx(docid,stext)"
1364 " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs"
1365 " WHERE type='w' AND NOT idxed;"
1366 );
1367 if( db_changes()==0 ) return;
1368 db_multi_exec(
1369 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1416,19 +1617,22 @@
1416 ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
1417 **
1418 ** The "fossil fts-config" command configures the full-text search capabilities
1419 ** of the repository. Subcommands:
1420 **
1421 ** reindex Rebuild the search index. Create it if it does
1422 ** not already exist
1423 **
1424 ** index (on|off) Turn the search index on or off
1425 **
1426 ** enable cdtw Enable various kinds of search. c=Check-ins,
1427 ** d=Documents, t=Tickets, w=Wiki.
1428 **
1429 ** disable cdtw Disable versious kinds of search
 
 
 
1430 **
1431 ** The current search settings are displayed after any changes are applied.
1432 ** Run this command with no arguments to simply see the settings.
1433 */
1434 void test_fts_cmd(void){
@@ -1435,18 +1639,19 @@
1435 static const struct { int iCmd; const char *z; } aCmd[] = {
1436 { 1, "reindex" },
1437 { 2, "index" },
1438 { 3, "disable" },
1439 { 4, "enable" },
 
1440 };
1441 static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
1442 { "search-ckin", "check-in search:", "c" },
1443 { "search-doc", "document search:", "d" },
1444 { "search-tkt", "ticket search:", "t" },
1445 { "search-wiki", "wiki search:", "w" },
1446 };
1447 char *zSubCmd;
1448 int i, j, n;
1449 int iCmd = 0;
1450 int iAction = 0;
1451 db_find_and_open_repository(0, 0);
1452 if( g.argc>2 ){
@@ -1464,11 +1669,11 @@
1464 return;
1465 }
1466 iCmd = aCmd[i].iCmd;
1467 }
1468 if( iCmd==1 ){
1469 iAction = 2;
1470 }
1471 if( iCmd==2 ){
1472 if( g.argc<3 ) usage("index (on|off)");
1473 iAction = 1 + is_truth(g.argv[3]);
1474 }
@@ -1475,18 +1680,23 @@
1475 db_begin_transaction();
1476
1477 /* Adjust search settings */
1478 if( iCmd==3 || iCmd==4 ){
1479 const char *zCtrl;
1480 if( g.argc<4 ) usage("enable STRING");
1481 zCtrl = g.argv[3];
1482 for(j=0; j<ArraySize(aSetng); j++){
1483 if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
1484 db_set_int(aSetng[j].zSetting, iCmd-3, 0);
1485 }
1486 }
1487 }
 
 
 
 
 
1488
1489 /* destroy or rebuild the index, if requested */
1490 if( iAction>=1 ){
1491 search_drop_index();
1492 }
@@ -1497,14 +1707,16 @@
1497 /* Always show the status before ending */
1498 for(i=0; i<ArraySize(aSetng); i++){
1499 fossil_print("%-16s %s\n", aSetng[i].zName,
1500 db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
1501 }
 
 
1502 if( search_index_exists() ){
1503 fossil_print("%-16s enabled\n", "full-text index:");
1504 fossil_print("%-16s %d\n", "documents:",
1505 db_int(0, "SELECT count(*) FROM ftsdocs"));
1506 }else{
1507 fossil_print("%-16s disabled\n", "full-text index:");
1508 }
1509 db_end_transaction(0);
1510 }
1511
--- src/search.c
+++ src/search.c
@@ -213,11 +213,11 @@
213 aiLastDoc[j] = iDoc;
214 aiLastOfst[j] = i;
215 for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){}
216 for(ii=0; ii<k; ii++){
217 if( anMatch[j-ii]<k ){
218 anMatch[j-ii] = k*(nDoc-iDoc);
219 aiBestDoc[j-ii] = aiLastDoc[j-ii];
220 aiBestOfst[j-ii] = aiLastOfst[j-ii];
221 }
222 }
223 break;
@@ -396,14 +396,18 @@
396 static void search_match_sqlfunc(
397 sqlite3_context *context,
398 int argc,
399 sqlite3_value **argv
400 ){
401 const char *azDoc[5];
402 int nDoc;
403 int rc;
404 for(nDoc=0; nDoc<ArraySize(azDoc) && nDoc<argc; nDoc++){
405 azDoc[nDoc] = (const char*)sqlite3_value_text(argv[nDoc]);
406 if( azDoc[nDoc]==0 ) azDoc[nDoc] = "";
407 }
408 rc = search_match(&gSearch, nDoc, azDoc);
409 sqlite3_result_int(context, rc);
410 }
411
412 /*
413 ** These SQL functions return the results of the last
@@ -435,16 +439,43 @@
439 static void search_stext_sqlfunc(
440 sqlite3_context *context,
441 int argc,
442 sqlite3_value **argv
443 ){
444 const char *zType = (const char*)sqlite3_value_text(argv[0]);
445 int rid = sqlite3_value_int(argv[1]);
446 const char *zName = (const char*)sqlite3_value_text(argv[2]);
447 sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1,
448 SQLITE_TRANSIENT);
449 }
450 static void search_title_sqlfunc(
451 sqlite3_context *context,
452 int argc,
453 sqlite3_value **argv
454 ){
455 const char *zType = (const char*)sqlite3_value_text(argv[0]);
456 int rid = sqlite3_value_int(argv[1]);
457 const char *zName = (const char*)sqlite3_value_text(argv[2]);
458 int nHdr;
459 char *z = search_stext_cached(zType[0], rid, zName, &nHdr);
460 if( nHdr || zType[0]!='d' ){
461 sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT);
462 }else{
463 sqlite3_result_value(context, argv[2]);
464 }
465 }
466 static void search_body_sqlfunc(
467 sqlite3_context *context,
468 int argc,
469 sqlite3_value **argv
470 ){
471 const char *zType = (const char*)sqlite3_value_text(argv[0]);
472 int rid = sqlite3_value_int(argv[1]);
473 const char *zName = (const char*)sqlite3_value_text(argv[2]);
474 int nHdr;
475 char *z = search_stext_cached(zType[0], rid, zName, &nHdr);
476 sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT);
477 }
478
479 /*
480 ** Encode a string for use as a query parameter in a URL
481 */
@@ -463,20 +494,24 @@
494 ** do not delete the Search object.
495 */
496 void search_sql_setup(sqlite3 *db){
497 static int once = 0;
498 if( once++ ) return;
499 sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0,
500 search_match_sqlfunc, 0, 0);
501 sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0,
502 search_score_sqlfunc, 0, 0);
503 sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0,
504 search_snippet_sqlfunc, 0, 0);
505 sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0,
506 search_init_sqlfunc, 0, 0);
507 sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0,
508 search_stext_sqlfunc, 0, 0);
509 sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0,
510 search_title_sqlfunc, 0, 0);
511 sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0,
512 search_body_sqlfunc, 0, 0);
513 sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0,
514 search_urlencode_sqlfunc, 0, 0);
515 }
516
517 /*
@@ -616,21 +651,23 @@
651 if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
652 db_multi_exec(
653 "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
654 );
655 db_multi_exec(
656 "INSERT INTO x(label,url,score,id,date,snip)"
657 " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename)),"
658 " printf('/doc/%T/%%s',foci.filename),"
659 " search_score(),"
660 " 'd'||blob.rid,"
661 " (SELECT datetime(event.mtime) FROM event"
662 " WHERE objid=symbolic_name_to_rid('trunk')),"
663 " search_snippet()"
664 " FROM foci CROSS JOIN blob"
665 " WHERE checkinID=symbolic_name_to_rid('trunk')"
666 " AND blob.uuid=foci.uuid"
667 " AND search_match(title('d',blob.rid,foci.filename),"
668 " body('d',blob.rid,foci.filename))"
669 " AND %z",
670 zDocBr, glob_expr("foci.filename", zDocGlob)
671 );
672 }
673 }
@@ -641,18 +678,19 @@
678 " FROM tag, tagxref"
679 " WHERE tag.tagname GLOB 'wiki-*'"
680 " AND tagxref.tagid=tag.tagid"
681 " GROUP BY 1"
682 ")"
683 "INSERT INTO x(label,url,score,id,date,snip)"
684 " SELECT printf('Wiki: %%s',name),"
685 " printf('/wiki?name=%%s',urlencode(name)),"
686 " search_score(),"
687 " 'w'||rid,"
688 " datetime(mtime),"
689 " search_snippet()"
690 " FROM wiki"
691 " WHERE search_match(title('w',rid,name),body('w',rid,name));"
692 );
693 }
694 if( (srchFlags & SRCH_CKIN)!=0 ){
695 db_multi_exec(
696 "WITH ckin(uuid,rid,mtime) AS ("
@@ -659,34 +697,45 @@
697 " SELECT blob.uuid, event.objid, event.mtime"
698 " FROM event, blob"
699 " WHERE event.type='ci'"
700 " AND blob.rid=event.objid"
701 ")"
702 "INSERT INTO x(label,url,score,id,date,snip)"
703 " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
704 " printf('/timeline?c=%%s&n=8&y=ci',uuid),"
705 " search_score(),"
706 " 'c'||rid,"
707 " datetime(mtime),"
708 " search_snippet()"
709 " FROM ckin"
710 " WHERE search_match('',body('c',rid,NULL));"
711 );
712 }
713 if( (srchFlags & SRCH_TKT)!=0 ){
714 db_multi_exec(
715 "INSERT INTO x(label,url,score,id,date,snip)"
716 " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL),"
717 "datetime(tkt_mtime)),"
718 " printf('/tktview/%%.20s',tkt_uuid),"
719 " search_score(),"
720 " 't'||tkt_id,"
721 " datetime(tkt_mtime),"
722 " search_snippet()"
723 " FROM ticket"
724 " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));"
725 );
726 }
727 }
728
729 /*
730 ** Number of significant bits in a u32
731 */
732 static int nbits(u32 x){
733 int n = 0;
734 while( x ){ n++; x >>= 1; }
735 return n;
736 }
737
738 /*
739 ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')).
740 */
741 static void search_rank_sqlfunc(
@@ -694,24 +743,45 @@
743 int argc,
744 sqlite3_value **argv
745 ){
746 const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]);
747 int nVal = sqlite3_value_bytes(argv[0])/4;
748 int nCol; /* Number of columns in the index */
749 int nTerm; /* Number of search terms in the query */
750 int i, j; /* Loop counter */
751 double r = 0.0; /* Score */
752 const unsigned *aX, *aS;
753
754 if( nVal<2 ) return;
 
755 nTerm = aVal[0];
756 nCol = aVal[1];
757 if( nVal<2+3*nCol*nTerm+nCol ) return;
758 aS = aVal+2;
759 aX = aS+nCol;
760 for(j=0; j<nCol; j++){
761 double x;
762 if( aS[j]>0 ){
763 x = 0.0;
764 for(i=0; i<nTerm; i++){
765 int hits_this_row;
766 int hits_all_rows;
767 int rows_with_hit;
768 double avg_hits_per_row;
769
770 hits_this_row = aX[j + i*nCol*3];
771 if( hits_this_row==0 )continue;
772 hits_all_rows = aX[j + i*nCol*3 + 1];
773 rows_with_hit = aX[j + i*nCol*3 + 2];
774 if( rows_with_hit==0 ) continue;
775 avg_hits_per_row = hits_all_rows/(double)rows_with_hit;
776 x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit));
777 }
778 x *= (1<<((30*(aS[j]-1))/nTerm));
779 }else{
780 x = 0.0;
781 }
782 r = r*10.0 + x;
783 }
784 #define SEARCH_DEBUG_RANK 0
785 #if SEARCH_DEBUG_RANK
786 {
787 Blob x;
@@ -746,14 +816,15 @@
816 if( srchFlags==0 ) return;
817 sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0,
818 search_rank_sqlfunc, 0, 0);
819 blob_init(&sql, 0, 0);
820 blob_appendf(&sql,
821 "INSERT INTO x(label,url,score,id,date,snip) "
822 " SELECT ftsdocs.label,"
823 " ftsdocs.url,"
824 " rank(matchinfo(ftsidx,'pcsx')),"
825 " ftsdocs.type || ftsdocs.rid,"
826 " datetime(ftsdocs.mtime),"
827 " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
828 " FROM ftsidx CROSS JOIN ftsdocs"
829 " WHERE ftsidx MATCH %Q"
830 " AND ftsdocs.rowid=ftsidx.docid",
@@ -838,29 +909,30 @@
909 **
910 ** Return the number of rows.
911 */
912 int search_run_and_output(
913 const char *zPattern, /* The query pattern */
914 unsigned int srchFlags, /* What to search over */
915 int fDebug /* Extra debugging output */
916 ){
917 Stmt q;
918 int nRow = 0;
919
920 srchFlags = search_restrict(srchFlags);
921 if( srchFlags==0 ) return 0;
922 search_sql_setup(g.db);
923 add_content_sql_commands(g.db);
924 db_multi_exec(
925 "CREATE TEMP TABLE x(label,url,score,id,date,snip);"
926 );
927 if( !search_index_exists() ){
928 search_fullscan(zPattern, srchFlags);
929 }else{
930 search_update_index(srchFlags);
931 search_indexed(zPattern, srchFlags);
932 }
933 db_prepare(&q, "SELECT url, snip, label, score, id"
934 " FROM x"
935 " ORDER BY score DESC, date DESC;");
936 while( db_step(&q)==SQLITE_ROW ){
937 const char *zUrl = db_column_text(&q, 0);
938 const char *zSnippet = db_column_text(&q, 1);
@@ -867,12 +939,15 @@
939 const char *zLabel = db_column_text(&q, 2);
940 if( nRow==0 ){
941 @ <ol>
942 }
943 nRow++;
944 @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a>
945 if( fDebug ){
946 @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4)))
947 }
948 @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li>
949 }
950 db_finalize(&q);
951 if( nRow ){
952 @ </ol>
953 }
@@ -900,10 +975,11 @@
975 const char *zType = 0;
976 const char *zClass = 0;
977 const char *zDisable1;
978 const char *zDisable2;
979 const char *zPattern;
980 int fDebug = PB("debug");
981 srchFlags = search_restrict(srchFlags);
982 switch( srchFlags ){
983 case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
984 case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
985 case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
@@ -947,10 +1023,13 @@
1023 cgi_printf(">%s</option>\n", aY[i].zNm);
1024 }
1025 @ </select>
1026 srchFlags = newFlags;
1027 }
1028 if( fDebug ){
1029 @ <input type="hidden" name="debug" value="1">
1030 }
1031 @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
1032 if( srchFlags==0 ){
1033 @ <p class="generalError">Search is disabled</p>
1034 }
1035 @ </div></form>
@@ -959,11 +1038,11 @@
1038 if( zClass ){
1039 @ <div class='searchResult searchResult%s(zClass)'>
1040 }else{
1041 @ <div class='searchResult'>
1042 }
1043 if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
1044 @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
1045 }
1046 @ </div>
1047 }
1048 }
@@ -983,10 +1062,14 @@
1062
1063
1064 /*
1065 ** This is a helper function for search_stext(). Writing into pOut
1066 ** the search text obtained from pIn according to zMimetype.
1067 **
1068 ** The title of the document is the first line of text. All subsequent
1069 ** lines are the body. If the document has no title, the first line
1070 ** is blank.
1071 */
1072 static void get_stext_by_mimetype(
1073 Blob *pIn,
1074 const char *zMimetype,
1075 Blob *pOut
@@ -994,41 +1077,74 @@
1077 Blob html, title;
1078 blob_init(&html, 0, 0);
1079 blob_init(&title, 0, 0);
1080 if( zMimetype==0 ) zMimetype = "text/plain";
1081 if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
1082 Blob tail;
1083 blob_init(&tail, 0, 0);
1084 if( wiki_find_title(pIn, &title, &tail) ){
1085 blob_appendf(pOut, "%s\n", blob_str(&title));
1086 wiki_convert(&tail, &html, 0);
1087 blob_reset(&tail);
1088 }else{
1089 blob_append(pOut, "\n", 1);
1090 wiki_convert(pIn, &html, 0);
1091 }
1092 html_to_plaintext(blob_str(&html), pOut);
1093 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1094 markdown_to_html(pIn, &title, &html);
1095 if( blob_size(&title) ){
1096 blob_appendf(pOut, "%s\n", blob_str(&title));
1097 }else{
1098 blob_append(pOut, "\n", 1);
1099 }
1100 html_to_plaintext(blob_str(&html), pOut);
1101 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1102 if( doc_is_embedded_html(pIn, &title) ){
1103 blob_appendf(pOut, "%s\n", blob_str(&title));
1104 }
1105 html_to_plaintext(blob_str(pIn), pOut);
1106 }else{
1107 blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
 
1108 }
1109 blob_reset(&html);
1110 blob_reset(&title);
1111 }
1112
1113 /*
1114 ** Query pQuery is pointing at a single row of output. Append a text
1115 ** representation of every text-compatible column to pAccum.
1116 */
1117 static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){
1118 int n = db_column_count(pQuery);
1119 int i;
1120 const char *zMime = 0;
1121 if( iTitle>=0 && iTitle<n ){
1122 if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){
1123 blob_append(pAccum, db_column_text(pQuery,iTitle), -1);
1124 }
1125 blob_append(pAccum, "\n", 1);
1126 }
1127 for(i=0; i<n; i++){
1128 const char *zColName = db_column_name(pQuery,i);
1129 int eType = db_column_type(pQuery,i);
1130 if( i==iTitle ) continue;
1131 if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue;
1132 if( fossil_strnicmp(zColName,"private_",8)==0 ) continue;
1133 if( eType==SQLITE_BLOB || eType==SQLITE_NULL ) continue;
1134 if( fossil_stricmp(zColName,"mimetype")==0 ){
1135 zMime = db_column_text(pQuery,i);
1136 if( fossil_strcmp(zMime,"text/plain")==0 ) zMime = 0;
1137 }else if( zMime==0 || eType!=SQLITE_TEXT ){
1138 blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i));
1139 }else{
1140 Blob txt;
1141 blob_init(&txt, db_column_text(pQuery,i), -1);
1142 blob_appendf(pAccum, "%s: ", zColName);
1143 get_stext_by_mimetype(&txt, zMime, pAccum);
1144 blob_append(pAccum, " |", 2);
1145 blob_reset(&txt);
1146 }
1147 }
1148 }
1149
1150
@@ -1054,11 +1170,11 @@
1170 ){
1171 blob_init(pOut, 0, 0);
1172 switch( cType ){
1173 case 'd': { /* Documents */
1174 Blob doc;
1175 content_get(rid, &doc);
1176 blob_to_utf8_no_bom(&doc, 0);
1177 get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
1178 blob_reset(&doc);
1179 break;
1180 }
@@ -1073,10 +1189,11 @@
1189 manifest_destroy(pWiki);
1190 break;
1191 }
1192 case 'c': { /* Check-in Comments */
1193 static Stmt q;
1194 static int isPlainText = -1;
1195 db_static_prepare(&q,
1196 "SELECT coalesce(ecomment,comment)"
1197 " ||' (user: '||coalesce(euser,user,'?')"
1198 " ||', tags: '||"
1199 " (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,44 +1200,99 @@
1200 " FROM tag, tagxref"
1201 " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
1202 " AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
1203 " ||')'"
1204 " FROM event WHERE objid=:x AND type='ci'");
1205 if( isPlainText<0 ){
1206 isPlainText = db_get_boolean("timeline-plaintext",0);
1207 }
1208 db_bind_int(&q, ":x", rid);
1209 if( db_step(&q)==SQLITE_ROW ){
 
1210 blob_append(pOut, "\n", 1);
1211 if( isPlainText ){
1212 db_column_blob(&q, 0, pOut);
1213 }else{
1214 Blob x;
1215 blob_init(&x,0,0);
1216 db_column_blob(&q, 0, &x);
1217 get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut);
1218 blob_reset(&x);
1219 }
1220 }
1221 db_reset(&q);
1222 break;
1223 }
1224 case 't': { /* Tickets */
1225 static Stmt q1;
1226 static int iTitle = -1;
1227 db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
 
1228 db_bind_int(&q1, ":rid", rid);
1229 if( db_step(&q1)==SQLITE_ROW ){
1230 if( iTitle<0 ){
1231 int n = db_column_count(&q1);
1232 for(iTitle=0; iTitle<n; iTitle++){
1233 if( fossil_stricmp(db_column_name(&q1,iTitle),"title")==0 ) break;
1234 }
1235 }
1236 append_all_ticket_fields(pOut, &q1, iTitle);
1237 }
1238 db_reset(&q1);
1239 if( db_table_exists("repository","ticketchng") ){
1240 static Stmt q2;
1241 db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid"
1242 " ORDER BY tkt_mtime");
1243 db_bind_int(&q2, ":rid", rid);
1244 while( db_step(&q2)==SQLITE_ROW ){
1245 append_all_ticket_fields(pOut, &q2, -1);
1246 }
1247 db_reset(&q2);
1248 }
 
 
1249 break;
1250 }
1251 }
1252 }
1253
1254 /*
1255 ** This routine is a wrapper around search_stext().
1256 **
1257 ** This routine looks up the search text, stores it in an internal
1258 ** buffer, and returns a pointer to the text. Subsequent requests
1259 ** for the same document return the same pointer. The returned pointer
1260 ** is valid until the next invocation of this routine. Call this routine
1261 ** with an eType of 0 to clear the cache.
1262 */
1263 char *search_stext_cached(
1264 char cType, /* Type of document */
1265 int rid, /* BLOB.RID or TAG.TAGID value for document */
1266 const char *zName, /* Auxiliary information */
1267 int *pnTitle /* OUT: length of title in bytes excluding \n */
1268 ){
1269 static struct {
1270 Blob stext; /* Cached search text */
1271 char cType; /* The type */
1272 int rid; /* The RID */
1273 int nTitle; /* Number of bytes in title */
1274 } cache;
1275 int i;
1276 char *z;
1277 if( cType!=cache.cType || rid!=cache.rid ){
1278 if( cache.rid>0 ){
1279 blob_reset(&cache.stext);
1280 }else{
1281 blob_init(&cache.stext,0,0);
1282 }
1283 cache.cType = cType;
1284 cache.rid = rid;
1285 if( cType==0 ) return 0;
1286 search_stext(cType, rid, zName, &cache.stext);
1287 z = blob_str(&cache.stext);
1288 for(i=0; z[i] && z[i]!='\n'; i++){}
1289 cache.nTitle = i;
1290 }
1291 if( pnTitle ) *pnTitle = cache.nTitle;
1292 return blob_str(&cache.stext);
1293 }
1294
1295 /*
1296 ** COMMAND: test-search-stext
1297 **
1298 ** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1131,10 +1303,30 @@
1303 if( g.argc!=5 ) usage("TYPE RID NAME");
1304 search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
1305 fossil_print("%s\n",blob_str(&out));
1306 blob_reset(&out);
1307 }
1308
1309 /*
1310 ** COMMAND: test-convert-stext
1311 **
1312 ** Usage: fossil test-convert-stext FILE MIMETYPE
1313 **
1314 ** Read the content of FILE and convert it to stext according to MIMETYPE.
1315 ** Send the result to standard output.
1316 */
1317 void test_convert_stext(void){
1318 Blob in, out;
1319 db_find_and_open_repository(0,0);
1320 if( g.argc!=4 ) usage("FILENAME MIMETYPE");
1321 blob_read_from_file(&in, g.argv[2]);
1322 blob_init(&out, 0, 0);
1323 get_stext_by_mimetype(&in, g.argv[3], &out);
1324 fossil_print("%s\n",blob_str(&out));
1325 blob_reset(&in);
1326 blob_reset(&out);
1327 }
1328
1329 /* The schema for the full-text index
1330 */
1331 static const char zFtsSchema[] =
1332 @ -- One entry for each possible search result
@@ -1145,20 +1337,21 @@
1337 @ name TEXT, -- Additional document description
1338 @ idxed BOOLEAN, -- True if currently in the index
1339 @ label TEXT, -- Label to print on search results
1340 @ url TEXT, -- URL to access this document
1341 @ mtime DATE, -- Date when document created
1342 @ bx TEXT, -- Temporary "body" content cache
1343 @ UNIQUE(type,rid)
1344 @ );
1345 @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
1346 @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
1347 @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
1348 @ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1349 @ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body'
1350 @ FROM ftsdocs;
1351 @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1352 @ USING fts4(content="ftscontent", title, body%s);
1353 ;
1354 static const char zFtsDrop[] =
1355 @ DROP TABLE IF EXISTS "%w".ftsidx;
1356 @ DROP VIEW IF EXISTS "%w".ftscontent;
1357 @ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1168,13 +1361,15 @@
1361 ** Create or drop the tables associated with a full-text index.
1362 */
1363 static int searchIdxExists = -1;
1364 void search_create_index(void){
1365 const char *zDb = db_name("repository");
1366 int useStemmer = db_get_boolean("search-stemmer",0);
1367 const char *zExtra = useStemmer ? ",tokenize=porter" : "";
1368 search_sql_setup(g.db);
1369 db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/,
1370 zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/);
1371 searchIdxExists = 1;
1372 }
1373 void search_drop_index(void){
1374 const char *zDb = db_name("repository");
1375 db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1292,34 +1487,39 @@
1487 db_multi_exec(
1488 "DELETE FROM ftsdocs WHERE type='d'"
1489 " AND rid NOT IN (SELECT rid FROM current_docs)"
1490 );
1491 db_multi_exec(
1492 "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)"
1493 " SELECT 'd', rid, name, 0,"
1494 " title('d',rid,name),"
1495 " body('d',rid,name),"
1496 " printf('/doc/%q/%%s',urlencode(name)),"
1497 " %.17g"
1498 " FROM current_docs",
1499 zBrUuid, rTime
1500 );
1501 db_multi_exec(
1502 "INSERT INTO ftsidx(docid,title,body)"
1503 " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed"
1504 );
1505 db_multi_exec(
1506 "UPDATE ftsdocs SET"
1507 " idxed=1,"
1508 " bx=NULL,"
1509 " label='Document: '||label"
1510 " WHERE type='d' AND NOT idxed"
1511 );
1512 }
1513
1514 /*
1515 ** Deal with all of the unindexed 'c' terms in FTSDOCS
1516 */
1517 static void search_update_checkin_index(void){
1518 db_multi_exec(
1519 "INSERT INTO ftsidx(docid,title,body)"
1520 " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs"
1521 " WHERE type='c' AND NOT idxed;"
1522 );
1523 db_multi_exec(
1524 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1525 " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
@@ -1336,19 +1536,20 @@
1536 /*
1537 ** Deal with all of the unindexed 't' terms in FTSDOCS
1538 */
1539 static void search_update_ticket_index(void){
1540 db_multi_exec(
1541 "INSERT INTO ftsidx(docid,title,body)"
1542 " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs"
1543 " WHERE type='t' AND NOT idxed;"
1544 );
1545 if( db_changes()==0 ) return;
1546 db_multi_exec(
1547 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1548 " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
1549 " printf('Ticket: %%s (%%s)',title('t',tkt_id,null),"
1550 " datetime(tkt_mtime)),"
1551 " printf('/tktview/%%.20s',tkt_uuid),"
1552 " tkt_mtime"
1553 " FROM ftsdocs, ticket"
1554 " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
1555 " AND ticket.tkt_id=ftsdocs.rid"
@@ -1358,12 +1559,12 @@
1559 /*
1560 ** Deal with all of the unindexed 'w' terms in FTSDOCS
1561 */
1562 static void search_update_wiki_index(void){
1563 db_multi_exec(
1564 "INSERT INTO ftsidx(docid,title,body)"
1565 " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs"
1566 " WHERE type='w' AND NOT idxed;"
1567 );
1568 if( db_changes()==0 ) return;
1569 db_multi_exec(
1570 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1416,19 +1617,22 @@
1617 ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
1618 **
1619 ** The "fossil fts-config" command configures the full-text search capabilities
1620 ** of the repository. Subcommands:
1621 **
1622 ** reindex Rebuild the search index. This is a no-op if
1623 ** index search is disabled
1624 **
1625 ** index (on|off) Turn the search index on or off
1626 **
1627 ** enable cdtw Enable various kinds of search. c=Check-ins,
1628 ** d=Documents, t=Tickets, w=Wiki.
1629 **
1630 ** disable cdtw Disable versious kinds of search
1631 **
1632 ** stemmer (on|off) Turn the Porter stemmer on or off for indexed
1633 ** search. (Unindexed search is never stemmed.)
1634 **
1635 ** The current search settings are displayed after any changes are applied.
1636 ** Run this command with no arguments to simply see the settings.
1637 */
1638 void test_fts_cmd(void){
@@ -1435,18 +1639,19 @@
1639 static const struct { int iCmd; const char *z; } aCmd[] = {
1640 { 1, "reindex" },
1641 { 2, "index" },
1642 { 3, "disable" },
1643 { 4, "enable" },
1644 { 5, "stemmer" },
1645 };
1646 static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
1647 { "search-ckin", "check-in search:", "c" },
1648 { "search-doc", "document search:", "d" },
1649 { "search-tkt", "ticket search:", "t" },
1650 { "search-wiki", "wiki search:", "w" },
1651 };
1652 char *zSubCmd = 0;
1653 int i, j, n;
1654 int iCmd = 0;
1655 int iAction = 0;
1656 db_find_and_open_repository(0, 0);
1657 if( g.argc>2 ){
@@ -1464,11 +1669,11 @@
1669 return;
1670 }
1671 iCmd = aCmd[i].iCmd;
1672 }
1673 if( iCmd==1 ){
1674 if( search_index_exists() ) iAction = 2;
1675 }
1676 if( iCmd==2 ){
1677 if( g.argc<3 ) usage("index (on|off)");
1678 iAction = 1 + is_truth(g.argv[3]);
1679 }
@@ -1475,18 +1680,23 @@
1680 db_begin_transaction();
1681
1682 /* Adjust search settings */
1683 if( iCmd==3 || iCmd==4 ){
1684 const char *zCtrl;
1685 if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd));
1686 zCtrl = g.argv[3];
1687 for(j=0; j<ArraySize(aSetng); j++){
1688 if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
1689 db_set_int(aSetng[j].zSetting, iCmd-3, 0);
1690 }
1691 }
1692 }
1693 if( iCmd==5 ){
1694 if( g.argc<4 ) usage("porter ON/OFF");
1695 db_set_int("search-stemmer", is_truth(g.argv[3]), 0);
1696 }
1697
1698
1699 /* destroy or rebuild the index, if requested */
1700 if( iAction>=1 ){
1701 search_drop_index();
1702 }
@@ -1497,14 +1707,16 @@
1707 /* Always show the status before ending */
1708 for(i=0; i<ArraySize(aSetng); i++){
1709 fossil_print("%-16s %s\n", aSetng[i].zName,
1710 db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
1711 }
1712 fossil_print("%-16s %s\n", "Porter stemmer:",
1713 db_get_boolean("search-stemmer",0) ? "on" : "off");
1714 if( search_index_exists() ){
1715 fossil_print("%-16s enabled\n", "full-text index:");
1716 fossil_print("%-16s %d\n", "documents:",
1717 db_int(0, "SELECT count(*) FROM ftsdocs"));
1718 }else{
1719 fossil_print("%-16s disabled\n", "full-text index:");
1720 }
1721 db_end_transaction(0);
1722 }
1723
+43 -26
--- src/setup.c
+++ src/setup.c
@@ -59,11 +59,11 @@
5959
** WEBPAGE: /setup
6060
*/
6161
void setup_page(void){
6262
login_check_credentials();
6363
if( !g.perm.Setup ){
64
- login_needed();
64
+ login_needed(0);
6565
}
6666
6767
style_header("Server Administration");
6868
6969
/* Make sure the header contains <base href="...">. Issue a warning
@@ -152,11 +152,11 @@
152152
Stmt s;
153153
int prevLevel = 0;
154154
155155
login_check_credentials();
156156
if( !g.perm.Admin ){
157
- login_needed();
157
+ login_needed(0);
158158
return;
159159
}
160160
161161
style_submenu_element("Add", "Add User", "setup_uedit");
162162
style_header("User List");
@@ -336,11 +336,11 @@
336336
const char *oa[128];
337337
338338
/* Must have ADMIN privileges to access this page
339339
*/
340340
login_check_credentials();
341
- if( !g.perm.Admin ){ login_needed(); return; }
341
+ if( !g.perm.Admin ){ login_needed(0); return; }
342342
343343
/* Check to see if an ADMIN user is trying to edit a SETUP account.
344344
** Don't allow that.
345345
*/
346346
zId = PD("id", "0");
@@ -998,11 +998,12 @@
998998
** WEBPAGE: setup_access
999999
*/
10001000
void setup_access(void){
10011001
login_check_credentials();
10021002
if( !g.perm.Setup ){
1003
- login_needed();
1003
+ login_needed(0);
1004
+ return;
10041005
}
10051006
10061007
style_header("Access Control Settings");
10071008
db_begin_transaction();
10081009
@ <form action="%s(g.zTop)/setup_access" method="post"><div>
@@ -1018,25 +1019,25 @@
10181019
@ <hr />
10191020
onoff_attribute("Require password for local access",
10201021
"localauth", "localauth", 0, 0);
10211022
@ <p>When enabled, the password sign-in is always required for
10221023
@ web access. When disabled, unrestricted web access from 127.0.0.1
1023
- @ is allowed for the <a href="%s(g.zTop)/help/ui">fossil ui</a> command or
1024
- @ from the <a href="%s(g.zTop)/help/server">fossil server</a>,
1025
- @ <a href="%s(g.zTop)/help/http">fossil http</a> commands when the
1024
+ @ is allowed for the <a href="%R/help/ui">fossil ui</a> command or
1025
+ @ from the <a href="%R/help/server">fossil server</a>,
1026
+ @ <a href="%R/help/http">fossil http</a> commands when the
10261027
@ "--localauth" command line options is used, or from the
1027
- @ <a href="%s(g.zTop)/help/cgi">fossil cgi</a> if a line containing
1028
+ @ <a href="%R/help/cgi">fossil cgi</a> if a line containing
10281029
@ the word "localauth" appears in the CGI script.
10291030
@
10301031
@ <p>A password is always required if any one or more
10311032
@ of the following are true:
10321033
@ <ol>
10331034
@ <li> This button is checked
10341035
@ <li> The inbound TCP/IP connection is not from 127.0.0.1
10351036
@ <li> The server is started using either of the
1036
- @ <a href="%s(g.zTop)/help/server">fossil server</a> or
1037
- @ <a href="%s(g.zTop)/help/server">fossil http</a> commands
1037
+ @ <a href="%R/help/server">fossil server</a> or
1038
+ @ <a href="%R/help/server">fossil http</a> commands
10381039
@ without the "--localauth" option.
10391040
@ <li> The server is started from CGI without the "localauth" keyword
10401041
@ in the CGI script.
10411042
@ </ol>
10421043
@
@@ -1203,11 +1204,12 @@
12031204
const char *zPw = PD("pw", "");
12041205
const char *zNewName = PD("newname", "New Login Group");
12051206
12061207
login_check_credentials();
12071208
if( !g.perm.Setup ){
1208
- login_needed();
1209
+ login_needed(0);
1210
+ return;
12091211
}
12101212
file_canonical_name(g.zRepositoryName, &fullName, 0);
12111213
zSelfRepo = fossil_strdup(blob_str(&fullName));
12121214
blob_reset(&fullName);
12131215
if( P("join")!=0 ){
@@ -1315,11 +1317,12 @@
13151317
"3", "YYMMDD HH:MM",
13161318
"4", "(off)"
13171319
};
13181320
login_check_credentials();
13191321
if( !g.perm.Setup ){
1320
- login_needed();
1322
+ login_needed(0);
1323
+ return;
13211324
}
13221325
13231326
style_header("Timeline Display Preferences");
13241327
db_begin_transaction();
13251328
@ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1393,11 +1396,12 @@
13931396
void setup_settings(void){
13941397
Setting const *pSet;
13951398
13961399
login_check_credentials();
13971400
if( !g.perm.Setup ){
1398
- login_needed();
1401
+ login_needed(0);
1402
+ return;
13991403
}
14001404
14011405
(void) aCmdHelp; /* NOTE: Silence compiler warning. */
14021406
style_header("Settings");
14031407
if(!g.repositoryOpen){
@@ -1473,11 +1477,12 @@
14731477
** WEBPAGE: setup_config
14741478
*/
14751479
void setup_config(void){
14761480
login_check_credentials();
14771481
if( !g.perm.Setup ){
1478
- login_needed();
1482
+ login_needed(0);
1483
+ return;
14791484
}
14801485
14811486
style_header("WWW Configuration");
14821487
db_begin_transaction();
14831488
@ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1551,11 +1556,12 @@
15511556
** WEBPAGE: setup_editcss
15521557
*/
15531558
void setup_editcss(void){
15541559
login_check_credentials();
15551560
if( !g.perm.Setup ){
1556
- login_needed();
1561
+ login_needed(0);
1562
+ return;
15571563
}
15581564
db_begin_transaction();
15591565
if( P("clear")!=0 ){
15601566
db_multi_exec("DELETE FROM config WHERE name='css'");
15611567
cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1596,11 +1602,12 @@
15961602
** WEBPAGE: setup_header
15971603
*/
15981604
void setup_header(void){
15991605
login_check_credentials();
16001606
if( !g.perm.Setup ){
1601
- login_needed();
1607
+ login_needed(0);
1608
+ return;
16021609
}
16031610
db_begin_transaction();
16041611
if( P("clear")!=0 ){
16051612
db_multi_exec("DELETE FROM config WHERE name='header'");
16061613
cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1660,11 +1667,12 @@
16601667
** WEBPAGE: setup_footer
16611668
*/
16621669
void setup_footer(void){
16631670
login_check_credentials();
16641671
if( !g.perm.Setup ){
1665
- login_needed();
1672
+ login_needed(0);
1673
+ return;
16661674
}
16671675
db_begin_transaction();
16681676
if( P("clear")!=0 ){
16691677
db_multi_exec("DELETE FROM config WHERE name='footer'");
16701678
cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1697,11 +1705,12 @@
16971705
** WEBPAGE: setup_modreq
16981706
*/
16991707
void setup_modreq(void){
17001708
login_check_credentials();
17011709
if( !g.perm.Setup ){
1702
- login_needed();
1710
+ login_needed(0);
1711
+ return;
17031712
}
17041713
17051714
style_header("Moderator For Wiki And Tickets");
17061715
db_begin_transaction();
17071716
@ <form action="%R/setup_modreq" method="post"><div>
@@ -1708,11 +1717,11 @@
17081717
login_insert_csrf_secret();
17091718
@ <hr />
17101719
onoff_attribute("Moderate ticket changes",
17111720
"modreq-tkt", "modreq-tkt", 0, 0);
17121721
@ <p>When enabled, any change to tickets is subject to the approval
1713
- @ a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1722
+ @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege.
17141723
@ Ticket changes enter the system and are shown locally, but are not
17151724
@ synced until they are approved. The moderator has the option to
17161725
@ delete the change rather than approve it. Ticket changes made by
17171726
@ a user who has the Mod-Tkt privilege are never subject to
17181727
@ moderation.
@@ -1719,11 +1728,11 @@
17191728
@
17201729
@ <hr />
17211730
onoff_attribute("Moderate wiki changes",
17221731
"modreq-wiki", "modreq-wiki", 0, 0);
17231732
@ <p>When enabled, any change to wiki is subject to the approval
1724
- @ a ticket moderator - a user with the "l" or Mod-Wiki privilege.
1733
+ @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege.
17251734
@ Wiki changes enter the system and are shown locally, but are not
17261735
@ synced until they are approved. The moderator has the option to
17271736
@ delete the change rather than approve it. Wiki changes made by
17281737
@ a user who has the Mod-Wiki privilege are never subject to
17291738
@ moderation.
@@ -1741,11 +1750,12 @@
17411750
** WEBPAGE: setup_adunit
17421751
*/
17431752
void setup_adunit(void){
17441753
login_check_credentials();
17451754
if( !g.perm.Setup ){
1746
- login_needed();
1755
+ login_needed(0);
1756
+ return;
17471757
}
17481758
db_begin_transaction();
17491759
if( P("clear")!=0 ){
17501760
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
17511761
cgi_replace_parameter("adunit","");
@@ -1822,11 +1832,12 @@
18221832
if( szBgImg>0 ){
18231833
zBgMime = PD("bgim:mimetype","image/gif");
18241834
}
18251835
login_check_credentials();
18261836
if( !g.perm.Setup ){
1827
- login_needed();
1837
+ login_needed(0);
1838
+ return;
18281839
}
18291840
db_begin_transaction();
18301841
if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
18311842
Blob img;
18321843
Stmt ins;
@@ -1961,11 +1972,12 @@
19611972
void sql_page(void){
19621973
const char *zQ = P("q");
19631974
int go = P("go")!=0;
19641975
login_check_credentials();
19651976
if( !g.perm.Setup ){
1966
- login_needed();
1977
+ login_needed(0);
1978
+ return;
19671979
}
19681980
db_begin_transaction();
19691981
style_header("Raw SQL Commands");
19701982
@ <p><b>Caution:</b> There are no restrictions on the SQL that can be
19711983
@ run by this page. You can do serious and irrepairable damage to the
@@ -2082,11 +2094,12 @@
20822094
void th1_page(void){
20832095
const char *zQ = P("q");
20842096
int go = P("go")!=0;
20852097
login_check_credentials();
20862098
if( !g.perm.Setup ){
2087
- login_needed();
2099
+ login_needed(0);
2100
+ return;
20882101
}
20892102
db_begin_transaction();
20902103
style_header("Raw TH1 Commands");
20912104
@ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
20922105
@ run by this page. If Tcl integration was enabled at compile-time and
@@ -2142,11 +2155,12 @@
21422155
int limit;
21432156
int fLogEnabled;
21442157
int counter = 0;
21452158
login_check_credentials();
21462159
if( !g.perm.Setup && !g.perm.Admin ){
2147
- login_needed();
2160
+ login_needed(0);
2161
+ return;
21482162
}
21492163
style_header("Admin Log");
21502164
create_admin_log_table();
21512165
limit = atoi(PD("n","20"));
21522166
fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2199,11 +2213,12 @@
21992213
** Configure the search engine.
22002214
*/
22012215
void page_srchsetup(){
22022216
login_check_credentials();
22032217
if( !g.perm.Setup && !g.perm.Admin ){
2204
- login_needed();
2218
+ login_needed(0);
2219
+ return;
22052220
}
22062221
style_header("Search Configuration");
22072222
@ <form action="%s(g.zTop)/srchsetup" method="post"><div>
22082223
login_insert_csrf_secret();
22092224
@ <div style="text-align:center;font-weight:bold;">
@@ -2251,16 +2266,18 @@
22512266
search_update_index(search_restrict(SRCH_ALL));
22522267
}
22532268
if( search_index_exists() ){
22542269
@ <p>Currently using an SQLite FTS4 search index. This makes search
22552270
@ run faster, especially on large repositories, but takes up space.</p>
2271
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22562272
@ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
22572273
@ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
22582274
}else{
22592275
@ <p>The SQLite FTS4 search index is disabled. All searching will be
22602276
@ a full-text scan. This usually works fine, but can be slow for
22612277
@ larger repositories.</p>
2278
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22622279
@ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
22632280
}
22642281
@ </div></form>
22652282
style_footer();
22662283
}
22672284
--- src/setup.c
+++ src/setup.c
@@ -59,11 +59,11 @@
59 ** WEBPAGE: /setup
60 */
61 void setup_page(void){
62 login_check_credentials();
63 if( !g.perm.Setup ){
64 login_needed();
65 }
66
67 style_header("Server Administration");
68
69 /* Make sure the header contains <base href="...">. Issue a warning
@@ -152,11 +152,11 @@
152 Stmt s;
153 int prevLevel = 0;
154
155 login_check_credentials();
156 if( !g.perm.Admin ){
157 login_needed();
158 return;
159 }
160
161 style_submenu_element("Add", "Add User", "setup_uedit");
162 style_header("User List");
@@ -336,11 +336,11 @@
336 const char *oa[128];
337
338 /* Must have ADMIN privileges to access this page
339 */
340 login_check_credentials();
341 if( !g.perm.Admin ){ login_needed(); return; }
342
343 /* Check to see if an ADMIN user is trying to edit a SETUP account.
344 ** Don't allow that.
345 */
346 zId = PD("id", "0");
@@ -998,11 +998,12 @@
998 ** WEBPAGE: setup_access
999 */
1000 void setup_access(void){
1001 login_check_credentials();
1002 if( !g.perm.Setup ){
1003 login_needed();
 
1004 }
1005
1006 style_header("Access Control Settings");
1007 db_begin_transaction();
1008 @ <form action="%s(g.zTop)/setup_access" method="post"><div>
@@ -1018,25 +1019,25 @@
1018 @ <hr />
1019 onoff_attribute("Require password for local access",
1020 "localauth", "localauth", 0, 0);
1021 @ <p>When enabled, the password sign-in is always required for
1022 @ web access. When disabled, unrestricted web access from 127.0.0.1
1023 @ is allowed for the <a href="%s(g.zTop)/help/ui">fossil ui</a> command or
1024 @ from the <a href="%s(g.zTop)/help/server">fossil server</a>,
1025 @ <a href="%s(g.zTop)/help/http">fossil http</a> commands when the
1026 @ "--localauth" command line options is used, or from the
1027 @ <a href="%s(g.zTop)/help/cgi">fossil cgi</a> if a line containing
1028 @ the word "localauth" appears in the CGI script.
1029 @
1030 @ <p>A password is always required if any one or more
1031 @ of the following are true:
1032 @ <ol>
1033 @ <li> This button is checked
1034 @ <li> The inbound TCP/IP connection is not from 127.0.0.1
1035 @ <li> The server is started using either of the
1036 @ <a href="%s(g.zTop)/help/server">fossil server</a> or
1037 @ <a href="%s(g.zTop)/help/server">fossil http</a> commands
1038 @ without the "--localauth" option.
1039 @ <li> The server is started from CGI without the "localauth" keyword
1040 @ in the CGI script.
1041 @ </ol>
1042 @
@@ -1203,11 +1204,12 @@
1203 const char *zPw = PD("pw", "");
1204 const char *zNewName = PD("newname", "New Login Group");
1205
1206 login_check_credentials();
1207 if( !g.perm.Setup ){
1208 login_needed();
 
1209 }
1210 file_canonical_name(g.zRepositoryName, &fullName, 0);
1211 zSelfRepo = fossil_strdup(blob_str(&fullName));
1212 blob_reset(&fullName);
1213 if( P("join")!=0 ){
@@ -1315,11 +1317,12 @@
1315 "3", "YYMMDD HH:MM",
1316 "4", "(off)"
1317 };
1318 login_check_credentials();
1319 if( !g.perm.Setup ){
1320 login_needed();
 
1321 }
1322
1323 style_header("Timeline Display Preferences");
1324 db_begin_transaction();
1325 @ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1393,11 +1396,12 @@
1393 void setup_settings(void){
1394 Setting const *pSet;
1395
1396 login_check_credentials();
1397 if( !g.perm.Setup ){
1398 login_needed();
 
1399 }
1400
1401 (void) aCmdHelp; /* NOTE: Silence compiler warning. */
1402 style_header("Settings");
1403 if(!g.repositoryOpen){
@@ -1473,11 +1477,12 @@
1473 ** WEBPAGE: setup_config
1474 */
1475 void setup_config(void){
1476 login_check_credentials();
1477 if( !g.perm.Setup ){
1478 login_needed();
 
1479 }
1480
1481 style_header("WWW Configuration");
1482 db_begin_transaction();
1483 @ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1551,11 +1556,12 @@
1551 ** WEBPAGE: setup_editcss
1552 */
1553 void setup_editcss(void){
1554 login_check_credentials();
1555 if( !g.perm.Setup ){
1556 login_needed();
 
1557 }
1558 db_begin_transaction();
1559 if( P("clear")!=0 ){
1560 db_multi_exec("DELETE FROM config WHERE name='css'");
1561 cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1596,11 +1602,12 @@
1596 ** WEBPAGE: setup_header
1597 */
1598 void setup_header(void){
1599 login_check_credentials();
1600 if( !g.perm.Setup ){
1601 login_needed();
 
1602 }
1603 db_begin_transaction();
1604 if( P("clear")!=0 ){
1605 db_multi_exec("DELETE FROM config WHERE name='header'");
1606 cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1660,11 +1667,12 @@
1660 ** WEBPAGE: setup_footer
1661 */
1662 void setup_footer(void){
1663 login_check_credentials();
1664 if( !g.perm.Setup ){
1665 login_needed();
 
1666 }
1667 db_begin_transaction();
1668 if( P("clear")!=0 ){
1669 db_multi_exec("DELETE FROM config WHERE name='footer'");
1670 cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1697,11 +1705,12 @@
1697 ** WEBPAGE: setup_modreq
1698 */
1699 void setup_modreq(void){
1700 login_check_credentials();
1701 if( !g.perm.Setup ){
1702 login_needed();
 
1703 }
1704
1705 style_header("Moderator For Wiki And Tickets");
1706 db_begin_transaction();
1707 @ <form action="%R/setup_modreq" method="post"><div>
@@ -1708,11 +1717,11 @@
1708 login_insert_csrf_secret();
1709 @ <hr />
1710 onoff_attribute("Moderate ticket changes",
1711 "modreq-tkt", "modreq-tkt", 0, 0);
1712 @ <p>When enabled, any change to tickets is subject to the approval
1713 @ a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1714 @ Ticket changes enter the system and are shown locally, but are not
1715 @ synced until they are approved. The moderator has the option to
1716 @ delete the change rather than approve it. Ticket changes made by
1717 @ a user who has the Mod-Tkt privilege are never subject to
1718 @ moderation.
@@ -1719,11 +1728,11 @@
1719 @
1720 @ <hr />
1721 onoff_attribute("Moderate wiki changes",
1722 "modreq-wiki", "modreq-wiki", 0, 0);
1723 @ <p>When enabled, any change to wiki is subject to the approval
1724 @ a ticket moderator - a user with the "l" or Mod-Wiki privilege.
1725 @ Wiki changes enter the system and are shown locally, but are not
1726 @ synced until they are approved. The moderator has the option to
1727 @ delete the change rather than approve it. Wiki changes made by
1728 @ a user who has the Mod-Wiki privilege are never subject to
1729 @ moderation.
@@ -1741,11 +1750,12 @@
1741 ** WEBPAGE: setup_adunit
1742 */
1743 void setup_adunit(void){
1744 login_check_credentials();
1745 if( !g.perm.Setup ){
1746 login_needed();
 
1747 }
1748 db_begin_transaction();
1749 if( P("clear")!=0 ){
1750 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1751 cgi_replace_parameter("adunit","");
@@ -1822,11 +1832,12 @@
1822 if( szBgImg>0 ){
1823 zBgMime = PD("bgim:mimetype","image/gif");
1824 }
1825 login_check_credentials();
1826 if( !g.perm.Setup ){
1827 login_needed();
 
1828 }
1829 db_begin_transaction();
1830 if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1831 Blob img;
1832 Stmt ins;
@@ -1961,11 +1972,12 @@
1961 void sql_page(void){
1962 const char *zQ = P("q");
1963 int go = P("go")!=0;
1964 login_check_credentials();
1965 if( !g.perm.Setup ){
1966 login_needed();
 
1967 }
1968 db_begin_transaction();
1969 style_header("Raw SQL Commands");
1970 @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
1971 @ run by this page. You can do serious and irrepairable damage to the
@@ -2082,11 +2094,12 @@
2082 void th1_page(void){
2083 const char *zQ = P("q");
2084 int go = P("go")!=0;
2085 login_check_credentials();
2086 if( !g.perm.Setup ){
2087 login_needed();
 
2088 }
2089 db_begin_transaction();
2090 style_header("Raw TH1 Commands");
2091 @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
2092 @ run by this page. If Tcl integration was enabled at compile-time and
@@ -2142,11 +2155,12 @@
2142 int limit;
2143 int fLogEnabled;
2144 int counter = 0;
2145 login_check_credentials();
2146 if( !g.perm.Setup && !g.perm.Admin ){
2147 login_needed();
 
2148 }
2149 style_header("Admin Log");
2150 create_admin_log_table();
2151 limit = atoi(PD("n","20"));
2152 fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2199,11 +2213,12 @@
2199 ** Configure the search engine.
2200 */
2201 void page_srchsetup(){
2202 login_check_credentials();
2203 if( !g.perm.Setup && !g.perm.Admin ){
2204 login_needed();
 
2205 }
2206 style_header("Search Configuration");
2207 @ <form action="%s(g.zTop)/srchsetup" method="post"><div>
2208 login_insert_csrf_secret();
2209 @ <div style="text-align:center;font-weight:bold;">
@@ -2251,16 +2266,18 @@
2251 search_update_index(search_restrict(SRCH_ALL));
2252 }
2253 if( search_index_exists() ){
2254 @ <p>Currently using an SQLite FTS4 search index. This makes search
2255 @ run faster, especially on large repositories, but takes up space.</p>
 
2256 @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
2257 @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
2258 }else{
2259 @ <p>The SQLite FTS4 search index is disabled. All searching will be
2260 @ a full-text scan. This usually works fine, but can be slow for
2261 @ larger repositories.</p>
 
2262 @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
2263 }
2264 @ </div></form>
2265 style_footer();
2266 }
2267
--- src/setup.c
+++ src/setup.c
@@ -59,11 +59,11 @@
59 ** WEBPAGE: /setup
60 */
61 void setup_page(void){
62 login_check_credentials();
63 if( !g.perm.Setup ){
64 login_needed(0);
65 }
66
67 style_header("Server Administration");
68
69 /* Make sure the header contains <base href="...">. Issue a warning
@@ -152,11 +152,11 @@
152 Stmt s;
153 int prevLevel = 0;
154
155 login_check_credentials();
156 if( !g.perm.Admin ){
157 login_needed(0);
158 return;
159 }
160
161 style_submenu_element("Add", "Add User", "setup_uedit");
162 style_header("User List");
@@ -336,11 +336,11 @@
336 const char *oa[128];
337
338 /* Must have ADMIN privileges to access this page
339 */
340 login_check_credentials();
341 if( !g.perm.Admin ){ login_needed(0); return; }
342
343 /* Check to see if an ADMIN user is trying to edit a SETUP account.
344 ** Don't allow that.
345 */
346 zId = PD("id", "0");
@@ -998,11 +998,12 @@
998 ** WEBPAGE: setup_access
999 */
1000 void setup_access(void){
1001 login_check_credentials();
1002 if( !g.perm.Setup ){
1003 login_needed(0);
1004 return;
1005 }
1006
1007 style_header("Access Control Settings");
1008 db_begin_transaction();
1009 @ <form action="%s(g.zTop)/setup_access" method="post"><div>
@@ -1018,25 +1019,25 @@
1019 @ <hr />
1020 onoff_attribute("Require password for local access",
1021 "localauth", "localauth", 0, 0);
1022 @ <p>When enabled, the password sign-in is always required for
1023 @ web access. When disabled, unrestricted web access from 127.0.0.1
1024 @ is allowed for the <a href="%R/help/ui">fossil ui</a> command or
1025 @ from the <a href="%R/help/server">fossil server</a>,
1026 @ <a href="%R/help/http">fossil http</a> commands when the
1027 @ "--localauth" command line options is used, or from the
1028 @ <a href="%R/help/cgi">fossil cgi</a> if a line containing
1029 @ the word "localauth" appears in the CGI script.
1030 @
1031 @ <p>A password is always required if any one or more
1032 @ of the following are true:
1033 @ <ol>
1034 @ <li> This button is checked
1035 @ <li> The inbound TCP/IP connection is not from 127.0.0.1
1036 @ <li> The server is started using either of the
1037 @ <a href="%R/help/server">fossil server</a> or
1038 @ <a href="%R/help/server">fossil http</a> commands
1039 @ without the "--localauth" option.
1040 @ <li> The server is started from CGI without the "localauth" keyword
1041 @ in the CGI script.
1042 @ </ol>
1043 @
@@ -1203,11 +1204,12 @@
1204 const char *zPw = PD("pw", "");
1205 const char *zNewName = PD("newname", "New Login Group");
1206
1207 login_check_credentials();
1208 if( !g.perm.Setup ){
1209 login_needed(0);
1210 return;
1211 }
1212 file_canonical_name(g.zRepositoryName, &fullName, 0);
1213 zSelfRepo = fossil_strdup(blob_str(&fullName));
1214 blob_reset(&fullName);
1215 if( P("join")!=0 ){
@@ -1315,11 +1317,12 @@
1317 "3", "YYMMDD HH:MM",
1318 "4", "(off)"
1319 };
1320 login_check_credentials();
1321 if( !g.perm.Setup ){
1322 login_needed(0);
1323 return;
1324 }
1325
1326 style_header("Timeline Display Preferences");
1327 db_begin_transaction();
1328 @ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1393,11 +1396,12 @@
1396 void setup_settings(void){
1397 Setting const *pSet;
1398
1399 login_check_credentials();
1400 if( !g.perm.Setup ){
1401 login_needed(0);
1402 return;
1403 }
1404
1405 (void) aCmdHelp; /* NOTE: Silence compiler warning. */
1406 style_header("Settings");
1407 if(!g.repositoryOpen){
@@ -1473,11 +1477,12 @@
1477 ** WEBPAGE: setup_config
1478 */
1479 void setup_config(void){
1480 login_check_credentials();
1481 if( !g.perm.Setup ){
1482 login_needed(0);
1483 return;
1484 }
1485
1486 style_header("WWW Configuration");
1487 db_begin_transaction();
1488 @ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1551,11 +1556,12 @@
1556 ** WEBPAGE: setup_editcss
1557 */
1558 void setup_editcss(void){
1559 login_check_credentials();
1560 if( !g.perm.Setup ){
1561 login_needed(0);
1562 return;
1563 }
1564 db_begin_transaction();
1565 if( P("clear")!=0 ){
1566 db_multi_exec("DELETE FROM config WHERE name='css'");
1567 cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1596,11 +1602,12 @@
1602 ** WEBPAGE: setup_header
1603 */
1604 void setup_header(void){
1605 login_check_credentials();
1606 if( !g.perm.Setup ){
1607 login_needed(0);
1608 return;
1609 }
1610 db_begin_transaction();
1611 if( P("clear")!=0 ){
1612 db_multi_exec("DELETE FROM config WHERE name='header'");
1613 cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1660,11 +1667,12 @@
1667 ** WEBPAGE: setup_footer
1668 */
1669 void setup_footer(void){
1670 login_check_credentials();
1671 if( !g.perm.Setup ){
1672 login_needed(0);
1673 return;
1674 }
1675 db_begin_transaction();
1676 if( P("clear")!=0 ){
1677 db_multi_exec("DELETE FROM config WHERE name='footer'");
1678 cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1697,11 +1705,12 @@
1705 ** WEBPAGE: setup_modreq
1706 */
1707 void setup_modreq(void){
1708 login_check_credentials();
1709 if( !g.perm.Setup ){
1710 login_needed(0);
1711 return;
1712 }
1713
1714 style_header("Moderator For Wiki And Tickets");
1715 db_begin_transaction();
1716 @ <form action="%R/setup_modreq" method="post"><div>
@@ -1708,11 +1717,11 @@
1717 login_insert_csrf_secret();
1718 @ <hr />
1719 onoff_attribute("Moderate ticket changes",
1720 "modreq-tkt", "modreq-tkt", 0, 0);
1721 @ <p>When enabled, any change to tickets is subject to the approval
1722 @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1723 @ Ticket changes enter the system and are shown locally, but are not
1724 @ synced until they are approved. The moderator has the option to
1725 @ delete the change rather than approve it. Ticket changes made by
1726 @ a user who has the Mod-Tkt privilege are never subject to
1727 @ moderation.
@@ -1719,11 +1728,11 @@
1728 @
1729 @ <hr />
1730 onoff_attribute("Moderate wiki changes",
1731 "modreq-wiki", "modreq-wiki", 0, 0);
1732 @ <p>When enabled, any change to wiki is subject to the approval
1733 @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege.
1734 @ Wiki changes enter the system and are shown locally, but are not
1735 @ synced until they are approved. The moderator has the option to
1736 @ delete the change rather than approve it. Wiki changes made by
1737 @ a user who has the Mod-Wiki privilege are never subject to
1738 @ moderation.
@@ -1741,11 +1750,12 @@
1750 ** WEBPAGE: setup_adunit
1751 */
1752 void setup_adunit(void){
1753 login_check_credentials();
1754 if( !g.perm.Setup ){
1755 login_needed(0);
1756 return;
1757 }
1758 db_begin_transaction();
1759 if( P("clear")!=0 ){
1760 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1761 cgi_replace_parameter("adunit","");
@@ -1822,11 +1832,12 @@
1832 if( szBgImg>0 ){
1833 zBgMime = PD("bgim:mimetype","image/gif");
1834 }
1835 login_check_credentials();
1836 if( !g.perm.Setup ){
1837 login_needed(0);
1838 return;
1839 }
1840 db_begin_transaction();
1841 if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1842 Blob img;
1843 Stmt ins;
@@ -1961,11 +1972,12 @@
1972 void sql_page(void){
1973 const char *zQ = P("q");
1974 int go = P("go")!=0;
1975 login_check_credentials();
1976 if( !g.perm.Setup ){
1977 login_needed(0);
1978 return;
1979 }
1980 db_begin_transaction();
1981 style_header("Raw SQL Commands");
1982 @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
1983 @ run by this page. You can do serious and irrepairable damage to the
@@ -2082,11 +2094,12 @@
2094 void th1_page(void){
2095 const char *zQ = P("q");
2096 int go = P("go")!=0;
2097 login_check_credentials();
2098 if( !g.perm.Setup ){
2099 login_needed(0);
2100 return;
2101 }
2102 db_begin_transaction();
2103 style_header("Raw TH1 Commands");
2104 @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
2105 @ run by this page. If Tcl integration was enabled at compile-time and
@@ -2142,11 +2155,12 @@
2155 int limit;
2156 int fLogEnabled;
2157 int counter = 0;
2158 login_check_credentials();
2159 if( !g.perm.Setup && !g.perm.Admin ){
2160 login_needed(0);
2161 return;
2162 }
2163 style_header("Admin Log");
2164 create_admin_log_table();
2165 limit = atoi(PD("n","20"));
2166 fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2199,11 +2213,12 @@
2213 ** Configure the search engine.
2214 */
2215 void page_srchsetup(){
2216 login_check_credentials();
2217 if( !g.perm.Setup && !g.perm.Admin ){
2218 login_needed(0);
2219 return;
2220 }
2221 style_header("Search Configuration");
2222 @ <form action="%s(g.zTop)/srchsetup" method="post"><div>
2223 login_insert_csrf_secret();
2224 @ <div style="text-align:center;font-weight:bold;">
@@ -2251,16 +2266,18 @@
2266 search_update_index(search_restrict(SRCH_ALL));
2267 }
2268 if( search_index_exists() ){
2269 @ <p>Currently using an SQLite FTS4 search index. This makes search
2270 @ run faster, especially on large repositories, but takes up space.</p>
2271 onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
2272 @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
2273 @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
2274 }else{
2275 @ <p>The SQLite FTS4 search index is disabled. All searching will be
2276 @ a full-text scan. This usually works fine, but can be slow for
2277 @ larger repositories.</p>
2278 onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
2279 @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
2280 }
2281 @ </div></form>
2282 style_footer();
2283 }
2284
+120 -5
--- src/shell.c
+++ src/shell.c
@@ -1742,10 +1742,11 @@
17421742
static char zHelp[] =
17431743
".backup ?DB? FILE Backup DB (default \"main\") to FILE\n"
17441744
".bail on|off Stop after hitting an error. Default OFF\n"
17451745
".clone NEWDB Clone data into NEWDB from the existing database\n"
17461746
".databases List names and files of attached databases\n"
1747
+ ".dbinfo ?DB? Show status information about the database\n"
17471748
".dump ?TABLE? ... Dump the database in an SQL text format\n"
17481749
" If TABLE specified, only dump tables matching\n"
17491750
" LIKE pattern TABLE.\n"
17501751
".echo on|off Turn command echo on or off\n"
17511752
".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n"
@@ -1754,12 +1755,12 @@
17541755
" With no args, it turns EXPLAIN on.\n"
17551756
".fullschema Show schema and the content of sqlite_stat tables\n"
17561757
".headers on|off Turn display of headers on or off\n"
17571758
".help Show this message\n"
17581759
".import FILE TABLE Import data from FILE into TABLE\n"
1759
- ".indices ?TABLE? Show names of all indices\n"
1760
- " If TABLE specified, only show indices for tables\n"
1760
+ ".indexes ?TABLE? Show names of all indexes\n"
1761
+ " If TABLE specified, only show indexes for tables\n"
17611762
" matching LIKE pattern TABLE.\n"
17621763
#ifdef SQLITE_ENABLE_IOTRACE
17631764
".iotrace FILE Enable I/O diagnostic logging to FILE\n"
17641765
#endif
17651766
#ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -2434,10 +2435,119 @@
24342435
output_file_close(p->out);
24352436
}
24362437
p->outfile[0] = 0;
24372438
p->out = stdout;
24382439
}
2440
+
2441
+/*
2442
+** Run an SQL command and return the single integer result.
2443
+*/
2444
+static int db_int(ShellState *p, const char *zSql){
2445
+ sqlite3_stmt *pStmt;
2446
+ int res = 0;
2447
+ sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
2448
+ if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
2449
+ res = sqlite3_column_int(pStmt,0);
2450
+ }
2451
+ sqlite3_finalize(pStmt);
2452
+ return res;
2453
+}
2454
+
2455
+/*
2456
+** Convert a 2-byte or 4-byte big-endian integer into a native integer
2457
+*/
2458
+unsigned int get2byteInt(unsigned char *a){
2459
+ return (a[0]<<8) + a[1];
2460
+}
2461
+unsigned int get4byteInt(unsigned char *a){
2462
+ return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
2463
+}
2464
+
2465
+/*
2466
+** Implementation of the ".info" command.
2467
+**
2468
+** Return 1 on error, 2 to exit, and 0 otherwise.
2469
+*/
2470
+static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
2471
+ static const struct { const char *zName; int ofst; } aField[] = {
2472
+ { "file change counter:", 24 },
2473
+ { "database page count:", 28 },
2474
+ { "freelist page count:", 36 },
2475
+ { "schema cookie:", 40 },
2476
+ { "schema format:", 44 },
2477
+ { "default cache size:", 48 },
2478
+ { "autovacuum top root:", 52 },
2479
+ { "incremental vacuum:", 64 },
2480
+ { "text encoding:", 56 },
2481
+ { "user version:", 60 },
2482
+ { "application id:", 68 },
2483
+ { "software version:", 96 },
2484
+ };
2485
+ static const struct { const char *zName; const char *zSql; } aQuery[] = {
2486
+ { "number of tables:",
2487
+ "SELECT count(*) FROM %s WHERE type='table'" },
2488
+ { "number of indexes:",
2489
+ "SELECT count(*) FROM %s WHERE type='index'" },
2490
+ { "number of triggers:",
2491
+ "SELECT count(*) FROM %s WHERE type='trigger'" },
2492
+ { "number of views:",
2493
+ "SELECT count(*) FROM %s WHERE type='view'" },
2494
+ { "schema size:",
2495
+ "SELECT total(length(sql)) FROM %s" },
2496
+ };
2497
+ sqlite3_file *pFile;
2498
+ int i;
2499
+ char *zSchemaTab;
2500
+ char *zDb = nArg>=2 ? azArg[1] : "main";
2501
+ unsigned char aHdr[100];
2502
+ open_db(p, 0);
2503
+ if( p->db==0 ) return 1;
2504
+ sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_FILE_POINTER, &pFile);
2505
+ if( pFile==0 || pFile->pMethods==0 || pFile->pMethods->xRead==0 ){
2506
+ return 1;
2507
+ }
2508
+ i = pFile->pMethods->xRead(pFile, aHdr, 100, 0);
2509
+ if( i!=SQLITE_OK ){
2510
+ fprintf(stderr, "unable to read database header\n");
2511
+ return 1;
2512
+ }
2513
+ i = get2byteInt(aHdr+16);
2514
+ if( i==1 ) i = 65536;
2515
+ fprintf(p->out, "%-20s %d\n", "database page size:", i);
2516
+ fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
2517
+ fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
2518
+ fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
2519
+ for(i=0; i<sizeof(aField)/sizeof(aField[0]); i++){
2520
+ int ofst = aField[i].ofst;
2521
+ unsigned int val = get4byteInt(aHdr + ofst);
2522
+ fprintf(p->out, "%-20s %u", aField[i].zName, val);
2523
+ switch( ofst ){
2524
+ case 56: {
2525
+ if( val==1 ) fprintf(p->out, " (utf8)");
2526
+ if( val==2 ) fprintf(p->out, " (utf16le)");
2527
+ if( val==3 ) fprintf(p->out, " (utf16be)");
2528
+ }
2529
+ }
2530
+ fprintf(p->out, "\n");
2531
+ }
2532
+ if( zDb==0 ){
2533
+ zSchemaTab = sqlite3_mprintf("main.sqlite_master");
2534
+ }else if( strcmp(zDb,"temp")==0 ){
2535
+ zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_master");
2536
+ }else{
2537
+ zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_master", zDb);
2538
+ }
2539
+ for(i=0; i<sizeof(aQuery)/sizeof(aQuery[0]); i++){
2540
+ char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
2541
+ int val = db_int(p, zSql);
2542
+ sqlite3_free(zSql);
2543
+ fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
2544
+ }
2545
+ sqlite3_free(zSchemaTab);
2546
+ return 0;
2547
+}
2548
+
24392549
24402550
/*
24412551
** If an input line begins with "." then invoke this routine to
24422552
** process that line.
24432553
**
@@ -2576,10 +2686,14 @@
25762686
fprintf(stderr,"Error: %s\n", zErrMsg);
25772687
sqlite3_free(zErrMsg);
25782688
rc = 1;
25792689
}
25802690
}else
2691
+
2692
+ if( c=='d' && strncmp(azArg[0], "dbinfo", n)==0 ){
2693
+ rc = shell_dbinfo_command(p, nArg, azArg);
2694
+ }else
25812695
25822696
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
25832697
open_db(p, 0);
25842698
/* When playing back a "dump", the content might appear in an order
25852699
** which causes immediate foreign key constraints to be violated.
@@ -2916,11 +3030,11 @@
29163030
sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
29173031
if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
29183032
fprintf(stderr, "%s:%d: expected %d columns but found %d - "
29193033
"filling the rest with NULL\n",
29203034
sCtx.zFile, startLine, nCol, i+1);
2921
- i++;
3035
+ i += 2;
29223036
while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
29233037
}
29243038
}
29253039
if( sCtx.cTerm==sCtx.cColSep ){
29263040
do{
@@ -2945,11 +3059,12 @@
29453059
sqlite3_free(sCtx.z);
29463060
sqlite3_finalize(pStmt);
29473061
if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
29483062
}else
29493063
2950
- if( c=='i' && strncmp(azArg[0], "indices", n)==0 ){
3064
+ if( c=='i' && (strncmp(azArg[0], "indices", n)==0
3065
+ || strncmp(azArg[0], "indexes", n)==0) ){
29513066
ShellState data;
29523067
char *zErrMsg = 0;
29533068
open_db(p, 0);
29543069
memcpy(&data, p, sizeof(data));
29553070
data.showHeader = 0;
@@ -2975,11 +3090,11 @@
29753090
"ORDER BY 1",
29763091
callback, &data, &zErrMsg
29773092
);
29783093
zShellStatic = 0;
29793094
}else{
2980
- fprintf(stderr, "Usage: .indices ?LIKE-PATTERN?\n");
3095
+ fprintf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
29813096
rc = 1;
29823097
goto meta_command_exit;
29833098
}
29843099
if( zErrMsg ){
29853100
fprintf(stderr,"Error: %s\n", zErrMsg);
29863101
--- src/shell.c
+++ src/shell.c
@@ -1742,10 +1742,11 @@
1742 static char zHelp[] =
1743 ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n"
1744 ".bail on|off Stop after hitting an error. Default OFF\n"
1745 ".clone NEWDB Clone data into NEWDB from the existing database\n"
1746 ".databases List names and files of attached databases\n"
 
1747 ".dump ?TABLE? ... Dump the database in an SQL text format\n"
1748 " If TABLE specified, only dump tables matching\n"
1749 " LIKE pattern TABLE.\n"
1750 ".echo on|off Turn command echo on or off\n"
1751 ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n"
@@ -1754,12 +1755,12 @@
1754 " With no args, it turns EXPLAIN on.\n"
1755 ".fullschema Show schema and the content of sqlite_stat tables\n"
1756 ".headers on|off Turn display of headers on or off\n"
1757 ".help Show this message\n"
1758 ".import FILE TABLE Import data from FILE into TABLE\n"
1759 ".indices ?TABLE? Show names of all indices\n"
1760 " If TABLE specified, only show indices for tables\n"
1761 " matching LIKE pattern TABLE.\n"
1762 #ifdef SQLITE_ENABLE_IOTRACE
1763 ".iotrace FILE Enable I/O diagnostic logging to FILE\n"
1764 #endif
1765 #ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -2434,10 +2435,119 @@
2434 output_file_close(p->out);
2435 }
2436 p->outfile[0] = 0;
2437 p->out = stdout;
2438 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2439
2440 /*
2441 ** If an input line begins with "." then invoke this routine to
2442 ** process that line.
2443 **
@@ -2576,10 +2686,14 @@
2576 fprintf(stderr,"Error: %s\n", zErrMsg);
2577 sqlite3_free(zErrMsg);
2578 rc = 1;
2579 }
2580 }else
 
 
 
 
2581
2582 if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
2583 open_db(p, 0);
2584 /* When playing back a "dump", the content might appear in an order
2585 ** which causes immediate foreign key constraints to be violated.
@@ -2916,11 +3030,11 @@
2916 sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
2917 if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
2918 fprintf(stderr, "%s:%d: expected %d columns but found %d - "
2919 "filling the rest with NULL\n",
2920 sCtx.zFile, startLine, nCol, i+1);
2921 i++;
2922 while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
2923 }
2924 }
2925 if( sCtx.cTerm==sCtx.cColSep ){
2926 do{
@@ -2945,11 +3059,12 @@
2945 sqlite3_free(sCtx.z);
2946 sqlite3_finalize(pStmt);
2947 if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
2948 }else
2949
2950 if( c=='i' && strncmp(azArg[0], "indices", n)==0 ){
 
2951 ShellState data;
2952 char *zErrMsg = 0;
2953 open_db(p, 0);
2954 memcpy(&data, p, sizeof(data));
2955 data.showHeader = 0;
@@ -2975,11 +3090,11 @@
2975 "ORDER BY 1",
2976 callback, &data, &zErrMsg
2977 );
2978 zShellStatic = 0;
2979 }else{
2980 fprintf(stderr, "Usage: .indices ?LIKE-PATTERN?\n");
2981 rc = 1;
2982 goto meta_command_exit;
2983 }
2984 if( zErrMsg ){
2985 fprintf(stderr,"Error: %s\n", zErrMsg);
2986
--- src/shell.c
+++ src/shell.c
@@ -1742,10 +1742,11 @@
1742 static char zHelp[] =
1743 ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n"
1744 ".bail on|off Stop after hitting an error. Default OFF\n"
1745 ".clone NEWDB Clone data into NEWDB from the existing database\n"
1746 ".databases List names and files of attached databases\n"
1747 ".dbinfo ?DB? Show status information about the database\n"
1748 ".dump ?TABLE? ... Dump the database in an SQL text format\n"
1749 " If TABLE specified, only dump tables matching\n"
1750 " LIKE pattern TABLE.\n"
1751 ".echo on|off Turn command echo on or off\n"
1752 ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n"
@@ -1754,12 +1755,12 @@
1755 " With no args, it turns EXPLAIN on.\n"
1756 ".fullschema Show schema and the content of sqlite_stat tables\n"
1757 ".headers on|off Turn display of headers on or off\n"
1758 ".help Show this message\n"
1759 ".import FILE TABLE Import data from FILE into TABLE\n"
1760 ".indexes ?TABLE? Show names of all indexes\n"
1761 " If TABLE specified, only show indexes for tables\n"
1762 " matching LIKE pattern TABLE.\n"
1763 #ifdef SQLITE_ENABLE_IOTRACE
1764 ".iotrace FILE Enable I/O diagnostic logging to FILE\n"
1765 #endif
1766 #ifndef SQLITE_OMIT_LOAD_EXTENSION
@@ -2434,10 +2435,119 @@
2435 output_file_close(p->out);
2436 }
2437 p->outfile[0] = 0;
2438 p->out = stdout;
2439 }
2440
2441 /*
2442 ** Run an SQL command and return the single integer result.
2443 */
2444 static int db_int(ShellState *p, const char *zSql){
2445 sqlite3_stmt *pStmt;
2446 int res = 0;
2447 sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
2448 if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
2449 res = sqlite3_column_int(pStmt,0);
2450 }
2451 sqlite3_finalize(pStmt);
2452 return res;
2453 }
2454
2455 /*
2456 ** Convert a 2-byte or 4-byte big-endian integer into a native integer
2457 */
2458 unsigned int get2byteInt(unsigned char *a){
2459 return (a[0]<<8) + a[1];
2460 }
2461 unsigned int get4byteInt(unsigned char *a){
2462 return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
2463 }
2464
2465 /*
2466 ** Implementation of the ".info" command.
2467 **
2468 ** Return 1 on error, 2 to exit, and 0 otherwise.
2469 */
2470 static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
2471 static const struct { const char *zName; int ofst; } aField[] = {
2472 { "file change counter:", 24 },
2473 { "database page count:", 28 },
2474 { "freelist page count:", 36 },
2475 { "schema cookie:", 40 },
2476 { "schema format:", 44 },
2477 { "default cache size:", 48 },
2478 { "autovacuum top root:", 52 },
2479 { "incremental vacuum:", 64 },
2480 { "text encoding:", 56 },
2481 { "user version:", 60 },
2482 { "application id:", 68 },
2483 { "software version:", 96 },
2484 };
2485 static const struct { const char *zName; const char *zSql; } aQuery[] = {
2486 { "number of tables:",
2487 "SELECT count(*) FROM %s WHERE type='table'" },
2488 { "number of indexes:",
2489 "SELECT count(*) FROM %s WHERE type='index'" },
2490 { "number of triggers:",
2491 "SELECT count(*) FROM %s WHERE type='trigger'" },
2492 { "number of views:",
2493 "SELECT count(*) FROM %s WHERE type='view'" },
2494 { "schema size:",
2495 "SELECT total(length(sql)) FROM %s" },
2496 };
2497 sqlite3_file *pFile;
2498 int i;
2499 char *zSchemaTab;
2500 char *zDb = nArg>=2 ? azArg[1] : "main";
2501 unsigned char aHdr[100];
2502 open_db(p, 0);
2503 if( p->db==0 ) return 1;
2504 sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_FILE_POINTER, &pFile);
2505 if( pFile==0 || pFile->pMethods==0 || pFile->pMethods->xRead==0 ){
2506 return 1;
2507 }
2508 i = pFile->pMethods->xRead(pFile, aHdr, 100, 0);
2509 if( i!=SQLITE_OK ){
2510 fprintf(stderr, "unable to read database header\n");
2511 return 1;
2512 }
2513 i = get2byteInt(aHdr+16);
2514 if( i==1 ) i = 65536;
2515 fprintf(p->out, "%-20s %d\n", "database page size:", i);
2516 fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
2517 fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
2518 fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
2519 for(i=0; i<sizeof(aField)/sizeof(aField[0]); i++){
2520 int ofst = aField[i].ofst;
2521 unsigned int val = get4byteInt(aHdr + ofst);
2522 fprintf(p->out, "%-20s %u", aField[i].zName, val);
2523 switch( ofst ){
2524 case 56: {
2525 if( val==1 ) fprintf(p->out, " (utf8)");
2526 if( val==2 ) fprintf(p->out, " (utf16le)");
2527 if( val==3 ) fprintf(p->out, " (utf16be)");
2528 }
2529 }
2530 fprintf(p->out, "\n");
2531 }
2532 if( zDb==0 ){
2533 zSchemaTab = sqlite3_mprintf("main.sqlite_master");
2534 }else if( strcmp(zDb,"temp")==0 ){
2535 zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_master");
2536 }else{
2537 zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_master", zDb);
2538 }
2539 for(i=0; i<sizeof(aQuery)/sizeof(aQuery[0]); i++){
2540 char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
2541 int val = db_int(p, zSql);
2542 sqlite3_free(zSql);
2543 fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
2544 }
2545 sqlite3_free(zSchemaTab);
2546 return 0;
2547 }
2548
2549
2550 /*
2551 ** If an input line begins with "." then invoke this routine to
2552 ** process that line.
2553 **
@@ -2576,10 +2686,14 @@
2686 fprintf(stderr,"Error: %s\n", zErrMsg);
2687 sqlite3_free(zErrMsg);
2688 rc = 1;
2689 }
2690 }else
2691
2692 if( c=='d' && strncmp(azArg[0], "dbinfo", n)==0 ){
2693 rc = shell_dbinfo_command(p, nArg, azArg);
2694 }else
2695
2696 if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
2697 open_db(p, 0);
2698 /* When playing back a "dump", the content might appear in an order
2699 ** which causes immediate foreign key constraints to be violated.
@@ -2916,11 +3030,11 @@
3030 sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
3031 if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
3032 fprintf(stderr, "%s:%d: expected %d columns but found %d - "
3033 "filling the rest with NULL\n",
3034 sCtx.zFile, startLine, nCol, i+1);
3035 i += 2;
3036 while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
3037 }
3038 }
3039 if( sCtx.cTerm==sCtx.cColSep ){
3040 do{
@@ -2945,11 +3059,12 @@
3059 sqlite3_free(sCtx.z);
3060 sqlite3_finalize(pStmt);
3061 if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
3062 }else
3063
3064 if( c=='i' && (strncmp(azArg[0], "indices", n)==0
3065 || strncmp(azArg[0], "indexes", n)==0) ){
3066 ShellState data;
3067 char *zErrMsg = 0;
3068 open_db(p, 0);
3069 memcpy(&data, p, sizeof(data));
3070 data.showHeader = 0;
@@ -2975,11 +3090,11 @@
3090 "ORDER BY 1",
3091 callback, &data, &zErrMsg
3092 );
3093 zShellStatic = 0;
3094 }else{
3095 fprintf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
3096 rc = 1;
3097 goto meta_command_exit;
3098 }
3099 if( zErrMsg ){
3100 fprintf(stderr,"Error: %s\n", zErrMsg);
3101
+10 -7
--- src/shun.c
+++ src/shun.c
@@ -49,11 +49,12 @@
4949
int numRows = 3;
5050
char *zCanonical = 0;
5151
5252
login_check_credentials();
5353
if( !g.perm.Admin ){
54
- login_needed();
54
+ login_needed(0);
55
+ return;
5556
}
5657
if( P("rebuild") ){
5758
db_close(1);
5859
db_open_repository(g.zRepositoryName);
5960
db_begin_transaction();
@@ -108,11 +109,11 @@
108109
p += UUID_SIZE+1;
109110
}
110111
if( allExist ){
111112
@ <p class="noMoreShun">Artifact(s)<br />
112113
for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
113
- @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
114
+ @ <a href="%R/artifact/%s(p)">%s(p)</a><br />
114115
}
115116
@ are no longer being shunned.</p>
116117
}else{
117118
@ <p class="noMoreShun">Artifact(s)<br />
118119
for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -146,11 +147,11 @@
146147
admin_log("Shunned %Q", p);
147148
p += UUID_SIZE+1;
148149
}
149150
@ <p class="shunned">Artifact(s)<br />
150151
for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
151
- @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
152
+ @ <a href="%R/artifact/%s(p)">%s(p)</a><br />
152153
}
153154
@ have been shunned. They will no longer be pushed.
154155
@ They will be removed from the repository the next time the repository
155156
@ is rebuilt using the <b>fossil rebuild</b> command-line</p>
156157
}
@@ -248,11 +249,11 @@
248249
while( db_step(&q)==SQLITE_ROW ){
249250
const char *zUuid = db_column_text(&q, 0);
250251
int stillExists = db_column_int(&q, 1);
251252
cnt++;
252253
if( stillExists ){
253
- @ <b><a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a></b><br />
254
+ @ <b><a href="%R/artifact/%s(zUuid)">%s(zUuid)</a></b><br />
254255
}else{
255256
@ <b>%s(zUuid)</b><br />
256257
}
257258
}
258259
if( cnt==0 ){
@@ -301,11 +302,12 @@
301302
int cnt;
302303
Stmt q;
303304
304305
login_check_credentials();
305306
if( !g.perm.Admin ){
306
- login_needed();
307
+ login_needed(0);
308
+ return;
307309
}
308310
style_header("Artifact Receipts");
309311
if( showAll ){
310312
ofst = 0;
311313
}else{
@@ -381,11 +383,12 @@
381383
int rcvid = atoi(PD("rcvid","0"));
382384
Stmt q;
383385
384386
login_check_credentials();
385387
if( !g.perm.Admin ){
386
- login_needed();
388
+ login_needed(0);
389
+ return;
387390
}
388391
style_header("Artifact Receipt %d", rcvid);
389392
if( db_exists(
390393
"SELECT 1 FROM blob WHERE rcvid=%d AND"
391394
" NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
@@ -436,13 +439,13 @@
436439
while( db_step(&q)==SQLITE_ROW ){
437440
const char *zUuid = db_column_text(&q, 1);
438441
int size = db_column_int(&q, 2);
439442
const char *zDesc = db_column_text(&q, 3);
440443
if( zDesc==0 ) zDesc = "";
441
- @ <a href="%s(g.zTop)/info/%s(zUuid)">%s(zUuid)</a>
444
+ @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a>
442445
@ %h(zDesc) (size: %d(size))<br />
443446
}
444447
@ </td></tr>
445448
@ </table>
446449
db_finalize(&q);
447450
style_footer();
448451
}
449452
--- src/shun.c
+++ src/shun.c
@@ -49,11 +49,12 @@
49 int numRows = 3;
50 char *zCanonical = 0;
51
52 login_check_credentials();
53 if( !g.perm.Admin ){
54 login_needed();
 
55 }
56 if( P("rebuild") ){
57 db_close(1);
58 db_open_repository(g.zRepositoryName);
59 db_begin_transaction();
@@ -108,11 +109,11 @@
108 p += UUID_SIZE+1;
109 }
110 if( allExist ){
111 @ <p class="noMoreShun">Artifact(s)<br />
112 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
113 @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
114 }
115 @ are no longer being shunned.</p>
116 }else{
117 @ <p class="noMoreShun">Artifact(s)<br />
118 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -146,11 +147,11 @@
146 admin_log("Shunned %Q", p);
147 p += UUID_SIZE+1;
148 }
149 @ <p class="shunned">Artifact(s)<br />
150 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
151 @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
152 }
153 @ have been shunned. They will no longer be pushed.
154 @ They will be removed from the repository the next time the repository
155 @ is rebuilt using the <b>fossil rebuild</b> command-line</p>
156 }
@@ -248,11 +249,11 @@
248 while( db_step(&q)==SQLITE_ROW ){
249 const char *zUuid = db_column_text(&q, 0);
250 int stillExists = db_column_int(&q, 1);
251 cnt++;
252 if( stillExists ){
253 @ <b><a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a></b><br />
254 }else{
255 @ <b>%s(zUuid)</b><br />
256 }
257 }
258 if( cnt==0 ){
@@ -301,11 +302,12 @@
301 int cnt;
302 Stmt q;
303
304 login_check_credentials();
305 if( !g.perm.Admin ){
306 login_needed();
 
307 }
308 style_header("Artifact Receipts");
309 if( showAll ){
310 ofst = 0;
311 }else{
@@ -381,11 +383,12 @@
381 int rcvid = atoi(PD("rcvid","0"));
382 Stmt q;
383
384 login_check_credentials();
385 if( !g.perm.Admin ){
386 login_needed();
 
387 }
388 style_header("Artifact Receipt %d", rcvid);
389 if( db_exists(
390 "SELECT 1 FROM blob WHERE rcvid=%d AND"
391 " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
@@ -436,13 +439,13 @@
436 while( db_step(&q)==SQLITE_ROW ){
437 const char *zUuid = db_column_text(&q, 1);
438 int size = db_column_int(&q, 2);
439 const char *zDesc = db_column_text(&q, 3);
440 if( zDesc==0 ) zDesc = "";
441 @ <a href="%s(g.zTop)/info/%s(zUuid)">%s(zUuid)</a>
442 @ %h(zDesc) (size: %d(size))<br />
443 }
444 @ </td></tr>
445 @ </table>
446 db_finalize(&q);
447 style_footer();
448 }
449
--- src/shun.c
+++ src/shun.c
@@ -49,11 +49,12 @@
49 int numRows = 3;
50 char *zCanonical = 0;
51
52 login_check_credentials();
53 if( !g.perm.Admin ){
54 login_needed(0);
55 return;
56 }
57 if( P("rebuild") ){
58 db_close(1);
59 db_open_repository(g.zRepositoryName);
60 db_begin_transaction();
@@ -108,11 +109,11 @@
109 p += UUID_SIZE+1;
110 }
111 if( allExist ){
112 @ <p class="noMoreShun">Artifact(s)<br />
113 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
114 @ <a href="%R/artifact/%s(p)">%s(p)</a><br />
115 }
116 @ are no longer being shunned.</p>
117 }else{
118 @ <p class="noMoreShun">Artifact(s)<br />
119 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -146,11 +147,11 @@
147 admin_log("Shunned %Q", p);
148 p += UUID_SIZE+1;
149 }
150 @ <p class="shunned">Artifact(s)<br />
151 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
152 @ <a href="%R/artifact/%s(p)">%s(p)</a><br />
153 }
154 @ have been shunned. They will no longer be pushed.
155 @ They will be removed from the repository the next time the repository
156 @ is rebuilt using the <b>fossil rebuild</b> command-line</p>
157 }
@@ -248,11 +249,11 @@
249 while( db_step(&q)==SQLITE_ROW ){
250 const char *zUuid = db_column_text(&q, 0);
251 int stillExists = db_column_int(&q, 1);
252 cnt++;
253 if( stillExists ){
254 @ <b><a href="%R/artifact/%s(zUuid)">%s(zUuid)</a></b><br />
255 }else{
256 @ <b>%s(zUuid)</b><br />
257 }
258 }
259 if( cnt==0 ){
@@ -301,11 +302,12 @@
302 int cnt;
303 Stmt q;
304
305 login_check_credentials();
306 if( !g.perm.Admin ){
307 login_needed(0);
308 return;
309 }
310 style_header("Artifact Receipts");
311 if( showAll ){
312 ofst = 0;
313 }else{
@@ -381,11 +383,12 @@
383 int rcvid = atoi(PD("rcvid","0"));
384 Stmt q;
385
386 login_check_credentials();
387 if( !g.perm.Admin ){
388 login_needed(0);
389 return;
390 }
391 style_header("Artifact Receipt %d", rcvid);
392 if( db_exists(
393 "SELECT 1 FROM blob WHERE rcvid=%d AND"
394 " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid)
@@ -436,13 +439,13 @@
439 while( db_step(&q)==SQLITE_ROW ){
440 const char *zUuid = db_column_text(&q, 1);
441 int size = db_column_int(&q, 2);
442 const char *zDesc = db_column_text(&q, 3);
443 if( zDesc==0 ) zDesc = "";
444 @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a>
445 @ %h(zDesc) (size: %d(size))<br />
446 }
447 @ </td></tr>
448 @ </table>
449 db_finalize(&q);
450 style_footer();
451 }
452
+24 -12
--- src/sitemap.c
+++ src/sitemap.c
@@ -34,62 +34,74 @@
3434
@ The following links are just a few of the many web-pages available for
3535
@ this Fossil repository:
3636
@ </p>
3737
@
3838
@ <ul>
39
- @ <li>%z(href("%R/home"))Home Page</a></li>
39
+ @ <li>%z(href("%R/home"))Home Page</a>
40
+ @ <ul>
41
+ @ <li>%z(href("%R/docsrc"))Search Project Documentation</a></li>
42
+ @ </ul></li>
4043
@ <li>%z(href("%R/tree"))File Browser</a></li>
41
- @ <ul>
42
- @ <li>%z(href("%R/tree?ci=trunk"))Tree-view, Trunk Checkin</a></li>
44
+ @ <ul>
45
+ @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
46
+ @ Trunk Checkin</a></li>
4347
@ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
4448
@ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
4549
@ </ul>
4650
@ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
4751
@ <ul>
48
- @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li>
52
+ @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10
53
+ @ checkins</a></li>
4954
@ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name
5055
@ changes</a></li>
5156
@ <li>%z(href("%R/reports"))Activity Reports</a></li>
5257
@ </ul>
53
- @ <li>Branches and Tags</a>
58
+ @ <li>%z(href("%R/brlist"))Branches</a></li>
5459
@ <ul>
55
- @ <li>%z(href("%R/brlist"))Branches</a></li>
5660
@ <li>%z(href("%R/leaves"))Leaf Checkins</a></li>
5761
@ <li>%z(href("%R/taglist"))List of Tags</a></li>
5862
@ </ul>
5963
@ </li>
60
- @ <li>%z(href("%R/wiki"))Wiki</a>
64
+ @ <li>%z(href("%R/wikihelp"))Wiki</a>
6165
@ <ul>
66
+ @ <li>%z(href("%R/wikisrch"))Wiki Search</a></li>
6267
@ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li>
6368
@ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li>
6469
@ <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li>
70
+ @ <li>%z(href("%R/md_rules"))Markdown Formatting Rules</a></li>
71
+ @ <li>%z(href("%R/wiki?name=Sandbox"))Sandbox</a></li>
6572
@ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
6673
@ </ul>
6774
@ </li>
6875
@ <li>%z(href("%R/reportlist"))Tickets</a>
6976
@ <ul>
77
+ @ <li>%z(href("%R/tktsrch"))Ticket Search</a></li>
7078
@ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li>
7179
@ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
7280
@ </ul>
7381
@ </li>
82
+ @ <li>%z(href("%R/search"))Full-Text Search</a></li>
7483
@ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li>
75
- @ <li>Repository Status
84
+ @ <li>%z(href("%R/stat"))Repository Status</a>
7685
@ <ul>
77
- @ <li>%z(href("%R/stat"))Status Summary</a></li>
78
- @ <li>%z(href("%R/urllist"))List of URLs used to access this repository</a></li>
86
+ @ <li>%z(href("%R/hash-collisions"))Collisions on SHA1 hash
87
+ @ prefixes</a></li>
88
+ @ <li>%z(href("%R/urllist"))List of URLs used to access
89
+ @ this repository</a></li>
7990
@ <li>%z(href("%R/bloblist"))List of Artifacts</a></li>
8091
@ </ul></li>
8192
@ <li>On-line Documentation
8293
@ <ul>
8394
@ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li>
8495
@ <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li>
96
+ @ <li>%z(href("%R/mimetype_list"))Filename suffix to mimetype map</a></li>
8597
@ </ul></li>
86
- @ <li>Administration Pages
98
+ @ <li>%z(href("%R/setup"))Administration Pages</a>
8799
@ <ul>
88
- @ <li>%z(href("%R/setup"))Configuration and Setup Menu</a></li>
89100
@ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li>
90101
@ <li>%z(href("%R/admin_log"))Admin log</a></li>
102
+ @ <li>%z(href("%R/cachestat"))Status of the web-page cache</a></li>
91103
@ </ul></li>
92104
@ <li>Test Pages
93105
@ <ul>
94106
@ <li>%z(href("%R/test_env"))CGI Environment Test</a></li>
95107
@ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li>
96108
--- src/sitemap.c
+++ src/sitemap.c
@@ -34,62 +34,74 @@
34 @ The following links are just a few of the many web-pages available for
35 @ this Fossil repository:
36 @ </p>
37 @
38 @ <ul>
39 @ <li>%z(href("%R/home"))Home Page</a></li>
 
 
 
40 @ <li>%z(href("%R/tree"))File Browser</a></li>
41 @ <ul>
42 @ <li>%z(href("%R/tree?ci=trunk"))Tree-view, Trunk Checkin</a></li>
 
43 @ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
44 @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
45 @ </ul>
46 @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
47 @ <ul>
48 @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li>
 
49 @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name
50 @ changes</a></li>
51 @ <li>%z(href("%R/reports"))Activity Reports</a></li>
52 @ </ul>
53 @ <li>Branches and Tags</a>
54 @ <ul>
55 @ <li>%z(href("%R/brlist"))Branches</a></li>
56 @ <li>%z(href("%R/leaves"))Leaf Checkins</a></li>
57 @ <li>%z(href("%R/taglist"))List of Tags</a></li>
58 @ </ul>
59 @ </li>
60 @ <li>%z(href("%R/wiki"))Wiki</a>
61 @ <ul>
 
62 @ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li>
63 @ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li>
64 @ <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li>
 
 
65 @ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
66 @ </ul>
67 @ </li>
68 @ <li>%z(href("%R/reportlist"))Tickets</a>
69 @ <ul>
 
70 @ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li>
71 @ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
72 @ </ul>
73 @ </li>
 
74 @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li>
75 @ <li>Repository Status
76 @ <ul>
77 @ <li>%z(href("%R/stat"))Status Summary</a></li>
78 @ <li>%z(href("%R/urllist"))List of URLs used to access this repository</a></li>
 
 
79 @ <li>%z(href("%R/bloblist"))List of Artifacts</a></li>
80 @ </ul></li>
81 @ <li>On-line Documentation
82 @ <ul>
83 @ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li>
84 @ <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li>
 
85 @ </ul></li>
86 @ <li>Administration Pages
87 @ <ul>
88 @ <li>%z(href("%R/setup"))Configuration and Setup Menu</a></li>
89 @ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li>
90 @ <li>%z(href("%R/admin_log"))Admin log</a></li>
 
91 @ </ul></li>
92 @ <li>Test Pages
93 @ <ul>
94 @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li>
95 @ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li>
96
--- src/sitemap.c
+++ src/sitemap.c
@@ -34,62 +34,74 @@
34 @ The following links are just a few of the many web-pages available for
35 @ this Fossil repository:
36 @ </p>
37 @
38 @ <ul>
39 @ <li>%z(href("%R/home"))Home Page</a>
40 @ <ul>
41 @ <li>%z(href("%R/docsrc"))Search Project Documentation</a></li>
42 @ </ul></li>
43 @ <li>%z(href("%R/tree"))File Browser</a></li>
44 @ <ul>
45 @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
46 @ Trunk Checkin</a></li>
47 @ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
48 @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
49 @ </ul>
50 @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
51 @ <ul>
52 @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10
53 @ checkins</a></li>
54 @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name
55 @ changes</a></li>
56 @ <li>%z(href("%R/reports"))Activity Reports</a></li>
57 @ </ul>
58 @ <li>%z(href("%R/brlist"))Branches</a></li>
59 @ <ul>
 
60 @ <li>%z(href("%R/leaves"))Leaf Checkins</a></li>
61 @ <li>%z(href("%R/taglist"))List of Tags</a></li>
62 @ </ul>
63 @ </li>
64 @ <li>%z(href("%R/wikihelp"))Wiki</a>
65 @ <ul>
66 @ <li>%z(href("%R/wikisrch"))Wiki Search</a></li>
67 @ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li>
68 @ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li>
69 @ <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li>
70 @ <li>%z(href("%R/md_rules"))Markdown Formatting Rules</a></li>
71 @ <li>%z(href("%R/wiki?name=Sandbox"))Sandbox</a></li>
72 @ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
73 @ </ul>
74 @ </li>
75 @ <li>%z(href("%R/reportlist"))Tickets</a>
76 @ <ul>
77 @ <li>%z(href("%R/tktsrch"))Ticket Search</a></li>
78 @ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li>
79 @ <li>%z(href("%R/attachlist"))List of Attachments</a></li>
80 @ </ul>
81 @ </li>
82 @ <li>%z(href("%R/search"))Full-Text Search</a></li>
83 @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li>
84 @ <li>%z(href("%R/stat"))Repository Status</a>
85 @ <ul>
86 @ <li>%z(href("%R/hash-collisions"))Collisions on SHA1 hash
87 @ prefixes</a></li>
88 @ <li>%z(href("%R/urllist"))List of URLs used to access
89 @ this repository</a></li>
90 @ <li>%z(href("%R/bloblist"))List of Artifacts</a></li>
91 @ </ul></li>
92 @ <li>On-line Documentation
93 @ <ul>
94 @ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li>
95 @ <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li>
96 @ <li>%z(href("%R/mimetype_list"))Filename suffix to mimetype map</a></li>
97 @ </ul></li>
98 @ <li>%z(href("%R/setup"))Administration Pages</a>
99 @ <ul>
 
100 @ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li>
101 @ <li>%z(href("%R/admin_log"))Admin log</a></li>
102 @ <li>%z(href("%R/cachestat"))Status of the web-page cache</a></li>
103 @ </ul></li>
104 @ <li>Test Pages
105 @ <ul>
106 @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li>
107 @ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li>
108
+120 -9
--- src/skins.c
+++ src/skins.c
@@ -37,21 +37,120 @@
3737
** 4. Make an entry in the following array for the new skin.
3838
*/
3939
static struct BuiltinSkin {
4040
const char *zDesc; /* Description of this skin */
4141
const char *zLabel; /* The directory under skins/ holding this skin */
42
+ int whiteForeground; /* True if this skin uses a light-colored foreground */
4243
char *zSQL; /* Filled in at run-time with SQL to insert this skin */
4344
} aBuiltinSkin[] = {
44
- { "Default", "default", 0 },
45
- { "Plain Gray, No Logo", "plain_gray", 0 },
46
- { "Khaki, No Logo", "khaki", 0 },
47
- { "Black & White, Menu on Left", "black_and_white", 0 },
48
- { "Shadow boxes & Rounded Corners", "rounded1", 0 },
49
- { "Enhanced Default", "enhanced1", 0 },
50
- { "San Francisco Modern", "etienne1", 0 },
51
- { "Eagle", "eagle", 0 },
45
+ { "Default", "default", 0, 0 },
46
+ { "Plain Gray, No Logo", "plain_gray", 0, 0 },
47
+ { "Khaki, No Logo", "khaki", 0, 0 },
48
+ { "Black & White, Menu on Left", "black_and_white", 0, 0 },
49
+ { "Shadow boxes & Rounded Corners", "rounded1", 0, 0 },
50
+ { "Enhanced Default", "enhanced1", 0, 0 },
51
+ { "San Francisco Modern", "etienne1", 0, 0 },
52
+ { "Eagle", "eagle", 1, 0 },
5253
};
54
+
55
+/*
56
+** Alternative skins can be specified in the CGI script or by options
57
+** on the "http", "ui", and "server" commands. The alternative skin
58
+** name must be one of the aBuiltinSkin[].zLabel names. If there is
59
+** a match, that alternative is used.
60
+**
61
+** The following static variable holds the name of the alternative skin,
62
+** or NULL if the skin should be as configured.
63
+*/
64
+static struct BuiltinSkin *pAltSkin = 0;
65
+static char *zAltSkinDir = 0;
66
+
67
+/*
68
+** Invoke this routine to set the alternative skin. Return NULL if the
69
+** alternative was successfully installed. Return a string listing all
70
+** available skins if zName does not match an available skin. Memory
71
+** for the returned string comes from fossil_malloc() and should be freed
72
+** by the caller.
73
+**
74
+** If the alternative skin name contains one or more '/' characters, then
75
+** it is assumed to be a directory on disk that holds override css.txt,
76
+** footer.txt, and header.txt. This mode can be used for interactive
77
+** development of new skins.
78
+*/
79
+char *skin_use_alternative(const char *zName){
80
+ int i;
81
+ Blob err = BLOB_INITIALIZER;
82
+ if( strchr(zName, '/')!=0 ){
83
+ zAltSkinDir = fossil_strdup(zName);
84
+ return 0;
85
+ }
86
+ for(i=0; i<ArraySize(aBuiltinSkin); i++){
87
+ if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
88
+ pAltSkin = &aBuiltinSkin[i];
89
+ return 0;
90
+ }
91
+ }
92
+ blob_appendf(&err, "available skins: %s", aBuiltinSkin[0].zLabel);
93
+ for(i=1; i<ArraySize(aBuiltinSkin); i++){
94
+ blob_append(&err, " ", 1);
95
+ blob_append(&err, aBuiltinSkin[i].zLabel, -1);
96
+ }
97
+ return blob_str(&err);
98
+}
99
+
100
+/*
101
+** Look for the --skin command-line option and process it. Or
102
+** call fossil_fatal() if an unknown skin is specified.
103
+*/
104
+void skin_override(void){
105
+ const char *zSkin = find_option("skin",0,1);
106
+ if( zSkin ){
107
+ char *zErr = skin_use_alternative(zSkin);
108
+ if( zErr ) fossil_fatal("%s", zErr);
109
+ }
110
+}
111
+
112
+/*
113
+** The following routines return the various components of the skin
114
+** that should be used for the current run.
115
+*/
116
+const char *skin_get(const char *zWhat){
117
+ const char *zOut;
118
+ char *z;
119
+ if( zAltSkinDir ){
120
+ char *z = mprintf("%s/%s.txt", zAltSkinDir, zWhat);
121
+ if( file_isfile(z) ){
122
+ Blob x;
123
+ blob_read_from_file(&x, z);
124
+ fossil_free(z);
125
+ return blob_str(&x);
126
+ }
127
+ fossil_free(z);
128
+ }
129
+ if( pAltSkin ){
130
+ z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
131
+ zOut = builtin_text(z);
132
+ fossil_free(z);
133
+ }else{
134
+ zOut = db_get(zWhat, 0);
135
+ if( zOut==0 ){
136
+ z = mprintf("skins/default/%s.txt", zWhat);
137
+ zOut = builtin_text(z);
138
+ fossil_free(z);
139
+ }
140
+ }
141
+ return zOut;
142
+}
143
+int skin_white_foreground(void){
144
+ int rc;
145
+ if( pAltSkin ){
146
+ rc = pAltSkin->whiteForeground;
147
+ }else{
148
+ rc = db_get_boolean("white-foreground",0);
149
+ }
150
+ return rc;
151
+}
53152
54153
/*
55154
** For a skin named zSkinName, compute the name of the CONFIG table
56155
** entry where that skin is stored and return it.
57156
**
@@ -210,11 +309,12 @@
210309
Stmt q;
211310
int seenCurrent = 0;
212311
213312
login_check_credentials();
214313
if( !g.perm.Setup ){
215
- login_needed();
314
+ login_needed(0);
315
+ return;
216316
}
217317
db_begin_transaction();
218318
zCurrent = getSkin(0);
219319
for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
220320
aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel);
@@ -288,10 +388,18 @@
288388
@ <a href="setup_editcss">CSS</a>,
289389
@ <a href="setup_header">Header</a>, and
290390
@ <a href="setup_footer">Footer</a> that determines the look and feel
291391
@ of the web interface.</p>
292392
@
393
+ if( pAltSkin ){
394
+ @ <p class="generalError">
395
+ @ This page is generated using an skin override named
396
+ @ "%h(pAltSkin->zLabel)". You can change the skin configuration
397
+ @ below, but the changes will not take effect until the Fossil server
398
+ @ is restarted without the override.</p>
399
+ @
400
+ }
293401
@ <h2>Available Skins:</h2>
294402
@ <table border="0">
295403
for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
296404
z = aBuiltinSkin[i].zDesc;
297405
@ <tr><td>%d(i+1).<td>%h(z)<td>&nbsp;&nbsp;<td>
@@ -300,10 +408,13 @@
300408
seenCurrent = 1;
301409
}else{
302410
@ <form action="%s(g.zTop)/setup_skin" method="post">
303411
@ <input type="hidden" name="sn" value="%h(z)" />
304412
@ <input type="submit" name="load" value="Install" />
413
+ if( pAltSkin==&aBuiltinSkin[i] ){
414
+ @ (Current override)
415
+ }
305416
@ </form>
306417
}
307418
@ </tr>
308419
}
309420
db_prepare(&q,
310421
--- src/skins.c
+++ src/skins.c
@@ -37,21 +37,120 @@
37 ** 4. Make an entry in the following array for the new skin.
38 */
39 static struct BuiltinSkin {
40 const char *zDesc; /* Description of this skin */
41 const char *zLabel; /* The directory under skins/ holding this skin */
 
42 char *zSQL; /* Filled in at run-time with SQL to insert this skin */
43 } aBuiltinSkin[] = {
44 { "Default", "default", 0 },
45 { "Plain Gray, No Logo", "plain_gray", 0 },
46 { "Khaki, No Logo", "khaki", 0 },
47 { "Black & White, Menu on Left", "black_and_white", 0 },
48 { "Shadow boxes & Rounded Corners", "rounded1", 0 },
49 { "Enhanced Default", "enhanced1", 0 },
50 { "San Francisco Modern", "etienne1", 0 },
51 { "Eagle", "eagle", 0 },
52 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
54 /*
55 ** For a skin named zSkinName, compute the name of the CONFIG table
56 ** entry where that skin is stored and return it.
57 **
@@ -210,11 +309,12 @@
210 Stmt q;
211 int seenCurrent = 0;
212
213 login_check_credentials();
214 if( !g.perm.Setup ){
215 login_needed();
 
216 }
217 db_begin_transaction();
218 zCurrent = getSkin(0);
219 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
220 aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel);
@@ -288,10 +388,18 @@
288 @ <a href="setup_editcss">CSS</a>,
289 @ <a href="setup_header">Header</a>, and
290 @ <a href="setup_footer">Footer</a> that determines the look and feel
291 @ of the web interface.</p>
292 @
 
 
 
 
 
 
 
 
293 @ <h2>Available Skins:</h2>
294 @ <table border="0">
295 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
296 z = aBuiltinSkin[i].zDesc;
297 @ <tr><td>%d(i+1).<td>%h(z)<td>&nbsp;&nbsp;<td>
@@ -300,10 +408,13 @@
300 seenCurrent = 1;
301 }else{
302 @ <form action="%s(g.zTop)/setup_skin" method="post">
303 @ <input type="hidden" name="sn" value="%h(z)" />
304 @ <input type="submit" name="load" value="Install" />
 
 
 
305 @ </form>
306 }
307 @ </tr>
308 }
309 db_prepare(&q,
310
--- src/skins.c
+++ src/skins.c
@@ -37,21 +37,120 @@
37 ** 4. Make an entry in the following array for the new skin.
38 */
39 static struct BuiltinSkin {
40 const char *zDesc; /* Description of this skin */
41 const char *zLabel; /* The directory under skins/ holding this skin */
42 int whiteForeground; /* True if this skin uses a light-colored foreground */
43 char *zSQL; /* Filled in at run-time with SQL to insert this skin */
44 } aBuiltinSkin[] = {
45 { "Default", "default", 0, 0 },
46 { "Plain Gray, No Logo", "plain_gray", 0, 0 },
47 { "Khaki, No Logo", "khaki", 0, 0 },
48 { "Black & White, Menu on Left", "black_and_white", 0, 0 },
49 { "Shadow boxes & Rounded Corners", "rounded1", 0, 0 },
50 { "Enhanced Default", "enhanced1", 0, 0 },
51 { "San Francisco Modern", "etienne1", 0, 0 },
52 { "Eagle", "eagle", 1, 0 },
53 };
54
55 /*
56 ** Alternative skins can be specified in the CGI script or by options
57 ** on the "http", "ui", and "server" commands. The alternative skin
58 ** name must be one of the aBuiltinSkin[].zLabel names. If there is
59 ** a match, that alternative is used.
60 **
61 ** The following static variable holds the name of the alternative skin,
62 ** or NULL if the skin should be as configured.
63 */
64 static struct BuiltinSkin *pAltSkin = 0;
65 static char *zAltSkinDir = 0;
66
67 /*
68 ** Invoke this routine to set the alternative skin. Return NULL if the
69 ** alternative was successfully installed. Return a string listing all
70 ** available skins if zName does not match an available skin. Memory
71 ** for the returned string comes from fossil_malloc() and should be freed
72 ** by the caller.
73 **
74 ** If the alternative skin name contains one or more '/' characters, then
75 ** it is assumed to be a directory on disk that holds override css.txt,
76 ** footer.txt, and header.txt. This mode can be used for interactive
77 ** development of new skins.
78 */
79 char *skin_use_alternative(const char *zName){
80 int i;
81 Blob err = BLOB_INITIALIZER;
82 if( strchr(zName, '/')!=0 ){
83 zAltSkinDir = fossil_strdup(zName);
84 return 0;
85 }
86 for(i=0; i<ArraySize(aBuiltinSkin); i++){
87 if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
88 pAltSkin = &aBuiltinSkin[i];
89 return 0;
90 }
91 }
92 blob_appendf(&err, "available skins: %s", aBuiltinSkin[0].zLabel);
93 for(i=1; i<ArraySize(aBuiltinSkin); i++){
94 blob_append(&err, " ", 1);
95 blob_append(&err, aBuiltinSkin[i].zLabel, -1);
96 }
97 return blob_str(&err);
98 }
99
100 /*
101 ** Look for the --skin command-line option and process it. Or
102 ** call fossil_fatal() if an unknown skin is specified.
103 */
104 void skin_override(void){
105 const char *zSkin = find_option("skin",0,1);
106 if( zSkin ){
107 char *zErr = skin_use_alternative(zSkin);
108 if( zErr ) fossil_fatal("%s", zErr);
109 }
110 }
111
112 /*
113 ** The following routines return the various components of the skin
114 ** that should be used for the current run.
115 */
116 const char *skin_get(const char *zWhat){
117 const char *zOut;
118 char *z;
119 if( zAltSkinDir ){
120 char *z = mprintf("%s/%s.txt", zAltSkinDir, zWhat);
121 if( file_isfile(z) ){
122 Blob x;
123 blob_read_from_file(&x, z);
124 fossil_free(z);
125 return blob_str(&x);
126 }
127 fossil_free(z);
128 }
129 if( pAltSkin ){
130 z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
131 zOut = builtin_text(z);
132 fossil_free(z);
133 }else{
134 zOut = db_get(zWhat, 0);
135 if( zOut==0 ){
136 z = mprintf("skins/default/%s.txt", zWhat);
137 zOut = builtin_text(z);
138 fossil_free(z);
139 }
140 }
141 return zOut;
142 }
143 int skin_white_foreground(void){
144 int rc;
145 if( pAltSkin ){
146 rc = pAltSkin->whiteForeground;
147 }else{
148 rc = db_get_boolean("white-foreground",0);
149 }
150 return rc;
151 }
152
153 /*
154 ** For a skin named zSkinName, compute the name of the CONFIG table
155 ** entry where that skin is stored and return it.
156 **
@@ -210,11 +309,12 @@
309 Stmt q;
310 int seenCurrent = 0;
311
312 login_check_credentials();
313 if( !g.perm.Setup ){
314 login_needed(0);
315 return;
316 }
317 db_begin_transaction();
318 zCurrent = getSkin(0);
319 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
320 aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel);
@@ -288,10 +388,18 @@
388 @ <a href="setup_editcss">CSS</a>,
389 @ <a href="setup_header">Header</a>, and
390 @ <a href="setup_footer">Footer</a> that determines the look and feel
391 @ of the web interface.</p>
392 @
393 if( pAltSkin ){
394 @ <p class="generalError">
395 @ This page is generated using an skin override named
396 @ "%h(pAltSkin->zLabel)". You can change the skin configuration
397 @ below, but the changes will not take effect until the Fossil server
398 @ is restarted without the override.</p>
399 @
400 }
401 @ <h2>Available Skins:</h2>
402 @ <table border="0">
403 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
404 z = aBuiltinSkin[i].zDesc;
405 @ <tr><td>%d(i+1).<td>%h(z)<td>&nbsp;&nbsp;<td>
@@ -300,10 +408,13 @@
408 seenCurrent = 1;
409 }else{
410 @ <form action="%s(g.zTop)/setup_skin" method="post">
411 @ <input type="hidden" name="sn" value="%h(z)" />
412 @ <input type="submit" name="load" value="Install" />
413 if( pAltSkin==&aBuiltinSkin[i] ){
414 @ (Current override)
415 }
416 @ </form>
417 }
418 @ </tr>
419 }
420 db_prepare(&q,
421
+5 -4
--- src/stat.c
+++ src/stat.c
@@ -52,20 +52,21 @@
5252
int brief;
5353
char zBuf[100];
5454
const char *p;
5555
5656
login_check_credentials();
57
- if( !g.perm.Read ){ login_needed(); return; }
57
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
5858
brief = P("brief")!=0;
5959
style_header("Repository Statistics");
6060
style_adunit_config(ADUNIT_RIGHT_OK);
6161
if( g.perm.Admin ){
6262
style_submenu_element("URLs", "URLs and Checkouts", "urllist");
6363
style_submenu_element("Schema", "Repository Schema", "repo_schema");
6464
style_submenu_element("Web-Cache", "Web-Cache Stats", "cachestat");
6565
}
66
- style_submenu_element("Activity", "Activity Reports", "reports");
66
+ style_submenu_element("Activity Reports", 0, "reports");
67
+ style_submenu_element("SHA1 Collisions", 0, "hash-collisions");
6768
@ <table class="label-value">
6869
@ <tr><th>Repository&nbsp;Size:</th><td>
6970
fsize = file_size(g.zRepositoryName);
7071
bigSizeName(sizeof(zBuf), zBuf, fsize);
7172
@ %s(zBuf)
@@ -291,11 +292,11 @@
291292
*/
292293
void urllist_page(void){
293294
Stmt q;
294295
int cnt;
295296
login_check_credentials();
296
- if( !g.perm.Admin ){ login_needed(); return; }
297
+ if( !g.perm.Admin ){ login_needed(0); return; }
297298
298299
style_header("URLs and Checkouts");
299300
style_adunit_config(ADUNIT_RIGHT_OK);
300301
style_submenu_element("Stat", "Repository Stats", "stat");
301302
style_submenu_element("Schema", "Repository Schema", "repo_schema");
@@ -338,11 +339,11 @@
338339
** Show the repository schema
339340
*/
340341
void repo_schema_page(void){
341342
Stmt q;
342343
login_check_credentials();
343
- if( !g.perm.Admin ){ login_needed(); return; }
344
+ if( !g.perm.Admin ){ login_needed(0); return; }
344345
345346
style_header("Repository Schema");
346347
style_adunit_config(ADUNIT_RIGHT_OK);
347348
style_submenu_element("Stat", "Repository Stats", "stat");
348349
style_submenu_element("URLs", "URLs and Checkouts", "urllist");
349350
--- src/stat.c
+++ src/stat.c
@@ -52,20 +52,21 @@
52 int brief;
53 char zBuf[100];
54 const char *p;
55
56 login_check_credentials();
57 if( !g.perm.Read ){ login_needed(); return; }
58 brief = P("brief")!=0;
59 style_header("Repository Statistics");
60 style_adunit_config(ADUNIT_RIGHT_OK);
61 if( g.perm.Admin ){
62 style_submenu_element("URLs", "URLs and Checkouts", "urllist");
63 style_submenu_element("Schema", "Repository Schema", "repo_schema");
64 style_submenu_element("Web-Cache", "Web-Cache Stats", "cachestat");
65 }
66 style_submenu_element("Activity", "Activity Reports", "reports");
 
67 @ <table class="label-value">
68 @ <tr><th>Repository&nbsp;Size:</th><td>
69 fsize = file_size(g.zRepositoryName);
70 bigSizeName(sizeof(zBuf), zBuf, fsize);
71 @ %s(zBuf)
@@ -291,11 +292,11 @@
291 */
292 void urllist_page(void){
293 Stmt q;
294 int cnt;
295 login_check_credentials();
296 if( !g.perm.Admin ){ login_needed(); return; }
297
298 style_header("URLs and Checkouts");
299 style_adunit_config(ADUNIT_RIGHT_OK);
300 style_submenu_element("Stat", "Repository Stats", "stat");
301 style_submenu_element("Schema", "Repository Schema", "repo_schema");
@@ -338,11 +339,11 @@
338 ** Show the repository schema
339 */
340 void repo_schema_page(void){
341 Stmt q;
342 login_check_credentials();
343 if( !g.perm.Admin ){ login_needed(); return; }
344
345 style_header("Repository Schema");
346 style_adunit_config(ADUNIT_RIGHT_OK);
347 style_submenu_element("Stat", "Repository Stats", "stat");
348 style_submenu_element("URLs", "URLs and Checkouts", "urllist");
349
--- src/stat.c
+++ src/stat.c
@@ -52,20 +52,21 @@
52 int brief;
53 char zBuf[100];
54 const char *p;
55
56 login_check_credentials();
57 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
58 brief = P("brief")!=0;
59 style_header("Repository Statistics");
60 style_adunit_config(ADUNIT_RIGHT_OK);
61 if( g.perm.Admin ){
62 style_submenu_element("URLs", "URLs and Checkouts", "urllist");
63 style_submenu_element("Schema", "Repository Schema", "repo_schema");
64 style_submenu_element("Web-Cache", "Web-Cache Stats", "cachestat");
65 }
66 style_submenu_element("Activity Reports", 0, "reports");
67 style_submenu_element("SHA1 Collisions", 0, "hash-collisions");
68 @ <table class="label-value">
69 @ <tr><th>Repository&nbsp;Size:</th><td>
70 fsize = file_size(g.zRepositoryName);
71 bigSizeName(sizeof(zBuf), zBuf, fsize);
72 @ %s(zBuf)
@@ -291,11 +292,11 @@
292 */
293 void urllist_page(void){
294 Stmt q;
295 int cnt;
296 login_check_credentials();
297 if( !g.perm.Admin ){ login_needed(0); return; }
298
299 style_header("URLs and Checkouts");
300 style_adunit_config(ADUNIT_RIGHT_OK);
301 style_submenu_element("Stat", "Repository Stats", "stat");
302 style_submenu_element("Schema", "Repository Schema", "repo_schema");
@@ -338,11 +339,11 @@
339 ** Show the repository schema
340 */
341 void repo_schema_page(void){
342 Stmt q;
343 login_check_credentials();
344 if( !g.perm.Admin ){ login_needed(0); return; }
345
346 style_header("Repository Schema");
347 style_adunit_config(ADUNIT_RIGHT_OK);
348 style_submenu_element("Stat", "Repository Stats", "stat");
349 style_submenu_element("URLs", "URLs and Checkouts", "urllist");
350
+10 -10
--- src/statrep.c
+++ src/statrep.c
@@ -129,11 +129,11 @@
129129
assert( statsReportType && "Must call stats_report_init_view() first." );
130130
switch( statsReportType ){
131131
case 'c':
132132
return "checkins";
133133
case 'e':
134
- return "events";
134
+ return "technotes";
135135
case 'w':
136136
return "wiki changes";
137137
case 't':
138138
return "ticket changes";
139139
case 'g':
@@ -171,13 +171,13 @@
171171
cgi_printf(" <strong>checkins</strong>", zTop);
172172
}else{
173173
cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop);
174174
}
175175
if('e' == statsReportType){
176
- cgi_printf(" <strong>events</strong>", zTop);
176
+ cgi_printf(" <strong>technotes</strong>", zTop);
177177
}else{
178
- cgi_printf(" <a href='%s&type=e'>events</a>", zTop);
178
+ cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop);
179179
}
180180
if( 't' == statsReportType ){
181181
cgi_printf(" <strong>tickets</strong>", zTop);
182182
}else{
183183
cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop);
@@ -216,13 +216,13 @@
216216
strlen(zTimeframe),
217217
zTimeframe);
218218
while( SQLITE_ROW == db_step(&stWeek) ){
219219
const char *zWeek = db_column_text(&stWeek,0);
220220
const int nCount = db_column_int(&stWeek,1);
221
- cgi_printf("<a href='%s/timeline?"
221
+ cgi_printf("<a href='%R/timeline?"
222222
"yw=%t-%t&n=%d&y=%s'>%s</a>",
223
- g.zTop, yearPart, zWeek,
223
+ yearPart, zWeek,
224224
nCount, statsReportTimelineYFlag, zWeek);
225225
}
226226
db_finalize(&stWeek);
227227
}
228228
@@ -328,13 +328,13 @@
328328
nEventTotal += nCount;
329329
nEventsPerYear += nCount;
330330
@<tr class='row%d(rowClass)'>
331331
@ <td>
332332
if(includeMonth){
333
- cgi_printf("<a href='%s/timeline?"
333
+ cgi_printf("<a href='%R/timeline?"
334334
"ym=%t&n=%d&y=%s",
335
- g.zTop, zTimeframe, nCount,
335
+ zTimeframe, nCount,
336336
statsReportTimelineYFlag );
337337
/* Reminder: n=nCount is not actually correct for bymonth unless
338338
that was the only user who caused events.
339339
*/
340340
if( zUserName && *zUserName ){
@@ -672,12 +672,12 @@
672672
? (int)(100 * nCount / nMaxEvents)
673673
: 0;
674674
if(!nSize) nSize = 1;
675675
total += nCount;
676676
cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
677
- cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s",
678
- g.zTop, zYear, zWeek, nCount,
677
+ cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
678
+ zYear, zWeek, nCount,
679679
statsReportTimelineYFlag);
680680
if(zUserName && *zUserName){
681681
cgi_printf("&u=%t",zUserName);
682682
}
683683
cgi_printf("'>%s</a></td>",zWeek);
@@ -728,11 +728,11 @@
728728
HQuery url; /* URL for various branch links */
729729
const char *zView = P("view"); /* Which view/report to show. */
730730
const char *zUserName = P("user");
731731
732732
login_check_credentials();
733
- if( !g.perm.Read ){ login_needed(); return; }
733
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
734734
if(!zUserName) zUserName = P("u");
735735
url_initialize(&url, "reports");
736736
if(zUserName && *zUserName){
737737
url_add_parameter(&url,"user", zUserName);
738738
statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user");
739739
--- src/statrep.c
+++ src/statrep.c
@@ -129,11 +129,11 @@
129 assert( statsReportType && "Must call stats_report_init_view() first." );
130 switch( statsReportType ){
131 case 'c':
132 return "checkins";
133 case 'e':
134 return "events";
135 case 'w':
136 return "wiki changes";
137 case 't':
138 return "ticket changes";
139 case 'g':
@@ -171,13 +171,13 @@
171 cgi_printf(" <strong>checkins</strong>", zTop);
172 }else{
173 cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop);
174 }
175 if('e' == statsReportType){
176 cgi_printf(" <strong>events</strong>", zTop);
177 }else{
178 cgi_printf(" <a href='%s&type=e'>events</a>", zTop);
179 }
180 if( 't' == statsReportType ){
181 cgi_printf(" <strong>tickets</strong>", zTop);
182 }else{
183 cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop);
@@ -216,13 +216,13 @@
216 strlen(zTimeframe),
217 zTimeframe);
218 while( SQLITE_ROW == db_step(&stWeek) ){
219 const char *zWeek = db_column_text(&stWeek,0);
220 const int nCount = db_column_int(&stWeek,1);
221 cgi_printf("<a href='%s/timeline?"
222 "yw=%t-%t&n=%d&y=%s'>%s</a>",
223 g.zTop, yearPart, zWeek,
224 nCount, statsReportTimelineYFlag, zWeek);
225 }
226 db_finalize(&stWeek);
227 }
228
@@ -328,13 +328,13 @@
328 nEventTotal += nCount;
329 nEventsPerYear += nCount;
330 @<tr class='row%d(rowClass)'>
331 @ <td>
332 if(includeMonth){
333 cgi_printf("<a href='%s/timeline?"
334 "ym=%t&n=%d&y=%s",
335 g.zTop, zTimeframe, nCount,
336 statsReportTimelineYFlag );
337 /* Reminder: n=nCount is not actually correct for bymonth unless
338 that was the only user who caused events.
339 */
340 if( zUserName && *zUserName ){
@@ -672,12 +672,12 @@
672 ? (int)(100 * nCount / nMaxEvents)
673 : 0;
674 if(!nSize) nSize = 1;
675 total += nCount;
676 cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
677 cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s",
678 g.zTop, zYear, zWeek, nCount,
679 statsReportTimelineYFlag);
680 if(zUserName && *zUserName){
681 cgi_printf("&u=%t",zUserName);
682 }
683 cgi_printf("'>%s</a></td>",zWeek);
@@ -728,11 +728,11 @@
728 HQuery url; /* URL for various branch links */
729 const char *zView = P("view"); /* Which view/report to show. */
730 const char *zUserName = P("user");
731
732 login_check_credentials();
733 if( !g.perm.Read ){ login_needed(); return; }
734 if(!zUserName) zUserName = P("u");
735 url_initialize(&url, "reports");
736 if(zUserName && *zUserName){
737 url_add_parameter(&url,"user", zUserName);
738 statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user");
739
--- src/statrep.c
+++ src/statrep.c
@@ -129,11 +129,11 @@
129 assert( statsReportType && "Must call stats_report_init_view() first." );
130 switch( statsReportType ){
131 case 'c':
132 return "checkins";
133 case 'e':
134 return "technotes";
135 case 'w':
136 return "wiki changes";
137 case 't':
138 return "ticket changes";
139 case 'g':
@@ -171,13 +171,13 @@
171 cgi_printf(" <strong>checkins</strong>", zTop);
172 }else{
173 cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop);
174 }
175 if('e' == statsReportType){
176 cgi_printf(" <strong>technotes</strong>", zTop);
177 }else{
178 cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop);
179 }
180 if( 't' == statsReportType ){
181 cgi_printf(" <strong>tickets</strong>", zTop);
182 }else{
183 cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop);
@@ -216,13 +216,13 @@
216 strlen(zTimeframe),
217 zTimeframe);
218 while( SQLITE_ROW == db_step(&stWeek) ){
219 const char *zWeek = db_column_text(&stWeek,0);
220 const int nCount = db_column_int(&stWeek,1);
221 cgi_printf("<a href='%R/timeline?"
222 "yw=%t-%t&n=%d&y=%s'>%s</a>",
223 yearPart, zWeek,
224 nCount, statsReportTimelineYFlag, zWeek);
225 }
226 db_finalize(&stWeek);
227 }
228
@@ -328,13 +328,13 @@
328 nEventTotal += nCount;
329 nEventsPerYear += nCount;
330 @<tr class='row%d(rowClass)'>
331 @ <td>
332 if(includeMonth){
333 cgi_printf("<a href='%R/timeline?"
334 "ym=%t&n=%d&y=%s",
335 zTimeframe, nCount,
336 statsReportTimelineYFlag );
337 /* Reminder: n=nCount is not actually correct for bymonth unless
338 that was the only user who caused events.
339 */
340 if( zUserName && *zUserName ){
@@ -672,12 +672,12 @@
672 ? (int)(100 * nCount / nMaxEvents)
673 : 0;
674 if(!nSize) nSize = 1;
675 total += nCount;
676 cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
677 cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
678 zYear, zWeek, nCount,
679 statsReportTimelineYFlag);
680 if(zUserName && *zUserName){
681 cgi_printf("&u=%t",zUserName);
682 }
683 cgi_printf("'>%s</a></td>",zWeek);
@@ -728,11 +728,11 @@
728 HQuery url; /* URL for various branch links */
729 const char *zView = P("view"); /* Which view/report to show. */
730 const char *zUserName = P("user");
731
732 login_check_credentials();
733 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
734 if(!zUserName) zUserName = P("u");
735 url_initialize(&url, "reports");
736 if(zUserName && *zUserName){
737 url_add_parameter(&url,"user", zUserName);
738 statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user");
739
+15 -10
--- src/style.c
+++ src/style.c
@@ -353,12 +353,11 @@
353353
** Draw the header.
354354
*/
355355
void style_header(const char *zTitleFormat, ...){
356356
va_list ap;
357357
char *zTitle;
358
- const char *zHeader = db_get("header", 0);
359
- if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
358
+ const char *zHeader = skin_get("header");
360359
login_check_credentials();
361360
362361
va_start(ap, zTitleFormat);
363362
zTitle = vmprintf(zTitleFormat, ap);
364363
va_end(ap);
@@ -504,15 +503,15 @@
504503
}
505504
switch( aSubmenuCtrl[i].eType ){
506505
case FF_ENTRY: {
507506
cgi_printf(
508507
"<span class='submenuctrl'>"
509
- "&nbsp;%h<input type='text' name='%s' size='%d' "
508
+ "&nbsp;%h<input type='text' name='%s' size='%d' maxlength='%d'"
510509
"value='%h'%s></span>\n",
511510
aSubmenuCtrl[i].zLabel,
512511
zQPN,
513
- aSubmenuCtrl[i].iSize,
512
+ aSubmenuCtrl[i].iSize, aSubmenuCtrl[i].iSize,
514513
PD(zQPN,""),
515514
zDisabled
516515
);
517516
break;
518517
}
@@ -592,12 +591,11 @@
592591
593592
/* Set the href= field on hyperlinks. Do this before the footer since
594593
** the footer will be generating </html> */
595594
style_resolve_href();
596595
597
- zFooter = db_get("footer", 0);
598
- if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
596
+ zFooter = skin_get("footer");
599597
if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
600598
Th_Render(zFooter);
601599
if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
602600
603601
/* Render trace log if TH1 tracing is enabled. */
@@ -1266,11 +1264,10 @@
12661264
@ padding-left: 1em;
12671265
@ padding-right: 1em;
12681266
},
12691267
{ ".fileage td:nth-child(3)",
12701268
"fileage third column (the check-in comment)",
1271
- @ word-break: break-all;
12721269
@ word-wrap: break-word;
12731270
@ max-width: 50%;
12741271
},
12751272
{ ".brlist table", "The list of branches",
12761273
@ border-spacing: 0;
@@ -1367,11 +1364,11 @@
13671364
void page_style_css(void){
13681365
Blob css;
13691366
int i;
13701367
13711368
cgi_set_content_type("text/css");
1372
- blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1369
+ blob_init(&css,skin_get("css"),-1);
13731370
13741371
/* add special missing definitions */
13751372
for(i=1; cssDefaultList[i].elementClass; i++){
13761373
char *z = blob_str(&css);
13771374
if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1413,11 +1410,11 @@
14131410
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
14141411
};
14151412
14161413
login_check_credentials();
14171414
if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1418
- login_needed();
1415
+ login_needed(0);
14191416
return;
14201417
}
14211418
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
14221419
style_header("Environment Test");
14231420
showAll = atoi(PD("showall","0"));
@@ -1432,17 +1429,25 @@
14321429
@ g.zBaseURL = %h(g.zBaseURL)<br />
14331430
@ g.zHttpsURL = %h(g.zHttpsURL)<br />
14341431
@ g.zTop = %h(g.zTop)<br />
14351432
@ g.zPath = %h(g.zPath)<br />
14361433
for(i=0, c='a'; c<='z'; c++){
1437
- if( login_has_capability(&c, 1) ) zCap[i++] = c;
1434
+ if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
14381435
}
14391436
zCap[i] = 0;
14401437
@ g.userUid = %d(g.userUid)<br />
14411438
@ g.zLogin = %h(g.zLogin)<br />
14421439
@ g.isHuman = %d(g.isHuman)<br />
14431440
@ capabilities = %s(zCap)<br />
1441
+ for(i=0, c='a'; c<='z'; c++){
1442
+ if( login_has_capability(&c, 1, LOGIN_ANON)
1443
+ && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1444
+ }
1445
+ zCap[i] = 0;
1446
+ if( i>0 ){
1447
+ @ anonymous-adds = %s(zCap)<br />
1448
+ }
14441449
@ g.zRepositoryName = %h(g.zRepositoryName)<br />
14451450
@ load_average() = %f(load_average())<br />
14461451
@ <hr>
14471452
P("HTTP_USER_AGENT");
14481453
cgi_print_all(showAll);
14491454
--- src/style.c
+++ src/style.c
@@ -353,12 +353,11 @@
353 ** Draw the header.
354 */
355 void style_header(const char *zTitleFormat, ...){
356 va_list ap;
357 char *zTitle;
358 const char *zHeader = db_get("header", 0);
359 if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
360 login_check_credentials();
361
362 va_start(ap, zTitleFormat);
363 zTitle = vmprintf(zTitleFormat, ap);
364 va_end(ap);
@@ -504,15 +503,15 @@
504 }
505 switch( aSubmenuCtrl[i].eType ){
506 case FF_ENTRY: {
507 cgi_printf(
508 "<span class='submenuctrl'>"
509 "&nbsp;%h<input type='text' name='%s' size='%d' "
510 "value='%h'%s></span>\n",
511 aSubmenuCtrl[i].zLabel,
512 zQPN,
513 aSubmenuCtrl[i].iSize,
514 PD(zQPN,""),
515 zDisabled
516 );
517 break;
518 }
@@ -592,12 +591,11 @@
592
593 /* Set the href= field on hyperlinks. Do this before the footer since
594 ** the footer will be generating </html> */
595 style_resolve_href();
596
597 zFooter = db_get("footer", 0);
598 if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
599 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
600 Th_Render(zFooter);
601 if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
602
603 /* Render trace log if TH1 tracing is enabled. */
@@ -1266,11 +1264,10 @@
1266 @ padding-left: 1em;
1267 @ padding-right: 1em;
1268 },
1269 { ".fileage td:nth-child(3)",
1270 "fileage third column (the check-in comment)",
1271 @ word-break: break-all;
1272 @ word-wrap: break-word;
1273 @ max-width: 50%;
1274 },
1275 { ".brlist table", "The list of branches",
1276 @ border-spacing: 0;
@@ -1367,11 +1364,11 @@
1367 void page_style_css(void){
1368 Blob css;
1369 int i;
1370
1371 cgi_set_content_type("text/css");
1372 blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1373
1374 /* add special missing definitions */
1375 for(i=1; cssDefaultList[i].elementClass; i++){
1376 char *z = blob_str(&css);
1377 if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1413,11 +1410,11 @@
1413 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1414 };
1415
1416 login_check_credentials();
1417 if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1418 login_needed();
1419 return;
1420 }
1421 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1422 style_header("Environment Test");
1423 showAll = atoi(PD("showall","0"));
@@ -1432,17 +1429,25 @@
1432 @ g.zBaseURL = %h(g.zBaseURL)<br />
1433 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1434 @ g.zTop = %h(g.zTop)<br />
1435 @ g.zPath = %h(g.zPath)<br />
1436 for(i=0, c='a'; c<='z'; c++){
1437 if( login_has_capability(&c, 1) ) zCap[i++] = c;
1438 }
1439 zCap[i] = 0;
1440 @ g.userUid = %d(g.userUid)<br />
1441 @ g.zLogin = %h(g.zLogin)<br />
1442 @ g.isHuman = %d(g.isHuman)<br />
1443 @ capabilities = %s(zCap)<br />
 
 
 
 
 
 
 
 
1444 @ g.zRepositoryName = %h(g.zRepositoryName)<br />
1445 @ load_average() = %f(load_average())<br />
1446 @ <hr>
1447 P("HTTP_USER_AGENT");
1448 cgi_print_all(showAll);
1449
--- src/style.c
+++ src/style.c
@@ -353,12 +353,11 @@
353 ** Draw the header.
354 */
355 void style_header(const char *zTitleFormat, ...){
356 va_list ap;
357 char *zTitle;
358 const char *zHeader = skin_get("header");
 
359 login_check_credentials();
360
361 va_start(ap, zTitleFormat);
362 zTitle = vmprintf(zTitleFormat, ap);
363 va_end(ap);
@@ -504,15 +503,15 @@
503 }
504 switch( aSubmenuCtrl[i].eType ){
505 case FF_ENTRY: {
506 cgi_printf(
507 "<span class='submenuctrl'>"
508 "&nbsp;%h<input type='text' name='%s' size='%d' maxlength='%d'"
509 "value='%h'%s></span>\n",
510 aSubmenuCtrl[i].zLabel,
511 zQPN,
512 aSubmenuCtrl[i].iSize, aSubmenuCtrl[i].iSize,
513 PD(zQPN,""),
514 zDisabled
515 );
516 break;
517 }
@@ -592,12 +591,11 @@
591
592 /* Set the href= field on hyperlinks. Do this before the footer since
593 ** the footer will be generating </html> */
594 style_resolve_href();
595
596 zFooter = skin_get("footer");
 
597 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
598 Th_Render(zFooter);
599 if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
600
601 /* Render trace log if TH1 tracing is enabled. */
@@ -1266,11 +1264,10 @@
1264 @ padding-left: 1em;
1265 @ padding-right: 1em;
1266 },
1267 { ".fileage td:nth-child(3)",
1268 "fileage third column (the check-in comment)",
 
1269 @ word-wrap: break-word;
1270 @ max-width: 50%;
1271 },
1272 { ".brlist table", "The list of branches",
1273 @ border-spacing: 0;
@@ -1367,11 +1364,11 @@
1364 void page_style_css(void){
1365 Blob css;
1366 int i;
1367
1368 cgi_set_content_type("text/css");
1369 blob_init(&css,skin_get("css"),-1);
1370
1371 /* add special missing definitions */
1372 for(i=1; cssDefaultList[i].elementClass; i++){
1373 char *z = blob_str(&css);
1374 if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1413,11 +1410,11 @@
1410 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1411 };
1412
1413 login_check_credentials();
1414 if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1415 login_needed(0);
1416 return;
1417 }
1418 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1419 style_header("Environment Test");
1420 showAll = atoi(PD("showall","0"));
@@ -1432,17 +1429,25 @@
1429 @ g.zBaseURL = %h(g.zBaseURL)<br />
1430 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1431 @ g.zTop = %h(g.zTop)<br />
1432 @ g.zPath = %h(g.zPath)<br />
1433 for(i=0, c='a'; c<='z'; c++){
1434 if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1435 }
1436 zCap[i] = 0;
1437 @ g.userUid = %d(g.userUid)<br />
1438 @ g.zLogin = %h(g.zLogin)<br />
1439 @ g.isHuman = %d(g.isHuman)<br />
1440 @ capabilities = %s(zCap)<br />
1441 for(i=0, c='a'; c<='z'; c++){
1442 if( login_has_capability(&c, 1, LOGIN_ANON)
1443 && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1444 }
1445 zCap[i] = 0;
1446 if( i>0 ){
1447 @ anonymous-adds = %s(zCap)<br />
1448 }
1449 @ g.zRepositoryName = %h(g.zRepositoryName)<br />
1450 @ load_average() = %f(load_average())<br />
1451 @ <hr>
1452 P("HTTP_USER_AGENT");
1453 cgi_print_all(showAll);
1454
+2 -2
--- src/tag.c
+++ src/tag.c
@@ -542,11 +542,11 @@
542542
void taglist_page(void){
543543
Stmt q;
544544
545545
login_check_credentials();
546546
if( !g.perm.Read ){
547
- login_needed();
547
+ login_needed(g.anon.Read);
548548
}
549549
login_anonymous_available();
550550
style_header("Tags");
551551
style_adunit_config(ADUNIT_RIGHT_OK);
552552
style_submenu_element("Timeline", "Timeline", "tagtimeline");
@@ -580,11 +580,11 @@
580580
*/
581581
void tagtimeline_page(void){
582582
Stmt q;
583583
584584
login_check_credentials();
585
- if( !g.perm.Read ){ login_needed(); return; }
585
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
586586
587587
style_header("Tagged Check-ins");
588588
style_submenu_element("List", "List", "taglist");
589589
login_anonymous_available();
590590
@ <h2>Check-ins with non-propagating tags:</h2>
591591
--- src/tag.c
+++ src/tag.c
@@ -542,11 +542,11 @@
542 void taglist_page(void){
543 Stmt q;
544
545 login_check_credentials();
546 if( !g.perm.Read ){
547 login_needed();
548 }
549 login_anonymous_available();
550 style_header("Tags");
551 style_adunit_config(ADUNIT_RIGHT_OK);
552 style_submenu_element("Timeline", "Timeline", "tagtimeline");
@@ -580,11 +580,11 @@
580 */
581 void tagtimeline_page(void){
582 Stmt q;
583
584 login_check_credentials();
585 if( !g.perm.Read ){ login_needed(); return; }
586
587 style_header("Tagged Check-ins");
588 style_submenu_element("List", "List", "taglist");
589 login_anonymous_available();
590 @ <h2>Check-ins with non-propagating tags:</h2>
591
--- src/tag.c
+++ src/tag.c
@@ -542,11 +542,11 @@
542 void taglist_page(void){
543 Stmt q;
544
545 login_check_credentials();
546 if( !g.perm.Read ){
547 login_needed(g.anon.Read);
548 }
549 login_anonymous_available();
550 style_header("Tags");
551 style_adunit_config(ADUNIT_RIGHT_OK);
552 style_submenu_element("Timeline", "Timeline", "tagtimeline");
@@ -580,11 +580,11 @@
580 */
581 void tagtimeline_page(void){
582 Stmt q;
583
584 login_check_credentials();
585 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
586
587 style_header("Tagged Check-ins");
588 style_submenu_element("List", "List", "taglist");
589 login_anonymous_available();
590 @ <h2>Check-ins with non-propagating tags:</h2>
591
+12 -1
--- src/tar.c
+++ src/tar.c
@@ -603,11 +603,11 @@
603603
char *zName, *zRid, *zKey;
604604
int nName, nRid;
605605
Blob tarball;
606606
607607
login_check_credentials();
608
- if( !g.perm.Zip ){ login_needed(); return; }
608
+ if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
609609
load_control();
610610
zName = mprintf("%s", PD("name",""));
611611
nName = strlen(zName);
612612
zRid = mprintf("%s", PD("uuid","trunk"));
613613
nRid = strlen(zRid);
@@ -638,10 +638,21 @@
638638
@ zName = "%h(zName)"<br>
639639
@ rid = %d(rid)<br>
640640
@ zKey = "%h(zKey)"
641641
style_footer();
642642
return;
643
+ }
644
+ if( referred_from_login() ){
645
+ style_header("Tarball Download");
646
+ @ <form action='%R/tarball'>
647
+ cgi_query_parameters_to_hidden();
648
+ @ <p>Tarball named <b>%h(zName).tar.gz</b> holding the content
649
+ @ of check-in <b>%h(zRid)</b>:
650
+ @ <input type="submit" value="Download" />
651
+ @ </form>
652
+ style_footer();
653
+ return;
643654
}
644655
blob_zero(&tarball);
645656
if( cache_read(&tarball, zKey)==0 ){
646657
tarball_of_checkin(rid, &tarball, zName);
647658
cache_write(&tarball, zKey);
648659
--- src/tar.c
+++ src/tar.c
@@ -603,11 +603,11 @@
603 char *zName, *zRid, *zKey;
604 int nName, nRid;
605 Blob tarball;
606
607 login_check_credentials();
608 if( !g.perm.Zip ){ login_needed(); return; }
609 load_control();
610 zName = mprintf("%s", PD("name",""));
611 nName = strlen(zName);
612 zRid = mprintf("%s", PD("uuid","trunk"));
613 nRid = strlen(zRid);
@@ -638,10 +638,21 @@
638 @ zName = "%h(zName)"<br>
639 @ rid = %d(rid)<br>
640 @ zKey = "%h(zKey)"
641 style_footer();
642 return;
 
 
 
 
 
 
 
 
 
 
 
643 }
644 blob_zero(&tarball);
645 if( cache_read(&tarball, zKey)==0 ){
646 tarball_of_checkin(rid, &tarball, zName);
647 cache_write(&tarball, zKey);
648
--- src/tar.c
+++ src/tar.c
@@ -603,11 +603,11 @@
603 char *zName, *zRid, *zKey;
604 int nName, nRid;
605 Blob tarball;
606
607 login_check_credentials();
608 if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
609 load_control();
610 zName = mprintf("%s", PD("name",""));
611 nName = strlen(zName);
612 zRid = mprintf("%s", PD("uuid","trunk"));
613 nRid = strlen(zRid);
@@ -638,10 +638,21 @@
638 @ zName = "%h(zName)"<br>
639 @ rid = %d(rid)<br>
640 @ zKey = "%h(zKey)"
641 style_footer();
642 return;
643 }
644 if( referred_from_login() ){
645 style_header("Tarball Download");
646 @ <form action='%R/tarball'>
647 cgi_query_parameters_to_hidden();
648 @ <p>Tarball named <b>%h(zName).tar.gz</b> holding the content
649 @ of check-in <b>%h(zRid)</b>:
650 @ <input type="submit" value="Download" />
651 @ </form>
652 style_footer();
653 return;
654 }
655 blob_zero(&tarball);
656 if( cache_read(&tarball, zKey)==0 ){
657 tarball_of_checkin(rid, &tarball, zName);
658 cache_write(&tarball, zKey);
659
+120 -17
--- src/th_main.c
+++ src/th_main.c
@@ -246,10 +246,81 @@
246246
sendText("ERROR: ", -1, 0);
247247
sendText((char*)z, n, 1);
248248
sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
249249
enableOutput = savedEnable;
250250
}
251
+
252
+/*
253
+** Convert name to an rid. This function was copied from name_to_typed_rid()
254
+** in name.c; however, it has been modified to report TH1 script errors instead
255
+** of "fatal errors".
256
+*/
257
+int th1_name_to_typed_rid(
258
+ Th_Interp *interp,
259
+ const char *zName,
260
+ const char *zType
261
+){
262
+ int rid;
263
+
264
+ if( zName==0 || zName[0]==0 ) return 0;
265
+ rid = symbolic_name_to_rid(zName, zType);
266
+ if( rid<0 ){
267
+ Th_SetResult(interp, "ambiguous name", -1);
268
+ }else if( rid==0 ){
269
+ Th_SetResult(interp, "name not found", -1);
270
+ }
271
+ return rid;
272
+}
273
+
274
+/*
275
+** Attempt to lookup the specified checkin and file name into an rid.
276
+** This function was copied from artifact_from_ci_and_filename() in
277
+** info.c; however, it has been modified to report TH1 script errors
278
+** instead of "fatal errors".
279
+*/
280
+int th1_artifact_from_ci_and_filename(
281
+ Th_Interp *interp,
282
+ const char *zCI,
283
+ const char *zFilename
284
+){
285
+ int cirid;
286
+ Blob err;
287
+ Manifest *pManifest;
288
+ ManifestFile *pFile;
289
+
290
+ if( zCI==0 ){
291
+ Th_SetResult(interp, "invalid check-in", -1);
292
+ return 0;
293
+ }
294
+ if( zFilename==0 ){
295
+ Th_SetResult(interp, "invalid file name", -1);
296
+ return 0;
297
+ }
298
+ cirid = th1_name_to_typed_rid(interp, zCI, "*");
299
+ blob_zero(&err);
300
+ pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err);
301
+ if( pManifest==0 ){
302
+ if( blob_size(&err)>0 ){
303
+ Th_SetResult(interp, blob_str(&err), blob_size(&err));
304
+ }else{
305
+ Th_SetResult(interp, "manifest not found", -1);
306
+ }
307
+ blob_reset(&err);
308
+ return 0;
309
+ }
310
+ blob_reset(&err);
311
+ manifest_file_rewind(pManifest);
312
+ while( (pFile = manifest_file_next(pManifest,0))!=0 ){
313
+ if( fossil_strcmp(zFilename, pFile->zName)==0 ){
314
+ int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
315
+ manifest_destroy(pManifest);
316
+ return rid;
317
+ }
318
+ }
319
+ Th_SetResult(interp, "file name not found in manifest", -1);
320
+ return 0;
321
+}
251322
252323
/*
253324
** TH1 command: puts STRING
254325
** TH1 command: html STRING
255326
**
@@ -342,12 +413,14 @@
342413
return TH_OK;
343414
}
344415
345416
/*
346417
** TH1 command: hascap STRING...
418
+** TH1 command: anoncap STRING...
347419
**
348
-** Return true if the user has all of the capabilities listed in STRING.
420
+** Return true if the current user (hascap) or if the anonymous user
421
+** (anoncap) has all of the capabilities listed in STRING.
349422
*/
350423
static int hascapCmd(
351424
Th_Interp *interp,
352425
void *p,
353426
int argc,
@@ -357,11 +430,11 @@
357430
int rc = 0, i;
358431
if( argc<2 ){
359432
return Th_WrongNumArgs(interp, "hascap STRING ...");
360433
}
361434
for(i=1; i<argc && rc==0; i++){
362
- rc = login_has_capability((char*)argv[i],argl[i]);
435
+ rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
363436
}
364437
if( g.thTrace ){
365438
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
366439
}
367440
Th_SetResultInt(interp, rc);
@@ -543,11 +616,12 @@
543616
544617
545618
/*
546619
** TH1 command: anycap STRING
547620
**
548
-** Return true if the user has any one of the capabilities listed in STRING.
621
+** Return true if the current user user
622
+** has any one of the capabilities listed in STRING.
549623
*/
550624
static int anycapCmd(
551625
Th_Interp *interp,
552626
void *p,
553627
int argc,
@@ -558,11 +632,11 @@
558632
int i;
559633
if( argc!=2 ){
560634
return Th_WrongNumArgs(interp, "anycap STRING");
561635
}
562636
for(i=0; rc==0 && i<argl[1]; i++){
563
- rc = login_has_capability((char*)&argv[1][i],1);
637
+ rc = login_has_capability((char*)&argv[1][i],1,0);
564638
}
565639
if( g.thTrace ){
566640
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
567641
}
568642
Th_SetResultInt(interp, rc);
@@ -978,20 +1052,19 @@
9781052
}
9791053
if( Th_IsRepositoryOpen() ){
9801054
int rid;
9811055
Blob content;
9821056
if( argc==3 ){
983
- rid = artifact_from_ci_and_filename(argv[1], argv[2]);
1057
+ rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]);
9841058
}else{
985
- rid = name_to_rid(argv[1]);
1059
+ rid = th1_name_to_typed_rid(interp, argv[1], "*");
9861060
}
9871061
if( rid!=0 && content_get(rid, &content) ){
9881062
Th_SetResult(interp, blob_str(&content), blob_size(&content));
9891063
blob_reset(&content);
9901064
return TH_OK;
9911065
}else{
992
- Th_SetResult(interp, "artifact not found", -1);
9931066
return TH_ERROR;
9941067
}
9951068
}else{
9961069
Th_SetResult(interp, "repository unavailable", -1);
9971070
return TH_ERROR;
@@ -1450,15 +1523,18 @@
14501523
int needConfig = flags & TH_INIT_NEED_CONFIG;
14511524
int forceReset = flags & TH_INIT_FORCE_RESET;
14521525
int forceTcl = flags & TH_INIT_FORCE_TCL;
14531526
int forceSetup = flags & TH_INIT_FORCE_SETUP;
14541527
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
1528
+ static int anonFlag = LOGIN_ANON;
1529
+ static int zeroInt = 0;
14551530
static struct _Command {
14561531
const char *zName;
14571532
Th_CommandProc xProc;
14581533
void *pContext;
14591534
} aCommand[] = {
1535
+ {"anoncap", hascapCmd, (void*)&anonFlag},
14601536
{"anycap", anycapCmd, 0},
14611537
{"artifact", artifactCmd, 0},
14621538
{"checkout", checkoutCmd, 0},
14631539
{"combobox", comboboxCmd, 0},
14641540
{"date", dateCmd, 0},
@@ -1465,11 +1541,11 @@
14651541
{"decorate", wikiCmd, (void*)&aFlags[2]},
14661542
{"enable_output", enableOutputCmd, 0},
14671543
{"getParameter", getParameterCmd, 0},
14681544
{"globalState", globalStateCmd, 0},
14691545
{"httpize", httpizeCmd, 0},
1470
- {"hascap", hascapCmd, 0},
1546
+ {"hascap", hascapCmd, (void*)&zeroInt},
14711547
{"hasfeature", hasfeatureCmd, 0},
14721548
{"html", putsCmd, (void*)&aFlags[0]},
14731549
{"htmlize", htmlizeCmd, 0},
14741550
{"http", httpCmd, 0},
14751551
{"linecount", linecntCmd, 0},
@@ -1952,21 +2028,35 @@
19522028
return rc;
19532029
}
19542030
19552031
/*
19562032
** COMMAND: test-th-render
2033
+**
2034
+** Usage: %fossil test-th-render FILE
2035
+**
2036
+** Read the content of the file named "FILE" as if it were a header or
2037
+** footer or ticket rendering script, evaluate it, and show the results
2038
+** on standard output.
2039
+**
2040
+** Options:
2041
+**
2042
+** --cgi Include a CGI response header in the output
2043
+** --http Include an HTTP response header in the output
2044
+** --open-config Open the configuration database
19572045
*/
19582046
void test_th_render(void){
1959
- int forceCgi, fullHttpReply;
2047
+ int forceCgi = 0, fullHttpReply = 0;
19602048
Blob in;
19612049
Th_InitTraceLog();
1962
- forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1963
- fullHttpReply = find_option("th-full-http", 0, 0)!=0;
2050
+ forceCgi = find_option("cgi", 0, 0)!=0;
2051
+ fullHttpReply = find_option("http", 0, 0)!=0;
2052
+ if( fullHttpReply ) forceCgi = 1;
19642053
if( forceCgi ) Th_ForceCgi(fullHttpReply);
1965
- if( find_option("th-open-config", 0, 0)!=0 ){
2054
+ if( find_option("open-config", 0, 0)!=0 ){
19662055
Th_OpenConfig(1);
19672056
}
2057
+ verify_all_options();
19682058
if( g.argc<3 ){
19692059
usage("FILE");
19702060
}
19712061
blob_zero(&in);
19722062
blob_read_from_file(&in, g.argv[2]);
@@ -1975,20 +2065,32 @@
19752065
if( forceCgi ) cgi_reply();
19762066
}
19772067
19782068
/*
19792069
** COMMAND: test-th-eval
2070
+**
2071
+** Usage: %fossil test-th-eval SCRIPT
2072
+**
2073
+** Evaluate SCRIPT as if it were a header or footer or ticket rendering
2074
+** script, evaluate it, and show the results on standard output.
2075
+**
2076
+** Options:
2077
+**
2078
+** --cgi Include a CGI response header in the output
2079
+** --http Include an HTTP response header in the output
2080
+** --open-config Open the configuration database
19802081
*/
19812082
void test_th_eval(void){
19822083
int rc;
19832084
const char *zRc;
19842085
int forceCgi, fullHttpReply;
19852086
Th_InitTraceLog();
1986
- forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1987
- fullHttpReply = find_option("th-full-http", 0, 0)!=0;
2087
+ forceCgi = find_option("cgi", 0, 0)!=0;
2088
+ fullHttpReply = find_option("http", 0, 0)!=0;
2089
+ if( fullHttpReply ) forceCgi = 1;
19882090
if( forceCgi ) Th_ForceCgi(fullHttpReply);
1989
- if( find_option("th-open-config", 0, 0)!=0 ){
2091
+ if( find_option("open-config", 0, 0)!=0 ){
19902092
Th_OpenConfig(1);
19912093
}
19922094
if( g.argc!=3 ){
19932095
usage("script");
19942096
}
@@ -2008,12 +2110,13 @@
20082110
int rc = TH_OK;
20092111
int nResult = 0;
20102112
char *zResult;
20112113
int forceCgi, fullHttpReply;
20122114
Th_InitTraceLog();
2013
- forceCgi = find_option("th-force-cgi", 0, 0)!=0;
2014
- fullHttpReply = find_option("th-full-http", 0, 0)!=0;
2115
+ forceCgi = find_option("cgi", 0, 0)!=0;
2116
+ fullHttpReply = find_option("http", 0, 0)!=0;
2117
+ if( fullHttpReply ) forceCgi = 1;
20152118
if( forceCgi ) Th_ForceCgi(fullHttpReply);
20162119
if( g.argc<5 ){
20172120
usage("TYPE NAME FLAGS");
20182121
}
20192122
if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){
20202123
--- src/th_main.c
+++ src/th_main.c
@@ -246,10 +246,81 @@
246 sendText("ERROR: ", -1, 0);
247 sendText((char*)z, n, 1);
248 sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
249 enableOutput = savedEnable;
250 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
252 /*
253 ** TH1 command: puts STRING
254 ** TH1 command: html STRING
255 **
@@ -342,12 +413,14 @@
342 return TH_OK;
343 }
344
345 /*
346 ** TH1 command: hascap STRING...
 
347 **
348 ** Return true if the user has all of the capabilities listed in STRING.
 
349 */
350 static int hascapCmd(
351 Th_Interp *interp,
352 void *p,
353 int argc,
@@ -357,11 +430,11 @@
357 int rc = 0, i;
358 if( argc<2 ){
359 return Th_WrongNumArgs(interp, "hascap STRING ...");
360 }
361 for(i=1; i<argc && rc==0; i++){
362 rc = login_has_capability((char*)argv[i],argl[i]);
363 }
364 if( g.thTrace ){
365 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
366 }
367 Th_SetResultInt(interp, rc);
@@ -543,11 +616,12 @@
543
544
545 /*
546 ** TH1 command: anycap STRING
547 **
548 ** Return true if the user has any one of the capabilities listed in STRING.
 
549 */
550 static int anycapCmd(
551 Th_Interp *interp,
552 void *p,
553 int argc,
@@ -558,11 +632,11 @@
558 int i;
559 if( argc!=2 ){
560 return Th_WrongNumArgs(interp, "anycap STRING");
561 }
562 for(i=0; rc==0 && i<argl[1]; i++){
563 rc = login_has_capability((char*)&argv[1][i],1);
564 }
565 if( g.thTrace ){
566 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
567 }
568 Th_SetResultInt(interp, rc);
@@ -978,20 +1052,19 @@
978 }
979 if( Th_IsRepositoryOpen() ){
980 int rid;
981 Blob content;
982 if( argc==3 ){
983 rid = artifact_from_ci_and_filename(argv[1], argv[2]);
984 }else{
985 rid = name_to_rid(argv[1]);
986 }
987 if( rid!=0 && content_get(rid, &content) ){
988 Th_SetResult(interp, blob_str(&content), blob_size(&content));
989 blob_reset(&content);
990 return TH_OK;
991 }else{
992 Th_SetResult(interp, "artifact not found", -1);
993 return TH_ERROR;
994 }
995 }else{
996 Th_SetResult(interp, "repository unavailable", -1);
997 return TH_ERROR;
@@ -1450,15 +1523,18 @@
1450 int needConfig = flags & TH_INIT_NEED_CONFIG;
1451 int forceReset = flags & TH_INIT_FORCE_RESET;
1452 int forceTcl = flags & TH_INIT_FORCE_TCL;
1453 int forceSetup = flags & TH_INIT_FORCE_SETUP;
1454 static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
 
 
1455 static struct _Command {
1456 const char *zName;
1457 Th_CommandProc xProc;
1458 void *pContext;
1459 } aCommand[] = {
 
1460 {"anycap", anycapCmd, 0},
1461 {"artifact", artifactCmd, 0},
1462 {"checkout", checkoutCmd, 0},
1463 {"combobox", comboboxCmd, 0},
1464 {"date", dateCmd, 0},
@@ -1465,11 +1541,11 @@
1465 {"decorate", wikiCmd, (void*)&aFlags[2]},
1466 {"enable_output", enableOutputCmd, 0},
1467 {"getParameter", getParameterCmd, 0},
1468 {"globalState", globalStateCmd, 0},
1469 {"httpize", httpizeCmd, 0},
1470 {"hascap", hascapCmd, 0},
1471 {"hasfeature", hasfeatureCmd, 0},
1472 {"html", putsCmd, (void*)&aFlags[0]},
1473 {"htmlize", htmlizeCmd, 0},
1474 {"http", httpCmd, 0},
1475 {"linecount", linecntCmd, 0},
@@ -1952,21 +2028,35 @@
1952 return rc;
1953 }
1954
1955 /*
1956 ** COMMAND: test-th-render
 
 
 
 
 
 
 
 
 
 
 
 
1957 */
1958 void test_th_render(void){
1959 int forceCgi, fullHttpReply;
1960 Blob in;
1961 Th_InitTraceLog();
1962 forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1963 fullHttpReply = find_option("th-full-http", 0, 0)!=0;
 
1964 if( forceCgi ) Th_ForceCgi(fullHttpReply);
1965 if( find_option("th-open-config", 0, 0)!=0 ){
1966 Th_OpenConfig(1);
1967 }
 
1968 if( g.argc<3 ){
1969 usage("FILE");
1970 }
1971 blob_zero(&in);
1972 blob_read_from_file(&in, g.argv[2]);
@@ -1975,20 +2065,32 @@
1975 if( forceCgi ) cgi_reply();
1976 }
1977
1978 /*
1979 ** COMMAND: test-th-eval
 
 
 
 
 
 
 
 
 
 
 
1980 */
1981 void test_th_eval(void){
1982 int rc;
1983 const char *zRc;
1984 int forceCgi, fullHttpReply;
1985 Th_InitTraceLog();
1986 forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1987 fullHttpReply = find_option("th-full-http", 0, 0)!=0;
 
1988 if( forceCgi ) Th_ForceCgi(fullHttpReply);
1989 if( find_option("th-open-config", 0, 0)!=0 ){
1990 Th_OpenConfig(1);
1991 }
1992 if( g.argc!=3 ){
1993 usage("script");
1994 }
@@ -2008,12 +2110,13 @@
2008 int rc = TH_OK;
2009 int nResult = 0;
2010 char *zResult;
2011 int forceCgi, fullHttpReply;
2012 Th_InitTraceLog();
2013 forceCgi = find_option("th-force-cgi", 0, 0)!=0;
2014 fullHttpReply = find_option("th-full-http", 0, 0)!=0;
 
2015 if( forceCgi ) Th_ForceCgi(fullHttpReply);
2016 if( g.argc<5 ){
2017 usage("TYPE NAME FLAGS");
2018 }
2019 if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){
2020
--- src/th_main.c
+++ src/th_main.c
@@ -246,10 +246,81 @@
246 sendText("ERROR: ", -1, 0);
247 sendText((char*)z, n, 1);
248 sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
249 enableOutput = savedEnable;
250 }
251
252 /*
253 ** Convert name to an rid. This function was copied from name_to_typed_rid()
254 ** in name.c; however, it has been modified to report TH1 script errors instead
255 ** of "fatal errors".
256 */
257 int th1_name_to_typed_rid(
258 Th_Interp *interp,
259 const char *zName,
260 const char *zType
261 ){
262 int rid;
263
264 if( zName==0 || zName[0]==0 ) return 0;
265 rid = symbolic_name_to_rid(zName, zType);
266 if( rid<0 ){
267 Th_SetResult(interp, "ambiguous name", -1);
268 }else if( rid==0 ){
269 Th_SetResult(interp, "name not found", -1);
270 }
271 return rid;
272 }
273
274 /*
275 ** Attempt to lookup the specified checkin and file name into an rid.
276 ** This function was copied from artifact_from_ci_and_filename() in
277 ** info.c; however, it has been modified to report TH1 script errors
278 ** instead of "fatal errors".
279 */
280 int th1_artifact_from_ci_and_filename(
281 Th_Interp *interp,
282 const char *zCI,
283 const char *zFilename
284 ){
285 int cirid;
286 Blob err;
287 Manifest *pManifest;
288 ManifestFile *pFile;
289
290 if( zCI==0 ){
291 Th_SetResult(interp, "invalid check-in", -1);
292 return 0;
293 }
294 if( zFilename==0 ){
295 Th_SetResult(interp, "invalid file name", -1);
296 return 0;
297 }
298 cirid = th1_name_to_typed_rid(interp, zCI, "*");
299 blob_zero(&err);
300 pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err);
301 if( pManifest==0 ){
302 if( blob_size(&err)>0 ){
303 Th_SetResult(interp, blob_str(&err), blob_size(&err));
304 }else{
305 Th_SetResult(interp, "manifest not found", -1);
306 }
307 blob_reset(&err);
308 return 0;
309 }
310 blob_reset(&err);
311 manifest_file_rewind(pManifest);
312 while( (pFile = manifest_file_next(pManifest,0))!=0 ){
313 if( fossil_strcmp(zFilename, pFile->zName)==0 ){
314 int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
315 manifest_destroy(pManifest);
316 return rid;
317 }
318 }
319 Th_SetResult(interp, "file name not found in manifest", -1);
320 return 0;
321 }
322
323 /*
324 ** TH1 command: puts STRING
325 ** TH1 command: html STRING
326 **
@@ -342,12 +413,14 @@
413 return TH_OK;
414 }
415
416 /*
417 ** TH1 command: hascap STRING...
418 ** TH1 command: anoncap STRING...
419 **
420 ** Return true if the current user (hascap) or if the anonymous user
421 ** (anoncap) has all of the capabilities listed in STRING.
422 */
423 static int hascapCmd(
424 Th_Interp *interp,
425 void *p,
426 int argc,
@@ -357,11 +430,11 @@
430 int rc = 0, i;
431 if( argc<2 ){
432 return Th_WrongNumArgs(interp, "hascap STRING ...");
433 }
434 for(i=1; i<argc && rc==0; i++){
435 rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
436 }
437 if( g.thTrace ){
438 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
439 }
440 Th_SetResultInt(interp, rc);
@@ -543,11 +616,12 @@
616
617
618 /*
619 ** TH1 command: anycap STRING
620 **
621 ** Return true if the current user user
622 ** has any one of the capabilities listed in STRING.
623 */
624 static int anycapCmd(
625 Th_Interp *interp,
626 void *p,
627 int argc,
@@ -558,11 +632,11 @@
632 int i;
633 if( argc!=2 ){
634 return Th_WrongNumArgs(interp, "anycap STRING");
635 }
636 for(i=0; rc==0 && i<argl[1]; i++){
637 rc = login_has_capability((char*)&argv[1][i],1,0);
638 }
639 if( g.thTrace ){
640 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
641 }
642 Th_SetResultInt(interp, rc);
@@ -978,20 +1052,19 @@
1052 }
1053 if( Th_IsRepositoryOpen() ){
1054 int rid;
1055 Blob content;
1056 if( argc==3 ){
1057 rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]);
1058 }else{
1059 rid = th1_name_to_typed_rid(interp, argv[1], "*");
1060 }
1061 if( rid!=0 && content_get(rid, &content) ){
1062 Th_SetResult(interp, blob_str(&content), blob_size(&content));
1063 blob_reset(&content);
1064 return TH_OK;
1065 }else{
 
1066 return TH_ERROR;
1067 }
1068 }else{
1069 Th_SetResult(interp, "repository unavailable", -1);
1070 return TH_ERROR;
@@ -1450,15 +1523,18 @@
1523 int needConfig = flags & TH_INIT_NEED_CONFIG;
1524 int forceReset = flags & TH_INIT_FORCE_RESET;
1525 int forceTcl = flags & TH_INIT_FORCE_TCL;
1526 int forceSetup = flags & TH_INIT_FORCE_SETUP;
1527 static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
1528 static int anonFlag = LOGIN_ANON;
1529 static int zeroInt = 0;
1530 static struct _Command {
1531 const char *zName;
1532 Th_CommandProc xProc;
1533 void *pContext;
1534 } aCommand[] = {
1535 {"anoncap", hascapCmd, (void*)&anonFlag},
1536 {"anycap", anycapCmd, 0},
1537 {"artifact", artifactCmd, 0},
1538 {"checkout", checkoutCmd, 0},
1539 {"combobox", comboboxCmd, 0},
1540 {"date", dateCmd, 0},
@@ -1465,11 +1541,11 @@
1541 {"decorate", wikiCmd, (void*)&aFlags[2]},
1542 {"enable_output", enableOutputCmd, 0},
1543 {"getParameter", getParameterCmd, 0},
1544 {"globalState", globalStateCmd, 0},
1545 {"httpize", httpizeCmd, 0},
1546 {"hascap", hascapCmd, (void*)&zeroInt},
1547 {"hasfeature", hasfeatureCmd, 0},
1548 {"html", putsCmd, (void*)&aFlags[0]},
1549 {"htmlize", htmlizeCmd, 0},
1550 {"http", httpCmd, 0},
1551 {"linecount", linecntCmd, 0},
@@ -1952,21 +2028,35 @@
2028 return rc;
2029 }
2030
2031 /*
2032 ** COMMAND: test-th-render
2033 **
2034 ** Usage: %fossil test-th-render FILE
2035 **
2036 ** Read the content of the file named "FILE" as if it were a header or
2037 ** footer or ticket rendering script, evaluate it, and show the results
2038 ** on standard output.
2039 **
2040 ** Options:
2041 **
2042 ** --cgi Include a CGI response header in the output
2043 ** --http Include an HTTP response header in the output
2044 ** --open-config Open the configuration database
2045 */
2046 void test_th_render(void){
2047 int forceCgi = 0, fullHttpReply = 0;
2048 Blob in;
2049 Th_InitTraceLog();
2050 forceCgi = find_option("cgi", 0, 0)!=0;
2051 fullHttpReply = find_option("http", 0, 0)!=0;
2052 if( fullHttpReply ) forceCgi = 1;
2053 if( forceCgi ) Th_ForceCgi(fullHttpReply);
2054 if( find_option("open-config", 0, 0)!=0 ){
2055 Th_OpenConfig(1);
2056 }
2057 verify_all_options();
2058 if( g.argc<3 ){
2059 usage("FILE");
2060 }
2061 blob_zero(&in);
2062 blob_read_from_file(&in, g.argv[2]);
@@ -1975,20 +2065,32 @@
2065 if( forceCgi ) cgi_reply();
2066 }
2067
2068 /*
2069 ** COMMAND: test-th-eval
2070 **
2071 ** Usage: %fossil test-th-eval SCRIPT
2072 **
2073 ** Evaluate SCRIPT as if it were a header or footer or ticket rendering
2074 ** script, evaluate it, and show the results on standard output.
2075 **
2076 ** Options:
2077 **
2078 ** --cgi Include a CGI response header in the output
2079 ** --http Include an HTTP response header in the output
2080 ** --open-config Open the configuration database
2081 */
2082 void test_th_eval(void){
2083 int rc;
2084 const char *zRc;
2085 int forceCgi, fullHttpReply;
2086 Th_InitTraceLog();
2087 forceCgi = find_option("cgi", 0, 0)!=0;
2088 fullHttpReply = find_option("http", 0, 0)!=0;
2089 if( fullHttpReply ) forceCgi = 1;
2090 if( forceCgi ) Th_ForceCgi(fullHttpReply);
2091 if( find_option("open-config", 0, 0)!=0 ){
2092 Th_OpenConfig(1);
2093 }
2094 if( g.argc!=3 ){
2095 usage("script");
2096 }
@@ -2008,12 +2110,13 @@
2110 int rc = TH_OK;
2111 int nResult = 0;
2112 char *zResult;
2113 int forceCgi, fullHttpReply;
2114 Th_InitTraceLog();
2115 forceCgi = find_option("cgi", 0, 0)!=0;
2116 fullHttpReply = find_option("http", 0, 0)!=0;
2117 if( fullHttpReply ) forceCgi = 1;
2118 if( forceCgi ) Th_ForceCgi(fullHttpReply);
2119 if( g.argc<5 ){
2120 usage("TYPE NAME FLAGS");
2121 }
2122 if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){
2123
+74 -50
--- src/timeline.c
+++ src/timeline.c
@@ -41,11 +41,11 @@
4141
/*
4242
** Generate a hyperlink to a version.
4343
*/
4444
void hyperlink_to_uuid(const char *zUuid){
4545
if( g.perm.Hyperlink ){
46
- @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%S(zUuid)]</a>
46
+ @ %z(xhref("class='timelineHistLink'","%R/info/%!S",zUuid))[%S(zUuid)]</a>
4747
}else{
4848
@ <span class="timelineHistDsp">[%S(zUuid)]</span>
4949
}
5050
}
5151
@@ -163,11 +163,11 @@
163163
void test_hash_color_page(void){
164164
const char *zBr;
165165
char zNm[10];
166166
int i, cnt;
167167
login_check_credentials();
168
- if( !g.perm.Read ){ login_needed(); return; }
168
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
169169
170170
style_header("Hash Color Test");
171171
for(i=cnt=0; i<10; i++){
172172
sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
173173
zBr = P(zNm);
@@ -453,11 +453,11 @@
453453
@ (user: %h(zDispUser)%s(zTagList?",":"\051")
454454
}
455455
456456
/* Generate a "detail" link for tags. */
457457
if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
458
- @ [%z(href("%R/info/%s",zUuid))details</a>]
458
+ @ [%z(href("%R/info/%!S",zUuid))details</a>]
459459
}
460460
461461
/* Generate the "tags: TAGLIST" at the end of the comment, together
462462
** with hyperlinks to the tag list.
463463
*/
@@ -522,11 +522,12 @@
522522
int fid = db_column_int(&fchngQuery, 1);
523523
int isDel = fid==0;
524524
const char *zOldName = db_column_text(&fchngQuery, 5);
525525
const char *zOld = db_column_text(&fchngQuery, 4);
526526
const char *zNew = db_column_text(&fchngQuery, 3);
527
- const char *zUnpubTag = "";
527
+ const char *zUnpub = "";
528
+ char *zA;
528529
char zId[20];
529530
if( !inUl ){
530531
@ <ul class="filelist">
531532
inUl = 1;
532533
}
@@ -539,29 +540,31 @@
539540
if( !isNew && !isDel && zOldName!=0 ){
540541
@ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId)
541542
}
542543
continue;
543544
}
545
+ zA = href("%R/artifact/%!S",fid?zNew:zOld);
544546
if( content_is_private(fid) ){
545
- zUnpubTag = UNPUB_TAG;
547
+ zUnpub = UNPUB_TAG;
546548
}
547549
if( isNew ){
548
- @ <li> %h(zFilename)%s(zId) %s(zUnpubTag) (new file) &nbsp;
549
- @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
550
+ @ <li> %s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) (new file) &nbsp;
551
+ @ %z(href("%R/artifact/%!S",zNew))[view]</a></li>
550552
}else if( isDel ){
551
- @ <li> %h(zFilename) (deleted)</li>
553
+ @ <li> %s(zA)%h(zFilename)</a> (deleted)</li>
552554
}else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
553
- @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId) %s(zUnpubTag)
554
- @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
555
+ @ <li> %h(zOldName) &rarr; %s(zA)%h(zFilename)</a>%s(zId)
556
+ @ %s(zUnpub) %z(href("%R/artifact/%!S",zNew))[view]</a></li>
555557
}else{
556558
if( zOldName!=0 ){
557
- @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId) %s(zUnpubTag)
559
+ @ <li>%h(zOldName) &rarr; %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub)
558560
}else{
559
- @ <li> %h(zFilename)%s(zId) &nbsp; %s(zUnpubTag)
561
+ @ <li>%s(zA)%h(zFilename)</a>%s(zId) &nbsp; %s(zUnpub)
560562
}
561
- @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li>
563
+ @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zOld,zNew))[diff]</a></li>
562564
}
565
+ fossil_free(zA);
563566
}
564567
db_reset(&fchngQuery);
565568
if( inUl ){
566569
@ </ul>
567570
}
@@ -1033,11 +1036,11 @@
10331036
/*
10341037
** Add the select/option box to the timeline submenu that is used to
10351038
** set the y= parameter that determines which elements to display
10361039
** on the timeline.
10371040
*/
1038
-static void timeline_y_submenu(void){
1041
+static void timeline_y_submenu(int isDisabled){
10391042
static int i = 0;
10401043
static const char *az[12];
10411044
if( i==0 ){
10421045
az[0] = "all";
10431046
az[1] = "Any Type";
@@ -1061,11 +1064,11 @@
10611064
az[i++] = "Wiki";
10621065
}
10631066
assert( i<=ArraySize(az) );
10641067
}
10651068
if( i>2 ){
1066
- style_submenu_multichoice("y", i/2, az, 0);
1069
+ style_submenu_multichoice("y", i/2, az, isDisabled);
10671070
}
10681071
}
10691072
10701073
/*
10711074
** WEBPAGE: timeline
@@ -1073,10 +1076,11 @@
10731076
** Query parameters:
10741077
**
10751078
** a=TIMEORTAG after this event
10761079
** b=TIMEORTAG before this event
10771080
** c=TIMEORTAG "circa" this event
1081
+** m=TIMEORTAG mark this event
10781082
** n=COUNT max number of events in output
10791083
** p=UUID artifact and up to COUNT parents and ancestors
10801084
** d=UUID artifact and up to COUNT descendants
10811085
** dp=UUID The same as d=UUID&p=UUID
10821086
** t=TAGID show only check-ins with the given tagid
@@ -1099,14 +1103,14 @@
10991103
** datefmt=N Override the date format
11001104
**
11011105
** p= and d= can appear individually or together. If either p= or d=
11021106
** appear, then u=, y=, a=, and b= are ignored.
11031107
**
1104
-** If a= and b= appear, only a= is used. If neither appear, the most
1105
-** recent events are chosen.
1108
+** If both a= and b= appear then both upper and lower bounds are honored.
11061109
**
1107
-** If n= is missing, the default count is 20.
1110
+** If n= is missing, the default count is 50 for most queries but
1111
+** drops to 11 for c= queries.
11081112
*/
11091113
void page_timeline(void){
11101114
Stmt q; /* Query used to generate the timeline */
11111115
Blob sql; /* text of SQL used to generate timeline */
11121116
Blob desc; /* Description of the timeline */
@@ -1117,10 +1121,11 @@
11171121
const char *zUser = P("u"); /* All entries by this user if not NULL */
11181122
const char *zType = PD("y","all"); /* Type of events. All if NULL */
11191123
const char *zAfter = P("a"); /* Events after this time */
11201124
const char *zBefore = P("b"); /* Events before this time */
11211125
const char *zCirca = P("c"); /* Events near this time */
1126
+ const char *zMark = P("m"); /* Mark this event or an event this time */
11221127
const char *zTagName = P("t"); /* Show events with this tag */
11231128
const char *zBrName = P("r"); /* Show events related to this tag */
11241129
const char *zSearch = P("s"); /* Search string */
11251130
const char *zUses = P("uf"); /* Only show checkins hold this file */
11261131
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
@@ -1140,10 +1145,11 @@
11401145
int pd_rid;
11411146
double rBefore, rAfter, rCirca; /* Boundary times */
11421147
const char *z;
11431148
char *zOlderButton = 0; /* URL for Older button at the bottom */
11441149
int selectedRid = -9999999; /* Show a highlight on this RID */
1150
+ int disableY = 0; /* Disable type selector on submenu */
11451151
11461152
/* Set number of rows to display */
11471153
z = P("n");
11481154
if( z ){
11491155
if( fossil_strcmp(z,"all")==0 ){
@@ -1169,11 +1175,11 @@
11691175
if( pd_rid ){
11701176
p_rid = d_rid = pd_rid;
11711177
}
11721178
login_check_credentials();
11731179
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1174
- login_needed();
1180
+ login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
11751181
return;
11761182
}
11771183
url_initialize(&url, "timeline");
11781184
cgi_query_parameters_to_url(&url);
11791185
if( zTagName && g.perm.Read ){
@@ -1183,15 +1189,20 @@
11831189
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
11841190
zThisTag = zBrName;
11851191
}else{
11861192
tagid = 0;
11871193
}
1194
+ if( zMark && zMark[0]==0 ){
1195
+ if( zAfter ) zMark = zAfter;
1196
+ if( zBefore ) zMark = zBefore;
1197
+ if( zCirca ) zMark = zCirca;
1198
+ }
11881199
if( tagid>0
11891200
&& db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
11901201
){
1191
- zCirca = zBefore = zAfter = 0;
11921202
nEntry = -1;
1203
+ zCirca = 0;
11931204
}
11941205
if( zType[0]=='a' ){
11951206
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
11961207
}else{
11971208
tmFlags |= TIMELINE_GRAPH;
@@ -1213,10 +1224,11 @@
12131224
if( ufid ){
12141225
zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
12151226
db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
12161227
compute_uses_file("usesfile", ufid, 0);
12171228
zType = "ci";
1229
+ disableY = 1;
12181230
}else{
12191231
zUses = 0;
12201232
}
12211233
}
12221234
if( renameOnly ){
@@ -1223,10 +1235,11 @@
12231235
db_multi_exec(
12241236
"CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
12251237
"INSERT OR IGNORE INTO rnfile"
12261238
" SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
12271239
);
1240
+ disableY = 1;
12281241
}
12291242
12301243
style_header("Timeline");
12311244
login_anonymous_available();
12321245
timeline_temp_table();
@@ -1238,11 +1251,11 @@
12381251
tmFlags |= TIMELINE_FCHANGES;
12391252
}
12401253
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
12411254
blob_append_sql(&sql,
12421255
" AND NOT EXISTS(SELECT 1 FROM tagxref"
1243
- " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1256
+ " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
12441257
TAG_HIDDEN
12451258
);
12461259
}
12471260
if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
12481261
/* If from= and to= are present, display all nodes on a path connecting
@@ -1309,19 +1322,19 @@
13091322
db_multi_exec("%s", blob_sql_text(&sql));
13101323
}
13111324
if( useDividers ) selectedRid = p_rid;
13121325
}
13131326
blob_appendf(&desc, " of %z[%S]</a>",
1314
- href("%R/info/%s", zUuid), zUuid);
1327
+ href("%R/info/%!S", zUuid), zUuid);
13151328
if( d_rid ){
13161329
if( p_rid ){
13171330
/* If both p= and d= are set, we don't have the uuid of d yet. */
13181331
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
13191332
}
13201333
}
1321
- style_submenu_entry("n","Max:",1,0);
1322
- timeline_y_submenu();
1334
+ style_submenu_entry("n","Max:",4,0);
1335
+ timeline_y_submenu(1);
13231336
style_submenu_binary("v","With Files","Without Files",
13241337
zType[0]!='a' && zType[0]!='c');
13251338
}else if( f_rid && g.perm.Read ){
13261339
/* If f= is present, ignore all other parameters other than n= */
13271340
char *zUuid;
@@ -1335,11 +1348,11 @@
13351348
blob_append_sql(&sql, " AND event.objid IN ok");
13361349
db_multi_exec("%s", blob_sql_text(&sql));
13371350
if( useDividers ) selectedRid = f_rid;
13381351
blob_appendf(&desc, "Parents and children of check-in ");
13391352
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1340
- blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1353
+ blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
13411354
tmFlags |= TIMELINE_DISJOINT;
13421355
style_submenu_binary("v","With Files","Without Files",
13431356
zType[0]!='a' && zType[0]!='c');
13441357
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
13451358
timeline_submenu(&url, "Unhide", "unhide", "", 0);
@@ -1363,12 +1376,12 @@
13631376
blob_append_sql(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
13641377
zYearWeek);
13651378
}
13661379
if( tagid>0 ){
13671380
blob_append_sql(&sql,
1368
- "AND (EXISTS(SELECT 1 FROM tagxref"
1369
- " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1381
+ " AND (EXISTS(SELECT 1 FROM tagxref"
1382
+ " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", tagid);
13701383
13711384
if( zBrName ){
13721385
/* The next two blob_appendf() calls add SQL that causes checkins that
13731386
** are not part of the branch which are parents or children of the
13741387
** branch to be included in the report. This related check-ins are
@@ -1375,30 +1388,30 @@
13751388
** useful in helping to visualize what has happened on a quiescent
13761389
** branch that is infrequently merged with a much more activate branch.
13771390
*/
13781391
blob_append_sql(&sql,
13791392
" OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1380
- " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
1393
+ " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
13811394
tagid
13821395
);
13831396
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
13841397
blob_append_sql(&sql,
13851398
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
1386
- " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
1399
+ " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
13871400
TAG_HIDDEN
13881401
);
13891402
}
13901403
if( P("mionly")==0 ){
13911404
blob_append_sql(&sql,
13921405
" OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1393
- " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1406
+ " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
13941407
tagid
13951408
);
13961409
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
13971410
blob_append_sql(&sql,
13981411
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1399
- " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1412
+ " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
14001413
TAG_HIDDEN
14011414
);
14021415
}
14031416
}
14041417
}
@@ -1437,11 +1450,11 @@
14371450
}else if( zType[0]=='w' ){
14381451
zEType = "wiki edit";
14391452
}else if( zType[0]=='t' ){
14401453
zEType = "ticket change";
14411454
}else if( zType[0]=='e' ){
1442
- zEType = "event";
1455
+ zEType = "technical note";
14431456
}else if( zType[0]=='g' ){
14441457
zEType = "tag";
14451458
}
14461459
}
14471460
if( zUser ){
@@ -1484,41 +1497,45 @@
14841497
url_add_parameter(&url, "c", 0);
14851498
}else if( rCirca>0.0 ){
14861499
Blob sql2;
14871500
blob_init(&sql2, blob_sql_text(&sql), -1);
14881501
blob_append_sql(&sql2,
1489
- " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
1490
- rCirca, (nEntry+1)/2
1491
- );
1502
+ " AND event.mtime<=%f ORDER BY event.mtime DESC", rCirca);
1503
+ if( nEntry>0 ){
1504
+ blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
1505
+ nEntry -= (nEntry+1)/2;
1506
+ }
1507
+ if( PB("showsql") ){
1508
+ @ <pre>%h(blob_sql_text(&sql2))</pre>
1509
+ }
14921510
db_multi_exec("%s", blob_sql_text(&sql2));
14931511
blob_reset(&sql2);
1494
- blob_append_sql(&sql,
1512
+ blob_append_sql(&sql,
14951513
" AND event.mtime>=%f ORDER BY event.mtime ASC",
14961514
rCirca
14971515
);
1498
- nEntry -= (nEntry+1)/2;
1516
+ if( zMark==0 ) zMark = zCirca;
14991517
}else{
15001518
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
15011519
}
15021520
if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
15031521
db_multi_exec("%s", blob_sql_text(&sql));
1504
- if( zCirca && useDividers ) selectedRid = timeline_add_divider(rCirca);
15051522
15061523
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
15071524
if( zYearMonth ){
15081525
blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
15091526
}else if( zYearWeek ){
15101527
blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1511
- }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1528
+ }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
15121529
blob_appendf(&desc, "%d most recent %ss", n, zEType);
15131530
}else{
15141531
blob_appendf(&desc, "%d %ss", n, zEType);
15151532
}
15161533
if( zUses ){
15171534
char *zFilenames = names_of_file(zUses);
15181535
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1519
- href("%R/artifact/%s",zUses), zUses);
1536
+ href("%R/artifact/%!S",zUses), zUses);
15201537
tmFlags |= TIMELINE_DISJOINT;
15211538
}
15221539
if( renameOnly ){
15231540
blob_appendf(&desc, " that contain filename changes");
15241541
tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1564,23 +1581,27 @@
15641581
if( zType[0]=='a' || zType[0]=='c' ){
15651582
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
15661583
timeline_submenu(&url, "Unhide", "unhide", "", 0);
15671584
}
15681585
}
1569
- style_submenu_entry("n","Max:",1,0);
1570
- if( zUses==0 ) timeline_y_submenu();
1586
+ style_submenu_entry("n","Max:",4,0);
1587
+ timeline_y_submenu(disableY);
15711588
style_submenu_binary("v","With Files","Without Files",
15721589
zType[0]!='a' && zType[0]!='c');
15731590
}
15741591
}
1575
- if( P("showsql") ){
1576
- @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1592
+ if( PB("showsql") ){
1593
+ @ <pre>%h(blob_sql_text(&sql))</pre>
15771594
}
15781595
if( search_restrict(SRCH_CKIN)!=0 ){
15791596
style_submenu_element("Search", 0, "%R/search?y=c");
15801597
}
1581
- if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
1598
+ if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID;
1599
+ if( useDividers && zMark && zMark[0] ){
1600
+ double r = symbolic_name_to_mtime(zMark);
1601
+ if( r>0.0 ) selectedRid = timeline_add_divider(r);
1602
+ }
15821603
blob_zero(&sql);
15831604
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
15841605
@ <h2>%b(&desc)</h2>
15851606
blob_reset(&desc);
15861607
www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
@@ -1786,11 +1807,11 @@
17861807
** before
17871808
** after
17881809
** descendants | children
17891810
** ancestors | parents
17901811
**
1791
-** The BASELINE can be any unique prefix of 4 characters or more.
1812
+** The CHECKIN can be any unique prefix of 4 characters or more.
17921813
** The DATETIME should be in the ISO8601 format. For
17931814
** examples: "2007-08-18 07:21:21". You can also say "current"
17941815
** for the current version or "now" for the current time.
17951816
**
17961817
** Options:
@@ -1799,11 +1820,11 @@
17991820
** -p|--path PATH Output items affecting PATH only.
18001821
** PATH can be a file or a sub directory.
18011822
** --offset P skip P changes
18021823
** -t|--type TYPE Output items from the given types only, such as:
18031824
** ci = file commits only
1804
-** e = events only
1825
+** e = technical notes only
18051826
** t = tickets only
18061827
** w = wiki commits only
18071828
** -v|--verbose Output the list of files changed by each commit
18081829
** and the type of each change (edited, deleted,
18091830
** etc.) after the checkin comment.
@@ -1934,11 +1955,11 @@
19341955
}
19351956
19361957
if( mode==0 ) mode = 1;
19371958
blob_zero(&sql);
19381959
blob_append(&sql, timeline_query_for_tty(), -1);
1939
- blob_append_sql(&sql, " AND event.mtime %s %s",
1960
+ blob_append_sql(&sql, "\n AND event.mtime %s %s",
19401961
(mode==1 || mode==4) ? "<=" : ">=",
19411962
zDate /*safe-for-%s*/
19421963
);
19431964
19441965
if( mode==3 || mode==4 ){
@@ -2052,11 +2073,14 @@
20522073
*/
20532074
void test_timewarp_page(void){
20542075
Stmt q;
20552076
20562077
login_check_credentials();
2057
- if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
2078
+ if( !g.perm.Read || !g.perm.Hyperlink ){
2079
+ login_needed(g.anon.Read && g.anon.Hyperlink);
2080
+ return;
2081
+ }
20582082
style_header("Instances of timewarp");
20592083
@ <ul>
20602084
db_prepare(&q,
20612085
"SELECT blob.uuid "
20622086
" FROM plink p, plink c, blob"
@@ -2064,10 +2088,10 @@
20642088
" AND blob.rid=c.cid"
20652089
);
20662090
while( db_step(&q)==SQLITE_ROW ){
20672091
const char *zUuid = db_column_text(&q, 0);
20682092
@ <li>
2069
- @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2093
+ @ <a href="%R/timeline?dp=%!S(zUuid)&amp;unhide">%S(zUuid)</a>
20702094
}
20712095
db_finalize(&q);
20722096
style_footer();
20732097
}
20742098
--- src/timeline.c
+++ src/timeline.c
@@ -41,11 +41,11 @@
41 /*
42 ** Generate a hyperlink to a version.
43 */
44 void hyperlink_to_uuid(const char *zUuid){
45 if( g.perm.Hyperlink ){
46 @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%S(zUuid)]</a>
47 }else{
48 @ <span class="timelineHistDsp">[%S(zUuid)]</span>
49 }
50 }
51
@@ -163,11 +163,11 @@
163 void test_hash_color_page(void){
164 const char *zBr;
165 char zNm[10];
166 int i, cnt;
167 login_check_credentials();
168 if( !g.perm.Read ){ login_needed(); return; }
169
170 style_header("Hash Color Test");
171 for(i=cnt=0; i<10; i++){
172 sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
173 zBr = P(zNm);
@@ -453,11 +453,11 @@
453 @ (user: %h(zDispUser)%s(zTagList?",":"\051")
454 }
455
456 /* Generate a "detail" link for tags. */
457 if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
458 @ [%z(href("%R/info/%s",zUuid))details</a>]
459 }
460
461 /* Generate the "tags: TAGLIST" at the end of the comment, together
462 ** with hyperlinks to the tag list.
463 */
@@ -522,11 +522,12 @@
522 int fid = db_column_int(&fchngQuery, 1);
523 int isDel = fid==0;
524 const char *zOldName = db_column_text(&fchngQuery, 5);
525 const char *zOld = db_column_text(&fchngQuery, 4);
526 const char *zNew = db_column_text(&fchngQuery, 3);
527 const char *zUnpubTag = "";
 
528 char zId[20];
529 if( !inUl ){
530 @ <ul class="filelist">
531 inUl = 1;
532 }
@@ -539,29 +540,31 @@
539 if( !isNew && !isDel && zOldName!=0 ){
540 @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId)
541 }
542 continue;
543 }
 
544 if( content_is_private(fid) ){
545 zUnpubTag = UNPUB_TAG;
546 }
547 if( isNew ){
548 @ <li> %h(zFilename)%s(zId) %s(zUnpubTag) (new file) &nbsp;
549 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
550 }else if( isDel ){
551 @ <li> %h(zFilename) (deleted)</li>
552 }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
553 @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId) %s(zUnpubTag)
554 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
555 }else{
556 if( zOldName!=0 ){
557 @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId) %s(zUnpubTag)
558 }else{
559 @ <li> %h(zFilename)%s(zId) &nbsp; %s(zUnpubTag)
560 }
561 @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li>
562 }
 
563 }
564 db_reset(&fchngQuery);
565 if( inUl ){
566 @ </ul>
567 }
@@ -1033,11 +1036,11 @@
1033 /*
1034 ** Add the select/option box to the timeline submenu that is used to
1035 ** set the y= parameter that determines which elements to display
1036 ** on the timeline.
1037 */
1038 static void timeline_y_submenu(void){
1039 static int i = 0;
1040 static const char *az[12];
1041 if( i==0 ){
1042 az[0] = "all";
1043 az[1] = "Any Type";
@@ -1061,11 +1064,11 @@
1061 az[i++] = "Wiki";
1062 }
1063 assert( i<=ArraySize(az) );
1064 }
1065 if( i>2 ){
1066 style_submenu_multichoice("y", i/2, az, 0);
1067 }
1068 }
1069
1070 /*
1071 ** WEBPAGE: timeline
@@ -1073,10 +1076,11 @@
1073 ** Query parameters:
1074 **
1075 ** a=TIMEORTAG after this event
1076 ** b=TIMEORTAG before this event
1077 ** c=TIMEORTAG "circa" this event
 
1078 ** n=COUNT max number of events in output
1079 ** p=UUID artifact and up to COUNT parents and ancestors
1080 ** d=UUID artifact and up to COUNT descendants
1081 ** dp=UUID The same as d=UUID&p=UUID
1082 ** t=TAGID show only check-ins with the given tagid
@@ -1099,14 +1103,14 @@
1099 ** datefmt=N Override the date format
1100 **
1101 ** p= and d= can appear individually or together. If either p= or d=
1102 ** appear, then u=, y=, a=, and b= are ignored.
1103 **
1104 ** If a= and b= appear, only a= is used. If neither appear, the most
1105 ** recent events are chosen.
1106 **
1107 ** If n= is missing, the default count is 20.
 
1108 */
1109 void page_timeline(void){
1110 Stmt q; /* Query used to generate the timeline */
1111 Blob sql; /* text of SQL used to generate timeline */
1112 Blob desc; /* Description of the timeline */
@@ -1117,10 +1121,11 @@
1117 const char *zUser = P("u"); /* All entries by this user if not NULL */
1118 const char *zType = PD("y","all"); /* Type of events. All if NULL */
1119 const char *zAfter = P("a"); /* Events after this time */
1120 const char *zBefore = P("b"); /* Events before this time */
1121 const char *zCirca = P("c"); /* Events near this time */
 
1122 const char *zTagName = P("t"); /* Show events with this tag */
1123 const char *zBrName = P("r"); /* Show events related to this tag */
1124 const char *zSearch = P("s"); /* Search string */
1125 const char *zUses = P("uf"); /* Only show checkins hold this file */
1126 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
@@ -1140,10 +1145,11 @@
1140 int pd_rid;
1141 double rBefore, rAfter, rCirca; /* Boundary times */
1142 const char *z;
1143 char *zOlderButton = 0; /* URL for Older button at the bottom */
1144 int selectedRid = -9999999; /* Show a highlight on this RID */
 
1145
1146 /* Set number of rows to display */
1147 z = P("n");
1148 if( z ){
1149 if( fossil_strcmp(z,"all")==0 ){
@@ -1169,11 +1175,11 @@
1169 if( pd_rid ){
1170 p_rid = d_rid = pd_rid;
1171 }
1172 login_check_credentials();
1173 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1174 login_needed();
1175 return;
1176 }
1177 url_initialize(&url, "timeline");
1178 cgi_query_parameters_to_url(&url);
1179 if( zTagName && g.perm.Read ){
@@ -1183,15 +1189,20 @@
1183 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1184 zThisTag = zBrName;
1185 }else{
1186 tagid = 0;
1187 }
 
 
 
 
 
1188 if( tagid>0
1189 && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1190 ){
1191 zCirca = zBefore = zAfter = 0;
1192 nEntry = -1;
 
1193 }
1194 if( zType[0]=='a' ){
1195 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1196 }else{
1197 tmFlags |= TIMELINE_GRAPH;
@@ -1213,10 +1224,11 @@
1213 if( ufid ){
1214 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1215 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1216 compute_uses_file("usesfile", ufid, 0);
1217 zType = "ci";
 
1218 }else{
1219 zUses = 0;
1220 }
1221 }
1222 if( renameOnly ){
@@ -1223,10 +1235,11 @@
1223 db_multi_exec(
1224 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1225 "INSERT OR IGNORE INTO rnfile"
1226 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1227 );
 
1228 }
1229
1230 style_header("Timeline");
1231 login_anonymous_available();
1232 timeline_temp_table();
@@ -1238,11 +1251,11 @@
1238 tmFlags |= TIMELINE_FCHANGES;
1239 }
1240 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1241 blob_append_sql(&sql,
1242 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1243 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1244 TAG_HIDDEN
1245 );
1246 }
1247 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1248 /* If from= and to= are present, display all nodes on a path connecting
@@ -1309,19 +1322,19 @@
1309 db_multi_exec("%s", blob_sql_text(&sql));
1310 }
1311 if( useDividers ) selectedRid = p_rid;
1312 }
1313 blob_appendf(&desc, " of %z[%S]</a>",
1314 href("%R/info/%s", zUuid), zUuid);
1315 if( d_rid ){
1316 if( p_rid ){
1317 /* If both p= and d= are set, we don't have the uuid of d yet. */
1318 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1319 }
1320 }
1321 style_submenu_entry("n","Max:",1,0);
1322 timeline_y_submenu();
1323 style_submenu_binary("v","With Files","Without Files",
1324 zType[0]!='a' && zType[0]!='c');
1325 }else if( f_rid && g.perm.Read ){
1326 /* If f= is present, ignore all other parameters other than n= */
1327 char *zUuid;
@@ -1335,11 +1348,11 @@
1335 blob_append_sql(&sql, " AND event.objid IN ok");
1336 db_multi_exec("%s", blob_sql_text(&sql));
1337 if( useDividers ) selectedRid = f_rid;
1338 blob_appendf(&desc, "Parents and children of check-in ");
1339 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1340 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1341 tmFlags |= TIMELINE_DISJOINT;
1342 style_submenu_binary("v","With Files","Without Files",
1343 zType[0]!='a' && zType[0]!='c');
1344 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1345 timeline_submenu(&url, "Unhide", "unhide", "", 0);
@@ -1363,12 +1376,12 @@
1363 blob_append_sql(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
1364 zYearWeek);
1365 }
1366 if( tagid>0 ){
1367 blob_append_sql(&sql,
1368 "AND (EXISTS(SELECT 1 FROM tagxref"
1369 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1370
1371 if( zBrName ){
1372 /* The next two blob_appendf() calls add SQL that causes checkins that
1373 ** are not part of the branch which are parents or children of the
1374 ** branch to be included in the report. This related check-ins are
@@ -1375,30 +1388,30 @@
1375 ** useful in helping to visualize what has happened on a quiescent
1376 ** branch that is infrequently merged with a much more activate branch.
1377 */
1378 blob_append_sql(&sql,
1379 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1380 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
1381 tagid
1382 );
1383 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1384 blob_append_sql(&sql,
1385 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
1386 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)",
1387 TAG_HIDDEN
1388 );
1389 }
1390 if( P("mionly")==0 ){
1391 blob_append_sql(&sql,
1392 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1393 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1394 tagid
1395 );
1396 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1397 blob_append_sql(&sql,
1398 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1399 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1400 TAG_HIDDEN
1401 );
1402 }
1403 }
1404 }
@@ -1437,11 +1450,11 @@
1437 }else if( zType[0]=='w' ){
1438 zEType = "wiki edit";
1439 }else if( zType[0]=='t' ){
1440 zEType = "ticket change";
1441 }else if( zType[0]=='e' ){
1442 zEType = "event";
1443 }else if( zType[0]=='g' ){
1444 zEType = "tag";
1445 }
1446 }
1447 if( zUser ){
@@ -1484,41 +1497,45 @@
1484 url_add_parameter(&url, "c", 0);
1485 }else if( rCirca>0.0 ){
1486 Blob sql2;
1487 blob_init(&sql2, blob_sql_text(&sql), -1);
1488 blob_append_sql(&sql2,
1489 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
1490 rCirca, (nEntry+1)/2
1491 );
 
 
 
 
 
1492 db_multi_exec("%s", blob_sql_text(&sql2));
1493 blob_reset(&sql2);
1494 blob_append_sql(&sql,
1495 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1496 rCirca
1497 );
1498 nEntry -= (nEntry+1)/2;
1499 }else{
1500 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1501 }
1502 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1503 db_multi_exec("%s", blob_sql_text(&sql));
1504 if( zCirca && useDividers ) selectedRid = timeline_add_divider(rCirca);
1505
1506 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1507 if( zYearMonth ){
1508 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1509 }else if( zYearWeek ){
1510 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1511 }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1512 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1513 }else{
1514 blob_appendf(&desc, "%d %ss", n, zEType);
1515 }
1516 if( zUses ){
1517 char *zFilenames = names_of_file(zUses);
1518 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1519 href("%R/artifact/%s",zUses), zUses);
1520 tmFlags |= TIMELINE_DISJOINT;
1521 }
1522 if( renameOnly ){
1523 blob_appendf(&desc, " that contain filename changes");
1524 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1564,23 +1581,27 @@
1564 if( zType[0]=='a' || zType[0]=='c' ){
1565 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1566 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1567 }
1568 }
1569 style_submenu_entry("n","Max:",1,0);
1570 if( zUses==0 ) timeline_y_submenu();
1571 style_submenu_binary("v","With Files","Without Files",
1572 zType[0]!='a' && zType[0]!='c');
1573 }
1574 }
1575 if( P("showsql") ){
1576 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1577 }
1578 if( search_restrict(SRCH_CKIN)!=0 ){
1579 style_submenu_element("Search", 0, "%R/search?y=c");
1580 }
1581 if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
 
 
 
 
1582 blob_zero(&sql);
1583 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1584 @ <h2>%b(&desc)</h2>
1585 blob_reset(&desc);
1586 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
@@ -1786,11 +1807,11 @@
1786 ** before
1787 ** after
1788 ** descendants | children
1789 ** ancestors | parents
1790 **
1791 ** The BASELINE can be any unique prefix of 4 characters or more.
1792 ** The DATETIME should be in the ISO8601 format. For
1793 ** examples: "2007-08-18 07:21:21". You can also say "current"
1794 ** for the current version or "now" for the current time.
1795 **
1796 ** Options:
@@ -1799,11 +1820,11 @@
1799 ** -p|--path PATH Output items affecting PATH only.
1800 ** PATH can be a file or a sub directory.
1801 ** --offset P skip P changes
1802 ** -t|--type TYPE Output items from the given types only, such as:
1803 ** ci = file commits only
1804 ** e = events only
1805 ** t = tickets only
1806 ** w = wiki commits only
1807 ** -v|--verbose Output the list of files changed by each commit
1808 ** and the type of each change (edited, deleted,
1809 ** etc.) after the checkin comment.
@@ -1934,11 +1955,11 @@
1934 }
1935
1936 if( mode==0 ) mode = 1;
1937 blob_zero(&sql);
1938 blob_append(&sql, timeline_query_for_tty(), -1);
1939 blob_append_sql(&sql, " AND event.mtime %s %s",
1940 (mode==1 || mode==4) ? "<=" : ">=",
1941 zDate /*safe-for-%s*/
1942 );
1943
1944 if( mode==3 || mode==4 ){
@@ -2052,11 +2073,14 @@
2052 */
2053 void test_timewarp_page(void){
2054 Stmt q;
2055
2056 login_check_credentials();
2057 if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
 
 
 
2058 style_header("Instances of timewarp");
2059 @ <ul>
2060 db_prepare(&q,
2061 "SELECT blob.uuid "
2062 " FROM plink p, plink c, blob"
@@ -2064,10 +2088,10 @@
2064 " AND blob.rid=c.cid"
2065 );
2066 while( db_step(&q)==SQLITE_ROW ){
2067 const char *zUuid = db_column_text(&q, 0);
2068 @ <li>
2069 @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2070 }
2071 db_finalize(&q);
2072 style_footer();
2073 }
2074
--- src/timeline.c
+++ src/timeline.c
@@ -41,11 +41,11 @@
41 /*
42 ** Generate a hyperlink to a version.
43 */
44 void hyperlink_to_uuid(const char *zUuid){
45 if( g.perm.Hyperlink ){
46 @ %z(xhref("class='timelineHistLink'","%R/info/%!S",zUuid))[%S(zUuid)]</a>
47 }else{
48 @ <span class="timelineHistDsp">[%S(zUuid)]</span>
49 }
50 }
51
@@ -163,11 +163,11 @@
163 void test_hash_color_page(void){
164 const char *zBr;
165 char zNm[10];
166 int i, cnt;
167 login_check_credentials();
168 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
169
170 style_header("Hash Color Test");
171 for(i=cnt=0; i<10; i++){
172 sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
173 zBr = P(zNm);
@@ -453,11 +453,11 @@
453 @ (user: %h(zDispUser)%s(zTagList?",":"\051")
454 }
455
456 /* Generate a "detail" link for tags. */
457 if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
458 @ [%z(href("%R/info/%!S",zUuid))details</a>]
459 }
460
461 /* Generate the "tags: TAGLIST" at the end of the comment, together
462 ** with hyperlinks to the tag list.
463 */
@@ -522,11 +522,12 @@
522 int fid = db_column_int(&fchngQuery, 1);
523 int isDel = fid==0;
524 const char *zOldName = db_column_text(&fchngQuery, 5);
525 const char *zOld = db_column_text(&fchngQuery, 4);
526 const char *zNew = db_column_text(&fchngQuery, 3);
527 const char *zUnpub = "";
528 char *zA;
529 char zId[20];
530 if( !inUl ){
531 @ <ul class="filelist">
532 inUl = 1;
533 }
@@ -539,29 +540,31 @@
540 if( !isNew && !isDel && zOldName!=0 ){
541 @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId)
542 }
543 continue;
544 }
545 zA = href("%R/artifact/%!S",fid?zNew:zOld);
546 if( content_is_private(fid) ){
547 zUnpub = UNPUB_TAG;
548 }
549 if( isNew ){
550 @ <li> %s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) (new file) &nbsp;
551 @ %z(href("%R/artifact/%!S",zNew))[view]</a></li>
552 }else if( isDel ){
553 @ <li> %s(zA)%h(zFilename)</a> (deleted)</li>
554 }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
555 @ <li> %h(zOldName) &rarr; %s(zA)%h(zFilename)</a>%s(zId)
556 @ %s(zUnpub) %z(href("%R/artifact/%!S",zNew))[view]</a></li>
557 }else{
558 if( zOldName!=0 ){
559 @ <li>%h(zOldName) &rarr; %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub)
560 }else{
561 @ <li>%s(zA)%h(zFilename)</a>%s(zId) &nbsp; %s(zUnpub)
562 }
563 @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zOld,zNew))[diff]</a></li>
564 }
565 fossil_free(zA);
566 }
567 db_reset(&fchngQuery);
568 if( inUl ){
569 @ </ul>
570 }
@@ -1033,11 +1036,11 @@
1036 /*
1037 ** Add the select/option box to the timeline submenu that is used to
1038 ** set the y= parameter that determines which elements to display
1039 ** on the timeline.
1040 */
1041 static void timeline_y_submenu(int isDisabled){
1042 static int i = 0;
1043 static const char *az[12];
1044 if( i==0 ){
1045 az[0] = "all";
1046 az[1] = "Any Type";
@@ -1061,11 +1064,11 @@
1064 az[i++] = "Wiki";
1065 }
1066 assert( i<=ArraySize(az) );
1067 }
1068 if( i>2 ){
1069 style_submenu_multichoice("y", i/2, az, isDisabled);
1070 }
1071 }
1072
1073 /*
1074 ** WEBPAGE: timeline
@@ -1073,10 +1076,11 @@
1076 ** Query parameters:
1077 **
1078 ** a=TIMEORTAG after this event
1079 ** b=TIMEORTAG before this event
1080 ** c=TIMEORTAG "circa" this event
1081 ** m=TIMEORTAG mark this event
1082 ** n=COUNT max number of events in output
1083 ** p=UUID artifact and up to COUNT parents and ancestors
1084 ** d=UUID artifact and up to COUNT descendants
1085 ** dp=UUID The same as d=UUID&p=UUID
1086 ** t=TAGID show only check-ins with the given tagid
@@ -1099,14 +1103,14 @@
1103 ** datefmt=N Override the date format
1104 **
1105 ** p= and d= can appear individually or together. If either p= or d=
1106 ** appear, then u=, y=, a=, and b= are ignored.
1107 **
1108 ** If both a= and b= appear then both upper and lower bounds are honored.
 
1109 **
1110 ** If n= is missing, the default count is 50 for most queries but
1111 ** drops to 11 for c= queries.
1112 */
1113 void page_timeline(void){
1114 Stmt q; /* Query used to generate the timeline */
1115 Blob sql; /* text of SQL used to generate timeline */
1116 Blob desc; /* Description of the timeline */
@@ -1117,10 +1121,11 @@
1121 const char *zUser = P("u"); /* All entries by this user if not NULL */
1122 const char *zType = PD("y","all"); /* Type of events. All if NULL */
1123 const char *zAfter = P("a"); /* Events after this time */
1124 const char *zBefore = P("b"); /* Events before this time */
1125 const char *zCirca = P("c"); /* Events near this time */
1126 const char *zMark = P("m"); /* Mark this event or an event this time */
1127 const char *zTagName = P("t"); /* Show events with this tag */
1128 const char *zBrName = P("r"); /* Show events related to this tag */
1129 const char *zSearch = P("s"); /* Search string */
1130 const char *zUses = P("uf"); /* Only show checkins hold this file */
1131 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
@@ -1140,10 +1145,11 @@
1145 int pd_rid;
1146 double rBefore, rAfter, rCirca; /* Boundary times */
1147 const char *z;
1148 char *zOlderButton = 0; /* URL for Older button at the bottom */
1149 int selectedRid = -9999999; /* Show a highlight on this RID */
1150 int disableY = 0; /* Disable type selector on submenu */
1151
1152 /* Set number of rows to display */
1153 z = P("n");
1154 if( z ){
1155 if( fossil_strcmp(z,"all")==0 ){
@@ -1169,11 +1175,11 @@
1175 if( pd_rid ){
1176 p_rid = d_rid = pd_rid;
1177 }
1178 login_check_credentials();
1179 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1180 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
1181 return;
1182 }
1183 url_initialize(&url, "timeline");
1184 cgi_query_parameters_to_url(&url);
1185 if( zTagName && g.perm.Read ){
@@ -1183,15 +1189,20 @@
1189 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1190 zThisTag = zBrName;
1191 }else{
1192 tagid = 0;
1193 }
1194 if( zMark && zMark[0]==0 ){
1195 if( zAfter ) zMark = zAfter;
1196 if( zBefore ) zMark = zBefore;
1197 if( zCirca ) zMark = zCirca;
1198 }
1199 if( tagid>0
1200 && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1201 ){
 
1202 nEntry = -1;
1203 zCirca = 0;
1204 }
1205 if( zType[0]=='a' ){
1206 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1207 }else{
1208 tmFlags |= TIMELINE_GRAPH;
@@ -1213,10 +1224,11 @@
1224 if( ufid ){
1225 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1226 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1227 compute_uses_file("usesfile", ufid, 0);
1228 zType = "ci";
1229 disableY = 1;
1230 }else{
1231 zUses = 0;
1232 }
1233 }
1234 if( renameOnly ){
@@ -1223,10 +1235,11 @@
1235 db_multi_exec(
1236 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1237 "INSERT OR IGNORE INTO rnfile"
1238 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1239 );
1240 disableY = 1;
1241 }
1242
1243 style_header("Timeline");
1244 login_anonymous_available();
1245 timeline_temp_table();
@@ -1238,11 +1251,11 @@
1251 tmFlags |= TIMELINE_FCHANGES;
1252 }
1253 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1254 blob_append_sql(&sql,
1255 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1256 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
1257 TAG_HIDDEN
1258 );
1259 }
1260 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1261 /* If from= and to= are present, display all nodes on a path connecting
@@ -1309,19 +1322,19 @@
1322 db_multi_exec("%s", blob_sql_text(&sql));
1323 }
1324 if( useDividers ) selectedRid = p_rid;
1325 }
1326 blob_appendf(&desc, " of %z[%S]</a>",
1327 href("%R/info/%!S", zUuid), zUuid);
1328 if( d_rid ){
1329 if( p_rid ){
1330 /* If both p= and d= are set, we don't have the uuid of d yet. */
1331 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1332 }
1333 }
1334 style_submenu_entry("n","Max:",4,0);
1335 timeline_y_submenu(1);
1336 style_submenu_binary("v","With Files","Without Files",
1337 zType[0]!='a' && zType[0]!='c');
1338 }else if( f_rid && g.perm.Read ){
1339 /* If f= is present, ignore all other parameters other than n= */
1340 char *zUuid;
@@ -1335,11 +1348,11 @@
1348 blob_append_sql(&sql, " AND event.objid IN ok");
1349 db_multi_exec("%s", blob_sql_text(&sql));
1350 if( useDividers ) selectedRid = f_rid;
1351 blob_appendf(&desc, "Parents and children of check-in ");
1352 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1353 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
1354 tmFlags |= TIMELINE_DISJOINT;
1355 style_submenu_binary("v","With Files","Without Files",
1356 zType[0]!='a' && zType[0]!='c');
1357 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1358 timeline_submenu(&url, "Unhide", "unhide", "", 0);
@@ -1363,12 +1376,12 @@
1376 blob_append_sql(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
1377 zYearWeek);
1378 }
1379 if( tagid>0 ){
1380 blob_append_sql(&sql,
1381 " AND (EXISTS(SELECT 1 FROM tagxref"
1382 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", tagid);
1383
1384 if( zBrName ){
1385 /* The next two blob_appendf() calls add SQL that causes checkins that
1386 ** are not part of the branch which are parents or children of the
1387 ** branch to be included in the report. This related check-ins are
@@ -1375,30 +1388,30 @@
1388 ** useful in helping to visualize what has happened on a quiescent
1389 ** branch that is infrequently merged with a much more activate branch.
1390 */
1391 blob_append_sql(&sql,
1392 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1393 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
1394 tagid
1395 );
1396 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1397 blob_append_sql(&sql,
1398 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
1399 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
1400 TAG_HIDDEN
1401 );
1402 }
1403 if( P("mionly")==0 ){
1404 blob_append_sql(&sql,
1405 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1406 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
1407 tagid
1408 );
1409 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1410 blob_append_sql(&sql,
1411 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1412 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
1413 TAG_HIDDEN
1414 );
1415 }
1416 }
1417 }
@@ -1437,11 +1450,11 @@
1450 }else if( zType[0]=='w' ){
1451 zEType = "wiki edit";
1452 }else if( zType[0]=='t' ){
1453 zEType = "ticket change";
1454 }else if( zType[0]=='e' ){
1455 zEType = "technical note";
1456 }else if( zType[0]=='g' ){
1457 zEType = "tag";
1458 }
1459 }
1460 if( zUser ){
@@ -1484,41 +1497,45 @@
1497 url_add_parameter(&url, "c", 0);
1498 }else if( rCirca>0.0 ){
1499 Blob sql2;
1500 blob_init(&sql2, blob_sql_text(&sql), -1);
1501 blob_append_sql(&sql2,
1502 " AND event.mtime<=%f ORDER BY event.mtime DESC", rCirca);
1503 if( nEntry>0 ){
1504 blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
1505 nEntry -= (nEntry+1)/2;
1506 }
1507 if( PB("showsql") ){
1508 @ <pre>%h(blob_sql_text(&sql2))</pre>
1509 }
1510 db_multi_exec("%s", blob_sql_text(&sql2));
1511 blob_reset(&sql2);
1512 blob_append_sql(&sql,
1513 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1514 rCirca
1515 );
1516 if( zMark==0 ) zMark = zCirca;
1517 }else{
1518 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1519 }
1520 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1521 db_multi_exec("%s", blob_sql_text(&sql));
 
1522
1523 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1524 if( zYearMonth ){
1525 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1526 }else if( zYearWeek ){
1527 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1528 }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1529 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1530 }else{
1531 blob_appendf(&desc, "%d %ss", n, zEType);
1532 }
1533 if( zUses ){
1534 char *zFilenames = names_of_file(zUses);
1535 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1536 href("%R/artifact/%!S",zUses), zUses);
1537 tmFlags |= TIMELINE_DISJOINT;
1538 }
1539 if( renameOnly ){
1540 blob_appendf(&desc, " that contain filename changes");
1541 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1564,23 +1581,27 @@
1581 if( zType[0]=='a' || zType[0]=='c' ){
1582 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1583 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1584 }
1585 }
1586 style_submenu_entry("n","Max:",4,0);
1587 timeline_y_submenu(disableY);
1588 style_submenu_binary("v","With Files","Without Files",
1589 zType[0]!='a' && zType[0]!='c');
1590 }
1591 }
1592 if( PB("showsql") ){
1593 @ <pre>%h(blob_sql_text(&sql))</pre>
1594 }
1595 if( search_restrict(SRCH_CKIN)!=0 ){
1596 style_submenu_element("Search", 0, "%R/search?y=c");
1597 }
1598 if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID;
1599 if( useDividers && zMark && zMark[0] ){
1600 double r = symbolic_name_to_mtime(zMark);
1601 if( r>0.0 ) selectedRid = timeline_add_divider(r);
1602 }
1603 blob_zero(&sql);
1604 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1605 @ <h2>%b(&desc)</h2>
1606 blob_reset(&desc);
1607 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
@@ -1786,11 +1807,11 @@
1807 ** before
1808 ** after
1809 ** descendants | children
1810 ** ancestors | parents
1811 **
1812 ** The CHECKIN can be any unique prefix of 4 characters or more.
1813 ** The DATETIME should be in the ISO8601 format. For
1814 ** examples: "2007-08-18 07:21:21". You can also say "current"
1815 ** for the current version or "now" for the current time.
1816 **
1817 ** Options:
@@ -1799,11 +1820,11 @@
1820 ** -p|--path PATH Output items affecting PATH only.
1821 ** PATH can be a file or a sub directory.
1822 ** --offset P skip P changes
1823 ** -t|--type TYPE Output items from the given types only, such as:
1824 ** ci = file commits only
1825 ** e = technical notes only
1826 ** t = tickets only
1827 ** w = wiki commits only
1828 ** -v|--verbose Output the list of files changed by each commit
1829 ** and the type of each change (edited, deleted,
1830 ** etc.) after the checkin comment.
@@ -1934,11 +1955,11 @@
1955 }
1956
1957 if( mode==0 ) mode = 1;
1958 blob_zero(&sql);
1959 blob_append(&sql, timeline_query_for_tty(), -1);
1960 blob_append_sql(&sql, "\n AND event.mtime %s %s",
1961 (mode==1 || mode==4) ? "<=" : ">=",
1962 zDate /*safe-for-%s*/
1963 );
1964
1965 if( mode==3 || mode==4 ){
@@ -2052,11 +2073,14 @@
2073 */
2074 void test_timewarp_page(void){
2075 Stmt q;
2076
2077 login_check_credentials();
2078 if( !g.perm.Read || !g.perm.Hyperlink ){
2079 login_needed(g.anon.Read && g.anon.Hyperlink);
2080 return;
2081 }
2082 style_header("Instances of timewarp");
2083 @ <ul>
2084 db_prepare(&q,
2085 "SELECT blob.uuid "
2086 " FROM plink p, plink c, blob"
@@ -2064,10 +2088,10 @@
2088 " AND blob.rid=c.cid"
2089 );
2090 while( db_step(&q)==SQLITE_ROW ){
2091 const char *zUuid = db_column_text(&q, 0);
2092 @ <li>
2093 @ <a href="%R/timeline?dp=%!S(zUuid)&amp;unhide">%S(zUuid)</a>
2094 }
2095 db_finalize(&q);
2096 style_footer();
2097 }
2098
+22 -13
--- src/tkt.c
+++ src/tkt.c
@@ -428,11 +428,11 @@
428428
@ <font color="blue">
429429
@ <p>Database fields:</p><ul>
430430
for(i=0; i<nField; i++){
431431
@ <li>aField[%d(i)].zName = "%h(aField[i].zName)";
432432
@ originally = "%h(aField[i].zValue)";
433
- @ currently = "%h(PD(aField[i].zName,""))"";
433
+ @ currently = "%h(PD(aField[i].zName,""))";
434434
if( aField[i].zAppend ){
435435
@ zAppend = "%h(aField[i].zAppend)";
436436
}
437437
@ mUsed = %d(aField[i].mUsed);
438438
}
@@ -449,12 +449,12 @@
449449
const char *zScript;
450450
char *zFullName;
451451
const char *zUuid = PD("name","");
452452
453453
login_check_credentials();
454
- if( !g.perm.RdTkt ){ login_needed(); return; }
455
- if( g.perm.WrTkt || g.perm.ApndTkt ){
454
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
455
+ if( g.anon.WrTkt || g.anon.ApndTkt ){
456456
style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
457457
g.zTop, PD("name",""));
458458
}
459459
if( g.perm.Hyperlink ){
460460
style_submenu_element("History", "History Of This Ticket",
@@ -462,15 +462,15 @@
462462
style_submenu_element("Timeline", "Timeline Of This Ticket",
463463
"%s/tkttimeline/%T", g.zTop, zUuid);
464464
style_submenu_element("Check-ins", "Check-ins Of This Ticket",
465465
"%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
466466
}
467
- if( g.perm.NewTkt ){
467
+ if( g.anon.NewTkt ){
468468
style_submenu_element("New Ticket", "Create a new ticket",
469469
"%s/tktnew", g.zTop);
470470
}
471
- if( g.perm.ApndTkt && g.perm.Attach ){
471
+ if( g.anon.ApndTkt && g.anon.Attach ){
472472
style_submenu_element("Attach", "Add An Attachment",
473473
"%s/attachadd?tkt=%T&from=%s/tktview/%t",
474474
g.zTop, zUuid, g.zTop, zUuid);
475475
}
476476
if( P("plaintext") ){
@@ -687,11 +687,11 @@
687687
void tktnew_page(void){
688688
const char *zScript;
689689
char *zNewUuid = 0;
690690
691691
login_check_credentials();
692
- if( !g.perm.NewTkt ){ login_needed(); return; }
692
+ if( !g.perm.NewTkt ){ login_needed(g.anon.NewTkt); return; }
693693
if( P("cancel") ){
694694
cgi_redirect("home");
695695
}
696696
style_header("New Ticket");
697697
ticket_standard_submenu(T_ALL_BUT(T_NEW));
@@ -738,11 +738,14 @@
738738
int nName;
739739
const char *zName;
740740
int nRec;
741741
742742
login_check_credentials();
743
- if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; }
743
+ if( !g.perm.ApndTkt && !g.perm.WrTkt ){
744
+ login_needed(g.anon.ApndTkt || g.anon.WrTkt);
745
+ return;
746
+ }
744747
zName = P("name");
745748
if( P("cancel") ){
746749
cgi_redirectf("tktview?name=%T", zName);
747750
}
748751
style_header("Edit Ticket");
@@ -839,11 +842,14 @@
839842
int tagid;
840843
char zGlobPattern[50];
841844
const char *zType;
842845
843846
login_check_credentials();
844
- if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
847
+ if( !g.perm.Hyperlink || !g.perm.RdTkt ){
848
+ login_needed(g.anon.Hyperlink && g.anon.RdTkt);
849
+ return;
850
+ }
845851
zUuid = PD("name","");
846852
zType = PD("y","a");
847853
if( zType[0]!='c' ){
848854
style_submenu_element("Check-ins", "Check-ins",
849855
"%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
@@ -912,11 +918,14 @@
912918
const char *zUuid;
913919
int tagid;
914920
int nChng = 0;
915921
916922
login_check_credentials();
917
- if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
923
+ if( !g.perm.Hyperlink || !g.perm.RdTkt ){
924
+ login_needed(g.anon.Hyperlink && g.anon.RdTkt);
925
+ return;
926
+ }
918927
zUuid = PD("name","");
919928
zTitle = mprintf("History Of Ticket %h", zUuid);
920929
style_submenu_element("Status", "Status",
921930
"%s/info/%s", g.zTop, zUuid);
922931
style_submenu_element("Check-ins", "Check-ins",
@@ -968,22 +977,22 @@
968977
@
969978
@ <li><p>Delete attachment "%h(zFile)"
970979
}else{
971980
@
972981
@ <li><p>Add attachment
973
- @ "%z(href("%R/artifact/%s",zSrc))%s(zFile)</a>"
982
+ @ "%z(href("%R/artifact/%!S",zSrc))%s(zFile)</a>"
974983
}
975
- @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>]
984
+ @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
976985
@ (rid %d(rid)) by
977986
hyperlink_to_user(zUser,zDate," on");
978987
hyperlink_to_date(zDate, ".</p>");
979988
}else{
980989
pTicket = manifest_get(rid, CFTYPE_TICKET, 0);
981990
if( pTicket ){
982991
@
983992
@ <li><p>Ticket change
984
- @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>]
993
+ @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
985994
@ (rid %d(rid)) by
986995
hyperlink_to_user(pTicket->zUser,zDate," on");
987996
hyperlink_to_date(zDate, ":");
988997
@ </p>
989998
ticket_output_change_artifact(pTicket, "a");
@@ -1403,11 +1412,11 @@
14031412
style_submenu_element("Search","Search","%R/tktsrch");
14041413
}
14051414
if( (ok & T_REPLIST)!=0 ){
14061415
style_submenu_element("Reports","Reports","%R/reportlist");
14071416
}
1408
- if( (ok & T_NEW)!=0 && g.perm.NewTkt ){
1417
+ if( (ok & T_NEW)!=0 && g.anon.NewTkt ){
14091418
style_submenu_element("New","New","%R/tktnew");
14101419
}
14111420
}
14121421
14131422
/*
14141423
--- src/tkt.c
+++ src/tkt.c
@@ -428,11 +428,11 @@
428 @ <font color="blue">
429 @ <p>Database fields:</p><ul>
430 for(i=0; i<nField; i++){
431 @ <li>aField[%d(i)].zName = "%h(aField[i].zName)";
432 @ originally = "%h(aField[i].zValue)";
433 @ currently = "%h(PD(aField[i].zName,""))"";
434 if( aField[i].zAppend ){
435 @ zAppend = "%h(aField[i].zAppend)";
436 }
437 @ mUsed = %d(aField[i].mUsed);
438 }
@@ -449,12 +449,12 @@
449 const char *zScript;
450 char *zFullName;
451 const char *zUuid = PD("name","");
452
453 login_check_credentials();
454 if( !g.perm.RdTkt ){ login_needed(); return; }
455 if( g.perm.WrTkt || g.perm.ApndTkt ){
456 style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
457 g.zTop, PD("name",""));
458 }
459 if( g.perm.Hyperlink ){
460 style_submenu_element("History", "History Of This Ticket",
@@ -462,15 +462,15 @@
462 style_submenu_element("Timeline", "Timeline Of This Ticket",
463 "%s/tkttimeline/%T", g.zTop, zUuid);
464 style_submenu_element("Check-ins", "Check-ins Of This Ticket",
465 "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
466 }
467 if( g.perm.NewTkt ){
468 style_submenu_element("New Ticket", "Create a new ticket",
469 "%s/tktnew", g.zTop);
470 }
471 if( g.perm.ApndTkt && g.perm.Attach ){
472 style_submenu_element("Attach", "Add An Attachment",
473 "%s/attachadd?tkt=%T&from=%s/tktview/%t",
474 g.zTop, zUuid, g.zTop, zUuid);
475 }
476 if( P("plaintext") ){
@@ -687,11 +687,11 @@
687 void tktnew_page(void){
688 const char *zScript;
689 char *zNewUuid = 0;
690
691 login_check_credentials();
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));
@@ -738,11 +738,14 @@
738 int nName;
739 const char *zName;
740 int nRec;
741
742 login_check_credentials();
743 if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; }
 
 
 
744 zName = P("name");
745 if( P("cancel") ){
746 cgi_redirectf("tktview?name=%T", zName);
747 }
748 style_header("Edit Ticket");
@@ -839,11 +842,14 @@
839 int tagid;
840 char zGlobPattern[50];
841 const char *zType;
842
843 login_check_credentials();
844 if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
 
 
 
845 zUuid = PD("name","");
846 zType = PD("y","a");
847 if( zType[0]!='c' ){
848 style_submenu_element("Check-ins", "Check-ins",
849 "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
@@ -912,11 +918,14 @@
912 const char *zUuid;
913 int tagid;
914 int nChng = 0;
915
916 login_check_credentials();
917 if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
 
 
 
918 zUuid = PD("name","");
919 zTitle = mprintf("History Of Ticket %h", zUuid);
920 style_submenu_element("Status", "Status",
921 "%s/info/%s", g.zTop, zUuid);
922 style_submenu_element("Check-ins", "Check-ins",
@@ -968,22 +977,22 @@
968 @
969 @ <li><p>Delete attachment "%h(zFile)"
970 }else{
971 @
972 @ <li><p>Add attachment
973 @ "%z(href("%R/artifact/%s",zSrc))%s(zFile)</a>"
974 }
975 @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>]
976 @ (rid %d(rid)) by
977 hyperlink_to_user(zUser,zDate," on");
978 hyperlink_to_date(zDate, ".</p>");
979 }else{
980 pTicket = manifest_get(rid, CFTYPE_TICKET, 0);
981 if( pTicket ){
982 @
983 @ <li><p>Ticket change
984 @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>]
985 @ (rid %d(rid)) by
986 hyperlink_to_user(pTicket->zUser,zDate," on");
987 hyperlink_to_date(zDate, ":");
988 @ </p>
989 ticket_output_change_artifact(pTicket, "a");
@@ -1403,11 +1412,11 @@
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
--- src/tkt.c
+++ src/tkt.c
@@ -428,11 +428,11 @@
428 @ <font color="blue">
429 @ <p>Database fields:</p><ul>
430 for(i=0; i<nField; i++){
431 @ <li>aField[%d(i)].zName = "%h(aField[i].zName)";
432 @ originally = "%h(aField[i].zValue)";
433 @ currently = "%h(PD(aField[i].zName,""))";
434 if( aField[i].zAppend ){
435 @ zAppend = "%h(aField[i].zAppend)";
436 }
437 @ mUsed = %d(aField[i].mUsed);
438 }
@@ -449,12 +449,12 @@
449 const char *zScript;
450 char *zFullName;
451 const char *zUuid = PD("name","");
452
453 login_check_credentials();
454 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
455 if( g.anon.WrTkt || g.anon.ApndTkt ){
456 style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
457 g.zTop, PD("name",""));
458 }
459 if( g.perm.Hyperlink ){
460 style_submenu_element("History", "History Of This Ticket",
@@ -462,15 +462,15 @@
462 style_submenu_element("Timeline", "Timeline Of This Ticket",
463 "%s/tkttimeline/%T", g.zTop, zUuid);
464 style_submenu_element("Check-ins", "Check-ins Of This Ticket",
465 "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
466 }
467 if( g.anon.NewTkt ){
468 style_submenu_element("New Ticket", "Create a new ticket",
469 "%s/tktnew", g.zTop);
470 }
471 if( g.anon.ApndTkt && g.anon.Attach ){
472 style_submenu_element("Attach", "Add An Attachment",
473 "%s/attachadd?tkt=%T&from=%s/tktview/%t",
474 g.zTop, zUuid, g.zTop, zUuid);
475 }
476 if( P("plaintext") ){
@@ -687,11 +687,11 @@
687 void tktnew_page(void){
688 const char *zScript;
689 char *zNewUuid = 0;
690
691 login_check_credentials();
692 if( !g.perm.NewTkt ){ login_needed(g.anon.NewTkt); return; }
693 if( P("cancel") ){
694 cgi_redirect("home");
695 }
696 style_header("New Ticket");
697 ticket_standard_submenu(T_ALL_BUT(T_NEW));
@@ -738,11 +738,14 @@
738 int nName;
739 const char *zName;
740 int nRec;
741
742 login_check_credentials();
743 if( !g.perm.ApndTkt && !g.perm.WrTkt ){
744 login_needed(g.anon.ApndTkt || g.anon.WrTkt);
745 return;
746 }
747 zName = P("name");
748 if( P("cancel") ){
749 cgi_redirectf("tktview?name=%T", zName);
750 }
751 style_header("Edit Ticket");
@@ -839,11 +842,14 @@
842 int tagid;
843 char zGlobPattern[50];
844 const char *zType;
845
846 login_check_credentials();
847 if( !g.perm.Hyperlink || !g.perm.RdTkt ){
848 login_needed(g.anon.Hyperlink && g.anon.RdTkt);
849 return;
850 }
851 zUuid = PD("name","");
852 zType = PD("y","a");
853 if( zType[0]!='c' ){
854 style_submenu_element("Check-ins", "Check-ins",
855 "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
@@ -912,11 +918,14 @@
918 const char *zUuid;
919 int tagid;
920 int nChng = 0;
921
922 login_check_credentials();
923 if( !g.perm.Hyperlink || !g.perm.RdTkt ){
924 login_needed(g.anon.Hyperlink && g.anon.RdTkt);
925 return;
926 }
927 zUuid = PD("name","");
928 zTitle = mprintf("History Of Ticket %h", zUuid);
929 style_submenu_element("Status", "Status",
930 "%s/info/%s", g.zTop, zUuid);
931 style_submenu_element("Check-ins", "Check-ins",
@@ -968,22 +977,22 @@
977 @
978 @ <li><p>Delete attachment "%h(zFile)"
979 }else{
980 @
981 @ <li><p>Add attachment
982 @ "%z(href("%R/artifact/%!S",zSrc))%s(zFile)</a>"
983 }
984 @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
985 @ (rid %d(rid)) by
986 hyperlink_to_user(zUser,zDate," on");
987 hyperlink_to_date(zDate, ".</p>");
988 }else{
989 pTicket = manifest_get(rid, CFTYPE_TICKET, 0);
990 if( pTicket ){
991 @
992 @ <li><p>Ticket change
993 @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
994 @ (rid %d(rid)) by
995 hyperlink_to_user(pTicket->zUser,zDate," on");
996 hyperlink_to_date(zDate, ":");
997 @ </p>
998 ticket_output_change_artifact(pTicket, "a");
@@ -1403,11 +1412,11 @@
1412 style_submenu_element("Search","Search","%R/tktsrch");
1413 }
1414 if( (ok & T_REPLIST)!=0 ){
1415 style_submenu_element("Reports","Reports","%R/reportlist");
1416 }
1417 if( (ok & T_NEW)!=0 && g.anon.NewTkt ){
1418 style_submenu_element("New","New","%R/tktnew");
1419 }
1420 }
1421
1422 /*
1423
+11 -8
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -27,11 +27,12 @@
2727
** WEBPAGE: tktsetup
2828
*/
2929
void tktsetup_page(void){
3030
login_check_credentials();
3131
if( !g.perm.Setup ){
32
- login_needed();
32
+ login_needed(0);
33
+ return;
3334
}
3435
3536
style_header("Ticket Setup");
3637
@ <table border="0" cellspacing="20">
3738
setup_menu_entry("Table", "tktsetup_tab",
@@ -118,13 +119,14 @@
118119
const char *z;
119120
int isSubmit;
120121
121122
login_check_credentials();
122123
if( !g.perm.Setup ){
123
- login_needed();
124
+ login_needed(0);
125
+ return;
124126
}
125
- if( P("setup") ){
127
+ if( PB("setup") ){
126128
cgi_redirect("tktsetup");
127129
}
128130
isSubmit = P("submit")!=0;
129131
z = P("x");
130132
if( z==0 ){
@@ -713,11 +715,11 @@
713715
/*
714716
** The default report list page
715717
*/
716718
static const char zDefaultReportList[] =
717719
@ <th1>
718
-@ if {[hascap n]} {
720
+@ if {[anoncap n]} {
719721
@ html "<p>Enter a new ticket:</p>"
720722
@ html "<ul><li><a href='tktnew'>New ticket</a></li></ul>"
721723
@ }
722724
@ </th1>
723725
@
@@ -725,16 +727,16 @@
725727
@ <ol>
726728
@ <th1>html $report_items</th1>
727729
@ </ol>
728730
@
729731
@ <th1>
730
-@ if {[hascap t q]} {
732
+@ if {[anoncap t q]} {
731733
@ html "<p>Other options:</p>\n<ul>\n"
732
-@ if {[hascap t]} {
734
+@ if {[anoncap t]} {
733735
@ html "<li><a href='rptnew'>New report format</a></li>\n"
734736
@ }
735
-@ if {[hascap q]} {
737
+@ if {[anoncap q]} {
736738
@ html "<li><a href='modreq'>Tend to pending moderation requests</a></li>\n"
737739
@ }
738740
@ }
739741
@ </th1>
740742
;
@@ -858,11 +860,12 @@
858860
** WEBPAGE: tktsetup_timeline
859861
*/
860862
void tktsetup_timeline_page(void){
861863
login_check_credentials();
862864
if( !g.perm.Setup ){
863
- login_needed();
865
+ login_needed(0);
866
+ return;
864867
}
865868
866869
if( P("setup") ){
867870
cgi_redirect("tktsetup");
868871
}
869872
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -27,11 +27,12 @@
27 ** WEBPAGE: tktsetup
28 */
29 void tktsetup_page(void){
30 login_check_credentials();
31 if( !g.perm.Setup ){
32 login_needed();
 
33 }
34
35 style_header("Ticket Setup");
36 @ <table border="0" cellspacing="20">
37 setup_menu_entry("Table", "tktsetup_tab",
@@ -118,13 +119,14 @@
118 const char *z;
119 int isSubmit;
120
121 login_check_credentials();
122 if( !g.perm.Setup ){
123 login_needed();
 
124 }
125 if( P("setup") ){
126 cgi_redirect("tktsetup");
127 }
128 isSubmit = P("submit")!=0;
129 z = P("x");
130 if( z==0 ){
@@ -713,11 +715,11 @@
713 /*
714 ** The default report list page
715 */
716 static const char zDefaultReportList[] =
717 @ <th1>
718 @ if {[hascap n]} {
719 @ html "<p>Enter a new ticket:</p>"
720 @ html "<ul><li><a href='tktnew'>New ticket</a></li></ul>"
721 @ }
722 @ </th1>
723 @
@@ -725,16 +727,16 @@
725 @ <ol>
726 @ <th1>html $report_items</th1>
727 @ </ol>
728 @
729 @ <th1>
730 @ if {[hascap t q]} {
731 @ html "<p>Other options:</p>\n<ul>\n"
732 @ if {[hascap t]} {
733 @ html "<li><a href='rptnew'>New report format</a></li>\n"
734 @ }
735 @ if {[hascap q]} {
736 @ html "<li><a href='modreq'>Tend to pending moderation requests</a></li>\n"
737 @ }
738 @ }
739 @ </th1>
740 ;
@@ -858,11 +860,12 @@
858 ** WEBPAGE: tktsetup_timeline
859 */
860 void tktsetup_timeline_page(void){
861 login_check_credentials();
862 if( !g.perm.Setup ){
863 login_needed();
 
864 }
865
866 if( P("setup") ){
867 cgi_redirect("tktsetup");
868 }
869
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -27,11 +27,12 @@
27 ** WEBPAGE: tktsetup
28 */
29 void tktsetup_page(void){
30 login_check_credentials();
31 if( !g.perm.Setup ){
32 login_needed(0);
33 return;
34 }
35
36 style_header("Ticket Setup");
37 @ <table border="0" cellspacing="20">
38 setup_menu_entry("Table", "tktsetup_tab",
@@ -118,13 +119,14 @@
119 const char *z;
120 int isSubmit;
121
122 login_check_credentials();
123 if( !g.perm.Setup ){
124 login_needed(0);
125 return;
126 }
127 if( PB("setup") ){
128 cgi_redirect("tktsetup");
129 }
130 isSubmit = P("submit")!=0;
131 z = P("x");
132 if( z==0 ){
@@ -713,11 +715,11 @@
715 /*
716 ** The default report list page
717 */
718 static const char zDefaultReportList[] =
719 @ <th1>
720 @ if {[anoncap n]} {
721 @ html "<p>Enter a new ticket:</p>"
722 @ html "<ul><li><a href='tktnew'>New ticket</a></li></ul>"
723 @ }
724 @ </th1>
725 @
@@ -725,16 +727,16 @@
727 @ <ol>
728 @ <th1>html $report_items</th1>
729 @ </ol>
730 @
731 @ <th1>
732 @ if {[anoncap t q]} {
733 @ html "<p>Other options:</p>\n<ul>\n"
734 @ if {[anoncap t]} {
735 @ html "<li><a href='rptnew'>New report format</a></li>\n"
736 @ }
737 @ if {[anoncap q]} {
738 @ html "<li><a href='modreq'>Tend to pending moderation requests</a></li>\n"
739 @ }
740 @ }
741 @ </th1>
742 ;
@@ -858,11 +860,12 @@
860 ** WEBPAGE: tktsetup_timeline
861 */
862 void tktsetup_timeline_page(void){
863 login_check_credentials();
864 if( !g.perm.Setup ){
865 login_needed(0);
866 return;
867 }
868
869 if( P("setup") ){
870 cgi_redirect("tktsetup");
871 }
872
+1 -1
--- src/user.c
+++ src/user.c
@@ -424,11 +424,11 @@
424424
Stmt q;
425425
int cnt = 0;
426426
int rc;
427427
428428
login_check_credentials();
429
- if( !g.perm.Admin ){ login_needed(); return; }
429
+ if( !g.perm.Admin ){ login_needed(0); return; }
430430
create_accesslog_table();
431431
432432
if( P("delall") && P("delallbtn") ){
433433
db_multi_exec("DELETE FROM accesslog");
434434
cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip);
435435
--- src/user.c
+++ src/user.c
@@ -424,11 +424,11 @@
424 Stmt q;
425 int cnt = 0;
426 int rc;
427
428 login_check_credentials();
429 if( !g.perm.Admin ){ login_needed(); return; }
430 create_accesslog_table();
431
432 if( P("delall") && P("delallbtn") ){
433 db_multi_exec("DELETE FROM accesslog");
434 cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip);
435
--- src/user.c
+++ src/user.c
@@ -424,11 +424,11 @@
424 Stmt q;
425 int cnt = 0;
426 int rc;
427
428 login_check_credentials();
429 if( !g.perm.Admin ){ login_needed(0); return; }
430 create_accesslog_table();
431
432 if( P("delall") && P("delallbtn") ){
433 db_multi_exec("DELETE FROM accesslog");
434 cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip);
435
+29 -22
--- src/wiki.c
+++ src/wiki.c
@@ -134,22 +134,28 @@
134134
}
135135
return "text/x-fossil-wiki";
136136
}
137137
138138
/*
139
-** Render wiki text according to its mimetype
139
+** Render wiki text according to its mimetype.
140
+**
141
+** text/x-fossil-wiki Fossil wiki
142
+** text/x-markdown Markdown
143
+** anything else... Plain text
140144
*/
141145
void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){
142146
if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
143147
wiki_convert(pWiki, 0, 0);
144148
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
145149
Blob title = BLOB_INITIALIZER;
146150
Blob tail = BLOB_INITIALIZER;
147151
markdown_to_html(pWiki, &title, &tail);
152
+#if 0
148153
if( blob_size(&title)>0 ){
149154
@ <h1>%s(blob_str(&title))</h1>
150155
}
156
+#endif
151157
@ %s(blob_str(&tail))
152158
blob_reset(&title);
153159
blob_reset(&tail);
154160
}else{
155161
@ <pre>
@@ -222,11 +228,11 @@
222228
style_submenu_element("List","List","%R/wcontent");
223229
}
224230
if( (ok & W_HELP)!=0 ){
225231
style_submenu_element("Help","Help","%R/wikihelp");
226232
}
227
- if( (ok & W_NEW)!=0 && g.perm.NewWiki ){
233
+ if( (ok & W_NEW)!=0 && g.anon.NewWiki ){
228234
style_submenu_element("New","New","%R/wikinew");
229235
}
230236
#if 0
231237
if( (ok & W_BLOG)!=0
232238
#endif
@@ -239,11 +245,11 @@
239245
** WEBPAGE: wikihelp
240246
** A generic landing page for wiki.
241247
*/
242248
void wiki_helppage(void){
243249
login_check_credentials();
244
- if( !g.perm.RdWiki ){ login_needed(); return; }
250
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
245251
style_header("Wiki Help");
246252
wiki_standard_submenu(W_ALL_BUT(W_HELP));
247253
@ <h2>Wiki Links</h2>
248254
@ <ul>
249255
{ char *zWikiHomePageName = db_get("index-page",0);
@@ -261,19 +267,19 @@
261267
@ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
262268
@ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for
263269
@ %z(href("%R/md_rules"))Markdown Wiki</a>.</li>
264270
@ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
265271
@ to experiment.</li>
266
- if( g.perm.NewWiki ){
272
+ if( g.anon.NewWiki ){
267273
@ <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>
274
+ if( g.anon.Write ){
275
+ @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li>
270276
}
271277
}
272278
@ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
273279
@ available on this server.</li>
274
- if( g.perm.ModWiki ){
280
+ if( g.anon.ModWiki ){
275281
@ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
276282
}
277283
if( search_restrict(SRCH_WIKI)!=0 ){
278284
@ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key
279285
@ words</li>
@@ -312,11 +318,11 @@
312318
const char *zPageName;
313319
const char *zMimetype = 0;
314320
char *zBody = mprintf("%s","<i>Empty Page</i>");
315321
316322
login_check_credentials();
317
- if( !g.perm.RdWiki ){ login_needed(); return; }
323
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
318324
zPageName = P("name");
319325
if( zPageName==0 ){
320326
if( search_restrict(SRCH_WIKI)!=0 ){
321327
wiki_srchpage();
322328
}else{
@@ -355,11 +361,11 @@
355361
"%R/wdiff?name=%T&a=%d", zPageName, rid);
356362
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
357363
style_submenu_element("Details", "Details",
358364
"%R/info/%s", zUuid);
359365
}
360
- if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
366
+ if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){
361367
if( db_get_boolean("wysiwyg-wiki", 0) ){
362368
style_submenu_element("Edit", "Edit Wiki Page",
363369
"%s/wikiedit?name=%T&wysiwyg=1",
364370
g.zTop, zPageName);
365371
}else{
@@ -366,16 +372,16 @@
366372
style_submenu_element("Edit", "Edit Wiki Page",
367373
"%s/wikiedit?name=%T",
368374
g.zTop, zPageName);
369375
}
370376
}
371
- if( rid && g.perm.ApndWiki && g.perm.Attach ){
377
+ if( rid && g.anon.ApndWiki && g.anon.Attach ){
372378
style_submenu_element("Attach", "Add An Attachment",
373379
"%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
374380
g.zTop, zPageName, g.zTop, zPageName);
375381
}
376
- if( rid && g.perm.ApndWiki ){
382
+ if( rid && g.anon.ApndWiki ){
377383
style_submenu_element("Append", "Add A Comment",
378384
"%s/wikiappend?name=%T&mimetype=%s",
379385
g.zTop, zPageName, zMimetype);
380386
}
381387
if( g.perm.Hyperlink ){
@@ -423,13 +429,13 @@
423429
424430
/*
425431
** Output a selection box from which the user can select the
426432
** wiki mimetype.
427433
*/
428
-static void mimetype_option_menu(const char *zMimetype){
434
+void mimetype_option_menu(const char *zMimetype){
429435
unsigned i;
430
- @ Markup style: <select name="mimetype" size="1">
436
+ @ <select name="mimetype" size="1">
431437
for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
432438
if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
433439
@ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
434440
}else{
435441
@ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
@@ -485,11 +491,11 @@
485491
zPageName = PD("name","");
486492
if( check_name(zPageName) ) return;
487493
isSandbox = is_sandbox(zPageName);
488494
if( isSandbox ){
489495
if( !g.perm.WrWiki ){
490
- login_needed();
496
+ login_needed(g.anon.WrWiki);
491497
return;
492498
}
493499
if( zBody==0 ){
494500
zBody = db_get("sandbox","");
495501
zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
@@ -501,11 +507,11 @@
501507
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
502508
" ORDER BY mtime DESC", zTag
503509
);
504510
free(zTag);
505511
if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
506
- login_needed();
512
+ login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki);
507513
return;
508514
}
509515
if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
510516
zBody = pWiki->zWiki;
511517
zMimetype = pWiki->zMimetype;
@@ -573,11 +579,11 @@
573579
if( n<20 ) n = 20;
574580
if( n>30 ) n = 30;
575581
if( !isWysiwyg ){
576582
/* Traditional markup-only editing */
577583
form_begin(0, "%R/wikiedit");
578
- @ <div>
584
+ @ <div>Markup style:
579585
mimetype_option_menu(zMimetype);
580586
@ <br /><textarea name="w" class="wikiedit" cols="80"
581587
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
582588
@ <br />
583589
if( db_get_boolean("wysiwyg-wiki", 0) ){
@@ -625,11 +631,11 @@
625631
void wikinew_page(void){
626632
const char *zName;
627633
const char *zMimetype;
628634
login_check_credentials();
629635
if( !g.perm.NewWiki ){
630
- login_needed();
636
+ login_needed(g.anon.NewWiki);
631637
return;
632638
}
633639
zName = PD("name","");
634640
zMimetype = wiki_filter_mimetypes(P("mimetype"));
635641
if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
@@ -646,10 +652,11 @@
646652
@ <p>Rules for wiki page names:</p>
647653
well_formed_wiki_name_rules();
648654
form_begin(0, "%R/wikinew");
649655
@ <p>Name of new wiki page:
650656
@ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
657
+ @ Markup style:
651658
mimetype_option_menu("text/x-fossil-wiki");
652659
@ <br /><input type="submit" value="Create" />
653660
@ </p></form>
654661
if( zName[0] ){
655662
@ <p><span class="wikiError">
@@ -727,11 +734,11 @@
727734
fossil_redirect_home();
728735
return;
729736
}
730737
}
731738
if( !g.perm.ApndWiki ){
732
- login_needed();
739
+ login_needed(g.anon.ApndWiki);
733740
return;
734741
}
735742
if( P("submit")!=0 && P("r")!=0 && P("u")!=0
736743
&& (goodCaptcha = captcha_is_correct())
737744
){
@@ -840,11 +847,11 @@
840847
*/
841848
void whistory_page(void){
842849
Stmt q;
843850
const char *zPageName;
844851
login_check_credentials();
845
- if( !g.perm.Hyperlink ){ login_needed(); return; }
852
+ if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
846853
zPageName = PD("name","");
847854
style_header("History Of %s", zPageName);
848855
849856
db_prepare(&q, "%s AND event.objid IN "
850857
" (SELECT rid FROM tagxref WHERE tagid="
@@ -872,11 +879,11 @@
872879
Blob w1, w2, d;
873880
u64 diffFlags;
874881
875882
login_check_credentials();
876883
rid1 = atoi(PD("a","0"));
877
- if( !g.perm.Hyperlink ){ login_needed(); return; }
884
+ if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
878885
if( rid1==0 ) fossil_redirect_home();
879886
rid2 = atoi(PD("b","0"));
880887
zPageName = PD("name","");
881888
style_header("Changes To %s", zPageName);
882889
@@ -935,11 +942,11 @@
935942
void wcontent_page(void){
936943
Stmt q;
937944
int showAll = P("all")!=0;
938945
939946
login_check_credentials();
940
- if( !g.perm.RdWiki ){ login_needed(); return; }
947
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
941948
style_header("Available Wiki Pages");
942949
if( showAll ){
943950
style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
944951
}else{
945952
style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
@@ -969,11 +976,11 @@
969976
*/
970977
void wfind_page(void){
971978
Stmt q;
972979
const char *zTitle;
973980
login_check_credentials();
974
- if( !g.perm.RdWiki ){ login_needed(); return; }
981
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
975982
zTitle = PD("title","*");
976983
style_header("Wiki Pages Found");
977984
@ <ul>
978985
db_prepare(&q,
979986
"SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
980987
--- src/wiki.c
+++ src/wiki.c
@@ -134,22 +134,28 @@
134 }
135 return "text/x-fossil-wiki";
136 }
137
138 /*
139 ** Render wiki text according to its mimetype
 
 
 
 
140 */
141 void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){
142 if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
143 wiki_convert(pWiki, 0, 0);
144 }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
145 Blob title = BLOB_INITIALIZER;
146 Blob tail = BLOB_INITIALIZER;
147 markdown_to_html(pWiki, &title, &tail);
 
148 if( blob_size(&title)>0 ){
149 @ <h1>%s(blob_str(&title))</h1>
150 }
 
151 @ %s(blob_str(&tail))
152 blob_reset(&title);
153 blob_reset(&tail);
154 }else{
155 @ <pre>
@@ -222,11 +228,11 @@
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
@@ -239,11 +245,11 @@
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);
@@ -261,19 +267,19 @@
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>
@@ -312,11 +318,11 @@
312 const char *zPageName;
313 const char *zMimetype = 0;
314 char *zBody = mprintf("%s","<i>Empty Page</i>");
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{
@@ -355,11 +361,11 @@
355 "%R/wdiff?name=%T&a=%d", zPageName, rid);
356 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
357 style_submenu_element("Details", "Details",
358 "%R/info/%s", zUuid);
359 }
360 if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
361 if( db_get_boolean("wysiwyg-wiki", 0) ){
362 style_submenu_element("Edit", "Edit Wiki Page",
363 "%s/wikiedit?name=%T&wysiwyg=1",
364 g.zTop, zPageName);
365 }else{
@@ -366,16 +372,16 @@
366 style_submenu_element("Edit", "Edit Wiki Page",
367 "%s/wikiedit?name=%T",
368 g.zTop, zPageName);
369 }
370 }
371 if( rid && g.perm.ApndWiki && g.perm.Attach ){
372 style_submenu_element("Attach", "Add An Attachment",
373 "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
374 g.zTop, zPageName, g.zTop, zPageName);
375 }
376 if( rid && g.perm.ApndWiki ){
377 style_submenu_element("Append", "Add A Comment",
378 "%s/wikiappend?name=%T&mimetype=%s",
379 g.zTop, zPageName, zMimetype);
380 }
381 if( g.perm.Hyperlink ){
@@ -423,13 +429,13 @@
423
424 /*
425 ** Output a selection box from which the user can select the
426 ** wiki mimetype.
427 */
428 static void mimetype_option_menu(const char *zMimetype){
429 unsigned i;
430 @ Markup style: <select name="mimetype" size="1">
431 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
432 if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
433 @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
434 }else{
435 @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
@@ -485,11 +491,11 @@
485 zPageName = PD("name","");
486 if( check_name(zPageName) ) return;
487 isSandbox = is_sandbox(zPageName);
488 if( isSandbox ){
489 if( !g.perm.WrWiki ){
490 login_needed();
491 return;
492 }
493 if( zBody==0 ){
494 zBody = db_get("sandbox","");
495 zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
@@ -501,11 +507,11 @@
501 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
502 " ORDER BY mtime DESC", zTag
503 );
504 free(zTag);
505 if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
506 login_needed();
507 return;
508 }
509 if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
510 zBody = pWiki->zWiki;
511 zMimetype = pWiki->zMimetype;
@@ -573,11 +579,11 @@
573 if( n<20 ) n = 20;
574 if( n>30 ) n = 30;
575 if( !isWysiwyg ){
576 /* Traditional markup-only editing */
577 form_begin(0, "%R/wikiedit");
578 @ <div>
579 mimetype_option_menu(zMimetype);
580 @ <br /><textarea name="w" class="wikiedit" cols="80"
581 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
582 @ <br />
583 if( db_get_boolean("wysiwyg-wiki", 0) ){
@@ -625,11 +631,11 @@
625 void wikinew_page(void){
626 const char *zName;
627 const char *zMimetype;
628 login_check_credentials();
629 if( !g.perm.NewWiki ){
630 login_needed();
631 return;
632 }
633 zName = PD("name","");
634 zMimetype = wiki_filter_mimetypes(P("mimetype"));
635 if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
@@ -646,10 +652,11 @@
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 />
 
651 mimetype_option_menu("text/x-fossil-wiki");
652 @ <br /><input type="submit" value="Create" />
653 @ </p></form>
654 if( zName[0] ){
655 @ <p><span class="wikiError">
@@ -727,11 +734,11 @@
727 fossil_redirect_home();
728 return;
729 }
730 }
731 if( !g.perm.ApndWiki ){
732 login_needed();
733 return;
734 }
735 if( P("submit")!=0 && P("r")!=0 && P("u")!=0
736 && (goodCaptcha = captcha_is_correct())
737 ){
@@ -840,11 +847,11 @@
840 */
841 void whistory_page(void){
842 Stmt q;
843 const char *zPageName;
844 login_check_credentials();
845 if( !g.perm.Hyperlink ){ login_needed(); return; }
846 zPageName = PD("name","");
847 style_header("History Of %s", zPageName);
848
849 db_prepare(&q, "%s AND event.objid IN "
850 " (SELECT rid FROM tagxref WHERE tagid="
@@ -872,11 +879,11 @@
872 Blob w1, w2, d;
873 u64 diffFlags;
874
875 login_check_credentials();
876 rid1 = atoi(PD("a","0"));
877 if( !g.perm.Hyperlink ){ login_needed(); return; }
878 if( rid1==0 ) fossil_redirect_home();
879 rid2 = atoi(PD("b","0"));
880 zPageName = PD("name","");
881 style_header("Changes To %s", zPageName);
882
@@ -935,11 +942,11 @@
935 void wcontent_page(void){
936 Stmt q;
937 int showAll = P("all")!=0;
938
939 login_check_credentials();
940 if( !g.perm.RdWiki ){ login_needed(); return; }
941 style_header("Available Wiki Pages");
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);
@@ -969,11 +976,11 @@
969 */
970 void wfind_page(void){
971 Stmt q;
972 const char *zTitle;
973 login_check_credentials();
974 if( !g.perm.RdWiki ){ login_needed(); return; }
975 zTitle = PD("title","*");
976 style_header("Wiki Pages Found");
977 @ <ul>
978 db_prepare(&q,
979 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
980
--- src/wiki.c
+++ src/wiki.c
@@ -134,22 +134,28 @@
134 }
135 return "text/x-fossil-wiki";
136 }
137
138 /*
139 ** Render wiki text according to its mimetype.
140 **
141 ** text/x-fossil-wiki Fossil wiki
142 ** text/x-markdown Markdown
143 ** anything else... Plain text
144 */
145 void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){
146 if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
147 wiki_convert(pWiki, 0, 0);
148 }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
149 Blob title = BLOB_INITIALIZER;
150 Blob tail = BLOB_INITIALIZER;
151 markdown_to_html(pWiki, &title, &tail);
152 #if 0
153 if( blob_size(&title)>0 ){
154 @ <h1>%s(blob_str(&title))</h1>
155 }
156 #endif
157 @ %s(blob_str(&tail))
158 blob_reset(&title);
159 blob_reset(&tail);
160 }else{
161 @ <pre>
@@ -222,11 +228,11 @@
228 style_submenu_element("List","List","%R/wcontent");
229 }
230 if( (ok & W_HELP)!=0 ){
231 style_submenu_element("Help","Help","%R/wikihelp");
232 }
233 if( (ok & W_NEW)!=0 && g.anon.NewWiki ){
234 style_submenu_element("New","New","%R/wikinew");
235 }
236 #if 0
237 if( (ok & W_BLOG)!=0
238 #endif
@@ -239,11 +245,11 @@
245 ** WEBPAGE: wikihelp
246 ** A generic landing page for wiki.
247 */
248 void wiki_helppage(void){
249 login_check_credentials();
250 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
251 style_header("Wiki Help");
252 wiki_standard_submenu(W_ALL_BUT(W_HELP));
253 @ <h2>Wiki Links</h2>
254 @ <ul>
255 { char *zWikiHomePageName = db_get("index-page",0);
@@ -261,19 +267,19 @@
267 @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
268 @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for
269 @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li>
270 @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
271 @ to experiment.</li>
272 if( g.anon.NewWiki ){
273 @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li>
274 if( g.anon.Write ){
275 @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li>
276 }
277 }
278 @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
279 @ available on this server.</li>
280 if( g.anon.ModWiki ){
281 @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
282 }
283 if( search_restrict(SRCH_WIKI)!=0 ){
284 @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key
285 @ words</li>
@@ -312,11 +318,11 @@
318 const char *zPageName;
319 const char *zMimetype = 0;
320 char *zBody = mprintf("%s","<i>Empty Page</i>");
321
322 login_check_credentials();
323 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
324 zPageName = P("name");
325 if( zPageName==0 ){
326 if( search_restrict(SRCH_WIKI)!=0 ){
327 wiki_srchpage();
328 }else{
@@ -355,11 +361,11 @@
361 "%R/wdiff?name=%T&a=%d", zPageName, rid);
362 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
363 style_submenu_element("Details", "Details",
364 "%R/info/%s", zUuid);
365 }
366 if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){
367 if( db_get_boolean("wysiwyg-wiki", 0) ){
368 style_submenu_element("Edit", "Edit Wiki Page",
369 "%s/wikiedit?name=%T&wysiwyg=1",
370 g.zTop, zPageName);
371 }else{
@@ -366,16 +372,16 @@
372 style_submenu_element("Edit", "Edit Wiki Page",
373 "%s/wikiedit?name=%T",
374 g.zTop, zPageName);
375 }
376 }
377 if( rid && g.anon.ApndWiki && g.anon.Attach ){
378 style_submenu_element("Attach", "Add An Attachment",
379 "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
380 g.zTop, zPageName, g.zTop, zPageName);
381 }
382 if( rid && g.anon.ApndWiki ){
383 style_submenu_element("Append", "Add A Comment",
384 "%s/wikiappend?name=%T&mimetype=%s",
385 g.zTop, zPageName, zMimetype);
386 }
387 if( g.perm.Hyperlink ){
@@ -423,13 +429,13 @@
429
430 /*
431 ** Output a selection box from which the user can select the
432 ** wiki mimetype.
433 */
434 void mimetype_option_menu(const char *zMimetype){
435 unsigned i;
436 @ <select name="mimetype" size="1">
437 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
438 if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
439 @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
440 }else{
441 @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
@@ -485,11 +491,11 @@
491 zPageName = PD("name","");
492 if( check_name(zPageName) ) return;
493 isSandbox = is_sandbox(zPageName);
494 if( isSandbox ){
495 if( !g.perm.WrWiki ){
496 login_needed(g.anon.WrWiki);
497 return;
498 }
499 if( zBody==0 ){
500 zBody = db_get("sandbox","");
501 zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
@@ -501,11 +507,11 @@
507 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
508 " ORDER BY mtime DESC", zTag
509 );
510 free(zTag);
511 if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
512 login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki);
513 return;
514 }
515 if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
516 zBody = pWiki->zWiki;
517 zMimetype = pWiki->zMimetype;
@@ -573,11 +579,11 @@
579 if( n<20 ) n = 20;
580 if( n>30 ) n = 30;
581 if( !isWysiwyg ){
582 /* Traditional markup-only editing */
583 form_begin(0, "%R/wikiedit");
584 @ <div>Markup style:
585 mimetype_option_menu(zMimetype);
586 @ <br /><textarea name="w" class="wikiedit" cols="80"
587 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
588 @ <br />
589 if( db_get_boolean("wysiwyg-wiki", 0) ){
@@ -625,11 +631,11 @@
631 void wikinew_page(void){
632 const char *zName;
633 const char *zMimetype;
634 login_check_credentials();
635 if( !g.perm.NewWiki ){
636 login_needed(g.anon.NewWiki);
637 return;
638 }
639 zName = PD("name","");
640 zMimetype = wiki_filter_mimetypes(P("mimetype"));
641 if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
@@ -646,10 +652,11 @@
652 @ <p>Rules for wiki page names:</p>
653 well_formed_wiki_name_rules();
654 form_begin(0, "%R/wikinew");
655 @ <p>Name of new wiki page:
656 @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
657 @ Markup style:
658 mimetype_option_menu("text/x-fossil-wiki");
659 @ <br /><input type="submit" value="Create" />
660 @ </p></form>
661 if( zName[0] ){
662 @ <p><span class="wikiError">
@@ -727,11 +734,11 @@
734 fossil_redirect_home();
735 return;
736 }
737 }
738 if( !g.perm.ApndWiki ){
739 login_needed(g.anon.ApndWiki);
740 return;
741 }
742 if( P("submit")!=0 && P("r")!=0 && P("u")!=0
743 && (goodCaptcha = captcha_is_correct())
744 ){
@@ -840,11 +847,11 @@
847 */
848 void whistory_page(void){
849 Stmt q;
850 const char *zPageName;
851 login_check_credentials();
852 if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
853 zPageName = PD("name","");
854 style_header("History Of %s", zPageName);
855
856 db_prepare(&q, "%s AND event.objid IN "
857 " (SELECT rid FROM tagxref WHERE tagid="
@@ -872,11 +879,11 @@
879 Blob w1, w2, d;
880 u64 diffFlags;
881
882 login_check_credentials();
883 rid1 = atoi(PD("a","0"));
884 if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
885 if( rid1==0 ) fossil_redirect_home();
886 rid2 = atoi(PD("b","0"));
887 zPageName = PD("name","");
888 style_header("Changes To %s", zPageName);
889
@@ -935,11 +942,11 @@
942 void wcontent_page(void){
943 Stmt q;
944 int showAll = P("all")!=0;
945
946 login_check_credentials();
947 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
948 style_header("Available Wiki Pages");
949 if( showAll ){
950 style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
951 }else{
952 style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
@@ -969,11 +976,11 @@
976 */
977 void wfind_page(void){
978 Stmt q;
979 const char *zTitle;
980 login_check_credentials();
981 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
982 zTitle = PD("title","*");
983 style_header("Wiki Pages Found");
984 @ <ul>
985 db_prepare(&q,
986 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
987
+71 -15
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -762,11 +762,11 @@
762762
** Parse this element into the p structure.
763763
**
764764
** The content of z[] might be modified by converting characters
765765
** to lowercase and by inserting some "\000" characters.
766766
*/
767
-static void parseMarkup(ParsedMarkup *p, char *z){
767
+static int parseMarkup(ParsedMarkup *p, char *z){
768768
int i, j, c;
769769
int iACode;
770770
char *zValue;
771771
int seen = 0;
772772
char zTag[100];
@@ -794,11 +794,11 @@
794794
p->aAttr[0].zValue = &z[i];
795795
while( fossil_isalnum(z[i]) ){ i++; }
796796
p->aAttr[0].cTerm = c = z[i];
797797
z[i++] = 0;
798798
p->nAttr = 1;
799
- if( c=='>' ) return;
799
+ if( c=='>' ) return 0;
800800
}
801801
while( fossil_isspace(z[i]) ){ i++; }
802802
while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
803803
int attrOk; /* True to preserve attribute. False to ignore it */
804804
j = 0;
@@ -841,10 +841,11 @@
841841
p->nAttr++;
842842
}
843843
while( fossil_isspace(z[i]) ){ i++; }
844844
if( z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
845845
}
846
+ return seen;
846847
}
847848
848849
/*
849850
** Render markup on the given blob.
850851
*/
@@ -960,11 +961,11 @@
960961
static void popStack(Renderer *p){
961962
if( p->nStack ){
962963
int iCode;
963964
p->nStack--;
964965
iCode = p->aStack[p->nStack].iCode;
965
- if( iCode!=MARKUP_DIV && p->pOut ){
966
+ if( (iCode!=MARKUP_DIV || p->aStack[p->nStack].zId==0) && p->pOut ){
966967
blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
967968
}
968969
}
969970
}
970971
@@ -1206,11 +1207,11 @@
12061207
|| strncmp(zTarget, "ftp:", 4)==0
12071208
|| strncmp(zTarget, "mailto:", 7)==0
12081209
){
12091210
blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
12101211
}else if( zTarget[0]=='/' ){
1211
- blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1212
+ blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget);
12121213
}else if( zTarget[0]=='.'
12131214
&& (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
12141215
&& (p->state & WIKI_LINKSONLY)==0 ){
12151216
blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
12161217
}else if( zTarget[0]=='#' ){
@@ -1480,11 +1481,11 @@
14801481
break;
14811482
}
14821483
case TOKEN_MARKUP: {
14831484
const char *zId;
14841485
int iDiv;
1485
- parseMarkup(&markup, z);
1486
+ int mAttr = parseMarkup(&markup, z);
14861487
14871488
/* Convert <title> to <h1 align='center'> */
14881489
if( markup.iCode==MARKUP_TITLE && !p->inVerbatim ){
14891490
markup.iCode = MARKUP_H1;
14901491
markup.nAttr = 1;
@@ -1567,11 +1568,11 @@
15671568
popStackToTag(p, markup.iCode);
15681569
}else
15691570
15701571
/* Push <div> markup onto the stack together with the id=ID attribute.
15711572
*/
1572
- if( markup.iCode==MARKUP_DIV ){
1573
+ if( markup.iCode==MARKUP_DIV && (mAttr & ATTR_ID)!=0 ){
15731574
pushStackWithId(p, markup.iCode, markupId(&markup),
15741575
(p->state & ALLOW_WIKI)!=0);
15751576
}else
15761577
15771578
/* Enter <verbatim> processing. With verbatim enabled, all other
@@ -1965,17 +1966,26 @@
19651966
** z points to the start of a token. Return the number of
19661967
** characters in that token.
19671968
*/
19681969
static int nextHtmlToken(const char *z){
19691970
int n;
1970
- if( z[0]=='<' ){
1971
+ char c;
1972
+ if( (c=z[0])=='<' ){
19711973
n = markupLength(z);
19721974
if( n<=0 ) n = 1;
1973
- }else if( fossil_isspace(z[0]) ){
1975
+ }else if( fossil_isspace(c) ){
19741976
for(n=1; z[n] && fossil_isspace(z[n]); n++){}
1977
+ }else if( c=='&' ){
1978
+ n = z[1]=='#' ? 2 : 1;
1979
+ while( fossil_isalnum(z[n]) ) n++;
1980
+ if( z[n]==';' ) n++;
19751981
}else{
1976
- for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){}
1982
+ n = 1;
1983
+ for(n=1; 1; n++){
1984
+ if( (c = z[n]) > '<' ) continue;
1985
+ if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break;
1986
+ }
19771987
}
19781988
return n;
19791989
}
19801990
19811991
/*
@@ -2100,16 +2110,22 @@
21002110
}
21012111
21022112
/*
21032113
** Remove all HTML markup from the input text. The output written into
21042114
** pOut is pure text.
2115
+**
2116
+** Put the title on the first line, if there is any <title> markup.
2117
+** If there is no <title>, then create a blank first line.
21052118
*/
21062119
void html_to_plaintext(const char *zIn, Blob *pOut){
21072120
int n;
21082121
int i, j;
2122
+ int inTitle = 0; /* True between <title>...</title> */
2123
+ int seenText = 0; /* True after first non-whitespace seen */
21092124
int nNL = 0; /* Number of \n characters at the end of pOut */
21102125
int nWS = 0; /* True if pOut ends with whitespace */
2126
+ while( fossil_isspace(zIn[0]) ) zIn++;
21112127
while( zIn[0] ){
21122128
n = nextHtmlToken(zIn);
21132129
if( zIn[0]=='<' && n>1 ){
21142130
int isCloseTag;
21152131
int eTag;
@@ -2130,26 +2146,66 @@
21302146
zIn += n;
21312147
}
21322148
if( zIn[0]=='<' ) zIn += n;
21332149
continue;
21342150
}
2135
- if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2136
- if( nNL==0 ){
2151
+ if( eTag==MARKUP_TITLE ){
2152
+ inTitle = !isCloseTag;
2153
+ }
2154
+ if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2155
+ if( nNL==0 ){
21372156
blob_append(pOut, "\n", 1);
21382157
nNL++;
21392158
}
21402159
nWS = 1;
21412160
}
21422161
}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);
2162
+ if( seenText ){
2163
+ nNL = 0;
2164
+ if( !inTitle ){ /* '\n' -> ' ' within <title> */
2165
+ for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2166
+ }
2167
+ if( !nWS ){
2168
+ blob_append(pOut, nNL ? "\n" : " ", 1);
2169
+ nWS = 1;
2170
+ }
2171
+ }
2172
+ }else if( zIn[0]=='&' ){
2173
+ char c = '?';
2174
+ if( zIn[1]=='#' ){
2175
+ int x = atoi(&zIn[1]);
2176
+ if( x>0 && x<=127 ) c = x;
2177
+ }else{
2178
+ static const struct { int n; char c; char *z; } aEntity[] = {
2179
+ { 5, '&', "&amp;" },
2180
+ { 4, '<', "&lt;" },
2181
+ { 4, '>', "&gt;" },
2182
+ { 6, ' ', "&nbsp;" },
2183
+ };
2184
+ int jj;
2185
+ for(jj=0; jj<ArraySize(aEntity); jj++){
2186
+ if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
2187
+ c = aEntity[jj].c;
2188
+ break;
2189
+ }
2190
+ }
2191
+ }
2192
+ if( fossil_isspace(c) ){
2193
+ if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
21462194
nWS = 1;
2195
+ nNL = c=='\n';
2196
+ }else{
2197
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2198
+ seenText = 1;
2199
+ nNL = nWS = 0;
2200
+ blob_append(pOut, &c, 1);
21472201
}
21482202
}else{
2149
- blob_append(pOut, zIn, n);
2203
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2204
+ seenText = 1;
21502205
nNL = nWS = 0;
2206
+ blob_append(pOut, zIn, n);
21512207
}
21522208
zIn += n;
21532209
}
21542210
if( nNL==0 ) blob_append(pOut, "\n", 1);
21552211
}
21562212
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -762,11 +762,11 @@
762 ** Parse this element into the p structure.
763 **
764 ** The content of z[] might be modified by converting characters
765 ** to lowercase and by inserting some "\000" characters.
766 */
767 static void parseMarkup(ParsedMarkup *p, char *z){
768 int i, j, c;
769 int iACode;
770 char *zValue;
771 int seen = 0;
772 char zTag[100];
@@ -794,11 +794,11 @@
794 p->aAttr[0].zValue = &z[i];
795 while( fossil_isalnum(z[i]) ){ i++; }
796 p->aAttr[0].cTerm = c = z[i];
797 z[i++] = 0;
798 p->nAttr = 1;
799 if( c=='>' ) return;
800 }
801 while( fossil_isspace(z[i]) ){ i++; }
802 while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
803 int attrOk; /* True to preserve attribute. False to ignore it */
804 j = 0;
@@ -841,10 +841,11 @@
841 p->nAttr++;
842 }
843 while( fossil_isspace(z[i]) ){ i++; }
844 if( z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
845 }
 
846 }
847
848 /*
849 ** Render markup on the given blob.
850 */
@@ -960,11 +961,11 @@
960 static void popStack(Renderer *p){
961 if( p->nStack ){
962 int iCode;
963 p->nStack--;
964 iCode = p->aStack[p->nStack].iCode;
965 if( iCode!=MARKUP_DIV && p->pOut ){
966 blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
967 }
968 }
969 }
970
@@ -1206,11 +1207,11 @@
1206 || strncmp(zTarget, "ftp:", 4)==0
1207 || strncmp(zTarget, "mailto:", 7)==0
1208 ){
1209 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1210 }else if( zTarget[0]=='/' ){
1211 blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1212 }else if( zTarget[0]=='.'
1213 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1214 && (p->state & WIKI_LINKSONLY)==0 ){
1215 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1216 }else if( zTarget[0]=='#' ){
@@ -1480,11 +1481,11 @@
1480 break;
1481 }
1482 case TOKEN_MARKUP: {
1483 const char *zId;
1484 int iDiv;
1485 parseMarkup(&markup, z);
1486
1487 /* Convert <title> to <h1 align='center'> */
1488 if( markup.iCode==MARKUP_TITLE && !p->inVerbatim ){
1489 markup.iCode = MARKUP_H1;
1490 markup.nAttr = 1;
@@ -1567,11 +1568,11 @@
1567 popStackToTag(p, markup.iCode);
1568 }else
1569
1570 /* Push <div> markup onto the stack together with the id=ID attribute.
1571 */
1572 if( markup.iCode==MARKUP_DIV ){
1573 pushStackWithId(p, markup.iCode, markupId(&markup),
1574 (p->state & ALLOW_WIKI)!=0);
1575 }else
1576
1577 /* Enter <verbatim> processing. With verbatim enabled, all other
@@ -1965,17 +1966,26 @@
1965 ** z points to the start of a token. Return the number of
1966 ** characters in that token.
1967 */
1968 static int nextHtmlToken(const char *z){
1969 int n;
1970 if( z[0]=='<' ){
 
1971 n = markupLength(z);
1972 if( n<=0 ) n = 1;
1973 }else if( fossil_isspace(z[0]) ){
1974 for(n=1; z[n] && fossil_isspace(z[n]); n++){}
 
 
 
 
1975 }else{
1976 for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){}
 
 
 
 
1977 }
1978 return n;
1979 }
1980
1981 /*
@@ -2100,16 +2110,22 @@
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;
@@ -2130,26 +2146,66 @@
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
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -762,11 +762,11 @@
762 ** Parse this element into the p structure.
763 **
764 ** The content of z[] might be modified by converting characters
765 ** to lowercase and by inserting some "\000" characters.
766 */
767 static int parseMarkup(ParsedMarkup *p, char *z){
768 int i, j, c;
769 int iACode;
770 char *zValue;
771 int seen = 0;
772 char zTag[100];
@@ -794,11 +794,11 @@
794 p->aAttr[0].zValue = &z[i];
795 while( fossil_isalnum(z[i]) ){ i++; }
796 p->aAttr[0].cTerm = c = z[i];
797 z[i++] = 0;
798 p->nAttr = 1;
799 if( c=='>' ) return 0;
800 }
801 while( fossil_isspace(z[i]) ){ i++; }
802 while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
803 int attrOk; /* True to preserve attribute. False to ignore it */
804 j = 0;
@@ -841,10 +841,11 @@
841 p->nAttr++;
842 }
843 while( fossil_isspace(z[i]) ){ i++; }
844 if( z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
845 }
846 return seen;
847 }
848
849 /*
850 ** Render markup on the given blob.
851 */
@@ -960,11 +961,11 @@
961 static void popStack(Renderer *p){
962 if( p->nStack ){
963 int iCode;
964 p->nStack--;
965 iCode = p->aStack[p->nStack].iCode;
966 if( (iCode!=MARKUP_DIV || p->aStack[p->nStack].zId==0) && p->pOut ){
967 blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
968 }
969 }
970 }
971
@@ -1206,11 +1207,11 @@
1207 || strncmp(zTarget, "ftp:", 4)==0
1208 || strncmp(zTarget, "mailto:", 7)==0
1209 ){
1210 blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
1211 }else if( zTarget[0]=='/' ){
1212 blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget);
1213 }else if( zTarget[0]=='.'
1214 && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
1215 && (p->state & WIKI_LINKSONLY)==0 ){
1216 blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
1217 }else if( zTarget[0]=='#' ){
@@ -1480,11 +1481,11 @@
1481 break;
1482 }
1483 case TOKEN_MARKUP: {
1484 const char *zId;
1485 int iDiv;
1486 int mAttr = parseMarkup(&markup, z);
1487
1488 /* Convert <title> to <h1 align='center'> */
1489 if( markup.iCode==MARKUP_TITLE && !p->inVerbatim ){
1490 markup.iCode = MARKUP_H1;
1491 markup.nAttr = 1;
@@ -1567,11 +1568,11 @@
1568 popStackToTag(p, markup.iCode);
1569 }else
1570
1571 /* Push <div> markup onto the stack together with the id=ID attribute.
1572 */
1573 if( markup.iCode==MARKUP_DIV && (mAttr & ATTR_ID)!=0 ){
1574 pushStackWithId(p, markup.iCode, markupId(&markup),
1575 (p->state & ALLOW_WIKI)!=0);
1576 }else
1577
1578 /* Enter <verbatim> processing. With verbatim enabled, all other
@@ -1965,17 +1966,26 @@
1966 ** z points to the start of a token. Return the number of
1967 ** characters in that token.
1968 */
1969 static int nextHtmlToken(const char *z){
1970 int n;
1971 char c;
1972 if( (c=z[0])=='<' ){
1973 n = markupLength(z);
1974 if( n<=0 ) n = 1;
1975 }else if( fossil_isspace(c) ){
1976 for(n=1; z[n] && fossil_isspace(z[n]); n++){}
1977 }else if( c=='&' ){
1978 n = z[1]=='#' ? 2 : 1;
1979 while( fossil_isalnum(z[n]) ) n++;
1980 if( z[n]==';' ) n++;
1981 }else{
1982 n = 1;
1983 for(n=1; 1; n++){
1984 if( (c = z[n]) > '<' ) continue;
1985 if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break;
1986 }
1987 }
1988 return n;
1989 }
1990
1991 /*
@@ -2100,16 +2110,22 @@
2110 }
2111
2112 /*
2113 ** Remove all HTML markup from the input text. The output written into
2114 ** pOut is pure text.
2115 **
2116 ** Put the title on the first line, if there is any <title> markup.
2117 ** If there is no <title>, then create a blank first line.
2118 */
2119 void html_to_plaintext(const char *zIn, Blob *pOut){
2120 int n;
2121 int i, j;
2122 int inTitle = 0; /* True between <title>...</title> */
2123 int seenText = 0; /* True after first non-whitespace seen */
2124 int nNL = 0; /* Number of \n characters at the end of pOut */
2125 int nWS = 0; /* True if pOut ends with whitespace */
2126 while( fossil_isspace(zIn[0]) ) zIn++;
2127 while( zIn[0] ){
2128 n = nextHtmlToken(zIn);
2129 if( zIn[0]=='<' && n>1 ){
2130 int isCloseTag;
2131 int eTag;
@@ -2130,26 +2146,66 @@
2146 zIn += n;
2147 }
2148 if( zIn[0]=='<' ) zIn += n;
2149 continue;
2150 }
2151 if( eTag==MARKUP_TITLE ){
2152 inTitle = !isCloseTag;
2153 }
2154 if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2155 if( nNL==0 ){
2156 blob_append(pOut, "\n", 1);
2157 nNL++;
2158 }
2159 nWS = 1;
2160 }
2161 }else if( fossil_isspace(zIn[0]) ){
2162 if( seenText ){
2163 nNL = 0;
2164 if( !inTitle ){ /* '\n' -> ' ' within <title> */
2165 for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2166 }
2167 if( !nWS ){
2168 blob_append(pOut, nNL ? "\n" : " ", 1);
2169 nWS = 1;
2170 }
2171 }
2172 }else if( zIn[0]=='&' ){
2173 char c = '?';
2174 if( zIn[1]=='#' ){
2175 int x = atoi(&zIn[1]);
2176 if( x>0 && x<=127 ) c = x;
2177 }else{
2178 static const struct { int n; char c; char *z; } aEntity[] = {
2179 { 5, '&', "&amp;" },
2180 { 4, '<', "&lt;" },
2181 { 4, '>', "&gt;" },
2182 { 6, ' ', "&nbsp;" },
2183 };
2184 int jj;
2185 for(jj=0; jj<ArraySize(aEntity); jj++){
2186 if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
2187 c = aEntity[jj].c;
2188 break;
2189 }
2190 }
2191 }
2192 if( fossil_isspace(c) ){
2193 if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
2194 nWS = 1;
2195 nNL = c=='\n';
2196 }else{
2197 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2198 seenText = 1;
2199 nNL = nWS = 0;
2200 blob_append(pOut, &c, 1);
2201 }
2202 }else{
2203 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2204 seenText = 1;
2205 nNL = nWS = 0;
2206 blob_append(pOut, zIn, n);
2207 }
2208 zIn += n;
2209 }
2210 if( nNL==0 ) blob_append(pOut, "\n", 1);
2211 }
2212
--- src/winhttp.c
+++ src/winhttp.c
@@ -260,10 +260,13 @@
260260
blob_appendf(&options, " --files-urlenc %T", zFileGlob);
261261
}
262262
if( g.useLocalauth ){
263263
blob_appendf(&options, " --localauth");
264264
}
265
+ if( flags & HTTP_SERVER_REPOLIST ){
266
+ blob_appendf(&options, " --repolist");
267
+ }
265268
if( WSAStartup(MAKEWORD(1,1), &wd) ){
266269
fossil_fatal("unable to initialize winsock");
267270
}
268271
while( iPort<=mxPort ){
269272
s = socket(AF_INET, SOCK_STREAM, 0);
270273
--- src/winhttp.c
+++ src/winhttp.c
@@ -260,10 +260,13 @@
260 blob_appendf(&options, " --files-urlenc %T", zFileGlob);
261 }
262 if( g.useLocalauth ){
263 blob_appendf(&options, " --localauth");
264 }
 
 
 
265 if( WSAStartup(MAKEWORD(1,1), &wd) ){
266 fossil_fatal("unable to initialize winsock");
267 }
268 while( iPort<=mxPort ){
269 s = socket(AF_INET, SOCK_STREAM, 0);
270
--- src/winhttp.c
+++ src/winhttp.c
@@ -260,10 +260,13 @@
260 blob_appendf(&options, " --files-urlenc %T", zFileGlob);
261 }
262 if( g.useLocalauth ){
263 blob_appendf(&options, " --localauth");
264 }
265 if( flags & HTTP_SERVER_REPOLIST ){
266 blob_appendf(&options, " --repolist");
267 }
268 if( WSAStartup(MAKEWORD(1,1), &wd) ){
269 fossil_fatal("unable to initialize winsock");
270 }
271 while( iPort<=mxPort ){
272 s = socket(AF_INET, SOCK_STREAM, 0);
273
+4 -2
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -27,11 +27,12 @@
2727
** WEBPAGE: xfersetup
2828
*/
2929
void xfersetup_page(void){
3030
login_check_credentials();
3131
if( !g.perm.Setup ){
32
- login_needed();
32
+ login_needed(0);
33
+ return;
3334
}
3435
3536
style_header("Transfer Setup");
3637
3738
@ <table border="0" cellspacing="20">
@@ -104,11 +105,12 @@
104105
const char *z;
105106
int isSubmit;
106107
107108
login_check_credentials();
108109
if( !g.perm.Setup ){
109
- login_needed();
110
+ login_needed(0);
111
+ return;
110112
}
111113
if( P("setup") ){
112114
cgi_redirect("xfersetup");
113115
}
114116
isSubmit = P("submit")!=0;
115117
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -27,11 +27,12 @@
27 ** WEBPAGE: xfersetup
28 */
29 void xfersetup_page(void){
30 login_check_credentials();
31 if( !g.perm.Setup ){
32 login_needed();
 
33 }
34
35 style_header("Transfer Setup");
36
37 @ <table border="0" cellspacing="20">
@@ -104,11 +105,12 @@
104 const char *z;
105 int isSubmit;
106
107 login_check_credentials();
108 if( !g.perm.Setup ){
109 login_needed();
 
110 }
111 if( P("setup") ){
112 cgi_redirect("xfersetup");
113 }
114 isSubmit = P("submit")!=0;
115
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -27,11 +27,12 @@
27 ** WEBPAGE: xfersetup
28 */
29 void xfersetup_page(void){
30 login_check_credentials();
31 if( !g.perm.Setup ){
32 login_needed(0);
33 return;
34 }
35
36 style_header("Transfer Setup");
37
38 @ <table border="0" cellspacing="20">
@@ -104,11 +105,12 @@
105 const char *z;
106 int isSubmit;
107
108 login_check_credentials();
109 if( !g.perm.Setup ){
110 login_needed(0);
111 return;
112 }
113 if( P("setup") ){
114 cgi_redirect("xfersetup");
115 }
116 isSubmit = P("submit")!=0;
117
+12 -1
--- src/zip.c
+++ src/zip.c
@@ -448,11 +448,11 @@
448448
int nName, nRid;
449449
Blob zip;
450450
char *zKey;
451451
452452
login_check_credentials();
453
- if( !g.perm.Zip ){ login_needed(); return; }
453
+ if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
454454
load_control();
455455
zName = mprintf("%s", PD("name",""));
456456
nName = strlen(zName);
457457
zRid = mprintf("%s", PD("uuid","trunk"));
458458
nRid = strlen(zRid);
@@ -472,10 +472,21 @@
472472
}
473473
rid = name_to_typed_rid(nRid?zRid:zName,"ci");
474474
if( rid==0 ){
475475
@ Not found
476476
return;
477
+ }
478
+ if( referred_from_login() ){
479
+ style_header("ZIP Archive Download");
480
+ @ <form action='%R/zip'>
481
+ cgi_query_parameters_to_hidden();
482
+ @ <p>ZIP Archive named <b>%h(zName).zip</b> holding the content
483
+ @ of check-in <b>%h(zRid)</b>:
484
+ @ <input type="submit" value="Download" />
485
+ @ </form>
486
+ style_footer();
487
+ return;
477488
}
478489
if( nRid==0 && nName>10 ) zName[10] = 0;
479490
zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid);
480491
blob_zero(&zip);
481492
if( cache_read(&zip, zKey)==0 ){
482493
--- src/zip.c
+++ src/zip.c
@@ -448,11 +448,11 @@
448 int nName, nRid;
449 Blob zip;
450 char *zKey;
451
452 login_check_credentials();
453 if( !g.perm.Zip ){ login_needed(); return; }
454 load_control();
455 zName = mprintf("%s", PD("name",""));
456 nName = strlen(zName);
457 zRid = mprintf("%s", PD("uuid","trunk"));
458 nRid = strlen(zRid);
@@ -472,10 +472,21 @@
472 }
473 rid = name_to_typed_rid(nRid?zRid:zName,"ci");
474 if( rid==0 ){
475 @ Not found
476 return;
 
 
 
 
 
 
 
 
 
 
 
477 }
478 if( nRid==0 && nName>10 ) zName[10] = 0;
479 zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid);
480 blob_zero(&zip);
481 if( cache_read(&zip, zKey)==0 ){
482
--- src/zip.c
+++ src/zip.c
@@ -448,11 +448,11 @@
448 int nName, nRid;
449 Blob zip;
450 char *zKey;
451
452 login_check_credentials();
453 if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
454 load_control();
455 zName = mprintf("%s", PD("name",""));
456 nName = strlen(zName);
457 zRid = mprintf("%s", PD("uuid","trunk"));
458 nRid = strlen(zRid);
@@ -472,10 +472,21 @@
472 }
473 rid = name_to_typed_rid(nRid?zRid:zName,"ci");
474 if( rid==0 ){
475 @ Not found
476 return;
477 }
478 if( referred_from_login() ){
479 style_header("ZIP Archive Download");
480 @ <form action='%R/zip'>
481 cgi_query_parameters_to_hidden();
482 @ <p>ZIP Archive named <b>%h(zName).zip</b> holding the content
483 @ of check-in <b>%h(zRid)</b>:
484 @ <input type="submit" value="Download" />
485 @ </form>
486 style_footer();
487 return;
488 }
489 if( nRid==0 && nName>10 ) zName[10] = 0;
490 zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid);
491 blob_zero(&zip);
492 if( cache_read(&zip, zKey)==0 ){
493
+10 -10
--- test/th1-tcl.test
+++ test/th1-tcl.test
@@ -32,11 +32,11 @@
3232
3333
set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test.
3434
3535
###############################################################################
3636
37
-fossil test-th-render --th-open-config \
37
+fossil test-th-render --open-config \
3838
[file nativename [file join $dir th1-tcl1.txt]]
3939
4040
test th1-tcl-1 {[regexp -- {^tclReady\(before\) = 0
4141
tclReady\(after\) = 1
4242
\d+
@@ -59,60 +59,60 @@
5959
three words now
6060
$} [string map [list \r\n \n] $RESULT]]}
6161
6262
###############################################################################
6363
64
-fossil test-th-render --th-open-config \
64
+fossil test-th-render --open-config \
6565
[file nativename [file join $dir th1-tcl2.txt]]
6666
6767
test th1-tcl-2 {[regexp -- {^\d+
6868
$} [string map [list \r\n \n] $RESULT]]}
6969
7070
###############################################################################
7171
72
-fossil test-th-render --th-open-config \
72
+fossil test-th-render --open-config \
7373
[file nativename [file join $dir th1-tcl3.txt]]
7474
7575
test th1-tcl-3 {$RESULT eq {<hr><p class="thmainError">ERROR:\
7676
invalid command name &quot;bad_command&quot;</p>}}
7777
7878
###############################################################################
7979
80
-fossil test-th-render --th-open-config \
80
+fossil test-th-render --open-config \
8181
[file nativename [file join $dir th1-tcl4.txt]]
8282
8383
test th1-tcl-4 {$RESULT eq {<hr><p class="thmainError">ERROR:\
8484
divide by zero</p>}}
8585
8686
###############################################################################
8787
88
-fossil test-th-render --th-open-config \
88
+fossil test-th-render --open-config \
8989
[file nativename [file join $dir th1-tcl5.txt]]
9090
9191
test th1-tcl-5 {$RESULT eq {<hr><p class="thmainError">ERROR:\
9292
Tcl command not found: bad_command</p>} || $RESULT eq {<hr><p\
9393
class="thmainError">ERROR: invalid command name &quot;bad_command&quot;</p>}}
9494
9595
###############################################################################
9696
97
-fossil test-th-render --th-open-config \
97
+fossil test-th-render --open-config \
9898
[file nativename [file join $dir th1-tcl6.txt]]
9999
100100
test th1-tcl-6 {$RESULT eq {<hr><p class="thmainError">ERROR:\
101101
no such command: bad_command</p>}}
102102
103103
###############################################################################
104104
105
-fossil test-th-render --th-open-config \
105
+fossil test-th-render --open-config \
106106
[file nativename [file join $dir th1-tcl7.txt]]
107107
108108
test th1-tcl-7 {$RESULT eq {<hr><p class="thmainError">ERROR:\
109109
syntax error in expression: &quot;2**0&quot;</p>}}
110110
111111
###############################################################################
112112
113
-fossil test-th-render --th-open-config \
113
+fossil test-th-render --open-config \
114114
[file nativename [file join $dir th1-tcl8.txt]]
115115
116116
test th1-tcl-8 {$RESULT eq {<hr><p class="thmainError">ERROR:\
117117
cannot invoke Tcl command: tailcall</p>} || $RESULT eq {<hr><p\
118118
class="thmainError">ERROR: tailcall can only be called from a proc or\
@@ -119,11 +119,11 @@
119119
lambda</p>} || $RESULT eq {<hr><p class="thmainError">ERROR: This test\
120120
requires Tcl 8.6 or higher.</p>}}
121121
122122
###############################################################################
123123
124
-fossil test-th-render --th-open-config \
124
+fossil test-th-render --open-config \
125125
[file nativename [file join $dir th1-tcl9.txt]]
126126
127127
test th1-tcl-9 {[string trim $RESULT] eq [list [file tail $fossilexe] 3 \
128
-[list test-th-render --th-open-config [file nativename [file join $dir \
128
+[list test-th-render --open-config [file nativename [file join $dir \
129129
th1-tcl9.txt]]]]}
130130
--- test/th1-tcl.test
+++ test/th1-tcl.test
@@ -32,11 +32,11 @@
32
33 set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test.
34
35 ###############################################################################
36
37 fossil test-th-render --th-open-config \
38 [file nativename [file join $dir th1-tcl1.txt]]
39
40 test th1-tcl-1 {[regexp -- {^tclReady\(before\) = 0
41 tclReady\(after\) = 1
42 \d+
@@ -59,60 +59,60 @@
59 three words now
60 $} [string map [list \r\n \n] $RESULT]]}
61
62 ###############################################################################
63
64 fossil test-th-render --th-open-config \
65 [file nativename [file join $dir th1-tcl2.txt]]
66
67 test th1-tcl-2 {[regexp -- {^\d+
68 $} [string map [list \r\n \n] $RESULT]]}
69
70 ###############################################################################
71
72 fossil test-th-render --th-open-config \
73 [file nativename [file join $dir th1-tcl3.txt]]
74
75 test th1-tcl-3 {$RESULT eq {<hr><p class="thmainError">ERROR:\
76 invalid command name &quot;bad_command&quot;</p>}}
77
78 ###############################################################################
79
80 fossil test-th-render --th-open-config \
81 [file nativename [file join $dir th1-tcl4.txt]]
82
83 test th1-tcl-4 {$RESULT eq {<hr><p class="thmainError">ERROR:\
84 divide by zero</p>}}
85
86 ###############################################################################
87
88 fossil test-th-render --th-open-config \
89 [file nativename [file join $dir th1-tcl5.txt]]
90
91 test th1-tcl-5 {$RESULT eq {<hr><p class="thmainError">ERROR:\
92 Tcl command not found: bad_command</p>} || $RESULT eq {<hr><p\
93 class="thmainError">ERROR: invalid command name &quot;bad_command&quot;</p>}}
94
95 ###############################################################################
96
97 fossil test-th-render --th-open-config \
98 [file nativename [file join $dir th1-tcl6.txt]]
99
100 test th1-tcl-6 {$RESULT eq {<hr><p class="thmainError">ERROR:\
101 no such command: bad_command</p>}}
102
103 ###############################################################################
104
105 fossil test-th-render --th-open-config \
106 [file nativename [file join $dir th1-tcl7.txt]]
107
108 test th1-tcl-7 {$RESULT eq {<hr><p class="thmainError">ERROR:\
109 syntax error in expression: &quot;2**0&quot;</p>}}
110
111 ###############################################################################
112
113 fossil test-th-render --th-open-config \
114 [file nativename [file join $dir th1-tcl8.txt]]
115
116 test th1-tcl-8 {$RESULT eq {<hr><p class="thmainError">ERROR:\
117 cannot invoke Tcl command: tailcall</p>} || $RESULT eq {<hr><p\
118 class="thmainError">ERROR: tailcall can only be called from a proc or\
@@ -119,11 +119,11 @@
119 lambda</p>} || $RESULT eq {<hr><p class="thmainError">ERROR: This test\
120 requires Tcl 8.6 or higher.</p>}}
121
122 ###############################################################################
123
124 fossil test-th-render --th-open-config \
125 [file nativename [file join $dir th1-tcl9.txt]]
126
127 test th1-tcl-9 {[string trim $RESULT] eq [list [file tail $fossilexe] 3 \
128 [list test-th-render --th-open-config [file nativename [file join $dir \
129 th1-tcl9.txt]]]]}
130
--- test/th1-tcl.test
+++ test/th1-tcl.test
@@ -32,11 +32,11 @@
32
33 set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test.
34
35 ###############################################################################
36
37 fossil test-th-render --open-config \
38 [file nativename [file join $dir th1-tcl1.txt]]
39
40 test th1-tcl-1 {[regexp -- {^tclReady\(before\) = 0
41 tclReady\(after\) = 1
42 \d+
@@ -59,60 +59,60 @@
59 three words now
60 $} [string map [list \r\n \n] $RESULT]]}
61
62 ###############################################################################
63
64 fossil test-th-render --open-config \
65 [file nativename [file join $dir th1-tcl2.txt]]
66
67 test th1-tcl-2 {[regexp -- {^\d+
68 $} [string map [list \r\n \n] $RESULT]]}
69
70 ###############################################################################
71
72 fossil test-th-render --open-config \
73 [file nativename [file join $dir th1-tcl3.txt]]
74
75 test th1-tcl-3 {$RESULT eq {<hr><p class="thmainError">ERROR:\
76 invalid command name &quot;bad_command&quot;</p>}}
77
78 ###############################################################################
79
80 fossil test-th-render --open-config \
81 [file nativename [file join $dir th1-tcl4.txt]]
82
83 test th1-tcl-4 {$RESULT eq {<hr><p class="thmainError">ERROR:\
84 divide by zero</p>}}
85
86 ###############################################################################
87
88 fossil test-th-render --open-config \
89 [file nativename [file join $dir th1-tcl5.txt]]
90
91 test th1-tcl-5 {$RESULT eq {<hr><p class="thmainError">ERROR:\
92 Tcl command not found: bad_command</p>} || $RESULT eq {<hr><p\
93 class="thmainError">ERROR: invalid command name &quot;bad_command&quot;</p>}}
94
95 ###############################################################################
96
97 fossil test-th-render --open-config \
98 [file nativename [file join $dir th1-tcl6.txt]]
99
100 test th1-tcl-6 {$RESULT eq {<hr><p class="thmainError">ERROR:\
101 no such command: bad_command</p>}}
102
103 ###############################################################################
104
105 fossil test-th-render --open-config \
106 [file nativename [file join $dir th1-tcl7.txt]]
107
108 test th1-tcl-7 {$RESULT eq {<hr><p class="thmainError">ERROR:\
109 syntax error in expression: &quot;2**0&quot;</p>}}
110
111 ###############################################################################
112
113 fossil test-th-render --open-config \
114 [file nativename [file join $dir th1-tcl8.txt]]
115
116 test th1-tcl-8 {$RESULT eq {<hr><p class="thmainError">ERROR:\
117 cannot invoke Tcl command: tailcall</p>} || $RESULT eq {<hr><p\
118 class="thmainError">ERROR: tailcall can only be called from a proc or\
@@ -119,11 +119,11 @@
119 lambda</p>} || $RESULT eq {<hr><p class="thmainError">ERROR: This test\
120 requires Tcl 8.6 or higher.</p>}}
121
122 ###############################################################################
123
124 fossil test-th-render --open-config \
125 [file nativename [file join $dir th1-tcl9.txt]]
126
127 test th1-tcl-9 {[string trim $RESULT] eq [list [file tail $fossilexe] 3 \
128 [list test-th-render --open-config [file nativename [file join $dir \
129 th1-tcl9.txt]]]]}
130
+21 -21
--- test/th1.test
+++ test/th1.test
@@ -16,63 +16,63 @@
1616
############################################################################
1717
#
1818
# TH1 Commands
1919
#
2020
21
-fossil test-th-eval --th-open-config "setting th1-hooks"
21
+fossil test-th-eval --open-config "setting th1-hooks"
2222
set th1Hooks [expr {$RESULT eq "1"}]
2323
2424
###############################################################################
2525
26
-fossil test-th-eval --th-open-config "setting abc"
26
+fossil test-th-eval --open-config "setting abc"
2727
test th1-setting-1 {$RESULT eq ""}
2828
2929
###############################################################################
3030
31
-fossil test-th-eval --th-open-config "setting -- abc"
31
+fossil test-th-eval --open-config "setting -- abc"
3232
test th1-setting-2 {$RESULT eq ""}
3333
3434
###############################################################################
3535
36
-fossil test-th-eval --th-open-config "setting -strict abc"
36
+fossil test-th-eval --open-config "setting -strict abc"
3737
test th1-setting-3 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
3838
3939
###############################################################################
4040
41
-fossil test-th-eval --th-open-config "setting -strict -- abc"
41
+fossil test-th-eval --open-config "setting -strict -- abc"
4242
test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
4343
4444
###############################################################################
4545
46
-fossil test-th-eval --th-open-config "setting autosync"
46
+fossil test-th-eval --open-config "setting autosync"
4747
test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1}
4848
4949
###############################################################################
5050
51
-fossil test-th-eval --th-open-config "setting -strict autosync"
51
+fossil test-th-eval --open-config "setting -strict autosync"
5252
test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1}
5353
5454
###############################################################################
5555
56
-fossil test-th-eval --th-open-config "setting --"
56
+fossil test-th-eval --open-config "setting --"
5757
test th1-setting-7 {$RESULT eq \
5858
{TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
5959
6060
###############################################################################
6161
62
-fossil test-th-eval --th-open-config "setting -strict --"
62
+fossil test-th-eval --open-config "setting -strict --"
6363
test th1-setting-8 {$RESULT eq \
6464
{TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
6565
6666
###############################################################################
6767
68
-fossil test-th-eval --th-open-config "setting -- --"
68
+fossil test-th-eval --open-config "setting -- --"
6969
test th1-setting-9 {$RESULT eq {}}
7070
7171
###############################################################################
7272
73
-fossil test-th-eval --th-open-config "setting -strict -- --"
73
+fossil test-th-eval --open-config "setting -strict -- --"
7474
test th1-setting-10 {$RESULT eq {TH_ERROR: no value for setting "--"}}
7575
7676
###############################################################################
7777
7878
fossil test-th-eval "expr 42/0"
@@ -595,26 +595,26 @@
595595
fossil test-th-eval "styleHeader {Page Title Here}"
596596
test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
597597
598598
###############################################################################
599599
600
-fossil test-th-eval --th-open-config "styleHeader {Page Title Here}"
600
+fossil test-th-eval --open-config "styleHeader {Page Title Here}"
601601
test th1-header-2 {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
602602
603603
###############################################################################
604604
605605
fossil test-th-eval "styleFooter"
606606
test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
607607
608608
###############################################################################
609609
610
-fossil test-th-eval --th-open-config "styleFooter"
610
+fossil test-th-eval --open-config "styleFooter"
611611
test th1-footer-2 {$RESULT eq {}}
612612
613613
###############################################################################
614614
615
-fossil test-th-eval --th-open-config --th-force-cgi "styleHeader {}; styleFooter"
615
+fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter"
616616
test th1-footer-3 {[regexp -- {</body></html>} $RESULT]}
617617
618618
###############################################################################
619619
620620
fossil test-th-eval "getParameter"
@@ -678,42 +678,42 @@
678678
fossil test-th-eval "artifact tip"
679679
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
680680
681681
###############################################################################
682682
683
-fossil test-th-eval --th-open-config "artifact tip"
683
+fossil test-th-eval --open-config "artifact tip"
684684
test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]}
685685
686686
###############################################################################
687687
688688
fossil test-th-eval "artifact 0000000000"
689689
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
690690
691691
###############################################################################
692692
693
-fossil test-th-eval --th-open-config "artifact 0000000000"
694
-test th1-artifact-5 {$RESULT eq {TH_ERROR: artifact not found}}
693
+fossil test-th-eval --open-config "artifact 0000000000"
694
+test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
695695
696696
###############################################################################
697697
698698
fossil test-th-eval "artifact tip test/th1.test"
699699
test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
700700
701701
###############################################################################
702702
703
-fossil test-th-eval --th-open-config "artifact tip test/th1.test"
703
+fossil test-th-eval --open-config "artifact tip test/th1.test"
704704
test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]}
705705
706706
###############################################################################
707707
708708
fossil test-th-eval "artifact 0000000000 test/th1.test"
709709
test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
710710
711711
###############################################################################
712712
713
-fossil test-th-eval --th-open-config "artifact 0000000000 test/th1.test"
714
-test th1-artifact-9 {$RESULT eq {TH_ERROR: artifact not found}}
713
+fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
714
+test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
715715
716716
###############################################################################
717717
718718
fossil test-th-eval "globalState checkout"
719719
test th1-globalState-1 {[string length $RESULT] > 0}
@@ -728,11 +728,11 @@
728728
fossil test-th-eval "globalState configuration"
729729
test th1-globalState-3 {[string length $RESULT] == 0}
730730
731731
###############################################################################
732732
733
-fossil test-th-eval --th-open-config "globalState configuration"
733
+fossil test-th-eval --open-config "globalState configuration"
734734
test th1-globalState-4 {[string length $RESULT] > 0}
735735
736736
###############################################################################
737737
738738
fossil test-th-eval "globalState executable"
739739
--- test/th1.test
+++ test/th1.test
@@ -16,63 +16,63 @@
16 ############################################################################
17 #
18 # TH1 Commands
19 #
20
21 fossil test-th-eval --th-open-config "setting th1-hooks"
22 set th1Hooks [expr {$RESULT eq "1"}]
23
24 ###############################################################################
25
26 fossil test-th-eval --th-open-config "setting abc"
27 test th1-setting-1 {$RESULT eq ""}
28
29 ###############################################################################
30
31 fossil test-th-eval --th-open-config "setting -- abc"
32 test th1-setting-2 {$RESULT eq ""}
33
34 ###############################################################################
35
36 fossil test-th-eval --th-open-config "setting -strict abc"
37 test th1-setting-3 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
38
39 ###############################################################################
40
41 fossil test-th-eval --th-open-config "setting -strict -- abc"
42 test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
43
44 ###############################################################################
45
46 fossil test-th-eval --th-open-config "setting autosync"
47 test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1}
48
49 ###############################################################################
50
51 fossil test-th-eval --th-open-config "setting -strict autosync"
52 test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1}
53
54 ###############################################################################
55
56 fossil test-th-eval --th-open-config "setting --"
57 test th1-setting-7 {$RESULT eq \
58 {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
59
60 ###############################################################################
61
62 fossil test-th-eval --th-open-config "setting -strict --"
63 test th1-setting-8 {$RESULT eq \
64 {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
65
66 ###############################################################################
67
68 fossil test-th-eval --th-open-config "setting -- --"
69 test th1-setting-9 {$RESULT eq {}}
70
71 ###############################################################################
72
73 fossil test-th-eval --th-open-config "setting -strict -- --"
74 test th1-setting-10 {$RESULT eq {TH_ERROR: no value for setting "--"}}
75
76 ###############################################################################
77
78 fossil test-th-eval "expr 42/0"
@@ -595,26 +595,26 @@
595 fossil test-th-eval "styleHeader {Page Title Here}"
596 test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
597
598 ###############################################################################
599
600 fossil test-th-eval --th-open-config "styleHeader {Page Title Here}"
601 test th1-header-2 {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
602
603 ###############################################################################
604
605 fossil test-th-eval "styleFooter"
606 test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
607
608 ###############################################################################
609
610 fossil test-th-eval --th-open-config "styleFooter"
611 test th1-footer-2 {$RESULT eq {}}
612
613 ###############################################################################
614
615 fossil test-th-eval --th-open-config --th-force-cgi "styleHeader {}; styleFooter"
616 test th1-footer-3 {[regexp -- {</body></html>} $RESULT]}
617
618 ###############################################################################
619
620 fossil test-th-eval "getParameter"
@@ -678,42 +678,42 @@
678 fossil test-th-eval "artifact tip"
679 test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
680
681 ###############################################################################
682
683 fossil test-th-eval --th-open-config "artifact tip"
684 test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]}
685
686 ###############################################################################
687
688 fossil test-th-eval "artifact 0000000000"
689 test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
690
691 ###############################################################################
692
693 fossil test-th-eval --th-open-config "artifact 0000000000"
694 test th1-artifact-5 {$RESULT eq {TH_ERROR: artifact not found}}
695
696 ###############################################################################
697
698 fossil test-th-eval "artifact tip test/th1.test"
699 test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
700
701 ###############################################################################
702
703 fossil test-th-eval --th-open-config "artifact tip test/th1.test"
704 test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]}
705
706 ###############################################################################
707
708 fossil test-th-eval "artifact 0000000000 test/th1.test"
709 test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
710
711 ###############################################################################
712
713 fossil test-th-eval --th-open-config "artifact 0000000000 test/th1.test"
714 test th1-artifact-9 {$RESULT eq {TH_ERROR: artifact not found}}
715
716 ###############################################################################
717
718 fossil test-th-eval "globalState checkout"
719 test th1-globalState-1 {[string length $RESULT] > 0}
@@ -728,11 +728,11 @@
728 fossil test-th-eval "globalState configuration"
729 test th1-globalState-3 {[string length $RESULT] == 0}
730
731 ###############################################################################
732
733 fossil test-th-eval --th-open-config "globalState configuration"
734 test th1-globalState-4 {[string length $RESULT] > 0}
735
736 ###############################################################################
737
738 fossil test-th-eval "globalState executable"
739
--- test/th1.test
+++ test/th1.test
@@ -16,63 +16,63 @@
16 ############################################################################
17 #
18 # TH1 Commands
19 #
20
21 fossil test-th-eval --open-config "setting th1-hooks"
22 set th1Hooks [expr {$RESULT eq "1"}]
23
24 ###############################################################################
25
26 fossil test-th-eval --open-config "setting abc"
27 test th1-setting-1 {$RESULT eq ""}
28
29 ###############################################################################
30
31 fossil test-th-eval --open-config "setting -- abc"
32 test th1-setting-2 {$RESULT eq ""}
33
34 ###############################################################################
35
36 fossil test-th-eval --open-config "setting -strict abc"
37 test th1-setting-3 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
38
39 ###############################################################################
40
41 fossil test-th-eval --open-config "setting -strict -- abc"
42 test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}}
43
44 ###############################################################################
45
46 fossil test-th-eval --open-config "setting autosync"
47 test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1}
48
49 ###############################################################################
50
51 fossil test-th-eval --open-config "setting -strict autosync"
52 test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1}
53
54 ###############################################################################
55
56 fossil test-th-eval --open-config "setting --"
57 test th1-setting-7 {$RESULT eq \
58 {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
59
60 ###############################################################################
61
62 fossil test-th-eval --open-config "setting -strict --"
63 test th1-setting-8 {$RESULT eq \
64 {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}
65
66 ###############################################################################
67
68 fossil test-th-eval --open-config "setting -- --"
69 test th1-setting-9 {$RESULT eq {}}
70
71 ###############################################################################
72
73 fossil test-th-eval --open-config "setting -strict -- --"
74 test th1-setting-10 {$RESULT eq {TH_ERROR: no value for setting "--"}}
75
76 ###############################################################################
77
78 fossil test-th-eval "expr 42/0"
@@ -595,26 +595,26 @@
595 fossil test-th-eval "styleHeader {Page Title Here}"
596 test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
597
598 ###############################################################################
599
600 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
601 test th1-header-2 {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
602
603 ###############################################################################
604
605 fossil test-th-eval "styleFooter"
606 test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
607
608 ###############################################################################
609
610 fossil test-th-eval --open-config "styleFooter"
611 test th1-footer-2 {$RESULT eq {}}
612
613 ###############################################################################
614
615 fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter"
616 test th1-footer-3 {[regexp -- {</body></html>} $RESULT]}
617
618 ###############################################################################
619
620 fossil test-th-eval "getParameter"
@@ -678,42 +678,42 @@
678 fossil test-th-eval "artifact tip"
679 test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
680
681 ###############################################################################
682
683 fossil test-th-eval --open-config "artifact tip"
684 test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]}
685
686 ###############################################################################
687
688 fossil test-th-eval "artifact 0000000000"
689 test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
690
691 ###############################################################################
692
693 fossil test-th-eval --open-config "artifact 0000000000"
694 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
695
696 ###############################################################################
697
698 fossil test-th-eval "artifact tip test/th1.test"
699 test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
700
701 ###############################################################################
702
703 fossil test-th-eval --open-config "artifact tip test/th1.test"
704 test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]}
705
706 ###############################################################################
707
708 fossil test-th-eval "artifact 0000000000 test/th1.test"
709 test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
710
711 ###############################################################################
712
713 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
714 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
715
716 ###############################################################################
717
718 fossil test-th-eval "globalState checkout"
719 test th1-globalState-1 {[string length $RESULT] > 0}
@@ -728,11 +728,11 @@
728 fossil test-th-eval "globalState configuration"
729 test th1-globalState-3 {[string length $RESULT] == 0}
730
731 ###############################################################################
732
733 fossil test-th-eval --open-config "globalState configuration"
734 test th1-globalState-4 {[string length $RESULT] > 0}
735
736 ###############################################################################
737
738 fossil test-th-eval "globalState executable"
739
--- 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_ENABLE_FTS4 -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_ENABLE_FTS3_PARENTHESIS -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_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.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_ENABLE_FTS3_PARENTHESIS -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 -DSQLITE_ENABLE_FTS4
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 -DSQLITE_ENABLE_FTS3_PARENTHESIS
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 -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.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 -DSQLITE_ENABLE_FTS3_PARENTHESIS
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
@@ -2025,10 +2025,11 @@
20252025
-DSQLITE_THREADSAFE=0 \
20262026
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
20272027
-DSQLITE_OMIT_DEPRECATED \
20282028
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
20292029
-DSQLITE_ENABLE_FTS4 \
2030
+ -DSQLITE_ENABLE_FTS3_PARENTHESIS \
20302031
-DSQLITE_WIN32_NO_ANSI \
20312032
-D_HAVE__MINGW_H \
20322033
-DSQLITE_USE_MALLOC_H \
20332034
-DSQLITE_USE_MSIZE
20342035
20352036
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -2025,10 +2025,11 @@
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.mingw
+++ win/Makefile.mingw
@@ -2025,10 +2025,11 @@
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_ENABLE_FTS3_PARENTHESIS \
2031 -DSQLITE_WIN32_NO_ANSI \
2032 -D_HAVE__MINGW_H \
2033 -DSQLITE_USE_MALLOC_H \
2034 -DSQLITE_USE_MSIZE
2035
2036
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -2024,10 +2024,12 @@
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 \
2030
+ -DSQLITE_ENABLE_FTS3_PARENTHESIS \
20292031
-DSQLITE_WIN32_NO_ANSI \
20302032
-D_HAVE__MINGW_H \
20312033
-DSQLITE_USE_MALLOC_H \
20322034
-DSQLITE_USE_MSIZE
20332035
20342036
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -2024,10 +2024,12 @@
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.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -2024,10 +2024,12 @@
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_ENABLE_FTS3_PARENTHESIS \
2031 -DSQLITE_WIN32_NO_ANSI \
2032 -D_HAVE__MINGW_H \
2033 -DSQLITE_USE_MALLOC_H \
2034 -DSQLITE_USE_MSIZE
2035
2036
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -190,10 +190,11 @@
190190
/DSQLITE_THREADSAFE=0 \
191191
/DSQLITE_DEFAULT_FILE_FORMAT=4 \
192192
/DSQLITE_OMIT_DEPRECATED \
193193
/DSQLITE_ENABLE_EXPLAIN_COMMENTS \
194194
/DSQLITE_ENABLE_FTS4 \
195
+ /DSQLITE_ENABLE_FTS3_PARENTHESIS \
195196
/DSQLITE_WIN32_NO_ANSI
196197
197198
SHELL_OPTIONS = /Dmain=sqlite3_shell \
198199
/DSQLITE_OMIT_LOAD_EXTENSION=1 \
199200
/DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
200201
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -190,10 +190,11 @@
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
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -190,10 +190,11 @@
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_ENABLE_FTS3_PARENTHESIS \
196 /DSQLITE_WIN32_NO_ANSI
197
198 SHELL_OPTIONS = /Dmain=sqlite3_shell \
199 /DSQLITE_OMIT_LOAD_EXTENSION=1 \
200 /DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
201
--- www/branching.wiki
+++ www/branching.wiki
@@ -180,11 +180,11 @@
180180
181181
A tag can be a one-time tag, a propagating tag or a cancellation tag.
182182
A one-time tag only applies to the check-in to which it is attached. A
183183
propagating tag applies to the check-in to which it is attached and also
184184
to all direct descendants of that check-in. A <i>direct descendant</i>
185
-is a descendant through direct children. Tags propagation does not
185
+is a descendant through direct children. Tag propagation does not
186186
cross merges. Tag propagation also stops as soon
187187
as it encounters another check-in with the same tag. A cancellation tag
188188
is attached to a single check-in in order to either override a one-time
189189
tag that was previously placed on that same check-in, or to block
190190
tag propagation from an ancestor.
191191
--- www/branching.wiki
+++ www/branching.wiki
@@ -180,11 +180,11 @@
180
181 A tag can be a one-time tag, a propagating tag or a cancellation tag.
182 A one-time tag only applies to the check-in to which it is attached. A
183 propagating tag applies to the check-in to which it is attached and also
184 to all direct descendants of that check-in. A <i>direct descendant</i>
185 is a descendant through direct children. Tags propagation does not
186 cross merges. Tag propagation also stops as soon
187 as it encounters another check-in with the same tag. A cancellation tag
188 is attached to a single check-in in order to either override a one-time
189 tag that was previously placed on that same check-in, or to block
190 tag propagation from an ancestor.
191
--- www/branching.wiki
+++ www/branching.wiki
@@ -180,11 +180,11 @@
180
181 A tag can be a one-time tag, a propagating tag or a cancellation tag.
182 A one-time tag only applies to the check-in to which it is attached. A
183 propagating tag applies to the check-in to which it is attached and also
184 to all direct descendants of that check-in. A <i>direct descendant</i>
185 is a descendant through direct children. Tag propagation does not
186 cross merges. Tag propagation also stops as soon
187 as it encounters another check-in with the same tag. A cancellation tag
188 is attached to a single check-in in order to either override a one-time
189 tag that was previously placed on that same check-in, or to block
190 tag propagation from an ancestor.
191
+48 -15
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,26 +1,59 @@
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]. (??)
3
+<h2>Changes For Version 1.31 (2015-02-23)</h2>
4
+ * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID
5
+ columns to the schema, to support better drawing of file change graphs.
6
+ A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required.
7
+ so that the new graph drawing logic can work effectively.
8
+ * Added [/search|search] over Check-in comments, Documents, Tickets and
9
+ Wiki. Disabled by default. The search can be either a full-scan or it
10
+ can use an index that is kept up-to-date automatically. The new
11
+ /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command
12
+ were added to help configure the search capability. Expect further
13
+ enhancements to the search capabilities in subsequent releases.
14
+ * Added form elements to some submenus (in particular the /timeline)
15
+ for easier operation.
16
+ * Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild].
17
+ * Added "override skins" using the "skin:" line of the CGI script or
18
+ using the --skin LABEL option on the
19
+ [/help?cmd=server|server],
20
+ [/help?cmd=ui|ui], or
21
+ [/help?cmd=http|http] commands.
22
+ * Embedded html documents that begin with
23
+ &lt;doc class="fossil-doc"&gt; are displayed with standard
24
+ headers and footers added.
25
+ * Allow &lt;div style='...'&gt; markup in [/wiki_rules|wiki].
26
+ * Renamed "Events" to "Technical Notes", while updating the technote
27
+ display and control pages. Add support for technotes as plain text
28
+ or as Markdown.
29
+ * Added the [/md_rules] pages containing summary instructions on the
30
+ Markdown format.
31
+ * Added the --repolist and --nojail options to the various server commands
32
+ (ex: [/help?cmd=server|fossil server]).
33
+ * Added the [/help?cmd=all|fossil all add] subcommand to "fossil all".
34
+ * Improvements to the /login page. Some hyperlinks to pages that require
35
+ "anonymous" privileges are displayed even if the current user is "nobody"
36
+ but automatically redirect to /login.
37
+ * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file
38
+ "404.md" from the top-level directory (if such a file exists) in
39
+ place of its built-in 404 text.
40
+ * Download of Tarballs and ZIP Archives by user "nobody" is now enabled
41
+ by default in new repositories.
42
+ * Enhancements to the table sorting controls. More display tables
43
+ are now sortable.
644
* Add IPv6 support to [/help?cmd=sync|fossil sync] and
745
[/help?cmd=clone|fossil clone]
846
* 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.
2047
* During shutdown, check to see if the check-out database (".fslckout")
21
- contains a lot of free space, and if it does, VACUUM it.
48
+ contains a lot of free space, and if it does, VACUUM it.
49
+ * Added the [/mimetype_list] page.
50
+ * Added the [/hash-collisions] page.
51
+ * Allow the user of Common Table Expressions in the SQL that defaults
52
+ ticket reports.
53
+ * Break out the components (css, footer, and header) for the
54
+ various built-in skins into separate files in the source tree.
2255
2356
<h2>Changes For Version 1.30 (2015-01-19)</h2>
2457
* Added the [/help?cmd=bundle|fossil bundle] command.
2558
* Added the [/help?cmd=purge|fossil purge] command.
2659
* Added the [/help?cmd=publish|fossil publish] command.
2760
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,26 +1,59 @@
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
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,26 +1,59 @@
1 <title>Change Log</title>
2
3 <h2>Changes For Version 1.31 (2015-02-23)</h2>
4 * Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID
5 columns to the schema, to support better drawing of file change graphs.
6 A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required.
7 so that the new graph drawing logic can work effectively.
8 * Added [/search|search] over Check-in comments, Documents, Tickets and
9 Wiki. Disabled by default. The search can be either a full-scan or it
10 can use an index that is kept up-to-date automatically. The new
11 /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command
12 were added to help configure the search capability. Expect further
13 enhancements to the search capabilities in subsequent releases.
14 * Added form elements to some submenus (in particular the /timeline)
15 for easier operation.
16 * Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild].
17 * Added "override skins" using the "skin:" line of the CGI script or
18 using the --skin LABEL option on the
19 [/help?cmd=server|server],
20 [/help?cmd=ui|ui], or
21 [/help?cmd=http|http] commands.
22 * Embedded html documents that begin with
23 &lt;doc class="fossil-doc"&gt; are displayed with standard
24 headers and footers added.
25 * Allow &lt;div style='...'&gt; markup in [/wiki_rules|wiki].
26 * Renamed "Events" to "Technical Notes", while updating the technote
27 display and control pages. Add support for technotes as plain text
28 or as Markdown.
29 * Added the [/md_rules] pages containing summary instructions on the
30 Markdown format.
31 * Added the --repolist and --nojail options to the various server commands
32 (ex: [/help?cmd=server|fossil server]).
33 * Added the [/help?cmd=all|fossil all add] subcommand to "fossil all".
34 * Improvements to the /login page. Some hyperlinks to pages that require
35 "anonymous" privileges are displayed even if the current user is "nobody"
36 but automatically redirect to /login.
37 * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file
38 "404.md" from the top-level directory (if such a file exists) in
39 place of its built-in 404 text.
40 * Download of Tarballs and ZIP Archives by user "nobody" is now enabled
41 by default in new repositories.
42 * Enhancements to the table sorting controls. More display tables
43 are now sortable.
44 * Add IPv6 support to [/help?cmd=sync|fossil sync] and
45 [/help?cmd=clone|fossil clone]
46 * Add more skins such as "San Francisco Modern" and "Eagle".
 
 
 
 
 
 
 
 
 
 
 
47 * During shutdown, check to see if the check-out database (".fslckout")
48 contains a lot of free space, and if it does, VACUUM it.
49 * Added the [/mimetype_list] page.
50 * Added the [/hash-collisions] page.
51 * Allow the user of Common Table Expressions in the SQL that defaults
52 ticket reports.
53 * Break out the components (css, footer, and header) for the
54 various built-in skins into separate files in the source tree.
55
56 <h2>Changes For Version 1.30 (2015-01-19)</h2>
57 * Added the [/help?cmd=bundle|fossil bundle] command.
58 * Added the [/help?cmd=purge|fossil purge] command.
59 * Added the [/help?cmd=publish|fossil publish] command.
60
+25 -64
--- www/concepts.wiki
+++ www/concepts.wiki
@@ -197,11 +197,15 @@
197197
is identified by your VISUAL environment variable. Fossil will also
198198
use GPG to clearsign your manifests if you happen to have it installed,
199199
but fossil will skip that step if GPG missing from your system.
200200
You can optionally set up fossil to use external "diff" programs,
201201
though fossil has an excellent built-in "diff" algorithm that works
202
-fine for most people.
202
+fine for most people. If you happen to have Tcl/Tk installed on your
203
+system, Fossil will use it to generate a graphical "diff" display when
204
+you use the --tk option to the "diff" command, but this too is entirely
205
+optional.
206
+
203207
204208
To uninstall fossil, simply delete the executable.
205209
206210
To upgrade an older version of fossil to a newer version, just
207211
replace the old executable with the new one. You might need to
@@ -391,75 +395,32 @@
391395
<h2>5.0 Setting Up A Fossil Server</h2>
392396
393397
With other configuration management software, setting up a server is
394398
a lot of work and normally takes time, patience, and a lot of system
395399
knowledge. Fossil is designed to avoid this frustration. Setting up
396
-a server with fossil is ridiculously easy. You have three options:</p>
400
+a server with fossil is ridiculously easy. You have four options:</p>
397401
398402
<ol>
399
-<li><b><a name="saserv"></a>Setting up a stand-alone server</b>
400
-
401
-From within your source tree just use the <b>server</b> command and
402
-fossil will start listening for incoming requests on TCP port 8080.
403
-You can point your web browser at <a href="http://localhost:8080/">
404
-http://localhost:8080/</a> and begin exploring. Or your coworkers
405
-can do pushes or pulls against your server. Use the <b>--port</b>
406
-option to the server command to specify a different TCP port. If
407
-you do not have a local source tree, use the <b>-R</b> command-line
408
-option to specify the repository file.
409
-
410
-The "fossil server" command is a great way to set of transient connections
411
-between coworkers for doing quick pushes or pulls. But you can also
412
-set up a permanent stand-alone server if you prefer. Just make
413
-arrangements for fossil to be launched with appropriate arguments
414
-after every reboot.
415
-
416
-If you just want a server to browse the built-in fossil website
417
-locally, use the <b>ui</b> command in place of <b>server</b>. The
418
-<b>ui</b> command starts up a local server too, but it also takes
419
-the additional step of automatically launching your webbrowser and
420
-pointing at the new server.
421
-</li>
422
-
423
-<li><b>Setting up a CGI server</b>
424
-
425
-If you have a web-server running on your machine already, you can
426
-set up fossil to be run from CGI. Simply create an executable script
427
-that looks something like this:
428
-
429
-<blockquote><pre>
430
-#!/usr/local/bin/fossil
431
-repository: /home/me/bigproject.fossil
432
-</pre></blockquote>
433
-
434
-Edit this script to use whatever pathnames are appropriate for
435
-your project. Then point your web browser at the script and off you
436
-go. The [./selfhost.wiki | self-hosting fossil repositories] are
437
-all set up this way.</li>
438
-
439
-<li><b>Setting up an inetd server</b>
440
-
441
-If you have inetd or xinetd running on your system, you can set
442
-those services up to launch fossil to deal with inbound TCP/IP connections
443
-on whatever port you want. Set up inetd or xinetd to launch fossil
444
-like this:
445
-
446
-<blockquote><pre>
447
-/usr/local/bin/fossil http /home/me/bigproject.fossil
448
-</pre></blockquote>
449
-
450
-As before, change the filenames to whatever is appropriate for
451
-your system. You can have fossil run as any user that has write
452
-permission on the repository and on the directory that contains the
453
-repository. But it is safer to run fossil as root. When fossil
454
-sees that it is running as root, it automatically puts itself into
455
-a <a href="http://en.wikipedia.org/wiki/Chroot">chroot jail</a> and
456
-drops all privileges prior to reading any information from the client.
457
-Since fossil is a stand-alone program, you do not need to put anything
458
-in the chroot jail with fossil in order for it to do its job.
459
-</li>
460
-</ol>
403
+<li><p><b>Stand-alone server.</b>
404
+Simply run the [/help?cmd=server|fossil server] or
405
+[/help?cmd=ui|fossil ui] command from the command-line.
406
+
407
+<li><p><b>CGI.</b>
408
+Install a 2-line CGI script on a CGI-enabled web-server like Apache.
409
+
410
+<li><p><b>SCGI.</b>
411
+Start an SCGI server using the
412
+[/help?cmd=server| fossil server --scgi] command for handling
413
+SCGI requests from web-servers like Nginx.
414
+
415
+<li><p><b>Inetd or Stunnel.</b>
416
+Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests
417
+directly to the [/help?cmd=http|fossil http] command.
418
+</ol>
419
+
420
+See the [./server.wiki | How To Configure A Fossil Server] document
421
+for details.
461422
462423
<h2>6.0 Review Of Key Concepts</h2>
463424
464425
<ul>
465426
<li>The <b>fossil</b> program is a self-contained stand-alone executable.
466427
467428
ADDED www/customskin.md
--- www/concepts.wiki
+++ www/concepts.wiki
@@ -197,11 +197,15 @@
197 is identified by your VISUAL environment variable. Fossil will also
198 use GPG to clearsign your manifests if you happen to have it installed,
199 but fossil will skip that step if GPG missing from your system.
200 You can optionally set up fossil to use external "diff" programs,
201 though fossil has an excellent built-in "diff" algorithm that works
202 fine for most people.
 
 
 
 
203
204 To uninstall fossil, simply delete the executable.
205
206 To upgrade an older version of fossil to a newer version, just
207 replace the old executable with the new one. You might need to
@@ -391,75 +395,32 @@
391 <h2>5.0 Setting Up A Fossil Server</h2>
392
393 With other configuration management software, setting up a server is
394 a lot of work and normally takes time, patience, and a lot of system
395 knowledge. Fossil is designed to avoid this frustration. Setting up
396 a server with fossil is ridiculously easy. You have three options:</p>
397
398 <ol>
399 <li><b><a name="saserv"></a>Setting up a stand-alone server</b>
400
401 From within your source tree just use the <b>server</b> command and
402 fossil will start listening for incoming requests on TCP port 8080.
403 You can point your web browser at <a href="http://localhost:8080/">
404 http://localhost:8080/</a> and begin exploring. Or your coworkers
405 can do pushes or pulls against your server. Use the <b>--port</b>
406 option to the server command to specify a different TCP port. If
407 you do not have a local source tree, use the <b>-R</b> command-line
408 option to specify the repository file.
409
410 The "fossil server" command is a great way to set of transient connections
411 between coworkers for doing quick pushes or pulls. But you can also
412 set up a permanent stand-alone server if you prefer. Just make
413 arrangements for fossil to be launched with appropriate arguments
414 after every reboot.
415
416 If you just want a server to browse the built-in fossil website
417 locally, use the <b>ui</b> command in place of <b>server</b>. The
418 <b>ui</b> command starts up a local server too, but it also takes
419 the additional step of automatically launching your webbrowser and
420 pointing at the new server.
421 </li>
422
423 <li><b>Setting up a CGI server</b>
424
425 If you have a web-server running on your machine already, you can
426 set up fossil to be run from CGI. Simply create an executable script
427 that looks something like this:
428
429 <blockquote><pre>
430 #!/usr/local/bin/fossil
431 repository: /home/me/bigproject.fossil
432 </pre></blockquote>
433
434 Edit this script to use whatever pathnames are appropriate for
435 your project. Then point your web browser at the script and off you
436 go. The [./selfhost.wiki | self-hosting fossil repositories] are
437 all set up this way.</li>
438
439 <li><b>Setting up an inetd server</b>
440
441 If you have inetd or xinetd running on your system, you can set
442 those services up to launch fossil to deal with inbound TCP/IP connections
443 on whatever port you want. Set up inetd or xinetd to launch fossil
444 like this:
445
446 <blockquote><pre>
447 /usr/local/bin/fossil http /home/me/bigproject.fossil
448 </pre></blockquote>
449
450 As before, change the filenames to whatever is appropriate for
451 your system. You can have fossil run as any user that has write
452 permission on the repository and on the directory that contains the
453 repository. But it is safer to run fossil as root. When fossil
454 sees that it is running as root, it automatically puts itself into
455 a <a href="http://en.wikipedia.org/wiki/Chroot">chroot jail</a> and
456 drops all privileges prior to reading any information from the client.
457 Since fossil is a stand-alone program, you do not need to put anything
458 in the chroot jail with fossil in order for it to do its job.
459 </li>
460 </ol>
461
462 <h2>6.0 Review Of Key Concepts</h2>
463
464 <ul>
465 <li>The <b>fossil</b> program is a self-contained stand-alone executable.
466
467 DDED www/customskin.md
--- www/concepts.wiki
+++ www/concepts.wiki
@@ -197,11 +197,15 @@
197 is identified by your VISUAL environment variable. Fossil will also
198 use GPG to clearsign your manifests if you happen to have it installed,
199 but fossil will skip that step if GPG missing from your system.
200 You can optionally set up fossil to use external "diff" programs,
201 though fossil has an excellent built-in "diff" algorithm that works
202 fine for most people. If you happen to have Tcl/Tk installed on your
203 system, Fossil will use it to generate a graphical "diff" display when
204 you use the --tk option to the "diff" command, but this too is entirely
205 optional.
206
207
208 To uninstall fossil, simply delete the executable.
209
210 To upgrade an older version of fossil to a newer version, just
211 replace the old executable with the new one. You might need to
@@ -391,75 +395,32 @@
395 <h2>5.0 Setting Up A Fossil Server</h2>
396
397 With other configuration management software, setting up a server is
398 a lot of work and normally takes time, patience, and a lot of system
399 knowledge. Fossil is designed to avoid this frustration. Setting up
400 a server with fossil is ridiculously easy. You have four options:</p>
401
402 <ol>
403 <li><p><b>Stand-alone server.</b>
404 Simply run the [/help?cmd=server|fossil server] or
405 [/help?cmd=ui|fossil ui] command from the command-line.
406
407 <li><p><b>CGI.</b>
408 Install a 2-line CGI script on a CGI-enabled web-server like Apache.
409
410 <li><p><b>SCGI.</b>
411 Start an SCGI server using the
412 [/help?cmd=server| fossil server --scgi] command for handling
413 SCGI requests from web-servers like Nginx.
414
415 <li><p><b>Inetd or Stunnel.</b>
416 Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests
417 directly to the [/help?cmd=http|fossil http] command.
418 </ol>
419
420 See the [./server.wiki | How To Configure A Fossil Server] document
421 for details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
423 <h2>6.0 Review Of Key Concepts</h2>
424
425 <ul>
426 <li>The <b>fossil</b> program is a self-contained stand-alone executable.
427
428 DDED www/customskin.md
--- a/www/customskin.md
+++ b/www/customskin.md
@@ -0,0 +1,5 @@
1
+Theming
2
+=======onal)default header looks# Sk</div
3
+Tcontent sectionnd the footer# SkNoticee that there are no `<html>` or `<head>` elements in the header,
4
+nor three files,three under the
5
+ Admin menu.
--- a/www/customskin.md
+++ b/www/customskin.md
@@ -0,0 +1,5 @@
 
 
 
 
 
--- a/www/customskin.md
+++ b/www/customskin.md
@@ -0,0 +1,5 @@
1 Theming
2 =======onal)default header looks# Sk</div
3 Tcontent sectionnd the footer# SkNoticee that there are no `<html>` or `<head>` elements in the header,
4 nor three files,three under the
5 Admin menu.
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -1,7 +1,7 @@
1
-<title>Managing Project Documentation</title>
2
-<h1 align="center">Managing Project Documentation</h1>
1
+<title>Project Documentation</title>
2
+<h1 align="center">Project Documentation</h1>
33
44
Fossil provides a built-in <a href="wikitheory.wiki">wiki</a>
55
that can be used to store the
66
documentation for a project. This is sufficient for many projects.
77
If your project is well-served by wiki documentation, then you
@@ -63,26 +63,40 @@
6363
Finally, the <i>&lt;filename&gt;</i> element of the URL is the
6464
pathname of the documentation file relative to the root of the source
6565
tree.
6666
6767
The mimetype (and thus the rendering) of documentation files is
68
-determined by the file suffix. Fossil currently understands 197
69
-different file suffixes, including all the popular ones such as
70
-".css", ".gif", ".htm", ".html", ".jpg", ".jpeg", ".png", and ".txt".
68
+determined by the file suffix. Fossil currently understands
69
+[/mimetype_list|many different file suffixes],
70
+including all the popular ones such as ".css", ".gif", ".htm",
71
+".html", ".jpg", ".jpeg", ".png", and ".txt".
7172
7273
Documentation files whose names end in ".wiki" use the
7374
[/wiki_rules | same markup as wiki pages] -
7475
a safe subset of HTML together with some wiki rules for paragraph
7576
breaks, lists, and hyperlinks.
7677
Documentation files ending in ".md" or ".markdown" use the
77
-Markdown markup langauge.
78
+[/md_rules | Markdown markup langauge].
7879
Documentation files ending in ".txt" are plain text.
7980
Wiki, markdown, and plain text documentation files
8081
are rendered with the standard fossil header and footer added.
81
-All other mimetypes (including ".html" files)
82
-are delivered directly to the requesting
82
+Most other mimetypes are delivered directly to the requesting
8383
web browser without interpretation, additions, or changes.
84
+
85
+Files with the mimetype "text/html" (the .html or .htm suffix) are
86
+usually rendered directly to the browser without interpretation.
87
+However, if the file begins with a &lt;div&gt; element like this:
88
+
89
+ <b>&lt;div class='fossil-doc' data-title='<i>Title Text</i>'&gt;</b>
90
+
91
+Then the standard Fossil header and footer are added to the document
92
+prior to being displayed. The "class='fossil-doc'" attribute is
93
+required for this to occur. The "data-title='...'" attribute is
94
+optional, but if it is present the text will become the title displayed
95
+in the Fossil header. An example of this can be seen in the text
96
+of the [/artifact/84b4b3d041d93a?txt=1 | Index Of Fossil Documentation]
97
+document.
8498
8599
<h2>Examples</h2>
86100
87101
This file that you are currently reading is an example of
88102
embedded documentation. The name of this file in the fossil
89103
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -1,7 +1,7 @@
1 <title>Managing Project Documentation</title>
2 <h1 align="center">Managing Project Documentation</h1>
3
4 Fossil provides a built-in <a href="wikitheory.wiki">wiki</a>
5 that can be used to store the
6 documentation for a project. This is sufficient for many projects.
7 If your project is well-served by wiki documentation, then you
@@ -63,26 +63,40 @@
63 Finally, the <i>&lt;filename&gt;</i> element of the URL is the
64 pathname of the documentation file relative to the root of the source
65 tree.
66
67 The mimetype (and thus the rendering) of documentation files is
68 determined by the file suffix. Fossil currently understands 197
69 different file suffixes, including all the popular ones such as
70 ".css", ".gif", ".htm", ".html", ".jpg", ".jpeg", ".png", and ".txt".
 
71
72 Documentation files whose names end in ".wiki" use the
73 [/wiki_rules | same markup as wiki pages] -
74 a safe subset of HTML together with some wiki rules for paragraph
75 breaks, lists, and hyperlinks.
76 Documentation files ending in ".md" or ".markdown" use the
77 Markdown markup langauge.
78 Documentation files ending in ".txt" are plain text.
79 Wiki, markdown, and plain text documentation files
80 are rendered with the standard fossil header and footer added.
81 All other mimetypes (including ".html" files)
82 are delivered directly to the requesting
83 web browser without interpretation, additions, or changes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
85 <h2>Examples</h2>
86
87 This file that you are currently reading is an example of
88 embedded documentation. The name of this file in the fossil
89
--- www/embeddeddoc.wiki
+++ www/embeddeddoc.wiki
@@ -1,7 +1,7 @@
1 <title>Project Documentation</title>
2 <h1 align="center">Project Documentation</h1>
3
4 Fossil provides a built-in <a href="wikitheory.wiki">wiki</a>
5 that can be used to store the
6 documentation for a project. This is sufficient for many projects.
7 If your project is well-served by wiki documentation, then you
@@ -63,26 +63,40 @@
63 Finally, the <i>&lt;filename&gt;</i> element of the URL is the
64 pathname of the documentation file relative to the root of the source
65 tree.
66
67 The mimetype (and thus the rendering) of documentation files is
68 determined by the file suffix. Fossil currently understands
69 [/mimetype_list|many different file suffixes],
70 including all the popular ones such as ".css", ".gif", ".htm",
71 ".html", ".jpg", ".jpeg", ".png", and ".txt".
72
73 Documentation files whose names end in ".wiki" use the
74 [/wiki_rules | same markup as wiki pages] -
75 a safe subset of HTML together with some wiki rules for paragraph
76 breaks, lists, and hyperlinks.
77 Documentation files ending in ".md" or ".markdown" use the
78 [/md_rules | Markdown markup langauge].
79 Documentation files ending in ".txt" are plain text.
80 Wiki, markdown, and plain text documentation files
81 are rendered with the standard fossil header and footer added.
82 Most other mimetypes are delivered directly to the requesting
 
83 web browser without interpretation, additions, or changes.
84
85 Files with the mimetype "text/html" (the .html or .htm suffix) are
86 usually rendered directly to the browser without interpretation.
87 However, if the file begins with a &lt;div&gt; element like this:
88
89 <b>&lt;div class='fossil-doc' data-title='<i>Title Text</i>'&gt;</b>
90
91 Then the standard Fossil header and footer are added to the document
92 prior to being displayed. The "class='fossil-doc'" attribute is
93 required for this to occur. The "data-title='...'" attribute is
94 optional, but if it is present the text will become the title displayed
95 in the Fossil header. An example of this can be seen in the text
96 of the [/artifact/84b4b3d041d93a?txt=1 | Index Of Fossil Documentation]
97 document.
98
99 <h2>Examples</h2>
100
101 This file that you are currently reading is an example of
102 embedded documentation. The name of this file in the fossil
103
+30 -30
--- www/event.wiki
+++ www/event.wiki
@@ -1,19 +1,21 @@
1
-<title>Events</title>
1
+<title>Technical Notes</title>
22
3
-<h2>What Is An "Event"?</h2>
3
+<h2>What Is A "Technote"?</h2>
44
5
-In Fossil, and "event" is a special kind of [./wikitheory.wiki | wiki page]
5
+In Fossil, a "technical note" or "technote" (formerly called an "event")
6
+is a special kind of [./wikitheory.wiki | wiki page]
67
that is associated with a point in time rather than having a page name.
7
-Each event causes a single entry to appear on the [/timeline | Timeline Page].
8
-Clicking on the hyperlink of the timeline entry cause a jump to the wiki
9
-content for the event. The wiki content, the timeline entry text, the
8
+Each technote causes a single entry to appear on the
9
+[/timeline?y=e | Timeline Page].
10
+Clicking on the timeline link will display the text of the technote.
11
+The wiki content, the timeline entry text, the
1012
time of the event, and the timeline background color can all be edited.
1113
12
-As with check-ins, wiki, and tickets, all events automatically synchronize
13
-to other repositories. Hence, events can be viewed, created, and edited
14
-off-line. And the complete edit history for events is maintained
14
+As with check-ins, wiki, and tickets, all technotes automatically synchronize
15
+to other repositories. Hence, technotes can be viewed, created, and edited
16
+off-line. And the complete edit history for technotes is maintained
1517
for auditing purposes.
1618
1719
Possible uses for events include:
1820
1921
* <b>Milestones</b>. Project milestones, such as releases or beta-test
@@ -41,35 +43,33 @@
4143
4244
* <b>Announcements</b>. Changes to the composition of the development
4345
team or acquisition of new project sponsors can be communicated as
4446
announcements which can be implemented as events.
4547
46
-No project is required to use events. But events can help many projects
48
+No project is required to use technotes. But technotes can help many projects
4749
stay better organized and provide a better historical record of the
4850
development progress.
4951
50
-<h2>Viewing Events</h2>
52
+<h2>Viewing Technotes</h2>
5153
52
-Because events are considered a special kind of wiki,
54
+Because technotes are considered a special kind of wiki,
5355
users must have permission to read wiki in order read events.
5456
Enable the "j" permission under the /Setup/Users menu in order
5557
to give specific users or user classes the ability to view wiki
56
-and events.
57
-
58
-Events show up on the timeline. Click on the hyperlink beside the
59
-event title to see the details of the event.
60
-
61
-<h2>Creating And Editing Events</h2>
62
-
63
-There is a hyperlink under the /Wiki menu that can be used to create
64
-new events. And there is a submenu hyperlink on event displays for
65
-editing existing events.
58
+and technotes.
59
+
60
+Technotes show up on the timeline. Click on the hyperlink beside the
61
+technote title to see the complete text.
62
+
63
+<h2>Creating And Editing Technotes</h2>
64
+
65
+There is a hyperlink under the /wikihelp menu that can be used to create
66
+new technotes. And there is a submenu hyperlink on technote displays for
67
+editing existing technotes.
6668
6769
Users must have check-in privileges (permission "i") in order to
68
-create or edit events. In addition, users must have create-wiki
69
-privilege (permission "f") to create new events and edit-wiki
70
-privilege (permission "k") in order to edit existing events.
71
-
72
-If the first non-whitespace text of the event wiki content is
73
-&lt;title&gt;...&lt;/title&gt; then that markup is omitted from
74
-the body of the wiki pages and is instead displayed as the page
75
-title.
70
+create or edit technotes. In addition, users must have create-wiki
71
+privilege (permission "f") to create new technotes and edit-wiki
72
+privilege (permission "k") in order to edit existing technotes.
73
+
74
+Technote content may be formatted as [/wiki_rules | Fossil wiki],
75
+[/md_rules | Markdown], or a plain text.
7676
--- www/event.wiki
+++ www/event.wiki
@@ -1,19 +1,21 @@
1 <title>Events</title>
2
3 <h2>What Is An "Event"?</h2>
4
5 In Fossil, and "event" is a special kind of [./wikitheory.wiki | wiki page]
 
6 that is associated with a point in time rather than having a page name.
7 Each event causes a single entry to appear on the [/timeline | Timeline Page].
8 Clicking on the hyperlink of the timeline entry cause a jump to the wiki
9 content for the event. The wiki content, the timeline entry text, the
 
10 time of the event, and the timeline background color can all be edited.
11
12 As with check-ins, wiki, and tickets, all events automatically synchronize
13 to other repositories. Hence, events can be viewed, created, and edited
14 off-line. And the complete edit history for events is maintained
15 for auditing purposes.
16
17 Possible uses for events include:
18
19 * <b>Milestones</b>. Project milestones, such as releases or beta-test
@@ -41,35 +43,33 @@
41
42 * <b>Announcements</b>. Changes to the composition of the development
43 team or acquisition of new project sponsors can be communicated as
44 announcements which can be implemented as events.
45
46 No project is required to use events. But events can help many projects
47 stay better organized and provide a better historical record of the
48 development progress.
49
50 <h2>Viewing Events</h2>
51
52 Because events are considered a special kind of wiki,
53 users must have permission to read wiki in order read events.
54 Enable the "j" permission under the /Setup/Users menu in order
55 to give specific users or user classes the ability to view wiki
56 and events.
57
58 Events show up on the timeline. Click on the hyperlink beside the
59 event title to see the details of the event.
60
61 <h2>Creating And Editing Events</h2>
62
63 There is a hyperlink under the /Wiki menu that can be used to create
64 new events. And there is a submenu hyperlink on event displays for
65 editing existing events.
66
67 Users must have check-in privileges (permission "i") in order to
68 create or edit events. In addition, users must have create-wiki
69 privilege (permission "f") to create new events and edit-wiki
70 privilege (permission "k") in order to edit existing events.
71
72 If the first non-whitespace text of the event wiki content is
73 &lt;title&gt;...&lt;/title&gt; then that markup is omitted from
74 the body of the wiki pages and is instead displayed as the page
75 title.
76
--- www/event.wiki
+++ www/event.wiki
@@ -1,19 +1,21 @@
1 <title>Technical Notes</title>
2
3 <h2>What Is A "Technote"?</h2>
4
5 In Fossil, a "technical note" or "technote" (formerly called an "event")
6 is a special kind of [./wikitheory.wiki | wiki page]
7 that is associated with a point in time rather than having a page name.
8 Each technote causes a single entry to appear on the
9 [/timeline?y=e | Timeline Page].
10 Clicking on the timeline link will display the text of the technote.
11 The wiki content, the timeline entry text, the
12 time of the event, and the timeline background color can all be edited.
13
14 As with check-ins, wiki, and tickets, all technotes automatically synchronize
15 to other repositories. Hence, technotes can be viewed, created, and edited
16 off-line. And the complete edit history for technotes is maintained
17 for auditing purposes.
18
19 Possible uses for events include:
20
21 * <b>Milestones</b>. Project milestones, such as releases or beta-test
@@ -41,35 +43,33 @@
43
44 * <b>Announcements</b>. Changes to the composition of the development
45 team or acquisition of new project sponsors can be communicated as
46 announcements which can be implemented as events.
47
48 No project is required to use technotes. But technotes can help many projects
49 stay better organized and provide a better historical record of the
50 development progress.
51
52 <h2>Viewing Technotes</h2>
53
54 Because technotes are considered a special kind of wiki,
55 users must have permission to read wiki in order read events.
56 Enable the "j" permission under the /Setup/Users menu in order
57 to give specific users or user classes the ability to view wiki
58 and technotes.
59
60 Technotes show up on the timeline. Click on the hyperlink beside the
61 technote title to see the complete text.
62
63 <h2>Creating And Editing Technotes</h2>
64
65 There is a hyperlink under the /wikihelp menu that can be used to create
66 new technotes. And there is a submenu hyperlink on technote displays for
67 editing existing technotes.
68
69 Users must have check-in privileges (permission "i") in order to
70 create or edit technotes. In addition, users must have create-wiki
71 privilege (permission "f") to create new technotes and edit-wiki
72 privilege (permission "k") in order to edit existing technotes.
73
74 Technote content may be formatted as [/wiki_rules | Fossil wiki],
75 [/md_rules | Markdown], or a plain text.
 
 
76
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -43,11 +43,11 @@
4343
<li> [#cluster | Clusters] </li>
4444
<li> [#ctrl | Control Artifacts] </li>
4545
<li> [#wikichng | Wiki Pages] </li>
4646
<li> [#tktchng | Ticket Changes] </li>
4747
<li> [#attachment | Attachments] </li>
48
-<li> [#event | Events] </li>
48
+<li> [#event | TechNotes] </li>
4949
</ul>
5050
5151
These seven artifact types are described in the following sections.
5252
5353
In the current implementation (as of 2009-01-25) the artifacts that
@@ -423,11 +423,12 @@
423423
424424
<a name="attachment"></a>
425425
<h2>6.0 Attachments</h2>
426426
427427
An attachment artifact associates some other artifact that is the
428
-attachment (the source artifact) with a ticket or wiki page or event to which
428
+attachment (the source artifact) with a ticket or wiki page or
429
+technical note to which
429430
the attachment is connected (the target artifact).
430431
The following cards are allowed on an attachment artifact:
431432
432433
<blockquote>
433434
<b>A</b> <i>filename target</i> ?<i>source</i>?<br />
@@ -437,12 +438,12 @@
437438
<b>U</b> <i>user-name</i><br />
438439
<b>Z</b> <i>checksum</i>
439440
</blockquote>
440441
441442
The A card specifies a filename for the attachment in its first argument.
442
-The second argument to the A card is the name
443
-of the wiki page or ticket or event to which the attachment is connected. The
443
+The second argument to the A card is the name of the wiki page or
444
+ticket or technical note to which the attachment is connected. The
444445
third argument is either missing or else it is the 40-character artifact
445446
ID of the attachment itself. A missing third argument means that the
446447
attachment should be deleted.
447448
448449
The C card is an optional comment describing what the attachment is about.
@@ -461,72 +462,73 @@
461462
The Z card is the usual checksum over the rest of the attachment artifact.
462463
The Z card is required.
463464
464465
465466
<a name="event"></a>
466
-<h2>7.0 Events</h2>
467
+<h2>7.0 Technical Notes</h2>
467468
468
-An event artifact associates a timeline comment and a page of text
469
-(similar to a wiki page) with a point in time. Events can be used
469
+A technical note or "technote" artifact (formerly known as an "event" artifact)
470
+associates a timeline comment and a page of text
471
+(similar to a wiki page) with a point in time. Technotes can be used
470472
to record project milestones, release notes, blog entries, process
471473
checkpoints, or news articles.
472
-The following cards are allowed on an event artifact:
474
+The following cards are allowed on an technote artifact:
473475
474476
<blockquote>
475477
<b>C</b> <i>comment</i><br>
476478
<b>D</b> <i>time-and-date-stamp</i><br />
477
-<b>E</b> <i>event-time</i> <i>event-id</i><br />
479
+<b>E</b> <i>technote-time</i> <i>technote-id</i><br />
478480
<b>N</b> <i>mimetype</i><br />
479481
<b>P</b> <i>parent-artifact-id</i>+<br />
480482
<b>T</b> <b>+</b><i>tag-name</i> <b>*</b> ?<i>value</i>?<br />
481483
<b>U</b> <i>user-name</i><br />
482484
<b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
483485
<b>Z</b> <i>checksum</i>
484486
</blockquote>
485487
486488
The C card contains text that is displayed on the timeline for the
487
-event. The C card is optional, but there can only be one.
489
+technote. The C card is optional, but there can only be one.
488490
489491
A single D card is required to give the date and time when the
490
-event artifact was created. This is different from the time at which
491
-the event occurs.
492
+technote artifact was created. This is different from the time at which
493
+the technote appears on the timeline.
492494
493
-A single E card gives the time of the event (the point on the timeline
494
-where the event is displayed) and a unique identifier for the event.
495
-When there are multiple artifacts with the same event-id, the one with
496
-the most recent D card is the only one used. The event-id must be a
495
+A single E card gives the time of the technote (the point on the timeline
496
+where the technote is displayed) and a unique identifier for the technote.
497
+When there are multiple artifacts with the same technote-id, the one with
498
+the most recent D card is the only one used. The technote-id must be a
497499
40-character lower-case hexadecimal string.
498500
499
-The optional N card specifies the mimetype of the text of the event
501
+The optional N card specifies the mimetype of the text of the technote
500502
that is contained in the W card. If the N card is omitted, then the
501503
W card text mimetype is assumed to be text/x-fossil, which is the
502504
Fossil wiki format.
503505
504
-The optional P card specifies a prior event with the same event-id from
505
-which the current event is an edit. The P card is a hint to the system
506
-that it might be space efficient to store one event as a delta of the
507
-other.
506
+The optional P card specifies a prior technote with the same technote-id
507
+from which the current technote is an edit. The P card is a hint to the
508
+system that it might be space efficient to store one technote as a delta of
509
+the other.
508510
509
-An event might contain one or more T-cards used to set
511
+A technote might contain one or more T-cards used to set
510512
[./branching.wiki#tags | tags or properties]
511
-on the event. The format of the T-card is the same as
513
+on the technote. The format of the T-card is the same as
512514
described in [#ctrl | Control Artifacts] section above, except that the
513515
second argument is the single character "<b>*</b>" instead of an
514516
artifact ID and the name is always prefaced by "<b>+</b>".
515517
The <b>*</b> in place of the artifact ID indicates that
516518
the tag or property applies to the current artifact. It is not
517519
possible to encode the current artifact ID as part of an artifact,
518520
since the act of inserting the artifact ID would change the artifact ID,
519521
hence a <b>*</b> is used to represent "self". The "<b>+</b>" on the
520522
name means that tags can only be add and they can only be non-propagating
521
-tags. A an event, T cards are normally used to set the background
523
+tags. In a technote, T cards are normally used to set the background
522524
display color for timelines.
523525
524
-The optional U card gives name of the user who entered the event.
526
+The optional U card gives name of the user who entered the technote.
525527
526528
A single W card provides wiki text for the document associated with the
527
-event. The format of the W card is exactly the same as for a
529
+technote. The format of the W card is exactly the same as for a
528530
[#wikichng | wiki artifact].
529531
530532
The Z card is the required checksum over the rest of the artifact.
531533
532534
@@ -550,11 +552,11 @@
550552
<th>Cluster</th>
551553
<th>Control</th>
552554
<th>Wiki</th>
553555
<th>Ticket</th>
554556
<th>Attachment</th>
555
-<th>Event</th>
557
+<th>Technote</th>
556558
</tr>
557559
<tr>
558560
<td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td>
559561
<td>&nbsp;</td>
560562
<td>&nbsp;</td>
@@ -594,11 +596,11 @@
594596
<td align=center><b>1</b></td>
595597
<td align=center><b>1</b></td>
596598
<td align=center><b>1</b></td>
597599
</tr>
598600
<tr>
599
-<td><b>E</b> <i>event-time event-id</i></td>
601
+<td><b>E</b> <i>technote-time technote-id</i></td>
600602
<td>&nbsp;</td>
601603
<td>&nbsp;</td>
602604
<td>&nbsp;</td>
603605
<td>&nbsp;</td>
604606
<td>&nbsp;</td>
605607
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -43,11 +43,11 @@
43 <li> [#cluster | Clusters] </li>
44 <li> [#ctrl | Control Artifacts] </li>
45 <li> [#wikichng | Wiki Pages] </li>
46 <li> [#tktchng | Ticket Changes] </li>
47 <li> [#attachment | Attachments] </li>
48 <li> [#event | Events] </li>
49 </ul>
50
51 These seven artifact types are described in the following sections.
52
53 In the current implementation (as of 2009-01-25) the artifacts that
@@ -423,11 +423,12 @@
423
424 <a name="attachment"></a>
425 <h2>6.0 Attachments</h2>
426
427 An attachment artifact associates some other artifact that is the
428 attachment (the source artifact) with a ticket or wiki page or event to which
 
429 the attachment is connected (the target artifact).
430 The following cards are allowed on an attachment artifact:
431
432 <blockquote>
433 <b>A</b> <i>filename target</i> ?<i>source</i>?<br />
@@ -437,12 +438,12 @@
437 <b>U</b> <i>user-name</i><br />
438 <b>Z</b> <i>checksum</i>
439 </blockquote>
440
441 The A card specifies a filename for the attachment in its first argument.
442 The second argument to the A card is the name
443 of the wiki page or ticket or event to which the attachment is connected. The
444 third argument is either missing or else it is the 40-character artifact
445 ID of the attachment itself. A missing third argument means that the
446 attachment should be deleted.
447
448 The C card is an optional comment describing what the attachment is about.
@@ -461,72 +462,73 @@
461 The Z card is the usual checksum over the rest of the attachment artifact.
462 The Z card is required.
463
464
465 <a name="event"></a>
466 <h2>7.0 Events</h2>
467
468 An event artifact associates a timeline comment and a page of text
469 (similar to a wiki page) with a point in time. Events can be used
 
470 to record project milestones, release notes, blog entries, process
471 checkpoints, or news articles.
472 The following cards are allowed on an event artifact:
473
474 <blockquote>
475 <b>C</b> <i>comment</i><br>
476 <b>D</b> <i>time-and-date-stamp</i><br />
477 <b>E</b> <i>event-time</i> <i>event-id</i><br />
478 <b>N</b> <i>mimetype</i><br />
479 <b>P</b> <i>parent-artifact-id</i>+<br />
480 <b>T</b> <b>+</b><i>tag-name</i> <b>*</b> ?<i>value</i>?<br />
481 <b>U</b> <i>user-name</i><br />
482 <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
483 <b>Z</b> <i>checksum</i>
484 </blockquote>
485
486 The C card contains text that is displayed on the timeline for the
487 event. The C card is optional, but there can only be one.
488
489 A single D card is required to give the date and time when the
490 event artifact was created. This is different from the time at which
491 the event occurs.
492
493 A single E card gives the time of the event (the point on the timeline
494 where the event is displayed) and a unique identifier for the event.
495 When there are multiple artifacts with the same event-id, the one with
496 the most recent D card is the only one used. The event-id must be a
497 40-character lower-case hexadecimal string.
498
499 The optional N card specifies the mimetype of the text of the event
500 that is contained in the W card. If the N card is omitted, then the
501 W card text mimetype is assumed to be text/x-fossil, which is the
502 Fossil wiki format.
503
504 The optional P card specifies a prior event with the same event-id from
505 which the current event is an edit. The P card is a hint to the system
506 that it might be space efficient to store one event as a delta of the
507 other.
508
509 An event might contain one or more T-cards used to set
510 [./branching.wiki#tags | tags or properties]
511 on the event. The format of the T-card is the same as
512 described in [#ctrl | Control Artifacts] section above, except that the
513 second argument is the single character "<b>*</b>" instead of an
514 artifact ID and the name is always prefaced by "<b>+</b>".
515 The <b>*</b> in place of the artifact ID indicates that
516 the tag or property applies to the current artifact. It is not
517 possible to encode the current artifact ID as part of an artifact,
518 since the act of inserting the artifact ID would change the artifact ID,
519 hence a <b>*</b> is used to represent "self". The "<b>+</b>" on the
520 name means that tags can only be add and they can only be non-propagating
521 tags. A an event, T cards are normally used to set the background
522 display color for timelines.
523
524 The optional U card gives name of the user who entered the event.
525
526 A single W card provides wiki text for the document associated with the
527 event. The format of the W card is exactly the same as for a
528 [#wikichng | wiki artifact].
529
530 The Z card is the required checksum over the rest of the artifact.
531
532
@@ -550,11 +552,11 @@
550 <th>Cluster</th>
551 <th>Control</th>
552 <th>Wiki</th>
553 <th>Ticket</th>
554 <th>Attachment</th>
555 <th>Event</th>
556 </tr>
557 <tr>
558 <td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td>
559 <td>&nbsp;</td>
560 <td>&nbsp;</td>
@@ -594,11 +596,11 @@
594 <td align=center><b>1</b></td>
595 <td align=center><b>1</b></td>
596 <td align=center><b>1</b></td>
597 </tr>
598 <tr>
599 <td><b>E</b> <i>event-time event-id</i></td>
600 <td>&nbsp;</td>
601 <td>&nbsp;</td>
602 <td>&nbsp;</td>
603 <td>&nbsp;</td>
604 <td>&nbsp;</td>
605
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -43,11 +43,11 @@
43 <li> [#cluster | Clusters] </li>
44 <li> [#ctrl | Control Artifacts] </li>
45 <li> [#wikichng | Wiki Pages] </li>
46 <li> [#tktchng | Ticket Changes] </li>
47 <li> [#attachment | Attachments] </li>
48 <li> [#event | TechNotes] </li>
49 </ul>
50
51 These seven artifact types are described in the following sections.
52
53 In the current implementation (as of 2009-01-25) the artifacts that
@@ -423,11 +423,12 @@
423
424 <a name="attachment"></a>
425 <h2>6.0 Attachments</h2>
426
427 An attachment artifact associates some other artifact that is the
428 attachment (the source artifact) with a ticket or wiki page or
429 technical note to which
430 the attachment is connected (the target artifact).
431 The following cards are allowed on an attachment artifact:
432
433 <blockquote>
434 <b>A</b> <i>filename target</i> ?<i>source</i>?<br />
@@ -437,12 +438,12 @@
438 <b>U</b> <i>user-name</i><br />
439 <b>Z</b> <i>checksum</i>
440 </blockquote>
441
442 The A card specifies a filename for the attachment in its first argument.
443 The second argument to the A card is the name of the wiki page or
444 ticket or technical note to which the attachment is connected. The
445 third argument is either missing or else it is the 40-character artifact
446 ID of the attachment itself. A missing third argument means that the
447 attachment should be deleted.
448
449 The C card is an optional comment describing what the attachment is about.
@@ -461,72 +462,73 @@
462 The Z card is the usual checksum over the rest of the attachment artifact.
463 The Z card is required.
464
465
466 <a name="event"></a>
467 <h2>7.0 Technical Notes</h2>
468
469 A technical note or "technote" artifact (formerly known as an "event" artifact)
470 associates a timeline comment and a page of text
471 (similar to a wiki page) with a point in time. Technotes can be used
472 to record project milestones, release notes, blog entries, process
473 checkpoints, or news articles.
474 The following cards are allowed on an technote artifact:
475
476 <blockquote>
477 <b>C</b> <i>comment</i><br>
478 <b>D</b> <i>time-and-date-stamp</i><br />
479 <b>E</b> <i>technote-time</i> <i>technote-id</i><br />
480 <b>N</b> <i>mimetype</i><br />
481 <b>P</b> <i>parent-artifact-id</i>+<br />
482 <b>T</b> <b>+</b><i>tag-name</i> <b>*</b> ?<i>value</i>?<br />
483 <b>U</b> <i>user-name</i><br />
484 <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
485 <b>Z</b> <i>checksum</i>
486 </blockquote>
487
488 The C card contains text that is displayed on the timeline for the
489 technote. The C card is optional, but there can only be one.
490
491 A single D card is required to give the date and time when the
492 technote artifact was created. This is different from the time at which
493 the technote appears on the timeline.
494
495 A single E card gives the time of the technote (the point on the timeline
496 where the technote is displayed) and a unique identifier for the technote.
497 When there are multiple artifacts with the same technote-id, the one with
498 the most recent D card is the only one used. The technote-id must be a
499 40-character lower-case hexadecimal string.
500
501 The optional N card specifies the mimetype of the text of the technote
502 that is contained in the W card. If the N card is omitted, then the
503 W card text mimetype is assumed to be text/x-fossil, which is the
504 Fossil wiki format.
505
506 The optional P card specifies a prior technote with the same technote-id
507 from which the current technote is an edit. The P card is a hint to the
508 system that it might be space efficient to store one technote as a delta of
509 the other.
510
511 A technote might contain one or more T-cards used to set
512 [./branching.wiki#tags | tags or properties]
513 on the technote. The format of the T-card is the same as
514 described in [#ctrl | Control Artifacts] section above, except that the
515 second argument is the single character "<b>*</b>" instead of an
516 artifact ID and the name is always prefaced by "<b>+</b>".
517 The <b>*</b> in place of the artifact ID indicates that
518 the tag or property applies to the current artifact. It is not
519 possible to encode the current artifact ID as part of an artifact,
520 since the act of inserting the artifact ID would change the artifact ID,
521 hence a <b>*</b> is used to represent "self". The "<b>+</b>" on the
522 name means that tags can only be add and they can only be non-propagating
523 tags. In a technote, T cards are normally used to set the background
524 display color for timelines.
525
526 The optional U card gives name of the user who entered the technote.
527
528 A single W card provides wiki text for the document associated with the
529 technote. The format of the W card is exactly the same as for a
530 [#wikichng | wiki artifact].
531
532 The Z card is the required checksum over the rest of the artifact.
533
534
@@ -550,11 +552,11 @@
552 <th>Cluster</th>
553 <th>Control</th>
554 <th>Wiki</th>
555 <th>Ticket</th>
556 <th>Attachment</th>
557 <th>Technote</th>
558 </tr>
559 <tr>
560 <td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td>
561 <td>&nbsp;</td>
562 <td>&nbsp;</td>
@@ -594,11 +596,11 @@
596 <td align=center><b>1</b></td>
597 <td align=center><b>1</b></td>
598 <td align=center><b>1</b></td>
599 </tr>
600 <tr>
601 <td><b>E</b> <i>technote-time technote-id</i></td>
602 <td>&nbsp;</td>
603 <td>&nbsp;</td>
604 <td>&nbsp;</td>
605 <td>&nbsp;</td>
606 <td>&nbsp;</td>
607
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -17,17 +17,20 @@
1717
<h2>2.0 Executive Summary:</h2>
1818
1919
<blockquote><center><table border=1 cellpadding=5>
2020
<tr><th width="50%">GIT</th><th width="50%">FOSSIL</th></tr>
2121
<tr><td>File versioning only</td>
22
- <td>Versioning, Tickets, Wiki, and Blog/News</td></tr>
22
+ <td>Versioning, Tickets, Wiki, and Technotes</td></tr>
2323
<tr><td>Sharding</td><td>Replicating</td></tr>
2424
<tr><td>Developer branches</td><td>Feature branches</td></tr>
2525
<tr><td>Complex</td><td>Intuitive</td></tr>
2626
<tr><td>Separate web tools</td><td>Integrated Web interface</td></tr>
2727
<tr><td>Lots of little tools</td><td>Single executable</td></tr>
28
-<tr><td>Pile-of-files repository</td><td>Single file repository</td></tr>
28
+<tr><td>Pile-of-files repository</td>
29
+ <td>Single-file relational database</td></tr>
30
+<tr><td>One check-out per repository</td>
31
+ <td>Many check-outs per repository</td></tr>
2932
<tr><td>Uses "<tt>rebase</tt>"</td><td>Immutable</td></tr>
3033
<tr><td>GPL</td><td>BSD</td></tr>
3134
</table></center></blockquote>
3235
3336
<h2>3.0 Discussion</h2>
@@ -36,11 +39,11 @@
3639
3740
Git provides file versioning services only, whereas Fossil adds an
3841
integrated [./wikitheory.wiki | wiki],
3942
[./bugtheory.wiki | ticketing &amp; bug tracking],
4043
[./embeddeddoc.wiki | embedded documentation], and
41
-[./event.wiki | News/Blog features].
44
+[./event.wiki | Technical notes].
4245
These additional capabilities are available for Git as 3rd-party
4346
user-installed add-ons, but with Fossil they are integrated into
4447
the design. One way to describe Fossil is that it is
4548
"[https://github.com/ | github]-in-a-box".
4649
@@ -148,10 +151,13 @@
148151
Fossil strives for simplicity. Fossil wants to be easy to learn and to
149152
require little thinking about how to operating it.
150153
[./quotes.wiki | Reports from the field]
151154
indicate that Fossil is mostly successful at this effort.
152155
156
+Fossil will <u>never</u> get you into anything like the
157
+"disconnected head state" which has frustrated so many Git users.
158
+
153159
<h3>3.5 Web Interface</h3>
154160
155161
Git has a web interface, but it requires a fair amount of setup and an
156162
external web server. Fossil comes with a fully functional
157163
[./webui.wiki | built-in web-server]
@@ -189,11 +195,27 @@
189195
repository can serve multiple simultaneous working checkouts.
190196
A Fossil repository is an SQLite database, so it is highly resistant
191197
to damage from a power-loss or system crash - incomplete transactions
192198
are simply rolled back after the system reboots.
193199
194
-<h3>3.8 Audit Trail</h3>
200
+<h3>3.8 Check-outs Per Repository</h3>
201
+
202
+In Git, a check-out and a repository are joined in a fundamental way
203
+so that only a single version of the project history, or a single branch,
204
+can be open at once. If you have a project with multiple branches and
205
+you want to have two or more branches open at the same time (perhaps to
206
+do performance comparisons, or maybe to run simultaneous builds using
207
+different compile-time options) then in Git you actually have to create
208
+a new clone of the repository for each open checkout.
209
+
210
+In Fossil, the repository and the check-out are distinct entities and
211
+so a single repository can support multiple simultaneous checkouts.
212
+This feature is <em>extensively</em> used by the Fossil developers
213
+themselves. Perhaps we are biased, but we not understand how anyone
214
+can work efficiently with just one check-out per repository.
215
+
216
+<h3>3.9 Audit Trail</h3>
195217
196218
Git features the "rebase" command which can be used to change the
197219
sequence of check-ins in the repository. Rebase can be used to "clean up"
198220
a complex sequence of check-ins to make their intent easier for others
199221
to understand. This is important if you view the history of a project
@@ -204,24 +226,47 @@
204226
Fossil allows mistakes to be corrected (for example, check-in comments
205227
can be revised, and check-ins can be moved onto new branches even after
206228
the check-in has occurred) but the correction is an addition to the repository
207229
and the original actions are preserved and displayed alongside
208230
the corrections, thus preserving an historically accurate audit trail.
209
-This is analogous to an accountant marking through an incorrect
210
-entry in a ledger and writing in a correction beside it, rather than
211
-erasing and incorrect entry.
231
+This is analogous to an accounting practice of marking through an incorrect
232
+entry in a ledger and writing a correction beside it.
233
+
212234
213235
To put it another way, Git remembers what you should have done whereas
214236
Fossil remembers what you actually did.
215237
216238
The lack of a "rebase" command and the inability to rewrite history
217239
is considered a feature of Fossil, not an omission or bug.
218240
219
-<h3>3.9 License</h3>
241
+<h3>3.10 License</h3>
220242
221243
Both Git and Fossil are open-source. Git is under
222244
[http://www.gnu.org/licenses/gpl.html | GPL] whereas Fossil is
223245
under the
224246
[http://en.wikipedia.org/wiki/BSD_licenses | two-clause BSD license].
225
-The difference should not be of a concern to most users. However,
226
-some corporate lawyers have objections to using GPL products and
227
-are more comfortable with a BSD-style license.
247
+The different licenses parallel, to some extent, the different philosophies
248
+of Git and Fossil.
249
+There are exceptions on both sides, but to a first approximation, Git
250
+works better for GPL projects and Fossil works better for BSD projects.
251
+
252
+The GPL is designed to provide a very contributor-friendly environment.
253
+No legal paperwork is needed to contribute to a GPL project because
254
+the GPL is cleverly designed so that the act of contributing
255
+to the project (or even reading the code for the project) constitutes
256
+an acceptance of the licensing terms. GPL encourages a bazaar-style
257
+development model, with lots of anonymous programmers contributing
258
+drive-by patches. The theory is that with many eyeballs, all bugs
259
+are shallow. Surprisingly, this has actually been demonstrated to
260
+work in many well-known projects.
261
+
262
+The BSD-style licenses are more user-friendly. BSD-style licenses
263
+place fewer restrictions on the users of the software at the expense
264
+of making it more difficult to contribute changes or enhancements.
265
+To protect against IP claims,
266
+every contributor to a BSD-style project must sign legal documents in
267
+which they agree to release their contributions under the same license.
268
+(Some BSD-licensed projects omit this formality, but do so at their peril.)
269
+A BSD-style license encourages a more cathedral-style approach to development.
270
+There is a small team of developers. Drive-by patches and anonymous
271
+contributors are discouraged and/or prohibited. Contributors are expected
272
+to be experts and be available to support their changes for the long-term.
228273
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -17,17 +17,20 @@
17 <h2>2.0 Executive Summary:</h2>
18
19 <blockquote><center><table border=1 cellpadding=5>
20 <tr><th width="50%">GIT</th><th width="50%">FOSSIL</th></tr>
21 <tr><td>File versioning only</td>
22 <td>Versioning, Tickets, Wiki, and Blog/News</td></tr>
23 <tr><td>Sharding</td><td>Replicating</td></tr>
24 <tr><td>Developer branches</td><td>Feature branches</td></tr>
25 <tr><td>Complex</td><td>Intuitive</td></tr>
26 <tr><td>Separate web tools</td><td>Integrated Web interface</td></tr>
27 <tr><td>Lots of little tools</td><td>Single executable</td></tr>
28 <tr><td>Pile-of-files repository</td><td>Single file repository</td></tr>
 
 
 
29 <tr><td>Uses "<tt>rebase</tt>"</td><td>Immutable</td></tr>
30 <tr><td>GPL</td><td>BSD</td></tr>
31 </table></center></blockquote>
32
33 <h2>3.0 Discussion</h2>
@@ -36,11 +39,11 @@
36
37 Git provides file versioning services only, whereas Fossil adds an
38 integrated [./wikitheory.wiki | wiki],
39 [./bugtheory.wiki | ticketing &amp; bug tracking],
40 [./embeddeddoc.wiki | embedded documentation], and
41 [./event.wiki | News/Blog features].
42 These additional capabilities are available for Git as 3rd-party
43 user-installed add-ons, but with Fossil they are integrated into
44 the design. One way to describe Fossil is that it is
45 "[https://github.com/ | github]-in-a-box".
46
@@ -148,10 +151,13 @@
148 Fossil strives for simplicity. Fossil wants to be easy to learn and to
149 require little thinking about how to operating it.
150 [./quotes.wiki | Reports from the field]
151 indicate that Fossil is mostly successful at this effort.
152
 
 
 
153 <h3>3.5 Web Interface</h3>
154
155 Git has a web interface, but it requires a fair amount of setup and an
156 external web server. Fossil comes with a fully functional
157 [./webui.wiki | built-in web-server]
@@ -189,11 +195,27 @@
189 repository can serve multiple simultaneous working checkouts.
190 A Fossil repository is an SQLite database, so it is highly resistant
191 to damage from a power-loss or system crash - incomplete transactions
192 are simply rolled back after the system reboots.
193
194 <h3>3.8 Audit Trail</h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
196 Git features the "rebase" command which can be used to change the
197 sequence of check-ins in the repository. Rebase can be used to "clean up"
198 a complex sequence of check-ins to make their intent easier for others
199 to understand. This is important if you view the history of a project
@@ -204,24 +226,47 @@
204 Fossil allows mistakes to be corrected (for example, check-in comments
205 can be revised, and check-ins can be moved onto new branches even after
206 the check-in has occurred) but the correction is an addition to the repository
207 and the original actions are preserved and displayed alongside
208 the corrections, thus preserving an historically accurate audit trail.
209 This is analogous to an accountant marking through an incorrect
210 entry in a ledger and writing in a correction beside it, rather than
211 erasing and incorrect entry.
212
213 To put it another way, Git remembers what you should have done whereas
214 Fossil remembers what you actually did.
215
216 The lack of a "rebase" command and the inability to rewrite history
217 is considered a feature of Fossil, not an omission or bug.
218
219 <h3>3.9 License</h3>
220
221 Both Git and Fossil are open-source. Git is under
222 [http://www.gnu.org/licenses/gpl.html | GPL] whereas Fossil is
223 under the
224 [http://en.wikipedia.org/wiki/BSD_licenses | two-clause BSD license].
225 The difference should not be of a concern to most users. However,
226 some corporate lawyers have objections to using GPL products and
227 are more comfortable with a BSD-style license.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -17,17 +17,20 @@
17 <h2>2.0 Executive Summary:</h2>
18
19 <blockquote><center><table border=1 cellpadding=5>
20 <tr><th width="50%">GIT</th><th width="50%">FOSSIL</th></tr>
21 <tr><td>File versioning only</td>
22 <td>Versioning, Tickets, Wiki, and Technotes</td></tr>
23 <tr><td>Sharding</td><td>Replicating</td></tr>
24 <tr><td>Developer branches</td><td>Feature branches</td></tr>
25 <tr><td>Complex</td><td>Intuitive</td></tr>
26 <tr><td>Separate web tools</td><td>Integrated Web interface</td></tr>
27 <tr><td>Lots of little tools</td><td>Single executable</td></tr>
28 <tr><td>Pile-of-files repository</td>
29 <td>Single-file relational database</td></tr>
30 <tr><td>One check-out per repository</td>
31 <td>Many check-outs per repository</td></tr>
32 <tr><td>Uses "<tt>rebase</tt>"</td><td>Immutable</td></tr>
33 <tr><td>GPL</td><td>BSD</td></tr>
34 </table></center></blockquote>
35
36 <h2>3.0 Discussion</h2>
@@ -36,11 +39,11 @@
39
40 Git provides file versioning services only, whereas Fossil adds an
41 integrated [./wikitheory.wiki | wiki],
42 [./bugtheory.wiki | ticketing &amp; bug tracking],
43 [./embeddeddoc.wiki | embedded documentation], and
44 [./event.wiki | Technical notes].
45 These additional capabilities are available for Git as 3rd-party
46 user-installed add-ons, but with Fossil they are integrated into
47 the design. One way to describe Fossil is that it is
48 "[https://github.com/ | github]-in-a-box".
49
@@ -148,10 +151,13 @@
151 Fossil strives for simplicity. Fossil wants to be easy to learn and to
152 require little thinking about how to operating it.
153 [./quotes.wiki | Reports from the field]
154 indicate that Fossil is mostly successful at this effort.
155
156 Fossil will <u>never</u> get you into anything like the
157 "disconnected head state" which has frustrated so many Git users.
158
159 <h3>3.5 Web Interface</h3>
160
161 Git has a web interface, but it requires a fair amount of setup and an
162 external web server. Fossil comes with a fully functional
163 [./webui.wiki | built-in web-server]
@@ -189,11 +195,27 @@
195 repository can serve multiple simultaneous working checkouts.
196 A Fossil repository is an SQLite database, so it is highly resistant
197 to damage from a power-loss or system crash - incomplete transactions
198 are simply rolled back after the system reboots.
199
200 <h3>3.8 Check-outs Per Repository</h3>
201
202 In Git, a check-out and a repository are joined in a fundamental way
203 so that only a single version of the project history, or a single branch,
204 can be open at once. If you have a project with multiple branches and
205 you want to have two or more branches open at the same time (perhaps to
206 do performance comparisons, or maybe to run simultaneous builds using
207 different compile-time options) then in Git you actually have to create
208 a new clone of the repository for each open checkout.
209
210 In Fossil, the repository and the check-out are distinct entities and
211 so a single repository can support multiple simultaneous checkouts.
212 This feature is <em>extensively</em> used by the Fossil developers
213 themselves. Perhaps we are biased, but we not understand how anyone
214 can work efficiently with just one check-out per repository.
215
216 <h3>3.9 Audit Trail</h3>
217
218 Git features the "rebase" command which can be used to change the
219 sequence of check-ins in the repository. Rebase can be used to "clean up"
220 a complex sequence of check-ins to make their intent easier for others
221 to understand. This is important if you view the history of a project
@@ -204,24 +226,47 @@
226 Fossil allows mistakes to be corrected (for example, check-in comments
227 can be revised, and check-ins can be moved onto new branches even after
228 the check-in has occurred) but the correction is an addition to the repository
229 and the original actions are preserved and displayed alongside
230 the corrections, thus preserving an historically accurate audit trail.
231 This is analogous to an accounting practice of marking through an incorrect
232 entry in a ledger and writing a correction beside it.
233
234
235 To put it another way, Git remembers what you should have done whereas
236 Fossil remembers what you actually did.
237
238 The lack of a "rebase" command and the inability to rewrite history
239 is considered a feature of Fossil, not an omission or bug.
240
241 <h3>3.10 License</h3>
242
243 Both Git and Fossil are open-source. Git is under
244 [http://www.gnu.org/licenses/gpl.html | GPL] whereas Fossil is
245 under the
246 [http://en.wikipedia.org/wiki/BSD_licenses | two-clause BSD license].
247 The different licenses parallel, to some extent, the different philosophies
248 of Git and Fossil.
249 There are exceptions on both sides, but to a first approximation, Git
250 works better for GPL projects and Fossil works better for BSD projects.
251
252 The GPL is designed to provide a very contributor-friendly environment.
253 No legal paperwork is needed to contribute to a GPL project because
254 the GPL is cleverly designed so that the act of contributing
255 to the project (or even reading the code for the project) constitutes
256 an acceptance of the licensing terms. GPL encourages a bazaar-style
257 development model, with lots of anonymous programmers contributing
258 drive-by patches. The theory is that with many eyeballs, all bugs
259 are shallow. Surprisingly, this has actually been demonstrated to
260 work in many well-known projects.
261
262 The BSD-style licenses are more user-friendly. BSD-style licenses
263 place fewer restrictions on the users of the software at the expense
264 of making it more difficult to contribute changes or enhancements.
265 To protect against IP claims,
266 every contributor to a BSD-style project must sign legal documents in
267 which they agree to release their contributions under the same license.
268 (Some BSD-licensed projects omit this formality, but do so at their peril.)
269 A BSD-style license encourages a more cathedral-style approach to development.
270 There is a small team of developers. Drive-by patches and anonymous
271 contributors are discouraged and/or prohibited. Contributors are expected
272 to be experts and be available to support their changes for the long-term.
273
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -2,10 +2,11 @@
22
33
The following links are of interest to programmers who want to modify
44
or enhance Fossil. Ordinary users can safely ignore this information.
55
66
* [./build.wiki | How To Compile And Install Fossil]
7
+ * [./customskin.md | Theming Fossil]
78
* [./makefile.wiki | The Fossil Build Process]
89
* [./tech_overview.wiki | A Technical Overview of Fossil]
910
* [./adding_code.wiki | Adding Features To Fossil]
1011
* [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
1112
* [./style.wiki | Coding Style Guidelines]
1213
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -2,10 +2,11 @@
2
3 The following links are of interest to programmers who want to modify
4 or enhance Fossil. Ordinary users can safely ignore this information.
5
6 * [./build.wiki | How To Compile And Install Fossil]
 
7 * [./makefile.wiki | The Fossil Build Process]
8 * [./tech_overview.wiki | A Technical Overview of Fossil]
9 * [./adding_code.wiki | Adding Features To Fossil]
10 * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
11 * [./style.wiki | Coding Style Guidelines]
12
--- www/hacker-howto.wiki
+++ www/hacker-howto.wiki
@@ -2,10 +2,11 @@
2
3 The following links are of interest to programmers who want to modify
4 or enhance Fossil. Ordinary users can safely ignore this information.
5
6 * [./build.wiki | How To Compile And Install Fossil]
7 * [./customskin.md | Theming Fossil]
8 * [./makefile.wiki | The Fossil Build Process]
9 * [./tech_overview.wiki | A Technical Overview of Fossil]
10 * [./adding_code.wiki | Adding Features To Fossil]
11 * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
12 * [./style.wiki | Coding Style Guidelines]
13
+51 -75
--- www/index.wiki
+++ www/index.wiki
@@ -1,115 +1,91 @@
1
-<title>Fossil</title>
2
-
3
-
4
-<p align="center">
5
-<font size="3">
6
-<i>Simple, high-reliability, distributed software configuration management</i>
7
-</font>
8
-</p>
9
-
10
-
11
-<h3>Why Use Fossil?</h3>
12
-
13
-<table border="0" cellspacing="10" bgcolor="white" align="right"
14
-cellpadding="2">
15
-<tr><td bgcolor="#446979">
16
-<table border="0" cellpadding="10" bgcolor="white">
17
-<tr><td>
1
+<title>Home</title>
2
+
3
+<h3>What Is Fossil?</h3>
4
+
5
+<div style='width:200px;float:right;border:2px solid #446979;padding:10px;margin:0px 10px;'>
186
<ul>
197
<li> [http://www.fossil-scm.org/download.html | Download]
208
<li> [./quickstart.wiki | Quick Start]
219
<li> [./build.wiki | Install]
2210
<li> [../COPYRIGHT-BSD2.txt | License]
23
-<li> [/timeline | Recent changes]
2411
<li> [./faq.wiki | FAQ]
25
-<li> [./hacker-howto.wiki | Hacker How-To]
2612
<li> [./changes.wiki | Change Log]
13
+<li> [./hacker-howto.wiki | Hacker How-To]
2714
<li> [./hints.wiki | Tip &amp; Hints]
2815
<li> [./permutedindex.html | Documentation Index]
2916
<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
3017
<li> Mailing list
3118
<ul>
3219
<li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up]
3320
<li> [http://www.mail-archive.com/[email protected] | archives]
34
- <ul>
21
+ </ul>
3522
</ul>
36
-</td></tr>
37
-<tr><td>
3823
<center><img src="fossil3.gif"></center>
39
-</td></tr>
40
-</table>
41
-</table>
42
-
43
-There are plenty of open-source version control systems available on the
44
-internet these days. What makes Fossil worthy of attention?
45
-
46
- 1. <b>Bug Tracking And Wiki</b> -
24
+</div>
25
+
26
+<p>Fossil is a simple, high-reliability, distributed software configuration
27
+management with these advanced features:
28
+
29
+ 1. <b>Integrated Bug Tracking, Wiki, and Technotes</b> -
4730
In addition to doing [./concepts.wiki | distributed version control]
4831
like Git and Mercurial,
49
- Fossil also supports [./bugtheory.wiki | distributed bug tracking],
50
- [./wikitheory.wiki | distributed wiki], and a
51
- [./event.wiki | distributed blog] mechanism all in a single
52
- integrated package.
53
-
54
- 2. <b>Web Interface</b> -
55
- Fossil has a built-in and easy-to-use [./webui.wiki | web interface]
56
- that simplifies project tracking and promotes situational awareness.
57
- Simply type "fossil&nbsp;ui" from within any check-out and Fossil
58
- automatically opens your web browser in a page that gives detailed
59
- [/timeline?n=100&y=ci | graphical history] and status information
60
- on that project.
61
-
62
- This entire website (except the
63
- [http://www.fossil-scm.org/download.html | download] page)
32
+ Fossil also supports [./bugtheory.wiki | bug tracking],
33
+ [./wikitheory.wiki | wiki], and [./event.wiki | technotes].
34
+
35
+ 2. <b>Built-in Web Interface</b> -
36
+ Fossil has a built-in and intuitive [./webui.wiki | web interface]
37
+ with a rich assortment of information pages
38
+ ([./webpage-ex.md|examples]) designed to promote situational awareness.
39
+
40
+ This entire website&#185;
6441
is just a running instance of Fossil. The pages you see here
6542
are all [./wikitheory.wiki | wiki] or
6643
[./embeddeddoc.wiki | embedded documentation].
6744
When you clone Fossil from one of its
6845
[./selfhost.wiki | self-hosting repositories],
6946
you get more than just source code - you get this entire website.
47
+ <span style='font-size:75%;'>(&#185;except the
48
+ [http://www.fossil-scm.org/download.html | download] page)</span>
49
+
50
+ 3. <b>Self-Contained</b> -
51
+ Fossil is a single self-contained stand-alone executable.
52
+ To install, simply download a
53
+ <a href="http://www.fossil-scm.org/download.html">precompiled binary</a>
54
+ for Linux, Mac, OpenBSD, or Windows and put it on your $PATH.
55
+ [./build.wiki | Easy-to-compile source code] is also available.
56
+
57
+ 4. <b>Simple Networking</b> -
58
+ No custom protocols or TCP ports.
59
+ Fossil uses ordinary HTTP (or HTTPS or SSH)
60
+ for network communications, so it works fine from behind
61
+ restrictive firewalls, including [./quickstart.wiki#proxy|proxies].
62
+ The protocol is
63
+ [./stats.wiki | bandwidth efficient] to the point that Fossil can be
64
+ used comfortably over dial-up.
65
+
66
+ 5. <b>CGI/SCGI Enabled</b> - No server is required, but if you want to
67
+ set one up, Fossil supports four easy
68
+ [./server.wiki | server configurations].
7069
71
- 3. <b>Autosync</b> -
70
+ 6. <b>Autosync</b> -
7271
Fossil supports [./concepts.wiki#workflow | "autosync" mode]
7372
which helps to keep projects moving
7473
forward by reducing the amount of needless
7574
[./branching.wiki | forking and merging] often
7675
associated with distributed projects.
7776
78
- 4. <b>Self-Contained</b> -
79
- Fossil is a single stand-alone executable that contains everything
80
- needed to do configuration management.
81
- Installation is trivial: simply download a
82
- <a href="http://www.fossil-scm.org/download.html">precompiled binary</a>
83
- for Linux, Mac, or Windows and put it on your $PATH.
84
- [./build.wiki | Easy-to-compile source code] is available for
85
- users on other platforms. Fossil sources are also mostly self-contained,
86
- requiring only the standard C library to build.
87
-
88
- 5. <b>Simple Networking</b> -
89
- Fossil uses plain old HTTP (with
90
- [./quickstart.wiki#proxy | proxy support])
91
- for all network communications, meaning that it works fine from behind
92
- restrictive firewalls. The protocol is
93
- [./stats.wiki | bandwidth efficient] to the point that Fossil can be
94
- used comfortably over a dial-up internet connection.
95
-
96
- 6. <b>CGI/SCGI Enabled</b> -
97
- No server is required to use fossil. But a
98
- server does make collaboration easier. Fossil supports four different
99
- yet simple [./server.wiki | server configurations].
100
- The most popular is a 2-line CGI script. This is the approach
101
- used by the [./selfhost.wiki | self-hosting fossil repositories].
102
-
10377
7. <b>Robust &amp; Reliable</b> -
10478
Fossil stores content using an [./fileformat.wiki | enduring file format]
10579
in an SQLite database so that transactions are
106
- atomic even if interrupted by a power loss or system crash. Furthermore,
107
- automatic [./selfcheck.wiki | self-checks] verify that all aspects of
108
- the repository are consistent prior to each commit. In over six years
80
+ atomic even if interrupted by a power loss or system crash.
81
+ Automatic [./selfcheck.wiki | self-checks] verify that all aspects of
82
+ the repository are consistent prior to each commit. In over seven years
10983
of operation, no work has ever been lost after having been committed to
11084
a Fossil repository.
85
+
86
+ 8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
11187
11288
<hr>
11389
<h3>Links For Fossil Users:</h3>
11490
11591
* "Fuel" is cross-platform GUI front-end for Fossil
@@ -159,11 +135,11 @@
159135
* [./fossil-v-git.wiki | Fossil versus Git].
160136
* [./fiveminutes.wiki | Up and running in 5 minutes as a single user]
161137
(contributed by Gilles Ganault on 2013-01-08).
162138
* [./antibot.wiki | How Fossil defends against abuse by spiders and bots].
163139
164
-<h3>Links For Fossil Developer:</h3>
140
+<h3>Links For Fossil Developers:</h3>
165141
166142
* [./contribute.wiki | Contributing] code or documentation to the
167143
Fossil project.
168144
* [./theory1.wiki | Thoughts On The Design Of Fossil].
169145
* [./pop.wiki | Principles Of Operation]
170146
--- www/index.wiki
+++ www/index.wiki
@@ -1,115 +1,91 @@
1 <title>Fossil</title>
2
3
4 <p align="center">
5 <font size="3">
6 <i>Simple, high-reliability, distributed software configuration management</i>
7 </font>
8 </p>
9
10
11 <h3>Why Use Fossil?</h3>
12
13 <table border="0" cellspacing="10" bgcolor="white" align="right"
14 cellpadding="2">
15 <tr><td bgcolor="#446979">
16 <table border="0" cellpadding="10" bgcolor="white">
17 <tr><td>
18 <ul>
19 <li> [http://www.fossil-scm.org/download.html | Download]
20 <li> [./quickstart.wiki | Quick Start]
21 <li> [./build.wiki | Install]
22 <li> [../COPYRIGHT-BSD2.txt | License]
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 <ul>
35 </ul>
36 </td></tr>
37 <tr><td>
38 <center><img src="fossil3.gif"></center>
39 </td></tr>
40 </table>
41 </table>
42
43 There are plenty of open-source version control systems available on the
44 internet these days. What makes Fossil worthy of attention?
45
46 1. <b>Bug Tracking And Wiki</b> -
47 In addition to doing [./concepts.wiki | distributed version control]
48 like Git and Mercurial,
49 Fossil also supports [./bugtheory.wiki | distributed bug tracking],
50 [./wikitheory.wiki | distributed wiki], and a
51 [./event.wiki | distributed blog] mechanism all in a single
52 integrated package.
53
54 2. <b>Web Interface</b> -
55 Fossil has a built-in and easy-to-use [./webui.wiki | web interface]
56 that simplifies project tracking and promotes situational awareness.
57 Simply type "fossil&nbsp;ui" from within any check-out and Fossil
58 automatically opens your web browser in a page that gives detailed
59 [/timeline?n=100&y=ci | graphical history] and status information
60 on that project.
61
62 This entire website (except the
63 [http://www.fossil-scm.org/download.html | download] page)
64 is just a running instance of Fossil. The pages you see here
65 are all [./wikitheory.wiki | wiki] or
66 [./embeddeddoc.wiki | embedded documentation].
67 When you clone Fossil from one of its
68 [./selfhost.wiki | self-hosting repositories],
69 you get more than just source code - you get this entire website.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
71 3. <b>Autosync</b> -
72 Fossil supports [./concepts.wiki#workflow | "autosync" mode]
73 which helps to keep projects moving
74 forward by reducing the amount of needless
75 [./branching.wiki | forking and merging] often
76 associated with distributed projects.
77
78 4. <b>Self-Contained</b> -
79 Fossil is a single stand-alone executable that contains everything
80 needed to do configuration management.
81 Installation is trivial: simply download a
82 <a href="http://www.fossil-scm.org/download.html">precompiled binary</a>
83 for Linux, Mac, or Windows and put it on your $PATH.
84 [./build.wiki | Easy-to-compile source code] is available for
85 users on other platforms. Fossil sources are also mostly self-contained,
86 requiring only the standard C library to build.
87
88 5. <b>Simple Networking</b> -
89 Fossil uses plain old HTTP (with
90 [./quickstart.wiki#proxy | proxy support])
91 for all network communications, meaning that it works fine from behind
92 restrictive firewalls. The protocol is
93 [./stats.wiki | bandwidth efficient] to the point that Fossil can be
94 used comfortably over a dial-up internet connection.
95
96 6. <b>CGI/SCGI Enabled</b> -
97 No server is required to use fossil. But a
98 server does make collaboration easier. Fossil supports four different
99 yet simple [./server.wiki | server configurations].
100 The most popular is a 2-line CGI script. This is the approach
101 used by the [./selfhost.wiki | self-hosting fossil repositories].
102
103 7. <b>Robust &amp; Reliable</b> -
104 Fossil stores content using an [./fileformat.wiki | enduring file format]
105 in an SQLite database so that transactions are
106 atomic even if interrupted by a power loss or system crash. Furthermore,
107 automatic [./selfcheck.wiki | self-checks] verify that all aspects of
108 the repository are consistent prior to each commit. In over six years
109 of operation, no work has ever been lost after having been committed to
110 a Fossil repository.
 
 
111
112 <hr>
113 <h3>Links For Fossil Users:</h3>
114
115 * "Fuel" is cross-platform GUI front-end for Fossil
@@ -159,11 +135,11 @@
159 * [./fossil-v-git.wiki | Fossil versus Git].
160 * [./fiveminutes.wiki | Up and running in 5 minutes as a single user]
161 (contributed by Gilles Ganault on 2013-01-08).
162 * [./antibot.wiki | How Fossil defends against abuse by spiders and bots].
163
164 <h3>Links For Fossil Developer:</h3>
165
166 * [./contribute.wiki | Contributing] code or documentation to the
167 Fossil project.
168 * [./theory1.wiki | Thoughts On The Design Of Fossil].
169 * [./pop.wiki | Principles Of Operation]
170
--- www/index.wiki
+++ www/index.wiki
@@ -1,115 +1,91 @@
1 <title>Home</title>
2
3 <h3>What Is Fossil?</h3>
4
5 <div style='width:200px;float:right;border:2px solid #446979;padding:10px;margin:0px 10px;'>
 
 
 
 
 
 
 
 
 
 
 
 
6 <ul>
7 <li> [http://www.fossil-scm.org/download.html | Download]
8 <li> [./quickstart.wiki | Quick Start]
9 <li> [./build.wiki | Install]
10 <li> [../COPYRIGHT-BSD2.txt | License]
 
11 <li> [./faq.wiki | FAQ]
 
12 <li> [./changes.wiki | Change Log]
13 <li> [./hacker-howto.wiki | Hacker How-To]
14 <li> [./hints.wiki | Tip &amp; Hints]
15 <li> [./permutedindex.html | Documentation Index]
16 <li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
17 <li> Mailing list
18 <ul>
19 <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up]
20 <li> [http://www.mail-archive.com/[email protected] | archives]
21 </ul>
22 </ul>
 
 
23 <center><img src="fossil3.gif"></center>
24 </div>
25
26 <p>Fossil is a simple, high-reliability, distributed software configuration
27 management with these advanced features:
28
29 1. <b>Integrated Bug Tracking, Wiki, and Technotes</b> -
 
 
30 In addition to doing [./concepts.wiki | distributed version control]
31 like Git and Mercurial,
32 Fossil also supports [./bugtheory.wiki | bug tracking],
33 [./wikitheory.wiki | wiki], and [./event.wiki | technotes].
34
35 2. <b>Built-in Web Interface</b> -
36 Fossil has a built-in and intuitive [./webui.wiki | web interface]
37 with a rich assortment of information pages
38 ([./webpage-ex.md|examples]) designed to promote situational awareness.
39
40 This entire website&#185;
 
 
 
 
 
 
41 is just a running instance of Fossil. The pages you see here
42 are all [./wikitheory.wiki | wiki] or
43 [./embeddeddoc.wiki | embedded documentation].
44 When you clone Fossil from one of its
45 [./selfhost.wiki | self-hosting repositories],
46 you get more than just source code - you get this entire website.
47 <span style='font-size:75%;'>(&#185;except the
48 [http://www.fossil-scm.org/download.html | download] page)</span>
49
50 3. <b>Self-Contained</b> -
51 Fossil is a single self-contained stand-alone executable.
52 To install, simply download a
53 <a href="http://www.fossil-scm.org/download.html">precompiled binary</a>
54 for Linux, Mac, OpenBSD, or Windows and put it on your $PATH.
55 [./build.wiki | Easy-to-compile source code] is also available.
56
57 4. <b>Simple Networking</b> -
58 No custom protocols or TCP ports.
59 Fossil uses ordinary HTTP (or HTTPS or SSH)
60 for network communications, so it works fine from behind
61 restrictive firewalls, including [./quickstart.wiki#proxy|proxies].
62 The protocol is
63 [./stats.wiki | bandwidth efficient] to the point that Fossil can be
64 used comfortably over dial-up.
65
66 5. <b>CGI/SCGI Enabled</b> - No server is required, but if you want to
67 set one up, Fossil supports four easy
68 [./server.wiki | server configurations].
69
70 6. <b>Autosync</b> -
71 Fossil supports [./concepts.wiki#workflow | "autosync" mode]
72 which helps to keep projects moving
73 forward by reducing the amount of needless
74 [./branching.wiki | forking and merging] often
75 associated with distributed projects.
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77 7. <b>Robust &amp; Reliable</b> -
78 Fossil stores content using an [./fileformat.wiki | enduring file format]
79 in an SQLite database so that transactions are
80 atomic even if interrupted by a power loss or system crash.
81 Automatic [./selfcheck.wiki | self-checks] verify that all aspects of
82 the repository are consistent prior to each commit. In over seven years
83 of operation, no work has ever been lost after having been committed to
84 a Fossil repository.
85
86 8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>Links For Fossil Users:</h3>
90
91 * "Fuel" is cross-platform GUI front-end for Fossil
@@ -159,11 +135,11 @@
135 * [./fossil-v-git.wiki | Fossil versus Git].
136 * [./fiveminutes.wiki | Up and running in 5 minutes as a single user]
137 (contributed by Gilles Ganault on 2013-01-08).
138 * [./antibot.wiki | How Fossil defends against abuse by spiders and bots].
139
140 <h3>Links For Fossil Developers:</h3>
141
142 * [./contribute.wiki | Contributing] code or documentation to the
143 Fossil project.
144 * [./theory1.wiki | Thoughts On The Design Of Fossil].
145 * [./pop.wiki | Principles Of Operation]
146
--- www/inout.wiki
+++ www/inout.wiki
@@ -48,5 +48,16 @@
4848
since the git-fast-export file format is currently the only VCS interchange
4949
format that Fossil will generate. However,
5050
future versions of Fossil might add the ability to generate other
5151
VCS interchange formats, and so for compatibility, the use of the --git
5252
option recommented.
53
+
54
+An anonymous user sends this comment:
55
+
56
+<blockquote>
57
+The main Fossil branch is called "trunk", while the main git branch is
58
+called "master". After you've exported your FOSSIL repo to git, you won't
59
+see any files and gitk will complain about a missing "HEAD". You can
60
+resolve this problem by merging "trunk" with "master"
61
+(first verify using git status that you are on the "master" branch):
62
+<tt>git merge trunk</tt>
63
+</blockquote>
5364
--- www/inout.wiki
+++ www/inout.wiki
@@ -48,5 +48,16 @@
48 since the git-fast-export file format is currently the only VCS interchange
49 format that Fossil will generate. However,
50 future versions of Fossil might add the ability to generate other
51 VCS interchange formats, and so for compatibility, the use of the --git
52 option recommented.
 
 
 
 
 
 
 
 
 
 
 
53
--- www/inout.wiki
+++ www/inout.wiki
@@ -48,5 +48,16 @@
48 since the git-fast-export file format is currently the only VCS interchange
49 format that Fossil will generate. However,
50 future versions of Fossil might add the ability to generate other
51 VCS interchange formats, and so for compatibility, the use of the --git
52 option recommented.
53
54 An anonymous user sends this comment:
55
56 <blockquote>
57 The main Fossil branch is called "trunk", while the main git branch is
58 called "master". After you've exported your FOSSIL repo to git, you won't
59 see any files and gitk will complain about a missing "HEAD". You can
60 resolve this problem by merging "trunk" with "master"
61 (first verify using git status that you are on the "master" branch):
62 <tt>git merge trunk</tt>
63 </blockquote>
64
+23 -24
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -5,42 +5,41 @@
55
#
66
#
77
set out [open download.html w]
88
fconfigure $out -encoding utf-8 -translation lf
99
puts $out \
10
-{<!DOCTYPE html><html>
11
-<head>
12
-<base href="/" />
13
-<title>Fossil: Downloads</title>
14
-<link rel="stylesheet" href="/fossil/style.css" type="text/css"
15
- media="screen">
16
-</head>
17
-<body>
18
-<div class="header">
19
- <div class="logo">
20
- <img src="/fossil/logo" alt="logo">
21
- <br /><nobr>Fossil</nobr>
22
- </div>
23
-
24
- <div class="title">Fossil Downloads</div>
25
-</div>
26
-<div class="mainmenu">
10
+{<!DOCTYPE html>
11
+<html>
12
+ <head>
13
+ <base href="https://www.fossil-scm.org/download.html" />
14
+ <title>Fossil: Download</title>
15
+ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
16
+ href="/fossil/timeline.rss" />
17
+ <link rel="stylesheet" href="/fossil/style.css?default" type="text/css"
18
+ media="screen" />
19
+ </head>
20
+
21
+ <body>
22
+ <div class="header">
23
+ <div class="title"><h1>Fossil</h1>Download</div>
24
+ </div>
25
+ <div class="mainmenu">
2726
<a href='/fossil/doc/trunk/www/index.wiki'>Home</a>
2827
<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>
28
+<a href='/fossil/dir?ci=tip'>Code</a>
29
+<a href='/fossil/doc/trunk/www/permutedindex.html'>Docs</a>
3230
<a href='/fossil/brlist'>Branches</a>
33
-<a href='/fossil/taglist'>Tags</a>
34
-<a href='/fossil/reportlist'>Tickets</a>
31
+<a href='/fossil/ticket'>Tickets</a>
32
+<a href='/fossil/wiki'>Wiki</a>
33
+<a href='/download.html' class='active'>Download</a>
3534
</div>
3635
<div class="content">
3736
<p>
3837
3938
<center><font size=4>}
4039
puts $out \
41
-"<b>To install Fossil \u2192</b> download the stand-alone executable"
40
+"<b>To install Fossil &rarr;</b> download the stand-alone executable"
4241
puts $out \
4342
{and put it on your $PATH.
4443
</font><p><small>
4544
RPMs available
4645
<a href="http://download.opensuse.org/repositories/home:/rmax:/fossil/">
@@ -111,11 +110,11 @@
111110
puts $out "</td></tr>"
112111
}
113112
}
114113
puts $out "<tr><td colspan=5><hr></td></tr>"
115114
116
-puts $out {</table>
115
+puts $out {</table></div>
117116
</body>
118117
</html>
119118
}
120119
121120
close $out
122121
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -5,42 +5,41 @@
5 #
6 #
7 set out [open download.html w]
8 fconfigure $out -encoding utf-8 -translation lf
9 puts $out \
10 {<!DOCTYPE html><html>
11 <head>
12 <base href="/" />
13 <title>Fossil: Downloads</title>
14 <link rel="stylesheet" href="/fossil/style.css" type="text/css"
15 media="screen">
16 </head>
17 <body>
18 <div class="header">
19 <div class="logo">
20 <img src="/fossil/logo" alt="logo">
21 <br /><nobr>Fossil</nobr>
22 </div>
23
24 <div class="title">Fossil Downloads</div>
25 </div>
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 <p>
38
39 <center><font size=4>}
40 puts $out \
41 "<b>To install Fossil \u2192</b> download the stand-alone executable"
42 puts $out \
43 {and put it on your $PATH.
44 </font><p><small>
45 RPMs available
46 <a href="http://download.opensuse.org/repositories/home:/rmax:/fossil/">
@@ -111,11 +110,11 @@
111 puts $out "</td></tr>"
112 }
113 }
114 puts $out "<tr><td colspan=5><hr></td></tr>"
115
116 puts $out {</table>
117 </body>
118 </html>
119 }
120
121 close $out
122
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -5,42 +5,41 @@
5 #
6 #
7 set out [open download.html w]
8 fconfigure $out -encoding utf-8 -translation lf
9 puts $out \
10 {<!DOCTYPE html>
11 <html>
12 <head>
13 <base href="https://www.fossil-scm.org/download.html" />
14 <title>Fossil: Download</title>
15 <link rel="alternate" type="application/rss+xml" title="RSS Feed"
16 href="/fossil/timeline.rss" />
17 <link rel="stylesheet" href="/fossil/style.css?default" type="text/css"
18 media="screen" />
19 </head>
20
21 <body>
22 <div class="header">
23 <div class="title"><h1>Fossil</h1>Download</div>
24 </div>
25 <div class="mainmenu">
 
26 <a href='/fossil/doc/trunk/www/index.wiki'>Home</a>
27 <a href='/fossil/timeline?y=ci'>Timeline</a>
28 <a href='/fossil/dir?ci=tip'>Code</a>
29 <a href='/fossil/doc/trunk/www/permutedindex.html'>Docs</a>
 
30 <a href='/fossil/brlist'>Branches</a>
31 <a href='/fossil/ticket'>Tickets</a>
32 <a href='/fossil/wiki'>Wiki</a>
33 <a href='/download.html' class='active'>Download</a>
34 </div>
35 <div class="content">
36 <p>
37
38 <center><font size=4>}
39 puts $out \
40 "<b>To install Fossil &rarr;</b> download the stand-alone executable"
41 puts $out \
42 {and put it on your $PATH.
43 </font><p><small>
44 RPMs available
45 <a href="http://download.opensuse.org/repositories/home:/rmax:/fossil/">
@@ -111,11 +110,11 @@
110 puts $out "</td></tr>"
111 }
112 }
113 puts $out "<tr><td colspan=5><hr></td></tr>"
114
115 puts $out {</table></div>
116 </body>
117 </html>
118 }
119
120 close $out
121
+3 -1
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -17,10 +17,11 @@
1717
checkin.wiki {Check-in Checklist}
1818
changes.wiki {Fossil Changelog}
1919
copyright-release.html {Contributor License Agreement}
2020
concepts.wiki {Fossil Core Concepts}
2121
contribute.wiki {Contributing Code or Documentation To The Fossil Project}
22
+ customskin.md {Theming: Customizing The Appearance of Web Pages}
2223
custom_ticket.wiki {Customizing The Ticket System}
2324
delta_encoder_algorithm.wiki {Fossil Delta Encoding Algorithm}
2425
delta_format.wiki {Fossil Delta Format}
2526
embeddeddoc.wiki {Embedded Project Documentation}
2627
event.wiki {Events}
@@ -55,10 +56,11 @@
5556
ssl.wiki {Using SSL with Fossil}
5657
sync.wiki {The Fossil Sync Protocol}
5758
tech_overview.wiki {A Technical Overview Of The Design And Implementation
5859
Of Fossil}
5960
tech_overview.wiki {SQLite Databases Used By Fossil}
61
+ th1.md {The TH1 Scripting Language}
6062
tickets.wiki {The Fossil Ticket System}
6163
theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
6264
webui.wiki {The Fossil Web Interface}
6365
wikitheory.wiki {Wiki In Fossil}
6466
}
@@ -93,11 +95,11 @@
9395
<h2>Primary Documents:</h2>
9496
<ul>
9597
<li> <a href='quickstart.wiki'>Quick-start Guide</a>
9698
<li> <a href='faq.wiki'>FAQ</a>
9799
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
98
-<li> <a href='COPYRIGHT-BSD2.txt'>License</a>
100
+<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
99101
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
100102
book</a>
101103
<li> <a href='../../../help'>Command-line help</a>
102104
</ul>
103105
<a name="pindex"></a>
104106
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -17,10 +17,11 @@
17 checkin.wiki {Check-in Checklist}
18 changes.wiki {Fossil Changelog}
19 copyright-release.html {Contributor License Agreement}
20 concepts.wiki {Fossil Core Concepts}
21 contribute.wiki {Contributing Code or Documentation To The Fossil Project}
 
22 custom_ticket.wiki {Customizing The Ticket System}
23 delta_encoder_algorithm.wiki {Fossil Delta Encoding Algorithm}
24 delta_format.wiki {Fossil Delta Format}
25 embeddeddoc.wiki {Embedded Project Documentation}
26 event.wiki {Events}
@@ -55,10 +56,11 @@
55 ssl.wiki {Using SSL with Fossil}
56 sync.wiki {The Fossil Sync Protocol}
57 tech_overview.wiki {A Technical Overview Of The Design And Implementation
58 Of Fossil}
59 tech_overview.wiki {SQLite Databases Used By Fossil}
 
60 tickets.wiki {The Fossil Ticket System}
61 theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
62 webui.wiki {The Fossil Web Interface}
63 wikitheory.wiki {Wiki In Fossil}
64 }
@@ -93,11 +95,11 @@
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
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -17,10 +17,11 @@
17 checkin.wiki {Check-in Checklist}
18 changes.wiki {Fossil Changelog}
19 copyright-release.html {Contributor License Agreement}
20 concepts.wiki {Fossil Core Concepts}
21 contribute.wiki {Contributing Code or Documentation To The Fossil Project}
22 customskin.md {Theming: Customizing The Appearance of Web Pages}
23 custom_ticket.wiki {Customizing The Ticket System}
24 delta_encoder_algorithm.wiki {Fossil Delta Encoding Algorithm}
25 delta_format.wiki {Fossil Delta Format}
26 embeddeddoc.wiki {Embedded Project Documentation}
27 event.wiki {Events}
@@ -55,10 +56,11 @@
56 ssl.wiki {Using SSL with Fossil}
57 sync.wiki {The Fossil Sync Protocol}
58 tech_overview.wiki {A Technical Overview Of The Design And Implementation
59 Of Fossil}
60 tech_overview.wiki {SQLite Databases Used By Fossil}
61 th1.md {The TH1 Scripting Language}
62 tickets.wiki {The Fossil Ticket System}
63 theory1.wiki {Thoughts On The Design Of The Fossil DVCS}
64 webui.wiki {The Fossil Web Interface}
65 wikitheory.wiki {Wiki In Fossil}
66 }
@@ -93,11 +95,11 @@
95 <h2>Primary Documents:</h2>
96 <ul>
97 <li> <a href='quickstart.wiki'>Quick-start Guide</a>
98 <li> <a href='faq.wiki'>FAQ</a>
99 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
100 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
101 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
102 book</a>
103 <li> <a href='../../../help'>Command-line help</a>
104 </ul>
105 <a name="pindex"></a>
106
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,11 +9,11 @@
99
<h2>Primary Documents:</h2>
1010
<ul>
1111
<li> <a href='quickstart.wiki'>Quick-start Guide</a>
1212
<li> <a href='faq.wiki'>FAQ</a>
1313
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
14
-<li> <a href='COPYRIGHT-BSD2.txt'>License</a>
14
+<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
1515
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
1616
book</a>
1717
<li> <a href='../../../help'>Command-line help</a>
1818
</ul>
1919
<a name="pindex"></a>
@@ -24,10 +24,11 @@
2424
<li><a href="tech_overview.wiki">A Technical Overview Of The Design And Implementation Of Fossil</a></li>
2525
<li><a href="adding_code.wiki">Adding New Features To Fossil</a></li>
2626
<li><a href="antibot.wiki">against Spiders and Bots &mdash; Defense</a></li>
2727
<li><a href="copyright-release.html">Agreement &mdash; Contributor License</a></li>
2828
<li><a href="delta_encoder_algorithm.wiki">Algorithm &mdash; Fossil Delta Encoding</a></li>
29
+<li><a href="customskin.md">Appearance of Web Pages &mdash; Theming: Customizing The</a></li>
2930
<li><a href="fiveminutes.wiki">as a Single User &mdash; Update and Running in 5 Minutes</a></li>
3031
<li><a href="faq.wiki">Asked Questions &mdash; Frequently</a></li>
3132
<li><a href="password.wiki">Authentication &mdash; Password Management And</a></li>
3233
<li><a href="antibot.wiki">Bots &mdash; Defense against Spiders and</a></li>
3334
<li><a href="private.wiki">Branches &mdash; Creating, Syncing, and Deleting Private</a></li>
@@ -51,10 +52,11 @@
5152
<li><a href="copyright-release.html">Contributor License Agreement</a></li>
5253
<li><a href="concepts.wiki">Core Concepts &mdash; Fossil</a></li>
5354
<li><a href="newrepo.wiki">Create A New Fossil Repository &mdash; How To</a></li>
5455
<li><a href="private.wiki">Creating, Syncing, and Deleting Private Branches</a></li>
5556
<li><a href="qandc.wiki">Criticisms &mdash; Questions And</a></li>
57
+<li><a href="customskin.md">Customizing The Appearance of Web Pages &mdash; Theming:</a></li>
5658
<li><a href="custom_ticket.wiki">Customizing The Ticket System</a></li>
5759
<li><a href="tech_overview.wiki">Databases Used By Fossil &mdash; SQLite</a></li>
5860
<li><a href="antibot.wiki">Defense against Spiders and Bots</a></li>
5961
<li><a href="shunning.wiki">Deleting Content From Fossil &mdash; Shunning:</a></li>
6062
<li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
@@ -110,10 +112,11 @@
110112
<li><a href="inout.wiki">Import And Export To And From Git</a></li>
111113
<li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
112114
<li><a href="fossil-from-msvc.wiki">Integrating Fossil in the Microsoft Express 2010 IDE</a></li>
113115
<li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
114116
<li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
117
+<li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
115118
<li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
116119
<li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
117120
<li><a href="branching.wiki">Merging, and Tagging &mdash; Branching, Forking,</a></li>
118121
<li><a href="fossil-from-msvc.wiki">Microsoft Express 2010 IDE &mdash; Integrating Fossil in the</a></li>
119122
<li><a href="fiveminutes.wiki">Minutes as a Single User &mdash; Update and Running in 5</a></li>
@@ -122,10 +125,11 @@
122125
<li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
123126
<li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
124127
<li><a href="pop.wiki">Operations &mdash; Principles Of</a></li>
125128
<li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil &mdash; A Technical</a></li>
126129
<li><a href="index.wiki">Page &mdash; Home</a></li>
130
+<li><a href="customskin.md">Pages &mdash; Theming: Customizing The Appearance of Web</a></li>
127131
<li><a href="password.wiki">Password Management And Authentication</a></li>
128132
<li><a href="quotes.wiki">People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What</a></li>
129133
<li><a href="stats.wiki">Performance Statistics</a></li>
130134
<li><a href="../test/release-checklist.wiki">Pre-Release Testing Checklist</a></li>
131135
<li><a href="pop.wiki">Principles Of Operations</a></li>
@@ -143,10 +147,11 @@
143147
<li><a href="newrepo.wiki">Repository &mdash; How To Create A New Fossil</a></li>
144148
<li><a href="selfcheck.wiki">Repository Integrity Self Checks &mdash; Fossil</a></li>
145149
<li><a href="reviews.wiki">Reviews</a></li>
146150
<li><a href="fiveminutes.wiki">Running in 5 Minutes as a Single User &mdash; Update and</a></li>
147151
<li><a href="quotes.wiki">Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What People Are</a></li>
152
+<li><a href="th1.md">Scripting Language &mdash; The TH1</a></li>
148153
<li><a href="selfcheck.wiki">Self Checks &mdash; Fossil Repository Integrity</a></li>
149154
<li><a href="selfhost.wiki">Self Hosting Repositories &mdash; Fossil</a></li>
150155
<li><a href="server.wiki">Server &mdash; How To Configure A Fossil</a></li>
151156
<li><a href="settings.wiki">Settings &mdash; Fossil</a></li>
152157
<li><a href="shunning.wiki">Shunning: Deleting Content From Fossil</a></li>
@@ -164,14 +169,17 @@
164169
<li><a href="custom_ticket.wiki">System &mdash; Customizing The Ticket</a></li>
165170
<li><a href="tickets.wiki">System &mdash; The Fossil Ticket</a></li>
166171
<li><a href="branching.wiki">Tagging &mdash; Branching, Forking, Merging, and</a></li>
167172
<li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil &mdash; A</a></li>
168173
<li><a href="../test/release-checklist.wiki">Testing Checklist &mdash; Pre-Release</a></li>
174
+<li><a href="th1.md">TH1 Scripting Language &mdash; The</a></li>
169175
<li><a href="makefile.wiki">The Fossil Build Process</a></li>
170176
<li><a href="sync.wiki">The Fossil Sync Protocol</a></li>
171177
<li><a href="tickets.wiki">The Fossil Ticket System</a></li>
172178
<li><a href="webui.wiki">The Fossil Web Interface</a></li>
179
+<li><a href="th1.md">The TH1 Scripting Language</a></li>
180
+<li><a href="customskin.md">Theming: Customizing The Appearance of Web Pages</a></li>
173181
<li><a href="theory1.wiki">Thoughts On The Design Of The Fossil DVCS</a></li>
174182
<li><a href="custom_ticket.wiki">Ticket System &mdash; Customizing The</a></li>
175183
<li><a href="tickets.wiki">Ticket System &mdash; The Fossil</a></li>
176184
<li><a href="hints.wiki">Tips And Usage Hints &mdash; Fossil</a></li>
177185
<li><a href="bugtheory.wiki">Tracking In Fossil &mdash; Bug</a></li>
@@ -180,9 +188,10 @@
180188
<li><a href="fiveminutes.wiki">User &mdash; Update and Running in 5 Minutes as a Single</a></li>
181189
<li><a href="ssl.wiki">Using SSL with Fossil</a></li>
182190
<li><a href="checkin_names.wiki">Version Names &mdash; Checkin And</a></li>
183191
<li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
184192
<li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
193
+<li><a href="customskin.md">Web Pages &mdash; Theming: Customizing The Appearance of</a></li>
185194
<li><a href="quotes.wiki">What People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes:</a></li>
186195
<li><a href="wikitheory.wiki">Wiki In Fossil</a></li>
187196
<li><a href="ssl.wiki">with Fossil &mdash; Using SSL</a></li>
188197
</ul></div>
189198
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,11 +9,11 @@
9 <h2>Primary Documents:</h2>
10 <ul>
11 <li> <a href='quickstart.wiki'>Quick-start Guide</a>
12 <li> <a href='faq.wiki'>FAQ</a>
13 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
14 <li> <a href='COPYRIGHT-BSD2.txt'>License</a>
15 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
16 book</a>
17 <li> <a href='../../../help'>Command-line help</a>
18 </ul>
19 <a name="pindex"></a>
@@ -24,10 +24,11 @@
24 <li><a href="tech_overview.wiki">A Technical Overview Of The Design And Implementation Of Fossil</a></li>
25 <li><a href="adding_code.wiki">Adding New Features To Fossil</a></li>
26 <li><a href="antibot.wiki">against Spiders and Bots &mdash; Defense</a></li>
27 <li><a href="copyright-release.html">Agreement &mdash; Contributor License</a></li>
28 <li><a href="delta_encoder_algorithm.wiki">Algorithm &mdash; Fossil Delta Encoding</a></li>
 
29 <li><a href="fiveminutes.wiki">as a Single User &mdash; Update and Running in 5 Minutes</a></li>
30 <li><a href="faq.wiki">Asked Questions &mdash; Frequently</a></li>
31 <li><a href="password.wiki">Authentication &mdash; Password Management And</a></li>
32 <li><a href="antibot.wiki">Bots &mdash; Defense against Spiders and</a></li>
33 <li><a href="private.wiki">Branches &mdash; Creating, Syncing, and Deleting Private</a></li>
@@ -51,10 +52,11 @@
51 <li><a href="copyright-release.html">Contributor License Agreement</a></li>
52 <li><a href="concepts.wiki">Core Concepts &mdash; Fossil</a></li>
53 <li><a href="newrepo.wiki">Create A New Fossil Repository &mdash; How To</a></li>
54 <li><a href="private.wiki">Creating, Syncing, and Deleting Private Branches</a></li>
55 <li><a href="qandc.wiki">Criticisms &mdash; Questions And</a></li>
 
56 <li><a href="custom_ticket.wiki">Customizing The Ticket System</a></li>
57 <li><a href="tech_overview.wiki">Databases Used By Fossil &mdash; SQLite</a></li>
58 <li><a href="antibot.wiki">Defense against Spiders and Bots</a></li>
59 <li><a href="shunning.wiki">Deleting Content From Fossil &mdash; Shunning:</a></li>
60 <li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
@@ -110,10 +112,11 @@
110 <li><a href="inout.wiki">Import And Export To And From Git</a></li>
111 <li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
112 <li><a href="fossil-from-msvc.wiki">Integrating Fossil in the Microsoft Express 2010 IDE</a></li>
113 <li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
114 <li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
 
115 <li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
116 <li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
117 <li><a href="branching.wiki">Merging, and Tagging &mdash; Branching, Forking,</a></li>
118 <li><a href="fossil-from-msvc.wiki">Microsoft Express 2010 IDE &mdash; Integrating Fossil in the</a></li>
119 <li><a href="fiveminutes.wiki">Minutes as a Single User &mdash; Update and Running in 5</a></li>
@@ -122,10 +125,11 @@
122 <li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
123 <li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
124 <li><a href="pop.wiki">Operations &mdash; Principles Of</a></li>
125 <li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil &mdash; A Technical</a></li>
126 <li><a href="index.wiki">Page &mdash; Home</a></li>
 
127 <li><a href="password.wiki">Password Management And Authentication</a></li>
128 <li><a href="quotes.wiki">People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What</a></li>
129 <li><a href="stats.wiki">Performance Statistics</a></li>
130 <li><a href="../test/release-checklist.wiki">Pre-Release Testing Checklist</a></li>
131 <li><a href="pop.wiki">Principles Of Operations</a></li>
@@ -143,10 +147,11 @@
143 <li><a href="newrepo.wiki">Repository &mdash; How To Create A New Fossil</a></li>
144 <li><a href="selfcheck.wiki">Repository Integrity Self Checks &mdash; Fossil</a></li>
145 <li><a href="reviews.wiki">Reviews</a></li>
146 <li><a href="fiveminutes.wiki">Running in 5 Minutes as a Single User &mdash; Update and</a></li>
147 <li><a href="quotes.wiki">Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What People Are</a></li>
 
148 <li><a href="selfcheck.wiki">Self Checks &mdash; Fossil Repository Integrity</a></li>
149 <li><a href="selfhost.wiki">Self Hosting Repositories &mdash; Fossil</a></li>
150 <li><a href="server.wiki">Server &mdash; How To Configure A Fossil</a></li>
151 <li><a href="settings.wiki">Settings &mdash; Fossil</a></li>
152 <li><a href="shunning.wiki">Shunning: Deleting Content From Fossil</a></li>
@@ -164,14 +169,17 @@
164 <li><a href="custom_ticket.wiki">System &mdash; Customizing The Ticket</a></li>
165 <li><a href="tickets.wiki">System &mdash; The Fossil Ticket</a></li>
166 <li><a href="branching.wiki">Tagging &mdash; Branching, Forking, Merging, and</a></li>
167 <li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil &mdash; A</a></li>
168 <li><a href="../test/release-checklist.wiki">Testing Checklist &mdash; Pre-Release</a></li>
 
169 <li><a href="makefile.wiki">The Fossil Build Process</a></li>
170 <li><a href="sync.wiki">The Fossil Sync Protocol</a></li>
171 <li><a href="tickets.wiki">The Fossil Ticket System</a></li>
172 <li><a href="webui.wiki">The Fossil Web Interface</a></li>
 
 
173 <li><a href="theory1.wiki">Thoughts On The Design Of The Fossil DVCS</a></li>
174 <li><a href="custom_ticket.wiki">Ticket System &mdash; Customizing The</a></li>
175 <li><a href="tickets.wiki">Ticket System &mdash; The Fossil</a></li>
176 <li><a href="hints.wiki">Tips And Usage Hints &mdash; Fossil</a></li>
177 <li><a href="bugtheory.wiki">Tracking In Fossil &mdash; Bug</a></li>
@@ -180,9 +188,10 @@
180 <li><a href="fiveminutes.wiki">User &mdash; Update and Running in 5 Minutes as a Single</a></li>
181 <li><a href="ssl.wiki">Using SSL with Fossil</a></li>
182 <li><a href="checkin_names.wiki">Version Names &mdash; Checkin And</a></li>
183 <li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
184 <li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
 
185 <li><a href="quotes.wiki">What People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes:</a></li>
186 <li><a href="wikitheory.wiki">Wiki In Fossil</a></li>
187 <li><a href="ssl.wiki">with Fossil &mdash; Using SSL</a></li>
188 </ul></div>
189
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,11 +9,11 @@
9 <h2>Primary Documents:</h2>
10 <ul>
11 <li> <a href='quickstart.wiki'>Quick-start Guide</a>
12 <li> <a href='faq.wiki'>FAQ</a>
13 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
14 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
15 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
16 book</a>
17 <li> <a href='../../../help'>Command-line help</a>
18 </ul>
19 <a name="pindex"></a>
@@ -24,10 +24,11 @@
24 <li><a href="tech_overview.wiki">A Technical Overview Of The Design And Implementation Of Fossil</a></li>
25 <li><a href="adding_code.wiki">Adding New Features To Fossil</a></li>
26 <li><a href="antibot.wiki">against Spiders and Bots &mdash; Defense</a></li>
27 <li><a href="copyright-release.html">Agreement &mdash; Contributor License</a></li>
28 <li><a href="delta_encoder_algorithm.wiki">Algorithm &mdash; Fossil Delta Encoding</a></li>
29 <li><a href="customskin.md">Appearance of Web Pages &mdash; Theming: Customizing The</a></li>
30 <li><a href="fiveminutes.wiki">as a Single User &mdash; Update and Running in 5 Minutes</a></li>
31 <li><a href="faq.wiki">Asked Questions &mdash; Frequently</a></li>
32 <li><a href="password.wiki">Authentication &mdash; Password Management And</a></li>
33 <li><a href="antibot.wiki">Bots &mdash; Defense against Spiders and</a></li>
34 <li><a href="private.wiki">Branches &mdash; Creating, Syncing, and Deleting Private</a></li>
@@ -51,10 +52,11 @@
52 <li><a href="copyright-release.html">Contributor License Agreement</a></li>
53 <li><a href="concepts.wiki">Core Concepts &mdash; Fossil</a></li>
54 <li><a href="newrepo.wiki">Create A New Fossil Repository &mdash; How To</a></li>
55 <li><a href="private.wiki">Creating, Syncing, and Deleting Private Branches</a></li>
56 <li><a href="qandc.wiki">Criticisms &mdash; Questions And</a></li>
57 <li><a href="customskin.md">Customizing The Appearance of Web Pages &mdash; Theming:</a></li>
58 <li><a href="custom_ticket.wiki">Customizing The Ticket System</a></li>
59 <li><a href="tech_overview.wiki">Databases Used By Fossil &mdash; SQLite</a></li>
60 <li><a href="antibot.wiki">Defense against Spiders and Bots</a></li>
61 <li><a href="shunning.wiki">Deleting Content From Fossil &mdash; Shunning:</a></li>
62 <li><a href="private.wiki">Deleting Private Branches &mdash; Creating, Syncing, and</a></li>
@@ -110,10 +112,11 @@
112 <li><a href="inout.wiki">Import And Export To And From Git</a></li>
113 <li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
114 <li><a href="fossil-from-msvc.wiki">Integrating Fossil in the Microsoft Express 2010 IDE</a></li>
115 <li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
116 <li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
117 <li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
118 <li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
119 <li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
120 <li><a href="branching.wiki">Merging, and Tagging &mdash; Branching, Forking,</a></li>
121 <li><a href="fossil-from-msvc.wiki">Microsoft Express 2010 IDE &mdash; Integrating Fossil in the</a></li>
122 <li><a href="fiveminutes.wiki">Minutes as a Single User &mdash; Update and Running in 5</a></li>
@@ -122,10 +125,11 @@
125 <li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
126 <li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
127 <li><a href="pop.wiki">Operations &mdash; Principles Of</a></li>
128 <li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil &mdash; A Technical</a></li>
129 <li><a href="index.wiki">Page &mdash; Home</a></li>
130 <li><a href="customskin.md">Pages &mdash; Theming: Customizing The Appearance of Web</a></li>
131 <li><a href="password.wiki">Password Management And Authentication</a></li>
132 <li><a href="quotes.wiki">People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What</a></li>
133 <li><a href="stats.wiki">Performance Statistics</a></li>
134 <li><a href="../test/release-checklist.wiki">Pre-Release Testing Checklist</a></li>
135 <li><a href="pop.wiki">Principles Of Operations</a></li>
@@ -143,10 +147,11 @@
147 <li><a href="newrepo.wiki">Repository &mdash; How To Create A New Fossil</a></li>
148 <li><a href="selfcheck.wiki">Repository Integrity Self Checks &mdash; Fossil</a></li>
149 <li><a href="reviews.wiki">Reviews</a></li>
150 <li><a href="fiveminutes.wiki">Running in 5 Minutes as a Single User &mdash; Update and</a></li>
151 <li><a href="quotes.wiki">Saying About Fossil, Git, and DVCSes in General &mdash; Quotes: What People Are</a></li>
152 <li><a href="th1.md">Scripting Language &mdash; The TH1</a></li>
153 <li><a href="selfcheck.wiki">Self Checks &mdash; Fossil Repository Integrity</a></li>
154 <li><a href="selfhost.wiki">Self Hosting Repositories &mdash; Fossil</a></li>
155 <li><a href="server.wiki">Server &mdash; How To Configure A Fossil</a></li>
156 <li><a href="settings.wiki">Settings &mdash; Fossil</a></li>
157 <li><a href="shunning.wiki">Shunning: Deleting Content From Fossil</a></li>
@@ -164,14 +169,17 @@
169 <li><a href="custom_ticket.wiki">System &mdash; Customizing The Ticket</a></li>
170 <li><a href="tickets.wiki">System &mdash; The Fossil Ticket</a></li>
171 <li><a href="branching.wiki">Tagging &mdash; Branching, Forking, Merging, and</a></li>
172 <li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil &mdash; A</a></li>
173 <li><a href="../test/release-checklist.wiki">Testing Checklist &mdash; Pre-Release</a></li>
174 <li><a href="th1.md">TH1 Scripting Language &mdash; The</a></li>
175 <li><a href="makefile.wiki">The Fossil Build Process</a></li>
176 <li><a href="sync.wiki">The Fossil Sync Protocol</a></li>
177 <li><a href="tickets.wiki">The Fossil Ticket System</a></li>
178 <li><a href="webui.wiki">The Fossil Web Interface</a></li>
179 <li><a href="th1.md">The TH1 Scripting Language</a></li>
180 <li><a href="customskin.md">Theming: Customizing The Appearance of Web Pages</a></li>
181 <li><a href="theory1.wiki">Thoughts On The Design Of The Fossil DVCS</a></li>
182 <li><a href="custom_ticket.wiki">Ticket System &mdash; Customizing The</a></li>
183 <li><a href="tickets.wiki">Ticket System &mdash; The Fossil</a></li>
184 <li><a href="hints.wiki">Tips And Usage Hints &mdash; Fossil</a></li>
185 <li><a href="bugtheory.wiki">Tracking In Fossil &mdash; Bug</a></li>
@@ -180,9 +188,10 @@
188 <li><a href="fiveminutes.wiki">User &mdash; Update and Running in 5 Minutes as a Single</a></li>
189 <li><a href="ssl.wiki">Using SSL with Fossil</a></li>
190 <li><a href="checkin_names.wiki">Version Names &mdash; Checkin And</a></li>
191 <li><a href="fossil-v-git.wiki">Versus Git &mdash; Fossil</a></li>
192 <li><a href="webui.wiki">Web Interface &mdash; The Fossil</a></li>
193 <li><a href="customskin.md">Web Pages &mdash; Theming: Customizing The Appearance of</a></li>
194 <li><a href="quotes.wiki">What People Are Saying About Fossil, Git, and DVCSes in General &mdash; Quotes:</a></li>
195 <li><a href="wikitheory.wiki">Wiki In Fossil</a></li>
196 <li><a href="ssl.wiki">with Fossil &mdash; Using SSL</a></li>
197 </ul></div>
198
+9 -2
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -71,16 +71,23 @@
7171
<blockquote>
7272
<i>Stephen Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
7373
2014-09-01.</i>
7474
</blockquote>
7575
76
+<li>If programmers _really_ wanted to help scientists, they'd build a version control
77
+system that was more usable than Git.
78
+
79
+<blockquote>
80
+<i>Tweet by Greg Wilson @gvwilson on 2015-02-22 17:47</i>
81
+</blockquote>
82
+
7683
</ol>
7784
7885
<h2>On The Usability Of Fossil:</h2>
7986
8087
<ol>
81
-<li value=9>
88
+<li value=10>
8289
Fossil mesmerizes me with simplicity especially after I struggled to
8390
get a bug-tracking system to work with mercurial.
8491
8592
<blockquote>
8693
<i>rawjeev at [http://stackoverflow.com/questions/156322/what-do-people-think-of-the-fossil-dvcs]</i>
@@ -108,11 +115,11 @@
108115
109116
110117
<h2>On Git Versus Fossil</h2>
111118
112119
<ol>
113
-<li value=12>
120
+<li value=13>
114121
Just want to say thanks for fossil making my life easier....
115122
Also <nowiki>[for]</nowiki> not having a misanthropic command line interface.
116123
117124
<blockquote>
118125
<i>Joshua Paine at [http://www.mail-archive.com/[email protected]/msg02736.html]</i>
119126
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -71,16 +71,23 @@
71 <blockquote>
72 <i>Stephen Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
73 2014-09-01.</i>
74 </blockquote>
75
 
 
 
 
 
 
 
76 </ol>
77
78 <h2>On The Usability Of Fossil:</h2>
79
80 <ol>
81 <li value=9>
82 Fossil mesmerizes me with simplicity especially after I struggled to
83 get a bug-tracking system to work with mercurial.
84
85 <blockquote>
86 <i>rawjeev at [http://stackoverflow.com/questions/156322/what-do-people-think-of-the-fossil-dvcs]</i>
@@ -108,11 +115,11 @@
108
109
110 <h2>On Git Versus Fossil</h2>
111
112 <ol>
113 <li value=12>
114 Just want to say thanks for fossil making my life easier....
115 Also <nowiki>[for]</nowiki> not having a misanthropic command line interface.
116
117 <blockquote>
118 <i>Joshua Paine at [http://www.mail-archive.com/[email protected]/msg02736.html]</i>
119
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -71,16 +71,23 @@
71 <blockquote>
72 <i>Stephen Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
73 2014-09-01.</i>
74 </blockquote>
75
76 <li>If programmers _really_ wanted to help scientists, they'd build a version control
77 system that was more usable than Git.
78
79 <blockquote>
80 <i>Tweet by Greg Wilson @gvwilson on 2015-02-22 17:47</i>
81 </blockquote>
82
83 </ol>
84
85 <h2>On The Usability Of Fossil:</h2>
86
87 <ol>
88 <li value=10>
89 Fossil mesmerizes me with simplicity especially after I struggled to
90 get a bug-tracking system to work with mercurial.
91
92 <blockquote>
93 <i>rawjeev at [http://stackoverflow.com/questions/156322/what-do-people-think-of-the-fossil-dvcs]</i>
@@ -108,11 +115,11 @@
115
116
117 <h2>On Git Versus Fossil</h2>
118
119 <ol>
120 <li value=13>
121 Just want to say thanks for fossil making my life easier....
122 Also <nowiki>[for]</nowiki> not having a misanthropic command line interface.
123
124 <blockquote>
125 <i>Joshua Paine at [http://www.mail-archive.com/[email protected]/msg02736.html]</i>
126
--- www/selfcheck.wiki
+++ www/selfcheck.wiki
@@ -44,11 +44,11 @@
4444
new delta-encoding mechanism designed expressly for fossil. We want
4545
to make sure that bugs in these encoding mechanisms do not lead to
4646
loss of data.
4747
4848
To increase our confidence that everything in the repository is
49
-recoverable, fossil makes sure it can extract an exact replicate
49
+recoverable, fossil makes sure it can extract an exact replica
5050
of every content file that it changes just prior to transaction
5151
commit. So during the course of check-in (or other repository
5252
operation) many different files
5353
in the repository might be modified. Some files are simply
5454
compressed. Other files are delta encoded and then compressed.
5555
5656
ADDED www/th1.md
5757
ADDED www/webpage-ex.md
--- www/selfcheck.wiki
+++ www/selfcheck.wiki
@@ -44,11 +44,11 @@
44 new delta-encoding mechanism designed expressly for fossil. We want
45 to make sure that bugs in these encoding mechanisms do not lead to
46 loss of data.
47
48 To increase our confidence that everything in the repository is
49 recoverable, fossil makes sure it can extract an exact replicate
50 of every content file that it changes just prior to transaction
51 commit. So during the course of check-in (or other repository
52 operation) many different files
53 in the repository might be modified. Some files are simply
54 compressed. Other files are delta encoded and then compressed.
55
56 DDED www/th1.md
57 DDED www/webpage-ex.md
--- www/selfcheck.wiki
+++ www/selfcheck.wiki
@@ -44,11 +44,11 @@
44 new delta-encoding mechanism designed expressly for fossil. We want
45 to make sure that bugs in these encoding mechanisms do not lead to
46 loss of data.
47
48 To increase our confidence that everything in the repository is
49 recoverable, fossil makes sure it can extract an exact replica
50 of every content file that it changes just prior to transaction
51 commit. So during the course of check-in (or other repository
52 operation) many different files
53 in the repository might be modified. Some files are simply
54 compressed. Other files are delta encoded and then compressed.
55
56 DDED www/th1.md
57 DDED www/webpage-ex.md
+2
--- a/www/th1.md
+++ b/www/th1.md
@@ -0,0 +1,2 @@
1
+ sCL CLCLCLCLCL. Refer to the
2
+TCL ttpiz_main.c or thsource fileeach each command does.
--- a/www/th1.md
+++ b/www/th1.md
@@ -0,0 +1,2 @@
 
 
--- a/www/th1.md
+++ b/www/th1.md
@@ -0,0 +1,2 @@
1 sCL CLCLCLCLCL. Refer to the
2 TCL ttpiz_main.c or thsource fileeach each command does.
--- a/www/webpage-ex.md
+++ b/www/webpage-ex.md
@@ -0,0 +1,60 @@
1
+Web-Page Examples
2
+============
3
+
4
+Here are just a fewThis is not an exhaustive list.
5
+Exp t<style>
6
+.exbtn {
7
+ border: 1px solid #000;
8
+ margin: 1ex;
9
+ border-radius: 1ex;
10
+ padding: 0 1ex;
11
+ background-color: #eee;
12
+}
13
+</style>
14
+
15
+ * <a tar=25&y=ci&a=1970-01-01'>(Example)</a> &../../..
16
+ Firsy=ci&n=100'>Example</a>ss='exbtn'
17
+ href='$ROOT/tim to see many
18
+other exampl../../..mple</a>>src/file.c</b> source file.
19
+
20
+ * <a target='_blank' class='exbtn'
21
+=200&uf=0c3c2d086a'>(Example)</a> &rarr;
22
+ All check-ins using../../..rticExample</a>merge with that bran source file.
23
+
24
+ * <a target='_blank' class='exbtn'
25
+ href='$ROOT/timeline?n href='$ROOT/timelch.
26
+
27
+ * <a targ../../..-ins betwee/a>roject.)
28
+
29
+ * <a Web-Page Examples
30
+===============
31
+
32
+Here are just a few examples of the many web pages supported
33
+by Fossil. ../../..-ins betwee/a>roject.)
34
+
35
+ * <a target='_blank' class='exbtn'
36
+ hre
37
+
38
+ * <a Web-Page Examples
39
+===============
40
+
41
+Here are just a few examples of the many webrmple)</a> &rarr;
42
+ S ../../..t branch.
43
+
44
+ * <a target='_blank' class='exbtn'
45
+ href='$ROOT/timeline?n=200&tget='_blank' ll check-ins of the "svn-import" branch only.
46
+
47
+ * <aExample</../../..===============
48
+
49
+Here are just a few examples of the many web pages supported
50
+by Fossil. Follow hyperlinks on the examples below t<style>
51
+.exbtn {
52
+ border: 1px solid #000;
53
+ margin: 1ex;
54
+ bordeWeb-Pagen the most direct path from
55
+ versionExample</a>merge with that branclank' class='exbtn'
56
+ href='$ROOT/timeline?namechng'>(Example)</a> &rarr;
57
+ Show check-ins that contain file name changes
58
+
59
+ * <a Example</a>timeline?u=drh&c=2014-01-08&y=ci'>(Example)</a> &rarr;
60
+ S
--- a/www/webpage-ex.md
+++ b/www/webpage-ex.md
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/www/webpage-ex.md
+++ b/www/webpage-ex.md
@@ -0,0 +1,60 @@
1 Web-Page Examples
2 ============
3
4 Here are just a fewThis is not an exhaustive list.
5 Exp t<style>
6 .exbtn {
7 border: 1px solid #000;
8 margin: 1ex;
9 border-radius: 1ex;
10 padding: 0 1ex;
11 background-color: #eee;
12 }
13 </style>
14
15 * <a tar=25&y=ci&a=1970-01-01'>(Example)</a> &../../..
16 Firsy=ci&n=100'>Example</a>ss='exbtn'
17 href='$ROOT/tim to see many
18 other exampl../../..mple</a>>src/file.c</b> source file.
19
20 * <a target='_blank' class='exbtn'
21 =200&uf=0c3c2d086a'>(Example)</a> &rarr;
22 All check-ins using../../..rticExample</a>merge with that bran source file.
23
24 * <a target='_blank' class='exbtn'
25 href='$ROOT/timeline?n href='$ROOT/timelch.
26
27 * <a targ../../..-ins betwee/a>roject.)
28
29 * <a Web-Page Examples
30 ===============
31
32 Here are just a few examples of the many web pages supported
33 by Fossil. ../../..-ins betwee/a>roject.)
34
35 * <a target='_blank' class='exbtn'
36 hre
37
38 * <a Web-Page Examples
39 ===============
40
41 Here are just a few examples of the many webrmple)</a> &rarr;
42 S ../../..t branch.
43
44 * <a target='_blank' class='exbtn'
45 href='$ROOT/timeline?n=200&tget='_blank' ll check-ins of the "svn-import" branch only.
46
47 * <aExample</../../..===============
48
49 Here are just a few examples of the many web pages supported
50 by Fossil. Follow hyperlinks on the examples below t<style>
51 .exbtn {
52 border: 1px solid #000;
53 margin: 1ex;
54 bordeWeb-Pagen the most direct path from
55 versionExample</a>merge with that branclank' class='exbtn'
56 href='$ROOT/timeline?namechng'>(Example)</a> &rarr;
57 Show check-ins that contain file name changes
58
59 * <a Example</a>timeline?u=drh&c=2014-01-08&y=ci'>(Example)</a> &rarr;
60 S
+2 -2
--- www/webui.wiki
+++ www/webui.wiki
@@ -8,19 +8,19 @@
88
* [./wikitheory.wiki | Wiki]
99
* [./embeddeddoc.wiki | On-line documentation]
1010
* Status information
1111
* Timelines
1212
* Graphs of revision and branching history
13
- * [./event.wiki | Blogs, News, and Announcements]
13
+ * [./event.wiki | Technical notes]
1414
* File and version lists and differences
1515
* Download historical versions as ZIP archives
1616
* Historical change data
1717
* Add and remove tags on checkins
1818
* Move checkins between branches
1919
* Revise checkin comments
2020
* Manage user credentials and access permissions
21
- * And so forth...
21
+ * And so forth... (some [./webpage-ex.md|examples])
2222
2323
You get all of this, and more, for free when you use Fossil.
2424
There are no extra programs to install or setup.
2525
Everything you need is already pre-configured and built into the
2626
self-contained, stand-alone Fossil executable.
2727
--- www/webui.wiki
+++ www/webui.wiki
@@ -8,19 +8,19 @@
8 * [./wikitheory.wiki | Wiki]
9 * [./embeddeddoc.wiki | On-line documentation]
10 * Status information
11 * Timelines
12 * Graphs of revision and branching history
13 * [./event.wiki | Blogs, News, and Announcements]
14 * File and version lists and differences
15 * Download historical versions as ZIP archives
16 * Historical change data
17 * Add and remove tags on checkins
18 * Move checkins between branches
19 * Revise checkin comments
20 * Manage user credentials and access permissions
21 * And so forth...
22
23 You get all of this, and more, for free when you use Fossil.
24 There are no extra programs to install or setup.
25 Everything you need is already pre-configured and built into the
26 self-contained, stand-alone Fossil executable.
27
--- www/webui.wiki
+++ www/webui.wiki
@@ -8,19 +8,19 @@
8 * [./wikitheory.wiki | Wiki]
9 * [./embeddeddoc.wiki | On-line documentation]
10 * Status information
11 * Timelines
12 * Graphs of revision and branching history
13 * [./event.wiki | Technical notes]
14 * File and version lists and differences
15 * Download historical versions as ZIP archives
16 * Historical change data
17 * Add and remove tags on checkins
18 * Move checkins between branches
19 * Revise checkin comments
20 * Manage user credentials and access permissions
21 * And so forth... (some [./webpage-ex.md|examples])
22
23 You get all of this, and more, for free when you use Fossil.
24 There are no extra programs to install or setup.
25 Everything you need is already pre-configured and built into the
26 self-contained, stand-alone Fossil executable.
27
--- www/wikitheory.wiki
+++ www/wikitheory.wiki
@@ -5,12 +5,12 @@
55
66
* Stand-alone wiki pages.
77
* Description and comments in [./bugtheory.wiki | bug reports].
88
* Check-in comments.
99
* [./embeddeddoc.wiki | Embedded documentation] files whose
10
- name ends in "wiki".
11
- * [./event.wiki | Event descriptions].
10
+ name ends in ".wiki".
11
+ * [./event.wiki | Technical notes].
1212
1313
The [/wiki_rules | formatting rules] for fossil wiki
1414
are designed to be simple and intuitive. The idea is that wiki provides
1515
paragraph breaks, numbered and bulleted lists, and hyperlinking for
1616
simple documents together with a safe subset of HTML for more complex
@@ -31,10 +31,14 @@
3131
3. Where the fossil wiki markup language is insufficient, HTML is
3232
used. HTML is a standard language familiar to most programmers so
3333
there is nothing new to learn. And, though cumbersome, the HTML
3434
does not need to be used very often so is not a burden.
3535
36
+UPDATE: Since 2012, Fossil also contains a [/md_rules | Markdown]
37
+rendering engine. Markdown can optionally be used to format
38
+[./embeddeddoc.wiki | embedded documents], wiki pages,
39
+[./event.wiki | technical notes], and bug report text.
3640
3741
<h2>Stand-alone Wiki Pages</h2>
3842
3943
Each wiki page has its own revision history which is independent of
4044
the sequence of check-ins (check-ins). Wiki pages can branch and merge
4145
--- www/wikitheory.wiki
+++ www/wikitheory.wiki
@@ -5,12 +5,12 @@
5
6 * Stand-alone wiki pages.
7 * Description and comments in [./bugtheory.wiki | bug reports].
8 * Check-in comments.
9 * [./embeddeddoc.wiki | Embedded documentation] files whose
10 name ends in "wiki".
11 * [./event.wiki | Event descriptions].
12
13 The [/wiki_rules | formatting rules] for fossil wiki
14 are designed to be simple and intuitive. The idea is that wiki provides
15 paragraph breaks, numbered and bulleted lists, and hyperlinking for
16 simple documents together with a safe subset of HTML for more complex
@@ -31,10 +31,14 @@
31 3. Where the fossil wiki markup language is insufficient, HTML is
32 used. HTML is a standard language familiar to most programmers so
33 there is nothing new to learn. And, though cumbersome, the HTML
34 does not need to be used very often so is not a burden.
35
 
 
 
 
36
37 <h2>Stand-alone Wiki Pages</h2>
38
39 Each wiki page has its own revision history which is independent of
40 the sequence of check-ins (check-ins). Wiki pages can branch and merge
41
--- www/wikitheory.wiki
+++ www/wikitheory.wiki
@@ -5,12 +5,12 @@
5
6 * Stand-alone wiki pages.
7 * Description and comments in [./bugtheory.wiki | bug reports].
8 * Check-in comments.
9 * [./embeddeddoc.wiki | Embedded documentation] files whose
10 name ends in ".wiki".
11 * [./event.wiki | Technical notes].
12
13 The [/wiki_rules | formatting rules] for fossil wiki
14 are designed to be simple and intuitive. The idea is that wiki provides
15 paragraph breaks, numbered and bulleted lists, and hyperlinking for
16 simple documents together with a safe subset of HTML for more complex
@@ -31,10 +31,14 @@
31 3. Where the fossil wiki markup language is insufficient, HTML is
32 used. HTML is a standard language familiar to most programmers so
33 there is nothing new to learn. And, though cumbersome, the HTML
34 does not need to be used very often so is not a burden.
35
36 UPDATE: Since 2012, Fossil also contains a [/md_rules | Markdown]
37 rendering engine. Markdown can optionally be used to format
38 [./embeddeddoc.wiki | embedded documents], wiki pages,
39 [./event.wiki | technical notes], and bug report text.
40
41 <h2>Stand-alone Wiki Pages</h2>
42
43 Each wiki page has its own revision history which is independent of
44 the sequence of check-ins (check-ins). Wiki pages can branch and merge
45

Keyboard Shortcuts

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