Fossil SCM

Merge updates from trunk.

mistachkin 2015-02-16 03:21 mvAndRmFiles merge
Commit df36d6b85bbc25500e0fc7e4de79a66d6c96f7a4
83 files changed +4 -4 +4 -4 +4 +1 +4 -4 +4 -4 +9 -3 +19 -51 +4 -4 +4 -4 -1 +4 -4 +2 -1 +2 -1 +51 -14 +34 -22 +6 -6 +55 -51 +2 -2 +55 -4 +3 -3 +8 -8 +8 -8 +2 -2 +12 -12 +3 -3 +25 -10 +142 -95 +11 -20 +22 -10 +1 +47 -40 +76 -53 +154 -108 +82 -24 +1 -1 +13 -13 +16 -10 +5 -2 +85 -10 +2 -2 +39 -6 +20 -10 +7 -4 +2 -2 +1 -1 +362 -124 +78 -28 +120 -5 +10 -7 +25 -13 +101 -10 +5 -4 +10 -10 +221 -42 +3 -3 +19 -9 +177 -15 +217 -175 +25 -16 +11 -8 +10 -10 +1 -1 +33 -9 +1 -1 +2 -2 +29 -24 +65 -10 +4 -2 +12 -1 +14 +3 +5 +10 -10 +21 -21 +44 -15 +22 -8 +30 -30 +30 -28 +3 -3 +2 -2 +2 -2 +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;
@@ -118,12 +118,11 @@
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;
@@ -118,12 +118,11 @@
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;
@@ -118,12 +118,11 @@
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 {$url 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 {$url 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
+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
+55 -4
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
5252
*/
5353
#define P(x) cgi_parameter((x),0)
5454
#define PD(x,y) cgi_parameter((x),(y))
5555
#define PT(x) cgi_parameter_trimmed((x),0)
5656
#define PDT(x,y) cgi_parameter_trimmed((x),(y))
57
+#define PB(x) cgi_parameter_boolean(x)
5758
5859
5960
/*
6061
** Destinations for output text.
6162
*/
@@ -436,11 +437,12 @@
436437
static int seqQP = 0; /* Sequence numbers */
437438
static struct QParam { /* One entry for each query parameter or cookie */
438439
const char *zName; /* Parameter or cookie name */
439440
const char *zValue; /* Value of the query parameter or cookie */
440441
int seq; /* Order of insertion */
441
- int isQP; /* True for query parameters */
442
+ char isQP; /* True for query parameters */
443
+ char cTag; /* Tag on query parameters */
442444
} *aParamQP; /* An array of all parameters and cookies */
443445
444446
/*
445447
** Add another query parameter or cookie to the parameter set.
446448
** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
490492
aParamQP[i].zValue = zValue;
491493
return;
492494
}
493495
}
494496
cgi_set_parameter_nocopy(zName, zValue, 0);
497
+}
498
+void cgi_replace_query_parameter(const char *zName, const char *zValue){
499
+ int i;
500
+ for(i=0; i<nUsedQP; i++){
501
+ if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
502
+ aParamQP[i].zValue = zValue;
503
+ assert( aParamQP[i].isQP );
504
+ return;
505
+ }
506
+ }
507
+ cgi_set_parameter_nocopy(zName, zValue, 1);
495508
}
496509
497510
/*
498511
** Add a query parameter. The zName portion is fixed but a copy
499512
** must be made of zValue.
@@ -1064,10 +1077,20 @@
10641077
zOut = fossil_strdup(zIn);
10651078
for(i=0; zOut[i]; i++){}
10661079
while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
10671080
return zOut;
10681081
}
1082
+
1083
+/*
1084
+** Return true if the CGI parameter zName exists and is not equal to 0,
1085
+** or "no" or "off".
1086
+*/
1087
+int cgi_parameter_boolean(const char *zName){
1088
+ const char *zIn = cgi_parameter(zName, 0);
1089
+ if( zIn==0 ) return 0;
1090
+ return zIn[0]==0 || is_truth(zIn);
1091
+}
10691092
10701093
/*
10711094
** Return the name of the i-th CGI parameter. Return NULL if there
10721095
** are fewer than i registered CGI parameters.
10731096
*/
@@ -1142,23 +1165,51 @@
11421165
cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
11431166
}
11441167
}
11451168
11461169
/*
1147
-** Export all query parameters (but not cookies or environment variables)
1148
-** as hidden values of a form.
1170
+** Export all untagged query parameters (but not cookies or environment
1171
+** variables) as hidden values of a form.
11491172
*/
11501173
void cgi_query_parameters_to_hidden(void){
11511174
int i;
11521175
const char *zN, *zV;
11531176
for(i=0; i<nUsedQP; i++){
1154
- if( aParamQP[i].isQP==0 ) continue;
1177
+ if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
11551178
zN = aParamQP[i].zName;
11561179
zV = aParamQP[i].zValue;
11571180
@ <input type="hidden" name="%h(zN)" value="%h(zV)">
11581181
}
11591182
}
1183
+
1184
+/*
1185
+** Export all untagged query parameters (but not cookies or environment
1186
+** variables) to the HQuery object.
1187
+*/
1188
+void cgi_query_parameters_to_url(HQuery *p){
1189
+ int i;
1190
+ for(i=0; i<nUsedQP; i++){
1191
+ if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1192
+ url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1193
+ }
1194
+}
1195
+
1196
+/*
1197
+** Tag query parameter zName so that it is not exported by
1198
+** cgi_query_parameters_to_hidden(). Or if zName==0, then
1199
+** untag all query parameters.
1200
+*/
1201
+void cgi_tag_query_parameter(const char *zName){
1202
+ int i;
1203
+ if( zName==0 ){
1204
+ for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
1205
+ }else{
1206
+ for(i=0; i<nUsedQP; i++){
1207
+ if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
1208
+ }
1209
+ }
1210
+}
11601211
11611212
/*
11621213
** This routine works like "printf" except that it has the
11631214
** extra formatting capabilities such as %h and %t.
11641215
*/
11651216
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
52 */
53 #define P(x) cgi_parameter((x),0)
54 #define PD(x,y) cgi_parameter((x),(y))
55 #define PT(x) cgi_parameter_trimmed((x),0)
56 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
 
57
58
59 /*
60 ** Destinations for output text.
61 */
@@ -436,11 +437,12 @@
436 static int seqQP = 0; /* Sequence numbers */
437 static struct QParam { /* One entry for each query parameter or cookie */
438 const char *zName; /* Parameter or cookie name */
439 const char *zValue; /* Value of the query parameter or cookie */
440 int seq; /* Order of insertion */
441 int isQP; /* True for query parameters */
 
442 } *aParamQP; /* An array of all parameters and cookies */
443
444 /*
445 ** Add another query parameter or cookie to the parameter set.
446 ** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
490 aParamQP[i].zValue = zValue;
491 return;
492 }
493 }
494 cgi_set_parameter_nocopy(zName, zValue, 0);
 
 
 
 
 
 
 
 
 
 
 
495 }
496
497 /*
498 ** Add a query parameter. The zName portion is fixed but a copy
499 ** must be made of zValue.
@@ -1064,10 +1077,20 @@
1064 zOut = fossil_strdup(zIn);
1065 for(i=0; zOut[i]; i++){}
1066 while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
1067 return zOut;
1068 }
 
 
 
 
 
 
 
 
 
 
1069
1070 /*
1071 ** Return the name of the i-th CGI parameter. Return NULL if there
1072 ** are fewer than i registered CGI parameters.
1073 */
@@ -1142,23 +1165,51 @@
1142 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1143 }
1144 }
1145
1146 /*
1147 ** Export all query parameters (but not cookies or environment variables)
1148 ** as hidden values of a form.
1149 */
1150 void cgi_query_parameters_to_hidden(void){
1151 int i;
1152 const char *zN, *zV;
1153 for(i=0; i<nUsedQP; i++){
1154 if( aParamQP[i].isQP==0 ) continue;
1155 zN = aParamQP[i].zName;
1156 zV = aParamQP[i].zValue;
1157 @ <input type="hidden" name="%h(zN)" value="%h(zV)">
1158 }
1159 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1160
1161 /*
1162 ** This routine works like "printf" except that it has the
1163 ** extra formatting capabilities such as %h and %t.
1164 */
1165
--- src/cgi.c
+++ src/cgi.c
@@ -52,10 +52,11 @@
52 */
53 #define P(x) cgi_parameter((x),0)
54 #define PD(x,y) cgi_parameter((x),(y))
55 #define PT(x) cgi_parameter_trimmed((x),0)
56 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
57 #define PB(x) cgi_parameter_boolean(x)
58
59
60 /*
61 ** Destinations for output text.
62 */
@@ -436,11 +437,12 @@
437 static int seqQP = 0; /* Sequence numbers */
438 static struct QParam { /* One entry for each query parameter or cookie */
439 const char *zName; /* Parameter or cookie name */
440 const char *zValue; /* Value of the query parameter or cookie */
441 int seq; /* Order of insertion */
442 char isQP; /* True for query parameters */
443 char cTag; /* Tag on query parameters */
444 } *aParamQP; /* An array of all parameters and cookies */
445
446 /*
447 ** Add another query parameter or cookie to the parameter set.
448 ** zName is the name of the query parameter or cookie and zValue
@@ -490,10 +492,21 @@
492 aParamQP[i].zValue = zValue;
493 return;
494 }
495 }
496 cgi_set_parameter_nocopy(zName, zValue, 0);
497 }
498 void cgi_replace_query_parameter(const char *zName, const char *zValue){
499 int i;
500 for(i=0; i<nUsedQP; i++){
501 if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
502 aParamQP[i].zValue = zValue;
503 assert( aParamQP[i].isQP );
504 return;
505 }
506 }
507 cgi_set_parameter_nocopy(zName, zValue, 1);
508 }
509
510 /*
511 ** Add a query parameter. The zName portion is fixed but a copy
512 ** must be made of zValue.
@@ -1064,10 +1077,20 @@
1077 zOut = fossil_strdup(zIn);
1078 for(i=0; zOut[i]; i++){}
1079 while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
1080 return zOut;
1081 }
1082
1083 /*
1084 ** Return true if the CGI parameter zName exists and is not equal to 0,
1085 ** or "no" or "off".
1086 */
1087 int cgi_parameter_boolean(const char *zName){
1088 const char *zIn = cgi_parameter(zName, 0);
1089 if( zIn==0 ) return 0;
1090 return zIn[0]==0 || is_truth(zIn);
1091 }
1092
1093 /*
1094 ** Return the name of the i-th CGI parameter. Return NULL if there
1095 ** are fewer than i registered CGI parameters.
1096 */
@@ -1142,23 +1165,51 @@
1165 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1166 }
1167 }
1168
1169 /*
1170 ** Export all untagged query parameters (but not cookies or environment
1171 ** variables) as hidden values of a form.
1172 */
1173 void cgi_query_parameters_to_hidden(void){
1174 int i;
1175 const char *zN, *zV;
1176 for(i=0; i<nUsedQP; i++){
1177 if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1178 zN = aParamQP[i].zName;
1179 zV = aParamQP[i].zValue;
1180 @ <input type="hidden" name="%h(zN)" value="%h(zV)">
1181 }
1182 }
1183
1184 /*
1185 ** Export all untagged query parameters (but not cookies or environment
1186 ** variables) to the HQuery object.
1187 */
1188 void cgi_query_parameters_to_url(HQuery *p){
1189 int i;
1190 for(i=0; i<nUsedQP; i++){
1191 if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1192 url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1193 }
1194 }
1195
1196 /*
1197 ** Tag query parameter zName so that it is not exported by
1198 ** cgi_query_parameters_to_hidden(). Or if zName==0, then
1199 ** untag all query parameters.
1200 */
1201 void cgi_tag_query_parameter(const char *zName){
1202 int i;
1203 if( zName==0 ){
1204 for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
1205 }else{
1206 for(i=0; i<nUsedQP; i++){
1207 if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
1208 }
1209 }
1210 }
1211
1212 /*
1213 ** This routine works like "printf" except that it has the
1214 ** extra formatting capabilities such as %h and %t.
1215 */
1216
+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" },
@@ -2567,10 +2563,13 @@
25672563
** gmerge-command A graphical merge conflict resolver command operating
25682564
** on four files.
25692565
** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
25702566
** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
25712567
** Ex: meld "%baseline" "%original" "%merge" "%output"
2568
+**
2569
+** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570
+** display. (Default: 10; Minimum: 6)
25722571
**
25732572
** http-port The TCP/IP port number to use by the "server"
25742573
** and "ui" commands. Default: 8080
25752574
**
25762575
** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
27422741
if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
27432742
fossil_fatal("cannot set 'manifest' globally");
27442743
}
27452744
if( unsetFlag || g.argc==4 ){
27462745
int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2747
- if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2746
+ if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2747
+ fossil_strncmp(pSetting[1].name, zName, n)==0 ){
27482748
Blob x;
27492749
int i;
27502750
blob_init(&x,0,0);
27512751
for(i=0; pSetting[i].name; i++){
27522752
if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
27532753
--- 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" },
@@ -2567,10 +2563,13 @@
2567 ** gmerge-command A graphical merge conflict resolver command operating
2568 ** on four files.
2569 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2570 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2571 ** Ex: meld "%baseline" "%original" "%merge" "%output"
 
 
 
2572 **
2573 ** http-port The TCP/IP port number to use by the "server"
2574 ** and "ui" commands. Default: 8080
2575 **
2576 ** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
2742 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2743 fossil_fatal("cannot set 'manifest' globally");
2744 }
2745 if( unsetFlag || g.argc==4 ){
2746 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2747 if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
 
2748 Blob x;
2749 int i;
2750 blob_init(&x,0,0);
2751 for(i=0; pSetting[i].name; i++){
2752 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2753
--- 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" },
@@ -2567,10 +2563,13 @@
2563 ** gmerge-command A graphical merge conflict resolver command operating
2564 ** on four files.
2565 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2566 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2567 ** Ex: meld "%baseline" "%original" "%merge" "%output"
2568 **
2569 ** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570 ** display. (Default: 10; Minimum: 6)
2571 **
2572 ** http-port The TCP/IP port number to use by the "server"
2573 ** and "ui" commands. Default: 8080
2574 **
2575 ** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
2741 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2742 fossil_fatal("cannot set 'manifest' globally");
2743 }
2744 if( unsetFlag || g.argc==4 ){
2745 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2746 if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2747 fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2748 Blob x;
2749 int i;
2750 blob_init(&x,0,0);
2751 for(i=0; pSetting[i].name; i++){
2752 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2753
+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" },
@@ -2567,10 +2563,13 @@
25672563
** gmerge-command A graphical merge conflict resolver command operating
25682564
** on four files.
25692565
** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
25702566
** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
25712567
** Ex: meld "%baseline" "%original" "%merge" "%output"
2568
+**
2569
+** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570
+** display. (Default: 10; Minimum: 6)
25722571
**
25732572
** http-port The TCP/IP port number to use by the "server"
25742573
** and "ui" commands. Default: 8080
25752574
**
25762575
** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
27422741
if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
27432742
fossil_fatal("cannot set 'manifest' globally");
27442743
}
27452744
if( unsetFlag || g.argc==4 ){
27462745
int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2747
- if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2746
+ if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2747
+ fossil_strncmp(pSetting[1].name, zName, n)==0 ){
27482748
Blob x;
27492749
int i;
27502750
blob_init(&x,0,0);
27512751
for(i=0; pSetting[i].name; i++){
27522752
if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
27532753
--- 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" },
@@ -2567,10 +2563,13 @@
2567 ** gmerge-command A graphical merge conflict resolver command operating
2568 ** on four files.
2569 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2570 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2571 ** Ex: meld "%baseline" "%original" "%merge" "%output"
 
 
 
2572 **
2573 ** http-port The TCP/IP port number to use by the "server"
2574 ** and "ui" commands. Default: 8080
2575 **
2576 ** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
2742 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2743 fossil_fatal("cannot set 'manifest' globally");
2744 }
2745 if( unsetFlag || g.argc==4 ){
2746 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2747 if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){
 
2748 Blob x;
2749 int i;
2750 blob_init(&x,0,0);
2751 for(i=0; pSetting[i].name; i++){
2752 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2753
--- 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" },
@@ -2567,10 +2563,13 @@
2563 ** gmerge-command A graphical merge conflict resolver command operating
2564 ** on four files.
2565 ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
2566 ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
2567 ** Ex: meld "%baseline" "%original" "%merge" "%output"
2568 **
2569 ** hash-digits The number of hexadecimal digits of the SHA1 hash to
2570 ** display. (Default: 10; Minimum: 6)
2571 **
2572 ** http-port The TCP/IP port number to use by the "server"
2573 ** and "ui" commands. Default: 8080
2574 **
2575 ** https-login Send login credentials using HTTPS instead of HTTP
@@ -2742,11 +2741,12 @@
2741 if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
2742 fossil_fatal("cannot set 'manifest' globally");
2743 }
2744 if( unsetFlag || g.argc==4 ){
2745 int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
2746 if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
2747 fossil_strncmp(pSetting[1].name, zName, n)==0 ){
2748 Blob x;
2749 int i;
2750 blob_init(&x,0,0);
2751 for(i=0; pSetting[i].name; i++){
2752 if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
2753
--- 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
+25 -10
--- 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'>
@@ -403,11 +418,11 @@
403418
const char *zAttr;
404419
const char *zValue;
405420
int nAttr, nValue;
406421
int seenClass = 0;
407422
int seenTitle = 0;
408
-
423
+
409424
while( fossil_isspace(zIn[0]) ) zIn++;
410425
if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
411426
zIn += 4;
412427
while( zIn[0] ){
413428
if( fossil_isspace(zIn[0]) ) zIn++;
@@ -444,11 +459,11 @@
444459
seenTitle = 1;
445460
if( seenClass ) return 1;
446461
}
447462
}
448463
return seenClass;
449
-}
464
+}
450465
451466
/*
452467
** Look for a file named zName in the checkin with RID=vid. Load the content
453468
** of that file into pContent and return the RID for the file. Or return 0
454469
** if the file is not found or could not be loaded.
@@ -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++){}
@@ -785,8 +800,8 @@
785800
** Search for documents that match a user-supplied pattern.
786801
*/
787802
void doc_search_page(void){
788803
login_check_credentials();
789804
style_header("Document Search");
790
- search_screen(SRCH_DOC, "docsrch");
805
+ search_screen(SRCH_DOC, 0);
791806
style_footer();
792807
}
793808
--- 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'>
@@ -403,11 +418,11 @@
403 const char *zAttr;
404 const char *zValue;
405 int nAttr, nValue;
406 int seenClass = 0;
407 int seenTitle = 0;
408
409 while( fossil_isspace(zIn[0]) ) zIn++;
410 if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
411 zIn += 4;
412 while( zIn[0] ){
413 if( fossil_isspace(zIn[0]) ) zIn++;
@@ -444,11 +459,11 @@
444 seenTitle = 1;
445 if( seenClass ) return 1;
446 }
447 }
448 return seenClass;
449 }
450
451 /*
452 ** Look for a file named zName in the checkin with RID=vid. Load the content
453 ** of that file into pContent and return the RID for the file. Or return 0
454 ** if the file is not found or could not be loaded.
@@ -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++){}
@@ -785,8 +800,8 @@
785 ** Search for documents that match a user-supplied pattern.
786 */
787 void doc_search_page(void){
788 login_check_credentials();
789 style_header("Document Search");
790 search_screen(SRCH_DOC, "docsrch");
791 style_footer();
792 }
793
--- 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'>
@@ -403,11 +418,11 @@
418 const char *zAttr;
419 const char *zValue;
420 int nAttr, nValue;
421 int seenClass = 0;
422 int seenTitle = 0;
423
424 while( fossil_isspace(zIn[0]) ) zIn++;
425 if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
426 zIn += 4;
427 while( zIn[0] ){
428 if( fossil_isspace(zIn[0]) ) zIn++;
@@ -444,11 +459,11 @@
459 seenTitle = 1;
460 if( seenClass ) return 1;
461 }
462 }
463 return seenClass;
464 }
465
466 /*
467 ** Look for a file named zName in the checkin with RID=vid. Load the content
468 ** of that file into pContent and return the RID for the file. Or return 0
469 ** if the file is not found or could not be loaded.
@@ -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++){}
@@ -785,8 +800,8 @@
800 ** Search for documents that match a user-supplied pattern.
801 */
802 void doc_search_page(void){
803 login_check_credentials();
804 style_header("Document Search");
805 search_screen(SRCH_DOC, 0);
806 style_footer();
807 }
808
+142 -95
--- 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,160 +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", "Context", "%s/timeline?c=%T",
135
- g.zTop, zETime);
156
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157
+ style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
136158
if( g.perm.Hyperlink ){
137159
if( verboseFlag ){
138
- style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
139
- g.zTop, zEventId, zUuid);
160
+ style_submenu_element("Plain", 0,
161
+ "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
162
+ zId, zUuid);
140163
if( nextRid ){
141164
char *zNext;
142165
zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
143
- style_submenu_element("Next", "Next",
144
- "%s/event?name=%s&aid=%s&v",
145
- g.zTop, zEventId, zNext);
166
+ style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v",
167
+ zId, zNext);
146168
free(zNext);
147169
}
148170
if( prevRid ){
149171
char *zPrev;
150172
zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
151
- style_submenu_element("Prev", "Prev",
152
- "%s/event?name=%s&aid=%s&v",
153
- g.zTop, zEventId, zPrev);
173
+ style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v",
174
+ zId, zPrev);
154175
free(zPrev);
155176
}
156177
}else{
157
- style_submenu_element("Detail", "Detail",
158
- "%s/event?name=%s&aid=%s&v",
159
- g.zTop, zEventId, zUuid);
178
+ style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v",
179
+ zId, zUuid);
160180
}
161181
}
162182
163183
if( verboseFlag && g.perm.Hyperlink ){
164184
int i;
165185
const char *zClr = 0;
166186
Blob comment;
167187
168
- zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
169
- @ <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
170190
@ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
171
- @ entered by user <b>%h(pEvent->zUser)</b> on
191
+ @ entered by user <b>%h(pTNote->zUser)</b> on
172192
@ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
173193
@ <blockquote>
174
- for(i=0; i<pEvent->nTag; i++){
175
- if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
176
- 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;
177197
}
178198
}
179199
if( zClr && zClr[0]==0 ) zClr = 0;
180200
if( zClr ){
181201
@ <div style="background-color: %h(zClr);">
182202
}else{
183203
@ <div>
184204
}
185
- blob_init(&comment, pEvent->zComment, -1);
205
+ blob_init(&comment, pTNote->zComment, -1);
186206
wiki_convert(&comment, 0, WIKI_INLINE);
187207
blob_reset(&comment);
188208
@ </div>
189209
@ </blockquote><hr />
190210
}
191211
192
- 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
+ }
193221
style_footer();
194
- manifest_destroy(pEvent);
222
+ manifest_destroy(pTNote);
195223
}
196224
197225
/*
226
+** WEBPAGE: technoteedit
198227
** WEBPAGE: eventedit
199
-** URL: /eventedit?name=EVENTID
228
+**
229
+** Revise or create a technical note (formerly called an 'event').
230
+**
231
+** Parameters:
200232
**
201
-** 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.
202235
*/
203236
void eventedit_page(void){
204237
char *zTag;
205238
int rid = 0;
206239
Blob event;
207
- const char *zEventId;
240
+ const char *zId;
208241
int n;
209242
const char *z;
210243
char *zBody = (char*)P("w");
211244
char *zETime = (char*)P("t");
212245
const char *zComment = P("c");
213246
const char *zTags = P("g");
214247
const char *zClr;
248
+ const char *zMimetype = P("mimetype");
249
+ int isNew = 0;
215250
216251
if( zBody ){
217252
zBody = mprintf("%s", zBody);
218253
}
219254
login_check_credentials();
220
- zEventId = P("name");
221
- if( zEventId==0 ){
222
- 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;
223259
}else{
224
- int nEventId = strlen(zEventId);
225
- if( nEventId!=40 || !validate16(zEventId, 40) ){
260
+ int nId = strlen(zId);
261
+ if( !validate16(zId, nId) ){
226262
fossil_redirect_home();
227263
return;
228264
}
229265
}
230
- zTag = mprintf("event-%s", zEventId);
266
+ zTag = mprintf("event-%s", zId);
231267
rid = db_int(0,
232268
"SELECT rid FROM tagxref"
233
- " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
269
+ " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')"
234270
" ORDER BY mtime DESC", zTag
235271
);
236272
free(zTag);
237273
238274
/* Need both check-in and wiki-write or wiki-create privileges in order
239275
** to edit/create an event.
240276
*/
241277
if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
242
- login_needed();
278
+ login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
243279
return;
244280
}
245281
246282
/* Figure out the color */
247283
if( rid ){
248
- zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
284
+ zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
249285
}else{
250286
zClr = "";
287
+ isNew = 1;
251288
}
252289
zClr = PD("clr",zClr);
253290
if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
254291
255292
256293
/* If editing an existing event, extract the key fields to use as
257294
** a starting point for the edit.
258295
*/
259
- if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
260
- Manifest *pEvent;
261
- pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
262
- if( pEvent && pEvent->type==CFTYPE_EVENT ){
263
- 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;
264303
if( zETime==0 ){
265
- zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
304
+ zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
266305
}
267
- if( zComment==0 ) zComment = pEvent->zComment;
306
+ if( zComment==0 ) zComment = pTNote->zComment;
307
+ if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
268308
}
269309
if( zTags==0 ){
270310
zTags = db_text(0,
271311
"SELECT group_concat(substr(tagname,5),', ')"
272312
" FROM tagxref, tag"
@@ -280,11 +320,11 @@
280320
zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
281321
if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
282322
char *zDate;
283323
Blob cksum;
284324
int nrid, n;
285
- blob_zero(&event);
325
+ blob_init(&event, 0, 0);
286326
db_begin_transaction();
287327
login_verify_csrf_secret();
288328
while( fossil_isspace(zComment[0]) ) zComment++;
289329
n = strlen(zComment);
290330
while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -293,17 +333,20 @@
293333
}
294334
zDate = date_in_standard_format("now");
295335
blob_appendf(&event, "D %s\n", zDate);
296336
free(zDate);
297337
zETime[10] = 'T';
298
- blob_appendf(&event, "E %s %s\n", zETime, zEventId);
338
+ blob_appendf(&event, "E %s %s\n", zETime, zId);
299339
zETime[10] = ' ';
300340
if( rid ){
301341
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
302342
blob_appendf(&event, "P %s\n", zUuid);
303343
free(zUuid);
304344
}
345
+ if( zMimetype && zMimetype[0] ){
346
+ blob_appendf(&event, "N %s\n", zMimetype);
347
+ }
305348
if( zClr && zClr[0] ){
306349
blob_appendf(&event, "T +bgcolor * %F\n", zClr);
307350
}
308351
if( zTags && zTags[0] ){
309352
Blob tags, one;
@@ -354,22 +397,26 @@
354397
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
355398
manifest_crosslink(nrid, &event, MC_NONE);
356399
assert( blob_is_reset(&event) );
357400
content_deltify(rid, nrid, 0);
358401
db_end_transaction(0);
359
- cgi_redirectf("event?name=%T", zEventId);
402
+ cgi_redirectf("technote?name=%T", zId);
360403
}
361404
if( P("cancel")!=0 ){
362
- cgi_redirectf("event?name=%T", zEventId);
405
+ cgi_redirectf("technote?name=%T", zId);
363406
return;
364407
}
365408
if( zBody==0 ){
366
- zBody = mprintf("<i>Event Text</i>");
409
+ zBody = mprintf("Insert new content here...");
367410
}
368
- 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
+ }
369416
if( P("preview")!=0 ){
370
- Blob title, tail, com;
417
+ Blob com;
371418
@ <p><b>Timeline comment preview:</b></p>
372419
@ <blockquote>
373420
@ <table border="0">
374421
if( zClr && zClr[0] ){
375422
@ <tr><td style="background-color: %h(zClr);">
@@ -381,55 +428,55 @@
381428
wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
382429
@ </td></tr></table>
383430
@ </blockquote>
384431
@ <p><b>Page content preview:</b><p>
385432
@ <blockquote>
386
- blob_zero(&event);
433
+ blob_init(&event, 0, 0);
387434
blob_append(&event, zBody, -1);
388
- if( wiki_find_title(&event, &title, &tail) ){
389
- @ <h2 align="center">%h(blob_str(&title))</h2>
390
- wiki_convert(&tail, 0, 0);
391
- }else{
392
- wiki_convert(&event, 0, 0);
393
- }
435
+ wiki_render_by_mimetype(&event, zMimetype);
394436
@ </blockquote><hr />
395437
blob_reset(&event);
396438
}
397439
for(n=2, z=zBody; z[0]; z++){
398440
if( z[0]=='\n' ) n++;
399441
}
400442
if( n<20 ) n = 20;
401443
if( n>40 ) n = 40;
402
- @ <form method="post" action="%s(g.zTop)/eventedit"><div>
444
+ @ <form method="post" action="%R/technoteedit"><div>
403445
login_insert_csrf_secret();
404
- @ <input type="hidden" name="name" value="%h(zEventId)" />
446
+ @ <input type="hidden" name="name" value="%h(zId)" />
405447
@ <table border="0" cellspacing="10">
406448
407
- @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
449
+ @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
408450
@ <td valign="top">
409451
@ <input type="text" name="t" size="25" value="%h(zETime)" />
410452
@ </td></tr>
411453
412
- @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
454
+ @ <tr><th align="right" valign="top">Timeline Comment:</th>
413455
@ <td valign="top">
414
- @ <textarea name="c" class="eventedit" cols="80"
456
+ @ <textarea name="c" class="technoteedit" cols="80"
415457
@ rows="3" wrap="virtual">%h(zComment)</textarea>
416458
@ </td></tr>
417459
418
- @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
460
+ @ <tr><th align="right" valign="top">Timeline Background Color:</th>
419461
@ <td valign="top">
420462
render_color_chooser(0, zClr, 0, "clr", "cclr");
421463
@ </td></tr>
422464
423465
@ <tr><th align="right" valign="top">Tags:</th>
424466
@ <td valign="top">
425467
@ <input type="text" name="g" size="40" value="%h(zTags)" />
426468
@ </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>
427474
428475
@ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
429476
@ <td valign="top">
430
- @ <textarea name="w" class="eventedit" cols="80"
477
+ @ <textarea name="w" class="technoteedit" cols="80"
431478
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
432479
@ </td></tr>
433480
434481
@ <tr><td colspan="2">
435482
@ <input type="submit" name="preview" value="Preview Your Changes" />
436483
--- 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,160 +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", "Context", "%s/timeline?c=%T",
135 g.zTop, zETime);
136 if( g.perm.Hyperlink ){
137 if( verboseFlag ){
138 style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
139 g.zTop, zEventId, zUuid);
 
140 if( nextRid ){
141 char *zNext;
142 zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
143 style_submenu_element("Next", "Next",
144 "%s/event?name=%s&aid=%s&v",
145 g.zTop, zEventId, zNext);
146 free(zNext);
147 }
148 if( prevRid ){
149 char *zPrev;
150 zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
151 style_submenu_element("Prev", "Prev",
152 "%s/event?name=%s&aid=%s&v",
153 g.zTop, zEventId, zPrev);
154 free(zPrev);
155 }
156 }else{
157 style_submenu_element("Detail", "Detail",
158 "%s/event?name=%s&aid=%s&v",
159 g.zTop, zEventId, zUuid);
160 }
161 }
162
163 if( verboseFlag && g.perm.Hyperlink ){
164 int i;
165 const char *zClr = 0;
166 Blob comment;
167
168 zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
169 @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at
170 @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
171 @ entered by user <b>%h(pEvent->zUser)</b> on
172 @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
173 @ <blockquote>
174 for(i=0; i<pEvent->nTag; i++){
175 if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
176 zClr = pEvent->aTag[i].zValue;
177 }
178 }
179 if( zClr && zClr[0]==0 ) zClr = 0;
180 if( zClr ){
181 @ <div style="background-color: %h(zClr);">
182 }else{
183 @ <div>
184 }
185 blob_init(&comment, pEvent->zComment, -1);
186 wiki_convert(&comment, 0, WIKI_INLINE);
187 blob_reset(&comment);
188 @ </div>
189 @ </blockquote><hr />
190 }
191
192 wiki_convert(&tail, 0, 0);
 
 
 
 
 
 
 
 
193 style_footer();
194 manifest_destroy(pEvent);
195 }
196
197 /*
 
198 ** WEBPAGE: eventedit
199 ** URL: /eventedit?name=EVENTID
 
 
 
200 **
201 ** Edit an event. If name is omitted, create a new event.
 
202 */
203 void eventedit_page(void){
204 char *zTag;
205 int rid = 0;
206 Blob event;
207 const char *zEventId;
208 int n;
209 const char *z;
210 char *zBody = (char*)P("w");
211 char *zETime = (char*)P("t");
212 const char *zComment = P("c");
213 const char *zTags = P("g");
214 const char *zClr;
 
 
215
216 if( zBody ){
217 zBody = mprintf("%s", zBody);
218 }
219 login_check_credentials();
220 zEventId = P("name");
221 if( zEventId==0 ){
222 zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))");
 
