Fossil SCM

Merge trunk. Fix compiler warning on VS.

jan.nijtmans 2015-02-16 10:18 svn-import merge
Commit ca336d2a9b6e99c888b0b54faeb6015a4942a93b
79 files changed +4 -4 +4 -4 +4 +1 +4 -4 +4 -4 +13 -7 +19 -51 +4 -4 +4 -4 -1 +4 -4 +2 -1 +51 -14 +34 -22 +6 -6 +55 -51 +2 -2 +3 -3 +8 -8 +2 -2 +12 -12 +3 -3 +22 -7 +142 -91 +11 -27 +22 -10 +1 +2 -2 +2 -2 +76 -53 +154 -108 +82 -24 +1 -1 +13 -13 +16 -10 +5 -2 +85 -10 +2 -2 +39 -6 +17 -7 +7 -4 +292 -80 +59 -26 +120 -5 +10 -7 +24 -12 +100 -9 +5 -4 +10 -10 +95 -62 +3 -3 +19 -9 +120 -17 +107 -74 +23 -14 +11 -8 +10 -10 +1 -1 +9 -2 +1 -1 +2 -2 +28 -23 +65 -10 +4 -2 +12 -1 +14 +3 +5 +10 -10 +21 -21 +44 -15 +22 -8 +30 -30 +30 -28 +3 -3 +1 -1 +1 -1 +6 -2
--- 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/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
4747
ADDED skins/eagle/README.md
--- 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
47 DDED skins/eagle/README.md
--- 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
47 DDED skins/eagle/README.md
--- a/skins/eagle/README.md
+++ b/skins/eagle/README.md
@@ -0,0 +1,4 @@
1
+For this skin to look exactly as it was intended to, the **white-foreground**
2
+setting should be enabled.
3
+
4
+This skin was contributed by Joe Mistachkin.
--- a/skins/eagle/README.md
+++ b/skins/eagle/README.md
@@ -0,0 +1,4 @@
 
 
 
 
--- a/skins/eagle/README.md
+++ b/skins/eagle/README.md
@@ -0,0 +1,4 @@
1 For this skin to look exactly as it was intended to, the **white-foreground**
2 setting should be enabled.
3
4 This skin was contributed by Joe Mistachkin.
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -159,10 +159,11 @@
159159
table.timelineTable {
160160
cellspacing: 0;
161161
border: 0;
162162
cellpadding: 0;
163163
font-family: "courier new";
164
+ border-collapse: collapse;
164165
}
165166
166167
/* Side-by-side diff */
167168
table.sbsdiff {
168169
background-color: #485D7B;
169170
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -159,10 +159,11 @@
159 table.timelineTable {
160 cellspacing: 0;
161 border: 0;
162 cellpadding: 0;
163 font-family: "courier new";
 
164 }
165
166 /* Side-by-side diff */
167 table.sbsdiff {
168 background-color: #485D7B;
169
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -159,10 +159,11 @@
159 table.timelineTable {
160 cellspacing: 0;
161 border: 0;
162 cellpadding: 0;
163 font-family: "courier new";
164 border-collapse: collapse;
165 }
166
167 /* Side-by-side diff */
168 table.sbsdiff {
169 background-color: #485D7B;
170
--- 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/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
@@ -13,11 +13,11 @@
1313
color: #4183C4;
1414
text-decoration: underline;
1515
}
1616
1717
hr {
18
- border: 0px;
18
+ color: #eee;
1919
}
2020
2121
.title {
2222
color: #4183C4;
2323
float:left;
@@ -100,30 +100,29 @@
100100
padding: 10px;
101101
border-bottom: 1px solid #ccc;
102102
}
103103
104104
.submenu a {
105
- padding: 10px;
105
+ padding: 10px 11px;
106106
text-decoration:none;
107107
color: #777;
108108
}
109109
110110
.submenu a:hover {
111
+ padding: 6px 10px;
111112
border: 1px solid #ccc;
112
- border-bottom: 1px solid #fff;
113
- border-top-left-radius: 5px;
114
- border-top-right-radius: 5px;
113
+ border-radius: 5px;
114
+ color: #000;
115115
}
116116
117117
.content {
118118
padding-top: 10px;
119119
font-size:.8em;
120120
color: #444;
121121
}
122122
123
-.udiff, .sbsdiff,
124
-.content blockquote {
123
+.udiff, .sbsdiff {
125124
font-size: .85em !important;
126125
overflow: auto;
127126
border: 1px solid #ccc;
128127
border-radius: 5px;
129128
}
@@ -183,6 +182,13 @@
183182
border-top: 1px solid #ccc;
184183
padding: 10px;
185184
font-size:.7em;
186185
margin-top: 10px;
187186
color: #ccc;
187
+}
188
+div.timelineDate {
189
+ font-weight: bold;
190
+ white-space: nowrap;
191
+}
192
+span.submenuctrl, span.submenuctrl input, select.submenuctrl {
193
+ color: #777;
188194
}
189195
--- skins/etienne1/css.txt
+++ skins/etienne1/css.txt
@@ -13,11 +13,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 +100,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 }
@@ -183,6 +182,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
@@ -13,11 +13,11 @@
13 color: #4183C4;
14 text-decoration: underline;
15 }
16
17 hr {
18 color: #eee;
19 }
20
21 .title {
22 color: #4183C4;
23 float:left;
@@ -100,30 +100,29 @@
100 padding: 10px;
101 border-bottom: 1px solid #ccc;
102 }
103
104 .submenu a {
105 padding: 10px 11px;
106 text-decoration:none;
107 color: #777;
108 }
109
110 .submenu a:hover {
111 padding: 6px 10px;
112 border: 1px solid #ccc;
113 border-radius: 5px;
114 color: #000;
 
115 }
116
117 .content {
118 padding-top: 10px;
119 font-size:.8em;
120 color: #444;
121 }
122
123 .udiff, .sbsdiff {
 
124 font-size: .85em !important;
125 overflow: auto;
126 border: 1px solid #ccc;
127 border-radius: 5px;
128 }
@@ -183,6 +182,13 @@
182 border-top: 1px solid #ccc;
183 padding: 10px;
184 font-size:.7em;
185 margin-top: 10px;
186 color: #ccc;
187 }
188 div.timelineDate {
189 font-weight: bold;
190 white-space: nowrap;
191 }
192 span.submenuctrl, span.submenuctrl input, select.submenuctrl {
193 color: #777;
194 }
195
--- 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/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/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
@@ -181,11 +181,10 @@
181181
cursor: pointer;
182182
}
183183
184184
table.report tr td {
185185
padding: 3px 5px;
186
- cursor: pointer;
187186
}
188187
189188
textarea {
190189
font-size: 1em;
191190
}
192191
--- skins/rounded1/css.txt
+++ skins/rounded1/css.txt
@@ -181,11 +181,10 @@
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
--- skins/rounded1/css.txt
+++ skins/rounded1/css.txt
@@ -181,11 +181,10 @@
181 cursor: pointer;
182 }
183
184 table.report tr td {
185 padding: 3px 5px;
 
186 }
187
188 textarea {
189 font-size: 1em;
190 }
191
--- 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
+2 -1
--- src/add.c
+++ src/add.c
@@ -153,14 +153,15 @@
153153
" WHERE pathname=%Q %s", zPath, filename_collation()) ){
154154
db_multi_exec("UPDATE vfile SET deleted=0"
155155
" WHERE pathname=%Q %s", zPath, filename_collation());
156156
}else{
157157
char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
158
+ int isExe = file_wd_isexe(zFullname);
158159
db_multi_exec(
159160
"INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)"
160161
"VALUES(%d,0,0,0,%Q,%d,%d)",
161
- vid, zPath, file_wd_isexe(zFullname), file_wd_islink(zFullname));
162
+ vid, zPath, isExe, file_wd_islink(0));
162163
fossil_free(zFullname);
163164
}
164165
if( db_changes() ){
165166
fossil_print("ADDED %s\n", zPath);
166167
return 1;
167168
--- src/add.c
+++ src/add.c
@@ -153,14 +153,15 @@
153 " WHERE pathname=%Q %s", zPath, filename_collation()) ){
154 db_multi_exec("UPDATE vfile SET deleted=0"
155 " WHERE pathname=%Q %s", zPath, filename_collation());
156 }else{
157 char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
 
158 db_multi_exec(
159 "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)"
160 "VALUES(%d,0,0,0,%Q,%d,%d)",
161 vid, zPath, file_wd_isexe(zFullname), file_wd_islink(zFullname));
162 fossil_free(zFullname);
163 }
164 if( db_changes() ){
165 fossil_print("ADDED %s\n", zPath);
166 return 1;
167
--- src/add.c
+++ src/add.c
@@ -153,14 +153,15 @@
153 " WHERE pathname=%Q %s", zPath, filename_collation()) ){
154 db_multi_exec("UPDATE vfile SET deleted=0"
155 " WHERE pathname=%Q %s", zPath, filename_collation());
156 }else{
157 char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
158 int isExe = file_wd_isexe(zFullname);
159 db_multi_exec(
160 "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)"
161 "VALUES(%d,0,0,0,%Q,%d,%d)",
162 vid, zPath, isExe, file_wd_islink(0));
163 fossil_free(zFullname);
164 }
165 if( db_changes() ){
166 fossil_print("ADDED %s\n", zPath);
167 return 1;
168
+51 -14
--- 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
**
@@ -230,11 +237,12 @@
230237
collect_argument(&extra, "deanalyze",0);
231238
collect_argument(&extra, "analyze",0);
232239
collect_argument(&extra, "wal",0);
233240
collect_argument(&extra, "stats",0);
234241
collect_argument(&extra, "index",0);
235
- collect_argument(&extra, "no-index",0);
242
+ collect_argument(&extra, "noindex",0);
243
+ collect_argument(&extra, "ifneeded", 0);
236244
}else if( strncmp(zCmd, "setting", n)==0 ){
237245
zCmd = "setting -R";
238246
collect_argv(&extra, 3);
239247
}else if( strncmp(zCmd, "unset", n)==0 ){
240248
zCmd = "unset -R";
@@ -258,26 +266,55 @@
258266
useCheckouts = 1;
259267
stopOnError = 0;
260268
quiet = 1;
261269
}else if( strncmp(zCmd, "ignore", n)==0 ){
262270
int j;
271
+ Blob fn = BLOB_INITIALIZER;
272
+ Blob sql = BLOB_INITIALIZER;
263273
useCheckouts = find_option("ckout","c",0)!=0;
264274
verify_all_options();
265275
db_begin_transaction();
266
- for(j=3; j<g.argc; j++){
267
- Blob sql;
268
- 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);
269278
blob_append_sql(&sql,
270279
"DELETE FROM global_config WHERE name GLOB '%s:%q'",
271
- useCheckouts?"ckout":"repo", g.argv[j]
280
+ useCheckouts?"ckout":"repo", blob_str(&fn)
272281
);
273282
if( dryRunFlag ){
274283
fossil_print("%s\n", blob_sql_text(&sql));
275284
}else{
276285
db_multi_exec("%s", blob_sql_text(&sql));
277286
}
278
- 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
+ }
279316
}
280317
db_end_transaction(0);
281318
return;
282319
}else if( strncmp(zCmd, "info", n)==0 ){
283320
zCmd = "info";
284321
--- 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 **
@@ -230,11 +237,12 @@
230 collect_argument(&extra, "deanalyze",0);
231 collect_argument(&extra, "analyze",0);
232 collect_argument(&extra, "wal",0);
233 collect_argument(&extra, "stats",0);
234 collect_argument(&extra, "index",0);
235 collect_argument(&extra, "no-index",0);
 
236 }else if( strncmp(zCmd, "setting", n)==0 ){
237 zCmd = "setting -R";
238 collect_argv(&extra, 3);
239 }else if( strncmp(zCmd, "unset", n)==0 ){
240 zCmd = "unset -R";
@@ -258,26 +266,55 @@
258 useCheckouts = 1;
259 stopOnError = 0;
260 quiet = 1;
261 }else if( strncmp(zCmd, "ignore", n)==0 ){
262 int j;
 
 
263 useCheckouts = find_option("ckout","c",0)!=0;
264 verify_all_options();
265 db_begin_transaction();
266 for(j=3; j<g.argc; j++){
267 Blob sql;
268 blob_zero(&sql);
269 blob_append_sql(&sql,
270 "DELETE FROM global_config WHERE name GLOB '%s:%q'",
271 useCheckouts?"ckout":"repo", g.argv[j]
272 );
273 if( dryRunFlag ){
274 fossil_print("%s\n", blob_sql_text(&sql));
275 }else{
276 db_multi_exec("%s", blob_sql_text(&sql));
277 }
278 blob_reset(&sql);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279 }
280 db_end_transaction(0);
281 return;
282 }else if( strncmp(zCmd, "info", n)==0 ){
283 zCmd = "info";
284
--- 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 **
@@ -230,11 +237,12 @@
237 collect_argument(&extra, "deanalyze",0);
238 collect_argument(&extra, "analyze",0);
239 collect_argument(&extra, "wal",0);
240 collect_argument(&extra, "stats",0);
241 collect_argument(&extra, "index",0);
242 collect_argument(&extra, "noindex",0);
243 collect_argument(&extra, "ifneeded", 0);
244 }else if( strncmp(zCmd, "setting", n)==0 ){
245 zCmd = "setting -R";
246 collect_argv(&extra, 3);
247 }else if( strncmp(zCmd, "unset", n)==0 ){
248 zCmd = "unset -R";
@@ -258,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
+6 -6
--- 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>
@@ -527,9 +527,9 @@
527527
"%s AND blob.rid IN (SELECT rid FROM tagxref"
528528
" WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
529529
" ORDER BY event.mtime DESC",
530530
timeline_query_for_www(), TAG_BRANCH
531531
);
532
- www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
532
+ www_print_timeline(&q, 0, 0, 0, 0, brtimeline_extra);
533533
db_finalize(&q);
534534
style_footer();
535535
}
536536
--- 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>
@@ -527,9 +527,9 @@
527 "%s AND blob.rid IN (SELECT rid FROM tagxref"
528 " WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
529 " ORDER BY event.mtime DESC",
530 timeline_query_for_www(), TAG_BRANCH
531 );
532 www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
533 db_finalize(&q);
534 style_footer();
535 }
536
--- 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>
@@ -527,9 +527,9 @@
527 "%s AND blob.rid IN (SELECT rid FROM tagxref"
528 " WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
529 " ORDER BY event.mtime DESC",
530 timeline_query_for_www(), TAG_BRANCH
531 );
532 www_print_timeline(&q, 0, 0, 0, 0, brtimeline_extra);
533 db_finalize(&q);
534 style_footer();
535 }
536
+55 -51
--- 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>",
@@ -128,19 +128,20 @@
128128
const char *zSubdirLink;
129129
int linkTrunk = 1;
130130
int linkTip = 1;
131131
HQuery sURI;
132132
133
- if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
133
+ 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);
141141
url_initialize(&sURI, "dir");
142
+ cgi_query_parameters_to_url(&sURI);
142143
143144
/* If the name= parameter is an empty string, make it a NULL pointer */
144145
if( zD && strlen(zD)==0 ){ zD = 0; }
145146
146147
/* If a specific check-in is requested, fetch and parse it. If the
@@ -152,20 +153,18 @@
152153
if( pM ){
153154
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
154155
linkTrunk = trunkRid && rid != trunkRid;
155156
linkTip = rid != symbolic_name_to_rid("tip", "ci");
156157
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
157
- url_add_parameter(&sURI, "ci", zCI);
158158
}else{
159159
zCI = 0;
160160
}
161161
}
162162
163163
/* Compute the title of the page */
164164
blob_zero(&dirname);
165165
if( zD ){
166
- url_add_parameter(&sURI, "name", zD);
167166
blob_append(&dirname, "in directory ", -1);
168167
hyperlinked_path(zD, &dirname, zCI, "dir", "");
169168
zPrefix = mprintf("%s/", zD);
170169
style_submenu_element("Top-Level", "Top-Level", "%s",
171170
url_render(&sURI, "name", 0, 0, 0));
@@ -180,15 +179,15 @@
180179
if( linkTip ){
181180
style_submenu_element("Tip", "Tip", "%s",
182181
url_render(&sURI, "ci", "tip", 0, 0));
183182
}
184183
if( zCI ){
185
- @ <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>]
186185
@ %s(blob_str(&dirname))</h2>
187
- zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
186
+ zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix);
188187
if( nD==0 ){
189
- style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
188
+ style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%!S",
190189
zUuid);
191190
}
192191
}else{
193192
@ <h2>The union of all files from all check-ins
194193
@ %s(blob_str(&dirname))</h2>
@@ -282,11 +281,11 @@
282281
@ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
283282
}else{
284283
const char *zLink;
285284
if( zCI ){
286285
const char *zUuid = db_column_text(&q, 1);
287
- zLink = href("%R/artifact/%s",zUuid);
286
+ zLink = href("%R/artifact/%!S",zUuid);
288287
}else{
289288
zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
290289
}
291290
@ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
292291
}
@@ -540,39 +539,37 @@
540539
int startExpanded; /* True to start out with the tree expanded */
541540
int showDirOnly; /* Show directories only. Omit files */
542541
int nDir = 0; /* Number of directories. Used for ID attributes */
543542
char *zProjectName = db_get("project-name", 0);
544543
545
- if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
544
+ if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
546545
memset(&sTree, 0, sizeof(sTree));
547546
login_check_credentials();
548
- if( !g.perm.Read ){ login_needed(); return; }
547
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
549548
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
550549
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
551550
pathelementFunc, 0, 0);
552551
url_initialize(&sURI, "tree");
553
- if( P("nofiles")!=0 ){
552
+ cgi_query_parameters_to_url(&sURI);
553
+ if( PB("nofiles") ){
554554
showDirOnly = 1;
555
- url_add_parameter(&sURI, "nofiles", "1");
556555
style_header("Folder Hierarchy");
557556
}else{
558557
showDirOnly = 0;
559558
style_header("File Tree");
560559
}
561560
style_adunit_config(ADUNIT_RIGHT_OK);
562
- if( P("expand")!=0 ){
561
+ if( PB("expand") ){
563562
startExpanded = 1;
564
- url_add_parameter(&sURI, "expand", "1");
565563
}else{
566564
startExpanded = 0;
567565
}
568566
569567
/* If a regular expression is specified, compile it */
570568
zRE = P("re");
571569
if( zRE ){
572570
re_compile(&pRE, zRE, 0);
573
- url_add_parameter(&sURI, "re", zRE);
574571
zREx = mprintf("&re=%T", zRE);
575572
}
576573
577574
/* If the name= parameter is an empty string, make it a NULL pointer */
578575
if( zD && strlen(zD)==0 ){ zD = 0; }
@@ -586,11 +583,10 @@
586583
if( pM ){
587584
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
588585
linkTrunk = trunkRid && rid != trunkRid;
589586
linkTip = rid != symbolic_name_to_rid("tip", "ci");
590587
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
591
- url_add_parameter(&sURI, "ci", zCI);
592588
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
593589
zNow = db_text("", "SELECT datetime(mtime,'localtime')"
594590
" FROM event WHERE objid=%d", rid);
595591
}else{
596592
zCI = 0;
@@ -602,11 +598,10 @@
602598
}
603599
604600
/* Compute the title of the page */
605601
blob_zero(&dirname);
606602
if( zD ){
607
- url_add_parameter(&sURI, "name", zD);
608603
blob_append(&dirname, "within directory ", -1);
609604
hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
610605
if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
611606
style_submenu_element("Top-Level", "Top-Level", "%s",
612607
url_render(&sURI, "name", 0, 0, 0));
@@ -613,18 +608,11 @@
613608
}else{
614609
if( zRE ){
615610
blob_appendf(&dirname, "matching \"%s\"", zRE);
616611
}
617612
}
618
- if( useMtime ){
619
- style_submenu_element("Sort By Filename","Sort By Filename", "%s",
620
- url_render(&sURI, 0, 0, 0, 0));
621
- url_add_parameter(&sURI, "mtime", "1");
622
- }else{
623
- style_submenu_element("Sort By Time","Sort By Time", "%s",
624
- url_render(&sURI, "mtime", "1", 0, 0));
625
- }
613
+ style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
626614
if( zCI ){
627615
style_submenu_element("All", "All", "%s",
628616
url_render(&sURI, "ci", 0, 0, 0));
629617
if( nD==0 && !showDirOnly ){
630618
style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
@@ -705,11 +693,11 @@
705693
if( zCI ){
706694
@ <h2>%s(zObjType) from
707695
if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
708696
@ "%h(zCI)"
709697
}
710
- @ [%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))
711699
}else{
712700
int n = db_int(0, "SELECT count(*) FROM plink");
713701
@ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
714702
}
715703
if( useMtime ){
@@ -767,11 +755,11 @@
767755
nDir++;
768756
}else if( !showDirOnly ){
769757
const char *zFileClass = fileext_class(p->zName);
770758
char *zLink;
771759
if( zCI ){
772
- zLink = href("%R/artifact/%.16s",p->zUuid);
760
+ zLink = href("%R/artifact/%!S",p->zUuid);
773761
}else{
774762
zLink = href("%R/finfo?name=%T",p->zFullName);
775763
}
776764
@ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
777765
@ %z(zLink)%h(p->zName)</a>
@@ -901,24 +889,29 @@
901889
@ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;
902890
;
903891
904892
static const char zComputeFileAgeRun[] =
905893
@ WITH RECURSIVE
906
-@ ckin(x) AS (VALUES(:ckin) UNION ALL
907
-@ SELECT pid FROM ckin, plink WHERE cid=x AND isprim)
894
+@ ckin(x,m) AS (SELECT objid, mtime FROM event WHERE objid=:ckin
895
+@ UNION
896
+@ SELECT plink.pid, event.mtime
897
+@ FROM ckin, plink, event
898
+@ WHERE plink.cid=ckin.x AND event.objid=plink.pid
899
+@ ORDER BY 2 DESC)
908900
@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname)
909
-@ SELECT mlink.fnid, mlink.fid, x, event.mtime, filename.name
910
-@ FROM ckin, mlink, event, filename
911
-@ WHERE mlink.mid=ckin.x
912
-@ AND mlink.fnid IN (SELECT fnid FROM foci, filename
913
-@ WHERE foci.checkinID=:ckin
914
-@ AND filename.name=foci.filename
915
-@ AND filename.name GLOB :glob)
916
-@ AND filename.fnid=mlink.fnid
917
-@ AND event.objid=mlink.mid;
918
-;
919
-
901
+@ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name
902
+@ FROM foci, filename, blob, mlink, event
903
+@ WHERE foci.checkinID=:ckin
904
+@ AND foci.filename GLOB :glob
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
+;
920913
921914
/*
922915
** Look at all file containing in the version "vid". Construct a
923916
** temporary table named "fileage" that contains the file-id for each
924917
** files, the pathname, the check-in where the file was added, and the
@@ -926,11 +919,11 @@
926919
** the given glob are computed.
927920
*/
928921
int compute_fileage(int vid, const char* zGlob){
929922
Stmt q;
930923
db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
931
- db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
924
+ db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
932925
db_bind_int(&q, ":ckin", vid);
933926
db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
934927
db_exec(&q);
935928
db_finalize(&q);
936929
return 0;
@@ -997,21 +990,23 @@
997990
**
998991
** Parameters:
999992
** name=VERSION Selects the checkin version (default=tip).
1000993
** glob=STRING Only shows files matching this glob pattern
1001994
** (e.g. *.c or *.txt).
995
+** showid Show RID values for debugging
1002996
*/
1003997
void fileage_page(void){
1004998
int rid;
1005999
const char *zName;
10061000
const char *zGlob;
10071001
const char *zUuid;
10081002
const char *zNow; /* Time of checkin */
1003
+ int showId = PB("showid");
10091004
Stmt q1, q2;
10101005
double baseTime;
10111006
login_check_credentials();
1012
- if( !g.perm.Read ){ login_needed(); return; }
1007
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
10131008
zName = P("name");
10141009
if( zName==0 ) zName = "tip";
10151010
rid = symbolic_name_to_rid(zName, "ci");
10161011
if( rid==0 ){
10171012
fossil_fatal("not a valid check-in: %s", zName);
@@ -1018,26 +1013,27 @@
10181013
}
10191014
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
10201015
baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
10211016
zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event"
10221017
" WHERE objid=%d", rid);
1023
- style_submenu_element("Tree-View", "Tree-View", "%R/tree?ci=%T&mtime=1",
1018
+ style_submenu_element("Tree-View", "Tree-View",
1019
+ "%R/tree?ci=%T&mtime=1&type=tree",
10241020
zName);
10251021
style_header("File Ages");
10261022
zGlob = P("glob");
10271023
compute_fileage(rid,zGlob);
10281024
db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
10291025
10301026
@ <h2>Files in
1031
- @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a>
1027
+ @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
10321028
if( zGlob && zGlob[0] ){
10331029
@ that match "%h(zGlob)" and
10341030
}
10351031
@ ordered by check-in time</h2>
10361032
@
10371033
@ <p>Times are relative to the checkin time for
1038
- @ %z(href("%R/ci/%s",zUuid))[%S(zUuid)]</a> which is
1034
+ @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> which is
10391035
@ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
10401036
@
10411037
@ <div class='fileage'><table>
10421038
@ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
10431039
db_prepare(&q1,
@@ -1052,11 +1048,11 @@
10521048
" AND blob.rid=event.objid\n"
10531049
" ORDER BY event.mtime DESC;",
10541050
TAG_BRANCH
10551051
);
10561052
db_prepare(&q2,
1057
- "SELECT blob.uuid, filename.name\n"
1053
+ "SELECT blob.uuid, filename.name, fileage.fid\n"
10581054
" FROM fileage, blob, filename\n"
10591055
" WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
10601056
" AND blob.rid=fileage.fid;"
10611057
);
10621058
while( db_step(&q1)==SQLITE_ROW ){
@@ -1071,24 +1067,32 @@
10711067
@ <td>
10721068
db_bind_int(&q2, ":mid", mid);
10731069
while( db_step(&q2)==SQLITE_ROW ){
10741070
const char *zFUuid = db_column_text(&q2,0);
10751071
const char *zFile = db_column_text(&q2,1);
1076
- @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a><br>
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
+ }
10771078
}
10781079
db_reset(&q2);
10791080
@ </td>
10801081
@ <td>
1081
- @ %z(href("%R/info/%s",zUuid))[%S(zUuid)]</a>
1082
+ @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
1083
+ if( showId ){
1084
+ @ (%d(mid))
1085
+ }
10821086
@ %W(zComment) (user:
1083
- @ %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>,
10841088
@ branch:
1085
- @ %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>)
10861090
@ </td></tr>
10871091
@
10881092
fossil_free(zAge);
10891093
}
10901094
@ </table></div>
10911095
db_finalize(&q1);
10921096
db_finalize(&q2);
10931097
style_footer();
10941098
}
10951099
--- 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>",
@@ -128,19 +128,20 @@
128 const char *zSubdirLink;
129 int linkTrunk = 1;
130 int linkTip = 1;
131 HQuery sURI;
132
133 if( strcmp(PD("type",""),"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);
141 url_initialize(&sURI, "dir");
 
142
143 /* If the name= parameter is an empty string, make it a NULL pointer */
144 if( zD && strlen(zD)==0 ){ zD = 0; }
145
146 /* If a specific check-in is requested, fetch and parse it. If the
@@ -152,20 +153,18 @@
152 if( pM ){
153 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
154 linkTrunk = trunkRid && rid != trunkRid;
155 linkTip = rid != symbolic_name_to_rid("tip", "ci");
156 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
157 url_add_parameter(&sURI, "ci", zCI);
158 }else{
159 zCI = 0;
160 }
161 }
162
163 /* Compute the title of the page */
164 blob_zero(&dirname);
165 if( zD ){
166 url_add_parameter(&sURI, "name", zD);
167 blob_append(&dirname, "in directory ", -1);
168 hyperlinked_path(zD, &dirname, zCI, "dir", "");
169 zPrefix = mprintf("%s/", zD);
170 style_submenu_element("Top-Level", "Top-Level", "%s",
171 url_render(&sURI, "name", 0, 0, 0));
@@ -180,15 +179,15 @@
180 if( linkTip ){
181 style_submenu_element("Tip", "Tip", "%s",
182 url_render(&sURI, "ci", "tip", 0, 0));
183 }
184 if( zCI ){
185 @ <h2>Files of check-in [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>]
186 @ %s(blob_str(&dirname))</h2>
187 zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
188 if( nD==0 ){
189 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
190 zUuid);
191 }
192 }else{
193 @ <h2>The union of all files from all check-ins
194 @ %s(blob_str(&dirname))</h2>
@@ -282,11 +281,11 @@
282 @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
283 }else{
284 const char *zLink;
285 if( zCI ){
286 const char *zUuid = db_column_text(&q, 1);
287 zLink = href("%R/artifact/%s",zUuid);
288 }else{
289 zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
290 }
291 @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
292 }
@@ -540,39 +539,37 @@
540 int startExpanded; /* True to start out with the tree expanded */
541 int showDirOnly; /* Show directories only. Omit files */
542 int nDir = 0; /* Number of directories. Used for ID attributes */
543 char *zProjectName = db_get("project-name", 0);
544
545 if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
546 memset(&sTree, 0, sizeof(sTree));
547 login_check_credentials();
548 if( !g.perm.Read ){ login_needed(); return; }
549 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
550 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
551 pathelementFunc, 0, 0);
552 url_initialize(&sURI, "tree");
553 if( P("nofiles")!=0 ){
 
554 showDirOnly = 1;
555 url_add_parameter(&sURI, "nofiles", "1");
556 style_header("Folder Hierarchy");
557 }else{
558 showDirOnly = 0;
559 style_header("File Tree");
560 }
561 style_adunit_config(ADUNIT_RIGHT_OK);
562 if( P("expand")!=0 ){
563 startExpanded = 1;
564 url_add_parameter(&sURI, "expand", "1");
565 }else{
566 startExpanded = 0;
567 }
568
569 /* If a regular expression is specified, compile it */
570 zRE = P("re");
571 if( zRE ){
572 re_compile(&pRE, zRE, 0);
573 url_add_parameter(&sURI, "re", zRE);
574 zREx = mprintf("&re=%T", zRE);
575 }
576
577 /* If the name= parameter is an empty string, make it a NULL pointer */
578 if( zD && strlen(zD)==0 ){ zD = 0; }
@@ -586,11 +583,10 @@
586 if( pM ){
587 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
588 linkTrunk = trunkRid && rid != trunkRid;
589 linkTip = rid != symbolic_name_to_rid("tip", "ci");
590 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
591 url_add_parameter(&sURI, "ci", zCI);
592 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
593 zNow = db_text("", "SELECT datetime(mtime,'localtime')"
594 " FROM event WHERE objid=%d", rid);
595 }else{
596 zCI = 0;
@@ -602,11 +598,10 @@
602 }
603
604 /* Compute the title of the page */
605 blob_zero(&dirname);
606 if( zD ){
607 url_add_parameter(&sURI, "name", zD);
608 blob_append(&dirname, "within directory ", -1);
609 hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
610 if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
611 style_submenu_element("Top-Level", "Top-Level", "%s",
612 url_render(&sURI, "name", 0, 0, 0));
@@ -613,18 +608,11 @@
613 }else{
614 if( zRE ){
615 blob_appendf(&dirname, "matching \"%s\"", zRE);
616 }
617 }
618 if( useMtime ){
619 style_submenu_element("Sort By Filename","Sort By Filename", "%s",
620 url_render(&sURI, 0, 0, 0, 0));
621 url_add_parameter(&sURI, "mtime", "1");
622 }else{
623 style_submenu_element("Sort By Time","Sort By Time", "%s",
624 url_render(&sURI, "mtime", "1", 0, 0));
625 }
626 if( zCI ){
627 style_submenu_element("All", "All", "%s",
628 url_render(&sURI, "ci", 0, 0, 0));
629 if( nD==0 && !showDirOnly ){
630 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
@@ -705,11 +693,11 @@
705 if( zCI ){
706 @ <h2>%s(zObjType) from
707 if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
708 @ "%h(zCI)"
709 }
710 @ [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
711 }else{
712 int n = db_int(0, "SELECT count(*) FROM plink");
713 @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
714 }
715 if( useMtime ){
@@ -767,11 +755,11 @@
767 nDir++;
768 }else if( !showDirOnly ){
769 const char *zFileClass = fileext_class(p->zName);
770 char *zLink;
771 if( zCI ){
772 zLink = href("%R/artifact/%.16s",p->zUuid);
773 }else{
774 zLink = href("%R/finfo?name=%T",p->zFullName);
775 }
776 @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
777 @ %z(zLink)%h(p->zName)</a>
@@ -901,24 +889,29 @@
901 @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;
902 ;
903
904 static const char zComputeFileAgeRun[] =
905 @ WITH RECURSIVE
906 @ ckin(x) AS (VALUES(:ckin) UNION ALL
907 @ SELECT pid FROM ckin, plink WHERE cid=x AND isprim)
 
 
 
 
908 @ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname)
909 @ SELECT mlink.fnid, mlink.fid, x, event.mtime, filename.name
910 @ FROM ckin, mlink, event, filename
911 @ WHERE mlink.mid=ckin.x
912 @ AND mlink.fnid IN (SELECT fnid FROM foci, filename
913 @ WHERE foci.checkinID=:ckin
914 @ AND filename.name=foci.filename
915 @ AND filename.name GLOB :glob)
916 @ AND filename.fnid=mlink.fnid
917 @ AND event.objid=mlink.mid;
918 ;
919
 
920
921 /*
922 ** Look at all file containing in the version "vid". Construct a
923 ** temporary table named "fileage" that contains the file-id for each
924 ** files, the pathname, the check-in where the file was added, and the
@@ -926,11 +919,11 @@
926 ** the given glob are computed.
927 */
928 int compute_fileage(int vid, const char* zGlob){
929 Stmt q;
930 db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
931 db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
932 db_bind_int(&q, ":ckin", vid);
933 db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
934 db_exec(&q);
935 db_finalize(&q);
936 return 0;
@@ -997,21 +990,23 @@
997 **
998 ** Parameters:
999 ** name=VERSION Selects the checkin version (default=tip).
1000 ** glob=STRING Only shows files matching this glob pattern
1001 ** (e.g. *.c or *.txt).
 
1002 */
1003 void fileage_page(void){
1004 int rid;
1005 const char *zName;
1006 const char *zGlob;
1007 const char *zUuid;
1008 const char *zNow; /* Time of checkin */
 
1009 Stmt q1, q2;
1010 double baseTime;
1011 login_check_credentials();
1012 if( !g.perm.Read ){ login_needed(); return; }
1013 zName = P("name");
1014 if( zName==0 ) zName = "tip";
1015 rid = symbolic_name_to_rid(zName, "ci");
1016 if( rid==0 ){
1017 fossil_fatal("not a valid check-in: %s", zName);
@@ -1018,26 +1013,27 @@
1018 }
1019 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1020 baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
1021 zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event"
1022 " WHERE objid=%d", rid);
1023 style_submenu_element("Tree-View", "Tree-View", "%R/tree?ci=%T&mtime=1",
 
1024 zName);
1025 style_header("File Ages");
1026 zGlob = P("glob");
1027 compute_fileage(rid,zGlob);
1028 db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
1029
1030 @ <h2>Files in
1031 @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a>
1032 if( zGlob && zGlob[0] ){
1033 @ that match "%h(zGlob)" and
1034 }
1035 @ ordered by check-in time</h2>
1036 @
1037 @ <p>Times are relative to the checkin time for
1038 @ %z(href("%R/ci/%s",zUuid))[%S(zUuid)]</a> which is
1039 @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
1040 @
1041 @ <div class='fileage'><table>
1042 @ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
1043 db_prepare(&q1,
@@ -1052,11 +1048,11 @@
1052 " AND blob.rid=event.objid\n"
1053 " ORDER BY event.mtime DESC;",
1054 TAG_BRANCH
1055 );
1056 db_prepare(&q2,
1057 "SELECT blob.uuid, filename.name\n"
1058 " FROM fileage, blob, filename\n"
1059 " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
1060 " AND blob.rid=fileage.fid;"
1061 );
1062 while( db_step(&q1)==SQLITE_ROW ){
@@ -1071,24 +1067,32 @@
1071 @ <td>
1072 db_bind_int(&q2, ":mid", mid);
1073 while( db_step(&q2)==SQLITE_ROW ){
1074 const char *zFUuid = db_column_text(&q2,0);
1075 const char *zFile = db_column_text(&q2,1);
1076 @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a><br>
 
 
 
 
 
1077 }
1078 db_reset(&q2);
1079 @ </td>
1080 @ <td>
1081 @ %z(href("%R/info/%s",zUuid))[%S(zUuid)]</a>
 
 
 
1082 @ %W(zComment) (user:
1083 @ %z(href("%R/timeline?u=%t&c=%t&nd&n=200",zUser,zUuid))%h(zUser)</a>,
1084 @ branch:
1085 @ %z(href("%R/timeline?r=%t&c=%t&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
1086 @ </td></tr>
1087 @
1088 fossil_free(zAge);
1089 }
1090 @ </table></div>
1091 db_finalize(&q1);
1092 db_finalize(&q2);
1093 style_footer();
1094 }
1095
--- 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>",
@@ -128,19 +128,20 @@
128 const char *zSubdirLink;
129 int linkTrunk = 1;
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);
141 url_initialize(&sURI, "dir");
142 cgi_query_parameters_to_url(&sURI);
143
144 /* If the name= parameter is an empty string, make it a NULL pointer */
145 if( zD && strlen(zD)==0 ){ zD = 0; }
146
147 /* If a specific check-in is requested, fetch and parse it. If the
@@ -152,20 +153,18 @@
153 if( pM ){
154 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
155 linkTrunk = trunkRid && rid != trunkRid;
156 linkTip = rid != symbolic_name_to_rid("tip", "ci");
157 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
 
158 }else{
159 zCI = 0;
160 }
161 }
162
163 /* Compute the title of the page */
164 blob_zero(&dirname);
165 if( zD ){
 
166 blob_append(&dirname, "in directory ", -1);
167 hyperlinked_path(zD, &dirname, zCI, "dir", "");
168 zPrefix = mprintf("%s/", zD);
169 style_submenu_element("Top-Level", "Top-Level", "%s",
170 url_render(&sURI, "name", 0, 0, 0));
@@ -180,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>
@@ -282,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 }
@@ -540,39 +539,37 @@
539 int startExpanded; /* True to start out with the tree expanded */
540 int showDirOnly; /* Show directories only. Omit files */
541 int nDir = 0; /* Number of directories. Used for ID attributes */
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);
553 if( PB("nofiles") ){
554 showDirOnly = 1;
 
555 style_header("Folder Hierarchy");
556 }else{
557 showDirOnly = 0;
558 style_header("File Tree");
559 }
560 style_adunit_config(ADUNIT_RIGHT_OK);
561 if( PB("expand") ){
562 startExpanded = 1;
 
563 }else{
564 startExpanded = 0;
565 }
566
567 /* If a regular expression is specified, compile it */
568 zRE = P("re");
569 if( zRE ){
570 re_compile(&pRE, zRE, 0);
 
571 zREx = mprintf("&re=%T", zRE);
572 }
573
574 /* If the name= parameter is an empty string, make it a NULL pointer */
575 if( zD && strlen(zD)==0 ){ zD = 0; }
@@ -586,11 +583,10 @@
583 if( pM ){
584 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
585 linkTrunk = trunkRid && rid != trunkRid;
586 linkTip = rid != symbolic_name_to_rid("tip", "ci");
587 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
 
588 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
589 zNow = db_text("", "SELECT datetime(mtime,'localtime')"
590 " FROM event WHERE objid=%d", rid);
591 }else{
592 zCI = 0;
@@ -602,11 +598,10 @@
598 }
599
600 /* Compute the title of the page */
601 blob_zero(&dirname);
602 if( zD ){
 
603 blob_append(&dirname, "within directory ", -1);
604 hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
605 if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
606 style_submenu_element("Top-Level", "Top-Level", "%s",
607 url_render(&sURI, "name", 0, 0, 0));
@@ -613,18 +608,11 @@
608 }else{
609 if( zRE ){
610 blob_appendf(&dirname, "matching \"%s\"", zRE);
611 }
612 }
613 style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
 
 
 
 
 
 
 
614 if( zCI ){
615 style_submenu_element("All", "All", "%s",
616 url_render(&sURI, "ci", 0, 0, 0));
617 if( nD==0 && !showDirOnly ){
618 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
@@ -705,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 ){
@@ -767,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>
@@ -901,24 +889,29 @@
889 @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;
890 ;
891
892 static const char zComputeFileAgeRun[] =
893 @ WITH RECURSIVE
894 @ ckin(x,m) AS (SELECT objid, mtime FROM event WHERE objid=:ckin
895 @ UNION
896 @ SELECT plink.pid, event.mtime
897 @ FROM ckin, plink, event
898 @ WHERE plink.cid=ckin.x AND event.objid=plink.pid
899 @ ORDER BY 2 DESC)
900 @ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname)
901 @ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name
902 @ FROM foci, filename, blob, mlink, event
903 @ WHERE foci.checkinID=:ckin
904 @ AND foci.filename GLOB :glob
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
916 ** temporary table named "fileage" that contains the file-id for each
917 ** files, the pathname, the check-in where the file was added, and the
@@ -926,11 +919,11 @@
919 ** the given glob are computed.
920 */
921 int compute_fileage(int vid, const char* zGlob){
922 Stmt q;
923 db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
924 db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
925 db_bind_int(&q, ":ckin", vid);
926 db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
927 db_exec(&q);
928 db_finalize(&q);
929 return 0;
@@ -997,21 +990,23 @@
990 **
991 ** Parameters:
992 ** name=VERSION Selects the checkin version (default=tip).
993 ** glob=STRING Only shows files matching this glob pattern
994 ** (e.g. *.c or *.txt).
995 ** showid Show RID values for debugging
996 */
997 void fileage_page(void){
998 int rid;
999 const char *zName;
1000 const char *zGlob;
1001 const char *zUuid;
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);
@@ -1018,26 +1013,27 @@
1013 }
1014 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1015 baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
1016 zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event"
1017 " WHERE objid=%d", rid);
1018 style_submenu_element("Tree-View", "Tree-View",
1019 "%R/tree?ci=%T&mtime=1&type=tree",
1020 zName);
1021 style_header("File Ages");
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,
@@ -1052,11 +1048,11 @@
1048 " AND blob.rid=event.objid\n"
1049 " ORDER BY event.mtime DESC;",
1050 TAG_BRANCH
1051 );
1052 db_prepare(&q2,
1053 "SELECT blob.uuid, filename.name, fileage.fid\n"
1054 " FROM fileage, blob, filename\n"
1055 " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
1056 " AND blob.rid=fileage.fid;"
1057 );
1058 while( db_step(&q1)==SQLITE_ROW ){
@@ -1071,24 +1067,32 @@
1067 @ <td>
1068 db_bind_int(&q2, ":mid", mid);
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
+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
+8 -8
--- 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
}
@@ -2389,10 +2384,11 @@
23892384
{ "editor", 0, 32, 0, 0, "" },
23902385
{ "empty-dirs", 0, 40, 1, 0, "" },
23912386
{ "encoding-glob", 0, 40, 1, 0, "" },
23922387
{ "gdiff-command", 0, 40, 0, 0, "gdiff" },
23932388
{ "gmerge-command", 0, 40, 0, 0, "" },
2389
+ { "hash-digits", 0, 5, 0, 0, "10" },
23942390
{ "http-port", 0, 16, 0, 0, "8080" },
23952391
{ "https-login", 0, 0, 0, 0, "off" },
23962392
{ "ignore-glob", 0, 40, 1, 0, "" },
23972393
{ "keep-glob", 0, 40, 1, 0, "" },
23982394
{ "localauth", 0, 0, 0, 0, "off" },
@@ -2565,10 +2561,13 @@
25652561
** gmerge-command A graphical merge conflict resolver command operating
25662562
** on four files.
25672563
** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
25682564
** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
25692565
** Ex: meld "%baseline" "%original" "%merge" "%output"
2566
+**
2567
+** hash-digits The number of hexadecimal digits of the SHA1 hash to
2568
+** display. (Default: 10; Minimum: 6)
25702569
**
25712570
** http-port The TCP/IP port number to use by the "server"
25722571
** and "ui" commands. Default: 8080
25732572
**
25742573
** https-login Send login credentials using HTTPS instead of HTTP
@@ -2733,11 +2732,12 @@
27332732
if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
27342733
fossil_fatal("cannot set 'manifest' globally");
27352734
}
27362735
if( unsetFlag || g.argc==4 ){
27372736
int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2738
- if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2737
+ if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2738
+ fossil_strncmp(pSetting[1].name, zName, n)==0 ){
27392739
Blob x;
27402740
int i;
27412741
blob_init(&x,0,0);
27422742
for(i=0; pSetting[i].name; i++){
27432743
if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
27442744
--- 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 }
@@ -2389,10 +2384,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 +2561,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
@@ -2733,11 +2732,12 @@
2733 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2734 fossil_fatal("cannot set 'manifest' globally");
2735 }
2736 if( unsetFlag || g.argc==4 ){
2737 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2738 if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
 
2739 Blob x;
2740 int i;
2741 blob_init(&x,0,0);
2742 for(i=0; pSetting[i].name; i++){
2743 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2744
--- 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 }
@@ -2389,10 +2384,11 @@
2384 { "editor", 0, 32, 0, 0, "" },
2385 { "empty-dirs", 0, 40, 1, 0, "" },
2386 { "encoding-glob", 0, 40, 1, 0, "" },
2387 { "gdiff-command", 0, 40, 0, 0, "gdiff" },
2388 { "gmerge-command", 0, 40, 0, 0, "" },
2389 { "hash-digits", 0, 5, 0, 0, "10" },
2390 { "http-port", 0, 16, 0, 0, "8080" },
2391 { "https-login", 0, 0, 0, 0, "off" },
2392 { "ignore-glob", 0, 40, 1, 0, "" },
2393 { "keep-glob", 0, 40, 1, 0, "" },
2394 { "localauth", 0, 0, 0, 0, "off" },
@@ -2565,10 +2561,13 @@
2561 ** gmerge-command A graphical merge conflict resolver command operating
2562 ** on four files.
2563 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2564 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2565 ** Ex: meld "%baseline" "%original" "%merge" "%output"
2566 **
2567 ** hash-digits The number of hexadecimal digits of the SHA1 hash to
2568 ** display. (Default: 10; Minimum: 6)
2569 **
2570 ** http-port The TCP/IP port number to use by the "server"
2571 ** and "ui" commands. Default: 8080
2572 **
2573 ** https-login Send login credentials using HTTPS instead of HTTP
@@ -2733,11 +2732,12 @@
2732 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2733 fossil_fatal("cannot set 'manifest' globally");
2734 }
2735 if( unsetFlag || g.argc==4 ){
2736 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2737 if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2738 fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2739 Blob x;
2740 int i;
2741 blob_init(&x,0,0);
2742 for(i=0; pSetting[i].name; i++){
2743 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2744
--- 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 ){
@@ -482,11 +482,11 @@
482482
}else if( !showAll ){
483483
blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
484484
}
485485
db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
486486
blob_reset(&sql);
487
- www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0);
487
+ www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0, 0);
488488
db_finalize(&q);
489489
@ <br />
490490
style_footer();
491491
}
492492
493493
--- 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 ){
@@ -482,11 +482,11 @@
482 }else if( !showAll ){
483 blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
484 }
485 db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
486 blob_reset(&sql);
487 www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0);
488 db_finalize(&q);
489 @ <br />
490 style_footer();
491 }
492
493
--- 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 ){
@@ -482,11 +482,11 @@
482 }else if( !showAll ){
483 blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
484 }
485 db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
486 blob_reset(&sql);
487 www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0, 0);
488 db_finalize(&q);
489 @ <br />
490 style_footer();
491 }
492
493
+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
+3 -3
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -109,11 +109,11 @@
109109
/* Read content of zFile2 into memory */
110110
blob_zero(&file2);
111111
if( file_wd_size(zFile2)<0 ){
112112
zName2 = NULL_DEVICE;
113113
}else{
114
- if( file_wd_islink(zFile2) ){
114
+ if( file_wd_islink(0) ){
115115
blob_read_link(&file2, zFile2);
116116
}else{
117117
blob_read_from_file(&file2, zFile2);
118118
}
119119
zName2 = zName;
@@ -156,11 +156,11 @@
156156
}
157157
glob_free(pBinary);
158158
}
159159
blob_zero(&file2);
160160
if( file_wd_size(zFile2)>=0 ){
161
- if( file_wd_islink(zFile2) ){
161
+ if( file_wd_islink(0) ){
162162
blob_read_link(&file2, zFile2);
163163
}else{
164164
blob_read_from_file(&file2, zFile2);
165165
}
166166
}
@@ -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
@@ -109,11 +109,11 @@
109 /* Read content of zFile2 into memory */
110 blob_zero(&file2);
111 if( file_wd_size(zFile2)<0 ){
112 zName2 = NULL_DEVICE;
113 }else{
114 if( file_wd_islink(zFile2) ){
115 blob_read_link(&file2, zFile2);
116 }else{
117 blob_read_from_file(&file2, zFile2);
118 }
119 zName2 = zName;
@@ -156,11 +156,11 @@
156 }
157 glob_free(pBinary);
158 }
159 blob_zero(&file2);
160 if( file_wd_size(zFile2)>=0 ){
161 if( file_wd_islink(zFile2) ){
162 blob_read_link(&file2, zFile2);
163 }else{
164 blob_read_from_file(&file2, zFile2);
165 }
166 }
@@ -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
@@ -109,11 +109,11 @@
109 /* Read content of zFile2 into memory */
110 blob_zero(&file2);
111 if( file_wd_size(zFile2)<0 ){
112 zName2 = NULL_DEVICE;
113 }else{
114 if( file_wd_islink(0) ){
115 blob_read_link(&file2, zFile2);
116 }else{
117 blob_read_from_file(&file2, zFile2);
118 }
119 zName2 = zName;
@@ -156,11 +156,11 @@
156 }
157 glob_free(pBinary);
158 }
159 blob_zero(&file2);
160 if( file_wd_size(zFile2)>=0 ){
161 if( file_wd_islink(0) ){
162 blob_read_link(&file2, zFile2);
163 }else{
164 blob_read_from_file(&file2, zFile2);
165 }
166 }
@@ -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
+22 -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" },
@@ -182,10 +183,13 @@
182183
{ "mpga", 4, "audio/mpeg" },
183184
{ "ms", 2, "application/x-troff-ms" },
184185
{ "msh", 3, "model/mesh" },
185186
{ "nc", 2, "application/x-netcdf" },
186187
{ "oda", 3, "application/oda" },
188
+ { "odp", 3, "application/vnd.oasis.opendocument.presentation" },
189
+ { "ods", 3, "application/vnd.oasis.opendocument.spreadsheet" },
190
+ { "odt", 3, "application/vnd.oasis.opendocument.text" },
187191
{ "ogg", 3, "application/ogg" },
188192
{ "ogm", 3, "application/ogg" },
189193
{ "pbm", 3, "image/x-portable-bitmap" },
190194
{ "pdb", 3, "chemical/x-pdb" },
191195
{ "pdf", 3, "application/pdf" },
@@ -288,10 +292,24 @@
288292
{ "xpm", 3, "image/x-xpixmap" },
289293
{ "xwd", 3, "image/x-xwindowdump" },
290294
{ "xyz", 3, "chemical/x-pdb" },
291295
{ "zip", 3, "application/zip" },
292296
};
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
+}
293311
294312
/*
295313
** Guess the mime-type of a document based on its name.
296314
*/
297315
const char *mimetype_from_name(const char *zName){
@@ -305,16 +323,11 @@
305323
#ifdef FOSSIL_DEBUG
306324
/* This is test code to make sure the table above is in the correct
307325
** order
308326
*/
309327
if( fossil_strcmp(zName, "mimetype-test")==0 ){
310
- for(i=1; i<ArraySize(aMime); i++){
311
- if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
312
- fossil_fatal("mimetypes out of sequence: %s before %s",
313
- aMime[i-1].zSuffix, aMime[i].zSuffix);
314
- }
315
- }
328
+ mimetype_verify();
316329
return "ok";
317330
}
318331
#endif
319332
320333
z = zName;
@@ -353,10 +366,11 @@
353366
** filename is special and verifies the integrity of the mimetype table.
354367
** It should return "ok".
355368
*/
356369
void mimetype_test_cmd(void){
357370
int i;
371
+ mimetype_verify();
358372
for(i=2; i<g.argc; i++){
359373
fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
360374
}
361375
}
362376
@@ -366,10 +380,11 @@
366380
** Show the built-in table used to guess embedded document mimetypes
367381
** from file suffixes.
368382
*/
369383
void mimetype_list_page(void){
370384
int i;
385
+ mimetype_verify();
371386
style_header("Mimetype List");
372387
@ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
373388
@ suffixes and the following table to guess at the appropriate mimetype
374389
@ for each document.</p>
375390
@ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
@@ -528,11 +543,11 @@
528543
static const char *const azSuffix[] = {
529544
"index.html", "index.wiki", "index.md"
530545
};
531546
532547
login_check_credentials();
533
- if( !g.perm.Read ){ login_needed(); return; }
548
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
534549
blob_init(&title, 0, 0);
535550
db_begin_transaction();
536551
while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
537552
zName = PD("name", "tip/index.wiki");
538553
for(i=0; zName[i] && zName[i]!='/'; i++){}
539554
--- 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" },
@@ -182,10 +183,13 @@
182 { "mpga", 4, "audio/mpeg" },
183 { "ms", 2, "application/x-troff-ms" },
184 { "msh", 3, "model/mesh" },
185 { "nc", 2, "application/x-netcdf" },
186 { "oda", 3, "application/oda" },
 
 
 
187 { "ogg", 3, "application/ogg" },
188 { "ogm", 3, "application/ogg" },
189 { "pbm", 3, "image/x-portable-bitmap" },
190 { "pdb", 3, "chemical/x-pdb" },
191 { "pdf", 3, "application/pdf" },
@@ -288,10 +292,24 @@
288 { "xpm", 3, "image/x-xpixmap" },
289 { "xwd", 3, "image/x-xwindowdump" },
290 { "xyz", 3, "chemical/x-pdb" },
291 { "zip", 3, "application/zip" },
292 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
294 /*
295 ** Guess the mime-type of a document based on its name.
296 */
297 const char *mimetype_from_name(const char *zName){
@@ -305,16 +323,11 @@
305 #ifdef FOSSIL_DEBUG
306 /* This is test code to make sure the table above is in the correct
307 ** order
308 */
309 if( fossil_strcmp(zName, "mimetype-test")==0 ){
310 for(i=1; i<ArraySize(aMime); i++){
311 if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
312 fossil_fatal("mimetypes out of sequence: %s before %s",
313 aMime[i-1].zSuffix, aMime[i].zSuffix);
314 }
315 }
316 return "ok";
317 }
318 #endif
319
320 z = zName;
@@ -353,10 +366,11 @@
353 ** filename is special and verifies the integrity of the mimetype table.
354 ** It should return "ok".
355 */
356 void mimetype_test_cmd(void){
357 int i;
 
358 for(i=2; i<g.argc; i++){
359 fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
360 }
361 }
362
@@ -366,10 +380,11 @@
366 ** Show the built-in table used to guess embedded document mimetypes
367 ** from file suffixes.
368 */
369 void mimetype_list_page(void){
370 int i;
 
371 style_header("Mimetype List");
372 @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
373 @ suffixes and the following table to guess at the appropriate mimetype
374 @ for each document.</p>
375 @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
@@ -528,11 +543,11 @@
528 static const char *const azSuffix[] = {
529 "index.html", "index.wiki", "index.md"
530 };
531
532 login_check_credentials();
533 if( !g.perm.Read ){ login_needed(); return; }
534 blob_init(&title, 0, 0);
535 db_begin_transaction();
536 while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
537 zName = PD("name", "tip/index.wiki");
538 for(i=0; zName[i] && zName[i]!='/'; i++){}
539
--- 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" },
@@ -182,10 +183,13 @@
183 { "mpga", 4, "audio/mpeg" },
184 { "ms", 2, "application/x-troff-ms" },
185 { "msh", 3, "model/mesh" },
186 { "nc", 2, "application/x-netcdf" },
187 { "oda", 3, "application/oda" },
188 { "odp", 3, "application/vnd.oasis.opendocument.presentation" },
189 { "ods", 3, "application/vnd.oasis.opendocument.spreadsheet" },
190 { "odt", 3, "application/vnd.oasis.opendocument.text" },
191 { "ogg", 3, "application/ogg" },
192 { "ogm", 3, "application/ogg" },
193 { "pbm", 3, "image/x-portable-bitmap" },
194 { "pdb", 3, "chemical/x-pdb" },
195 { "pdf", 3, "application/pdf" },
@@ -288,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){
@@ -305,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;
@@ -353,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
@@ -366,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'>
@@ -528,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
+142 -91
--- 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,188 @@
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
);
232272
free(zTag);
233273
234274
/* Need both check-in and wiki-write or wiki-create privileges in order
235275
** to edit/create an event.
236276
*/
237277
if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
238
- login_needed();
278
+ login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
239279
return;
240280
}
241281
242282
/* Figure out the color */
243283
if( rid ){
244
- zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
284
+ zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
245285
}else{
246286
zClr = "";
287
+ isNew = 1;
247288
}
248289
zClr = PD("clr",zClr);
249290
if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
250291
251292
252293
/* If editing an existing event, extract the key fields to use as
253294
** a starting point for the edit.
254295
*/
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;
296
+ if( rid
297
+ && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0)
298
+ ){
299
+ Manifest *pTNote;
300
+ pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
301
+ if( pTNote && pTNote->type==CFTYPE_EVENT ){
302
+ if( zBody==0 ) zBody = pTNote->zWiki;
260303
if( zETime==0 ){
261
- zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
304
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
262305
}
263
- if( zComment==0 ) zComment = pEvent->zComment;
306
+ if( zComment==0 ) zComment = pTNote->zComment;
307
+ if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
264308
}
265309
if( zTags==0 ){
266310
zTags = db_text(0,
267311
"SELECT group_concat(substr(tagname,5),', ')"
268312
" FROM tagxref, tag"
@@ -276,11 +320,11 @@
276320
zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
277321
if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
278322
char *zDate;
279323
Blob cksum;
280324
int nrid, n;
281
- blob_zero(&event);
325
+ blob_init(&event, 0, 0);
282326
db_begin_transaction();
283327
login_verify_csrf_secret();
284328
while( fossil_isspace(zComment[0]) ) zComment++;
285329
n = strlen(zComment);
286330
while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -289,17 +333,20 @@
289333
}
290334
zDate = date_in_standard_format("now");
291335
blob_appendf(&event, "D %s\n", zDate);
292336
free(zDate);
293337
zETime[10] = 'T';
294
- blob_appendf(&event, "E %s %s\n", zETime, zEventId);
338
+ blob_appendf(&event, "E %s %s\n", zETime, zId);
295339
zETime[10] = ' ';
296340
if( rid ){
297341
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
298342
blob_appendf(&event, "P %s\n", zUuid);
299343
free(zUuid);
300344
}
345
+ if( zMimetype && zMimetype[0] ){
346
+ blob_appendf(&event, "N %s\n", zMimetype);
347
+ }
301348
if( zClr && zClr[0] ){
302349
blob_appendf(&event, "T +bgcolor * %F\n", zClr);
303350
}
304351
if( zTags && zTags[0] ){
305352
Blob tags, one;
@@ -350,22 +397,26 @@
350397
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
351398
manifest_crosslink(nrid, &event, MC_NONE);
352399
assert( blob_is_reset(&event) );
353400
content_deltify(rid, nrid, 0);
354401
db_end_transaction(0);
355
- cgi_redirectf("event?name=%T", zEventId);
402
+ cgi_redirectf("technote?name=%T", zId);
356403
}
357404
if( P("cancel")!=0 ){
358
- cgi_redirectf("event?name=%T", zEventId);
405
+ cgi_redirectf("technote?name=%T", zId);
359406
return;
360407
}
361408
if( zBody==0 ){
362
- zBody = mprintf("<i>Event Text</i>");
409
+ zBody = mprintf("Insert new content here...");
363410
}
364
- style_header("Edit Event %S", zEventId);
411
+ if( isNew ){
412
+ style_header("New Tech-note %S", zId);
413
+ }else{
414
+ style_header("Edit Tech-note %S", zId);
415
+ }
365416
if( P("preview")!=0 ){
366
- Blob title, tail, com;
417
+ Blob com;
367418
@ <p><b>Timeline comment preview:</b></p>
368419
@ <blockquote>
369420
@ <table border="0">
370421
if( zClr && zClr[0] ){
371422
@ <tr><td style="background-color: %h(zClr);">
@@ -377,55 +428,55 @@
377428
wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
378429
@ </td></tr></table>
379430
@ </blockquote>
380431
@ <p><b>Page content preview:</b><p>
381432
@ <blockquote>
382
- blob_zero(&event);
433
+ blob_init(&event, 0, 0);
383434
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
- }
435
+ wiki_render_by_mimetype(&event, zMimetype);
390436
@ </blockquote><hr />
391437
blob_reset(&event);
392438
}
393439
for(n=2, z=zBody; z[0]; z++){
394440
if( z[0]=='\n' ) n++;
395441
}
396442
if( n<20 ) n = 20;
397443
if( n>40 ) n = 40;
398
- @ <form method="post" action="%s(g.zTop)/eventedit"><div>
444
+ @ <form method="post" action="%R/technoteedit"><div>
399445
login_insert_csrf_secret();
400
- @ <input type="hidden" name="name" value="%h(zEventId)" />
446
+ @ <input type="hidden" name="name" value="%h(zId)" />
401447
@ <table border="0" cellspacing="10">
402448
403
- @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
449
+ @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
404450
@ <td valign="top">
405451
@ <input type="text" name="t" size="25" value="%h(zETime)" />
406452
@ </td></tr>
407453
408
- @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
454
+ @ <tr><th align="right" valign="top">Timeline Comment:</th>
409455
@ <td valign="top">
410
- @ <textarea name="c" class="eventedit" cols="80"
456
+ @ <textarea name="c" class="technoteedit" cols="80"
411457
@ rows="3" wrap="virtual">%h(zComment)</textarea>
412458
@ </td></tr>
413459
414
- @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
460
+ @ <tr><th align="right" valign="top">Timeline Background Color:</th>
415461
@ <td valign="top">
416462
render_color_chooser(0, zClr, 0, "clr", "cclr");
417463
@ </td></tr>
418464
419465
@ <tr><th align="right" valign="top">Tags:</th>
420466
@ <td valign="top">
421467
@ <input type="text" name="g" size="40" value="%h(zTags)" />
422468
@ </td></tr>
469
+
470
+ @ <tr><th align="right" valign="top">Markup Style:</th>
471
+ @ <td valign="top">
472
+ mimetype_option_menu(zMimetype);
473
+ @ </td></tr>
423474
424475
@ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
425476
@ <td valign="top">
426
- @ <textarea name="w" class="eventedit" cols="80"
477
+ @ <textarea name="w" class="technoteedit" cols="80"
427478
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
428479
@ </td></tr>
429480
430481
@ <tr><td colspan="2">
431482
@ <input type="submit" name="preview" value="Preview Your Changes" />
432483
--- 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,188 @@
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 +320,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 +333,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;
@@ -350,22 +397,26 @@
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 +428,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,188 @@
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 free(zTag);
273
274 /* Need both check-in and wiki-write or wiki-create privileges in order
275 ** to edit/create an event.
276 */
277 if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
278 login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
279 return;
280 }
281
282 /* Figure out the color */
283 if( rid ){
284 zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
285 }else{
286 zClr = "";
287 isNew = 1;
288 }
289 zClr = PD("clr",zClr);
290 if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
291
292
293 /* If editing an existing event, extract the key fields to use as
294 ** a starting point for the edit.
295 */
296 if( rid
297 && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0)
298 ){
299 Manifest *pTNote;
300 pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
301 if( pTNote && pTNote->type==CFTYPE_EVENT ){
302 if( zBody==0 ) zBody = pTNote->zWiki;
303 if( zETime==0 ){
304 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
305 }
306 if( zComment==0 ) zComment = pTNote->zComment;
307 if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
308 }
309 if( zTags==0 ){
310 zTags = db_text(0,
311 "SELECT group_concat(substr(tagname,5),', ')"
312 " FROM tagxref, tag"
@@ -276,11 +320,11 @@
320 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
321 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
322 char *zDate;
323 Blob cksum;
324 int nrid, n;
325 blob_init(&event, 0, 0);
326 db_begin_transaction();
327 login_verify_csrf_secret();
328 while( fossil_isspace(zComment[0]) ) zComment++;
329 n = strlen(zComment);
330 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -289,17 +333,20 @@
333 }
334 zDate = date_in_standard_format("now");
335 blob_appendf(&event, "D %s\n", zDate);
336 free(zDate);
337 zETime[10] = 'T';
338 blob_appendf(&event, "E %s %s\n", zETime, zId);
339 zETime[10] = ' ';
340 if( rid ){
341 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
342 blob_appendf(&event, "P %s\n", zUuid);
343 free(zUuid);
344 }
345 if( zMimetype && zMimetype[0] ){
346 blob_appendf(&event, "N %s\n", zMimetype);
347 }
348 if( zClr && zClr[0] ){
349 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
350 }
351 if( zTags && zTags[0] ){
352 Blob tags, one;
@@ -350,22 +397,26 @@
397 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
398 manifest_crosslink(nrid, &event, MC_NONE);
399 assert( blob_is_reset(&event) );
400 content_deltify(rid, nrid, 0);
401 db_end_transaction(0);
402 cgi_redirectf("technote?name=%T", zId);
403 }
404 if( P("cancel")!=0 ){
405 cgi_redirectf("technote?name=%T", zId);
406 return;
407 }
408 if( zBody==0 ){
409 zBody = mprintf("Insert new content here...");
410 }
411 if( isNew ){
412 style_header("New Tech-note %S", zId);
413 }else{
414 style_header("Edit Tech-note %S", zId);
415 }
416 if( P("preview")!=0 ){
417 Blob com;
418 @ <p><b>Timeline comment preview:</b></p>
419 @ <blockquote>
420 @ <table border="0">
421 if( zClr && zClr[0] ){
422 @ <tr><td style="background-color: %h(zClr);">
@@ -377,55 +428,55 @@
428 wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
429 @ </td></tr></table>
430 @ </blockquote>
431 @ <p><b>Page content preview:</b><p>
432 @ <blockquote>
433 blob_init(&event, 0, 0);
434 blob_append(&event, zBody, -1);
435 wiki_render_by_mimetype(&event, zMimetype);
 
 
 
 
 
436 @ </blockquote><hr />
437 blob_reset(&event);
438 }
439 for(n=2, z=zBody; z[0]; z++){
440 if( z[0]=='\n' ) n++;
441 }
442 if( n<20 ) n = 20;
443 if( n>40 ) n = 40;
444 @ <form method="post" action="%R/technoteedit"><div>
445 login_insert_csrf_secret();
446 @ <input type="hidden" name="name" value="%h(zId)" />
447 @ <table border="0" cellspacing="10">
448
449 @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
450 @ <td valign="top">
451 @ <input type="text" name="t" size="25" value="%h(zETime)" />
452 @ </td></tr>
453
454 @ <tr><th align="right" valign="top">Timeline Comment:</th>
455 @ <td valign="top">
456 @ <textarea name="c" class="technoteedit" cols="80"
457 @ rows="3" wrap="virtual">%h(zComment)</textarea>
458 @ </td></tr>
459
460 @ <tr><th align="right" valign="top">Timeline Background Color:</th>
461 @ <td valign="top">
462 render_color_chooser(0, zClr, 0, "clr", "cclr");
463 @ </td></tr>
464
465 @ <tr><th align="right" valign="top">Tags:</th>
466 @ <td valign="top">
467 @ <input type="text" name="g" size="40" value="%h(zTags)" />
468 @ </td></tr>
469
470 @ <tr><th align="right" valign="top">Markup Style:</th>
471 @ <td valign="top">
472 mimetype_option_menu(zMimetype);
473 @ </td></tr>
474
475 @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
476 @ <td valign="top">
477 @ <textarea name="w" class="technoteedit" cols="80"
478 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
479 @ </td></tr>
480
481 @ <tr><td colspan="2">
482 @ <input type="submit" name="preview" value="Preview Your Changes" />
483
+11 -27
--- src/file.c
+++ src/file.c
@@ -230,42 +230,26 @@
230230
blob_read_link(&content, zFrom);
231231
symlink_create(blob_str(&content), zTo);
232232
blob_reset(&content);
233233
}
234234
235
-#ifdef __CYGWIN__
236
-/* Workaround for recently introduced Cygwin bug: group execute */
237
-/* permission is always set, so it cannot be relied upon! */
238
-# undef S_IXGRP
239
-# define S_IXGRP 0
240
-#endif
241
-
242235
/*
243236
** Return file permissions (normal, executable, or symlink):
244
-** - PERM_EXE if file is executable;
237
+** - PERM_EXE on Unix if file is executable;
245238
** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on;
246239
** - PERM_REG for all other cases (regular file, directory, fifo, etc).
247240
*/
248241
int file_wd_perm(const char *zFilename){
249
- if( getStat(zFilename, 1) ) return PERM_REG;
250
-#if defined(_WIN32)
251
-# ifndef S_IXUSR
252
-# define S_IXUSR _S_IEXEC
253
-# endif
254
- if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
255
- return PERM_EXE;
256
- else
257
- return PERM_REG;
258
-#else
259
- if( S_ISREG(fileStat.st_mode) &&
260
- ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )
261
- return PERM_EXE;
262
- else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
263
- return PERM_LNK;
264
- else
265
- return PERM_REG;
242
+#if !defined(_WIN32)
243
+ if( !getStat(zFilename, 1) ){
244
+ if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
245
+ return PERM_EXE;
246
+ else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
247
+ return PERM_LNK;
248
+ }
266249
#endif
250
+ return PERM_REG;
267251
}
268252
269253
/*
270254
** Return TRUE if the named file is an executable. Return false
271255
** for directories, devices, fifos, symlinks, etc.
@@ -439,16 +423,16 @@
439423
#if !defined(_WIN32)
440424
struct stat buf;
441425
if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0;
442426
if( onoff ){
443427
int targetMode = (buf.st_mode & 0444)>>2;
444
- if( (buf.st_mode & 0111)!=targetMode ){
428
+ if( (buf.st_mode & 0100) == 0 ){
445429
chmod(zFilename, buf.st_mode | targetMode);
446430
rc = 1;
447431
}
448432
}else{
449
- if( (buf.st_mode & 0111)!=0 ){
433
+ if( (buf.st_mode & 0100) != 0 ){
450434
chmod(zFilename, buf.st_mode & ~0111);
451435
rc = 1;
452436
}
453437
}
454438
#endif /* _WIN32 */
455439
--- src/file.c
+++ src/file.c
@@ -230,42 +230,26 @@
230 blob_read_link(&content, zFrom);
231 symlink_create(blob_str(&content), zTo);
232 blob_reset(&content);
233 }
234
235 #ifdef __CYGWIN__
236 /* Workaround for recently introduced Cygwin bug: group execute */
237 /* permission is always set, so it cannot be relied upon! */
238 # undef S_IXGRP
239 # define S_IXGRP 0
240 #endif
241
242 /*
243 ** Return file permissions (normal, executable, or symlink):
244 ** - PERM_EXE if file is executable;
245 ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on;
246 ** - PERM_REG for all other cases (regular file, directory, fifo, etc).
247 */
248 int file_wd_perm(const char *zFilename){
249 if( getStat(zFilename, 1) ) return PERM_REG;
250 #if defined(_WIN32)
251 # ifndef S_IXUSR
252 # define S_IXUSR _S_IEXEC
253 # endif
254 if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
255 return PERM_EXE;
256 else
257 return PERM_REG;
258 #else
259 if( S_ISREG(fileStat.st_mode) &&
260 ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )
261 return PERM_EXE;
262 else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
263 return PERM_LNK;
264 else
265 return PERM_REG;
266 #endif
 
267 }
268
269 /*
270 ** Return TRUE if the named file is an executable. Return false
271 ** for directories, devices, fifos, symlinks, etc.
@@ -439,16 +423,16 @@
439 #if !defined(_WIN32)
440 struct stat buf;
441 if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0;
442 if( onoff ){
443 int targetMode = (buf.st_mode & 0444)>>2;
444 if( (buf.st_mode & 0111)!=targetMode ){
445 chmod(zFilename, buf.st_mode | targetMode);
446 rc = 1;
447 }
448 }else{
449 if( (buf.st_mode & 0111)!=0 ){
450 chmod(zFilename, buf.st_mode & ~0111);
451 rc = 1;
452 }
453 }
454 #endif /* _WIN32 */
455
--- src/file.c
+++ src/file.c
@@ -230,42 +230,26 @@
230 blob_read_link(&content, zFrom);
231 symlink_create(blob_str(&content), zTo);
232 blob_reset(&content);
233 }
234
 
 
 
 
 
 
 
235 /*
236 ** Return file permissions (normal, executable, or symlink):
237 ** - PERM_EXE on Unix if file is executable;
238 ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on;
239 ** - PERM_REG for all other cases (regular file, directory, fifo, etc).
240 */
241 int file_wd_perm(const char *zFilename){
242 #if !defined(_WIN32)
243 if( !getStat(zFilename, 1) ){
244 if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
245 return PERM_EXE;
246 else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
247 return PERM_LNK;
248 }
 
 
 
 
 
 
 
 
 
 
249 #endif
250 return PERM_REG;
251 }
252
253 /*
254 ** Return TRUE if the named file is an executable. Return false
255 ** for directories, devices, fifos, symlinks, etc.
@@ -439,16 +423,16 @@
423 #if !defined(_WIN32)
424 struct stat buf;
425 if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0;
426 if( onoff ){
427 int targetMode = (buf.st_mode & 0444)>>2;
428 if( (buf.st_mode & 0100) == 0 ){
429 chmod(zFilename, buf.st_mode | targetMode);
430 rc = 1;
431 }
432 }else{
433 if( (buf.st_mode & 0100) != 0 ){
434 chmod(zFilename, buf.st_mode & ~0111);
435 rc = 1;
436 }
437 }
438 #endif /* _WIN32 */
439
+22 -10
--- 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);
@@ -358,12 +358,21 @@
358358
}
359359
if( (zB = P("b"))!=0 ){
360360
blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB);
361361
url_add_parameter(&url, "b", zB);
362362
}
363
+ /* We only want each version of a file to appear on the graph once,
364
+ ** at its earliest appearance. All the other times that it gets merged
365
+ ** into this or that branch can be ignored. An exception is for when
366
+ ** files are deleted (when they have mlink.fid==0). If the same file
367
+ ** is deleted in multiple places, we want to show each deletion, so
368
+ ** use a "fake fid" which is derived from the parent-fid for grouping.
369
+ ** The same fake-fid must be used on the graph.
370
+ */
363371
blob_append_sql(&sql,
364
- " GROUP BY mlink.fid"
372
+ " GROUP BY"
373
+ " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END"
365374
" ORDER BY event.mtime DESC /*sort*/"
366375
);
367376
if( (n = atoi(PD("n","0")))>0 ){
368377
blob_append_sql(&sql, " LIMIT %d", n);
369378
url_add_parameter(&url, "n", P("n"));
@@ -374,11 +383,11 @@
374383
}
375384
blob_reset(&sql);
376385
blob_zero(&title);
377386
if( baseCheckin ){
378387
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379
- char *zLink = href("%R/info/%s", zUuid);
388
+ char *zLink = href("%R/info/%!S", zUuid);
380389
blob_appendf(&title, "Ancestors of file ");
381390
hyperlinked_path(zFilename, &title, zUuid, "tree", "");
382391
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
383392
blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
384393
if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
@@ -432,11 +441,12 @@
432441
if( uBg ){
433442
zBgClr = hash_color(zUser);
434443
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
435444
zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
436445
}
437
- gidx = graph_add_row(pGraph, frid, nParent, aParent, zBr, zBgClr,
446
+ gidx = graph_add_row(pGraph, frid>0 ? frid : fpid+1000000000,
447
+ nParent, aParent, zBr, zBgClr,
438448
zUuid, 0);
439449
if( strncmp(zDate, zPrevDate, 10) ){
440450
sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
441451
@ <tr><td>
442452
@ <div class="divider">%s(zPrevDate)</div>
@@ -459,11 +469,11 @@
459469
char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
460470
pfnid);
461471
@ <b>Renamed</b> from
462472
@ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
463473
}
464
- @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a>
474
+ @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a>
465475
if( fShowId ){
466476
@ (%d(frid))
467477
}
468478
@ part of check-in
469479
}else{
@@ -486,32 +496,34 @@
486496
if( fShowId ){
487497
@ (%d(fmid))
488498
}
489499
@ %W(zCom) (user:
490500
hyperlink_to_user(zUser, zDate, "");
491
- @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>
501
+ @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>)
492502
if( g.perm.Hyperlink && zUuid ){
493503
const char *z = zFilename;
494504
@ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
495505
@ [annotate]</a>
496506
@ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
497507
@ [blame]</a>
498
- @ %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>
499509
if( fpid ){
500
- @ %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>
501511
}
502512
}
503513
if( fDebug & FINFO_DEBUG_MLINK ){
504514
int ii;
515
+ char *zAncLink;
505516
@ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
506517
if( nParent>0 ){
507518
@ parents=%d(aParent[0])
508519
for(ii=1; ii<nParent; ii++){
509520
@ %d(aParent[ii])
510521
}
511522
}
512
- @ %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>
513525
}
514526
tag_private_status(frid);
515527
@ </td></tr>
516528
}
517529
db_finalize(&q);
518530
--- 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);
@@ -358,12 +358,21 @@
358 }
359 if( (zB = P("b"))!=0 ){
360 blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB);
361 url_add_parameter(&url, "b", zB);
362 }
 
 
 
 
 
 
 
 
363 blob_append_sql(&sql,
364 " GROUP BY mlink.fid"
 
365 " ORDER BY event.mtime DESC /*sort*/"
366 );
367 if( (n = atoi(PD("n","0")))>0 ){
368 blob_append_sql(&sql, " LIMIT %d", n);
369 url_add_parameter(&url, "n", P("n"));
@@ -374,11 +383,11 @@
374 }
375 blob_reset(&sql);
376 blob_zero(&title);
377 if( baseCheckin ){
378 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379 char *zLink = href("%R/info/%s", zUuid);
380 blob_appendf(&title, "Ancestors of file ");
381 hyperlinked_path(zFilename, &title, zUuid, "tree", "");
382 if( fShowId ) blob_appendf(&title, " (%d)", fnid);
383 blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
384 if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
@@ -432,11 +441,12 @@
432 if( uBg ){
433 zBgClr = hash_color(zUser);
434 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
435 zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
436 }
437 gidx = graph_add_row(pGraph, frid, nParent, aParent, zBr, zBgClr,
 
438 zUuid, 0);
439 if( strncmp(zDate, zPrevDate, 10) ){
440 sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
441 @ <tr><td>
442 @ <div class="divider">%s(zPrevDate)</div>
@@ -459,11 +469,11 @@
459 char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
460 pfnid);
461 @ <b>Renamed</b> from
462 @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
463 }
464 @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a>
465 if( fShowId ){
466 @ (%d(frid))
467 }
468 @ part of check-in
469 }else{
@@ -486,32 +496,34 @@
486 if( fShowId ){
487 @ (%d(fmid))
488 }
489 @ %W(zCom) (user:
490 hyperlink_to_user(zUser, zDate, "");
491 @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>
492 if( g.perm.Hyperlink && zUuid ){
493 const char *z = zFilename;
494 @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
495 @ [annotate]</a>
496 @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
497 @ [blame]</a>
498 @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins&nbsp;using]</a>
499 if( fpid ){
500 @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
501 }
502 }
503 if( fDebug & FINFO_DEBUG_MLINK ){
504 int ii;
 
505 @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
506 if( nParent>0 ){
507 @ parents=%d(aParent[0])
508 for(ii=1; ii<nParent; ii++){
509 @ %d(aParent[ii])
510 }
511 }
512 @ %z(href("%R/finfo?name=%T&ci=%s&debug=1",zFilename,zCkin))[ancestry]</a>
 
513 }
514 tag_private_status(frid);
515 @ </td></tr>
516 }
517 db_finalize(&q);
518
--- 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);
@@ -358,12 +358,21 @@
358 }
359 if( (zB = P("b"))!=0 ){
360 blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB);
361 url_add_parameter(&url, "b", zB);
362 }
363 /* We only want each version of a file to appear on the graph once,
364 ** at its earliest appearance. All the other times that it gets merged
365 ** into this or that branch can be ignored. An exception is for when
366 ** files are deleted (when they have mlink.fid==0). If the same file
367 ** is deleted in multiple places, we want to show each deletion, so
368 ** use a "fake fid" which is derived from the parent-fid for grouping.
369 ** The same fake-fid must be used on the graph.
370 */
371 blob_append_sql(&sql,
372 " GROUP BY"
373 " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END"
374 " ORDER BY event.mtime DESC /*sort*/"
375 );
376 if( (n = atoi(PD("n","0")))>0 ){
377 blob_append_sql(&sql, " LIMIT %d", n);
378 url_add_parameter(&url, "n", P("n"));
@@ -374,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);
@@ -432,11 +441,12 @@
441 if( uBg ){
442 zBgClr = hash_color(zUser);
443 }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
444 zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
445 }
446 gidx = graph_add_row(pGraph, frid>0 ? frid : fpid+1000000000,
447 nParent, aParent, zBr, zBgClr,
448 zUuid, 0);
449 if( strncmp(zDate, zPrevDate, 10) ){
450 sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
451 @ <tr><td>
452 @ <div class="divider">%s(zPrevDate)</div>
@@ -459,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{
@@ -486,32 +496,34 @@
496 if( fShowId ){
497 @ (%d(fmid))
498 }
499 @ %W(zCom) (user:
500 hyperlink_to_user(zUser, zDate, "");
501 @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>)
502 if( g.perm.Hyperlink && zUuid ){
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
--- src/http_socket.c
+++ src/http_socket.c
@@ -173,10 +173,11 @@
173173
break;
174174
}
175175
if( p==0 ){
176176
socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
177177
pUrlData->port);
178
+ rc = 1;
178179
}
179180
#if !defined(_WIN32)
180181
signal(SIGPIPE, SIG_IGN);
181182
#endif
182183
end_socket_open:
183184
--- src/http_socket.c
+++ src/http_socket.c
@@ -173,10 +173,11 @@
173 break;
174 }
175 if( p==0 ){
176 socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
177 pUrlData->port);
 
178 }
179 #if !defined(_WIN32)
180 signal(SIGPIPE, SIG_IGN);
181 #endif
182 end_socket_open:
183
--- src/http_socket.c
+++ src/http_socket.c
@@ -173,10 +173,11 @@
173 break;
174 }
175 if( p==0 ){
176 socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
177 pUrlData->port);
178 rc = 1;
179 }
180 #if !defined(_WIN32)
181 signal(SIGPIPE, SIG_IGN);
182 #endif
183 end_socket_open:
184
+2 -2
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
14901490
FILE *pIn;
14911491
Stmt q;
14921492
const char *zBase = find_option("base", 0, 1);
14931493
int forceFlag = find_option("force", "f", 0)!=0;
14941494
int incrFlag = find_option("incremental", "i", 0)!=0;
1495
+ int flatFlag = find_option("flat", 0, 0)!=0;
1496
+
14951497
gsvn.zTrunk = find_option("trunk", 0, 1);
14961498
gsvn.zBranches = find_option("branches", 0, 1);
14971499
gsvn.zTags = find_option("tags", 0, 1);
1498
- int flatFlag = find_option("flat", 0, 0)!=0;
1499
-
15001500
verify_all_options();
15011501
if( g.argc!=4 && g.argc!=5 ){
15021502
usage("FORMAT REPOSITORY-NAME");
15031503
}
15041504
if( g.argc==5 ){
15051505
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
1490 FILE *pIn;
1491 Stmt q;
1492 const char *zBase = find_option("base", 0, 1);
1493 int forceFlag = find_option("force", "f", 0)!=0;
1494 int incrFlag = find_option("incremental", "i", 0)!=0;
 
 
1495 gsvn.zTrunk = find_option("trunk", 0, 1);
1496 gsvn.zBranches = find_option("branches", 0, 1);
1497 gsvn.zTags = find_option("tags", 0, 1);
1498 int flatFlag = find_option("flat", 0, 0)!=0;
1499
1500 verify_all_options();
1501 if( g.argc!=4 && g.argc!=5 ){
1502 usage("FORMAT REPOSITORY-NAME");
1503 }
1504 if( g.argc==5 ){
1505
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
1490 FILE *pIn;
1491 Stmt q;
1492 const char *zBase = find_option("base", 0, 1);
1493 int forceFlag = find_option("force", "f", 0)!=0;
1494 int incrFlag = find_option("incremental", "i", 0)!=0;
1495 int flatFlag = find_option("flat", 0, 0)!=0;
1496
1497 gsvn.zTrunk = find_option("trunk", 0, 1);
1498 gsvn.zBranches = find_option("branches", 0, 1);
1499 gsvn.zTags = find_option("tags", 0, 1);
 
 
1500 verify_all_options();
1501 if( g.argc!=4 && g.argc!=5 ){
1502 usage("FORMAT REPOSITORY-NAME");
1503 }
1504 if( g.argc==5 ){
1505
+2 -2
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
14901490
FILE *pIn;
14911491
Stmt q;
14921492
const char *zBase = find_option("base", 0, 1);
14931493
int forceFlag = find_option("force", "f", 0)!=0;
14941494
int incrFlag = find_option("incremental", "i", 0)!=0;
1495
+ int flatFlag = find_option("flat", 0, 0)!=0;
1496
+
14951497
gsvn.zTrunk = find_option("trunk", 0, 1);
14961498
gsvn.zBranches = find_option("branches", 0, 1);
14971499
gsvn.zTags = find_option("tags", 0, 1);
1498
- int flatFlag = find_option("flat", 0, 0)!=0;
1499
-
15001500
verify_all_options();
15011501
if( g.argc!=4 && g.argc!=5 ){
15021502
usage("FORMAT REPOSITORY-NAME");
15031503
}
15041504
if( g.argc==5 ){
15051505
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
1490 FILE *pIn;
1491 Stmt q;
1492 const char *zBase = find_option("base", 0, 1);
1493 int forceFlag = find_option("force", "f", 0)!=0;
1494 int incrFlag = find_option("incremental", "i", 0)!=0;
 
 
1495 gsvn.zTrunk = find_option("trunk", 0, 1);
1496 gsvn.zBranches = find_option("branches", 0, 1);
1497 gsvn.zTags = find_option("tags", 0, 1);
1498 int flatFlag = find_option("flat", 0, 0)!=0;
1499
1500 verify_all_options();
1501 if( g.argc!=4 && g.argc!=5 ){
1502 usage("FORMAT REPOSITORY-NAME");
1503 }
1504 if( g.argc==5 ){
1505
--- src/import.c
+++ src/import.c
@@ -1490,15 +1490,15 @@
1490 FILE *pIn;
1491 Stmt q;
1492 const char *zBase = find_option("base", 0, 1);
1493 int forceFlag = find_option("force", "f", 0)!=0;
1494 int incrFlag = find_option("incremental", "i", 0)!=0;
1495 int flatFlag = find_option("flat", 0, 0)!=0;
1496
1497 gsvn.zTrunk = find_option("trunk", 0, 1);
1498 gsvn.zBranches = find_option("branches", 0, 1);
1499 gsvn.zTags = find_option("tags", 0, 1);
 
 
1500 verify_all_options();
1501 if( g.argc!=4 && g.argc!=5 ){
1502 usage("FORMAT REPOSITORY-NAME");
1503 }
1504 if( g.argc==5 ){
1505
+76 -53
--- src/info.c
+++ src/info.c
@@ -240,23 +240,23 @@
240240
show_common_info(rid, "uuid:", 1, 1);
241241
}
242242
}
243243
244244
/*
245
-** Show information about all tags on a given node.
245
+** Show information about all tags on a given check-in.
246246
*/
247
-static void showTags(int rid, const char *zNotGlob){
247
+static void showTags(int rid){
248248
Stmt q;
249249
int cnt = 0;
250250
db_prepare(&q,
251251
"SELECT tag.tagid, tagname, "
252252
" (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
253253
" value, datetime(tagxref.mtime%s), tagtype,"
254254
" (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
255255
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
256
- " WHERE tagxref.rid=%d AND tagname NOT GLOB '%q'"
257
- " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid, zNotGlob
256
+ " WHERE tagxref.rid=%d"
257
+ " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid
258258
);
259259
while( db_step(&q)==SQLITE_ROW ){
260260
const char *zTagname = db_column_text(&q, 1);
261261
const char *zSrcUuid = db_column_text(&q, 2);
262262
const char *zValue = db_column_text(&q, 3);
@@ -305,10 +305,33 @@
305305
db_finalize(&q);
306306
if( cnt ){
307307
@ </ul>
308308
}
309309
}
310
+
311
+/*
312
+** Show the context graph (immediate parents and children) for
313
+** check-in rid.
314
+*/
315
+static void showContext(int rid){
316
+ Blob sql;
317
+ Stmt q;
318
+ @ <div class="section">Context</div>
319
+ blob_zero(&sql);
320
+ blob_append(&sql, timeline_query_for_www(), -1);
321
+ db_multi_exec(
322
+ "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
323
+ "INSERT INTO ok VALUES(%d);"
324
+ "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
325
+ "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
326
+ rid, rid, rid
327
+ );
328
+ blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
329
+ db_prepare(&q, "%s", blob_sql_text(&sql));
330
+ www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0);
331
+ db_finalize(&q);
332
+}
310333
311334
312335
/*
313336
** Append the difference between artifacts to the output
314337
*/
@@ -380,32 +403,32 @@
380403
}
381404
}else{
382405
if( zOld && zNew ){
383406
if( fossil_strcmp(zOld, zNew)!=0 ){
384407
@ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
385
- @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
386
- @ 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>.
387410
}else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
388411
@ <p>Name change
389412
@ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
390413
@ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
391414
}else{
392415
@ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
393416
@ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
394417
}
395418
}else if( zOld ){
396
- @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
397
- @ 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>
398421
}else{
399422
@ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
400
- @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
423
+ @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
401424
}
402425
if( diffFlags ){
403426
append_diff(zOld, zNew, diffFlags, pRe);
404427
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
405428
@ &nbsp;&nbsp;
406
- @ %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>
407430
}
408431
}
409432
}
410433
411434
/*
@@ -504,11 +527,11 @@
504527
const char *zW; /* URL param for ignoring whitespace */
505528
const char *zPage = "vinfo"; /* Page that shows diffs */
506529
const char *zPageHide = "ci"; /* Page that hides diffs */
507530
508531
login_check_credentials();
509
- if( !g.perm.Read ){ login_needed(); return; }
532
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
510533
zName = P("name");
511534
rid = name_to_rid_www("name");
512535
if( rid==0 ){
513536
style_header("Check-in Information Error");
514537
@ No such object: %h(g.argv[2])
@@ -612,19 +635,19 @@
612635
if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
613636
zPJ[jj] = '_';
614637
}
615638
}
616639
@ <tr><th>Timelines:</th><td>
617
- @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
640
+ @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
618641
if( zParent ){
619
- @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
642
+ @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
620643
}
621644
if( !isLeaf ){
622
- @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
645
+ @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
623646
}
624647
if( zParent && !isLeaf ){
625
- @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
648
+ @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
626649
}
627650
db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
628651
" WHERE rid=%d AND tagtype>0 "
629652
" AND tag.tagid=tagxref.tagid "
630653
" AND +tag.tagname GLOB 'sym-*'", rid);
@@ -634,29 +657,29 @@
634657
}
635658
db_finalize(&q2);
636659
637660
638661
/* The Download: line */
639
- if( g.perm.Zip ){
662
+ if( g.anon.Zip ){
640663
char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
641664
zPJ, zUuid, zUuid);
642665
@ </td></tr>
643666
@ <tr><th>Downloads:</th><td>
644667
@ %z(href("%s",zUrl))Tarball</a>
645
- @ | %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))
646669
@ ZIP archive</a>
647670
fossil_free(zUrl);
648671
}
649672
@ </td></tr>
650673
@ <tr><th>Other&nbsp;Links:</th>
651674
@ <td>
652
- @ %z(href("%R/tree?ci=%s",zUuid))files</a>
653
- @ | %z(href("%R/fileage?name=%s",zUuid))file ages</a>
654
- @ | %z(href("%R/tree?ci=%s&nofiles",zUuid))folders</a>
655
- @ | %z(href("%R/artifact/%s",zUuid))manifest</a>
656
- if( g.perm.Write ){
657
- @ | %z(href("%R/ci_edit?r=%s",zUuid))edit</a>
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
+ if( g.anon.Write ){
680
+ @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
658681
}
659682
@ </td>
660683
@ </tr>
661684
blob_reset(&projName);
662685
}
@@ -664,11 +687,12 @@
664687
}else{
665688
style_header("Check-in Information");
666689
login_anonymous_available();
667690
}
668691
db_finalize(&q1);
669
- showTags(rid, "");
692
+ showTags(rid);
693
+ showContext(rid);
670694
@ <div class="section">Changes</div>
671695
@ <div class="sectionmenu">
672696
verboseFlag = g.zPath[0]!='c';
673697
if( db_get_boolean("show-version-diffs", 0)==0 ){
674698
verboseFlag = !verboseFlag;
@@ -699,11 +723,11 @@
699723
@ Show&nbsp;Unified&nbsp;Diffs</a>
700724
@ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
701725
@ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
702726
}
703727
if( zParent ){
704
- @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
728
+ @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
705729
@ Patch</a>
706730
}
707731
@</div>
708732
if( pRe ){
709733
@ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -749,11 +773,11 @@
749773
Blob wiki;
750774
int modPending;
751775
const char *zModAction;
752776
753777
login_check_credentials();
754
- if( !g.perm.RdWiki ){ login_needed(); return; }
778
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
755779
rid = name_to_rid_www("name");
756780
if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
757781
style_header("Wiki Page Information Error");
758782
@ No such object: %h(P("name"))
759783
style_footer();
@@ -789,11 +813,11 @@
789813
pWiki->zWikiTitle);
790814
login_anonymous_available();
791815
@ <div class="section">Overview</div>
792816
@ <p><table class="label-value">
793817
@ <tr><th>Artifact&nbsp;ID:</th>
794
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
818
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
795819
if( g.perm.Setup ){
796820
@ (%d(rid))
797821
}
798822
modPending = moderation_pending(rid);
799823
if( modPending ){
@@ -808,11 +832,11 @@
808832
if( pWiki->nParent>0 ){
809833
int i;
810834
@ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
811835
for(i=0; i<pWiki->nParent; i++){
812836
char *zParent = pWiki->azParent[i];
813
- @ %z(href("info/%s",zParent))%s(zParent)</a>
837
+ @ %z(href("info/%!S",zParent))%s(zParent)</a>
814838
}
815839
@ </td></tr>
816840
}
817841
@ </table>
818842
@@ -967,11 +991,11 @@
967991
const char *zW;
968992
const char *zVerbose;
969993
const char *zGlob;
970994
ReCompiled *pRe = 0;
971995
login_check_credentials();
972
- if( !g.perm.Read ){ login_needed(); return; }
996
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
973997
login_anonymous_available();
974998
zRe = P("regex");
975999
if( zRe ) re_compile(&pRe, zRe, 0);
9761000
zBranch = P("branch");
9771001
if( zBranch && zBranch[0] ){
@@ -1182,17 +1206,17 @@
11821206
int mPerm = db_column_int(&q, 5);
11831207
const char *zBr = db_column_text(&q, 6);
11841208
int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
11851209
if( sameFilename && !showDetail ){
11861210
if( cnt==1 ){
1187
- @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1211
+ @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
11881212
}
11891213
cnt++;
11901214
continue;
11911215
}
11921216
if( !sameFilename ){
1193
- if( prevName ) {
1217
+ if( prevName && showDetail ) {
11941218
@ </ul>
11951219
}
11961220
if( mPerm==PERM_LNK ){
11971221
@ <li>Symbolic link
11981222
objType |= OBJTYPE_SYMLINK;
@@ -1225,14 +1249,14 @@
12251249
@ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
12261250
}
12271251
@ &mdash; %!w(zCom) (user:
12281252
hyperlink_to_user(zUser,zDate,")");
12291253
if( g.perm.Hyperlink ){
1230
- @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1231
- @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1254
+ @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1255
+ @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
12321256
@ [annotate]</a>
1233
- @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1257
+ @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
12341258
@ [blame]</a>
12351259
}
12361260
cnt++;
12371261
if( pDownloadName && blob_size(pDownloadName)==0 ){
12381262
blob_append(pDownloadName, zName, -1);
@@ -1312,11 +1336,11 @@
13121336
}
13131337
@ - %!w(zCom) by
13141338
hyperlink_to_user(zUser,zDate," on");
13151339
hyperlink_to_date(zDate, ".");
13161340
if( pDownloadName && blob_size(pDownloadName)==0 ){
1317
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1341
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13181342
}
13191343
tag_private_status(rid);
13201344
cnt++;
13211345
}
13221346
db_finalize(&q);
@@ -1339,17 +1363,17 @@
13391363
}else{
13401364
@ Attachment "%h(zFilename)" to
13411365
}
13421366
objType |= OBJTYPE_ATTACHMENT;
13431367
if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1344
- if( g.perm.Hyperlink && g.perm.RdTkt ){
1345
- @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1368
+ if( g.perm.Hyperlink && g.anon.RdTkt ){
1369
+ @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
13461370
}else{
13471371
@ ticket [%S(zTarget)]
13481372
}
13491373
}else{
1350
- if( g.perm.Hyperlink && g.perm.RdWiki ){
1374
+ if( g.perm.Hyperlink && g.anon.RdWiki ){
13511375
@ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
13521376
}else{
13531377
@ wiki page [%h(zTarget)]
13541378
}
13551379
}
@@ -1364,11 +1388,11 @@
13641388
}
13651389
db_finalize(&q);
13661390
if( cnt==0 ){
13671391
@ Control artifact.
13681392
if( pDownloadName && blob_size(pDownloadName)==0 ){
1369
- blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1393
+ blob_appendf(pDownloadName, "%S.txt", zUuid);
13701394
}
13711395
tag_private_status(rid);
13721396
}
13731397
return objType;
13741398
}
@@ -1397,11 +1421,11 @@
13971421
ReCompiled *pRe = 0;
13981422
u64 diffFlags;
13991423
u32 objdescFlags = 0;
14001424
14011425
login_check_credentials();
1402
- if( !g.perm.Read ){ login_needed(); return; }
1426
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
14031427
v1 = name_to_rid_www("v1");
14041428
v2 = name_to_rid_www("v2");
14051429
if( v1==0 || v2==0 ) fossil_redirect_home();
14061430
zRe = P("regex");
14071431
if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1448,17 +1472,17 @@
14481472
g.zTop, P("v1"), P("v2"), zW);
14491473
}
14501474
14511475
if( P("smhdr")!=0 ){
14521476
@ <h2>Differences From Artifact
1453
- @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1454
- @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1477
+ @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1478
+ @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
14551479
}else{
14561480
@ <h2>Differences From
1457
- @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1481
+ @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
14581482
object_description(v1, objdescFlags, 0);
1459
- @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1483
+ @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
14601484
object_description(v2, objdescFlags, 0);
14611485
}
14621486
if( pRe ){
14631487
@ <b>Only differences that match regular expression "%h(zRe)"
14641488
@ are shown.</b>
@@ -1482,11 +1506,11 @@
14821506
const char *zMime;
14831507
Blob content;
14841508
14851509
rid = name_to_rid_www("name");
14861510
login_check_credentials();
1487
- if( !g.perm.Read ){ login_needed(); return; }
1511
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
14881512
if( rid==0 ) fossil_redirect_home();
14891513
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
14901514
if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
14911515
g.isConst = 1;
14921516
}
@@ -1579,11 +1603,11 @@
15791603
char *zUuid;
15801604
u32 objdescFlags = 0;
15811605
15821606
rid = name_to_rid_www("name");
15831607
login_check_credentials();
1584
- if( !g.perm.Read ){ login_needed(); return; }
1608
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
15851609
if( rid==0 ) fossil_redirect_home();
15861610
if( g.perm.Admin ){
15871611
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
15881612
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
15891613
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1765,11 +1789,11 @@
17651789
if( rid==0 ){
17661790
rid = name_to_rid_www("name");
17671791
}
17681792
17691793
login_check_credentials();
1770
- if( !g.perm.Read ){ login_needed(); return; }
1794
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
17711795
if( rid==0 ) fossil_redirect_home();
17721796
if( g.perm.Admin ){
17731797
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
17741798
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
17751799
style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1880,11 +1904,11 @@
18801904
Manifest *pTktChng;
18811905
int modPending;
18821906
const char *zModAction;
18831907
char *zTktTitle;
18841908
login_check_credentials();
1885
- if( !g.perm.RdTkt ){ login_needed(); return; }
1909
+ if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
18861910
rid = name_to_rid_www("name");
18871911
if( rid==0 ){ fossil_redirect_home(); }
18881912
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
18891913
if( g.perm.Admin ){
18901914
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1935,11 +1959,11 @@
19351959
}
19361960
19371961
@ <div class="section">Overview</div>
19381962
@ <p><table class="label-value">
19391963
@ <tr><th>Artifact&nbsp;ID:</th>
1940
- @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1964
+ @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
19411965
if( g.perm.Setup ){
19421966
@ (%d(rid))
19431967
}
19441968
modPending = moderation_pending(rid);
19451969
if( modPending ){
@@ -2252,11 +2276,11 @@
22522276
Blob comment;
22532277
char *zBranchName = 0;
22542278
Stmt q;
22552279
22562280
login_check_credentials();
2257
- if( !g.perm.Write ){ login_needed(); return; }
2281
+ if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
22582282
rid = name_to_typed_rid(P("r"), "ci");
22592283
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
22602284
zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
22612285
" FROM event WHERE objid=%d", rid);
22622286
if( zComment==0 ) fossil_redirect_home();
@@ -2454,11 +2478,11 @@
24542478
@ </blockquote>
24552479
@ <hr />
24562480
blob_reset(&suffix);
24572481
}
24582482
@ <p>Make changes to attributes of check-in
2459
- @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2483
+ @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
24602484
form_begin(0, "%R/ci_edit");
24612485
login_insert_csrf_secret();
24622486
@ <div><input type="hidden" name="r" value="%s(zUuid)" />
24632487
@ <table border="0" cellspacing="10">
24642488
@@ -2572,12 +2596,11 @@
25722596
@ <tr><th align="right" valign="top">Branch Closure:</th>
25732597
@ <td valign="top">
25742598
@ <label><input type="checkbox" name="close"%s(zCloseFlag) />
25752599
@ Mark branch
25762600
@ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2577
- @ as "closed" so that its leafs no longer appear on the "leaves" page
2578
- @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2601
+ @ as "closed".</label>
25792602
@ </td></tr>
25802603
}
25812604
}
25822605
if( zBranchName ) fossil_free(zBranchName);
25832606
25842607
--- src/info.c
+++ src/info.c
@@ -240,23 +240,23 @@
240 show_common_info(rid, "uuid:", 1, 1);
241 }
242 }
243
244 /*
245 ** Show information about all tags on a given node.
246 */
247 static void showTags(int rid, const char *zNotGlob){
248 Stmt q;
249 int cnt = 0;
250 db_prepare(&q,
251 "SELECT tag.tagid, tagname, "
252 " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
253 " value, datetime(tagxref.mtime%s), tagtype,"
254 " (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
255 " FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
256 " WHERE tagxref.rid=%d AND tagname NOT GLOB '%q'"
257 " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid, zNotGlob
258 );
259 while( db_step(&q)==SQLITE_ROW ){
260 const char *zTagname = db_column_text(&q, 1);
261 const char *zSrcUuid = db_column_text(&q, 2);
262 const char *zValue = db_column_text(&q, 3);
@@ -305,10 +305,33 @@
305 db_finalize(&q);
306 if( cnt ){
307 @ </ul>
308 }
309 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
311
312 /*
313 ** Append the difference between artifacts to the output
314 */
@@ -380,32 +403,32 @@
380 }
381 }else{
382 if( zOld && zNew ){
383 if( fossil_strcmp(zOld, zNew)!=0 ){
384 @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
385 @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
386 @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)].</a>
387 }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
388 @ <p>Name change
389 @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
390 @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
391 }else{
392 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
393 @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
394 }
395 }else if( zOld ){
396 @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
397 @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
398 }else{
399 @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
400 @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
401 }
402 if( diffFlags ){
403 append_diff(zOld, zNew, diffFlags, pRe);
404 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
405 @ &nbsp;&nbsp;
406 @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
407 }
408 }
409 }
410
411 /*
@@ -504,11 +527,11 @@
504 const char *zW; /* URL param for ignoring whitespace */
505 const char *zPage = "vinfo"; /* Page that shows diffs */
506 const char *zPageHide = "ci"; /* Page that hides diffs */
507
508 login_check_credentials();
509 if( !g.perm.Read ){ login_needed(); return; }
510 zName = P("name");
511 rid = name_to_rid_www("name");
512 if( rid==0 ){
513 style_header("Check-in Information Error");
514 @ No such object: %h(g.argv[2])
@@ -612,19 +635,19 @@
612 if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
613 zPJ[jj] = '_';
614 }
615 }
616 @ <tr><th>Timelines:</th><td>
617 @ %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
618 if( zParent ){
619 @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
620 }
621 if( !isLeaf ){
622 @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
623 }
624 if( zParent && !isLeaf ){
625 @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
626 }
627 db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
628 " WHERE rid=%d AND tagtype>0 "
629 " AND tag.tagid=tagxref.tagid "
630 " AND +tag.tagname GLOB 'sym-*'", rid);
@@ -634,29 +657,29 @@
634 }
635 db_finalize(&q2);
636
637
638 /* The Download: line */
639 if( g.perm.Zip ){
640 char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
641 zPJ, zUuid, zUuid);
642 @ </td></tr>
643 @ <tr><th>Downloads:</th><td>
644 @ %z(href("%s",zUrl))Tarball</a>
645 @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
646 @ ZIP archive</a>
647 fossil_free(zUrl);
648 }
649 @ </td></tr>
650 @ <tr><th>Other&nbsp;Links:</th>
651 @ <td>
652 @ %z(href("%R/tree?ci=%s",zUuid))files</a>
653 @ | %z(href("%R/fileage?name=%s",zUuid))file ages</a>
654 @ | %z(href("%R/tree?ci=%s&nofiles",zUuid))folders</a>
655 @ | %z(href("%R/artifact/%s",zUuid))manifest</a>
656 if( g.perm.Write ){
657 @ | %z(href("%R/ci_edit?r=%s",zUuid))edit</a>
658 }
659 @ </td>
660 @ </tr>
661 blob_reset(&projName);
662 }
@@ -664,11 +687,12 @@
664 }else{
665 style_header("Check-in Information");
666 login_anonymous_available();
667 }
668 db_finalize(&q1);
669 showTags(rid, "");
 
670 @ <div class="section">Changes</div>
671 @ <div class="sectionmenu">
672 verboseFlag = g.zPath[0]!='c';
673 if( db_get_boolean("show-version-diffs", 0)==0 ){
674 verboseFlag = !verboseFlag;
@@ -699,11 +723,11 @@
699 @ Show&nbsp;Unified&nbsp;Diffs</a>
700 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
701 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
702 }
703 if( zParent ){
704 @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
705 @ Patch</a>
706 }
707 @</div>
708 if( pRe ){
709 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -749,11 +773,11 @@
749 Blob wiki;
750 int modPending;
751 const char *zModAction;
752
753 login_check_credentials();
754 if( !g.perm.RdWiki ){ login_needed(); return; }
755 rid = name_to_rid_www("name");
756 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
757 style_header("Wiki Page Information Error");
758 @ No such object: %h(P("name"))
759 style_footer();
@@ -789,11 +813,11 @@
789 pWiki->zWikiTitle);
790 login_anonymous_available();
791 @ <div class="section">Overview</div>
792 @ <p><table class="label-value">
793 @ <tr><th>Artifact&nbsp;ID:</th>
794 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
795 if( g.perm.Setup ){
796 @ (%d(rid))
797 }
798 modPending = moderation_pending(rid);
799 if( modPending ){
@@ -808,11 +832,11 @@
808 if( pWiki->nParent>0 ){
809 int i;
810 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
811 for(i=0; i<pWiki->nParent; i++){
812 char *zParent = pWiki->azParent[i];
813 @ %z(href("info/%s",zParent))%s(zParent)</a>
814 }
815 @ </td></tr>
816 }
817 @ </table>
818
@@ -967,11 +991,11 @@
967 const char *zW;
968 const char *zVerbose;
969 const char *zGlob;
970 ReCompiled *pRe = 0;
971 login_check_credentials();
972 if( !g.perm.Read ){ login_needed(); return; }
973 login_anonymous_available();
974 zRe = P("regex");
975 if( zRe ) re_compile(&pRe, zRe, 0);
976 zBranch = P("branch");
977 if( zBranch && zBranch[0] ){
@@ -1182,17 +1206,17 @@
1182 int mPerm = db_column_int(&q, 5);
1183 const char *zBr = db_column_text(&q, 6);
1184 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1185 if( sameFilename && !showDetail ){
1186 if( cnt==1 ){
1187 @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
1188 }
1189 cnt++;
1190 continue;
1191 }
1192 if( !sameFilename ){
1193 if( prevName ) {
1194 @ </ul>
1195 }
1196 if( mPerm==PERM_LNK ){
1197 @ <li>Symbolic link
1198 objType |= OBJTYPE_SYMLINK;
@@ -1225,14 +1249,14 @@
1225 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1226 }
1227 @ &mdash; %!w(zCom) (user:
1228 hyperlink_to_user(zUser,zDate,")");
1229 if( g.perm.Hyperlink ){
1230 @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
1231 @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
1232 @ [annotate]</a>
1233 @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
1234 @ [blame]</a>
1235 }
1236 cnt++;
1237 if( pDownloadName && blob_size(pDownloadName)==0 ){
1238 blob_append(pDownloadName, zName, -1);
@@ -1312,11 +1336,11 @@
1312 }
1313 @ - %!w(zCom) by
1314 hyperlink_to_user(zUser,zDate," on");
1315 hyperlink_to_date(zDate, ".");
1316 if( pDownloadName && blob_size(pDownloadName)==0 ){
1317 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1318 }
1319 tag_private_status(rid);
1320 cnt++;
1321 }
1322 db_finalize(&q);
@@ -1339,17 +1363,17 @@
1339 }else{
1340 @ Attachment "%h(zFilename)" to
1341 }
1342 objType |= OBJTYPE_ATTACHMENT;
1343 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1344 if( g.perm.Hyperlink && g.perm.RdTkt ){
1345 @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
1346 }else{
1347 @ ticket [%S(zTarget)]
1348 }
1349 }else{
1350 if( g.perm.Hyperlink && g.perm.RdWiki ){
1351 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1352 }else{
1353 @ wiki page [%h(zTarget)]
1354 }
1355 }
@@ -1364,11 +1388,11 @@
1364 }
1365 db_finalize(&q);
1366 if( cnt==0 ){
1367 @ Control artifact.
1368 if( pDownloadName && blob_size(pDownloadName)==0 ){
1369 blob_appendf(pDownloadName, "%.10s.txt", zUuid);
1370 }
1371 tag_private_status(rid);
1372 }
1373 return objType;
1374 }
@@ -1397,11 +1421,11 @@
1397 ReCompiled *pRe = 0;
1398 u64 diffFlags;
1399 u32 objdescFlags = 0;
1400
1401 login_check_credentials();
1402 if( !g.perm.Read ){ login_needed(); return; }
1403 v1 = name_to_rid_www("v1");
1404 v2 = name_to_rid_www("v2");
1405 if( v1==0 || v2==0 ) fossil_redirect_home();
1406 zRe = P("regex");
1407 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1448,17 +1472,17 @@
1448 g.zTop, P("v1"), P("v2"), zW);
1449 }
1450
1451 if( P("smhdr")!=0 ){
1452 @ <h2>Differences From Artifact
1453 @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
1454 @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
1455 }else{
1456 @ <h2>Differences From
1457 @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
1458 object_description(v1, objdescFlags, 0);
1459 @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
1460 object_description(v2, objdescFlags, 0);
1461 }
1462 if( pRe ){
1463 @ <b>Only differences that match regular expression "%h(zRe)"
1464 @ are shown.</b>
@@ -1482,11 +1506,11 @@
1482 const char *zMime;
1483 Blob content;
1484
1485 rid = name_to_rid_www("name");
1486 login_check_credentials();
1487 if( !g.perm.Read ){ login_needed(); return; }
1488 if( rid==0 ) fossil_redirect_home();
1489 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1490 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1491 g.isConst = 1;
1492 }
@@ -1579,11 +1603,11 @@
1579 char *zUuid;
1580 u32 objdescFlags = 0;
1581
1582 rid = name_to_rid_www("name");
1583 login_check_credentials();
1584 if( !g.perm.Read ){ login_needed(); return; }
1585 if( rid==0 ) fossil_redirect_home();
1586 if( g.perm.Admin ){
1587 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1588 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1589 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1765,11 +1789,11 @@
1765 if( rid==0 ){
1766 rid = name_to_rid_www("name");
1767 }
1768
1769 login_check_credentials();
1770 if( !g.perm.Read ){ login_needed(); return; }
1771 if( rid==0 ) fossil_redirect_home();
1772 if( g.perm.Admin ){
1773 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1774 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1775 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1880,11 +1904,11 @@
1880 Manifest *pTktChng;
1881 int modPending;
1882 const char *zModAction;
1883 char *zTktTitle;
1884 login_check_credentials();
1885 if( !g.perm.RdTkt ){ login_needed(); return; }
1886 rid = name_to_rid_www("name");
1887 if( rid==0 ){ fossil_redirect_home(); }
1888 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1889 if( g.perm.Admin ){
1890 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1935,11 +1959,11 @@
1935 }
1936
1937 @ <div class="section">Overview</div>
1938 @ <p><table class="label-value">
1939 @ <tr><th>Artifact&nbsp;ID:</th>
1940 @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
1941 if( g.perm.Setup ){
1942 @ (%d(rid))
1943 }
1944 modPending = moderation_pending(rid);
1945 if( modPending ){
@@ -2252,11 +2276,11 @@
2252 Blob comment;
2253 char *zBranchName = 0;
2254 Stmt q;
2255
2256 login_check_credentials();
2257 if( !g.perm.Write ){ login_needed(); return; }
2258 rid = name_to_typed_rid(P("r"), "ci");
2259 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2260 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2261 " FROM event WHERE objid=%d", rid);
2262 if( zComment==0 ) fossil_redirect_home();
@@ -2454,11 +2478,11 @@
2454 @ </blockquote>
2455 @ <hr />
2456 blob_reset(&suffix);
2457 }
2458 @ <p>Make changes to attributes of check-in
2459 @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
2460 form_begin(0, "%R/ci_edit");
2461 login_insert_csrf_secret();
2462 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2463 @ <table border="0" cellspacing="10">
2464
@@ -2572,12 +2596,11 @@
2572 @ <tr><th align="right" valign="top">Branch Closure:</th>
2573 @ <td valign="top">
2574 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2575 @ Mark branch
2576 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2577 @ as "closed" so that its leafs no longer appear on the "leaves" page
2578 @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
2579 @ </td></tr>
2580 }
2581 }
2582 if( zBranchName ) fossil_free(zBranchName);
2583
2584
--- src/info.c
+++ src/info.c
@@ -240,23 +240,23 @@
240 show_common_info(rid, "uuid:", 1, 1);
241 }
242 }
243
244 /*
245 ** Show information about all tags on a given check-in.
246 */
247 static void showTags(int rid){
248 Stmt q;
249 int cnt = 0;
250 db_prepare(&q,
251 "SELECT tag.tagid, tagname, "
252 " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
253 " value, datetime(tagxref.mtime%s), tagtype,"
254 " (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
255 " FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
256 " WHERE tagxref.rid=%d"
257 " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid
258 );
259 while( db_step(&q)==SQLITE_ROW ){
260 const char *zTagname = db_column_text(&q, 1);
261 const char *zSrcUuid = db_column_text(&q, 2);
262 const char *zValue = db_column_text(&q, 3);
@@ -305,10 +305,33 @@
305 db_finalize(&q);
306 if( cnt ){
307 @ </ul>
308 }
309 }
310
311 /*
312 ** Show the context graph (immediate parents and children) for
313 ** check-in rid.
314 */
315 static void showContext(int rid){
316 Blob sql;
317 Stmt q;
318 @ <div class="section">Context</div>
319 blob_zero(&sql);
320 blob_append(&sql, timeline_query_for_www(), -1);
321 db_multi_exec(
322 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
323 "INSERT INTO ok VALUES(%d);"
324 "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
325 "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
326 rid, rid, rid
327 );
328 blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
329 db_prepare(&q, "%s", blob_sql_text(&sql));
330 www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0);
331 db_finalize(&q);
332 }
333
334
335 /*
336 ** Append the difference between artifacts to the output
337 */
@@ -380,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 /*
@@ -504,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])
@@ -612,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);
@@ -634,29 +657,29 @@
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 if( g.anon.Write ){
680 @ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
681 }
682 @ </td>
683 @ </tr>
684 blob_reset(&projName);
685 }
@@ -664,11 +687,12 @@
687 }else{
688 style_header("Check-in Information");
689 login_anonymous_available();
690 }
691 db_finalize(&q1);
692 showTags(rid);
693 showContext(rid);
694 @ <div class="section">Changes</div>
695 @ <div class="sectionmenu">
696 verboseFlag = g.zPath[0]!='c';
697 if( db_get_boolean("show-version-diffs", 0)==0 ){
698 verboseFlag = !verboseFlag;
@@ -699,11 +723,11 @@
723 @ Show&nbsp;Unified&nbsp;Diffs</a>
724 @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
725 @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
726 }
727 if( zParent ){
728 @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
729 @ Patch</a>
730 }
731 @</div>
732 if( pRe ){
733 @ <p><b>Only differences that match regular expression "%h(zRe)"
@@ -749,11 +773,11 @@
773 Blob wiki;
774 int modPending;
775 const char *zModAction;
776
777 login_check_credentials();
778 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
779 rid = name_to_rid_www("name");
780 if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
781 style_header("Wiki Page Information Error");
782 @ No such object: %h(P("name"))
783 style_footer();
@@ -789,11 +813,11 @@
813 pWiki->zWikiTitle);
814 login_anonymous_available();
815 @ <div class="section">Overview</div>
816 @ <p><table class="label-value">
817 @ <tr><th>Artifact&nbsp;ID:</th>
818 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
819 if( g.perm.Setup ){
820 @ (%d(rid))
821 }
822 modPending = moderation_pending(rid);
823 if( modPending ){
@@ -808,11 +832,11 @@
832 if( pWiki->nParent>0 ){
833 int i;
834 @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
835 for(i=0; i<pWiki->nParent; i++){
836 char *zParent = pWiki->azParent[i];
837 @ %z(href("info/%!S",zParent))%s(zParent)</a>
838 }
839 @ </td></tr>
840 }
841 @ </table>
842
@@ -967,11 +991,11 @@
991 const char *zW;
992 const char *zVerbose;
993 const char *zGlob;
994 ReCompiled *pRe = 0;
995 login_check_credentials();
996 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
997 login_anonymous_available();
998 zRe = P("regex");
999 if( zRe ) re_compile(&pRe, zRe, 0);
1000 zBranch = P("branch");
1001 if( zBranch && zBranch[0] ){
@@ -1182,17 +1206,17 @@
1206 int mPerm = db_column_int(&q, 5);
1207 const char *zBr = db_column_text(&q, 6);
1208 int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
1209 if( sameFilename && !showDetail ){
1210 if( cnt==1 ){
1211 @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
1212 }
1213 cnt++;
1214 continue;
1215 }
1216 if( !sameFilename ){
1217 if( prevName && showDetail ) {
1218 @ </ul>
1219 }
1220 if( mPerm==PERM_LNK ){
1221 @ <li>Symbolic link
1222 objType |= OBJTYPE_SYMLINK;
@@ -1225,14 +1249,14 @@
1249 @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
1250 }
1251 @ &mdash; %!w(zCom) (user:
1252 hyperlink_to_user(zUser,zDate,")");
1253 if( g.perm.Hyperlink ){
1254 @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
1255 @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
1256 @ [annotate]</a>
1257 @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
1258 @ [blame]</a>
1259 }
1260 cnt++;
1261 if( pDownloadName && blob_size(pDownloadName)==0 ){
1262 blob_append(pDownloadName, zName, -1);
@@ -1312,11 +1336,11 @@
1336 }
1337 @ - %!w(zCom) by
1338 hyperlink_to_user(zUser,zDate," on");
1339 hyperlink_to_date(zDate, ".");
1340 if( pDownloadName && blob_size(pDownloadName)==0 ){
1341 blob_appendf(pDownloadName, "%S.txt", zUuid);
1342 }
1343 tag_private_status(rid);
1344 cnt++;
1345 }
1346 db_finalize(&q);
@@ -1339,17 +1363,17 @@
1363 }else{
1364 @ Attachment "%h(zFilename)" to
1365 }
1366 objType |= OBJTYPE_ATTACHMENT;
1367 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
1368 if( g.perm.Hyperlink && g.anon.RdTkt ){
1369 @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
1370 }else{
1371 @ ticket [%S(zTarget)]
1372 }
1373 }else{
1374 if( g.perm.Hyperlink && g.anon.RdWiki ){
1375 @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
1376 }else{
1377 @ wiki page [%h(zTarget)]
1378 }
1379 }
@@ -1364,11 +1388,11 @@
1388 }
1389 db_finalize(&q);
1390 if( cnt==0 ){
1391 @ Control artifact.
1392 if( pDownloadName && blob_size(pDownloadName)==0 ){
1393 blob_appendf(pDownloadName, "%S.txt", zUuid);
1394 }
1395 tag_private_status(rid);
1396 }
1397 return objType;
1398 }
@@ -1397,11 +1421,11 @@
1421 ReCompiled *pRe = 0;
1422 u64 diffFlags;
1423 u32 objdescFlags = 0;
1424
1425 login_check_credentials();
1426 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1427 v1 = name_to_rid_www("v1");
1428 v2 = name_to_rid_www("v2");
1429 if( v1==0 || v2==0 ) fossil_redirect_home();
1430 zRe = P("regex");
1431 if( zRe ) re_compile(&pRe, zRe, 0);
@@ -1448,17 +1472,17 @@
1472 g.zTop, P("v1"), P("v2"), zW);
1473 }
1474
1475 if( P("smhdr")!=0 ){
1476 @ <h2>Differences From Artifact
1477 @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
1478 @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
1479 }else{
1480 @ <h2>Differences From
1481 @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
1482 object_description(v1, objdescFlags, 0);
1483 @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
1484 object_description(v2, objdescFlags, 0);
1485 }
1486 if( pRe ){
1487 @ <b>Only differences that match regular expression "%h(zRe)"
1488 @ are shown.</b>
@@ -1482,11 +1506,11 @@
1506 const char *zMime;
1507 Blob content;
1508
1509 rid = name_to_rid_www("name");
1510 login_check_credentials();
1511 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1512 if( rid==0 ) fossil_redirect_home();
1513 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1514 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
1515 g.isConst = 1;
1516 }
@@ -1579,11 +1603,11 @@
1603 char *zUuid;
1604 u32 objdescFlags = 0;
1605
1606 rid = name_to_rid_www("name");
1607 login_check_credentials();
1608 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1609 if( rid==0 ) fossil_redirect_home();
1610 if( g.perm.Admin ){
1611 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1612 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1613 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
@@ -1765,11 +1789,11 @@
1789 if( rid==0 ){
1790 rid = name_to_rid_www("name");
1791 }
1792
1793 login_check_credentials();
1794 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1795 if( rid==0 ) fossil_redirect_home();
1796 if( g.perm.Admin ){
1797 const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1798 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1799 style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
@@ -1880,11 +1904,11 @@
1904 Manifest *pTktChng;
1905 int modPending;
1906 const char *zModAction;
1907 char *zTktTitle;
1908 login_check_credentials();
1909 if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
1910 rid = name_to_rid_www("name");
1911 if( rid==0 ){ fossil_redirect_home(); }
1912 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
1913 if( g.perm.Admin ){
1914 if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
@@ -1935,11 +1959,11 @@
1959 }
1960
1961 @ <div class="section">Overview</div>
1962 @ <p><table class="label-value">
1963 @ <tr><th>Artifact&nbsp;ID:</th>
1964 @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
1965 if( g.perm.Setup ){
1966 @ (%d(rid))
1967 }
1968 modPending = moderation_pending(rid);
1969 if( modPending ){
@@ -2252,11 +2276,11 @@
2276 Blob comment;
2277 char *zBranchName = 0;
2278 Stmt q;
2279
2280 login_check_credentials();
2281 if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
2282 rid = name_to_typed_rid(P("r"), "ci");
2283 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
2284 zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
2285 " FROM event WHERE objid=%d", rid);
2286 if( zComment==0 ) fossil_redirect_home();
@@ -2454,11 +2478,11 @@
2478 @ </blockquote>
2479 @ <hr />
2480 blob_reset(&suffix);
2481 }
2482 @ <p>Make changes to attributes of check-in
2483 @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
2484 form_begin(0, "%R/ci_edit");
2485 login_insert_csrf_secret();
2486 @ <div><input type="hidden" name="r" value="%s(zUuid)" />
2487 @ <table border="0" cellspacing="10">
2488
@@ -2572,12 +2596,11 @@
2596 @ <tr><th align="right" valign="top">Branch Closure:</th>
2597 @ <td valign="top">
2598 @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
2599 @ Mark branch
2600 @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
2601 @ as "closed".</label>
 
2602 @ </td></tr>
2603 }
2604 }
2605 if( zBranchName ) fossil_free(zBranchName);
2606
2607
+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
+82 -24
--- 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
@@ -689,12 +694,33 @@
689694
g.argv = zNewArgv;
690695
}
691696
zCmdName = g.argv[1];
692697
}
693698
#ifndef _WIN32
694
- if( !is_valid_fd(2) ) fossil_panic("file descriptor 2 not open");
695
- /* if( is_valid_fd(3) ) fossil_warning("file descriptor 3 is open"); */
699
+ /* There is a bug in stunnel4 in which it sometimes starts up client
700
+ ** processes without first opening file descriptor 2 (standard error).
701
+ ** If this happens, and a subsequent open() of a database returns file
702
+ ** descriptor 2, and then an assert() fires and writes on fd 2, that
703
+ ** can corrupt the data file. To avoid this problem, make sure open()
704
+ ** will never return file descriptor 2 or less. */
705
+ if( !is_valid_fd(2) ){
706
+ int nTry = 0;
707
+ int fd = 0;
708
+ int x = 0;
709
+ do{
710
+ fd = open("/dev/null",O_WRONLY);
711
+ if( fd>=2 ) break;
712
+ if( fd<0 ) x = errno;
713
+ }while( nTry++ < 2 );
714
+ if( fd<2 ){
715
+ g.cgiOutput = 1;
716
+ g.httpOut = stdout;
717
+ g.fullHttpReply = !g.isHTTP;
718
+ fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)",
719
+ fd, x);
720
+ }
721
+ }
696722
#endif
697723
rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx);
698724
if( rc==1 ){
699725
#ifdef FOSSIL_ENABLE_TH1_HOOKS
700726
if( !g.isHTTP && !g.fNoThHook ){
@@ -1152,11 +1178,11 @@
11521178
const char *z = aCommand[i].zName;
11531179
if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
11541180
if( j==0 ){
11551181
@ <td valign="top"><ul>
11561182
}
1157
- @ <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>
11581184
j++;
11591185
if( j>=n ){
11601186
@ </ul></td>
11611187
j = 0;
11621188
}
@@ -1180,11 +1206,11 @@
11801206
if( '/'!=*z ) continue;
11811207
if( j==0 ){
11821208
@ <td valign="top"><ul>
11831209
}
11841210
if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1185
- @ <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>
11861212
}else{
11871213
@ <li>%s(z+1)</li>
11881214
}
11891215
j++;
11901216
if( j>=n ){
@@ -1210,11 +1236,11 @@
12101236
if( strncmp(z,"test",4)!=0 ) continue;
12111237
if( j==0 ){
12121238
@ <td valign="top"><ul>
12131239
}
12141240
if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1215
- @ <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>
12161242
}else{
12171243
@ <li>%s(z)</li>
12181244
}
12191245
j++;
12201246
if( j>=n ){
@@ -1331,12 +1357,15 @@
13311357
** zRepo might be a directory itself. In that case chroot into
13321358
** the directory zRepo.
13331359
**
13341360
** Assume the user-id and group-id of the repository, or if zRepo
13351361
** 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.
13361365
*/
1337
-static char *enter_chroot_jail(char *zRepo){
1366
+static char *enter_chroot_jail(char *zRepo, int noJail){
13381367
#if !defined(_WIN32)
13391368
if( getuid()==0 ){
13401369
int i;
13411370
struct stat sStat;
13421371
Blob dir;
@@ -1345,26 +1374,28 @@
13451374
db_close(1);
13461375
}
13471376
13481377
file_canonical_name(zRepo, &dir, 0);
13491378
zDir = blob_str(&dir);
1350
- if( file_isdir(zDir)==1 ){
1351
- if( file_chdir(zDir, 1) ){
1352
- fossil_fatal("unable to chroot into %s", zDir);
1353
- }
1354
- zRepo = "/";
1355
- }else{
1356
- for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1357
- if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1358
- if( i>0 ){
1359
- zDir[i] = 0;
1379
+ if( !noJail ){
1380
+ if( file_isdir(zDir)==1 ){
13601381
if( file_chdir(zDir, 1) ){
13611382
fossil_fatal("unable to chroot into %s", zDir);
13621383
}
1363
- 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];
13641396
}
1365
- zRepo = &zDir[i];
13661397
}
13671398
if( stat(zRepo, &sStat)!=0 ){
13681399
fossil_fatal("cannot stat() repository: %s", zRepo);
13691400
}
13701401
i = setgid(sStat.st_gid);
@@ -1900,10 +1931,22 @@
19001931
*/
19011932
cgi_setenv("HOME", blob_str(&value));
19021933
blob_reset(&value);
19031934
continue;
19041935
}
1936
+ if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
1937
+ /* skin: LABEL
1938
+ **
1939
+ ** Use one of the built-in skins defined by LABEL. LABEL is the
1940
+ ** name of the subdirectory under the skins/ directory that holds
1941
+ ** the elements of the built-in skin. If LABEL does not match,
1942
+ ** this directive is a silent no-op.
1943
+ */
1944
+ skin_use_alternative(blob_str(&value));
1945
+ blob_reset(&value);
1946
+ continue;
1947
+ }
19051948
}
19061949
blob_reset(&config);
19071950
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
19081951
cgi_panic("Unable to find or open the project repository");
19091952
}
@@ -1988,15 +2031,17 @@
19882031
**
19892032
** Options:
19902033
** --localauth enable automatic login for local connections
19912034
** --host NAME specify hostname of the server
19922035
** --https signal a request coming in via https
2036
+** --nojail drop root privilege but do not enter the chroot jail
19932037
** --nossl signal that no SSL connections are available
19942038
** --notfound URL use URL as "HTTP 404, object not found" page.
19952039
** --files GLOB comma-separate glob patterns for static file to serve
19962040
** --baseurl URL base URL (useful with reverse proxies)
19972041
** --scgi Interpret input as SCGI rather than HTTP
2042
+** --skin LABEL Use override skin LABEL
19982043
**
19992044
** See also: cgi, server, winsrv
20002045
*/
20012046
void cmd_http(void){
20022047
const char *zIpAddr = 0;
@@ -2003,10 +2048,11 @@
20032048
const char *zNotFound;
20042049
const char *zHost;
20052050
const char *zAltBase;
20062051
const char *zFileGlob;
20072052
int useSCGI;
2053
+ int noJail;
20082054
20092055
/* The winhttp module passes the --files option as --files-urlenc with
20102056
** the argument being URL encoded, to avoid wildcard expansion in the
20112057
** shell. This option is for internal use and is undocumented.
20122058
*/
@@ -2016,11 +2062,13 @@
20162062
dehttpize(z);
20172063
zFileGlob = z;
20182064
}else{
20192065
zFileGlob = find_option("files",0,1);
20202066
}
2067
+ skin_override();
20212068
zNotFound = find_option("notfound", 0, 1);
2069
+ noJail = find_option("nojail",0,0)!=0;
20222070
g.useLocalauth = find_option("localauth", 0, 0)!=0;
20232071
g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
20242072
useSCGI = find_option("scgi", 0, 0)!=0;
20252073
zAltBase = find_option("baseurl", 0, 1);
20262074
if( zAltBase ) set_base_url(zAltBase);
@@ -2053,11 +2101,11 @@
20532101
zIpAddr = cgi_ssh_remote_addr(0);
20542102
if( zIpAddr && zIpAddr[0] ){
20552103
g.fSshClient |= CGI_SSH_CLIENT;
20562104
}
20572105
}
2058
- g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2106
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
20592107
if( useSCGI ){
20602108
cgi_handle_scgi_request();
20612109
}else if( g.fSshClient & CGI_SSH_CLIENT ){
20622110
ssh_request_loop(zIpAddr, glob_create(zFileGlob));
20632111
}else{
@@ -2171,18 +2219,21 @@
21712219
** and the connection is from localhost. The optional REPOSITORY argument
21722220
** to "ui" may be a directory and will function as "server" if and only if
21732221
** the --notfound option is used.
21742222
**
21752223
** Options:
2224
+** --baseurl URL Use URL as the base (useful for reverse proxies)
2225
+** --files GLOBLIST Comma-separated list of glob patterns for static files
21762226
** --localauth enable automatic login for requests from localhost
21772227
** --localhost listen on 127.0.0.1 only (always true for "ui")
2228
+** --nojail Drop root privileges but do not enter the chroot jail
2229
+** --notfound URL Redirect
21782230
** -P|--port TCPPORT listen to request on port TCPPORT
21792231
** --th-trace trace TH1 execution (for debugging purposes)
2180
-** --baseurl URL Use URL as the base (useful for reverse proxies)
2181
-** --notfound URL Redirect
2182
-** --files GLOBLIST Comma-separated list of glob patterns for static files
21832232
** --scgi Accept SCGI rather than HTTP
2233
+** --skin LABEL Use override skin LABEL
2234
+
21842235
**
21852236
** See also: cgi, http, winsrv
21862237
*/
21872238
void cmd_webserver(void){
21882239
int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2190,10 +2241,13 @@
21902241
const char *zBrowser; /* Name of web browser program */
21912242
char *zBrowserCmd = 0; /* Command to launch the web browser */
21922243
int isUiCmd; /* True if command is "ui", not "server' */
21932244
const char *zNotFound; /* The --notfound option or NULL */
21942245
int flags = 0; /* Server flags */
2246
+#if !defined(_WIN32)
2247
+ int noJail; /* Do not enter the chroot jail */
2248
+#endif
21952249
const char *zAltBase; /* Argument to the --baseurl option */
21962250
const char *zFileGlob; /* Static content must match this */
21972251
char *zIpAddr = 0; /* Bind to this IP address */
21982252
21992253
#if defined(_WIN32)
@@ -2207,10 +2261,14 @@
22072261
dehttpize(z);
22082262
zFileGlob = z;
22092263
}else{
22102264
zFileGlob = find_option("files",0,1);
22112265
}
2266
+ skin_override();
2267
+#if !defined(_WIN32)
2268
+ noJail = find_option("nojail",0,0)!=0;
2269
+#endif
22122270
g.useLocalauth = find_option("localauth", 0, 0)!=0;
22132271
Th_InitTraceLog();
22142272
zPort = find_option("port", "P", 1);
22152273
zNotFound = find_option("notfound", 0, 1);
22162274
zAltBase = find_option("baseurl", 0, 1);
@@ -2284,11 +2342,11 @@
22842342
if( g.fHttpTrace || g.fSqlTrace ){
22852343
fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
22862344
}
22872345
g.cgiOutput = 1;
22882346
find_server_repository(isUiCmd && zNotFound==0, 2);
2289
- g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2347
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
22902348
if( flags & HTTP_SERVER_SCGI ){
22912349
cgi_handle_scgi_request();
22922350
}else{
22932351
cgi_handle_http_request(0);
22942352
}
22952353
--- 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
@@ -689,12 +694,33 @@
689 g.argv = zNewArgv;
690 }
691 zCmdName = g.argv[1];
692 }
693 #ifndef _WIN32
694 if( !is_valid_fd(2) ) fossil_panic("file descriptor 2 not open");
695 /* if( is_valid_fd(3) ) fossil_warning("file descriptor 3 is open"); */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696 #endif
697 rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx);
698 if( rc==1 ){
699 #ifdef FOSSIL_ENABLE_TH1_HOOKS
700 if( !g.isHTTP && !g.fNoThHook ){
@@ -1152,11 +1178,11 @@
1152 const char *z = aCommand[i].zName;
1153 if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
1154 if( j==0 ){
1155 @ <td valign="top"><ul>
1156 }
1157 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1158 j++;
1159 if( j>=n ){
1160 @ </ul></td>
1161 j = 0;
1162 }
@@ -1180,11 +1206,11 @@
1180 if( '/'!=*z ) continue;
1181 if( j==0 ){
1182 @ <td valign="top"><ul>
1183 }
1184 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1185 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z+1)</a></li>
1186 }else{
1187 @ <li>%s(z+1)</li>
1188 }
1189 j++;
1190 if( j>=n ){
@@ -1210,11 +1236,11 @@
1210 if( strncmp(z,"test",4)!=0 ) continue;
1211 if( j==0 ){
1212 @ <td valign="top"><ul>
1213 }
1214 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1215 @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
1216 }else{
1217 @ <li>%s(z)</li>
1218 }
1219 j++;
1220 if( j>=n ){
@@ -1331,12 +1357,15 @@
1331 ** zRepo might be a directory itself. In that case chroot into
1332 ** the directory zRepo.
1333 **
1334 ** Assume the user-id and group-id of the repository, or if zRepo
1335 ** is a directory, of that directory.
 
 
 
1336 */
1337 static char *enter_chroot_jail(char *zRepo){
1338 #if !defined(_WIN32)
1339 if( getuid()==0 ){
1340 int i;
1341 struct stat sStat;
1342 Blob dir;
@@ -1345,26 +1374,28 @@
1345 db_close(1);
1346 }
1347
1348 file_canonical_name(zRepo, &dir, 0);
1349 zDir = blob_str(&dir);
1350 if( file_isdir(zDir)==1 ){
1351 if( file_chdir(zDir, 1) ){
1352 fossil_fatal("unable to chroot into %s", zDir);
1353 }
1354 zRepo = "/";
1355 }else{
1356 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1357 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1358 if( i>0 ){
1359 zDir[i] = 0;
1360 if( file_chdir(zDir, 1) ){
1361 fossil_fatal("unable to chroot into %s", zDir);
1362 }
1363 zDir[i] = '/';
 
 
 
 
 
 
 
 
 
 
 
1364 }
1365 zRepo = &zDir[i];
1366 }
1367 if( stat(zRepo, &sStat)!=0 ){
1368 fossil_fatal("cannot stat() repository: %s", zRepo);
1369 }
1370 i = setgid(sStat.st_gid);
@@ -1900,10 +1931,22 @@
1900 */
1901 cgi_setenv("HOME", blob_str(&value));
1902 blob_reset(&value);
1903 continue;
1904 }
 
 
 
 
 
 
 
 
 
 
 
 
1905 }
1906 blob_reset(&config);
1907 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
1908 cgi_panic("Unable to find or open the project repository");
1909 }
@@ -1988,15 +2031,17 @@
1988 **
1989 ** Options:
1990 ** --localauth enable automatic login for local connections
1991 ** --host NAME specify hostname of the server
1992 ** --https signal a request coming in via https
 
1993 ** --nossl signal that no SSL connections are available
1994 ** --notfound URL use URL as "HTTP 404, object not found" page.
1995 ** --files GLOB comma-separate glob patterns for static file to serve
1996 ** --baseurl URL base URL (useful with reverse proxies)
1997 ** --scgi Interpret input as SCGI rather than HTTP
 
1998 **
1999 ** See also: cgi, server, winsrv
2000 */
2001 void cmd_http(void){
2002 const char *zIpAddr = 0;
@@ -2003,10 +2048,11 @@
2003 const char *zNotFound;
2004 const char *zHost;
2005 const char *zAltBase;
2006 const char *zFileGlob;
2007 int useSCGI;
 
2008
2009 /* The winhttp module passes the --files option as --files-urlenc with
2010 ** the argument being URL encoded, to avoid wildcard expansion in the
2011 ** shell. This option is for internal use and is undocumented.
2012 */
@@ -2016,11 +2062,13 @@
2016 dehttpize(z);
2017 zFileGlob = z;
2018 }else{
2019 zFileGlob = find_option("files",0,1);
2020 }
 
2021 zNotFound = find_option("notfound", 0, 1);
 
2022 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2023 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2024 useSCGI = find_option("scgi", 0, 0)!=0;
2025 zAltBase = find_option("baseurl", 0, 1);
2026 if( zAltBase ) set_base_url(zAltBase);
@@ -2053,11 +2101,11 @@
2053 zIpAddr = cgi_ssh_remote_addr(0);
2054 if( zIpAddr && zIpAddr[0] ){
2055 g.fSshClient |= CGI_SSH_CLIENT;
2056 }
2057 }
2058 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2059 if( useSCGI ){
2060 cgi_handle_scgi_request();
2061 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2062 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2063 }else{
@@ -2171,18 +2219,21 @@
2171 ** and the connection is from localhost. The optional REPOSITORY argument
2172 ** to "ui" may be a directory and will function as "server" if and only if
2173 ** the --notfound option is used.
2174 **
2175 ** Options:
 
 
2176 ** --localauth enable automatic login for requests from localhost
2177 ** --localhost listen on 127.0.0.1 only (always true for "ui")
 
 
2178 ** -P|--port TCPPORT listen to request on port TCPPORT
2179 ** --th-trace trace TH1 execution (for debugging purposes)
2180 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2181 ** --notfound URL Redirect
2182 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2183 ** --scgi Accept SCGI rather than HTTP
 
 
2184 **
2185 ** See also: cgi, http, winsrv
2186 */
2187 void cmd_webserver(void){
2188 int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2190,10 +2241,13 @@
2190 const char *zBrowser; /* Name of web browser program */
2191 char *zBrowserCmd = 0; /* Command to launch the web browser */
2192 int isUiCmd; /* True if command is "ui", not "server' */
2193 const char *zNotFound; /* The --notfound option or NULL */
2194 int flags = 0; /* Server flags */
 
 
 
2195 const char *zAltBase; /* Argument to the --baseurl option */
2196 const char *zFileGlob; /* Static content must match this */
2197 char *zIpAddr = 0; /* Bind to this IP address */
2198
2199 #if defined(_WIN32)
@@ -2207,10 +2261,14 @@
2207 dehttpize(z);
2208 zFileGlob = z;
2209 }else{
2210 zFileGlob = find_option("files",0,1);
2211 }
 
 
 
 
2212 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2213 Th_InitTraceLog();
2214 zPort = find_option("port", "P", 1);
2215 zNotFound = find_option("notfound", 0, 1);
2216 zAltBase = find_option("baseurl", 0, 1);
@@ -2284,11 +2342,11 @@
2284 if( g.fHttpTrace || g.fSqlTrace ){
2285 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2286 }
2287 g.cgiOutput = 1;
2288 find_server_repository(isUiCmd && zNotFound==0, 2);
2289 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
2290 if( flags & HTTP_SERVER_SCGI ){
2291 cgi_handle_scgi_request();
2292 }else{
2293 cgi_handle_http_request(0);
2294 }
2295
--- 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
@@ -689,12 +694,33 @@
694 g.argv = zNewArgv;
695 }
696 zCmdName = g.argv[1];
697 }
698 #ifndef _WIN32
699 /* There is a bug in stunnel4 in which it sometimes starts up client
700 ** processes without first opening file descriptor 2 (standard error).
701 ** If this happens, and a subsequent open() of a database returns file
702 ** descriptor 2, and then an assert() fires and writes on fd 2, that
703 ** can corrupt the data file. To avoid this problem, make sure open()
704 ** will never return file descriptor 2 or less. */
705 if( !is_valid_fd(2) ){
706 int nTry = 0;
707 int fd = 0;
708 int x = 0;
709 do{
710 fd = open("/dev/null",O_WRONLY);
711 if( fd>=2 ) break;
712 if( fd<0 ) x = errno;
713 }while( nTry++ < 2 );
714 if( fd<2 ){
715 g.cgiOutput = 1;
716 g.httpOut = stdout;
717 g.fullHttpReply = !g.isHTTP;
718 fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)",
719 fd, x);
720 }
721 }
722 #endif
723 rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx);
724 if( rc==1 ){
725 #ifdef FOSSIL_ENABLE_TH1_HOOKS
726 if( !g.isHTTP && !g.fNoThHook ){
@@ -1152,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 }
@@ -1180,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 ){
@@ -1210,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 ){
@@ -1331,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;
@@ -1345,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);
@@ -1900,10 +1931,22 @@
1931 */
1932 cgi_setenv("HOME", blob_str(&value));
1933 blob_reset(&value);
1934 continue;
1935 }
1936 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
1937 /* skin: LABEL
1938 **
1939 ** Use one of the built-in skins defined by LABEL. LABEL is the
1940 ** name of the subdirectory under the skins/ directory that holds
1941 ** the elements of the built-in skin. If LABEL does not match,
1942 ** this directive is a silent no-op.
1943 */
1944 skin_use_alternative(blob_str(&value));
1945 blob_reset(&value);
1946 continue;
1947 }
1948 }
1949 blob_reset(&config);
1950 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
1951 cgi_panic("Unable to find or open the project repository");
1952 }
@@ -1988,15 +2031,17 @@
2031 **
2032 ** Options:
2033 ** --localauth enable automatic login for local connections
2034 ** --host NAME specify hostname of the server
2035 ** --https signal a request coming in via https
2036 ** --nojail drop root privilege but do not enter the chroot jail
2037 ** --nossl signal that no SSL connections are available
2038 ** --notfound URL use URL as "HTTP 404, object not found" page.
2039 ** --files GLOB comma-separate glob patterns for static file to serve
2040 ** --baseurl URL base URL (useful with reverse proxies)
2041 ** --scgi Interpret input as SCGI rather than HTTP
2042 ** --skin LABEL Use override skin LABEL
2043 **
2044 ** See also: cgi, server, winsrv
2045 */
2046 void cmd_http(void){
2047 const char *zIpAddr = 0;
@@ -2003,10 +2048,11 @@
2048 const char *zNotFound;
2049 const char *zHost;
2050 const char *zAltBase;
2051 const char *zFileGlob;
2052 int useSCGI;
2053 int noJail;
2054
2055 /* The winhttp module passes the --files option as --files-urlenc with
2056 ** the argument being URL encoded, to avoid wildcard expansion in the
2057 ** shell. This option is for internal use and is undocumented.
2058 */
@@ -2016,11 +2062,13 @@
2062 dehttpize(z);
2063 zFileGlob = z;
2064 }else{
2065 zFileGlob = find_option("files",0,1);
2066 }
2067 skin_override();
2068 zNotFound = find_option("notfound", 0, 1);
2069 noJail = find_option("nojail",0,0)!=0;
2070 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2071 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2072 useSCGI = find_option("scgi", 0, 0)!=0;
2073 zAltBase = find_option("baseurl", 0, 1);
2074 if( zAltBase ) set_base_url(zAltBase);
@@ -2053,11 +2101,11 @@
2101 zIpAddr = cgi_ssh_remote_addr(0);
2102 if( zIpAddr && zIpAddr[0] ){
2103 g.fSshClient |= CGI_SSH_CLIENT;
2104 }
2105 }
2106 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2107 if( useSCGI ){
2108 cgi_handle_scgi_request();
2109 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2110 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2111 }else{
@@ -2171,18 +2219,21 @@
2219 ** and the connection is from localhost. The optional REPOSITORY argument
2220 ** to "ui" may be a directory and will function as "server" if and only if
2221 ** the --notfound option is used.
2222 **
2223 ** Options:
2224 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2225 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2226 ** --localauth enable automatic login for requests from localhost
2227 ** --localhost listen on 127.0.0.1 only (always true for "ui")
2228 ** --nojail Drop root privileges but do not enter the chroot jail
2229 ** --notfound URL Redirect
2230 ** -P|--port TCPPORT listen to request on port TCPPORT
2231 ** --th-trace trace TH1 execution (for debugging purposes)
 
 
 
2232 ** --scgi Accept SCGI rather than HTTP
2233 ** --skin LABEL Use override skin LABEL
2234
2235 **
2236 ** See also: cgi, http, winsrv
2237 */
2238 void cmd_webserver(void){
2239 int iPort, mxPort; /* Range of TCP ports allowed */
@@ -2190,10 +2241,13 @@
2241 const char *zBrowser; /* Name of web browser program */
2242 char *zBrowserCmd = 0; /* Command to launch the web browser */
2243 int isUiCmd; /* True if command is "ui", not "server' */
2244 const char *zNotFound; /* The --notfound option or NULL */
2245 int flags = 0; /* Server flags */
2246 #if !defined(_WIN32)
2247 int noJail; /* Do not enter the chroot jail */
2248 #endif
2249 const char *zAltBase; /* Argument to the --baseurl option */
2250 const char *zFileGlob; /* Static content must match this */
2251 char *zIpAddr = 0; /* Bind to this IP address */
2252
2253 #if defined(_WIN32)
@@ -2207,10 +2261,14 @@
2261 dehttpize(z);
2262 zFileGlob = z;
2263 }else{
2264 zFileGlob = find_option("files",0,1);
2265 }
2266 skin_override();
2267 #if !defined(_WIN32)
2268 noJail = find_option("nojail",0,0)!=0;
2269 #endif
2270 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2271 Th_InitTraceLog();
2272 zPort = find_option("port", "P", 1);
2273 zNotFound = find_option("notfound", 0, 1);
2274 zAltBase = find_option("baseurl", 0, 1);
@@ -2284,11 +2342,11 @@
2342 if( g.fHttpTrace || g.fSqlTrace ){
2343 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2344 }
2345 g.cgiOutput = 1;
2346 find_server_repository(isUiCmd && zNotFound==0, 2);
2347 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2348 if( flags & HTTP_SERVER_SCGI ){
2349 cgi_handle_scgi_request();
2350 }else{
2351 cgi_handle_http_request(0);
2352 }
2353
+1 -1
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491491
$(OBJDIR)/cson_amalgamation.o
492492
493493
494494
$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495495
$(OBJDIR)/codecheck1 $(TRANS_SRC)
496
- $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
496
+ $(TCC) $(CFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497497
498498
# This rule prevents make from using its default rules to try build
499499
# an executable named "manifest" out of the file named "manifest.c"
500500
#
501501
$(SRCDIR)/../manifest:
502502
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491 $(OBJDIR)/cson_amalgamation.o
492
493
494 $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495 $(OBJDIR)/codecheck1 $(TRANS_SRC)
496 $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497
498 # This rule prevents make from using its default rules to try build
499 # an executable named "manifest" out of the file named "manifest.c"
500 #
501 $(SRCDIR)/../manifest:
502
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491 $(OBJDIR)/cson_amalgamation.o
492
493
494 $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495 $(OBJDIR)/codecheck1 $(TRANS_SRC)
496 $(TCC) $(CFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497
498 # This rule prevents make from using its default rules to try build
499 # an executable named "manifest" out of the file named "manifest.c"
500 #
501 $(SRCDIR)/../manifest:
502
+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
+5 -2
--- src/moderate.c
+++ src/moderate.c
@@ -144,20 +144,23 @@
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
" AND event.objid IN (SELECT objid FROM modreq)"
156159
" ORDER BY event.mtime DESC"
157160
);
158161
db_prepare(&q, "%s", blob_sql_text(&sql));
159
- www_print_timeline(&q, 0, 0, 0, 0);
162
+ www_print_timeline(&q, 0, 0, 0, 0, 0);
160163
db_finalize(&q);
161164
}
162165
style_footer();
163166
}
164167
--- src/moderate.c
+++ src/moderate.c
@@ -144,20 +144,23 @@
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 " AND event.objid IN (SELECT objid FROM modreq)"
156 " ORDER BY event.mtime DESC"
157 );
158 db_prepare(&q, "%s", blob_sql_text(&sql));
159 www_print_timeline(&q, 0, 0, 0, 0);
160 db_finalize(&q);
161 }
162 style_footer();
163 }
164
--- src/moderate.c
+++ src/moderate.c
@@ -144,20 +144,23 @@
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 " AND event.objid IN (SELECT objid FROM modreq)"
159 " ORDER BY event.mtime DESC"
160 );
161 db_prepare(&q, "%s", blob_sql_text(&sql));
162 www_print_timeline(&q, 0, 0, 0, 0, 0);
163 db_finalize(&q);
164 }
165 style_footer();
166 }
167
+85 -10
--- src/name.c
+++ src/name.c
@@ -104,10 +104,15 @@
104104
** If zType is NULL or "" or "*" then any type of artifact will serve.
105105
** If zType is "br" then find the first check-in of the named branch
106106
** rather than the last.
107107
** zType is "ci" in most use cases since we are usually searching for
108108
** a check-in.
109
+**
110
+** Note that the input zTag for types "t" and "e" is the SHA1 hash of
111
+** the ticket-change or event-change artifact, not the randomly generated
112
+** hexadecimal identifier assigned to tickets and events. Those identifiers
113
+** live in a separate namespace.
109114
*/
110115
int symbolic_name_to_rid(const char *zTag, const char *zType){
111116
int vid;
112117
int rid = 0;
113118
int nTag;
@@ -444,11 +449,11 @@
444449
canonical16(z, strlen(z));
445450
db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
446451
while( db_step(&q)==SQLITE_ROW ){
447452
const char *zUuid = db_column_text(&q, 0);
448453
int rid = db_column_int(&q, 1);
449
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
454
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
450455
@ %s(zUuid)</a> -
451456
object_description(rid, 0, 0);
452457
@ </p></li>
453458
}
454459
db_finalize(&q);
@@ -461,11 +466,11 @@
461466
" ORDER BY tkt_ctime DESC", z);
462467
while( db_step(&q)==SQLITE_ROW ){
463468
int rid = db_column_int(&q, 0);
464469
const char *zUuid = db_column_text(&q, 1);
465470
const char *zTitle = db_column_text(&q, 2);
466
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
471
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
467472
@ %s(zUuid)</a> -
468473
@ <ul></ul>
469474
@ Ticket
470475
hyperlink_to_uuid(zUuid);
471476
@ - %s(zTitle).
@@ -481,11 +486,11 @@
481486
" FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
482487
" AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
483488
while( db_step(&q)==SQLITE_ROW ){
484489
int rid = db_column_int(&q, 0);
485490
const char* zUuid = db_column_text(&q, 1);
486
- @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
491
+ @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
487492
@ %s(zUuid)</a> -
488493
@ <ul><li>
489494
object_description(rid, 0, 0);
490495
@ </li></ul>
491496
@ </p></li>
@@ -777,18 +782,18 @@
777782
}
778783
779784
/*
780785
** Schema for the description table
781786
*/
782
-static const char zDescTab[] =
787
+static const char zDescTab[] =
783788
@ CREATE TEMP TABLE IF NOT EXISTS description(
784
-@ rid INTEGER PRIMARY KEY, -- RID of the object
785
-@ uuid TEXT, -- SHA1 hash of the object
786
-@ ctime DATETIME, -- Time of creation
789
+@ rid INTEGER PRIMARY KEY, -- RID of the object
790
+@ uuid TEXT, -- SHA1 hash of the object
791
+@ ctime DATETIME, -- Time of creation
787792
@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
788793
@ type TEXT, -- file, checkin, wiki, ticket, etc.
789
-@ summary TEXT, -- Summary comment for the object
794
+@ summary TEXT, -- Summary comment for the object
790795
@ detail TEXT -- filename, checkin comment, etc
791796
@ );
792797
;
793798
794799
/*
@@ -982,11 +987,11 @@
982987
int n = atoi(PD("n","5000"));
983988
int mx = db_int(0, "SELECT max(rid) FROM blob");
984989
char *zRange;
985990
986991
login_check_credentials();
987
- if( !g.perm.Read ){ login_needed(); return; }
992
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
988993
style_header("List Of Artifacts");
989994
if( mx>n && P("s")==0 ){
990995
int i;
991996
@ <p>Select a range of artifacts to view:</p>
992997
@ <ul>
@@ -1011,11 +1016,11 @@
10111016
int rid = db_column_int(&q,0);
10121017
const char *zUuid = db_column_text(&q, 1);
10131018
const char *zDesc = db_column_text(&q, 2);
10141019
int isPriv = db_column_int(&q,2);
10151020
@ <tr><td align="right">%d(rid)</td>
1016
- @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1021
+ @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
10171022
@ <td align="left">%h(zDesc)</td>
10181023
if( isPriv ){
10191024
@ <td>(unpublished)</td>
10201025
}
10211026
@ </tr>
@@ -1058,5 +1063,75 @@
10581063
*/
10591064
void test_phatoms_cmd(void){
10601065
db_find_and_open_repository(0,0);
10611066
describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
10621067
}
1068
+
1069
+/* Maximum number of collision examples to remember */
1070
+#define MAX_COLLIDE 25
1071
+
1072
+/*
1073
+** WEBPAGE: hash-collisions
1074
+**
1075
+** Show the number of hash collisions for hash prefixes of various lengths.
1076
+*/
1077
+void hash_collisions_webpage(void){
1078
+ int i, j, kk;
1079
+ int nHash = 0;
1080
+ Stmt q;
1081
+ char zPrev[UUID_SIZE+1];
1082
+ struct {
1083
+ int cnt;
1084
+ char *azHit[MAX_COLLIDE];
1085
+ char z[UUID_SIZE+1];
1086
+ } aCollide[UUID_SIZE+1];
1087
+ login_check_credentials();
1088
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1089
+ memset(aCollide, 0, sizeof(aCollide));
1090
+ memset(zPrev, 0, sizeof(zPrev));
1091
+ db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1092
+ while( db_step(&q)==SQLITE_ROW ){
1093
+ const char *zUuid = db_column_text(&q,0);
1094
+ int n = db_column_bytes(&q,0);
1095
+ int i;
1096
+ nHash++;
1097
+ for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1098
+ if( i>0 && i<=UUID_SIZE ){
1099
+ if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1100
+ aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1101
+ }
1102
+ aCollide[i].cnt++;
1103
+ if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1104
+ }
1105
+ memcpy(zPrev, zUuid, n+1);
1106
+ }
1107
+ db_finalize(&q);
1108
+ style_header("SHA1 Prefix Collisions");
1109
+ style_submenu_element("Activity Reports", 0, "reports");
1110
+ style_submenu_element("Stats", 0, "stat");
1111
+ @ <table border=1><thead>
1112
+ @ <tr><th>Length<th>Instances<th>First Instance</tr>
1113
+ @ </thead><tbody>
1114
+ for(i=1; i<=UUID_SIZE; i++){
1115
+ if( aCollide[i].cnt==0 ) continue;
1116
+ @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1117
+ }
1118
+ @ </tbody></table>
1119
+ @ <p>Total number of hashes: %d(nHash)</p>
1120
+ kk = 0;
1121
+ for(i=UUID_SIZE; i>=4; i--){
1122
+ if( aCollide[i].cnt==0 ) continue;
1123
+ if( aCollide[i].cnt>200 ) break;
1124
+ kk += aCollide[i].cnt;
1125
+ if( aCollide[i].cnt<25 ){
1126
+ @ <p>Collisions of length %d(i):
1127
+ }else{
1128
+ @ <p>First 25 collisions of length %d(i):
1129
+ }
1130
+ for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1131
+ char *zId = aCollide[i].azHit[j];
1132
+ if( zId==0 ) continue;
1133
+ @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1134
+ }
1135
+ }
1136
+ style_footer();
1137
+}
10631138
--- src/name.c
+++ src/name.c
@@ -104,10 +104,15 @@
104 ** If zType is NULL or "" or "*" then any type of artifact will serve.
105 ** If zType is "br" then find the first check-in of the named branch
106 ** rather than the last.
107 ** zType is "ci" in most use cases since we are usually searching for
108 ** a check-in.
 
 
 
 
 
109 */
110 int symbolic_name_to_rid(const char *zTag, const char *zType){
111 int vid;
112 int rid = 0;
113 int nTag;
@@ -444,11 +449,11 @@
444 canonical16(z, strlen(z));
445 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
446 while( db_step(&q)==SQLITE_ROW ){
447 const char *zUuid = db_column_text(&q, 0);
448 int rid = db_column_int(&q, 1);
449 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
450 @ %s(zUuid)</a> -
451 object_description(rid, 0, 0);
452 @ </p></li>
453 }
454 db_finalize(&q);
@@ -461,11 +466,11 @@
461 " ORDER BY tkt_ctime DESC", z);
462 while( db_step(&q)==SQLITE_ROW ){
463 int rid = db_column_int(&q, 0);
464 const char *zUuid = db_column_text(&q, 1);
465 const char *zTitle = db_column_text(&q, 2);
466 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
467 @ %s(zUuid)</a> -
468 @ <ul></ul>
469 @ Ticket
470 hyperlink_to_uuid(zUuid);
471 @ - %s(zTitle).
@@ -481,11 +486,11 @@
481 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
482 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
483 while( db_step(&q)==SQLITE_ROW ){
484 int rid = db_column_int(&q, 0);
485 const char* zUuid = db_column_text(&q, 1);
486 @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
487 @ %s(zUuid)</a> -
488 @ <ul><li>
489 object_description(rid, 0, 0);
490 @ </li></ul>
491 @ </p></li>
@@ -777,18 +782,18 @@
777 }
778
779 /*
780 ** Schema for the description table
781 */
782 static const char zDescTab[] =
783 @ CREATE TEMP TABLE IF NOT EXISTS description(
784 @ rid INTEGER PRIMARY KEY, -- RID of the object
785 @ uuid TEXT, -- SHA1 hash of the object
786 @ ctime DATETIME, -- Time of creation
787 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
788 @ type TEXT, -- file, checkin, wiki, ticket, etc.
789 @ summary TEXT, -- Summary comment for the object
790 @ detail TEXT -- filename, checkin comment, etc
791 @ );
792 ;
793
794 /*
@@ -982,11 +987,11 @@
982 int n = atoi(PD("n","5000"));
983 int mx = db_int(0, "SELECT max(rid) FROM blob");
984 char *zRange;
985
986 login_check_credentials();
987 if( !g.perm.Read ){ login_needed(); return; }
988 style_header("List Of Artifacts");
989 if( mx>n && P("s")==0 ){
990 int i;
991 @ <p>Select a range of artifacts to view:</p>
992 @ <ul>
@@ -1011,11 +1016,11 @@
1011 int rid = db_column_int(&q,0);
1012 const char *zUuid = db_column_text(&q, 1);
1013 const char *zDesc = db_column_text(&q, 2);
1014 int isPriv = db_column_int(&q,2);
1015 @ <tr><td align="right">%d(rid)</td>
1016 @ <td>&nbsp;%z(href("%R/info/%s",zUuid))%s(zUuid)</a>&nbsp;</td>
1017 @ <td align="left">%h(zDesc)</td>
1018 if( isPriv ){
1019 @ <td>(unpublished)</td>
1020 }
1021 @ </tr>
@@ -1058,5 +1063,75 @@
1058 */
1059 void test_phatoms_cmd(void){
1060 db_find_and_open_repository(0,0);
1061 describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
1062 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
--- src/name.c
+++ src/name.c
@@ -104,10 +104,15 @@
104 ** If zType is NULL or "" or "*" then any type of artifact will serve.
105 ** If zType is "br" then find the first check-in of the named branch
106 ** rather than the last.
107 ** zType is "ci" in most use cases since we are usually searching for
108 ** a check-in.
109 **
110 ** Note that the input zTag for types "t" and "e" is the SHA1 hash of
111 ** the ticket-change or event-change artifact, not the randomly generated
112 ** hexadecimal identifier assigned to tickets and events. Those identifiers
113 ** live in a separate namespace.
114 */
115 int symbolic_name_to_rid(const char *zTag, const char *zType){
116 int vid;
117 int rid = 0;
118 int nTag;
@@ -444,11 +449,11 @@
449 canonical16(z, strlen(z));
450 db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
451 while( db_step(&q)==SQLITE_ROW ){
452 const char *zUuid = db_column_text(&q, 0);
453 int rid = db_column_int(&q, 1);
454 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
455 @ %s(zUuid)</a> -
456 object_description(rid, 0, 0);
457 @ </p></li>
458 }
459 db_finalize(&q);
@@ -461,11 +466,11 @@
466 " ORDER BY tkt_ctime DESC", z);
467 while( db_step(&q)==SQLITE_ROW ){
468 int rid = db_column_int(&q, 0);
469 const char *zUuid = db_column_text(&q, 1);
470 const char *zTitle = db_column_text(&q, 2);
471 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
472 @ %s(zUuid)</a> -
473 @ <ul></ul>
474 @ Ticket
475 hyperlink_to_uuid(zUuid);
476 @ - %s(zTitle).
@@ -481,11 +486,11 @@
486 " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
487 " AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
488 while( db_step(&q)==SQLITE_ROW ){
489 int rid = db_column_int(&q, 0);
490 const char* zUuid = db_column_text(&q, 1);
491 @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
492 @ %s(zUuid)</a> -
493 @ <ul><li>
494 object_description(rid, 0, 0);
495 @ </li></ul>
496 @ </p></li>
@@ -777,18 +782,18 @@
782 }
783
784 /*
785 ** Schema for the description table
786 */
787 static const char zDescTab[] =
788 @ CREATE TEMP TABLE IF NOT EXISTS description(
789 @ rid INTEGER PRIMARY KEY, -- RID of the object
790 @ uuid TEXT, -- SHA1 hash of the object
791 @ ctime DATETIME, -- Time of creation
792 @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
793 @ type TEXT, -- file, checkin, wiki, ticket, etc.
794 @ summary TEXT, -- Summary comment for the object
795 @ detail TEXT -- filename, checkin comment, etc
796 @ );
797 ;
798
799 /*
@@ -982,11 +987,11 @@
987 int n = atoi(PD("n","5000"));
988 int mx = db_int(0, "SELECT max(rid) FROM blob");
989 char *zRange;
990
991 login_check_credentials();
992 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
993 style_header("List Of Artifacts");
994 if( mx>n && P("s")==0 ){
995 int i;
996 @ <p>Select a range of artifacts to view:</p>
997 @ <ul>
@@ -1011,11 +1016,11 @@
1016 int rid = db_column_int(&q,0);
1017 const char *zUuid = db_column_text(&q, 1);
1018 const char *zDesc = db_column_text(&q, 2);
1019 int isPriv = db_column_int(&q,2);
1020 @ <tr><td align="right">%d(rid)</td>
1021 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
1022 @ <td align="left">%h(zDesc)</td>
1023 if( isPriv ){
1024 @ <td>(unpublished)</td>
1025 }
1026 @ </tr>
@@ -1058,5 +1063,75 @@
1063 */
1064 void test_phatoms_cmd(void){
1065 db_find_and_open_repository(0,0);
1066 describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
1067 }
1068
1069 /* Maximum number of collision examples to remember */
1070 #define MAX_COLLIDE 25
1071
1072 /*
1073 ** WEBPAGE: hash-collisions
1074 **
1075 ** Show the number of hash collisions for hash prefixes of various lengths.
1076 */
1077 void hash_collisions_webpage(void){
1078 int i, j, kk;
1079 int nHash = 0;
1080 Stmt q;
1081 char zPrev[UUID_SIZE+1];
1082 struct {
1083 int cnt;
1084 char *azHit[MAX_COLLIDE];
1085 char z[UUID_SIZE+1];
1086 } aCollide[UUID_SIZE+1];
1087 login_check_credentials();
1088 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1089 memset(aCollide, 0, sizeof(aCollide));
1090 memset(zPrev, 0, sizeof(zPrev));
1091 db_prepare(&q,"SELECT uuid FROM blob ORDER BY 1");
1092 while( db_step(&q)==SQLITE_ROW ){
1093 const char *zUuid = db_column_text(&q,0);
1094 int n = db_column_bytes(&q,0);
1095 int i;
1096 nHash++;
1097 for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1098 if( i>0 && i<=UUID_SIZE ){
1099 if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1100 aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1101 }
1102 aCollide[i].cnt++;
1103 if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1104 }
1105 memcpy(zPrev, zUuid, n+1);
1106 }
1107 db_finalize(&q);
1108 style_header("SHA1 Prefix Collisions");
1109 style_submenu_element("Activity Reports", 0, "reports");
1110 style_submenu_element("Stats", 0, "stat");
1111 @ <table border=1><thead>
1112 @ <tr><th>Length<th>Instances<th>First Instance</tr>
1113 @ </thead><tbody>
1114 for(i=1; i<=UUID_SIZE; i++){
1115 if( aCollide[i].cnt==0 ) continue;
1116 @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1117 }
1118 @ </tbody></table>
1119 @ <p>Total number of hashes: %d(nHash)</p>
1120 kk = 0;
1121 for(i=UUID_SIZE; i>=4; i--){
1122 if( aCollide[i].cnt==0 ) continue;
1123 if( aCollide[i].cnt>200 ) break;
1124 kk += aCollide[i].cnt;
1125 if( aCollide[i].cnt<25 ){
1126 @ <p>Collisions of length %d(i):
1127 }else{
1128 @ <p>First 25 collisions of length %d(i):
1129 }
1130 for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1131 char *zId = aCollide[i].azHit[j];
1132 if( zId==0 ) continue;
1133 @ %z(href("%R/whatis/%s",zId))%h(zId)</a>
1134 }
1135 }
1136 style_footer();
1137 }
1138
+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
+17 -7
--- src/rebuild.c
+++ src/rebuild.c
@@ -522,23 +522,24 @@
522522
** Reconstruct the named repository database from the core
523523
** records. Run this command after updating the fossil
524524
** executable in a way that changes the database schema.
525525
**
526526
** Options:
527
+** --analyze Run ANALYZE on the database after rebuilding
527528
** --cluster Compute clusters for unclustered artifacts
528529
** --compress Strive to make the database as small as possible
530
+** --deanalyze Remove ANALYZE tables from the database
529531
** --force Force the rebuild to complete even if errors are seen
532
+** --ifneeded Only do the rebuild if it would change the schema version
533
+** --index Always add in the full-text search index
530534
** --noverify Skip the verification of changes to the BLOB table
535
+** --noindex Always omit the full-text search index
531536
** --pagesize N Set the database pagesize to N. (512..65536 and power of 2)
532537
** --randomize Scan artifacts in a random order
538
+** --stats Show artifact statistics after rebuilding
533539
** --vacuum Run VACUUM on the database after rebuilding
534
-** --deanalyze Remove ANALYZE tables from the database
535
-** --analyze Run ANALYZE on the database after rebuilding
536540
** --wal Set Write-Ahead-Log journalling mode on the database
537
-** --stats Show artifact statistics after rebuilding
538
-** --index Always add in the full-text search index
539
-** --no-index Always omit the full-text search index
540541
**
541542
** See also: deconstruct, reconstruct
542543
*/
543544
void rebuild_database(void){
544545
int forceFlag;
@@ -553,10 +554,13 @@
553554
int runDeanalyze;
554555
int runAnalyze;
555556
int runCompress;
556557
int showStats;
557558
int runReindex;
559
+ int optNoIndex;
560
+ int optIndex;
561
+ int optIfNeeded;
558562
559563
omitVerify = find_option("noverify",0,0)!=0;
560564
forceFlag = find_option("force","f",0)!=0;
561565
randomizeFlag = find_option("randomize", 0, 0)!=0;
562566
doClustering = find_option("cluster", 0, 0)!=0;
@@ -564,10 +568,13 @@
564568
runDeanalyze = find_option("deanalyze",0,0)!=0;
565569
runAnalyze = find_option("analyze",0,0)!=0;
566570
runCompress = find_option("compress",0,0)!=0;
567571
zPagesize = find_option("pagesize",0,1);
568572
showStats = find_option("stats",0,0)!=0;
573
+ optIndex = find_option("index",0,0)!=0;
574
+ optNoIndex = find_option("noindex",0,0)!=0;
575
+ optIfNeeded = find_option("ifneeded",0,0)!=0;
569576
if( zPagesize ){
570577
newPagesize = atoi(zPagesize);
571578
if( newPagesize<512 || newPagesize>65536
572579
|| (newPagesize&(newPagesize-1))!=0
573580
){
@@ -584,12 +591,15 @@
584591
}
585592
db_close(1);
586593
db_open_repository(g.zRepositoryName);
587594
}
588595
runReindex = search_index_exists();
589
- if( find_option("index",0,0)!=0 ) runReindex = 1;
590
- if( find_option("no-index",0,0)!=0 ) runReindex = 0;
596
+ if( optIndex ) runReindex = 1;
597
+ if( optNoIndex ) runReindex = 0;
598
+ if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){
599
+ return;
600
+ }
591601
592602
/* We should be done with options.. */
593603
verify_all_options();
594604
595605
db_begin_transaction();
596606
--- src/rebuild.c
+++ src/rebuild.c
@@ -522,23 +522,24 @@
522 ** Reconstruct the named repository database from the core
523 ** records. Run this command after updating the fossil
524 ** executable in a way that changes the database schema.
525 **
526 ** Options:
 
527 ** --cluster Compute clusters for unclustered artifacts
528 ** --compress Strive to make the database as small as possible
 
529 ** --force Force the rebuild to complete even if errors are seen
 
 
530 ** --noverify Skip the verification of changes to the BLOB table
 
531 ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2)
532 ** --randomize Scan artifacts in a random order
 
533 ** --vacuum Run VACUUM on the database after rebuilding
534 ** --deanalyze Remove ANALYZE tables from the database
535 ** --analyze Run ANALYZE on the database after rebuilding
536 ** --wal Set Write-Ahead-Log journalling mode on the database
537 ** --stats Show artifact statistics after rebuilding
538 ** --index Always add in the full-text search index
539 ** --no-index Always omit the full-text search index
540 **
541 ** See also: deconstruct, reconstruct
542 */
543 void rebuild_database(void){
544 int forceFlag;
@@ -553,10 +554,13 @@
553 int runDeanalyze;
554 int runAnalyze;
555 int runCompress;
556 int showStats;
557 int runReindex;
 
 
 
558
559 omitVerify = find_option("noverify",0,0)!=0;
560 forceFlag = find_option("force","f",0)!=0;
561 randomizeFlag = find_option("randomize", 0, 0)!=0;
562 doClustering = find_option("cluster", 0, 0)!=0;
@@ -564,10 +568,13 @@
564 runDeanalyze = find_option("deanalyze",0,0)!=0;
565 runAnalyze = find_option("analyze",0,0)!=0;
566 runCompress = find_option("compress",0,0)!=0;
567 zPagesize = find_option("pagesize",0,1);
568 showStats = find_option("stats",0,0)!=0;
 
 
 
569 if( zPagesize ){
570 newPagesize = atoi(zPagesize);
571 if( newPagesize<512 || newPagesize>65536
572 || (newPagesize&(newPagesize-1))!=0
573 ){
@@ -584,12 +591,15 @@
584 }
585 db_close(1);
586 db_open_repository(g.zRepositoryName);
587 }
588 runReindex = search_index_exists();
589 if( find_option("index",0,0)!=0 ) runReindex = 1;
590 if( find_option("no-index",0,0)!=0 ) runReindex = 0;
 
 
 
591
592 /* We should be done with options.. */
593 verify_all_options();
594
595 db_begin_transaction();
596
--- src/rebuild.c
+++ src/rebuild.c
@@ -522,23 +522,24 @@
522 ** Reconstruct the named repository database from the core
523 ** records. Run this command after updating the fossil
524 ** executable in a way that changes the database schema.
525 **
526 ** Options:
527 ** --analyze Run ANALYZE on the database after rebuilding
528 ** --cluster Compute clusters for unclustered artifacts
529 ** --compress Strive to make the database as small as possible
530 ** --deanalyze Remove ANALYZE tables from the database
531 ** --force Force the rebuild to complete even if errors are seen
532 ** --ifneeded Only do the rebuild if it would change the schema version
533 ** --index Always add in the full-text search index
534 ** --noverify Skip the verification of changes to the BLOB table
535 ** --noindex Always omit the full-text search index
536 ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2)
537 ** --randomize Scan artifacts in a random order
538 ** --stats Show artifact statistics after rebuilding
539 ** --vacuum Run VACUUM on the database after rebuilding
 
 
540 ** --wal Set Write-Ahead-Log journalling mode on the database
 
 
 
541 **
542 ** See also: deconstruct, reconstruct
543 */
544 void rebuild_database(void){
545 int forceFlag;
@@ -553,10 +554,13 @@
554 int runDeanalyze;
555 int runAnalyze;
556 int runCompress;
557 int showStats;
558 int runReindex;
559 int optNoIndex;
560 int optIndex;
561 int optIfNeeded;
562
563 omitVerify = find_option("noverify",0,0)!=0;
564 forceFlag = find_option("force","f",0)!=0;
565 randomizeFlag = find_option("randomize", 0, 0)!=0;
566 doClustering = find_option("cluster", 0, 0)!=0;
@@ -564,10 +568,13 @@
568 runDeanalyze = find_option("deanalyze",0,0)!=0;
569 runAnalyze = find_option("analyze",0,0)!=0;
570 runCompress = find_option("compress",0,0)!=0;
571 zPagesize = find_option("pagesize",0,1);
572 showStats = find_option("stats",0,0)!=0;
573 optIndex = find_option("index",0,0)!=0;
574 optNoIndex = find_option("noindex",0,0)!=0;
575 optIfNeeded = find_option("ifneeded",0,0)!=0;
576 if( zPagesize ){
577 newPagesize = atoi(zPagesize);
578 if( newPagesize<512 || newPagesize>65536
579 || (newPagesize&(newPagesize-1))!=0
580 ){
@@ -584,12 +591,15 @@
591 }
592 db_close(1);
593 db_open_repository(g.zRepositoryName);
594 }
595 runReindex = search_index_exists();
596 if( optIndex ) runReindex = 1;
597 if( optNoIndex ) runReindex = 0;
598 if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){
599 return;
600 }
601
602 /* We should be done with options.. */
603 verify_all_options();
604
605 db_begin_transaction();
606
+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
+59 -26
--- src/setup.c
+++ src/setup.c
@@ -50,18 +50,20 @@
5050
}else{
5151
@ %h(zTitle)
5252
}
5353
@ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr>
5454
}
55
+
56
+
5557
5658
/*
5759
** WEBPAGE: /setup
5860
*/
5961
void setup_page(void){
6062
login_check_credentials();
6163
if( !g.perm.Setup ){
62
- login_needed();
64
+ login_needed(0);
6365
}
6466
6567
style_header("Server Administration");
6668
6769
/* Make sure the header contains <base href="...">. Issue a warning
@@ -69,10 +71,24 @@
6971
if( !cgi_header_contains("<base href=") ){
7072
@ <p class="generalError"><b>Configuration Error:</b> Please add
7173
@ <tt>&lt;base href="$secureurl/$current_page"&gt;</tt> after
7274
@ <tt>&lt;head&gt;</tt> in the <a href="setup_header">HTML header</a>!</p>
7375
}
76
+
77
+#if !defined(_WIN32)
78
+ /* Check for /dev/null and /dev/urandom. We want both devices to be present,
79
+ ** but they are sometimes omitted (by mistake) from chroot jails. */
80
+ if( access("/dev/null", R_OK|W_OK) ){
81
+ @ <p class="generalError">WARNING: Device "/dev/null" is not available
82
+ @ for reading and writing.</p>
83
+ }
84
+ if( access("/dev/urandom", R_OK) ){
85
+ @ <p class="generalError">WARNING: Device "/dev/urandom" is not available
86
+ @ for reading. This means that the pseudo-random number generator used
87
+ @ by SQLite will be poorly seeded.</p>
88
+ }
89
+#endif
7490
7591
@ <table border="0" cellspacing="3">
7692
setup_menu_entry("Users", "setup_ulist",
7793
"Grant privileges to individual users.");
7894
setup_menu_entry("Access", "setup_access",
@@ -136,11 +152,11 @@
136152
Stmt s;
137153
int prevLevel = 0;
138154
139155
login_check_credentials();
140156
if( !g.perm.Admin ){
141
- login_needed();
157
+ login_needed(0);
142158
return;
143159
}
144160
145161
style_submenu_element("Add", "Add User", "setup_uedit");
146162
style_header("User List");
@@ -320,11 +336,11 @@
320336
const char *oa[128];
321337
322338
/* Must have ADMIN privileges to access this page
323339
*/
324340
login_check_credentials();
325
- if( !g.perm.Admin ){ login_needed(); return; }
341
+ if( !g.perm.Admin ){ login_needed(0); return; }
326342
327343
/* Check to see if an ADMIN user is trying to edit a SETUP account.
328344
** Don't allow that.
329345
*/
330346
zId = PD("id", "0");
@@ -982,11 +998,12 @@
982998
** WEBPAGE: setup_access
983999
*/
9841000
void setup_access(void){
9851001
login_check_credentials();
9861002
if( !g.perm.Setup ){
987
- login_needed();
1003
+ login_needed(0);
1004
+ return;
9881005
}
9891006
9901007
style_header("Access Control Settings");
9911008
db_begin_transaction();
9921009
@ <form action="%s(g.zTop)/setup_access" method="post"><div>
@@ -1002,25 +1019,25 @@
10021019
@ <hr />
10031020
onoff_attribute("Require password for local access",
10041021
"localauth", "localauth", 0, 0);
10051022
@ <p>When enabled, the password sign-in is always required for
10061023
@ web access. When disabled, unrestricted web access from 127.0.0.1
1007
- @ is allowed for the <a href="%s(g.zTop)/help/ui">fossil ui</a> command or
1008
- @ from the <a href="%s(g.zTop)/help/server">fossil server</a>,
1009
- @ <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
10101027
@ "--localauth" command line options is used, or from the
1011
- @ <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
10121029
@ the word "localauth" appears in the CGI script.
10131030
@
10141031
@ <p>A password is always required if any one or more
10151032
@ of the following are true:
10161033
@ <ol>
10171034
@ <li> This button is checked
10181035
@ <li> The inbound TCP/IP connection is not from 127.0.0.1
10191036
@ <li> The server is started using either of the
1020
- @ <a href="%s(g.zTop)/help/server">fossil server</a> or
1021
- @ <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
10221039
@ without the "--localauth" option.
10231040
@ <li> The server is started from CGI without the "localauth" keyword
10241041
@ in the CGI script.
10251042
@ </ol>
10261043
@
@@ -1187,11 +1204,12 @@
11871204
const char *zPw = PD("pw", "");
11881205
const char *zNewName = PD("newname", "New Login Group");
11891206
11901207
login_check_credentials();
11911208
if( !g.perm.Setup ){
1192
- login_needed();
1209
+ login_needed(0);
1210
+ return;
11931211
}
11941212
file_canonical_name(g.zRepositoryName, &fullName, 0);
11951213
zSelfRepo = fossil_strdup(blob_str(&fullName));
11961214
blob_reset(&fullName);
11971215
if( P("join")!=0 ){
@@ -1299,11 +1317,12 @@
12991317
"3", "YYMMDD HH:MM",
13001318
"4", "(off)"
13011319
};
13021320
login_check_credentials();
13031321
if( !g.perm.Setup ){
1304
- login_needed();
1322
+ login_needed(0);
1323
+ return;
13051324
}
13061325
13071326
style_header("Timeline Display Preferences");
13081327
db_begin_transaction();
13091328
@ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1377,11 +1396,12 @@
13771396
void setup_settings(void){
13781397
Setting const *pSet;
13791398
13801399
login_check_credentials();
13811400
if( !g.perm.Setup ){
1382
- login_needed();
1401
+ login_needed(0);
1402
+ return;
13831403
}
13841404
13851405
(void) aCmdHelp; /* NOTE: Silence compiler warning. */
13861406
style_header("Settings");
13871407
if(!g.repositoryOpen){
@@ -1457,11 +1477,12 @@
14571477
** WEBPAGE: setup_config
14581478
*/
14591479
void setup_config(void){
14601480
login_check_credentials();
14611481
if( !g.perm.Setup ){
1462
- login_needed();
1482
+ login_needed(0);
1483
+ return;
14631484
}
14641485
14651486
style_header("WWW Configuration");
14661487
db_begin_transaction();
14671488
@ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1535,11 +1556,12 @@
15351556
** WEBPAGE: setup_editcss
15361557
*/
15371558
void setup_editcss(void){
15381559
login_check_credentials();
15391560
if( !g.perm.Setup ){
1540
- login_needed();
1561
+ login_needed(0);
1562
+ return;
15411563
}
15421564
db_begin_transaction();
15431565
if( P("clear")!=0 ){
15441566
db_multi_exec("DELETE FROM config WHERE name='css'");
15451567
cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1580,11 +1602,12 @@
15801602
** WEBPAGE: setup_header
15811603
*/
15821604
void setup_header(void){
15831605
login_check_credentials();
15841606
if( !g.perm.Setup ){
1585
- login_needed();
1607
+ login_needed(0);
1608
+ return;
15861609
}
15871610
db_begin_transaction();
15881611
if( P("clear")!=0 ){
15891612
db_multi_exec("DELETE FROM config WHERE name='header'");
15901613
cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1644,11 +1667,12 @@
16441667
** WEBPAGE: setup_footer
16451668
*/
16461669
void setup_footer(void){
16471670
login_check_credentials();
16481671
if( !g.perm.Setup ){
1649
- login_needed();
1672
+ login_needed(0);
1673
+ return;
16501674
}
16511675
db_begin_transaction();
16521676
if( P("clear")!=0 ){
16531677
db_multi_exec("DELETE FROM config WHERE name='footer'");
16541678
cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1681,11 +1705,12 @@
16811705
** WEBPAGE: setup_modreq
16821706
*/
16831707
void setup_modreq(void){
16841708
login_check_credentials();
16851709
if( !g.perm.Setup ){
1686
- login_needed();
1710
+ login_needed(0);
1711
+ return;
16871712
}
16881713
16891714
style_header("Moderator For Wiki And Tickets");
16901715
db_begin_transaction();
16911716
@ <form action="%R/setup_modreq" method="post"><div>
@@ -1692,11 +1717,11 @@
16921717
login_insert_csrf_secret();
16931718
@ <hr />
16941719
onoff_attribute("Moderate ticket changes",
16951720
"modreq-tkt", "modreq-tkt", 0, 0);
16961721
@ <p>When enabled, any change to tickets is subject to the approval
1697
- @ 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.
16981723
@ Ticket changes enter the system and are shown locally, but are not
16991724
@ synced until they are approved. The moderator has the option to
17001725
@ delete the change rather than approve it. Ticket changes made by
17011726
@ a user who has the Mod-Tkt privilege are never subject to
17021727
@ moderation.
@@ -1703,11 +1728,11 @@
17031728
@
17041729
@ <hr />
17051730
onoff_attribute("Moderate wiki changes",
17061731
"modreq-wiki", "modreq-wiki", 0, 0);
17071732
@ <p>When enabled, any change to wiki is subject to the approval
1708
- @ 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.
17091734
@ Wiki changes enter the system and are shown locally, but are not
17101735
@ synced until they are approved. The moderator has the option to
17111736
@ delete the change rather than approve it. Wiki changes made by
17121737
@ a user who has the Mod-Wiki privilege are never subject to
17131738
@ moderation.
@@ -1725,11 +1750,12 @@
17251750
** WEBPAGE: setup_adunit
17261751
*/
17271752
void setup_adunit(void){
17281753
login_check_credentials();
17291754
if( !g.perm.Setup ){
1730
- login_needed();
1755
+ login_needed(0);
1756
+ return;
17311757
}
17321758
db_begin_transaction();
17331759
if( P("clear")!=0 ){
17341760
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
17351761
cgi_replace_parameter("adunit","");
@@ -1806,11 +1832,12 @@
18061832
if( szBgImg>0 ){
18071833
zBgMime = PD("bgim:mimetype","image/gif");
18081834
}
18091835
login_check_credentials();
18101836
if( !g.perm.Setup ){
1811
- login_needed();
1837
+ login_needed(0);
1838
+ return;
18121839
}
18131840
db_begin_transaction();
18141841
if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
18151842
Blob img;
18161843
Stmt ins;
@@ -1945,11 +1972,12 @@
19451972
void sql_page(void){
19461973
const char *zQ = P("q");
19471974
int go = P("go")!=0;
19481975
login_check_credentials();
19491976
if( !g.perm.Setup ){
1950
- login_needed();
1977
+ login_needed(0);
1978
+ return;
19511979
}
19521980
db_begin_transaction();
19531981
style_header("Raw SQL Commands");
19541982
@ <p><b>Caution:</b> There are no restrictions on the SQL that can be
19551983
@ run by this page. You can do serious and irrepairable damage to the
@@ -2066,11 +2094,12 @@
20662094
void th1_page(void){
20672095
const char *zQ = P("q");
20682096
int go = P("go")!=0;
20692097
login_check_credentials();
20702098
if( !g.perm.Setup ){
2071
- login_needed();
2099
+ login_needed(0);
2100
+ return;
20722101
}
20732102
db_begin_transaction();
20742103
style_header("Raw TH1 Commands");
20752104
@ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
20762105
@ run by this page. If Tcl integration was enabled at compile-time and
@@ -2126,11 +2155,12 @@
21262155
int limit;
21272156
int fLogEnabled;
21282157
int counter = 0;
21292158
login_check_credentials();
21302159
if( !g.perm.Setup && !g.perm.Admin ){
2131
- login_needed();
2160
+ login_needed(0);
2161
+ return;
21322162
}
21332163
style_header("Admin Log");
21342164
create_admin_log_table();
21352165
limit = atoi(PD("n","20"));
21362166
fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2183,11 +2213,12 @@
21832213
** Configure the search engine.
21842214
*/
21852215
void page_srchsetup(){
21862216
login_check_credentials();
21872217
if( !g.perm.Setup && !g.perm.Admin ){
2188
- login_needed();
2218
+ login_needed(0);
2219
+ return;
21892220
}
21902221
style_header("Search Configuration");
21912222
@ <form action="%s(g.zTop)/srchsetup" method="post"><div>
21922223
login_insert_csrf_secret();
21932224
@ <div style="text-align:center;font-weight:bold;">
@@ -2235,16 +2266,18 @@
22352266
search_update_index(search_restrict(SRCH_ALL));
22362267
}
22372268
if( search_index_exists() ){
22382269
@ <p>Currently using an SQLite FTS4 search index. This makes search
22392270
@ run faster, especially on large repositories, but takes up space.</p>
2271
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22402272
@ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
22412273
@ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
22422274
}else{
22432275
@ <p>The SQLite FTS4 search index is disabled. All searching will be
22442276
@ a full-text scan. This usually works fine, but can be slow for
22452277
@ larger repositories.</p>
2278
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22462279
@ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
22472280
}
22482281
@ </div></form>
22492282
style_footer();
22502283
}
22512284
--- src/setup.c
+++ src/setup.c
@@ -50,18 +50,20 @@
50 }else{
51 @ %h(zTitle)
52 }
53 @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr>
54 }
 
 
55
56 /*
57 ** WEBPAGE: /setup
58 */
59 void setup_page(void){
60 login_check_credentials();
61 if( !g.perm.Setup ){
62 login_needed();
63 }
64
65 style_header("Server Administration");
66
67 /* Make sure the header contains <base href="...">. Issue a warning
@@ -69,10 +71,24 @@
69 if( !cgi_header_contains("<base href=") ){
70 @ <p class="generalError"><b>Configuration Error:</b> Please add
71 @ <tt>&lt;base href="$secureurl/$current_page"&gt;</tt> after
72 @ <tt>&lt;head&gt;</tt> in the <a href="setup_header">HTML header</a>!</p>
73 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
75 @ <table border="0" cellspacing="3">
76 setup_menu_entry("Users", "setup_ulist",
77 "Grant privileges to individual users.");
78 setup_menu_entry("Access", "setup_access",
@@ -136,11 +152,11 @@
136 Stmt s;
137 int prevLevel = 0;
138
139 login_check_credentials();
140 if( !g.perm.Admin ){
141 login_needed();
142 return;
143 }
144
145 style_submenu_element("Add", "Add User", "setup_uedit");
146 style_header("User List");
@@ -320,11 +336,11 @@
320 const char *oa[128];
321
322 /* Must have ADMIN privileges to access this page
323 */
324 login_check_credentials();
325 if( !g.perm.Admin ){ login_needed(); return; }
326
327 /* Check to see if an ADMIN user is trying to edit a SETUP account.
328 ** Don't allow that.
329 */
330 zId = PD("id", "0");
@@ -982,11 +998,12 @@
982 ** WEBPAGE: setup_access
983 */
984 void setup_access(void){
985 login_check_credentials();
986 if( !g.perm.Setup ){
987 login_needed();
 
988 }
989
990 style_header("Access Control Settings");
991 db_begin_transaction();
992 @ <form action="%s(g.zTop)/setup_access" method="post"><div>
@@ -1002,25 +1019,25 @@
1002 @ <hr />
1003 onoff_attribute("Require password for local access",
1004 "localauth", "localauth", 0, 0);
1005 @ <p>When enabled, the password sign-in is always required for
1006 @ web access. When disabled, unrestricted web access from 127.0.0.1
1007 @ is allowed for the <a href="%s(g.zTop)/help/ui">fossil ui</a> command or
1008 @ from the <a href="%s(g.zTop)/help/server">fossil server</a>,
1009 @ <a href="%s(g.zTop)/help/http">fossil http</a> commands when the
1010 @ "--localauth" command line options is used, or from the
1011 @ <a href="%s(g.zTop)/help/cgi">fossil cgi</a> if a line containing
1012 @ the word "localauth" appears in the CGI script.
1013 @
1014 @ <p>A password is always required if any one or more
1015 @ of the following are true:
1016 @ <ol>
1017 @ <li> This button is checked
1018 @ <li> The inbound TCP/IP connection is not from 127.0.0.1
1019 @ <li> The server is started using either of the
1020 @ <a href="%s(g.zTop)/help/server">fossil server</a> or
1021 @ <a href="%s(g.zTop)/help/server">fossil http</a> commands
1022 @ without the "--localauth" option.
1023 @ <li> The server is started from CGI without the "localauth" keyword
1024 @ in the CGI script.
1025 @ </ol>
1026 @
@@ -1187,11 +1204,12 @@
1187 const char *zPw = PD("pw", "");
1188 const char *zNewName = PD("newname", "New Login Group");
1189
1190 login_check_credentials();
1191 if( !g.perm.Setup ){
1192 login_needed();
 
1193 }
1194 file_canonical_name(g.zRepositoryName, &fullName, 0);
1195 zSelfRepo = fossil_strdup(blob_str(&fullName));
1196 blob_reset(&fullName);
1197 if( P("join")!=0 ){
@@ -1299,11 +1317,12 @@
1299 "3", "YYMMDD HH:MM",
1300 "4", "(off)"
1301 };
1302 login_check_credentials();
1303 if( !g.perm.Setup ){
1304 login_needed();
 
1305 }
1306
1307 style_header("Timeline Display Preferences");
1308 db_begin_transaction();
1309 @ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1377,11 +1396,12 @@
1377 void setup_settings(void){
1378 Setting const *pSet;
1379
1380 login_check_credentials();
1381 if( !g.perm.Setup ){
1382 login_needed();
 
1383 }
1384
1385 (void) aCmdHelp; /* NOTE: Silence compiler warning. */
1386 style_header("Settings");
1387 if(!g.repositoryOpen){
@@ -1457,11 +1477,12 @@
1457 ** WEBPAGE: setup_config
1458 */
1459 void setup_config(void){
1460 login_check_credentials();
1461 if( !g.perm.Setup ){
1462 login_needed();
 
1463 }
1464
1465 style_header("WWW Configuration");
1466 db_begin_transaction();
1467 @ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1535,11 +1556,12 @@
1535 ** WEBPAGE: setup_editcss
1536 */
1537 void setup_editcss(void){
1538 login_check_credentials();
1539 if( !g.perm.Setup ){
1540 login_needed();
 
1541 }
1542 db_begin_transaction();
1543 if( P("clear")!=0 ){
1544 db_multi_exec("DELETE FROM config WHERE name='css'");
1545 cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1580,11 +1602,12 @@
1580 ** WEBPAGE: setup_header
1581 */
1582 void setup_header(void){
1583 login_check_credentials();
1584 if( !g.perm.Setup ){
1585 login_needed();
 
1586 }
1587 db_begin_transaction();
1588 if( P("clear")!=0 ){
1589 db_multi_exec("DELETE FROM config WHERE name='header'");
1590 cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1644,11 +1667,12 @@
1644 ** WEBPAGE: setup_footer
1645 */
1646 void setup_footer(void){
1647 login_check_credentials();
1648 if( !g.perm.Setup ){
1649 login_needed();
 
1650 }
1651 db_begin_transaction();
1652 if( P("clear")!=0 ){
1653 db_multi_exec("DELETE FROM config WHERE name='footer'");
1654 cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1681,11 +1705,12 @@
1681 ** WEBPAGE: setup_modreq
1682 */
1683 void setup_modreq(void){
1684 login_check_credentials();
1685 if( !g.perm.Setup ){
1686 login_needed();
 
1687 }
1688
1689 style_header("Moderator For Wiki And Tickets");
1690 db_begin_transaction();
1691 @ <form action="%R/setup_modreq" method="post"><div>
@@ -1692,11 +1717,11 @@
1692 login_insert_csrf_secret();
1693 @ <hr />
1694 onoff_attribute("Moderate ticket changes",
1695 "modreq-tkt", "modreq-tkt", 0, 0);
1696 @ <p>When enabled, any change to tickets is subject to the approval
1697 @ a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1698 @ Ticket changes enter the system and are shown locally, but are not
1699 @ synced until they are approved. The moderator has the option to
1700 @ delete the change rather than approve it. Ticket changes made by
1701 @ a user who has the Mod-Tkt privilege are never subject to
1702 @ moderation.
@@ -1703,11 +1728,11 @@
1703 @
1704 @ <hr />
1705 onoff_attribute("Moderate wiki changes",
1706 "modreq-wiki", "modreq-wiki", 0, 0);
1707 @ <p>When enabled, any change to wiki is subject to the approval
1708 @ a ticket moderator - a user with the "l" or Mod-Wiki privilege.
1709 @ Wiki changes enter the system and are shown locally, but are not
1710 @ synced until they are approved. The moderator has the option to
1711 @ delete the change rather than approve it. Wiki changes made by
1712 @ a user who has the Mod-Wiki privilege are never subject to
1713 @ moderation.
@@ -1725,11 +1750,12 @@
1725 ** WEBPAGE: setup_adunit
1726 */
1727 void setup_adunit(void){
1728 login_check_credentials();
1729 if( !g.perm.Setup ){
1730 login_needed();
 
1731 }
1732 db_begin_transaction();
1733 if( P("clear")!=0 ){
1734 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1735 cgi_replace_parameter("adunit","");
@@ -1806,11 +1832,12 @@
1806 if( szBgImg>0 ){
1807 zBgMime = PD("bgim:mimetype","image/gif");
1808 }
1809 login_check_credentials();
1810 if( !g.perm.Setup ){
1811 login_needed();
 
1812 }
1813 db_begin_transaction();
1814 if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1815 Blob img;
1816 Stmt ins;
@@ -1945,11 +1972,12 @@
1945 void sql_page(void){
1946 const char *zQ = P("q");
1947 int go = P("go")!=0;
1948 login_check_credentials();
1949 if( !g.perm.Setup ){
1950 login_needed();
 
1951 }
1952 db_begin_transaction();
1953 style_header("Raw SQL Commands");
1954 @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
1955 @ run by this page. You can do serious and irrepairable damage to the
@@ -2066,11 +2094,12 @@
2066 void th1_page(void){
2067 const char *zQ = P("q");
2068 int go = P("go")!=0;
2069 login_check_credentials();
2070 if( !g.perm.Setup ){
2071 login_needed();
 
2072 }
2073 db_begin_transaction();
2074 style_header("Raw TH1 Commands");
2075 @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
2076 @ run by this page. If Tcl integration was enabled at compile-time and
@@ -2126,11 +2155,12 @@
2126 int limit;
2127 int fLogEnabled;
2128 int counter = 0;
2129 login_check_credentials();
2130 if( !g.perm.Setup && !g.perm.Admin ){
2131 login_needed();
 
2132 }
2133 style_header("Admin Log");
2134 create_admin_log_table();
2135 limit = atoi(PD("n","20"));
2136 fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2183,11 +2213,12 @@
2183 ** Configure the search engine.
2184 */
2185 void page_srchsetup(){
2186 login_check_credentials();
2187 if( !g.perm.Setup && !g.perm.Admin ){
2188 login_needed();
 
2189 }
2190 style_header("Search Configuration");
2191 @ <form action="%s(g.zTop)/srchsetup" method="post"><div>
2192 login_insert_csrf_secret();
2193 @ <div style="text-align:center;font-weight:bold;">
@@ -2235,16 +2266,18 @@
2235 search_update_index(search_restrict(SRCH_ALL));
2236 }
2237 if( search_index_exists() ){
2238 @ <p>Currently using an SQLite FTS4 search index. This makes search
2239 @ run faster, especially on large repositories, but takes up space.</p>
 
2240 @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
2241 @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
2242 }else{
2243 @ <p>The SQLite FTS4 search index is disabled. All searching will be
2244 @ a full-text scan. This usually works fine, but can be slow for
2245 @ larger repositories.</p>
 
2246 @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
2247 }
2248 @ </div></form>
2249 style_footer();
2250 }
2251
--- src/setup.c
+++ src/setup.c
@@ -50,18 +50,20 @@
50 }else{
51 @ %h(zTitle)
52 }
53 @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr>
54 }
55
56
57
58 /*
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
@@ -69,10 +71,24 @@
71 if( !cgi_header_contains("<base href=") ){
72 @ <p class="generalError"><b>Configuration Error:</b> Please add
73 @ <tt>&lt;base href="$secureurl/$current_page"&gt;</tt> after
74 @ <tt>&lt;head&gt;</tt> in the <a href="setup_header">HTML header</a>!</p>
75 }
76
77 #if !defined(_WIN32)
78 /* Check for /dev/null and /dev/urandom. We want both devices to be present,
79 ** but they are sometimes omitted (by mistake) from chroot jails. */
80 if( access("/dev/null", R_OK|W_OK) ){
81 @ <p class="generalError">WARNING: Device "/dev/null" is not available
82 @ for reading and writing.</p>
83 }
84 if( access("/dev/urandom", R_OK) ){
85 @ <p class="generalError">WARNING: Device "/dev/urandom" is not available
86 @ for reading. This means that the pseudo-random number generator used
87 @ by SQLite will be poorly seeded.</p>
88 }
89 #endif
90
91 @ <table border="0" cellspacing="3">
92 setup_menu_entry("Users", "setup_ulist",
93 "Grant privileges to individual users.");
94 setup_menu_entry("Access", "setup_access",
@@ -136,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");
@@ -320,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");
@@ -982,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>
@@ -1002,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 @
@@ -1187,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 ){
@@ -1299,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>
@@ -1377,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){
@@ -1457,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>
@@ -1535,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"));
@@ -1580,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"));
@@ -1644,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"));
@@ -1681,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>
@@ -1692,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.
@@ -1703,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.
@@ -1725,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","");
@@ -1806,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;
@@ -1945,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
@@ -2066,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
@@ -2126,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);
@@ -2183,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;">
@@ -2235,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
+100 -9
--- src/skins.c
+++ src/skins.c
@@ -37,21 +37,100 @@
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
+
66
+/*
67
+** Invoke this routine to set the alternative skin. Return NULL if the
68
+** alternative was successfully installed. Return a string listing all
69
+** available skins if zName does not match an available skin. Memory
70
+** for the returned string comes from fossil_malloc() and should be freed
71
+** by the caller.
72
+*/
73
+char *skin_use_alternative(const char *zName){
74
+ int i;
75
+ Blob err;
76
+ for(i=0; i<ArraySize(aBuiltinSkin); i++){
77
+ if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
78
+ pAltSkin = &aBuiltinSkin[i];
79
+ return 0;
80
+ }
81
+ }
82
+ blob_init(&err, aBuiltinSkin[0].zLabel, -1);
83
+ for(i=1; i<ArraySize(aBuiltinSkin); i++){
84
+ blob_append(&err, " ", 1);
85
+ blob_append(&err, aBuiltinSkin[i].zLabel, -1);
86
+ }
87
+ return blob_str(&err);
88
+}
89
+
90
+/*
91
+** Look for the --skin command-line option and process it. Or
92
+** call fossil_fatal() if an unknown skin is specified.
93
+*/
94
+void skin_override(void){
95
+ const char *zSkin = find_option("skin",0,1);
96
+ if( zSkin ){
97
+ char *zErr = skin_use_alternative(zSkin);
98
+ if( zErr ) fossil_fatal("available skins: %s", zErr);
99
+ }
100
+}
101
+
102
+/*
103
+** The following routines return the various components of the skin
104
+** that should be used for the current run.
105
+*/
106
+const char *skin_get(const char *zWhat){
107
+ const char *zOut;
108
+ char *z;
109
+ if( pAltSkin ){
110
+ z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
111
+ zOut = builtin_text(z);
112
+ fossil_free(z);
113
+ }else{
114
+ zOut = db_get(zWhat, 0);
115
+ if( zOut==0 ){
116
+ z = mprintf("skins/default/%s.txt", zWhat);
117
+ zOut = builtin_text(z);
118
+ fossil_free(z);
119
+ }
120
+ }
121
+ return zOut;
122
+}
123
+int skin_white_foreground(void){
124
+ int rc;
125
+ if( pAltSkin ){
126
+ rc = pAltSkin->whiteForeground;
127
+ }else{
128
+ rc = db_get_boolean("white-foreground",0);
129
+ }
130
+ return rc;
131
+}
53132
54133
/*
55134
** For a skin named zSkinName, compute the name of the CONFIG table
56135
** entry where that skin is stored and return it.
57136
**
@@ -210,11 +289,12 @@
210289
Stmt q;
211290
int seenCurrent = 0;
212291
213292
login_check_credentials();
214293
if( !g.perm.Setup ){
215
- login_needed();
294
+ login_needed(0);
295
+ return;
216296
}
217297
db_begin_transaction();
218298
zCurrent = getSkin(0);
219299
for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
220300
aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel);
@@ -288,10 +368,18 @@
288368
@ <a href="setup_editcss">CSS</a>,
289369
@ <a href="setup_header">Header</a>, and
290370
@ <a href="setup_footer">Footer</a> that determines the look and feel
291371
@ of the web interface.</p>
292372
@
373
+ if( pAltSkin ){
374
+ @ <p class="generalError">
375
+ @ This page is generated using an skin override named
376
+ @ "%h(pAltSkin->zLabel)". You can change the skin configuration
377
+ @ below, but the changes will not take effect until the Fossil server
378
+ @ is restarted without the override.</p>
379
+ @
380
+ }
293381
@ <h2>Available Skins:</h2>
294382
@ <table border="0">
295383
for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
296384
z = aBuiltinSkin[i].zDesc;
297385
@ <tr><td>%d(i+1).<td>%h(z)<td>&nbsp;&nbsp;<td>
@@ -300,10 +388,13 @@
300388
seenCurrent = 1;
301389
}else{
302390
@ <form action="%s(g.zTop)/setup_skin" method="post">
303391
@ <input type="hidden" name="sn" value="%h(z)" />
304392
@ <input type="submit" name="load" value="Install" />
393
+ if( pAltSkin==&aBuiltinSkin[i] ){
394
+ @ (Current override)
395
+ }
305396
@ </form>
306397
}
307398
@ </tr>
308399
}
309400
db_prepare(&q,
310401
--- src/skins.c
+++ src/skins.c
@@ -37,21 +37,100 @@
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 +289,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 +368,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 +388,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,100 @@
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
66 /*
67 ** Invoke this routine to set the alternative skin. Return NULL if the
68 ** alternative was successfully installed. Return a string listing all
69 ** available skins if zName does not match an available skin. Memory
70 ** for the returned string comes from fossil_malloc() and should be freed
71 ** by the caller.
72 */
73 char *skin_use_alternative(const char *zName){
74 int i;
75 Blob err;
76 for(i=0; i<ArraySize(aBuiltinSkin); i++){
77 if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
78 pAltSkin = &aBuiltinSkin[i];
79 return 0;
80 }
81 }
82 blob_init(&err, aBuiltinSkin[0].zLabel, -1);
83 for(i=1; i<ArraySize(aBuiltinSkin); i++){
84 blob_append(&err, " ", 1);
85 blob_append(&err, aBuiltinSkin[i].zLabel, -1);
86 }
87 return blob_str(&err);
88 }
89
90 /*
91 ** Look for the --skin command-line option and process it. Or
92 ** call fossil_fatal() if an unknown skin is specified.
93 */
94 void skin_override(void){
95 const char *zSkin = find_option("skin",0,1);
96 if( zSkin ){
97 char *zErr = skin_use_alternative(zSkin);
98 if( zErr ) fossil_fatal("available skins: %s", zErr);
99 }
100 }
101
102 /*
103 ** The following routines return the various components of the skin
104 ** that should be used for the current run.
105 */
106 const char *skin_get(const char *zWhat){
107 const char *zOut;
108 char *z;
109 if( pAltSkin ){
110 z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
111 zOut = builtin_text(z);
112 fossil_free(z);
113 }else{
114 zOut = db_get(zWhat, 0);
115 if( zOut==0 ){
116 z = mprintf("skins/default/%s.txt", zWhat);
117 zOut = builtin_text(z);
118 fossil_free(z);
119 }
120 }
121 return zOut;
122 }
123 int skin_white_foreground(void){
124 int rc;
125 if( pAltSkin ){
126 rc = pAltSkin->whiteForeground;
127 }else{
128 rc = db_get_boolean("white-foreground",0);
129 }
130 return rc;
131 }
132
133 /*
134 ** For a skin named zSkinName, compute the name of the CONFIG table
135 ** entry where that skin is stored and return it.
136 **
@@ -210,11 +289,12 @@
289 Stmt q;
290 int seenCurrent = 0;
291
292 login_check_credentials();
293 if( !g.perm.Setup ){
294 login_needed(0);
295 return;
296 }
297 db_begin_transaction();
298 zCurrent = getSkin(0);
299 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
300 aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel);
@@ -288,10 +368,18 @@
368 @ <a href="setup_editcss">CSS</a>,
369 @ <a href="setup_header">Header</a>, and
370 @ <a href="setup_footer">Footer</a> that determines the look and feel
371 @ of the web interface.</p>
372 @
373 if( pAltSkin ){
374 @ <p class="generalError">
375 @ This page is generated using an skin override named
376 @ "%h(pAltSkin->zLabel)". You can change the skin configuration
377 @ below, but the changes will not take effect until the Fossil server
378 @ is restarted without the override.</p>
379 @
380 }
381 @ <h2>Available Skins:</h2>
382 @ <table border="0">
383 for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
384 z = aBuiltinSkin[i].zDesc;
385 @ <tr><td>%d(i+1).<td>%h(z)<td>&nbsp;&nbsp;<td>
@@ -300,10 +388,13 @@
388 seenCurrent = 1;
389 }else{
390 @ <form action="%s(g.zTop)/setup_skin" method="post">
391 @ <input type="hidden" name="sn" value="%h(z)" />
392 @ <input type="submit" name="load" value="Install" />
393 if( pAltSkin==&aBuiltinSkin[i] ){
394 @ (Current override)
395 }
396 @ </form>
397 }
398 @ </tr>
399 }
400 db_prepare(&q,
401
+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
+95 -62
--- src/style.c
+++ src/style.c
@@ -45,20 +45,20 @@
4545
} aSubmenu[30];
4646
static int nSubmenu = 0; /* Number of buttons */
4747
static struct SubmenuCtrl {
4848
const char *zName; /* Form query parameter */
4949
const char *zLabel; /* Label. Might be NULL for FF_MULTI */
50
- int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */
51
- int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
50
+ unsigned char eType; /* FF_ENTRY, FF_MULTI, FF_BINARY */
51
+ unsigned char isDisabled; /* True if this control is grayed out */
52
+ short int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
5253
const char **azChoice; /* value/display pairs for FF_MULTI */
5354
const char *zFalse; /* FF_BINARY label when false */
5455
} aSubmenuCtrl[20];
5556
static int nSubmenuCtrl = 0;
5657
#define FF_ENTRY 1
57
-#define FF_CKBOX 2
58
-#define FF_MULTI 3
59
-#define FF_BINARY 4
58
+#define FF_MULTI 2
59
+#define FF_BINARY 3
6060
6161
/*
6262
** Remember that the header has been generated. The footer is omitted
6363
** if an error occurs before the header.
6464
*/
@@ -246,50 +246,46 @@
246246
nSubmenu++;
247247
}
248248
void style_submenu_entry(
249249
const char *zName, /* Query parameter name */
250250
const char *zLabel, /* Label before the entry box */
251
- int iSize /* Size of the entry box */
251
+ int iSize, /* Size of the entry box */
252
+ int isDisabled /* True if disabled */
252253
){
253254
assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
254255
aSubmenuCtrl[nSubmenuCtrl].zName = zName;
255256
aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
256257
aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
258
+ aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
257259
aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
258260
nSubmenuCtrl++;
259261
}
260
-void style_submenu_checkbox(
261
- const char *zName, /* Query parameter name */
262
- const char *zLabel /* Label before the checkbox */
263
-){
264
- assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
265
- aSubmenuCtrl[nSubmenuCtrl].zName = zName;
266
- aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
267
- aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX;
268
- nSubmenuCtrl++;
269
-}
270262
void style_submenu_binary(
271263
const char *zName, /* Query parameter name */
272264
const char *zTrue, /* Label to show when parameter is true */
273
- const char *zFalse /* Label to show when the parameter is false */
265
+ const char *zFalse, /* Label to show when the parameter is false */
266
+ int isDisabled /* True if this control is disabled */
274267
){
275268
assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
276269
aSubmenuCtrl[nSubmenuCtrl].zName = zName;
277270
aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
278271
aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
272
+ aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
279273
aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
280274
nSubmenuCtrl++;
281275
}
282276
void style_submenu_multichoice(
283277
const char *zName, /* Query parameter name */
284278
int nChoice, /* Number of options */
285
- const char **azChoice /* value/display pairs. 2*nChoice entries */
279
+ const char **azChoice, /* value/display pairs. 2*nChoice entries */
280
+ int isDisabled /* True if this control is disabled */
286281
){
287282
assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
288283
aSubmenuCtrl[nSubmenuCtrl].zName = zName;
289284
aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
290285
aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
286
+ aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
291287
aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
292288
nSubmenuCtrl++;
293289
}
294290
295291
@@ -357,12 +353,11 @@
357353
** Draw the header.
358354
*/
359355
void style_header(const char *zTitleFormat, ...){
360356
va_list ap;
361357
char *zTitle;
362
- const char *zHeader = db_get("header", 0);
363
- if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
358
+ const char *zHeader = skin_get("header");
364359
login_check_credentials();
365360
366361
va_start(ap, zTitleFormat);
367362
zTitle = vmprintf(zTitleFormat, ap);
368363
va_end(ap);
@@ -499,42 +494,36 @@
499494
}
500495
}
501496
if( nSubmenuCtrl>0 ){
502497
for(i=0; i<nSubmenuCtrl; i++){
503498
const char *zQPN = aSubmenuCtrl[i].zName;
504
- cgi_tag_query_parameter(zQPN);
499
+ const char *zDisabled = " disabled";
500
+ if( !aSubmenuCtrl[i].isDisabled ){
501
+ zDisabled = "";
502
+ cgi_tag_query_parameter(zQPN);
503
+ }
505504
switch( aSubmenuCtrl[i].eType ){
506505
case FF_ENTRY: {
507506
cgi_printf(
508507
"<span class='submenuctrl'>"
509
- "%h:&nbsp;<input type='text' name='%s' size='%d' "
510
- "value='%h'></span>\n",
511
- aSubmenuCtrl[i].zLabel,
512
- zQPN,
513
- aSubmenuCtrl[i].iSize,
514
- PD(zQPN,"")
515
- );
516
- break;
517
- }
518
- case FF_CKBOX: {
519
- cgi_printf(
520
- "<span class='submenuctrl'>"
521
- "%h:&nbsp;<input type='checkbox' name='%s'%s "
522
- "onchange='gebi(\"f01\").submit();'></span>\n",
523
- aSubmenuCtrl[i].zLabel,
524
- zQPN,
525
- PB(zQPN) ? " checked":""
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
526515
);
527516
break;
528517
}
529518
case FF_MULTI: {
530519
int j;
531520
const char *zVal = P(zQPN);
532521
cgi_printf(
533
- "<select class='submenuctrl' size='1' name='%s' "
522
+ "<select class='submenuctrl' size='1' name='%s'%s "
534523
"onchange='gebi(\"f01\").submit();'>\n",
535
- zQPN
524
+ zQPN, zDisabled
536525
);
537526
for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
538527
const char *zQPV = aSubmenuCtrl[i].azChoice[j];
539528
cgi_printf(
540529
"<option value='%h'%s>%h</option>\n",
@@ -547,13 +536,13 @@
547536
break;
548537
}
549538
case FF_BINARY: {
550539
int isTrue = PB(zQPN);
551540
cgi_printf(
552
- "<select class='submenuctrl' size='1' name='%s' "
541
+ "<select class='submenuctrl' size='1' name='%s'%s "
553542
"onchange='gebi(\"f01\").submit();'>\n",
554
- zQPN
543
+ zQPN, zDisabled
555544
);
556545
cgi_printf(
557546
"<option value='1'%s>%h</option>\n",
558547
isTrue ? " selected":"", aSubmenuCtrl[i].zLabel
559548
);
@@ -602,12 +591,11 @@
602591
603592
/* Set the href= field on hyperlinks. Do this before the footer since
604593
** the footer will be generating </html> */
605594
style_resolve_href();
606595
607
- zFooter = db_get("footer", 0);
608
- if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
596
+ zFooter = skin_get("footer");
609597
if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
610598
Th_Render(zFooter);
611599
if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
612600
613601
/* Render trace log if TH1 tracing is enabled. */
@@ -673,10 +661,11 @@
673661
@ font-size: small;
674662
},
675663
{ "table.timelineTable",
676664
"the format for the timeline data table",
677665
@ border: 0;
666
+ @ border-collapse: collapse;
678667
},
679668
{ "td.timelineTableCell",
680669
"the format for the timeline data cells",
681670
@ vertical-align: top;
682671
@ text-align: left;
@@ -683,10 +672,21 @@
683672
},
684673
{ "tr.timelineCurrent td.timelineTableCell",
685674
"the format for the timeline data cell of the current checkout",
686675
@ padding: .1em .2em;
687676
@ border: 1px dashed #446979;
677
+ },
678
+ { "tr.timelineSelected",
679
+ "The row in the timeline table that contains the entry of interest",
680
+ @ padding: .1em .2em;
681
+ @ border: 2px solid lightgray;
682
+ @ background-color: #ffc;
683
+ @ box-shadow: 4px 4px 2px #888;
684
+ },
685
+ { "tr.timelineSpacer",
686
+ "An extra row inserted to give vertical space between two rows",
687
+ @ height: 1ex;
688688
},
689689
{ "span.timelineLeaf",
690690
"the format for the timeline leaf marks",
691691
@ font-weight: bold;
692692
},
@@ -745,16 +745,10 @@
745745
{ "td.browser",
746746
"format for cells in the file browser",
747747
@ width: 24%;
748748
@ vertical-align: top;
749749
},
750
- { "ul.browser",
751
- "format for the list in the file browser",
752
- @ margin-left: 0.5em;
753
- @ padding-left: 0.5em;
754
- @ white-space: nowrap;
755
- },
756750
{ ".filetree",
757751
"tree-view file browser",
758752
@ margin: 1em 0;
759753
@ line-height: 1.5;
760754
},
@@ -808,28 +802,59 @@
808802
"hide lines for last-child directories",
809803
@ display: none;
810804
},
811805
{ ".filetree a",
812806
"tree-view links",
813
- @ position: relative;
814
- @ z-index: 1;
815
- @ display: table-cell;
816
- @ min-height: 16px;
817
- @ padding-left: 21px;
818
- @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);
819
- @ background-position: center left;
820
- @ background-repeat: no-repeat;
807
+ " position: relative;\n"
808
+ " z-index: 1;\n"
809
+ " display: table-cell;\n"
810
+ " min-height: 16px;\n"
811
+ " padding-left: 21px;\n"
812
+ " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP"
813
+ "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmg"
814
+ "OUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n"
815
+ " background-position: center left;\n"
816
+ " background-repeat: no-repeat;\n"
817
+ },
818
+ { "ul.browser",
819
+ "list of files in the 'flat-view' file browser",
820
+ @ list-style-type: none;
821
+ @ padding: 10px;
822
+ @ margin: 0px;
823
+ @ white-space: nowrap;
824
+ },
825
+ { "ul.browser li.file",
826
+ "List element in the 'flat-view' file browser for a file",
827
+ " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP"
828
+ "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfm"
829
+ "gOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n"
830
+ " background-repeat: no-repeat;\n"
831
+ " background-position: 0px center;\n"
832
+ " padding-left: 20px;\n"
833
+ " padding-top: 2px;\n"
834
+ },
835
+ { "ul.browser li.dir",
836
+ "List element in the 'flat-view file browser for a directory",
837
+ " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi"
838
+ "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaX"
839
+ "po+jUs6b5Z/K4siDu5RPUFADs=);\n"
840
+ " background-repeat: no-repeat;\n"
841
+ " background-position: 0px center;\n"
842
+ " padding-left: 20px;\n"
843
+ " padding-top: 2px;\n"
821844
},
822845
{ "div.filetreeline",
823846
"line of a file tree",
824847
@ display: table;
825848
@ width: 100%;
826849
@ white-space: nowrap;
827850
},
828851
{ ".filetree .dir > div.filetreeline > a",
829852
"tree-view directory links",
830
- @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=);
853
+ " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi"
854
+ "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXp"
855
+ "o+jUs6b5Z/K4siDu5RPUFADs=);\n"
831856
},
832857
{ "div.filetreeage",
833858
"Last change floating display on the right",
834859
@ display: table-cell;
835860
@ padding-left: 3em;
@@ -1340,11 +1365,11 @@
13401365
void page_style_css(void){
13411366
Blob css;
13421367
int i;
13431368
13441369
cgi_set_content_type("text/css");
1345
- blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1370
+ blob_init(&css,skin_get("css"),-1);
13461371
13471372
/* add special missing definitions */
13481373
for(i=1; cssDefaultList[i].elementClass; i++){
13491374
char *z = blob_str(&css);
13501375
if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1386,11 +1411,11 @@
13861411
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
13871412
};
13881413
13891414
login_check_credentials();
13901415
if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1391
- login_needed();
1416
+ login_needed(0);
13921417
return;
13931418
}
13941419
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
13951420
style_header("Environment Test");
13961421
showAll = atoi(PD("showall","0"));
@@ -1405,17 +1430,25 @@
14051430
@ g.zBaseURL = %h(g.zBaseURL)<br />
14061431
@ g.zHttpsURL = %h(g.zHttpsURL)<br />
14071432
@ g.zTop = %h(g.zTop)<br />
14081433
@ g.zPath = %h(g.zPath)<br />
14091434
for(i=0, c='a'; c<='z'; c++){
1410
- if( login_has_capability(&c, 1) ) zCap[i++] = c;
1435
+ if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
14111436
}
14121437
zCap[i] = 0;
14131438
@ g.userUid = %d(g.userUid)<br />
14141439
@ g.zLogin = %h(g.zLogin)<br />
14151440
@ g.isHuman = %d(g.isHuman)<br />
14161441
@ capabilities = %s(zCap)<br />
1442
+ for(i=0, c='a'; c<='z'; c++){
1443
+ if( login_has_capability(&c, 1, LOGIN_ANON)
1444
+ && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1445
+ }
1446
+ zCap[i] = 0;
1447
+ if( i>0 ){
1448
+ @ anonymous-adds = %s(zCap)<br />
1449
+ }
14171450
@ g.zRepositoryName = %h(g.zRepositoryName)<br />
14181451
@ load_average() = %f(load_average())<br />
14191452
@ <hr>
14201453
P("HTTP_USER_AGENT");
14211454
cgi_print_all(showAll);
14221455
--- src/style.c
+++ src/style.c
@@ -45,20 +45,20 @@
45 } aSubmenu[30];
46 static int nSubmenu = 0; /* Number of buttons */
47 static struct SubmenuCtrl {
48 const char *zName; /* Form query parameter */
49 const char *zLabel; /* Label. Might be NULL for FF_MULTI */
50 int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */
51 int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
 
52 const char **azChoice; /* value/display pairs for FF_MULTI */
53 const char *zFalse; /* FF_BINARY label when false */
54 } aSubmenuCtrl[20];
55 static int nSubmenuCtrl = 0;
56 #define FF_ENTRY 1
57 #define FF_CKBOX 2
58 #define FF_MULTI 3
59 #define FF_BINARY 4
60
61 /*
62 ** Remember that the header has been generated. The footer is omitted
63 ** if an error occurs before the header.
64 */
@@ -246,50 +246,46 @@
246 nSubmenu++;
247 }
248 void style_submenu_entry(
249 const char *zName, /* Query parameter name */
250 const char *zLabel, /* Label before the entry box */
251 int iSize /* Size of the entry box */
 
252 ){
253 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
254 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
255 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
256 aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
 
257 aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
258 nSubmenuCtrl++;
259 }
260 void style_submenu_checkbox(
261 const char *zName, /* Query parameter name */
262 const char *zLabel /* Label before the checkbox */
263 ){
264 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
265 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
266 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
267 aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX;
268 nSubmenuCtrl++;
269 }
270 void style_submenu_binary(
271 const char *zName, /* Query parameter name */
272 const char *zTrue, /* Label to show when parameter is true */
273 const char *zFalse /* Label to show when the parameter is false */
 
274 ){
275 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
276 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
277 aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
278 aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
 
279 aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
280 nSubmenuCtrl++;
281 }
282 void style_submenu_multichoice(
283 const char *zName, /* Query parameter name */
284 int nChoice, /* Number of options */
285 const char **azChoice /* value/display pairs. 2*nChoice entries */
 
286 ){
287 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
288 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
289 aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
290 aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
 
291 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
292 nSubmenuCtrl++;
293 }
294
295
@@ -357,12 +353,11 @@
357 ** Draw the header.
358 */
359 void style_header(const char *zTitleFormat, ...){
360 va_list ap;
361 char *zTitle;
362 const char *zHeader = db_get("header", 0);
363 if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
364 login_check_credentials();
365
366 va_start(ap, zTitleFormat);
367 zTitle = vmprintf(zTitleFormat, ap);
368 va_end(ap);
@@ -499,42 +494,36 @@
499 }
500 }
501 if( nSubmenuCtrl>0 ){
502 for(i=0; i<nSubmenuCtrl; i++){
503 const char *zQPN = aSubmenuCtrl[i].zName;
504 cgi_tag_query_parameter(zQPN);
 
 
 
 
505 switch( aSubmenuCtrl[i].eType ){
506 case FF_ENTRY: {
507 cgi_printf(
508 "<span class='submenuctrl'>"
509 "%h:&nbsp;<input type='text' name='%s' size='%d' "
510 "value='%h'></span>\n",
511 aSubmenuCtrl[i].zLabel,
512 zQPN,
513 aSubmenuCtrl[i].iSize,
514 PD(zQPN,"")
515 );
516 break;
517 }
518 case FF_CKBOX: {
519 cgi_printf(
520 "<span class='submenuctrl'>"
521 "%h:&nbsp;<input type='checkbox' name='%s'%s "
522 "onchange='gebi(\"f01\").submit();'></span>\n",
523 aSubmenuCtrl[i].zLabel,
524 zQPN,
525 PB(zQPN) ? " checked":""
526 );
527 break;
528 }
529 case FF_MULTI: {
530 int j;
531 const char *zVal = P(zQPN);
532 cgi_printf(
533 "<select class='submenuctrl' size='1' name='%s' "
534 "onchange='gebi(\"f01\").submit();'>\n",
535 zQPN
536 );
537 for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
538 const char *zQPV = aSubmenuCtrl[i].azChoice[j];
539 cgi_printf(
540 "<option value='%h'%s>%h</option>\n",
@@ -547,13 +536,13 @@
547 break;
548 }
549 case FF_BINARY: {
550 int isTrue = PB(zQPN);
551 cgi_printf(
552 "<select class='submenuctrl' size='1' name='%s' "
553 "onchange='gebi(\"f01\").submit();'>\n",
554 zQPN
555 );
556 cgi_printf(
557 "<option value='1'%s>%h</option>\n",
558 isTrue ? " selected":"", aSubmenuCtrl[i].zLabel
559 );
@@ -602,12 +591,11 @@
602
603 /* Set the href= field on hyperlinks. Do this before the footer since
604 ** the footer will be generating </html> */
605 style_resolve_href();
606
607 zFooter = db_get("footer", 0);
608 if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
609 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
610 Th_Render(zFooter);
611 if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
612
613 /* Render trace log if TH1 tracing is enabled. */
@@ -673,10 +661,11 @@
673 @ font-size: small;
674 },
675 { "table.timelineTable",
676 "the format for the timeline data table",
677 @ border: 0;
 
678 },
679 { "td.timelineTableCell",
680 "the format for the timeline data cells",
681 @ vertical-align: top;
682 @ text-align: left;
@@ -683,10 +672,21 @@
683 },
684 { "tr.timelineCurrent td.timelineTableCell",
685 "the format for the timeline data cell of the current checkout",
686 @ padding: .1em .2em;
687 @ border: 1px dashed #446979;
 
 
 
 
 
 
 
 
 
 
 
688 },
689 { "span.timelineLeaf",
690 "the format for the timeline leaf marks",
691 @ font-weight: bold;
692 },
@@ -745,16 +745,10 @@
745 { "td.browser",
746 "format for cells in the file browser",
747 @ width: 24%;
748 @ vertical-align: top;
749 },
750 { "ul.browser",
751 "format for the list in the file browser",
752 @ margin-left: 0.5em;
753 @ padding-left: 0.5em;
754 @ white-space: nowrap;
755 },
756 { ".filetree",
757 "tree-view file browser",
758 @ margin: 1em 0;
759 @ line-height: 1.5;
760 },
@@ -808,28 +802,59 @@
808 "hide lines for last-child directories",
809 @ display: none;
810 },
811 { ".filetree a",
812 "tree-view links",
813 @ position: relative;
814 @ z-index: 1;
815 @ display: table-cell;
816 @ min-height: 16px;
817 @ padding-left: 21px;
818 @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);
819 @ background-position: center left;
820 @ background-repeat: no-repeat;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821 },
822 { "div.filetreeline",
823 "line of a file tree",
824 @ display: table;
825 @ width: 100%;
826 @ white-space: nowrap;
827 },
828 { ".filetree .dir > div.filetreeline > a",
829 "tree-view directory links",
830 @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=);
 
 
831 },
832 { "div.filetreeage",
833 "Last change floating display on the right",
834 @ display: table-cell;
835 @ padding-left: 3em;
@@ -1340,11 +1365,11 @@
1340 void page_style_css(void){
1341 Blob css;
1342 int i;
1343
1344 cgi_set_content_type("text/css");
1345 blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1346
1347 /* add special missing definitions */
1348 for(i=1; cssDefaultList[i].elementClass; i++){
1349 char *z = blob_str(&css);
1350 if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1386,11 +1411,11 @@
1386 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1387 };
1388
1389 login_check_credentials();
1390 if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1391 login_needed();
1392 return;
1393 }
1394 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1395 style_header("Environment Test");
1396 showAll = atoi(PD("showall","0"));
@@ -1405,17 +1430,25 @@
1405 @ g.zBaseURL = %h(g.zBaseURL)<br />
1406 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1407 @ g.zTop = %h(g.zTop)<br />
1408 @ g.zPath = %h(g.zPath)<br />
1409 for(i=0, c='a'; c<='z'; c++){
1410 if( login_has_capability(&c, 1) ) zCap[i++] = c;
1411 }
1412 zCap[i] = 0;
1413 @ g.userUid = %d(g.userUid)<br />
1414 @ g.zLogin = %h(g.zLogin)<br />
1415 @ g.isHuman = %d(g.isHuman)<br />
1416 @ capabilities = %s(zCap)<br />
 
 
 
 
 
 
 
 
1417 @ g.zRepositoryName = %h(g.zRepositoryName)<br />
1418 @ load_average() = %f(load_average())<br />
1419 @ <hr>
1420 P("HTTP_USER_AGENT");
1421 cgi_print_all(showAll);
1422
--- src/style.c
+++ src/style.c
@@ -45,20 +45,20 @@
45 } aSubmenu[30];
46 static int nSubmenu = 0; /* Number of buttons */
47 static struct SubmenuCtrl {
48 const char *zName; /* Form query parameter */
49 const char *zLabel; /* Label. Might be NULL for FF_MULTI */
50 unsigned char eType; /* FF_ENTRY, FF_MULTI, FF_BINARY */
51 unsigned char isDisabled; /* True if this control is grayed out */
52 short int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
53 const char **azChoice; /* value/display pairs for FF_MULTI */
54 const char *zFalse; /* FF_BINARY label when false */
55 } aSubmenuCtrl[20];
56 static int nSubmenuCtrl = 0;
57 #define FF_ENTRY 1
58 #define FF_MULTI 2
59 #define FF_BINARY 3
 
60
61 /*
62 ** Remember that the header has been generated. The footer is omitted
63 ** if an error occurs before the header.
64 */
@@ -246,50 +246,46 @@
246 nSubmenu++;
247 }
248 void style_submenu_entry(
249 const char *zName, /* Query parameter name */
250 const char *zLabel, /* Label before the entry box */
251 int iSize, /* Size of the entry box */
252 int isDisabled /* True if disabled */
253 ){
254 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
255 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
256 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
257 aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
258 aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
259 aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
260 nSubmenuCtrl++;
261 }
 
 
 
 
 
 
 
 
 
 
262 void style_submenu_binary(
263 const char *zName, /* Query parameter name */
264 const char *zTrue, /* Label to show when parameter is true */
265 const char *zFalse, /* Label to show when the parameter is false */
266 int isDisabled /* True if this control is disabled */
267 ){
268 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
269 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
270 aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
271 aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
272 aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
273 aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
274 nSubmenuCtrl++;
275 }
276 void style_submenu_multichoice(
277 const char *zName, /* Query parameter name */
278 int nChoice, /* Number of options */
279 const char **azChoice, /* value/display pairs. 2*nChoice entries */
280 int isDisabled /* True if this control is disabled */
281 ){
282 assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) );
283 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
284 aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
285 aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
286 aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled;
287 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
288 nSubmenuCtrl++;
289 }
290
291
@@ -357,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);
@@ -499,42 +494,36 @@
494 }
495 }
496 if( nSubmenuCtrl>0 ){
497 for(i=0; i<nSubmenuCtrl; i++){
498 const char *zQPN = aSubmenuCtrl[i].zName;
499 const char *zDisabled = " disabled";
500 if( !aSubmenuCtrl[i].isDisabled ){
501 zDisabled = "";
502 cgi_tag_query_parameter(zQPN);
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 }
518 case FF_MULTI: {
519 int j;
520 const char *zVal = P(zQPN);
521 cgi_printf(
522 "<select class='submenuctrl' size='1' name='%s'%s "
523 "onchange='gebi(\"f01\").submit();'>\n",
524 zQPN, zDisabled
525 );
526 for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
527 const char *zQPV = aSubmenuCtrl[i].azChoice[j];
528 cgi_printf(
529 "<option value='%h'%s>%h</option>\n",
@@ -547,13 +536,13 @@
536 break;
537 }
538 case FF_BINARY: {
539 int isTrue = PB(zQPN);
540 cgi_printf(
541 "<select class='submenuctrl' size='1' name='%s'%s "
542 "onchange='gebi(\"f01\").submit();'>\n",
543 zQPN, zDisabled
544 );
545 cgi_printf(
546 "<option value='1'%s>%h</option>\n",
547 isTrue ? " selected":"", aSubmenuCtrl[i].zLabel
548 );
@@ -602,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. */
@@ -673,10 +661,11 @@
661 @ font-size: small;
662 },
663 { "table.timelineTable",
664 "the format for the timeline data table",
665 @ border: 0;
666 @ border-collapse: collapse;
667 },
668 { "td.timelineTableCell",
669 "the format for the timeline data cells",
670 @ vertical-align: top;
671 @ text-align: left;
@@ -683,10 +672,21 @@
672 },
673 { "tr.timelineCurrent td.timelineTableCell",
674 "the format for the timeline data cell of the current checkout",
675 @ padding: .1em .2em;
676 @ border: 1px dashed #446979;
677 },
678 { "tr.timelineSelected",
679 "The row in the timeline table that contains the entry of interest",
680 @ padding: .1em .2em;
681 @ border: 2px solid lightgray;
682 @ background-color: #ffc;
683 @ box-shadow: 4px 4px 2px #888;
684 },
685 { "tr.timelineSpacer",
686 "An extra row inserted to give vertical space between two rows",
687 @ height: 1ex;
688 },
689 { "span.timelineLeaf",
690 "the format for the timeline leaf marks",
691 @ font-weight: bold;
692 },
@@ -745,16 +745,10 @@
745 { "td.browser",
746 "format for cells in the file browser",
747 @ width: 24%;
748 @ vertical-align: top;
749 },
 
 
 
 
 
 
750 { ".filetree",
751 "tree-view file browser",
752 @ margin: 1em 0;
753 @ line-height: 1.5;
754 },
@@ -808,28 +802,59 @@
802 "hide lines for last-child directories",
803 @ display: none;
804 },
805 { ".filetree a",
806 "tree-view links",
807 " position: relative;\n"
808 " z-index: 1;\n"
809 " display: table-cell;\n"
810 " min-height: 16px;\n"
811 " padding-left: 21px;\n"
812 " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP"
813 "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmg"
814 "OUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n"
815 " background-position: center left;\n"
816 " background-repeat: no-repeat;\n"
817 },
818 { "ul.browser",
819 "list of files in the 'flat-view' file browser",
820 @ list-style-type: none;
821 @ padding: 10px;
822 @ margin: 0px;
823 @ white-space: nowrap;
824 },
825 { "ul.browser li.file",
826 "List element in the 'flat-view' file browser for a file",
827 " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP"
828 "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfm"
829 "gOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n"
830 " background-repeat: no-repeat;\n"
831 " background-position: 0px center;\n"
832 " padding-left: 20px;\n"
833 " padding-top: 2px;\n"
834 },
835 { "ul.browser li.dir",
836 "List element in the 'flat-view file browser for a directory",
837 " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi"
838 "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaX"
839 "po+jUs6b5Z/K4siDu5RPUFADs=);\n"
840 " background-repeat: no-repeat;\n"
841 " background-position: 0px center;\n"
842 " padding-left: 20px;\n"
843 " padding-top: 2px;\n"
844 },
845 { "div.filetreeline",
846 "line of a file tree",
847 @ display: table;
848 @ width: 100%;
849 @ white-space: nowrap;
850 },
851 { ".filetree .dir > div.filetreeline > a",
852 "tree-view directory links",
853 " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi"
854 "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXp"
855 "o+jUs6b5Z/K4siDu5RPUFADs=);\n"
856 },
857 { "div.filetreeage",
858 "Last change floating display on the right",
859 @ display: table-cell;
860 @ padding-left: 3em;
@@ -1340,11 +1365,11 @@
1365 void page_style_css(void){
1366 Blob css;
1367 int i;
1368
1369 cgi_set_content_type("text/css");
1370 blob_init(&css,skin_get("css"),-1);
1371
1372 /* add special missing definitions */
1373 for(i=1; cssDefaultList[i].elementClass; i++){
1374 char *z = blob_str(&css);
1375 if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1386,11 +1411,11 @@
1411 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1412 };
1413
1414 login_check_credentials();
1415 if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1416 login_needed(0);
1417 return;
1418 }
1419 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1420 style_header("Environment Test");
1421 showAll = atoi(PD("showall","0"));
@@ -1405,17 +1430,25 @@
1430 @ g.zBaseURL = %h(g.zBaseURL)<br />
1431 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1432 @ g.zTop = %h(g.zTop)<br />
1433 @ g.zPath = %h(g.zPath)<br />
1434 for(i=0, c='a'; c<='z'; c++){
1435 if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1436 }
1437 zCap[i] = 0;
1438 @ g.userUid = %d(g.userUid)<br />
1439 @ g.zLogin = %h(g.zLogin)<br />
1440 @ g.isHuman = %d(g.isHuman)<br />
1441 @ capabilities = %s(zCap)<br />
1442 for(i=0, c='a'; c<='z'; c++){
1443 if( login_has_capability(&c, 1, LOGIN_ANON)
1444 && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
1445 }
1446 zCap[i] = 0;
1447 if( i>0 ){
1448 @ anonymous-adds = %s(zCap)<br />
1449 }
1450 @ g.zRepositoryName = %h(g.zRepositoryName)<br />
1451 @ load_average() = %f(load_average())<br />
1452 @ <hr>
1453 P("HTTP_USER_AGENT");
1454 cgi_print_all(showAll);
1455
+3 -3
--- 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>
@@ -594,10 +594,10 @@
594594
" AND tagid IN (SELECT tagid FROM tag "
595595
" WHERE tagname GLOB 'sym-*'))"
596596
" ORDER BY event.mtime DESC",
597597
timeline_query_for_www()
598598
);
599
- www_print_timeline(&q, 0, 0, 0, 0);
599
+ www_print_timeline(&q, 0, 0, 0, 0, 0);
600600
db_finalize(&q);
601601
@ <br />
602602
style_footer();
603603
}
604604
--- 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>
@@ -594,10 +594,10 @@
594 " AND tagid IN (SELECT tagid FROM tag "
595 " WHERE tagname GLOB 'sym-*'))"
596 " ORDER BY event.mtime DESC",
597 timeline_query_for_www()
598 );
599 www_print_timeline(&q, 0, 0, 0, 0);
600 db_finalize(&q);
601 @ <br />
602 style_footer();
603 }
604
--- 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>
@@ -594,10 +594,10 @@
594 " AND tagid IN (SELECT tagid FROM tag "
595 " WHERE tagname GLOB 'sym-*'))"
596 " ORDER BY event.mtime DESC",
597 timeline_query_for_www()
598 );
599 www_print_timeline(&q, 0, 0, 0, 0, 0);
600 db_finalize(&q);
601 @ <br />
602 style_footer();
603 }
604
+19 -9
--- src/tar.c
+++ src/tar.c
@@ -285,11 +285,11 @@
285285
const char *zName, /* Name of the object */
286286
int nName, /* Number of characters in zName */
287287
int iMode, /* Mode. 0644 or 0755 */
288288
unsigned int mTime, /* File modification time */
289289
int iSize, /* Size of the object in bytes */
290
- char cType /* Type of object:
290
+ char cType /* Type of object:
291291
'0'==file. '2'==symlink. '5'==directory */
292292
){
293293
/* set mode and modification time */
294294
sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode);
295295
sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime);
@@ -341,11 +341,11 @@
341341
unsigned int mTime /* Modification time */
342342
){
343343
int i;
344344
for(i=nName-1; i>0 && zName[i]!='/'; i--){}
345345
if( i<=0 ) return;
346
- if( i<tball.nPrevDirAlloc
346
+ if( i<tball.nPrevDirAlloc
347347
&& strncmp(tball.zPrevDir, zName, i)==0
348348
&& tball.zPrevDir[i]==0 ) return;
349349
db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName);
350350
if( sqlite3_changes(g.db)==0 ) return;
351351
tar_add_directory_of(zName, i-1, mTime);
@@ -377,11 +377,11 @@
377377
char cType = '0';
378378
379379
/* length check moved to tar_split_path */
380380
tar_add_directory_of(zName, nName, mTime);
381381
382
- /*
382
+ /*
383383
* If we have a symlink, write its destination path (which is stored in
384384
* pContent) into header, and set content length to 0 to avoid storing path
385385
* as file content in the next step. Since 'linkname' header is limited to
386386
* 100 bytes (-1 byte for terminating zero), if path is greater than that,
387387
* store symlink as a plain-text file. (Not sure how TAR handles long links.)
@@ -390,11 +390,11 @@
390390
sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent));
391391
cType = '2';
392392
n = 0;
393393
}
394394
395
- tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644,
395
+ tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644,
396396
mTime, n, cType);
397397
if( n ){
398398
gzip_step(blob_buffer(pContent), n);
399399
lastPage = n % 512;
400400
if( lastPage!=0 ){
@@ -428,21 +428,20 @@
428428
** that contains files given in the second and subsequent arguments.
429429
*/
430430
void test_tarball_cmd(void){
431431
int i;
432432
Blob zip;
433
- Blob file;
434433
if( g.argc<3 ){
435434
usage("ARCHIVE FILE....");
436435
}
437436
sqlite3_open(":memory:", &g.db);
438437
tar_begin(-1);
439438
for(i=3; i<g.argc; i++){
439
+ Blob file;
440440
blob_zero(&file);
441441
blob_read_from_file(&file, g.argv[i]);
442
- tar_add_file(g.argv[i], &file,
443
- file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i]));
442
+ tar_add_file(g.argv[i], &file, file_wd_perm(0), file_wd_mtime(0));
444443
blob_reset(&file);
445444
}
446445
tar_finish(&zip);
447446
blob_write_to_file(&zip, g.argv[2]);
448447
}
@@ -591,11 +590,11 @@
591590
** Optional URL Parameters:
592591
**
593592
** - name=NAME[.tar.gz] is base name of the output file. Defaults to
594593
** something project/version-specific. The prefix of the name, up to
595594
** the last '.', are used as the top-most directory name in the tar
596
-** output.
595
+** output.
597596
**
598597
** - uuid=the version to tar (may be a tag/branch name).
599598
** Defaults to "trunk".
600599
**
601600
*/
@@ -604,11 +603,11 @@
604603
char *zName, *zRid, *zKey;
605604
int nName, nRid;
606605
Blob tarball;
607606
608607
login_check_credentials();
609
- if( !g.perm.Zip ){ login_needed(); return; }
608
+ if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
610609
load_control();
611610
zName = mprintf("%s", PD("name",""));
612611
nName = strlen(zName);
613612
zRid = mprintf("%s", PD("uuid","trunk"));
614613
nRid = strlen(zRid);
@@ -639,10 +638,21 @@
639638
@ zName = "%h(zName)"<br>
640639
@ rid = %d(rid)<br>
641640
@ zKey = "%h(zKey)"
642641
style_footer();
643642
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;
644654
}
645655
blob_zero(&tarball);
646656
if( cache_read(&tarball, zKey)==0 ){
647657
tarball_of_checkin(rid, &tarball, zName);
648658
cache_write(&tarball, zKey);
649659
--- src/tar.c
+++ src/tar.c
@@ -285,11 +285,11 @@
285 const char *zName, /* Name of the object */
286 int nName, /* Number of characters in zName */
287 int iMode, /* Mode. 0644 or 0755 */
288 unsigned int mTime, /* File modification time */
289 int iSize, /* Size of the object in bytes */
290 char cType /* Type of object:
291 '0'==file. '2'==symlink. '5'==directory */
292 ){
293 /* set mode and modification time */
294 sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode);
295 sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime);
@@ -341,11 +341,11 @@
341 unsigned int mTime /* Modification time */
342 ){
343 int i;
344 for(i=nName-1; i>0 && zName[i]!='/'; i--){}
345 if( i<=0 ) return;
346 if( i<tball.nPrevDirAlloc
347 && strncmp(tball.zPrevDir, zName, i)==0
348 && tball.zPrevDir[i]==0 ) return;
349 db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName);
350 if( sqlite3_changes(g.db)==0 ) return;
351 tar_add_directory_of(zName, i-1, mTime);
@@ -377,11 +377,11 @@
377 char cType = '0';
378
379 /* length check moved to tar_split_path */
380 tar_add_directory_of(zName, nName, mTime);
381
382 /*
383 * If we have a symlink, write its destination path (which is stored in
384 * pContent) into header, and set content length to 0 to avoid storing path
385 * as file content in the next step. Since 'linkname' header is limited to
386 * 100 bytes (-1 byte for terminating zero), if path is greater than that,
387 * store symlink as a plain-text file. (Not sure how TAR handles long links.)
@@ -390,11 +390,11 @@
390 sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent));
391 cType = '2';
392 n = 0;
393 }
394
395 tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644,
396 mTime, n, cType);
397 if( n ){
398 gzip_step(blob_buffer(pContent), n);
399 lastPage = n % 512;
400 if( lastPage!=0 ){
@@ -428,21 +428,20 @@
428 ** that contains files given in the second and subsequent arguments.
429 */
430 void test_tarball_cmd(void){
431 int i;
432 Blob zip;
433 Blob file;
434 if( g.argc<3 ){
435 usage("ARCHIVE FILE....");
436 }
437 sqlite3_open(":memory:", &g.db);
438 tar_begin(-1);
439 for(i=3; i<g.argc; i++){
 
440 blob_zero(&file);
441 blob_read_from_file(&file, g.argv[i]);
442 tar_add_file(g.argv[i], &file,
443 file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i]));
444 blob_reset(&file);
445 }
446 tar_finish(&zip);
447 blob_write_to_file(&zip, g.argv[2]);
448 }
@@ -591,11 +590,11 @@
591 ** Optional URL Parameters:
592 **
593 ** - name=NAME[.tar.gz] is base name of the output file. Defaults to
594 ** something project/version-specific. The prefix of the name, up to
595 ** the last '.', are used as the top-most directory name in the tar
596 ** output.
597 **
598 ** - uuid=the version to tar (may be a tag/branch name).
599 ** Defaults to "trunk".
600 **
601 */
@@ -604,11 +603,11 @@
604 char *zName, *zRid, *zKey;
605 int nName, nRid;
606 Blob tarball;
607
608 login_check_credentials();
609 if( !g.perm.Zip ){ login_needed(); return; }
610 load_control();
611 zName = mprintf("%s", PD("name",""));
612 nName = strlen(zName);
613 zRid = mprintf("%s", PD("uuid","trunk"));
614 nRid = strlen(zRid);
@@ -639,10 +638,21 @@
639 @ zName = "%h(zName)"<br>
640 @ rid = %d(rid)<br>
641 @ zKey = "%h(zKey)"
642 style_footer();
643 return;
 
 
 
 
 
 
 
 
 
 
 
644 }
645 blob_zero(&tarball);
646 if( cache_read(&tarball, zKey)==0 ){
647 tarball_of_checkin(rid, &tarball, zName);
648 cache_write(&tarball, zKey);
649
--- src/tar.c
+++ src/tar.c
@@ -285,11 +285,11 @@
285 const char *zName, /* Name of the object */
286 int nName, /* Number of characters in zName */
287 int iMode, /* Mode. 0644 or 0755 */
288 unsigned int mTime, /* File modification time */
289 int iSize, /* Size of the object in bytes */
290 char cType /* Type of object:
291 '0'==file. '2'==symlink. '5'==directory */
292 ){
293 /* set mode and modification time */
294 sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode);
295 sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime);
@@ -341,11 +341,11 @@
341 unsigned int mTime /* Modification time */
342 ){
343 int i;
344 for(i=nName-1; i>0 && zName[i]!='/'; i--){}
345 if( i<=0 ) return;
346 if( i<tball.nPrevDirAlloc
347 && strncmp(tball.zPrevDir, zName, i)==0
348 && tball.zPrevDir[i]==0 ) return;
349 db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName);
350 if( sqlite3_changes(g.db)==0 ) return;
351 tar_add_directory_of(zName, i-1, mTime);
@@ -377,11 +377,11 @@
377 char cType = '0';
378
379 /* length check moved to tar_split_path */
380 tar_add_directory_of(zName, nName, mTime);
381
382 /*
383 * If we have a symlink, write its destination path (which is stored in
384 * pContent) into header, and set content length to 0 to avoid storing path
385 * as file content in the next step. Since 'linkname' header is limited to
386 * 100 bytes (-1 byte for terminating zero), if path is greater than that,
387 * store symlink as a plain-text file. (Not sure how TAR handles long links.)
@@ -390,11 +390,11 @@
390 sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent));
391 cType = '2';
392 n = 0;
393 }
394
395 tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644,
396 mTime, n, cType);
397 if( n ){
398 gzip_step(blob_buffer(pContent), n);
399 lastPage = n % 512;
400 if( lastPage!=0 ){
@@ -428,21 +428,20 @@
428 ** that contains files given in the second and subsequent arguments.
429 */
430 void test_tarball_cmd(void){
431 int i;
432 Blob zip;
 
433 if( g.argc<3 ){
434 usage("ARCHIVE FILE....");
435 }
436 sqlite3_open(":memory:", &g.db);
437 tar_begin(-1);
438 for(i=3; i<g.argc; i++){
439 Blob file;
440 blob_zero(&file);
441 blob_read_from_file(&file, g.argv[i]);
442 tar_add_file(g.argv[i], &file, file_wd_perm(0), file_wd_mtime(0));
 
443 blob_reset(&file);
444 }
445 tar_finish(&zip);
446 blob_write_to_file(&zip, g.argv[2]);
447 }
@@ -591,11 +590,11 @@
590 ** Optional URL Parameters:
591 **
592 ** - name=NAME[.tar.gz] is base name of the output file. Defaults to
593 ** something project/version-specific. The prefix of the name, up to
594 ** the last '.', are used as the top-most directory name in the tar
595 ** output.
596 **
597 ** - uuid=the version to tar (may be a tag/branch name).
598 ** Defaults to "trunk".
599 **
600 */
@@ -604,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);
@@ -639,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
+107 -74
--- src/timeline.c
+++ src/timeline.c
@@ -21,10 +21,15 @@
2121
#include "config.h"
2222
#include <string.h>
2323
#include <time.h>
2424
#include "timeline.h"
2525
26
+/*
27
+** The value of one second in julianday notation
28
+*/
29
+#define ONE_SECOND (1.0/86400.0)
30
+
2631
/*
2732
** Add an appropriate tag to the output if "rid" is unpublished (private)
2833
*/
2934
#define UNPUB_TAG "<em>(unpublished)</em>"
3035
void tag_private_status(int rid){
@@ -36,11 +41,11 @@
3641
/*
3742
** Generate a hyperlink to a version.
3843
*/
3944
void hyperlink_to_uuid(const char *zUuid){
4045
if( g.perm.Hyperlink ){
41
- @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%S(zUuid)]</a>
46
+ @ %z(xhref("class='timelineHistLink'","%R/info/%!S",zUuid))[%S(zUuid)]</a>
4247
}else{
4348
@ <span class="timelineHistDsp">[%S(zUuid)]</span>
4449
}
4550
}
4651
@@ -158,11 +163,11 @@
158163
void test_hash_color_page(void){
159164
const char *zBr;
160165
char zNm[10];
161166
int i, cnt;
162167
login_check_credentials();
163
- if( !g.perm.Read ){ login_needed(); return; }
168
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
164169
165170
style_header("Hash Color Test");
166171
for(i=cnt=0; i<10; i++){
167172
sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
168173
zBr = P(zNm);
@@ -209,10 +214,11 @@
209214
void www_print_timeline(
210215
Stmt *pQuery, /* Query to implement the timeline */
211216
int tmFlags, /* Flags controlling display behavior */
212217
const char *zThisUser, /* Suppress links to this user */
213218
const char *zThisTag, /* Suppress links to this tag */
219
+ int selectedRid, /* Highlight the line with this RID value */
214220
void (*xExtra)(int) /* Routine to call on each line of display */
215221
){
216222
int mxWikiLen;
217223
Blob comment;
218224
int prevTagid = 0;
@@ -289,15 +295,18 @@
289295
@ event%s(suppressCnt>1?"s":"") omitted.</span>
290296
suppressCnt = 0;
291297
}
292298
if( pendingEndTr ){
293299
@ </td></tr>
300
+ if( pendingEndTr>1 ){
301
+ @ <tr class="timelineSpacer"></tr>
302
+ }
294303
pendingEndTr = 0;
295304
}
296305
if( fossil_strcmp(zType,"div")==0 ){
297306
if( !prevWasDivider ){
298
- @ <tr><td colspan="3"><hr /></td></tr>
307
+ @ <tr><td colspan="3"><hr class="timelineMarker"/></td></tr>
299308
}
300309
prevWasDivider = 1;
301310
continue;
302311
}
303312
prevWasDivider = 0;
@@ -332,11 +341,16 @@
332341
zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */
333342
zTime[pos++] = 0;
334343
}else{
335344
zTime[0] = 0;
336345
}
337
- if( rid == vid ){
346
+ pendingEndTr = 1;
347
+ if( rid==selectedRid ){
348
+ @ <tr class="timelineSpacer"></tr>
349
+ @ <tr class="timelineSelected">
350
+ pendingEndTr = 2;
351
+ }else if( rid==vid ){
338352
@ <tr class="timelineCurrent">
339353
}else {
340354
@ <tr>
341355
}
342356
@ <td class="timelineTime">%s(zTime)</td>
@@ -379,11 +393,11 @@
379393
zUuid, isLeaf);
380394
db_reset(&qbranch);
381395
@ <div id="m%d(gidx)"></div>
382396
}
383397
@</td>
384
- if( zBgClr && zBgClr[0] ){
398
+ if( zBgClr && zBgClr[0] && rid!=selectedRid ){
385399
@ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
386400
}else{
387401
@ <td class="timelineTableCell">
388402
}
389403
if( pGraph && zType[0]!='c' ){
@@ -439,11 +453,11 @@
439453
@ (user: %h(zDispUser)%s(zTagList?",":"\051")
440454
}
441455
442456
/* Generate a "detail" link for tags. */
443457
if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
444
- @ [%z(href("%R/info/%s",zUuid))details</a>]
458
+ @ [%z(href("%R/info/%!S",zUuid))details</a>]
445459
}
446460
447461
/* Generate the "tags: TAGLIST" at the end of the comment, together
448462
** with hyperlinks to the tag list.
449463
*/
@@ -508,47 +522,55 @@
508522
int fid = db_column_int(&fchngQuery, 1);
509523
int isDel = fid==0;
510524
const char *zOldName = db_column_text(&fchngQuery, 5);
511525
const char *zOld = db_column_text(&fchngQuery, 4);
512526
const char *zNew = db_column_text(&fchngQuery, 3);
513
- const char *zUnpubTag = "";
527
+ const char *zUnpub = "";
528
+ char *zA;
529
+ char zId[20];
514530
if( !inUl ){
515531
@ <ul class="filelist">
516532
inUl = 1;
533
+ }
534
+ if( tmFlags & TIMELINE_SHOWRID ){
535
+ sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid);
536
+ }else{
537
+ zId[0] = 0;
517538
}
518539
if( (tmFlags & TIMELINE_FRENAMES)!=0 ){
519540
if( !isNew && !isDel && zOldName!=0 ){
520
- @ <li> %h(zOldName) &rarr; %h(zFilename)
541
+ @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId)
521542
}
522543
continue;
523544
}
545
+ zA = href("%R/artifact/%!S",fid?zNew:zOld);
524546
if( content_is_private(fid) ){
525
- zUnpubTag = UNPUB_TAG;
547
+ zUnpub = UNPUB_TAG;
526548
}
527549
if( isNew ){
528
- @ <li> %h(zFilename) %s(zUnpubTag) (new file) &nbsp;
529
- @ %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>
530552
}else if( isDel ){
531
- @ <li> %h(zFilename) (deleted)</li>
553
+ @ <li> %s(zA)%h(zFilename)</a> (deleted)</li>
532554
}else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
533
- @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
534
- @ %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>
535557
}else{
536558
if( zOldName!=0 ){
537
- @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
559
+ @ <li>%h(zOldName) &rarr; %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub)
538560
}else{
539
- @ <li> %h(zFilename) &nbsp; %s(zUnpubTag)
561
+ @ <li>%s(zA)%h(zFilename)</a>%s(zId) &nbsp; %s(zUnpub)
540562
}
541
- @ %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>
542564
}
565
+ fossil_free(zA);
543566
}
544567
db_reset(&fchngQuery);
545568
if( inUl ){
546569
@ </ul>
547570
}
548571
}
549
- pendingEndTr = 1;
550572
}
551573
if( suppressCnt ){
552574
@ <span class="timelineDisabled">... %d(suppressCnt) similar
553575
@ event%s(suppressCnt>1?"s":"") omitted.</span>
554576
suppressCnt = 0;
@@ -969,36 +991,23 @@
969991
}
970992
return mtime;
971993
}
972994
973995
/*
974
-** The value of one second in julianday notation
975
-*/
976
-#define ONE_SECOND (1.0/86400.0)
977
-
978
-/*
979
-** zDate is a localtime date. Insert records into the
980
-** "timeline" table to cause <hr> to be inserted before and after
981
-** entries of that date. If zDate==NULL then put dividers around
982
-** the event identified by rid.
983
-*/
984
-static void timeline_add_dividers(double rDate, int rid){
985
- char *zToDel = 0;
986
- if( rDate==0 ){
987
- rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
988
- }
989
- db_multi_exec(
990
- "INSERT INTO timeline(rid,sortby,etype)"
991
- "VALUES(-1,%.16g,'div')",
992
- rDate-ONE_SECOND
993
- );
994
- db_multi_exec(
995
- "INSERT INTO timeline(rid,sortby,etype)"
996
- "VALUES(-2,%.17g,'div')",
997
- rDate+ONE_SECOND
998
- );
999
- fossil_free(zToDel);
996
+** zDate is a localtime date. Insert records into the
997
+** "timeline" table to cause <hr> to be inserted on zDate.
998
+*/
999
+static int timeline_add_divider(double rDate){
1000
+ int rid = db_int(-1,
1001
+ "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate
1002
+ );
1003
+ if( rid>0 ) return rid;
1004
+ db_multi_exec(
1005
+ "INSERT INTO timeline(rid,sortby,etype) VALUES(-1,%.16g,'div')",
1006
+ rDate
1007
+ );
1008
+ return -1;
10001009
}
10011010
10021011
/*
10031012
** Return all possible names for file zUuid.
10041013
*/
@@ -1027,16 +1036,16 @@
10271036
/*
10281037
** Add the select/option box to the timeline submenu that is used to
10291038
** set the y= parameter that determines which elements to display
10301039
** on the timeline.
10311040
*/
1032
-static void timeline_y_submenu(void){
1041
+static void timeline_y_submenu(int isDisabled){
10331042
static int i = 0;
10341043
static const char *az[12];
10351044
if( i==0 ){
10361045
az[0] = "all";
1037
- az[1] = "All Types";
1046
+ az[1] = "Any Type";
10381047
i = 2;
10391048
if( g.perm.Read ){
10401049
az[i++] = "ci";
10411050
az[i++] = "Check-ins";
10421051
az[i++] = "g";
@@ -1055,11 +1064,11 @@
10551064
az[i++] = "Wiki";
10561065
}
10571066
assert( i<=ArraySize(az) );
10581067
}
10591068
if( i>2 ){
1060
- style_submenu_multichoice("y", i/2, az);
1069
+ style_submenu_multichoice("y", i/2, az, isDisabled);
10611070
}
10621071
}
10631072
10641073
/*
10651074
** WEBPAGE: timeline
@@ -1067,10 +1076,11 @@
10671076
** Query parameters:
10681077
**
10691078
** a=TIMEORTAG after this event
10701079
** b=TIMEORTAG before this event
10711080
** c=TIMEORTAG "circa" this event
1081
+** m=TIMEORTAG mark this event
10721082
** n=COUNT max number of events in output
10731083
** p=UUID artifact and up to COUNT parents and ancestors
10741084
** d=UUID artifact and up to COUNT descendants
10751085
** dp=UUID The same as d=UUID&p=UUID
10761086
** t=TAGID show only check-ins with the given tagid
@@ -1093,14 +1103,14 @@
10931103
** datefmt=N Override the date format
10941104
**
10951105
** p= and d= can appear individually or together. If either p= or d=
10961106
** appear, then u=, y=, a=, and b= are ignored.
10971107
**
1098
-** If a= and b= appear, only a= is used. If neither appear, the most
1099
-** recent events are chosen.
1108
+** If both a= and b= appear then both upper and lower bounds are honored.
11001109
**
1101
-** 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.
11021112
*/
11031113
void page_timeline(void){
11041114
Stmt q; /* Query used to generate the timeline */
11051115
Blob sql; /* text of SQL used to generate timeline */
11061116
Blob desc; /* Description of the timeline */
@@ -1111,10 +1121,11 @@
11111121
const char *zUser = P("u"); /* All entries by this user if not NULL */
11121122
const char *zType = PD("y","all"); /* Type of events. All if NULL */
11131123
const char *zAfter = P("a"); /* Events after this time */
11141124
const char *zBefore = P("b"); /* Events before this time */
11151125
const char *zCirca = P("c"); /* Events near this time */
1126
+ const char *zMark = P("m"); /* Mark this event or an event this time */
11161127
const char *zTagName = P("t"); /* Show events with this tag */
11171128
const char *zBrName = P("r"); /* Show events related to this tag */
11181129
const char *zSearch = P("s"); /* Search string */
11191130
const char *zUses = P("uf"); /* Only show checkins hold this file */
11201131
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
@@ -1133,10 +1144,12 @@
11331144
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
11341145
int pd_rid;
11351146
double rBefore, rAfter, rCirca; /* Boundary times */
11361147
const char *z;
11371148
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 */
11381151
11391152
/* Set number of rows to display */
11401153
z = P("n");
11411154
if( z ){
11421155
if( fossil_strcmp(z,"all")==0 ){
@@ -1162,11 +1175,11 @@
11621175
if( pd_rid ){
11631176
p_rid = d_rid = pd_rid;
11641177
}
11651178
login_check_credentials();
11661179
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1167
- login_needed();
1180
+ login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
11681181
return;
11691182
}
11701183
url_initialize(&url, "timeline");
11711184
cgi_query_parameters_to_url(&url);
11721185
if( zTagName && g.perm.Read ){
@@ -1176,14 +1189,18 @@
11761189
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
11771190
zThisTag = zBrName;
11781191
}else{
11791192
tagid = 0;
11801193
}
1194
+ if( zMark && zMark[0]==0 ){
1195
+ if( zAfter ) zMark = zAfter;
1196
+ if( zBefore ) zMark = zBefore;
1197
+ if( zCirca ) zMark = zCirca;
1198
+ }
11811199
if( tagid>0
11821200
&& db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
11831201
){
1184
- zCirca = zBefore = zAfter = 0;
11851202
nEntry = -1;
11861203
}
11871204
if( zType[0]=='a' ){
11881205
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
11891206
}else{
@@ -1206,10 +1223,11 @@
12061223
if( ufid ){
12071224
zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
12081225
db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
12091226
compute_uses_file("usesfile", ufid, 0);
12101227
zType = "ci";
1228
+ disableY = 1;
12111229
}else{
12121230
zUses = 0;
12131231
}
12141232
}
12151233
if( renameOnly ){
@@ -1216,10 +1234,11 @@
12161234
db_multi_exec(
12171235
"CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
12181236
"INSERT OR IGNORE INTO rnfile"
12191237
" SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
12201238
);
1239
+ disableY = 1;
12211240
}
12221241
12231242
style_header("Timeline");
12241243
login_anonymous_available();
12251244
timeline_temp_table();
@@ -1288,11 +1307,11 @@
12881307
if( d_rid ){
12891308
compute_descendants(d_rid, nEntry+1);
12901309
nd = db_int(0, "SELECT count(*)-1 FROM ok");
12911310
if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
12921311
if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
1293
- if( useDividers ) timeline_add_dividers(0, d_rid);
1312
+ if( useDividers ) selectedRid = d_rid;
12941313
db_multi_exec("DELETE FROM ok");
12951314
}
12961315
if( p_rid ){
12971316
compute_ancestors(p_rid, nEntry+1, 0);
12981317
np = db_int(0, "SELECT count(*)-1 FROM ok");
@@ -1299,23 +1318,24 @@
12991318
if( np>0 ){
13001319
if( nd>0 ) blob_appendf(&desc, " and ");
13011320
blob_appendf(&desc, "%d ancestors", np);
13021321
db_multi_exec("%s", blob_sql_text(&sql));
13031322
}
1304
- if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1323
+ if( useDividers ) selectedRid = p_rid;
13051324
}
13061325
blob_appendf(&desc, " of %z[%S]</a>",
1307
- href("%R/info/%s", zUuid), zUuid);
1326
+ href("%R/info/%!S", zUuid), zUuid);
13081327
if( d_rid ){
13091328
if( p_rid ){
13101329
/* If both p= and d= are set, we don't have the uuid of d yet. */
13111330
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
13121331
}
13131332
}
1314
- style_submenu_binary("v","With Files","Without Files");
1315
- style_submenu_entry("n","Lines",1);
1316
- timeline_y_submenu();
1333
+ style_submenu_entry("n","Max:",4,0);
1334
+ timeline_y_submenu(1);
1335
+ style_submenu_binary("v","With Files","Without Files",
1336
+ zType[0]!='a' && zType[0]!='c');
13171337
}else if( f_rid && g.perm.Read ){
13181338
/* If f= is present, ignore all other parameters other than n= */
13191339
char *zUuid;
13201340
db_multi_exec(
13211341
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1324,16 +1344,17 @@
13241344
"INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
13251345
f_rid, f_rid, f_rid
13261346
);
13271347
blob_append_sql(&sql, " AND event.objid IN ok");
13281348
db_multi_exec("%s", blob_sql_text(&sql));
1329
- if( useDividers ) timeline_add_dividers(0, f_rid);
1349
+ if( useDividers ) selectedRid = f_rid;
13301350
blob_appendf(&desc, "Parents and children of check-in ");
13311351
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1332
- blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1352
+ blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
13331353
tmFlags |= TIMELINE_DISJOINT;
1334
- style_submenu_binary("v","With Files","Without Files");
1354
+ style_submenu_binary("v","With Files","Without Files",
1355
+ zType[0]!='a' && zType[0]!='c');
13351356
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
13361357
timeline_submenu(&url, "Unhide", "unhide", "", 0);
13371358
}
13381359
}else{
13391360
/* Otherwise, a timeline based on a span of time */
@@ -1428,11 +1449,11 @@
14281449
}else if( zType[0]=='w' ){
14291450
zEType = "wiki edit";
14301451
}else if( zType[0]=='t' ){
14311452
zEType = "ticket change";
14321453
}else if( zType[0]=='e' ){
1433
- zEType = "event";
1454
+ zEType = "technical note";
14341455
}else if( zType[0]=='g' ){
14351456
zEType = "tag";
14361457
}
14371458
}
14381459
if( zUser ){
@@ -1463,14 +1484,18 @@
14631484
}else{
14641485
blob_append_sql(&sql,
14651486
" AND event.mtime>=%.17g ORDER BY event.mtime ASC",
14661487
rAfter-ONE_SECOND);
14671488
}
1489
+ zCirca = 0;
1490
+ url_add_parameter(&url, "c", 0);
14681491
}else if( rBefore>0.0 ){
14691492
blob_append_sql(&sql,
14701493
" AND event.mtime<=%.17g ORDER BY event.mtime DESC",
14711494
rBefore+ONE_SECOND);
1495
+ zCirca = 0;
1496
+ url_add_parameter(&url, "c", 0);
14721497
}else if( rCirca>0.0 ){
14731498
Blob sql2;
14741499
blob_init(&sql2, blob_sql_text(&sql), -1);
14751500
blob_append_sql(&sql2,
14761501
" AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1481,11 +1506,11 @@
14811506
blob_append_sql(&sql,
14821507
" AND event.mtime>=%f ORDER BY event.mtime ASC",
14831508
rCirca
14841509
);
14851510
nEntry -= (nEntry+1)/2;
1486
- if( useDividers ) timeline_add_dividers(rCirca, 0);
1511
+ if( zMark==0 ) zMark = zCirca;
14871512
}else{
14881513
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
14891514
}
14901515
if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
14911516
db_multi_exec("%s", blob_sql_text(&sql));
@@ -1493,19 +1518,19 @@
14931518
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
14941519
if( zYearMonth ){
14951520
blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
14961521
}else if( zYearWeek ){
14971522
blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1498
- }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1523
+ }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
14991524
blob_appendf(&desc, "%d most recent %ss", n, zEType);
15001525
}else{
15011526
blob_appendf(&desc, "%d %ss", n, zEType);
15021527
}
15031528
if( zUses ){
15041529
char *zFilenames = names_of_file(zUses);
15051530
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1506
- href("%R/artifact/%s",zUses), zUses);
1531
+ href("%R/artifact/%!S",zUses), zUses);
15071532
tmFlags |= TIMELINE_DISJOINT;
15081533
}
15091534
if( renameOnly ){
15101535
blob_appendf(&desc, " that contain filename changes");
15111536
tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1551,27 +1576,32 @@
15511576
if( zType[0]=='a' || zType[0]=='c' ){
15521577
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
15531578
timeline_submenu(&url, "Unhide", "unhide", "", 0);
15541579
}
15551580
}
1556
- style_submenu_binary("v","With Files","Without Files");
1557
- if( zUses==0 ) timeline_y_submenu();
1558
- style_submenu_entry("n","Lines",1);
1581
+ style_submenu_entry("n","Max:",4,0);
1582
+ timeline_y_submenu(disableY);
1583
+ style_submenu_binary("v","With Files","Without Files",
1584
+ zType[0]!='a' && zType[0]!='c');
15591585
}
15601586
}
1561
- if( P("showsql") ){
1587
+ if( PB("showsql") ){
15621588
@ <blockquote>%h(blob_sql_text(&sql))</blockquote>
15631589
}
15641590
if( search_restrict(SRCH_CKIN)!=0 ){
15651591
style_submenu_element("Search", 0, "%R/search?y=c");
15661592
}
1567
- if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
1593
+ if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID;
1594
+ if( useDividers && zMark && zMark[0] ){
1595
+ double r = symbolic_name_to_mtime(zMark);
1596
+ if( r>0.0 ) selectedRid = timeline_add_divider(r);
1597
+ }
15681598
blob_zero(&sql);
15691599
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
15701600
@ <h2>%b(&desc)</h2>
15711601
blob_reset(&desc);
1572
- www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1602
+ www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
15731603
db_finalize(&q);
15741604
if( zOlderButton ){
15751605
@ %z(xhref("class='button'","%z",zOlderButton))Older</a>
15761606
}
15771607
style_footer();
@@ -1785,11 +1815,11 @@
17851815
** -p|--path PATH Output items affecting PATH only.
17861816
** PATH can be a file or a sub directory.
17871817
** --offset P skip P changes
17881818
** -t|--type TYPE Output items from the given types only, such as:
17891819
** ci = file commits only
1790
-** e = events only
1820
+** e = technical notes only
17911821
** t = tickets only
17921822
** w = wiki commits only
17931823
** -v|--verbose Output the list of files changed by each commit
17941824
** and the type of each change (edited, deleted,
17951825
** etc.) after the checkin comment.
@@ -2038,11 +2068,14 @@
20382068
*/
20392069
void test_timewarp_page(void){
20402070
Stmt q;
20412071
20422072
login_check_credentials();
2043
- if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
2073
+ if( !g.perm.Read || !g.perm.Hyperlink ){
2074
+ login_needed(g.anon.Read && g.anon.Hyperlink);
2075
+ return;
2076
+ }
20442077
style_header("Instances of timewarp");
20452078
@ <ul>
20462079
db_prepare(&q,
20472080
"SELECT blob.uuid "
20482081
" FROM plink p, plink c, blob"
@@ -2050,10 +2083,10 @@
20502083
" AND blob.rid=c.cid"
20512084
);
20522085
while( db_step(&q)==SQLITE_ROW ){
20532086
const char *zUuid = db_column_text(&q, 0);
20542087
@ <li>
2055
- @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2088
+ @ <a href="%R/timeline?dp=%!S(zUuid)&amp;unhide">%S(zUuid)</a>
20562089
}
20572090
db_finalize(&q);
20582091
style_footer();
20592092
}
20602093
--- src/timeline.c
+++ src/timeline.c
@@ -21,10 +21,15 @@
21 #include "config.h"
22 #include <string.h>
23 #include <time.h>
24 #include "timeline.h"
25
 
 
 
 
 
26 /*
27 ** Add an appropriate tag to the output if "rid" is unpublished (private)
28 */
29 #define UNPUB_TAG "<em>(unpublished)</em>"
30 void tag_private_status(int rid){
@@ -36,11 +41,11 @@
36 /*
37 ** Generate a hyperlink to a version.
38 */
39 void hyperlink_to_uuid(const char *zUuid){
40 if( g.perm.Hyperlink ){
41 @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%S(zUuid)]</a>
42 }else{
43 @ <span class="timelineHistDsp">[%S(zUuid)]</span>
44 }
45 }
46
@@ -158,11 +163,11 @@
158 void test_hash_color_page(void){
159 const char *zBr;
160 char zNm[10];
161 int i, cnt;
162 login_check_credentials();
163 if( !g.perm.Read ){ login_needed(); return; }
164
165 style_header("Hash Color Test");
166 for(i=cnt=0; i<10; i++){
167 sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
168 zBr = P(zNm);
@@ -209,10 +214,11 @@
209 void www_print_timeline(
210 Stmt *pQuery, /* Query to implement the timeline */
211 int tmFlags, /* Flags controlling display behavior */
212 const char *zThisUser, /* Suppress links to this user */
213 const char *zThisTag, /* Suppress links to this tag */
 
214 void (*xExtra)(int) /* Routine to call on each line of display */
215 ){
216 int mxWikiLen;
217 Blob comment;
218 int prevTagid = 0;
@@ -289,15 +295,18 @@
289 @ event%s(suppressCnt>1?"s":"") omitted.</span>
290 suppressCnt = 0;
291 }
292 if( pendingEndTr ){
293 @ </td></tr>
 
 
 
294 pendingEndTr = 0;
295 }
296 if( fossil_strcmp(zType,"div")==0 ){
297 if( !prevWasDivider ){
298 @ <tr><td colspan="3"><hr /></td></tr>
299 }
300 prevWasDivider = 1;
301 continue;
302 }
303 prevWasDivider = 0;
@@ -332,11 +341,16 @@
332 zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */
333 zTime[pos++] = 0;
334 }else{
335 zTime[0] = 0;
336 }
337 if( rid == vid ){
 
 
 
 
 
338 @ <tr class="timelineCurrent">
339 }else {
340 @ <tr>
341 }
342 @ <td class="timelineTime">%s(zTime)</td>
@@ -379,11 +393,11 @@
379 zUuid, isLeaf);
380 db_reset(&qbranch);
381 @ <div id="m%d(gidx)"></div>
382 }
383 @</td>
384 if( zBgClr && zBgClr[0] ){
385 @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
386 }else{
387 @ <td class="timelineTableCell">
388 }
389 if( pGraph && zType[0]!='c' ){
@@ -439,11 +453,11 @@
439 @ (user: %h(zDispUser)%s(zTagList?",":"\051")
440 }
441
442 /* Generate a "detail" link for tags. */
443 if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
444 @ [%z(href("%R/info/%s",zUuid))details</a>]
445 }
446
447 /* Generate the "tags: TAGLIST" at the end of the comment, together
448 ** with hyperlinks to the tag list.
449 */
@@ -508,47 +522,55 @@
508 int fid = db_column_int(&fchngQuery, 1);
509 int isDel = fid==0;
510 const char *zOldName = db_column_text(&fchngQuery, 5);
511 const char *zOld = db_column_text(&fchngQuery, 4);
512 const char *zNew = db_column_text(&fchngQuery, 3);
513 const char *zUnpubTag = "";
 
 
514 if( !inUl ){
515 @ <ul class="filelist">
516 inUl = 1;
 
 
 
 
 
517 }
518 if( (tmFlags & TIMELINE_FRENAMES)!=0 ){
519 if( !isNew && !isDel && zOldName!=0 ){
520 @ <li> %h(zOldName) &rarr; %h(zFilename)
521 }
522 continue;
523 }
 
524 if( content_is_private(fid) ){
525 zUnpubTag = UNPUB_TAG;
526 }
527 if( isNew ){
528 @ <li> %h(zFilename) %s(zUnpubTag) (new file) &nbsp;
529 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
530 }else if( isDel ){
531 @ <li> %h(zFilename) (deleted)</li>
532 }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
533 @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
534 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
535 }else{
536 if( zOldName!=0 ){
537 @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
538 }else{
539 @ <li> %h(zFilename) &nbsp; %s(zUnpubTag)
540 }
541 @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li>
542 }
 
543 }
544 db_reset(&fchngQuery);
545 if( inUl ){
546 @ </ul>
547 }
548 }
549 pendingEndTr = 1;
550 }
551 if( suppressCnt ){
552 @ <span class="timelineDisabled">... %d(suppressCnt) similar
553 @ event%s(suppressCnt>1?"s":"") omitted.</span>
554 suppressCnt = 0;
@@ -969,36 +991,23 @@
969 }
970 return mtime;
971 }
972
973 /*
974 ** The value of one second in julianday notation
975 */
976 #define ONE_SECOND (1.0/86400.0)
977
978 /*
979 ** zDate is a localtime date. Insert records into the
980 ** "timeline" table to cause <hr> to be inserted before and after
981 ** entries of that date. If zDate==NULL then put dividers around
982 ** the event identified by rid.
983 */
984 static void timeline_add_dividers(double rDate, int rid){
985 char *zToDel = 0;
986 if( rDate==0 ){
987 rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
988 }
989 db_multi_exec(
990 "INSERT INTO timeline(rid,sortby,etype)"
991 "VALUES(-1,%.16g,'div')",
992 rDate-ONE_SECOND
993 );
994 db_multi_exec(
995 "INSERT INTO timeline(rid,sortby,etype)"
996 "VALUES(-2,%.17g,'div')",
997 rDate+ONE_SECOND
998 );
999 fossil_free(zToDel);
1000 }
1001
1002 /*
1003 ** Return all possible names for file zUuid.
1004 */
@@ -1027,16 +1036,16 @@
1027 /*
1028 ** Add the select/option box to the timeline submenu that is used to
1029 ** set the y= parameter that determines which elements to display
1030 ** on the timeline.
1031 */
1032 static void timeline_y_submenu(void){
1033 static int i = 0;
1034 static const char *az[12];
1035 if( i==0 ){
1036 az[0] = "all";
1037 az[1] = "All Types";
1038 i = 2;
1039 if( g.perm.Read ){
1040 az[i++] = "ci";
1041 az[i++] = "Check-ins";
1042 az[i++] = "g";
@@ -1055,11 +1064,11 @@
1055 az[i++] = "Wiki";
1056 }
1057 assert( i<=ArraySize(az) );
1058 }
1059 if( i>2 ){
1060 style_submenu_multichoice("y", i/2, az);
1061 }
1062 }
1063
1064 /*
1065 ** WEBPAGE: timeline
@@ -1067,10 +1076,11 @@
1067 ** Query parameters:
1068 **
1069 ** a=TIMEORTAG after this event
1070 ** b=TIMEORTAG before this event
1071 ** c=TIMEORTAG "circa" this event
 
1072 ** n=COUNT max number of events in output
1073 ** p=UUID artifact and up to COUNT parents and ancestors
1074 ** d=UUID artifact and up to COUNT descendants
1075 ** dp=UUID The same as d=UUID&p=UUID
1076 ** t=TAGID show only check-ins with the given tagid
@@ -1093,14 +1103,14 @@
1093 ** datefmt=N Override the date format
1094 **
1095 ** p= and d= can appear individually or together. If either p= or d=
1096 ** appear, then u=, y=, a=, and b= are ignored.
1097 **
1098 ** If a= and b= appear, only a= is used. If neither appear, the most
1099 ** recent events are chosen.
1100 **
1101 ** If n= is missing, the default count is 20.
 
1102 */
1103 void page_timeline(void){
1104 Stmt q; /* Query used to generate the timeline */
1105 Blob sql; /* text of SQL used to generate timeline */
1106 Blob desc; /* Description of the timeline */
@@ -1111,10 +1121,11 @@
1111 const char *zUser = P("u"); /* All entries by this user if not NULL */
1112 const char *zType = PD("y","all"); /* Type of events. All if NULL */
1113 const char *zAfter = P("a"); /* Events after this time */
1114 const char *zBefore = P("b"); /* Events before this time */
1115 const char *zCirca = P("c"); /* Events near this time */
 
1116 const char *zTagName = P("t"); /* Show events with this tag */
1117 const char *zBrName = P("r"); /* Show events related to this tag */
1118 const char *zSearch = P("s"); /* Search string */
1119 const char *zUses = P("uf"); /* Only show checkins hold this file */
1120 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
@@ -1133,10 +1144,12 @@
1133 int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
1134 int pd_rid;
1135 double rBefore, rAfter, rCirca; /* Boundary times */
1136 const char *z;
1137 char *zOlderButton = 0; /* URL for Older button at the bottom */
 
 
1138
1139 /* Set number of rows to display */
1140 z = P("n");
1141 if( z ){
1142 if( fossil_strcmp(z,"all")==0 ){
@@ -1162,11 +1175,11 @@
1162 if( pd_rid ){
1163 p_rid = d_rid = pd_rid;
1164 }
1165 login_check_credentials();
1166 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1167 login_needed();
1168 return;
1169 }
1170 url_initialize(&url, "timeline");
1171 cgi_query_parameters_to_url(&url);
1172 if( zTagName && g.perm.Read ){
@@ -1176,14 +1189,18 @@
1176 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1177 zThisTag = zBrName;
1178 }else{
1179 tagid = 0;
1180 }
 
 
 
 
 
1181 if( tagid>0
1182 && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1183 ){
1184 zCirca = zBefore = zAfter = 0;
1185 nEntry = -1;
1186 }
1187 if( zType[0]=='a' ){
1188 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1189 }else{
@@ -1206,10 +1223,11 @@
1206 if( ufid ){
1207 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1208 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1209 compute_uses_file("usesfile", ufid, 0);
1210 zType = "ci";
 
1211 }else{
1212 zUses = 0;
1213 }
1214 }
1215 if( renameOnly ){
@@ -1216,10 +1234,11 @@
1216 db_multi_exec(
1217 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1218 "INSERT OR IGNORE INTO rnfile"
1219 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1220 );
 
1221 }
1222
1223 style_header("Timeline");
1224 login_anonymous_available();
1225 timeline_temp_table();
@@ -1288,11 +1307,11 @@
1288 if( d_rid ){
1289 compute_descendants(d_rid, nEntry+1);
1290 nd = db_int(0, "SELECT count(*)-1 FROM ok");
1291 if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
1292 if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
1293 if( useDividers ) timeline_add_dividers(0, d_rid);
1294 db_multi_exec("DELETE FROM ok");
1295 }
1296 if( p_rid ){
1297 compute_ancestors(p_rid, nEntry+1, 0);
1298 np = db_int(0, "SELECT count(*)-1 FROM ok");
@@ -1299,23 +1318,24 @@
1299 if( np>0 ){
1300 if( nd>0 ) blob_appendf(&desc, " and ");
1301 blob_appendf(&desc, "%d ancestors", np);
1302 db_multi_exec("%s", blob_sql_text(&sql));
1303 }
1304 if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1305 }
1306 blob_appendf(&desc, " of %z[%S]</a>",
1307 href("%R/info/%s", zUuid), zUuid);
1308 if( d_rid ){
1309 if( p_rid ){
1310 /* If both p= and d= are set, we don't have the uuid of d yet. */
1311 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1312 }
1313 }
1314 style_submenu_binary("v","With Files","Without Files");
1315 style_submenu_entry("n","Lines",1);
1316 timeline_y_submenu();
 
1317 }else if( f_rid && g.perm.Read ){
1318 /* If f= is present, ignore all other parameters other than n= */
1319 char *zUuid;
1320 db_multi_exec(
1321 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1324,16 +1344,17 @@
1324 "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
1325 f_rid, f_rid, f_rid
1326 );
1327 blob_append_sql(&sql, " AND event.objid IN ok");
1328 db_multi_exec("%s", blob_sql_text(&sql));
1329 if( useDividers ) timeline_add_dividers(0, f_rid);
1330 blob_appendf(&desc, "Parents and children of check-in ");
1331 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1332 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1333 tmFlags |= TIMELINE_DISJOINT;
1334 style_submenu_binary("v","With Files","Without Files");
 
1335 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1336 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1337 }
1338 }else{
1339 /* Otherwise, a timeline based on a span of time */
@@ -1428,11 +1449,11 @@
1428 }else if( zType[0]=='w' ){
1429 zEType = "wiki edit";
1430 }else if( zType[0]=='t' ){
1431 zEType = "ticket change";
1432 }else if( zType[0]=='e' ){
1433 zEType = "event";
1434 }else if( zType[0]=='g' ){
1435 zEType = "tag";
1436 }
1437 }
1438 if( zUser ){
@@ -1463,14 +1484,18 @@
1463 }else{
1464 blob_append_sql(&sql,
1465 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
1466 rAfter-ONE_SECOND);
1467 }
 
 
1468 }else if( rBefore>0.0 ){
1469 blob_append_sql(&sql,
1470 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
1471 rBefore+ONE_SECOND);
 
 
1472 }else if( rCirca>0.0 ){
1473 Blob sql2;
1474 blob_init(&sql2, blob_sql_text(&sql), -1);
1475 blob_append_sql(&sql2,
1476 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1481,11 +1506,11 @@
1481 blob_append_sql(&sql,
1482 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1483 rCirca
1484 );
1485 nEntry -= (nEntry+1)/2;
1486 if( useDividers ) timeline_add_dividers(rCirca, 0);
1487 }else{
1488 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1489 }
1490 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1491 db_multi_exec("%s", blob_sql_text(&sql));
@@ -1493,19 +1518,19 @@
1493 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1494 if( zYearMonth ){
1495 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1496 }else if( zYearWeek ){
1497 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1498 }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1499 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1500 }else{
1501 blob_appendf(&desc, "%d %ss", n, zEType);
1502 }
1503 if( zUses ){
1504 char *zFilenames = names_of_file(zUses);
1505 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1506 href("%R/artifact/%s",zUses), zUses);
1507 tmFlags |= TIMELINE_DISJOINT;
1508 }
1509 if( renameOnly ){
1510 blob_appendf(&desc, " that contain filename changes");
1511 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1551,27 +1576,32 @@
1551 if( zType[0]=='a' || zType[0]=='c' ){
1552 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1553 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1554 }
1555 }
1556 style_submenu_binary("v","With Files","Without Files");
1557 if( zUses==0 ) timeline_y_submenu();
1558 style_submenu_entry("n","Lines",1);
 
1559 }
1560 }
1561 if( P("showsql") ){
1562 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1563 }
1564 if( search_restrict(SRCH_CKIN)!=0 ){
1565 style_submenu_element("Search", 0, "%R/search?y=c");
1566 }
1567 if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
 
 
 
 
1568 blob_zero(&sql);
1569 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1570 @ <h2>%b(&desc)</h2>
1571 blob_reset(&desc);
1572 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1573 db_finalize(&q);
1574 if( zOlderButton ){
1575 @ %z(xhref("class='button'","%z",zOlderButton))Older</a>
1576 }
1577 style_footer();
@@ -1785,11 +1815,11 @@
1785 ** -p|--path PATH Output items affecting PATH only.
1786 ** PATH can be a file or a sub directory.
1787 ** --offset P skip P changes
1788 ** -t|--type TYPE Output items from the given types only, such as:
1789 ** ci = file commits only
1790 ** e = events only
1791 ** t = tickets only
1792 ** w = wiki commits only
1793 ** -v|--verbose Output the list of files changed by each commit
1794 ** and the type of each change (edited, deleted,
1795 ** etc.) after the checkin comment.
@@ -2038,11 +2068,14 @@
2038 */
2039 void test_timewarp_page(void){
2040 Stmt q;
2041
2042 login_check_credentials();
2043 if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
 
 
 
2044 style_header("Instances of timewarp");
2045 @ <ul>
2046 db_prepare(&q,
2047 "SELECT blob.uuid "
2048 " FROM plink p, plink c, blob"
@@ -2050,10 +2083,10 @@
2050 " AND blob.rid=c.cid"
2051 );
2052 while( db_step(&q)==SQLITE_ROW ){
2053 const char *zUuid = db_column_text(&q, 0);
2054 @ <li>
2055 @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2056 }
2057 db_finalize(&q);
2058 style_footer();
2059 }
2060
--- src/timeline.c
+++ src/timeline.c
@@ -21,10 +21,15 @@
21 #include "config.h"
22 #include <string.h>
23 #include <time.h>
24 #include "timeline.h"
25
26 /*
27 ** The value of one second in julianday notation
28 */
29 #define ONE_SECOND (1.0/86400.0)
30
31 /*
32 ** Add an appropriate tag to the output if "rid" is unpublished (private)
33 */
34 #define UNPUB_TAG "<em>(unpublished)</em>"
35 void tag_private_status(int rid){
@@ -36,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
@@ -158,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);
@@ -209,10 +214,11 @@
214 void www_print_timeline(
215 Stmt *pQuery, /* Query to implement the timeline */
216 int tmFlags, /* Flags controlling display behavior */
217 const char *zThisUser, /* Suppress links to this user */
218 const char *zThisTag, /* Suppress links to this tag */
219 int selectedRid, /* Highlight the line with this RID value */
220 void (*xExtra)(int) /* Routine to call on each line of display */
221 ){
222 int mxWikiLen;
223 Blob comment;
224 int prevTagid = 0;
@@ -289,15 +295,18 @@
295 @ event%s(suppressCnt>1?"s":"") omitted.</span>
296 suppressCnt = 0;
297 }
298 if( pendingEndTr ){
299 @ </td></tr>
300 if( pendingEndTr>1 ){
301 @ <tr class="timelineSpacer"></tr>
302 }
303 pendingEndTr = 0;
304 }
305 if( fossil_strcmp(zType,"div")==0 ){
306 if( !prevWasDivider ){
307 @ <tr><td colspan="3"><hr class="timelineMarker"/></td></tr>
308 }
309 prevWasDivider = 1;
310 continue;
311 }
312 prevWasDivider = 0;
@@ -332,11 +341,16 @@
341 zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */
342 zTime[pos++] = 0;
343 }else{
344 zTime[0] = 0;
345 }
346 pendingEndTr = 1;
347 if( rid==selectedRid ){
348 @ <tr class="timelineSpacer"></tr>
349 @ <tr class="timelineSelected">
350 pendingEndTr = 2;
351 }else if( rid==vid ){
352 @ <tr class="timelineCurrent">
353 }else {
354 @ <tr>
355 }
356 @ <td class="timelineTime">%s(zTime)</td>
@@ -379,11 +393,11 @@
393 zUuid, isLeaf);
394 db_reset(&qbranch);
395 @ <div id="m%d(gidx)"></div>
396 }
397 @</td>
398 if( zBgClr && zBgClr[0] && rid!=selectedRid ){
399 @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
400 }else{
401 @ <td class="timelineTableCell">
402 }
403 if( pGraph && zType[0]!='c' ){
@@ -439,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 */
@@ -508,47 +522,55 @@
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 }
534 if( tmFlags & TIMELINE_SHOWRID ){
535 sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid);
536 }else{
537 zId[0] = 0;
538 }
539 if( (tmFlags & TIMELINE_FRENAMES)!=0 ){
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 }
571 }
 
572 }
573 if( suppressCnt ){
574 @ <span class="timelineDisabled">... %d(suppressCnt) similar
575 @ event%s(suppressCnt>1?"s":"") omitted.</span>
576 suppressCnt = 0;
@@ -969,36 +991,23 @@
991 }
992 return mtime;
993 }
994
995 /*
996 ** zDate is a localtime date. Insert records into the
997 ** "timeline" table to cause <hr> to be inserted on zDate.
998 */
999 static int timeline_add_divider(double rDate){
1000 int rid = db_int(-1,
1001 "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate
1002 );
1003 if( rid>0 ) return rid;
1004 db_multi_exec(
1005 "INSERT INTO timeline(rid,sortby,etype) VALUES(-1,%.16g,'div')",
1006 rDate
1007 );
1008 return -1;
 
 
 
 
 
 
 
 
 
 
 
 
 
1009 }
1010
1011 /*
1012 ** Return all possible names for file zUuid.
1013 */
@@ -1027,16 +1036,16 @@
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";
1047 i = 2;
1048 if( g.perm.Read ){
1049 az[i++] = "ci";
1050 az[i++] = "Check-ins";
1051 az[i++] = "g";
@@ -1055,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
@@ -1067,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
@@ -1093,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 */
@@ -1111,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 */
@@ -1133,10 +1144,12 @@
1144 int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
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 ){
@@ -1162,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 ){
@@ -1176,14 +1189,18 @@
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 }
1204 if( zType[0]=='a' ){
1205 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1206 }else{
@@ -1206,10 +1223,11 @@
1223 if( ufid ){
1224 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1225 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1226 compute_uses_file("usesfile", ufid, 0);
1227 zType = "ci";
1228 disableY = 1;
1229 }else{
1230 zUses = 0;
1231 }
1232 }
1233 if( renameOnly ){
@@ -1216,10 +1234,11 @@
1234 db_multi_exec(
1235 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1236 "INSERT OR IGNORE INTO rnfile"
1237 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1238 );
1239 disableY = 1;
1240 }
1241
1242 style_header("Timeline");
1243 login_anonymous_available();
1244 timeline_temp_table();
@@ -1288,11 +1307,11 @@
1307 if( d_rid ){
1308 compute_descendants(d_rid, nEntry+1);
1309 nd = db_int(0, "SELECT count(*)-1 FROM ok");
1310 if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
1311 if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
1312 if( useDividers ) selectedRid = d_rid;
1313 db_multi_exec("DELETE FROM ok");
1314 }
1315 if( p_rid ){
1316 compute_ancestors(p_rid, nEntry+1, 0);
1317 np = db_int(0, "SELECT count(*)-1 FROM ok");
@@ -1299,23 +1318,24 @@
1318 if( np>0 ){
1319 if( nd>0 ) blob_appendf(&desc, " and ");
1320 blob_appendf(&desc, "%d ancestors", np);
1321 db_multi_exec("%s", blob_sql_text(&sql));
1322 }
1323 if( useDividers ) selectedRid = p_rid;
1324 }
1325 blob_appendf(&desc, " of %z[%S]</a>",
1326 href("%R/info/%!S", zUuid), zUuid);
1327 if( d_rid ){
1328 if( p_rid ){
1329 /* If both p= and d= are set, we don't have the uuid of d yet. */
1330 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1331 }
1332 }
1333 style_submenu_entry("n","Max:",4,0);
1334 timeline_y_submenu(1);
1335 style_submenu_binary("v","With Files","Without Files",
1336 zType[0]!='a' && zType[0]!='c');
1337 }else if( f_rid && g.perm.Read ){
1338 /* If f= is present, ignore all other parameters other than n= */
1339 char *zUuid;
1340 db_multi_exec(
1341 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1324,16 +1344,17 @@
1344 "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
1345 f_rid, f_rid, f_rid
1346 );
1347 blob_append_sql(&sql, " AND event.objid IN ok");
1348 db_multi_exec("%s", blob_sql_text(&sql));
1349 if( useDividers ) selectedRid = f_rid;
1350 blob_appendf(&desc, "Parents and children of check-in ");
1351 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1352 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
1353 tmFlags |= TIMELINE_DISJOINT;
1354 style_submenu_binary("v","With Files","Without Files",
1355 zType[0]!='a' && zType[0]!='c');
1356 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1357 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1358 }
1359 }else{
1360 /* Otherwise, a timeline based on a span of time */
@@ -1428,11 +1449,11 @@
1449 }else if( zType[0]=='w' ){
1450 zEType = "wiki edit";
1451 }else if( zType[0]=='t' ){
1452 zEType = "ticket change";
1453 }else if( zType[0]=='e' ){
1454 zEType = "technical note";
1455 }else if( zType[0]=='g' ){
1456 zEType = "tag";
1457 }
1458 }
1459 if( zUser ){
@@ -1463,14 +1484,18 @@
1484 }else{
1485 blob_append_sql(&sql,
1486 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
1487 rAfter-ONE_SECOND);
1488 }
1489 zCirca = 0;
1490 url_add_parameter(&url, "c", 0);
1491 }else if( rBefore>0.0 ){
1492 blob_append_sql(&sql,
1493 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
1494 rBefore+ONE_SECOND);
1495 zCirca = 0;
1496 url_add_parameter(&url, "c", 0);
1497 }else if( rCirca>0.0 ){
1498 Blob sql2;
1499 blob_init(&sql2, blob_sql_text(&sql), -1);
1500 blob_append_sql(&sql2,
1501 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1481,11 +1506,11 @@
1506 blob_append_sql(&sql,
1507 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1508 rCirca
1509 );
1510 nEntry -= (nEntry+1)/2;
1511 if( zMark==0 ) zMark = zCirca;
1512 }else{
1513 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1514 }
1515 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1516 db_multi_exec("%s", blob_sql_text(&sql));
@@ -1493,19 +1518,19 @@
1518 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1519 if( zYearMonth ){
1520 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1521 }else if( zYearWeek ){
1522 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1523 }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1524 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1525 }else{
1526 blob_appendf(&desc, "%d %ss", n, zEType);
1527 }
1528 if( zUses ){
1529 char *zFilenames = names_of_file(zUses);
1530 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1531 href("%R/artifact/%!S",zUses), zUses);
1532 tmFlags |= TIMELINE_DISJOINT;
1533 }
1534 if( renameOnly ){
1535 blob_appendf(&desc, " that contain filename changes");
1536 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1551,27 +1576,32 @@
1576 if( zType[0]=='a' || zType[0]=='c' ){
1577 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1578 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1579 }
1580 }
1581 style_submenu_entry("n","Max:",4,0);
1582 timeline_y_submenu(disableY);
1583 style_submenu_binary("v","With Files","Without Files",
1584 zType[0]!='a' && zType[0]!='c');
1585 }
1586 }
1587 if( PB("showsql") ){
1588 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1589 }
1590 if( search_restrict(SRCH_CKIN)!=0 ){
1591 style_submenu_element("Search", 0, "%R/search?y=c");
1592 }
1593 if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID;
1594 if( useDividers && zMark && zMark[0] ){
1595 double r = symbolic_name_to_mtime(zMark);
1596 if( r>0.0 ) selectedRid = timeline_add_divider(r);
1597 }
1598 blob_zero(&sql);
1599 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1600 @ <h2>%b(&desc)</h2>
1601 blob_reset(&desc);
1602 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
1603 db_finalize(&q);
1604 if( zOlderButton ){
1605 @ %z(xhref("class='button'","%z",zOlderButton))Older</a>
1606 }
1607 style_footer();
@@ -1785,11 +1815,11 @@
1815 ** -p|--path PATH Output items affecting PATH only.
1816 ** PATH can be a file or a sub directory.
1817 ** --offset P skip P changes
1818 ** -t|--type TYPE Output items from the given types only, such as:
1819 ** ci = file commits only
1820 ** e = technical notes only
1821 ** t = tickets only
1822 ** w = wiki commits only
1823 ** -v|--verbose Output the list of files changed by each commit
1824 ** and the type of each change (edited, deleted,
1825 ** etc.) after the checkin comment.
@@ -2038,11 +2068,14 @@
2068 */
2069 void test_timewarp_page(void){
2070 Stmt q;
2071
2072 login_check_credentials();
2073 if( !g.perm.Read || !g.perm.Hyperlink ){
2074 login_needed(g.anon.Read && g.anon.Hyperlink);
2075 return;
2076 }
2077 style_header("Instances of timewarp");
2078 @ <ul>
2079 db_prepare(&q,
2080 "SELECT blob.uuid "
2081 " FROM plink p, plink c, blob"
@@ -2050,10 +2083,10 @@
2083 " AND blob.rid=c.cid"
2084 );
2085 while( db_step(&q)==SQLITE_ROW ){
2086 const char *zUuid = db_column_text(&q, 0);
2087 @ <li>
2088 @ <a href="%R/timeline?dp=%!S(zUuid)&amp;unhide">%S(zUuid)</a>
2089 }
2090 db_finalize(&q);
2091 style_footer();
2092 }
2093
+23 -14
--- 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);
@@ -893,11 +899,11 @@
893899
timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
894900
);
895901
}
896902
db_prepare(&q, "%z", zSQL/*safe-for-%s*/);
897903
www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH,
898
- 0, 0, 0);
904
+ 0, 0, 0, 0);
899905
db_finalize(&q);
900906
style_footer();
901907
}
902908
903909
/*
@@ -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);
@@ -893,11 +899,11 @@
893 timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
894 );
895 }
896 db_prepare(&q, "%z", zSQL/*safe-for-%s*/);
897 www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH,
898 0, 0, 0);
899 db_finalize(&q);
900 style_footer();
901 }
902
903 /*
@@ -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);
@@ -893,11 +899,11 @@
899 timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
900 );
901 }
902 db_prepare(&q, "%z", zSQL/*safe-for-%s*/);
903 www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH,
904 0, 0, 0, 0);
905 db_finalize(&q);
906 style_footer();
907 }
908
909 /*
@@ -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
+10 -10
--- src/undo.c
+++ src/undo.c
@@ -12,11 +12,11 @@
1212
** Author contact information:
1313
** [email protected]
1414
** http://www.hwaci.com/drh/
1515
**
1616
*******************************************************************************
17
-**
17
+**
1818
** This file implements the undo/redo functionality.
1919
*/
2020
#include "config.h"
2121
#include "undo.h"
2222
@@ -45,19 +45,19 @@
4545
int old_link;
4646
Blob current;
4747
Blob new;
4848
zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
4949
old_link = db_column_int(&q, 3);
50
- new_link = file_wd_islink(zFullname);
5150
new_exists = file_wd_size(zFullname)>=0;
51
+ new_link = file_wd_islink(0);
5252
if( new_exists ){
5353
if( new_link ){
5454
blob_read_link(&current, zFullname);
5555
}else{
56
- blob_read_from_file(&current, zFullname);
56
+ blob_read_from_file(&current, zFullname);
5757
}
58
- new_exe = file_wd_isexe(zFullname);
58
+ new_exe = file_wd_isexe(0);
5959
}else{
6060
blob_zero(&current);
6161
new_exe = 0;
6262
}
6363
blob_zero(&new);
@@ -86,11 +86,11 @@
8686
file_delete(zFullname);
8787
}
8888
blob_reset(&new);
8989
free(zFullname);
9090
db_finalize(&q);
91
- db_prepare(&q,
91
+ db_prepare(&q,
9292
"UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d,"
9393
" redoflag=NOT redoflag"
9494
" WHERE pathname=%Q",
9595
new_exists, new_exe, new_link, zPathname
9696
);
@@ -217,11 +217,11 @@
217217
** Begin capturing a snapshot that can be undone.
218218
*/
219219
void undo_begin(void){
220220
int cid;
221221
const char *zDb = db_name("localdb");
222
- static const char zSql[] =
222
+ static const char zSql[] =
223223
@ CREATE TABLE "%w".undo(
224224
@ pathname TEXT UNIQUE, -- Name of the file
225225
@ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable
226226
@ existsflag BOOLEAN, -- True if the file exists
227227
@ isExe BOOLEAN, -- True if the file is executable
@@ -240,11 +240,11 @@
240240
db_lset("undo_cmdline", undoCmd);
241241
undoActive = 1;
242242
}
243243
244244
/*
245
-** Permanently disable undo
245
+** Permanently disable undo
246246
*/
247247
void undo_disable(void){
248248
undoDisable = 1;
249249
}
250250
@@ -279,11 +279,11 @@
279279
" VALUES(%Q,0,%d,%d,%d,:c)",
280280
zPathname, existsFlag, file_wd_isexe(zFullname), isLink
281281
);
282282
if( existsFlag ){
283283
if( isLink ){
284
- blob_read_link(&content, zFullname);
284
+ blob_read_link(&content, zFullname);
285285
}else{
286286
blob_read_from_file(&content, zFullname);
287287
}
288288
db_bind_blob(&q, ":c", &content);
289289
}
@@ -363,11 +363,11 @@
363363
** (2) fossil merge (6) fossil stash drop
364364
** (3) fossil revert (7) fossil stash goto
365365
** (4) fossil stash pop
366366
**
367367
** If FILENAME is specified then restore the content of the named
368
-** file(s) but otherwise leave the update or merge or revert in effect.
368
+** file(s) but otherwise leave the update or merge or revert in effect.
369369
** The redo command undoes the effect of the most recent undo.
370370
**
371371
** If the -n|--dry-run option is present, no changes are made and instead
372372
** the undo or redo command explains what actions the undo or redo would
373373
** have done had the -n|--dry-run been omitted.
@@ -410,11 +410,11 @@
410410
if( nChng==0 ){
411411
fossil_print("The following file changes would occur if the "
412412
"command above is %sne:\n\n", zCmd);
413413
}
414414
nChng++;
415
- fossil_print("%s %s\n",
415
+ fossil_print("%s %s\n",
416416
db_column_int(&q,0) ? "UPDATE" : "DELETE",
417417
db_column_text(&q, 1)
418418
);
419419
}
420420
db_finalize(&q);
421421
--- src/undo.c
+++ src/undo.c
@@ -12,11 +12,11 @@
12 ** Author contact information:
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file implements the undo/redo functionality.
19 */
20 #include "config.h"
21 #include "undo.h"
22
@@ -45,19 +45,19 @@
45 int old_link;
46 Blob current;
47 Blob new;
48 zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
49 old_link = db_column_int(&q, 3);
50 new_link = file_wd_islink(zFullname);
51 new_exists = file_wd_size(zFullname)>=0;
 
52 if( new_exists ){
53 if( new_link ){
54 blob_read_link(&current, zFullname);
55 }else{
56 blob_read_from_file(&current, zFullname);
57 }
58 new_exe = file_wd_isexe(zFullname);
59 }else{
60 blob_zero(&current);
61 new_exe = 0;
62 }
63 blob_zero(&new);
@@ -86,11 +86,11 @@
86 file_delete(zFullname);
87 }
88 blob_reset(&new);
89 free(zFullname);
90 db_finalize(&q);
91 db_prepare(&q,
92 "UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d,"
93 " redoflag=NOT redoflag"
94 " WHERE pathname=%Q",
95 new_exists, new_exe, new_link, zPathname
96 );
@@ -217,11 +217,11 @@
217 ** Begin capturing a snapshot that can be undone.
218 */
219 void undo_begin(void){
220 int cid;
221 const char *zDb = db_name("localdb");
222 static const char zSql[] =
223 @ CREATE TABLE "%w".undo(
224 @ pathname TEXT UNIQUE, -- Name of the file
225 @ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable
226 @ existsflag BOOLEAN, -- True if the file exists
227 @ isExe BOOLEAN, -- True if the file is executable
@@ -240,11 +240,11 @@
240 db_lset("undo_cmdline", undoCmd);
241 undoActive = 1;
242 }
243
244 /*
245 ** Permanently disable undo
246 */
247 void undo_disable(void){
248 undoDisable = 1;
249 }
250
@@ -279,11 +279,11 @@
279 " VALUES(%Q,0,%d,%d,%d,:c)",
280 zPathname, existsFlag, file_wd_isexe(zFullname), isLink
281 );
282 if( existsFlag ){
283 if( isLink ){
284 blob_read_link(&content, zFullname);
285 }else{
286 blob_read_from_file(&content, zFullname);
287 }
288 db_bind_blob(&q, ":c", &content);
289 }
@@ -363,11 +363,11 @@
363 ** (2) fossil merge (6) fossil stash drop
364 ** (3) fossil revert (7) fossil stash goto
365 ** (4) fossil stash pop
366 **
367 ** If FILENAME is specified then restore the content of the named
368 ** file(s) but otherwise leave the update or merge or revert in effect.
369 ** The redo command undoes the effect of the most recent undo.
370 **
371 ** If the -n|--dry-run option is present, no changes are made and instead
372 ** the undo or redo command explains what actions the undo or redo would
373 ** have done had the -n|--dry-run been omitted.
@@ -410,11 +410,11 @@
410 if( nChng==0 ){
411 fossil_print("The following file changes would occur if the "
412 "command above is %sne:\n\n", zCmd);
413 }
414 nChng++;
415 fossil_print("%s %s\n",
416 db_column_int(&q,0) ? "UPDATE" : "DELETE",
417 db_column_text(&q, 1)
418 );
419 }
420 db_finalize(&q);
421
--- src/undo.c
+++ src/undo.c
@@ -12,11 +12,11 @@
12 ** Author contact information:
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file implements the undo/redo functionality.
19 */
20 #include "config.h"
21 #include "undo.h"
22
@@ -45,19 +45,19 @@
45 int old_link;
46 Blob current;
47 Blob new;
48 zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
49 old_link = db_column_int(&q, 3);
 
50 new_exists = file_wd_size(zFullname)>=0;
51 new_link = file_wd_islink(0);
52 if( new_exists ){
53 if( new_link ){
54 blob_read_link(&current, zFullname);
55 }else{
56 blob_read_from_file(&current, zFullname);
57 }
58 new_exe = file_wd_isexe(0);
59 }else{
60 blob_zero(&current);
61 new_exe = 0;
62 }
63 blob_zero(&new);
@@ -86,11 +86,11 @@
86 file_delete(zFullname);
87 }
88 blob_reset(&new);
89 free(zFullname);
90 db_finalize(&q);
91 db_prepare(&q,
92 "UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d,"
93 " redoflag=NOT redoflag"
94 " WHERE pathname=%Q",
95 new_exists, new_exe, new_link, zPathname
96 );
@@ -217,11 +217,11 @@
217 ** Begin capturing a snapshot that can be undone.
218 */
219 void undo_begin(void){
220 int cid;
221 const char *zDb = db_name("localdb");
222 static const char zSql[] =
223 @ CREATE TABLE "%w".undo(
224 @ pathname TEXT UNIQUE, -- Name of the file
225 @ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable
226 @ existsflag BOOLEAN, -- True if the file exists
227 @ isExe BOOLEAN, -- True if the file is executable
@@ -240,11 +240,11 @@
240 db_lset("undo_cmdline", undoCmd);
241 undoActive = 1;
242 }
243
244 /*
245 ** Permanently disable undo
246 */
247 void undo_disable(void){
248 undoDisable = 1;
249 }
250
@@ -279,11 +279,11 @@
279 " VALUES(%Q,0,%d,%d,%d,:c)",
280 zPathname, existsFlag, file_wd_isexe(zFullname), isLink
281 );
282 if( existsFlag ){
283 if( isLink ){
284 blob_read_link(&content, zFullname);
285 }else{
286 blob_read_from_file(&content, zFullname);
287 }
288 db_bind_blob(&q, ":c", &content);
289 }
@@ -363,11 +363,11 @@
363 ** (2) fossil merge (6) fossil stash drop
364 ** (3) fossil revert (7) fossil stash goto
365 ** (4) fossil stash pop
366 **
367 ** If FILENAME is specified then restore the content of the named
368 ** file(s) but otherwise leave the update or merge or revert in effect.
369 ** The redo command undoes the effect of the most recent undo.
370 **
371 ** If the -n|--dry-run option is present, no changes are made and instead
372 ** the undo or redo command explains what actions the undo or redo would
373 ** have done had the -n|--dry-run been omitted.
@@ -410,11 +410,11 @@
410 if( nChng==0 ){
411 fossil_print("The following file changes would occur if the "
412 "command above is %sne:\n\n", zCmd);
413 }
414 nChng++;
415 fossil_print("%s %s\n",
416 db_column_int(&q,0) ? "UPDATE" : "DELETE",
417 db_column_text(&q, 1)
418 );
419 }
420 db_finalize(&q);
421
+1 -1
--- src/update.c
+++ src/update.c
@@ -809,11 +809,11 @@
809809
zFile, zFile
810810
);
811811
}else{
812812
sqlite3_int64 mtime;
813813
undo_save(zFile);
814
- if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){
814
+ if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ){
815815
file_delete(zFull);
816816
}
817817
if( isLink ){
818818
symlink_create(blob_str(&record), zFull);
819819
}else{
820820
--- src/update.c
+++ src/update.c
@@ -809,11 +809,11 @@
809 zFile, zFile
810 );
811 }else{
812 sqlite3_int64 mtime;
813 undo_save(zFile);
814 if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){
815 file_delete(zFull);
816 }
817 if( isLink ){
818 symlink_create(blob_str(&record), zFull);
819 }else{
820
--- src/update.c
+++ src/update.c
@@ -809,11 +809,11 @@
809 zFile, zFile
810 );
811 }else{
812 sqlite3_int64 mtime;
813 undo_save(zFile);
814 if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ){
815 file_delete(zFull);
816 }
817 if( isLink ){
818 symlink_create(blob_str(&record), zFull);
819 }else{
820
+9 -2
--- src/url.c
+++ src/url.c
@@ -460,21 +460,28 @@
460460
fossil_free(p->azValue);
461461
url_initialize(p, p->zBase);
462462
}
463463
464464
/*
465
-** Add a fixed parameter to an HQuery.
465
+** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0.
466466
*/
467467
void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
468468
int i;
469469
for(i=0; i<p->nParam; i++){
470470
if( fossil_strcmp(p->azName[i],zName)==0 ){
471
- p->azValue[i] = zValue;
471
+ if( zValue==0 ){
472
+ p->nParam--;
473
+ p->azValue[i] = p->azValue[p->nParam];
474
+ p->azName[i] = p->azName[p->nParam];
475
+ }else{
476
+ p->azValue[i] = zValue;
477
+ }
472478
return;
473479
}
474480
}
475481
assert( i==p->nParam );
482
+ if( zValue==0 ) return;
476483
if( i>=p->nAlloc ){
477484
p->nAlloc = p->nAlloc*2 + 10;
478485
p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc);
479486
p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc);
480487
}
481488
--- src/url.c
+++ src/url.c
@@ -460,21 +460,28 @@
460 fossil_free(p->azValue);
461 url_initialize(p, p->zBase);
462 }
463
464 /*
465 ** Add a fixed parameter to an HQuery.
466 */
467 void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
468 int i;
469 for(i=0; i<p->nParam; i++){
470 if( fossil_strcmp(p->azName[i],zName)==0 ){
471 p->azValue[i] = zValue;
 
 
 
 
 
 
472 return;
473 }
474 }
475 assert( i==p->nParam );
 
476 if( i>=p->nAlloc ){
477 p->nAlloc = p->nAlloc*2 + 10;
478 p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc);
479 p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc);
480 }
481
--- src/url.c
+++ src/url.c
@@ -460,21 +460,28 @@
460 fossil_free(p->azValue);
461 url_initialize(p, p->zBase);
462 }
463
464 /*
465 ** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0.
466 */
467 void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
468 int i;
469 for(i=0; i<p->nParam; i++){
470 if( fossil_strcmp(p->azName[i],zName)==0 ){
471 if( zValue==0 ){
472 p->nParam--;
473 p->azValue[i] = p->azValue[p->nParam];
474 p->azName[i] = p->azName[p->nParam];
475 }else{
476 p->azValue[i] = zValue;
477 }
478 return;
479 }
480 }
481 assert( i==p->nParam );
482 if( zValue==0 ) return;
483 if( i>=p->nAlloc ){
484 p->nAlloc = p->nAlloc*2 + 10;
485 p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc);
486 p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc);
487 }
488
+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
+2 -2
--- src/vfile.c
+++ src/vfile.c
@@ -189,12 +189,12 @@
189189
zName = db_column_text(&q, 1);
190190
rid = db_column_int(&q, 2);
191191
isDeleted = db_column_int(&q, 3);
192192
oldChnged = chnged = db_column_int(&q, 4);
193193
oldMtime = db_column_int64(&q, 7);
194
- currentSize = file_wd_size(zName);
195194
origSize = db_column_int64(&q, 6);
195
+ currentSize = file_wd_size(zName);
196196
currentMtime = file_wd_mtime(0);
197197
if( chnged==0 && (isDeleted || rid==0) ){
198198
/* "fossil rm" or "fossil add" always change the file */
199199
chnged = 1;
200200
}else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
@@ -320,11 +320,11 @@
320320
if( verbose ) fossil_print("%s\n", &zName[nRepos]);
321321
if( file_wd_isdir(zName) == 1 ){
322322
/*TODO(dchest): remove directories? */
323323
fossil_fatal("%s is directory, cannot overwrite\n", zName);
324324
}
325
- if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){
325
+ if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){
326326
file_delete(zName);
327327
}
328328
if( isLink ){
329329
symlink_create(blob_str(&content), zName);
330330
}else{
331331
--- src/vfile.c
+++ src/vfile.c
@@ -189,12 +189,12 @@
189 zName = db_column_text(&q, 1);
190 rid = db_column_int(&q, 2);
191 isDeleted = db_column_int(&q, 3);
192 oldChnged = chnged = db_column_int(&q, 4);
193 oldMtime = db_column_int64(&q, 7);
194 currentSize = file_wd_size(zName);
195 origSize = db_column_int64(&q, 6);
 
196 currentMtime = file_wd_mtime(0);
197 if( chnged==0 && (isDeleted || rid==0) ){
198 /* "fossil rm" or "fossil add" always change the file */
199 chnged = 1;
200 }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
@@ -320,11 +320,11 @@
320 if( verbose ) fossil_print("%s\n", &zName[nRepos]);
321 if( file_wd_isdir(zName) == 1 ){
322 /*TODO(dchest): remove directories? */
323 fossil_fatal("%s is directory, cannot overwrite\n", zName);
324 }
325 if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){
326 file_delete(zName);
327 }
328 if( isLink ){
329 symlink_create(blob_str(&content), zName);
330 }else{
331
--- src/vfile.c
+++ src/vfile.c
@@ -189,12 +189,12 @@
189 zName = db_column_text(&q, 1);
190 rid = db_column_int(&q, 2);
191 isDeleted = db_column_int(&q, 3);
192 oldChnged = chnged = db_column_int(&q, 4);
193 oldMtime = db_column_int64(&q, 7);
 
194 origSize = db_column_int64(&q, 6);
195 currentSize = file_wd_size(zName);
196 currentMtime = file_wd_mtime(0);
197 if( chnged==0 && (isDeleted || rid==0) ){
198 /* "fossil rm" or "fossil add" always change the file */
199 chnged = 1;
200 }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){
@@ -320,11 +320,11 @@
320 if( verbose ) fossil_print("%s\n", &zName[nRepos]);
321 if( file_wd_isdir(zName) == 1 ){
322 /*TODO(dchest): remove directories? */
323 fossil_fatal("%s is directory, cannot overwrite\n", zName);
324 }
325 if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){
326 file_delete(zName);
327 }
328 if( isLink ){
329 symlink_create(blob_str(&content), zName);
330 }else{
331
+28 -23
--- src/wiki.c
+++ src/wiki.c
@@ -134,11 +134,15 @@
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 ){
@@ -222,11 +226,11 @@
222226
style_submenu_element("List","List","%R/wcontent");
223227
}
224228
if( (ok & W_HELP)!=0 ){
225229
style_submenu_element("Help","Help","%R/wikihelp");
226230
}
227
- if( (ok & W_NEW)!=0 && g.perm.NewWiki ){
231
+ if( (ok & W_NEW)!=0 && g.anon.NewWiki ){
228232
style_submenu_element("New","New","%R/wikinew");
229233
}
230234
#if 0
231235
if( (ok & W_BLOG)!=0
232236
#endif
@@ -239,11 +243,11 @@
239243
** WEBPAGE: wikihelp
240244
** A generic landing page for wiki.
241245
*/
242246
void wiki_helppage(void){
243247
login_check_credentials();
244
- if( !g.perm.RdWiki ){ login_needed(); return; }
248
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
245249
style_header("Wiki Help");
246250
wiki_standard_submenu(W_ALL_BUT(W_HELP));
247251
@ <h2>Wiki Links</h2>
248252
@ <ul>
249253
{ char *zWikiHomePageName = db_get("index-page",0);
@@ -261,19 +265,19 @@
261265
@ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
262266
@ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for
263267
@ %z(href("%R/md_rules"))Markdown Wiki</a>.</li>
264268
@ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
265269
@ to experiment.</li>
266
- if( g.perm.NewWiki ){
270
+ if( g.anon.NewWiki ){
267271
@ <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>
272
+ if( g.anon.Write ){
273
+ @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li>
270274
}
271275
}
272276
@ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
273277
@ available on this server.</li>
274
- if( g.perm.ModWiki ){
278
+ if( g.anon.ModWiki ){
275279
@ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
276280
}
277281
if( search_restrict(SRCH_WIKI)!=0 ){
278282
@ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key
279283
@ words</li>
@@ -312,11 +316,11 @@
312316
const char *zPageName;
313317
const char *zMimetype = 0;
314318
char *zBody = mprintf("%s","<i>Empty Page</i>");
315319
316320
login_check_credentials();
317
- if( !g.perm.RdWiki ){ login_needed(); return; }
321
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
318322
zPageName = P("name");
319323
if( zPageName==0 ){
320324
if( search_restrict(SRCH_WIKI)!=0 ){
321325
wiki_srchpage();
322326
}else{
@@ -355,11 +359,11 @@
355359
"%R/wdiff?name=%T&a=%d", zPageName, rid);
356360
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
357361
style_submenu_element("Details", "Details",
358362
"%R/info/%s", zUuid);
359363
}
360
- if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
364
+ if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){
361365
if( db_get_boolean("wysiwyg-wiki", 0) ){
362366
style_submenu_element("Edit", "Edit Wiki Page",
363367
"%s/wikiedit?name=%T&wysiwyg=1",
364368
g.zTop, zPageName);
365369
}else{
@@ -366,16 +370,16 @@
366370
style_submenu_element("Edit", "Edit Wiki Page",
367371
"%s/wikiedit?name=%T",
368372
g.zTop, zPageName);
369373
}
370374
}
371
- if( rid && g.perm.ApndWiki && g.perm.Attach ){
375
+ if( rid && g.anon.ApndWiki && g.anon.Attach ){
372376
style_submenu_element("Attach", "Add An Attachment",
373377
"%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
374378
g.zTop, zPageName, g.zTop, zPageName);
375379
}
376
- if( rid && g.perm.ApndWiki ){
380
+ if( rid && g.anon.ApndWiki ){
377381
style_submenu_element("Append", "Add A Comment",
378382
"%s/wikiappend?name=%T&mimetype=%s",
379383
g.zTop, zPageName, zMimetype);
380384
}
381385
if( g.perm.Hyperlink ){
@@ -423,13 +427,13 @@
423427
424428
/*
425429
** Output a selection box from which the user can select the
426430
** wiki mimetype.
427431
*/
428
-static void mimetype_option_menu(const char *zMimetype){
432
+void mimetype_option_menu(const char *zMimetype){
429433
unsigned i;
430
- @ Markup style: <select name="mimetype" size="1">
434
+ @ <select name="mimetype" size="1">
431435
for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
432436
if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
433437
@ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
434438
}else{
435439
@ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
@@ -485,11 +489,11 @@
485489
zPageName = PD("name","");
486490
if( check_name(zPageName) ) return;
487491
isSandbox = is_sandbox(zPageName);
488492
if( isSandbox ){
489493
if( !g.perm.WrWiki ){
490
- login_needed();
494
+ login_needed(g.anon.WrWiki);
491495
return;
492496
}
493497
if( zBody==0 ){
494498
zBody = db_get("sandbox","");
495499
zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
@@ -501,11 +505,11 @@
501505
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
502506
" ORDER BY mtime DESC", zTag
503507
);
504508
free(zTag);
505509
if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
506
- login_needed();
510
+ login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki);
507511
return;
508512
}
509513
if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
510514
zBody = pWiki->zWiki;
511515
zMimetype = pWiki->zMimetype;
@@ -573,11 +577,11 @@
573577
if( n<20 ) n = 20;
574578
if( n>30 ) n = 30;
575579
if( !isWysiwyg ){
576580
/* Traditional markup-only editing */
577581
form_begin(0, "%R/wikiedit");
578
- @ <div>
582
+ @ <div>Markup style:
579583
mimetype_option_menu(zMimetype);
580584
@ <br /><textarea name="w" class="wikiedit" cols="80"
581585
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
582586
@ <br />
583587
if( db_get_boolean("wysiwyg-wiki", 0) ){
@@ -625,11 +629,11 @@
625629
void wikinew_page(void){
626630
const char *zName;
627631
const char *zMimetype;
628632
login_check_credentials();
629633
if( !g.perm.NewWiki ){
630
- login_needed();
634
+ login_needed(g.anon.NewWiki);
631635
return;
632636
}
633637
zName = PD("name","");
634638
zMimetype = wiki_filter_mimetypes(P("mimetype"));
635639
if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
@@ -646,10 +650,11 @@
646650
@ <p>Rules for wiki page names:</p>
647651
well_formed_wiki_name_rules();
648652
form_begin(0, "%R/wikinew");
649653
@ <p>Name of new wiki page:
650654
@ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
655
+ @ Markup style:
651656
mimetype_option_menu("text/x-fossil-wiki");
652657
@ <br /><input type="submit" value="Create" />
653658
@ </p></form>
654659
if( zName[0] ){
655660
@ <p><span class="wikiError">
@@ -727,11 +732,11 @@
727732
fossil_redirect_home();
728733
return;
729734
}
730735
}
731736
if( !g.perm.ApndWiki ){
732
- login_needed();
737
+ login_needed(g.anon.ApndWiki);
733738
return;
734739
}
735740
if( P("submit")!=0 && P("r")!=0 && P("u")!=0
736741
&& (goodCaptcha = captcha_is_correct())
737742
){
@@ -840,11 +845,11 @@
840845
*/
841846
void whistory_page(void){
842847
Stmt q;
843848
const char *zPageName;
844849
login_check_credentials();
845
- if( !g.perm.Hyperlink ){ login_needed(); return; }
850
+ if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
846851
zPageName = PD("name","");
847852
style_header("History Of %s", zPageName);
848853
849854
db_prepare(&q, "%s AND event.objid IN "
850855
" (SELECT rid FROM tagxref WHERE tagid="
@@ -852,11 +857,11 @@
852857
" UNION SELECT attachid FROM attachment"
853858
" WHERE target=%Q)"
854859
"ORDER BY mtime DESC",
855860
timeline_query_for_www(), zPageName, zPageName);
856861
zWikiPageName = zPageName;
857
- www_print_timeline(&q, TIMELINE_ARTID, 0, 0, wiki_history_extra);
862
+ www_print_timeline(&q, TIMELINE_ARTID, 0, 0, 0, wiki_history_extra);
858863
db_finalize(&q);
859864
style_footer();
860865
}
861866
862867
/*
@@ -872,11 +877,11 @@
872877
Blob w1, w2, d;
873878
u64 diffFlags;
874879
875880
login_check_credentials();
876881
rid1 = atoi(PD("a","0"));
877
- if( !g.perm.Hyperlink ){ login_needed(); return; }
882
+ if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
878883
if( rid1==0 ) fossil_redirect_home();
879884
rid2 = atoi(PD("b","0"));
880885
zPageName = PD("name","");
881886
style_header("Changes To %s", zPageName);
882887
@@ -935,11 +940,11 @@
935940
void wcontent_page(void){
936941
Stmt q;
937942
int showAll = P("all")!=0;
938943
939944
login_check_credentials();
940
- if( !g.perm.RdWiki ){ login_needed(); return; }
945
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
941946
style_header("Available Wiki Pages");
942947
if( showAll ){
943948
style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
944949
}else{
945950
style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
@@ -969,11 +974,11 @@
969974
*/
970975
void wfind_page(void){
971976
Stmt q;
972977
const char *zTitle;
973978
login_check_credentials();
974
- if( !g.perm.RdWiki ){ login_needed(); return; }
979
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
975980
zTitle = PD("title","*");
976981
style_header("Wiki Pages Found");
977982
@ <ul>
978983
db_prepare(&q,
979984
"SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
980985
--- src/wiki.c
+++ src/wiki.c
@@ -134,11 +134,15 @@
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 ){
@@ -222,11 +226,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 +243,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 +265,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 +316,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 +359,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 +370,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 +427,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 +489,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 +505,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 +577,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 +629,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 +650,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 +732,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 +845,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="
@@ -852,11 +857,11 @@
852 " UNION SELECT attachid FROM attachment"
853 " WHERE target=%Q)"
854 "ORDER BY mtime DESC",
855 timeline_query_for_www(), zPageName, zPageName);
856 zWikiPageName = zPageName;
857 www_print_timeline(&q, TIMELINE_ARTID, 0, 0, wiki_history_extra);
858 db_finalize(&q);
859 style_footer();
860 }
861
862 /*
@@ -872,11 +877,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 +940,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 +974,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,11 +134,15 @@
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 ){
@@ -222,11 +226,11 @@
226 style_submenu_element("List","List","%R/wcontent");
227 }
228 if( (ok & W_HELP)!=0 ){
229 style_submenu_element("Help","Help","%R/wikihelp");
230 }
231 if( (ok & W_NEW)!=0 && g.anon.NewWiki ){
232 style_submenu_element("New","New","%R/wikinew");
233 }
234 #if 0
235 if( (ok & W_BLOG)!=0
236 #endif
@@ -239,11 +243,11 @@
243 ** WEBPAGE: wikihelp
244 ** A generic landing page for wiki.
245 */
246 void wiki_helppage(void){
247 login_check_credentials();
248 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
249 style_header("Wiki Help");
250 wiki_standard_submenu(W_ALL_BUT(W_HELP));
251 @ <h2>Wiki Links</h2>
252 @ <ul>
253 { char *zWikiHomePageName = db_get("index-page",0);
@@ -261,19 +265,19 @@
265 @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
266 @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for
267 @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li>
268 @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
269 @ to experiment.</li>
270 if( g.anon.NewWiki ){
271 @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li>
272 if( g.anon.Write ){
273 @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li>
274 }
275 }
276 @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
277 @ available on this server.</li>
278 if( g.anon.ModWiki ){
279 @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li>
280 }
281 if( search_restrict(SRCH_WIKI)!=0 ){
282 @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key
283 @ words</li>
@@ -312,11 +316,11 @@
316 const char *zPageName;
317 const char *zMimetype = 0;
318 char *zBody = mprintf("%s","<i>Empty Page</i>");
319
320 login_check_credentials();
321 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
322 zPageName = P("name");
323 if( zPageName==0 ){
324 if( search_restrict(SRCH_WIKI)!=0 ){
325 wiki_srchpage();
326 }else{
@@ -355,11 +359,11 @@
359 "%R/wdiff?name=%T&a=%d", zPageName, rid);
360 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
361 style_submenu_element("Details", "Details",
362 "%R/info/%s", zUuid);
363 }
364 if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){
365 if( db_get_boolean("wysiwyg-wiki", 0) ){
366 style_submenu_element("Edit", "Edit Wiki Page",
367 "%s/wikiedit?name=%T&wysiwyg=1",
368 g.zTop, zPageName);
369 }else{
@@ -366,16 +370,16 @@
370 style_submenu_element("Edit", "Edit Wiki Page",
371 "%s/wikiedit?name=%T",
372 g.zTop, zPageName);
373 }
374 }
375 if( rid && g.anon.ApndWiki && g.anon.Attach ){
376 style_submenu_element("Attach", "Add An Attachment",
377 "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
378 g.zTop, zPageName, g.zTop, zPageName);
379 }
380 if( rid && g.anon.ApndWiki ){
381 style_submenu_element("Append", "Add A Comment",
382 "%s/wikiappend?name=%T&mimetype=%s",
383 g.zTop, zPageName, zMimetype);
384 }
385 if( g.perm.Hyperlink ){
@@ -423,13 +427,13 @@
427
428 /*
429 ** Output a selection box from which the user can select the
430 ** wiki mimetype.
431 */
432 void mimetype_option_menu(const char *zMimetype){
433 unsigned i;
434 @ <select name="mimetype" size="1">
435 for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){
436 if( fossil_strcmp(zMimetype,azStyles[i])==0 ){
437 @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option>
438 }else{
439 @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option>
@@ -485,11 +489,11 @@
489 zPageName = PD("name","");
490 if( check_name(zPageName) ) return;
491 isSandbox = is_sandbox(zPageName);
492 if( isSandbox ){
493 if( !g.perm.WrWiki ){
494 login_needed(g.anon.WrWiki);
495 return;
496 }
497 if( zBody==0 ){
498 zBody = db_get("sandbox","");
499 zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
@@ -501,11 +505,11 @@
505 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
506 " ORDER BY mtime DESC", zTag
507 );
508 free(zTag);
509 if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
510 login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki);
511 return;
512 }
513 if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
514 zBody = pWiki->zWiki;
515 zMimetype = pWiki->zMimetype;
@@ -573,11 +577,11 @@
577 if( n<20 ) n = 20;
578 if( n>30 ) n = 30;
579 if( !isWysiwyg ){
580 /* Traditional markup-only editing */
581 form_begin(0, "%R/wikiedit");
582 @ <div>Markup style:
583 mimetype_option_menu(zMimetype);
584 @ <br /><textarea name="w" class="wikiedit" cols="80"
585 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
586 @ <br />
587 if( db_get_boolean("wysiwyg-wiki", 0) ){
@@ -625,11 +629,11 @@
629 void wikinew_page(void){
630 const char *zName;
631 const char *zMimetype;
632 login_check_credentials();
633 if( !g.perm.NewWiki ){
634 login_needed(g.anon.NewWiki);
635 return;
636 }
637 zName = PD("name","");
638 zMimetype = wiki_filter_mimetypes(P("mimetype"));
639 if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
@@ -646,10 +650,11 @@
650 @ <p>Rules for wiki page names:</p>
651 well_formed_wiki_name_rules();
652 form_begin(0, "%R/wikinew");
653 @ <p>Name of new wiki page:
654 @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
655 @ Markup style:
656 mimetype_option_menu("text/x-fossil-wiki");
657 @ <br /><input type="submit" value="Create" />
658 @ </p></form>
659 if( zName[0] ){
660 @ <p><span class="wikiError">
@@ -727,11 +732,11 @@
732 fossil_redirect_home();
733 return;
734 }
735 }
736 if( !g.perm.ApndWiki ){
737 login_needed(g.anon.ApndWiki);
738 return;
739 }
740 if( P("submit")!=0 && P("r")!=0 && P("u")!=0
741 && (goodCaptcha = captcha_is_correct())
742 ){
@@ -840,11 +845,11 @@
845 */
846 void whistory_page(void){
847 Stmt q;
848 const char *zPageName;
849 login_check_credentials();
850 if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
851 zPageName = PD("name","");
852 style_header("History Of %s", zPageName);
853
854 db_prepare(&q, "%s AND event.objid IN "
855 " (SELECT rid FROM tagxref WHERE tagid="
@@ -852,11 +857,11 @@
857 " UNION SELECT attachid FROM attachment"
858 " WHERE target=%Q)"
859 "ORDER BY mtime DESC",
860 timeline_query_for_www(), zPageName, zPageName);
861 zWikiPageName = zPageName;
862 www_print_timeline(&q, TIMELINE_ARTID, 0, 0, 0, wiki_history_extra);
863 db_finalize(&q);
864 style_footer();
865 }
866
867 /*
@@ -872,11 +877,11 @@
877 Blob w1, w2, d;
878 u64 diffFlags;
879
880 login_check_credentials();
881 rid1 = atoi(PD("a","0"));
882 if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; }
883 if( rid1==0 ) fossil_redirect_home();
884 rid2 = atoi(PD("b","0"));
885 zPageName = PD("name","");
886 style_header("Changes To %s", zPageName);
887
@@ -935,11 +940,11 @@
940 void wcontent_page(void){
941 Stmt q;
942 int showAll = P("all")!=0;
943
944 login_check_credentials();
945 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
946 style_header("Available Wiki Pages");
947 if( showAll ){
948 style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
949 }else{
950 style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
@@ -969,11 +974,11 @@
974 */
975 void wfind_page(void){
976 Stmt q;
977 const char *zTitle;
978 login_check_credentials();
979 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
980 zTitle = PD("title","*");
981 style_header("Wiki Pages Found");
982 @ <ul>
983 db_prepare(&q,
984 "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
985
+65 -10
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1206,11 +1206,11 @@
12061206
|| strncmp(zTarget, "ftp:", 4)==0
12071207
|| strncmp(zTarget, "mailto:", 7)==0
12081208
){
12091209
blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
12101210
}else if( zTarget[0]=='/' ){
1211
- blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
1211
+ blob_appendf(p->pOut, "<a href=\"%R%h\">", zTarget);
12121212
}else if( zTarget[0]=='.'
12131213
&& (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
12141214
&& (p->state & WIKI_LINKSONLY)==0 ){
12151215
blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
12161216
}else if( zTarget[0]=='#' ){
@@ -1965,17 +1965,26 @@
19651965
** z points to the start of a token. Return the number of
19661966
** characters in that token.
19671967
*/
19681968
static int nextHtmlToken(const char *z){
19691969
int n;
1970
- if( z[0]=='<' ){
1970
+ char c;
1971
+ if( (c=z[0])=='<' ){
19711972
n = markupLength(z);
19721973
if( n<=0 ) n = 1;
1973
- }else if( fossil_isspace(z[0]) ){
1974
+ }else if( fossil_isspace(c) ){
19741975
for(n=1; z[n] && fossil_isspace(z[n]); n++){}
1976
+ }else if( c=='&' ){
1977
+ n = z[1]=='#' ? 2 : 1;
1978
+ while( fossil_isalnum(z[n]) ) n++;
1979
+ if( z[n]==';' ) n++;
19751980
}else{
1976
- for(n=1; z[n] && z[n]!='<' && !fossil_isspace(z[n]); n++){}
1981
+ n = 1;
1982
+ for(n=1; 1; n++){
1983
+ if( (c = z[n]) > '<' ) continue;
1984
+ if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break;
1985
+ }
19771986
}
19781987
return n;
19791988
}
19801989
19811990
/*
@@ -2100,16 +2109,22 @@
21002109
}
21012110
21022111
/*
21032112
** Remove all HTML markup from the input text. The output written into
21042113
** pOut is pure text.
2114
+**
2115
+** Put the title on the first line, if there is any <title> markup.
2116
+** If there is no <title>, then create a blank first line.
21052117
*/
21062118
void html_to_plaintext(const char *zIn, Blob *pOut){
21072119
int n;
21082120
int i, j;
2121
+ int inTitle = 0; /* True between <title>...</title> */
2122
+ int seenText = 0; /* True after first non-whitespace seen */
21092123
int nNL = 0; /* Number of \n characters at the end of pOut */
21102124
int nWS = 0; /* True if pOut ends with whitespace */
2125
+ while( fossil_isspace(zIn[0]) ) zIn++;
21112126
while( zIn[0] ){
21122127
n = nextHtmlToken(zIn);
21132128
if( zIn[0]=='<' && n>1 ){
21142129
int isCloseTag;
21152130
int eTag;
@@ -2130,26 +2145,66 @@
21302145
zIn += n;
21312146
}
21322147
if( zIn[0]=='<' ) zIn += n;
21332148
continue;
21342149
}
2135
- if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2136
- if( nNL==0 ){
2150
+ if( eTag==MARKUP_TITLE ){
2151
+ inTitle = !isCloseTag;
2152
+ }
2153
+ if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2154
+ if( nNL==0 ){
21372155
blob_append(pOut, "\n", 1);
21382156
nNL++;
21392157
}
21402158
nWS = 1;
21412159
}
21422160
}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);
2161
+ if( seenText ){
2162
+ nNL = 0;
2163
+ if( !inTitle ){ /* '\n' -> ' ' within <title> */
2164
+ for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2165
+ }
2166
+ if( !nWS ){
2167
+ blob_append(pOut, nNL ? "\n" : " ", 1);
2168
+ nWS = 1;
2169
+ }
2170
+ }
2171
+ }else if( zIn[0]=='&' ){
2172
+ char c = '?';
2173
+ if( zIn[1]=='#' ){
2174
+ int x = atoi(&zIn[1]);
2175
+ if( x>0 && x<=127 ) c = x;
2176
+ }else{
2177
+ static const struct { int n; char c; char *z; } aEntity[] = {
2178
+ { 5, '&', "&amp;" },
2179
+ { 4, '<', "&lt;" },
2180
+ { 4, '>', "&gt;" },
2181
+ { 6, ' ', "&nbsp;" },
2182
+ };
2183
+ int jj;
2184
+ for(jj=0; jj<ArraySize(aEntity); jj++){
2185
+ if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
2186
+ c = aEntity[jj].c;
2187
+ break;
2188
+ }
2189
+ }
2190
+ }
2191
+ if( fossil_isspace(c) ){
2192
+ if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
21462193
nWS = 1;
2194
+ nNL = c=='\n';
2195
+ }else{
2196
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2197
+ seenText = 1;
2198
+ nNL = nWS = 0;
2199
+ blob_append(pOut, &c, 1);
21472200
}
21482201
}else{
2149
- blob_append(pOut, zIn, n);
2202
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2203
+ seenText = 1;
21502204
nNL = nWS = 0;
2205
+ blob_append(pOut, zIn, n);
21512206
}
21522207
zIn += n;
21532208
}
21542209
if( nNL==0 ) blob_append(pOut, "\n", 1);
21552210
}
21562211
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -1206,11 +1206,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]=='#' ){
@@ -1965,17 +1965,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 +2109,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 +2145,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
@@ -1206,11 +1206,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=\"%R%h\">", 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]=='#' ){
@@ -1965,17 +1965,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 char c;
1971 if( (c=z[0])=='<' ){
1972 n = markupLength(z);
1973 if( n<=0 ) n = 1;
1974 }else if( fossil_isspace(c) ){
1975 for(n=1; z[n] && fossil_isspace(z[n]); n++){}
1976 }else if( c=='&' ){
1977 n = z[1]=='#' ? 2 : 1;
1978 while( fossil_isalnum(z[n]) ) n++;
1979 if( z[n]==';' ) n++;
1980 }else{
1981 n = 1;
1982 for(n=1; 1; n++){
1983 if( (c = z[n]) > '<' ) continue;
1984 if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break;
1985 }
1986 }
1987 return n;
1988 }
1989
1990 /*
@@ -2100,16 +2109,22 @@
2109 }
2110
2111 /*
2112 ** Remove all HTML markup from the input text. The output written into
2113 ** pOut is pure text.
2114 **
2115 ** Put the title on the first line, if there is any <title> markup.
2116 ** If there is no <title>, then create a blank first line.
2117 */
2118 void html_to_plaintext(const char *zIn, Blob *pOut){
2119 int n;
2120 int i, j;
2121 int inTitle = 0; /* True between <title>...</title> */
2122 int seenText = 0; /* True after first non-whitespace seen */
2123 int nNL = 0; /* Number of \n characters at the end of pOut */
2124 int nWS = 0; /* True if pOut ends with whitespace */
2125 while( fossil_isspace(zIn[0]) ) zIn++;
2126 while( zIn[0] ){
2127 n = nextHtmlToken(zIn);
2128 if( zIn[0]=='<' && n>1 ){
2129 int isCloseTag;
2130 int eTag;
@@ -2130,26 +2145,66 @@
2145 zIn += n;
2146 }
2147 if( zIn[0]=='<' ) zIn += n;
2148 continue;
2149 }
2150 if( eTag==MARKUP_TITLE ){
2151 inTitle = !isCloseTag;
2152 }
2153 if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2154 if( nNL==0 ){
2155 blob_append(pOut, "\n", 1);
2156 nNL++;
2157 }
2158 nWS = 1;
2159 }
2160 }else if( fossil_isspace(zIn[0]) ){
2161 if( seenText ){
2162 nNL = 0;
2163 if( !inTitle ){ /* '\n' -> ' ' within <title> */
2164 for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2165 }
2166 if( !nWS ){
2167 blob_append(pOut, nNL ? "\n" : " ", 1);
2168 nWS = 1;
2169 }
2170 }
2171 }else if( zIn[0]=='&' ){
2172 char c = '?';
2173 if( zIn[1]=='#' ){
2174 int x = atoi(&zIn[1]);
2175 if( x>0 && x<=127 ) c = x;
2176 }else{
2177 static const struct { int n; char c; char *z; } aEntity[] = {
2178 { 5, '&', "&amp;" },
2179 { 4, '<', "&lt;" },
2180 { 4, '>', "&gt;" },
2181 { 6, ' ', "&nbsp;" },
2182 };
2183 int jj;
2184 for(jj=0; jj<ArraySize(aEntity); jj++){
2185 if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
2186 c = aEntity[jj].c;
2187 break;
2188 }
2189 }
2190 }
2191 if( fossil_isspace(c) ){
2192 if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
2193 nWS = 1;
2194 nNL = c=='\n';
2195 }else{
2196 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2197 seenText = 1;
2198 nNL = nWS = 0;
2199 blob_append(pOut, &c, 1);
2200 }
2201 }else{
2202 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2203 seenText = 1;
2204 nNL = nWS = 0;
2205 blob_append(pOut, zIn, n);
2206 }
2207 zIn += n;
2208 }
2209 if( nNL==0 ) blob_append(pOut, "\n", 1);
2210 }
2211
+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
483494
ADDED test/fileage-test-1.wiki
--- 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
483 DDED test/fileage-test-1.wiki
--- 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
494 DDED test/fileage-test-1.wiki
--- a/test/fileage-test-1.wiki
+++ b/test/fileage-test-1.wiki
@@ -0,0 +1,14 @@
1
+
2
+This page contains URLs for file-age computations that have given
3
+trouble in the past. Shif-click on on the links, one-by-one, to verify
4
+that the current implementation works correctly:
5
+
6
+ * [/fileage?name=c9df0dcdaa402] - Verify that the many
7
+ execute permission changes that occurred about 24 hours before
8
+ check-in c9df0dcdaa402 do not appear as file changes.
9
+
10
+ * [/tree?ci=c9df0dcdaa40&mtime=0&type=tree] - Verify that all
11
+ three skin files (css.txt, footer.txt, and header.txt) appear
12
+ in all of the skin/*/ folders.
13
+
14
+ * On both of the above, check for excessive computation time.
--- a/test/fileage-test-1.wiki
+++ b/test/fileage-test-1.wiki
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/fileage-test-1.wiki
+++ b/test/fileage-test-1.wiki
@@ -0,0 +1,14 @@
1
2 This page contains URLs for file-age computations that have given
3 trouble in the past. Shif-click on on the links, one-by-one, to verify
4 that the current implementation works correctly:
5
6 * [/fileage?name=c9df0dcdaa402] - Verify that the many
7 execute permission changes that occurred about 24 hours before
8 check-in c9df0dcdaa402 do not appear as file changes.
9
10 * [/tree?ci=c9df0dcdaa40&mtime=0&type=tree] - Verify that all
11 three skin files (css.txt, footer.txt, and header.txt) appear
12 in all of the skin/*/ folders.
13
14 * On both of the above, check for excessive computation time.
--- test/graph-test-1.wiki
+++ test/graph-test-1.wiki
@@ -69,5 +69,8 @@
6969
7070
External:
7171
7272
* <a href="http://www.sqlite.org/src/timeline?c=2010-09-29&nd"
7373
target="testwindow">Timewarp due to a mis-configured system clock.</a>
74
+ * <a href="http://core.tcl.tk/tk/finfo?name=tests/id.test"
75
+ target="testwindow">Show all three separate deletions of "id.test".
76
+ (Scroll down for the third deletion.)
7477
--- test/graph-test-1.wiki
+++ test/graph-test-1.wiki
@@ -69,5 +69,8 @@
69
70 External:
71
72 * <a href="http://www.sqlite.org/src/timeline?c=2010-09-29&nd"
73 target="testwindow">Timewarp due to a mis-configured system clock.</a>
 
 
 
74
--- test/graph-test-1.wiki
+++ test/graph-test-1.wiki
@@ -69,5 +69,8 @@
69
70 External:
71
72 * <a href="http://www.sqlite.org/src/timeline?c=2010-09-29&nd"
73 target="testwindow">Timewarp due to a mis-configured system clock.</a>
74 * <a href="http://core.tcl.tk/tk/finfo?name=tests/id.test"
75 target="testwindow">Show all three separate deletions of "id.test".
76 (Scroll down for the third deletion.)
77
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -17,18 +17,23 @@
1717
1818
<li><p>
1919
Click on each of the links in in the
2020
[./diff-test-1.wiki] document and verify that all diffs are
2121
rendered correctly.
22
+
2223
<li><p>
2324
Click on the following link to verify that it works: [./test-page%2b%2b.wiki | ./test-page++.wiki]
2425
(NB: Many web servers automatically block
2526
or rewrite URLs that contain "+" characters, even when those "+"
2627
characters are encoded as "%2B". On such web servers, the URL
2728
above will not work. This test is only guaranteed to work
2829
when running "fossil ui".)
2930
31
+<li><p>
32
+Shift-click on each of the links in [./fileage-test-1.wiki] and verify
33
+correct operation of the file-age computation.
34
+
3035
<li><p>
3136
Verify correct name-change tracking behavior (no net changes) for:
3237
<blockquote><b>
3338
fossil test-name-changes --debug b120bc8b262ac 374920b20944b
3439
</b></blockquote>
3540
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -17,18 +17,23 @@
17
18 <li><p>
19 Click on each of the links in in the
20 [./diff-test-1.wiki] document and verify that all diffs are
21 rendered correctly.
 
22 <li><p>
23 Click on the following link to verify that it works: [./test-page%2b%2b.wiki | ./test-page++.wiki]
24 (NB: Many web servers automatically block
25 or rewrite URLs that contain "+" characters, even when those "+"
26 characters are encoded as "%2B". On such web servers, the URL
27 above will not work. This test is only guaranteed to work
28 when running "fossil ui".)
29
 
 
 
 
30 <li><p>
31 Verify correct name-change tracking behavior (no net changes) for:
32 <blockquote><b>
33 fossil test-name-changes --debug b120bc8b262ac 374920b20944b
34 </b></blockquote>
35
--- test/release-checklist.wiki
+++ test/release-checklist.wiki
@@ -17,18 +17,23 @@
17
18 <li><p>
19 Click on each of the links in in the
20 [./diff-test-1.wiki] document and verify that all diffs are
21 rendered correctly.
22
23 <li><p>
24 Click on the following link to verify that it works: [./test-page%2b%2b.wiki | ./test-page++.wiki]
25 (NB: Many web servers automatically block
26 or rewrite URLs that contain "+" characters, even when those "+"
27 characters are encoded as "%2B". On such web servers, the URL
28 above will not work. This test is only guaranteed to work
29 when running "fossil ui".)
30
31 <li><p>
32 Shift-click on each of the links in [./fileage-test-1.wiki] and verify
33 correct operation of the file-age computation.
34
35 <li><p>
36 Verify correct name-change tracking behavior (no net changes) for:
37 <blockquote><b>
38 fossil test-name-changes --debug b120bc8b262ac 374920b20944b
39 </b></blockquote>
40
+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
+44 -15
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,26 +1,55 @@
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-??)</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
+ * Renamed "Events" to "Technical Notes", while updating the technote
26
+ display and control pages. Add support for technotes as plain text
27
+ or as Markdown.
28
+ * Added the [/md_rules] pages containing summary instructions on the
29
+ Markdown format.
30
+ * Improvements to the /login page. Some hyperlinks to pages that require
31
+ "anonymous" privileges are displayed even if the current user is "nobody"
32
+ but automatically redirect to /login.
33
+ * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file
34
+ "404.md" from the top-level directory (if such a file exists) in
35
+ place of its built-in 404 text.
36
+ * Download of Tarballs and ZIP Archives by user "nobody" is now enabled
37
+ by default in new repositories.
38
+ * Enhancements to the table sorting controls. More display tables
39
+ are now sortable.
640
* Add IPv6 support to [/help?cmd=sync|fossil sync] and
741
[/help?cmd=clone|fossil clone]
842
* 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.
2043
* During shutdown, check to see if the check-out database (".fslckout")
21
- contains a lot of free space, and if it does, VACUUM it.
44
+ contains a lot of free space, and if it does, VACUUM it.
45
+ * Added the [/mimetype_list] page.
46
+ * Added the [/hash-collisions] page.
47
+ * Allow the user of Common Table Expressions in the SQL that defaults
48
+ ticket reports.
49
+ * Break out the components (css, footer, and header) for the
50
+ various built-in skins into separate files in the source tree.
2251
2352
<h2>Changes For Version 1.30 (2015-01-19)</h2>
2453
* Added the [/help?cmd=bundle|fossil bundle] command.
2554
* Added the [/help?cmd=purge|fossil purge] command.
2655
* Added the [/help?cmd=publish|fossil publish] command.
2756
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,26 +1,55 @@
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,55 @@
1 <title>Change Log</title>
2
3 <h2>Changes For Version 1.31 (2015-02-??)</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 * Renamed "Events" to "Technical Notes", while updating the technote
26 display and control pages. Add support for technotes as plain text
27 or as Markdown.
28 * Added the [/md_rules] pages containing summary instructions on the
29 Markdown format.
30 * Improvements to the /login page. Some hyperlinks to pages that require
31 "anonymous" privileges are displayed even if the current user is "nobody"
32 but automatically redirect to /login.
33 * The [/help?cmd=/doc|/doc] web-page will now try to deliver the file
34 "404.md" from the top-level directory (if such a file exists) in
35 place of its built-in 404 text.
36 * Download of Tarballs and ZIP Archives by user "nobody" is now enabled
37 by default in new repositories.
38 * Enhancements to the table sorting controls. More display tables
39 are now sortable.
40 * Add IPv6 support to [/help?cmd=sync|fossil sync] and
41 [/help?cmd=clone|fossil clone]
42 * Add more skins such as "San Francisco Modern" and "Eagle".
 
 
 
 
 
 
 
 
 
 
 
43 * During shutdown, check to see if the check-out database (".fslckout")
44 contains a lot of free space, and if it does, VACUUM it.
45 * Added the [/mimetype_list] page.
46 * Added the [/hash-collisions] page.
47 * Allow the user of Common Table Expressions in the SQL that defaults
48 ticket reports.
49 * Break out the components (css, footer, and header) for the
50 various built-in skins into separate files in the source tree.
51
52 <h2>Changes For Version 1.30 (2015-01-19)</h2>
53 * Added the [/help?cmd=bundle|fossil bundle] command.
54 * Added the [/help?cmd=purge|fossil purge] command.
55 * Added the [/help?cmd=publish|fossil publish] command.
56
--- 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/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -7,11 +7,11 @@
77
set out [open download.html w]
88
fconfigure $out -encoding utf-8 -translation lf
99
puts $out \
1010
{<!DOCTYPE html><html>
1111
<head>
12
-<base href="http://www.fossil-scm.org/" />
12
+<base href="/" />
1313
<title>Fossil: Downloads</title>
1414
<link rel="stylesheet" href="/fossil/style.css" type="text/css"
1515
media="screen">
1616
</head>
1717
<body>
@@ -68,11 +68,11 @@
6868
set dt [string range $datetime 0 3]-[string range $datetime 4 5]-
6969
append dt "[string range $datetime 6 7] "
7070
append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:"
7171
append dt "[string range $datetime 12 13]"
7272
set link [string map {{ } +} $dt]
73
- set hr "http://www.fossil-scm.org/fossil/timeline?c=$link&amp;y=ci"
73
+ set hr "/fossil/timeline?c=$link&amp;y=ci"
7474
puts $out "<tr><td colspan=6 align=left><hr>"
7575
puts $out "<center><b><a href=\"$hr\">$dt</a></b></center>"
7676
puts $out "</td></tr>"
7777
puts $out "<tr>"
7878
@@ -128,14 +128,14 @@
128128
<title>Fossil Download Checksums</title>
129129
<body>
130130
<h1 align="center">Checksums For Fossil Downloads</h1>
131131
<p>The following table shows the SHA1 checksums for the precompiled
132132
binaries available on the
133
-<a href="http://www.fossil-scm.org/download.html">Fossil website</a>.</p>
133
+<a href="/download.html">Fossil website</a>.</p>
134134
<pre>}
135135
136136
foreach file [lsort [glob -nocomplain download/fossil-*.zip]] {
137137
set sha1sum [lindex [exec sha1sum $file] 0]
138138
puts $out "$sha1sum [file tail $file]"
139139
}
140140
puts $out {</pre></body></html>}
141141
close $out
142142
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -7,11 +7,11 @@
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="http://www.fossil-scm.org/" />
13 <title>Fossil: Downloads</title>
14 <link rel="stylesheet" href="/fossil/style.css" type="text/css"
15 media="screen">
16 </head>
17 <body>
@@ -68,11 +68,11 @@
68 set dt [string range $datetime 0 3]-[string range $datetime 4 5]-
69 append dt "[string range $datetime 6 7] "
70 append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:"
71 append dt "[string range $datetime 12 13]"
72 set link [string map {{ } +} $dt]
73 set hr "http://www.fossil-scm.org/fossil/timeline?c=$link&amp;y=ci"
74 puts $out "<tr><td colspan=6 align=left><hr>"
75 puts $out "<center><b><a href=\"$hr\">$dt</a></b></center>"
76 puts $out "</td></tr>"
77 puts $out "<tr>"
78
@@ -128,14 +128,14 @@
128 <title>Fossil Download Checksums</title>
129 <body>
130 <h1 align="center">Checksums For Fossil Downloads</h1>
131 <p>The following table shows the SHA1 checksums for the precompiled
132 binaries available on the
133 <a href="http://www.fossil-scm.org/download.html">Fossil website</a>.</p>
134 <pre>}
135
136 foreach file [lsort [glob -nocomplain download/fossil-*.zip]] {
137 set sha1sum [lindex [exec sha1sum $file] 0]
138 puts $out "$sha1sum [file tail $file]"
139 }
140 puts $out {</pre></body></html>}
141 close $out
142
--- www/mkdownload.tcl
+++ www/mkdownload.tcl
@@ -7,11 +7,11 @@
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>
@@ -68,11 +68,11 @@
68 set dt [string range $datetime 0 3]-[string range $datetime 4 5]-
69 append dt "[string range $datetime 6 7] "
70 append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:"
71 append dt "[string range $datetime 12 13]"
72 set link [string map {{ } +} $dt]
73 set hr "/fossil/timeline?c=$link&amp;y=ci"
74 puts $out "<tr><td colspan=6 align=left><hr>"
75 puts $out "<center><b><a href=\"$hr\">$dt</a></b></center>"
76 puts $out "</td></tr>"
77 puts $out "<tr>"
78
@@ -128,14 +128,14 @@
128 <title>Fossil Download Checksums</title>
129 <body>
130 <h1 align="center">Checksums For Fossil Downloads</h1>
131 <p>The following table shows the SHA1 checksums for the precompiled
132 binaries available on the
133 <a href="/download.html">Fossil website</a>.</p>
134 <pre>}
135
136 foreach file [lsort [glob -nocomplain download/fossil-*.zip]] {
137 set sha1sum [lindex [exec sha1sum $file] 0]
138 puts $out "$sha1sum [file tail $file]"
139 }
140 puts $out {</pre></body></html>}
141 close $out
142
+1 -1
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -93,11 +93,11 @@
9393
<h2>Primary Documents:</h2>
9494
<ul>
9595
<li> <a href='quickstart.wiki'>Quick-start Guide</a>
9696
<li> <a href='faq.wiki'>FAQ</a>
9797
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
98
-<li> <a href='COPYRIGHT-BSD2.txt'>License</a>
98
+<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
9999
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
100100
book</a>
101101
<li> <a href='../../../help'>Command-line help</a>
102102
</ul>
103103
<a name="pindex"></a>
104104
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -93,11 +93,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
@@ -93,11 +93,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/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>
2020
--- 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>
20
--- 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>
20
--- 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