223 }else{
224 int nEventId = strlen(zEventId);
225 if( nEventId!=40 || !validate16(zEventId, 40) ){
226 fossil_redirect_home();
227 return;
228 }
229 }
230 zTag = mprintf("event-%s", zEventId);
231 rid = db_int(0,
232 "SELECT rid FROM tagxref"
233 " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
234 " ORDER BY mtime DESC", zTag
235 );
236 free(zTag);
237
238 /* Need both check-in and wiki-write or wiki-create privileges in order
239 ** to edit/create an event.
240 */
241 if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
242 login_needed();
243 return;
244 }
245
246 /* Figure out the color */
247 if( rid ){
248 zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
249 }else{
250 zClr = "";
 
251 }
252 zClr = PD("clr",zClr);
253 if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
254
255
256 /* If editing an existing event, extract the key fields to use as
257 ** a starting point for the edit.
258 */
259 if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
260 Manifest *pEvent;
261 pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
262 if( pEvent && pEvent->type==CFTYPE_EVENT ){
263 if( zBody==0 ) zBody = pEvent->zWiki;
 
 
264 if( zETime==0 ){
265 zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
266 }
267 if( zComment==0 ) zComment = pEvent->zComment;
 
268 }
269 if( zTags==0 ){
270 zTags = db_text(0,
271 "SELECT group_concat(substr(tagname,5),', ')"
272 " FROM tagxref, tag"
@@ -280,11 +320,11 @@
280 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
281 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
282 char *zDate;
283 Blob cksum;
284 int nrid, n;
285 blob_zero(&event);
286 db_begin_transaction();
287 login_verify_csrf_secret();
288 while( fossil_isspace(zComment[0]) ) zComment++;
289 n = strlen(zComment);
290 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
@@ -293,17 +333,20 @@
293 }
294 zDate = date_in_standard_format("now");
295 blob_appendf(&event, "D %s\n", zDate);
296 free(zDate);
297 zETime[10] = 'T';
298 blob_appendf(&event, "E %s %s\n", zETime, zEventId);
299 zETime[10] = ' ';
300 if( rid ){
301 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
302 blob_appendf(&event, "P %s\n", zUuid);
303 free(zUuid);
304 }
 
 
 
305 if( zClr && zClr[0] ){
306 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
307 }
308 if( zTags && zTags[0] ){
309 Blob tags, one;
@@ -354,22 +397,26 @@
354 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
355 manifest_crosslink(nrid, &event, MC_NONE);
356 assert( blob_is_reset(&event) );
357 content_deltify(rid, nrid, 0);
358 db_end_transaction(0);
359 cgi_redirectf("event?name=%T", zEventId);
360 }
361 if( P("cancel")!=0 ){
362 cgi_redirectf("event?name=%T", zEventId);
363 return;
364 }
365 if( zBody==0 ){
366 zBody = mprintf("<i>Event Text</i>");
367 }
368 style_header("Edit Event %S", zEventId);
 
 
 
 
369 if( P("preview")!=0 ){
370 Blob title, tail, com;
371 @ <p><b>Timeline comment preview:</b></p>
372 @ <blockquote>
373 @ <table border="0">
374 if( zClr && zClr[0] ){
375 @ <tr><td style="background-color: %h(zClr);">
@@ -381,55 +428,55 @@
381 wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
382 @ </td></tr></table>
383 @ </blockquote>
384 @ <p><b>Page content preview:</b><p>
385 @ <blockquote>
386 blob_zero(&event);
387 blob_append(&event, zBody, -1);
388 if( wiki_find_title(&event, &title, &tail) ){
389 @ <h2 align="center">%h(blob_str(&title))</h2>
390 wiki_convert(&tail, 0, 0);
391 }else{
392 wiki_convert(&event, 0, 0);
393 }
394 @ </blockquote><hr />
395 blob_reset(&event);
396 }
397 for(n=2, z=zBody; z[0]; z++){
398 if( z[0]=='\n' ) n++;
399 }
400 if( n<20 ) n = 20;
401 if( n>40 ) n = 40;
402 @ <form method="post" action="%s(g.zTop)/eventedit"><div>
403 login_insert_csrf_secret();
404 @ <input type="hidden" name="name" value="%h(zEventId)" />
405 @ <table border="0" cellspacing="10">
406
407 @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
408 @ <td valign="top">
409 @ <input type="text" name="t" size="25" value="%h(zETime)" />
410 @ </td></tr>
411
412 @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
413 @ <td valign="top">
414 @ <textarea name="c" class="eventedit" cols="80"
415 @ rows="3" wrap="virtual">%h(zComment)</textarea>
416 @ </td></tr>
417
418 @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
419 @ <td valign="top">
420 render_color_chooser(0, zClr, 0, "clr", "cclr");
421 @ </td></tr>
422
423 @ <tr><th align="right" valign="top">Tags:</th>
424 @ <td valign="top">
425 @ <input type="text" name="g" size="40" value="%h(zTags)" />
426 @ </td></tr>
 
 
 
 
 
427
428 @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
429 @ <td valign="top">
430 @ <textarea name="w" class="eventedit" cols="80"
431 @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
432 @ </td></tr>
433
434 @ <tr><td colspan="2">
435 @ <input type="submit" name="preview" value="Preview Your Changes" />
436
--- 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,160 +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"
@@ -280,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--; }
@@ -293,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;
@@ -354,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);">
@@ -381,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 -20
--- src/file.c
+++ src/file.c
@@ -232,33 +232,24 @@
232232
blob_reset(&content);
233233
}
234234
235235
/*
236236
** Return file permissions (normal, executable, or symlink):
237
-** - PERM_EXE if file is executable;
237
+** - PERM_EXE on Unix if file is executable;
238238
** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on;
239239
** - PERM_REG for all other cases (regular file, directory, fifo, etc).
240240
*/
241241
int file_wd_perm(const char *zFilename){
242
- if( getStat(zFilename, 1) ) return PERM_REG;
243
-#if defined(_WIN32)
244
-# ifndef S_IXUSR
245
-# define S_IXUSR _S_IEXEC
246
-# endif
247
- if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
248
- return PERM_EXE;
249
- else
250
- return PERM_REG;
251
-#else
252
- if( S_ISREG(fileStat.st_mode) &&
253
- ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )
254
- return PERM_EXE;
255
- else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
256
- return PERM_LNK;
257
- else
258
- 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
+ }
259249
#endif
250
+ return PERM_REG;
260251
}
261252
262253
/*
263254
** Return TRUE if the named file is an executable. Return false
264255
** for directories, devices, fifos, symlinks, etc.
@@ -432,16 +423,16 @@
432423
#if !defined(_WIN32)
433424
struct stat buf;
434425
if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0;
435426
if( onoff ){
436427
int targetMode = (buf.st_mode & 0444)>>2;
437
- if( (buf.st_mode & 0111)!=targetMode ){
428
+ if( (buf.st_mode & 0100) == 0 ){
438429
chmod(zFilename, buf.st_mode | targetMode);
439430
rc = 1;
440431
}
441432
}else{
442
- if( (buf.st_mode & 0111)!=0 ){
433
+ if( (buf.st_mode & 0100) != 0 ){
443434
chmod(zFilename, buf.st_mode & ~0111);
444435
rc = 1;
445436
}
446437
}
447438
#endif /* _WIN32 */
448439
--- src/file.c
+++ src/file.c
@@ -232,33 +232,24 @@
232 blob_reset(&content);
233 }
234
235 /*
236 ** Return file permissions (normal, executable, or symlink):
237 ** - PERM_EXE 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( getStat(zFilename, 1) ) return PERM_REG;
243 #if defined(_WIN32)
244 # ifndef S_IXUSR
245 # define S_IXUSR _S_IEXEC
246 # endif
247 if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
248 return PERM_EXE;
249 else
250 return PERM_REG;
251 #else
252 if( S_ISREG(fileStat.st_mode) &&
253 ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 )
254 return PERM_EXE;
255 else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
256 return PERM_LNK;
257 else
258 return PERM_REG;
259 #endif
 
260 }
261
262 /*
263 ** Return TRUE if the named file is an executable. Return false
264 ** for directories, devices, fifos, symlinks, etc.
@@ -432,16 +423,16 @@
432 #if !defined(_WIN32)
433 struct stat buf;
434 if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0;
435 if( onoff ){
436 int targetMode = (buf.st_mode & 0444)>>2;
437 if( (buf.st_mode & 0111)!=targetMode ){
438 chmod(zFilename, buf.st_mode | targetMode);
439 rc = 1;
440 }
441 }else{
442 if( (buf.st_mode & 0111)!=0 ){
443 chmod(zFilename, buf.st_mode & ~0111);
444 rc = 1;
445 }
446 }
447 #endif /* _WIN32 */
448
--- src/file.c
+++ src/file.c
@@ -232,33 +232,24 @@
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.
@@ -432,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
+47 -40
--- src/import.c
+++ src/import.c
@@ -720,39 +720,42 @@
720720
}
721721
722722
/*
723723
** COMMAND: import
724724
**
725
-** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY
725
+** Usage: %fossil import ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
726726
**
727
-** Read text generated by the git-fast-export command and use it to
727
+** Read interchange format generated by another VCS and use it to
728728
** construct a new Fossil repository named by the NEW-REPOSITORY
729
-** argument. The git-fast-export text is read from standard input.
729
+** argument. If no input file is supplied the interchange format
730
+** data is read from standard input.
730731
**
731732
** The git-fast-export file format is currently the only VCS interchange
732733
** format that is understood, though other interchange formats may be added
733734
** in the future.
734735
**
735736
** The --incremental option allows an existing repository to be extended
736737
** with new content.
737738
**
738739
** Options:
740
+** --git import from the git-fast-export file format (default)
739741
** --incremental allow importing into an existing repository
740742
**
741743
** See also: export
742744
*/
743
-void git_import_cmd(void){
745
+void import_cmd(void){
744746
char *zPassword;
745747
FILE *pIn;
746748
Stmt q;
747749
int forceFlag = find_option("force", "f", 0)!=0;
748750
int incrFlag = find_option("incremental", "i", 0)!=0;
751
+ int svnFlag = find_option("svn", 0, 0)!=0;
749752
750753
find_option("git",0,0); /* Skip the --git option for now */
751754
verify_all_options();
752755
if( g.argc!=3 && g.argc!=4 ){
753
- usage("REPOSITORY-NAME");
756
+ usage("NEW-REPOSITORY ?INPUT-FILE?");
754757
}
755758
if( g.argc==4 ){
756759
pIn = fossil_fopen(g.argv[3], "rb");
757760
}else{
758761
pIn = stdin;
@@ -762,48 +765,52 @@
762765
if( forceFlag ) file_delete(g.argv[2]);
763766
db_create_repository(g.argv[2]);
764767
}
765768
db_open_repository(g.argv[2]);
766769
db_open_config(0);
767
-
768
- /* The following temp-tables are used to hold information needed for
769
- ** the import.
770
- **
771
- ** The XMARK table provides a mapping from fast-import "marks" and symbols
772
- ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
773
- ** Given any valid fast-import symbol, the corresponding fossil rid and
774
- ** uuid can found by searching against the xmark.tname field.
775
- **
776
- ** The XBRANCH table maps commit marks and symbols into the branch those
777
- ** commits belong to. If xbranch.tname is a fast-import symbol for a
778
- ** checkin then xbranch.brnm is the branch that checkin is part of.
779
- **
780
- ** The XTAG table records information about tags that need to be applied
781
- ** to various branches after the import finishes. The xtag.tcontent field
782
- ** contains the text of an artifact that will add a tag to a check-in.
783
- ** The git-fast-export file format might specify the same tag multiple
784
- ** times but only the last tag should be used. And we do not know which
785
- ** occurrence of the tag is the last until the import finishes.
786
- */
787
- db_multi_exec(
788
- "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
789
- "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
790
- "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
791
- );
792
-
793770
794771
db_begin_transaction();
795772
if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
796
- git_fast_import(pIn);
797
- db_prepare(&q, "SELECT tcontent FROM xtag");
798
- while( db_step(&q)==SQLITE_ROW ){
799
- Blob record;
800
- db_ephemeral_blob(&q, 0, &record);
801
- fast_insert_content(&record, 0, 0);
802
- import_reset(0);
803
- }
804
- db_finalize(&q);
773
+
774
+ if( svnFlag ){
775
+ fossil_fatal("--svn format not implemented yet");
776
+ }else{
777
+ /* The following temp-tables are used to hold information needed for
778
+ ** the import.
779
+ **
780
+ ** The XMARK table provides a mapping from fast-import "marks" and symbols
781
+ ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
782
+ ** Given any valid fast-import symbol, the corresponding fossil rid and
783
+ ** uuid can found by searching against the xmark.tname field.
784
+ **
785
+ ** The XBRANCH table maps commit marks and symbols into the branch those
786
+ ** commits belong to. If xbranch.tname is a fast-import symbol for a
787
+ ** checkin then xbranch.brnm is the branch that checkin is part of.
788
+ **
789
+ ** The XTAG table records information about tags that need to be applied
790
+ ** to various branches after the import finishes. The xtag.tcontent field
791
+ ** contains the text of an artifact that will add a tag to a check-in.
792
+ ** The git-fast-export file format might specify the same tag multiple
793
+ ** times but only the last tag should be used. And we do not know which
794
+ ** occurrence of the tag is the last until the import finishes.
795
+ */
796
+ db_multi_exec(
797
+ "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
798
+ "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
799
+ "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
800
+ );
801
+
802
+ git_fast_import(pIn);
803
+ db_prepare(&q, "SELECT tcontent FROM xtag");
804
+ while( db_step(&q)==SQLITE_ROW ){
805
+ Blob record;
806
+ db_ephemeral_blob(&q, 0, &record);
807
+ fast_insert_content(&record, 0, 0);
808
+ import_reset(0);
809
+ }
810
+ db_finalize(&q);
811
+ }
805812
db_end_transaction(0);
806813
db_begin_transaction();
807814
fossil_print("Rebuilding repository meta-data...\n");
808815
rebuild_db(0, 1, !incrFlag);
809816
verify_cancel();
810817
--- src/import.c
+++ src/import.c
@@ -720,39 +720,42 @@
720 }
721
722 /*
723 ** COMMAND: import
724 **
725 ** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY
726 **
727 ** Read text generated by the git-fast-export command and use it to
728 ** construct a new Fossil repository named by the NEW-REPOSITORY
729 ** argument. The git-fast-export text is read from standard input.
 
730 **
731 ** The git-fast-export file format is currently the only VCS interchange
732 ** format that is understood, though other interchange formats may be added
733 ** in the future.
734 **
735 ** The --incremental option allows an existing repository to be extended
736 ** with new content.
737 **
738 ** Options:
 
739 ** --incremental allow importing into an existing repository
740 **
741 ** See also: export
742 */
743 void git_import_cmd(void){
744 char *zPassword;
745 FILE *pIn;
746 Stmt q;
747 int forceFlag = find_option("force", "f", 0)!=0;
748 int incrFlag = find_option("incremental", "i", 0)!=0;
 
749
750 find_option("git",0,0); /* Skip the --git option for now */
751 verify_all_options();
752 if( g.argc!=3 && g.argc!=4 ){
753 usage("REPOSITORY-NAME");
754 }
755 if( g.argc==4 ){
756 pIn = fossil_fopen(g.argv[3], "rb");
757 }else{
758 pIn = stdin;
@@ -762,48 +765,52 @@
762 if( forceFlag ) file_delete(g.argv[2]);
763 db_create_repository(g.argv[2]);
764 }
765 db_open_repository(g.argv[2]);
766 db_open_config(0);
767
768 /* The following temp-tables are used to hold information needed for
769 ** the import.
770 **
771 ** The XMARK table provides a mapping from fast-import "marks" and symbols
772 ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
773 ** Given any valid fast-import symbol, the corresponding fossil rid and
774 ** uuid can found by searching against the xmark.tname field.
775 **
776 ** The XBRANCH table maps commit marks and symbols into the branch those
777 ** commits belong to. If xbranch.tname is a fast-import symbol for a
778 ** checkin then xbranch.brnm is the branch that checkin is part of.
779 **
780 ** The XTAG table records information about tags that need to be applied
781 ** to various branches after the import finishes. The xtag.tcontent field
782 ** contains the text of an artifact that will add a tag to a check-in.
783 ** The git-fast-export file format might specify the same tag multiple
784 ** times but only the last tag should be used. And we do not know which
785 ** occurrence of the tag is the last until the import finishes.
786 */
787 db_multi_exec(
788 "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
789 "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
790 "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
791 );
792
793
794 db_begin_transaction();
795 if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
796 git_fast_import(pIn);
797 db_prepare(&q, "SELECT tcontent FROM xtag");
798 while( db_step(&q)==SQLITE_ROW ){
799 Blob record;
800 db_ephemeral_blob(&q, 0, &record);
801 fast_insert_content(&record, 0, 0);
802 import_reset(0);
803 }
804 db_finalize(&q);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805 db_end_transaction(0);
806 db_begin_transaction();
807 fossil_print("Rebuilding repository meta-data...\n");
808 rebuild_db(0, 1, !incrFlag);
809 verify_cancel();
810
--- src/import.c
+++ src/import.c
@@ -720,39 +720,42 @@
720 }
721
722 /*
723 ** COMMAND: import
724 **
725 ** Usage: %fossil import ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
726 **
727 ** Read interchange format generated by another VCS and use it to
728 ** construct a new Fossil repository named by the NEW-REPOSITORY
729 ** argument. If no input file is supplied the interchange format
730 ** data is read from standard input.
731 **
732 ** The git-fast-export file format is currently the only VCS interchange
733 ** format that is understood, though other interchange formats may be added
734 ** in the future.
735 **
736 ** The --incremental option allows an existing repository to be extended
737 ** with new content.
738 **
739 ** Options:
740 ** --git import from the git-fast-export file format (default)
741 ** --incremental allow importing into an existing repository
742 **
743 ** See also: export
744 */
745 void import_cmd(void){
746 char *zPassword;
747 FILE *pIn;
748 Stmt q;
749 int forceFlag = find_option("force", "f", 0)!=0;
750 int incrFlag = find_option("incremental", "i", 0)!=0;
751 int svnFlag = find_option("svn", 0, 0)!=0;
752
753 find_option("git",0,0); /* Skip the --git option for now */
754 verify_all_options();
755 if( g.argc!=3 && g.argc!=4 ){
756 usage("NEW-REPOSITORY ?INPUT-FILE?");
757 }
758 if( g.argc==4 ){
759 pIn = fossil_fopen(g.argv[3], "rb");
760 }else{
761 pIn = stdin;
@@ -762,48 +765,52 @@
765 if( forceFlag ) file_delete(g.argv[2]);
766 db_create_repository(g.argv[2]);
767 }
768 db_open_repository(g.argv[2]);
769 db_open_config(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
771 db_begin_transaction();
772 if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
773
774 if( svnFlag ){
775 fossil_fatal("--svn format not implemented yet");
776 }else{
777 /* The following temp-tables are used to hold information needed for
778 ** the import.
779 **
780 ** The XMARK table provides a mapping from fast-import "marks" and symbols
781 ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
782 ** Given any valid fast-import symbol, the corresponding fossil rid and
783 ** uuid can found by searching against the xmark.tname field.
784 **
785 ** The XBRANCH table maps commit marks and symbols into the branch those
786 ** commits belong to. If xbranch.tname is a fast-import symbol for a
787 ** checkin then xbranch.brnm is the branch that checkin is part of.
788 **
789 ** The XTAG table records information about tags that need to be applied
790 ** to various branches after the import finishes. The xtag.tcontent field
791 ** contains the text of an artifact that will add a tag to a check-in.
792 ** The git-fast-export file format might specify the same tag multiple
793 ** times but only the last tag should be used. And we do not know which
794 ** occurrence of the tag is the last until the import finishes.
795 */
796 db_multi_exec(
797 "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
798 "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
799 "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
800 );
801
802 git_fast_import(pIn);
803 db_prepare(&q, "SELECT tcontent FROM xtag");
804 while( db_step(&q)==SQLITE_ROW ){
805 Blob record;
806 db_ephemeral_blob(&q, 0, &record);
807 fast_insert_content(&record, 0, 0);
808 import_reset(0);
809 }
810 db_finalize(&q);
811 }
812 db_end_transaction(0);
813 db_begin_transaction();
814 fossil_print("Rebuilding repository meta-data...\n");
815 rebuild_db(0, 1, !incrFlag);
816 verify_cancel();
817
+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
+20 -10
--- 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,13 +591,16 @@
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;
591
-
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
+
592602
/* We should be done with options.. */
593603
verify_all_options();
594604
595605
db_begin_transaction();
596606
search_drop_index();
@@ -807,11 +817,11 @@
807817
int privateOnly = find_option("private",0,0)!=0;
808818
int bNeedRebuild = 0;
809819
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
810820
db_close(1);
811821
db_open_repository(g.zRepositoryName);
812
-
822
+
813823
/* We should be done with options.. */
814824
verify_all_options();
815825
816826
if( !bForce ){
817827
Blob ans;
@@ -937,11 +947,11 @@
937947
fossil_print("\"%s\" is not a directory\n\n", g.argv[3]);
938948
usage("FILENAME DIRECTORY");
939949
}
940950
db_create_repository(g.argv[2]);
941951
db_open_repository(g.argv[2]);
942
-
952
+
943953
/* We should be done with options.. */
944954
verify_all_options();
945955
946956
db_open_config(0);
947957
db_begin_transaction();
948958
--- 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,13 +591,16 @@
584 }
585 db_close(1);
586 db_open_repository(g.zRepositoryName);
587 }
588 runReindex = search_index_exists();
589 if( find_option("index",0,0)!=0 ) runReindex = 1;
590 if( find_option("no-index",0,0)!=0 ) runReindex = 0;
591
 
 
 
592 /* We should be done with options.. */
593 verify_all_options();
594
595 db_begin_transaction();
596 search_drop_index();
@@ -807,11 +817,11 @@
807 int privateOnly = find_option("private",0,0)!=0;
808 int bNeedRebuild = 0;
809 db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
810 db_close(1);
811 db_open_repository(g.zRepositoryName);
812
813 /* We should be done with options.. */
814 verify_all_options();
815
816 if( !bForce ){
817 Blob ans;
@@ -937,11 +947,11 @@
937 fossil_print("\"%s\" is not a directory\n\n", g.argv[3]);
938 usage("FILENAME DIRECTORY");
939 }
940 db_create_repository(g.argv[2]);
941 db_open_repository(g.argv[2]);
942
943 /* We should be done with options.. */
944 verify_all_options();
945
946 db_open_config(0);
947 db_begin_transaction();
948
--- 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,13 +591,16 @@
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 search_drop_index();
@@ -807,11 +817,11 @@
817 int privateOnly = find_option("private",0,0)!=0;
818 int bNeedRebuild = 0;
819 db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
820 db_close(1);
821 db_open_repository(g.zRepositoryName);
822
823 /* We should be done with options.. */
824 verify_all_options();
825
826 if( !bForce ){
827 Blob ans;
@@ -937,11 +947,11 @@
947 fossil_print("\"%s\" is not a directory\n\n", g.argv[3]);
948 usage("FILENAME DIRECTORY");
949 }
950 db_create_repository(g.argv[2]);
951 db_open_repository(g.argv[2]);
952
953 /* We should be done with options.. */
954 verify_all_options();
955
956 db_open_config(0);
957 db_begin_transaction();
958
+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
+2 -2
--- src/rss.c
+++ src/rss.c
@@ -65,11 +65,11 @@
6565
@ FROM event, blob
6666
@ WHERE blob.rid=event.objid
6767
;
6868
6969
login_check_credentials();
70
- if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
70
+ if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
7171
return;
7272
}
7373
7474
blob_zero(&bSQL);
7575
blob_append( &bSQL, zSQL1, -1 );
@@ -83,11 +83,11 @@
8383
if( !g.perm.Read ){
8484
if( g.perm.RdTkt && g.perm.RdWiki ){
8585
blob_append(&bSQL, " AND event.type!='ci'", -1);
8686
}else if( g.perm.RdTkt ){
8787
blob_append(&bSQL, " AND event.type=='t'", -1);
88
-
88
+
8989
}else{
9090
blob_append(&bSQL, " AND event.type=='w'", -1);
9191
}
9292
}else if( !g.perm.RdWiki ){
9393
if( g.perm.RdTkt ){
9494
--- src/rss.c
+++ src/rss.c
@@ -65,11 +65,11 @@
65 @ FROM event, blob
66 @ WHERE blob.rid=event.objid
67 ;
68
69 login_check_credentials();
70 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
71 return;
72 }
73
74 blob_zero(&bSQL);
75 blob_append( &bSQL, zSQL1, -1 );
@@ -83,11 +83,11 @@
83 if( !g.perm.Read ){
84 if( g.perm.RdTkt && g.perm.RdWiki ){
85 blob_append(&bSQL, " AND event.type!='ci'", -1);
86 }else if( g.perm.RdTkt ){
87 blob_append(&bSQL, " AND event.type=='t'", -1);
88
89 }else{
90 blob_append(&bSQL, " AND event.type=='w'", -1);
91 }
92 }else if( !g.perm.RdWiki ){
93 if( g.perm.RdTkt ){
94
--- src/rss.c
+++ src/rss.c
@@ -65,11 +65,11 @@
65 @ FROM event, blob
66 @ WHERE blob.rid=event.objid
67 ;
68
69 login_check_credentials();
70 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
71 return;
72 }
73
74 blob_zero(&bSQL);
75 blob_append( &bSQL, zSQL1, -1 );
@@ -83,11 +83,11 @@
83 if( !g.perm.Read ){
84 if( g.perm.RdTkt && g.perm.RdWiki ){
85 blob_append(&bSQL, " AND event.type!='ci'", -1);
86 }else if( g.perm.RdTkt ){
87 blob_append(&bSQL, " AND event.type=='t'", -1);
88
89 }else{
90 blob_append(&bSQL, " AND event.type=='w'", -1);
91 }
92 }else if( !g.perm.RdWiki ){
93 if( g.perm.RdTkt ){
94
+1 -1
--- src/schema.c
+++ src/schema.c
@@ -230,11 +230,11 @@
230230
@ name TEXT UNIQUE -- Name of file page
231231
@ );
232232
@
233233
@ -- Linkages between checkins, files created by each checkin, and
234234
@ -- the names of those files.
235
-@ --
235
+@ --
236236
@ -- Each entry represents a file that changed content from pid to fid
237237
@ -- due to the check-in that goes from pmid to mid. fnid is the name
238238
@ -- of the file in the mid check-in. If the file was renamed as part
239239
@ -- of the mid check-in, then pfnid is the previous filename.
240240
@
241241
--- src/schema.c
+++ src/schema.c
@@ -230,11 +230,11 @@
230 @ name TEXT UNIQUE -- Name of file page
231 @ );
232 @
233 @ -- Linkages between checkins, files created by each checkin, and
234 @ -- the names of those files.
235 @ --
236 @ -- Each entry represents a file that changed content from pid to fid
237 @ -- due to the check-in that goes from pmid to mid. fnid is the name
238 @ -- of the file in the mid check-in. If the file was renamed as part
239 @ -- of the mid check-in, then pfnid is the previous filename.
240 @
241
--- src/schema.c
+++ src/schema.c
@@ -230,11 +230,11 @@
230 @ name TEXT UNIQUE -- Name of file page
231 @ );
232 @
233 @ -- Linkages between checkins, files created by each checkin, and
234 @ -- the names of those files.
235 @ --
236 @ -- Each entry represents a file that changed content from pid to fid
237 @ -- due to the check-in that goes from pmid to mid. fnid is the name
238 @ -- of the file in the mid check-in. If the file was renamed as part
239 @ -- of the mid check-in, then pfnid is the previous filename.
240 @
241
+362 -124
--- src/search.c
+++ src/search.c
@@ -41,11 +41,11 @@
4141
char *z; /* Text */
4242
int n; /* length */
4343
} a[SEARCH_MAX_TERM];
4444
/* Snippet controls */
4545
char *zPattern; /* The search pattern */
46
- char *zMarkBegin; /* Start of a match */
46
+ char *zMarkBegin; /* Start of a match */
4747
char *zMarkEnd; /* End of a match */
4848
char *zMarkGap; /* A gap between two matches */
4949
unsigned fSrchFlg; /* Flags */
5050
int iScore; /* Score of the last match attempt */
5151
Blob snip; /* Snippet for the most recent match */
@@ -104,11 +104,11 @@
104104
/*
105105
** Compile a search pattern
106106
*/
107107
Search *search_init(
108108
const char *zPattern, /* The search pattern */
109
- const char *zMarkBegin, /* Start of a match */
109
+ const char *zMarkBegin, /* Start of a match */
110110
const char *zMarkEnd, /* End of a match */
111111
const char *zMarkGap, /* A gap between two matches */
112112
unsigned fSrchFlg /* Flags */
113113
){
114114
Search *p;
@@ -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;
@@ -227,11 +227,11 @@
227227
if( zDoc[i]==0 ) break;
228228
}
229229
}
230230
231231
/* Finished search all documents.
232
- ** Every term must be seen or else the score is zero
232
+ ** Every term must be seen or else the score is zero
233233
*/
234234
score = 1;
235235
for(j=0; j<p->nTerm; j++) score *= anMatch[j];
236236
blob_reset(&p->snip);
237237
p->iScore = score;
@@ -317,11 +317,11 @@
317317
}
318318
319319
/*
320320
** COMMAND: test-match
321321
**
322
-** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
322
+** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
323323
*/
324324
void test_match_cmd(void){
325325
Search *p;
326326
int i;
327327
Blob x;
@@ -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
@@ -427,24 +431,51 @@
427431
}
428432
}
429433
430434
/*
431435
** This is an SQLite function that computes the searchable text.
432
-** It is a wrapper around the search_stext() routine. See the
436
+** It is a wrapper around the search_stext() routine. See the
433437
** search_stext() routine for further detail.
434438
*/
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
/*
@@ -567,26 +602,33 @@
567602
/*
568603
** Remove bits from srchFlags which are disallowed by either the
569604
** current server configuration or by user permissions.
570605
*/
571606
unsigned int search_restrict(unsigned int srchFlags){
607
+ static unsigned int knownGood = 0;
608
+ static unsigned int knownBad = 0;
609
+ static const struct { unsigned m; const char *zKey; } aSetng[] = {
610
+ { SRCH_CKIN, "search-ci" },
611
+ { SRCH_DOC, "search-doc" },
612
+ { SRCH_TKT, "search-tkt" },
613
+ { SRCH_WIKI, "search-wiki" },
614
+ };
615
+ int i;
572616
if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
573617
if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
574618
if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
575
- if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){
576
- srchFlags &= ~SRCH_CKIN;
577
- }
578
- if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){
579
- srchFlags &= ~SRCH_DOC;
580
- }
581
- if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){
582
- srchFlags &= ~SRCH_TKT;
583
- }
584
- if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){
585
- srchFlags &= ~SRCH_WIKI;
586
- }
587
- return srchFlags;
619
+ for(i=0; i<ArraySize(aSetng); i++){
620
+ unsigned int m = aSetng[i].m;
621
+ if( (srchFlags & m)==0 ) continue;
622
+ if( ((knownGood|knownBad) & m)!=0 ) continue;
623
+ if( db_get_boolean(aSetng[i].zKey,0) ){
624
+ knownGood |= m;
625
+ }else{
626
+ knownBad |= m;
627
+ }
628
+ }
629
+ return srchFlags & ~knownBad;
588630
}
589631
590632
/*
591633
** When this routine is called, there already exists a table
592634
**
@@ -609,21 +651,23 @@
609651
if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
610652
db_multi_exec(
611653
"CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
612654
);
613655
db_multi_exec(
614
- "INSERT INTO x(label,url,score,date,snip)"
615
- " 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)),"
616658
" printf('/doc/%T/%%s',foci.filename),"
617659
" search_score(),"
660
+ " 'd'||blob.rid,"
618661
" (SELECT datetime(event.mtime) FROM event"
619662
" WHERE objid=symbolic_name_to_rid('trunk')),"
620663
" search_snippet()"
621664
" FROM foci CROSS JOIN blob"
622665
" WHERE checkinID=symbolic_name_to_rid('trunk')"
623666
" AND blob.uuid=foci.uuid"
624
- " 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))"
625669
" AND %z",
626670
zDocBr, glob_expr("foci.filename", zDocGlob)
627671
);
628672
}
629673
}
@@ -634,18 +678,19 @@
634678
" FROM tag, tagxref"
635679
" WHERE tag.tagname GLOB 'wiki-*'"
636680
" AND tagxref.tagid=tag.tagid"
637681
" GROUP BY 1"
638682
")"
639
- "INSERT INTO x(label,url,score,date,snip)"
683
+ "INSERT INTO x(label,url,score,id,date,snip)"
640684
" SELECT printf('Wiki: %%s',name),"
641685
" printf('/wiki?name=%%s',urlencode(name)),"
642686
" search_score(),"
687
+ " 'w'||rid,"
643688
" datetime(mtime),"
644689
" search_snippet()"
645690
" FROM wiki"
646
- " WHERE search_match(stext('w',rid,name));"
691
+ " WHERE search_match(title('w',rid,name),body('w',rid,name));"
647692
);
648693
}
649694
if( (srchFlags & SRCH_CKIN)!=0 ){
650695
db_multi_exec(
651696
"WITH ckin(uuid,rid,mtime) AS ("
@@ -652,34 +697,45 @@
652697
" SELECT blob.uuid, event.objid, event.mtime"
653698
" FROM event, blob"
654699
" WHERE event.type='ci'"
655700
" AND blob.rid=event.objid"
656701
")"
657
- "INSERT INTO x(label,url,score,date,snip)"
702
+ "INSERT INTO x(label,url,score,id,date,snip)"
658703
" SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
659704
" printf('/timeline?c=%%s&n=8&y=ci',uuid),"
660705
" search_score(),"
706
+ " 'c'||rid,"
661707
" datetime(mtime),"
662708
" search_snippet()"
663709
" FROM ckin"
664
- " WHERE search_match(stext('c',rid,NULL));"
710
+ " WHERE search_match('',body('c',rid,NULL));"
665711
);
666712
}
667713
if( (srchFlags & SRCH_TKT)!=0 ){
668714
db_multi_exec(
669
- "INSERT INTO x(label,url,score, date,snip)"
670
- " SELECT printf('Ticket [%%.17s] on %%s',"
671
- "tkt_uuid,datetime(tkt_mtime)),"
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)),"
672718
" printf('/tktview/%%.20s',tkt_uuid),"
673719
" search_score(),"
720
+ " 't'||tkt_id,"
674721
" datetime(tkt_mtime),"
675722
" search_snippet()"
676723
" FROM ticket"
677
- " WHERE search_match(stext('t',tkt_id,NULL));"
724
+ " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));"
678725
);
679726
}
680727
}
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
+}
681737
682738
/*
683739
** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')).
684740
*/
685741
static void search_rank_sqlfunc(
@@ -687,24 +743,45 @@
687743
int argc,
688744
sqlite3_value **argv
689745
){
690746
const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]);
691747
int nVal = sqlite3_value_bytes(argv[0])/4;
748
+ int nCol; /* Number of columns in the index */
692749
int nTerm; /* Number of search terms in the query */
693
- int i; /* Loop counter */
694
- double r = 1.0; /* Score */
750
+ int i, j; /* Loop counter */
751
+ double r = 0.0; /* Score */
752
+ const unsigned *aX, *aS;
695753
696
- if( nVal<6 ) return;
697
- if( aVal[1]!=1 ) return;
754
+ if( nVal<2 ) return;
698755
nTerm = aVal[0];
699
- r *= 1<<((30*(aVal[2]-1))/nTerm);
700
- for(i=1; i<=nTerm; i++){
701
- int hits_this_row = aVal[3*i];
702
- int hits_all_rows = aVal[3*i+1];
703
- int rows_with_hit = aVal[3*i+2];
704
- double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit;
705
- r *= hits_this_row/avg_hits_per_row;
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;
706783
}
707784
#define SEARCH_DEBUG_RANK 0
708785
#if SEARCH_DEBUG_RANK
709786
{
710787
Blob x;
@@ -739,14 +816,15 @@
739816
if( srchFlags==0 ) return;
740817
sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0,
741818
search_rank_sqlfunc, 0, 0);
742819
blob_init(&sql, 0, 0);
743820
blob_appendf(&sql,
744
- "INSERT INTO x(label,url,score,date,snip) "
821
+ "INSERT INTO x(label,url,score,id,date,snip) "
745822
" SELECT ftsdocs.label,"
746823
" ftsdocs.url,"
747824
" rank(matchinfo(ftsidx,'pcsx')),"
825
+ " ftsdocs.type || ftsdocs.rid,"
748826
" datetime(ftsdocs.mtime),"
749827
" snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
750828
" FROM ftsidx CROSS JOIN ftsdocs"
751829
" WHERE ftsidx MATCH %Q"
752830
" AND ftsdocs.rowid=ftsidx.docid",
@@ -776,11 +854,10 @@
776854
}
777855
778856
/*
779857
** If z[] is of the form "<mark>TEXT</mark>" where TEXT contains
780858
** no white-space or punctuation, then return the length of the mark.
781
-** If
782859
*/
783860
static int isSnippetMark(const char *z){
784861
int n;
785862
if( strncmp(z,"<mark>",6)!=0 ) return 0;
786863
n = 6;
@@ -789,11 +866,11 @@
789866
return n+7;
790867
}
791868
792869
/*
793870
** Return a copy of zSnip (in memory obtained from fossil_malloc()) that
794
-** has all "<" characters, other than those on <mark> and </mark>,
871
+** has all "<" characters, other than those on <mark> and </mark>,
795872
** converted into "&lt;". This is similar to htmlize() except that
796873
** <mark> and </mark> are preserved.
797874
*/
798875
static char *cleanSnippet(const char *zSnip){
799876
int i;
@@ -832,29 +909,30 @@
832909
**
833910
** Return the number of rows.
834911
*/
835912
int search_run_and_output(
836913
const char *zPattern, /* The query pattern */
837
- unsigned int srchFlags /* What to search over */
914
+ unsigned int srchFlags, /* What to search over */
915
+ int fDebug /* Extra debugging output */
838916
){
839917
Stmt q;
840918
int nRow = 0;
841919
842920
srchFlags = search_restrict(srchFlags);
843921
if( srchFlags==0 ) return 0;
844922
search_sql_setup(g.db);
845923
add_content_sql_commands(g.db);
846924
db_multi_exec(
847
- "CREATE TEMP TABLE x(label,url,score,date,snip);"
925
+ "CREATE TEMP TABLE x(label,url,score,id,date,snip);"
848926
);
849927
if( !search_index_exists() ){
850928
search_fullscan(zPattern, srchFlags);
851929
}else{
852930
search_update_index(srchFlags);
853931
search_indexed(zPattern, srchFlags);
854932
}
855
- db_prepare(&q, "SELECT url, snip, label"
933
+ db_prepare(&q, "SELECT url, snip, label, score, id"
856934
" FROM x"
857935
" ORDER BY score DESC, date DESC;");
858936
while( db_step(&q)==SQLITE_ROW ){
859937
const char *zUrl = db_column_text(&q, 0);
860938
const char *zSnippet = db_column_text(&q, 1);
@@ -861,12 +939,15 @@
861939
const char *zLabel = db_column_text(&q, 2);
862940
if( nRow==0 ){
863941
@ <ol>
864942
}
865943
nRow++;
866
- @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br>
867
- @ <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>
868949
}
869950
db_finalize(&q);
870951
if( nRow ){
871952
@ </ol>
872953
}
@@ -876,46 +957,79 @@
876957
/*
877958
** Generate some HTML for doing search. At a minimum include the
878959
** Search-Text entry form. If the "s" query parameter is present, also
879960
** show search results.
880961
**
881
-** The srchFlags parameter is used to customize some of the text of the
882
-** form and the results. srchFlags should be either a single search
883
-** category or all categories. Any srchFlags with two or more bits set
962
+** The srchFlags parameter restricts the set of documents to be searched.
963
+** srchFlags should normally be either a single search category or all
964
+** categories. Any srchFlags with two or more bits set
884965
** is treated like SRCH_ALL for display purposes.
885966
**
886
-** The entry box is shown disabled if srchFlags is 0.
967
+** This routine automatically restricts srchFlag according to user
968
+** permissions and the server configuration. The entry box is shown
969
+** disabled if srchFlags is 0 after these restrictions are applied.
970
+**
971
+** If useYparam is true, then this routine also looks at the y= query
972
+** parameter for further search restrictions.
887973
*/
888
-void search_screen(unsigned srchFlags, const char *zAction){
974
+void search_screen(unsigned srchFlags, int useYparam){
889975
const char *zType = 0;
890976
const char *zClass = 0;
891977
const char *zDisable1;
892978
const char *zDisable2;
893979
const char *zPattern;
980
+ int fDebug = PB("debug");
981
+ srchFlags = search_restrict(srchFlags);
894982
switch( srchFlags ){
895983
case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
896984
case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
897985
case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
898986
case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
899987
}
900
- srchFlags = search_restrict(srchFlags);
901988
if( srchFlags==0 ){
902989
zDisable1 = " disabled";
903990
zDisable2 = " disabled";
904991
zPattern = "";
905992
}else{
906993
zDisable1 = " autofocus";
907994
zDisable2 = "";
908995
zPattern = PD("s","");
909996
}
910
- @ <form method='GET' action='%s(zAction)'>
997
+ @ <form method='GET' action='%R/%t(g.zPath)'>
911998
if( zClass ){
912999
@ <div class='searchForm searchForm%s(zClass)'>
9131000
}else{
9141001
@ <div class='searchForm'>
9151002
}
9161003
@ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
1004
+ if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){
1005
+ static const struct { char *z; char *zNm; unsigned m; } aY[] = {
1006
+ { "all", "All", SRCH_ALL },
1007
+ { "c", "Check-ins", SRCH_CKIN },
1008
+ { "d", "Docs", SRCH_DOC },
1009
+ { "t", "Tickets", SRCH_TKT },
1010
+ { "w", "Wiki", SRCH_WIKI },
1011
+ };
1012
+ const char *zY = PD("y","all");
1013
+ unsigned newFlags = srchFlags;
1014
+ int i;
1015
+ @ <select size='1' name='y'>
1016
+ for(i=0; i<ArraySize(aY); i++){
1017
+ if( (aY[i].m & srchFlags)==0 ) continue;
1018
+ cgi_printf("<option value='%s'", aY[i].z);
1019
+ if( fossil_strcmp(zY,aY[i].z)==0 ){
1020
+ newFlags &= aY[i].m;
1021
+ cgi_printf(" selected");
1022
+ }
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
+ }
9171031
@ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
9181032
if( srchFlags==0 ){
9191033
@ <p class="generalError">Search is disabled</p>
9201034
}
9211035
@ </div></form>
@@ -924,11 +1038,11 @@
9241038
if( zClass ){
9251039
@ <div class='searchResult searchResult%s(zClass)'>
9261040
}else{
9271041
@ <div class='searchResult'>
9281042
}
929
- if( search_run_and_output(zPattern, srchFlags)==0 ){
1043
+ if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
9301044
@ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
9311045
}
9321046
@ </div>
9331047
}
9341048
}
@@ -938,29 +1052,24 @@
9381052
**
9391053
** Search for check-in comments, documents, tickets, or wiki that
9401054
** match a user-supplied pattern.
9411055
*/
9421056
void search_page(void){
943
- unsigned srchFlags = SRCH_ALL;
944
- const char *zOnly = P("only");
945
-
9461057
login_check_credentials();
947
- if( zOnly ){
948
- if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN;
949
- if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC;
950
- if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT;
951
- if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI;
952
- }
9531058
style_header("Search");
954
- search_screen(srchFlags, "search");
1059
+ search_screen(SRCH_ALL, 1);
9551060
style_footer();
9561061
}
9571062
9581063
9591064
/*
9601065
** This is a helper function for search_stext(). Writing into pOut
9611066
** 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.
9621071
*/
9631072
static void get_stext_by_mimetype(
9641073
Blob *pIn,
9651074
const char *zMimetype,
9661075
Blob *pOut
@@ -968,41 +1077,74 @@
9681077
Blob html, title;
9691078
blob_init(&html, 0, 0);
9701079
blob_init(&title, 0, 0);
9711080
if( zMimetype==0 ) zMimetype = "text/plain";
9721081
if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
973
- 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
+ }
9741092
html_to_plaintext(blob_str(&html), pOut);
9751093
}else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
9761094
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
+ }
9771100
html_to_plaintext(blob_str(&html), pOut);
9781101
}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
+ }
9791105
html_to_plaintext(blob_str(pIn), pOut);
9801106
}else{
981
- *pOut = *pIn;
982
- blob_init(pIn, 0, 0);
1107
+ blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
9831108
}
9841109
blob_reset(&html);
9851110
blob_reset(&title);
9861111
}
9871112
9881113
/*
9891114
** Query pQuery is pointing at a single row of output. Append a text
9901115
** representation of every text-compatible column to pAccum.
9911116
*/
992
-static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){
1117
+static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){
9931118
int n = db_column_count(pQuery);
9941119
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
+ }
9951127
for(i=0; i<n; i++){
9961128
const char *zColName = db_column_name(pQuery,i);
1129
+ int eType = db_column_type(pQuery,i);
1130
+ if( i==iTitle ) continue;
9971131
if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue;
998
- if( fossil_stricmp(zColName,"mimetype")==0 ) continue;
999
- switch( db_column_type(pQuery,i) ){
1000
- case SQLITE_INTEGER:
1001
- case SQLITE_FLOAT:
1002
- case SQLITE_TEXT:
1003
- 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);
10041146
}
10051147
}
10061148
}
10071149
10081150
@@ -1028,11 +1170,11 @@
10281170
){
10291171
blob_init(pOut, 0, 0);
10301172
switch( cType ){
10311173
case 'd': { /* Documents */
10321174
Blob doc;
1033
- content_get(rid, &doc);
1175
+ content_get(rid, &doc);
10341176
blob_to_utf8_no_bom(&doc, 0);
10351177
get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
10361178
blob_reset(&doc);
10371179
break;
10381180
}
@@ -1047,10 +1189,11 @@
10471189
manifest_destroy(pWiki);
10481190
break;
10491191
}
10501192
case 'c': { /* Check-in Comments */
10511193
static Stmt q;
1194
+ static int isPlainText = -1;
10521195
db_static_prepare(&q,
10531196
"SELECT coalesce(ecomment,comment)"
10541197
" ||' (user: '||coalesce(euser,user,'?')"
10551198
" ||', tags: '||"
10561199
" (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1057,44 +1200,99 @@
10571200
" FROM tag, tagxref"
10581201
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
10591202
" AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
10601203
" ||')'"
10611204
" FROM event WHERE objid=:x AND type='ci'");
1205
+ if( isPlainText<0 ){
1206
+ isPlainText = db_get_boolean("timeline-plaintext",0);
1207
+ }
10621208
db_bind_int(&q, ":x", rid);
10631209
if( db_step(&q)==SQLITE_ROW ){
1064
- db_column_blob(&q, 0, pOut);
10651210
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
+ }
10661220
}
10671221
db_reset(&q);
10681222
break;
10691223
}
10701224
case 't': { /* Tickets */
10711225
static Stmt q1;
1072
- Blob raw;
1226
+ static int iTitle = -1;
10731227
db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
1074
- blob_init(&raw,0,0);
10751228
db_bind_int(&q1, ":rid", rid);
10761229
if( db_step(&q1)==SQLITE_ROW ){
1077
- 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);
10781237
}
10791238
db_reset(&q1);
10801239
if( db_table_exists("repository","ticketchng") ){
10811240
static Stmt q2;
10821241
db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid"
10831242
" ORDER BY tkt_mtime");
10841243
db_bind_int(&q2, ":rid", rid);
10851244
while( db_step(&q2)==SQLITE_ROW ){
1086
- append_all_ticket_fields(&raw, &q2);
1245
+ append_all_ticket_fields(pOut, &q2, -1);
10871246
}
10881247
db_reset(&q2);
10891248
}
1090
- html_to_plaintext(blob_str(&raw), pOut);
1091
- blob_reset(&raw);
10921249
break;
10931250
}
10941251
}
10951252
}
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
+}
10961294
10971295
/*
10981296
** COMMAND: test-search-stext
10991297
**
11001298
** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1105,14 +1303,34 @@
11051303
if( g.argc!=5 ) usage("TYPE RID NAME");
11061304
search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
11071305
fossil_print("%s\n",blob_str(&out));
11081306
blob_reset(&out);
11091307
}
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
+}
11101328
11111329
/* The schema for the full-text index
11121330
*/
1113
-static const char zFtsSchema[] =
1331
+static const char zFtsSchema[] =
11141332
@ -- One entry for each possible search result
11151333
@ CREATE TABLE IF NOT EXISTS "%w".ftsdocs(
11161334
@ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
11171335
@ type CHAR(1), -- Type of document
11181336
@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document
@@ -1119,20 +1337,21 @@
11191337
@ name TEXT, -- Additional document description
11201338
@ idxed BOOLEAN, -- True if currently in the index
11211339
@ label TEXT, -- Label to print on search results
11221340
@ url TEXT, -- URL to access this document
11231341
@ mtime DATE, -- Date when document created
1342
+@ bx TEXT, -- Temporary "body" content cache
11241343
@ UNIQUE(type,rid)
11251344
@ );
11261345
@ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
11271346
@ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
11281347
@ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
11291348
@ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1130
-@ stext(type,rid,name) AS 'stext'
1349
+@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body'
11311350
@ FROM ftsdocs;
11321351
@ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1133
-@ USING fts4(content="ftscontent", stext);
1352
+@ USING fts4(content="ftscontent", title, body%s);
11341353
;
11351354
static const char zFtsDrop[] =
11361355
@ DROP TABLE IF EXISTS "%w".ftsidx;
11371356
@ DROP VIEW IF EXISTS "%w".ftscontent;
11381357
@ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1142,13 +1361,15 @@
11421361
** Create or drop the tables associated with a full-text index.
11431362
*/
11441363
static int searchIdxExists = -1;
11451364
void search_create_index(void){
11461365
const char *zDb = db_name("repository");
1366
+ int useStemmer = db_get_boolean("search-stemmer",0);
1367
+ const char *zExtra = useStemmer ? ",tokenize=porter" : "";
11471368
search_sql_setup(g.db);
1148
- db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/,
1149
- 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*/);
11501371
searchIdxExists = 1;
11511372
}
11521373
void search_drop_index(void){
11531374
const char *zDb = db_name("repository");
11541375
db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1230,11 +1451,11 @@
12301451
}
12311452
}
12321453
12331454
/*
12341455
** If the doc-glob and doc-br settings are valid for document search
1235
-** and if the latest check-in on doc-br is in the unindexed set of
1456
+** and if the latest check-in on doc-br is in the unindexed set of
12361457
** check-ins, then update all 'd' entries in FTSDOCS that have
12371458
** changed.
12381459
*/
12391460
static void search_update_doc_index(void){
12401461
const char *zDocBr = db_get("doc-branch","trunk");
@@ -1266,78 +1487,84 @@
12661487
db_multi_exec(
12671488
"DELETE FROM ftsdocs WHERE type='d'"
12681489
" AND rid NOT IN (SELECT rid FROM current_docs)"
12691490
);
12701491
db_multi_exec(
1271
- "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)"
12721493
" SELECT 'd', rid, name, 0,"
1273
- " printf('Document: %%s',name),"
1494
+ " title('d',rid,name),"
1495
+ " body('d',rid,name),"
12741496
" printf('/doc/%q/%%s',urlencode(name)),"
12751497
" %.17g"
12761498
" FROM current_docs",
12771499
zBrUuid, rTime
12781500
);
12791501
db_multi_exec(
1280
- "INSERT INTO ftsidx(docid,stext)"
1281
- " 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"
12821504
);
12831505
db_multi_exec(
1284
- "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"
12851511
);
12861512
}
12871513
12881514
/*
1289
-** Deal with all of the unindexed 'c' terms in FTSDOCS
1515
+** Deal with all of the unindexed 'c' terms in FTSDOCS
12901516
*/
12911517
static void search_update_checkin_index(void){
12921518
db_multi_exec(
1293
- "INSERT INTO ftsidx(docid,stext)"
1294
- " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs"
1519
+ "INSERT INTO ftsidx(docid,title,body)"
1520
+ " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs"
12951521
" WHERE type='c' AND NOT idxed;"
12961522
);
12971523
db_multi_exec(
12981524
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
12991525
" SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
13001526
" printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1301
- " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid),"
1527
+ " printf('/timeline?y=ci&c=%%.20s',blob.uuid),"
13021528
" event.mtime"
13031529
" FROM ftsdocs, event, blob"
13041530
" WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
13051531
" AND event.objid=ftsdocs.rid"
13061532
" AND blob.rid=ftsdocs.rid"
13071533
);
13081534
}
13091535
13101536
/*
1311
-** Deal with all of the unindexed 't' terms in FTSDOCS
1537
+** Deal with all of the unindexed 't' terms in FTSDOCS
13121538
*/
13131539
static void search_update_ticket_index(void){
13141540
db_multi_exec(
1315
- "INSERT INTO ftsidx(docid,stext)"
1316
- " 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"
13171543
" WHERE type='t' AND NOT idxed;"
13181544
);
13191545
if( db_changes()==0 ) return;
13201546
db_multi_exec(
13211547
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
13221548
" SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
1323
- " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime)),"
1549
+ " printf('Ticket: %%s (%%s)',title('t',tkt_id,null),"
1550
+ " datetime(tkt_mtime)),"
13241551
" printf('/tktview/%%.20s',tkt_uuid),"
13251552
" tkt_mtime"
13261553
" FROM ftsdocs, ticket"
13271554
" WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
13281555
" AND ticket.tkt_id=ftsdocs.rid"
13291556
);
13301557
}
13311558
13321559
/*
1333
-** Deal with all of the unindexed 'w' terms in FTSDOCS
1560
+** Deal with all of the unindexed 'w' terms in FTSDOCS
13341561
*/
13351562
static void search_update_wiki_index(void){
13361563
db_multi_exec(
1337
- "INSERT INTO ftsidx(docid,stext)"
1338
- " 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"
13391566
" WHERE type='w' AND NOT idxed;"
13401567
);
13411568
if( db_changes()==0 ) return;
13421569
db_multi_exec(
13431570
"REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1390,19 +1617,22 @@
13901617
** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
13911618
**
13921619
** The "fossil fts-config" command configures the full-text search capabilities
13931620
** of the repository. Subcommands:
13941621
**
1395
-** reindex Rebuild the search index. Create it if it does
1396
-** not already exist
1622
+** reindex Rebuild the search index. This is a no-op if
1623
+** index search is disabled
13971624
**
13981625
** index (on|off) Turn the search index on or off
13991626
**
14001627
** enable cdtw Enable various kinds of search. c=Check-ins,
14011628
** d=Documents, t=Tickets, w=Wiki.
14021629
**
14031630
** 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.)
14041634
**
14051635
** The current search settings are displayed after any changes are applied.
14061636
** Run this command with no arguments to simply see the settings.
14071637
*/
14081638
void test_fts_cmd(void){
@@ -1409,18 +1639,19 @@
14091639
static const struct { int iCmd; const char *z; } aCmd[] = {
14101640
{ 1, "reindex" },
14111641
{ 2, "index" },
14121642
{ 3, "disable" },
14131643
{ 4, "enable" },
1644
+ { 5, "stemmer" },
14141645
};
14151646
static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
1416
- { "search-ckin", "check-in search:", "c" },
1417
- { "search-doc", "document search:", "d" },
1418
- { "search-tkt", "ticket search:", "t" },
1419
- { "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" },
14201651
};
1421
- char *zSubCmd;
1652
+ char *zSubCmd = 0;
14221653
int i, j, n;
14231654
int iCmd = 0;
14241655
int iAction = 0;
14251656
db_find_and_open_repository(0, 0);
14261657
if( g.argc>2 ){
@@ -1438,11 +1669,11 @@
14381669
return;
14391670
}
14401671
iCmd = aCmd[i].iCmd;
14411672
}
14421673
if( iCmd==1 ){
1443
- iAction = 2;
1674
+ if( search_index_exists() ) iAction = 2;
14441675
}
14451676
if( iCmd==2 ){
14461677
if( g.argc<3 ) usage("index (on|off)");
14471678
iAction = 1 + is_truth(g.argv[3]);
14481679
}
@@ -1449,18 +1680,23 @@
14491680
db_begin_transaction();
14501681
14511682
/* Adjust search settings */
14521683
if( iCmd==3 || iCmd==4 ){
14531684
const char *zCtrl;
1454
- if( g.argc<4 ) usage("enable STRING");
1685
+ if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd));
14551686
zCtrl = g.argv[3];
14561687
for(j=0; j<ArraySize(aSetng); j++){
14571688
if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
14581689
db_set_int(aSetng[j].zSetting, iCmd-3, 0);
14591690
}
14601691
}
14611692
}
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
+
14621698
14631699
/* destroy or rebuild the index, if requested */
14641700
if( iAction>=1 ){
14651701
search_drop_index();
14661702
}
@@ -1468,17 +1704,19 @@
14681704
search_rebuild_index();
14691705
}
14701706
14711707
/* Always show the status before ending */
14721708
for(i=0; i<ArraySize(aSetng); i++){
1473
- fossil_print("%-16s %s\n", aSetng[i].zName,
1709
+ fossil_print("%-16s %s\n", aSetng[i].zName,
14741710
db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
14751711
}
1712
+ fossil_print("%-16s %s\n", "Porter stemmer:",
1713
+ db_get_boolean("search-stemmer",0) ? "on" : "off");
14761714
if( search_index_exists() ){
14771715
fossil_print("%-16s enabled\n", "full-text index:");
14781716
fossil_print("%-16s %d\n", "documents:",
14791717
db_int(0, "SELECT count(*) FROM ftsdocs"));
14801718
}else{
14811719
fossil_print("%-16s disabled\n", "full-text index:");
14821720
}
14831721
db_end_transaction(0);
14841722
}
14851723
--- src/search.c
+++ src/search.c
@@ -41,11 +41,11 @@
41 char *z; /* Text */
42 int n; /* length */
43 } a[SEARCH_MAX_TERM];
44 /* Snippet controls */
45 char *zPattern; /* The search pattern */
46 char *zMarkBegin; /* Start of a match */
47 char *zMarkEnd; /* End of a match */
48 char *zMarkGap; /* A gap between two matches */
49 unsigned fSrchFlg; /* Flags */
50 int iScore; /* Score of the last match attempt */
51 Blob snip; /* Snippet for the most recent match */
@@ -104,11 +104,11 @@
104 /*
105 ** Compile a search pattern
106 */
107 Search *search_init(
108 const char *zPattern, /* The search pattern */
109 const char *zMarkBegin, /* Start of a match */
110 const char *zMarkEnd, /* End of a match */
111 const char *zMarkGap, /* A gap between two matches */
112 unsigned fSrchFlg /* Flags */
113 ){
114 Search *p;
@@ -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;
@@ -227,11 +227,11 @@
227 if( zDoc[i]==0 ) break;
228 }
229 }
230
231 /* Finished search all documents.
232 ** Every term must be seen or else the score is zero
233 */
234 score = 1;
235 for(j=0; j<p->nTerm; j++) score *= anMatch[j];
236 blob_reset(&p->snip);
237 p->iScore = score;
@@ -317,11 +317,11 @@
317 }
318
319 /*
320 ** COMMAND: test-match
321 **
322 ** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
323 */
324 void test_match_cmd(void){
325 Search *p;
326 int i;
327 Blob x;
@@ -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
@@ -427,24 +431,51 @@
427 }
428 }
429
430 /*
431 ** This is an SQLite function that computes the searchable text.
432 ** It is a wrapper around the search_stext() routine. See the
433 ** search_stext() routine for further detail.
434 */
435 static void search_stext_sqlfunc(
436 sqlite3_context *context,
437 int argc,
438 sqlite3_value **argv
439 ){
440 Blob txt;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441 const char *zType = (const char*)sqlite3_value_text(argv[0]);
442 int rid = sqlite3_value_int(argv[1]);
443 const char *zName = (const char*)sqlite3_value_text(argv[2]);
444 search_stext(zType[0], rid, zName, &txt);
445 sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free);
 
446 }
447
448 /*
449 ** Encode a string for use as a query parameter in a URL
450 */
@@ -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 /*
@@ -567,26 +602,33 @@
567 /*
568 ** Remove bits from srchFlags which are disallowed by either the
569 ** current server configuration or by user permissions.
570 */
571 unsigned int search_restrict(unsigned int srchFlags){
 
 
 
 
 
 
 
 
 
572 if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
573 if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
574 if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
575 if( (srchFlags & SRCH_CKIN)!=0 && db_get_boolean("search-ci",0)==0 ){
576 srchFlags &= ~SRCH_CKIN;
577 }
578 if( (srchFlags & SRCH_DOC)!=0 && db_get_boolean("search-doc",0)==0 ){
579 srchFlags &= ~SRCH_DOC;
580 }
581 if( (srchFlags & SRCH_TKT)!=0 && db_get_boolean("search-tkt",0)==0 ){
582 srchFlags &= ~SRCH_TKT;
583 }
584 if( (srchFlags & SRCH_WIKI)!=0 && db_get_boolean("search-wiki",0)==0 ){
585 srchFlags &= ~SRCH_WIKI;
586 }
587 return srchFlags;
588 }
589
590 /*
591 ** When this routine is called, there already exists a table
592 **
@@ -609,21 +651,23 @@
609 if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
610 db_multi_exec(
611 "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
612 );
613 db_multi_exec(
614 "INSERT INTO x(label,url,score,date,snip)"
615 " SELECT printf('Document: %%s',foci.filename),"
616 " printf('/doc/%T/%%s',foci.filename),"
617 " search_score(),"
 
618 " (SELECT datetime(event.mtime) FROM event"
619 " WHERE objid=symbolic_name_to_rid('trunk')),"
620 " search_snippet()"
621 " FROM foci CROSS JOIN blob"
622 " WHERE checkinID=symbolic_name_to_rid('trunk')"
623 " AND blob.uuid=foci.uuid"
624 " AND search_match(stext('d',blob.rid,foci.filename))"
 
625 " AND %z",
626 zDocBr, glob_expr("foci.filename", zDocGlob)
627 );
628 }
629 }
@@ -634,18 +678,19 @@
634 " FROM tag, tagxref"
635 " WHERE tag.tagname GLOB 'wiki-*'"
636 " AND tagxref.tagid=tag.tagid"
637 " GROUP BY 1"
638 ")"
639 "INSERT INTO x(label,url,score,date,snip)"
640 " SELECT printf('Wiki: %%s',name),"
641 " printf('/wiki?name=%%s',urlencode(name)),"
642 " search_score(),"
 
643 " datetime(mtime),"
644 " search_snippet()"
645 " FROM wiki"
646 " WHERE search_match(stext('w',rid,name));"
647 );
648 }
649 if( (srchFlags & SRCH_CKIN)!=0 ){
650 db_multi_exec(
651 "WITH ckin(uuid,rid,mtime) AS ("
@@ -652,34 +697,45 @@
652 " SELECT blob.uuid, event.objid, event.mtime"
653 " FROM event, blob"
654 " WHERE event.type='ci'"
655 " AND blob.rid=event.objid"
656 ")"
657 "INSERT INTO x(label,url,score,date,snip)"
658 " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime)),"
659 " printf('/timeline?c=%%s&n=8&y=ci',uuid),"
660 " search_score(),"
 
661 " datetime(mtime),"
662 " search_snippet()"
663 " FROM ckin"
664 " WHERE search_match(stext('c',rid,NULL));"
665 );
666 }
667 if( (srchFlags & SRCH_TKT)!=0 ){
668 db_multi_exec(
669 "INSERT INTO x(label,url,score, date,snip)"
670 " SELECT printf('Ticket [%%.17s] on %%s',"
671 "tkt_uuid,datetime(tkt_mtime)),"
672 " printf('/tktview/%%.20s',tkt_uuid),"
673 " search_score(),"
 
674 " datetime(tkt_mtime),"
675 " search_snippet()"
676 " FROM ticket"
677 " WHERE search_match(stext('t',tkt_id,NULL));"
678 );
679 }
680 }
 
 
 
 
 
 
 
 
 
681
682 /*
683 ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')).
684 */
685 static void search_rank_sqlfunc(
@@ -687,24 +743,45 @@
687 int argc,
688 sqlite3_value **argv
689 ){
690 const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]);
691 int nVal = sqlite3_value_bytes(argv[0])/4;
 
692 int nTerm; /* Number of search terms in the query */
693 int i; /* Loop counter */
694 double r = 1.0; /* Score */
 
695
696 if( nVal<6 ) return;
697 if( aVal[1]!=1 ) return;
698 nTerm = aVal[0];
699 r *= 1<<((30*(aVal[2]-1))/nTerm);
700 for(i=1; i<=nTerm; i++){
701 int hits_this_row = aVal[3*i];
702 int hits_all_rows = aVal[3*i+1];
703 int rows_with_hit = aVal[3*i+2];
704 double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit;
705 r *= hits_this_row/avg_hits_per_row;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706 }
707 #define SEARCH_DEBUG_RANK 0
708 #if SEARCH_DEBUG_RANK
709 {
710 Blob x;
@@ -739,14 +816,15 @@
739 if( srchFlags==0 ) return;
740 sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0,
741 search_rank_sqlfunc, 0, 0);
742 blob_init(&sql, 0, 0);
743 blob_appendf(&sql,
744 "INSERT INTO x(label,url,score,date,snip) "
745 " SELECT ftsdocs.label,"
746 " ftsdocs.url,"
747 " rank(matchinfo(ftsidx,'pcsx')),"
 
748 " datetime(ftsdocs.mtime),"
749 " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
750 " FROM ftsidx CROSS JOIN ftsdocs"
751 " WHERE ftsidx MATCH %Q"
752 " AND ftsdocs.rowid=ftsidx.docid",
@@ -776,11 +854,10 @@
776 }
777
778 /*
779 ** If z[] is of the form "<mark>TEXT</mark>" where TEXT contains
780 ** no white-space or punctuation, then return the length of the mark.
781 ** If
782 */
783 static int isSnippetMark(const char *z){
784 int n;
785 if( strncmp(z,"<mark>",6)!=0 ) return 0;
786 n = 6;
@@ -789,11 +866,11 @@
789 return n+7;
790 }
791
792 /*
793 ** Return a copy of zSnip (in memory obtained from fossil_malloc()) that
794 ** has all "<" characters, other than those on <mark> and </mark>,
795 ** converted into "&lt;". This is similar to htmlize() except that
796 ** <mark> and </mark> are preserved.
797 */
798 static char *cleanSnippet(const char *zSnip){
799 int i;
@@ -832,29 +909,30 @@
832 **
833 ** Return the number of rows.
834 */
835 int search_run_and_output(
836 const char *zPattern, /* The query pattern */
837 unsigned int srchFlags /* What to search over */
 
838 ){
839 Stmt q;
840 int nRow = 0;
841
842 srchFlags = search_restrict(srchFlags);
843 if( srchFlags==0 ) return 0;
844 search_sql_setup(g.db);
845 add_content_sql_commands(g.db);
846 db_multi_exec(
847 "CREATE TEMP TABLE x(label,url,score,date,snip);"
848 );
849 if( !search_index_exists() ){
850 search_fullscan(zPattern, srchFlags);
851 }else{
852 search_update_index(srchFlags);
853 search_indexed(zPattern, srchFlags);
854 }
855 db_prepare(&q, "SELECT url, snip, label"
856 " FROM x"
857 " ORDER BY score DESC, date DESC;");
858 while( db_step(&q)==SQLITE_ROW ){
859 const char *zUrl = db_column_text(&q, 0);
860 const char *zSnippet = db_column_text(&q, 1);
@@ -861,12 +939,15 @@
861 const char *zLabel = db_column_text(&q, 2);
862 if( nRow==0 ){
863 @ <ol>
864 }
865 nRow++;
866 @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br>
867 @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li>
 
 
 
868 }
869 db_finalize(&q);
870 if( nRow ){
871 @ </ol>
872 }
@@ -876,46 +957,79 @@
876 /*
877 ** Generate some HTML for doing search. At a minimum include the
878 ** Search-Text entry form. If the "s" query parameter is present, also
879 ** show search results.
880 **
881 ** The srchFlags parameter is used to customize some of the text of the
882 ** form and the results. srchFlags should be either a single search
883 ** category or all categories. Any srchFlags with two or more bits set
884 ** is treated like SRCH_ALL for display purposes.
885 **
886 ** The entry box is shown disabled if srchFlags is 0.
 
 
 
 
 
887 */
888 void search_screen(unsigned srchFlags, const char *zAction){
889 const char *zType = 0;
890 const char *zClass = 0;
891 const char *zDisable1;
892 const char *zDisable2;
893 const char *zPattern;
 
 
894 switch( srchFlags ){
895 case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break;
896 case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break;
897 case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break;
898 case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
899 }
900 srchFlags = search_restrict(srchFlags);
901 if( srchFlags==0 ){
902 zDisable1 = " disabled";
903 zDisable2 = " disabled";
904 zPattern = "";
905 }else{
906 zDisable1 = " autofocus";
907 zDisable2 = "";
908 zPattern = PD("s","");
909 }
910 @ <form method='GET' action='%s(zAction)'>
911 if( zClass ){
912 @ <div class='searchForm searchForm%s(zClass)'>
913 }else{
914 @ <div class='searchForm'>
915 }
916 @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
917 @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
918 if( srchFlags==0 ){
919 @ <p class="generalError">Search is disabled</p>
920 }
921 @ </div></form>
@@ -924,11 +1038,11 @@
924 if( zClass ){
925 @ <div class='searchResult searchResult%s(zClass)'>
926 }else{
927 @ <div class='searchResult'>
928 }
929 if( search_run_and_output(zPattern, srchFlags)==0 ){
930 @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
931 }
932 @ </div>
933 }
934 }
@@ -938,29 +1052,24 @@
938 **
939 ** Search for check-in comments, documents, tickets, or wiki that
940 ** match a user-supplied pattern.
941 */
942 void search_page(void){
943 unsigned srchFlags = SRCH_ALL;
944 const char *zOnly = P("only");
945
946 login_check_credentials();
947 if( zOnly ){
948 if( strchr(zOnly,'c') ) srchFlags &= SRCH_CKIN;
949 if( strchr(zOnly,'d') ) srchFlags &= SRCH_DOC;
950 if( strchr(zOnly,'t') ) srchFlags &= SRCH_TKT;
951 if( strchr(zOnly,'w') ) srchFlags &= SRCH_WIKI;
952 }
953 style_header("Search");
954 search_screen(srchFlags, "search");
955 style_footer();
956 }
957
958
959 /*
960 ** This is a helper function for search_stext(). Writing into pOut
961 ** the search text obtained from pIn according to zMimetype.
 
 
 
 
962 */
963 static void get_stext_by_mimetype(
964 Blob *pIn,
965 const char *zMimetype,
966 Blob *pOut
@@ -968,41 +1077,74 @@
968 Blob html, title;
969 blob_init(&html, 0, 0);
970 blob_init(&title, 0, 0);
971 if( zMimetype==0 ) zMimetype = "text/plain";
972 if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
973 wiki_convert(pIn, &html, 0);
 
 
 
 
 
 
 
 
 
974 html_to_plaintext(blob_str(&html), pOut);
975 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
976 markdown_to_html(pIn, &title, &html);
 
 
 
 
 
977 html_to_plaintext(blob_str(&html), pOut);
978 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
 
 
 
979 html_to_plaintext(blob_str(pIn), pOut);
980 }else{
981 *pOut = *pIn;
982 blob_init(pIn, 0, 0);
983 }
984 blob_reset(&html);
985 blob_reset(&title);
986 }
987
988 /*
989 ** Query pQuery is pointing at a single row of output. Append a text
990 ** representation of every text-compatible column to pAccum.
991 */
992 static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){
993 int n = db_column_count(pQuery);
994 int i;
 
 
 
 
 
 
 
995 for(i=0; i<n; i++){
996 const char *zColName = db_column_name(pQuery,i);
 
 
997 if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue;
998 if( fossil_stricmp(zColName,"mimetype")==0 ) continue;
999 switch( db_column_type(pQuery,i) ){
1000 case SQLITE_INTEGER:
1001 case SQLITE_FLOAT:
1002 case SQLITE_TEXT:
1003 blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i));
 
 
 
 
 
 
 
 
1004 }
1005 }
1006 }
1007
1008
@@ -1028,11 +1170,11 @@
1028 ){
1029 blob_init(pOut, 0, 0);
1030 switch( cType ){
1031 case 'd': { /* Documents */
1032 Blob doc;
1033 content_get(rid, &doc);
1034 blob_to_utf8_no_bom(&doc, 0);
1035 get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
1036 blob_reset(&doc);
1037 break;
1038 }
@@ -1047,10 +1189,11 @@
1047 manifest_destroy(pWiki);
1048 break;
1049 }
1050 case 'c': { /* Check-in Comments */
1051 static Stmt q;
 
1052 db_static_prepare(&q,
1053 "SELECT coalesce(ecomment,comment)"
1054 " ||' (user: '||coalesce(euser,user,'?')"
1055 " ||', tags: '||"
1056 " (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1057,44 +1200,99 @@
1057 " FROM tag, tagxref"
1058 " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
1059 " AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
1060 " ||')'"
1061 " FROM event WHERE objid=:x AND type='ci'");
 
 
 
1062 db_bind_int(&q, ":x", rid);
1063 if( db_step(&q)==SQLITE_ROW ){
1064 db_column_blob(&q, 0, pOut);
1065 blob_append(pOut, "\n", 1);
 
 
 
 
 
 
 
 
 
1066 }
1067 db_reset(&q);
1068 break;
1069 }
1070 case 't': { /* Tickets */
1071 static Stmt q1;
1072 Blob raw;
1073 db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid");
1074 blob_init(&raw,0,0);
1075 db_bind_int(&q1, ":rid", rid);
1076 if( db_step(&q1)==SQLITE_ROW ){
1077 append_all_ticket_fields(&raw, &q1);
 
 
 
 
 
 
1078 }
1079 db_reset(&q1);
1080 if( db_table_exists("repository","ticketchng") ){
1081 static Stmt q2;
1082 db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid"
1083 " ORDER BY tkt_mtime");
1084 db_bind_int(&q2, ":rid", rid);
1085 while( db_step(&q2)==SQLITE_ROW ){
1086 append_all_ticket_fields(&raw, &q2);
1087 }
1088 db_reset(&q2);
1089 }
1090 html_to_plaintext(blob_str(&raw), pOut);
1091 blob_reset(&raw);
1092 break;
1093 }
1094 }
1095 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1096
1097 /*
1098 ** COMMAND: test-search-stext
1099 **
1100 ** Usage: fossil test-search-stext TYPE ARG1 ARG2
@@ -1105,14 +1303,34 @@
1105 if( g.argc!=5 ) usage("TYPE RID NAME");
1106 search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
1107 fossil_print("%s\n",blob_str(&out));
1108 blob_reset(&out);
1109 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1110
1111 /* The schema for the full-text index
1112 */
1113 static const char zFtsSchema[] =
1114 @ -- One entry for each possible search result
1115 @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs(
1116 @ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
1117 @ type CHAR(1), -- Type of document
1118 @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document
@@ -1119,20 +1337,21 @@
1119 @ name TEXT, -- Additional document description
1120 @ idxed BOOLEAN, -- True if currently in the index
1121 @ label TEXT, -- Label to print on search results
1122 @ url TEXT, -- URL to access this document
1123 @ mtime DATE, -- Date when document created
 
1124 @ UNIQUE(type,rid)
1125 @ );
1126 @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0;
1127 @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w';
1128 @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS
1129 @ SELECT rowid, type, rid, name, idxed, label, url, mtime,
1130 @ stext(type,rid,name) AS 'stext'
1131 @ FROM ftsdocs;
1132 @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx
1133 @ USING fts4(content="ftscontent", stext);
1134 ;
1135 static const char zFtsDrop[] =
1136 @ DROP TABLE IF EXISTS "%w".ftsidx;
1137 @ DROP VIEW IF EXISTS "%w".ftscontent;
1138 @ DROP TABLE IF EXISTS "%w".ftsdocs;
@@ -1142,13 +1361,15 @@
1142 ** Create or drop the tables associated with a full-text index.
1143 */
1144 static int searchIdxExists = -1;
1145 void search_create_index(void){
1146 const char *zDb = db_name("repository");
 
 
1147 search_sql_setup(g.db);
1148 db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/,
1149 zDb, zDb, zDb, zDb, zDb);
1150 searchIdxExists = 1;
1151 }
1152 void search_drop_index(void){
1153 const char *zDb = db_name("repository");
1154 db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb);
@@ -1230,11 +1451,11 @@
1230 }
1231 }
1232
1233 /*
1234 ** If the doc-glob and doc-br settings are valid for document search
1235 ** and if the latest check-in on doc-br is in the unindexed set of
1236 ** check-ins, then update all 'd' entries in FTSDOCS that have
1237 ** changed.
1238 */
1239 static void search_update_doc_index(void){
1240 const char *zDocBr = db_get("doc-branch","trunk");
@@ -1266,78 +1487,84 @@
1266 db_multi_exec(
1267 "DELETE FROM ftsdocs WHERE type='d'"
1268 " AND rid NOT IN (SELECT rid FROM current_docs)"
1269 );
1270 db_multi_exec(
1271 "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)"
1272 " SELECT 'd', rid, name, 0,"
1273 " printf('Document: %%s',name),"
 
1274 " printf('/doc/%q/%%s',urlencode(name)),"
1275 " %.17g"
1276 " FROM current_docs",
1277 zBrUuid, rTime
1278 );
1279 db_multi_exec(
1280 "INSERT INTO ftsidx(docid,stext)"
1281 " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed"
1282 );
1283 db_multi_exec(
1284 "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed"
 
 
 
 
1285 );
1286 }
1287
1288 /*
1289 ** Deal with all of the unindexed 'c' terms in FTSDOCS
1290 */
1291 static void search_update_checkin_index(void){
1292 db_multi_exec(
1293 "INSERT INTO ftsidx(docid,stext)"
1294 " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs"
1295 " WHERE type='c' AND NOT idxed;"
1296 );
1297 db_multi_exec(
1298 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1299 " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL,"
1300 " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1301 " printf('/timeline?y=ci&n=9&c=%%.20s',blob.uuid),"
1302 " event.mtime"
1303 " FROM ftsdocs, event, blob"
1304 " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
1305 " AND event.objid=ftsdocs.rid"
1306 " AND blob.rid=ftsdocs.rid"
1307 );
1308 }
1309
1310 /*
1311 ** Deal with all of the unindexed 't' terms in FTSDOCS
1312 */
1313 static void search_update_ticket_index(void){
1314 db_multi_exec(
1315 "INSERT INTO ftsidx(docid,stext)"
1316 " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs"
1317 " WHERE type='t' AND NOT idxed;"
1318 );
1319 if( db_changes()==0 ) return;
1320 db_multi_exec(
1321 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
1322 " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL,"
1323 " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime)),"
 
1324 " printf('/tktview/%%.20s',tkt_uuid),"
1325 " tkt_mtime"
1326 " FROM ftsdocs, ticket"
1327 " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed"
1328 " AND ticket.tkt_id=ftsdocs.rid"
1329 );
1330 }
1331
1332 /*
1333 ** Deal with all of the unindexed 'w' terms in FTSDOCS
1334 */
1335 static void search_update_wiki_index(void){
1336 db_multi_exec(
1337 "INSERT INTO ftsidx(docid,stext)"
1338 " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs"
1339 " WHERE type='w' AND NOT idxed;"
1340 );
1341 if( db_changes()==0 ) return;
1342 db_multi_exec(
1343 "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)"
@@ -1390,19 +1617,22 @@
1390 ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
1391 **
1392 ** The "fossil fts-config" command configures the full-text search capabilities
1393 ** of the repository. Subcommands:
1394 **
1395 ** reindex Rebuild the search index. Create it if it does
1396 ** not already exist
1397 **
1398 ** index (on|off) Turn the search index on or off
1399 **
1400 ** enable cdtw Enable various kinds of search. c=Check-ins,
1401 ** d=Documents, t=Tickets, w=Wiki.
1402 **
1403 ** disable cdtw Disable versious kinds of search
 
 
 
1404 **
1405 ** The current search settings are displayed after any changes are applied.
1406 ** Run this command with no arguments to simply see the settings.
1407 */
1408 void test_fts_cmd(void){
@@ -1409,18 +1639,19 @@
1409 static const struct { int iCmd; const char *z; } aCmd[] = {
1410 { 1, "reindex" },
1411 { 2, "index" },
1412 { 3, "disable" },
1413 { 4, "enable" },
 
1414 };
1415 static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
1416 { "search-ckin", "check-in search:", "c" },
1417 { "search-doc", "document search:", "d" },
1418 { "search-tkt", "ticket search:", "t" },
1419 { "search-wiki", "wiki search:", "w" },
1420 };
1421 char *zSubCmd;
1422 int i, j, n;
1423 int iCmd = 0;
1424 int iAction = 0;
1425 db_find_and_open_repository(0, 0);
1426 if( g.argc>2 ){
@@ -1438,11 +1669,11 @@
1438 return;
1439 }
1440 iCmd = aCmd[i].iCmd;
1441 }
1442 if( iCmd==1 ){
1443 iAction = 2;
1444 }
1445 if( iCmd==2 ){
1446 if( g.argc<3 ) usage("index (on|off)");
1447 iAction = 1 + is_truth(g.argv[3]);
1448 }
@@ -1449,18 +1680,23 @@
1449 db_begin_transaction();
1450
1451 /* Adjust search settings */
1452 if( iCmd==3 || iCmd==4 ){
1453 const char *zCtrl;
1454 if( g.argc<4 ) usage("enable STRING");
1455 zCtrl = g.argv[3];
1456 for(j=0; j<ArraySize(aSetng); j++){
1457 if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
1458 db_set_int(aSetng[j].zSetting, iCmd-3, 0);
1459 }
1460 }
1461 }
 
 
 
 
 
1462
1463 /* destroy or rebuild the index, if requested */
1464 if( iAction>=1 ){
1465 search_drop_index();
1466 }
@@ -1468,17 +1704,19 @@
1468 search_rebuild_index();
1469 }
1470
1471 /* Always show the status before ending */
1472 for(i=0; i<ArraySize(aSetng); i++){
1473 fossil_print("%-16s %s\n", aSetng[i].zName,
1474 db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
1475 }
 
 
1476 if( search_index_exists() ){
1477 fossil_print("%-16s enabled\n", "full-text index:");
1478 fossil_print("%-16s %d\n", "documents:",
1479 db_int(0, "SELECT count(*) FROM ftsdocs"));
1480 }else{
1481 fossil_print("%-16s disabled\n", "full-text index:");
1482 }
1483 db_end_transaction(0);
1484 }
1485
--- src/search.c
+++ src/search.c
@@ -41,11 +41,11 @@
41 char *z; /* Text */
42 int n; /* length */
43 } a[SEARCH_MAX_TERM];
44 /* Snippet controls */
45 char *zPattern; /* The search pattern */
46 char *zMarkBegin; /* Start of a match */
47 char *zMarkEnd; /* End of a match */
48 char *zMarkGap; /* A gap between two matches */
49 unsigned fSrchFlg; /* Flags */
50 int iScore; /* Score of the last match attempt */
51 Blob snip; /* Snippet for the most recent match */
@@ -104,11 +104,11 @@
104 /*
105 ** Compile a search pattern
106 */
107 Search *search_init(
108 const char *zPattern, /* The search pattern */
109 const char *zMarkBegin, /* Start of a match */
110 const char *zMarkEnd, /* End of a match */
111 const char *zMarkGap, /* A gap between two matches */
112 unsigned fSrchFlg /* Flags */
113 ){
114 Search *p;
@@ -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;
@@ -227,11 +227,11 @@
227 if( zDoc[i]==0 ) break;
228 }
229 }
230
231 /* Finished search all documents.
232 ** Every term must be seen or else the score is zero
233 */
234 score = 1;
235 for(j=0; j<p->nTerm; j++) score *= anMatch[j];
236 blob_reset(&p->snip);
237 p->iScore = score;
@@ -317,11 +317,11 @@
317 }
318
319 /*
320 ** COMMAND: test-match
321 **
322 ** Usage: fossil test-match SEARCHSTRING FILE1 FILE2 ...
323 */
324 void test_match_cmd(void){
325 Search *p;
326 int i;
327 Blob x;
@@ -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
@@ -427,24 +431,51 @@
431 }
432 }
433
434 /*
435 ** This is an SQLite function that computes the searchable text.
436 ** It is a wrapper around the search_stext() routine. See the
437 ** search_stext() routine for further detail.
438 */
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 /*
@@ -567,26 +602,33 @@
602 /*
603 ** Remove bits from srchFlags which are disallowed by either the
604 ** current server configuration or by user permissions.
605 */
606 unsigned int search_restrict(unsigned int srchFlags){
607 static unsigned int knownGood = 0;
608 static unsigned int knownBad = 0;
609 static const struct { unsigned m; const char *zKey; } aSetng[] = {
610 { SRCH_CKIN, "search-ci" },
611 { SRCH_DOC, "search-doc" },
612 { SRCH_TKT, "search-tkt" },
613 { SRCH_WIKI, "search-wiki" },
614 };
615 int i;
616 if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC);
617 if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT);
618 if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
619 for(i=0; i<ArraySize(aSetng); i++){
620 unsigned int m = aSetng[i].m;
621 if( (srchFlags & m)==0 ) continue;
622 if( ((knownGood|knownBad) & m)!=0 ) continue;
623 if( db_get_boolean(aSetng[i].zKey,0) ){
624 knownGood |= m;
625 }else{
626 knownBad |= m;
627 }
628 }
629 return srchFlags & ~knownBad;
 
 
630 }
631
632 /*
633 ** When this routine is called, there already exists a table
634 **
@@ -609,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 }
@@ -634,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 ("
@@ -652,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(
@@ -687,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;
@@ -739,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",
@@ -776,11 +854,10 @@
854 }
855
856 /*
857 ** If z[] is of the form "<mark>TEXT</mark>" where TEXT contains
858 ** no white-space or punctuation, then return the length of the mark.
 
859 */
860 static int isSnippetMark(const char *z){
861 int n;
862 if( strncmp(z,"<mark>",6)!=0 ) return 0;
863 n = 6;
@@ -789,11 +866,11 @@
866 return n+7;
867 }
868
869 /*
870 ** Return a copy of zSnip (in memory obtained from fossil_malloc()) that
871 ** has all "<" characters, other than those on <mark> and </mark>,
872 ** converted into "&lt;". This is similar to htmlize() except that
873 ** <mark> and </mark> are preserved.
874 */
875 static char *cleanSnippet(const char *zSnip){
876 int i;
@@ -832,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);
@@ -861,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 }
@@ -876,46 +957,79 @@
957 /*
958 ** Generate some HTML for doing search. At a minimum include the
959 ** Search-Text entry form. If the "s" query parameter is present, also
960 ** show search results.
961 **
962 ** The srchFlags parameter restricts the set of documents to be searched.
963 ** srchFlags should normally be either a single search category or all
964 ** categories. Any srchFlags with two or more bits set
965 ** is treated like SRCH_ALL for display purposes.
966 **
967 ** This routine automatically restricts srchFlag according to user
968 ** permissions and the server configuration. The entry box is shown
969 ** disabled if srchFlags is 0 after these restrictions are applied.
970 **
971 ** If useYparam is true, then this routine also looks at the y= query
972 ** parameter for further search restrictions.
973 */
974 void search_screen(unsigned srchFlags, int useYparam){
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;
986 case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break;
987 }
 
988 if( srchFlags==0 ){
989 zDisable1 = " disabled";
990 zDisable2 = " disabled";
991 zPattern = "";
992 }else{
993 zDisable1 = " autofocus";
994 zDisable2 = "";
995 zPattern = PD("s","");
996 }
997 @ <form method='GET' action='%R/%t(g.zPath)'>
998 if( zClass ){
999 @ <div class='searchForm searchForm%s(zClass)'>
1000 }else{
1001 @ <div class='searchForm'>
1002 }
1003 @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
1004 if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){
1005 static const struct { char *z; char *zNm; unsigned m; } aY[] = {
1006 { "all", "All", SRCH_ALL },
1007 { "c", "Check-ins", SRCH_CKIN },
1008 { "d", "Docs", SRCH_DOC },
1009 { "t", "Tickets", SRCH_TKT },
1010 { "w", "Wiki", SRCH_WIKI },
1011 };
1012 const char *zY = PD("y","all");
1013 unsigned newFlags = srchFlags;
1014 int i;
1015 @ <select size='1' name='y'>
1016 for(i=0; i<ArraySize(aY); i++){
1017 if( (aY[i].m & srchFlags)==0 ) continue;
1018 cgi_printf("<option value='%s'", aY[i].z);
1019 if( fossil_strcmp(zY,aY[i].z)==0 ){
1020 newFlags &= aY[i].m;
1021 cgi_printf(" selected");
1022 }
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>
@@ -924,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 }
@@ -938,29 +1052,24 @@
1052 **
1053 ** Search for check-in comments, documents, tickets, or wiki that
1054 ** match a user-supplied pattern.
1055 */
1056 void search_page(void){
 
 
 
1057 login_check_credentials();
 
 
 
 
 
 
1058 style_header("Search");
1059 search_screen(SRCH_ALL, 1);
1060 style_footer();
1061 }
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
@@ -968,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
@@ -1028,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 }
@@ -1047,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),',')"
@@ -1057,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
@@ -1105,14 +1303,34 @@
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
1333 @ CREATE TABLE IF NOT EXISTS "%w".ftsdocs(
1334 @ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid
1335 @ type CHAR(1), -- Type of document
1336 @ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document
@@ -1119,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;
@@ -1142,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);
@@ -1230,11 +1451,11 @@
1451 }
1452 }
1453
1454 /*
1455 ** If the doc-glob and doc-br settings are valid for document search
1456 ** and if the latest check-in on doc-br is in the unindexed set of
1457 ** check-ins, then update all 'd' entries in FTSDOCS that have
1458 ** changed.
1459 */
1460 static void search_update_doc_index(void){
1461 const char *zDocBr = db_get("doc-branch","trunk");
@@ -1266,78 +1487,84 @@
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,"
1526 " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime)),"
1527 " printf('/timeline?y=ci&c=%%.20s',blob.uuid),"
1528 " event.mtime"
1529 " FROM ftsdocs, event, blob"
1530 " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed"
1531 " AND event.objid=ftsdocs.rid"
1532 " AND blob.rid=ftsdocs.rid"
1533 );
1534 }
1535
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"
1556 );
1557 }
1558
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)"
@@ -1390,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){
@@ -1409,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 ){
@@ -1438,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 }
@@ -1449,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 }
@@ -1468,17 +1704,19 @@
1704 search_rebuild_index();
1705 }
1706
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
+78 -28
--- 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 ){
@@ -1263,10 +1281,27 @@
12631281
@ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
12641282
login_insert_csrf_secret();
12651283
@ To leave this login group press
12661284
@ <input type="submit" value="Leave Login Group" name="leave">
12671285
@ </form></p>
1286
+ @ <hr><h2>Implementation Details</h2>
1287
+ @ <p>The following are fields from the CONFIG table related to login-groups,
1288
+ @ provided here for instructional and debugging purposes:</p>
1289
+ @ <table border='1' id='configTab'>
1290
+ @ <thead><tr><th>Config.Name<th>Config.Value<th>Config.mtime</tr></thead><tbody>
1291
+ db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
1292
+ " WHERE name GLOB 'peer-*'"
1293
+ " OR name GLOB 'project-*'"
1294
+ " ORDER BY name");
1295
+ while( db_step(&q)==SQLITE_ROW ){
1296
+ @ <tr><td>%h(db_column_text(&q,0))</td>
1297
+ @ <td>%h(db_column_text(&q,1))</td>
1298
+ @ <td>%h(db_column_text(&q,2))</td></tr>
1299
+ }
1300
+ db_finalize(&q);
1301
+ @ </tbody></table>
1302
+ output_table_sorting_javascript("configTab","ttt",1);
12681303
}
12691304
style_footer();
12701305
}
12711306
12721307
/*
@@ -1282,11 +1317,12 @@
12821317
"3", "YYMMDD HH:MM",
12831318
"4", "(off)"
12841319
};
12851320
login_check_credentials();
12861321
if( !g.perm.Setup ){
1287
- login_needed();
1322
+ login_needed(0);
1323
+ return;
12881324
}
12891325
12901326
style_header("Timeline Display Preferences");
12911327
db_begin_transaction();
12921328
@ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1360,11 +1396,12 @@
13601396
void setup_settings(void){
13611397
Setting const *pSet;
13621398
13631399
login_check_credentials();
13641400
if( !g.perm.Setup ){
1365
- login_needed();
1401
+ login_needed(0);
1402
+ return;
13661403
}
13671404
13681405
(void) aCmdHelp; /* NOTE: Silence compiler warning. */
13691406
style_header("Settings");
13701407
if(!g.repositoryOpen){
@@ -1440,11 +1477,12 @@
14401477
** WEBPAGE: setup_config
14411478
*/
14421479
void setup_config(void){
14431480
login_check_credentials();
14441481
if( !g.perm.Setup ){
1445
- login_needed();
1482
+ login_needed(0);
1483
+ return;
14461484
}
14471485
14481486
style_header("WWW Configuration");
14491487
db_begin_transaction();
14501488
@ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1518,11 +1556,12 @@
15181556
** WEBPAGE: setup_editcss
15191557
*/
15201558
void setup_editcss(void){
15211559
login_check_credentials();
15221560
if( !g.perm.Setup ){
1523
- login_needed();
1561
+ login_needed(0);
1562
+ return;
15241563
}
15251564
db_begin_transaction();
15261565
if( P("clear")!=0 ){
15271566
db_multi_exec("DELETE FROM config WHERE name='css'");
15281567
cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1563,11 +1602,12 @@
15631602
** WEBPAGE: setup_header
15641603
*/
15651604
void setup_header(void){
15661605
login_check_credentials();
15671606
if( !g.perm.Setup ){
1568
- login_needed();
1607
+ login_needed(0);
1608
+ return;
15691609
}
15701610
db_begin_transaction();
15711611
if( P("clear")!=0 ){
15721612
db_multi_exec("DELETE FROM config WHERE name='header'");
15731613
cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1627,11 +1667,12 @@
16271667
** WEBPAGE: setup_footer
16281668
*/
16291669
void setup_footer(void){
16301670
login_check_credentials();
16311671
if( !g.perm.Setup ){
1632
- login_needed();
1672
+ login_needed(0);
1673
+ return;
16331674
}
16341675
db_begin_transaction();
16351676
if( P("clear")!=0 ){
16361677
db_multi_exec("DELETE FROM config WHERE name='footer'");
16371678
cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1664,11 +1705,12 @@
16641705
** WEBPAGE: setup_modreq
16651706
*/
16661707
void setup_modreq(void){
16671708
login_check_credentials();
16681709
if( !g.perm.Setup ){
1669
- login_needed();
1710
+ login_needed(0);
1711
+ return;
16701712
}
16711713
16721714
style_header("Moderator For Wiki And Tickets");
16731715
db_begin_transaction();
16741716
@ <form action="%R/setup_modreq" method="post"><div>
@@ -1675,11 +1717,11 @@
16751717
login_insert_csrf_secret();
16761718
@ <hr />
16771719
onoff_attribute("Moderate ticket changes",
16781720
"modreq-tkt", "modreq-tkt", 0, 0);
16791721
@ <p>When enabled, any change to tickets is subject to the approval
1680
- @ 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.
16811723
@ Ticket changes enter the system and are shown locally, but are not
16821724
@ synced until they are approved. The moderator has the option to
16831725
@ delete the change rather than approve it. Ticket changes made by
16841726
@ a user who has the Mod-Tkt privilege are never subject to
16851727
@ moderation.
@@ -1686,11 +1728,11 @@
16861728
@
16871729
@ <hr />
16881730
onoff_attribute("Moderate wiki changes",
16891731
"modreq-wiki", "modreq-wiki", 0, 0);
16901732
@ <p>When enabled, any change to wiki is subject to the approval
1691
- @ 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.
16921734
@ Wiki changes enter the system and are shown locally, but are not
16931735
@ synced until they are approved. The moderator has the option to
16941736
@ delete the change rather than approve it. Wiki changes made by
16951737
@ a user who has the Mod-Wiki privilege are never subject to
16961738
@ moderation.
@@ -1708,11 +1750,12 @@
17081750
** WEBPAGE: setup_adunit
17091751
*/
17101752
void setup_adunit(void){
17111753
login_check_credentials();
17121754
if( !g.perm.Setup ){
1713
- login_needed();
1755
+ login_needed(0);
1756
+ return;
17141757
}
17151758
db_begin_transaction();
17161759
if( P("clear")!=0 ){
17171760
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
17181761
cgi_replace_parameter("adunit","");
@@ -1789,11 +1832,12 @@
17891832
if( szBgImg>0 ){
17901833
zBgMime = PD("bgim:mimetype","image/gif");
17911834
}
17921835
login_check_credentials();
17931836
if( !g.perm.Setup ){
1794
- login_needed();
1837
+ login_needed(0);
1838
+ return;
17951839
}
17961840
db_begin_transaction();
17971841
if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
17981842
Blob img;
17991843
Stmt ins;
@@ -1928,11 +1972,12 @@
19281972
void sql_page(void){
19291973
const char *zQ = P("q");
19301974
int go = P("go")!=0;
19311975
login_check_credentials();
19321976
if( !g.perm.Setup ){
1933
- login_needed();
1977
+ login_needed(0);
1978
+ return;
19341979
}
19351980
db_begin_transaction();
19361981
style_header("Raw SQL Commands");
19371982
@ <p><b>Caution:</b> There are no restrictions on the SQL that can be
19381983
@ run by this page. You can do serious and irrepairable damage to the
@@ -2049,11 +2094,12 @@
20492094
void th1_page(void){
20502095
const char *zQ = P("q");
20512096
int go = P("go")!=0;
20522097
login_check_credentials();
20532098
if( !g.perm.Setup ){
2054
- login_needed();
2099
+ login_needed(0);
2100
+ return;
20552101
}
20562102
db_begin_transaction();
20572103
style_header("Raw TH1 Commands");
20582104
@ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
20592105
@ run by this page. If Tcl integration was enabled at compile-time and
@@ -2109,11 +2155,12 @@
21092155
int limit;
21102156
int fLogEnabled;
21112157
int counter = 0;
21122158
login_check_credentials();
21132159
if( !g.perm.Setup && !g.perm.Admin ){
2114
- login_needed();
2160
+ login_needed(0);
2161
+ return;
21152162
}
21162163
style_header("Admin Log");
21172164
create_admin_log_table();
21182165
limit = atoi(PD("n","20"));
21192166
fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2166,22 +2213,23 @@
21662213
** Configure the search engine.
21672214
*/
21682215
void page_srchsetup(){
21692216
login_check_credentials();
21702217
if( !g.perm.Setup && !g.perm.Admin ){
2171
- login_needed();
2218
+ login_needed(0);
2219
+ return;
21722220
}
21732221
style_header("Search Configuration");
21742222
@ <form action="%s(g.zTop)/srchsetup" method="post"><div>
21752223
login_insert_csrf_secret();
21762224
@ <div style="text-align:center;font-weight:bold;">
2177
- @ Server-specific settings that affect the
2225
+ @ Server-specific settings that affect the
21782226
@ <a href="%R/search">/search</a> webpage.
21792227
@ </div>
21802228
@ <hr />
21812229
textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0);
2182
- @ <p>The "Document Glob List" is a comma- or newline-separated list
2230
+ @ <p>The "Document Glob List" is a comma- or newline-separated list
21832231
@ of GLOB expressions that identify all documents within the source
21842232
@ tree that are to be searched when "Document Search" is enabled.
21852233
@ Some examples:
21862234
@ <table border=0 cellpadding=2 align=center>
21872235
@ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;">
@@ -2218,16 +2266,18 @@
22182266
search_update_index(search_restrict(SRCH_ALL));
22192267
}
22202268
if( search_index_exists() ){
22212269
@ <p>Currently using an SQLite FTS4 search index. This makes search
22222270
@ run faster, especially on large repositories, but takes up space.</p>
2271
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22232272
@ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
22242273
@ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
22252274
}else{
22262275
@ <p>The SQLite FTS4 search index is disabled. All searching will be
22272276
@ a full-text scan. This usually works fine, but can be slow for
22282277
@ larger repositories.</p>
2278
+ onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
22292279
@ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
22302280
}
22312281
@ </div></form>
22322282
style_footer();
22332283
}
22342284
--- 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 ){
@@ -1263,10 +1281,27 @@
1263 @ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
1264 login_insert_csrf_secret();
1265 @ To leave this login group press
1266 @ <input type="submit" value="Leave Login Group" name="leave">
1267 @ </form></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1268 }
1269 style_footer();
1270 }
1271
1272 /*
@@ -1282,11 +1317,12 @@
1282 "3", "YYMMDD HH:MM",
1283 "4", "(off)"
1284 };
1285 login_check_credentials();
1286 if( !g.perm.Setup ){
1287 login_needed();
 
1288 }
1289
1290 style_header("Timeline Display Preferences");
1291 db_begin_transaction();
1292 @ <form action="%s(g.zTop)/setup_timeline" method="post"><div>
@@ -1360,11 +1396,12 @@
1360 void setup_settings(void){
1361 Setting const *pSet;
1362
1363 login_check_credentials();
1364 if( !g.perm.Setup ){
1365 login_needed();
 
1366 }
1367
1368 (void) aCmdHelp; /* NOTE: Silence compiler warning. */
1369 style_header("Settings");
1370 if(!g.repositoryOpen){
@@ -1440,11 +1477,12 @@
1440 ** WEBPAGE: setup_config
1441 */
1442 void setup_config(void){
1443 login_check_credentials();
1444 if( !g.perm.Setup ){
1445 login_needed();
 
1446 }
1447
1448 style_header("WWW Configuration");
1449 db_begin_transaction();
1450 @ <form action="%s(g.zTop)/setup_config" method="post"><div>
@@ -1518,11 +1556,12 @@
1518 ** WEBPAGE: setup_editcss
1519 */
1520 void setup_editcss(void){
1521 login_check_credentials();
1522 if( !g.perm.Setup ){
1523 login_needed();
 
1524 }
1525 db_begin_transaction();
1526 if( P("clear")!=0 ){
1527 db_multi_exec("DELETE FROM config WHERE name='css'");
1528 cgi_replace_parameter("css", builtin_text("skins/default/css.txt"));
@@ -1563,11 +1602,12 @@
1563 ** WEBPAGE: setup_header
1564 */
1565 void setup_header(void){
1566 login_check_credentials();
1567 if( !g.perm.Setup ){
1568 login_needed();
 
1569 }
1570 db_begin_transaction();
1571 if( P("clear")!=0 ){
1572 db_multi_exec("DELETE FROM config WHERE name='header'");
1573 cgi_replace_parameter("header", builtin_text("skins/default/header.txt"));
@@ -1627,11 +1667,12 @@
1627 ** WEBPAGE: setup_footer
1628 */
1629 void setup_footer(void){
1630 login_check_credentials();
1631 if( !g.perm.Setup ){
1632 login_needed();
 
1633 }
1634 db_begin_transaction();
1635 if( P("clear")!=0 ){
1636 db_multi_exec("DELETE FROM config WHERE name='footer'");
1637 cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt"));
@@ -1664,11 +1705,12 @@
1664 ** WEBPAGE: setup_modreq
1665 */
1666 void setup_modreq(void){
1667 login_check_credentials();
1668 if( !g.perm.Setup ){
1669 login_needed();
 
1670 }
1671
1672 style_header("Moderator For Wiki And Tickets");
1673 db_begin_transaction();
1674 @ <form action="%R/setup_modreq" method="post"><div>
@@ -1675,11 +1717,11 @@
1675 login_insert_csrf_secret();
1676 @ <hr />
1677 onoff_attribute("Moderate ticket changes",
1678 "modreq-tkt", "modreq-tkt", 0, 0);
1679 @ <p>When enabled, any change to tickets is subject to the approval
1680 @ a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1681 @ Ticket changes enter the system and are shown locally, but are not
1682 @ synced until they are approved. The moderator has the option to
1683 @ delete the change rather than approve it. Ticket changes made by
1684 @ a user who has the Mod-Tkt privilege are never subject to
1685 @ moderation.
@@ -1686,11 +1728,11 @@
1686 @
1687 @ <hr />
1688 onoff_attribute("Moderate wiki changes",
1689 "modreq-wiki", "modreq-wiki", 0, 0);
1690 @ <p>When enabled, any change to wiki is subject to the approval
1691 @ a ticket moderator - a user with the "l" or Mod-Wiki privilege.
1692 @ Wiki changes enter the system and are shown locally, but are not
1693 @ synced until they are approved. The moderator has the option to
1694 @ delete the change rather than approve it. Wiki changes made by
1695 @ a user who has the Mod-Wiki privilege are never subject to
1696 @ moderation.
@@ -1708,11 +1750,12 @@
1708 ** WEBPAGE: setup_adunit
1709 */
1710 void setup_adunit(void){
1711 login_check_credentials();
1712 if( !g.perm.Setup ){
1713 login_needed();
 
1714 }
1715 db_begin_transaction();
1716 if( P("clear")!=0 ){
1717 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1718 cgi_replace_parameter("adunit","");
@@ -1789,11 +1832,12 @@
1789 if( szBgImg>0 ){
1790 zBgMime = PD("bgim:mimetype","image/gif");
1791 }
1792 login_check_credentials();
1793 if( !g.perm.Setup ){
1794 login_needed();
 
1795 }
1796 db_begin_transaction();
1797 if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1798 Blob img;
1799 Stmt ins;
@@ -1928,11 +1972,12 @@
1928 void sql_page(void){
1929 const char *zQ = P("q");
1930 int go = P("go")!=0;
1931 login_check_credentials();
1932 if( !g.perm.Setup ){
1933 login_needed();
 
1934 }
1935 db_begin_transaction();
1936 style_header("Raw SQL Commands");
1937 @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
1938 @ run by this page. You can do serious and irrepairable damage to the
@@ -2049,11 +2094,12 @@
2049 void th1_page(void){
2050 const char *zQ = P("q");
2051 int go = P("go")!=0;
2052 login_check_credentials();
2053 if( !g.perm.Setup ){
2054 login_needed();
 
2055 }
2056 db_begin_transaction();
2057 style_header("Raw TH1 Commands");
2058 @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
2059 @ run by this page. If Tcl integration was enabled at compile-time and
@@ -2109,11 +2155,12 @@
2109 int limit;
2110 int fLogEnabled;
2111 int counter = 0;
2112 login_check_credentials();
2113 if( !g.perm.Setup && !g.perm.Admin ){
2114 login_needed();
 
2115 }
2116 style_header("Admin Log");
2117 create_admin_log_table();
2118 limit = atoi(PD("n","20"));
2119 fLogEnabled = db_get_boolean("admin-log", 0);
@@ -2166,22 +2213,23 @@
2166 ** Configure the search engine.
2167 */
2168 void page_srchsetup(){
2169 login_check_credentials();
2170 if( !g.perm.Setup && !g.perm.Admin ){
2171 login_needed();
 
2172 }
2173 style_header("Search Configuration");
2174 @ <form action="%s(g.zTop)/srchsetup" method="post"><div>
2175 login_insert_csrf_secret();
2176 @ <div style="text-align:center;font-weight:bold;">
2177 @ Server-specific settings that affect the
2178 @ <a href="%R/search">/search</a> webpage.
2179 @ </div>
2180 @ <hr />
2181 textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0);
2182 @ <p>The "Document Glob List" is a comma- or newline-separated list
2183 @ of GLOB expressions that identify all documents within the source
2184 @ tree that are to be searched when "Document Search" is enabled.
2185 @ Some examples:
2186 @ <table border=0 cellpadding=2 align=center>
2187 @ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;">
@@ -2218,16 +2266,18 @@
2218 search_update_index(search_restrict(SRCH_ALL));
2219 }
2220 if( search_index_exists() ){
2221 @ <p>Currently using an SQLite FTS4 search index. This makes search
2222 @ run faster, especially on large repositories, but takes up space.</p>
 
2223 @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
2224 @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
2225 }else{
2226 @ <p>The SQLite FTS4 search index is disabled. All searching will be
2227 @ a full-text scan. This usually works fine, but can be slow for
2228 @ larger repositories.</p>
 
2229 @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
2230 }
2231 @ </div></form>
2232 style_footer();
2233 }
2234
--- 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 ){
@@ -1263,10 +1281,27 @@
1281 @ <p><form action="%s(g.zTop)/setup_login_group" method="post"><div>
1282 login_insert_csrf_secret();
1283 @ To leave this login group press
1284 @ <input type="submit" value="Leave Login Group" name="leave">
1285 @ </form></p>
1286 @ <hr><h2>Implementation Details</h2>
1287 @ <p>The following are fields from the CONFIG table related to login-groups,
1288 @ provided here for instructional and debugging purposes:</p>
1289 @ <table border='1' id='configTab'>
1290 @ <thead><tr><th>Config.Name<th>Config.Value<th>Config.mtime</tr></thead><tbody>
1291 db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
1292 " WHERE name GLOB 'peer-*'"
1293 " OR name GLOB 'project-*'"
1294 " ORDER BY name");
1295 while( db_step(&q)==SQLITE_ROW ){
1296 @ <tr><td>%h(db_column_text(&q,0))</td>
1297 @ <td>%h(db_column_text(&q,1))</td>
1298 @ <td>%h(db_column_text(&q,2))</td></tr>
1299 }
1300 db_finalize(&q);
1301 @ </tbody></table>
1302 output_table_sorting_javascript("configTab","ttt",1);
1303 }
1304 style_footer();
1305 }
1306
1307 /*
@@ -1282,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>
@@ -1360,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){
@@ -1440,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>
@@ -1518,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"));
@@ -1563,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"));
@@ -1627,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"));
@@ -1664,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>
@@ -1675,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.
@@ -1686,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.
@@ -1708,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","");
@@ -1789,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;
@@ -1928,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
@@ -2049,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
@@ -2109,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);
@@ -2166,22 +2213,23 @@
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;">
2225 @ Server-specific settings that affect the
2226 @ <a href="%R/search">/search</a> webpage.
2227 @ </div>
2228 @ <hr />
2229 textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0);
2230 @ <p>The "Document Glob List" is a comma- or newline-separated list
2231 @ of GLOB expressions that identify all documents within the source
2232 @ tree that are to be searched when "Document Search" is enabled.
2233 @ Some examples:
2234 @ <table border=0 cellpadding=2 align=center>
2235 @ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;">
@@ -2218,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
+25 -13
--- 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>
49
- @ <li>%z(href("%R/timeline?n=0&namechng"))All checkins with file name
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
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=0&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
+101 -10
--- src/skins.c
+++ src/skins.c
@@ -26,11 +26,11 @@
2626
**
2727
** To add new built-in skins:
2828
**
2929
** 1. Pick a name for the new skin. (Here we use "xyzzy").
3030
**
31
-** 2. Install files skins/xyzzy/css.txt, skins/xyzzy/header.txt,
31
+** 2. Install files skins/xyzzy/css.txt, skins/xyzzy/header.txt,
3232
** and skins/xyzzy/footer.txt into the source tree.
3333
**
3434
** 3. Rerun "tclsh makemake.tcl" in the src/ folder in order to
3535
** rebuild the makefiles to reference the new CSS, headers, and footers.
3636
**
@@ -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
@@ -26,11 +26,11 @@
26 **
27 ** To add new built-in skins:
28 **
29 ** 1. Pick a name for the new skin. (Here we use "xyzzy").
30 **
31 ** 2. Install files skins/xyzzy/css.txt, skins/xyzzy/header.txt,
32 ** and skins/xyzzy/footer.txt into the source tree.
33 **
34 ** 3. Rerun "tclsh makemake.tcl" in the src/ folder in order to
35 ** rebuild the makefiles to reference the new CSS, headers, and footers.
36 **
@@ -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
@@ -26,11 +26,11 @@
26 **
27 ** To add new built-in skins:
28 **
29 ** 1. Pick a name for the new skin. (Here we use "xyzzy").
30 **
31 ** 2. Install files skins/xyzzy/css.txt, skins/xyzzy/header.txt,
32 ** and skins/xyzzy/footer.txt into the source tree.
33 **
34 ** 3. Rerun "tclsh makemake.tcl" in the src/ folder in order to
35 ** rebuild the makefiles to reference the new CSS, headers, and footers.
36 **
@@ -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
+221 -42
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
2323
#include "style.h"
2424
2525
2626
/*
2727
** Elements of the submenu are collected into the following
28
-** structure and displayed below the main menu by style_header().
28
+** structure and displayed below the main menu.
29
+**
30
+** Populate these structure with calls to
31
+**
32
+** style_submenu_element()
33
+** style_submenu_entry()
34
+** style_submenu_checkbox()
35
+** style_submenu_multichoice()
2936
**
30
-** Populate this structure with calls to style_submenu_element()
31
-** prior to calling style_header().
37
+** prior to calling style_footer(). The style_footer() routine
38
+** will generate the appropriate HTML text just below the main
39
+** menu.
3240
*/
3341
static struct Submenu {
34
- const char *zLabel;
42
+ const char *zLabel; /* Button label */
3543
const char *zTitle;
36
- const char *zLink;
44
+ const char *zLink; /* Jump to this link when button is pressed */
3745
} aSubmenu[30];
38
-static int nSubmenu = 0;
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
3960
4061
/*
4162
** Remember that the header has been generated. The footer is omitted
4263
** if an error occurs before the header.
4364
*/
@@ -216,16 +237,59 @@
216237
...
217238
){
218239
va_list ap;
219240
assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
220241
aSubmenu[nSubmenu].zLabel = zLabel;
221
- aSubmenu[nSubmenu].zTitle = zTitle;
242
+ aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel;
222243
va_start(ap, zLink);
223244
aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
224245
va_end(ap);
225246
nSubmenu++;
226247
}
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
+
227291
228292
/*
229293
** Compare two submenu items for sorting purposes
230294
*/
231295
static int submenuCompare(const void *a, const void *b){
@@ -289,12 +353,11 @@
289353
** Draw the header.
290354
*/
291355
void style_header(const char *zTitleFormat, ...){
292356
va_list ap;
293357
char *zTitle;
294
- const char *zHeader = db_get("header", 0);
295
- if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
358
+ const char *zHeader = skin_get("header");
296359
login_check_credentials();
297360
298361
va_start(ap, zTitleFormat);
299362
zTitle = vmprintf(zTitleFormat, ap);
300363
va_end(ap);
@@ -411,23 +474,96 @@
411474
/* Go back and put the submenu at the top of the page. We delay the
412475
** creation of the submenu until the end so that we can add elements
413476
** to the submenu while generating page text.
414477
*/
415478
cgi_destination(CGI_HEADER);
416
- if( nSubmenu>0 ){
479
+ if( nSubmenu+nSubmenuCtrl>0 ){
417480
int i;
481
+ if( nSubmenuCtrl ){
482
+ cgi_printf("<form id='f01' method='GET' action='%R/%s'>", g.zPath);
483
+ }
418484
@ <div class="submenu">
419
- qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
420
- for(i=0; i<nSubmenu; i++){
421
- struct Submenu *p = &aSubmenu[i];
422
- if( p->zLink==0 ){
423
- @ <span class="label">%h(p->zLabel)</span>
424
- }else{
425
- @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
485
+ if( nSubmenu>0 ){
486
+ qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
487
+ for(i=0; i<nSubmenu; i++){
488
+ struct Submenu *p = &aSubmenu[i];
489
+ if( p->zLink==0 ){
490
+ @ <span class="label">%h(p->zLabel)</span>
491
+ }else{
492
+ @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
493
+ }
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",
530
+ zQPV,
531
+ fossil_strcmp(zVal,zQPV)==0 ? " selected" : "",
532
+ aSubmenuCtrl[i].azChoice[j+1]
533
+ );
534
+ }
535
+ @ </select>
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
+ );
549
+ cgi_printf(
550
+ "<option value='0'%s>%h</option>\n",
551
+ (!isTrue) ? " selected":"", aSubmenuCtrl[i].zFalse
552
+ );
553
+ @ </select>
554
+ break;
555
+ }
556
+ }
426557
}
427558
}
428559
@ </div>
560
+ if( nSubmenuCtrl ){
561
+ cgi_query_parameters_to_hidden();
562
+ cgi_tag_query_parameter(0);
563
+ @ </form>
564
+ }
429565
}
430566
431567
zAd = style_adunit_text(&mAdFlags);
432568
if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
433569
@ <div class="content adunit_right_container">
@@ -455,12 +591,11 @@
455591
456592
/* Set the href= field on hyperlinks. Do this before the footer since
457593
** the footer will be generating </html> */
458594
style_resolve_href();
459595
460
- zFooter = db_get("footer", 0);
461
- if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
596
+ zFooter = skin_get("footer");
462597
if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
463598
Th_Render(zFooter);
464599
if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
465600
466601
/* Render trace log if TH1 tracing is enabled. */
@@ -526,10 +661,11 @@
526661
@ font-size: small;
527662
},
528663
{ "table.timelineTable",
529664
"the format for the timeline data table",
530665
@ border: 0;
666
+ @ border-collapse: collapse;
531667
},
532668
{ "td.timelineTableCell",
533669
"the format for the timeline data cells",
534670
@ vertical-align: top;
535671
@ text-align: left;
@@ -536,10 +672,21 @@
536672
},
537673
{ "tr.timelineCurrent td.timelineTableCell",
538674
"the format for the timeline data cell of the current checkout",
539675
@ padding: .1em .2em;
540676
@ 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;
541688
},
542689
{ "span.timelineLeaf",
543690
"the format for the timeline leaf marks",
544691
@ font-weight: bold;
545692
},
@@ -598,16 +745,10 @@
598745
{ "td.browser",
599746
"format for cells in the file browser",
600747
@ width: 24%;
601748
@ vertical-align: top;
602749
},
603
- { "ul.browser",
604
- "format for the list in the file browser",
605
- @ margin-left: 0.5em;
606
- @ padding-left: 0.5em;
607
- @ white-space: nowrap;
608
- },
609750
{ ".filetree",
610751
"tree-view file browser",
611752
@ margin: 1em 0;
612753
@ line-height: 1.5;
613754
},
@@ -661,28 +802,59 @@
661802
"hide lines for last-child directories",
662803
@ display: none;
663804
},
664805
{ ".filetree a",
665806
"tree-view links",
666
- @ position: relative;
667
- @ z-index: 1;
668
- @ display: table-cell;
669
- @ min-height: 16px;
670
- @ padding-left: 21px;
671
- @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);
672
- @ background-position: center left;
673
- @ 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"
674844
},
675845
{ "div.filetreeline",
676846
"line of a file tree",
677847
@ display: table;
678848
@ width: 100%;
679849
@ white-space: nowrap;
680850
},
681851
{ ".filetree .dir > div.filetreeline > a",
682852
"tree-view directory links",
683
- @ 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"
684856
},
685857
{ "div.filetreeage",
686858
"Last change floating display on the right",
687859
@ display: table-cell;
688860
@ padding-left: 3em;
@@ -1183,21 +1355,21 @@
11831355
}
11841356
zHaystack = z + n;
11851357
}
11861358
return 0;
11871359
}
1188
-
1360
+
11891361
11901362
/*
11911363
** WEBPAGE: style.css
11921364
*/
11931365
void page_style_css(void){
11941366
Blob css;
11951367
int i;
11961368
11971369
cgi_set_content_type("text/css");
1198
- blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1370
+ blob_init(&css,skin_get("css"),-1);
11991371
12001372
/* add special missing definitions */
12011373
for(i=1; cssDefaultList[i].elementClass; i++){
12021374
char *z = blob_str(&css);
12031375
if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1239,37 +1411,44 @@
12391411
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
12401412
};
12411413
12421414
login_check_credentials();
12431415
if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1244
- login_needed();
1416
+ login_needed(0);
12451417
return;
12461418
}
12471419
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
12481420
style_header("Environment Test");
12491421
showAll = atoi(PD("showall","0"));
12501422
if( !showAll ){
1251
- style_submenu_element("Show Cookies", "Show Cookies",
1252
- "%s/test_env?showall=1", g.zTop);
1423
+ style_submenu_element("Show Cookies", 0, "%R/test_env?showall=1");
12531424
}else{
1254
- style_submenu_element("Hide Cookies", "Hide Cookies",
1255
- "%s/test_env", g.zTop);
1425
+ style_submenu_element("Hide Cookies", 0, "%R/test_env");
12561426
}
12571427
#if !defined(_WIN32)
12581428
@ uid=%d(getuid()), gid=%d(getgid())<br />
12591429
#endif
12601430
@ g.zBaseURL = %h(g.zBaseURL)<br />
12611431
@ g.zHttpsURL = %h(g.zHttpsURL)<br />
12621432
@ g.zTop = %h(g.zTop)<br />
1433
+ @ g.zPath = %h(g.zPath)<br />
12631434
for(i=0, c='a'; c<='z'; c++){
1264
- if( login_has_capability(&c, 1) ) zCap[i++] = c;
1435
+ if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
12651436
}
12661437
zCap[i] = 0;
12671438
@ g.userUid = %d(g.userUid)<br />
12681439
@ g.zLogin = %h(g.zLogin)<br />
12691440
@ g.isHuman = %d(g.isHuman)<br />
12701441
@ 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
+ }
12711450
@ g.zRepositoryName = %h(g.zRepositoryName)<br />
12721451
@ load_average() = %f(load_average())<br />
12731452
@ <hr>
12741453
P("HTTP_USER_AGENT");
12751454
cgi_print_all(showAll);
12761455
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
23 #include "style.h"
24
25
26 /*
27 ** Elements of the submenu are collected into the following
28 ** structure and displayed below the main menu by style_header().
 
 
 
 
 
 
 
29 **
30 ** Populate this structure with calls to style_submenu_element()
31 ** prior to calling style_header().
 
32 */
33 static struct Submenu {
34 const char *zLabel;
35 const char *zTitle;
36 const char *zLink;
37 } aSubmenu[30];
38 static int nSubmenu = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
39
40 /*
41 ** Remember that the header has been generated. The footer is omitted
42 ** if an error occurs before the header.
43 */
@@ -216,16 +237,59 @@
216 ...
217 ){
218 va_list ap;
219 assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
220 aSubmenu[nSubmenu].zLabel = zLabel;
221 aSubmenu[nSubmenu].zTitle = zTitle;
222 va_start(ap, zLink);
223 aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
224 va_end(ap);
225 nSubmenu++;
226 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
228 /*
229 ** Compare two submenu items for sorting purposes
230 */
231 static int submenuCompare(const void *a, const void *b){
@@ -289,12 +353,11 @@
289 ** Draw the header.
290 */
291 void style_header(const char *zTitleFormat, ...){
292 va_list ap;
293 char *zTitle;
294 const char *zHeader = db_get("header", 0);
295 if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt");
296 login_check_credentials();
297
298 va_start(ap, zTitleFormat);
299 zTitle = vmprintf(zTitleFormat, ap);
300 va_end(ap);
@@ -411,23 +474,96 @@
411 /* Go back and put the submenu at the top of the page. We delay the
412 ** creation of the submenu until the end so that we can add elements
413 ** to the submenu while generating page text.
414 */
415 cgi_destination(CGI_HEADER);
416 if( nSubmenu>0 ){
417 int i;
 
 
 
418 @ <div class="submenu">
419 qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
420 for(i=0; i<nSubmenu; i++){
421 struct Submenu *p = &aSubmenu[i];
422 if( p->zLink==0 ){
423 @ <span class="label">%h(p->zLabel)</span>
424 }else{
425 @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426 }
427 }
428 @ </div>
 
 
 
 
 
429 }
430
431 zAd = style_adunit_text(&mAdFlags);
432 if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
433 @ <div class="content adunit_right_container">
@@ -455,12 +591,11 @@
455
456 /* Set the href= field on hyperlinks. Do this before the footer since
457 ** the footer will be generating </html> */
458 style_resolve_href();
459
460 zFooter = db_get("footer", 0);
461 if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt");
462 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
463 Th_Render(zFooter);
464 if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
465
466 /* Render trace log if TH1 tracing is enabled. */
@@ -526,10 +661,11 @@
526 @ font-size: small;
527 },
528 { "table.timelineTable",
529 "the format for the timeline data table",
530 @ border: 0;
 
531 },
532 { "td.timelineTableCell",
533 "the format for the timeline data cells",
534 @ vertical-align: top;
535 @ text-align: left;
@@ -536,10 +672,21 @@
536 },
537 { "tr.timelineCurrent td.timelineTableCell",
538 "the format for the timeline data cell of the current checkout",
539 @ padding: .1em .2em;
540 @ border: 1px dashed #446979;
 
 
 
 
 
 
 
 
 
 
 
541 },
542 { "span.timelineLeaf",
543 "the format for the timeline leaf marks",
544 @ font-weight: bold;
545 },
@@ -598,16 +745,10 @@
598 { "td.browser",
599 "format for cells in the file browser",
600 @ width: 24%;
601 @ vertical-align: top;
602 },
603 { "ul.browser",
604 "format for the list in the file browser",
605 @ margin-left: 0.5em;
606 @ padding-left: 0.5em;
607 @ white-space: nowrap;
608 },
609 { ".filetree",
610 "tree-view file browser",
611 @ margin: 1em 0;
612 @ line-height: 1.5;
613 },
@@ -661,28 +802,59 @@
661 "hide lines for last-child directories",
662 @ display: none;
663 },
664 { ".filetree a",
665 "tree-view links",
666 @ position: relative;
667 @ z-index: 1;
668 @ display: table-cell;
669 @ min-height: 16px;
670 @ padding-left: 21px;
671 @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);
672 @ background-position: center left;
673 @ background-repeat: no-repeat;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674 },
675 { "div.filetreeline",
676 "line of a file tree",
677 @ display: table;
678 @ width: 100%;
679 @ white-space: nowrap;
680 },
681 { ".filetree .dir > div.filetreeline > a",
682 "tree-view directory links",
683 @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=);
 
 
684 },
685 { "div.filetreeage",
686 "Last change floating display on the right",
687 @ display: table-cell;
688 @ padding-left: 3em;
@@ -1183,21 +1355,21 @@
1183 }
1184 zHaystack = z + n;
1185 }
1186 return 0;
1187 }
1188
1189
1190 /*
1191 ** WEBPAGE: style.css
1192 */
1193 void page_style_css(void){
1194 Blob css;
1195 int i;
1196
1197 cgi_set_content_type("text/css");
1198 blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1);
1199
1200 /* add special missing definitions */
1201 for(i=1; cssDefaultList[i].elementClass; i++){
1202 char *z = blob_str(&css);
1203 if( !containsString(z, cssDefaultList[i].elementClass) ){
@@ -1239,37 +1411,44 @@
1239 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1240 };
1241
1242 login_check_credentials();
1243 if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
1244 login_needed();
1245 return;
1246 }
1247 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1248 style_header("Environment Test");
1249 showAll = atoi(PD("showall","0"));
1250 if( !showAll ){
1251 style_submenu_element("Show Cookies", "Show Cookies",
1252 "%s/test_env?showall=1", g.zTop);
1253 }else{
1254 style_submenu_element("Hide Cookies", "Hide Cookies",
1255 "%s/test_env", g.zTop);
1256 }
1257 #if !defined(_WIN32)
1258 @ uid=%d(getuid()), gid=%d(getgid())<br />
1259 #endif
1260 @ g.zBaseURL = %h(g.zBaseURL)<br />
1261 @ g.zHttpsURL = %h(g.zHttpsURL)<br />
1262 @ g.zTop = %h(g.zTop)<br />
 
1263 for(i=0, c='a'; c<='z'; c++){
1264 if( login_has_capability(&c, 1) ) zCap[i++] = c;
1265 }
1266 zCap[i] = 0;
1267 @ g.userUid = %d(g.userUid)<br />
1268 @ g.zLogin = %h(g.zLogin)<br />
1269 @ g.isHuman = %d(g.isHuman)<br />
1270 @ capabilities = %s(zCap)<br />
 
 
 
 
 
 
 
 
1271 @ g.zRepositoryName = %h(g.zRepositoryName)<br />
1272 @ load_average() = %f(load_average())<br />
1273 @ <hr>
1274 P("HTTP_USER_AGENT");
1275 cgi_print_all(showAll);
1276
--- src/style.c
+++ src/style.c
@@ -23,21 +23,42 @@
23 #include "style.h"
24
25
26 /*
27 ** Elements of the submenu are collected into the following
28 ** structure and displayed below the main menu.
29 **
30 ** Populate these structure with calls to
31 **
32 ** style_submenu_element()
33 ** style_submenu_entry()
34 ** style_submenu_checkbox()
35 ** style_submenu_multichoice()
36 **
37 ** prior to calling style_footer(). The style_footer() routine
38 ** will generate the appropriate HTML text just below the main
39 ** menu.
40 */
41 static struct Submenu {
42 const char *zLabel; /* Button label */
43 const char *zTitle;
44 const char *zLink; /* Jump to this link when button is pressed */
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 */
@@ -216,16 +237,59 @@
237 ...
238 ){
239 va_list ap;
240 assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
241 aSubmenu[nSubmenu].zLabel = zLabel;
242 aSubmenu[nSubmenu].zTitle = zTitle ? zTitle : zLabel;
243 va_start(ap, zLink);
244 aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
245 va_end(ap);
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
292 /*
293 ** Compare two submenu items for sorting purposes
294 */
295 static int submenuCompare(const void *a, const void *b){
@@ -289,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);
@@ -411,23 +474,96 @@
474 /* Go back and put the submenu at the top of the page. We delay the
475 ** creation of the submenu until the end so that we can add elements
476 ** to the submenu while generating page text.
477 */
478 cgi_destination(CGI_HEADER);
479 if( nSubmenu+nSubmenuCtrl>0 ){
480 int i;
481 if( nSubmenuCtrl ){
482 cgi_printf("<form id='f01' method='GET' action='%R/%s'>", g.zPath);
483 }
484 @ <div class="submenu">
485 if( nSubmenu>0 ){
486 qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
487 for(i=0; i<nSubmenu; i++){
488 struct Submenu *p = &aSubmenu[i];
489 if( p->zLink==0 ){
490 @ <span class="label">%h(p->zLabel)</span>
491 }else{
492 @ <a class="label" href="%h(p->zLink)">%h(p->zLabel)</a>
493 }
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",
530 zQPV,
531 fossil_strcmp(zVal,zQPV)==0 ? " selected" : "",
532 aSubmenuCtrl[i].azChoice[j+1]
533 );
534 }
535 @ </select>
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 );
549 cgi_printf(
550 "<option value='0'%s>%h</option>\n",
551 (!isTrue) ? " selected":"", aSubmenuCtrl[i].zFalse
552 );
553 @ </select>
554 break;
555 }
556 }
557 }
558 }
559 @ </div>
560 if( nSubmenuCtrl ){
561 cgi_query_parameters_to_hidden();
562 cgi_tag_query_parameter(0);
563 @ </form>
564 }
565 }
566
567 zAd = style_adunit_text(&mAdFlags);
568 if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
569 @ <div class="content adunit_right_container">
@@ -455,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. */
@@ -526,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;
@@ -536,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 },
@@ -598,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 },
@@ -661,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;
@@ -1183,21 +1355,21 @@
1355 }
1356 zHaystack = z + n;
1357 }
1358 return 0;
1359 }
1360
1361
1362 /*
1363 ** WEBPAGE: style.css
1364 */
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) ){
@@ -1239,37 +1411,44 @@
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"));
1422 if( !showAll ){
1423 style_submenu_element("Show Cookies", 0, "%R/test_env?showall=1");
 
1424 }else{
1425 style_submenu_element("Hide Cookies", 0, "%R/test_env");
 
1426 }
1427 #if !defined(_WIN32)
1428 @ uid=%d(getuid()), gid=%d(getgid())<br />
1429 #endif
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
+177 -15
--- 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,15 +430,74 @@
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
}
440
+ Th_SetResultInt(interp, rc);
441
+ return TH_OK;
442
+}
443
+
444
+/*
445
+** TH1 command: searchable STRING...
446
+**
447
+** Return true if searching in any of the document classes identified
448
+** by STRING is enabled for the repository and user has the necessary
449
+** capabilities to perform the search.
450
+**
451
+** Document classes:
452
+**
453
+** c Check-in comments
454
+** d Embedded documentation
455
+** t Tickets
456
+** w Wiki
457
+**
458
+** To be clear, only one of the document classes identified by each STRING
459
+** needs to be searchable in order for that argument to be true. But
460
+** all arguments must be true for this routine to return true. Hence, to
461
+** see if ALL document classes are searchable:
462
+**
463
+** if {[searchable c d t w]} {...}
464
+**
465
+** But to see if ANY document class is searchable:
466
+**
467
+** if {[searchable cdtw]} {...}
468
+**
469
+** This command is useful for enabling or disabling a "Search" entry
470
+** on the menu bar.
471
+*/
472
+static int searchableCmd(
473
+ Th_Interp *interp,
474
+ void *p,
475
+ int argc,
476
+ const char **argv,
477
+ int *argl
478
+){
479
+ int rc = 1, i, j;
480
+ unsigned int searchCap = search_restrict(SRCH_ALL);
481
+ if( argc<2 ){
482
+ return Th_WrongNumArgs(interp, "hascap STRING ...");
483
+ }
484
+ for(i=1; i<argc && rc; i++){
485
+ int match = 0;
486
+ for(j=0; j<argl[i]; j++){
487
+ switch( argv[i][j] ){
488
+ case 'c': match |= searchCap & SRCH_CKIN; break;
489
+ case 'd': match |= searchCap & SRCH_DOC; break;
490
+ case 't': match |= searchCap & SRCH_TKT; break;
491
+ case 'w': match |= searchCap & SRCH_WIKI; break;
492
+ }
493
+ }
494
+ if( !match ) rc = 0;
495
+ }
496
+ if( g.thTrace ){
497
+ Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc);
498
+ }
367499
Th_SetResultInt(interp, rc);
368500
return TH_OK;
369501
}
370502
371503
/*
@@ -484,11 +616,12 @@
484616
485617
486618
/*
487619
** TH1 command: anycap STRING
488620
**
489
-** 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.
490623
*/
491624
static int anycapCmd(
492625
Th_Interp *interp,
493626
void *p,
494627
int argc,
@@ -499,11 +632,11 @@
499632
int i;
500633
if( argc!=2 ){
501634
return Th_WrongNumArgs(interp, "anycap STRING");
502635
}
503636
for(i=0; rc==0 && i<argl[1]; i++){
504
- rc = login_has_capability((char*)&argv[1][i],1);
637
+ rc = login_has_capability((char*)&argv[1][i],1,0);
505638
}
506639
if( g.thTrace ){
507640
Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
508641
}
509642
Th_SetResultInt(interp, rc);
@@ -919,20 +1052,19 @@
9191052
}
9201053
if( Th_IsRepositoryOpen() ){
9211054
int rid;
9221055
Blob content;
9231056
if( argc==3 ){
924
- rid = artifact_from_ci_and_filename(argv[1], argv[2]);
1057
+ rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]);
9251058
}else{
926
- rid = name_to_rid(argv[1]);
1059
+ rid = th1_name_to_typed_rid(interp, argv[1], "*");
9271060
}
9281061
if( rid!=0 && content_get(rid, &content) ){
9291062
Th_SetResult(interp, blob_str(&content), blob_size(&content));
9301063
blob_reset(&content);
9311064
return TH_OK;
9321065
}else{
933
- Th_SetResult(interp, "artifact not found", -1);
9341066
return TH_ERROR;
9351067
}
9361068
}else{
9371069
Th_SetResult(interp, "repository unavailable", -1);
9381070
return TH_ERROR;
@@ -1391,15 +1523,18 @@
13911523
int needConfig = flags & TH_INIT_NEED_CONFIG;
13921524
int forceReset = flags & TH_INIT_FORCE_RESET;
13931525
int forceTcl = flags & TH_INIT_FORCE_TCL;
13941526
int forceSetup = flags & TH_INIT_FORCE_SETUP;
13951527
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
1528
+ static int anonFlag = LOGIN_ANON;
1529
+ static int zeroInt = 0;
13961530
static struct _Command {
13971531
const char *zName;
13981532
Th_CommandProc xProc;
13991533
void *pContext;
14001534
} aCommand[] = {
1535
+ {"anoncap", hascapCmd, (void*)&anonFlag},
14011536
{"anycap", anycapCmd, 0},
14021537
{"artifact", artifactCmd, 0},
14031538
{"checkout", checkoutCmd, 0},
14041539
{"combobox", comboboxCmd, 0},
14051540
{"date", dateCmd, 0},
@@ -1406,11 +1541,11 @@
14061541
{"decorate", wikiCmd, (void*)&aFlags[2]},
14071542
{"enable_output", enableOutputCmd, 0},
14081543
{"getParameter", getParameterCmd, 0},
14091544
{"globalState", globalStateCmd, 0},
14101545
{"httpize", httpizeCmd, 0},
1411
- {"hascap", hascapCmd, 0},
1546
+ {"hascap", hascapCmd, (void*)&zeroInt},
14121547
{"hasfeature", hasfeatureCmd, 0},
14131548
{"html", putsCmd, (void*)&aFlags[0]},
14141549
{"htmlize", htmlizeCmd, 0},
14151550
{"http", httpCmd, 0},
14161551
{"linecount", linecntCmd, 0},
@@ -1419,10 +1554,11 @@
14191554
{"randhex", randhexCmd, 0},
14201555
{"regexp", regexpCmd, 0},
14211556
{"reinitialize", reinitializeCmd, 0},
14221557
{"render", renderCmd, 0},
14231558
{"repository", repositoryCmd, 0},
1559
+ {"searchable", searchableCmd, 0},
14241560
{"setParameter", setParameterCmd, 0},
14251561
{"setting", settingCmd, 0},
14261562
{"styleHeader", styleHeaderCmd, 0},
14271563
{"styleFooter", styleFooterCmd, 0},
14281564
{"tclReady", tclReadyCmd, 0},
@@ -1892,21 +2028,35 @@
18922028
return rc;
18932029
}
18942030
18952031
/*
18962032
** 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
18972045
*/
18982046
void test_th_render(void){
1899
- int forceCgi, fullHttpReply;
2047
+ int forceCgi = 0, fullHttpReply = 0;
19002048
Blob in;
19012049
Th_InitTraceLog();
1902
- forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1903
- 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;
19042053
if( forceCgi ) Th_ForceCgi(fullHttpReply);
1905
- if( find_option("th-open-config", 0, 0)!=0 ){
2054
+ if( find_option("open-config", 0, 0)!=0 ){
19062055
Th_OpenConfig(1);
19072056
}
2057
+ verify_all_options();
19082058
if( g.argc<3 ){
19092059
usage("FILE");
19102060
}
19112061
blob_zero(&in);
19122062
blob_read_from_file(&in, g.argv[2]);
@@ -1915,20 +2065,32 @@
19152065
if( forceCgi ) cgi_reply();
19162066
}
19172067
19182068
/*
19192069
** 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
19202081
*/
19212082
void test_th_eval(void){
19222083
int rc;
19232084
const char *zRc;
19242085
int forceCgi, fullHttpReply;
19252086
Th_InitTraceLog();
1926
- forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1927
- 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;
19282090
if( forceCgi ) Th_ForceCgi(fullHttpReply);
1929
- if( find_option("th-open-config", 0, 0)!=0 ){
2091
+ if( find_option("open-config", 0, 0)!=0 ){
19302092
Th_OpenConfig(1);
19312093
}
19322094
if( g.argc!=3 ){
19332095
usage("script");
19342096
}
19352097
--- 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,15 +430,74 @@
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);
368 return TH_OK;
369 }
370
371 /*
@@ -484,11 +616,12 @@
484
485
486 /*
487 ** TH1 command: anycap STRING
488 **
489 ** Return true if the user has any one of the capabilities listed in STRING.
 
490 */
491 static int anycapCmd(
492 Th_Interp *interp,
493 void *p,
494 int argc,
@@ -499,11 +632,11 @@
499 int i;
500 if( argc!=2 ){
501 return Th_WrongNumArgs(interp, "anycap STRING");
502 }
503 for(i=0; rc==0 && i<argl[1]; i++){
504 rc = login_has_capability((char*)&argv[1][i],1);
505 }
506 if( g.thTrace ){
507 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
508 }
509 Th_SetResultInt(interp, rc);
@@ -919,20 +1052,19 @@
919 }
920 if( Th_IsRepositoryOpen() ){
921 int rid;
922 Blob content;
923 if( argc==3 ){
924 rid = artifact_from_ci_and_filename(argv[1], argv[2]);
925 }else{
926 rid = name_to_rid(argv[1]);
927 }
928 if( rid!=0 && content_get(rid, &content) ){
929 Th_SetResult(interp, blob_str(&content), blob_size(&content));
930 blob_reset(&content);
931 return TH_OK;
932 }else{
933 Th_SetResult(interp, "artifact not found", -1);
934 return TH_ERROR;
935 }
936 }else{
937 Th_SetResult(interp, "repository unavailable", -1);
938 return TH_ERROR;
@@ -1391,15 +1523,18 @@
1391 int needConfig = flags & TH_INIT_NEED_CONFIG;
1392 int forceReset = flags & TH_INIT_FORCE_RESET;
1393 int forceTcl = flags & TH_INIT_FORCE_TCL;
1394 int forceSetup = flags & TH_INIT_FORCE_SETUP;
1395 static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
 
 
1396 static struct _Command {
1397 const char *zName;
1398 Th_CommandProc xProc;
1399 void *pContext;
1400 } aCommand[] = {
 
1401 {"anycap", anycapCmd, 0},
1402 {"artifact", artifactCmd, 0},
1403 {"checkout", checkoutCmd, 0},
1404 {"combobox", comboboxCmd, 0},
1405 {"date", dateCmd, 0},
@@ -1406,11 +1541,11 @@
1406 {"decorate", wikiCmd, (void*)&aFlags[2]},
1407 {"enable_output", enableOutputCmd, 0},
1408 {"getParameter", getParameterCmd, 0},
1409 {"globalState", globalStateCmd, 0},
1410 {"httpize", httpizeCmd, 0},
1411 {"hascap", hascapCmd, 0},
1412 {"hasfeature", hasfeatureCmd, 0},
1413 {"html", putsCmd, (void*)&aFlags[0]},
1414 {"htmlize", htmlizeCmd, 0},
1415 {"http", httpCmd, 0},
1416 {"linecount", linecntCmd, 0},
@@ -1419,10 +1554,11 @@
1419 {"randhex", randhexCmd, 0},
1420 {"regexp", regexpCmd, 0},
1421 {"reinitialize", reinitializeCmd, 0},
1422 {"render", renderCmd, 0},
1423 {"repository", repositoryCmd, 0},
 
1424 {"setParameter", setParameterCmd, 0},
1425 {"setting", settingCmd, 0},
1426 {"styleHeader", styleHeaderCmd, 0},
1427 {"styleFooter", styleFooterCmd, 0},
1428 {"tclReady", tclReadyCmd, 0},
@@ -1892,21 +2028,35 @@
1892 return rc;
1893 }
1894
1895 /*
1896 ** COMMAND: test-th-render
 
 
 
 
 
 
 
 
 
 
 
 
1897 */
1898 void test_th_render(void){
1899 int forceCgi, fullHttpReply;
1900 Blob in;
1901 Th_InitTraceLog();
1902 forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1903 fullHttpReply = find_option("th-full-http", 0, 0)!=0;
 
1904 if( forceCgi ) Th_ForceCgi(fullHttpReply);
1905 if( find_option("th-open-config", 0, 0)!=0 ){
1906 Th_OpenConfig(1);
1907 }
 
1908 if( g.argc<3 ){
1909 usage("FILE");
1910 }
1911 blob_zero(&in);
1912 blob_read_from_file(&in, g.argv[2]);
@@ -1915,20 +2065,32 @@
1915 if( forceCgi ) cgi_reply();
1916 }
1917
1918 /*
1919 ** COMMAND: test-th-eval
 
 
 
 
 
 
 
 
 
 
 
1920 */
1921 void test_th_eval(void){
1922 int rc;
1923 const char *zRc;
1924 int forceCgi, fullHttpReply;
1925 Th_InitTraceLog();
1926 forceCgi = find_option("th-force-cgi", 0, 0)!=0;
1927 fullHttpReply = find_option("th-full-http", 0, 0)!=0;
 
1928 if( forceCgi ) Th_ForceCgi(fullHttpReply);
1929 if( find_option("th-open-config", 0, 0)!=0 ){
1930 Th_OpenConfig(1);
1931 }
1932 if( g.argc!=3 ){
1933 usage("script");
1934 }
1935
--- 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,15 +430,74 @@
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);
441 return TH_OK;
442 }
443
444 /*
445 ** TH1 command: searchable STRING...
446 **
447 ** Return true if searching in any of the document classes identified
448 ** by STRING is enabled for the repository and user has the necessary
449 ** capabilities to perform the search.
450 **
451 ** Document classes:
452 **
453 ** c Check-in comments
454 ** d Embedded documentation
455 ** t Tickets
456 ** w Wiki
457 **
458 ** To be clear, only one of the document classes identified by each STRING
459 ** needs to be searchable in order for that argument to be true. But
460 ** all arguments must be true for this routine to return true. Hence, to
461 ** see if ALL document classes are searchable:
462 **
463 ** if {[searchable c d t w]} {...}
464 **
465 ** But to see if ANY document class is searchable:
466 **
467 ** if {[searchable cdtw]} {...}
468 **
469 ** This command is useful for enabling or disabling a "Search" entry
470 ** on the menu bar.
471 */
472 static int searchableCmd(
473 Th_Interp *interp,
474 void *p,
475 int argc,
476 const char **argv,
477 int *argl
478 ){
479 int rc = 1, i, j;
480 unsigned int searchCap = search_restrict(SRCH_ALL);
481 if( argc<2 ){
482 return Th_WrongNumArgs(interp, "hascap STRING ...");
483 }
484 for(i=1; i<argc && rc; i++){
485 int match = 0;
486 for(j=0; j<argl[i]; j++){
487 switch( argv[i][j] ){
488 case 'c': match |= searchCap & SRCH_CKIN; break;
489 case 'd': match |= searchCap & SRCH_DOC; break;
490 case 't': match |= searchCap & SRCH_TKT; break;
491 case 'w': match |= searchCap & SRCH_WIKI; break;
492 }
493 }
494 if( !match ) rc = 0;
495 }
496 if( g.thTrace ){
497 Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc);
498 }
499 Th_SetResultInt(interp, rc);
500 return TH_OK;
501 }
502
503 /*
@@ -484,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,
@@ -499,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);
@@ -919,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;
@@ -1391,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},
@@ -1406,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},
@@ -1419,10 +1554,11 @@
1554 {"randhex", randhexCmd, 0},
1555 {"regexp", regexpCmd, 0},
1556 {"reinitialize", reinitializeCmd, 0},
1557 {"render", renderCmd, 0},
1558 {"repository", repositoryCmd, 0},
1559 {"searchable", searchableCmd, 0},
1560 {"setParameter", setParameterCmd, 0},
1561 {"setting", settingCmd, 0},
1562 {"styleHeader", styleHeaderCmd, 0},
1563 {"styleFooter", styleFooterCmd, 0},
1564 {"tclReady", tclReadyCmd, 0},
@@ -1892,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]);
@@ -1915,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 }
2097
+217 -175
--- 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
*/
@@ -494,10 +508,11 @@
494508
" (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
495509
" FROM mlink"
496510
" WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
497511
" AND (fid>0 OR"
498512
" fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
513
+ " AND NOT mlink.isaux"
499514
" ORDER BY 3 /*sort*/"
500515
);
501516
fchngQueryInit = 1;
502517
}
503518
db_bind_int(&fchngQuery, ":mid", rid);
@@ -507,47 +522,55 @@
507522
int fid = db_column_int(&fchngQuery, 1);
508523
int isDel = fid==0;
509524
const char *zOldName = db_column_text(&fchngQuery, 5);
510525
const char *zOld = db_column_text(&fchngQuery, 4);
511526
const char *zNew = db_column_text(&fchngQuery, 3);
512
- const char *zUnpubTag = "";
527
+ const char *zUnpub = "";
528
+ char *zA;
529
+ char zId[20];
513530
if( !inUl ){
514531
@ <ul class="filelist">
515532
inUl = 1;
533
+ }
534
+ if( tmFlags & TIMELINE_SHOWRID ){
535
+ sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid);
536
+ }else{
537
+ zId[0] = 0;
516538
}
517539
if( (tmFlags & TIMELINE_FRENAMES)!=0 ){
518540
if( !isNew && !isDel && zOldName!=0 ){
519
- @ <li> %h(zOldName) &rarr; %h(zFilename)
541
+ @ <li> %h(zOldName) &rarr; %h(zFilename)%s(zId)
520542
}
521543
continue;
522544
}
545
+ zA = href("%R/artifact/%!S",fid?zNew:zOld);
523546
if( content_is_private(fid) ){
524
- zUnpubTag = UNPUB_TAG;
547
+ zUnpub = UNPUB_TAG;
525548
}
526549
if( isNew ){
527
- @ <li> %h(zFilename) %s(zUnpubTag) (new file) &nbsp;
528
- @ %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>
529552
}else if( isDel ){
530
- @ <li> %h(zFilename) (deleted)</li>
553
+ @ <li> %s(zA)%h(zFilename)</a> (deleted)</li>
531554
}else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
532
- @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
533
- @ %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>
534557
}else{
535558
if( zOldName!=0 ){
536
- @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
559
+ @ <li>%h(zOldName) &rarr; %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub)
537560
}else{
538
- @ <li> %h(zFilename) &nbsp; %s(zUnpubTag)
561
+ @ <li>%s(zA)%h(zFilename)</a>%s(zId) &nbsp; %s(zUnpub)
539562
}
540
- @ %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>
541564
}
565
+ fossil_free(zA);
542566
}
543567
db_reset(&fchngQuery);
544568
if( inUl ){
545569
@ </ul>
546570
}
547571
}
548
- pendingEndTr = 1;
549572
}
550573
if( suppressCnt ){
551574
@ <span class="timelineDisabled">... %d(suppressCnt) similar
552575
@ event%s(suppressCnt>1?"s":"") omitted.</span>
553576
suppressCnt = 0;
@@ -666,16 +689,16 @@
666689
if( cSep=='[' ) cgi_printf("[");
667690
cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
668691
}
669692
cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
670693
graph_free(pGraph);
671
- @ var canvasDiv = gebi("canvas");
672
- @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null);
673
- @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black';
674
- @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white';
675
- @ if( bgColor=='transparent' ) bgColor = 'white';
676
- @ var boxColor = lineColor;
694
+ @ var cDiv = gebi("canvas");
695
+ @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null);
696
+ @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black';
697
+ @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white';
698
+ @ if( bgClr=='transparent' ) bgClr = 'white';
699
+ @ var boxColor = lineClr;
677700
@ function drawBox(color,x0,y0,x1,y1){
678701
@ var n = document.createElement("div");
679702
@ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
680703
@ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
681704
@ var w = x1-x0+1;
@@ -685,11 +708,11 @@
685708
@ n.style.left = x0+"px";
686709
@ n.style.top = y0+"px";
687710
@ n.style.width = w+"px";
688711
@ n.style.height = h+"px";
689712
@ n.style.backgroundColor = color;
690
- @ canvasDiv.appendChild(n);
713
+ @ cDiv.appendChild(n);
691714
@ return n;
692715
@ }
693716
@ function absoluteY(id){
694717
@ var obj = gebi(id);
695718
@ if( !obj ) return;
@@ -711,39 +734,39 @@
711734
@ }while( obj = obj.offsetParent );
712735
@ }
713736
@ return left;
714737
@ }
715738
@ function drawUpArrow(x,y0,y1){
716
- @ drawBox(lineColor,x,y0,x+1,y1);
739
+ @ drawBox(lineClr,x,y0,x+1,y1);
717740
@ if( y0+10>=y1 ){
718
- @ drawBox(lineColor,x-1,y0+1,x+2,y0+2);
719
- @ drawBox(lineColor,x-2,y0+3,x+3,y0+4);
741
+ @ drawBox(lineClr,x-1,y0+1,x+2,y0+2);
742
+ @ drawBox(lineClr,x-2,y0+3,x+3,y0+4);
720743
@ }else{
721
- @ drawBox(lineColor,x-1,y0+2,x+2,y0+4);
722
- @ drawBox(lineColor,x-2,y0+5,x+3,y0+7);
744
+ @ drawBox(lineClr,x-1,y0+2,x+2,y0+4);
745
+ @ drawBox(lineClr,x-2,y0+5,x+3,y0+7);
723746
@ }
724747
@ }
725748
@ function drawThinArrow(y,xFrom,xTo){
726749
@ if( xFrom<xTo ){
727
- @ drawBox(lineColor,xFrom,y,xTo,y);
728
- @ drawBox(lineColor,xTo-3,y-1,xTo-2,y+1);
729
- @ drawBox(lineColor,xTo-4,y-2,xTo-4,y+2);
750
+ @ drawBox(lineClr,xFrom,y,xTo,y);
751
+ @ drawBox(lineClr,xTo-3,y-1,xTo-2,y+1);
752
+ @ drawBox(lineClr,xTo-4,y-2,xTo-4,y+2);
730753
@ }else{
731
- @ drawBox(lineColor,xTo,y,xFrom,y);
732
- @ drawBox(lineColor,xTo+2,y-1,xTo+3,y+1);
733
- @ drawBox(lineColor,xTo+4,y-2,xTo+4,y+2);
754
+ @ drawBox(lineClr,xTo,y,xFrom,y);
755
+ @ drawBox(lineClr,xTo+2,y-1,xTo+3,y+1);
756
+ @ drawBox(lineClr,xTo+4,y-2,xTo+4,y+2);
734757
@ }
735758
@ }
736759
@ function drawThinLine(x0,y0,x1,y1){
737
- @ drawBox(lineColor,x0,y0,x1,y1);
760
+ @ drawBox(lineClr,x0,y0,x1,y1);
738761
@ }
739762
@ function drawNodeBox(color,x0,y0,x1,y1){
740763
@ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
741764
@ }
742765
@ function drawNode(p, left, btm){
743766
@ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
744
- @ drawNodeBox(p.bg||bgColor,p.x-4,p.y-4,p.x+5,p.y+5);
767
+ @ drawNodeBox(p.bg||bgClr,p.x-4,p.y-4,p.x+5,p.y+5);
745768
@ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
746769
@ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
747770
if( !omitDescenders ){
748771
@ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
749772
@ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +788,11 @@
765788
@ for(var i=0; i<n; i+=2){
766789
@ var x1 = p.au[i]*railPitch + left;
767790
@ var x0 = x1>p.x ? p.x+7 : p.x-6;
768791
@ var u = rowinfo[p.au[i+1]-1];
769792
@ if(u.id<p.id){
770
- @ drawBox(lineColor,x0,p.y,x1,p.y+1);
793
+ @ drawBox(lineClr,x0,p.y,x1,p.y+1);
771794
@ drawUpArrow(x1, u.y+6, p.y);
772795
@ }else{
773796
@ drawBox("#600000",x0,p.y,x1,p.y+1);
774797
@ drawBox("#600000",x1-1,p.y,x1,u.y+1);
775798
@ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,43 +975,39 @@
952975
if( z==0 ) return -1.0;
953976
if( fossil_isdate(z) ){
954977
mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
955978
if( mtime>0.0 ) return mtime;
956979
}
957
- rid = symbolic_name_to_rid(z, "ci");
958
- if( rid==0 ) return -1.0;
959
- mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
980
+ rid = symbolic_name_to_rid(z, "*");
981
+ if( rid ){
982
+ mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
983
+ }else{
984
+ mtime = db_double(-1.0,
985
+ "SELECT max(event.mtime) FROM event, tag, tagxref"
986
+ " WHERE tag.tagname GLOB 'event-%q*'"
987
+ " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
988
+ " AND event.objid=tagxref.rid",
989
+ z
990
+ );
991
+ }
960992
return mtime;
961993
}
962994
963995
/*
964
-** The value of one second in julianday notation
965
-*/
966
-#define ONE_SECOND (1.0/86400.0)
967
-
968
-/*
969
-** zDate is a localtime date. Insert records into the
970
-** "timeline" table to cause <hr> to be inserted before and after
971
-** entries of that date. If zDate==NULL then put dividers around
972
-** the event identified by rid.
973
-*/
974
-static void timeline_add_dividers(double rDate, int rid){
975
- char *zToDel = 0;
976
- if( rDate==0 ){
977
- rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
978
- }
979
- db_multi_exec(
980
- "INSERT INTO timeline(rid,sortby,etype)"
981
- "VALUES(-1,%.16g,'div')",
982
- rDate-ONE_SECOND
983
- );
984
- db_multi_exec(
985
- "INSERT INTO timeline(rid,sortby,etype)"
986
- "VALUES(-2,%.17g,'div')",
987
- rDate+ONE_SECOND
988
- );
989
- 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;
9901009
}
9911010
9921011
/*
9931012
** Return all possible names for file zUuid.
9941013
*/
@@ -1011,19 +1030,57 @@
10111030
}
10121031
db_finalize(&q);
10131032
return blob_str(&out);
10141033
}
10151034
1035
+
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";
1052
+ az[i++] = "Tags";
1053
+ }
1054
+ if( g.perm.RdWiki ){
1055
+ az[i++] = "e";
1056
+ az[i++] = "Tech Notes";
1057
+ }
1058
+ if( g.perm.RdTkt ){
1059
+ az[i++] = "t";
1060
+ az[i++] = "Tickets";
1061
+ }
1062
+ if( g.perm.RdWiki ){
1063
+ az[i++] = "w";
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
+}
10161072
10171073
/*
10181074
** WEBPAGE: timeline
10191075
**
10201076
** Query parameters:
10211077
**
10221078
** a=TIMEORTAG after this event
10231079
** b=TIMEORTAG before this event
10241080
** c=TIMEORTAG "circa" this event
1081
+** m=TIMEORTAG mark this event
10251082
** n=COUNT max number of events in output
10261083
** p=UUID artifact and up to COUNT parents and ancestors
10271084
** d=UUID artifact and up to COUNT descendants
10281085
** dp=UUID The same as d=UUID&p=UUID
10291086
** t=TAGID show only check-ins with the given tagid
@@ -1046,34 +1103,35 @@
10461103
** datefmt=N Override the date format
10471104
**
10481105
** p= and d= can appear individually or together. If either p= or d=
10491106
** appear, then u=, y=, a=, and b= are ignored.
10501107
**
1051
-** If a= and b= appear, only a= is used. If neither appear, the most
1052
-** recent events are chosen.
1108
+** If both a= and b= appear then both upper and lower bounds are honored.
10531109
**
1054
-** 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.
10551112
*/
10561113
void page_timeline(void){
10571114
Stmt q; /* Query used to generate the timeline */
10581115
Blob sql; /* text of SQL used to generate timeline */
10591116
Blob desc; /* Description of the timeline */
1060
- int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */
1117
+ int nEntry; /* Max number of entries on timeline */
10611118
int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
10621119
int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
10631120
int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
10641121
const char *zUser = P("u"); /* All entries by this user if not NULL */
10651122
const char *zType = PD("y","all"); /* Type of events. All if NULL */
10661123
const char *zAfter = P("a"); /* Events after this time */
10671124
const char *zBefore = P("b"); /* Events before this time */
10681125
const char *zCirca = P("c"); /* Events near this time */
1126
+ const char *zMark = P("m"); /* Mark this event or an event this time */
10691127
const char *zTagName = P("t"); /* Show events with this tag */
10701128
const char *zBrName = P("r"); /* Show events related to this tag */
10711129
const char *zSearch = P("s"); /* Search string */
10721130
const char *zUses = P("uf"); /* Only show checkins hold this file */
10731131
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1074
- const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (week-of-year)*/
1132
+ const char *zYearWeek = P("yw"); /* Checkins for YYYY-WW (week-of-year) */
10751133
int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
10761134
int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
10771135
int tagid; /* Tag ID */
10781136
int tmFlags = 0; /* Timeline flags */
10791137
const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,68 +1142,92 @@
10841142
int noMerge = P("shortest")==0; /* Follow merge links if shorter */
10851143
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
10861144
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
10871145
int pd_rid;
10881146
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 ){
1156
+ nEntry = 0;
1157
+ }else{
1158
+ nEntry = atoi(z);
1159
+ if( nEntry<=0 ){
1160
+ cgi_replace_query_parameter("n","10");
1161
+ nEntry = 10;
1162
+ }
1163
+ }
1164
+ }else if( zCirca ){
1165
+ cgi_replace_query_parameter("n","11");
1166
+ nEntry = 11;
1167
+ }else{
1168
+ cgi_replace_query_parameter("n","50");
1169
+ nEntry = 50;
1170
+ }
10891171
10901172
/* To view the timeline, must have permission to read project data.
10911173
*/
10921174
pd_rid = name_to_typed_rid(P("dp"),"ci");
10931175
if( pd_rid ){
10941176
p_rid = d_rid = pd_rid;
10951177
}
10961178
login_check_credentials();
10971179
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1098
- login_needed();
1180
+ login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
10991181
return;
11001182
}
11011183
url_initialize(&url, "timeline");
1184
+ cgi_query_parameters_to_url(&url);
11021185
if( zTagName && g.perm.Read ){
11031186
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
11041187
zThisTag = zTagName;
11051188
}else if( zBrName && g.perm.Read ){
11061189
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
11071190
zThisTag = zBrName;
11081191
}else{
11091192
tagid = 0;
11101193
}
1111
- if( tagid>0
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
11121200
&& db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
11131201
){
1114
- zCirca = zBefore = zAfter = 0;
11151202
nEntry = -1;
11161203
}
11171204
if( zType[0]=='a' ){
11181205
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
11191206
}else{
11201207
tmFlags |= TIMELINE_GRAPH;
11211208
}
1122
- if( nEntry>0 ) url_add_parameter(&url, "n", mprintf("%d", nEntry));
1123
- if( P("ng")!=0 || zSearch!=0 ){
1209
+ if( PB("ng") || zSearch!=0 ){
11241210
tmFlags &= ~TIMELINE_GRAPH;
1125
- url_add_parameter(&url, "ng", 0);
11261211
}
1127
- if( P("brbg")!=0 ){
1212
+ if( PB("brbg") ){
11281213
tmFlags |= TIMELINE_BRCOLOR;
1129
- url_add_parameter(&url, "brbg", 0);
11301214
}
1131
- if( P("unhide")!=0 ){
1215
+ if( PB("unhide") ){
11321216
tmFlags |= TIMELINE_UNHIDE;
1133
- url_add_parameter(&url, "unhide", 0);
11341217
}
1135
- if( P("ubg")!=0 ){
1218
+ if( PB("ubg") ){
11361219
tmFlags |= TIMELINE_UCOLOR;
1137
- url_add_parameter(&url, "ubg", 0);
11381220
}
11391221
if( zUses!=0 ){
11401222
int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
11411223
if( ufid ){
11421224
zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1143
- url_add_parameter(&url, "uf", zUses);
11441225
db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
11451226
compute_uses_file("usesfile", ufid, 0);
11461227
zType = "ci";
1228
+ disableY = 1;
11471229
}else{
11481230
zUses = 0;
11491231
}
11501232
}
11511233
if( renameOnly ){
@@ -1152,31 +1234,30 @@
11521234
db_multi_exec(
11531235
"CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
11541236
"INSERT OR IGNORE INTO rnfile"
11551237
" SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
11561238
);
1239
+ disableY = 1;
11571240
}
11581241
11591242
style_header("Timeline");
11601243
login_anonymous_available();
11611244
timeline_temp_table();
11621245
blob_zero(&sql);
11631246
blob_zero(&desc);
11641247
blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
11651248
blob_append(&sql, timeline_query_for_www(), -1);
1166
- if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){
1249
+ if( PB("fc") || PB("v") || PB("detail") ){
11671250
tmFlags |= TIMELINE_FCHANGES;
1168
- url_add_parameter(&url, "v", 0);
11691251
}
11701252
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
11711253
blob_append_sql(&sql,
11721254
" AND NOT EXISTS(SELECT 1 FROM tagxref"
11731255
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
11741256
TAG_HIDDEN
11751257
);
11761258
}
1177
- if( !useDividers ) url_add_parameter(&url, "nd", 0);
11781259
if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
11791260
/* If from= and to= are present, display all nodes on a path connecting
11801261
** the two */
11811262
PathNode *p = 0;
11821263
const char *zFrom = 0;
@@ -1226,11 +1307,11 @@
12261307
if( d_rid ){
12271308
compute_descendants(d_rid, nEntry+1);
12281309
nd = db_int(0, "SELECT count(*)-1 FROM ok");
12291310
if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
12301311
if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
1231
- if( useDividers ) timeline_add_dividers(0, d_rid);
1312
+ if( useDividers ) selectedRid = d_rid;
12321313
db_multi_exec("DELETE FROM ok");
12331314
}
12341315
if( p_rid ){
12351316
compute_ancestors(p_rid, nEntry+1, 0);
12361317
np = db_int(0, "SELECT count(*)-1 FROM ok");
@@ -1237,38 +1318,24 @@
12371318
if( np>0 ){
12381319
if( nd>0 ) blob_appendf(&desc, " and ");
12391320
blob_appendf(&desc, "%d ancestors", np);
12401321
db_multi_exec("%s", blob_sql_text(&sql));
12411322
}
1242
- if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1323
+ if( useDividers ) selectedRid = p_rid;
12431324
}
12441325
blob_appendf(&desc, " of %z[%S]</a>",
1245
- href("%R/info/%s", zUuid), zUuid);
1246
- if( p_rid ){
1247
- url_add_parameter(&url, "p", zUuid);
1248
- }
1326
+ href("%R/info/%!S", zUuid), zUuid);
12491327
if( d_rid ){
12501328
if( p_rid ){
12511329
/* If both p= and d= are set, we don't have the uuid of d yet. */
12521330
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
12531331
}
1254
- url_add_parameter(&url, "d", zUuid);
1255
- }
1256
- if( nEntry>20 ){
1257
- timeline_submenu(&url, "20 Entries", "n", "20", 0);
1258
- }
1259
- if( nEntry<200 && nEntry>0 ){
1260
- timeline_submenu(&url, "200 Entries", "n", "200", 0);
1261
- }
1262
- if( tmFlags & TIMELINE_FCHANGES ){
1263
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1264
- }else{
1265
- timeline_submenu(&url, "Show Files", "v", "", 0);
1266
- }
1267
- if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1268
- timeline_submenu(&url, "Unhide", "unhide", "", 0);
1269
- }
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');
12701337
}else if( f_rid && g.perm.Read ){
12711338
/* If f= is present, ignore all other parameters other than n= */
12721339
char *zUuid;
12731340
db_multi_exec(
12741341
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1277,21 +1344,17 @@
12771344
"INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
12781345
f_rid, f_rid, f_rid
12791346
);
12801347
blob_append_sql(&sql, " AND event.objid IN ok");
12811348
db_multi_exec("%s", blob_sql_text(&sql));
1282
- if( useDividers ) timeline_add_dividers(0, f_rid);
1349
+ if( useDividers ) selectedRid = f_rid;
12831350
blob_appendf(&desc, "Parents and children of check-in ");
12841351
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1285
- 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);
12861353
tmFlags |= TIMELINE_DISJOINT;
1287
- url_add_parameter(&url, "f", zUuid);
1288
- if( tmFlags & TIMELINE_FCHANGES ){
1289
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1290
- }else{
1291
- timeline_submenu(&url, "Show Files", "v", "", 0);
1292
- }
1354
+ style_submenu_binary("v","With Files","Without Files",
1355
+ zType[0]!='a' && zType[0]!='c');
12931356
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
12941357
timeline_submenu(&url, "Unhide", "unhide", "", 0);
12951358
}
12961359
}else{
12971360
/* Otherwise, a timeline based on a span of time */
@@ -1316,11 +1379,10 @@
13161379
blob_append_sql(&sql,
13171380
"AND (EXISTS(SELECT 1 FROM tagxref"
13181381
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
13191382
13201383
if( zBrName ){
1321
- url_add_parameter(&url, "r", zBrName);
13221384
/* The next two blob_appendf() calls add SQL that causes checkins that
13231385
** are not part of the branch which are parents or children of the
13241386
** branch to be included in the report. This related check-ins are
13251387
** useful in helping to visualize what has happened on a quiescent
13261388
** branch that is infrequently merged with a much more activate branch.
@@ -1342,21 +1404,17 @@
13421404
" OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
13431405
" WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
13441406
tagid
13451407
);
13461408
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1347
- blob_append_sql(&sql,
1409
+ blob_append_sql(&sql,
13481410
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
13491411
" WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
13501412
TAG_HIDDEN
13511413
);
13521414
}
1353
- }else{
1354
- url_add_parameter(&url, "mionly", "1");
13551415
}
1356
- }else{
1357
- url_add_parameter(&url, "t", zTagName);
13581416
}
13591417
blob_append_sql(&sql, ")");
13601418
}
13611419
if( (zType[0]=='w' && !g.perm.RdWiki)
13621420
|| (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,19 +1442,18 @@
13841442
}
13851443
blob_append_sql(&sql, ")");
13861444
}
13871445
}else{ /* zType!="all" */
13881446
blob_append_sql(&sql, " AND event.type=%Q", zType);
1389
- url_add_parameter(&url, "y", zType);
13901447
if( zType[0]=='c' ){
13911448
zEType = "checkin";
13921449
}else if( zType[0]=='w' ){
13931450
zEType = "wiki edit";
13941451
}else if( zType[0]=='t' ){
13951452
zEType = "ticket change";
13961453
}else if( zType[0]=='e' ){
1397
- zEType = "event";
1454
+ zEType = "technical note";
13981455
}else if( zType[0]=='g' ){
13991456
zEType = "tag";
14001457
}
14011458
}
14021459
if( zUser ){
@@ -1406,41 +1463,39 @@
14061463
zCirca = zBefore = zAfter = 0;
14071464
nEntry = -1;
14081465
}
14091466
blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
14101467
zUser, zUser);
1411
- url_add_parameter(&url, "u", zUser);
14121468
zThisUser = zUser;
14131469
}
14141470
if( zSearch ){
14151471
blob_append_sql(&sql,
14161472
" AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
14171473
zSearch, zSearch);
1418
- url_add_parameter(&url, "s", zSearch);
14191474
}
14201475
rBefore = symbolic_name_to_mtime(zBefore);
14211476
rAfter = symbolic_name_to_mtime(zAfter);
14221477
rCirca = symbolic_name_to_mtime(zCirca);
14231478
if( rAfter>0.0 ){
14241479
if( rBefore>0.0 ){
14251480
blob_append_sql(&sql,
14261481
" AND event.mtime>=%.17g AND event.mtime<=%.17g"
14271482
" ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
1428
- url_add_parameter(&url, "a", zAfter);
1429
- url_add_parameter(&url, "b", zBefore);
14301483
nEntry = -1;
14311484
}else{
14321485
blob_append_sql(&sql,
14331486
" AND event.mtime>=%.17g ORDER BY event.mtime ASC",
14341487
rAfter-ONE_SECOND);
1435
- url_add_parameter(&url, "a", zAfter);
14361488
}
1489
+ zCirca = 0;
1490
+ url_add_parameter(&url, "c", 0);
14371491
}else if( rBefore>0.0 ){
14381492
blob_append_sql(&sql,
14391493
" AND event.mtime<=%.17g ORDER BY event.mtime DESC",
14401494
rBefore+ONE_SECOND);
1441
- url_add_parameter(&url, "b", zBefore);
1495
+ zCirca = 0;
1496
+ url_add_parameter(&url, "c", 0);
14421497
}else if( rCirca>0.0 ){
14431498
Blob sql2;
14441499
blob_init(&sql2, blob_sql_text(&sql), -1);
14451500
blob_append_sql(&sql2,
14461501
" AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1451,12 +1506,11 @@
14511506
blob_append_sql(&sql,
14521507
" AND event.mtime>=%f ORDER BY event.mtime ASC",
14531508
rCirca
14541509
);
14551510
nEntry -= (nEntry+1)/2;
1456
- if( useDividers ) timeline_add_dividers(rCirca, 0);
1457
- url_add_parameter(&url, "c", zCirca);
1511
+ if( zMark==0 ) zMark = zCirca;
14581512
}else{
14591513
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
14601514
}
14611515
if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
14621516
db_multi_exec("%s", blob_sql_text(&sql));
@@ -1464,19 +1518,19 @@
14641518
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
14651519
if( zYearMonth ){
14661520
blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
14671521
}else if( zYearWeek ){
14681522
blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1469
- }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1523
+ }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
14701524
blob_appendf(&desc, "%d most recent %ss", n, zEType);
14711525
}else{
14721526
blob_appendf(&desc, "%d %ss", n, zEType);
14731527
}
14741528
if( zUses ){
14751529
char *zFilenames = names_of_file(zUses);
14761530
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1477
- href("%R/artifact/%s",zUses), zUses);
1531
+ href("%R/artifact/%!S",zUses), zUses);
14781532
tmFlags |= TIMELINE_DISJOINT;
14791533
}
14801534
if( renameOnly ){
14811535
blob_appendf(&desc, " that contain filename changes");
14821536
tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1509,64 +1563,49 @@
15091563
}
15101564
if( g.perm.Hyperlink ){
15111565
if( zAfter || n==nEntry ){
15121566
zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
15131567
timeline_submenu(&url, "Older", "b", zDate, "a");
1568
+ zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0));
15141569
free(zDate);
15151570
}
15161571
if( zBefore || (zAfter && n==nEntry) ){
15171572
zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
15181573
timeline_submenu(&url, "Newer", "a", zDate, "b");
15191574
free(zDate);
1520
- }else if( tagid==0 && zUses==0 ){
1521
- if( zType[0]!='a' ){
1522
- timeline_submenu(&url, "All Types", "y", "all", 0);
1523
- }
1524
- if( zType[0]!='w' && g.perm.RdWiki ){
1525
- timeline_submenu(&url, "Wiki Only", "y", "w", 0);
1526
- }
1527
- if( zType[0]!='c' && g.perm.Read ){
1528
- timeline_submenu(&url, "Checkins Only", "y", "ci", 0);
1529
- }
1530
- if( zType[0]!='t' && g.perm.RdTkt ){
1531
- timeline_submenu(&url, "Tickets Only", "y", "t", 0);
1532
- }
1533
- if( zType[0]!='e' && g.perm.RdWiki ){
1534
- timeline_submenu(&url, "Events Only", "y", "e", 0);
1535
- }
1536
- if( zType[0]!='g' && g.perm.Read ){
1537
- timeline_submenu(&url, "Tags Only", "y", "g", 0);
1538
- }
1539
- }
1540
- if( nEntry>20 ){
1541
- timeline_submenu(&url, "20 Entries", "n", "20", 0);
1542
- }
1543
- if( nEntry<200 && nEntry>0 ){
1544
- timeline_submenu(&url, "200 Entries", "n", "200", 0);
15451575
}
15461576
if( zType[0]=='a' || zType[0]=='c' ){
1547
- if( tmFlags & TIMELINE_FCHANGES ){
1548
- timeline_submenu(&url, "Hide Files", "v", 0, 0);
1549
- }else{
1550
- timeline_submenu(&url, "Show Files", "v", "", 0);
1551
- }
15521577
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
15531578
timeline_submenu(&url, "Unhide", "unhide", "", 0);
15541579
}
15551580
}
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');
15561585
}
15571586
}
1558
- if( P("showsql") ){
1587
+ if( PB("showsql") ){
15591588
@ <blockquote>%h(blob_sql_text(&sql))</blockquote>
15601589
}
1561
- if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
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
+ }
15621598
blob_zero(&sql);
15631599
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
15641600
@ <h2>%b(&desc)</h2>
15651601
blob_reset(&desc);
1566
- www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1602
+ www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0);
15671603
db_finalize(&q);
1604
+ if( zOlderButton ){
1605
+ @ %z(xhref("class='button'","%z",zOlderButton))Older</a>
1606
+ }
15681607
style_footer();
15691608
}
15701609
15711610
/*
15721611
** The input query q selects various records. Print a human-readable
@@ -1771,16 +1810,16 @@
17711810
** for the current version or "now" for the current time.
17721811
**
17731812
** Options:
17741813
** -n|--limit N Output the first N entries (default 20 lines).
17751814
** N=0 means no limit.
1776
-** -p|--path PATH Output items affecting PATH only.
1815
+** -p|--path PATH Output items affecting PATH only.
17771816
** PATH can be a file or a sub directory.
17781817
** --offset P skip P changes
17791818
** -t|--type TYPE Output items from the given types only, such as:
17801819
** ci = file commits only
1781
-** e = events only
1820
+** e = technical notes only
17821821
** t = tickets only
17831822
** w = wiki commits only
17841823
** -v|--verbose Output the list of files changed by each commit
17851824
** and the type of each change (edited, deleted,
17861825
** etc.) after the checkin comment.
@@ -1893,11 +1932,11 @@
18931932
if( mode==0 ){
18941933
if( isIsoDate(zOrigin) ) zShift = ",'+1 day'";
18951934
}
18961935
zDate = mprintf("(SELECT julianday(%Q%s, 'utc'))", zOrigin, zShift);
18971936
}
1898
-
1937
+
18991938
if( zFilePattern ){
19001939
if( zType==0 ){
19011940
/* When zFilePattern is specified and type is not specified, only show
19021941
* file checkins */
19031942
zType="ci";
@@ -2029,11 +2068,14 @@
20292068
*/
20302069
void test_timewarp_page(void){
20312070
Stmt q;
20322071
20332072
login_check_credentials();
2034
- 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
+ }
20352077
style_header("Instances of timewarp");
20362078
@ <ul>
20372079
db_prepare(&q,
20382080
"SELECT blob.uuid "
20392081
" FROM plink p, plink c, blob"
@@ -2041,10 +2083,10 @@
20412083
" AND blob.rid=c.cid"
20422084
);
20432085
while( db_step(&q)==SQLITE_ROW ){
20442086
const char *zUuid = db_column_text(&q, 0);
20452087
@ <li>
2046
- @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&amp;d=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2088
+ @ <a href="%R/timeline?dp=%!S(zUuid)&amp;unhide">%S(zUuid)</a>
20472089
}
20482090
db_finalize(&q);
20492091
style_footer();
20502092
}
20512093
--- 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 */
@@ -494,10 +508,11 @@
494 " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
495 " FROM mlink"
496 " WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
497 " AND (fid>0 OR"
498 " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
 
499 " ORDER BY 3 /*sort*/"
500 );
501 fchngQueryInit = 1;
502 }
503 db_bind_int(&fchngQuery, ":mid", rid);
@@ -507,47 +522,55 @@
507 int fid = db_column_int(&fchngQuery, 1);
508 int isDel = fid==0;
509 const char *zOldName = db_column_text(&fchngQuery, 5);
510 const char *zOld = db_column_text(&fchngQuery, 4);
511 const char *zNew = db_column_text(&fchngQuery, 3);
512 const char *zUnpubTag = "";
 
 
513 if( !inUl ){
514 @ <ul class="filelist">
515 inUl = 1;
 
 
 
 
 
516 }
517 if( (tmFlags & TIMELINE_FRENAMES)!=0 ){
518 if( !isNew && !isDel && zOldName!=0 ){
519 @ <li> %h(zOldName) &rarr; %h(zFilename)
520 }
521 continue;
522 }
 
523 if( content_is_private(fid) ){
524 zUnpubTag = UNPUB_TAG;
525 }
526 if( isNew ){
527 @ <li> %h(zFilename) %s(zUnpubTag) (new file) &nbsp;
528 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
529 }else if( isDel ){
530 @ <li> %h(zFilename) (deleted)</li>
531 }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
532 @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
533 @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
534 }else{
535 if( zOldName!=0 ){
536 @ <li> %h(zOldName) &rarr; %h(zFilename) %s(zUnpubTag)
537 }else{
538 @ <li> %h(zFilename) &nbsp; %s(zUnpubTag)
539 }
540 @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li>
541 }
 
542 }
543 db_reset(&fchngQuery);
544 if( inUl ){
545 @ </ul>
546 }
547 }
548 pendingEndTr = 1;
549 }
550 if( suppressCnt ){
551 @ <span class="timelineDisabled">... %d(suppressCnt) similar
552 @ event%s(suppressCnt>1?"s":"") omitted.</span>
553 suppressCnt = 0;
@@ -666,16 +689,16 @@
666 if( cSep=='[' ) cgi_printf("[");
667 cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
668 }
669 cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
670 graph_free(pGraph);
671 @ var canvasDiv = gebi("canvas");
672 @ var canvasStyle = window.getComputedStyle && window.getComputedStyle(canvasDiv,null);
673 @ var lineColor = (canvasStyle && canvasStyle.getPropertyValue('color')) || 'black';
674 @ var bgColor = (canvasStyle && canvasStyle.getPropertyValue('background-color')) || 'white';
675 @ if( bgColor=='transparent' ) bgColor = 'white';
676 @ var boxColor = lineColor;
677 @ function drawBox(color,x0,y0,x1,y1){
678 @ var n = document.createElement("div");
679 @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
680 @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
681 @ var w = x1-x0+1;
@@ -685,11 +708,11 @@
685 @ n.style.left = x0+"px";
686 @ n.style.top = y0+"px";
687 @ n.style.width = w+"px";
688 @ n.style.height = h+"px";
689 @ n.style.backgroundColor = color;
690 @ canvasDiv.appendChild(n);
691 @ return n;
692 @ }
693 @ function absoluteY(id){
694 @ var obj = gebi(id);
695 @ if( !obj ) return;
@@ -711,39 +734,39 @@
711 @ }while( obj = obj.offsetParent );
712 @ }
713 @ return left;
714 @ }
715 @ function drawUpArrow(x,y0,y1){
716 @ drawBox(lineColor,x,y0,x+1,y1);
717 @ if( y0+10>=y1 ){
718 @ drawBox(lineColor,x-1,y0+1,x+2,y0+2);
719 @ drawBox(lineColor,x-2,y0+3,x+3,y0+4);
720 @ }else{
721 @ drawBox(lineColor,x-1,y0+2,x+2,y0+4);
722 @ drawBox(lineColor,x-2,y0+5,x+3,y0+7);
723 @ }
724 @ }
725 @ function drawThinArrow(y,xFrom,xTo){
726 @ if( xFrom<xTo ){
727 @ drawBox(lineColor,xFrom,y,xTo,y);
728 @ drawBox(lineColor,xTo-3,y-1,xTo-2,y+1);
729 @ drawBox(lineColor,xTo-4,y-2,xTo-4,y+2);
730 @ }else{
731 @ drawBox(lineColor,xTo,y,xFrom,y);
732 @ drawBox(lineColor,xTo+2,y-1,xTo+3,y+1);
733 @ drawBox(lineColor,xTo+4,y-2,xTo+4,y+2);
734 @ }
735 @ }
736 @ function drawThinLine(x0,y0,x1,y1){
737 @ drawBox(lineColor,x0,y0,x1,y1);
738 @ }
739 @ function drawNodeBox(color,x0,y0,x1,y1){
740 @ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
741 @ }
742 @ function drawNode(p, left, btm){
743 @ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
744 @ drawNodeBox(p.bg||bgColor,p.x-4,p.y-4,p.x+5,p.y+5);
745 @ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
746 @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
747 if( !omitDescenders ){
748 @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
749 @ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +788,11 @@
765 @ for(var i=0; i<n; i+=2){
766 @ var x1 = p.au[i]*railPitch + left;
767 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
768 @ var u = rowinfo[p.au[i+1]-1];
769 @ if(u.id<p.id){
770 @ drawBox(lineColor,x0,p.y,x1,p.y+1);
771 @ drawUpArrow(x1, u.y+6, p.y);
772 @ }else{
773 @ drawBox("#600000",x0,p.y,x1,p.y+1);
774 @ drawBox("#600000",x1-1,p.y,x1,u.y+1);
775 @ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,43 +975,39 @@
952 if( z==0 ) return -1.0;
953 if( fossil_isdate(z) ){
954 mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
955 if( mtime>0.0 ) return mtime;
956 }
957 rid = symbolic_name_to_rid(z, "ci");
958 if( rid==0 ) return -1.0;
959 mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
 
 
 
 
 
 
 
 
 
960 return mtime;
961 }
962
963 /*
964 ** The value of one second in julianday notation
965 */
966 #define ONE_SECOND (1.0/86400.0)
967
968 /*
969 ** zDate is a localtime date. Insert records into the
970 ** "timeline" table to cause <hr> to be inserted before and after
971 ** entries of that date. If zDate==NULL then put dividers around
972 ** the event identified by rid.
973 */
974 static void timeline_add_dividers(double rDate, int rid){
975 char *zToDel = 0;
976 if( rDate==0 ){
977 rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
978 }
979 db_multi_exec(
980 "INSERT INTO timeline(rid,sortby,etype)"
981 "VALUES(-1,%.16g,'div')",
982 rDate-ONE_SECOND
983 );
984 db_multi_exec(
985 "INSERT INTO timeline(rid,sortby,etype)"
986 "VALUES(-2,%.17g,'div')",
987 rDate+ONE_SECOND
988 );
989 fossil_free(zToDel);
990 }
991
992 /*
993 ** Return all possible names for file zUuid.
994 */
@@ -1011,19 +1030,57 @@
1011 }
1012 db_finalize(&q);
1013 return blob_str(&out);
1014 }
1015
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
1017 /*
1018 ** WEBPAGE: timeline
1019 **
1020 ** Query parameters:
1021 **
1022 ** a=TIMEORTAG after this event
1023 ** b=TIMEORTAG before this event
1024 ** c=TIMEORTAG "circa" this event
 
1025 ** n=COUNT max number of events in output
1026 ** p=UUID artifact and up to COUNT parents and ancestors
1027 ** d=UUID artifact and up to COUNT descendants
1028 ** dp=UUID The same as d=UUID&p=UUID
1029 ** t=TAGID show only check-ins with the given tagid
@@ -1046,34 +1103,35 @@
1046 ** datefmt=N Override the date format
1047 **
1048 ** p= and d= can appear individually or together. If either p= or d=
1049 ** appear, then u=, y=, a=, and b= are ignored.
1050 **
1051 ** If a= and b= appear, only a= is used. If neither appear, the most
1052 ** recent events are chosen.
1053 **
1054 ** If n= is missing, the default count is 20.
 
1055 */
1056 void page_timeline(void){
1057 Stmt q; /* Query used to generate the timeline */
1058 Blob sql; /* text of SQL used to generate timeline */
1059 Blob desc; /* Description of the timeline */
1060 int nEntry = atoi(PD("n","20")); /* Max number of entries on timeline */
1061 int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
1062 int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
1063 int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
1064 const char *zUser = P("u"); /* All entries by this user if not NULL */
1065 const char *zType = PD("y","all"); /* Type of events. All if NULL */
1066 const char *zAfter = P("a"); /* Events after this time */
1067 const char *zBefore = P("b"); /* Events before this time */
1068 const char *zCirca = P("c"); /* Events near this time */
 
1069 const char *zTagName = P("t"); /* Show events with this tag */
1070 const char *zBrName = P("r"); /* Show events related to this tag */
1071 const char *zSearch = P("s"); /* Search string */
1072 const char *zUses = P("uf"); /* Only show checkins hold this file */
1073 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1074 const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (week-of-year)*/
1075 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1076 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1077 int tagid; /* Tag ID */
1078 int tmFlags = 0; /* Timeline flags */
1079 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,68 +1142,92 @@
1084 int noMerge = P("shortest")==0; /* Follow merge links if shorter */
1085 int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
1086 int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
1087 int pd_rid;
1088 double rBefore, rAfter, rCirca; /* Boundary times */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1089
1090 /* To view the timeline, must have permission to read project data.
1091 */
1092 pd_rid = name_to_typed_rid(P("dp"),"ci");
1093 if( pd_rid ){
1094 p_rid = d_rid = pd_rid;
1095 }
1096 login_check_credentials();
1097 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
1098 login_needed();
1099 return;
1100 }
1101 url_initialize(&url, "timeline");
 
1102 if( zTagName && g.perm.Read ){
1103 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
1104 zThisTag = zTagName;
1105 }else if( zBrName && g.perm.Read ){
1106 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1107 zThisTag = zBrName;
1108 }else{
1109 tagid = 0;
1110 }
1111 if( tagid>0
 
 
 
 
 
1112 && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1113 ){
1114 zCirca = zBefore = zAfter = 0;
1115 nEntry = -1;
1116 }
1117 if( zType[0]=='a' ){
1118 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH;
1119 }else{
1120 tmFlags |= TIMELINE_GRAPH;
1121 }
1122 if( nEntry>0 ) url_add_parameter(&url, "n", mprintf("%d", nEntry));
1123 if( P("ng")!=0 || zSearch!=0 ){
1124 tmFlags &= ~TIMELINE_GRAPH;
1125 url_add_parameter(&url, "ng", 0);
1126 }
1127 if( P("brbg")!=0 ){
1128 tmFlags |= TIMELINE_BRCOLOR;
1129 url_add_parameter(&url, "brbg", 0);
1130 }
1131 if( P("unhide")!=0 ){
1132 tmFlags |= TIMELINE_UNHIDE;
1133 url_add_parameter(&url, "unhide", 0);
1134 }
1135 if( P("ubg")!=0 ){
1136 tmFlags |= TIMELINE_UCOLOR;
1137 url_add_parameter(&url, "ubg", 0);
1138 }
1139 if( zUses!=0 ){
1140 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
1141 if( ufid ){
1142 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1143 url_add_parameter(&url, "uf", zUses);
1144 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
1145 compute_uses_file("usesfile", ufid, 0);
1146 zType = "ci";
 
1147 }else{
1148 zUses = 0;
1149 }
1150 }
1151 if( renameOnly ){
@@ -1152,31 +1234,30 @@
1152 db_multi_exec(
1153 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1154 "INSERT OR IGNORE INTO rnfile"
1155 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1156 );
 
1157 }
1158
1159 style_header("Timeline");
1160 login_anonymous_available();
1161 timeline_temp_table();
1162 blob_zero(&sql);
1163 blob_zero(&desc);
1164 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1165 blob_append(&sql, timeline_query_for_www(), -1);
1166 if( P("fc")!=0 || P("v")!=0 || P("detail")!=0 ){
1167 tmFlags |= TIMELINE_FCHANGES;
1168 url_add_parameter(&url, "v", 0);
1169 }
1170 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1171 blob_append_sql(&sql,
1172 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1173 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1174 TAG_HIDDEN
1175 );
1176 }
1177 if( !useDividers ) url_add_parameter(&url, "nd", 0);
1178 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1179 /* If from= and to= are present, display all nodes on a path connecting
1180 ** the two */
1181 PathNode *p = 0;
1182 const char *zFrom = 0;
@@ -1226,11 +1307,11 @@
1226 if( d_rid ){
1227 compute_descendants(d_rid, nEntry+1);
1228 nd = db_int(0, "SELECT count(*)-1 FROM ok");
1229 if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
1230 if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
1231 if( useDividers ) timeline_add_dividers(0, d_rid);
1232 db_multi_exec("DELETE FROM ok");
1233 }
1234 if( p_rid ){
1235 compute_ancestors(p_rid, nEntry+1, 0);
1236 np = db_int(0, "SELECT count(*)-1 FROM ok");
@@ -1237,38 +1318,24 @@
1237 if( np>0 ){
1238 if( nd>0 ) blob_appendf(&desc, " and ");
1239 blob_appendf(&desc, "%d ancestors", np);
1240 db_multi_exec("%s", blob_sql_text(&sql));
1241 }
1242 if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
1243 }
1244 blob_appendf(&desc, " of %z[%S]</a>",
1245 href("%R/info/%s", zUuid), zUuid);
1246 if( p_rid ){
1247 url_add_parameter(&url, "p", zUuid);
1248 }
1249 if( d_rid ){
1250 if( p_rid ){
1251 /* If both p= and d= are set, we don't have the uuid of d yet. */
1252 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
1253 }
1254 url_add_parameter(&url, "d", zUuid);
1255 }
1256 if( nEntry>20 ){
1257 timeline_submenu(&url, "20 Entries", "n", "20", 0);
1258 }
1259 if( nEntry<200 && nEntry>0 ){
1260 timeline_submenu(&url, "200 Entries", "n", "200", 0);
1261 }
1262 if( tmFlags & TIMELINE_FCHANGES ){
1263 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1264 }else{
1265 timeline_submenu(&url, "Show Files", "v", "", 0);
1266 }
1267 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1268 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1269 }
1270 }else if( f_rid && g.perm.Read ){
1271 /* If f= is present, ignore all other parameters other than n= */
1272 char *zUuid;
1273 db_multi_exec(
1274 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
@@ -1277,21 +1344,17 @@
1277 "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
1278 f_rid, f_rid, f_rid
1279 );
1280 blob_append_sql(&sql, " AND event.objid IN ok");
1281 db_multi_exec("%s", blob_sql_text(&sql));
1282 if( useDividers ) timeline_add_dividers(0, f_rid);
1283 blob_appendf(&desc, "Parents and children of check-in ");
1284 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1285 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid);
1286 tmFlags |= TIMELINE_DISJOINT;
1287 url_add_parameter(&url, "f", zUuid);
1288 if( tmFlags & TIMELINE_FCHANGES ){
1289 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1290 }else{
1291 timeline_submenu(&url, "Show Files", "v", "", 0);
1292 }
1293 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1294 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1295 }
1296 }else{
1297 /* Otherwise, a timeline based on a span of time */
@@ -1316,11 +1379,10 @@
1316 blob_append_sql(&sql,
1317 "AND (EXISTS(SELECT 1 FROM tagxref"
1318 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1319
1320 if( zBrName ){
1321 url_add_parameter(&url, "r", zBrName);
1322 /* The next two blob_appendf() calls add SQL that causes checkins that
1323 ** are not part of the branch which are parents or children of the
1324 ** branch to be included in the report. This related check-ins are
1325 ** useful in helping to visualize what has happened on a quiescent
1326 ** branch that is infrequently merged with a much more activate branch.
@@ -1342,21 +1404,17 @@
1342 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1343 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1344 tagid
1345 );
1346 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1347 blob_append_sql(&sql,
1348 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1349 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1350 TAG_HIDDEN
1351 );
1352 }
1353 }else{
1354 url_add_parameter(&url, "mionly", "1");
1355 }
1356 }else{
1357 url_add_parameter(&url, "t", zTagName);
1358 }
1359 blob_append_sql(&sql, ")");
1360 }
1361 if( (zType[0]=='w' && !g.perm.RdWiki)
1362 || (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,19 +1442,18 @@
1384 }
1385 blob_append_sql(&sql, ")");
1386 }
1387 }else{ /* zType!="all" */
1388 blob_append_sql(&sql, " AND event.type=%Q", zType);
1389 url_add_parameter(&url, "y", zType);
1390 if( zType[0]=='c' ){
1391 zEType = "checkin";
1392 }else if( zType[0]=='w' ){
1393 zEType = "wiki edit";
1394 }else if( zType[0]=='t' ){
1395 zEType = "ticket change";
1396 }else if( zType[0]=='e' ){
1397 zEType = "event";
1398 }else if( zType[0]=='g' ){
1399 zEType = "tag";
1400 }
1401 }
1402 if( zUser ){
@@ -1406,41 +1463,39 @@
1406 zCirca = zBefore = zAfter = 0;
1407 nEntry = -1;
1408 }
1409 blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
1410 zUser, zUser);
1411 url_add_parameter(&url, "u", zUser);
1412 zThisUser = zUser;
1413 }
1414 if( zSearch ){
1415 blob_append_sql(&sql,
1416 " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
1417 zSearch, zSearch);
1418 url_add_parameter(&url, "s", zSearch);
1419 }
1420 rBefore = symbolic_name_to_mtime(zBefore);
1421 rAfter = symbolic_name_to_mtime(zAfter);
1422 rCirca = symbolic_name_to_mtime(zCirca);
1423 if( rAfter>0.0 ){
1424 if( rBefore>0.0 ){
1425 blob_append_sql(&sql,
1426 " AND event.mtime>=%.17g AND event.mtime<=%.17g"
1427 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
1428 url_add_parameter(&url, "a", zAfter);
1429 url_add_parameter(&url, "b", zBefore);
1430 nEntry = -1;
1431 }else{
1432 blob_append_sql(&sql,
1433 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
1434 rAfter-ONE_SECOND);
1435 url_add_parameter(&url, "a", zAfter);
1436 }
 
 
1437 }else if( rBefore>0.0 ){
1438 blob_append_sql(&sql,
1439 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
1440 rBefore+ONE_SECOND);
1441 url_add_parameter(&url, "b", zBefore);
 
1442 }else if( rCirca>0.0 ){
1443 Blob sql2;
1444 blob_init(&sql2, blob_sql_text(&sql), -1);
1445 blob_append_sql(&sql2,
1446 " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d",
@@ -1451,12 +1506,11 @@
1451 blob_append_sql(&sql,
1452 " AND event.mtime>=%f ORDER BY event.mtime ASC",
1453 rCirca
1454 );
1455 nEntry -= (nEntry+1)/2;
1456 if( useDividers ) timeline_add_dividers(rCirca, 0);
1457 url_add_parameter(&url, "c", zCirca);
1458 }else{
1459 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
1460 }
1461 if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
1462 db_multi_exec("%s", blob_sql_text(&sql));
@@ -1464,19 +1518,19 @@
1464 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1465 if( zYearMonth ){
1466 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1467 }else if( zYearWeek ){
1468 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1469 }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){
1470 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1471 }else{
1472 blob_appendf(&desc, "%d %ss", n, zEType);
1473 }
1474 if( zUses ){
1475 char *zFilenames = names_of_file(zUses);
1476 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
1477 href("%R/artifact/%s",zUses), zUses);
1478 tmFlags |= TIMELINE_DISJOINT;
1479 }
1480 if( renameOnly ){
1481 blob_appendf(&desc, " that contain filename changes");
1482 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
@@ -1509,64 +1563,49 @@
1509 }
1510 if( g.perm.Hyperlink ){
1511 if( zAfter || n==nEntry ){
1512 zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
1513 timeline_submenu(&url, "Older", "b", zDate, "a");
 
1514 free(zDate);
1515 }
1516 if( zBefore || (zAfter && n==nEntry) ){
1517 zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
1518 timeline_submenu(&url, "Newer", "a", zDate, "b");
1519 free(zDate);
1520 }else if( tagid==0 && zUses==0 ){
1521 if( zType[0]!='a' ){
1522 timeline_submenu(&url, "All Types", "y", "all", 0);
1523 }
1524 if( zType[0]!='w' && g.perm.RdWiki ){
1525 timeline_submenu(&url, "Wiki Only", "y", "w", 0);
1526 }
1527 if( zType[0]!='c' && g.perm.Read ){
1528 timeline_submenu(&url, "Checkins Only", "y", "ci", 0);
1529 }
1530 if( zType[0]!='t' && g.perm.RdTkt ){
1531 timeline_submenu(&url, "Tickets Only", "y", "t", 0);
1532 }
1533 if( zType[0]!='e' && g.perm.RdWiki ){
1534 timeline_submenu(&url, "Events Only", "y", "e", 0);
1535 }
1536 if( zType[0]!='g' && g.perm.Read ){
1537 timeline_submenu(&url, "Tags Only", "y", "g", 0);
1538 }
1539 }
1540 if( nEntry>20 ){
1541 timeline_submenu(&url, "20 Entries", "n", "20", 0);
1542 }
1543 if( nEntry<200 && nEntry>0 ){
1544 timeline_submenu(&url, "200 Entries", "n", "200", 0);
1545 }
1546 if( zType[0]=='a' || zType[0]=='c' ){
1547 if( tmFlags & TIMELINE_FCHANGES ){
1548 timeline_submenu(&url, "Hide Files", "v", 0, 0);
1549 }else{
1550 timeline_submenu(&url, "Show Files", "v", "", 0);
1551 }
1552 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1553 timeline_submenu(&url, "Unhide", "unhide", "", 0);
1554 }
1555 }
 
 
 
 
1556 }
1557 }
1558 if( P("showsql") ){
1559 @ <blockquote>%h(blob_sql_text(&sql))</blockquote>
1560 }
1561 if( P("showid") ) tmFlags |= TIMELINE_SHOWRID;
 
 
 
 
 
 
 
1562 blob_zero(&sql);
1563 db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
1564 @ <h2>%b(&desc)</h2>
1565 blob_reset(&desc);
1566 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0);
1567 db_finalize(&q);
 
 
 
1568 style_footer();
1569 }
1570
1571 /*
1572 ** The input query q selects various records. Print a human-readable
@@ -1771,16 +1810,16 @@
1771 ** for the current version or "now" for the current time.
1772 **
1773 ** Options:
1774 ** -n|--limit N Output the first N entries (default 20 lines).
1775 ** N=0 means no limit.
1776 ** -p|--path PATH Output items affecting PATH only.
1777 ** PATH can be a file or a sub directory.
1778 ** --offset P skip P changes
1779 ** -t|--type TYPE Output items from the given types only, such as:
1780 ** ci = file commits only
1781 ** e = events only
1782 ** t = tickets only
1783 ** w = wiki commits only
1784 ** -v|--verbose Output the list of files changed by each commit
1785 ** and the type of each change (edited, deleted,
1786 ** etc.) after the checkin comment.
@@ -1893,11 +1932,11 @@
1893 if( mode==0 ){
1894 if( isIsoDate(zOrigin) ) zShift = ",'+1 day'";
1895 }
1896 zDate = mprintf("(SELECT julianday(%Q%s, 'utc'))", zOrigin, zShift);
1897 }
1898
1899 if( zFilePattern ){
1900 if( zType==0 ){
1901 /* When zFilePattern is specified and type is not specified, only show
1902 * file checkins */
1903 zType="ci";
@@ -2029,11 +2068,14 @@
2029 */
2030 void test_timewarp_page(void){
2031 Stmt q;
2032
2033 login_check_credentials();
2034 if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
 
 
 
2035 style_header("Instances of timewarp");
2036 @ <ul>
2037 db_prepare(&q,
2038 "SELECT blob.uuid "
2039 " FROM plink p, plink c, blob"
@@ -2041,10 +2083,10 @@
2041 " AND blob.rid=c.cid"
2042 );
2043 while( db_step(&q)==SQLITE_ROW ){
2044 const char *zUuid = db_column_text(&q, 0);
2045 @ <li>
2046 @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&amp;d=%s(zUuid)&amp;unhide">%S(zUuid)</a>
2047 }
2048 db_finalize(&q);
2049 style_footer();
2050 }
2051
--- 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 */
@@ -494,10 +508,11 @@
508 " (SELECT name FROM filename WHERE fnid=mlink.pfnid) AS oldnm"
509 " FROM mlink"
510 " WHERE mid=:mid AND (pid!=fid OR pfnid>0)"
511 " AND (fid>0 OR"
512 " fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=:mid))"
513 " AND NOT mlink.isaux"
514 " ORDER BY 3 /*sort*/"
515 );
516 fchngQueryInit = 1;
517 }
518 db_bind_int(&fchngQuery, ":mid", rid);
@@ -507,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;
@@ -666,16 +689,16 @@
689 if( cSep=='[' ) cgi_printf("[");
690 cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
691 }
692 cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
693 graph_free(pGraph);
694 @ var cDiv = gebi("canvas");
695 @ var csty = window.getComputedStyle && window.getComputedStyle(cDiv,null);
696 @ var lineClr = (csty && csty.getPropertyValue('color')) || 'black';
697 @ var bgClr = (csty && csty.getPropertyValue('background-color')) ||'white';
698 @ if( bgClr=='transparent' ) bgClr = 'white';
699 @ var boxColor = lineClr;
700 @ function drawBox(color,x0,y0,x1,y1){
701 @ var n = document.createElement("div");
702 @ if( x0>x1 ){ var t=x0; x0=x1; x1=t; }
703 @ if( y0>y1 ){ var t=y0; y0=y1; y1=t; }
704 @ var w = x1-x0+1;
@@ -685,11 +708,11 @@
708 @ n.style.left = x0+"px";
709 @ n.style.top = y0+"px";
710 @ n.style.width = w+"px";
711 @ n.style.height = h+"px";
712 @ n.style.backgroundColor = color;
713 @ cDiv.appendChild(n);
714 @ return n;
715 @ }
716 @ function absoluteY(id){
717 @ var obj = gebi(id);
718 @ if( !obj ) return;
@@ -711,39 +734,39 @@
734 @ }while( obj = obj.offsetParent );
735 @ }
736 @ return left;
737 @ }
738 @ function drawUpArrow(x,y0,y1){
739 @ drawBox(lineClr,x,y0,x+1,y1);
740 @ if( y0+10>=y1 ){
741 @ drawBox(lineClr,x-1,y0+1,x+2,y0+2);
742 @ drawBox(lineClr,x-2,y0+3,x+3,y0+4);
743 @ }else{
744 @ drawBox(lineClr,x-1,y0+2,x+2,y0+4);
745 @ drawBox(lineClr,x-2,y0+5,x+3,y0+7);
746 @ }
747 @ }
748 @ function drawThinArrow(y,xFrom,xTo){
749 @ if( xFrom<xTo ){
750 @ drawBox(lineClr,xFrom,y,xTo,y);
751 @ drawBox(lineClr,xTo-3,y-1,xTo-2,y+1);
752 @ drawBox(lineClr,xTo-4,y-2,xTo-4,y+2);
753 @ }else{
754 @ drawBox(lineClr,xTo,y,xFrom,y);
755 @ drawBox(lineClr,xTo+2,y-1,xTo+3,y+1);
756 @ drawBox(lineClr,xTo+4,y-2,xTo+4,y+2);
757 @ }
758 @ }
759 @ function drawThinLine(x0,y0,x1,y1){
760 @ drawBox(lineClr,x0,y0,x1,y1);
761 @ }
762 @ function drawNodeBox(color,x0,y0,x1,y1){
763 @ drawBox(color,x0,y0,x1,y1).style.cursor = "pointer";
764 @ }
765 @ function drawNode(p, left, btm){
766 @ drawNodeBox(boxColor,p.x-5,p.y-5,p.x+6,p.y+6);
767 @ drawNodeBox(p.bg||bgClr,p.x-4,p.y-4,p.x+5,p.y+5);
768 @ if( p.u>0 ) drawUpArrow(p.x, rowinfo[p.u-1].y+6, p.y-5);
769 @ if( p.f&1 ) drawNodeBox(boxColor,p.x-1,p.y-1,p.x+2,p.y+2);
770 if( !omitDescenders ){
771 @ if( p.u==0 ) drawUpArrow(p.x, 0, p.y-5);
772 @ if( p.d ) drawUpArrow(p.x, p.y+6, btm);
@@ -765,11 +788,11 @@
788 @ for(var i=0; i<n; i+=2){
789 @ var x1 = p.au[i]*railPitch + left;
790 @ var x0 = x1>p.x ? p.x+7 : p.x-6;
791 @ var u = rowinfo[p.au[i+1]-1];
792 @ if(u.id<p.id){
793 @ drawBox(lineClr,x0,p.y,x1,p.y+1);
794 @ drawUpArrow(x1, u.y+6, p.y);
795 @ }else{
796 @ drawBox("#600000",x0,p.y,x1,p.y+1);
797 @ drawBox("#600000",x1-1,p.y,x1,u.y+1);
798 @ drawBox("#600000",x1,u.y,u.x-6,u.y+1);
@@ -952,43 +975,39 @@
975 if( z==0 ) return -1.0;
976 if( fossil_isdate(z) ){
977 mtime = db_double(0.0, "SELECT julianday(%Q,'utc')", z);
978 if( mtime>0.0 ) return mtime;
979 }
980 rid = symbolic_name_to_rid(z, "*");
981 if( rid ){
982 mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
983 }else{
984 mtime = db_double(-1.0,
985 "SELECT max(event.mtime) FROM event, tag, tagxref"
986 " WHERE tag.tagname GLOB 'event-%q*'"
987 " AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
988 " AND event.objid=tagxref.rid",
989 z
990 );
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 */
@@ -1011,19 +1030,57 @@
1030 }
1031 db_finalize(&q);
1032 return blob_str(&out);
1033 }
1034
1035
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";
1052 az[i++] = "Tags";
1053 }
1054 if( g.perm.RdWiki ){
1055 az[i++] = "e";
1056 az[i++] = "Tech Notes";
1057 }
1058 if( g.perm.RdTkt ){
1059 az[i++] = "t";
1060 az[i++] = "Tickets";
1061 }
1062 if( g.perm.RdWiki ){
1063 az[i++] = "w";
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
1075 **
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
@@ -1046,34 +1103,35 @@
1103 ** datefmt=N Override the date format
1104 **
1105 ** p= and d= can appear individually or together. If either p= or d=
1106 ** appear, then u=, y=, a=, and b= are ignored.
1107 **
1108 ** If both a= and b= appear then both upper and lower bounds are honored.
 
1109 **
1110 ** If n= is missing, the default count is 50 for most queries but
1111 ** drops to 11 for c= queries.
1112 */
1113 void page_timeline(void){
1114 Stmt q; /* Query used to generate the timeline */
1115 Blob sql; /* text of SQL used to generate timeline */
1116 Blob desc; /* Description of the timeline */
1117 int nEntry; /* Max number of entries on timeline */
1118 int p_rid = name_to_typed_rid(P("p"),"ci"); /* artifact p and its parents */
1119 int d_rid = name_to_typed_rid(P("d"),"ci"); /* artifact d and descendants */
1120 int f_rid = name_to_typed_rid(P("f"),"ci"); /* artifact f and close family */
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 */
1132 const char *zYearWeek = P("yw"); /* Checkins for YYYY-WW (week-of-year) */
1133 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1134 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1135 int tagid; /* Tag ID */
1136 int tmFlags = 0; /* Timeline flags */
1137 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1084,68 +1142,92 @@
1142 int noMerge = P("shortest")==0; /* Follow merge links if shorter */
1143 int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
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 ){
1156 nEntry = 0;
1157 }else{
1158 nEntry = atoi(z);
1159 if( nEntry<=0 ){
1160 cgi_replace_query_parameter("n","10");
1161 nEntry = 10;
1162 }
1163 }
1164 }else if( zCirca ){
1165 cgi_replace_query_parameter("n","11");
1166 nEntry = 11;
1167 }else{
1168 cgi_replace_query_parameter("n","50");
1169 nEntry = 50;
1170 }
1171
1172 /* To view the timeline, must have permission to read project data.
1173 */
1174 pd_rid = name_to_typed_rid(P("dp"),"ci");
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 ){
1186 tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTagName);
1187 zThisTag = zTagName;
1188 }else if( zBrName && g.perm.Read ){
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{
1207 tmFlags |= TIMELINE_GRAPH;
1208 }
1209 if( PB("ng") || zSearch!=0 ){
 
1210 tmFlags &= ~TIMELINE_GRAPH;
 
1211 }
1212 if( PB("brbg") ){
1213 tmFlags |= TIMELINE_BRCOLOR;
 
1214 }
1215 if( PB("unhide") ){
1216 tmFlags |= TIMELINE_UNHIDE;
 
1217 }
1218 if( PB("ubg") ){
1219 tmFlags |= TIMELINE_UCOLOR;
 
1220 }
1221 if( zUses!=0 ){
1222 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
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 ){
@@ -1152,31 +1234,30 @@
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();
1245 blob_zero(&sql);
1246 blob_zero(&desc);
1247 blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
1248 blob_append(&sql, timeline_query_for_www(), -1);
1249 if( PB("fc") || PB("v") || PB("detail") ){
1250 tmFlags |= TIMELINE_FCHANGES;
 
1251 }
1252 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1253 blob_append_sql(&sql,
1254 " AND NOT EXISTS(SELECT 1 FROM tagxref"
1255 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)",
1256 TAG_HIDDEN
1257 );
1258 }
 
1259 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
1260 /* If from= and to= are present, display all nodes on a path connecting
1261 ** the two */
1262 PathNode *p = 0;
1263 const char *zFrom = 0;
@@ -1226,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");
@@ -1237,38 +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);"
@@ -1277,21 +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 */
@@ -1316,11 +1379,10 @@
1379 blob_append_sql(&sql,
1380 "AND (EXISTS(SELECT 1 FROM tagxref"
1381 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1382
1383 if( zBrName ){
 
1384 /* The next two blob_appendf() calls add SQL that causes checkins that
1385 ** are not part of the branch which are parents or children of the
1386 ** branch to be included in the report. This related check-ins are
1387 ** useful in helping to visualize what has happened on a quiescent
1388 ** branch that is infrequently merged with a much more activate branch.
@@ -1342,21 +1404,17 @@
1404 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1405 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1406 tagid
1407 );
1408 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1409 blob_append_sql(&sql,
1410 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1411 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)",
1412 TAG_HIDDEN
1413 );
1414 }
 
 
1415 }
 
 
1416 }
1417 blob_append_sql(&sql, ")");
1418 }
1419 if( (zType[0]=='w' && !g.perm.RdWiki)
1420 || (zType[0]=='t' && !g.perm.RdTkt)
@@ -1384,19 +1442,18 @@
1442 }
1443 blob_append_sql(&sql, ")");
1444 }
1445 }else{ /* zType!="all" */
1446 blob_append_sql(&sql, " AND event.type=%Q", zType);
 
1447 if( zType[0]=='c' ){
1448 zEType = "checkin";
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 ){
@@ -1406,41 +1463,39 @@
1463 zCirca = zBefore = zAfter = 0;
1464 nEntry = -1;
1465 }
1466 blob_append_sql(&sql, " AND (event.user=%Q OR event.euser=%Q)",
1467 zUser, zUser);
 
1468 zThisUser = zUser;
1469 }
1470 if( zSearch ){
1471 blob_append_sql(&sql,
1472 " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
1473 zSearch, zSearch);
 
1474 }
1475 rBefore = symbolic_name_to_mtime(zBefore);
1476 rAfter = symbolic_name_to_mtime(zAfter);
1477 rCirca = symbolic_name_to_mtime(zCirca);
1478 if( rAfter>0.0 ){
1479 if( rBefore>0.0 ){
1480 blob_append_sql(&sql,
1481 " AND event.mtime>=%.17g AND event.mtime<=%.17g"
1482 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
 
 
1483 nEntry = -1;
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",
@@ -1451,12 +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));
@@ -1464,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;
@@ -1509,64 +1563,49 @@
1563 }
1564 if( g.perm.Hyperlink ){
1565 if( zAfter || n==nEntry ){
1566 zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
1567 timeline_submenu(&url, "Older", "b", zDate, "a");
1568 zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0));
1569 free(zDate);
1570 }
1571 if( zBefore || (zAfter && n==nEntry) ){
1572 zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
1573 timeline_submenu(&url, "Newer", "a", zDate, "b");
1574 free(zDate);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1575 }
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();
1608 }
1609
1610 /*
1611 ** The input query q selects various records. Print a human-readable
@@ -1771,16 +1810,16 @@
1810 ** for the current version or "now" for the current time.
1811 **
1812 ** Options:
1813 ** -n|--limit N Output the first N entries (default 20 lines).
1814 ** N=0 means no limit.
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.
@@ -1893,11 +1932,11 @@
1932 if( mode==0 ){
1933 if( isIsoDate(zOrigin) ) zShift = ",'+1 day'";
1934 }
1935 zDate = mprintf("(SELECT julianday(%Q%s, 'utc'))", zOrigin, zShift);
1936 }
1937
1938 if( zFilePattern ){
1939 if( zType==0 ){
1940 /* When zFilePattern is specified and type is not specified, only show
1941 * file checkins */
1942 zType="ci";
@@ -2029,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"
@@ -2041,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
+25 -16
--- 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,20 +1412,20 @@
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
** WEBPAGE: ticket
14151424
**
14161425
** This is intended to be the primary "Ticket" page. Render as
1417
-** either ticket-search (if search is enabled) or as the
1426
+** either ticket-search (if search is enabled) or as the
14181427
** /reportlist page (if ticket search is disabled).
14191428
*/
14201429
void tkt_home_page(void){
14211430
login_check_credentials();
14221431
if( search_restrict(SRCH_TKT)!=0 ){
@@ -1434,8 +1443,8 @@
14341443
*/
14351444
void tkt_srchpage(void){
14361445
login_check_credentials();
14371446
style_header("Ticket Search");
14381447
ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1439
- search_screen(SRCH_TKT, "tktsrch");
1448
+ search_screen(SRCH_TKT, 0);
14401449
style_footer();
14411450
}
14421451
--- 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,20 +1412,20 @@
1403 style_submenu_element("Search","Search","%R/tktsrch");
1404 }
1405 if( (ok & T_REPLIST)!=0 ){
1406 style_submenu_element("Reports","Reports","%R/reportlist");
1407 }
1408 if( (ok & T_NEW)!=0 && g.perm.NewTkt ){
1409 style_submenu_element("New","New","%R/tktnew");
1410 }
1411 }
1412
1413 /*
1414 ** WEBPAGE: ticket
1415 **
1416 ** This is intended to be the primary "Ticket" page. Render as
1417 ** either ticket-search (if search is enabled) or as the
1418 ** /reportlist page (if ticket search is disabled).
1419 */
1420 void tkt_home_page(void){
1421 login_check_credentials();
1422 if( search_restrict(SRCH_TKT)!=0 ){
@@ -1434,8 +1443,8 @@
1434 */
1435 void tkt_srchpage(void){
1436 login_check_credentials();
1437 style_header("Ticket Search");
1438 ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1439 search_screen(SRCH_TKT, "tktsrch");
1440 style_footer();
1441 }
1442
--- 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,20 +1412,20 @@
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 ** WEBPAGE: ticket
1424 **
1425 ** This is intended to be the primary "Ticket" page. Render as
1426 ** either ticket-search (if search is enabled) or as the
1427 ** /reportlist page (if ticket search is disabled).
1428 */
1429 void tkt_home_page(void){
1430 login_check_credentials();
1431 if( search_restrict(SRCH_TKT)!=0 ){
@@ -1434,8 +1443,8 @@
1443 */
1444 void tkt_srchpage(void){
1445 login_check_credentials();
1446 style_header("Ticket Search");
1447 ticket_standard_submenu(T_ALL_BUT(T_SRCH));
1448 search_screen(SRCH_TKT, 0);
1449 style_footer();
1450 }
1451
+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
+33 -9
--- src/url.c
+++ src/url.c
@@ -432,47 +432,71 @@
432432
** An instance of this object is used to build a URL with query parameters.
433433
*/
434434
struct HQuery {
435435
Blob url; /* The URL */
436436
const char *zBase; /* The base URL */
437
- int nParam; /* Number of parameters. Max 10 */
438
- const char *azName[15]; /* Parameter names */
439
- const char *azValue[15]; /* Parameter values */
437
+ int nParam; /* Number of parameters. */
438
+ int nAlloc; /* Number of allocated slots */
439
+ const char **azName; /* Parameter names */
440
+ const char **azValue; /* Parameter values */
440441
};
441442
#endif
442443
443444
/*
444445
** Initialize the URL object.
445446
*/
446447
void url_initialize(HQuery *p, const char *zBase){
448
+ memset(p, 0, sizeof(*p));
447449
blob_zero(&p->url);
448450
p->zBase = zBase;
449
- p->nParam = 0;
450451
}
451452
452453
/*
453454
** Resets the given URL object, deallocating any memory
454455
** it uses.
455456
*/
456457
void url_reset(HQuery *p){
457458
blob_reset(&p->url);
459
+ fossil_free(p->azName);
460
+ fossil_free(p->azValue);
458461
url_initialize(p, p->zBase);
459462
}
460463
461464
/*
462
-** Add a fixed parameter to an HQuery.
465
+** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0.
463466
*/
464467
void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
465
- assert( p->nParam < count(p->azName) );
466
- assert( p->nParam < count(p->azValue) );
467
- p->azName[p->nParam] = zName;
468
- p->azValue[p->nParam] = 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
+ p->azName[i] = zName;
489
+ p->azValue[i] = zValue;
469490
p->nParam++;
470491
}
471492
472493
/*
473494
** Render the URL with a parameter override.
495
+**
496
+** Returned memory is transient and is overwritten on the next call to this
497
+** routine for the same HQuery, or until the HQuery object is destroyed.
474498
*/
475499
char *url_render(
476500
HQuery *p, /* Base URL */
477501
const char *zName1, /* First override */
478502
const char *zValue1, /* First override value */
479503
--- src/url.c
+++ src/url.c
@@ -432,47 +432,71 @@
432 ** An instance of this object is used to build a URL with query parameters.
433 */
434 struct HQuery {
435 Blob url; /* The URL */
436 const char *zBase; /* The base URL */
437 int nParam; /* Number of parameters. Max 10 */
438 const char *azName[15]; /* Parameter names */
439 const char *azValue[15]; /* Parameter values */
 
440 };
441 #endif
442
443 /*
444 ** Initialize the URL object.
445 */
446 void url_initialize(HQuery *p, const char *zBase){
 
447 blob_zero(&p->url);
448 p->zBase = zBase;
449 p->nParam = 0;
450 }
451
452 /*
453 ** Resets the given URL object, deallocating any memory
454 ** it uses.
455 */
456 void url_reset(HQuery *p){
457 blob_reset(&p->url);
 
 
458 url_initialize(p, p->zBase);
459 }
460
461 /*
462 ** Add a fixed parameter to an HQuery.
463 */
464 void url_add_parameter(HQuery *p, const char *zName, const char *zValue){
465 assert( p->nParam < count(p->azName) );
466 assert( p->nParam < count(p->azValue) );
467 p->azName[p->nParam] = zName;
468 p->azValue[p->nParam] = zValue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469 p->nParam++;
470 }
471
472 /*
473 ** Render the URL with a parameter override.
 
 
 
474 */
475 char *url_render(
476 HQuery *p, /* Base URL */
477 const char *zName1, /* First override */
478 const char *zValue1, /* First override value */
479
--- src/url.c
+++ src/url.c
@@ -432,47 +432,71 @@
432 ** An instance of this object is used to build a URL with query parameters.
433 */
434 struct HQuery {
435 Blob url; /* The URL */
436 const char *zBase; /* The base URL */
437 int nParam; /* Number of parameters. */
438 int nAlloc; /* Number of allocated slots */
439 const char **azName; /* Parameter names */
440 const char **azValue; /* Parameter values */
441 };
442 #endif
443
444 /*
445 ** Initialize the URL object.
446 */
447 void url_initialize(HQuery *p, const char *zBase){
448 memset(p, 0, sizeof(*p));
449 blob_zero(&p->url);
450 p->zBase = zBase;
 
451 }
452
453 /*
454 ** Resets the given URL object, deallocating any memory
455 ** it uses.
456 */
457 void url_reset(HQuery *p){
458 blob_reset(&p->url);
459 fossil_free(p->azName);
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 p->azName[i] = zName;
489 p->azValue[i] = zValue;
490 p->nParam++;
491 }
492
493 /*
494 ** Render the URL with a parameter override.
495 **
496 ** Returned memory is transient and is overwritten on the next call to this
497 ** routine for the same HQuery, or until the HQuery object is destroyed.
498 */
499 char *url_render(
500 HQuery *p, /* Base URL */
501 const char *zName1, /* First override */
502 const char *zValue1, /* First override value */
503
+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
+29 -24
--- 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>
@@ -291,11 +295,11 @@
291295
*/
292296
void wiki_srchpage(void){
293297
login_check_credentials();
294298
style_header("Wiki Search");
295299
wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
296
- search_screen(SRCH_WIKI, "wikisrch");
300
+ search_screen(SRCH_WIKI, 0);
297301
style_footer();
298302
}
299303
300304
/*
301305
** WEBPAGE: wiki
@@ -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>
@@ -291,11 +295,11 @@
291 */
292 void wiki_srchpage(void){
293 login_check_credentials();
294 style_header("Wiki Search");
295 wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
296 search_screen(SRCH_WIKI, "wikisrch");
297 style_footer();
298 }
299
300 /*
301 ** WEBPAGE: wiki
@@ -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>
@@ -291,11 +295,11 @@
295 */
296 void wiki_srchpage(void){
297 login_check_credentials();
298 style_header("Wiki Search");
299 wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
300 search_screen(SRCH_WIKI, 0);
301 style_footer();
302 }
303
304 /*
305 ** WEBPAGE: wiki
@@ -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
+2 -2
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -93,14 +93,14 @@
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>
101
-<li> <a href='../../help'>Command-line help</a>
101
+<li> <a href='../../../help'>Command-line help</a>
102102
</ul>
103103
<a name="pindex"></a>
104104
<h2>Permuted Index:</h2>
105105
<ul>}
106106
foreach entry $permindex {
107107
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -93,14 +93,14 @@
93 <h2>Primary Documents:</h2>
94 <ul>
95 <li> <a href='quickstart.wiki'>Quick-start Guide</a>
96 <li> <a href='faq.wiki'>FAQ</a>
97 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
98 <li> <a href='COPYRIGHT-BSD2.txt'>License</a>
99 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
100 book</a>
101 <li> <a href='../../help'>Command-line help</a>
102 </ul>
103 <a name="pindex"></a>
104 <h2>Permuted Index:</h2>
105 <ul>}
106 foreach entry $permindex {
107
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -93,14 +93,14 @@
93 <h2>Primary Documents:</h2>
94 <ul>
95 <li> <a href='quickstart.wiki'>Quick-start Guide</a>
96 <li> <a href='faq.wiki'>FAQ</a>
97 <li> <a href='build.wiki'>Compiling and installing Fossil</a>
98 <li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
99 <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
100 book</a>
101 <li> <a href='../../../help'>Command-line help</a>
102 </ul>
103 <a name="pindex"></a>
104 <h2>Permuted Index:</h2>
105 <ul>}
106 foreach entry $permindex {
107
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,14 +9,14 @@
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>
17
-<li> <a href='../../help'>Command-line help</a>
17
+<li> <a href='../../../help'>Command-line help</a>
1818
</ul>
1919
<a name="pindex"></a>
2020
<h2>Permuted Index:</h2>
2121
<ul>
2222
<li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Update and Running in</a></li>
2323
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,14 +9,14 @@
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 <h2>Permuted Index:</h2>
21 <ul>
22 <li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Update and Running in</a></li>
23
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -9,14 +9,14 @@
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 <h2>Permuted Index:</h2>
21 <ul>
22 <li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Update and Running in</a></li>
23
--- 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