Fossil SCM
Stay in sync with svn-import...
Commit
04308c2164c758c3943d30d31c3a1c61e1026be2
Parent
3c24fede5ae7f6b…
79 files changed
+4
-4
+4
-4
+4
+1
+4
-4
+4
-4
+13
-7
+19
-51
+4
-4
+4
-4
-1
+4
-4
+2
-1
+51
-14
+34
-22
+6
-6
+55
-51
+2
-2
+3
-3
+8
-8
+2
-2
+12
-12
+3
-3
+22
-7
+142
-91
+11
-27
+22
-10
+1
+1
-1
+1
-1
+76
-53
+154
-108
+82
-24
+1
-1
+13
-13
+16
-10
+5
-2
+85
-10
+2
-2
+39
-6
+17
-7
+7
-4
+292
-80
+59
-26
+120
-5
+10
-7
+24
-12
+100
-9
+5
-4
+10
-10
+95
-62
+3
-3
+19
-9
+120
-17
+107
-74
+23
-14
+11
-8
+10
-10
+1
-1
+9
-2
+1
-1
+2
-2
+28
-23
+65
-10
+4
-2
+12
-1
+14
+3
+5
+10
-10
+21
-21
+44
-15
+22
-8
+30
-30
+30
-28
+3
-3
+1
-1
+1
-1
+6
-2
~
skins/black_and_white/header.txt
~
skins/default/header.txt
~
skins/eagle/README.md
~
skins/eagle/css.txt
~
skins/eagle/header.txt
~
skins/enhanced1/header.txt
~
skins/etienne1/css.txt
~
skins/etienne1/header.txt
~
skins/khaki/header.txt
~
skins/plain_gray/header.txt
~
skins/rounded1/css.txt
~
skins/rounded1/header.txt
~
src/add.c
~
src/allrepo.c
~
src/attach.c
~
src/branch.c
~
src/browse.c
~
src/cache.c
~
src/checkin.c
~
src/db.c
~
src/descendants.c
~
src/diff.c
~
src/diffcmd.c
~
src/doc.c
~
src/event.c
~
src/file.c
~
src/finfo.c
~
src/http_socket.c
~
src/import.c
~
src/import.c
~
src/info.c
~
src/login.c
~
src/main.c
~
src/main.mk
~
src/manifest.c
~
src/markdown.c
~
src/moderate.c
~
src/name.c
~
src/path.c
~
src/printf.c
~
src/rebuild.c
~
src/report.c
~
src/search.c
~
src/setup.c
~
src/shell.c
~
src/shun.c
~
src/sitemap.c
~
src/skins.c
~
src/stat.c
~
src/statrep.c
~
src/style.c
~
src/tag.c
~
src/tar.c
~
src/th_main.c
~
src/timeline.c
~
src/tkt.c
~
src/tktsetup.c
~
src/undo.c
~
src/update.c
~
src/url.c
~
src/user.c
~
src/vfile.c
~
src/wiki.c
~
src/wikiformat.c
~
src/xfersetup.c
~
src/zip.c
~
test/fileage-test-1.wiki
~
test/graph-test-1.wiki
~
test/release-checklist.wiki
~
test/th1-tcl.test
~
test/th1.test
~
www/changes.wiki
~
www/embeddeddoc.wiki
~
www/event.wiki
~
www/fileformat.wiki
~
www/mkdownload.tcl
~
www/mkindex.tcl
~
www/permutedindex.html
~
www/wikitheory.wiki
+4
-4
| --- skins/black_and_white/header.txt | ||
| +++ skins/black_and_white/header.txt | ||
| @@ -26,21 +26,21 @@ | ||
| 26 | 26 | <th1> |
| 27 | 27 | html "<a href='$home$index_page'>Home</a>\n" |
| 28 | 28 | if {[anycap jor]} { |
| 29 | 29 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 30 | 30 | } |
| 31 | -if {[hascap oh]} { | |
| 31 | +if {[anoncap oh]} { | |
| 32 | 32 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 33 | 33 | } |
| 34 | -if {[hascap o]} { | |
| 34 | +if {[anoncap o]} { | |
| 35 | 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | 37 | } |
| 38 | -if {[hascap r]} { | |
| 38 | +if {[anoncap r]} { | |
| 39 | 39 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 40 | 40 | } |
| 41 | -if {[hascap j]} { | |
| 41 | +if {[anoncap j]} { | |
| 42 | 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | 43 | } |
| 44 | 44 | if {[hascap s]} { |
| 45 | 45 | html "<a href='$home/setup'>Admin</a>\n" |
| 46 | 46 | } elseif {[hascap a]} { |
| 47 | 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 {[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 |
+4
-4
| --- skins/default/header.txt | ||
| +++ skins/default/header.txt | ||
| @@ -25,21 +25,21 @@ | ||
| 25 | 25 | <th1> |
| 26 | 26 | html "<a href='$home$index_page'>Home</a>\n" |
| 27 | 27 | if {[anycap jor]} { |
| 28 | 28 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 29 | 29 | } |
| 30 | -if {[hascap oh]} { | |
| 30 | +if {[anoncap oh]} { | |
| 31 | 31 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 32 | 32 | } |
| 33 | -if {[hascap o]} { | |
| 33 | +if {[anoncap o]} { | |
| 34 | 34 | html "<a href='$home/brlist'>Branches</a>\n" |
| 35 | 35 | html "<a href='$home/taglist'>Tags</a>\n" |
| 36 | 36 | } |
| 37 | -if {[hascap r]} { | |
| 37 | +if {[anoncap r]} { | |
| 38 | 38 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 39 | 39 | } |
| 40 | -if {[hascap j]} { | |
| 40 | +if {[anoncap j]} { | |
| 41 | 41 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 42 | 42 | } |
| 43 | 43 | if {[hascap s]} { |
| 44 | 44 | html "<a href='$home/setup'>Admin</a>\n" |
| 45 | 45 | } elseif {[hascap a]} { |
| 46 | 46 | |
| 47 | 47 | 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. |
+1
| --- skins/eagle/css.txt | ||
| +++ skins/eagle/css.txt | ||
| @@ -159,10 +159,11 @@ | ||
| 159 | 159 | table.timelineTable { |
| 160 | 160 | cellspacing: 0; |
| 161 | 161 | border: 0; |
| 162 | 162 | cellpadding: 0; |
| 163 | 163 | font-family: "courier new"; |
| 164 | + border-collapse: collapse; | |
| 164 | 165 | } |
| 165 | 166 | |
| 166 | 167 | /* Side-by-side diff */ |
| 167 | 168 | table.sbsdiff { |
| 168 | 169 | background-color: #485D7B; |
| 169 | 170 |
| --- 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 |
+4
-4
| --- skins/eagle/header.txt | ||
| +++ skins/eagle/header.txt | ||
| @@ -106,21 +106,21 @@ | ||
| 106 | 106 | html "<a href='$home$index_page'>Home</a>\n" |
| 107 | 107 | html "<a href='$home/help'>Help</a>\n" |
| 108 | 108 | if {[anycap jor]} { |
| 109 | 109 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 110 | 110 | } |
| 111 | -if {[hascap oh]} { | |
| 111 | +if {[anoncap oh]} { | |
| 112 | 112 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 113 | 113 | } |
| 114 | -if {[hascap o]} { | |
| 114 | +if {[anoncap o]} { | |
| 115 | 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | 117 | } |
| 118 | -if {[hascap r]} { | |
| 118 | +if {[anoncap r]} { | |
| 119 | 119 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 120 | 120 | } |
| 121 | -if {[hascap j]} { | |
| 121 | +if {[anoncap j]} { | |
| 122 | 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | 123 | } |
| 124 | 124 | if {[hascap s]} { |
| 125 | 125 | html "<a href='$home/setup'>Admin</a>\n" |
| 126 | 126 | } elseif {[hascap a]} { |
| 127 | 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 {[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 |
+4
-4
| --- skins/enhanced1/header.txt | ||
| +++ skins/enhanced1/header.txt | ||
| @@ -106,21 +106,21 @@ | ||
| 106 | 106 | html "<a href='$home$index_page'>Home</a>\n" |
| 107 | 107 | html "<a href='$home/help'>Help</a>\n" |
| 108 | 108 | if {[anycap jor]} { |
| 109 | 109 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 110 | 110 | } |
| 111 | -if {[hascap oh]} { | |
| 111 | +if {[anoncap oh]} { | |
| 112 | 112 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 113 | 113 | } |
| 114 | -if {[hascap o]} { | |
| 114 | +if {[anoncap o]} { | |
| 115 | 115 | html "<a href='$home/brlist'>Branches</a>\n" |
| 116 | 116 | html "<a href='$home/taglist'>Tags</a>\n" |
| 117 | 117 | } |
| 118 | -if {[hascap r]} { | |
| 118 | +if {[anoncap r]} { | |
| 119 | 119 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 120 | 120 | } |
| 121 | -if {[hascap j]} { | |
| 121 | +if {[anoncap j]} { | |
| 122 | 122 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 123 | 123 | } |
| 124 | 124 | if {[hascap s]} { |
| 125 | 125 | html "<a href='$home/setup'>Admin</a>\n" |
| 126 | 126 | } elseif {[hascap a]} { |
| 127 | 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 {[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 |
+13
-7
| --- skins/etienne1/css.txt | ||
| +++ skins/etienne1/css.txt | ||
| @@ -13,11 +13,11 @@ | ||
| 13 | 13 | color: #4183C4; |
| 14 | 14 | text-decoration: underline; |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | hr { |
| 18 | - border: 0px; | |
| 18 | + color: #eee; | |
| 19 | 19 | } |
| 20 | 20 | |
| 21 | 21 | .title { |
| 22 | 22 | color: #4183C4; |
| 23 | 23 | float:left; |
| @@ -100,30 +100,29 @@ | ||
| 100 | 100 | padding: 10px; |
| 101 | 101 | border-bottom: 1px solid #ccc; |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | .submenu a { |
| 105 | - padding: 10px; | |
| 105 | + padding: 10px 11px; | |
| 106 | 106 | text-decoration:none; |
| 107 | 107 | color: #777; |
| 108 | 108 | } |
| 109 | 109 | |
| 110 | 110 | .submenu a:hover { |
| 111 | + padding: 6px 10px; | |
| 111 | 112 | border: 1px solid #ccc; |
| 112 | - border-bottom: 1px solid #fff; | |
| 113 | - border-top-left-radius: 5px; | |
| 114 | - border-top-right-radius: 5px; | |
| 113 | + border-radius: 5px; | |
| 114 | + color: #000; | |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | .content { |
| 118 | 118 | padding-top: 10px; |
| 119 | 119 | font-size:.8em; |
| 120 | 120 | color: #444; |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | -.udiff, .sbsdiff, | |
| 124 | -.content blockquote { | |
| 123 | +.udiff, .sbsdiff { | |
| 125 | 124 | font-size: .85em !important; |
| 126 | 125 | overflow: auto; |
| 127 | 126 | border: 1px solid #ccc; |
| 128 | 127 | border-radius: 5px; |
| 129 | 128 | } |
| @@ -183,6 +182,13 @@ | ||
| 183 | 182 | border-top: 1px solid #ccc; |
| 184 | 183 | padding: 10px; |
| 185 | 184 | font-size:.7em; |
| 186 | 185 | margin-top: 10px; |
| 187 | 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; | |
| 188 | 194 | } |
| 189 | 195 |
| --- skins/etienne1/css.txt | |
| +++ skins/etienne1/css.txt | |
| @@ -13,11 +13,11 @@ | |
| 13 | color: #4183C4; |
| 14 | text-decoration: underline; |
| 15 | } |
| 16 | |
| 17 | hr { |
| 18 | border: 0px; |
| 19 | } |
| 20 | |
| 21 | .title { |
| 22 | color: #4183C4; |
| 23 | float:left; |
| @@ -100,30 +100,29 @@ | |
| 100 | padding: 10px; |
| 101 | border-bottom: 1px solid #ccc; |
| 102 | } |
| 103 | |
| 104 | .submenu a { |
| 105 | padding: 10px; |
| 106 | text-decoration:none; |
| 107 | color: #777; |
| 108 | } |
| 109 | |
| 110 | .submenu a:hover { |
| 111 | border: 1px solid #ccc; |
| 112 | border-bottom: 1px solid #fff; |
| 113 | border-top-left-radius: 5px; |
| 114 | border-top-right-radius: 5px; |
| 115 | } |
| 116 | |
| 117 | .content { |
| 118 | padding-top: 10px; |
| 119 | font-size:.8em; |
| 120 | color: #444; |
| 121 | } |
| 122 | |
| 123 | .udiff, .sbsdiff, |
| 124 | .content blockquote { |
| 125 | font-size: .85em !important; |
| 126 | overflow: auto; |
| 127 | border: 1px solid #ccc; |
| 128 | border-radius: 5px; |
| 129 | } |
| @@ -183,6 +182,13 @@ | |
| 183 | border-top: 1px solid #ccc; |
| 184 | padding: 10px; |
| 185 | font-size:.7em; |
| 186 | margin-top: 10px; |
| 187 | color: #ccc; |
| 188 | } |
| 189 |
| --- skins/etienne1/css.txt | |
| +++ skins/etienne1/css.txt | |
| @@ -13,11 +13,11 @@ | |
| 13 | color: #4183C4; |
| 14 | text-decoration: underline; |
| 15 | } |
| 16 | |
| 17 | hr { |
| 18 | color: #eee; |
| 19 | } |
| 20 | |
| 21 | .title { |
| 22 | color: #4183C4; |
| 23 | float:left; |
| @@ -100,30 +100,29 @@ | |
| 100 | padding: 10px; |
| 101 | border-bottom: 1px solid #ccc; |
| 102 | } |
| 103 | |
| 104 | .submenu a { |
| 105 | padding: 10px 11px; |
| 106 | text-decoration:none; |
| 107 | color: #777; |
| 108 | } |
| 109 | |
| 110 | .submenu a:hover { |
| 111 | padding: 6px 10px; |
| 112 | border: 1px solid #ccc; |
| 113 | border-radius: 5px; |
| 114 | color: #000; |
| 115 | } |
| 116 | |
| 117 | .content { |
| 118 | padding-top: 10px; |
| 119 | font-size:.8em; |
| 120 | color: #444; |
| 121 | } |
| 122 | |
| 123 | .udiff, .sbsdiff { |
| 124 | font-size: .85em !important; |
| 125 | overflow: auto; |
| 126 | border: 1px solid #ccc; |
| 127 | border-radius: 5px; |
| 128 | } |
| @@ -183,6 +182,13 @@ | |
| 182 | border-top: 1px solid #ccc; |
| 183 | padding: 10px; |
| 184 | font-size:.7em; |
| 185 | margin-top: 10px; |
| 186 | color: #ccc; |
| 187 | } |
| 188 | div.timelineDate { |
| 189 | font-weight: bold; |
| 190 | white-space: nowrap; |
| 191 | } |
| 192 | span.submenuctrl, span.submenuctrl input, select.submenuctrl { |
| 193 | color: #777; |
| 194 | } |
| 195 |
+19
-51
| --- skins/etienne1/header.txt | ||
| +++ skins/etienne1/header.txt | ||
| @@ -19,70 +19,38 @@ | ||
| 19 | 19 | } |
| 20 | 20 | </th1></div> |
| 21 | 21 | </div> |
| 22 | 22 | |
| 23 | 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 | - | |
| 24 | +<th1> | |
| 25 | +proc menulink {url name} { | |
| 26 | + upvar current_page current | |
| 27 | + upvar home home | |
| 28 | + if {[string range $url 0 [string length $current]] eq "/$current"} { | |
| 29 | + html "<a href='$home$url' class='active'>$name</a>\n" | |
| 30 | + } else { | |
| 31 | + html "<a href='$home$url'>$name</a>\n" | |
| 32 | + } | |
| 33 | +} | |
| 34 | +menulink $index_page Home | |
| 67 | 35 | if {[anycap jor]} { |
| 68 | - menulink "timeline" "/timeline" Timeline | |
| 36 | + menulink /timeline Timeline | |
| 69 | 37 | } |
| 70 | 38 | if {[hascap oh]} { |
| 71 | - menulink "dir" "/dir?ci=tip" Files | |
| 39 | + menulink /dir?ci=tip Files | |
| 72 | 40 | } |
| 73 | 41 | if {[hascap o]} { |
| 74 | - menulink "brlist" "/brlist" Branches | |
| 75 | - menulink "taglist" "/taglist" Tags | |
| 42 | + menulink /brlist Branches | |
| 43 | + menulink /taglist Tags | |
| 76 | 44 | } |
| 77 | 45 | if {[hascap r]} { |
| 78 | - menulink "ticket" "/ticket" Tickets | |
| 46 | + menulink /ticket Tickets | |
| 79 | 47 | } |
| 80 | 48 | if {[hascap j]} { |
| 81 | - menulink "wiki" "/wiki" Wiki | |
| 49 | + menulink /wiki Wiki | |
| 82 | 50 | } |
| 83 | 51 | if {[hascap s]} { |
| 84 | - menulink "setup" "/setup" Admin | |
| 52 | + menulink /setup Admin | |
| 85 | 53 | } elseif {[hascap a]} { |
| 86 | - menulink "setup_ulist" "/setup_ulist" Users | |
| 54 | + menulink /setup_ulist Users | |
| 87 | 55 | } |
| 88 | 56 | </th1></div> |
| 89 | 57 |
| --- skins/etienne1/header.txt | |
| +++ skins/etienne1/header.txt | |
| @@ -19,70 +19,38 @@ | |
| 19 | } |
| 20 | </th1></div> |
| 21 | </div> |
| 22 | |
| 23 | <div class="mainmenu"> |
| 24 | <th1> |
| 25 | proc isin {val lst} { |
| 26 | set tot [llength $lst] |
| 27 | for {set i 0} {$i < $tot} {set i [expr {$i + 1}]} { |
| 28 | set cur [lindex $lst $i] |
| 29 | if {$val eq $cur} { |
| 30 | return 0 |
| 31 | } |
| 32 | } |
| 33 | return 1 |
| 34 | } |
| 35 | |
| 36 | proc menulink {pagename url name} { |
| 37 | upvar current_page current |
| 38 | upvar home home |
| 39 | |
| 40 | set compsetup [string compare [string range $current 0 4] setup] |
| 41 | set comphome [string compare [string range $current 0 3] home] |
| 42 | set comptag [string compare $current tagtimeline] |
| 43 | set compbr [string compare $current brtimeline] |
| 44 | set compdir [isin $current "artifact ci finfo hexdump"] |
| 45 | set comptl [string compare $current info] |
| 46 | set comptkt [isin $current "modreq rptedit tktnew rptsql rptview"] |
| 47 | |
| 48 | html "<a href='$home$url'" |
| 49 | |
| 50 | if {$pagename eq $current |
| 51 | || ($pagename eq "home" && $comphome == 0) |
| 52 | || ($pagename eq "setup" && $compsetup == 0) |
| 53 | || ($pagename eq "taglist" && $comptag == 0) |
| 54 | || ($pagename eq "dir" && $compdir == 0) |
| 55 | || ($pagename eq "timeline" && $comptl == 0) |
| 56 | || ($pagename eq "ticket" && $comptkt == 0) |
| 57 | || ($pagename eq "brlist" && $compbr == 0) |
| 58 | } { |
| 59 | html " class='active' " |
| 60 | } |
| 61 | |
| 62 | html ">$name</a>" |
| 63 | } |
| 64 | |
| 65 | menulink "home" $index_page Home |
| 66 | |
| 67 | if {[anycap jor]} { |
| 68 | menulink "timeline" "/timeline" Timeline |
| 69 | } |
| 70 | if {[hascap oh]} { |
| 71 | menulink "dir" "/dir?ci=tip" Files |
| 72 | } |
| 73 | if {[hascap o]} { |
| 74 | menulink "brlist" "/brlist" Branches |
| 75 | menulink "taglist" "/taglist" Tags |
| 76 | } |
| 77 | if {[hascap r]} { |
| 78 | menulink "ticket" "/ticket" Tickets |
| 79 | } |
| 80 | if {[hascap j]} { |
| 81 | menulink "wiki" "/wiki" Wiki |
| 82 | } |
| 83 | if {[hascap s]} { |
| 84 | menulink "setup" "/setup" Admin |
| 85 | } elseif {[hascap a]} { |
| 86 | menulink "setup_ulist" "/setup_ulist" Users |
| 87 | } |
| 88 | </th1></div> |
| 89 |
| --- skins/etienne1/header.txt | |
| +++ skins/etienne1/header.txt | |
| @@ -19,70 +19,38 @@ | |
| 19 | } |
| 20 | </th1></div> |
| 21 | </div> |
| 22 | |
| 23 | <div class="mainmenu"> |
| 24 | <th1> |
| 25 | proc menulink {url name} { |
| 26 | upvar current_page current |
| 27 | upvar home home |
| 28 | if {[string range $url 0 [string length $current]] eq "/$current"} { |
| 29 | html "<a href='$home$url' class='active'>$name</a>\n" |
| 30 | } else { |
| 31 | html "<a href='$home$url'>$name</a>\n" |
| 32 | } |
| 33 | } |
| 34 | menulink $index_page Home |
| 35 | if {[anycap jor]} { |
| 36 | menulink /timeline Timeline |
| 37 | } |
| 38 | if {[hascap oh]} { |
| 39 | menulink /dir?ci=tip Files |
| 40 | } |
| 41 | if {[hascap o]} { |
| 42 | menulink /brlist Branches |
| 43 | menulink /taglist Tags |
| 44 | } |
| 45 | if {[hascap r]} { |
| 46 | menulink /ticket Tickets |
| 47 | } |
| 48 | if {[hascap j]} { |
| 49 | menulink /wiki Wiki |
| 50 | } |
| 51 | if {[hascap s]} { |
| 52 | menulink /setup Admin |
| 53 | } elseif {[hascap a]} { |
| 54 | menulink /setup_ulist Users |
| 55 | } |
| 56 | </th1></div> |
| 57 |
+4
-4
| --- skins/khaki/header.txt | ||
| +++ skins/khaki/header.txt | ||
| @@ -24,21 +24,21 @@ | ||
| 24 | 24 | <th1> |
| 25 | 25 | html "<a href='$home$index_page'>Home</a>\n" |
| 26 | 26 | if {[anycap jor]} { |
| 27 | 27 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 28 | 28 | } |
| 29 | -if {[hascap oh]} { | |
| 29 | +if {[anoncap oh]} { | |
| 30 | 30 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 31 | 31 | } |
| 32 | -if {[hascap o]} { | |
| 32 | +if {[anoncap o]} { | |
| 33 | 33 | html "<a href='$home/brlist'>Branches</a>\n" |
| 34 | 34 | html "<a href='$home/taglist'>Tags</a>\n" |
| 35 | 35 | } |
| 36 | -if {[hascap r]} { | |
| 36 | +if {[anoncap r]} { | |
| 37 | 37 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 38 | 38 | } |
| 39 | -if {[hascap j]} { | |
| 39 | +if {[anoncap j]} { | |
| 40 | 40 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 41 | 41 | } |
| 42 | 42 | if {[hascap s]} { |
| 43 | 43 | html "<a href='$home/setup'>Admin</a>\n" |
| 44 | 44 | } elseif {[hascap a]} { |
| 45 | 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 {[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 |
+4
-4
| --- skins/plain_gray/header.txt | ||
| +++ skins/plain_gray/header.txt | ||
| @@ -22,21 +22,21 @@ | ||
| 22 | 22 | <th1> |
| 23 | 23 | html "<a href='$home$index_page'>Home</a>\n" |
| 24 | 24 | if {[anycap jor]} { |
| 25 | 25 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 26 | 26 | } |
| 27 | -if {[hascap oh]} { | |
| 27 | +if {[anoncap oh]} { | |
| 28 | 28 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 29 | 29 | } |
| 30 | -if {[hascap o]} { | |
| 30 | +if {[anoncap o]} { | |
| 31 | 31 | html "<a href='$home/brlist'>Branches</a>\n" |
| 32 | 32 | html "<a href='$home/taglist'>Tags</a>\n" |
| 33 | 33 | } |
| 34 | -if {[hascap r]} { | |
| 34 | +if {[anoncap r]} { | |
| 35 | 35 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 36 | 36 | } |
| 37 | -if {[hascap j]} { | |
| 37 | +if {[anoncap j]} { | |
| 38 | 38 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 39 | 39 | } |
| 40 | 40 | if {[hascap s]} { |
| 41 | 41 | html "<a href='$home/setup'>Admin</a>\n" |
| 42 | 42 | } elseif {[hascap a]} { |
| 43 | 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 {[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 @@ | ||
| 181 | 181 | cursor: pointer; |
| 182 | 182 | } |
| 183 | 183 | |
| 184 | 184 | table.report tr td { |
| 185 | 185 | padding: 3px 5px; |
| 186 | - cursor: pointer; | |
| 187 | 186 | } |
| 188 | 187 | |
| 189 | 188 | textarea { |
| 190 | 189 | font-size: 1em; |
| 191 | 190 | } |
| 192 | 191 |
| --- 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 |
+4
-4
| --- skins/rounded1/header.txt | ||
| +++ skins/rounded1/header.txt | ||
| @@ -26,21 +26,21 @@ | ||
| 26 | 26 | <th1> |
| 27 | 27 | html "<a href='$home$index_page'>Home</a>\n" |
| 28 | 28 | if {[anycap jor]} { |
| 29 | 29 | html "<a href='$home/timeline'>Timeline</a>\n" |
| 30 | 30 | } |
| 31 | -if {[hascap oh]} { | |
| 31 | +if {[anoncap oh]} { | |
| 32 | 32 | html "<a href='$home/tree?ci=tip'>Files</a>\n" |
| 33 | 33 | } |
| 34 | -if {[hascap o]} { | |
| 34 | +if {[anoncap o]} { | |
| 35 | 35 | html "<a href='$home/brlist'>Branches</a>\n" |
| 36 | 36 | html "<a href='$home/taglist'>Tags</a>\n" |
| 37 | 37 | } |
| 38 | -if {[hascap r]} { | |
| 38 | +if {[anoncap r]} { | |
| 39 | 39 | html "<a href='$home/ticket'>Tickets</a>\n" |
| 40 | 40 | } |
| 41 | -if {[hascap j]} { | |
| 41 | +if {[anoncap j]} { | |
| 42 | 42 | html "<a href='$home/wiki'>Wiki</a>\n" |
| 43 | 43 | } |
| 44 | 44 | if {[hascap s]} { |
| 45 | 45 | html "<a href='$home/setup'>Admin</a>\n" |
| 46 | 46 | } elseif {[hascap a]} { |
| 47 | 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 {[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 @@ | ||
| 153 | 153 | " WHERE pathname=%Q %s", zPath, filename_collation()) ){ |
| 154 | 154 | db_multi_exec("UPDATE vfile SET deleted=0" |
| 155 | 155 | " WHERE pathname=%Q %s", zPath, filename_collation()); |
| 156 | 156 | }else{ |
| 157 | 157 | char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath); |
| 158 | + int isExe = file_wd_isexe(zFullname); | |
| 158 | 159 | db_multi_exec( |
| 159 | 160 | "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)" |
| 160 | 161 | "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)); | |
| 162 | 163 | fossil_free(zFullname); |
| 163 | 164 | } |
| 164 | 165 | if( db_changes() ){ |
| 165 | 166 | fossil_print("ADDED %s\n", zPath); |
| 166 | 167 | return 1; |
| 167 | 168 |
| --- 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 @@ | ||
| 105 | 105 | ** |
| 106 | 106 | ** extras Shows "extra" files from all local checkouts. The command |
| 107 | 107 | ** line options supported by the extra command itself, if any |
| 108 | 108 | ** are present, are passed along verbatim. |
| 109 | 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 | 110 | ** info Run the "info" command on all repositories. |
| 116 | 111 | ** |
| 117 | -** list | ls Display the location of all repositories. The -c|--ckout | |
| 118 | -** option causes all local checkouts to be listed instead. | |
| 119 | -** | |
| 120 | 112 | ** pull Run a "pull" operation on all repositories. Only the |
| 121 | 113 | ** --verbose option is supported. |
| 122 | 114 | ** |
| 123 | 115 | ** push Run a "push" on all repositories. Only the --verbose |
| 124 | 116 | ** option is supported. |
| @@ -134,10 +126,25 @@ | ||
| 134 | 126 | ** setting Run the "setting", "set", or "unset" commands on all |
| 135 | 127 | ** set repositories. These command are particularly useful in |
| 136 | 128 | ** unset conjunction with the "max-loadavg" setting which cannot |
| 137 | 129 | ** otherwise be set globally. |
| 138 | 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 | +** | |
| 139 | 146 | ** Repositories are automatically added to the set of known repositories |
| 140 | 147 | ** when one of the following commands are run against the repository: |
| 141 | 148 | ** clone, info, pull, push, or sync. Even previously ignored repositories |
| 142 | 149 | ** are added back to the list of repositories by these commands. |
| 143 | 150 | ** |
| @@ -230,11 +237,12 @@ | ||
| 230 | 237 | collect_argument(&extra, "deanalyze",0); |
| 231 | 238 | collect_argument(&extra, "analyze",0); |
| 232 | 239 | collect_argument(&extra, "wal",0); |
| 233 | 240 | collect_argument(&extra, "stats",0); |
| 234 | 241 | collect_argument(&extra, "index",0); |
| 235 | - collect_argument(&extra, "no-index",0); | |
| 242 | + collect_argument(&extra, "noindex",0); | |
| 243 | + collect_argument(&extra, "ifneeded", 0); | |
| 236 | 244 | }else if( strncmp(zCmd, "setting", n)==0 ){ |
| 237 | 245 | zCmd = "setting -R"; |
| 238 | 246 | collect_argv(&extra, 3); |
| 239 | 247 | }else if( strncmp(zCmd, "unset", n)==0 ){ |
| 240 | 248 | zCmd = "unset -R"; |
| @@ -258,26 +266,55 @@ | ||
| 258 | 266 | useCheckouts = 1; |
| 259 | 267 | stopOnError = 0; |
| 260 | 268 | quiet = 1; |
| 261 | 269 | }else if( strncmp(zCmd, "ignore", n)==0 ){ |
| 262 | 270 | int j; |
| 271 | + Blob fn = BLOB_INITIALIZER; | |
| 272 | + Blob sql = BLOB_INITIALIZER; | |
| 263 | 273 | useCheckouts = find_option("ckout","c",0)!=0; |
| 264 | 274 | verify_all_options(); |
| 265 | 275 | 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); | |
| 269 | 278 | blob_append_sql(&sql, |
| 270 | 279 | "DELETE FROM global_config WHERE name GLOB '%s:%q'", |
| 271 | - useCheckouts?"ckout":"repo", g.argv[j] | |
| 280 | + useCheckouts?"ckout":"repo", blob_str(&fn) | |
| 272 | 281 | ); |
| 273 | 282 | if( dryRunFlag ){ |
| 274 | 283 | fossil_print("%s\n", blob_sql_text(&sql)); |
| 275 | 284 | }else{ |
| 276 | 285 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 277 | 286 | } |
| 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 | + } | |
| 279 | 316 | } |
| 280 | 317 | db_end_transaction(0); |
| 281 | 318 | return; |
| 282 | 319 | }else if( strncmp(zCmd, "info", n)==0 ){ |
| 283 | 320 | zCmd = "info"; |
| 284 | 321 |
| --- 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 @@ | ||
| 48 | 48 | " (SELECT uuid FROM blob WHERE rid=attachid), attachid" |
| 49 | 49 | " FROM attachment", |
| 50 | 50 | timeline_utc() |
| 51 | 51 | ); |
| 52 | 52 | if( zPage ){ |
| 53 | - if( g.perm.RdWiki==0 ) login_needed(); | |
| 53 | + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } | |
| 54 | 54 | style_header("Attachments To %h", zPage); |
| 55 | 55 | blob_append_sql(&sql, " WHERE target=%Q", zPage); |
| 56 | 56 | }else if( zTkt ){ |
| 57 | - if( g.perm.RdTkt==0 ) login_needed(); | |
| 57 | + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } | |
| 58 | 58 | style_header("Attachments To Ticket %S", zTkt); |
| 59 | 59 | blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt); |
| 60 | 60 | }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 | + } | |
| 62 | 65 | style_header("All Attachments"); |
| 63 | 66 | } |
| 64 | 67 | blob_append_sql(&sql, " ORDER BY mtime DESC"); |
| 65 | 68 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 66 | 69 | @ <ol> |
| @@ -86,11 +89,11 @@ | ||
| 86 | 89 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 87 | 90 | }else{ |
| 88 | 91 | zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); |
| 89 | 92 | } |
| 90 | 93 | @ <li><p> |
| 91 | - @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a> | |
| 94 | + @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a> | |
| 92 | 95 | if( moderation_pending(attachid) ){ |
| 93 | 96 | @ <span class="modpending">*** Awaiting Moderator Approval ***</span> |
| 94 | 97 | } |
| 95 | 98 | @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a> |
| 96 | 99 | @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> |
| @@ -103,14 +106,14 @@ | ||
| 103 | 106 | zSrc = "Deleted from"; |
| 104 | 107 | }else { |
| 105 | 108 | zSrc = "Added to"; |
| 106 | 109 | } |
| 107 | 110 | 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)"> | |
| 109 | 112 | @ %S(zTarget)</a> |
| 110 | 113 | }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)"> | |
| 112 | 115 | @ %h(zTarget)</a> |
| 113 | 116 | } |
| 114 | 117 | }else{ |
| 115 | 118 | if( zSrc==0 || zSrc[0]==0 ){ |
| 116 | 119 | @ Deleted |
| @@ -150,14 +153,14 @@ | ||
| 150 | 153 | |
| 151 | 154 | if( zPage && zTkt ) zTkt = 0; |
| 152 | 155 | if( zFile==0 ) fossil_redirect_home(); |
| 153 | 156 | login_check_credentials(); |
| 154 | 157 | if( zPage ){ |
| 155 | - if( g.perm.RdWiki==0 ) login_needed(); | |
| 158 | + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } | |
| 156 | 159 | zTarget = zPage; |
| 157 | 160 | }else if( zTkt ){ |
| 158 | - if( g.perm.RdTkt==0 ) login_needed(); | |
| 161 | + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } | |
| 159 | 162 | zTarget = zTkt; |
| 160 | 163 | }else{ |
| 161 | 164 | fossil_redirect_home(); |
| 162 | 165 | } |
| 163 | 166 | if( attachid>0 ){ |
| @@ -243,27 +246,33 @@ | ||
| 243 | 246 | if( P("cancel") ) cgi_redirect(zFrom); |
| 244 | 247 | if( zPage && zTkt ) fossil_redirect_home(); |
| 245 | 248 | if( zPage==0 && zTkt==0 ) fossil_redirect_home(); |
| 246 | 249 | login_check_credentials(); |
| 247 | 250 | 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 | + } | |
| 249 | 255 | if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ |
| 250 | 256 | fossil_redirect_home(); |
| 251 | 257 | } |
| 252 | 258 | 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); | |
| 255 | 261 | }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 | + } | |
| 257 | 266 | if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ |
| 258 | 267 | zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" |
| 259 | 268 | " WHERE tagname GLOB 'tkt-%q*'", zTkt); |
| 260 | 269 | if( zTkt==0 ) fossil_redirect_home(); |
| 261 | 270 | } |
| 262 | 271 | 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); | |
| 265 | 274 | } |
| 266 | 275 | if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); |
| 267 | 276 | if( P("cancel") ){ |
| 268 | 277 | cgi_redirect(zFrom); |
| 269 | 278 | } |
| @@ -369,11 +378,14 @@ | ||
| 369 | 378 | Blob attach; /* Content of the attachment */ |
| 370 | 379 | int fShowContent = 0; |
| 371 | 380 | const char *zLn = P("ln"); |
| 372 | 381 | |
| 373 | 382 | 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 | + } | |
| 375 | 387 | rid = name_to_rid_www("name"); |
| 376 | 388 | if( rid==0 ){ fossil_redirect_home(); } |
| 377 | 389 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 378 | 390 | #if 0 |
| 379 | 391 | /* Shunning here needs to get both the attachment control artifact and |
| @@ -399,17 +411,17 @@ | ||
| 399 | 411 | fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0; |
| 400 | 412 | if( validate16(zTarget, strlen(zTarget)) |
| 401 | 413 | && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget) |
| 402 | 414 | ){ |
| 403 | 415 | zTktUuid = zTarget; |
| 404 | - if( !g.perm.RdTkt ){ login_needed(); return; } | |
| 416 | + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } | |
| 405 | 417 | if( g.perm.WrTkt ){ |
| 406 | 418 | style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); |
| 407 | 419 | } |
| 408 | 420 | }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){ |
| 409 | 421 | zWikiName = zTarget; |
| 410 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 422 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 411 | 423 | if( g.perm.WrWiki ){ |
| 412 | 424 | style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); |
| 413 | 425 | } |
| 414 | 426 | } |
| 415 | 427 | zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); |
| @@ -443,11 +455,11 @@ | ||
| 443 | 455 | } |
| 444 | 456 | |
| 445 | 457 | if( P("del") |
| 446 | 458 | && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki)) |
| 447 | 459 | ){ |
| 448 | - form_begin(0, "%R/ainfo/%s", zUuid); | |
| 460 | + form_begin(0, "%R/ainfo/%!S", zUuid); | |
| 449 | 461 | @ <p>Confirm you want to delete the attachment shown below. |
| 450 | 462 | @ <input type="submit" name="confirm" value="Confirm"> |
| 451 | 463 | @ </form> |
| 452 | 464 | } |
| 453 | 465 | |
| @@ -456,11 +468,11 @@ | ||
| 456 | 468 | (zWikiName && g.perm.ModWiki); |
| 457 | 469 | if( isModerator && (zModAction = P("modaction"))!=0 ){ |
| 458 | 470 | if( strcmp(zModAction,"delete")==0 ){ |
| 459 | 471 | moderation_disapprove(rid); |
| 460 | 472 | if( zTktUuid ){ |
| 461 | - cgi_redirectf("%R/tktview/%s", zTktUuid); | |
| 473 | + cgi_redirectf("%R/tktview/%!S", zTktUuid); | |
| 462 | 474 | }else{ |
| 463 | 475 | cgi_redirectf("%R/wiki?name=%t", zWikiName); |
| 464 | 476 | } |
| 465 | 477 | return; |
| 466 | 478 | } |
| @@ -477,11 +489,11 @@ | ||
| 477 | 489 | } |
| 478 | 490 | |
| 479 | 491 | @ <div class="section">Overview</div> |
| 480 | 492 | @ <p><table class="label-value"> |
| 481 | 493 | @ <tr><th>Artifact ID:</th> |
| 482 | - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> | |
| 494 | + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> | |
| 483 | 495 | if( g.perm.Setup ){ |
| 484 | 496 | @ (%d(rid)) |
| 485 | 497 | } |
| 486 | 498 | modPending = moderation_pending(rid); |
| 487 | 499 | if( modPending ){ |
| @@ -579,17 +591,17 @@ | ||
| 579 | 591 | if( cnt==0 ){ |
| 580 | 592 | @ %s(zHeader) |
| 581 | 593 | } |
| 582 | 594 | cnt++; |
| 583 | 595 | @ <li> |
| 584 | - @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a> | |
| 596 | + @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> | |
| 585 | 597 | @ added by %h(zDispUser) on |
| 586 | 598 | hyperlink_to_date(zDate, "."); |
| 587 | - @ [%z(href("%R/ainfo/%s",zUuid))details</a>] | |
| 599 | + @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] | |
| 588 | 600 | @ </li> |
| 589 | 601 | } |
| 590 | 602 | if( cnt ){ |
| 591 | 603 | @ </ul> |
| 592 | 604 | } |
| 593 | 605 | db_finalize(&q); |
| 594 | 606 | |
| 595 | 607 | } |
| 596 | 608 |
| --- 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 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 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 @@ | ||
| 159 | 159 | fossil_fatal("%s\n", g.zErrMsg); |
| 160 | 160 | } |
| 161 | 161 | assert( blob_is_reset(&branch) ); |
| 162 | 162 | content_deltify(rootid, brid, 0); |
| 163 | 163 | 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); | |
| 165 | 165 | if( g.argc==3 ){ |
| 166 | 166 | fossil_print( |
| 167 | 167 | "\n" |
| 168 | 168 | "Note: the local check-out has not been updated to the new\n" |
| 169 | 169 | " branch. To begin working on the new branch, do this:\n" |
| @@ -339,11 +339,11 @@ | ||
| 339 | 339 | */ |
| 340 | 340 | static void new_brlist_page(void){ |
| 341 | 341 | Stmt q; |
| 342 | 342 | double rNow; |
| 343 | 343 | login_check_credentials(); |
| 344 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 344 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 345 | 345 | style_header("Branches"); |
| 346 | 346 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 347 | 347 | login_anonymous_available(); |
| 348 | 348 | |
| 349 | 349 | db_prepare(&q, brlistQuery/*works-like:""*/); |
| @@ -372,11 +372,11 @@ | ||
| 372 | 372 | @ <td>%d(nCkin)</td> |
| 373 | 373 | fossil_free(zAge); |
| 374 | 374 | @ <td>%s(isClosed?"closed":"")</td> |
| 375 | 375 | if( zMergeTo ){ |
| 376 | 376 | @ <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> | |
| 378 | 378 | }else{ |
| 379 | 379 | @ <td></td> |
| 380 | 380 | } |
| 381 | 381 | @ </tr> |
| 382 | 382 | } |
| @@ -408,11 +408,11 @@ | ||
| 408 | 408 | if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){ |
| 409 | 409 | new_brlist_page(); |
| 410 | 410 | return; |
| 411 | 411 | } |
| 412 | 412 | login_check_credentials(); |
| 413 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 413 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 414 | 414 | if( colorTest ){ |
| 415 | 415 | showClosed = 0; |
| 416 | 416 | showAll = 1; |
| 417 | 417 | } |
| 418 | 418 | if( showAll ) brFlags = BRL_BOTH; |
| @@ -515,11 +515,11 @@ | ||
| 515 | 515 | */ |
| 516 | 516 | void brtimeline_page(void){ |
| 517 | 517 | Stmt q; |
| 518 | 518 | |
| 519 | 519 | login_check_credentials(); |
| 520 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 520 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 521 | 521 | |
| 522 | 522 | style_header("Branches"); |
| 523 | 523 | style_submenu_element("List", "List", "brlist"); |
| 524 | 524 | login_anonymous_available(); |
| 525 | 525 | @ <h2>The initial check-in for each branch:</h2> |
| @@ -527,9 +527,9 @@ | ||
| 527 | 527 | "%s AND blob.rid IN (SELECT rid FROM tagxref" |
| 528 | 528 | " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" |
| 529 | 529 | " ORDER BY event.mtime DESC", |
| 530 | 530 | timeline_query_for_www(), TAG_BRANCH |
| 531 | 531 | ); |
| 532 | - www_print_timeline(&q, 0, 0, 0, brtimeline_extra); | |
| 532 | + www_print_timeline(&q, 0, 0, 0, 0, brtimeline_extra); | |
| 533 | 533 | db_finalize(&q); |
| 534 | 534 | style_footer(); |
| 535 | 535 | } |
| 536 | 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(); 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 @@ | ||
| 85 | 85 | |
| 86 | 86 | for(i=0; zPath[i]; i=j){ |
| 87 | 87 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 88 | 88 | if( zPath[j] && g.perm.Hyperlink ){ |
| 89 | 89 | 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); | |
| 91 | 91 | blob_appendf(pOut, "%s%z%#h</a>", |
| 92 | 92 | zSep, zLink, j-i, &zPath[i]); |
| 93 | 93 | }else{ |
| 94 | 94 | char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); |
| 95 | 95 | blob_appendf(pOut, "%s%z%#h</a>", |
| @@ -128,19 +128,20 @@ | ||
| 128 | 128 | const char *zSubdirLink; |
| 129 | 129 | int linkTrunk = 1; |
| 130 | 130 | int linkTip = 1; |
| 131 | 131 | HQuery sURI; |
| 132 | 132 | |
| 133 | - if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; } | |
| 133 | + if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } | |
| 134 | 134 | login_check_credentials(); |
| 135 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 135 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 136 | 136 | while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } |
| 137 | 137 | style_header("File List"); |
| 138 | 138 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 139 | 139 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 140 | 140 | pathelementFunc, 0, 0); |
| 141 | 141 | url_initialize(&sURI, "dir"); |
| 142 | + cgi_query_parameters_to_url(&sURI); | |
| 142 | 143 | |
| 143 | 144 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| 144 | 145 | if( zD && strlen(zD)==0 ){ zD = 0; } |
| 145 | 146 | |
| 146 | 147 | /* If a specific check-in is requested, fetch and parse it. If the |
| @@ -152,20 +153,18 @@ | ||
| 152 | 153 | if( pM ){ |
| 153 | 154 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 154 | 155 | linkTrunk = trunkRid && rid != trunkRid; |
| 155 | 156 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 156 | 157 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 157 | - url_add_parameter(&sURI, "ci", zCI); | |
| 158 | 158 | }else{ |
| 159 | 159 | zCI = 0; |
| 160 | 160 | } |
| 161 | 161 | } |
| 162 | 162 | |
| 163 | 163 | /* Compute the title of the page */ |
| 164 | 164 | blob_zero(&dirname); |
| 165 | 165 | if( zD ){ |
| 166 | - url_add_parameter(&sURI, "name", zD); | |
| 167 | 166 | blob_append(&dirname, "in directory ", -1); |
| 168 | 167 | hyperlinked_path(zD, &dirname, zCI, "dir", ""); |
| 169 | 168 | zPrefix = mprintf("%s/", zD); |
| 170 | 169 | style_submenu_element("Top-Level", "Top-Level", "%s", |
| 171 | 170 | url_render(&sURI, "name", 0, 0, 0)); |
| @@ -180,15 +179,15 @@ | ||
| 180 | 179 | if( linkTip ){ |
| 181 | 180 | style_submenu_element("Tip", "Tip", "%s", |
| 182 | 181 | url_render(&sURI, "ci", "tip", 0, 0)); |
| 183 | 182 | } |
| 184 | 183 | 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>] | |
| 186 | 185 | @ %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); | |
| 188 | 187 | 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", | |
| 190 | 189 | zUuid); |
| 191 | 190 | } |
| 192 | 191 | }else{ |
| 193 | 192 | @ <h2>The union of all files from all check-ins |
| 194 | 193 | @ %s(blob_str(&dirname))</h2> |
| @@ -282,11 +281,11 @@ | ||
| 282 | 281 | @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> |
| 283 | 282 | }else{ |
| 284 | 283 | const char *zLink; |
| 285 | 284 | if( zCI ){ |
| 286 | 285 | const char *zUuid = db_column_text(&q, 1); |
| 287 | - zLink = href("%R/artifact/%s",zUuid); | |
| 286 | + zLink = href("%R/artifact/%!S",zUuid); | |
| 288 | 287 | }else{ |
| 289 | 288 | zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); |
| 290 | 289 | } |
| 291 | 290 | @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> |
| 292 | 291 | } |
| @@ -540,39 +539,37 @@ | ||
| 540 | 539 | int startExpanded; /* True to start out with the tree expanded */ |
| 541 | 540 | int showDirOnly; /* Show directories only. Omit files */ |
| 542 | 541 | int nDir = 0; /* Number of directories. Used for ID attributes */ |
| 543 | 542 | char *zProjectName = db_get("project-name", 0); |
| 544 | 543 | |
| 545 | - if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; } | |
| 544 | + if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } | |
| 546 | 545 | memset(&sTree, 0, sizeof(sTree)); |
| 547 | 546 | login_check_credentials(); |
| 548 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 547 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 549 | 548 | while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } |
| 550 | 549 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 551 | 550 | pathelementFunc, 0, 0); |
| 552 | 551 | url_initialize(&sURI, "tree"); |
| 553 | - if( P("nofiles")!=0 ){ | |
| 552 | + cgi_query_parameters_to_url(&sURI); | |
| 553 | + if( PB("nofiles") ){ | |
| 554 | 554 | showDirOnly = 1; |
| 555 | - url_add_parameter(&sURI, "nofiles", "1"); | |
| 556 | 555 | style_header("Folder Hierarchy"); |
| 557 | 556 | }else{ |
| 558 | 557 | showDirOnly = 0; |
| 559 | 558 | style_header("File Tree"); |
| 560 | 559 | } |
| 561 | 560 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 562 | - if( P("expand")!=0 ){ | |
| 561 | + if( PB("expand") ){ | |
| 563 | 562 | startExpanded = 1; |
| 564 | - url_add_parameter(&sURI, "expand", "1"); | |
| 565 | 563 | }else{ |
| 566 | 564 | startExpanded = 0; |
| 567 | 565 | } |
| 568 | 566 | |
| 569 | 567 | /* If a regular expression is specified, compile it */ |
| 570 | 568 | zRE = P("re"); |
| 571 | 569 | if( zRE ){ |
| 572 | 570 | re_compile(&pRE, zRE, 0); |
| 573 | - url_add_parameter(&sURI, "re", zRE); | |
| 574 | 571 | zREx = mprintf("&re=%T", zRE); |
| 575 | 572 | } |
| 576 | 573 | |
| 577 | 574 | /* If the name= parameter is an empty string, make it a NULL pointer */ |
| 578 | 575 | if( zD && strlen(zD)==0 ){ zD = 0; } |
| @@ -586,11 +583,10 @@ | ||
| 586 | 583 | if( pM ){ |
| 587 | 584 | int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); |
| 588 | 585 | linkTrunk = trunkRid && rid != trunkRid; |
| 589 | 586 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 590 | 587 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 591 | - url_add_parameter(&sURI, "ci", zCI); | |
| 592 | 588 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 593 | 589 | zNow = db_text("", "SELECT datetime(mtime,'localtime')" |
| 594 | 590 | " FROM event WHERE objid=%d", rid); |
| 595 | 591 | }else{ |
| 596 | 592 | zCI = 0; |
| @@ -602,11 +598,10 @@ | ||
| 602 | 598 | } |
| 603 | 599 | |
| 604 | 600 | /* Compute the title of the page */ |
| 605 | 601 | blob_zero(&dirname); |
| 606 | 602 | if( zD ){ |
| 607 | - url_add_parameter(&sURI, "name", zD); | |
| 608 | 603 | blob_append(&dirname, "within directory ", -1); |
| 609 | 604 | hyperlinked_path(zD, &dirname, zCI, "tree", zREx); |
| 610 | 605 | if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); |
| 611 | 606 | style_submenu_element("Top-Level", "Top-Level", "%s", |
| 612 | 607 | url_render(&sURI, "name", 0, 0, 0)); |
| @@ -613,18 +608,11 @@ | ||
| 613 | 608 | }else{ |
| 614 | 609 | if( zRE ){ |
| 615 | 610 | blob_appendf(&dirname, "matching \"%s\"", zRE); |
| 616 | 611 | } |
| 617 | 612 | } |
| 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); | |
| 626 | 614 | if( zCI ){ |
| 627 | 615 | style_submenu_element("All", "All", "%s", |
| 628 | 616 | url_render(&sURI, "ci", 0, 0, 0)); |
| 629 | 617 | if( nD==0 && !showDirOnly ){ |
| 630 | 618 | style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s", |
| @@ -705,11 +693,11 @@ | ||
| 705 | 693 | if( zCI ){ |
| 706 | 694 | @ <h2>%s(zObjType) from |
| 707 | 695 | if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){ |
| 708 | 696 | @ "%h(zCI)" |
| 709 | 697 | } |
| 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)) | |
| 711 | 699 | }else{ |
| 712 | 700 | int n = db_int(0, "SELECT count(*) FROM plink"); |
| 713 | 701 | @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) |
| 714 | 702 | } |
| 715 | 703 | if( useMtime ){ |
| @@ -767,11 +755,11 @@ | ||
| 767 | 755 | nDir++; |
| 768 | 756 | }else if( !showDirOnly ){ |
| 769 | 757 | const char *zFileClass = fileext_class(p->zName); |
| 770 | 758 | char *zLink; |
| 771 | 759 | if( zCI ){ |
| 772 | - zLink = href("%R/artifact/%.16s",p->zUuid); | |
| 760 | + zLink = href("%R/artifact/%!S",p->zUuid); | |
| 773 | 761 | }else{ |
| 774 | 762 | zLink = href("%R/finfo?name=%T",p->zFullName); |
| 775 | 763 | } |
| 776 | 764 | @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> |
| 777 | 765 | @ %z(zLink)%h(p->zName)</a> |
| @@ -901,24 +889,29 @@ | ||
| 901 | 889 | @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; |
| 902 | 890 | ; |
| 903 | 891 | |
| 904 | 892 | static const char zComputeFileAgeRun[] = |
| 905 | 893 | @ 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) | |
| 908 | 900 | @ 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 | +; | |
| 920 | 913 | |
| 921 | 914 | /* |
| 922 | 915 | ** Look at all file containing in the version "vid". Construct a |
| 923 | 916 | ** temporary table named "fileage" that contains the file-id for each |
| 924 | 917 | ** files, the pathname, the check-in where the file was added, and the |
| @@ -926,11 +919,11 @@ | ||
| 926 | 919 | ** the given glob are computed. |
| 927 | 920 | */ |
| 928 | 921 | int compute_fileage(int vid, const char* zGlob){ |
| 929 | 922 | Stmt q; |
| 930 | 923 | db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/); |
| 931 | - db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/); | |
| 924 | + db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/); | |
| 932 | 925 | db_bind_int(&q, ":ckin", vid); |
| 933 | 926 | db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*"); |
| 934 | 927 | db_exec(&q); |
| 935 | 928 | db_finalize(&q); |
| 936 | 929 | return 0; |
| @@ -997,21 +990,23 @@ | ||
| 997 | 990 | ** |
| 998 | 991 | ** Parameters: |
| 999 | 992 | ** name=VERSION Selects the checkin version (default=tip). |
| 1000 | 993 | ** glob=STRING Only shows files matching this glob pattern |
| 1001 | 994 | ** (e.g. *.c or *.txt). |
| 995 | +** showid Show RID values for debugging | |
| 1002 | 996 | */ |
| 1003 | 997 | void fileage_page(void){ |
| 1004 | 998 | int rid; |
| 1005 | 999 | const char *zName; |
| 1006 | 1000 | const char *zGlob; |
| 1007 | 1001 | const char *zUuid; |
| 1008 | 1002 | const char *zNow; /* Time of checkin */ |
| 1003 | + int showId = PB("showid"); | |
| 1009 | 1004 | Stmt q1, q2; |
| 1010 | 1005 | double baseTime; |
| 1011 | 1006 | login_check_credentials(); |
| 1012 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 1007 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 1013 | 1008 | zName = P("name"); |
| 1014 | 1009 | if( zName==0 ) zName = "tip"; |
| 1015 | 1010 | rid = symbolic_name_to_rid(zName, "ci"); |
| 1016 | 1011 | if( rid==0 ){ |
| 1017 | 1012 | fossil_fatal("not a valid check-in: %s", zName); |
| @@ -1018,26 +1013,27 @@ | ||
| 1018 | 1013 | } |
| 1019 | 1014 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1020 | 1015 | baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); |
| 1021 | 1016 | zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event" |
| 1022 | 1017 | " 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", | |
| 1024 | 1020 | zName); |
| 1025 | 1021 | style_header("File Ages"); |
| 1026 | 1022 | zGlob = P("glob"); |
| 1027 | 1023 | compute_fileage(rid,zGlob); |
| 1028 | 1024 | db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); |
| 1029 | 1025 | |
| 1030 | 1026 | @ <h2>Files in |
| 1031 | - @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a> | |
| 1027 | + @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a> | |
| 1032 | 1028 | if( zGlob && zGlob[0] ){ |
| 1033 | 1029 | @ that match "%h(zGlob)" and |
| 1034 | 1030 | } |
| 1035 | 1031 | @ ordered by check-in time</h2> |
| 1036 | 1032 | @ |
| 1037 | 1033 | @ <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 | |
| 1039 | 1035 | @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> |
| 1040 | 1036 | @ |
| 1041 | 1037 | @ <div class='fileage'><table> |
| 1042 | 1038 | @ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr> |
| 1043 | 1039 | db_prepare(&q1, |
| @@ -1052,11 +1048,11 @@ | ||
| 1052 | 1048 | " AND blob.rid=event.objid\n" |
| 1053 | 1049 | " ORDER BY event.mtime DESC;", |
| 1054 | 1050 | TAG_BRANCH |
| 1055 | 1051 | ); |
| 1056 | 1052 | db_prepare(&q2, |
| 1057 | - "SELECT blob.uuid, filename.name\n" | |
| 1053 | + "SELECT blob.uuid, filename.name, fileage.fid\n" | |
| 1058 | 1054 | " FROM fileage, blob, filename\n" |
| 1059 | 1055 | " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" |
| 1060 | 1056 | " AND blob.rid=fileage.fid;" |
| 1061 | 1057 | ); |
| 1062 | 1058 | while( db_step(&q1)==SQLITE_ROW ){ |
| @@ -1071,24 +1067,32 @@ | ||
| 1071 | 1067 | @ <td> |
| 1072 | 1068 | db_bind_int(&q2, ":mid", mid); |
| 1073 | 1069 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1074 | 1070 | const char *zFUuid = db_column_text(&q2,0); |
| 1075 | 1071 | 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 | + } | |
| 1077 | 1078 | } |
| 1078 | 1079 | db_reset(&q2); |
| 1079 | 1080 | @ </td> |
| 1080 | 1081 | @ <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 | + } | |
| 1082 | 1086 | @ %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>, | |
| 1084 | 1088 | @ 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>) | |
| 1086 | 1090 | @ </td></tr> |
| 1087 | 1091 | @ |
| 1088 | 1092 | fossil_free(zAge); |
| 1089 | 1093 | } |
| 1090 | 1094 | @ </table></div> |
| 1091 | 1095 | db_finalize(&q1); |
| 1092 | 1096 | db_finalize(&q2); |
| 1093 | 1097 | style_footer(); |
| 1094 | 1098 | } |
| 1095 | 1099 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -85,11 +85,11 @@ | |
| 85 | |
| 86 | for(i=0; zPath[i]; i=j){ |
| 87 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 88 | if( zPath[j] && g.perm.Hyperlink ){ |
| 89 | if( zCI ){ |
| 90 | char *zLink = href("%R/%s?name=%#T%s&ci=%s", zURI, j, zPath, zREx, zCI); |
| 91 | blob_appendf(pOut, "%s%z%#h</a>", |
| 92 | zSep, zLink, j-i, &zPath[i]); |
| 93 | }else{ |
| 94 | char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); |
| 95 | blob_appendf(pOut, "%s%z%#h</a>", |
| @@ -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 @@ | ||
| 330 | 330 | sqlite3 *db; |
| 331 | 331 | sqlite3_stmt *pStmt; |
| 332 | 332 | char zBuf[100]; |
| 333 | 333 | |
| 334 | 334 | login_check_credentials(); |
| 335 | - if( !g.perm.Setup ){ login_needed(); return; } | |
| 335 | + if( !g.perm.Setup ){ login_needed(0); return; } | |
| 336 | 336 | style_header("Web Cache Status"); |
| 337 | 337 | db = cacheOpen(0); |
| 338 | 338 | if( db==0 ){ |
| 339 | 339 | @ The web-page cache is disabled for this repository |
| 340 | 340 | }else{ |
| @@ -378,11 +378,11 @@ | ||
| 378 | 378 | void cache_getpage(void){ |
| 379 | 379 | const char *zKey; |
| 380 | 380 | Blob content; |
| 381 | 381 | |
| 382 | 382 | login_check_credentials(); |
| 383 | - if( !g.perm.Setup ){ login_needed(); return; } | |
| 383 | + if( !g.perm.Setup ){ login_needed(0); return; } | |
| 384 | 384 | zKey = PD("key",""); |
| 385 | 385 | blob_zero(&content); |
| 386 | 386 | if( cache_read(&content, zKey)==0 ){ |
| 387 | 387 | style_header("Cache Download Error"); |
| 388 | 388 | @ The cache does not contain any entry with this key: "%h(zKey)" |
| 389 | 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(); return; } |
| 336 | style_header("Web Cache Status"); |
| 337 | db = cacheOpen(0); |
| 338 | if( db==0 ){ |
| 339 | @ The web-page cache is disabled for this repository |
| 340 | }else{ |
| @@ -378,11 +378,11 @@ | |
| 378 | void cache_getpage(void){ |
| 379 | const char *zKey; |
| 380 | Blob content; |
| 381 | |
| 382 | login_check_credentials(); |
| 383 | if( !g.perm.Setup ){ login_needed(); return; } |
| 384 | zKey = PD("key",""); |
| 385 | blob_zero(&content); |
| 386 | if( cache_read(&content, zKey)==0 ){ |
| 387 | style_header("Cache Download Error"); |
| 388 | @ The cache does not contain any entry with this key: "%h(zKey)" |
| 389 |
| --- src/cache.c | |
| +++ src/cache.c | |
| @@ -330,11 +330,11 @@ | |
| 330 | sqlite3 *db; |
| 331 | sqlite3_stmt *pStmt; |
| 332 | char zBuf[100]; |
| 333 | |
| 334 | login_check_credentials(); |
| 335 | if( !g.perm.Setup ){ login_needed(0); return; } |
| 336 | style_header("Web Cache Status"); |
| 337 | db = cacheOpen(0); |
| 338 | if( db==0 ){ |
| 339 | @ The web-page cache is disabled for this repository |
| 340 | }else{ |
| @@ -378,11 +378,11 @@ | |
| 378 | void cache_getpage(void){ |
| 379 | const char *zKey; |
| 380 | Blob content; |
| 381 | |
| 382 | login_check_credentials(); |
| 383 | if( !g.perm.Setup ){ login_needed(0); return; } |
| 384 | zKey = PD("key",""); |
| 385 | blob_zero(&content); |
| 386 | if( cache_read(&content, zKey)==0 ){ |
| 387 | style_header("Cache Download Error"); |
| 388 | @ The cache does not contain any entry with this key: "%h(zKey)" |
| 389 |
+3
-3
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -1936,18 +1936,18 @@ | ||
| 1936 | 1936 | db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid" |
| 1937 | 1937 | " WHERE id=-4"); |
| 1938 | 1938 | while( db_step(&q)==SQLITE_ROW ){ |
| 1939 | 1939 | const char *zIntegrateUuid = db_column_text(&q, 0); |
| 1940 | 1940 | if( is_a_leaf(db_column_int(&q, 1)) ){ |
| 1941 | - fossil_print("Closed: %s\n", zIntegrateUuid); | |
| 1941 | + fossil_print("Closed: %S\n", zIntegrateUuid); | |
| 1942 | 1942 | }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); | |
| 1944 | 1944 | } |
| 1945 | 1945 | } |
| 1946 | 1946 | db_finalize(&q); |
| 1947 | 1947 | |
| 1948 | - fossil_print("New_Version: %s\n", zUuid); | |
| 1948 | + fossil_print("New_Version: %S\n", zUuid); | |
| 1949 | 1949 | if( outputManifest ){ |
| 1950 | 1950 | zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 1951 | 1951 | blob_zero(&muuid); |
| 1952 | 1952 | blob_appendf(&muuid, "%s\n", zUuid); |
| 1953 | 1953 | blob_write_to_file(&muuid, zManifestFile); |
| 1954 | 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 |
| --- 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 |
M
src/db.c
+8
-8
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -65,14 +65,10 @@ | ||
| 65 | 65 | */ |
| 66 | 66 | static void db_err(const char *zFormat, ...){ |
| 67 | 67 | va_list ap; |
| 68 | 68 | char *z; |
| 69 | 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 | 70 | va_start(ap, zFormat); |
| 75 | 71 | z = vmprintf(zFormat, ap); |
| 76 | 72 | va_end(ap); |
| 77 | 73 | #ifdef FOSSIL_ENABLE_JSON |
| 78 | 74 | if( g.json.isJsonMode ){ |
| @@ -88,15 +84,14 @@ | ||
| 88 | 84 | @ error Database\serror:\s%F(z) |
| 89 | 85 | cgi_reply(); |
| 90 | 86 | } |
| 91 | 87 | else if( g.cgiOutput ){ |
| 92 | 88 | 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); | |
| 95 | 90 | cgi_reply(); |
| 96 | 91 | }else{ |
| 97 | - fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg); | |
| 92 | + fprintf(stderr, "%s: %s\n", g.argv[0], z); | |
| 98 | 93 | } |
| 99 | 94 | free(z); |
| 100 | 95 | db_force_rollback(); |
| 101 | 96 | fossil_exit(rc); |
| 102 | 97 | } |
| @@ -2389,10 +2384,11 @@ | ||
| 2389 | 2384 | { "editor", 0, 32, 0, 0, "" }, |
| 2390 | 2385 | { "empty-dirs", 0, 40, 1, 0, "" }, |
| 2391 | 2386 | { "encoding-glob", 0, 40, 1, 0, "" }, |
| 2392 | 2387 | { "gdiff-command", 0, 40, 0, 0, "gdiff" }, |
| 2393 | 2388 | { "gmerge-command", 0, 40, 0, 0, "" }, |
| 2389 | + { "hash-digits", 0, 5, 0, 0, "10" }, | |
| 2394 | 2390 | { "http-port", 0, 16, 0, 0, "8080" }, |
| 2395 | 2391 | { "https-login", 0, 0, 0, 0, "off" }, |
| 2396 | 2392 | { "ignore-glob", 0, 40, 1, 0, "" }, |
| 2397 | 2393 | { "keep-glob", 0, 40, 1, 0, "" }, |
| 2398 | 2394 | { "localauth", 0, 0, 0, 0, "off" }, |
| @@ -2565,10 +2561,13 @@ | ||
| 2565 | 2561 | ** gmerge-command A graphical merge conflict resolver command operating |
| 2566 | 2562 | ** on four files. |
| 2567 | 2563 | ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output" |
| 2568 | 2564 | ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output" |
| 2569 | 2565 | ** Ex: meld "%baseline" "%original" "%merge" "%output" |
| 2566 | +** | |
| 2567 | +** hash-digits The number of hexadecimal digits of the SHA1 hash to | |
| 2568 | +** display. (Default: 10; Minimum: 6) | |
| 2570 | 2569 | ** |
| 2571 | 2570 | ** http-port The TCP/IP port number to use by the "server" |
| 2572 | 2571 | ** and "ui" commands. Default: 8080 |
| 2573 | 2572 | ** |
| 2574 | 2573 | ** https-login Send login credentials using HTTPS instead of HTTP |
| @@ -2733,11 +2732,12 @@ | ||
| 2733 | 2732 | if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ |
| 2734 | 2733 | fossil_fatal("cannot set 'manifest' globally"); |
| 2735 | 2734 | } |
| 2736 | 2735 | if( unsetFlag || g.argc==4 ){ |
| 2737 | 2736 | int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; |
| 2738 | - if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){ | |
| 2737 | + if( n!=strlen(pSetting[0].name) && pSetting[1].name && | |
| 2738 | + fossil_strncmp(pSetting[1].name, zName, n)==0 ){ | |
| 2739 | 2739 | Blob x; |
| 2740 | 2740 | int i; |
| 2741 | 2741 | blob_init(&x,0,0); |
| 2742 | 2742 | for(i=0; pSetting[i].name; i++){ |
| 2743 | 2743 | if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; |
| 2744 | 2744 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -65,14 +65,10 @@ | |
| 65 | */ |
| 66 | static void db_err(const char *zFormat, ...){ |
| 67 | va_list ap; |
| 68 | char *z; |
| 69 | int rc = 1; |
| 70 | static const char zRebuildMsg[] = |
| 71 | "If you have recently updated your fossil executable, you might\n" |
| 72 | "need to run \"fossil all rebuild\" to bring the repository\n" |
| 73 | "schemas up to date.\n"; |
| 74 | va_start(ap, zFormat); |
| 75 | z = vmprintf(zFormat, ap); |
| 76 | va_end(ap); |
| 77 | #ifdef FOSSIL_ENABLE_JSON |
| 78 | if( g.json.isJsonMode ){ |
| @@ -88,15 +84,14 @@ | |
| 88 | @ error Database\serror:\s%F(z) |
| 89 | cgi_reply(); |
| 90 | } |
| 91 | else if( g.cgiOutput ){ |
| 92 | g.cgiOutput = 0; |
| 93 | cgi_printf("<h1>Database Error</h1>\n" |
| 94 | "<pre>%h</pre>\n<p>%s</p>\n", z, zRebuildMsg); |
| 95 | cgi_reply(); |
| 96 | }else{ |
| 97 | fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg); |
| 98 | } |
| 99 | free(z); |
| 100 | db_force_rollback(); |
| 101 | fossil_exit(rc); |
| 102 | } |
| @@ -2389,10 +2384,11 @@ | |
| 2389 | { "editor", 0, 32, 0, 0, "" }, |
| 2390 | { "empty-dirs", 0, 40, 1, 0, "" }, |
| 2391 | { "encoding-glob", 0, 40, 1, 0, "" }, |
| 2392 | { "gdiff-command", 0, 40, 0, 0, "gdiff" }, |
| 2393 | { "gmerge-command", 0, 40, 0, 0, "" }, |
| 2394 | { "http-port", 0, 16, 0, 0, "8080" }, |
| 2395 | { "https-login", 0, 0, 0, 0, "off" }, |
| 2396 | { "ignore-glob", 0, 40, 1, 0, "" }, |
| 2397 | { "keep-glob", 0, 40, 1, 0, "" }, |
| 2398 | { "localauth", 0, 0, 0, 0, "off" }, |
| @@ -2565,10 +2561,13 @@ | |
| 2565 | ** gmerge-command A graphical merge conflict resolver command operating |
| 2566 | ** on four files. |
| 2567 | ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output" |
| 2568 | ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output" |
| 2569 | ** Ex: meld "%baseline" "%original" "%merge" "%output" |
| 2570 | ** |
| 2571 | ** http-port The TCP/IP port number to use by the "server" |
| 2572 | ** and "ui" commands. Default: 8080 |
| 2573 | ** |
| 2574 | ** https-login Send login credentials using HTTPS instead of HTTP |
| @@ -2733,11 +2732,12 @@ | |
| 2733 | if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ |
| 2734 | fossil_fatal("cannot set 'manifest' globally"); |
| 2735 | } |
| 2736 | if( unsetFlag || g.argc==4 ){ |
| 2737 | int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; |
| 2738 | if( pSetting[1].name && fossil_strncmp(pSetting[1].name, zName, n)==0 ){ |
| 2739 | Blob x; |
| 2740 | int i; |
| 2741 | blob_init(&x,0,0); |
| 2742 | for(i=0; pSetting[i].name; i++){ |
| 2743 | if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; |
| 2744 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -65,14 +65,10 @@ | |
| 65 | */ |
| 66 | static void db_err(const char *zFormat, ...){ |
| 67 | va_list ap; |
| 68 | char *z; |
| 69 | int rc = 1; |
| 70 | va_start(ap, zFormat); |
| 71 | z = vmprintf(zFormat, ap); |
| 72 | va_end(ap); |
| 73 | #ifdef FOSSIL_ENABLE_JSON |
| 74 | if( g.json.isJsonMode ){ |
| @@ -88,15 +84,14 @@ | |
| 84 | @ error Database\serror:\s%F(z) |
| 85 | cgi_reply(); |
| 86 | } |
| 87 | else if( g.cgiOutput ){ |
| 88 | g.cgiOutput = 0; |
| 89 | cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z); |
| 90 | cgi_reply(); |
| 91 | }else{ |
| 92 | fprintf(stderr, "%s: %s\n", g.argv[0], z); |
| 93 | } |
| 94 | free(z); |
| 95 | db_force_rollback(); |
| 96 | fossil_exit(rc); |
| 97 | } |
| @@ -2389,10 +2384,11 @@ | |
| 2384 | { "editor", 0, 32, 0, 0, "" }, |
| 2385 | { "empty-dirs", 0, 40, 1, 0, "" }, |
| 2386 | { "encoding-glob", 0, 40, 1, 0, "" }, |
| 2387 | { "gdiff-command", 0, 40, 0, 0, "gdiff" }, |
| 2388 | { "gmerge-command", 0, 40, 0, 0, "" }, |
| 2389 | { "hash-digits", 0, 5, 0, 0, "10" }, |
| 2390 | { "http-port", 0, 16, 0, 0, "8080" }, |
| 2391 | { "https-login", 0, 0, 0, 0, "off" }, |
| 2392 | { "ignore-glob", 0, 40, 1, 0, "" }, |
| 2393 | { "keep-glob", 0, 40, 1, 0, "" }, |
| 2394 | { "localauth", 0, 0, 0, 0, "off" }, |
| @@ -2565,10 +2561,13 @@ | |
| 2561 | ** gmerge-command A graphical merge conflict resolver command operating |
| 2562 | ** on four files. |
| 2563 | ** Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output" |
| 2564 | ** Ex: xxdiff "%original" "%baseline" "%merge" -M "%output" |
| 2565 | ** Ex: meld "%baseline" "%original" "%merge" "%output" |
| 2566 | ** |
| 2567 | ** hash-digits The number of hexadecimal digits of the SHA1 hash to |
| 2568 | ** display. (Default: 10; Minimum: 6) |
| 2569 | ** |
| 2570 | ** http-port The TCP/IP port number to use by the "server" |
| 2571 | ** and "ui" commands. Default: 8080 |
| 2572 | ** |
| 2573 | ** https-login Send login credentials using HTTPS instead of HTTP |
| @@ -2733,11 +2732,12 @@ | |
| 2732 | if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){ |
| 2733 | fossil_fatal("cannot set 'manifest' globally"); |
| 2734 | } |
| 2735 | if( unsetFlag || g.argc==4 ){ |
| 2736 | int isManifest = fossil_strcmp(pSetting->name, "manifest")==0; |
| 2737 | if( n!=strlen(pSetting[0].name) && pSetting[1].name && |
| 2738 | fossil_strncmp(pSetting[1].name, zName, n)==0 ){ |
| 2739 | Blob x; |
| 2740 | int i; |
| 2741 | blob_init(&x,0,0); |
| 2742 | for(i=0; pSetting[i].name; i++){ |
| 2743 | if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break; |
| 2744 |
+2
-2
| --- src/descendants.c | ||
| +++ src/descendants.c | ||
| @@ -439,11 +439,11 @@ | ||
| 439 | 439 | Stmt q; |
| 440 | 440 | int showAll = P("all")!=0; |
| 441 | 441 | int showClosed = P("closed")!=0; |
| 442 | 442 | |
| 443 | 443 | login_check_credentials(); |
| 444 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 444 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 445 | 445 | |
| 446 | 446 | if( !showAll ){ |
| 447 | 447 | style_submenu_element("All", "All", "leaves?all"); |
| 448 | 448 | } |
| 449 | 449 | if( !showClosed ){ |
| @@ -482,11 +482,11 @@ | ||
| 482 | 482 | }else if( !showAll ){ |
| 483 | 483 | blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); |
| 484 | 484 | } |
| 485 | 485 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 486 | 486 | blob_reset(&sql); |
| 487 | - www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0); | |
| 487 | + www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0, 0); | |
| 488 | 488 | db_finalize(&q); |
| 489 | 489 | @ <br /> |
| 490 | 490 | style_footer(); |
| 491 | 491 | } |
| 492 | 492 | |
| 493 | 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(); 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 @@ | ||
| 2234 | 2234 | int bBlame = g.zPath[0]!='a';/* True for BLAME output. False for ANNOTATE. */ |
| 2235 | 2235 | |
| 2236 | 2236 | /* Gather query parameters */ |
| 2237 | 2237 | showLog = atoi(PD("log","1")); |
| 2238 | 2238 | login_check_credentials(); |
| 2239 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 2239 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 2240 | 2240 | if( exclude_spiders("annotate") ) return; |
| 2241 | 2241 | load_control(); |
| 2242 | 2242 | mid = name_to_typed_rid(PD("checkin","0"),"ci"); |
| 2243 | 2243 | zFilename = P("filename"); |
| 2244 | 2244 | fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); |
| @@ -2294,11 +2294,11 @@ | ||
| 2294 | 2294 | } |
| 2295 | 2295 | if( iLimit>20 ){ |
| 2296 | 2296 | style_submenu_element("20 Ancestors", "20 Ancestors", |
| 2297 | 2297 | "%s", url_render(&url, "limit", "20", 0, 0)); |
| 2298 | 2298 | } |
| 2299 | - if( db_get_boolean("white-foreground", 0) ){ | |
| 2299 | + if( skin_white_foreground() ){ | |
| 2300 | 2300 | clr1 = 0xa04040; |
| 2301 | 2301 | clr2 = 0x4059a0; |
| 2302 | 2302 | }else{ |
| 2303 | 2303 | clr1 = 0xffb5b5; /* Recent changes: red (hot) */ |
| 2304 | 2304 | clr2 = 0xb5e0ff; /* Older changes: blue (cold) */ |
| @@ -2307,17 +2307,17 @@ | ||
| 2307 | 2307 | clr = gradient_color(clr1, clr2, ann.nVers-1, i); |
| 2308 | 2308 | ann.aVers[i].zBgColor = mprintf("#%06x", clr); |
| 2309 | 2309 | } |
| 2310 | 2310 | |
| 2311 | 2311 | 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); | |
| 2313 | 2313 | @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2> |
| 2314 | 2314 | @ <ol> |
| 2315 | 2315 | for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){ |
| 2316 | 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> | |
| 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 | 2319 | @ </span> |
| 2320 | 2320 | #if 0 |
| 2321 | 2321 | if( i>0 ){ |
| 2322 | 2322 | char *zLink = xhref("target='infowindow'", |
| 2323 | 2323 | "%R/fdiff?v1=%S&v2=%S&sbs=1", |
| @@ -2335,17 +2335,17 @@ | ||
| 2335 | 2335 | @ </ol> |
| 2336 | 2336 | @ <hr> |
| 2337 | 2337 | } |
| 2338 | 2338 | if( !ann.bLimit ){ |
| 2339 | 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> | |
| 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 | 2342 | iLimit = ann.nVers+10; |
| 2343 | 2343 | }else{ |
| 2344 | 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> | |
| 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 | 2347 | } |
| 2348 | 2348 | @ <pre> |
| 2349 | 2349 | for(i=0; i<ann.nOrig; i++){ |
| 2350 | 2350 | int iVers = ann.aOrig[i].iVers; |
| 2351 | 2351 | char *z = (char*)ann.aOrig[i].z; |
| @@ -2355,11 +2355,11 @@ | ||
| 2355 | 2355 | if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1; |
| 2356 | 2356 | |
| 2357 | 2357 | if( bBlame ){ |
| 2358 | 2358 | if( iVers>=0 ){ |
| 2359 | 2359 | 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); | |
| 2361 | 2361 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, |
| 2362 | 2362 | "<span style='background-color:%s'>" |
| 2363 | 2363 | "%s%.10s</a> %s</span> %13.13s:", |
| 2364 | 2364 | p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser); |
| 2365 | 2365 | fossil_free(zLink); |
| @@ -2367,11 +2367,11 @@ | ||
| 2367 | 2367 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", ""); |
| 2368 | 2368 | } |
| 2369 | 2369 | }else{ |
| 2370 | 2370 | if( iVers>=0 ){ |
| 2371 | 2371 | 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); | |
| 2373 | 2373 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, |
| 2374 | 2374 | "<span style='background-color:%s'>" |
| 2375 | 2375 | "%s%.10s</a> %s</span> %4d:", |
| 2376 | 2376 | p->zBgColor, zLink, p->zMUuid, p->zDate, i+1); |
| 2377 | 2377 | fossil_free(zLink); |
| @@ -2434,11 +2434,11 @@ | ||
| 2434 | 2434 | if( find_option("ignore-all-space","w",0)!=0 ){ |
| 2435 | 2435 | annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */ |
| 2436 | 2436 | } |
| 2437 | 2437 | fileVers = find_option("filevers",0,0)!=0; |
| 2438 | 2438 | db_must_be_within_tree(); |
| 2439 | - | |
| 2439 | + | |
| 2440 | 2440 | /* We should be done with options.. */ |
| 2441 | 2441 | verify_all_options(); |
| 2442 | 2442 | |
| 2443 | 2443 | if( g.argc<3 ) { |
| 2444 | 2444 | usage("FILENAME"); |
| 2445 | 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(); 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 @@ | ||
| 109 | 109 | /* Read content of zFile2 into memory */ |
| 110 | 110 | blob_zero(&file2); |
| 111 | 111 | if( file_wd_size(zFile2)<0 ){ |
| 112 | 112 | zName2 = NULL_DEVICE; |
| 113 | 113 | }else{ |
| 114 | - if( file_wd_islink(zFile2) ){ | |
| 114 | + if( file_wd_islink(0) ){ | |
| 115 | 115 | blob_read_link(&file2, zFile2); |
| 116 | 116 | }else{ |
| 117 | 117 | blob_read_from_file(&file2, zFile2); |
| 118 | 118 | } |
| 119 | 119 | zName2 = zName; |
| @@ -156,11 +156,11 @@ | ||
| 156 | 156 | } |
| 157 | 157 | glob_free(pBinary); |
| 158 | 158 | } |
| 159 | 159 | blob_zero(&file2); |
| 160 | 160 | if( file_wd_size(zFile2)>=0 ){ |
| 161 | - if( file_wd_islink(zFile2) ){ | |
| 161 | + if( file_wd_islink(0) ){ | |
| 162 | 162 | blob_read_link(&file2, zFile2); |
| 163 | 163 | }else{ |
| 164 | 164 | blob_read_from_file(&file2, zFile2); |
| 165 | 165 | } |
| 166 | 166 | } |
| @@ -841,11 +841,11 @@ | ||
| 841 | 841 | */ |
| 842 | 842 | void vpatch_page(void){ |
| 843 | 843 | const char *zFrom = P("from"); |
| 844 | 844 | const char *zTo = P("to"); |
| 845 | 845 | login_check_credentials(); |
| 846 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 846 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 847 | 847 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 848 | 848 | |
| 849 | 849 | cgi_set_content_type("text/plain"); |
| 850 | 850 | diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE); |
| 851 | 851 | } |
| 852 | 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(zFile2) ){ |
| 115 | blob_read_link(&file2, zFile2); |
| 116 | }else{ |
| 117 | blob_read_from_file(&file2, zFile2); |
| 118 | } |
| 119 | zName2 = zName; |
| @@ -156,11 +156,11 @@ | |
| 156 | } |
| 157 | glob_free(pBinary); |
| 158 | } |
| 159 | blob_zero(&file2); |
| 160 | if( file_wd_size(zFile2)>=0 ){ |
| 161 | if( file_wd_islink(zFile2) ){ |
| 162 | blob_read_link(&file2, zFile2); |
| 163 | }else{ |
| 164 | blob_read_from_file(&file2, zFile2); |
| 165 | } |
| 166 | } |
| @@ -841,11 +841,11 @@ | |
| 841 | */ |
| 842 | void vpatch_page(void){ |
| 843 | const char *zFrom = P("from"); |
| 844 | const char *zTo = P("to"); |
| 845 | login_check_credentials(); |
| 846 | if( !g.perm.Read ){ login_needed(); return; } |
| 847 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 848 | |
| 849 | cgi_set_content_type("text/plain"); |
| 850 | diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE); |
| 851 | } |
| 852 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -109,11 +109,11 @@ | |
| 109 | /* Read content of zFile2 into memory */ |
| 110 | blob_zero(&file2); |
| 111 | if( file_wd_size(zFile2)<0 ){ |
| 112 | zName2 = NULL_DEVICE; |
| 113 | }else{ |
| 114 | if( file_wd_islink(0) ){ |
| 115 | blob_read_link(&file2, zFile2); |
| 116 | }else{ |
| 117 | blob_read_from_file(&file2, zFile2); |
| 118 | } |
| 119 | zName2 = zName; |
| @@ -156,11 +156,11 @@ | |
| 156 | } |
| 157 | glob_free(pBinary); |
| 158 | } |
| 159 | blob_zero(&file2); |
| 160 | if( file_wd_size(zFile2)>=0 ){ |
| 161 | if( file_wd_islink(0) ){ |
| 162 | blob_read_link(&file2, zFile2); |
| 163 | }else{ |
| 164 | blob_read_from_file(&file2, zFile2); |
| 165 | } |
| 166 | } |
| @@ -841,11 +841,11 @@ | |
| 841 | */ |
| 842 | void vpatch_page(void){ |
| 843 | const char *zFrom = P("from"); |
| 844 | const char *zTo = P("to"); |
| 845 | login_check_credentials(); |
| 846 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 847 | if( zFrom==0 || zTo==0 ) fossil_redirect_home(); |
| 848 | |
| 849 | cgi_set_content_type("text/plain"); |
| 850 | diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE); |
| 851 | } |
| 852 |
+22
-7
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -106,10 +106,11 @@ | ||
| 106 | 106 | { "com", 3, "application/x-msdos-program" }, |
| 107 | 107 | { "cpio", 4, "application/x-cpio" }, |
| 108 | 108 | { "cpt", 3, "application/mac-compactpro" }, |
| 109 | 109 | { "csh", 3, "application/x-csh" }, |
| 110 | 110 | { "css", 3, "text/css" }, |
| 111 | + { "csv", 3, "text/csv" }, | |
| 111 | 112 | { "dcr", 3, "application/x-director" }, |
| 112 | 113 | { "deb", 3, "application/x-debian-package" }, |
| 113 | 114 | { "dir", 3, "application/x-director" }, |
| 114 | 115 | { "dl", 2, "video/dl" }, |
| 115 | 116 | { "dms", 3, "application/octet-stream" }, |
| @@ -182,10 +183,13 @@ | ||
| 182 | 183 | { "mpga", 4, "audio/mpeg" }, |
| 183 | 184 | { "ms", 2, "application/x-troff-ms" }, |
| 184 | 185 | { "msh", 3, "model/mesh" }, |
| 185 | 186 | { "nc", 2, "application/x-netcdf" }, |
| 186 | 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" }, | |
| 187 | 191 | { "ogg", 3, "application/ogg" }, |
| 188 | 192 | { "ogm", 3, "application/ogg" }, |
| 189 | 193 | { "pbm", 3, "image/x-portable-bitmap" }, |
| 190 | 194 | { "pdb", 3, "chemical/x-pdb" }, |
| 191 | 195 | { "pdf", 3, "application/pdf" }, |
| @@ -288,10 +292,24 @@ | ||
| 288 | 292 | { "xpm", 3, "image/x-xpixmap" }, |
| 289 | 293 | { "xwd", 3, "image/x-xwindowdump" }, |
| 290 | 294 | { "xyz", 3, "chemical/x-pdb" }, |
| 291 | 295 | { "zip", 3, "application/zip" }, |
| 292 | 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 | +} | |
| 293 | 311 | |
| 294 | 312 | /* |
| 295 | 313 | ** Guess the mime-type of a document based on its name. |
| 296 | 314 | */ |
| 297 | 315 | const char *mimetype_from_name(const char *zName){ |
| @@ -305,16 +323,11 @@ | ||
| 305 | 323 | #ifdef FOSSIL_DEBUG |
| 306 | 324 | /* This is test code to make sure the table above is in the correct |
| 307 | 325 | ** order |
| 308 | 326 | */ |
| 309 | 327 | 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(); | |
| 316 | 329 | return "ok"; |
| 317 | 330 | } |
| 318 | 331 | #endif |
| 319 | 332 | |
| 320 | 333 | z = zName; |
| @@ -353,10 +366,11 @@ | ||
| 353 | 366 | ** filename is special and verifies the integrity of the mimetype table. |
| 354 | 367 | ** It should return "ok". |
| 355 | 368 | */ |
| 356 | 369 | void mimetype_test_cmd(void){ |
| 357 | 370 | int i; |
| 371 | + mimetype_verify(); | |
| 358 | 372 | for(i=2; i<g.argc; i++){ |
| 359 | 373 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 360 | 374 | } |
| 361 | 375 | } |
| 362 | 376 | |
| @@ -366,10 +380,11 @@ | ||
| 366 | 380 | ** Show the built-in table used to guess embedded document mimetypes |
| 367 | 381 | ** from file suffixes. |
| 368 | 382 | */ |
| 369 | 383 | void mimetype_list_page(void){ |
| 370 | 384 | int i; |
| 385 | + mimetype_verify(); | |
| 371 | 386 | style_header("Mimetype List"); |
| 372 | 387 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 373 | 388 | @ suffixes and the following table to guess at the appropriate mimetype |
| 374 | 389 | @ for each document.</p> |
| 375 | 390 | @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> |
| @@ -528,11 +543,11 @@ | ||
| 528 | 543 | static const char *const azSuffix[] = { |
| 529 | 544 | "index.html", "index.wiki", "index.md" |
| 530 | 545 | }; |
| 531 | 546 | |
| 532 | 547 | login_check_credentials(); |
| 533 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 548 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 534 | 549 | blob_init(&title, 0, 0); |
| 535 | 550 | db_begin_transaction(); |
| 536 | 551 | while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ |
| 537 | 552 | zName = PD("name", "tip/index.wiki"); |
| 538 | 553 | for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 539 | 554 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -106,10 +106,11 @@ | |
| 106 | { "com", 3, "application/x-msdos-program" }, |
| 107 | { "cpio", 4, "application/x-cpio" }, |
| 108 | { "cpt", 3, "application/mac-compactpro" }, |
| 109 | { "csh", 3, "application/x-csh" }, |
| 110 | { "css", 3, "text/css" }, |
| 111 | { "dcr", 3, "application/x-director" }, |
| 112 | { "deb", 3, "application/x-debian-package" }, |
| 113 | { "dir", 3, "application/x-director" }, |
| 114 | { "dl", 2, "video/dl" }, |
| 115 | { "dms", 3, "application/octet-stream" }, |
| @@ -182,10 +183,13 @@ | |
| 182 | { "mpga", 4, "audio/mpeg" }, |
| 183 | { "ms", 2, "application/x-troff-ms" }, |
| 184 | { "msh", 3, "model/mesh" }, |
| 185 | { "nc", 2, "application/x-netcdf" }, |
| 186 | { "oda", 3, "application/oda" }, |
| 187 | { "ogg", 3, "application/ogg" }, |
| 188 | { "ogm", 3, "application/ogg" }, |
| 189 | { "pbm", 3, "image/x-portable-bitmap" }, |
| 190 | { "pdb", 3, "chemical/x-pdb" }, |
| 191 | { "pdf", 3, "application/pdf" }, |
| @@ -288,10 +292,24 @@ | |
| 288 | { "xpm", 3, "image/x-xpixmap" }, |
| 289 | { "xwd", 3, "image/x-xwindowdump" }, |
| 290 | { "xyz", 3, "chemical/x-pdb" }, |
| 291 | { "zip", 3, "application/zip" }, |
| 292 | }; |
| 293 | |
| 294 | /* |
| 295 | ** Guess the mime-type of a document based on its name. |
| 296 | */ |
| 297 | const char *mimetype_from_name(const char *zName){ |
| @@ -305,16 +323,11 @@ | |
| 305 | #ifdef FOSSIL_DEBUG |
| 306 | /* This is test code to make sure the table above is in the correct |
| 307 | ** order |
| 308 | */ |
| 309 | if( fossil_strcmp(zName, "mimetype-test")==0 ){ |
| 310 | for(i=1; i<ArraySize(aMime); i++){ |
| 311 | if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ |
| 312 | fossil_fatal("mimetypes out of sequence: %s before %s", |
| 313 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 314 | } |
| 315 | } |
| 316 | return "ok"; |
| 317 | } |
| 318 | #endif |
| 319 | |
| 320 | z = zName; |
| @@ -353,10 +366,11 @@ | |
| 353 | ** filename is special and verifies the integrity of the mimetype table. |
| 354 | ** It should return "ok". |
| 355 | */ |
| 356 | void mimetype_test_cmd(void){ |
| 357 | int i; |
| 358 | for(i=2; i<g.argc; i++){ |
| 359 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 360 | } |
| 361 | } |
| 362 | |
| @@ -366,10 +380,11 @@ | |
| 366 | ** Show the built-in table used to guess embedded document mimetypes |
| 367 | ** from file suffixes. |
| 368 | */ |
| 369 | void mimetype_list_page(void){ |
| 370 | int i; |
| 371 | style_header("Mimetype List"); |
| 372 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 373 | @ suffixes and the following table to guess at the appropriate mimetype |
| 374 | @ for each document.</p> |
| 375 | @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> |
| @@ -528,11 +543,11 @@ | |
| 528 | static const char *const azSuffix[] = { |
| 529 | "index.html", "index.wiki", "index.md" |
| 530 | }; |
| 531 | |
| 532 | login_check_credentials(); |
| 533 | if( !g.perm.Read ){ login_needed(); return; } |
| 534 | blob_init(&title, 0, 0); |
| 535 | db_begin_transaction(); |
| 536 | while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ |
| 537 | zName = PD("name", "tip/index.wiki"); |
| 538 | for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 539 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -106,10 +106,11 @@ | |
| 106 | { "com", 3, "application/x-msdos-program" }, |
| 107 | { "cpio", 4, "application/x-cpio" }, |
| 108 | { "cpt", 3, "application/mac-compactpro" }, |
| 109 | { "csh", 3, "application/x-csh" }, |
| 110 | { "css", 3, "text/css" }, |
| 111 | { "csv", 3, "text/csv" }, |
| 112 | { "dcr", 3, "application/x-director" }, |
| 113 | { "deb", 3, "application/x-debian-package" }, |
| 114 | { "dir", 3, "application/x-director" }, |
| 115 | { "dl", 2, "video/dl" }, |
| 116 | { "dms", 3, "application/octet-stream" }, |
| @@ -182,10 +183,13 @@ | |
| 183 | { "mpga", 4, "audio/mpeg" }, |
| 184 | { "ms", 2, "application/x-troff-ms" }, |
| 185 | { "msh", 3, "model/mesh" }, |
| 186 | { "nc", 2, "application/x-netcdf" }, |
| 187 | { "oda", 3, "application/oda" }, |
| 188 | { "odp", 3, "application/vnd.oasis.opendocument.presentation" }, |
| 189 | { "ods", 3, "application/vnd.oasis.opendocument.spreadsheet" }, |
| 190 | { "odt", 3, "application/vnd.oasis.opendocument.text" }, |
| 191 | { "ogg", 3, "application/ogg" }, |
| 192 | { "ogm", 3, "application/ogg" }, |
| 193 | { "pbm", 3, "image/x-portable-bitmap" }, |
| 194 | { "pdb", 3, "chemical/x-pdb" }, |
| 195 | { "pdf", 3, "application/pdf" }, |
| @@ -288,10 +292,24 @@ | |
| 292 | { "xpm", 3, "image/x-xpixmap" }, |
| 293 | { "xwd", 3, "image/x-xwindowdump" }, |
| 294 | { "xyz", 3, "chemical/x-pdb" }, |
| 295 | { "zip", 3, "application/zip" }, |
| 296 | }; |
| 297 | |
| 298 | /* |
| 299 | ** Verify that all entries in the aMime[] table are in sorted order. |
| 300 | ** Abort with a fatal error if any is out-of-order. |
| 301 | */ |
| 302 | static void mimetype_verify(void){ |
| 303 | int i; |
| 304 | for(i=1; i<ArraySize(aMime); i++){ |
| 305 | if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){ |
| 306 | fossil_fatal("mimetypes out of sequence: %s before %s", |
| 307 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 308 | } |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | /* |
| 313 | ** Guess the mime-type of a document based on its name. |
| 314 | */ |
| 315 | const char *mimetype_from_name(const char *zName){ |
| @@ -305,16 +323,11 @@ | |
| 323 | #ifdef FOSSIL_DEBUG |
| 324 | /* This is test code to make sure the table above is in the correct |
| 325 | ** order |
| 326 | */ |
| 327 | if( fossil_strcmp(zName, "mimetype-test")==0 ){ |
| 328 | mimetype_verify(); |
| 329 | return "ok"; |
| 330 | } |
| 331 | #endif |
| 332 | |
| 333 | z = zName; |
| @@ -353,10 +366,11 @@ | |
| 366 | ** filename is special and verifies the integrity of the mimetype table. |
| 367 | ** It should return "ok". |
| 368 | */ |
| 369 | void mimetype_test_cmd(void){ |
| 370 | int i; |
| 371 | mimetype_verify(); |
| 372 | for(i=2; i<g.argc; i++){ |
| 373 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 374 | } |
| 375 | } |
| 376 | |
| @@ -366,10 +380,11 @@ | |
| 380 | ** Show the built-in table used to guess embedded document mimetypes |
| 381 | ** from file suffixes. |
| 382 | */ |
| 383 | void mimetype_list_page(void){ |
| 384 | int i; |
| 385 | mimetype_verify(); |
| 386 | style_header("Mimetype List"); |
| 387 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 388 | @ suffixes and the following table to guess at the appropriate mimetype |
| 389 | @ for each document.</p> |
| 390 | @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'> |
| @@ -528,11 +543,11 @@ | |
| 543 | static const char *const azSuffix[] = { |
| 544 | "index.html", "index.wiki", "index.md" |
| 545 | }; |
| 546 | |
| 547 | login_check_credentials(); |
| 548 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 549 | blob_init(&title, 0, 0); |
| 550 | db_begin_transaction(); |
| 551 | while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ |
| 552 | zName = PD("name", "tip/index.wiki"); |
| 553 | for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 554 |
+142
-91
| --- src/event.c | ||
| +++ src/event.c | ||
| @@ -15,78 +15,90 @@ | ||
| 15 | 15 | ** |
| 16 | 16 | ******************************************************************************* |
| 17 | 17 | ** |
| 18 | 18 | ** This file contains code to do formatting of event messages: |
| 19 | 19 | ** |
| 20 | +** Technical Notes | |
| 20 | 21 | ** Milestones |
| 21 | 22 | ** Blog posts |
| 22 | 23 | ** New articles |
| 23 | 24 | ** Process checkpoints |
| 24 | 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. | |
| 25 | 33 | */ |
| 26 | 34 | #include "config.h" |
| 27 | 35 | #include <assert.h> |
| 28 | 36 | #include <ctype.h> |
| 29 | 37 | #include "event.h" |
| 30 | 38 | |
| 31 | 39 | /* |
| 32 | -** Output a hyperlink to an event given its tagid. | |
| 40 | +** Output a hyperlink to an technote given its tagid. | |
| 33 | 41 | */ |
| 34 | 42 | 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", | |
| 37 | 45 | 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); | |
| 40 | 48 | } |
| 41 | 49 | |
| 42 | 50 | /* |
| 51 | +** WEBPAGE: technote | |
| 43 | 52 | ** WEBPAGE: event |
| 44 | -** URL: /event | |
| 53 | +** | |
| 54 | +** Display a "technical note" or "tech-note" (formerly called an "event"). | |
| 55 | +** | |
| 45 | 56 | ** PARAMETERS: |
| 46 | 57 | ** |
| 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. | |
| 50 | 61 | ** |
| 51 | 62 | ** Display an existing event identified by EVENTID |
| 52 | 63 | */ |
| 53 | 64 | void event_page(void){ |
| 54 | 65 | int rid = 0; /* rid of the event artifact */ |
| 55 | 66 | char *zUuid; /* UUID corresponding to rid */ |
| 56 | - const char *zEventId; /* Event identifier */ | |
| 67 | + const char *zId; /* Event identifier */ | |
| 57 | 68 | const char *zVerbose; /* Value of verbose option */ |
| 58 | - char *zETime; /* Time of the event */ | |
| 69 | + char *zETime; /* Time of the tech-note */ | |
| 59 | 70 | char *zATime; /* Time the artifact was created */ |
| 60 | 71 | 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 */ | |
| 65 | 76 | 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 */ | |
| 67 | 78 | int verboseFlag; /* True to show details */ |
| 79 | + const char *zMimetype = 0; /* Mimetype of the document */ | |
| 68 | 80 | |
| 69 | 81 | |
| 70 | - /* wiki-read privilege is needed in order to read events. | |
| 82 | + /* wiki-read privilege is needed in order to read tech-notes. | |
| 71 | 83 | */ |
| 72 | 84 | login_check_credentials(); |
| 73 | 85 | if( !g.perm.RdWiki ){ |
| 74 | - login_needed(); | |
| 86 | + login_needed(g.anon.RdWiki); | |
| 75 | 87 | return; |
| 76 | 88 | } |
| 77 | 89 | |
| 78 | - zEventId = P("name"); | |
| 79 | - if( zEventId==0 ){ fossil_redirect_home(); return; } | |
| 90 | + zId = P("name"); | |
| 91 | + if( zId==0 ){ fossil_redirect_home(); return; } | |
| 80 | 92 | zUuid = (char*)P("aid"); |
| 81 | 93 | specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; |
| 82 | 94 | rid = nextRid = prevRid = 0; |
| 83 | 95 | db_prepare(&q1, |
| 84 | 96 | "SELECT rid FROM tagxref" |
| 85 | 97 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" |
| 86 | 98 | " ORDER BY mtime DESC", |
| 87 | - zEventId | |
| 99 | + zId | |
| 88 | 100 | ); |
| 89 | 101 | while( db_step(&q1)==SQLITE_ROW ){ |
| 90 | 102 | nextRid = rid; |
| 91 | 103 | rid = db_column_int(&q1, 0); |
| 92 | 104 | if( specRid==0 || specRid==rid ){ |
| @@ -96,12 +108,12 @@ | ||
| 96 | 108 | break; |
| 97 | 109 | } |
| 98 | 110 | } |
| 99 | 111 | db_finalize(&q1); |
| 100 | 112 | 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>. | |
| 103 | 115 | style_footer(); |
| 104 | 116 | return; |
| 105 | 117 | } |
| 106 | 118 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 107 | 119 | zVerbose = P("v"); |
| @@ -113,156 +125,188 @@ | ||
| 113 | 125 | } |
| 114 | 126 | verboseFlag = (zVerbose!=0) && !is_false(zVerbose); |
| 115 | 127 | |
| 116 | 128 | /* Extract the event content. |
| 117 | 129 | */ |
| 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); | |
| 121 | 133 | } |
| 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 | + } | |
| 125 | 148 | }else{ |
| 126 | - style_header("Event %S", zEventId); | |
| 149 | + blob_appendf(&title, "Tech-note %S", zId); | |
| 127 | 150 | tail = fullbody; |
| 128 | 151 | } |
| 152 | + style_header("%s", blob_str(&title)); | |
| 129 | 153 | 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); | |
| 132 | 155 | } |
| 133 | - zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); | |
| 134 | - style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId); | |
| 156 | + zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); | |
| 157 | + style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId); | |
| 135 | 158 | if( g.perm.Hyperlink ){ |
| 136 | 159 | if( verboseFlag ){ |
| 137 | - style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s", | |
| 138 | - zEventId, zUuid); | |
| 160 | + style_submenu_element("Plain", 0, | |
| 161 | + "%R/technote?name=%!S&aid=%s&mimetype=text/plain", | |
| 162 | + zId, zUuid); | |
| 139 | 163 | if( nextRid ){ |
| 140 | 164 | char *zNext; |
| 141 | 165 | zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); |
| 142 | - style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v", | |
| 143 | - zEventId, zNext); | |
| 166 | + style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v", | |
| 167 | + zId, zNext); | |
| 144 | 168 | free(zNext); |
| 145 | 169 | } |
| 146 | 170 | if( prevRid ){ |
| 147 | 171 | char *zPrev; |
| 148 | 172 | zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); |
| 149 | - style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v", | |
| 150 | - zEventId, zPrev); | |
| 173 | + style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v", | |
| 174 | + zId, zPrev); | |
| 151 | 175 | free(zPrev); |
| 152 | 176 | } |
| 153 | 177 | }else{ |
| 154 | - style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v", | |
| 155 | - zEventId, zUuid); | |
| 178 | + style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v", | |
| 179 | + zId, zUuid); | |
| 156 | 180 | } |
| 157 | 181 | } |
| 158 | 182 | |
| 159 | 183 | if( verboseFlag && g.perm.Hyperlink ){ |
| 160 | 184 | int i; |
| 161 | 185 | const char *zClr = 0; |
| 162 | 186 | Blob comment; |
| 163 | 187 | |
| 164 | - zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); | |
| 165 | - @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at | |
| 188 | + zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate); | |
| 189 | + @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at | |
| 166 | 190 | @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>] |
| 167 | - @ entered by user <b>%h(pEvent->zUser)</b> on | |
| 191 | + @ entered by user <b>%h(pTNote->zUser)</b> on | |
| 168 | 192 | @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p> |
| 169 | 193 | @ <blockquote> |
| 170 | - for(i=0; i<pEvent->nTag; i++){ | |
| 171 | - if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ | |
| 172 | - zClr = pEvent->aTag[i].zValue; | |
| 194 | + for(i=0; i<pTNote->nTag; i++){ | |
| 195 | + if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){ | |
| 196 | + zClr = pTNote->aTag[i].zValue; | |
| 173 | 197 | } |
| 174 | 198 | } |
| 175 | 199 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 176 | 200 | if( zClr ){ |
| 177 | 201 | @ <div style="background-color: %h(zClr);"> |
| 178 | 202 | }else{ |
| 179 | 203 | @ <div> |
| 180 | 204 | } |
| 181 | - blob_init(&comment, pEvent->zComment, -1); | |
| 205 | + blob_init(&comment, pTNote->zComment, -1); | |
| 182 | 206 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 183 | 207 | blob_reset(&comment); |
| 184 | 208 | @ </div> |
| 185 | 209 | @ </blockquote><hr /> |
| 186 | 210 | } |
| 187 | 211 | |
| 188 | - wiki_convert(&tail, 0, 0); | |
| 212 | + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ | |
| 213 | + wiki_convert(&fullbody, 0, 0); | |
| 214 | + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ | |
| 215 | + cgi_append_content(blob_buffer(&tail), blob_size(&tail)); | |
| 216 | + }else{ | |
| 217 | + @ <pre> | |
| 218 | + @ %h(blob_str(&fullbody)) | |
| 219 | + @ </pre> | |
| 220 | + } | |
| 189 | 221 | style_footer(); |
| 190 | - manifest_destroy(pEvent); | |
| 222 | + manifest_destroy(pTNote); | |
| 191 | 223 | } |
| 192 | 224 | |
| 193 | 225 | /* |
| 226 | +** WEBPAGE: technoteedit | |
| 194 | 227 | ** WEBPAGE: eventedit |
| 195 | -** URL: /eventedit?name=EVENTID | |
| 228 | +** | |
| 229 | +** Revise or create a technical note (formerly called an 'event'). | |
| 230 | +** | |
| 231 | +** Parameters: | |
| 196 | 232 | ** |
| 197 | -** Edit an event. If name is omitted, create a new event. | |
| 233 | +** name=ID Hex hash ID of the tech-note. If omitted, a new | |
| 234 | +** tech-note is created. | |
| 198 | 235 | */ |
| 199 | 236 | void eventedit_page(void){ |
| 200 | 237 | char *zTag; |
| 201 | 238 | int rid = 0; |
| 202 | 239 | Blob event; |
| 203 | - const char *zEventId; | |
| 240 | + const char *zId; | |
| 204 | 241 | int n; |
| 205 | 242 | const char *z; |
| 206 | 243 | char *zBody = (char*)P("w"); |
| 207 | 244 | char *zETime = (char*)P("t"); |
| 208 | 245 | const char *zComment = P("c"); |
| 209 | 246 | const char *zTags = P("g"); |
| 210 | 247 | const char *zClr; |
| 248 | + const char *zMimetype = P("mimetype"); | |
| 249 | + int isNew = 0; | |
| 211 | 250 | |
| 212 | 251 | if( zBody ){ |
| 213 | 252 | zBody = mprintf("%s", zBody); |
| 214 | 253 | } |
| 215 | 254 | login_check_credentials(); |
| 216 | - zEventId = P("name"); | |
| 217 | - if( zEventId==0 ){ | |
| 218 | - zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))"); | |
| 255 | + zId = P("name"); | |
| 256 | + if( zId==0 ){ | |
| 257 | + zId = db_text(0, "SELECT lower(hex(randomblob(20)))"); | |
| 258 | + isNew = 1; | |
| 219 | 259 | }else{ |
| 220 | - int nEventId = strlen(zEventId); | |
| 221 | - if( nEventId!=40 || !validate16(zEventId, 40) ){ | |
| 260 | + int nId = strlen(zId); | |
| 261 | + if( !validate16(zId, nId) ){ | |
| 222 | 262 | fossil_redirect_home(); |
| 223 | 263 | return; |
| 224 | 264 | } |
| 225 | 265 | } |
| 226 | - zTag = mprintf("event-%s", zEventId); | |
| 266 | + zTag = mprintf("event-%s", zId); | |
| 227 | 267 | rid = db_int(0, |
| 228 | 268 | "SELECT rid FROM tagxref" |
| 229 | - " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" | |
| 269 | + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')" | |
| 230 | 270 | " ORDER BY mtime DESC", zTag |
| 231 | 271 | ); |
| 232 | 272 | free(zTag); |
| 233 | 273 | |
| 234 | 274 | /* Need both check-in and wiki-write or wiki-create privileges in order |
| 235 | 275 | ** to edit/create an event. |
| 236 | 276 | */ |
| 237 | 277 | if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 238 | - login_needed(); | |
| 278 | + login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki)); | |
| 239 | 279 | return; |
| 240 | 280 | } |
| 241 | 281 | |
| 242 | 282 | /* Figure out the color */ |
| 243 | 283 | if( rid ){ |
| 244 | - zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); | |
| 284 | + zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); | |
| 245 | 285 | }else{ |
| 246 | 286 | zClr = ""; |
| 287 | + isNew = 1; | |
| 247 | 288 | } |
| 248 | 289 | zClr = PD("clr",zClr); |
| 249 | 290 | if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); |
| 250 | 291 | |
| 251 | 292 | |
| 252 | 293 | /* If editing an existing event, extract the key fields to use as |
| 253 | 294 | ** a starting point for the edit. |
| 254 | 295 | */ |
| 255 | - if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ | |
| 256 | - Manifest *pEvent; | |
| 257 | - pEvent = manifest_get(rid, CFTYPE_EVENT, 0); | |
| 258 | - if( pEvent && pEvent->type==CFTYPE_EVENT ){ | |
| 259 | - if( zBody==0 ) zBody = pEvent->zWiki; | |
| 296 | + if( rid | |
| 297 | + && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0) | |
| 298 | + ){ | |
| 299 | + Manifest *pTNote; | |
| 300 | + pTNote = manifest_get(rid, CFTYPE_EVENT, 0); | |
| 301 | + if( pTNote && pTNote->type==CFTYPE_EVENT ){ | |
| 302 | + if( zBody==0 ) zBody = pTNote->zWiki; | |
| 260 | 303 | if( zETime==0 ){ |
| 261 | - zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); | |
| 304 | + zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); | |
| 262 | 305 | } |
| 263 | - if( zComment==0 ) zComment = pEvent->zComment; | |
| 306 | + if( zComment==0 ) zComment = pTNote->zComment; | |
| 307 | + if( zMimetype==0 ) zMimetype = pTNote->zMimetype; | |
| 264 | 308 | } |
| 265 | 309 | if( zTags==0 ){ |
| 266 | 310 | zTags = db_text(0, |
| 267 | 311 | "SELECT group_concat(substr(tagname,5),', ')" |
| 268 | 312 | " FROM tagxref, tag" |
| @@ -276,11 +320,11 @@ | ||
| 276 | 320 | zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); |
| 277 | 321 | if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ |
| 278 | 322 | char *zDate; |
| 279 | 323 | Blob cksum; |
| 280 | 324 | int nrid, n; |
| 281 | - blob_zero(&event); | |
| 325 | + blob_init(&event, 0, 0); | |
| 282 | 326 | db_begin_transaction(); |
| 283 | 327 | login_verify_csrf_secret(); |
| 284 | 328 | while( fossil_isspace(zComment[0]) ) zComment++; |
| 285 | 329 | n = strlen(zComment); |
| 286 | 330 | while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } |
| @@ -289,17 +333,20 @@ | ||
| 289 | 333 | } |
| 290 | 334 | zDate = date_in_standard_format("now"); |
| 291 | 335 | blob_appendf(&event, "D %s\n", zDate); |
| 292 | 336 | free(zDate); |
| 293 | 337 | zETime[10] = 'T'; |
| 294 | - blob_appendf(&event, "E %s %s\n", zETime, zEventId); | |
| 338 | + blob_appendf(&event, "E %s %s\n", zETime, zId); | |
| 295 | 339 | zETime[10] = ' '; |
| 296 | 340 | if( rid ){ |
| 297 | 341 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 298 | 342 | blob_appendf(&event, "P %s\n", zUuid); |
| 299 | 343 | free(zUuid); |
| 300 | 344 | } |
| 345 | + if( zMimetype && zMimetype[0] ){ | |
| 346 | + blob_appendf(&event, "N %s\n", zMimetype); | |
| 347 | + } | |
| 301 | 348 | if( zClr && zClr[0] ){ |
| 302 | 349 | blob_appendf(&event, "T +bgcolor * %F\n", zClr); |
| 303 | 350 | } |
| 304 | 351 | if( zTags && zTags[0] ){ |
| 305 | 352 | Blob tags, one; |
| @@ -350,22 +397,26 @@ | ||
| 350 | 397 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 351 | 398 | manifest_crosslink(nrid, &event, MC_NONE); |
| 352 | 399 | assert( blob_is_reset(&event) ); |
| 353 | 400 | content_deltify(rid, nrid, 0); |
| 354 | 401 | db_end_transaction(0); |
| 355 | - cgi_redirectf("event?name=%T", zEventId); | |
| 402 | + cgi_redirectf("technote?name=%T", zId); | |
| 356 | 403 | } |
| 357 | 404 | if( P("cancel")!=0 ){ |
| 358 | - cgi_redirectf("event?name=%T", zEventId); | |
| 405 | + cgi_redirectf("technote?name=%T", zId); | |
| 359 | 406 | return; |
| 360 | 407 | } |
| 361 | 408 | if( zBody==0 ){ |
| 362 | - zBody = mprintf("<i>Event Text</i>"); | |
| 409 | + zBody = mprintf("Insert new content here..."); | |
| 363 | 410 | } |
| 364 | - style_header("Edit Event %S", zEventId); | |
| 411 | + if( isNew ){ | |
| 412 | + style_header("New Tech-note %S", zId); | |
| 413 | + }else{ | |
| 414 | + style_header("Edit Tech-note %S", zId); | |
| 415 | + } | |
| 365 | 416 | if( P("preview")!=0 ){ |
| 366 | - Blob title, tail, com; | |
| 417 | + Blob com; | |
| 367 | 418 | @ <p><b>Timeline comment preview:</b></p> |
| 368 | 419 | @ <blockquote> |
| 369 | 420 | @ <table border="0"> |
| 370 | 421 | if( zClr && zClr[0] ){ |
| 371 | 422 | @ <tr><td style="background-color: %h(zClr);"> |
| @@ -377,55 +428,55 @@ | ||
| 377 | 428 | wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS); |
| 378 | 429 | @ </td></tr></table> |
| 379 | 430 | @ </blockquote> |
| 380 | 431 | @ <p><b>Page content preview:</b><p> |
| 381 | 432 | @ <blockquote> |
| 382 | - blob_zero(&event); | |
| 433 | + blob_init(&event, 0, 0); | |
| 383 | 434 | blob_append(&event, zBody, -1); |
| 384 | - if( wiki_find_title(&event, &title, &tail) ){ | |
| 385 | - @ <h2 align="center">%h(blob_str(&title))</h2> | |
| 386 | - wiki_convert(&tail, 0, 0); | |
| 387 | - }else{ | |
| 388 | - wiki_convert(&event, 0, 0); | |
| 389 | - } | |
| 435 | + wiki_render_by_mimetype(&event, zMimetype); | |
| 390 | 436 | @ </blockquote><hr /> |
| 391 | 437 | blob_reset(&event); |
| 392 | 438 | } |
| 393 | 439 | for(n=2, z=zBody; z[0]; z++){ |
| 394 | 440 | if( z[0]=='\n' ) n++; |
| 395 | 441 | } |
| 396 | 442 | if( n<20 ) n = 20; |
| 397 | 443 | if( n>40 ) n = 40; |
| 398 | - @ <form method="post" action="%s(g.zTop)/eventedit"><div> | |
| 444 | + @ <form method="post" action="%R/technoteedit"><div> | |
| 399 | 445 | login_insert_csrf_secret(); |
| 400 | - @ <input type="hidden" name="name" value="%h(zEventId)" /> | |
| 446 | + @ <input type="hidden" name="name" value="%h(zId)" /> | |
| 401 | 447 | @ <table border="0" cellspacing="10"> |
| 402 | 448 | |
| 403 | - @ <tr><th align="right" valign="top">Event Time (UTC):</th> | |
| 449 | + @ <tr><th align="right" valign="top">Timestamp (UTC):</th> | |
| 404 | 450 | @ <td valign="top"> |
| 405 | 451 | @ <input type="text" name="t" size="25" value="%h(zETime)" /> |
| 406 | 452 | @ </td></tr> |
| 407 | 453 | |
| 408 | - @ <tr><th align="right" valign="top">Timeline Comment:</th> | |
| 454 | + @ <tr><th align="right" valign="top">Timeline Comment:</th> | |
| 409 | 455 | @ <td valign="top"> |
| 410 | - @ <textarea name="c" class="eventedit" cols="80" | |
| 456 | + @ <textarea name="c" class="technoteedit" cols="80" | |
| 411 | 457 | @ rows="3" wrap="virtual">%h(zComment)</textarea> |
| 412 | 458 | @ </td></tr> |
| 413 | 459 | |
| 414 | - @ <tr><th align="right" valign="top">Background Color:</th> | |
| 460 | + @ <tr><th align="right" valign="top">Timeline Background Color:</th> | |
| 415 | 461 | @ <td valign="top"> |
| 416 | 462 | render_color_chooser(0, zClr, 0, "clr", "cclr"); |
| 417 | 463 | @ </td></tr> |
| 418 | 464 | |
| 419 | 465 | @ <tr><th align="right" valign="top">Tags:</th> |
| 420 | 466 | @ <td valign="top"> |
| 421 | 467 | @ <input type="text" name="g" size="40" value="%h(zTags)" /> |
| 422 | 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> | |
| 423 | 474 | |
| 424 | 475 | @ <tr><th align="right" valign="top">Page Content:</th> |
| 425 | 476 | @ <td valign="top"> |
| 426 | - @ <textarea name="w" class="eventedit" cols="80" | |
| 477 | + @ <textarea name="w" class="technoteedit" cols="80" | |
| 427 | 478 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 428 | 479 | @ </td></tr> |
| 429 | 480 | |
| 430 | 481 | @ <tr><td colspan="2"> |
| 431 | 482 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 432 | 483 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -15,78 +15,90 @@ | |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** This file contains code to do formatting of event messages: |
| 19 | ** |
| 20 | ** Milestones |
| 21 | ** Blog posts |
| 22 | ** New articles |
| 23 | ** Process checkpoints |
| 24 | ** Announcements |
| 25 | */ |
| 26 | #include "config.h" |
| 27 | #include <assert.h> |
| 28 | #include <ctype.h> |
| 29 | #include "event.h" |
| 30 | |
| 31 | /* |
| 32 | ** Output a hyperlink to an event given its tagid. |
| 33 | */ |
| 34 | void hyperlink_to_event_tagid(int tagid){ |
| 35 | char *zEventId; |
| 36 | zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", |
| 37 | tagid); |
| 38 | @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>] |
| 39 | free(zEventId); |
| 40 | } |
| 41 | |
| 42 | /* |
| 43 | ** WEBPAGE: event |
| 44 | ** URL: /event |
| 45 | ** PARAMETERS: |
| 46 | ** |
| 47 | ** name=EVENTID // Identify the event to display EVENTID must be complete |
| 48 | ** aid=ARTIFACTID // Which specific version of the event. Optional. |
| 49 | ** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. |
| 50 | ** |
| 51 | ** Display an existing event identified by EVENTID |
| 52 | */ |
| 53 | void event_page(void){ |
| 54 | int rid = 0; /* rid of the event artifact */ |
| 55 | char *zUuid; /* UUID corresponding to rid */ |
| 56 | const char *zEventId; /* Event identifier */ |
| 57 | const char *zVerbose; /* Value of verbose option */ |
| 58 | char *zETime; /* Time of the event */ |
| 59 | char *zATime; /* Time the artifact was created */ |
| 60 | int specRid; /* rid specified by aid= parameter */ |
| 61 | int prevRid, nextRid; /* Previous or next edits of this event */ |
| 62 | Manifest *pEvent; /* Parsed event artifact */ |
| 63 | Blob fullbody; /* Complete content of the event body */ |
| 64 | Blob title; /* Title extracted from the event body */ |
| 65 | Blob tail; /* Event body that comes after the title */ |
| 66 | Stmt q1; /* Query to search for the event */ |
| 67 | int verboseFlag; /* True to show details */ |
| 68 | |
| 69 | |
| 70 | /* wiki-read privilege is needed in order to read events. |
| 71 | */ |
| 72 | login_check_credentials(); |
| 73 | if( !g.perm.RdWiki ){ |
| 74 | login_needed(); |
| 75 | return; |
| 76 | } |
| 77 | |
| 78 | zEventId = P("name"); |
| 79 | if( zEventId==0 ){ fossil_redirect_home(); return; } |
| 80 | zUuid = (char*)P("aid"); |
| 81 | specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; |
| 82 | rid = nextRid = prevRid = 0; |
| 83 | db_prepare(&q1, |
| 84 | "SELECT rid FROM tagxref" |
| 85 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" |
| 86 | " ORDER BY mtime DESC", |
| 87 | zEventId |
| 88 | ); |
| 89 | while( db_step(&q1)==SQLITE_ROW ){ |
| 90 | nextRid = rid; |
| 91 | rid = db_column_int(&q1, 0); |
| 92 | if( specRid==0 || specRid==rid ){ |
| @@ -96,12 +108,12 @@ | |
| 96 | break; |
| 97 | } |
| 98 | } |
| 99 | db_finalize(&q1); |
| 100 | if( rid==0 || (specRid!=0 && specRid!=rid) ){ |
| 101 | style_header("No Such Event"); |
| 102 | @ Cannot locate specified event |
| 103 | style_footer(); |
| 104 | return; |
| 105 | } |
| 106 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 107 | zVerbose = P("v"); |
| @@ -113,156 +125,188 @@ | |
| 113 | } |
| 114 | verboseFlag = (zVerbose!=0) && !is_false(zVerbose); |
| 115 | |
| 116 | /* Extract the event content. |
| 117 | */ |
| 118 | pEvent = manifest_get(rid, CFTYPE_EVENT, 0); |
| 119 | if( pEvent==0 ){ |
| 120 | fossil_fatal("Object #%d is not an event", rid); |
| 121 | } |
| 122 | blob_init(&fullbody, pEvent->zWiki, -1); |
| 123 | if( wiki_find_title(&fullbody, &title, &tail) ){ |
| 124 | style_header("%s", blob_str(&title)); |
| 125 | }else{ |
| 126 | style_header("Event %S", zEventId); |
| 127 | tail = fullbody; |
| 128 | } |
| 129 | if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ |
| 130 | style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", |
| 131 | g.zTop, zEventId); |
| 132 | } |
| 133 | zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); |
| 134 | style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zEventId); |
| 135 | if( g.perm.Hyperlink ){ |
| 136 | if( verboseFlag ){ |
| 137 | style_submenu_element("Plain", 0, "%R/event?name=%.20s&aid=%s", |
| 138 | zEventId, zUuid); |
| 139 | if( nextRid ){ |
| 140 | char *zNext; |
| 141 | zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); |
| 142 | style_submenu_element("Next", 0,"%R/event?name=%.20s&aid=%s&v", |
| 143 | zEventId, zNext); |
| 144 | free(zNext); |
| 145 | } |
| 146 | if( prevRid ){ |
| 147 | char *zPrev; |
| 148 | zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); |
| 149 | style_submenu_element("Prev", 0, "%R/event?name=%s&aid=%s&v", |
| 150 | zEventId, zPrev); |
| 151 | free(zPrev); |
| 152 | } |
| 153 | }else{ |
| 154 | style_submenu_element("Detail", 0, "%R/event?name=%.20s&aid=%s&v", |
| 155 | zEventId, zUuid); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | if( verboseFlag && g.perm.Hyperlink ){ |
| 160 | int i; |
| 161 | const char *zClr = 0; |
| 162 | Blob comment; |
| 163 | |
| 164 | zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); |
| 165 | @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at |
| 166 | @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>] |
| 167 | @ entered by user <b>%h(pEvent->zUser)</b> on |
| 168 | @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p> |
| 169 | @ <blockquote> |
| 170 | for(i=0; i<pEvent->nTag; i++){ |
| 171 | if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ |
| 172 | zClr = pEvent->aTag[i].zValue; |
| 173 | } |
| 174 | } |
| 175 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 176 | if( zClr ){ |
| 177 | @ <div style="background-color: %h(zClr);"> |
| 178 | }else{ |
| 179 | @ <div> |
| 180 | } |
| 181 | blob_init(&comment, pEvent->zComment, -1); |
| 182 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 183 | blob_reset(&comment); |
| 184 | @ </div> |
| 185 | @ </blockquote><hr /> |
| 186 | } |
| 187 | |
| 188 | wiki_convert(&tail, 0, 0); |
| 189 | style_footer(); |
| 190 | manifest_destroy(pEvent); |
| 191 | } |
| 192 | |
| 193 | /* |
| 194 | ** WEBPAGE: eventedit |
| 195 | ** URL: /eventedit?name=EVENTID |
| 196 | ** |
| 197 | ** Edit an event. If name is omitted, create a new event. |
| 198 | */ |
| 199 | void eventedit_page(void){ |
| 200 | char *zTag; |
| 201 | int rid = 0; |
| 202 | Blob event; |
| 203 | const char *zEventId; |
| 204 | int n; |
| 205 | const char *z; |
| 206 | char *zBody = (char*)P("w"); |
| 207 | char *zETime = (char*)P("t"); |
| 208 | const char *zComment = P("c"); |
| 209 | const char *zTags = P("g"); |
| 210 | const char *zClr; |
| 211 | |
| 212 | if( zBody ){ |
| 213 | zBody = mprintf("%s", zBody); |
| 214 | } |
| 215 | login_check_credentials(); |
| 216 | zEventId = P("name"); |
| 217 | if( zEventId==0 ){ |
| 218 | zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 219 | }else{ |
| 220 | int nEventId = strlen(zEventId); |
| 221 | if( nEventId!=40 || !validate16(zEventId, 40) ){ |
| 222 | fossil_redirect_home(); |
| 223 | return; |
| 224 | } |
| 225 | } |
| 226 | zTag = mprintf("event-%s", zEventId); |
| 227 | rid = db_int(0, |
| 228 | "SELECT rid FROM tagxref" |
| 229 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 230 | " ORDER BY mtime DESC", zTag |
| 231 | ); |
| 232 | free(zTag); |
| 233 | |
| 234 | /* Need both check-in and wiki-write or wiki-create privileges in order |
| 235 | ** to edit/create an event. |
| 236 | */ |
| 237 | if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 238 | login_needed(); |
| 239 | return; |
| 240 | } |
| 241 | |
| 242 | /* Figure out the color */ |
| 243 | if( rid ){ |
| 244 | zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); |
| 245 | }else{ |
| 246 | zClr = ""; |
| 247 | } |
| 248 | zClr = PD("clr",zClr); |
| 249 | if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); |
| 250 | |
| 251 | |
| 252 | /* If editing an existing event, extract the key fields to use as |
| 253 | ** a starting point for the edit. |
| 254 | */ |
| 255 | if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ |
| 256 | Manifest *pEvent; |
| 257 | pEvent = manifest_get(rid, CFTYPE_EVENT, 0); |
| 258 | if( pEvent && pEvent->type==CFTYPE_EVENT ){ |
| 259 | if( zBody==0 ) zBody = pEvent->zWiki; |
| 260 | if( zETime==0 ){ |
| 261 | zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); |
| 262 | } |
| 263 | if( zComment==0 ) zComment = pEvent->zComment; |
| 264 | } |
| 265 | if( zTags==0 ){ |
| 266 | zTags = db_text(0, |
| 267 | "SELECT group_concat(substr(tagname,5),', ')" |
| 268 | " FROM tagxref, tag" |
| @@ -276,11 +320,11 @@ | |
| 276 | zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); |
| 277 | if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ |
| 278 | char *zDate; |
| 279 | Blob cksum; |
| 280 | int nrid, n; |
| 281 | blob_zero(&event); |
| 282 | db_begin_transaction(); |
| 283 | login_verify_csrf_secret(); |
| 284 | while( fossil_isspace(zComment[0]) ) zComment++; |
| 285 | n = strlen(zComment); |
| 286 | while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } |
| @@ -289,17 +333,20 @@ | |
| 289 | } |
| 290 | zDate = date_in_standard_format("now"); |
| 291 | blob_appendf(&event, "D %s\n", zDate); |
| 292 | free(zDate); |
| 293 | zETime[10] = 'T'; |
| 294 | blob_appendf(&event, "E %s %s\n", zETime, zEventId); |
| 295 | zETime[10] = ' '; |
| 296 | if( rid ){ |
| 297 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 298 | blob_appendf(&event, "P %s\n", zUuid); |
| 299 | free(zUuid); |
| 300 | } |
| 301 | if( zClr && zClr[0] ){ |
| 302 | blob_appendf(&event, "T +bgcolor * %F\n", zClr); |
| 303 | } |
| 304 | if( zTags && zTags[0] ){ |
| 305 | Blob tags, one; |
| @@ -350,22 +397,26 @@ | |
| 350 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 351 | manifest_crosslink(nrid, &event, MC_NONE); |
| 352 | assert( blob_is_reset(&event) ); |
| 353 | content_deltify(rid, nrid, 0); |
| 354 | db_end_transaction(0); |
| 355 | cgi_redirectf("event?name=%T", zEventId); |
| 356 | } |
| 357 | if( P("cancel")!=0 ){ |
| 358 | cgi_redirectf("event?name=%T", zEventId); |
| 359 | return; |
| 360 | } |
| 361 | if( zBody==0 ){ |
| 362 | zBody = mprintf("<i>Event Text</i>"); |
| 363 | } |
| 364 | style_header("Edit Event %S", zEventId); |
| 365 | if( P("preview")!=0 ){ |
| 366 | Blob title, tail, com; |
| 367 | @ <p><b>Timeline comment preview:</b></p> |
| 368 | @ <blockquote> |
| 369 | @ <table border="0"> |
| 370 | if( zClr && zClr[0] ){ |
| 371 | @ <tr><td style="background-color: %h(zClr);"> |
| @@ -377,55 +428,55 @@ | |
| 377 | wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS); |
| 378 | @ </td></tr></table> |
| 379 | @ </blockquote> |
| 380 | @ <p><b>Page content preview:</b><p> |
| 381 | @ <blockquote> |
| 382 | blob_zero(&event); |
| 383 | blob_append(&event, zBody, -1); |
| 384 | if( wiki_find_title(&event, &title, &tail) ){ |
| 385 | @ <h2 align="center">%h(blob_str(&title))</h2> |
| 386 | wiki_convert(&tail, 0, 0); |
| 387 | }else{ |
| 388 | wiki_convert(&event, 0, 0); |
| 389 | } |
| 390 | @ </blockquote><hr /> |
| 391 | blob_reset(&event); |
| 392 | } |
| 393 | for(n=2, z=zBody; z[0]; z++){ |
| 394 | if( z[0]=='\n' ) n++; |
| 395 | } |
| 396 | if( n<20 ) n = 20; |
| 397 | if( n>40 ) n = 40; |
| 398 | @ <form method="post" action="%s(g.zTop)/eventedit"><div> |
| 399 | login_insert_csrf_secret(); |
| 400 | @ <input type="hidden" name="name" value="%h(zEventId)" /> |
| 401 | @ <table border="0" cellspacing="10"> |
| 402 | |
| 403 | @ <tr><th align="right" valign="top">Event Time (UTC):</th> |
| 404 | @ <td valign="top"> |
| 405 | @ <input type="text" name="t" size="25" value="%h(zETime)" /> |
| 406 | @ </td></tr> |
| 407 | |
| 408 | @ <tr><th align="right" valign="top">Timeline Comment:</th> |
| 409 | @ <td valign="top"> |
| 410 | @ <textarea name="c" class="eventedit" cols="80" |
| 411 | @ rows="3" wrap="virtual">%h(zComment)</textarea> |
| 412 | @ </td></tr> |
| 413 | |
| 414 | @ <tr><th align="right" valign="top">Background Color:</th> |
| 415 | @ <td valign="top"> |
| 416 | render_color_chooser(0, zClr, 0, "clr", "cclr"); |
| 417 | @ </td></tr> |
| 418 | |
| 419 | @ <tr><th align="right" valign="top">Tags:</th> |
| 420 | @ <td valign="top"> |
| 421 | @ <input type="text" name="g" size="40" value="%h(zTags)" /> |
| 422 | @ </td></tr> |
| 423 | |
| 424 | @ <tr><th align="right" valign="top">Page Content:</th> |
| 425 | @ <td valign="top"> |
| 426 | @ <textarea name="w" class="eventedit" cols="80" |
| 427 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 428 | @ </td></tr> |
| 429 | |
| 430 | @ <tr><td colspan="2"> |
| 431 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 432 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -15,78 +15,90 @@ | |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** This file contains code to do formatting of event messages: |
| 19 | ** |
| 20 | ** Technical Notes |
| 21 | ** Milestones |
| 22 | ** Blog posts |
| 23 | ** New articles |
| 24 | ** Process checkpoints |
| 25 | ** Announcements |
| 26 | ** |
| 27 | ** Do not confuse "event" artifacts with the "event" table in the |
| 28 | ** repository database. An "event" artifact is a technical-note: a |
| 29 | ** wiki- or blog-like essay that appears on the timeline. The "event" |
| 30 | ** table records all entries on the timeline, including tech-notes. |
| 31 | ** |
| 32 | ** (2015-02-14): Changing the name to "tech-note" most everywhere. |
| 33 | */ |
| 34 | #include "config.h" |
| 35 | #include <assert.h> |
| 36 | #include <ctype.h> |
| 37 | #include "event.h" |
| 38 | |
| 39 | /* |
| 40 | ** Output a hyperlink to an technote given its tagid. |
| 41 | */ |
| 42 | void hyperlink_to_event_tagid(int tagid){ |
| 43 | char *zId; |
| 44 | zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", |
| 45 | tagid); |
| 46 | @ [%z(href("%R/technote/%s",zId))%S(zId)</a>] |
| 47 | free(zId); |
| 48 | } |
| 49 | |
| 50 | /* |
| 51 | ** WEBPAGE: technote |
| 52 | ** WEBPAGE: event |
| 53 | ** |
| 54 | ** Display a "technical note" or "tech-note" (formerly called an "event"). |
| 55 | ** |
| 56 | ** PARAMETERS: |
| 57 | ** |
| 58 | ** name=ID // Identify the tech-note to display. ID must be complete |
| 59 | ** aid=ARTIFACTID // Which specific version of the tech-note. Optional. |
| 60 | ** v=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. |
| 61 | ** |
| 62 | ** Display an existing event identified by EVENTID |
| 63 | */ |
| 64 | void event_page(void){ |
| 65 | int rid = 0; /* rid of the event artifact */ |
| 66 | char *zUuid; /* UUID corresponding to rid */ |
| 67 | const char *zId; /* Event identifier */ |
| 68 | const char *zVerbose; /* Value of verbose option */ |
| 69 | char *zETime; /* Time of the tech-note */ |
| 70 | char *zATime; /* Time the artifact was created */ |
| 71 | int specRid; /* rid specified by aid= parameter */ |
| 72 | int prevRid, nextRid; /* Previous or next edits of this tech-note */ |
| 73 | Manifest *pTNote; /* Parsed technote artifact */ |
| 74 | Blob fullbody; /* Complete content of the technote body */ |
| 75 | Blob title; /* Title extracted from the technote body */ |
| 76 | Blob tail; /* Event body that comes after the title */ |
| 77 | Stmt q1; /* Query to search for the technote */ |
| 78 | int verboseFlag; /* True to show details */ |
| 79 | const char *zMimetype = 0; /* Mimetype of the document */ |
| 80 | |
| 81 | |
| 82 | /* wiki-read privilege is needed in order to read tech-notes. |
| 83 | */ |
| 84 | login_check_credentials(); |
| 85 | if( !g.perm.RdWiki ){ |
| 86 | login_needed(g.anon.RdWiki); |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | zId = P("name"); |
| 91 | if( zId==0 ){ fossil_redirect_home(); return; } |
| 92 | zUuid = (char*)P("aid"); |
| 93 | specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; |
| 94 | rid = nextRid = prevRid = 0; |
| 95 | db_prepare(&q1, |
| 96 | "SELECT rid FROM tagxref" |
| 97 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" |
| 98 | " ORDER BY mtime DESC", |
| 99 | zId |
| 100 | ); |
| 101 | while( db_step(&q1)==SQLITE_ROW ){ |
| 102 | nextRid = rid; |
| 103 | rid = db_column_int(&q1, 0); |
| 104 | if( specRid==0 || specRid==rid ){ |
| @@ -96,12 +108,12 @@ | |
| 108 | break; |
| 109 | } |
| 110 | } |
| 111 | db_finalize(&q1); |
| 112 | if( rid==0 || (specRid!=0 && specRid!=rid) ){ |
| 113 | style_header("No Such Tech-Note"); |
| 114 | @ Cannot locate a technical note called <b>%h(zId)</b>. |
| 115 | style_footer(); |
| 116 | return; |
| 117 | } |
| 118 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 119 | zVerbose = P("v"); |
| @@ -113,156 +125,188 @@ | |
| 125 | } |
| 126 | verboseFlag = (zVerbose!=0) && !is_false(zVerbose); |
| 127 | |
| 128 | /* Extract the event content. |
| 129 | */ |
| 130 | pTNote = manifest_get(rid, CFTYPE_EVENT, 0); |
| 131 | if( pTNote==0 ){ |
| 132 | fossil_fatal("Object #%d is not a tech-note", rid); |
| 133 | } |
| 134 | zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype)); |
| 135 | blob_init(&fullbody, pTNote->zWiki, -1); |
| 136 | blob_init(&title, 0, 0); |
| 137 | blob_init(&tail, 0, 0); |
| 138 | if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 139 | if( !wiki_find_title(&fullbody, &title, &tail) ){ |
| 140 | blob_appendf(&title, "Tech-note %S", zId); |
| 141 | tail = fullbody; |
| 142 | } |
| 143 | }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| 144 | markdown_to_html(&fullbody, &title, &tail); |
| 145 | if( blob_size(&title)==0 ){ |
| 146 | blob_appendf(&title, "Tech-note %S", zId); |
| 147 | } |
| 148 | }else{ |
| 149 | blob_appendf(&title, "Tech-note %S", zId); |
| 150 | tail = fullbody; |
| 151 | } |
| 152 | style_header("%s", blob_str(&title)); |
| 153 | if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ |
| 154 | style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId); |
| 155 | } |
| 156 | zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); |
| 157 | style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId); |
| 158 | if( g.perm.Hyperlink ){ |
| 159 | if( verboseFlag ){ |
| 160 | style_submenu_element("Plain", 0, |
| 161 | "%R/technote?name=%!S&aid=%s&mimetype=text/plain", |
| 162 | zId, zUuid); |
| 163 | if( nextRid ){ |
| 164 | char *zNext; |
| 165 | zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); |
| 166 | style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v", |
| 167 | zId, zNext); |
| 168 | free(zNext); |
| 169 | } |
| 170 | if( prevRid ){ |
| 171 | char *zPrev; |
| 172 | zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); |
| 173 | style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v", |
| 174 | zId, zPrev); |
| 175 | free(zPrev); |
| 176 | } |
| 177 | }else{ |
| 178 | style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v", |
| 179 | zId, zUuid); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | if( verboseFlag && g.perm.Hyperlink ){ |
| 184 | int i; |
| 185 | const char *zClr = 0; |
| 186 | Blob comment; |
| 187 | |
| 188 | zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate); |
| 189 | @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at |
| 190 | @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>] |
| 191 | @ entered by user <b>%h(pTNote->zUser)</b> on |
| 192 | @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p> |
| 193 | @ <blockquote> |
| 194 | for(i=0; i<pTNote->nTag; i++){ |
| 195 | if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){ |
| 196 | zClr = pTNote->aTag[i].zValue; |
| 197 | } |
| 198 | } |
| 199 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 200 | if( zClr ){ |
| 201 | @ <div style="background-color: %h(zClr);"> |
| 202 | }else{ |
| 203 | @ <div> |
| 204 | } |
| 205 | blob_init(&comment, pTNote->zComment, -1); |
| 206 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 207 | blob_reset(&comment); |
| 208 | @ </div> |
| 209 | @ </blockquote><hr /> |
| 210 | } |
| 211 | |
| 212 | if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 213 | wiki_convert(&fullbody, 0, 0); |
| 214 | }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| 215 | cgi_append_content(blob_buffer(&tail), blob_size(&tail)); |
| 216 | }else{ |
| 217 | @ <pre> |
| 218 | @ %h(blob_str(&fullbody)) |
| 219 | @ </pre> |
| 220 | } |
| 221 | style_footer(); |
| 222 | manifest_destroy(pTNote); |
| 223 | } |
| 224 | |
| 225 | /* |
| 226 | ** WEBPAGE: technoteedit |
| 227 | ** WEBPAGE: eventedit |
| 228 | ** |
| 229 | ** Revise or create a technical note (formerly called an 'event'). |
| 230 | ** |
| 231 | ** Parameters: |
| 232 | ** |
| 233 | ** name=ID Hex hash ID of the tech-note. If omitted, a new |
| 234 | ** tech-note is created. |
| 235 | */ |
| 236 | void eventedit_page(void){ |
| 237 | char *zTag; |
| 238 | int rid = 0; |
| 239 | Blob event; |
| 240 | const char *zId; |
| 241 | int n; |
| 242 | const char *z; |
| 243 | char *zBody = (char*)P("w"); |
| 244 | char *zETime = (char*)P("t"); |
| 245 | const char *zComment = P("c"); |
| 246 | const char *zTags = P("g"); |
| 247 | const char *zClr; |
| 248 | const char *zMimetype = P("mimetype"); |
| 249 | int isNew = 0; |
| 250 | |
| 251 | if( zBody ){ |
| 252 | zBody = mprintf("%s", zBody); |
| 253 | } |
| 254 | login_check_credentials(); |
| 255 | zId = P("name"); |
| 256 | if( zId==0 ){ |
| 257 | zId = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 258 | isNew = 1; |
| 259 | }else{ |
| 260 | int nId = strlen(zId); |
| 261 | if( !validate16(zId, nId) ){ |
| 262 | fossil_redirect_home(); |
| 263 | return; |
| 264 | } |
| 265 | } |
| 266 | zTag = mprintf("event-%s", zId); |
| 267 | rid = db_int(0, |
| 268 | "SELECT rid FROM tagxref" |
| 269 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')" |
| 270 | " ORDER BY mtime DESC", zTag |
| 271 | ); |
| 272 | free(zTag); |
| 273 | |
| 274 | /* Need both check-in and wiki-write or wiki-create privileges in order |
| 275 | ** to edit/create an event. |
| 276 | */ |
| 277 | if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 278 | login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki)); |
| 279 | return; |
| 280 | } |
| 281 | |
| 282 | /* Figure out the color */ |
| 283 | if( rid ){ |
| 284 | zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); |
| 285 | }else{ |
| 286 | zClr = ""; |
| 287 | isNew = 1; |
| 288 | } |
| 289 | zClr = PD("clr",zClr); |
| 290 | if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); |
| 291 | |
| 292 | |
| 293 | /* If editing an existing event, extract the key fields to use as |
| 294 | ** a starting point for the edit. |
| 295 | */ |
| 296 | if( rid |
| 297 | && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0) |
| 298 | ){ |
| 299 | Manifest *pTNote; |
| 300 | pTNote = manifest_get(rid, CFTYPE_EVENT, 0); |
| 301 | if( pTNote && pTNote->type==CFTYPE_EVENT ){ |
| 302 | if( zBody==0 ) zBody = pTNote->zWiki; |
| 303 | if( zETime==0 ){ |
| 304 | zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); |
| 305 | } |
| 306 | if( zComment==0 ) zComment = pTNote->zComment; |
| 307 | if( zMimetype==0 ) zMimetype = pTNote->zMimetype; |
| 308 | } |
| 309 | if( zTags==0 ){ |
| 310 | zTags = db_text(0, |
| 311 | "SELECT group_concat(substr(tagname,5),', ')" |
| 312 | " FROM tagxref, tag" |
| @@ -276,11 +320,11 @@ | |
| 320 | zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); |
| 321 | if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ |
| 322 | char *zDate; |
| 323 | Blob cksum; |
| 324 | int nrid, n; |
| 325 | blob_init(&event, 0, 0); |
| 326 | db_begin_transaction(); |
| 327 | login_verify_csrf_secret(); |
| 328 | while( fossil_isspace(zComment[0]) ) zComment++; |
| 329 | n = strlen(zComment); |
| 330 | while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } |
| @@ -289,17 +333,20 @@ | |
| 333 | } |
| 334 | zDate = date_in_standard_format("now"); |
| 335 | blob_appendf(&event, "D %s\n", zDate); |
| 336 | free(zDate); |
| 337 | zETime[10] = 'T'; |
| 338 | blob_appendf(&event, "E %s %s\n", zETime, zId); |
| 339 | zETime[10] = ' '; |
| 340 | if( rid ){ |
| 341 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 342 | blob_appendf(&event, "P %s\n", zUuid); |
| 343 | free(zUuid); |
| 344 | } |
| 345 | if( zMimetype && zMimetype[0] ){ |
| 346 | blob_appendf(&event, "N %s\n", zMimetype); |
| 347 | } |
| 348 | if( zClr && zClr[0] ){ |
| 349 | blob_appendf(&event, "T +bgcolor * %F\n", zClr); |
| 350 | } |
| 351 | if( zTags && zTags[0] ){ |
| 352 | Blob tags, one; |
| @@ -350,22 +397,26 @@ | |
| 397 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 398 | manifest_crosslink(nrid, &event, MC_NONE); |
| 399 | assert( blob_is_reset(&event) ); |
| 400 | content_deltify(rid, nrid, 0); |
| 401 | db_end_transaction(0); |
| 402 | cgi_redirectf("technote?name=%T", zId); |
| 403 | } |
| 404 | if( P("cancel")!=0 ){ |
| 405 | cgi_redirectf("technote?name=%T", zId); |
| 406 | return; |
| 407 | } |
| 408 | if( zBody==0 ){ |
| 409 | zBody = mprintf("Insert new content here..."); |
| 410 | } |
| 411 | if( isNew ){ |
| 412 | style_header("New Tech-note %S", zId); |
| 413 | }else{ |
| 414 | style_header("Edit Tech-note %S", zId); |
| 415 | } |
| 416 | if( P("preview")!=0 ){ |
| 417 | Blob com; |
| 418 | @ <p><b>Timeline comment preview:</b></p> |
| 419 | @ <blockquote> |
| 420 | @ <table border="0"> |
| 421 | if( zClr && zClr[0] ){ |
| 422 | @ <tr><td style="background-color: %h(zClr);"> |
| @@ -377,55 +428,55 @@ | |
| 428 | wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS); |
| 429 | @ </td></tr></table> |
| 430 | @ </blockquote> |
| 431 | @ <p><b>Page content preview:</b><p> |
| 432 | @ <blockquote> |
| 433 | blob_init(&event, 0, 0); |
| 434 | blob_append(&event, zBody, -1); |
| 435 | wiki_render_by_mimetype(&event, zMimetype); |
| 436 | @ </blockquote><hr /> |
| 437 | blob_reset(&event); |
| 438 | } |
| 439 | for(n=2, z=zBody; z[0]; z++){ |
| 440 | if( z[0]=='\n' ) n++; |
| 441 | } |
| 442 | if( n<20 ) n = 20; |
| 443 | if( n>40 ) n = 40; |
| 444 | @ <form method="post" action="%R/technoteedit"><div> |
| 445 | login_insert_csrf_secret(); |
| 446 | @ <input type="hidden" name="name" value="%h(zId)" /> |
| 447 | @ <table border="0" cellspacing="10"> |
| 448 | |
| 449 | @ <tr><th align="right" valign="top">Timestamp (UTC):</th> |
| 450 | @ <td valign="top"> |
| 451 | @ <input type="text" name="t" size="25" value="%h(zETime)" /> |
| 452 | @ </td></tr> |
| 453 | |
| 454 | @ <tr><th align="right" valign="top">Timeline Comment:</th> |
| 455 | @ <td valign="top"> |
| 456 | @ <textarea name="c" class="technoteedit" cols="80" |
| 457 | @ rows="3" wrap="virtual">%h(zComment)</textarea> |
| 458 | @ </td></tr> |
| 459 | |
| 460 | @ <tr><th align="right" valign="top">Timeline Background Color:</th> |
| 461 | @ <td valign="top"> |
| 462 | render_color_chooser(0, zClr, 0, "clr", "cclr"); |
| 463 | @ </td></tr> |
| 464 | |
| 465 | @ <tr><th align="right" valign="top">Tags:</th> |
| 466 | @ <td valign="top"> |
| 467 | @ <input type="text" name="g" size="40" value="%h(zTags)" /> |
| 468 | @ </td></tr> |
| 469 | |
| 470 | @ <tr><th align="right" valign="top">Markup Style:</th> |
| 471 | @ <td valign="top"> |
| 472 | mimetype_option_menu(zMimetype); |
| 473 | @ </td></tr> |
| 474 | |
| 475 | @ <tr><th align="right" valign="top">Page Content:</th> |
| 476 | @ <td valign="top"> |
| 477 | @ <textarea name="w" class="technoteedit" cols="80" |
| 478 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 479 | @ </td></tr> |
| 480 | |
| 481 | @ <tr><td colspan="2"> |
| 482 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 483 |
+11
-27
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -230,42 +230,26 @@ | ||
| 230 | 230 | blob_read_link(&content, zFrom); |
| 231 | 231 | symlink_create(blob_str(&content), zTo); |
| 232 | 232 | blob_reset(&content); |
| 233 | 233 | } |
| 234 | 234 | |
| 235 | -#ifdef __CYGWIN__ | |
| 236 | -/* Workaround for recently introduced Cygwin bug: group execute */ | |
| 237 | -/* permission is always set, so it cannot be relied upon! */ | |
| 238 | -# undef S_IXGRP | |
| 239 | -# define S_IXGRP 0 | |
| 240 | -#endif | |
| 241 | - | |
| 242 | 235 | /* |
| 243 | 236 | ** Return file permissions (normal, executable, or symlink): |
| 244 | -** - PERM_EXE if file is executable; | |
| 237 | +** - PERM_EXE on Unix if file is executable; | |
| 245 | 238 | ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on; |
| 246 | 239 | ** - PERM_REG for all other cases (regular file, directory, fifo, etc). |
| 247 | 240 | */ |
| 248 | 241 | int file_wd_perm(const char *zFilename){ |
| 249 | - if( getStat(zFilename, 1) ) return PERM_REG; | |
| 250 | -#if defined(_WIN32) | |
| 251 | -# ifndef S_IXUSR | |
| 252 | -# define S_IXUSR _S_IEXEC | |
| 253 | -# endif | |
| 254 | - if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) | |
| 255 | - return PERM_EXE; | |
| 256 | - else | |
| 257 | - return PERM_REG; | |
| 258 | -#else | |
| 259 | - if( S_ISREG(fileStat.st_mode) && | |
| 260 | - ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 ) | |
| 261 | - return PERM_EXE; | |
| 262 | - else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) | |
| 263 | - return PERM_LNK; | |
| 264 | - else | |
| 265 | - return PERM_REG; | |
| 242 | +#if !defined(_WIN32) | |
| 243 | + if( !getStat(zFilename, 1) ){ | |
| 244 | + if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) | |
| 245 | + return PERM_EXE; | |
| 246 | + else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) | |
| 247 | + return PERM_LNK; | |
| 248 | + } | |
| 266 | 249 | #endif |
| 250 | + return PERM_REG; | |
| 267 | 251 | } |
| 268 | 252 | |
| 269 | 253 | /* |
| 270 | 254 | ** Return TRUE if the named file is an executable. Return false |
| 271 | 255 | ** for directories, devices, fifos, symlinks, etc. |
| @@ -439,16 +423,16 @@ | ||
| 439 | 423 | #if !defined(_WIN32) |
| 440 | 424 | struct stat buf; |
| 441 | 425 | if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; |
| 442 | 426 | if( onoff ){ |
| 443 | 427 | int targetMode = (buf.st_mode & 0444)>>2; |
| 444 | - if( (buf.st_mode & 0111)!=targetMode ){ | |
| 428 | + if( (buf.st_mode & 0100) == 0 ){ | |
| 445 | 429 | chmod(zFilename, buf.st_mode | targetMode); |
| 446 | 430 | rc = 1; |
| 447 | 431 | } |
| 448 | 432 | }else{ |
| 449 | - if( (buf.st_mode & 0111)!=0 ){ | |
| 433 | + if( (buf.st_mode & 0100) != 0 ){ | |
| 450 | 434 | chmod(zFilename, buf.st_mode & ~0111); |
| 451 | 435 | rc = 1; |
| 452 | 436 | } |
| 453 | 437 | } |
| 454 | 438 | #endif /* _WIN32 */ |
| 455 | 439 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -230,42 +230,26 @@ | |
| 230 | blob_read_link(&content, zFrom); |
| 231 | symlink_create(blob_str(&content), zTo); |
| 232 | blob_reset(&content); |
| 233 | } |
| 234 | |
| 235 | #ifdef __CYGWIN__ |
| 236 | /* Workaround for recently introduced Cygwin bug: group execute */ |
| 237 | /* permission is always set, so it cannot be relied upon! */ |
| 238 | # undef S_IXGRP |
| 239 | # define S_IXGRP 0 |
| 240 | #endif |
| 241 | |
| 242 | /* |
| 243 | ** Return file permissions (normal, executable, or symlink): |
| 244 | ** - PERM_EXE if file is executable; |
| 245 | ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on; |
| 246 | ** - PERM_REG for all other cases (regular file, directory, fifo, etc). |
| 247 | */ |
| 248 | int file_wd_perm(const char *zFilename){ |
| 249 | if( getStat(zFilename, 1) ) return PERM_REG; |
| 250 | #if defined(_WIN32) |
| 251 | # ifndef S_IXUSR |
| 252 | # define S_IXUSR _S_IEXEC |
| 253 | # endif |
| 254 | if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) |
| 255 | return PERM_EXE; |
| 256 | else |
| 257 | return PERM_REG; |
| 258 | #else |
| 259 | if( S_ISREG(fileStat.st_mode) && |
| 260 | ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0 ) |
| 261 | return PERM_EXE; |
| 262 | else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) |
| 263 | return PERM_LNK; |
| 264 | else |
| 265 | return PERM_REG; |
| 266 | #endif |
| 267 | } |
| 268 | |
| 269 | /* |
| 270 | ** Return TRUE if the named file is an executable. Return false |
| 271 | ** for directories, devices, fifos, symlinks, etc. |
| @@ -439,16 +423,16 @@ | |
| 439 | #if !defined(_WIN32) |
| 440 | struct stat buf; |
| 441 | if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; |
| 442 | if( onoff ){ |
| 443 | int targetMode = (buf.st_mode & 0444)>>2; |
| 444 | if( (buf.st_mode & 0111)!=targetMode ){ |
| 445 | chmod(zFilename, buf.st_mode | targetMode); |
| 446 | rc = 1; |
| 447 | } |
| 448 | }else{ |
| 449 | if( (buf.st_mode & 0111)!=0 ){ |
| 450 | chmod(zFilename, buf.st_mode & ~0111); |
| 451 | rc = 1; |
| 452 | } |
| 453 | } |
| 454 | #endif /* _WIN32 */ |
| 455 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -230,42 +230,26 @@ | |
| 230 | blob_read_link(&content, zFrom); |
| 231 | symlink_create(blob_str(&content), zTo); |
| 232 | blob_reset(&content); |
| 233 | } |
| 234 | |
| 235 | /* |
| 236 | ** Return file permissions (normal, executable, or symlink): |
| 237 | ** - PERM_EXE on Unix if file is executable; |
| 238 | ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on; |
| 239 | ** - PERM_REG for all other cases (regular file, directory, fifo, etc). |
| 240 | */ |
| 241 | int file_wd_perm(const char *zFilename){ |
| 242 | #if !defined(_WIN32) |
| 243 | if( !getStat(zFilename, 1) ){ |
| 244 | if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 ) |
| 245 | return PERM_EXE; |
| 246 | else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) ) |
| 247 | return PERM_LNK; |
| 248 | } |
| 249 | #endif |
| 250 | return PERM_REG; |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | ** Return TRUE if the named file is an executable. Return false |
| 255 | ** for directories, devices, fifos, symlinks, etc. |
| @@ -439,16 +423,16 @@ | |
| 423 | #if !defined(_WIN32) |
| 424 | struct stat buf; |
| 425 | if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; |
| 426 | if( onoff ){ |
| 427 | int targetMode = (buf.st_mode & 0444)>>2; |
| 428 | if( (buf.st_mode & 0100) == 0 ){ |
| 429 | chmod(zFilename, buf.st_mode | targetMode); |
| 430 | rc = 1; |
| 431 | } |
| 432 | }else{ |
| 433 | if( (buf.st_mode & 0100) != 0 ){ |
| 434 | chmod(zFilename, buf.st_mode & ~0111); |
| 435 | rc = 1; |
| 436 | } |
| 437 | } |
| 438 | #endif /* _WIN32 */ |
| 439 |
+22
-10
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -215,11 +215,11 @@ | ||
| 215 | 215 | zCiUuid, zCom, zUser, zFileUuid, zBr); |
| 216 | 216 | comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags); |
| 217 | 217 | fossil_free(zOut); |
| 218 | 218 | }else{ |
| 219 | 219 | blob_reset(&line); |
| 220 | - blob_appendf(&line, "%.10s ", zCiUuid); | |
| 220 | + blob_appendf(&line, "%S ", zCiUuid); | |
| 221 | 221 | blob_appendf(&line, "%.10s ", zDate); |
| 222 | 222 | blob_appendf(&line, "%8.8s ", zUser); |
| 223 | 223 | blob_appendf(&line, "%8.8s ", zBr); |
| 224 | 224 | blob_appendf(&line,"%-39.39s", zCom ); |
| 225 | 225 | comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags); |
| @@ -305,11 +305,11 @@ | ||
| 305 | 305 | int uBg = P("ubg")!=0; |
| 306 | 306 | int fDebug = atoi(PD("debug","0")); |
| 307 | 307 | int fShowId = P("showid")!=0; |
| 308 | 308 | |
| 309 | 309 | login_check_credentials(); |
| 310 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 310 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 311 | 311 | style_header("File History"); |
| 312 | 312 | login_anonymous_available(); |
| 313 | 313 | url_initialize(&url, "finfo"); |
| 314 | 314 | if( brBg ) url_add_parameter(&url, "brbg", 0); |
| 315 | 315 | if( uBg ) url_add_parameter(&url, "ubg", 0); |
| @@ -358,12 +358,21 @@ | ||
| 358 | 358 | } |
| 359 | 359 | if( (zB = P("b"))!=0 ){ |
| 360 | 360 | blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB); |
| 361 | 361 | url_add_parameter(&url, "b", zB); |
| 362 | 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 | + */ | |
| 363 | 371 | 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" | |
| 365 | 374 | " ORDER BY event.mtime DESC /*sort*/" |
| 366 | 375 | ); |
| 367 | 376 | if( (n = atoi(PD("n","0")))>0 ){ |
| 368 | 377 | blob_append_sql(&sql, " LIMIT %d", n); |
| 369 | 378 | url_add_parameter(&url, "n", P("n")); |
| @@ -374,11 +383,11 @@ | ||
| 374 | 383 | } |
| 375 | 384 | blob_reset(&sql); |
| 376 | 385 | blob_zero(&title); |
| 377 | 386 | if( baseCheckin ){ |
| 378 | 387 | 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); | |
| 380 | 389 | blob_appendf(&title, "Ancestors of file "); |
| 381 | 390 | hyperlinked_path(zFilename, &title, zUuid, "tree", ""); |
| 382 | 391 | if( fShowId ) blob_appendf(&title, " (%d)", fnid); |
| 383 | 392 | blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid); |
| 384 | 393 | if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin); |
| @@ -432,11 +441,12 @@ | ||
| 432 | 441 | if( uBg ){ |
| 433 | 442 | zBgClr = hash_color(zUser); |
| 434 | 443 | }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ |
| 435 | 444 | zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); |
| 436 | 445 | } |
| 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, | |
| 438 | 448 | zUuid, 0); |
| 439 | 449 | if( strncmp(zDate, zPrevDate, 10) ){ |
| 440 | 450 | sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); |
| 441 | 451 | @ <tr><td> |
| 442 | 452 | @ <div class="divider">%s(zPrevDate)</div> |
| @@ -459,11 +469,11 @@ | ||
| 459 | 469 | char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d", |
| 460 | 470 | pfnid); |
| 461 | 471 | @ <b>Renamed</b> from |
| 462 | 472 | @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a> |
| 463 | 473 | } |
| 464 | - @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a> | |
| 474 | + @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a> | |
| 465 | 475 | if( fShowId ){ |
| 466 | 476 | @ (%d(frid)) |
| 467 | 477 | } |
| 468 | 478 | @ part of check-in |
| 469 | 479 | }else{ |
| @@ -486,32 +496,34 @@ | ||
| 486 | 496 | if( fShowId ){ |
| 487 | 497 | @ (%d(fmid)) |
| 488 | 498 | } |
| 489 | 499 | @ %W(zCom) (user: |
| 490 | 500 | 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>) | |
| 492 | 502 | if( g.perm.Hyperlink && zUuid ){ |
| 493 | 503 | const char *z = zFilename; |
| 494 | 504 | @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin)) |
| 495 | 505 | @ [annotate]</a> |
| 496 | 506 | @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin)) |
| 497 | 507 | @ [blame]</a> |
| 498 | - @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins using]</a> | |
| 508 | + @ %z(href("%R/timeline?n=200&uf=%!S",zUuid))[checkins using]</a> | |
| 499 | 509 | 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> | |
| 501 | 511 | } |
| 502 | 512 | } |
| 503 | 513 | if( fDebug & FINFO_DEBUG_MLINK ){ |
| 504 | 514 | int ii; |
| 515 | + char *zAncLink; | |
| 505 | 516 | @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) |
| 506 | 517 | if( nParent>0 ){ |
| 507 | 518 | @ parents=%d(aParent[0]) |
| 508 | 519 | for(ii=1; ii<nParent; ii++){ |
| 509 | 520 | @ %d(aParent[ii]) |
| 510 | 521 | } |
| 511 | 522 | } |
| 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> | |
| 513 | 525 | } |
| 514 | 526 | tag_private_status(frid); |
| 515 | 527 | @ </td></tr> |
| 516 | 528 | } |
| 517 | 529 | db_finalize(&q); |
| 518 | 530 |
| --- 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 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 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 |
+1
| --- src/http_socket.c | ||
| +++ src/http_socket.c | ||
| @@ -173,10 +173,11 @@ | ||
| 173 | 173 | break; |
| 174 | 174 | } |
| 175 | 175 | if( p==0 ){ |
| 176 | 176 | socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name, |
| 177 | 177 | pUrlData->port); |
| 178 | + rc = 1; | |
| 178 | 179 | } |
| 179 | 180 | #if !defined(_WIN32) |
| 180 | 181 | signal(SIGPIPE, SIG_IGN); |
| 181 | 182 | #endif |
| 182 | 183 | end_socket_open: |
| 183 | 184 |
| --- 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 |
+1
-1
| --- src/import.c | ||
| +++ src/import.c | ||
| @@ -1499,11 +1499,11 @@ | ||
| 1499 | 1499 | Stmt q; |
| 1500 | 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | - | |
| 1504 | + | |
| 1505 | 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | 1509 | |
| 1510 | 1510 |
| --- src/import.c | |
| +++ src/import.c | |
| @@ -1499,11 +1499,11 @@ | |
| 1499 | Stmt q; |
| 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | |
| 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | |
| 1510 |
| --- src/import.c | |
| +++ src/import.c | |
| @@ -1499,11 +1499,11 @@ | |
| 1499 | Stmt q; |
| 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | |
| 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | |
| 1510 |
+1
-1
| --- src/import.c | ||
| +++ src/import.c | ||
| @@ -1499,11 +1499,11 @@ | ||
| 1499 | 1499 | Stmt q; |
| 1500 | 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | - | |
| 1504 | + | |
| 1505 | 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | 1509 | |
| 1510 | 1510 |
| --- src/import.c | |
| +++ src/import.c | |
| @@ -1499,11 +1499,11 @@ | |
| 1499 | Stmt q; |
| 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | |
| 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | |
| 1510 |
| --- src/import.c | |
| +++ src/import.c | |
| @@ -1499,11 +1499,11 @@ | |
| 1499 | Stmt q; |
| 1500 | const char *zBase = find_option("base", 0, 1); |
| 1501 | int forceFlag = find_option("force", "f", 0)!=0; |
| 1502 | int incrFlag = find_option("incremental", "i", 0)!=0; |
| 1503 | int flatFlag = find_option("flat", 0, 0)!=0; |
| 1504 | |
| 1505 | gsvn.zTrunk = find_option("trunk", 0, 1); |
| 1506 | gsvn.zBranches = find_option("branches", 0, 1); |
| 1507 | gsvn.zTags = find_option("tags", 0, 1); |
| 1508 | gsvn.noSvnRevFlag = find_option("no-svn-rev", 0, 0)!=0; |
| 1509 | |
| 1510 |
+76
-53
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -240,23 +240,23 @@ | ||
| 240 | 240 | show_common_info(rid, "uuid:", 1, 1); |
| 241 | 241 | } |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | 244 | /* |
| 245 | -** Show information about all tags on a given node. | |
| 245 | +** Show information about all tags on a given check-in. | |
| 246 | 246 | */ |
| 247 | -static void showTags(int rid, const char *zNotGlob){ | |
| 247 | +static void showTags(int rid){ | |
| 248 | 248 | Stmt q; |
| 249 | 249 | int cnt = 0; |
| 250 | 250 | db_prepare(&q, |
| 251 | 251 | "SELECT tag.tagid, tagname, " |
| 252 | 252 | " (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d)," |
| 253 | 253 | " value, datetime(tagxref.mtime%s), tagtype," |
| 254 | 254 | " (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)" |
| 255 | 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 | |
| 256 | + " WHERE tagxref.rid=%d" | |
| 257 | + " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid | |
| 258 | 258 | ); |
| 259 | 259 | while( db_step(&q)==SQLITE_ROW ){ |
| 260 | 260 | const char *zTagname = db_column_text(&q, 1); |
| 261 | 261 | const char *zSrcUuid = db_column_text(&q, 2); |
| 262 | 262 | const char *zValue = db_column_text(&q, 3); |
| @@ -305,10 +305,33 @@ | ||
| 305 | 305 | db_finalize(&q); |
| 306 | 306 | if( cnt ){ |
| 307 | 307 | @ </ul> |
| 308 | 308 | } |
| 309 | 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 | +} | |
| 310 | 333 | |
| 311 | 334 | |
| 312 | 335 | /* |
| 313 | 336 | ** Append the difference between artifacts to the output |
| 314 | 337 | */ |
| @@ -380,32 +403,32 @@ | ||
| 380 | 403 | } |
| 381 | 404 | }else{ |
| 382 | 405 | if( zOld && zNew ){ |
| 383 | 406 | if( fossil_strcmp(zOld, zNew)!=0 ){ |
| 384 | 407 | @ <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>. | |
| 387 | 410 | }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ |
| 388 | 411 | @ <p>Name change |
| 389 | 412 | @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a> |
| 390 | 413 | @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>. |
| 391 | 414 | }else{ |
| 392 | 415 | @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for |
| 393 | 416 | @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a> |
| 394 | 417 | } |
| 395 | 418 | }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> | |
| 398 | 421 | }else{ |
| 399 | 422 | @ <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> | |
| 401 | 424 | } |
| 402 | 425 | if( diffFlags ){ |
| 403 | 426 | append_diff(zOld, zNew, diffFlags, pRe); |
| 404 | 427 | }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ |
| 405 | 428 | @ |
| 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> | |
| 407 | 430 | } |
| 408 | 431 | } |
| 409 | 432 | } |
| 410 | 433 | |
| 411 | 434 | /* |
| @@ -504,11 +527,11 @@ | ||
| 504 | 527 | const char *zW; /* URL param for ignoring whitespace */ |
| 505 | 528 | const char *zPage = "vinfo"; /* Page that shows diffs */ |
| 506 | 529 | const char *zPageHide = "ci"; /* Page that hides diffs */ |
| 507 | 530 | |
| 508 | 531 | login_check_credentials(); |
| 509 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 532 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 510 | 533 | zName = P("name"); |
| 511 | 534 | rid = name_to_rid_www("name"); |
| 512 | 535 | if( rid==0 ){ |
| 513 | 536 | style_header("Check-in Information Error"); |
| 514 | 537 | @ No such object: %h(g.argv[2]) |
| @@ -612,19 +635,19 @@ | ||
| 612 | 635 | if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ |
| 613 | 636 | zPJ[jj] = '_'; |
| 614 | 637 | } |
| 615 | 638 | } |
| 616 | 639 | @ <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> | |
| 618 | 641 | if( zParent ){ |
| 619 | - @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a> | |
| 642 | + @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a> | |
| 620 | 643 | } |
| 621 | 644 | if( !isLeaf ){ |
| 622 | - @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a> | |
| 645 | + @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a> | |
| 623 | 646 | } |
| 624 | 647 | 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> | |
| 626 | 649 | } |
| 627 | 650 | db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag " |
| 628 | 651 | " WHERE rid=%d AND tagtype>0 " |
| 629 | 652 | " AND tag.tagid=tagxref.tagid " |
| 630 | 653 | " AND +tag.tagname GLOB 'sym-*'", rid); |
| @@ -634,29 +657,29 @@ | ||
| 634 | 657 | } |
| 635 | 658 | db_finalize(&q2); |
| 636 | 659 | |
| 637 | 660 | |
| 638 | 661 | /* The Download: line */ |
| 639 | - if( g.perm.Zip ){ | |
| 662 | + if( g.anon.Zip ){ | |
| 640 | 663 | char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s", |
| 641 | 664 | zPJ, zUuid, zUuid); |
| 642 | 665 | @ </td></tr> |
| 643 | 666 | @ <tr><th>Downloads:</th><td> |
| 644 | 667 | @ %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)) | |
| 646 | 669 | @ ZIP archive</a> |
| 647 | 670 | fossil_free(zUrl); |
| 648 | 671 | } |
| 649 | 672 | @ </td></tr> |
| 650 | 673 | @ <tr><th>Other Links:</th> |
| 651 | 674 | @ <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> | |
| 658 | 681 | } |
| 659 | 682 | @ </td> |
| 660 | 683 | @ </tr> |
| 661 | 684 | blob_reset(&projName); |
| 662 | 685 | } |
| @@ -664,11 +687,12 @@ | ||
| 664 | 687 | }else{ |
| 665 | 688 | style_header("Check-in Information"); |
| 666 | 689 | login_anonymous_available(); |
| 667 | 690 | } |
| 668 | 691 | db_finalize(&q1); |
| 669 | - showTags(rid, ""); | |
| 692 | + showTags(rid); | |
| 693 | + showContext(rid); | |
| 670 | 694 | @ <div class="section">Changes</div> |
| 671 | 695 | @ <div class="sectionmenu"> |
| 672 | 696 | verboseFlag = g.zPath[0]!='c'; |
| 673 | 697 | if( db_get_boolean("show-version-diffs", 0)==0 ){ |
| 674 | 698 | verboseFlag = !verboseFlag; |
| @@ -699,11 +723,11 @@ | ||
| 699 | 723 | @ Show Unified Diffs</a> |
| 700 | 724 | @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName)) |
| 701 | 725 | @ Show Side-by-Side Diffs</a> |
| 702 | 726 | } |
| 703 | 727 | 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)) | |
| 705 | 729 | @ Patch</a> |
| 706 | 730 | } |
| 707 | 731 | @</div> |
| 708 | 732 | if( pRe ){ |
| 709 | 733 | @ <p><b>Only differences that match regular expression "%h(zRe)" |
| @@ -749,11 +773,11 @@ | ||
| 749 | 773 | Blob wiki; |
| 750 | 774 | int modPending; |
| 751 | 775 | const char *zModAction; |
| 752 | 776 | |
| 753 | 777 | login_check_credentials(); |
| 754 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 778 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 755 | 779 | rid = name_to_rid_www("name"); |
| 756 | 780 | if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){ |
| 757 | 781 | style_header("Wiki Page Information Error"); |
| 758 | 782 | @ No such object: %h(P("name")) |
| 759 | 783 | style_footer(); |
| @@ -789,11 +813,11 @@ | ||
| 789 | 813 | pWiki->zWikiTitle); |
| 790 | 814 | login_anonymous_available(); |
| 791 | 815 | @ <div class="section">Overview</div> |
| 792 | 816 | @ <p><table class="label-value"> |
| 793 | 817 | @ <tr><th>Artifact ID:</th> |
| 794 | - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> | |
| 818 | + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> | |
| 795 | 819 | if( g.perm.Setup ){ |
| 796 | 820 | @ (%d(rid)) |
| 797 | 821 | } |
| 798 | 822 | modPending = moderation_pending(rid); |
| 799 | 823 | if( modPending ){ |
| @@ -808,11 +832,11 @@ | ||
| 808 | 832 | if( pWiki->nParent>0 ){ |
| 809 | 833 | int i; |
| 810 | 834 | @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td> |
| 811 | 835 | for(i=0; i<pWiki->nParent; i++){ |
| 812 | 836 | char *zParent = pWiki->azParent[i]; |
| 813 | - @ %z(href("info/%s",zParent))%s(zParent)</a> | |
| 837 | + @ %z(href("info/%!S",zParent))%s(zParent)</a> | |
| 814 | 838 | } |
| 815 | 839 | @ </td></tr> |
| 816 | 840 | } |
| 817 | 841 | @ </table> |
| 818 | 842 | |
| @@ -967,11 +991,11 @@ | ||
| 967 | 991 | const char *zW; |
| 968 | 992 | const char *zVerbose; |
| 969 | 993 | const char *zGlob; |
| 970 | 994 | ReCompiled *pRe = 0; |
| 971 | 995 | login_check_credentials(); |
| 972 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 996 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 973 | 997 | login_anonymous_available(); |
| 974 | 998 | zRe = P("regex"); |
| 975 | 999 | if( zRe ) re_compile(&pRe, zRe, 0); |
| 976 | 1000 | zBranch = P("branch"); |
| 977 | 1001 | if( zBranch && zBranch[0] ){ |
| @@ -1182,17 +1206,17 @@ | ||
| 1182 | 1206 | int mPerm = db_column_int(&q, 5); |
| 1183 | 1207 | const char *zBr = db_column_text(&q, 6); |
| 1184 | 1208 | int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0; |
| 1185 | 1209 | if( sameFilename && !showDetail ){ |
| 1186 | 1210 | if( cnt==1 ){ |
| 1187 | - @ %z(href("%R/whatis/%s",zUuid))[more...]</a> | |
| 1211 | + @ %z(href("%R/whatis/%!S",zUuid))[more...]</a> | |
| 1188 | 1212 | } |
| 1189 | 1213 | cnt++; |
| 1190 | 1214 | continue; |
| 1191 | 1215 | } |
| 1192 | 1216 | if( !sameFilename ){ |
| 1193 | - if( prevName ) { | |
| 1217 | + if( prevName && showDetail ) { | |
| 1194 | 1218 | @ </ul> |
| 1195 | 1219 | } |
| 1196 | 1220 | if( mPerm==PERM_LNK ){ |
| 1197 | 1221 | @ <li>Symbolic link |
| 1198 | 1222 | objType |= OBJTYPE_SYMLINK; |
| @@ -1225,14 +1249,14 @@ | ||
| 1225 | 1249 | @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a> |
| 1226 | 1250 | } |
| 1227 | 1251 | @ — %!w(zCom) (user: |
| 1228 | 1252 | hyperlink_to_user(zUser,zDate,")"); |
| 1229 | 1253 | 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)) | |
| 1232 | 1256 | @ [annotate]</a> |
| 1233 | - @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers)) | |
| 1257 | + @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers)) | |
| 1234 | 1258 | @ [blame]</a> |
| 1235 | 1259 | } |
| 1236 | 1260 | cnt++; |
| 1237 | 1261 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 1238 | 1262 | blob_append(pDownloadName, zName, -1); |
| @@ -1312,11 +1336,11 @@ | ||
| 1312 | 1336 | } |
| 1313 | 1337 | @ - %!w(zCom) by |
| 1314 | 1338 | hyperlink_to_user(zUser,zDate," on"); |
| 1315 | 1339 | hyperlink_to_date(zDate, "."); |
| 1316 | 1340 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 1317 | - blob_appendf(pDownloadName, "%.10s.txt", zUuid); | |
| 1341 | + blob_appendf(pDownloadName, "%S.txt", zUuid); | |
| 1318 | 1342 | } |
| 1319 | 1343 | tag_private_status(rid); |
| 1320 | 1344 | cnt++; |
| 1321 | 1345 | } |
| 1322 | 1346 | db_finalize(&q); |
| @@ -1339,17 +1363,17 @@ | ||
| 1339 | 1363 | }else{ |
| 1340 | 1364 | @ Attachment "%h(zFilename)" to |
| 1341 | 1365 | } |
| 1342 | 1366 | objType |= OBJTYPE_ATTACHMENT; |
| 1343 | 1367 | 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>] | |
| 1346 | 1370 | }else{ |
| 1347 | 1371 | @ ticket [%S(zTarget)] |
| 1348 | 1372 | } |
| 1349 | 1373 | }else{ |
| 1350 | - if( g.perm.Hyperlink && g.perm.RdWiki ){ | |
| 1374 | + if( g.perm.Hyperlink && g.anon.RdWiki ){ | |
| 1351 | 1375 | @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>] |
| 1352 | 1376 | }else{ |
| 1353 | 1377 | @ wiki page [%h(zTarget)] |
| 1354 | 1378 | } |
| 1355 | 1379 | } |
| @@ -1364,11 +1388,11 @@ | ||
| 1364 | 1388 | } |
| 1365 | 1389 | db_finalize(&q); |
| 1366 | 1390 | if( cnt==0 ){ |
| 1367 | 1391 | @ Control artifact. |
| 1368 | 1392 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 1369 | - blob_appendf(pDownloadName, "%.10s.txt", zUuid); | |
| 1393 | + blob_appendf(pDownloadName, "%S.txt", zUuid); | |
| 1370 | 1394 | } |
| 1371 | 1395 | tag_private_status(rid); |
| 1372 | 1396 | } |
| 1373 | 1397 | return objType; |
| 1374 | 1398 | } |
| @@ -1397,11 +1421,11 @@ | ||
| 1397 | 1421 | ReCompiled *pRe = 0; |
| 1398 | 1422 | u64 diffFlags; |
| 1399 | 1423 | u32 objdescFlags = 0; |
| 1400 | 1424 | |
| 1401 | 1425 | login_check_credentials(); |
| 1402 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 1426 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 1403 | 1427 | v1 = name_to_rid_www("v1"); |
| 1404 | 1428 | v2 = name_to_rid_www("v2"); |
| 1405 | 1429 | if( v1==0 || v2==0 ) fossil_redirect_home(); |
| 1406 | 1430 | zRe = P("regex"); |
| 1407 | 1431 | if( zRe ) re_compile(&pRe, zRe, 0); |
| @@ -1448,17 +1472,17 @@ | ||
| 1448 | 1472 | g.zTop, P("v1"), P("v2"), zW); |
| 1449 | 1473 | } |
| 1450 | 1474 | |
| 1451 | 1475 | if( P("smhdr")!=0 ){ |
| 1452 | 1476 | @ <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> | |
| 1455 | 1479 | }else{ |
| 1456 | 1480 | @ <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> | |
| 1458 | 1482 | 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> | |
| 1460 | 1484 | object_description(v2, objdescFlags, 0); |
| 1461 | 1485 | } |
| 1462 | 1486 | if( pRe ){ |
| 1463 | 1487 | @ <b>Only differences that match regular expression "%h(zRe)" |
| 1464 | 1488 | @ are shown.</b> |
| @@ -1482,11 +1506,11 @@ | ||
| 1482 | 1506 | const char *zMime; |
| 1483 | 1507 | Blob content; |
| 1484 | 1508 | |
| 1485 | 1509 | rid = name_to_rid_www("name"); |
| 1486 | 1510 | login_check_credentials(); |
| 1487 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 1511 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 1488 | 1512 | if( rid==0 ) fossil_redirect_home(); |
| 1489 | 1513 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1490 | 1514 | if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){ |
| 1491 | 1515 | g.isConst = 1; |
| 1492 | 1516 | } |
| @@ -1579,11 +1603,11 @@ | ||
| 1579 | 1603 | char *zUuid; |
| 1580 | 1604 | u32 objdescFlags = 0; |
| 1581 | 1605 | |
| 1582 | 1606 | rid = name_to_rid_www("name"); |
| 1583 | 1607 | login_check_credentials(); |
| 1584 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 1608 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 1585 | 1609 | if( rid==0 ) fossil_redirect_home(); |
| 1586 | 1610 | if( g.perm.Admin ){ |
| 1587 | 1611 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1588 | 1612 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| 1589 | 1613 | style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun", |
| @@ -1765,11 +1789,11 @@ | ||
| 1765 | 1789 | if( rid==0 ){ |
| 1766 | 1790 | rid = name_to_rid_www("name"); |
| 1767 | 1791 | } |
| 1768 | 1792 | |
| 1769 | 1793 | login_check_credentials(); |
| 1770 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 1794 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 1771 | 1795 | if( rid==0 ) fossil_redirect_home(); |
| 1772 | 1796 | if( g.perm.Admin ){ |
| 1773 | 1797 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1774 | 1798 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| 1775 | 1799 | style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun", |
| @@ -1880,11 +1904,11 @@ | ||
| 1880 | 1904 | Manifest *pTktChng; |
| 1881 | 1905 | int modPending; |
| 1882 | 1906 | const char *zModAction; |
| 1883 | 1907 | char *zTktTitle; |
| 1884 | 1908 | login_check_credentials(); |
| 1885 | - if( !g.perm.RdTkt ){ login_needed(); return; } | |
| 1909 | + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } | |
| 1886 | 1910 | rid = name_to_rid_www("name"); |
| 1887 | 1911 | if( rid==0 ){ fossil_redirect_home(); } |
| 1888 | 1912 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1889 | 1913 | if( g.perm.Admin ){ |
| 1890 | 1914 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| @@ -1935,11 +1959,11 @@ | ||
| 1935 | 1959 | } |
| 1936 | 1960 | |
| 1937 | 1961 | @ <div class="section">Overview</div> |
| 1938 | 1962 | @ <p><table class="label-value"> |
| 1939 | 1963 | @ <tr><th>Artifact ID:</th> |
| 1940 | - @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> | |
| 1964 | + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> | |
| 1941 | 1965 | if( g.perm.Setup ){ |
| 1942 | 1966 | @ (%d(rid)) |
| 1943 | 1967 | } |
| 1944 | 1968 | modPending = moderation_pending(rid); |
| 1945 | 1969 | if( modPending ){ |
| @@ -2252,11 +2276,11 @@ | ||
| 2252 | 2276 | Blob comment; |
| 2253 | 2277 | char *zBranchName = 0; |
| 2254 | 2278 | Stmt q; |
| 2255 | 2279 | |
| 2256 | 2280 | login_check_credentials(); |
| 2257 | - if( !g.perm.Write ){ login_needed(); return; } | |
| 2281 | + if( !g.perm.Write ){ login_needed(g.anon.Write); return; } | |
| 2258 | 2282 | rid = name_to_typed_rid(P("r"), "ci"); |
| 2259 | 2283 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2260 | 2284 | zComment = db_text(0, "SELECT coalesce(ecomment,comment)" |
| 2261 | 2285 | " FROM event WHERE objid=%d", rid); |
| 2262 | 2286 | if( zComment==0 ) fossil_redirect_home(); |
| @@ -2454,11 +2478,11 @@ | ||
| 2454 | 2478 | @ </blockquote> |
| 2455 | 2479 | @ <hr /> |
| 2456 | 2480 | blob_reset(&suffix); |
| 2457 | 2481 | } |
| 2458 | 2482 | @ <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> | |
| 2460 | 2484 | form_begin(0, "%R/ci_edit"); |
| 2461 | 2485 | login_insert_csrf_secret(); |
| 2462 | 2486 | @ <div><input type="hidden" name="r" value="%s(zUuid)" /> |
| 2463 | 2487 | @ <table border="0" cellspacing="10"> |
| 2464 | 2488 | |
| @@ -2572,12 +2596,11 @@ | ||
| 2572 | 2596 | @ <tr><th align="right" valign="top">Branch Closure:</th> |
| 2573 | 2597 | @ <td valign="top"> |
| 2574 | 2598 | @ <label><input type="checkbox" name="close"%s(zCloseFlag) /> |
| 2575 | 2599 | @ Mark branch |
| 2576 | 2600 | @ <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> | |
| 2579 | 2602 | @ </td></tr> |
| 2580 | 2603 | } |
| 2581 | 2604 | } |
| 2582 | 2605 | if( zBranchName ) fossil_free(zBranchName); |
| 2583 | 2606 | |
| 2584 | 2607 |
| --- 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 | @ |
| 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 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 Unified Diffs</a> |
| 700 | @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName)) |
| 701 | @ Show Side-by-Side 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 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 | @ — %!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 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 | @ |
| 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 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 Unified Diffs</a> |
| 724 | @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName)) |
| 725 | @ Show Side-by-Side 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 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 | @ — %!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 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 @@ | ||
| 352 | 352 | login_cookie_path(), -86400); |
| 353 | 353 | db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " |
| 354 | 354 | " cexpire=0 WHERE uid=%d" |
| 355 | 355 | " AND login NOT IN ('anonymous','nobody'," |
| 356 | 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 | - ; | |
| 357 | + cgi_replace_parameter(cookie, NULL); | |
| 358 | + cgi_replace_parameter("anon", NULL); | |
| 363 | 359 | } |
| 364 | 360 | } |
| 365 | 361 | |
| 366 | 362 | /* |
| 367 | 363 | ** Return true if the prefix of zStr matches zPattern. Return false if |
| @@ -451,27 +447,46 @@ | ||
| 451 | 447 | } |
| 452 | 448 | sqlite3_result_int(context, rc); |
| 453 | 449 | } |
| 454 | 450 | |
| 455 | 451 | /* |
| 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 | +/* | |
| 462 | 467 | ** There used to be a page named "my" that was designed to show information |
| 463 | 468 | ** about a specific user. The "my" page was linked from the "Logged in as USER" |
| 464 | 469 | ** line on the title bar. The "my" page was never completed so it is now |
| 465 | 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" | |
| 466 | 481 | */ |
| 467 | 482 | void login_page(void){ |
| 468 | 483 | const char *zUsername, *zPasswd; |
| 469 | 484 | const char *zNew1, *zNew2; |
| 470 | 485 | const char *zAnonPw = 0; |
| 471 | 486 | const char *zGoto = P("g"); |
| 472 | - int anonFlag; | |
| 487 | + int anonFlag; /* Login as "anonymous" would be useful */ | |
| 473 | 488 | char *zErrMsg = ""; |
| 474 | 489 | int uid; /* User id logged in user */ |
| 475 | 490 | char *zSha1Pw; |
| 476 | 491 | const char *zIpAddr; /* IP address of requestor */ |
| 477 | 492 | const char *zReferer; |
| @@ -489,15 +504,20 @@ | ||
| 489 | 504 | } |
| 490 | 505 | sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
| 491 | 506 | constant_time_cmp_function, 0, 0); |
| 492 | 507 | zUsername = P("u"); |
| 493 | 508 | 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") ){ | |
| 496 | 513 | login_clear_login_data(); |
| 497 | 514 | redirect_to_g(); |
| 515 | + return; | |
| 498 | 516 | } |
| 517 | + | |
| 518 | + /* Deal with password-change requests */ | |
| 499 | 519 | if( g.perm.Password && zPasswd |
| 500 | 520 | && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 |
| 501 | 521 | ){ |
| 502 | 522 | /* The user requests a password change */ |
| 503 | 523 | zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); |
| @@ -577,19 +597,40 @@ | ||
| 577 | 597 | } |
| 578 | 598 | } |
| 579 | 599 | style_header("Login/Logout"); |
| 580 | 600 | style_adunit_config(ADUNIT_OFF); |
| 581 | 601 | @ %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 | + } | |
| 584 | 616 | } |
| 585 | 617 | form_begin(0, "%R/login"); |
| 586 | 618 | if( zGoto ){ |
| 587 | 619 | @ <input type="hidden" name="g" value="%h(zGoto)" /> |
| 588 | 620 | }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){ |
| 589 | 621 | @ <input type="hidden" name="g" value="%h(zReferer)" /> |
| 590 | 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 | + } | |
| 591 | 632 | @ <table class="login_out"> |
| 592 | 633 | @ <tr> |
| 593 | 634 | @ <td class="login_out_label">User ID:</td> |
| 594 | 635 | if( anonFlag ){ |
| 595 | 636 | @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td> |
| @@ -599,11 +640,11 @@ | ||
| 599 | 640 | @ </tr> |
| 600 | 641 | @ <tr> |
| 601 | 642 | @ <td class="login_out_label">Password:</td> |
| 602 | 643 | @ <td><input type="password" id="p" name="p" value="" size="30" /></td> |
| 603 | 644 | @ </tr> |
| 604 | - if( g.zLogin==0 ){ | |
| 645 | + if( g.zLogin==0 && (anonFlag || zGoto==0) ){ | |
| 605 | 646 | zAnonPw = db_text(0, "SELECT pw FROM user" |
| 606 | 647 | " WHERE login='anonymous'" |
| 607 | 648 | " AND cap!=''"); |
| 608 | 649 | } |
| 609 | 650 | @ <tr> |
| @@ -624,23 +665,14 @@ | ||
| 624 | 665 | @ form.action = "%h(zSSL)/login"; |
| 625 | 666 | @ } |
| 626 | 667 | } |
| 627 | 668 | @ } |
| 628 | 669 | @ </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> | |
| 639 | 671 | if( db_get_boolean("self-register", 0) ){ |
| 640 | 672 | @ <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>. | |
| 642 | 674 | } |
| 643 | 675 | if( zAnonPw ){ |
| 644 | 676 | unsigned int uSeed = captcha_seed(); |
| 645 | 677 | const char *zDecoded = captcha_decode(uSeed); |
| 646 | 678 | int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
| @@ -657,22 +689,14 @@ | ||
| 657 | 689 | @ onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" /> |
| 658 | 690 | } |
| 659 | 691 | @ </div> |
| 660 | 692 | free(zCaptcha); |
| 661 | 693 | } |
| 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 | 694 | @ </form> |
| 669 | 695 | if( g.perm.Password ){ |
| 670 | 696 | @ <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> | |
| 674 | 698 | form_begin(0, "%R/login"); |
| 675 | 699 | @ <table> |
| 676 | 700 | @ <tr><td class="login_out_label">Old Password:</td> |
| 677 | 701 | @ <td><input type="password" name="p" size="30" /></td></tr> |
| 678 | 702 | @ <tr><td class="login_out_label">New Password:</td> |
| @@ -811,10 +835,11 @@ | ||
| 811 | 835 | ** variables appropriately. |
| 812 | 836 | ** |
| 813 | 837 | ** g.userUid Database USER.UID value. Might be -1 for "nobody" |
| 814 | 838 | ** g.zLogin Database USER.LOGIN value. NULL for user "nobody" |
| 815 | 839 | ** g.perm Permissions granted to this user |
| 840 | +** g.anon Permissions that would be available to anonymous | |
| 816 | 841 | ** g.isHuman True if the user is human, not a spider or robot |
| 817 | 842 | ** |
| 818 | 843 | */ |
| 819 | 844 | void login_check_credentials(void){ |
| 820 | 845 | int uid = 0; /* User id */ |
| @@ -1002,23 +1027,32 @@ | ||
| 1002 | 1027 | ** Memory of settings |
| 1003 | 1028 | */ |
| 1004 | 1029 | static int login_anon_once = 1; |
| 1005 | 1030 | |
| 1006 | 1031 | /* |
| 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"). | |
| 1009 | 1038 | */ |
| 1010 | 1039 | void login_set_anon_nobody_capabilities(void){ |
| 1011 | - if( g.zLogin && login_anon_once ){ | |
| 1040 | + if( login_anon_once ){ | |
| 1012 | 1041 | const char *zCap; |
| 1013 | - /* All logged-in users inherit privileges from "nobody" */ | |
| 1042 | + /* All users get privileges from "nobody" */ | |
| 1014 | 1043 | zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'"); |
| 1015 | 1044 | 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 ){ | |
| 1017 | 1047 | /* All logged-in users inherit privileges from "anonymous" */ |
| 1018 | - zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); | |
| 1019 | 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); | |
| 1020 | 1054 | } |
| 1021 | 1055 | login_anon_once = 0; |
| 1022 | 1056 | } |
| 1023 | 1057 | } |
| 1024 | 1058 | |
| @@ -1025,55 +1059,57 @@ | ||
| 1025 | 1059 | /* |
| 1026 | 1060 | ** Flags passed into the 2nd argument of login_set/replace_capabilities(). |
| 1027 | 1061 | */ |
| 1028 | 1062 | #if INTERFACE |
| 1029 | 1063 | #define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */ |
| 1064 | +#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */ | |
| 1030 | 1065 | #endif |
| 1031 | 1066 | |
| 1032 | 1067 | /* |
| 1033 | -** Adds all capability flags in zCap to g.perm. | |
| 1068 | +** Adds all capability flags in zCap to g.perm or g.anon. | |
| 1034 | 1069 | */ |
| 1035 | 1070 | void login_set_capabilities(const char *zCap, unsigned flags){ |
| 1036 | 1071 | int i; |
| 1072 | + FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm; | |
| 1037 | 1073 | if(NULL==zCap){ |
| 1038 | 1074 | return; |
| 1039 | 1075 | } |
| 1040 | 1076 | for(i=0; zCap[i]; i++){ |
| 1041 | 1077 | 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; | |
| 1049 | 1085 | /* 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; | |
| 1075 | 1111 | |
| 1076 | 1112 | /* The "u" privileges is a little different. It recursively |
| 1077 | 1113 | ** inherits all privileges of the user named "reader" */ |
| 1078 | 1114 | case 'u': { |
| 1079 | 1115 | if( (flags & LOGIN_IGNORE_UV)==0 ){ |
| @@ -1110,42 +1146,43 @@ | ||
| 1110 | 1146 | /* |
| 1111 | 1147 | ** If the current login lacks any of the capabilities listed in |
| 1112 | 1148 | ** the input, then return 0. If all capabilities are present, then |
| 1113 | 1149 | ** return 1. |
| 1114 | 1150 | */ |
| 1115 | -int login_has_capability(const char *zCap, int nCap){ | |
| 1151 | +int login_has_capability(const char *zCap, int nCap, u32 flgs){ | |
| 1116 | 1152 | int i; |
| 1117 | 1153 | int rc = 1; |
| 1154 | + FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm; | |
| 1118 | 1155 | if( nCap<0 ) nCap = strlen(zCap); |
| 1119 | 1156 | for(i=0; i<nCap && rc && zCap[i]; i++){ |
| 1120 | 1157 | 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; | |
| 1141 | 1178 | /* case 'u': READER */ |
| 1142 | 1179 | /* 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; | |
| 1145 | 1182 | /* case 'y': */ |
| 1146 | - case 'z': rc = g.perm.Zip; break; | |
| 1183 | + case 'z': rc = p->Zip; break; | |
| 1147 | 1184 | default: rc = 0; break; |
| 1148 | 1185 | } |
| 1149 | 1186 | } |
| 1150 | 1187 | return rc; |
| 1151 | 1188 | } |
| @@ -1195,11 +1232,11 @@ | ||
| 1195 | 1232 | |
| 1196 | 1233 | /* |
| 1197 | 1234 | ** Call this routine when the credential check fails. It causes |
| 1198 | 1235 | ** a redirect to the "login" page. |
| 1199 | 1236 | */ |
| 1200 | -void login_needed(void){ | |
| 1237 | +void login_needed(int anonOk){ | |
| 1201 | 1238 | #ifdef FOSSIL_ENABLE_JSON |
| 1202 | 1239 | if(g.json.isJsonMode){ |
| 1203 | 1240 | json_err( FSL_JSON_E_DENIED, NULL, 1 ); |
| 1204 | 1241 | fossil_exit(0); |
| 1205 | 1242 | /* NOTREACHED */ |
| @@ -1206,11 +1243,23 @@ | ||
| 1206 | 1243 | assert(0); |
| 1207 | 1244 | }else |
| 1208 | 1245 | #endif /* FOSSIL_ENABLE_JSON */ |
| 1209 | 1246 | { |
| 1210 | 1247 | 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)); | |
| 1212 | 1261 | /* NOTREACHED */ |
| 1213 | 1262 | assert(0); |
| 1214 | 1263 | } |
| 1215 | 1264 | } |
| 1216 | 1265 | |
| @@ -1219,17 +1268,14 @@ | ||
| 1219 | 1268 | ** the anonymous user has Hyperlink permission, then paint a mesage |
| 1220 | 1269 | ** to inform the user that much more information is available by |
| 1221 | 1270 | ** logging in as anonymous. |
| 1222 | 1271 | */ |
| 1223 | 1272 | 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 ){ | |
| 1228 | 1274 | const char *zUrl = PD("REQUEST_URI", "index"); |
| 1229 | 1275 | @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> |
| 1230 | - @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> | |
| 1276 | + @ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> | |
| 1231 | 1277 | @ to enable hyperlinks.</p> |
| 1232 | 1278 | } |
| 1233 | 1279 | } |
| 1234 | 1280 | |
| 1235 | 1281 | /* |
| 1236 | 1282 |
| --- 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&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&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 @@ | ||
| 193 | 193 | /* Information used to populate the RCVFROM table */ |
| 194 | 194 | int rcvid; /* The rcvid. 0 if not yet defined. */ |
| 195 | 195 | char *zIpAddr; /* The remote IP address */ |
| 196 | 196 | char *zNonce; /* The nonce used for login */ |
| 197 | 197 | |
| 198 | - /* permissions used by the server */ | |
| 198 | + /* permissions available to current user */ | |
| 199 | 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; | |
| 200 | 205 | |
| 201 | 206 | #ifdef FOSSIL_ENABLE_TCL |
| 202 | 207 | /* all Tcl related context necessary for integration */ |
| 203 | 208 | struct TclContext tcl; |
| 204 | 209 | #endif |
| @@ -689,12 +694,33 @@ | ||
| 689 | 694 | g.argv = zNewArgv; |
| 690 | 695 | } |
| 691 | 696 | zCmdName = g.argv[1]; |
| 692 | 697 | } |
| 693 | 698 | #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 | + } | |
| 696 | 722 | #endif |
| 697 | 723 | rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx); |
| 698 | 724 | if( rc==1 ){ |
| 699 | 725 | #ifdef FOSSIL_ENABLE_TH1_HOOKS |
| 700 | 726 | if( !g.isHTTP && !g.fNoThHook ){ |
| @@ -1152,11 +1178,11 @@ | ||
| 1152 | 1178 | const char *z = aCommand[i].zName; |
| 1153 | 1179 | if( '/'==*z || strncmp(z,"test",4)==0 ) continue; |
| 1154 | 1180 | if( j==0 ){ |
| 1155 | 1181 | @ <td valign="top"><ul> |
| 1156 | 1182 | } |
| 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> | |
| 1158 | 1184 | j++; |
| 1159 | 1185 | if( j>=n ){ |
| 1160 | 1186 | @ </ul></td> |
| 1161 | 1187 | j = 0; |
| 1162 | 1188 | } |
| @@ -1180,11 +1206,11 @@ | ||
| 1180 | 1206 | if( '/'!=*z ) continue; |
| 1181 | 1207 | if( j==0 ){ |
| 1182 | 1208 | @ <td valign="top"><ul> |
| 1183 | 1209 | } |
| 1184 | 1210 | 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> | |
| 1186 | 1212 | }else{ |
| 1187 | 1213 | @ <li>%s(z+1)</li> |
| 1188 | 1214 | } |
| 1189 | 1215 | j++; |
| 1190 | 1216 | if( j>=n ){ |
| @@ -1210,11 +1236,11 @@ | ||
| 1210 | 1236 | if( strncmp(z,"test",4)!=0 ) continue; |
| 1211 | 1237 | if( j==0 ){ |
| 1212 | 1238 | @ <td valign="top"><ul> |
| 1213 | 1239 | } |
| 1214 | 1240 | 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> | |
| 1216 | 1242 | }else{ |
| 1217 | 1243 | @ <li>%s(z)</li> |
| 1218 | 1244 | } |
| 1219 | 1245 | j++; |
| 1220 | 1246 | if( j>=n ){ |
| @@ -1331,12 +1357,15 @@ | ||
| 1331 | 1357 | ** zRepo might be a directory itself. In that case chroot into |
| 1332 | 1358 | ** the directory zRepo. |
| 1333 | 1359 | ** |
| 1334 | 1360 | ** Assume the user-id and group-id of the repository, or if zRepo |
| 1335 | 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. | |
| 1336 | 1365 | */ |
| 1337 | -static char *enter_chroot_jail(char *zRepo){ | |
| 1366 | +static char *enter_chroot_jail(char *zRepo, int noJail){ | |
| 1338 | 1367 | #if !defined(_WIN32) |
| 1339 | 1368 | if( getuid()==0 ){ |
| 1340 | 1369 | int i; |
| 1341 | 1370 | struct stat sStat; |
| 1342 | 1371 | Blob dir; |
| @@ -1345,26 +1374,28 @@ | ||
| 1345 | 1374 | db_close(1); |
| 1346 | 1375 | } |
| 1347 | 1376 | |
| 1348 | 1377 | file_canonical_name(zRepo, &dir, 0); |
| 1349 | 1378 | 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 ){ | |
| 1360 | 1381 | if( file_chdir(zDir, 1) ){ |
| 1361 | 1382 | fossil_fatal("unable to chroot into %s", zDir); |
| 1362 | 1383 | } |
| 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]; | |
| 1364 | 1396 | } |
| 1365 | - zRepo = &zDir[i]; | |
| 1366 | 1397 | } |
| 1367 | 1398 | if( stat(zRepo, &sStat)!=0 ){ |
| 1368 | 1399 | fossil_fatal("cannot stat() repository: %s", zRepo); |
| 1369 | 1400 | } |
| 1370 | 1401 | i = setgid(sStat.st_gid); |
| @@ -1900,10 +1931,22 @@ | ||
| 1900 | 1931 | */ |
| 1901 | 1932 | cgi_setenv("HOME", blob_str(&value)); |
| 1902 | 1933 | blob_reset(&value); |
| 1903 | 1934 | continue; |
| 1904 | 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 | + } | |
| 1905 | 1948 | } |
| 1906 | 1949 | blob_reset(&config); |
| 1907 | 1950 | if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ |
| 1908 | 1951 | cgi_panic("Unable to find or open the project repository"); |
| 1909 | 1952 | } |
| @@ -1988,15 +2031,17 @@ | ||
| 1988 | 2031 | ** |
| 1989 | 2032 | ** Options: |
| 1990 | 2033 | ** --localauth enable automatic login for local connections |
| 1991 | 2034 | ** --host NAME specify hostname of the server |
| 1992 | 2035 | ** --https signal a request coming in via https |
| 2036 | +** --nojail drop root privilege but do not enter the chroot jail | |
| 1993 | 2037 | ** --nossl signal that no SSL connections are available |
| 1994 | 2038 | ** --notfound URL use URL as "HTTP 404, object not found" page. |
| 1995 | 2039 | ** --files GLOB comma-separate glob patterns for static file to serve |
| 1996 | 2040 | ** --baseurl URL base URL (useful with reverse proxies) |
| 1997 | 2041 | ** --scgi Interpret input as SCGI rather than HTTP |
| 2042 | +** --skin LABEL Use override skin LABEL | |
| 1998 | 2043 | ** |
| 1999 | 2044 | ** See also: cgi, server, winsrv |
| 2000 | 2045 | */ |
| 2001 | 2046 | void cmd_http(void){ |
| 2002 | 2047 | const char *zIpAddr = 0; |
| @@ -2003,10 +2048,11 @@ | ||
| 2003 | 2048 | const char *zNotFound; |
| 2004 | 2049 | const char *zHost; |
| 2005 | 2050 | const char *zAltBase; |
| 2006 | 2051 | const char *zFileGlob; |
| 2007 | 2052 | int useSCGI; |
| 2053 | + int noJail; | |
| 2008 | 2054 | |
| 2009 | 2055 | /* The winhttp module passes the --files option as --files-urlenc with |
| 2010 | 2056 | ** the argument being URL encoded, to avoid wildcard expansion in the |
| 2011 | 2057 | ** shell. This option is for internal use and is undocumented. |
| 2012 | 2058 | */ |
| @@ -2016,11 +2062,13 @@ | ||
| 2016 | 2062 | dehttpize(z); |
| 2017 | 2063 | zFileGlob = z; |
| 2018 | 2064 | }else{ |
| 2019 | 2065 | zFileGlob = find_option("files",0,1); |
| 2020 | 2066 | } |
| 2067 | + skin_override(); | |
| 2021 | 2068 | zNotFound = find_option("notfound", 0, 1); |
| 2069 | + noJail = find_option("nojail",0,0)!=0; | |
| 2022 | 2070 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 2023 | 2071 | g.sslNotAvailable = find_option("nossl", 0, 0)!=0; |
| 2024 | 2072 | useSCGI = find_option("scgi", 0, 0)!=0; |
| 2025 | 2073 | zAltBase = find_option("baseurl", 0, 1); |
| 2026 | 2074 | if( zAltBase ) set_base_url(zAltBase); |
| @@ -2053,11 +2101,11 @@ | ||
| 2053 | 2101 | zIpAddr = cgi_ssh_remote_addr(0); |
| 2054 | 2102 | if( zIpAddr && zIpAddr[0] ){ |
| 2055 | 2103 | g.fSshClient |= CGI_SSH_CLIENT; |
| 2056 | 2104 | } |
| 2057 | 2105 | } |
| 2058 | - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); | |
| 2106 | + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); | |
| 2059 | 2107 | if( useSCGI ){ |
| 2060 | 2108 | cgi_handle_scgi_request(); |
| 2061 | 2109 | }else if( g.fSshClient & CGI_SSH_CLIENT ){ |
| 2062 | 2110 | ssh_request_loop(zIpAddr, glob_create(zFileGlob)); |
| 2063 | 2111 | }else{ |
| @@ -2171,18 +2219,21 @@ | ||
| 2171 | 2219 | ** and the connection is from localhost. The optional REPOSITORY argument |
| 2172 | 2220 | ** to "ui" may be a directory and will function as "server" if and only if |
| 2173 | 2221 | ** the --notfound option is used. |
| 2174 | 2222 | ** |
| 2175 | 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 | |
| 2176 | 2226 | ** --localauth enable automatic login for requests from localhost |
| 2177 | 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 | |
| 2178 | 2230 | ** -P|--port TCPPORT listen to request on port TCPPORT |
| 2179 | 2231 | ** --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 | 2232 | ** --scgi Accept SCGI rather than HTTP |
| 2233 | +** --skin LABEL Use override skin LABEL | |
| 2234 | + | |
| 2184 | 2235 | ** |
| 2185 | 2236 | ** See also: cgi, http, winsrv |
| 2186 | 2237 | */ |
| 2187 | 2238 | void cmd_webserver(void){ |
| 2188 | 2239 | int iPort, mxPort; /* Range of TCP ports allowed */ |
| @@ -2190,10 +2241,13 @@ | ||
| 2190 | 2241 | const char *zBrowser; /* Name of web browser program */ |
| 2191 | 2242 | char *zBrowserCmd = 0; /* Command to launch the web browser */ |
| 2192 | 2243 | int isUiCmd; /* True if command is "ui", not "server' */ |
| 2193 | 2244 | const char *zNotFound; /* The --notfound option or NULL */ |
| 2194 | 2245 | int flags = 0; /* Server flags */ |
| 2246 | +#if !defined(_WIN32) | |
| 2247 | + int noJail; /* Do not enter the chroot jail */ | |
| 2248 | +#endif | |
| 2195 | 2249 | const char *zAltBase; /* Argument to the --baseurl option */ |
| 2196 | 2250 | const char *zFileGlob; /* Static content must match this */ |
| 2197 | 2251 | char *zIpAddr = 0; /* Bind to this IP address */ |
| 2198 | 2252 | |
| 2199 | 2253 | #if defined(_WIN32) |
| @@ -2207,10 +2261,14 @@ | ||
| 2207 | 2261 | dehttpize(z); |
| 2208 | 2262 | zFileGlob = z; |
| 2209 | 2263 | }else{ |
| 2210 | 2264 | zFileGlob = find_option("files",0,1); |
| 2211 | 2265 | } |
| 2266 | + skin_override(); | |
| 2267 | +#if !defined(_WIN32) | |
| 2268 | + noJail = find_option("nojail",0,0)!=0; | |
| 2269 | +#endif | |
| 2212 | 2270 | g.useLocalauth = find_option("localauth", 0, 0)!=0; |
| 2213 | 2271 | Th_InitTraceLog(); |
| 2214 | 2272 | zPort = find_option("port", "P", 1); |
| 2215 | 2273 | zNotFound = find_option("notfound", 0, 1); |
| 2216 | 2274 | zAltBase = find_option("baseurl", 0, 1); |
| @@ -2284,11 +2342,11 @@ | ||
| 2284 | 2342 | if( g.fHttpTrace || g.fSqlTrace ){ |
| 2285 | 2343 | fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); |
| 2286 | 2344 | } |
| 2287 | 2345 | g.cgiOutput = 1; |
| 2288 | 2346 | find_server_repository(isUiCmd && zNotFound==0, 2); |
| 2289 | - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); | |
| 2347 | + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); | |
| 2290 | 2348 | if( flags & HTTP_SERVER_SCGI ){ |
| 2291 | 2349 | cgi_handle_scgi_request(); |
| 2292 | 2350 | }else{ |
| 2293 | 2351 | cgi_handle_http_request(0); |
| 2294 | 2352 | } |
| 2295 | 2353 |
| --- 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 @@ | ||
| 491 | 491 | $(OBJDIR)/cson_amalgamation.o |
| 492 | 492 | |
| 493 | 493 | |
| 494 | 494 | $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ) |
| 495 | 495 | $(OBJDIR)/codecheck1 $(TRANS_SRC) |
| 496 | - $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) | |
| 496 | + $(TCC) $(CFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) | |
| 497 | 497 | |
| 498 | 498 | # This rule prevents make from using its default rules to try build |
| 499 | 499 | # an executable named "manifest" out of the file named "manifest.c" |
| 500 | 500 | # |
| 501 | 501 | $(SRCDIR)/../manifest: |
| 502 | 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) -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 @@ | ||
| 1621 | 1621 | if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ |
| 1622 | 1622 | zNewStatus = pManifest->aField[i].zValue; |
| 1623 | 1623 | } |
| 1624 | 1624 | } |
| 1625 | 1625 | if( zNewStatus ){ |
| 1626 | - blob_appendf(&comment, "%h ticket [%s|%S]: <i>%h</i>", | |
| 1626 | + blob_appendf(&comment, "%h ticket [%!S|%S]: <i>%h</i>", | |
| 1627 | 1627 | zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle |
| 1628 | 1628 | ); |
| 1629 | 1629 | if( pManifest->nField>1 ){ |
| 1630 | 1630 | blob_appendf(&comment, " plus %d other change%s", |
| 1631 | 1631 | pManifest->nField-1, pManifest->nField==2 ? "" : "s"); |
| 1632 | 1632 | } |
| 1633 | - blob_appendf(&brief, "%h ticket [%s|%S].", | |
| 1633 | + blob_appendf(&brief, "%h ticket [%!S|%S].", | |
| 1634 | 1634 | zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid); |
| 1635 | 1635 | }else{ |
| 1636 | 1636 | zNewStatus = db_text("unknown", |
| 1637 | 1637 | "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q", |
| 1638 | 1638 | zStatusColumn, pManifest->zTicketUuid |
| 1639 | 1639 | ); |
| 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 " | |
| 1641 | 1641 | "%d other change%s", |
| 1642 | 1642 | pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus, |
| 1643 | 1643 | pManifest->nField, pManifest->nField==1 ? "" : "s" |
| 1644 | 1644 | ); |
| 1645 | 1645 | fossil_free(zNewStatus); |
| 1646 | - blob_appendf(&brief, "Ticket [%s|%S]: %d change%s", | |
| 1646 | + blob_appendf(&brief, "Ticket [%!S|%S]: %d change%s", | |
| 1647 | 1647 | pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField, |
| 1648 | 1648 | pManifest->nField==1 ? "" : "s" |
| 1649 | 1649 | ); |
| 1650 | 1650 | } |
| 1651 | 1651 | }else{ |
| 1652 | - blob_appendf(&comment, "New ticket [%s|%S] <i>%h</i>.", | |
| 1652 | + blob_appendf(&comment, "New ticket [%!S|%S] <i>%h</i>.", | |
| 1653 | 1653 | pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle |
| 1654 | 1654 | ); |
| 1655 | - blob_appendf(&brief, "New ticket [%s|%S].", pManifest->zTicketUuid, | |
| 1655 | + blob_appendf(&brief, "New ticket [%!S|%S].", pManifest->zTicketUuid, | |
| 1656 | 1656 | pManifest->zTicketUuid); |
| 1657 | 1657 | } |
| 1658 | 1658 | fossil_free(zTitle); |
| 1659 | 1659 | db_multi_exec( |
| 1660 | 1660 | "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)" |
| @@ -1756,11 +1756,11 @@ | ||
| 1756 | 1756 | if( (p = manifest_cache_find(rid))!=0 ){ |
| 1757 | 1757 | blob_reset(pContent); |
| 1758 | 1758 | }else if( (p = manifest_parse(pContent, rid, 0))==0 ){ |
| 1759 | 1759 | assert( blob_is_reset(pContent) || pContent==0 ); |
| 1760 | 1760 | if( (flags & MC_NO_ERRORS)==0 ){ |
| 1761 | - fossil_error(1, "syntax error in manifest [%s]", | |
| 1761 | + fossil_error(1, "syntax error in manifest [%S]", | |
| 1762 | 1762 | db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid)); |
| 1763 | 1763 | } |
| 1764 | 1764 | return 0; |
| 1765 | 1765 | } |
| 1766 | 1766 | if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ |
| @@ -1771,11 +1771,11 @@ | ||
| 1771 | 1771 | } |
| 1772 | 1772 | if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){ |
| 1773 | 1773 | manifest_destroy(p); |
| 1774 | 1774 | assert( blob_is_reset(pContent) ); |
| 1775 | 1775 | 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]", | |
| 1777 | 1777 | db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid)); |
| 1778 | 1778 | } |
| 1779 | 1779 | return 0; |
| 1780 | 1780 | } |
| 1781 | 1781 | db_begin_transaction(); |
| @@ -2031,23 +2031,23 @@ | ||
| 2031 | 2031 | p->zAttachTarget, p->zAttachName |
| 2032 | 2032 | ); |
| 2033 | 2033 | if( 'w' == attachToType ){ |
| 2034 | 2034 | if( isAdd ){ |
| 2035 | 2035 | zComment = mprintf( |
| 2036 | - "Add attachment [/artifact/%s|%h] to wiki page [%h]", | |
| 2036 | + "Add attachment [/artifact/%!S|%h] to wiki page [%h]", | |
| 2037 | 2037 | p->zAttachSrc, p->zAttachName, p->zAttachTarget); |
| 2038 | 2038 | }else{ |
| 2039 | 2039 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 2040 | 2040 | p->zAttachName, p->zAttachTarget); |
| 2041 | 2041 | } |
| 2042 | 2042 | }else{ |
| 2043 | 2043 | if( isAdd ){ |
| 2044 | 2044 | zComment = mprintf( |
| 2045 | - "Add attachment [/artifact/%s|%h] to ticket [%s|%S]", | |
| 2045 | + "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", | |
| 2046 | 2046 | p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); |
| 2047 | 2047 | }else{ |
| 2048 | - zComment = mprintf("Delete attachment \"%h\" from ticket [%s|%S]", | |
| 2048 | + zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]", | |
| 2049 | 2049 | p->zAttachName, p->zAttachTarget, p->zAttachTarget); |
| 2050 | 2050 | } |
| 2051 | 2051 | } |
| 2052 | 2052 | db_multi_exec( |
| 2053 | 2053 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| @@ -2072,11 +2072,11 @@ | ||
| 2072 | 2072 | for(i=0; i<p->nTag; i++){ |
| 2073 | 2073 | zTagUuid = p->aTag[i].zUuid; |
| 2074 | 2074 | if( !zTagUuid ) continue; |
| 2075 | 2075 | if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){ |
| 2076 | 2076 | blob_appendf(&comment, |
| 2077 | - " Edit [%s|%S]:", | |
| 2077 | + " Edit [%!S|%S]:", | |
| 2078 | 2078 | zTagUuid, zTagUuid); |
| 2079 | 2079 | branchMove = 0; |
| 2080 | 2080 | if( permitHooks && db_exists("SELECT 1 FROM event, blob" |
| 2081 | 2081 | " WHERE event.type='ci' AND event.objid=blob.rid" |
| 2082 | 2082 | " AND blob.uuid=%Q", zTagUuid) ){ |
| @@ -2086,11 +2086,11 @@ | ||
| 2086 | 2086 | } |
| 2087 | 2087 | zName = p->aTag[i].zName; |
| 2088 | 2088 | zValue = p->aTag[i].zValue; |
| 2089 | 2089 | if( strcmp(zName, "*branch")==0 ){ |
| 2090 | 2090 | 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].", | |
| 2092 | 2092 | zValue, zTagUuid, zValue); |
| 2093 | 2093 | branchMove = 1; |
| 2094 | 2094 | continue; |
| 2095 | 2095 | }else if( strcmp(zName, "*bgcolor")==0 ){ |
| 2096 | 2096 | blob_appendf(&comment, |
| 2097 | 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 |
| --- 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 @@ | ||
| 842 | 842 | return end; |
| 843 | 843 | } |
| 844 | 844 | } |
| 845 | 845 | |
| 846 | 846 | |
| 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 | +*/ | |
| 848 | 850 | static int get_link_inline( |
| 849 | 851 | struct Blob *link, |
| 850 | 852 | struct Blob *title, |
| 851 | 853 | char *data, |
| 852 | 854 | size_t size |
| @@ -1522,11 +1524,11 @@ | ||
| 1522 | 1524 | if( !inter ){ |
| 1523 | 1525 | if( rndr->make.listitem ){ |
| 1524 | 1526 | rndr->make.listitem(ob, work, *flags, rndr->make.opaque); |
| 1525 | 1527 | } |
| 1526 | 1528 | if( work!=&fallback ) release_work_buffer(rndr, work); |
| 1527 | - blob_zero(&fallback); | |
| 1529 | + blob_reset(&fallback); | |
| 1528 | 1530 | return beg; |
| 1529 | 1531 | } |
| 1530 | 1532 | |
| 1531 | 1533 | /* render of li contents */ |
| 1532 | 1534 | if( has_inside_empty ) *flags |= MKD_LI_BLOCK; |
| @@ -1558,11 +1560,11 @@ | ||
| 1558 | 1560 | if( rndr->make.listitem ){ |
| 1559 | 1561 | rndr->make.listitem(ob, inter, *flags, rndr->make.opaque); |
| 1560 | 1562 | } |
| 1561 | 1563 | release_work_buffer(rndr, inter); |
| 1562 | 1564 | if( work!=&fallback ) release_work_buffer(rndr, work); |
| 1563 | - blob_zero(&fallback); | |
| 1565 | + blob_reset(&fallback); | |
| 1564 | 1566 | return beg; |
| 1565 | 1567 | } |
| 1566 | 1568 | |
| 1567 | 1569 | |
| 1568 | 1570 | /* parse_list -- parsing ordered or unordered list block */ |
| @@ -1584,11 +1586,11 @@ | ||
| 1584 | 1586 | if( !j || (flags & MKD_LI_END) ) break; |
| 1585 | 1587 | } |
| 1586 | 1588 | |
| 1587 | 1589 | if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque); |
| 1588 | 1590 | if( work!=&fallback ) release_work_buffer(rndr, work); |
| 1589 | - blob_zero(&fallback); | |
| 1591 | + blob_reset(&fallback); | |
| 1590 | 1592 | return i; |
| 1591 | 1593 | } |
| 1592 | 1594 | |
| 1593 | 1595 | |
| 1594 | 1596 | /* parse_atxheader -- parsing of atx-style headers */ |
| @@ -1631,11 +1633,15 @@ | ||
| 1631 | 1633 | } |
| 1632 | 1634 | |
| 1633 | 1635 | |
| 1634 | 1636 | /* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */ |
| 1635 | 1637 | /* 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 | +){ | |
| 1637 | 1643 | size_t i, w; |
| 1638 | 1644 | |
| 1639 | 1645 | /* assuming data[0]=='<' && data[1]=='/' already tested */ |
| 1640 | 1646 | |
| 1641 | 1647 | /* checking tag is a match */ |
| @@ -2224,17 +2230,17 @@ | ||
| 2224 | 2230 | if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque); |
| 2225 | 2231 | parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text)); |
| 2226 | 2232 | if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque); |
| 2227 | 2233 | |
| 2228 | 2234 | /* clean-up */ |
| 2229 | - blob_zero(&text); | |
| 2235 | + blob_reset(&text); | |
| 2230 | 2236 | lr = (struct link_ref *)blob_buffer(&rndr.refs); |
| 2231 | 2237 | end = blob_size(&rndr.refs)/sizeof(struct link_ref); |
| 2232 | 2238 | 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); | |
| 2236 | 2242 | } |
| 2237 | - blob_zero(&rndr.refs); | |
| 2243 | + blob_reset(&rndr.refs); | |
| 2238 | 2244 | blobarray_zero(rndr.work, rndr.make.max_work_stack); |
| 2239 | 2245 | fossil_free(rndr.work); |
| 2240 | 2246 | } |
| 2241 | 2247 |
| --- 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 @@ | ||
| 144 | 144 | void modreq_page(void){ |
| 145 | 145 | Blob sql; |
| 146 | 146 | Stmt q; |
| 147 | 147 | |
| 148 | 148 | 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 | + } | |
| 150 | 153 | style_header("Pending Moderation Requests"); |
| 151 | 154 | @ <h2>All Pending Moderation Requests</h2> |
| 152 | 155 | if( moderation_table_exists() ){ |
| 153 | 156 | blob_init(&sql, timeline_query_for_www(), -1); |
| 154 | 157 | blob_append_sql(&sql, |
| 155 | 158 | " AND event.objid IN (SELECT objid FROM modreq)" |
| 156 | 159 | " ORDER BY event.mtime DESC" |
| 157 | 160 | ); |
| 158 | 161 | 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); | |
| 160 | 163 | db_finalize(&q); |
| 161 | 164 | } |
| 162 | 165 | style_footer(); |
| 163 | 166 | } |
| 164 | 167 |
| --- 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 @@ | ||
| 104 | 104 | ** If zType is NULL or "" or "*" then any type of artifact will serve. |
| 105 | 105 | ** If zType is "br" then find the first check-in of the named branch |
| 106 | 106 | ** rather than the last. |
| 107 | 107 | ** zType is "ci" in most use cases since we are usually searching for |
| 108 | 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. | |
| 109 | 114 | */ |
| 110 | 115 | int symbolic_name_to_rid(const char *zTag, const char *zType){ |
| 111 | 116 | int vid; |
| 112 | 117 | int rid = 0; |
| 113 | 118 | int nTag; |
| @@ -444,11 +449,11 @@ | ||
| 444 | 449 | canonical16(z, strlen(z)); |
| 445 | 450 | db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z); |
| 446 | 451 | while( db_step(&q)==SQLITE_ROW ){ |
| 447 | 452 | const char *zUuid = db_column_text(&q, 0); |
| 448 | 453 | 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)"> | |
| 450 | 455 | @ %s(zUuid)</a> - |
| 451 | 456 | object_description(rid, 0, 0); |
| 452 | 457 | @ </p></li> |
| 453 | 458 | } |
| 454 | 459 | db_finalize(&q); |
| @@ -461,11 +466,11 @@ | ||
| 461 | 466 | " ORDER BY tkt_ctime DESC", z); |
| 462 | 467 | while( db_step(&q)==SQLITE_ROW ){ |
| 463 | 468 | int rid = db_column_int(&q, 0); |
| 464 | 469 | const char *zUuid = db_column_text(&q, 1); |
| 465 | 470 | 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)"> | |
| 467 | 472 | @ %s(zUuid)</a> - |
| 468 | 473 | @ <ul></ul> |
| 469 | 474 | @ Ticket |
| 470 | 475 | hyperlink_to_uuid(zUuid); |
| 471 | 476 | @ - %s(zTitle). |
| @@ -481,11 +486,11 @@ | ||
| 481 | 486 | " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid" |
| 482 | 487 | " AND tagname GLOB 'event-%q*') GROUP BY uuid", z); |
| 483 | 488 | while( db_step(&q)==SQLITE_ROW ){ |
| 484 | 489 | int rid = db_column_int(&q, 0); |
| 485 | 490 | 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)"> | |
| 487 | 492 | @ %s(zUuid)</a> - |
| 488 | 493 | @ <ul><li> |
| 489 | 494 | object_description(rid, 0, 0); |
| 490 | 495 | @ </li></ul> |
| 491 | 496 | @ </p></li> |
| @@ -777,18 +782,18 @@ | ||
| 777 | 782 | } |
| 778 | 783 | |
| 779 | 784 | /* |
| 780 | 785 | ** Schema for the description table |
| 781 | 786 | */ |
| 782 | -static const char zDescTab[] = | |
| 787 | +static const char zDescTab[] = | |
| 783 | 788 | @ 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 | |
| 787 | 792 | @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts |
| 788 | 793 | @ type TEXT, -- file, checkin, wiki, ticket, etc. |
| 789 | -@ summary TEXT, -- Summary comment for the object | |
| 794 | +@ summary TEXT, -- Summary comment for the object | |
| 790 | 795 | @ detail TEXT -- filename, checkin comment, etc |
| 791 | 796 | @ ); |
| 792 | 797 | ; |
| 793 | 798 | |
| 794 | 799 | /* |
| @@ -982,11 +987,11 @@ | ||
| 982 | 987 | int n = atoi(PD("n","5000")); |
| 983 | 988 | int mx = db_int(0, "SELECT max(rid) FROM blob"); |
| 984 | 989 | char *zRange; |
| 985 | 990 | |
| 986 | 991 | login_check_credentials(); |
| 987 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 992 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 988 | 993 | style_header("List Of Artifacts"); |
| 989 | 994 | if( mx>n && P("s")==0 ){ |
| 990 | 995 | int i; |
| 991 | 996 | @ <p>Select a range of artifacts to view:</p> |
| 992 | 997 | @ <ul> |
| @@ -1011,11 +1016,11 @@ | ||
| 1011 | 1016 | int rid = db_column_int(&q,0); |
| 1012 | 1017 | const char *zUuid = db_column_text(&q, 1); |
| 1013 | 1018 | const char *zDesc = db_column_text(&q, 2); |
| 1014 | 1019 | int isPriv = db_column_int(&q,2); |
| 1015 | 1020 | @ <tr><td align="right">%d(rid)</td> |
| 1016 | - @ <td> %z(href("%R/info/%s",zUuid))%s(zUuid)</a> </td> | |
| 1021 | + @ <td> %z(href("%R/info/%!S",zUuid))%s(zUuid)</a> </td> | |
| 1017 | 1022 | @ <td align="left">%h(zDesc)</td> |
| 1018 | 1023 | if( isPriv ){ |
| 1019 | 1024 | @ <td>(unpublished)</td> |
| 1020 | 1025 | } |
| 1021 | 1026 | @ </tr> |
| @@ -1058,5 +1063,75 @@ | ||
| 1058 | 1063 | */ |
| 1059 | 1064 | void test_phatoms_cmd(void){ |
| 1060 | 1065 | db_find_and_open_repository(0,0); |
| 1061 | 1066 | describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); |
| 1062 | 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 | +} | |
| 1063 | 1138 |
| --- 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> %z(href("%R/info/%s",zUuid))%s(zUuid)</a> </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> %z(href("%R/info/%!S",zUuid))%s(zUuid)</a> </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 @@ | ||
| 543 | 543 | */ |
| 544 | 544 | void test_rename_list_page(void){ |
| 545 | 545 | Stmt q; |
| 546 | 546 | |
| 547 | 547 | login_check_credentials(); |
| 548 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 548 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 549 | 549 | style_header("List Of File Name Changes"); |
| 550 | 550 | @ <h3>NB: Experimental Page</h3> |
| 551 | 551 | @ <table border="1" width="100%%"> |
| 552 | 552 | @ <tr><th>Date & Time</th> |
| 553 | 553 | @ <th>Old Name</th> |
| @@ -561,11 +561,11 @@ | ||
| 561 | 561 | const char *zUuid = db_column_text(&q, 3); |
| 562 | 562 | @ <tr> |
| 563 | 563 | @ <td>%z(href("%R/timeline?c=%t",zDate))%s(zDate)</a></td> |
| 564 | 564 | @ <td>%z(href("%R/finfo?name=%t",zOld))%h(zOld)</a></td> |
| 565 | 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> | |
| 566 | + @ <td>%z(href("%R/info/%!S",zUuid))%S(zUuid)</a></td></tr> | |
| 567 | 567 | } |
| 568 | 568 | @ </table> |
| 569 | 569 | db_finalize(&q); |
| 570 | 570 | style_footer(); |
| 571 | 571 | } |
| 572 | 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(); 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 & 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 & 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 @@ | ||
| 23 | 23 | #if defined(_WIN32) |
| 24 | 24 | # include <io.h> |
| 25 | 25 | # include <fcntl.h> |
| 26 | 26 | #endif |
| 27 | 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 | +} | |
| 28 | 66 | |
| 29 | 67 | /* |
| 30 | 68 | ** Conversion types fall into various categories as defined by the |
| 31 | 69 | ** following enumeration. |
| 32 | 70 | */ |
| @@ -620,16 +658,11 @@ | ||
| 620 | 658 | if( bufpt==0 ){ |
| 621 | 659 | bufpt = ""; |
| 622 | 660 | }else if( xtype==etDYNSTRING ){ |
| 623 | 661 | zExtra = bufpt; |
| 624 | 662 | }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); | |
| 631 | 664 | } |
| 632 | 665 | length = StrNLen32(bufpt, limit); |
| 633 | 666 | if( precision>=0 && precision<length ) length = precision; |
| 634 | 667 | break; |
| 635 | 668 | } |
| 636 | 669 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -23,10 +23,48 @@ | |
| 23 | #if defined(_WIN32) |
| 24 | # include <io.h> |
| 25 | # include <fcntl.h> |
| 26 | #endif |
| 27 | #include <time.h> |
| 28 | |
| 29 | /* |
| 30 | ** Conversion types fall into various categories as defined by the |
| 31 | ** following enumeration. |
| 32 | */ |
| @@ -620,16 +658,11 @@ | |
| 620 | if( bufpt==0 ){ |
| 621 | bufpt = ""; |
| 622 | }else if( xtype==etDYNSTRING ){ |
| 623 | zExtra = bufpt; |
| 624 | }else if( xtype==etSTRINGID ){ |
| 625 | precision = 0; |
| 626 | while( bufpt[precision]>='0' && bufpt[precision]<='9' ){ |
| 627 | precision++; |
| 628 | } |
| 629 | if( bufpt[precision]!=0 ) precision++; |
| 630 | if( precision<10 ) precision=10; |
| 631 | } |
| 632 | length = StrNLen32(bufpt, limit); |
| 633 | if( precision>=0 && precision<length ) length = precision; |
| 634 | break; |
| 635 | } |
| 636 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -23,10 +23,48 @@ | |
| 23 | #if defined(_WIN32) |
| 24 | # include <io.h> |
| 25 | # include <fcntl.h> |
| 26 | #endif |
| 27 | #include <time.h> |
| 28 | |
| 29 | /* Two custom conversions are used to show a prefix of SHA1 hashes: |
| 30 | ** |
| 31 | ** %!S Prefix of a length appropriate for URLs |
| 32 | ** %S Prefix of a length appropriate for human display |
| 33 | ** |
| 34 | ** The following macros help determine those lengths. FOSSIL_HASH_DIGITS |
| 35 | ** is the default number of digits to display to humans. This value can |
| 36 | ** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL |
| 37 | ** is the minimum number of digits to be used in URLs. The number used |
| 38 | ** will always be at least 6 more than the number used for human output, |
| 39 | ** or 40 if the number of digits in human output is 34 or more. |
| 40 | */ |
| 41 | #ifndef FOSSIL_HASH_DIGITS |
| 42 | # define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */ |
| 43 | #endif |
| 44 | #ifndef FOSSIL_HASH_DIGITS_URL |
| 45 | # define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */ |
| 46 | #endif |
| 47 | |
| 48 | /* |
| 49 | ** Return the number of SHA1 hash digits to display. The number is for |
| 50 | ** human output if the bForUrl is false and is destined for a URL if |
| 51 | ** bForUrl is false. |
| 52 | */ |
| 53 | static int hashDigits(int bForUrl){ |
| 54 | static int nDigitHuman = 0; |
| 55 | static int nDigitUrl = 0; |
| 56 | if( nDigitHuman==0 ){ |
| 57 | nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); |
| 58 | if( nDigitHuman < 6 ) nDigitHuman = 6; |
| 59 | if( nDigitHuman > 40 ) nDigitHuman = 40; |
| 60 | nDigitUrl = nDigitHuman + 6; |
| 61 | if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; |
| 62 | if( nDigitUrl > 40 ) nDigitUrl = 40; |
| 63 | } |
| 64 | return bForUrl ? nDigitUrl : nDigitHuman; |
| 65 | } |
| 66 | |
| 67 | /* |
| 68 | ** Conversion types fall into various categories as defined by the |
| 69 | ** following enumeration. |
| 70 | */ |
| @@ -620,16 +658,11 @@ | |
| 658 | if( bufpt==0 ){ |
| 659 | bufpt = ""; |
| 660 | }else if( xtype==etDYNSTRING ){ |
| 661 | zExtra = bufpt; |
| 662 | }else if( xtype==etSTRINGID ){ |
| 663 | precision = hashDigits(flag_altform2); |
| 664 | } |
| 665 | length = StrNLen32(bufpt, limit); |
| 666 | if( precision>=0 && precision<length ) length = precision; |
| 667 | break; |
| 668 | } |
| 669 |
+17
-7
| --- src/rebuild.c | ||
| +++ src/rebuild.c | ||
| @@ -522,23 +522,24 @@ | ||
| 522 | 522 | ** Reconstruct the named repository database from the core |
| 523 | 523 | ** records. Run this command after updating the fossil |
| 524 | 524 | ** executable in a way that changes the database schema. |
| 525 | 525 | ** |
| 526 | 526 | ** Options: |
| 527 | +** --analyze Run ANALYZE on the database after rebuilding | |
| 527 | 528 | ** --cluster Compute clusters for unclustered artifacts |
| 528 | 529 | ** --compress Strive to make the database as small as possible |
| 530 | +** --deanalyze Remove ANALYZE tables from the database | |
| 529 | 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 | |
| 530 | 534 | ** --noverify Skip the verification of changes to the BLOB table |
| 535 | +** --noindex Always omit the full-text search index | |
| 531 | 536 | ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2) |
| 532 | 537 | ** --randomize Scan artifacts in a random order |
| 538 | +** --stats Show artifact statistics after rebuilding | |
| 533 | 539 | ** --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 | 540 | ** --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 | ** |
| 541 | 542 | ** See also: deconstruct, reconstruct |
| 542 | 543 | */ |
| 543 | 544 | void rebuild_database(void){ |
| 544 | 545 | int forceFlag; |
| @@ -553,10 +554,13 @@ | ||
| 553 | 554 | int runDeanalyze; |
| 554 | 555 | int runAnalyze; |
| 555 | 556 | int runCompress; |
| 556 | 557 | int showStats; |
| 557 | 558 | int runReindex; |
| 559 | + int optNoIndex; | |
| 560 | + int optIndex; | |
| 561 | + int optIfNeeded; | |
| 558 | 562 | |
| 559 | 563 | omitVerify = find_option("noverify",0,0)!=0; |
| 560 | 564 | forceFlag = find_option("force","f",0)!=0; |
| 561 | 565 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 562 | 566 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -564,10 +568,13 @@ | ||
| 564 | 568 | runDeanalyze = find_option("deanalyze",0,0)!=0; |
| 565 | 569 | runAnalyze = find_option("analyze",0,0)!=0; |
| 566 | 570 | runCompress = find_option("compress",0,0)!=0; |
| 567 | 571 | zPagesize = find_option("pagesize",0,1); |
| 568 | 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; | |
| 569 | 576 | if( zPagesize ){ |
| 570 | 577 | newPagesize = atoi(zPagesize); |
| 571 | 578 | if( newPagesize<512 || newPagesize>65536 |
| 572 | 579 | || (newPagesize&(newPagesize-1))!=0 |
| 573 | 580 | ){ |
| @@ -584,12 +591,15 @@ | ||
| 584 | 591 | } |
| 585 | 592 | db_close(1); |
| 586 | 593 | db_open_repository(g.zRepositoryName); |
| 587 | 594 | } |
| 588 | 595 | runReindex = search_index_exists(); |
| 589 | - if( find_option("index",0,0)!=0 ) runReindex = 1; | |
| 590 | - if( find_option("no-index",0,0)!=0 ) runReindex = 0; | |
| 596 | + if( optIndex ) runReindex = 1; | |
| 597 | + if( optNoIndex ) runReindex = 0; | |
| 598 | + if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){ | |
| 599 | + return; | |
| 600 | + } | |
| 591 | 601 | |
| 592 | 602 | /* We should be done with options.. */ |
| 593 | 603 | verify_all_options(); |
| 594 | 604 | |
| 595 | 605 | db_begin_transaction(); |
| 596 | 606 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -522,23 +522,24 @@ | |
| 522 | ** Reconstruct the named repository database from the core |
| 523 | ** records. Run this command after updating the fossil |
| 524 | ** executable in a way that changes the database schema. |
| 525 | ** |
| 526 | ** Options: |
| 527 | ** --cluster Compute clusters for unclustered artifacts |
| 528 | ** --compress Strive to make the database as small as possible |
| 529 | ** --force Force the rebuild to complete even if errors are seen |
| 530 | ** --noverify Skip the verification of changes to the BLOB table |
| 531 | ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2) |
| 532 | ** --randomize Scan artifacts in a random order |
| 533 | ** --vacuum Run VACUUM on the database after rebuilding |
| 534 | ** --deanalyze Remove ANALYZE tables from the database |
| 535 | ** --analyze Run ANALYZE on the database after rebuilding |
| 536 | ** --wal Set Write-Ahead-Log journalling mode on the database |
| 537 | ** --stats Show artifact statistics after rebuilding |
| 538 | ** --index Always add in the full-text search index |
| 539 | ** --no-index Always omit the full-text search index |
| 540 | ** |
| 541 | ** See also: deconstruct, reconstruct |
| 542 | */ |
| 543 | void rebuild_database(void){ |
| 544 | int forceFlag; |
| @@ -553,10 +554,13 @@ | |
| 553 | int runDeanalyze; |
| 554 | int runAnalyze; |
| 555 | int runCompress; |
| 556 | int showStats; |
| 557 | int runReindex; |
| 558 | |
| 559 | omitVerify = find_option("noverify",0,0)!=0; |
| 560 | forceFlag = find_option("force","f",0)!=0; |
| 561 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 562 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -564,10 +568,13 @@ | |
| 564 | runDeanalyze = find_option("deanalyze",0,0)!=0; |
| 565 | runAnalyze = find_option("analyze",0,0)!=0; |
| 566 | runCompress = find_option("compress",0,0)!=0; |
| 567 | zPagesize = find_option("pagesize",0,1); |
| 568 | showStats = find_option("stats",0,0)!=0; |
| 569 | if( zPagesize ){ |
| 570 | newPagesize = atoi(zPagesize); |
| 571 | if( newPagesize<512 || newPagesize>65536 |
| 572 | || (newPagesize&(newPagesize-1))!=0 |
| 573 | ){ |
| @@ -584,12 +591,15 @@ | |
| 584 | } |
| 585 | db_close(1); |
| 586 | db_open_repository(g.zRepositoryName); |
| 587 | } |
| 588 | runReindex = search_index_exists(); |
| 589 | if( find_option("index",0,0)!=0 ) runReindex = 1; |
| 590 | if( find_option("no-index",0,0)!=0 ) runReindex = 0; |
| 591 | |
| 592 | /* We should be done with options.. */ |
| 593 | verify_all_options(); |
| 594 | |
| 595 | db_begin_transaction(); |
| 596 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -522,23 +522,24 @@ | |
| 522 | ** Reconstruct the named repository database from the core |
| 523 | ** records. Run this command after updating the fossil |
| 524 | ** executable in a way that changes the database schema. |
| 525 | ** |
| 526 | ** Options: |
| 527 | ** --analyze Run ANALYZE on the database after rebuilding |
| 528 | ** --cluster Compute clusters for unclustered artifacts |
| 529 | ** --compress Strive to make the database as small as possible |
| 530 | ** --deanalyze Remove ANALYZE tables from the database |
| 531 | ** --force Force the rebuild to complete even if errors are seen |
| 532 | ** --ifneeded Only do the rebuild if it would change the schema version |
| 533 | ** --index Always add in the full-text search index |
| 534 | ** --noverify Skip the verification of changes to the BLOB table |
| 535 | ** --noindex Always omit the full-text search index |
| 536 | ** --pagesize N Set the database pagesize to N. (512..65536 and power of 2) |
| 537 | ** --randomize Scan artifacts in a random order |
| 538 | ** --stats Show artifact statistics after rebuilding |
| 539 | ** --vacuum Run VACUUM on the database after rebuilding |
| 540 | ** --wal Set Write-Ahead-Log journalling mode on the database |
| 541 | ** |
| 542 | ** See also: deconstruct, reconstruct |
| 543 | */ |
| 544 | void rebuild_database(void){ |
| 545 | int forceFlag; |
| @@ -553,10 +554,13 @@ | |
| 554 | int runDeanalyze; |
| 555 | int runAnalyze; |
| 556 | int runCompress; |
| 557 | int showStats; |
| 558 | int runReindex; |
| 559 | int optNoIndex; |
| 560 | int optIndex; |
| 561 | int optIfNeeded; |
| 562 | |
| 563 | omitVerify = find_option("noverify",0,0)!=0; |
| 564 | forceFlag = find_option("force","f",0)!=0; |
| 565 | randomizeFlag = find_option("randomize", 0, 0)!=0; |
| 566 | doClustering = find_option("cluster", 0, 0)!=0; |
| @@ -564,10 +568,13 @@ | |
| 568 | runDeanalyze = find_option("deanalyze",0,0)!=0; |
| 569 | runAnalyze = find_option("analyze",0,0)!=0; |
| 570 | runCompress = find_option("compress",0,0)!=0; |
| 571 | zPagesize = find_option("pagesize",0,1); |
| 572 | showStats = find_option("stats",0,0)!=0; |
| 573 | optIndex = find_option("index",0,0)!=0; |
| 574 | optNoIndex = find_option("noindex",0,0)!=0; |
| 575 | optIfNeeded = find_option("ifneeded",0,0)!=0; |
| 576 | if( zPagesize ){ |
| 577 | newPagesize = atoi(zPagesize); |
| 578 | if( newPagesize<512 || newPagesize>65536 |
| 579 | || (newPagesize&(newPagesize-1))!=0 |
| 580 | ){ |
| @@ -584,12 +591,15 @@ | |
| 591 | } |
| 592 | db_close(1); |
| 593 | db_open_repository(g.zRepositoryName); |
| 594 | } |
| 595 | runReindex = search_index_exists(); |
| 596 | if( optIndex ) runReindex = 1; |
| 597 | if( optNoIndex ) runReindex = 0; |
| 598 | if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){ |
| 599 | return; |
| 600 | } |
| 601 | |
| 602 | /* We should be done with options.. */ |
| 603 | verify_all_options(); |
| 604 | |
| 605 | db_begin_transaction(); |
| 606 |
+7
-4
| --- src/report.c | ||
| +++ src/report.c | ||
| @@ -40,11 +40,14 @@ | ||
| 40 | 40 | Stmt q; |
| 41 | 41 | int rn = 0; |
| 42 | 42 | int cnt = 0; |
| 43 | 43 | |
| 44 | 44 | 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 | + } | |
| 46 | 49 | style_header("Ticket Main Menu"); |
| 47 | 50 | ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); |
| 48 | 51 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 49 | 52 | zScript = ticket_reportlist_code(); |
| 50 | 53 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| @@ -293,11 +296,11 @@ | ||
| 293 | 296 | const char *zClrKey; |
| 294 | 297 | Stmt q; |
| 295 | 298 | |
| 296 | 299 | login_check_credentials(); |
| 297 | 300 | if( !g.perm.TktFmt ){ |
| 298 | - login_needed(); | |
| 301 | + login_needed(g.anon.TktFmt); | |
| 299 | 302 | return; |
| 300 | 303 | } |
| 301 | 304 | rn = atoi(PD("rn","0")); |
| 302 | 305 | db_prepare(&q, "SELECT title, sqlcode, owner, cols " |
| 303 | 306 | "FROM reportfmt WHERE rn=%d",rn); |
| @@ -343,11 +346,11 @@ | ||
| 343 | 346 | char *zSQL; |
| 344 | 347 | char *zErr = 0; |
| 345 | 348 | |
| 346 | 349 | login_check_credentials(); |
| 347 | 350 | if( !g.perm.TktFmt ){ |
| 348 | - login_needed(); | |
| 351 | + login_needed(g.anon.TktFmt); | |
| 349 | 352 | return; |
| 350 | 353 | } |
| 351 | 354 | /*view_add_functions(0);*/ |
| 352 | 355 | rn = atoi(PD("rn","0")); |
| 353 | 356 | zTitle = P("t"); |
| @@ -1078,11 +1081,11 @@ | ||
| 1078 | 1081 | Stmt q; |
| 1079 | 1082 | char *zErr1 = 0; |
| 1080 | 1083 | char *zErr2 = 0; |
| 1081 | 1084 | |
| 1082 | 1085 | login_check_credentials(); |
| 1083 | - if( !g.perm.RdTkt ){ login_needed(); return; } | |
| 1086 | + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } | |
| 1084 | 1087 | rn = atoi(PD("rn","0")); |
| 1085 | 1088 | if( rn==0 ){ |
| 1086 | 1089 | cgi_redirect("reportlist"); |
| 1087 | 1090 | return; |
| 1088 | 1091 | } |
| 1089 | 1092 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -40,11 +40,14 @@ | |
| 40 | Stmt q; |
| 41 | int rn = 0; |
| 42 | int cnt = 0; |
| 43 | |
| 44 | login_check_credentials(); |
| 45 | if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; } |
| 46 | style_header("Ticket Main Menu"); |
| 47 | ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); |
| 48 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 49 | zScript = ticket_reportlist_code(); |
| 50 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| @@ -293,11 +296,11 @@ | |
| 293 | const char *zClrKey; |
| 294 | Stmt q; |
| 295 | |
| 296 | login_check_credentials(); |
| 297 | if( !g.perm.TktFmt ){ |
| 298 | login_needed(); |
| 299 | return; |
| 300 | } |
| 301 | rn = atoi(PD("rn","0")); |
| 302 | db_prepare(&q, "SELECT title, sqlcode, owner, cols " |
| 303 | "FROM reportfmt WHERE rn=%d",rn); |
| @@ -343,11 +346,11 @@ | |
| 343 | char *zSQL; |
| 344 | char *zErr = 0; |
| 345 | |
| 346 | login_check_credentials(); |
| 347 | if( !g.perm.TktFmt ){ |
| 348 | login_needed(); |
| 349 | return; |
| 350 | } |
| 351 | /*view_add_functions(0);*/ |
| 352 | rn = atoi(PD("rn","0")); |
| 353 | zTitle = P("t"); |
| @@ -1078,11 +1081,11 @@ | |
| 1078 | Stmt q; |
| 1079 | char *zErr1 = 0; |
| 1080 | char *zErr2 = 0; |
| 1081 | |
| 1082 | login_check_credentials(); |
| 1083 | if( !g.perm.RdTkt ){ login_needed(); return; } |
| 1084 | rn = atoi(PD("rn","0")); |
| 1085 | if( rn==0 ){ |
| 1086 | cgi_redirect("reportlist"); |
| 1087 | return; |
| 1088 | } |
| 1089 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -40,11 +40,14 @@ | |
| 40 | Stmt q; |
| 41 | int rn = 0; |
| 42 | int cnt = 0; |
| 43 | |
| 44 | login_check_credentials(); |
| 45 | if( !g.perm.RdTkt && !g.perm.NewTkt ){ |
| 46 | login_needed(g.anon.RdTkt || g.anon.NewTkt); |
| 47 | return; |
| 48 | } |
| 49 | style_header("Ticket Main Menu"); |
| 50 | ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); |
| 51 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1); |
| 52 | zScript = ticket_reportlist_code(); |
| 53 | if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1); |
| @@ -293,11 +296,11 @@ | |
| 296 | const char *zClrKey; |
| 297 | Stmt q; |
| 298 | |
| 299 | login_check_credentials(); |
| 300 | if( !g.perm.TktFmt ){ |
| 301 | login_needed(g.anon.TktFmt); |
| 302 | return; |
| 303 | } |
| 304 | rn = atoi(PD("rn","0")); |
| 305 | db_prepare(&q, "SELECT title, sqlcode, owner, cols " |
| 306 | "FROM reportfmt WHERE rn=%d",rn); |
| @@ -343,11 +346,11 @@ | |
| 346 | char *zSQL; |
| 347 | char *zErr = 0; |
| 348 | |
| 349 | login_check_credentials(); |
| 350 | if( !g.perm.TktFmt ){ |
| 351 | login_needed(g.anon.TktFmt); |
| 352 | return; |
| 353 | } |
| 354 | /*view_add_functions(0);*/ |
| 355 | rn = atoi(PD("rn","0")); |
| 356 | zTitle = P("t"); |
| @@ -1078,11 +1081,11 @@ | |
| 1081 | Stmt q; |
| 1082 | char *zErr1 = 0; |
| 1083 | char *zErr2 = 0; |
| 1084 | |
| 1085 | login_check_credentials(); |
| 1086 | if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } |
| 1087 | rn = atoi(PD("rn","0")); |
| 1088 | if( rn==0 ){ |
| 1089 | cgi_redirect("reportlist"); |
| 1090 | return; |
| 1091 | } |
| 1092 |
+292
-80
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -213,11 +213,11 @@ | ||
| 213 | 213 | aiLastDoc[j] = iDoc; |
| 214 | 214 | aiLastOfst[j] = i; |
| 215 | 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | 216 | for(ii=0; ii<k; ii++){ |
| 217 | 217 | if( anMatch[j-ii]<k ){ |
| 218 | - anMatch[j-ii] = k; | |
| 218 | + anMatch[j-ii] = k*(nDoc-iDoc); | |
| 219 | 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | 221 | } |
| 222 | 222 | } |
| 223 | 223 | break; |
| @@ -396,14 +396,18 @@ | ||
| 396 | 396 | static void search_match_sqlfunc( |
| 397 | 397 | sqlite3_context *context, |
| 398 | 398 | int argc, |
| 399 | 399 | sqlite3_value **argv |
| 400 | 400 | ){ |
| 401 | - const char *zSText = (const char*)sqlite3_value_text(argv[0]); | |
| 401 | + const char *azDoc[5]; | |
| 402 | + int nDoc; | |
| 402 | 403 | 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); | |
| 405 | 409 | sqlite3_result_int(context, rc); |
| 406 | 410 | } |
| 407 | 411 | |
| 408 | 412 | /* |
| 409 | 413 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | ||
| 435 | 439 | static void search_stext_sqlfunc( |
| 436 | 440 | sqlite3_context *context, |
| 437 | 441 | int argc, |
| 438 | 442 | sqlite3_value **argv |
| 439 | 443 | ){ |
| 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 | +){ | |
| 441 | 471 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 442 | 472 | int rid = sqlite3_value_int(argv[1]); |
| 443 | 473 | 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); | |
| 446 | 477 | } |
| 447 | 478 | |
| 448 | 479 | /* |
| 449 | 480 | ** Encode a string for use as a query parameter in a URL |
| 450 | 481 | */ |
| @@ -463,20 +494,24 @@ | ||
| 463 | 494 | ** do not delete the Search object. |
| 464 | 495 | */ |
| 465 | 496 | void search_sql_setup(sqlite3 *db){ |
| 466 | 497 | static int once = 0; |
| 467 | 498 | 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, | |
| 469 | 500 | search_match_sqlfunc, 0, 0); |
| 470 | 501 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 471 | 502 | search_score_sqlfunc, 0, 0); |
| 472 | 503 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 473 | 504 | search_snippet_sqlfunc, 0, 0); |
| 474 | 505 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 475 | 506 | search_init_sqlfunc, 0, 0); |
| 476 | 507 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 477 | 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); | |
| 478 | 513 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 479 | 514 | search_urlencode_sqlfunc, 0, 0); |
| 480 | 515 | } |
| 481 | 516 | |
| 482 | 517 | /* |
| @@ -616,21 +651,23 @@ | ||
| 616 | 651 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 617 | 652 | db_multi_exec( |
| 618 | 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 619 | 654 | ); |
| 620 | 655 | db_multi_exec( |
| 621 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 622 | - " SELECT printf('Document: %%s',foci.filename)," | |
| 656 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 657 | + " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," | |
| 623 | 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 624 | 659 | " search_score()," |
| 660 | + " 'd'||blob.rid," | |
| 625 | 661 | " (SELECT datetime(event.mtime) FROM event" |
| 626 | 662 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 627 | 663 | " search_snippet()" |
| 628 | 664 | " FROM foci CROSS JOIN blob" |
| 629 | 665 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 630 | 666 | " AND blob.uuid=foci.uuid" |
| 631 | - " AND search_match(stext('d',blob.rid,foci.filename))" | |
| 667 | + " AND search_match(title('d',blob.rid,foci.filename)," | |
| 668 | + " body('d',blob.rid,foci.filename))" | |
| 632 | 669 | " AND %z", |
| 633 | 670 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 634 | 671 | ); |
| 635 | 672 | } |
| 636 | 673 | } |
| @@ -641,18 +678,19 @@ | ||
| 641 | 678 | " FROM tag, tagxref" |
| 642 | 679 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 643 | 680 | " AND tagxref.tagid=tag.tagid" |
| 644 | 681 | " GROUP BY 1" |
| 645 | 682 | ")" |
| 646 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 683 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 647 | 684 | " SELECT printf('Wiki: %%s',name)," |
| 648 | 685 | " printf('/wiki?name=%%s',urlencode(name))," |
| 649 | 686 | " search_score()," |
| 687 | + " 'w'||rid," | |
| 650 | 688 | " datetime(mtime)," |
| 651 | 689 | " search_snippet()" |
| 652 | 690 | " FROM wiki" |
| 653 | - " WHERE search_match(stext('w',rid,name));" | |
| 691 | + " WHERE search_match(title('w',rid,name),body('w',rid,name));" | |
| 654 | 692 | ); |
| 655 | 693 | } |
| 656 | 694 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 657 | 695 | db_multi_exec( |
| 658 | 696 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -659,34 +697,45 @@ | ||
| 659 | 697 | " SELECT blob.uuid, event.objid, event.mtime" |
| 660 | 698 | " FROM event, blob" |
| 661 | 699 | " WHERE event.type='ci'" |
| 662 | 700 | " AND blob.rid=event.objid" |
| 663 | 701 | ")" |
| 664 | - "INSERT INTO x(label,url,score,date,snip)" | |
| 702 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 665 | 703 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 666 | 704 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 667 | 705 | " search_score()," |
| 706 | + " 'c'||rid," | |
| 668 | 707 | " datetime(mtime)," |
| 669 | 708 | " search_snippet()" |
| 670 | 709 | " FROM ckin" |
| 671 | - " WHERE search_match(stext('c',rid,NULL));" | |
| 710 | + " WHERE search_match('',body('c',rid,NULL));" | |
| 672 | 711 | ); |
| 673 | 712 | } |
| 674 | 713 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 675 | 714 | db_multi_exec( |
| 676 | - "INSERT INTO x(label,url,score, date,snip)" | |
| 677 | - " SELECT printf('Ticket [%%.17s] on %%s'," | |
| 678 | - "tkt_uuid,datetime(tkt_mtime))," | |
| 715 | + "INSERT INTO x(label,url,score,id,date,snip)" | |
| 716 | + " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," | |
| 717 | + "datetime(tkt_mtime))," | |
| 679 | 718 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 680 | 719 | " search_score()," |
| 720 | + " 't'||tkt_id," | |
| 681 | 721 | " datetime(tkt_mtime)," |
| 682 | 722 | " search_snippet()" |
| 683 | 723 | " FROM ticket" |
| 684 | - " WHERE search_match(stext('t',tkt_id,NULL));" | |
| 724 | + " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" | |
| 685 | 725 | ); |
| 686 | 726 | } |
| 687 | 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 | +} | |
| 688 | 737 | |
| 689 | 738 | /* |
| 690 | 739 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 691 | 740 | */ |
| 692 | 741 | static void search_rank_sqlfunc( |
| @@ -694,24 +743,45 @@ | ||
| 694 | 743 | int argc, |
| 695 | 744 | sqlite3_value **argv |
| 696 | 745 | ){ |
| 697 | 746 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 698 | 747 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 748 | + int nCol; /* Number of columns in the index */ | |
| 699 | 749 | int nTerm; /* Number of search terms in the query */ |
| 700 | - int i; /* Loop counter */ | |
| 701 | - double r = 1.0; /* Score */ | |
| 750 | + int i, j; /* Loop counter */ | |
| 751 | + double r = 0.0; /* Score */ | |
| 752 | + const unsigned *aX, *aS; | |
| 702 | 753 | |
| 703 | - if( nVal<6 ) return; | |
| 704 | - if( aVal[1]!=1 ) return; | |
| 754 | + if( nVal<2 ) return; | |
| 705 | 755 | nTerm = aVal[0]; |
| 706 | - r *= 1<<((30*(aVal[2]-1))/nTerm); | |
| 707 | - for(i=1; i<=nTerm; i++){ | |
| 708 | - int hits_this_row = aVal[3*i]; | |
| 709 | - int hits_all_rows = aVal[3*i+1]; | |
| 710 | - int rows_with_hit = aVal[3*i+2]; | |
| 711 | - double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; | |
| 712 | - r *= hits_this_row/avg_hits_per_row; | |
| 756 | + nCol = aVal[1]; | |
| 757 | + if( nVal<2+3*nCol*nTerm+nCol ) return; | |
| 758 | + aS = aVal+2; | |
| 759 | + aX = aS+nCol; | |
| 760 | + for(j=0; j<nCol; j++){ | |
| 761 | + double x; | |
| 762 | + if( aS[j]>0 ){ | |
| 763 | + x = 0.0; | |
| 764 | + for(i=0; i<nTerm; i++){ | |
| 765 | + int hits_this_row; | |
| 766 | + int hits_all_rows; | |
| 767 | + int rows_with_hit; | |
| 768 | + double avg_hits_per_row; | |
| 769 | + | |
| 770 | + hits_this_row = aX[j + i*nCol*3]; | |
| 771 | + if( hits_this_row==0 )continue; | |
| 772 | + hits_all_rows = aX[j + i*nCol*3 + 1]; | |
| 773 | + rows_with_hit = aX[j + i*nCol*3 + 2]; | |
| 774 | + if( rows_with_hit==0 ) continue; | |
| 775 | + avg_hits_per_row = hits_all_rows/(double)rows_with_hit; | |
| 776 | + x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit)); | |
| 777 | + } | |
| 778 | + x *= (1<<((30*(aS[j]-1))/nTerm)); | |
| 779 | + }else{ | |
| 780 | + x = 0.0; | |
| 781 | + } | |
| 782 | + r = r*10.0 + x; | |
| 713 | 783 | } |
| 714 | 784 | #define SEARCH_DEBUG_RANK 0 |
| 715 | 785 | #if SEARCH_DEBUG_RANK |
| 716 | 786 | { |
| 717 | 787 | Blob x; |
| @@ -746,14 +816,15 @@ | ||
| 746 | 816 | if( srchFlags==0 ) return; |
| 747 | 817 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 748 | 818 | search_rank_sqlfunc, 0, 0); |
| 749 | 819 | blob_init(&sql, 0, 0); |
| 750 | 820 | blob_appendf(&sql, |
| 751 | - "INSERT INTO x(label,url,score,date,snip) " | |
| 821 | + "INSERT INTO x(label,url,score,id,date,snip) " | |
| 752 | 822 | " SELECT ftsdocs.label," |
| 753 | 823 | " ftsdocs.url," |
| 754 | 824 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 825 | + " ftsdocs.type || ftsdocs.rid," | |
| 755 | 826 | " datetime(ftsdocs.mtime)," |
| 756 | 827 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 757 | 828 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 758 | 829 | " WHERE ftsidx MATCH %Q" |
| 759 | 830 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -838,29 +909,30 @@ | ||
| 838 | 909 | ** |
| 839 | 910 | ** Return the number of rows. |
| 840 | 911 | */ |
| 841 | 912 | int search_run_and_output( |
| 842 | 913 | const char *zPattern, /* The query pattern */ |
| 843 | - unsigned int srchFlags /* What to search over */ | |
| 914 | + unsigned int srchFlags, /* What to search over */ | |
| 915 | + int fDebug /* Extra debugging output */ | |
| 844 | 916 | ){ |
| 845 | 917 | Stmt q; |
| 846 | 918 | int nRow = 0; |
| 847 | 919 | |
| 848 | 920 | srchFlags = search_restrict(srchFlags); |
| 849 | 921 | if( srchFlags==0 ) return 0; |
| 850 | 922 | search_sql_setup(g.db); |
| 851 | 923 | add_content_sql_commands(g.db); |
| 852 | 924 | db_multi_exec( |
| 853 | - "CREATE TEMP TABLE x(label,url,score,date,snip);" | |
| 925 | + "CREATE TEMP TABLE x(label,url,score,id,date,snip);" | |
| 854 | 926 | ); |
| 855 | 927 | if( !search_index_exists() ){ |
| 856 | 928 | search_fullscan(zPattern, srchFlags); |
| 857 | 929 | }else{ |
| 858 | 930 | search_update_index(srchFlags); |
| 859 | 931 | search_indexed(zPattern, srchFlags); |
| 860 | 932 | } |
| 861 | - db_prepare(&q, "SELECT url, snip, label" | |
| 933 | + db_prepare(&q, "SELECT url, snip, label, score, id" | |
| 862 | 934 | " FROM x" |
| 863 | 935 | " ORDER BY score DESC, date DESC;"); |
| 864 | 936 | while( db_step(&q)==SQLITE_ROW ){ |
| 865 | 937 | const char *zUrl = db_column_text(&q, 0); |
| 866 | 938 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -867,12 +939,15 @@ | ||
| 867 | 939 | const char *zLabel = db_column_text(&q, 2); |
| 868 | 940 | if( nRow==0 ){ |
| 869 | 941 | @ <ol> |
| 870 | 942 | } |
| 871 | 943 | nRow++; |
| 872 | - @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> | |
| 873 | - @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> | |
| 944 | + @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a> | |
| 945 | + if( fDebug ){ | |
| 946 | + @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))) | |
| 947 | + } | |
| 948 | + @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> | |
| 874 | 949 | } |
| 875 | 950 | db_finalize(&q); |
| 876 | 951 | if( nRow ){ |
| 877 | 952 | @ </ol> |
| 878 | 953 | } |
| @@ -900,10 +975,11 @@ | ||
| 900 | 975 | const char *zType = 0; |
| 901 | 976 | const char *zClass = 0; |
| 902 | 977 | const char *zDisable1; |
| 903 | 978 | const char *zDisable2; |
| 904 | 979 | const char *zPattern; |
| 980 | + int fDebug = PB("debug"); | |
| 905 | 981 | srchFlags = search_restrict(srchFlags); |
| 906 | 982 | switch( srchFlags ){ |
| 907 | 983 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 908 | 984 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 909 | 985 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -947,10 +1023,13 @@ | ||
| 947 | 1023 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 948 | 1024 | } |
| 949 | 1025 | @ </select> |
| 950 | 1026 | srchFlags = newFlags; |
| 951 | 1027 | } |
| 1028 | + if( fDebug ){ | |
| 1029 | + @ <input type="hidden" name="debug" value="1"> | |
| 1030 | + } | |
| 952 | 1031 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 953 | 1032 | if( srchFlags==0 ){ |
| 954 | 1033 | @ <p class="generalError">Search is disabled</p> |
| 955 | 1034 | } |
| 956 | 1035 | @ </div></form> |
| @@ -959,11 +1038,11 @@ | ||
| 959 | 1038 | if( zClass ){ |
| 960 | 1039 | @ <div class='searchResult searchResult%s(zClass)'> |
| 961 | 1040 | }else{ |
| 962 | 1041 | @ <div class='searchResult'> |
| 963 | 1042 | } |
| 964 | - if( search_run_and_output(zPattern, srchFlags)==0 ){ | |
| 1043 | + if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ | |
| 965 | 1044 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 966 | 1045 | } |
| 967 | 1046 | @ </div> |
| 968 | 1047 | } |
| 969 | 1048 | } |
| @@ -983,10 +1062,14 @@ | ||
| 983 | 1062 | |
| 984 | 1063 | |
| 985 | 1064 | /* |
| 986 | 1065 | ** This is a helper function for search_stext(). Writing into pOut |
| 987 | 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. | |
| 988 | 1071 | */ |
| 989 | 1072 | static void get_stext_by_mimetype( |
| 990 | 1073 | Blob *pIn, |
| 991 | 1074 | const char *zMimetype, |
| 992 | 1075 | Blob *pOut |
| @@ -994,41 +1077,74 @@ | ||
| 994 | 1077 | Blob html, title; |
| 995 | 1078 | blob_init(&html, 0, 0); |
| 996 | 1079 | blob_init(&title, 0, 0); |
| 997 | 1080 | if( zMimetype==0 ) zMimetype = "text/plain"; |
| 998 | 1081 | if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ |
| 999 | - wiki_convert(pIn, &html, 0); | |
| 1082 | + Blob tail; | |
| 1083 | + blob_init(&tail, 0, 0); | |
| 1084 | + if( wiki_find_title(pIn, &title, &tail) ){ | |
| 1085 | + blob_appendf(pOut, "%s\n", blob_str(&title)); | |
| 1086 | + wiki_convert(&tail, &html, 0); | |
| 1087 | + blob_reset(&tail); | |
| 1088 | + }else{ | |
| 1089 | + blob_append(pOut, "\n", 1); | |
| 1090 | + wiki_convert(pIn, &html, 0); | |
| 1091 | + } | |
| 1000 | 1092 | html_to_plaintext(blob_str(&html), pOut); |
| 1001 | 1093 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1002 | 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 | + } | |
| 1003 | 1100 | html_to_plaintext(blob_str(&html), pOut); |
| 1004 | 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 | + } | |
| 1005 | 1105 | html_to_plaintext(blob_str(pIn), pOut); |
| 1006 | 1106 | }else{ |
| 1007 | - *pOut = *pIn; | |
| 1008 | - blob_init(pIn, 0, 0); | |
| 1107 | + blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); | |
| 1009 | 1108 | } |
| 1010 | 1109 | blob_reset(&html); |
| 1011 | 1110 | blob_reset(&title); |
| 1012 | 1111 | } |
| 1013 | 1112 | |
| 1014 | 1113 | /* |
| 1015 | 1114 | ** Query pQuery is pointing at a single row of output. Append a text |
| 1016 | 1115 | ** representation of every text-compatible column to pAccum. |
| 1017 | 1116 | */ |
| 1018 | -static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){ | |
| 1117 | +static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ | |
| 1019 | 1118 | int n = db_column_count(pQuery); |
| 1020 | 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 | + } | |
| 1021 | 1127 | for(i=0; i<n; i++){ |
| 1022 | 1128 | const char *zColName = db_column_name(pQuery,i); |
| 1129 | + int eType = db_column_type(pQuery,i); | |
| 1130 | + if( i==iTitle ) continue; | |
| 1023 | 1131 | if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue; |
| 1024 | - if( fossil_stricmp(zColName,"mimetype")==0 ) continue; | |
| 1025 | - switch( db_column_type(pQuery,i) ){ | |
| 1026 | - case SQLITE_INTEGER: | |
| 1027 | - case SQLITE_FLOAT: | |
| 1028 | - case SQLITE_TEXT: | |
| 1029 | - blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); | |
| 1132 | + if( fossil_strnicmp(zColName,"private_",8)==0 ) continue; | |
| 1133 | + if( eType==SQLITE_BLOB || eType==SQLITE_NULL ) continue; | |
| 1134 | + if( fossil_stricmp(zColName,"mimetype")==0 ){ | |
| 1135 | + zMime = db_column_text(pQuery,i); | |
| 1136 | + if( fossil_strcmp(zMime,"text/plain")==0 ) zMime = 0; | |
| 1137 | + }else if( zMime==0 || eType!=SQLITE_TEXT ){ | |
| 1138 | + blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); | |
| 1139 | + }else{ | |
| 1140 | + Blob txt; | |
| 1141 | + blob_init(&txt, db_column_text(pQuery,i), -1); | |
| 1142 | + blob_appendf(pAccum, "%s: ", zColName); | |
| 1143 | + get_stext_by_mimetype(&txt, zMime, pAccum); | |
| 1144 | + blob_append(pAccum, " |", 2); | |
| 1145 | + blob_reset(&txt); | |
| 1030 | 1146 | } |
| 1031 | 1147 | } |
| 1032 | 1148 | } |
| 1033 | 1149 | |
| 1034 | 1150 | |
| @@ -1054,11 +1170,11 @@ | ||
| 1054 | 1170 | ){ |
| 1055 | 1171 | blob_init(pOut, 0, 0); |
| 1056 | 1172 | switch( cType ){ |
| 1057 | 1173 | case 'd': { /* Documents */ |
| 1058 | 1174 | Blob doc; |
| 1059 | - content_get(rid, &doc); | |
| 1175 | + content_get(rid, &doc); | |
| 1060 | 1176 | blob_to_utf8_no_bom(&doc, 0); |
| 1061 | 1177 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 1062 | 1178 | blob_reset(&doc); |
| 1063 | 1179 | break; |
| 1064 | 1180 | } |
| @@ -1073,10 +1189,11 @@ | ||
| 1073 | 1189 | manifest_destroy(pWiki); |
| 1074 | 1190 | break; |
| 1075 | 1191 | } |
| 1076 | 1192 | case 'c': { /* Check-in Comments */ |
| 1077 | 1193 | static Stmt q; |
| 1194 | + static int isPlainText = -1; | |
| 1078 | 1195 | db_static_prepare(&q, |
| 1079 | 1196 | "SELECT coalesce(ecomment,comment)" |
| 1080 | 1197 | " ||' (user: '||coalesce(euser,user,'?')" |
| 1081 | 1198 | " ||', tags: '||" |
| 1082 | 1199 | " (SELECT group_concat(substr(tag.tagname,5),',')" |
| @@ -1083,44 +1200,99 @@ | ||
| 1083 | 1200 | " FROM tag, tagxref" |
| 1084 | 1201 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 1085 | 1202 | " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" |
| 1086 | 1203 | " ||')'" |
| 1087 | 1204 | " FROM event WHERE objid=:x AND type='ci'"); |
| 1205 | + if( isPlainText<0 ){ | |
| 1206 | + isPlainText = db_get_boolean("timeline-plaintext",0); | |
| 1207 | + } | |
| 1088 | 1208 | db_bind_int(&q, ":x", rid); |
| 1089 | 1209 | if( db_step(&q)==SQLITE_ROW ){ |
| 1090 | - db_column_blob(&q, 0, pOut); | |
| 1091 | 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 | + } | |
| 1092 | 1220 | } |
| 1093 | 1221 | db_reset(&q); |
| 1094 | 1222 | break; |
| 1095 | 1223 | } |
| 1096 | 1224 | case 't': { /* Tickets */ |
| 1097 | 1225 | static Stmt q1; |
| 1098 | - Blob raw; | |
| 1226 | + static int iTitle = -1; | |
| 1099 | 1227 | db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); |
| 1100 | - blob_init(&raw,0,0); | |
| 1101 | 1228 | db_bind_int(&q1, ":rid", rid); |
| 1102 | 1229 | if( db_step(&q1)==SQLITE_ROW ){ |
| 1103 | - append_all_ticket_fields(&raw, &q1); | |
| 1230 | + if( iTitle<0 ){ | |
| 1231 | + int n = db_column_count(&q1); | |
| 1232 | + for(iTitle=0; iTitle<n; iTitle++){ | |
| 1233 | + if( fossil_stricmp(db_column_name(&q1,iTitle),"title")==0 ) break; | |
| 1234 | + } | |
| 1235 | + } | |
| 1236 | + append_all_ticket_fields(pOut, &q1, iTitle); | |
| 1104 | 1237 | } |
| 1105 | 1238 | db_reset(&q1); |
| 1106 | 1239 | if( db_table_exists("repository","ticketchng") ){ |
| 1107 | 1240 | static Stmt q2; |
| 1108 | 1241 | db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid" |
| 1109 | 1242 | " ORDER BY tkt_mtime"); |
| 1110 | 1243 | db_bind_int(&q2, ":rid", rid); |
| 1111 | 1244 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1112 | - append_all_ticket_fields(&raw, &q2); | |
| 1245 | + append_all_ticket_fields(pOut, &q2, -1); | |
| 1113 | 1246 | } |
| 1114 | 1247 | db_reset(&q2); |
| 1115 | 1248 | } |
| 1116 | - html_to_plaintext(blob_str(&raw), pOut); | |
| 1117 | - blob_reset(&raw); | |
| 1118 | 1249 | break; |
| 1119 | 1250 | } |
| 1120 | 1251 | } |
| 1121 | 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 | +} | |
| 1122 | 1294 | |
| 1123 | 1295 | /* |
| 1124 | 1296 | ** COMMAND: test-search-stext |
| 1125 | 1297 | ** |
| 1126 | 1298 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| @@ -1131,10 +1303,30 @@ | ||
| 1131 | 1303 | if( g.argc!=5 ) usage("TYPE RID NAME"); |
| 1132 | 1304 | search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); |
| 1133 | 1305 | fossil_print("%s\n",blob_str(&out)); |
| 1134 | 1306 | blob_reset(&out); |
| 1135 | 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 | +} | |
| 1136 | 1328 | |
| 1137 | 1329 | /* The schema for the full-text index |
| 1138 | 1330 | */ |
| 1139 | 1331 | static const char zFtsSchema[] = |
| 1140 | 1332 | @ -- One entry for each possible search result |
| @@ -1145,20 +1337,21 @@ | ||
| 1145 | 1337 | @ name TEXT, -- Additional document description |
| 1146 | 1338 | @ idxed BOOLEAN, -- True if currently in the index |
| 1147 | 1339 | @ label TEXT, -- Label to print on search results |
| 1148 | 1340 | @ url TEXT, -- URL to access this document |
| 1149 | 1341 | @ mtime DATE, -- Date when document created |
| 1342 | +@ bx TEXT, -- Temporary "body" content cache | |
| 1150 | 1343 | @ UNIQUE(type,rid) |
| 1151 | 1344 | @ ); |
| 1152 | 1345 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 1153 | 1346 | @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; |
| 1154 | 1347 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 1155 | 1348 | @ SELECT rowid, type, rid, name, idxed, label, url, mtime, |
| 1156 | -@ stext(type,rid,name) AS 'stext' | |
| 1349 | +@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body' | |
| 1157 | 1350 | @ FROM ftsdocs; |
| 1158 | 1351 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 1159 | -@ USING fts4(content="ftscontent", stext); | |
| 1352 | +@ USING fts4(content="ftscontent", title, body%s); | |
| 1160 | 1353 | ; |
| 1161 | 1354 | static const char zFtsDrop[] = |
| 1162 | 1355 | @ DROP TABLE IF EXISTS "%w".ftsidx; |
| 1163 | 1356 | @ DROP VIEW IF EXISTS "%w".ftscontent; |
| 1164 | 1357 | @ DROP TABLE IF EXISTS "%w".ftsdocs; |
| @@ -1168,13 +1361,15 @@ | ||
| 1168 | 1361 | ** Create or drop the tables associated with a full-text index. |
| 1169 | 1362 | */ |
| 1170 | 1363 | static int searchIdxExists = -1; |
| 1171 | 1364 | void search_create_index(void){ |
| 1172 | 1365 | const char *zDb = db_name("repository"); |
| 1366 | + int useStemmer = db_get_boolean("search-stemmer",0); | |
| 1367 | + const char *zExtra = useStemmer ? ",tokenize=porter" : ""; | |
| 1173 | 1368 | search_sql_setup(g.db); |
| 1174 | - db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/, | |
| 1175 | - zDb, zDb, zDb, zDb, zDb); | |
| 1369 | + db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/, | |
| 1370 | + zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/); | |
| 1176 | 1371 | searchIdxExists = 1; |
| 1177 | 1372 | } |
| 1178 | 1373 | void search_drop_index(void){ |
| 1179 | 1374 | const char *zDb = db_name("repository"); |
| 1180 | 1375 | db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); |
| @@ -1292,34 +1487,39 @@ | ||
| 1292 | 1487 | db_multi_exec( |
| 1293 | 1488 | "DELETE FROM ftsdocs WHERE type='d'" |
| 1294 | 1489 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1295 | 1490 | ); |
| 1296 | 1491 | db_multi_exec( |
| 1297 | - "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" | |
| 1492 | + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" | |
| 1298 | 1493 | " SELECT 'd', rid, name, 0," |
| 1299 | - " printf('Document: %%s',name)," | |
| 1494 | + " title('d',rid,name)," | |
| 1495 | + " body('d',rid,name)," | |
| 1300 | 1496 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1301 | 1497 | " %.17g" |
| 1302 | 1498 | " FROM current_docs", |
| 1303 | 1499 | zBrUuid, rTime |
| 1304 | 1500 | ); |
| 1305 | 1501 | db_multi_exec( |
| 1306 | - "INSERT INTO ftsidx(docid,stext)" | |
| 1307 | - " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed" | |
| 1502 | + "INSERT INTO ftsidx(docid,title,body)" | |
| 1503 | + " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" | |
| 1308 | 1504 | ); |
| 1309 | 1505 | db_multi_exec( |
| 1310 | - "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" | |
| 1506 | + "UPDATE ftsdocs SET" | |
| 1507 | + " idxed=1," | |
| 1508 | + " bx=NULL," | |
| 1509 | + " label='Document: '||label" | |
| 1510 | + " WHERE type='d' AND NOT idxed" | |
| 1311 | 1511 | ); |
| 1312 | 1512 | } |
| 1313 | 1513 | |
| 1314 | 1514 | /* |
| 1315 | 1515 | ** Deal with all of the unindexed 'c' terms in FTSDOCS |
| 1316 | 1516 | */ |
| 1317 | 1517 | static void search_update_checkin_index(void){ |
| 1318 | 1518 | db_multi_exec( |
| 1319 | - "INSERT INTO ftsidx(docid,stext)" | |
| 1320 | - " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs" | |
| 1519 | + "INSERT INTO ftsidx(docid,title,body)" | |
| 1520 | + " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs" | |
| 1321 | 1521 | " WHERE type='c' AND NOT idxed;" |
| 1322 | 1522 | ); |
| 1323 | 1523 | db_multi_exec( |
| 1324 | 1524 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1325 | 1525 | " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," |
| @@ -1336,19 +1536,20 @@ | ||
| 1336 | 1536 | /* |
| 1337 | 1537 | ** Deal with all of the unindexed 't' terms in FTSDOCS |
| 1338 | 1538 | */ |
| 1339 | 1539 | static void search_update_ticket_index(void){ |
| 1340 | 1540 | db_multi_exec( |
| 1341 | - "INSERT INTO ftsidx(docid,stext)" | |
| 1342 | - " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs" | |
| 1541 | + "INSERT INTO ftsidx(docid,title,body)" | |
| 1542 | + " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs" | |
| 1343 | 1543 | " WHERE type='t' AND NOT idxed;" |
| 1344 | 1544 | ); |
| 1345 | 1545 | if( db_changes()==0 ) return; |
| 1346 | 1546 | db_multi_exec( |
| 1347 | 1547 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1348 | 1548 | " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," |
| 1349 | - " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime))," | |
| 1549 | + " printf('Ticket: %%s (%%s)',title('t',tkt_id,null)," | |
| 1550 | + " datetime(tkt_mtime))," | |
| 1350 | 1551 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 1351 | 1552 | " tkt_mtime" |
| 1352 | 1553 | " FROM ftsdocs, ticket" |
| 1353 | 1554 | " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" |
| 1354 | 1555 | " AND ticket.tkt_id=ftsdocs.rid" |
| @@ -1358,12 +1559,12 @@ | ||
| 1358 | 1559 | /* |
| 1359 | 1560 | ** Deal with all of the unindexed 'w' terms in FTSDOCS |
| 1360 | 1561 | */ |
| 1361 | 1562 | static void search_update_wiki_index(void){ |
| 1362 | 1563 | db_multi_exec( |
| 1363 | - "INSERT INTO ftsidx(docid,stext)" | |
| 1364 | - " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs" | |
| 1564 | + "INSERT INTO ftsidx(docid,title,body)" | |
| 1565 | + " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs" | |
| 1365 | 1566 | " WHERE type='w' AND NOT idxed;" |
| 1366 | 1567 | ); |
| 1367 | 1568 | if( db_changes()==0 ) return; |
| 1368 | 1569 | db_multi_exec( |
| 1369 | 1570 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| @@ -1416,19 +1617,22 @@ | ||
| 1416 | 1617 | ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? |
| 1417 | 1618 | ** |
| 1418 | 1619 | ** The "fossil fts-config" command configures the full-text search capabilities |
| 1419 | 1620 | ** of the repository. Subcommands: |
| 1420 | 1621 | ** |
| 1421 | -** reindex Rebuild the search index. Create it if it does | |
| 1422 | -** not already exist | |
| 1622 | +** reindex Rebuild the search index. This is a no-op if | |
| 1623 | +** index search is disabled | |
| 1423 | 1624 | ** |
| 1424 | 1625 | ** index (on|off) Turn the search index on or off |
| 1425 | 1626 | ** |
| 1426 | 1627 | ** enable cdtw Enable various kinds of search. c=Check-ins, |
| 1427 | 1628 | ** d=Documents, t=Tickets, w=Wiki. |
| 1428 | 1629 | ** |
| 1429 | 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.) | |
| 1430 | 1634 | ** |
| 1431 | 1635 | ** The current search settings are displayed after any changes are applied. |
| 1432 | 1636 | ** Run this command with no arguments to simply see the settings. |
| 1433 | 1637 | */ |
| 1434 | 1638 | void test_fts_cmd(void){ |
| @@ -1435,18 +1639,19 @@ | ||
| 1435 | 1639 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1436 | 1640 | { 1, "reindex" }, |
| 1437 | 1641 | { 2, "index" }, |
| 1438 | 1642 | { 3, "disable" }, |
| 1439 | 1643 | { 4, "enable" }, |
| 1644 | + { 5, "stemmer" }, | |
| 1440 | 1645 | }; |
| 1441 | 1646 | static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { |
| 1442 | - { "search-ckin", "check-in search:", "c" }, | |
| 1443 | - { "search-doc", "document search:", "d" }, | |
| 1444 | - { "search-tkt", "ticket search:", "t" }, | |
| 1445 | - { "search-wiki", "wiki search:", "w" }, | |
| 1647 | + { "search-ckin", "check-in search:", "c" }, | |
| 1648 | + { "search-doc", "document search:", "d" }, | |
| 1649 | + { "search-tkt", "ticket search:", "t" }, | |
| 1650 | + { "search-wiki", "wiki search:", "w" }, | |
| 1446 | 1651 | }; |
| 1447 | - char *zSubCmd; | |
| 1652 | + char *zSubCmd = 0; | |
| 1448 | 1653 | int i, j, n; |
| 1449 | 1654 | int iCmd = 0; |
| 1450 | 1655 | int iAction = 0; |
| 1451 | 1656 | db_find_and_open_repository(0, 0); |
| 1452 | 1657 | if( g.argc>2 ){ |
| @@ -1464,11 +1669,11 @@ | ||
| 1464 | 1669 | return; |
| 1465 | 1670 | } |
| 1466 | 1671 | iCmd = aCmd[i].iCmd; |
| 1467 | 1672 | } |
| 1468 | 1673 | if( iCmd==1 ){ |
| 1469 | - iAction = 2; | |
| 1674 | + if( search_index_exists() ) iAction = 2; | |
| 1470 | 1675 | } |
| 1471 | 1676 | if( iCmd==2 ){ |
| 1472 | 1677 | if( g.argc<3 ) usage("index (on|off)"); |
| 1473 | 1678 | iAction = 1 + is_truth(g.argv[3]); |
| 1474 | 1679 | } |
| @@ -1475,18 +1680,23 @@ | ||
| 1475 | 1680 | db_begin_transaction(); |
| 1476 | 1681 | |
| 1477 | 1682 | /* Adjust search settings */ |
| 1478 | 1683 | if( iCmd==3 || iCmd==4 ){ |
| 1479 | 1684 | const char *zCtrl; |
| 1480 | - if( g.argc<4 ) usage("enable STRING"); | |
| 1685 | + if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd)); | |
| 1481 | 1686 | zCtrl = g.argv[3]; |
| 1482 | 1687 | for(j=0; j<ArraySize(aSetng); j++){ |
| 1483 | 1688 | if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){ |
| 1484 | 1689 | db_set_int(aSetng[j].zSetting, iCmd-3, 0); |
| 1485 | 1690 | } |
| 1486 | 1691 | } |
| 1487 | 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 | + | |
| 1488 | 1698 | |
| 1489 | 1699 | /* destroy or rebuild the index, if requested */ |
| 1490 | 1700 | if( iAction>=1 ){ |
| 1491 | 1701 | search_drop_index(); |
| 1492 | 1702 | } |
| @@ -1497,14 +1707,16 @@ | ||
| 1497 | 1707 | /* Always show the status before ending */ |
| 1498 | 1708 | for(i=0; i<ArraySize(aSetng); i++){ |
| 1499 | 1709 | fossil_print("%-16s %s\n", aSetng[i].zName, |
| 1500 | 1710 | db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); |
| 1501 | 1711 | } |
| 1712 | + fossil_print("%-16s %s\n", "Porter stemmer:", | |
| 1713 | + db_get_boolean("search-stemmer",0) ? "on" : "off"); | |
| 1502 | 1714 | if( search_index_exists() ){ |
| 1503 | 1715 | fossil_print("%-16s enabled\n", "full-text index:"); |
| 1504 | 1716 | fossil_print("%-16s %d\n", "documents:", |
| 1505 | 1717 | db_int(0, "SELECT count(*) FROM ftsdocs")); |
| 1506 | 1718 | }else{ |
| 1507 | 1719 | fossil_print("%-16s disabled\n", "full-text index:"); |
| 1508 | 1720 | } |
| 1509 | 1721 | db_end_transaction(0); |
| 1510 | 1722 | } |
| 1511 | 1723 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -213,11 +213,11 @@ | |
| 213 | aiLastDoc[j] = iDoc; |
| 214 | aiLastOfst[j] = i; |
| 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | for(ii=0; ii<k; ii++){ |
| 217 | if( anMatch[j-ii]<k ){ |
| 218 | anMatch[j-ii] = k; |
| 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | } |
| 222 | } |
| 223 | break; |
| @@ -396,14 +396,18 @@ | |
| 396 | static void search_match_sqlfunc( |
| 397 | sqlite3_context *context, |
| 398 | int argc, |
| 399 | sqlite3_value **argv |
| 400 | ){ |
| 401 | const char *zSText = (const char*)sqlite3_value_text(argv[0]); |
| 402 | int rc; |
| 403 | if( zSText==0 ) return; |
| 404 | rc = search_match(&gSearch, 1, &zSText); |
| 405 | sqlite3_result_int(context, rc); |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | |
| 435 | static void search_stext_sqlfunc( |
| 436 | sqlite3_context *context, |
| 437 | int argc, |
| 438 | sqlite3_value **argv |
| 439 | ){ |
| 440 | Blob txt; |
| 441 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 442 | int rid = sqlite3_value_int(argv[1]); |
| 443 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 444 | search_stext(zType[0], rid, zName, &txt); |
| 445 | sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); |
| 446 | } |
| 447 | |
| 448 | /* |
| 449 | ** Encode a string for use as a query parameter in a URL |
| 450 | */ |
| @@ -463,20 +494,24 @@ | |
| 463 | ** do not delete the Search object. |
| 464 | */ |
| 465 | void search_sql_setup(sqlite3 *db){ |
| 466 | static int once = 0; |
| 467 | if( once++ ) return; |
| 468 | sqlite3_create_function(db, "search_match", 1, SQLITE_UTF8, 0, |
| 469 | search_match_sqlfunc, 0, 0); |
| 470 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 471 | search_score_sqlfunc, 0, 0); |
| 472 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 473 | search_snippet_sqlfunc, 0, 0); |
| 474 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 475 | search_init_sqlfunc, 0, 0); |
| 476 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 477 | search_stext_sqlfunc, 0, 0); |
| 478 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 479 | search_urlencode_sqlfunc, 0, 0); |
| 480 | } |
| 481 | |
| 482 | /* |
| @@ -616,21 +651,23 @@ | |
| 616 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 617 | db_multi_exec( |
| 618 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 619 | ); |
| 620 | db_multi_exec( |
| 621 | "INSERT INTO x(label,url,score,date,snip)" |
| 622 | " SELECT printf('Document: %%s',foci.filename)," |
| 623 | " printf('/doc/%T/%%s',foci.filename)," |
| 624 | " search_score()," |
| 625 | " (SELECT datetime(event.mtime) FROM event" |
| 626 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 627 | " search_snippet()" |
| 628 | " FROM foci CROSS JOIN blob" |
| 629 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 630 | " AND blob.uuid=foci.uuid" |
| 631 | " AND search_match(stext('d',blob.rid,foci.filename))" |
| 632 | " AND %z", |
| 633 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 634 | ); |
| 635 | } |
| 636 | } |
| @@ -641,18 +678,19 @@ | |
| 641 | " FROM tag, tagxref" |
| 642 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 643 | " AND tagxref.tagid=tag.tagid" |
| 644 | " GROUP BY 1" |
| 645 | ")" |
| 646 | "INSERT INTO x(label,url,score,date,snip)" |
| 647 | " SELECT printf('Wiki: %%s',name)," |
| 648 | " printf('/wiki?name=%%s',urlencode(name))," |
| 649 | " search_score()," |
| 650 | " datetime(mtime)," |
| 651 | " search_snippet()" |
| 652 | " FROM wiki" |
| 653 | " WHERE search_match(stext('w',rid,name));" |
| 654 | ); |
| 655 | } |
| 656 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 657 | db_multi_exec( |
| 658 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -659,34 +697,45 @@ | |
| 659 | " SELECT blob.uuid, event.objid, event.mtime" |
| 660 | " FROM event, blob" |
| 661 | " WHERE event.type='ci'" |
| 662 | " AND blob.rid=event.objid" |
| 663 | ")" |
| 664 | "INSERT INTO x(label,url,score,date,snip)" |
| 665 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 666 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 667 | " search_score()," |
| 668 | " datetime(mtime)," |
| 669 | " search_snippet()" |
| 670 | " FROM ckin" |
| 671 | " WHERE search_match(stext('c',rid,NULL));" |
| 672 | ); |
| 673 | } |
| 674 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 675 | db_multi_exec( |
| 676 | "INSERT INTO x(label,url,score, date,snip)" |
| 677 | " SELECT printf('Ticket [%%.17s] on %%s'," |
| 678 | "tkt_uuid,datetime(tkt_mtime))," |
| 679 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 680 | " search_score()," |
| 681 | " datetime(tkt_mtime)," |
| 682 | " search_snippet()" |
| 683 | " FROM ticket" |
| 684 | " WHERE search_match(stext('t',tkt_id,NULL));" |
| 685 | ); |
| 686 | } |
| 687 | } |
| 688 | |
| 689 | /* |
| 690 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 691 | */ |
| 692 | static void search_rank_sqlfunc( |
| @@ -694,24 +743,45 @@ | |
| 694 | int argc, |
| 695 | sqlite3_value **argv |
| 696 | ){ |
| 697 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 698 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 699 | int nTerm; /* Number of search terms in the query */ |
| 700 | int i; /* Loop counter */ |
| 701 | double r = 1.0; /* Score */ |
| 702 | |
| 703 | if( nVal<6 ) return; |
| 704 | if( aVal[1]!=1 ) return; |
| 705 | nTerm = aVal[0]; |
| 706 | r *= 1<<((30*(aVal[2]-1))/nTerm); |
| 707 | for(i=1; i<=nTerm; i++){ |
| 708 | int hits_this_row = aVal[3*i]; |
| 709 | int hits_all_rows = aVal[3*i+1]; |
| 710 | int rows_with_hit = aVal[3*i+2]; |
| 711 | double avg_hits_per_row = (double)hits_all_rows/(double)rows_with_hit; |
| 712 | r *= hits_this_row/avg_hits_per_row; |
| 713 | } |
| 714 | #define SEARCH_DEBUG_RANK 0 |
| 715 | #if SEARCH_DEBUG_RANK |
| 716 | { |
| 717 | Blob x; |
| @@ -746,14 +816,15 @@ | |
| 746 | if( srchFlags==0 ) return; |
| 747 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 748 | search_rank_sqlfunc, 0, 0); |
| 749 | blob_init(&sql, 0, 0); |
| 750 | blob_appendf(&sql, |
| 751 | "INSERT INTO x(label,url,score,date,snip) " |
| 752 | " SELECT ftsdocs.label," |
| 753 | " ftsdocs.url," |
| 754 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 755 | " datetime(ftsdocs.mtime)," |
| 756 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 757 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 758 | " WHERE ftsidx MATCH %Q" |
| 759 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -838,29 +909,30 @@ | |
| 838 | ** |
| 839 | ** Return the number of rows. |
| 840 | */ |
| 841 | int search_run_and_output( |
| 842 | const char *zPattern, /* The query pattern */ |
| 843 | unsigned int srchFlags /* What to search over */ |
| 844 | ){ |
| 845 | Stmt q; |
| 846 | int nRow = 0; |
| 847 | |
| 848 | srchFlags = search_restrict(srchFlags); |
| 849 | if( srchFlags==0 ) return 0; |
| 850 | search_sql_setup(g.db); |
| 851 | add_content_sql_commands(g.db); |
| 852 | db_multi_exec( |
| 853 | "CREATE TEMP TABLE x(label,url,score,date,snip);" |
| 854 | ); |
| 855 | if( !search_index_exists() ){ |
| 856 | search_fullscan(zPattern, srchFlags); |
| 857 | }else{ |
| 858 | search_update_index(srchFlags); |
| 859 | search_indexed(zPattern, srchFlags); |
| 860 | } |
| 861 | db_prepare(&q, "SELECT url, snip, label" |
| 862 | " FROM x" |
| 863 | " ORDER BY score DESC, date DESC;"); |
| 864 | while( db_step(&q)==SQLITE_ROW ){ |
| 865 | const char *zUrl = db_column_text(&q, 0); |
| 866 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -867,12 +939,15 @@ | |
| 867 | const char *zLabel = db_column_text(&q, 2); |
| 868 | if( nRow==0 ){ |
| 869 | @ <ol> |
| 870 | } |
| 871 | nRow++; |
| 872 | @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a><br> |
| 873 | @ <span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> |
| 874 | } |
| 875 | db_finalize(&q); |
| 876 | if( nRow ){ |
| 877 | @ </ol> |
| 878 | } |
| @@ -900,10 +975,11 @@ | |
| 900 | const char *zType = 0; |
| 901 | const char *zClass = 0; |
| 902 | const char *zDisable1; |
| 903 | const char *zDisable2; |
| 904 | const char *zPattern; |
| 905 | srchFlags = search_restrict(srchFlags); |
| 906 | switch( srchFlags ){ |
| 907 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 908 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 909 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -947,10 +1023,13 @@ | |
| 947 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 948 | } |
| 949 | @ </select> |
| 950 | srchFlags = newFlags; |
| 951 | } |
| 952 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 953 | if( srchFlags==0 ){ |
| 954 | @ <p class="generalError">Search is disabled</p> |
| 955 | } |
| 956 | @ </div></form> |
| @@ -959,11 +1038,11 @@ | |
| 959 | if( zClass ){ |
| 960 | @ <div class='searchResult searchResult%s(zClass)'> |
| 961 | }else{ |
| 962 | @ <div class='searchResult'> |
| 963 | } |
| 964 | if( search_run_and_output(zPattern, srchFlags)==0 ){ |
| 965 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 966 | } |
| 967 | @ </div> |
| 968 | } |
| 969 | } |
| @@ -983,10 +1062,14 @@ | |
| 983 | |
| 984 | |
| 985 | /* |
| 986 | ** This is a helper function for search_stext(). Writing into pOut |
| 987 | ** the search text obtained from pIn according to zMimetype. |
| 988 | */ |
| 989 | static void get_stext_by_mimetype( |
| 990 | Blob *pIn, |
| 991 | const char *zMimetype, |
| 992 | Blob *pOut |
| @@ -994,41 +1077,74 @@ | |
| 994 | Blob html, title; |
| 995 | blob_init(&html, 0, 0); |
| 996 | blob_init(&title, 0, 0); |
| 997 | if( zMimetype==0 ) zMimetype = "text/plain"; |
| 998 | if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ |
| 999 | wiki_convert(pIn, &html, 0); |
| 1000 | html_to_plaintext(blob_str(&html), pOut); |
| 1001 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1002 | markdown_to_html(pIn, &title, &html); |
| 1003 | html_to_plaintext(blob_str(&html), pOut); |
| 1004 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 1005 | html_to_plaintext(blob_str(pIn), pOut); |
| 1006 | }else{ |
| 1007 | *pOut = *pIn; |
| 1008 | blob_init(pIn, 0, 0); |
| 1009 | } |
| 1010 | blob_reset(&html); |
| 1011 | blob_reset(&title); |
| 1012 | } |
| 1013 | |
| 1014 | /* |
| 1015 | ** Query pQuery is pointing at a single row of output. Append a text |
| 1016 | ** representation of every text-compatible column to pAccum. |
| 1017 | */ |
| 1018 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery){ |
| 1019 | int n = db_column_count(pQuery); |
| 1020 | int i; |
| 1021 | for(i=0; i<n; i++){ |
| 1022 | const char *zColName = db_column_name(pQuery,i); |
| 1023 | if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue; |
| 1024 | if( fossil_stricmp(zColName,"mimetype")==0 ) continue; |
| 1025 | switch( db_column_type(pQuery,i) ){ |
| 1026 | case SQLITE_INTEGER: |
| 1027 | case SQLITE_FLOAT: |
| 1028 | case SQLITE_TEXT: |
| 1029 | blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); |
| 1030 | } |
| 1031 | } |
| 1032 | } |
| 1033 | |
| 1034 | |
| @@ -1054,11 +1170,11 @@ | |
| 1054 | ){ |
| 1055 | blob_init(pOut, 0, 0); |
| 1056 | switch( cType ){ |
| 1057 | case 'd': { /* Documents */ |
| 1058 | Blob doc; |
| 1059 | content_get(rid, &doc); |
| 1060 | blob_to_utf8_no_bom(&doc, 0); |
| 1061 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 1062 | blob_reset(&doc); |
| 1063 | break; |
| 1064 | } |
| @@ -1073,10 +1189,11 @@ | |
| 1073 | manifest_destroy(pWiki); |
| 1074 | break; |
| 1075 | } |
| 1076 | case 'c': { /* Check-in Comments */ |
| 1077 | static Stmt q; |
| 1078 | db_static_prepare(&q, |
| 1079 | "SELECT coalesce(ecomment,comment)" |
| 1080 | " ||' (user: '||coalesce(euser,user,'?')" |
| 1081 | " ||', tags: '||" |
| 1082 | " (SELECT group_concat(substr(tag.tagname,5),',')" |
| @@ -1083,44 +1200,99 @@ | |
| 1083 | " FROM tag, tagxref" |
| 1084 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 1085 | " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" |
| 1086 | " ||')'" |
| 1087 | " FROM event WHERE objid=:x AND type='ci'"); |
| 1088 | db_bind_int(&q, ":x", rid); |
| 1089 | if( db_step(&q)==SQLITE_ROW ){ |
| 1090 | db_column_blob(&q, 0, pOut); |
| 1091 | blob_append(pOut, "\n", 1); |
| 1092 | } |
| 1093 | db_reset(&q); |
| 1094 | break; |
| 1095 | } |
| 1096 | case 't': { /* Tickets */ |
| 1097 | static Stmt q1; |
| 1098 | Blob raw; |
| 1099 | db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); |
| 1100 | blob_init(&raw,0,0); |
| 1101 | db_bind_int(&q1, ":rid", rid); |
| 1102 | if( db_step(&q1)==SQLITE_ROW ){ |
| 1103 | append_all_ticket_fields(&raw, &q1); |
| 1104 | } |
| 1105 | db_reset(&q1); |
| 1106 | if( db_table_exists("repository","ticketchng") ){ |
| 1107 | static Stmt q2; |
| 1108 | db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid" |
| 1109 | " ORDER BY tkt_mtime"); |
| 1110 | db_bind_int(&q2, ":rid", rid); |
| 1111 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1112 | append_all_ticket_fields(&raw, &q2); |
| 1113 | } |
| 1114 | db_reset(&q2); |
| 1115 | } |
| 1116 | html_to_plaintext(blob_str(&raw), pOut); |
| 1117 | blob_reset(&raw); |
| 1118 | break; |
| 1119 | } |
| 1120 | } |
| 1121 | } |
| 1122 | |
| 1123 | /* |
| 1124 | ** COMMAND: test-search-stext |
| 1125 | ** |
| 1126 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| @@ -1131,10 +1303,30 @@ | |
| 1131 | if( g.argc!=5 ) usage("TYPE RID NAME"); |
| 1132 | search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); |
| 1133 | fossil_print("%s\n",blob_str(&out)); |
| 1134 | blob_reset(&out); |
| 1135 | } |
| 1136 | |
| 1137 | /* The schema for the full-text index |
| 1138 | */ |
| 1139 | static const char zFtsSchema[] = |
| 1140 | @ -- One entry for each possible search result |
| @@ -1145,20 +1337,21 @@ | |
| 1145 | @ name TEXT, -- Additional document description |
| 1146 | @ idxed BOOLEAN, -- True if currently in the index |
| 1147 | @ label TEXT, -- Label to print on search results |
| 1148 | @ url TEXT, -- URL to access this document |
| 1149 | @ mtime DATE, -- Date when document created |
| 1150 | @ UNIQUE(type,rid) |
| 1151 | @ ); |
| 1152 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 1153 | @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; |
| 1154 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 1155 | @ SELECT rowid, type, rid, name, idxed, label, url, mtime, |
| 1156 | @ stext(type,rid,name) AS 'stext' |
| 1157 | @ FROM ftsdocs; |
| 1158 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 1159 | @ USING fts4(content="ftscontent", stext); |
| 1160 | ; |
| 1161 | static const char zFtsDrop[] = |
| 1162 | @ DROP TABLE IF EXISTS "%w".ftsidx; |
| 1163 | @ DROP VIEW IF EXISTS "%w".ftscontent; |
| 1164 | @ DROP TABLE IF EXISTS "%w".ftsdocs; |
| @@ -1168,13 +1361,15 @@ | |
| 1168 | ** Create or drop the tables associated with a full-text index. |
| 1169 | */ |
| 1170 | static int searchIdxExists = -1; |
| 1171 | void search_create_index(void){ |
| 1172 | const char *zDb = db_name("repository"); |
| 1173 | search_sql_setup(g.db); |
| 1174 | db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w"*/, |
| 1175 | zDb, zDb, zDb, zDb, zDb); |
| 1176 | searchIdxExists = 1; |
| 1177 | } |
| 1178 | void search_drop_index(void){ |
| 1179 | const char *zDb = db_name("repository"); |
| 1180 | db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); |
| @@ -1292,34 +1487,39 @@ | |
| 1292 | db_multi_exec( |
| 1293 | "DELETE FROM ftsdocs WHERE type='d'" |
| 1294 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1295 | ); |
| 1296 | db_multi_exec( |
| 1297 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,url,mtime)" |
| 1298 | " SELECT 'd', rid, name, 0," |
| 1299 | " printf('Document: %%s',name)," |
| 1300 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1301 | " %.17g" |
| 1302 | " FROM current_docs", |
| 1303 | zBrUuid, rTime |
| 1304 | ); |
| 1305 | db_multi_exec( |
| 1306 | "INSERT INTO ftsidx(docid,stext)" |
| 1307 | " SELECT rowid, stext FROM ftscontent WHERE type='d' AND NOT idxed" |
| 1308 | ); |
| 1309 | db_multi_exec( |
| 1310 | "UPDATE ftsdocs SET idxed=1 WHERE type='d' AND NOT idxed" |
| 1311 | ); |
| 1312 | } |
| 1313 | |
| 1314 | /* |
| 1315 | ** Deal with all of the unindexed 'c' terms in FTSDOCS |
| 1316 | */ |
| 1317 | static void search_update_checkin_index(void){ |
| 1318 | db_multi_exec( |
| 1319 | "INSERT INTO ftsidx(docid,stext)" |
| 1320 | " SELECT rowid, stext('c',rid,NULL) FROM ftsdocs" |
| 1321 | " WHERE type='c' AND NOT idxed;" |
| 1322 | ); |
| 1323 | db_multi_exec( |
| 1324 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1325 | " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," |
| @@ -1336,19 +1536,20 @@ | |
| 1336 | /* |
| 1337 | ** Deal with all of the unindexed 't' terms in FTSDOCS |
| 1338 | */ |
| 1339 | static void search_update_ticket_index(void){ |
| 1340 | db_multi_exec( |
| 1341 | "INSERT INTO ftsidx(docid,stext)" |
| 1342 | " SELECT rowid, stext('t',rid,NULL) FROM ftsdocs" |
| 1343 | " WHERE type='t' AND NOT idxed;" |
| 1344 | ); |
| 1345 | if( db_changes()==0 ) return; |
| 1346 | db_multi_exec( |
| 1347 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1348 | " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," |
| 1349 | " printf('Ticket [%%.16s] on %%s',tkt_uuid,datetime(tkt_mtime))," |
| 1350 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 1351 | " tkt_mtime" |
| 1352 | " FROM ftsdocs, ticket" |
| 1353 | " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" |
| 1354 | " AND ticket.tkt_id=ftsdocs.rid" |
| @@ -1358,12 +1559,12 @@ | |
| 1358 | /* |
| 1359 | ** Deal with all of the unindexed 'w' terms in FTSDOCS |
| 1360 | */ |
| 1361 | static void search_update_wiki_index(void){ |
| 1362 | db_multi_exec( |
| 1363 | "INSERT INTO ftsidx(docid,stext)" |
| 1364 | " SELECT rowid, stext('w',rid,NULL) FROM ftsdocs" |
| 1365 | " WHERE type='w' AND NOT idxed;" |
| 1366 | ); |
| 1367 | if( db_changes()==0 ) return; |
| 1368 | db_multi_exec( |
| 1369 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| @@ -1416,19 +1617,22 @@ | |
| 1416 | ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? |
| 1417 | ** |
| 1418 | ** The "fossil fts-config" command configures the full-text search capabilities |
| 1419 | ** of the repository. Subcommands: |
| 1420 | ** |
| 1421 | ** reindex Rebuild the search index. Create it if it does |
| 1422 | ** not already exist |
| 1423 | ** |
| 1424 | ** index (on|off) Turn the search index on or off |
| 1425 | ** |
| 1426 | ** enable cdtw Enable various kinds of search. c=Check-ins, |
| 1427 | ** d=Documents, t=Tickets, w=Wiki. |
| 1428 | ** |
| 1429 | ** disable cdtw Disable versious kinds of search |
| 1430 | ** |
| 1431 | ** The current search settings are displayed after any changes are applied. |
| 1432 | ** Run this command with no arguments to simply see the settings. |
| 1433 | */ |
| 1434 | void test_fts_cmd(void){ |
| @@ -1435,18 +1639,19 @@ | |
| 1435 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1436 | { 1, "reindex" }, |
| 1437 | { 2, "index" }, |
| 1438 | { 3, "disable" }, |
| 1439 | { 4, "enable" }, |
| 1440 | }; |
| 1441 | static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { |
| 1442 | { "search-ckin", "check-in search:", "c" }, |
| 1443 | { "search-doc", "document search:", "d" }, |
| 1444 | { "search-tkt", "ticket search:", "t" }, |
| 1445 | { "search-wiki", "wiki search:", "w" }, |
| 1446 | }; |
| 1447 | char *zSubCmd; |
| 1448 | int i, j, n; |
| 1449 | int iCmd = 0; |
| 1450 | int iAction = 0; |
| 1451 | db_find_and_open_repository(0, 0); |
| 1452 | if( g.argc>2 ){ |
| @@ -1464,11 +1669,11 @@ | |
| 1464 | return; |
| 1465 | } |
| 1466 | iCmd = aCmd[i].iCmd; |
| 1467 | } |
| 1468 | if( iCmd==1 ){ |
| 1469 | iAction = 2; |
| 1470 | } |
| 1471 | if( iCmd==2 ){ |
| 1472 | if( g.argc<3 ) usage("index (on|off)"); |
| 1473 | iAction = 1 + is_truth(g.argv[3]); |
| 1474 | } |
| @@ -1475,18 +1680,23 @@ | |
| 1475 | db_begin_transaction(); |
| 1476 | |
| 1477 | /* Adjust search settings */ |
| 1478 | if( iCmd==3 || iCmd==4 ){ |
| 1479 | const char *zCtrl; |
| 1480 | if( g.argc<4 ) usage("enable STRING"); |
| 1481 | zCtrl = g.argv[3]; |
| 1482 | for(j=0; j<ArraySize(aSetng); j++){ |
| 1483 | if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){ |
| 1484 | db_set_int(aSetng[j].zSetting, iCmd-3, 0); |
| 1485 | } |
| 1486 | } |
| 1487 | } |
| 1488 | |
| 1489 | /* destroy or rebuild the index, if requested */ |
| 1490 | if( iAction>=1 ){ |
| 1491 | search_drop_index(); |
| 1492 | } |
| @@ -1497,14 +1707,16 @@ | |
| 1497 | /* Always show the status before ending */ |
| 1498 | for(i=0; i<ArraySize(aSetng); i++){ |
| 1499 | fossil_print("%-16s %s\n", aSetng[i].zName, |
| 1500 | db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); |
| 1501 | } |
| 1502 | if( search_index_exists() ){ |
| 1503 | fossil_print("%-16s enabled\n", "full-text index:"); |
| 1504 | fossil_print("%-16s %d\n", "documents:", |
| 1505 | db_int(0, "SELECT count(*) FROM ftsdocs")); |
| 1506 | }else{ |
| 1507 | fossil_print("%-16s disabled\n", "full-text index:"); |
| 1508 | } |
| 1509 | db_end_transaction(0); |
| 1510 | } |
| 1511 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -213,11 +213,11 @@ | |
| 213 | aiLastDoc[j] = iDoc; |
| 214 | aiLastOfst[j] = i; |
| 215 | for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} |
| 216 | for(ii=0; ii<k; ii++){ |
| 217 | if( anMatch[j-ii]<k ){ |
| 218 | anMatch[j-ii] = k*(nDoc-iDoc); |
| 219 | aiBestDoc[j-ii] = aiLastDoc[j-ii]; |
| 220 | aiBestOfst[j-ii] = aiLastOfst[j-ii]; |
| 221 | } |
| 222 | } |
| 223 | break; |
| @@ -396,14 +396,18 @@ | |
| 396 | static void search_match_sqlfunc( |
| 397 | sqlite3_context *context, |
| 398 | int argc, |
| 399 | sqlite3_value **argv |
| 400 | ){ |
| 401 | const char *azDoc[5]; |
| 402 | int nDoc; |
| 403 | int rc; |
| 404 | for(nDoc=0; nDoc<ArraySize(azDoc) && nDoc<argc; nDoc++){ |
| 405 | azDoc[nDoc] = (const char*)sqlite3_value_text(argv[nDoc]); |
| 406 | if( azDoc[nDoc]==0 ) azDoc[nDoc] = ""; |
| 407 | } |
| 408 | rc = search_match(&gSearch, nDoc, azDoc); |
| 409 | sqlite3_result_int(context, rc); |
| 410 | } |
| 411 | |
| 412 | /* |
| 413 | ** These SQL functions return the results of the last |
| @@ -435,16 +439,43 @@ | |
| 439 | static void search_stext_sqlfunc( |
| 440 | sqlite3_context *context, |
| 441 | int argc, |
| 442 | sqlite3_value **argv |
| 443 | ){ |
| 444 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 445 | int rid = sqlite3_value_int(argv[1]); |
| 446 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 447 | sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1, |
| 448 | SQLITE_TRANSIENT); |
| 449 | } |
| 450 | static void search_title_sqlfunc( |
| 451 | sqlite3_context *context, |
| 452 | int argc, |
| 453 | sqlite3_value **argv |
| 454 | ){ |
| 455 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 456 | int rid = sqlite3_value_int(argv[1]); |
| 457 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 458 | int nHdr; |
| 459 | char *z = search_stext_cached(zType[0], rid, zName, &nHdr); |
| 460 | if( nHdr || zType[0]!='d' ){ |
| 461 | sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT); |
| 462 | }else{ |
| 463 | sqlite3_result_value(context, argv[2]); |
| 464 | } |
| 465 | } |
| 466 | static void search_body_sqlfunc( |
| 467 | sqlite3_context *context, |
| 468 | int argc, |
| 469 | sqlite3_value **argv |
| 470 | ){ |
| 471 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 472 | int rid = sqlite3_value_int(argv[1]); |
| 473 | const char *zName = (const char*)sqlite3_value_text(argv[2]); |
| 474 | int nHdr; |
| 475 | char *z = search_stext_cached(zType[0], rid, zName, &nHdr); |
| 476 | sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT); |
| 477 | } |
| 478 | |
| 479 | /* |
| 480 | ** Encode a string for use as a query parameter in a URL |
| 481 | */ |
| @@ -463,20 +494,24 @@ | |
| 494 | ** do not delete the Search object. |
| 495 | */ |
| 496 | void search_sql_setup(sqlite3 *db){ |
| 497 | static int once = 0; |
| 498 | if( once++ ) return; |
| 499 | sqlite3_create_function(db, "search_match", -1, SQLITE_UTF8, 0, |
| 500 | search_match_sqlfunc, 0, 0); |
| 501 | sqlite3_create_function(db, "search_score", 0, SQLITE_UTF8, 0, |
| 502 | search_score_sqlfunc, 0, 0); |
| 503 | sqlite3_create_function(db, "search_snippet", 0, SQLITE_UTF8, 0, |
| 504 | search_snippet_sqlfunc, 0, 0); |
| 505 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 506 | search_init_sqlfunc, 0, 0); |
| 507 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 508 | search_stext_sqlfunc, 0, 0); |
| 509 | sqlite3_create_function(db, "title", 3, SQLITE_UTF8, 0, |
| 510 | search_title_sqlfunc, 0, 0); |
| 511 | sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0, |
| 512 | search_body_sqlfunc, 0, 0); |
| 513 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 514 | search_urlencode_sqlfunc, 0, 0); |
| 515 | } |
| 516 | |
| 517 | /* |
| @@ -616,21 +651,23 @@ | |
| 651 | if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ |
| 652 | db_multi_exec( |
| 653 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 654 | ); |
| 655 | db_multi_exec( |
| 656 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 657 | " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," |
| 658 | " printf('/doc/%T/%%s',foci.filename)," |
| 659 | " search_score()," |
| 660 | " 'd'||blob.rid," |
| 661 | " (SELECT datetime(event.mtime) FROM event" |
| 662 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 663 | " search_snippet()" |
| 664 | " FROM foci CROSS JOIN blob" |
| 665 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 666 | " AND blob.uuid=foci.uuid" |
| 667 | " AND search_match(title('d',blob.rid,foci.filename)," |
| 668 | " body('d',blob.rid,foci.filename))" |
| 669 | " AND %z", |
| 670 | zDocBr, glob_expr("foci.filename", zDocGlob) |
| 671 | ); |
| 672 | } |
| 673 | } |
| @@ -641,18 +678,19 @@ | |
| 678 | " FROM tag, tagxref" |
| 679 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 680 | " AND tagxref.tagid=tag.tagid" |
| 681 | " GROUP BY 1" |
| 682 | ")" |
| 683 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 684 | " SELECT printf('Wiki: %%s',name)," |
| 685 | " printf('/wiki?name=%%s',urlencode(name))," |
| 686 | " search_score()," |
| 687 | " 'w'||rid," |
| 688 | " datetime(mtime)," |
| 689 | " search_snippet()" |
| 690 | " FROM wiki" |
| 691 | " WHERE search_match(title('w',rid,name),body('w',rid,name));" |
| 692 | ); |
| 693 | } |
| 694 | if( (srchFlags & SRCH_CKIN)!=0 ){ |
| 695 | db_multi_exec( |
| 696 | "WITH ckin(uuid,rid,mtime) AS (" |
| @@ -659,34 +697,45 @@ | |
| 697 | " SELECT blob.uuid, event.objid, event.mtime" |
| 698 | " FROM event, blob" |
| 699 | " WHERE event.type='ci'" |
| 700 | " AND blob.rid=event.objid" |
| 701 | ")" |
| 702 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 703 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 704 | " printf('/timeline?c=%%s&n=8&y=ci',uuid)," |
| 705 | " search_score()," |
| 706 | " 'c'||rid," |
| 707 | " datetime(mtime)," |
| 708 | " search_snippet()" |
| 709 | " FROM ckin" |
| 710 | " WHERE search_match('',body('c',rid,NULL));" |
| 711 | ); |
| 712 | } |
| 713 | if( (srchFlags & SRCH_TKT)!=0 ){ |
| 714 | db_multi_exec( |
| 715 | "INSERT INTO x(label,url,score,id,date,snip)" |
| 716 | " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," |
| 717 | "datetime(tkt_mtime))," |
| 718 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 719 | " search_score()," |
| 720 | " 't'||tkt_id," |
| 721 | " datetime(tkt_mtime)," |
| 722 | " search_snippet()" |
| 723 | " FROM ticket" |
| 724 | " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" |
| 725 | ); |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | /* |
| 730 | ** Number of significant bits in a u32 |
| 731 | */ |
| 732 | static int nbits(u32 x){ |
| 733 | int n = 0; |
| 734 | while( x ){ n++; x >>= 1; } |
| 735 | return n; |
| 736 | } |
| 737 | |
| 738 | /* |
| 739 | ** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). |
| 740 | */ |
| 741 | static void search_rank_sqlfunc( |
| @@ -694,24 +743,45 @@ | |
| 743 | int argc, |
| 744 | sqlite3_value **argv |
| 745 | ){ |
| 746 | const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); |
| 747 | int nVal = sqlite3_value_bytes(argv[0])/4; |
| 748 | int nCol; /* Number of columns in the index */ |
| 749 | int nTerm; /* Number of search terms in the query */ |
| 750 | int i, j; /* Loop counter */ |
| 751 | double r = 0.0; /* Score */ |
| 752 | const unsigned *aX, *aS; |
| 753 | |
| 754 | if( nVal<2 ) return; |
| 755 | nTerm = aVal[0]; |
| 756 | nCol = aVal[1]; |
| 757 | if( nVal<2+3*nCol*nTerm+nCol ) return; |
| 758 | aS = aVal+2; |
| 759 | aX = aS+nCol; |
| 760 | for(j=0; j<nCol; j++){ |
| 761 | double x; |
| 762 | if( aS[j]>0 ){ |
| 763 | x = 0.0; |
| 764 | for(i=0; i<nTerm; i++){ |
| 765 | int hits_this_row; |
| 766 | int hits_all_rows; |
| 767 | int rows_with_hit; |
| 768 | double avg_hits_per_row; |
| 769 | |
| 770 | hits_this_row = aX[j + i*nCol*3]; |
| 771 | if( hits_this_row==0 )continue; |
| 772 | hits_all_rows = aX[j + i*nCol*3 + 1]; |
| 773 | rows_with_hit = aX[j + i*nCol*3 + 2]; |
| 774 | if( rows_with_hit==0 ) continue; |
| 775 | avg_hits_per_row = hits_all_rows/(double)rows_with_hit; |
| 776 | x += hits_this_row/(avg_hits_per_row*nbits(rows_with_hit)); |
| 777 | } |
| 778 | x *= (1<<((30*(aS[j]-1))/nTerm)); |
| 779 | }else{ |
| 780 | x = 0.0; |
| 781 | } |
| 782 | r = r*10.0 + x; |
| 783 | } |
| 784 | #define SEARCH_DEBUG_RANK 0 |
| 785 | #if SEARCH_DEBUG_RANK |
| 786 | { |
| 787 | Blob x; |
| @@ -746,14 +816,15 @@ | |
| 816 | if( srchFlags==0 ) return; |
| 817 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8, 0, |
| 818 | search_rank_sqlfunc, 0, 0); |
| 819 | blob_init(&sql, 0, 0); |
| 820 | blob_appendf(&sql, |
| 821 | "INSERT INTO x(label,url,score,id,date,snip) " |
| 822 | " SELECT ftsdocs.label," |
| 823 | " ftsdocs.url," |
| 824 | " rank(matchinfo(ftsidx,'pcsx'))," |
| 825 | " ftsdocs.type || ftsdocs.rid," |
| 826 | " datetime(ftsdocs.mtime)," |
| 827 | " snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)" |
| 828 | " FROM ftsidx CROSS JOIN ftsdocs" |
| 829 | " WHERE ftsidx MATCH %Q" |
| 830 | " AND ftsdocs.rowid=ftsidx.docid", |
| @@ -838,29 +909,30 @@ | |
| 909 | ** |
| 910 | ** Return the number of rows. |
| 911 | */ |
| 912 | int search_run_and_output( |
| 913 | const char *zPattern, /* The query pattern */ |
| 914 | unsigned int srchFlags, /* What to search over */ |
| 915 | int fDebug /* Extra debugging output */ |
| 916 | ){ |
| 917 | Stmt q; |
| 918 | int nRow = 0; |
| 919 | |
| 920 | srchFlags = search_restrict(srchFlags); |
| 921 | if( srchFlags==0 ) return 0; |
| 922 | search_sql_setup(g.db); |
| 923 | add_content_sql_commands(g.db); |
| 924 | db_multi_exec( |
| 925 | "CREATE TEMP TABLE x(label,url,score,id,date,snip);" |
| 926 | ); |
| 927 | if( !search_index_exists() ){ |
| 928 | search_fullscan(zPattern, srchFlags); |
| 929 | }else{ |
| 930 | search_update_index(srchFlags); |
| 931 | search_indexed(zPattern, srchFlags); |
| 932 | } |
| 933 | db_prepare(&q, "SELECT url, snip, label, score, id" |
| 934 | " FROM x" |
| 935 | " ORDER BY score DESC, date DESC;"); |
| 936 | while( db_step(&q)==SQLITE_ROW ){ |
| 937 | const char *zUrl = db_column_text(&q, 0); |
| 938 | const char *zSnippet = db_column_text(&q, 1); |
| @@ -867,12 +939,15 @@ | |
| 939 | const char *zLabel = db_column_text(&q, 2); |
| 940 | if( nRow==0 ){ |
| 941 | @ <ol> |
| 942 | } |
| 943 | nRow++; |
| 944 | @ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a> |
| 945 | if( fDebug ){ |
| 946 | @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))) |
| 947 | } |
| 948 | @ <br><span class='snippet'>%z(cleanSnippet(zSnippet))</span></li> |
| 949 | } |
| 950 | db_finalize(&q); |
| 951 | if( nRow ){ |
| 952 | @ </ol> |
| 953 | } |
| @@ -900,10 +975,11 @@ | |
| 975 | const char *zType = 0; |
| 976 | const char *zClass = 0; |
| 977 | const char *zDisable1; |
| 978 | const char *zDisable2; |
| 979 | const char *zPattern; |
| 980 | int fDebug = PB("debug"); |
| 981 | srchFlags = search_restrict(srchFlags); |
| 982 | switch( srchFlags ){ |
| 983 | case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; |
| 984 | case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; |
| 985 | case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; |
| @@ -947,10 +1023,13 @@ | |
| 1023 | cgi_printf(">%s</option>\n", aY[i].zNm); |
| 1024 | } |
| 1025 | @ </select> |
| 1026 | srchFlags = newFlags; |
| 1027 | } |
| 1028 | if( fDebug ){ |
| 1029 | @ <input type="hidden" name="debug" value="1"> |
| 1030 | } |
| 1031 | @ <input type="submit" value="Search%s(zType)"%s(zDisable2)> |
| 1032 | if( srchFlags==0 ){ |
| 1033 | @ <p class="generalError">Search is disabled</p> |
| 1034 | } |
| 1035 | @ </div></form> |
| @@ -959,11 +1038,11 @@ | |
| 1038 | if( zClass ){ |
| 1039 | @ <div class='searchResult searchResult%s(zClass)'> |
| 1040 | }else{ |
| 1041 | @ <div class='searchResult'> |
| 1042 | } |
| 1043 | if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ |
| 1044 | @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p> |
| 1045 | } |
| 1046 | @ </div> |
| 1047 | } |
| 1048 | } |
| @@ -983,10 +1062,14 @@ | |
| 1062 | |
| 1063 | |
| 1064 | /* |
| 1065 | ** This is a helper function for search_stext(). Writing into pOut |
| 1066 | ** the search text obtained from pIn according to zMimetype. |
| 1067 | ** |
| 1068 | ** The title of the document is the first line of text. All subsequent |
| 1069 | ** lines are the body. If the document has no title, the first line |
| 1070 | ** is blank. |
| 1071 | */ |
| 1072 | static void get_stext_by_mimetype( |
| 1073 | Blob *pIn, |
| 1074 | const char *zMimetype, |
| 1075 | Blob *pOut |
| @@ -994,41 +1077,74 @@ | |
| 1077 | Blob html, title; |
| 1078 | blob_init(&html, 0, 0); |
| 1079 | blob_init(&title, 0, 0); |
| 1080 | if( zMimetype==0 ) zMimetype = "text/plain"; |
| 1081 | if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ |
| 1082 | Blob tail; |
| 1083 | blob_init(&tail, 0, 0); |
| 1084 | if( wiki_find_title(pIn, &title, &tail) ){ |
| 1085 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1086 | wiki_convert(&tail, &html, 0); |
| 1087 | blob_reset(&tail); |
| 1088 | }else{ |
| 1089 | blob_append(pOut, "\n", 1); |
| 1090 | wiki_convert(pIn, &html, 0); |
| 1091 | } |
| 1092 | html_to_plaintext(blob_str(&html), pOut); |
| 1093 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1094 | markdown_to_html(pIn, &title, &html); |
| 1095 | if( blob_size(&title) ){ |
| 1096 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1097 | }else{ |
| 1098 | blob_append(pOut, "\n", 1); |
| 1099 | } |
| 1100 | html_to_plaintext(blob_str(&html), pOut); |
| 1101 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 1102 | if( doc_is_embedded_html(pIn, &title) ){ |
| 1103 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1104 | } |
| 1105 | html_to_plaintext(blob_str(pIn), pOut); |
| 1106 | }else{ |
| 1107 | blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); |
| 1108 | } |
| 1109 | blob_reset(&html); |
| 1110 | blob_reset(&title); |
| 1111 | } |
| 1112 | |
| 1113 | /* |
| 1114 | ** Query pQuery is pointing at a single row of output. Append a text |
| 1115 | ** representation of every text-compatible column to pAccum. |
| 1116 | */ |
| 1117 | static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ |
| 1118 | int n = db_column_count(pQuery); |
| 1119 | int i; |
| 1120 | const char *zMime = 0; |
| 1121 | if( iTitle>=0 && iTitle<n ){ |
| 1122 | if( db_column_type(pQuery,iTitle)==SQLITE_TEXT ){ |
| 1123 | blob_append(pAccum, db_column_text(pQuery,iTitle), -1); |
| 1124 | } |
| 1125 | blob_append(pAccum, "\n", 1); |
| 1126 | } |
| 1127 | for(i=0; i<n; i++){ |
| 1128 | const char *zColName = db_column_name(pQuery,i); |
| 1129 | int eType = db_column_type(pQuery,i); |
| 1130 | if( i==iTitle ) continue; |
| 1131 | if( fossil_strnicmp(zColName,"tkt_",4)==0 ) continue; |
| 1132 | if( fossil_strnicmp(zColName,"private_",8)==0 ) continue; |
| 1133 | if( eType==SQLITE_BLOB || eType==SQLITE_NULL ) continue; |
| 1134 | if( fossil_stricmp(zColName,"mimetype")==0 ){ |
| 1135 | zMime = db_column_text(pQuery,i); |
| 1136 | if( fossil_strcmp(zMime,"text/plain")==0 ) zMime = 0; |
| 1137 | }else if( zMime==0 || eType!=SQLITE_TEXT ){ |
| 1138 | blob_appendf(pAccum, "%s: %s |\n", zColName, db_column_text(pQuery,i)); |
| 1139 | }else{ |
| 1140 | Blob txt; |
| 1141 | blob_init(&txt, db_column_text(pQuery,i), -1); |
| 1142 | blob_appendf(pAccum, "%s: ", zColName); |
| 1143 | get_stext_by_mimetype(&txt, zMime, pAccum); |
| 1144 | blob_append(pAccum, " |", 2); |
| 1145 | blob_reset(&txt); |
| 1146 | } |
| 1147 | } |
| 1148 | } |
| 1149 | |
| 1150 | |
| @@ -1054,11 +1170,11 @@ | |
| 1170 | ){ |
| 1171 | blob_init(pOut, 0, 0); |
| 1172 | switch( cType ){ |
| 1173 | case 'd': { /* Documents */ |
| 1174 | Blob doc; |
| 1175 | content_get(rid, &doc); |
| 1176 | blob_to_utf8_no_bom(&doc, 0); |
| 1177 | get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut); |
| 1178 | blob_reset(&doc); |
| 1179 | break; |
| 1180 | } |
| @@ -1073,10 +1189,11 @@ | |
| 1189 | manifest_destroy(pWiki); |
| 1190 | break; |
| 1191 | } |
| 1192 | case 'c': { /* Check-in Comments */ |
| 1193 | static Stmt q; |
| 1194 | static int isPlainText = -1; |
| 1195 | db_static_prepare(&q, |
| 1196 | "SELECT coalesce(ecomment,comment)" |
| 1197 | " ||' (user: '||coalesce(euser,user,'?')" |
| 1198 | " ||', tags: '||" |
| 1199 | " (SELECT group_concat(substr(tag.tagname,5),',')" |
| @@ -1083,44 +1200,99 @@ | |
| 1200 | " FROM tag, tagxref" |
| 1201 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 1202 | " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" |
| 1203 | " ||')'" |
| 1204 | " FROM event WHERE objid=:x AND type='ci'"); |
| 1205 | if( isPlainText<0 ){ |
| 1206 | isPlainText = db_get_boolean("timeline-plaintext",0); |
| 1207 | } |
| 1208 | db_bind_int(&q, ":x", rid); |
| 1209 | if( db_step(&q)==SQLITE_ROW ){ |
| 1210 | blob_append(pOut, "\n", 1); |
| 1211 | if( isPlainText ){ |
| 1212 | db_column_blob(&q, 0, pOut); |
| 1213 | }else{ |
| 1214 | Blob x; |
| 1215 | blob_init(&x,0,0); |
| 1216 | db_column_blob(&q, 0, &x); |
| 1217 | get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut); |
| 1218 | blob_reset(&x); |
| 1219 | } |
| 1220 | } |
| 1221 | db_reset(&q); |
| 1222 | break; |
| 1223 | } |
| 1224 | case 't': { /* Tickets */ |
| 1225 | static Stmt q1; |
| 1226 | static int iTitle = -1; |
| 1227 | db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); |
| 1228 | db_bind_int(&q1, ":rid", rid); |
| 1229 | if( db_step(&q1)==SQLITE_ROW ){ |
| 1230 | if( iTitle<0 ){ |
| 1231 | int n = db_column_count(&q1); |
| 1232 | for(iTitle=0; iTitle<n; iTitle++){ |
| 1233 | if( fossil_stricmp(db_column_name(&q1,iTitle),"title")==0 ) break; |
| 1234 | } |
| 1235 | } |
| 1236 | append_all_ticket_fields(pOut, &q1, iTitle); |
| 1237 | } |
| 1238 | db_reset(&q1); |
| 1239 | if( db_table_exists("repository","ticketchng") ){ |
| 1240 | static Stmt q2; |
| 1241 | db_static_prepare(&q2, "SELECT * FROM ticketchng WHERE tkt_id=:rid" |
| 1242 | " ORDER BY tkt_mtime"); |
| 1243 | db_bind_int(&q2, ":rid", rid); |
| 1244 | while( db_step(&q2)==SQLITE_ROW ){ |
| 1245 | append_all_ticket_fields(pOut, &q2, -1); |
| 1246 | } |
| 1247 | db_reset(&q2); |
| 1248 | } |
| 1249 | break; |
| 1250 | } |
| 1251 | } |
| 1252 | } |
| 1253 | |
| 1254 | /* |
| 1255 | ** This routine is a wrapper around search_stext(). |
| 1256 | ** |
| 1257 | ** This routine looks up the search text, stores it in an internal |
| 1258 | ** buffer, and returns a pointer to the text. Subsequent requests |
| 1259 | ** for the same document return the same pointer. The returned pointer |
| 1260 | ** is valid until the next invocation of this routine. Call this routine |
| 1261 | ** with an eType of 0 to clear the cache. |
| 1262 | */ |
| 1263 | char *search_stext_cached( |
| 1264 | char cType, /* Type of document */ |
| 1265 | int rid, /* BLOB.RID or TAG.TAGID value for document */ |
| 1266 | const char *zName, /* Auxiliary information */ |
| 1267 | int *pnTitle /* OUT: length of title in bytes excluding \n */ |
| 1268 | ){ |
| 1269 | static struct { |
| 1270 | Blob stext; /* Cached search text */ |
| 1271 | char cType; /* The type */ |
| 1272 | int rid; /* The RID */ |
| 1273 | int nTitle; /* Number of bytes in title */ |
| 1274 | } cache; |
| 1275 | int i; |
| 1276 | char *z; |
| 1277 | if( cType!=cache.cType || rid!=cache.rid ){ |
| 1278 | if( cache.rid>0 ){ |
| 1279 | blob_reset(&cache.stext); |
| 1280 | }else{ |
| 1281 | blob_init(&cache.stext,0,0); |
| 1282 | } |
| 1283 | cache.cType = cType; |
| 1284 | cache.rid = rid; |
| 1285 | if( cType==0 ) return 0; |
| 1286 | search_stext(cType, rid, zName, &cache.stext); |
| 1287 | z = blob_str(&cache.stext); |
| 1288 | for(i=0; z[i] && z[i]!='\n'; i++){} |
| 1289 | cache.nTitle = i; |
| 1290 | } |
| 1291 | if( pnTitle ) *pnTitle = cache.nTitle; |
| 1292 | return blob_str(&cache.stext); |
| 1293 | } |
| 1294 | |
| 1295 | /* |
| 1296 | ** COMMAND: test-search-stext |
| 1297 | ** |
| 1298 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| @@ -1131,10 +1303,30 @@ | |
| 1303 | if( g.argc!=5 ) usage("TYPE RID NAME"); |
| 1304 | search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); |
| 1305 | fossil_print("%s\n",blob_str(&out)); |
| 1306 | blob_reset(&out); |
| 1307 | } |
| 1308 | |
| 1309 | /* |
| 1310 | ** COMMAND: test-convert-stext |
| 1311 | ** |
| 1312 | ** Usage: fossil test-convert-stext FILE MIMETYPE |
| 1313 | ** |
| 1314 | ** Read the content of FILE and convert it to stext according to MIMETYPE. |
| 1315 | ** Send the result to standard output. |
| 1316 | */ |
| 1317 | void test_convert_stext(void){ |
| 1318 | Blob in, out; |
| 1319 | db_find_and_open_repository(0,0); |
| 1320 | if( g.argc!=4 ) usage("FILENAME MIMETYPE"); |
| 1321 | blob_read_from_file(&in, g.argv[2]); |
| 1322 | blob_init(&out, 0, 0); |
| 1323 | get_stext_by_mimetype(&in, g.argv[3], &out); |
| 1324 | fossil_print("%s\n",blob_str(&out)); |
| 1325 | blob_reset(&in); |
| 1326 | blob_reset(&out); |
| 1327 | } |
| 1328 | |
| 1329 | /* The schema for the full-text index |
| 1330 | */ |
| 1331 | static const char zFtsSchema[] = |
| 1332 | @ -- One entry for each possible search result |
| @@ -1145,20 +1337,21 @@ | |
| 1337 | @ name TEXT, -- Additional document description |
| 1338 | @ idxed BOOLEAN, -- True if currently in the index |
| 1339 | @ label TEXT, -- Label to print on search results |
| 1340 | @ url TEXT, -- URL to access this document |
| 1341 | @ mtime DATE, -- Date when document created |
| 1342 | @ bx TEXT, -- Temporary "body" content cache |
| 1343 | @ UNIQUE(type,rid) |
| 1344 | @ ); |
| 1345 | @ CREATE INDEX "%w".ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; |
| 1346 | @ CREATE INDEX "%w".ftsdocName ON ftsdocs(name) WHERE type='w'; |
| 1347 | @ CREATE VIEW IF NOT EXISTS "%w".ftscontent AS |
| 1348 | @ SELECT rowid, type, rid, name, idxed, label, url, mtime, |
| 1349 | @ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body' |
| 1350 | @ FROM ftsdocs; |
| 1351 | @ CREATE VIRTUAL TABLE IF NOT EXISTS "%w".ftsidx |
| 1352 | @ USING fts4(content="ftscontent", title, body%s); |
| 1353 | ; |
| 1354 | static const char zFtsDrop[] = |
| 1355 | @ DROP TABLE IF EXISTS "%w".ftsidx; |
| 1356 | @ DROP VIEW IF EXISTS "%w".ftscontent; |
| 1357 | @ DROP TABLE IF EXISTS "%w".ftsdocs; |
| @@ -1168,13 +1361,15 @@ | |
| 1361 | ** Create or drop the tables associated with a full-text index. |
| 1362 | */ |
| 1363 | static int searchIdxExists = -1; |
| 1364 | void search_create_index(void){ |
| 1365 | const char *zDb = db_name("repository"); |
| 1366 | int useStemmer = db_get_boolean("search-stemmer",0); |
| 1367 | const char *zExtra = useStemmer ? ",tokenize=porter" : ""; |
| 1368 | search_sql_setup(g.db); |
| 1369 | db_multi_exec(zFtsSchema/*works-like:"%w%w%w%w%w%s"*/, |
| 1370 | zDb, zDb, zDb, zDb, zDb, zExtra/*safe-for-%s*/); |
| 1371 | searchIdxExists = 1; |
| 1372 | } |
| 1373 | void search_drop_index(void){ |
| 1374 | const char *zDb = db_name("repository"); |
| 1375 | db_multi_exec(zFtsDrop/*works-like:"%w%w%w"*/, zDb, zDb, zDb); |
| @@ -1292,34 +1487,39 @@ | |
| 1487 | db_multi_exec( |
| 1488 | "DELETE FROM ftsdocs WHERE type='d'" |
| 1489 | " AND rid NOT IN (SELECT rid FROM current_docs)" |
| 1490 | ); |
| 1491 | db_multi_exec( |
| 1492 | "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" |
| 1493 | " SELECT 'd', rid, name, 0," |
| 1494 | " title('d',rid,name)," |
| 1495 | " body('d',rid,name)," |
| 1496 | " printf('/doc/%q/%%s',urlencode(name))," |
| 1497 | " %.17g" |
| 1498 | " FROM current_docs", |
| 1499 | zBrUuid, rTime |
| 1500 | ); |
| 1501 | db_multi_exec( |
| 1502 | "INSERT INTO ftsidx(docid,title,body)" |
| 1503 | " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" |
| 1504 | ); |
| 1505 | db_multi_exec( |
| 1506 | "UPDATE ftsdocs SET" |
| 1507 | " idxed=1," |
| 1508 | " bx=NULL," |
| 1509 | " label='Document: '||label" |
| 1510 | " WHERE type='d' AND NOT idxed" |
| 1511 | ); |
| 1512 | } |
| 1513 | |
| 1514 | /* |
| 1515 | ** Deal with all of the unindexed 'c' terms in FTSDOCS |
| 1516 | */ |
| 1517 | static void search_update_checkin_index(void){ |
| 1518 | db_multi_exec( |
| 1519 | "INSERT INTO ftsidx(docid,title,body)" |
| 1520 | " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs" |
| 1521 | " WHERE type='c' AND NOT idxed;" |
| 1522 | ); |
| 1523 | db_multi_exec( |
| 1524 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1525 | " SELECT ftsdocs.rowid, 1, 'c', ftsdocs.rid, NULL," |
| @@ -1336,19 +1536,20 @@ | |
| 1536 | /* |
| 1537 | ** Deal with all of the unindexed 't' terms in FTSDOCS |
| 1538 | */ |
| 1539 | static void search_update_ticket_index(void){ |
| 1540 | db_multi_exec( |
| 1541 | "INSERT INTO ftsidx(docid,title,body)" |
| 1542 | " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs" |
| 1543 | " WHERE type='t' AND NOT idxed;" |
| 1544 | ); |
| 1545 | if( db_changes()==0 ) return; |
| 1546 | db_multi_exec( |
| 1547 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| 1548 | " SELECT ftsdocs.rowid, 1, 't', ftsdocs.rid, NULL," |
| 1549 | " printf('Ticket: %%s (%%s)',title('t',tkt_id,null)," |
| 1550 | " datetime(tkt_mtime))," |
| 1551 | " printf('/tktview/%%.20s',tkt_uuid)," |
| 1552 | " tkt_mtime" |
| 1553 | " FROM ftsdocs, ticket" |
| 1554 | " WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" |
| 1555 | " AND ticket.tkt_id=ftsdocs.rid" |
| @@ -1358,12 +1559,12 @@ | |
| 1559 | /* |
| 1560 | ** Deal with all of the unindexed 'w' terms in FTSDOCS |
| 1561 | */ |
| 1562 | static void search_update_wiki_index(void){ |
| 1563 | db_multi_exec( |
| 1564 | "INSERT INTO ftsidx(docid,title,body)" |
| 1565 | " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs" |
| 1566 | " WHERE type='w' AND NOT idxed;" |
| 1567 | ); |
| 1568 | if( db_changes()==0 ) return; |
| 1569 | db_multi_exec( |
| 1570 | "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" |
| @@ -1416,19 +1617,22 @@ | |
| 1617 | ** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? |
| 1618 | ** |
| 1619 | ** The "fossil fts-config" command configures the full-text search capabilities |
| 1620 | ** of the repository. Subcommands: |
| 1621 | ** |
| 1622 | ** reindex Rebuild the search index. This is a no-op if |
| 1623 | ** index search is disabled |
| 1624 | ** |
| 1625 | ** index (on|off) Turn the search index on or off |
| 1626 | ** |
| 1627 | ** enable cdtw Enable various kinds of search. c=Check-ins, |
| 1628 | ** d=Documents, t=Tickets, w=Wiki. |
| 1629 | ** |
| 1630 | ** disable cdtw Disable versious kinds of search |
| 1631 | ** |
| 1632 | ** stemmer (on|off) Turn the Porter stemmer on or off for indexed |
| 1633 | ** search. (Unindexed search is never stemmed.) |
| 1634 | ** |
| 1635 | ** The current search settings are displayed after any changes are applied. |
| 1636 | ** Run this command with no arguments to simply see the settings. |
| 1637 | */ |
| 1638 | void test_fts_cmd(void){ |
| @@ -1435,18 +1639,19 @@ | |
| 1639 | static const struct { int iCmd; const char *z; } aCmd[] = { |
| 1640 | { 1, "reindex" }, |
| 1641 | { 2, "index" }, |
| 1642 | { 3, "disable" }, |
| 1643 | { 4, "enable" }, |
| 1644 | { 5, "stemmer" }, |
| 1645 | }; |
| 1646 | static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { |
| 1647 | { "search-ckin", "check-in search:", "c" }, |
| 1648 | { "search-doc", "document search:", "d" }, |
| 1649 | { "search-tkt", "ticket search:", "t" }, |
| 1650 | { "search-wiki", "wiki search:", "w" }, |
| 1651 | }; |
| 1652 | char *zSubCmd = 0; |
| 1653 | int i, j, n; |
| 1654 | int iCmd = 0; |
| 1655 | int iAction = 0; |
| 1656 | db_find_and_open_repository(0, 0); |
| 1657 | if( g.argc>2 ){ |
| @@ -1464,11 +1669,11 @@ | |
| 1669 | return; |
| 1670 | } |
| 1671 | iCmd = aCmd[i].iCmd; |
| 1672 | } |
| 1673 | if( iCmd==1 ){ |
| 1674 | if( search_index_exists() ) iAction = 2; |
| 1675 | } |
| 1676 | if( iCmd==2 ){ |
| 1677 | if( g.argc<3 ) usage("index (on|off)"); |
| 1678 | iAction = 1 + is_truth(g.argv[3]); |
| 1679 | } |
| @@ -1475,18 +1680,23 @@ | |
| 1680 | db_begin_transaction(); |
| 1681 | |
| 1682 | /* Adjust search settings */ |
| 1683 | if( iCmd==3 || iCmd==4 ){ |
| 1684 | const char *zCtrl; |
| 1685 | if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd)); |
| 1686 | zCtrl = g.argv[3]; |
| 1687 | for(j=0; j<ArraySize(aSetng); j++){ |
| 1688 | if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){ |
| 1689 | db_set_int(aSetng[j].zSetting, iCmd-3, 0); |
| 1690 | } |
| 1691 | } |
| 1692 | } |
| 1693 | if( iCmd==5 ){ |
| 1694 | if( g.argc<4 ) usage("porter ON/OFF"); |
| 1695 | db_set_int("search-stemmer", is_truth(g.argv[3]), 0); |
| 1696 | } |
| 1697 | |
| 1698 | |
| 1699 | /* destroy or rebuild the index, if requested */ |
| 1700 | if( iAction>=1 ){ |
| 1701 | search_drop_index(); |
| 1702 | } |
| @@ -1497,14 +1707,16 @@ | |
| 1707 | /* Always show the status before ending */ |
| 1708 | for(i=0; i<ArraySize(aSetng); i++){ |
| 1709 | fossil_print("%-16s %s\n", aSetng[i].zName, |
| 1710 | db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off"); |
| 1711 | } |
| 1712 | fossil_print("%-16s %s\n", "Porter stemmer:", |
| 1713 | db_get_boolean("search-stemmer",0) ? "on" : "off"); |
| 1714 | if( search_index_exists() ){ |
| 1715 | fossil_print("%-16s enabled\n", "full-text index:"); |
| 1716 | fossil_print("%-16s %d\n", "documents:", |
| 1717 | db_int(0, "SELECT count(*) FROM ftsdocs")); |
| 1718 | }else{ |
| 1719 | fossil_print("%-16s disabled\n", "full-text index:"); |
| 1720 | } |
| 1721 | db_end_transaction(0); |
| 1722 | } |
| 1723 |
+59
-26
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -50,18 +50,20 @@ | ||
| 50 | 50 | }else{ |
| 51 | 51 | @ %h(zTitle) |
| 52 | 52 | } |
| 53 | 53 | @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr> |
| 54 | 54 | } |
| 55 | + | |
| 56 | + | |
| 55 | 57 | |
| 56 | 58 | /* |
| 57 | 59 | ** WEBPAGE: /setup |
| 58 | 60 | */ |
| 59 | 61 | void setup_page(void){ |
| 60 | 62 | login_check_credentials(); |
| 61 | 63 | if( !g.perm.Setup ){ |
| 62 | - login_needed(); | |
| 64 | + login_needed(0); | |
| 63 | 65 | } |
| 64 | 66 | |
| 65 | 67 | style_header("Server Administration"); |
| 66 | 68 | |
| 67 | 69 | /* Make sure the header contains <base href="...">. Issue a warning |
| @@ -69,10 +71,24 @@ | ||
| 69 | 71 | if( !cgi_header_contains("<base href=") ){ |
| 70 | 72 | @ <p class="generalError"><b>Configuration Error:</b> Please add |
| 71 | 73 | @ <tt><base href="$secureurl/$current_page"></tt> after |
| 72 | 74 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 73 | 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 | |
| 74 | 90 | |
| 75 | 91 | @ <table border="0" cellspacing="3"> |
| 76 | 92 | setup_menu_entry("Users", "setup_ulist", |
| 77 | 93 | "Grant privileges to individual users."); |
| 78 | 94 | setup_menu_entry("Access", "setup_access", |
| @@ -136,11 +152,11 @@ | ||
| 136 | 152 | Stmt s; |
| 137 | 153 | int prevLevel = 0; |
| 138 | 154 | |
| 139 | 155 | login_check_credentials(); |
| 140 | 156 | if( !g.perm.Admin ){ |
| 141 | - login_needed(); | |
| 157 | + login_needed(0); | |
| 142 | 158 | return; |
| 143 | 159 | } |
| 144 | 160 | |
| 145 | 161 | style_submenu_element("Add", "Add User", "setup_uedit"); |
| 146 | 162 | style_header("User List"); |
| @@ -320,11 +336,11 @@ | ||
| 320 | 336 | const char *oa[128]; |
| 321 | 337 | |
| 322 | 338 | /* Must have ADMIN privileges to access this page |
| 323 | 339 | */ |
| 324 | 340 | login_check_credentials(); |
| 325 | - if( !g.perm.Admin ){ login_needed(); return; } | |
| 341 | + if( !g.perm.Admin ){ login_needed(0); return; } | |
| 326 | 342 | |
| 327 | 343 | /* Check to see if an ADMIN user is trying to edit a SETUP account. |
| 328 | 344 | ** Don't allow that. |
| 329 | 345 | */ |
| 330 | 346 | zId = PD("id", "0"); |
| @@ -982,11 +998,12 @@ | ||
| 982 | 998 | ** WEBPAGE: setup_access |
| 983 | 999 | */ |
| 984 | 1000 | void setup_access(void){ |
| 985 | 1001 | login_check_credentials(); |
| 986 | 1002 | if( !g.perm.Setup ){ |
| 987 | - login_needed(); | |
| 1003 | + login_needed(0); | |
| 1004 | + return; | |
| 988 | 1005 | } |
| 989 | 1006 | |
| 990 | 1007 | style_header("Access Control Settings"); |
| 991 | 1008 | db_begin_transaction(); |
| 992 | 1009 | @ <form action="%s(g.zTop)/setup_access" method="post"><div> |
| @@ -1002,25 +1019,25 @@ | ||
| 1002 | 1019 | @ <hr /> |
| 1003 | 1020 | onoff_attribute("Require password for local access", |
| 1004 | 1021 | "localauth", "localauth", 0, 0); |
| 1005 | 1022 | @ <p>When enabled, the password sign-in is always required for |
| 1006 | 1023 | @ 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 | |
| 1010 | 1027 | @ "--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 | |
| 1012 | 1029 | @ the word "localauth" appears in the CGI script. |
| 1013 | 1030 | @ |
| 1014 | 1031 | @ <p>A password is always required if any one or more |
| 1015 | 1032 | @ of the following are true: |
| 1016 | 1033 | @ <ol> |
| 1017 | 1034 | @ <li> This button is checked |
| 1018 | 1035 | @ <li> The inbound TCP/IP connection is not from 127.0.0.1 |
| 1019 | 1036 | @ <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 | |
| 1022 | 1039 | @ without the "--localauth" option. |
| 1023 | 1040 | @ <li> The server is started from CGI without the "localauth" keyword |
| 1024 | 1041 | @ in the CGI script. |
| 1025 | 1042 | @ </ol> |
| 1026 | 1043 | @ |
| @@ -1187,11 +1204,12 @@ | ||
| 1187 | 1204 | const char *zPw = PD("pw", ""); |
| 1188 | 1205 | const char *zNewName = PD("newname", "New Login Group"); |
| 1189 | 1206 | |
| 1190 | 1207 | login_check_credentials(); |
| 1191 | 1208 | if( !g.perm.Setup ){ |
| 1192 | - login_needed(); | |
| 1209 | + login_needed(0); | |
| 1210 | + return; | |
| 1193 | 1211 | } |
| 1194 | 1212 | file_canonical_name(g.zRepositoryName, &fullName, 0); |
| 1195 | 1213 | zSelfRepo = fossil_strdup(blob_str(&fullName)); |
| 1196 | 1214 | blob_reset(&fullName); |
| 1197 | 1215 | if( P("join")!=0 ){ |
| @@ -1299,11 +1317,12 @@ | ||
| 1299 | 1317 | "3", "YYMMDD HH:MM", |
| 1300 | 1318 | "4", "(off)" |
| 1301 | 1319 | }; |
| 1302 | 1320 | login_check_credentials(); |
| 1303 | 1321 | if( !g.perm.Setup ){ |
| 1304 | - login_needed(); | |
| 1322 | + login_needed(0); | |
| 1323 | + return; | |
| 1305 | 1324 | } |
| 1306 | 1325 | |
| 1307 | 1326 | style_header("Timeline Display Preferences"); |
| 1308 | 1327 | db_begin_transaction(); |
| 1309 | 1328 | @ <form action="%s(g.zTop)/setup_timeline" method="post"><div> |
| @@ -1377,11 +1396,12 @@ | ||
| 1377 | 1396 | void setup_settings(void){ |
| 1378 | 1397 | Setting const *pSet; |
| 1379 | 1398 | |
| 1380 | 1399 | login_check_credentials(); |
| 1381 | 1400 | if( !g.perm.Setup ){ |
| 1382 | - login_needed(); | |
| 1401 | + login_needed(0); | |
| 1402 | + return; | |
| 1383 | 1403 | } |
| 1384 | 1404 | |
| 1385 | 1405 | (void) aCmdHelp; /* NOTE: Silence compiler warning. */ |
| 1386 | 1406 | style_header("Settings"); |
| 1387 | 1407 | if(!g.repositoryOpen){ |
| @@ -1457,11 +1477,12 @@ | ||
| 1457 | 1477 | ** WEBPAGE: setup_config |
| 1458 | 1478 | */ |
| 1459 | 1479 | void setup_config(void){ |
| 1460 | 1480 | login_check_credentials(); |
| 1461 | 1481 | if( !g.perm.Setup ){ |
| 1462 | - login_needed(); | |
| 1482 | + login_needed(0); | |
| 1483 | + return; | |
| 1463 | 1484 | } |
| 1464 | 1485 | |
| 1465 | 1486 | style_header("WWW Configuration"); |
| 1466 | 1487 | db_begin_transaction(); |
| 1467 | 1488 | @ <form action="%s(g.zTop)/setup_config" method="post"><div> |
| @@ -1535,11 +1556,12 @@ | ||
| 1535 | 1556 | ** WEBPAGE: setup_editcss |
| 1536 | 1557 | */ |
| 1537 | 1558 | void setup_editcss(void){ |
| 1538 | 1559 | login_check_credentials(); |
| 1539 | 1560 | if( !g.perm.Setup ){ |
| 1540 | - login_needed(); | |
| 1561 | + login_needed(0); | |
| 1562 | + return; | |
| 1541 | 1563 | } |
| 1542 | 1564 | db_begin_transaction(); |
| 1543 | 1565 | if( P("clear")!=0 ){ |
| 1544 | 1566 | db_multi_exec("DELETE FROM config WHERE name='css'"); |
| 1545 | 1567 | cgi_replace_parameter("css", builtin_text("skins/default/css.txt")); |
| @@ -1580,11 +1602,12 @@ | ||
| 1580 | 1602 | ** WEBPAGE: setup_header |
| 1581 | 1603 | */ |
| 1582 | 1604 | void setup_header(void){ |
| 1583 | 1605 | login_check_credentials(); |
| 1584 | 1606 | if( !g.perm.Setup ){ |
| 1585 | - login_needed(); | |
| 1607 | + login_needed(0); | |
| 1608 | + return; | |
| 1586 | 1609 | } |
| 1587 | 1610 | db_begin_transaction(); |
| 1588 | 1611 | if( P("clear")!=0 ){ |
| 1589 | 1612 | db_multi_exec("DELETE FROM config WHERE name='header'"); |
| 1590 | 1613 | cgi_replace_parameter("header", builtin_text("skins/default/header.txt")); |
| @@ -1644,11 +1667,12 @@ | ||
| 1644 | 1667 | ** WEBPAGE: setup_footer |
| 1645 | 1668 | */ |
| 1646 | 1669 | void setup_footer(void){ |
| 1647 | 1670 | login_check_credentials(); |
| 1648 | 1671 | if( !g.perm.Setup ){ |
| 1649 | - login_needed(); | |
| 1672 | + login_needed(0); | |
| 1673 | + return; | |
| 1650 | 1674 | } |
| 1651 | 1675 | db_begin_transaction(); |
| 1652 | 1676 | if( P("clear")!=0 ){ |
| 1653 | 1677 | db_multi_exec("DELETE FROM config WHERE name='footer'"); |
| 1654 | 1678 | cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt")); |
| @@ -1681,11 +1705,12 @@ | ||
| 1681 | 1705 | ** WEBPAGE: setup_modreq |
| 1682 | 1706 | */ |
| 1683 | 1707 | void setup_modreq(void){ |
| 1684 | 1708 | login_check_credentials(); |
| 1685 | 1709 | if( !g.perm.Setup ){ |
| 1686 | - login_needed(); | |
| 1710 | + login_needed(0); | |
| 1711 | + return; | |
| 1687 | 1712 | } |
| 1688 | 1713 | |
| 1689 | 1714 | style_header("Moderator For Wiki And Tickets"); |
| 1690 | 1715 | db_begin_transaction(); |
| 1691 | 1716 | @ <form action="%R/setup_modreq" method="post"><div> |
| @@ -1692,11 +1717,11 @@ | ||
| 1692 | 1717 | login_insert_csrf_secret(); |
| 1693 | 1718 | @ <hr /> |
| 1694 | 1719 | onoff_attribute("Moderate ticket changes", |
| 1695 | 1720 | "modreq-tkt", "modreq-tkt", 0, 0); |
| 1696 | 1721 | @ <p>When enabled, any change to tickets is subject to the approval |
| 1697 | - @ a ticket moderator - a user with the "q" or Mod-Tkt privilege. | |
| 1722 | + @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege. | |
| 1698 | 1723 | @ Ticket changes enter the system and are shown locally, but are not |
| 1699 | 1724 | @ synced until they are approved. The moderator has the option to |
| 1700 | 1725 | @ delete the change rather than approve it. Ticket changes made by |
| 1701 | 1726 | @ a user who has the Mod-Tkt privilege are never subject to |
| 1702 | 1727 | @ moderation. |
| @@ -1703,11 +1728,11 @@ | ||
| 1703 | 1728 | @ |
| 1704 | 1729 | @ <hr /> |
| 1705 | 1730 | onoff_attribute("Moderate wiki changes", |
| 1706 | 1731 | "modreq-wiki", "modreq-wiki", 0, 0); |
| 1707 | 1732 | @ <p>When enabled, any change to wiki is subject to the approval |
| 1708 | - @ a ticket moderator - a user with the "l" or Mod-Wiki privilege. | |
| 1733 | + @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege. | |
| 1709 | 1734 | @ Wiki changes enter the system and are shown locally, but are not |
| 1710 | 1735 | @ synced until they are approved. The moderator has the option to |
| 1711 | 1736 | @ delete the change rather than approve it. Wiki changes made by |
| 1712 | 1737 | @ a user who has the Mod-Wiki privilege are never subject to |
| 1713 | 1738 | @ moderation. |
| @@ -1725,11 +1750,12 @@ | ||
| 1725 | 1750 | ** WEBPAGE: setup_adunit |
| 1726 | 1751 | */ |
| 1727 | 1752 | void setup_adunit(void){ |
| 1728 | 1753 | login_check_credentials(); |
| 1729 | 1754 | if( !g.perm.Setup ){ |
| 1730 | - login_needed(); | |
| 1755 | + login_needed(0); | |
| 1756 | + return; | |
| 1731 | 1757 | } |
| 1732 | 1758 | db_begin_transaction(); |
| 1733 | 1759 | if( P("clear")!=0 ){ |
| 1734 | 1760 | db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); |
| 1735 | 1761 | cgi_replace_parameter("adunit",""); |
| @@ -1806,11 +1832,12 @@ | ||
| 1806 | 1832 | if( szBgImg>0 ){ |
| 1807 | 1833 | zBgMime = PD("bgim:mimetype","image/gif"); |
| 1808 | 1834 | } |
| 1809 | 1835 | login_check_credentials(); |
| 1810 | 1836 | if( !g.perm.Setup ){ |
| 1811 | - login_needed(); | |
| 1837 | + login_needed(0); | |
| 1838 | + return; | |
| 1812 | 1839 | } |
| 1813 | 1840 | db_begin_transaction(); |
| 1814 | 1841 | if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ |
| 1815 | 1842 | Blob img; |
| 1816 | 1843 | Stmt ins; |
| @@ -1945,11 +1972,12 @@ | ||
| 1945 | 1972 | void sql_page(void){ |
| 1946 | 1973 | const char *zQ = P("q"); |
| 1947 | 1974 | int go = P("go")!=0; |
| 1948 | 1975 | login_check_credentials(); |
| 1949 | 1976 | if( !g.perm.Setup ){ |
| 1950 | - login_needed(); | |
| 1977 | + login_needed(0); | |
| 1978 | + return; | |
| 1951 | 1979 | } |
| 1952 | 1980 | db_begin_transaction(); |
| 1953 | 1981 | style_header("Raw SQL Commands"); |
| 1954 | 1982 | @ <p><b>Caution:</b> There are no restrictions on the SQL that can be |
| 1955 | 1983 | @ run by this page. You can do serious and irrepairable damage to the |
| @@ -2066,11 +2094,12 @@ | ||
| 2066 | 2094 | void th1_page(void){ |
| 2067 | 2095 | const char *zQ = P("q"); |
| 2068 | 2096 | int go = P("go")!=0; |
| 2069 | 2097 | login_check_credentials(); |
| 2070 | 2098 | if( !g.perm.Setup ){ |
| 2071 | - login_needed(); | |
| 2099 | + login_needed(0); | |
| 2100 | + return; | |
| 2072 | 2101 | } |
| 2073 | 2102 | db_begin_transaction(); |
| 2074 | 2103 | style_header("Raw TH1 Commands"); |
| 2075 | 2104 | @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be |
| 2076 | 2105 | @ run by this page. If Tcl integration was enabled at compile-time and |
| @@ -2126,11 +2155,12 @@ | ||
| 2126 | 2155 | int limit; |
| 2127 | 2156 | int fLogEnabled; |
| 2128 | 2157 | int counter = 0; |
| 2129 | 2158 | login_check_credentials(); |
| 2130 | 2159 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2131 | - login_needed(); | |
| 2160 | + login_needed(0); | |
| 2161 | + return; | |
| 2132 | 2162 | } |
| 2133 | 2163 | style_header("Admin Log"); |
| 2134 | 2164 | create_admin_log_table(); |
| 2135 | 2165 | limit = atoi(PD("n","20")); |
| 2136 | 2166 | fLogEnabled = db_get_boolean("admin-log", 0); |
| @@ -2183,11 +2213,12 @@ | ||
| 2183 | 2213 | ** Configure the search engine. |
| 2184 | 2214 | */ |
| 2185 | 2215 | void page_srchsetup(){ |
| 2186 | 2216 | login_check_credentials(); |
| 2187 | 2217 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2188 | - login_needed(); | |
| 2218 | + login_needed(0); | |
| 2219 | + return; | |
| 2189 | 2220 | } |
| 2190 | 2221 | style_header("Search Configuration"); |
| 2191 | 2222 | @ <form action="%s(g.zTop)/srchsetup" method="post"><div> |
| 2192 | 2223 | login_insert_csrf_secret(); |
| 2193 | 2224 | @ <div style="text-align:center;font-weight:bold;"> |
| @@ -2235,16 +2266,18 @@ | ||
| 2235 | 2266 | search_update_index(search_restrict(SRCH_ALL)); |
| 2236 | 2267 | } |
| 2237 | 2268 | if( search_index_exists() ){ |
| 2238 | 2269 | @ <p>Currently using an SQLite FTS4 search index. This makes search |
| 2239 | 2270 | @ run faster, especially on large repositories, but takes up space.</p> |
| 2271 | + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); | |
| 2240 | 2272 | @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index"> |
| 2241 | 2273 | @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index"> |
| 2242 | 2274 | }else{ |
| 2243 | 2275 | @ <p>The SQLite FTS4 search index is disabled. All searching will be |
| 2244 | 2276 | @ a full-text scan. This usually works fine, but can be slow for |
| 2245 | 2277 | @ larger repositories.</p> |
| 2278 | + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); | |
| 2246 | 2279 | @ <p><input type="submit" name="fts1" value="Create A Full-Text Index"> |
| 2247 | 2280 | } |
| 2248 | 2281 | @ </div></form> |
| 2249 | 2282 | style_footer(); |
| 2250 | 2283 | } |
| 2251 | 2284 |
| --- 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><base href="$secureurl/$current_page"></tt> after |
| 72 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 73 | } |
| 74 | |
| 75 | @ <table border="0" cellspacing="3"> |
| 76 | setup_menu_entry("Users", "setup_ulist", |
| 77 | "Grant privileges to individual users."); |
| 78 | setup_menu_entry("Access", "setup_access", |
| @@ -136,11 +152,11 @@ | |
| 136 | Stmt s; |
| 137 | int prevLevel = 0; |
| 138 | |
| 139 | login_check_credentials(); |
| 140 | if( !g.perm.Admin ){ |
| 141 | login_needed(); |
| 142 | return; |
| 143 | } |
| 144 | |
| 145 | style_submenu_element("Add", "Add User", "setup_uedit"); |
| 146 | style_header("User List"); |
| @@ -320,11 +336,11 @@ | |
| 320 | const char *oa[128]; |
| 321 | |
| 322 | /* Must have ADMIN privileges to access this page |
| 323 | */ |
| 324 | login_check_credentials(); |
| 325 | if( !g.perm.Admin ){ login_needed(); return; } |
| 326 | |
| 327 | /* Check to see if an ADMIN user is trying to edit a SETUP account. |
| 328 | ** Don't allow that. |
| 329 | */ |
| 330 | zId = PD("id", "0"); |
| @@ -982,11 +998,12 @@ | |
| 982 | ** WEBPAGE: setup_access |
| 983 | */ |
| 984 | void setup_access(void){ |
| 985 | login_check_credentials(); |
| 986 | if( !g.perm.Setup ){ |
| 987 | login_needed(); |
| 988 | } |
| 989 | |
| 990 | style_header("Access Control Settings"); |
| 991 | db_begin_transaction(); |
| 992 | @ <form action="%s(g.zTop)/setup_access" method="post"><div> |
| @@ -1002,25 +1019,25 @@ | |
| 1002 | @ <hr /> |
| 1003 | onoff_attribute("Require password for local access", |
| 1004 | "localauth", "localauth", 0, 0); |
| 1005 | @ <p>When enabled, the password sign-in is always required for |
| 1006 | @ web access. When disabled, unrestricted web access from 127.0.0.1 |
| 1007 | @ is allowed for the <a href="%s(g.zTop)/help/ui">fossil ui</a> command or |
| 1008 | @ from the <a href="%s(g.zTop)/help/server">fossil server</a>, |
| 1009 | @ <a href="%s(g.zTop)/help/http">fossil http</a> commands when the |
| 1010 | @ "--localauth" command line options is used, or from the |
| 1011 | @ <a href="%s(g.zTop)/help/cgi">fossil cgi</a> if a line containing |
| 1012 | @ the word "localauth" appears in the CGI script. |
| 1013 | @ |
| 1014 | @ <p>A password is always required if any one or more |
| 1015 | @ of the following are true: |
| 1016 | @ <ol> |
| 1017 | @ <li> This button is checked |
| 1018 | @ <li> The inbound TCP/IP connection is not from 127.0.0.1 |
| 1019 | @ <li> The server is started using either of the |
| 1020 | @ <a href="%s(g.zTop)/help/server">fossil server</a> or |
| 1021 | @ <a href="%s(g.zTop)/help/server">fossil http</a> commands |
| 1022 | @ without the "--localauth" option. |
| 1023 | @ <li> The server is started from CGI without the "localauth" keyword |
| 1024 | @ in the CGI script. |
| 1025 | @ </ol> |
| 1026 | @ |
| @@ -1187,11 +1204,12 @@ | |
| 1187 | const char *zPw = PD("pw", ""); |
| 1188 | const char *zNewName = PD("newname", "New Login Group"); |
| 1189 | |
| 1190 | login_check_credentials(); |
| 1191 | if( !g.perm.Setup ){ |
| 1192 | login_needed(); |
| 1193 | } |
| 1194 | file_canonical_name(g.zRepositoryName, &fullName, 0); |
| 1195 | zSelfRepo = fossil_strdup(blob_str(&fullName)); |
| 1196 | blob_reset(&fullName); |
| 1197 | if( P("join")!=0 ){ |
| @@ -1299,11 +1317,12 @@ | |
| 1299 | "3", "YYMMDD HH:MM", |
| 1300 | "4", "(off)" |
| 1301 | }; |
| 1302 | login_check_credentials(); |
| 1303 | if( !g.perm.Setup ){ |
| 1304 | login_needed(); |
| 1305 | } |
| 1306 | |
| 1307 | style_header("Timeline Display Preferences"); |
| 1308 | db_begin_transaction(); |
| 1309 | @ <form action="%s(g.zTop)/setup_timeline" method="post"><div> |
| @@ -1377,11 +1396,12 @@ | |
| 1377 | void setup_settings(void){ |
| 1378 | Setting const *pSet; |
| 1379 | |
| 1380 | login_check_credentials(); |
| 1381 | if( !g.perm.Setup ){ |
| 1382 | login_needed(); |
| 1383 | } |
| 1384 | |
| 1385 | (void) aCmdHelp; /* NOTE: Silence compiler warning. */ |
| 1386 | style_header("Settings"); |
| 1387 | if(!g.repositoryOpen){ |
| @@ -1457,11 +1477,12 @@ | |
| 1457 | ** WEBPAGE: setup_config |
| 1458 | */ |
| 1459 | void setup_config(void){ |
| 1460 | login_check_credentials(); |
| 1461 | if( !g.perm.Setup ){ |
| 1462 | login_needed(); |
| 1463 | } |
| 1464 | |
| 1465 | style_header("WWW Configuration"); |
| 1466 | db_begin_transaction(); |
| 1467 | @ <form action="%s(g.zTop)/setup_config" method="post"><div> |
| @@ -1535,11 +1556,12 @@ | |
| 1535 | ** WEBPAGE: setup_editcss |
| 1536 | */ |
| 1537 | void setup_editcss(void){ |
| 1538 | login_check_credentials(); |
| 1539 | if( !g.perm.Setup ){ |
| 1540 | login_needed(); |
| 1541 | } |
| 1542 | db_begin_transaction(); |
| 1543 | if( P("clear")!=0 ){ |
| 1544 | db_multi_exec("DELETE FROM config WHERE name='css'"); |
| 1545 | cgi_replace_parameter("css", builtin_text("skins/default/css.txt")); |
| @@ -1580,11 +1602,12 @@ | |
| 1580 | ** WEBPAGE: setup_header |
| 1581 | */ |
| 1582 | void setup_header(void){ |
| 1583 | login_check_credentials(); |
| 1584 | if( !g.perm.Setup ){ |
| 1585 | login_needed(); |
| 1586 | } |
| 1587 | db_begin_transaction(); |
| 1588 | if( P("clear")!=0 ){ |
| 1589 | db_multi_exec("DELETE FROM config WHERE name='header'"); |
| 1590 | cgi_replace_parameter("header", builtin_text("skins/default/header.txt")); |
| @@ -1644,11 +1667,12 @@ | |
| 1644 | ** WEBPAGE: setup_footer |
| 1645 | */ |
| 1646 | void setup_footer(void){ |
| 1647 | login_check_credentials(); |
| 1648 | if( !g.perm.Setup ){ |
| 1649 | login_needed(); |
| 1650 | } |
| 1651 | db_begin_transaction(); |
| 1652 | if( P("clear")!=0 ){ |
| 1653 | db_multi_exec("DELETE FROM config WHERE name='footer'"); |
| 1654 | cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt")); |
| @@ -1681,11 +1705,12 @@ | |
| 1681 | ** WEBPAGE: setup_modreq |
| 1682 | */ |
| 1683 | void setup_modreq(void){ |
| 1684 | login_check_credentials(); |
| 1685 | if( !g.perm.Setup ){ |
| 1686 | login_needed(); |
| 1687 | } |
| 1688 | |
| 1689 | style_header("Moderator For Wiki And Tickets"); |
| 1690 | db_begin_transaction(); |
| 1691 | @ <form action="%R/setup_modreq" method="post"><div> |
| @@ -1692,11 +1717,11 @@ | |
| 1692 | login_insert_csrf_secret(); |
| 1693 | @ <hr /> |
| 1694 | onoff_attribute("Moderate ticket changes", |
| 1695 | "modreq-tkt", "modreq-tkt", 0, 0); |
| 1696 | @ <p>When enabled, any change to tickets is subject to the approval |
| 1697 | @ a ticket moderator - a user with the "q" or Mod-Tkt privilege. |
| 1698 | @ Ticket changes enter the system and are shown locally, but are not |
| 1699 | @ synced until they are approved. The moderator has the option to |
| 1700 | @ delete the change rather than approve it. Ticket changes made by |
| 1701 | @ a user who has the Mod-Tkt privilege are never subject to |
| 1702 | @ moderation. |
| @@ -1703,11 +1728,11 @@ | |
| 1703 | @ |
| 1704 | @ <hr /> |
| 1705 | onoff_attribute("Moderate wiki changes", |
| 1706 | "modreq-wiki", "modreq-wiki", 0, 0); |
| 1707 | @ <p>When enabled, any change to wiki is subject to the approval |
| 1708 | @ a ticket moderator - a user with the "l" or Mod-Wiki privilege. |
| 1709 | @ Wiki changes enter the system and are shown locally, but are not |
| 1710 | @ synced until they are approved. The moderator has the option to |
| 1711 | @ delete the change rather than approve it. Wiki changes made by |
| 1712 | @ a user who has the Mod-Wiki privilege are never subject to |
| 1713 | @ moderation. |
| @@ -1725,11 +1750,12 @@ | |
| 1725 | ** WEBPAGE: setup_adunit |
| 1726 | */ |
| 1727 | void setup_adunit(void){ |
| 1728 | login_check_credentials(); |
| 1729 | if( !g.perm.Setup ){ |
| 1730 | login_needed(); |
| 1731 | } |
| 1732 | db_begin_transaction(); |
| 1733 | if( P("clear")!=0 ){ |
| 1734 | db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); |
| 1735 | cgi_replace_parameter("adunit",""); |
| @@ -1806,11 +1832,12 @@ | |
| 1806 | if( szBgImg>0 ){ |
| 1807 | zBgMime = PD("bgim:mimetype","image/gif"); |
| 1808 | } |
| 1809 | login_check_credentials(); |
| 1810 | if( !g.perm.Setup ){ |
| 1811 | login_needed(); |
| 1812 | } |
| 1813 | db_begin_transaction(); |
| 1814 | if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ |
| 1815 | Blob img; |
| 1816 | Stmt ins; |
| @@ -1945,11 +1972,12 @@ | |
| 1945 | void sql_page(void){ |
| 1946 | const char *zQ = P("q"); |
| 1947 | int go = P("go")!=0; |
| 1948 | login_check_credentials(); |
| 1949 | if( !g.perm.Setup ){ |
| 1950 | login_needed(); |
| 1951 | } |
| 1952 | db_begin_transaction(); |
| 1953 | style_header("Raw SQL Commands"); |
| 1954 | @ <p><b>Caution:</b> There are no restrictions on the SQL that can be |
| 1955 | @ run by this page. You can do serious and irrepairable damage to the |
| @@ -2066,11 +2094,12 @@ | |
| 2066 | void th1_page(void){ |
| 2067 | const char *zQ = P("q"); |
| 2068 | int go = P("go")!=0; |
| 2069 | login_check_credentials(); |
| 2070 | if( !g.perm.Setup ){ |
| 2071 | login_needed(); |
| 2072 | } |
| 2073 | db_begin_transaction(); |
| 2074 | style_header("Raw TH1 Commands"); |
| 2075 | @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be |
| 2076 | @ run by this page. If Tcl integration was enabled at compile-time and |
| @@ -2126,11 +2155,12 @@ | |
| 2126 | int limit; |
| 2127 | int fLogEnabled; |
| 2128 | int counter = 0; |
| 2129 | login_check_credentials(); |
| 2130 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2131 | login_needed(); |
| 2132 | } |
| 2133 | style_header("Admin Log"); |
| 2134 | create_admin_log_table(); |
| 2135 | limit = atoi(PD("n","20")); |
| 2136 | fLogEnabled = db_get_boolean("admin-log", 0); |
| @@ -2183,11 +2213,12 @@ | |
| 2183 | ** Configure the search engine. |
| 2184 | */ |
| 2185 | void page_srchsetup(){ |
| 2186 | login_check_credentials(); |
| 2187 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2188 | login_needed(); |
| 2189 | } |
| 2190 | style_header("Search Configuration"); |
| 2191 | @ <form action="%s(g.zTop)/srchsetup" method="post"><div> |
| 2192 | login_insert_csrf_secret(); |
| 2193 | @ <div style="text-align:center;font-weight:bold;"> |
| @@ -2235,16 +2266,18 @@ | |
| 2235 | search_update_index(search_restrict(SRCH_ALL)); |
| 2236 | } |
| 2237 | if( search_index_exists() ){ |
| 2238 | @ <p>Currently using an SQLite FTS4 search index. This makes search |
| 2239 | @ run faster, especially on large repositories, but takes up space.</p> |
| 2240 | @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index"> |
| 2241 | @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index"> |
| 2242 | }else{ |
| 2243 | @ <p>The SQLite FTS4 search index is disabled. All searching will be |
| 2244 | @ a full-text scan. This usually works fine, but can be slow for |
| 2245 | @ larger repositories.</p> |
| 2246 | @ <p><input type="submit" name="fts1" value="Create A Full-Text Index"> |
| 2247 | } |
| 2248 | @ </div></form> |
| 2249 | style_footer(); |
| 2250 | } |
| 2251 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -50,18 +50,20 @@ | |
| 50 | }else{ |
| 51 | @ %h(zTitle) |
| 52 | } |
| 53 | @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr> |
| 54 | } |
| 55 | |
| 56 | |
| 57 | |
| 58 | /* |
| 59 | ** WEBPAGE: /setup |
| 60 | */ |
| 61 | void setup_page(void){ |
| 62 | login_check_credentials(); |
| 63 | if( !g.perm.Setup ){ |
| 64 | login_needed(0); |
| 65 | } |
| 66 | |
| 67 | style_header("Server Administration"); |
| 68 | |
| 69 | /* Make sure the header contains <base href="...">. Issue a warning |
| @@ -69,10 +71,24 @@ | |
| 71 | if( !cgi_header_contains("<base href=") ){ |
| 72 | @ <p class="generalError"><b>Configuration Error:</b> Please add |
| 73 | @ <tt><base href="$secureurl/$current_page"></tt> after |
| 74 | @ <tt><head></tt> in the <a href="setup_header">HTML header</a>!</p> |
| 75 | } |
| 76 | |
| 77 | #if !defined(_WIN32) |
| 78 | /* Check for /dev/null and /dev/urandom. We want both devices to be present, |
| 79 | ** but they are sometimes omitted (by mistake) from chroot jails. */ |
| 80 | if( access("/dev/null", R_OK|W_OK) ){ |
| 81 | @ <p class="generalError">WARNING: Device "/dev/null" is not available |
| 82 | @ for reading and writing.</p> |
| 83 | } |
| 84 | if( access("/dev/urandom", R_OK) ){ |
| 85 | @ <p class="generalError">WARNING: Device "/dev/urandom" is not available |
| 86 | @ for reading. This means that the pseudo-random number generator used |
| 87 | @ by SQLite will be poorly seeded.</p> |
| 88 | } |
| 89 | #endif |
| 90 | |
| 91 | @ <table border="0" cellspacing="3"> |
| 92 | setup_menu_entry("Users", "setup_ulist", |
| 93 | "Grant privileges to individual users."); |
| 94 | setup_menu_entry("Access", "setup_access", |
| @@ -136,11 +152,11 @@ | |
| 152 | Stmt s; |
| 153 | int prevLevel = 0; |
| 154 | |
| 155 | login_check_credentials(); |
| 156 | if( !g.perm.Admin ){ |
| 157 | login_needed(0); |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | style_submenu_element("Add", "Add User", "setup_uedit"); |
| 162 | style_header("User List"); |
| @@ -320,11 +336,11 @@ | |
| 336 | const char *oa[128]; |
| 337 | |
| 338 | /* Must have ADMIN privileges to access this page |
| 339 | */ |
| 340 | login_check_credentials(); |
| 341 | if( !g.perm.Admin ){ login_needed(0); return; } |
| 342 | |
| 343 | /* Check to see if an ADMIN user is trying to edit a SETUP account. |
| 344 | ** Don't allow that. |
| 345 | */ |
| 346 | zId = PD("id", "0"); |
| @@ -982,11 +998,12 @@ | |
| 998 | ** WEBPAGE: setup_access |
| 999 | */ |
| 1000 | void setup_access(void){ |
| 1001 | login_check_credentials(); |
| 1002 | if( !g.perm.Setup ){ |
| 1003 | login_needed(0); |
| 1004 | return; |
| 1005 | } |
| 1006 | |
| 1007 | style_header("Access Control Settings"); |
| 1008 | db_begin_transaction(); |
| 1009 | @ <form action="%s(g.zTop)/setup_access" method="post"><div> |
| @@ -1002,25 +1019,25 @@ | |
| 1019 | @ <hr /> |
| 1020 | onoff_attribute("Require password for local access", |
| 1021 | "localauth", "localauth", 0, 0); |
| 1022 | @ <p>When enabled, the password sign-in is always required for |
| 1023 | @ web access. When disabled, unrestricted web access from 127.0.0.1 |
| 1024 | @ is allowed for the <a href="%R/help/ui">fossil ui</a> command or |
| 1025 | @ from the <a href="%R/help/server">fossil server</a>, |
| 1026 | @ <a href="%R/help/http">fossil http</a> commands when the |
| 1027 | @ "--localauth" command line options is used, or from the |
| 1028 | @ <a href="%R/help/cgi">fossil cgi</a> if a line containing |
| 1029 | @ the word "localauth" appears in the CGI script. |
| 1030 | @ |
| 1031 | @ <p>A password is always required if any one or more |
| 1032 | @ of the following are true: |
| 1033 | @ <ol> |
| 1034 | @ <li> This button is checked |
| 1035 | @ <li> The inbound TCP/IP connection is not from 127.0.0.1 |
| 1036 | @ <li> The server is started using either of the |
| 1037 | @ <a href="%R/help/server">fossil server</a> or |
| 1038 | @ <a href="%R/help/server">fossil http</a> commands |
| 1039 | @ without the "--localauth" option. |
| 1040 | @ <li> The server is started from CGI without the "localauth" keyword |
| 1041 | @ in the CGI script. |
| 1042 | @ </ol> |
| 1043 | @ |
| @@ -1187,11 +1204,12 @@ | |
| 1204 | const char *zPw = PD("pw", ""); |
| 1205 | const char *zNewName = PD("newname", "New Login Group"); |
| 1206 | |
| 1207 | login_check_credentials(); |
| 1208 | if( !g.perm.Setup ){ |
| 1209 | login_needed(0); |
| 1210 | return; |
| 1211 | } |
| 1212 | file_canonical_name(g.zRepositoryName, &fullName, 0); |
| 1213 | zSelfRepo = fossil_strdup(blob_str(&fullName)); |
| 1214 | blob_reset(&fullName); |
| 1215 | if( P("join")!=0 ){ |
| @@ -1299,11 +1317,12 @@ | |
| 1317 | "3", "YYMMDD HH:MM", |
| 1318 | "4", "(off)" |
| 1319 | }; |
| 1320 | login_check_credentials(); |
| 1321 | if( !g.perm.Setup ){ |
| 1322 | login_needed(0); |
| 1323 | return; |
| 1324 | } |
| 1325 | |
| 1326 | style_header("Timeline Display Preferences"); |
| 1327 | db_begin_transaction(); |
| 1328 | @ <form action="%s(g.zTop)/setup_timeline" method="post"><div> |
| @@ -1377,11 +1396,12 @@ | |
| 1396 | void setup_settings(void){ |
| 1397 | Setting const *pSet; |
| 1398 | |
| 1399 | login_check_credentials(); |
| 1400 | if( !g.perm.Setup ){ |
| 1401 | login_needed(0); |
| 1402 | return; |
| 1403 | } |
| 1404 | |
| 1405 | (void) aCmdHelp; /* NOTE: Silence compiler warning. */ |
| 1406 | style_header("Settings"); |
| 1407 | if(!g.repositoryOpen){ |
| @@ -1457,11 +1477,12 @@ | |
| 1477 | ** WEBPAGE: setup_config |
| 1478 | */ |
| 1479 | void setup_config(void){ |
| 1480 | login_check_credentials(); |
| 1481 | if( !g.perm.Setup ){ |
| 1482 | login_needed(0); |
| 1483 | return; |
| 1484 | } |
| 1485 | |
| 1486 | style_header("WWW Configuration"); |
| 1487 | db_begin_transaction(); |
| 1488 | @ <form action="%s(g.zTop)/setup_config" method="post"><div> |
| @@ -1535,11 +1556,12 @@ | |
| 1556 | ** WEBPAGE: setup_editcss |
| 1557 | */ |
| 1558 | void setup_editcss(void){ |
| 1559 | login_check_credentials(); |
| 1560 | if( !g.perm.Setup ){ |
| 1561 | login_needed(0); |
| 1562 | return; |
| 1563 | } |
| 1564 | db_begin_transaction(); |
| 1565 | if( P("clear")!=0 ){ |
| 1566 | db_multi_exec("DELETE FROM config WHERE name='css'"); |
| 1567 | cgi_replace_parameter("css", builtin_text("skins/default/css.txt")); |
| @@ -1580,11 +1602,12 @@ | |
| 1602 | ** WEBPAGE: setup_header |
| 1603 | */ |
| 1604 | void setup_header(void){ |
| 1605 | login_check_credentials(); |
| 1606 | if( !g.perm.Setup ){ |
| 1607 | login_needed(0); |
| 1608 | return; |
| 1609 | } |
| 1610 | db_begin_transaction(); |
| 1611 | if( P("clear")!=0 ){ |
| 1612 | db_multi_exec("DELETE FROM config WHERE name='header'"); |
| 1613 | cgi_replace_parameter("header", builtin_text("skins/default/header.txt")); |
| @@ -1644,11 +1667,12 @@ | |
| 1667 | ** WEBPAGE: setup_footer |
| 1668 | */ |
| 1669 | void setup_footer(void){ |
| 1670 | login_check_credentials(); |
| 1671 | if( !g.perm.Setup ){ |
| 1672 | login_needed(0); |
| 1673 | return; |
| 1674 | } |
| 1675 | db_begin_transaction(); |
| 1676 | if( P("clear")!=0 ){ |
| 1677 | db_multi_exec("DELETE FROM config WHERE name='footer'"); |
| 1678 | cgi_replace_parameter("footer", builtin_text("skins/default/footer.txt")); |
| @@ -1681,11 +1705,12 @@ | |
| 1705 | ** WEBPAGE: setup_modreq |
| 1706 | */ |
| 1707 | void setup_modreq(void){ |
| 1708 | login_check_credentials(); |
| 1709 | if( !g.perm.Setup ){ |
| 1710 | login_needed(0); |
| 1711 | return; |
| 1712 | } |
| 1713 | |
| 1714 | style_header("Moderator For Wiki And Tickets"); |
| 1715 | db_begin_transaction(); |
| 1716 | @ <form action="%R/setup_modreq" method="post"><div> |
| @@ -1692,11 +1717,11 @@ | |
| 1717 | login_insert_csrf_secret(); |
| 1718 | @ <hr /> |
| 1719 | onoff_attribute("Moderate ticket changes", |
| 1720 | "modreq-tkt", "modreq-tkt", 0, 0); |
| 1721 | @ <p>When enabled, any change to tickets is subject to the approval |
| 1722 | @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege. |
| 1723 | @ Ticket changes enter the system and are shown locally, but are not |
| 1724 | @ synced until they are approved. The moderator has the option to |
| 1725 | @ delete the change rather than approve it. Ticket changes made by |
| 1726 | @ a user who has the Mod-Tkt privilege are never subject to |
| 1727 | @ moderation. |
| @@ -1703,11 +1728,11 @@ | |
| 1728 | @ |
| 1729 | @ <hr /> |
| 1730 | onoff_attribute("Moderate wiki changes", |
| 1731 | "modreq-wiki", "modreq-wiki", 0, 0); |
| 1732 | @ <p>When enabled, any change to wiki is subject to the approval |
| 1733 | @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege. |
| 1734 | @ Wiki changes enter the system and are shown locally, but are not |
| 1735 | @ synced until they are approved. The moderator has the option to |
| 1736 | @ delete the change rather than approve it. Wiki changes made by |
| 1737 | @ a user who has the Mod-Wiki privilege are never subject to |
| 1738 | @ moderation. |
| @@ -1725,11 +1750,12 @@ | |
| 1750 | ** WEBPAGE: setup_adunit |
| 1751 | */ |
| 1752 | void setup_adunit(void){ |
| 1753 | login_check_credentials(); |
| 1754 | if( !g.perm.Setup ){ |
| 1755 | login_needed(0); |
| 1756 | return; |
| 1757 | } |
| 1758 | db_begin_transaction(); |
| 1759 | if( P("clear")!=0 ){ |
| 1760 | db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); |
| 1761 | cgi_replace_parameter("adunit",""); |
| @@ -1806,11 +1832,12 @@ | |
| 1832 | if( szBgImg>0 ){ |
| 1833 | zBgMime = PD("bgim:mimetype","image/gif"); |
| 1834 | } |
| 1835 | login_check_credentials(); |
| 1836 | if( !g.perm.Setup ){ |
| 1837 | login_needed(0); |
| 1838 | return; |
| 1839 | } |
| 1840 | db_begin_transaction(); |
| 1841 | if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ |
| 1842 | Blob img; |
| 1843 | Stmt ins; |
| @@ -1945,11 +1972,12 @@ | |
| 1972 | void sql_page(void){ |
| 1973 | const char *zQ = P("q"); |
| 1974 | int go = P("go")!=0; |
| 1975 | login_check_credentials(); |
| 1976 | if( !g.perm.Setup ){ |
| 1977 | login_needed(0); |
| 1978 | return; |
| 1979 | } |
| 1980 | db_begin_transaction(); |
| 1981 | style_header("Raw SQL Commands"); |
| 1982 | @ <p><b>Caution:</b> There are no restrictions on the SQL that can be |
| 1983 | @ run by this page. You can do serious and irrepairable damage to the |
| @@ -2066,11 +2094,12 @@ | |
| 2094 | void th1_page(void){ |
| 2095 | const char *zQ = P("q"); |
| 2096 | int go = P("go")!=0; |
| 2097 | login_check_credentials(); |
| 2098 | if( !g.perm.Setup ){ |
| 2099 | login_needed(0); |
| 2100 | return; |
| 2101 | } |
| 2102 | db_begin_transaction(); |
| 2103 | style_header("Raw TH1 Commands"); |
| 2104 | @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be |
| 2105 | @ run by this page. If Tcl integration was enabled at compile-time and |
| @@ -2126,11 +2155,12 @@ | |
| 2155 | int limit; |
| 2156 | int fLogEnabled; |
| 2157 | int counter = 0; |
| 2158 | login_check_credentials(); |
| 2159 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2160 | login_needed(0); |
| 2161 | return; |
| 2162 | } |
| 2163 | style_header("Admin Log"); |
| 2164 | create_admin_log_table(); |
| 2165 | limit = atoi(PD("n","20")); |
| 2166 | fLogEnabled = db_get_boolean("admin-log", 0); |
| @@ -2183,11 +2213,12 @@ | |
| 2213 | ** Configure the search engine. |
| 2214 | */ |
| 2215 | void page_srchsetup(){ |
| 2216 | login_check_credentials(); |
| 2217 | if( !g.perm.Setup && !g.perm.Admin ){ |
| 2218 | login_needed(0); |
| 2219 | return; |
| 2220 | } |
| 2221 | style_header("Search Configuration"); |
| 2222 | @ <form action="%s(g.zTop)/srchsetup" method="post"><div> |
| 2223 | login_insert_csrf_secret(); |
| 2224 | @ <div style="text-align:center;font-weight:bold;"> |
| @@ -2235,16 +2266,18 @@ | |
| 2266 | search_update_index(search_restrict(SRCH_ALL)); |
| 2267 | } |
| 2268 | if( search_index_exists() ){ |
| 2269 | @ <p>Currently using an SQLite FTS4 search index. This makes search |
| 2270 | @ run faster, especially on large repositories, but takes up space.</p> |
| 2271 | onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); |
| 2272 | @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index"> |
| 2273 | @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index"> |
| 2274 | }else{ |
| 2275 | @ <p>The SQLite FTS4 search index is disabled. All searching will be |
| 2276 | @ a full-text scan. This usually works fine, but can be slow for |
| 2277 | @ larger repositories.</p> |
| 2278 | onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); |
| 2279 | @ <p><input type="submit" name="fts1" value="Create A Full-Text Index"> |
| 2280 | } |
| 2281 | @ </div></form> |
| 2282 | style_footer(); |
| 2283 | } |
| 2284 |
+120
-5
| --- src/shell.c | ||
| +++ src/shell.c | ||
| @@ -1742,10 +1742,11 @@ | ||
| 1742 | 1742 | static char zHelp[] = |
| 1743 | 1743 | ".backup ?DB? FILE Backup DB (default \"main\") to FILE\n" |
| 1744 | 1744 | ".bail on|off Stop after hitting an error. Default OFF\n" |
| 1745 | 1745 | ".clone NEWDB Clone data into NEWDB from the existing database\n" |
| 1746 | 1746 | ".databases List names and files of attached databases\n" |
| 1747 | + ".dbinfo ?DB? Show status information about the database\n" | |
| 1747 | 1748 | ".dump ?TABLE? ... Dump the database in an SQL text format\n" |
| 1748 | 1749 | " If TABLE specified, only dump tables matching\n" |
| 1749 | 1750 | " LIKE pattern TABLE.\n" |
| 1750 | 1751 | ".echo on|off Turn command echo on or off\n" |
| 1751 | 1752 | ".eqp on|off Enable or disable automatic EXPLAIN QUERY PLAN\n" |
| @@ -1754,12 +1755,12 @@ | ||
| 1754 | 1755 | " With no args, it turns EXPLAIN on.\n" |
| 1755 | 1756 | ".fullschema Show schema and the content of sqlite_stat tables\n" |
| 1756 | 1757 | ".headers on|off Turn display of headers on or off\n" |
| 1757 | 1758 | ".help Show this message\n" |
| 1758 | 1759 | ".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" | |
| 1761 | 1762 | " matching LIKE pattern TABLE.\n" |
| 1762 | 1763 | #ifdef SQLITE_ENABLE_IOTRACE |
| 1763 | 1764 | ".iotrace FILE Enable I/O diagnostic logging to FILE\n" |
| 1764 | 1765 | #endif |
| 1765 | 1766 | #ifndef SQLITE_OMIT_LOAD_EXTENSION |
| @@ -2434,10 +2435,119 @@ | ||
| 2434 | 2435 | output_file_close(p->out); |
| 2435 | 2436 | } |
| 2436 | 2437 | p->outfile[0] = 0; |
| 2437 | 2438 | p->out = stdout; |
| 2438 | 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 | + | |
| 2439 | 2549 | |
| 2440 | 2550 | /* |
| 2441 | 2551 | ** If an input line begins with "." then invoke this routine to |
| 2442 | 2552 | ** process that line. |
| 2443 | 2553 | ** |
| @@ -2576,10 +2686,14 @@ | ||
| 2576 | 2686 | fprintf(stderr,"Error: %s\n", zErrMsg); |
| 2577 | 2687 | sqlite3_free(zErrMsg); |
| 2578 | 2688 | rc = 1; |
| 2579 | 2689 | } |
| 2580 | 2690 | }else |
| 2691 | + | |
| 2692 | + if( c=='d' && strncmp(azArg[0], "dbinfo", n)==0 ){ | |
| 2693 | + rc = shell_dbinfo_command(p, nArg, azArg); | |
| 2694 | + }else | |
| 2581 | 2695 | |
| 2582 | 2696 | if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ |
| 2583 | 2697 | open_db(p, 0); |
| 2584 | 2698 | /* When playing back a "dump", the content might appear in an order |
| 2585 | 2699 | ** which causes immediate foreign key constraints to be violated. |
| @@ -2916,11 +3030,11 @@ | ||
| 2916 | 3030 | sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); |
| 2917 | 3031 | if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){ |
| 2918 | 3032 | fprintf(stderr, "%s:%d: expected %d columns but found %d - " |
| 2919 | 3033 | "filling the rest with NULL\n", |
| 2920 | 3034 | sCtx.zFile, startLine, nCol, i+1); |
| 2921 | - i++; | |
| 3035 | + i += 2; | |
| 2922 | 3036 | while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; } |
| 2923 | 3037 | } |
| 2924 | 3038 | } |
| 2925 | 3039 | if( sCtx.cTerm==sCtx.cColSep ){ |
| 2926 | 3040 | do{ |
| @@ -2945,11 +3059,12 @@ | ||
| 2945 | 3059 | sqlite3_free(sCtx.z); |
| 2946 | 3060 | sqlite3_finalize(pStmt); |
| 2947 | 3061 | if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0); |
| 2948 | 3062 | }else |
| 2949 | 3063 | |
| 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) ){ | |
| 2951 | 3066 | ShellState data; |
| 2952 | 3067 | char *zErrMsg = 0; |
| 2953 | 3068 | open_db(p, 0); |
| 2954 | 3069 | memcpy(&data, p, sizeof(data)); |
| 2955 | 3070 | data.showHeader = 0; |
| @@ -2975,11 +3090,11 @@ | ||
| 2975 | 3090 | "ORDER BY 1", |
| 2976 | 3091 | callback, &data, &zErrMsg |
| 2977 | 3092 | ); |
| 2978 | 3093 | zShellStatic = 0; |
| 2979 | 3094 | }else{ |
| 2980 | - fprintf(stderr, "Usage: .indices ?LIKE-PATTERN?\n"); | |
| 3095 | + fprintf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); | |
| 2981 | 3096 | rc = 1; |
| 2982 | 3097 | goto meta_command_exit; |
| 2983 | 3098 | } |
| 2984 | 3099 | if( zErrMsg ){ |
| 2985 | 3100 | fprintf(stderr,"Error: %s\n", zErrMsg); |
| 2986 | 3101 |
| --- 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 @@ | ||
| 49 | 49 | int numRows = 3; |
| 50 | 50 | char *zCanonical = 0; |
| 51 | 51 | |
| 52 | 52 | login_check_credentials(); |
| 53 | 53 | if( !g.perm.Admin ){ |
| 54 | - login_needed(); | |
| 54 | + login_needed(0); | |
| 55 | + return; | |
| 55 | 56 | } |
| 56 | 57 | if( P("rebuild") ){ |
| 57 | 58 | db_close(1); |
| 58 | 59 | db_open_repository(g.zRepositoryName); |
| 59 | 60 | db_begin_transaction(); |
| @@ -108,11 +109,11 @@ | ||
| 108 | 109 | p += UUID_SIZE+1; |
| 109 | 110 | } |
| 110 | 111 | if( allExist ){ |
| 111 | 112 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 112 | 113 | 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 /> | |
| 114 | 115 | } |
| 115 | 116 | @ are no longer being shunned.</p> |
| 116 | 117 | }else{ |
| 117 | 118 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 118 | 119 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| @@ -146,11 +147,11 @@ | ||
| 146 | 147 | admin_log("Shunned %Q", p); |
| 147 | 148 | p += UUID_SIZE+1; |
| 148 | 149 | } |
| 149 | 150 | @ <p class="shunned">Artifact(s)<br /> |
| 150 | 151 | 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 /> | |
| 152 | 153 | } |
| 153 | 154 | @ have been shunned. They will no longer be pushed. |
| 154 | 155 | @ They will be removed from the repository the next time the repository |
| 155 | 156 | @ is rebuilt using the <b>fossil rebuild</b> command-line</p> |
| 156 | 157 | } |
| @@ -248,11 +249,11 @@ | ||
| 248 | 249 | while( db_step(&q)==SQLITE_ROW ){ |
| 249 | 250 | const char *zUuid = db_column_text(&q, 0); |
| 250 | 251 | int stillExists = db_column_int(&q, 1); |
| 251 | 252 | cnt++; |
| 252 | 253 | 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 /> | |
| 254 | 255 | }else{ |
| 255 | 256 | @ <b>%s(zUuid)</b><br /> |
| 256 | 257 | } |
| 257 | 258 | } |
| 258 | 259 | if( cnt==0 ){ |
| @@ -301,11 +302,12 @@ | ||
| 301 | 302 | int cnt; |
| 302 | 303 | Stmt q; |
| 303 | 304 | |
| 304 | 305 | login_check_credentials(); |
| 305 | 306 | if( !g.perm.Admin ){ |
| 306 | - login_needed(); | |
| 307 | + login_needed(0); | |
| 308 | + return; | |
| 307 | 309 | } |
| 308 | 310 | style_header("Artifact Receipts"); |
| 309 | 311 | if( showAll ){ |
| 310 | 312 | ofst = 0; |
| 311 | 313 | }else{ |
| @@ -381,11 +383,12 @@ | ||
| 381 | 383 | int rcvid = atoi(PD("rcvid","0")); |
| 382 | 384 | Stmt q; |
| 383 | 385 | |
| 384 | 386 | login_check_credentials(); |
| 385 | 387 | if( !g.perm.Admin ){ |
| 386 | - login_needed(); | |
| 388 | + login_needed(0); | |
| 389 | + return; | |
| 387 | 390 | } |
| 388 | 391 | style_header("Artifact Receipt %d", rcvid); |
| 389 | 392 | if( db_exists( |
| 390 | 393 | "SELECT 1 FROM blob WHERE rcvid=%d AND" |
| 391 | 394 | " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) |
| @@ -436,13 +439,13 @@ | ||
| 436 | 439 | while( db_step(&q)==SQLITE_ROW ){ |
| 437 | 440 | const char *zUuid = db_column_text(&q, 1); |
| 438 | 441 | int size = db_column_int(&q, 2); |
| 439 | 442 | const char *zDesc = db_column_text(&q, 3); |
| 440 | 443 | 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> | |
| 442 | 445 | @ %h(zDesc) (size: %d(size))<br /> |
| 443 | 446 | } |
| 444 | 447 | @ </td></tr> |
| 445 | 448 | @ </table> |
| 446 | 449 | db_finalize(&q); |
| 447 | 450 | style_footer(); |
| 448 | 451 | } |
| 449 | 452 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -49,11 +49,12 @@ | |
| 49 | int numRows = 3; |
| 50 | char *zCanonical = 0; |
| 51 | |
| 52 | login_check_credentials(); |
| 53 | if( !g.perm.Admin ){ |
| 54 | login_needed(); |
| 55 | } |
| 56 | if( P("rebuild") ){ |
| 57 | db_close(1); |
| 58 | db_open_repository(g.zRepositoryName); |
| 59 | db_begin_transaction(); |
| @@ -108,11 +109,11 @@ | |
| 108 | p += UUID_SIZE+1; |
| 109 | } |
| 110 | if( allExist ){ |
| 111 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 112 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| 113 | @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br /> |
| 114 | } |
| 115 | @ are no longer being shunned.</p> |
| 116 | }else{ |
| 117 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 118 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| @@ -146,11 +147,11 @@ | |
| 146 | admin_log("Shunned %Q", p); |
| 147 | p += UUID_SIZE+1; |
| 148 | } |
| 149 | @ <p class="shunned">Artifact(s)<br /> |
| 150 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| 151 | @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br /> |
| 152 | } |
| 153 | @ have been shunned. They will no longer be pushed. |
| 154 | @ They will be removed from the repository the next time the repository |
| 155 | @ is rebuilt using the <b>fossil rebuild</b> command-line</p> |
| 156 | } |
| @@ -248,11 +249,11 @@ | |
| 248 | while( db_step(&q)==SQLITE_ROW ){ |
| 249 | const char *zUuid = db_column_text(&q, 0); |
| 250 | int stillExists = db_column_int(&q, 1); |
| 251 | cnt++; |
| 252 | if( stillExists ){ |
| 253 | @ <b><a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a></b><br /> |
| 254 | }else{ |
| 255 | @ <b>%s(zUuid)</b><br /> |
| 256 | } |
| 257 | } |
| 258 | if( cnt==0 ){ |
| @@ -301,11 +302,12 @@ | |
| 301 | int cnt; |
| 302 | Stmt q; |
| 303 | |
| 304 | login_check_credentials(); |
| 305 | if( !g.perm.Admin ){ |
| 306 | login_needed(); |
| 307 | } |
| 308 | style_header("Artifact Receipts"); |
| 309 | if( showAll ){ |
| 310 | ofst = 0; |
| 311 | }else{ |
| @@ -381,11 +383,12 @@ | |
| 381 | int rcvid = atoi(PD("rcvid","0")); |
| 382 | Stmt q; |
| 383 | |
| 384 | login_check_credentials(); |
| 385 | if( !g.perm.Admin ){ |
| 386 | login_needed(); |
| 387 | } |
| 388 | style_header("Artifact Receipt %d", rcvid); |
| 389 | if( db_exists( |
| 390 | "SELECT 1 FROM blob WHERE rcvid=%d AND" |
| 391 | " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) |
| @@ -436,13 +439,13 @@ | |
| 436 | while( db_step(&q)==SQLITE_ROW ){ |
| 437 | const char *zUuid = db_column_text(&q, 1); |
| 438 | int size = db_column_int(&q, 2); |
| 439 | const char *zDesc = db_column_text(&q, 3); |
| 440 | if( zDesc==0 ) zDesc = ""; |
| 441 | @ <a href="%s(g.zTop)/info/%s(zUuid)">%s(zUuid)</a> |
| 442 | @ %h(zDesc) (size: %d(size))<br /> |
| 443 | } |
| 444 | @ </td></tr> |
| 445 | @ </table> |
| 446 | db_finalize(&q); |
| 447 | style_footer(); |
| 448 | } |
| 449 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -49,11 +49,12 @@ | |
| 49 | int numRows = 3; |
| 50 | char *zCanonical = 0; |
| 51 | |
| 52 | login_check_credentials(); |
| 53 | if( !g.perm.Admin ){ |
| 54 | login_needed(0); |
| 55 | return; |
| 56 | } |
| 57 | if( P("rebuild") ){ |
| 58 | db_close(1); |
| 59 | db_open_repository(g.zRepositoryName); |
| 60 | db_begin_transaction(); |
| @@ -108,11 +109,11 @@ | |
| 109 | p += UUID_SIZE+1; |
| 110 | } |
| 111 | if( allExist ){ |
| 112 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 113 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| 114 | @ <a href="%R/artifact/%s(p)">%s(p)</a><br /> |
| 115 | } |
| 116 | @ are no longer being shunned.</p> |
| 117 | }else{ |
| 118 | @ <p class="noMoreShun">Artifact(s)<br /> |
| 119 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| @@ -146,11 +147,11 @@ | |
| 147 | admin_log("Shunned %Q", p); |
| 148 | p += UUID_SIZE+1; |
| 149 | } |
| 150 | @ <p class="shunned">Artifact(s)<br /> |
| 151 | for( p = zUuid ; *p ; p += UUID_SIZE+1 ){ |
| 152 | @ <a href="%R/artifact/%s(p)">%s(p)</a><br /> |
| 153 | } |
| 154 | @ have been shunned. They will no longer be pushed. |
| 155 | @ They will be removed from the repository the next time the repository |
| 156 | @ is rebuilt using the <b>fossil rebuild</b> command-line</p> |
| 157 | } |
| @@ -248,11 +249,11 @@ | |
| 249 | while( db_step(&q)==SQLITE_ROW ){ |
| 250 | const char *zUuid = db_column_text(&q, 0); |
| 251 | int stillExists = db_column_int(&q, 1); |
| 252 | cnt++; |
| 253 | if( stillExists ){ |
| 254 | @ <b><a href="%R/artifact/%s(zUuid)">%s(zUuid)</a></b><br /> |
| 255 | }else{ |
| 256 | @ <b>%s(zUuid)</b><br /> |
| 257 | } |
| 258 | } |
| 259 | if( cnt==0 ){ |
| @@ -301,11 +302,12 @@ | |
| 302 | int cnt; |
| 303 | Stmt q; |
| 304 | |
| 305 | login_check_credentials(); |
| 306 | if( !g.perm.Admin ){ |
| 307 | login_needed(0); |
| 308 | return; |
| 309 | } |
| 310 | style_header("Artifact Receipts"); |
| 311 | if( showAll ){ |
| 312 | ofst = 0; |
| 313 | }else{ |
| @@ -381,11 +383,12 @@ | |
| 383 | int rcvid = atoi(PD("rcvid","0")); |
| 384 | Stmt q; |
| 385 | |
| 386 | login_check_credentials(); |
| 387 | if( !g.perm.Admin ){ |
| 388 | login_needed(0); |
| 389 | return; |
| 390 | } |
| 391 | style_header("Artifact Receipt %d", rcvid); |
| 392 | if( db_exists( |
| 393 | "SELECT 1 FROM blob WHERE rcvid=%d AND" |
| 394 | " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) |
| @@ -436,13 +439,13 @@ | |
| 439 | while( db_step(&q)==SQLITE_ROW ){ |
| 440 | const char *zUuid = db_column_text(&q, 1); |
| 441 | int size = db_column_int(&q, 2); |
| 442 | const char *zDesc = db_column_text(&q, 3); |
| 443 | if( zDesc==0 ) zDesc = ""; |
| 444 | @ <a href="%R/info/%s(zUuid)">%s(zUuid)</a> |
| 445 | @ %h(zDesc) (size: %d(size))<br /> |
| 446 | } |
| 447 | @ </td></tr> |
| 448 | @ </table> |
| 449 | db_finalize(&q); |
| 450 | style_footer(); |
| 451 | } |
| 452 |
+24
-12
| --- src/sitemap.c | ||
| +++ src/sitemap.c | ||
| @@ -34,62 +34,74 @@ | ||
| 34 | 34 | @ The following links are just a few of the many web-pages available for |
| 35 | 35 | @ this Fossil repository: |
| 36 | 36 | @ </p> |
| 37 | 37 | @ |
| 38 | 38 | @ <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> | |
| 40 | 43 | @ <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> | |
| 43 | 47 | @ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li> |
| 44 | 48 | @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li> |
| 45 | 49 | @ </ul> |
| 46 | 50 | @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li> |
| 47 | 51 | @ <ul> |
| 48 | - @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li> | |
| 52 | + @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 | |
| 53 | + @ checkins</a></li> | |
| 49 | 54 | @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name |
| 50 | 55 | @ changes</a></li> |
| 51 | 56 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 52 | 57 | @ </ul> |
| 53 | - @ <li>Branches and Tags</a> | |
| 58 | + @ <li>%z(href("%R/brlist"))Branches</a></li> | |
| 54 | 59 | @ <ul> |
| 55 | - @ <li>%z(href("%R/brlist"))Branches</a></li> | |
| 56 | 60 | @ <li>%z(href("%R/leaves"))Leaf Checkins</a></li> |
| 57 | 61 | @ <li>%z(href("%R/taglist"))List of Tags</a></li> |
| 58 | 62 | @ </ul> |
| 59 | 63 | @ </li> |
| 60 | - @ <li>%z(href("%R/wiki"))Wiki</a> | |
| 64 | + @ <li>%z(href("%R/wikihelp"))Wiki</a> | |
| 61 | 65 | @ <ul> |
| 66 | + @ <li>%z(href("%R/wikisrch"))Wiki Search</a></li> | |
| 62 | 67 | @ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li> |
| 63 | 68 | @ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li> |
| 64 | 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> | |
| 65 | 72 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 66 | 73 | @ </ul> |
| 67 | 74 | @ </li> |
| 68 | 75 | @ <li>%z(href("%R/reportlist"))Tickets</a> |
| 69 | 76 | @ <ul> |
| 77 | + @ <li>%z(href("%R/tktsrch"))Ticket Search</a></li> | |
| 70 | 78 | @ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li> |
| 71 | 79 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 72 | 80 | @ </ul> |
| 73 | 81 | @ </li> |
| 82 | + @ <li>%z(href("%R/search"))Full-Text Search</a></li> | |
| 74 | 83 | @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li> |
| 75 | - @ <li>Repository Status | |
| 84 | + @ <li>%z(href("%R/stat"))Repository Status</a> | |
| 76 | 85 | @ <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> | |
| 79 | 90 | @ <li>%z(href("%R/bloblist"))List of Artifacts</a></li> |
| 80 | 91 | @ </ul></li> |
| 81 | 92 | @ <li>On-line Documentation |
| 82 | 93 | @ <ul> |
| 83 | 94 | @ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li> |
| 84 | 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> | |
| 85 | 97 | @ </ul></li> |
| 86 | - @ <li>Administration Pages | |
| 98 | + @ <li>%z(href("%R/setup"))Administration Pages</a> | |
| 87 | 99 | @ <ul> |
| 88 | - @ <li>%z(href("%R/setup"))Configuration and Setup Menu</a></li> | |
| 89 | 100 | @ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li> |
| 90 | 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> | |
| 91 | 103 | @ </ul></li> |
| 92 | 104 | @ <li>Test Pages |
| 93 | 105 | @ <ul> |
| 94 | 106 | @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li> |
| 95 | 107 | @ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li> |
| 96 | 108 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -34,62 +34,74 @@ | |
| 34 | @ The following links are just a few of the many web-pages available for |
| 35 | @ this Fossil repository: |
| 36 | @ </p> |
| 37 | @ |
| 38 | @ <ul> |
| 39 | @ <li>%z(href("%R/home"))Home Page</a></li> |
| 40 | @ <li>%z(href("%R/tree"))File Browser</a></li> |
| 41 | @ <ul> |
| 42 | @ <li>%z(href("%R/tree?ci=trunk"))Tree-view, Trunk Checkin</a></li> |
| 43 | @ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li> |
| 44 | @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li> |
| 45 | @ </ul> |
| 46 | @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li> |
| 47 | @ <ul> |
| 48 | @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 checkins</a></li> |
| 49 | @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name |
| 50 | @ changes</a></li> |
| 51 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 52 | @ </ul> |
| 53 | @ <li>Branches and Tags</a> |
| 54 | @ <ul> |
| 55 | @ <li>%z(href("%R/brlist"))Branches</a></li> |
| 56 | @ <li>%z(href("%R/leaves"))Leaf Checkins</a></li> |
| 57 | @ <li>%z(href("%R/taglist"))List of Tags</a></li> |
| 58 | @ </ul> |
| 59 | @ </li> |
| 60 | @ <li>%z(href("%R/wiki"))Wiki</a> |
| 61 | @ <ul> |
| 62 | @ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li> |
| 63 | @ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li> |
| 64 | @ <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li> |
| 65 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 66 | @ </ul> |
| 67 | @ </li> |
| 68 | @ <li>%z(href("%R/reportlist"))Tickets</a> |
| 69 | @ <ul> |
| 70 | @ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li> |
| 71 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 72 | @ </ul> |
| 73 | @ </li> |
| 74 | @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li> |
| 75 | @ <li>Repository Status |
| 76 | @ <ul> |
| 77 | @ <li>%z(href("%R/stat"))Status Summary</a></li> |
| 78 | @ <li>%z(href("%R/urllist"))List of URLs used to access this repository</a></li> |
| 79 | @ <li>%z(href("%R/bloblist"))List of Artifacts</a></li> |
| 80 | @ </ul></li> |
| 81 | @ <li>On-line Documentation |
| 82 | @ <ul> |
| 83 | @ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li> |
| 84 | @ <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li> |
| 85 | @ </ul></li> |
| 86 | @ <li>Administration Pages |
| 87 | @ <ul> |
| 88 | @ <li>%z(href("%R/setup"))Configuration and Setup Menu</a></li> |
| 89 | @ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li> |
| 90 | @ <li>%z(href("%R/admin_log"))Admin log</a></li> |
| 91 | @ </ul></li> |
| 92 | @ <li>Test Pages |
| 93 | @ <ul> |
| 94 | @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li> |
| 95 | @ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li> |
| 96 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -34,62 +34,74 @@ | |
| 34 | @ The following links are just a few of the many web-pages available for |
| 35 | @ this Fossil repository: |
| 36 | @ </p> |
| 37 | @ |
| 38 | @ <ul> |
| 39 | @ <li>%z(href("%R/home"))Home Page</a> |
| 40 | @ <ul> |
| 41 | @ <li>%z(href("%R/docsrc"))Search Project Documentation</a></li> |
| 42 | @ </ul></li> |
| 43 | @ <li>%z(href("%R/tree"))File Browser</a></li> |
| 44 | @ <ul> |
| 45 | @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view, |
| 46 | @ Trunk Checkin</a></li> |
| 47 | @ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li> |
| 48 | @ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li> |
| 49 | @ </ul> |
| 50 | @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li> |
| 51 | @ <ul> |
| 52 | @ <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10 |
| 53 | @ checkins</a></li> |
| 54 | @ <li>%z(href("%R/timeline?n=all&namechng"))All checkins with file name |
| 55 | @ changes</a></li> |
| 56 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 57 | @ </ul> |
| 58 | @ <li>%z(href("%R/brlist"))Branches</a></li> |
| 59 | @ <ul> |
| 60 | @ <li>%z(href("%R/leaves"))Leaf Checkins</a></li> |
| 61 | @ <li>%z(href("%R/taglist"))List of Tags</a></li> |
| 62 | @ </ul> |
| 63 | @ </li> |
| 64 | @ <li>%z(href("%R/wikihelp"))Wiki</a> |
| 65 | @ <ul> |
| 66 | @ <li>%z(href("%R/wikisrch"))Wiki Search</a></li> |
| 67 | @ <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li> |
| 68 | @ <li>%z(href("%R/timeline?y=w"))Recent activity</a></li> |
| 69 | @ <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li> |
| 70 | @ <li>%z(href("%R/md_rules"))Markdown Formatting Rules</a></li> |
| 71 | @ <li>%z(href("%R/wiki?name=Sandbox"))Sandbox</a></li> |
| 72 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 73 | @ </ul> |
| 74 | @ </li> |
| 75 | @ <li>%z(href("%R/reportlist"))Tickets</a> |
| 76 | @ <ul> |
| 77 | @ <li>%z(href("%R/tktsrch"))Ticket Search</a></li> |
| 78 | @ <li>%z(href("%R/timeline?y=t"))Recent activity</a></li> |
| 79 | @ <li>%z(href("%R/attachlist"))List of Attachments</a></li> |
| 80 | @ </ul> |
| 81 | @ </li> |
| 82 | @ <li>%z(href("%R/search"))Full-Text Search</a></li> |
| 83 | @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li> |
| 84 | @ <li>%z(href("%R/stat"))Repository Status</a> |
| 85 | @ <ul> |
| 86 | @ <li>%z(href("%R/hash-collisions"))Collisions on SHA1 hash |
| 87 | @ prefixes</a></li> |
| 88 | @ <li>%z(href("%R/urllist"))List of URLs used to access |
| 89 | @ this repository</a></li> |
| 90 | @ <li>%z(href("%R/bloblist"))List of Artifacts</a></li> |
| 91 | @ </ul></li> |
| 92 | @ <li>On-line Documentation |
| 93 | @ <ul> |
| 94 | @ <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li> |
| 95 | @ <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li> |
| 96 | @ <li>%z(href("%R/mimetype_list"))Filename suffix to mimetype map</a></li> |
| 97 | @ </ul></li> |
| 98 | @ <li>%z(href("%R/setup"))Administration Pages</a> |
| 99 | @ <ul> |
| 100 | @ <li>%z(href("%R/modreq"))Pending Moderation Requests</a></li> |
| 101 | @ <li>%z(href("%R/admin_log"))Admin log</a></li> |
| 102 | @ <li>%z(href("%R/cachestat"))Status of the web-page cache</a></li> |
| 103 | @ </ul></li> |
| 104 | @ <li>Test Pages |
| 105 | @ <ul> |
| 106 | @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li> |
| 107 | @ <li>%z(href("%R/test_timewarps"))List of "Timewarp" Checkins</a></li> |
| 108 |
+100
-9
| --- src/skins.c | ||
| +++ src/skins.c | ||
| @@ -37,21 +37,100 @@ | ||
| 37 | 37 | ** 4. Make an entry in the following array for the new skin. |
| 38 | 38 | */ |
| 39 | 39 | static struct BuiltinSkin { |
| 40 | 40 | const char *zDesc; /* Description of this skin */ |
| 41 | 41 | const char *zLabel; /* The directory under skins/ holding this skin */ |
| 42 | + int whiteForeground; /* True if this skin uses a light-colored foreground */ | |
| 42 | 43 | char *zSQL; /* Filled in at run-time with SQL to insert this skin */ |
| 43 | 44 | } 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 }, | |
| 52 | 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 | +} | |
| 53 | 132 | |
| 54 | 133 | /* |
| 55 | 134 | ** For a skin named zSkinName, compute the name of the CONFIG table |
| 56 | 135 | ** entry where that skin is stored and return it. |
| 57 | 136 | ** |
| @@ -210,11 +289,12 @@ | ||
| 210 | 289 | Stmt q; |
| 211 | 290 | int seenCurrent = 0; |
| 212 | 291 | |
| 213 | 292 | login_check_credentials(); |
| 214 | 293 | if( !g.perm.Setup ){ |
| 215 | - login_needed(); | |
| 294 | + login_needed(0); | |
| 295 | + return; | |
| 216 | 296 | } |
| 217 | 297 | db_begin_transaction(); |
| 218 | 298 | zCurrent = getSkin(0); |
| 219 | 299 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 220 | 300 | aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel); |
| @@ -288,10 +368,18 @@ | ||
| 288 | 368 | @ <a href="setup_editcss">CSS</a>, |
| 289 | 369 | @ <a href="setup_header">Header</a>, and |
| 290 | 370 | @ <a href="setup_footer">Footer</a> that determines the look and feel |
| 291 | 371 | @ of the web interface.</p> |
| 292 | 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 | + } | |
| 293 | 381 | @ <h2>Available Skins:</h2> |
| 294 | 382 | @ <table border="0"> |
| 295 | 383 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 296 | 384 | z = aBuiltinSkin[i].zDesc; |
| 297 | 385 | @ <tr><td>%d(i+1).<td>%h(z)<td> <td> |
| @@ -300,10 +388,13 @@ | ||
| 300 | 388 | seenCurrent = 1; |
| 301 | 389 | }else{ |
| 302 | 390 | @ <form action="%s(g.zTop)/setup_skin" method="post"> |
| 303 | 391 | @ <input type="hidden" name="sn" value="%h(z)" /> |
| 304 | 392 | @ <input type="submit" name="load" value="Install" /> |
| 393 | + if( pAltSkin==&aBuiltinSkin[i] ){ | |
| 394 | + @ (Current override) | |
| 395 | + } | |
| 305 | 396 | @ </form> |
| 306 | 397 | } |
| 307 | 398 | @ </tr> |
| 308 | 399 | } |
| 309 | 400 | db_prepare(&q, |
| 310 | 401 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -37,21 +37,100 @@ | |
| 37 | ** 4. Make an entry in the following array for the new skin. |
| 38 | */ |
| 39 | static struct BuiltinSkin { |
| 40 | const char *zDesc; /* Description of this skin */ |
| 41 | const char *zLabel; /* The directory under skins/ holding this skin */ |
| 42 | char *zSQL; /* Filled in at run-time with SQL to insert this skin */ |
| 43 | } aBuiltinSkin[] = { |
| 44 | { "Default", "default", 0 }, |
| 45 | { "Plain Gray, No Logo", "plain_gray", 0 }, |
| 46 | { "Khaki, No Logo", "khaki", 0 }, |
| 47 | { "Black & White, Menu on Left", "black_and_white", 0 }, |
| 48 | { "Shadow boxes & Rounded Corners", "rounded1", 0 }, |
| 49 | { "Enhanced Default", "enhanced1", 0 }, |
| 50 | { "San Francisco Modern", "etienne1", 0 }, |
| 51 | { "Eagle", "eagle", 0 }, |
| 52 | }; |
| 53 | |
| 54 | /* |
| 55 | ** For a skin named zSkinName, compute the name of the CONFIG table |
| 56 | ** entry where that skin is stored and return it. |
| 57 | ** |
| @@ -210,11 +289,12 @@ | |
| 210 | Stmt q; |
| 211 | int seenCurrent = 0; |
| 212 | |
| 213 | login_check_credentials(); |
| 214 | if( !g.perm.Setup ){ |
| 215 | login_needed(); |
| 216 | } |
| 217 | db_begin_transaction(); |
| 218 | zCurrent = getSkin(0); |
| 219 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 220 | aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel); |
| @@ -288,10 +368,18 @@ | |
| 288 | @ <a href="setup_editcss">CSS</a>, |
| 289 | @ <a href="setup_header">Header</a>, and |
| 290 | @ <a href="setup_footer">Footer</a> that determines the look and feel |
| 291 | @ of the web interface.</p> |
| 292 | @ |
| 293 | @ <h2>Available Skins:</h2> |
| 294 | @ <table border="0"> |
| 295 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 296 | z = aBuiltinSkin[i].zDesc; |
| 297 | @ <tr><td>%d(i+1).<td>%h(z)<td> <td> |
| @@ -300,10 +388,13 @@ | |
| 300 | seenCurrent = 1; |
| 301 | }else{ |
| 302 | @ <form action="%s(g.zTop)/setup_skin" method="post"> |
| 303 | @ <input type="hidden" name="sn" value="%h(z)" /> |
| 304 | @ <input type="submit" name="load" value="Install" /> |
| 305 | @ </form> |
| 306 | } |
| 307 | @ </tr> |
| 308 | } |
| 309 | db_prepare(&q, |
| 310 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -37,21 +37,100 @@ | |
| 37 | ** 4. Make an entry in the following array for the new skin. |
| 38 | */ |
| 39 | static struct BuiltinSkin { |
| 40 | const char *zDesc; /* Description of this skin */ |
| 41 | const char *zLabel; /* The directory under skins/ holding this skin */ |
| 42 | int whiteForeground; /* True if this skin uses a light-colored foreground */ |
| 43 | char *zSQL; /* Filled in at run-time with SQL to insert this skin */ |
| 44 | } aBuiltinSkin[] = { |
| 45 | { "Default", "default", 0, 0 }, |
| 46 | { "Plain Gray, No Logo", "plain_gray", 0, 0 }, |
| 47 | { "Khaki, No Logo", "khaki", 0, 0 }, |
| 48 | { "Black & White, Menu on Left", "black_and_white", 0, 0 }, |
| 49 | { "Shadow boxes & Rounded Corners", "rounded1", 0, 0 }, |
| 50 | { "Enhanced Default", "enhanced1", 0, 0 }, |
| 51 | { "San Francisco Modern", "etienne1", 0, 0 }, |
| 52 | { "Eagle", "eagle", 1, 0 }, |
| 53 | }; |
| 54 | |
| 55 | /* |
| 56 | ** Alternative skins can be specified in the CGI script or by options |
| 57 | ** on the "http", "ui", and "server" commands. The alternative skin |
| 58 | ** name must be one of the aBuiltinSkin[].zLabel names. If there is |
| 59 | ** a match, that alternative is used. |
| 60 | ** |
| 61 | ** The following static variable holds the name of the alternative skin, |
| 62 | ** or NULL if the skin should be as configured. |
| 63 | */ |
| 64 | static struct BuiltinSkin *pAltSkin = 0; |
| 65 | |
| 66 | /* |
| 67 | ** Invoke this routine to set the alternative skin. Return NULL if the |
| 68 | ** alternative was successfully installed. Return a string listing all |
| 69 | ** available skins if zName does not match an available skin. Memory |
| 70 | ** for the returned string comes from fossil_malloc() and should be freed |
| 71 | ** by the caller. |
| 72 | */ |
| 73 | char *skin_use_alternative(const char *zName){ |
| 74 | int i; |
| 75 | Blob err; |
| 76 | for(i=0; i<ArraySize(aBuiltinSkin); i++){ |
| 77 | if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){ |
| 78 | pAltSkin = &aBuiltinSkin[i]; |
| 79 | return 0; |
| 80 | } |
| 81 | } |
| 82 | blob_init(&err, aBuiltinSkin[0].zLabel, -1); |
| 83 | for(i=1; i<ArraySize(aBuiltinSkin); i++){ |
| 84 | blob_append(&err, " ", 1); |
| 85 | blob_append(&err, aBuiltinSkin[i].zLabel, -1); |
| 86 | } |
| 87 | return blob_str(&err); |
| 88 | } |
| 89 | |
| 90 | /* |
| 91 | ** Look for the --skin command-line option and process it. Or |
| 92 | ** call fossil_fatal() if an unknown skin is specified. |
| 93 | */ |
| 94 | void skin_override(void){ |
| 95 | const char *zSkin = find_option("skin",0,1); |
| 96 | if( zSkin ){ |
| 97 | char *zErr = skin_use_alternative(zSkin); |
| 98 | if( zErr ) fossil_fatal("available skins: %s", zErr); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /* |
| 103 | ** The following routines return the various components of the skin |
| 104 | ** that should be used for the current run. |
| 105 | */ |
| 106 | const char *skin_get(const char *zWhat){ |
| 107 | const char *zOut; |
| 108 | char *z; |
| 109 | if( pAltSkin ){ |
| 110 | z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat); |
| 111 | zOut = builtin_text(z); |
| 112 | fossil_free(z); |
| 113 | }else{ |
| 114 | zOut = db_get(zWhat, 0); |
| 115 | if( zOut==0 ){ |
| 116 | z = mprintf("skins/default/%s.txt", zWhat); |
| 117 | zOut = builtin_text(z); |
| 118 | fossil_free(z); |
| 119 | } |
| 120 | } |
| 121 | return zOut; |
| 122 | } |
| 123 | int skin_white_foreground(void){ |
| 124 | int rc; |
| 125 | if( pAltSkin ){ |
| 126 | rc = pAltSkin->whiteForeground; |
| 127 | }else{ |
| 128 | rc = db_get_boolean("white-foreground",0); |
| 129 | } |
| 130 | return rc; |
| 131 | } |
| 132 | |
| 133 | /* |
| 134 | ** For a skin named zSkinName, compute the name of the CONFIG table |
| 135 | ** entry where that skin is stored and return it. |
| 136 | ** |
| @@ -210,11 +289,12 @@ | |
| 289 | Stmt q; |
| 290 | int seenCurrent = 0; |
| 291 | |
| 292 | login_check_credentials(); |
| 293 | if( !g.perm.Setup ){ |
| 294 | login_needed(0); |
| 295 | return; |
| 296 | } |
| 297 | db_begin_transaction(); |
| 298 | zCurrent = getSkin(0); |
| 299 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 300 | aBuiltinSkin[i].zSQL = getSkin(aBuiltinSkin[i].zLabel); |
| @@ -288,10 +368,18 @@ | |
| 368 | @ <a href="setup_editcss">CSS</a>, |
| 369 | @ <a href="setup_header">Header</a>, and |
| 370 | @ <a href="setup_footer">Footer</a> that determines the look and feel |
| 371 | @ of the web interface.</p> |
| 372 | @ |
| 373 | if( pAltSkin ){ |
| 374 | @ <p class="generalError"> |
| 375 | @ This page is generated using an skin override named |
| 376 | @ "%h(pAltSkin->zLabel)". You can change the skin configuration |
| 377 | @ below, but the changes will not take effect until the Fossil server |
| 378 | @ is restarted without the override.</p> |
| 379 | @ |
| 380 | } |
| 381 | @ <h2>Available Skins:</h2> |
| 382 | @ <table border="0"> |
| 383 | for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){ |
| 384 | z = aBuiltinSkin[i].zDesc; |
| 385 | @ <tr><td>%d(i+1).<td>%h(z)<td> <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 @@ | ||
| 52 | 52 | int brief; |
| 53 | 53 | char zBuf[100]; |
| 54 | 54 | const char *p; |
| 55 | 55 | |
| 56 | 56 | login_check_credentials(); |
| 57 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 57 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 58 | 58 | brief = P("brief")!=0; |
| 59 | 59 | style_header("Repository Statistics"); |
| 60 | 60 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 61 | 61 | if( g.perm.Admin ){ |
| 62 | 62 | style_submenu_element("URLs", "URLs and Checkouts", "urllist"); |
| 63 | 63 | style_submenu_element("Schema", "Repository Schema", "repo_schema"); |
| 64 | 64 | style_submenu_element("Web-Cache", "Web-Cache Stats", "cachestat"); |
| 65 | 65 | } |
| 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"); | |
| 67 | 68 | @ <table class="label-value"> |
| 68 | 69 | @ <tr><th>Repository Size:</th><td> |
| 69 | 70 | fsize = file_size(g.zRepositoryName); |
| 70 | 71 | bigSizeName(sizeof(zBuf), zBuf, fsize); |
| 71 | 72 | @ %s(zBuf) |
| @@ -291,11 +292,11 @@ | ||
| 291 | 292 | */ |
| 292 | 293 | void urllist_page(void){ |
| 293 | 294 | Stmt q; |
| 294 | 295 | int cnt; |
| 295 | 296 | login_check_credentials(); |
| 296 | - if( !g.perm.Admin ){ login_needed(); return; } | |
| 297 | + if( !g.perm.Admin ){ login_needed(0); return; } | |
| 297 | 298 | |
| 298 | 299 | style_header("URLs and Checkouts"); |
| 299 | 300 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 300 | 301 | style_submenu_element("Stat", "Repository Stats", "stat"); |
| 301 | 302 | style_submenu_element("Schema", "Repository Schema", "repo_schema"); |
| @@ -338,11 +339,11 @@ | ||
| 338 | 339 | ** Show the repository schema |
| 339 | 340 | */ |
| 340 | 341 | void repo_schema_page(void){ |
| 341 | 342 | Stmt q; |
| 342 | 343 | login_check_credentials(); |
| 343 | - if( !g.perm.Admin ){ login_needed(); return; } | |
| 344 | + if( !g.perm.Admin ){ login_needed(0); return; } | |
| 344 | 345 | |
| 345 | 346 | style_header("Repository Schema"); |
| 346 | 347 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 347 | 348 | style_submenu_element("Stat", "Repository Stats", "stat"); |
| 348 | 349 | style_submenu_element("URLs", "URLs and Checkouts", "urllist"); |
| 349 | 350 |
| --- 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 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 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 @@ | ||
| 129 | 129 | assert( statsReportType && "Must call stats_report_init_view() first." ); |
| 130 | 130 | switch( statsReportType ){ |
| 131 | 131 | case 'c': |
| 132 | 132 | return "checkins"; |
| 133 | 133 | case 'e': |
| 134 | - return "events"; | |
| 134 | + return "technotes"; | |
| 135 | 135 | case 'w': |
| 136 | 136 | return "wiki changes"; |
| 137 | 137 | case 't': |
| 138 | 138 | return "ticket changes"; |
| 139 | 139 | case 'g': |
| @@ -171,13 +171,13 @@ | ||
| 171 | 171 | cgi_printf(" <strong>checkins</strong>", zTop); |
| 172 | 172 | }else{ |
| 173 | 173 | cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); |
| 174 | 174 | } |
| 175 | 175 | if('e' == statsReportType){ |
| 176 | - cgi_printf(" <strong>events</strong>", zTop); | |
| 176 | + cgi_printf(" <strong>technotes</strong>", zTop); | |
| 177 | 177 | }else{ |
| 178 | - cgi_printf(" <a href='%s&type=e'>events</a>", zTop); | |
| 178 | + cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop); | |
| 179 | 179 | } |
| 180 | 180 | if( 't' == statsReportType ){ |
| 181 | 181 | cgi_printf(" <strong>tickets</strong>", zTop); |
| 182 | 182 | }else{ |
| 183 | 183 | cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| @@ -216,13 +216,13 @@ | ||
| 216 | 216 | strlen(zTimeframe), |
| 217 | 217 | zTimeframe); |
| 218 | 218 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 219 | 219 | const char *zWeek = db_column_text(&stWeek,0); |
| 220 | 220 | const int nCount = db_column_int(&stWeek,1); |
| 221 | - cgi_printf("<a href='%s/timeline?" | |
| 221 | + cgi_printf("<a href='%R/timeline?" | |
| 222 | 222 | "yw=%t-%t&n=%d&y=%s'>%s</a>", |
| 223 | - g.zTop, yearPart, zWeek, | |
| 223 | + yearPart, zWeek, | |
| 224 | 224 | nCount, statsReportTimelineYFlag, zWeek); |
| 225 | 225 | } |
| 226 | 226 | db_finalize(&stWeek); |
| 227 | 227 | } |
| 228 | 228 | |
| @@ -328,13 +328,13 @@ | ||
| 328 | 328 | nEventTotal += nCount; |
| 329 | 329 | nEventsPerYear += nCount; |
| 330 | 330 | @<tr class='row%d(rowClass)'> |
| 331 | 331 | @ <td> |
| 332 | 332 | if(includeMonth){ |
| 333 | - cgi_printf("<a href='%s/timeline?" | |
| 333 | + cgi_printf("<a href='%R/timeline?" | |
| 334 | 334 | "ym=%t&n=%d&y=%s", |
| 335 | - g.zTop, zTimeframe, nCount, | |
| 335 | + zTimeframe, nCount, | |
| 336 | 336 | statsReportTimelineYFlag ); |
| 337 | 337 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | 338 | that was the only user who caused events. |
| 339 | 339 | */ |
| 340 | 340 | if( zUserName && *zUserName ){ |
| @@ -672,12 +672,12 @@ | ||
| 672 | 672 | ? (int)(100 * nCount / nMaxEvents) |
| 673 | 673 | : 0; |
| 674 | 674 | if(!nSize) nSize = 1; |
| 675 | 675 | total += nCount; |
| 676 | 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, | |
| 677 | + cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", | |
| 678 | + zYear, zWeek, nCount, | |
| 679 | 679 | statsReportTimelineYFlag); |
| 680 | 680 | if(zUserName && *zUserName){ |
| 681 | 681 | cgi_printf("&u=%t",zUserName); |
| 682 | 682 | } |
| 683 | 683 | cgi_printf("'>%s</a></td>",zWeek); |
| @@ -728,11 +728,11 @@ | ||
| 728 | 728 | HQuery url; /* URL for various branch links */ |
| 729 | 729 | const char *zView = P("view"); /* Which view/report to show. */ |
| 730 | 730 | const char *zUserName = P("user"); |
| 731 | 731 | |
| 732 | 732 | login_check_credentials(); |
| 733 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 733 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 734 | 734 | if(!zUserName) zUserName = P("u"); |
| 735 | 735 | url_initialize(&url, "reports"); |
| 736 | 736 | if(zUserName && *zUserName){ |
| 737 | 737 | url_add_parameter(&url,"user", zUserName); |
| 738 | 738 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 739 | 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 "events"; |
| 135 | case 'w': |
| 136 | return "wiki changes"; |
| 137 | case 't': |
| 138 | return "ticket changes"; |
| 139 | case 'g': |
| @@ -171,13 +171,13 @@ | |
| 171 | cgi_printf(" <strong>checkins</strong>", zTop); |
| 172 | }else{ |
| 173 | cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); |
| 174 | } |
| 175 | if('e' == statsReportType){ |
| 176 | cgi_printf(" <strong>events</strong>", zTop); |
| 177 | }else{ |
| 178 | cgi_printf(" <a href='%s&type=e'>events</a>", zTop); |
| 179 | } |
| 180 | if( 't' == statsReportType ){ |
| 181 | cgi_printf(" <strong>tickets</strong>", zTop); |
| 182 | }else{ |
| 183 | cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| @@ -216,13 +216,13 @@ | |
| 216 | strlen(zTimeframe), |
| 217 | zTimeframe); |
| 218 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 219 | const char *zWeek = db_column_text(&stWeek,0); |
| 220 | const int nCount = db_column_int(&stWeek,1); |
| 221 | cgi_printf("<a href='%s/timeline?" |
| 222 | "yw=%t-%t&n=%d&y=%s'>%s</a>", |
| 223 | g.zTop, yearPart, zWeek, |
| 224 | nCount, statsReportTimelineYFlag, zWeek); |
| 225 | } |
| 226 | db_finalize(&stWeek); |
| 227 | } |
| 228 | |
| @@ -328,13 +328,13 @@ | |
| 328 | nEventTotal += nCount; |
| 329 | nEventsPerYear += nCount; |
| 330 | @<tr class='row%d(rowClass)'> |
| 331 | @ <td> |
| 332 | if(includeMonth){ |
| 333 | cgi_printf("<a href='%s/timeline?" |
| 334 | "ym=%t&n=%d&y=%s", |
| 335 | g.zTop, zTimeframe, nCount, |
| 336 | statsReportTimelineYFlag ); |
| 337 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | that was the only user who caused events. |
| 339 | */ |
| 340 | if( zUserName && *zUserName ){ |
| @@ -672,12 +672,12 @@ | |
| 672 | ? (int)(100 * nCount / nMaxEvents) |
| 673 | : 0; |
| 674 | if(!nSize) nSize = 1; |
| 675 | total += nCount; |
| 676 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 677 | cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s", |
| 678 | g.zTop, zYear, zWeek, nCount, |
| 679 | statsReportTimelineYFlag); |
| 680 | if(zUserName && *zUserName){ |
| 681 | cgi_printf("&u=%t",zUserName); |
| 682 | } |
| 683 | cgi_printf("'>%s</a></td>",zWeek); |
| @@ -728,11 +728,11 @@ | |
| 728 | HQuery url; /* URL for various branch links */ |
| 729 | const char *zView = P("view"); /* Which view/report to show. */ |
| 730 | const char *zUserName = P("user"); |
| 731 | |
| 732 | login_check_credentials(); |
| 733 | if( !g.perm.Read ){ login_needed(); return; } |
| 734 | if(!zUserName) zUserName = P("u"); |
| 735 | url_initialize(&url, "reports"); |
| 736 | if(zUserName && *zUserName){ |
| 737 | url_add_parameter(&url,"user", zUserName); |
| 738 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 739 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -129,11 +129,11 @@ | |
| 129 | assert( statsReportType && "Must call stats_report_init_view() first." ); |
| 130 | switch( statsReportType ){ |
| 131 | case 'c': |
| 132 | return "checkins"; |
| 133 | case 'e': |
| 134 | return "technotes"; |
| 135 | case 'w': |
| 136 | return "wiki changes"; |
| 137 | case 't': |
| 138 | return "ticket changes"; |
| 139 | case 'g': |
| @@ -171,13 +171,13 @@ | |
| 171 | cgi_printf(" <strong>checkins</strong>", zTop); |
| 172 | }else{ |
| 173 | cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); |
| 174 | } |
| 175 | if('e' == statsReportType){ |
| 176 | cgi_printf(" <strong>technotes</strong>", zTop); |
| 177 | }else{ |
| 178 | cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop); |
| 179 | } |
| 180 | if( 't' == statsReportType ){ |
| 181 | cgi_printf(" <strong>tickets</strong>", zTop); |
| 182 | }else{ |
| 183 | cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| @@ -216,13 +216,13 @@ | |
| 216 | strlen(zTimeframe), |
| 217 | zTimeframe); |
| 218 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 219 | const char *zWeek = db_column_text(&stWeek,0); |
| 220 | const int nCount = db_column_int(&stWeek,1); |
| 221 | cgi_printf("<a href='%R/timeline?" |
| 222 | "yw=%t-%t&n=%d&y=%s'>%s</a>", |
| 223 | yearPart, zWeek, |
| 224 | nCount, statsReportTimelineYFlag, zWeek); |
| 225 | } |
| 226 | db_finalize(&stWeek); |
| 227 | } |
| 228 | |
| @@ -328,13 +328,13 @@ | |
| 328 | nEventTotal += nCount; |
| 329 | nEventsPerYear += nCount; |
| 330 | @<tr class='row%d(rowClass)'> |
| 331 | @ <td> |
| 332 | if(includeMonth){ |
| 333 | cgi_printf("<a href='%R/timeline?" |
| 334 | "ym=%t&n=%d&y=%s", |
| 335 | zTimeframe, nCount, |
| 336 | statsReportTimelineYFlag ); |
| 337 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | that was the only user who caused events. |
| 339 | */ |
| 340 | if( zUserName && *zUserName ){ |
| @@ -672,12 +672,12 @@ | |
| 672 | ? (int)(100 * nCount / nMaxEvents) |
| 673 | : 0; |
| 674 | if(!nSize) nSize = 1; |
| 675 | total += nCount; |
| 676 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 677 | cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 678 | zYear, zWeek, nCount, |
| 679 | statsReportTimelineYFlag); |
| 680 | if(zUserName && *zUserName){ |
| 681 | cgi_printf("&u=%t",zUserName); |
| 682 | } |
| 683 | cgi_printf("'>%s</a></td>",zWeek); |
| @@ -728,11 +728,11 @@ | |
| 728 | HQuery url; /* URL for various branch links */ |
| 729 | const char *zView = P("view"); /* Which view/report to show. */ |
| 730 | const char *zUserName = P("user"); |
| 731 | |
| 732 | login_check_credentials(); |
| 733 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 734 | if(!zUserName) zUserName = P("u"); |
| 735 | url_initialize(&url, "reports"); |
| 736 | if(zUserName && *zUserName){ |
| 737 | url_add_parameter(&url,"user", zUserName); |
| 738 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 739 |
+95
-62
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -45,20 +45,20 @@ | ||
| 45 | 45 | } aSubmenu[30]; |
| 46 | 46 | static int nSubmenu = 0; /* Number of buttons */ |
| 47 | 47 | static struct SubmenuCtrl { |
| 48 | 48 | const char *zName; /* Form query parameter */ |
| 49 | 49 | const char *zLabel; /* Label. Might be NULL for FF_MULTI */ |
| 50 | - int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */ | |
| 51 | - int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */ | |
| 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 */ | |
| 52 | 53 | const char **azChoice; /* value/display pairs for FF_MULTI */ |
| 53 | 54 | const char *zFalse; /* FF_BINARY label when false */ |
| 54 | 55 | } aSubmenuCtrl[20]; |
| 55 | 56 | static int nSubmenuCtrl = 0; |
| 56 | 57 | #define FF_ENTRY 1 |
| 57 | -#define FF_CKBOX 2 | |
| 58 | -#define FF_MULTI 3 | |
| 59 | -#define FF_BINARY 4 | |
| 58 | +#define FF_MULTI 2 | |
| 59 | +#define FF_BINARY 3 | |
| 60 | 60 | |
| 61 | 61 | /* |
| 62 | 62 | ** Remember that the header has been generated. The footer is omitted |
| 63 | 63 | ** if an error occurs before the header. |
| 64 | 64 | */ |
| @@ -246,50 +246,46 @@ | ||
| 246 | 246 | nSubmenu++; |
| 247 | 247 | } |
| 248 | 248 | void style_submenu_entry( |
| 249 | 249 | const char *zName, /* Query parameter name */ |
| 250 | 250 | const char *zLabel, /* Label before the entry box */ |
| 251 | - int iSize /* Size of the entry box */ | |
| 251 | + int iSize, /* Size of the entry box */ | |
| 252 | + int isDisabled /* True if disabled */ | |
| 252 | 253 | ){ |
| 253 | 254 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 254 | 255 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 255 | 256 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; |
| 256 | 257 | aSubmenuCtrl[nSubmenuCtrl].iSize = iSize; |
| 258 | + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; | |
| 257 | 259 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY; |
| 258 | 260 | nSubmenuCtrl++; |
| 259 | 261 | } |
| 260 | -void style_submenu_checkbox( | |
| 261 | - const char *zName, /* Query parameter name */ | |
| 262 | - const char *zLabel /* Label before the checkbox */ | |
| 263 | -){ | |
| 264 | - assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); | |
| 265 | - aSubmenuCtrl[nSubmenuCtrl].zName = zName; | |
| 266 | - aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; | |
| 267 | - aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX; | |
| 268 | - nSubmenuCtrl++; | |
| 269 | -} | |
| 270 | 262 | void style_submenu_binary( |
| 271 | 263 | const char *zName, /* Query parameter name */ |
| 272 | 264 | const char *zTrue, /* Label to show when parameter is true */ |
| 273 | - const char *zFalse /* Label to show when the parameter is false */ | |
| 265 | + const char *zFalse, /* Label to show when the parameter is false */ | |
| 266 | + int isDisabled /* True if this control is disabled */ | |
| 274 | 267 | ){ |
| 275 | 268 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 276 | 269 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 277 | 270 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue; |
| 278 | 271 | aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse; |
| 272 | + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; | |
| 279 | 273 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY; |
| 280 | 274 | nSubmenuCtrl++; |
| 281 | 275 | } |
| 282 | 276 | void style_submenu_multichoice( |
| 283 | 277 | const char *zName, /* Query parameter name */ |
| 284 | 278 | int nChoice, /* Number of options */ |
| 285 | - const char **azChoice /* value/display pairs. 2*nChoice entries */ | |
| 279 | + const char **azChoice, /* value/display pairs. 2*nChoice entries */ | |
| 280 | + int isDisabled /* True if this control is disabled */ | |
| 286 | 281 | ){ |
| 287 | 282 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 288 | 283 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 289 | 284 | aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice; |
| 290 | 285 | aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice; |
| 286 | + aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; | |
| 291 | 287 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; |
| 292 | 288 | nSubmenuCtrl++; |
| 293 | 289 | } |
| 294 | 290 | |
| 295 | 291 | |
| @@ -357,12 +353,11 @@ | ||
| 357 | 353 | ** Draw the header. |
| 358 | 354 | */ |
| 359 | 355 | void style_header(const char *zTitleFormat, ...){ |
| 360 | 356 | va_list ap; |
| 361 | 357 | char *zTitle; |
| 362 | - const char *zHeader = db_get("header", 0); | |
| 363 | - if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt"); | |
| 358 | + const char *zHeader = skin_get("header"); | |
| 364 | 359 | login_check_credentials(); |
| 365 | 360 | |
| 366 | 361 | va_start(ap, zTitleFormat); |
| 367 | 362 | zTitle = vmprintf(zTitleFormat, ap); |
| 368 | 363 | va_end(ap); |
| @@ -499,42 +494,36 @@ | ||
| 499 | 494 | } |
| 500 | 495 | } |
| 501 | 496 | if( nSubmenuCtrl>0 ){ |
| 502 | 497 | for(i=0; i<nSubmenuCtrl; i++){ |
| 503 | 498 | const char *zQPN = aSubmenuCtrl[i].zName; |
| 504 | - cgi_tag_query_parameter(zQPN); | |
| 499 | + const char *zDisabled = " disabled"; | |
| 500 | + if( !aSubmenuCtrl[i].isDisabled ){ | |
| 501 | + zDisabled = ""; | |
| 502 | + cgi_tag_query_parameter(zQPN); | |
| 503 | + } | |
| 505 | 504 | switch( aSubmenuCtrl[i].eType ){ |
| 506 | 505 | case FF_ENTRY: { |
| 507 | 506 | cgi_printf( |
| 508 | 507 | "<span class='submenuctrl'>" |
| 509 | - "%h: <input type='text' name='%s' size='%d' " | |
| 510 | - "value='%h'></span>\n", | |
| 511 | - aSubmenuCtrl[i].zLabel, | |
| 512 | - zQPN, | |
| 513 | - aSubmenuCtrl[i].iSize, | |
| 514 | - PD(zQPN,"") | |
| 515 | - ); | |
| 516 | - break; | |
| 517 | - } | |
| 518 | - case FF_CKBOX: { | |
| 519 | - cgi_printf( | |
| 520 | - "<span class='submenuctrl'>" | |
| 521 | - "%h: <input type='checkbox' name='%s'%s " | |
| 522 | - "onchange='gebi(\"f01\").submit();'></span>\n", | |
| 523 | - aSubmenuCtrl[i].zLabel, | |
| 524 | - zQPN, | |
| 525 | - PB(zQPN) ? " checked":"" | |
| 508 | + " %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 | |
| 526 | 515 | ); |
| 527 | 516 | break; |
| 528 | 517 | } |
| 529 | 518 | case FF_MULTI: { |
| 530 | 519 | int j; |
| 531 | 520 | const char *zVal = P(zQPN); |
| 532 | 521 | cgi_printf( |
| 533 | - "<select class='submenuctrl' size='1' name='%s' " | |
| 522 | + "<select class='submenuctrl' size='1' name='%s'%s " | |
| 534 | 523 | "onchange='gebi(\"f01\").submit();'>\n", |
| 535 | - zQPN | |
| 524 | + zQPN, zDisabled | |
| 536 | 525 | ); |
| 537 | 526 | for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){ |
| 538 | 527 | const char *zQPV = aSubmenuCtrl[i].azChoice[j]; |
| 539 | 528 | cgi_printf( |
| 540 | 529 | "<option value='%h'%s>%h</option>\n", |
| @@ -547,13 +536,13 @@ | ||
| 547 | 536 | break; |
| 548 | 537 | } |
| 549 | 538 | case FF_BINARY: { |
| 550 | 539 | int isTrue = PB(zQPN); |
| 551 | 540 | cgi_printf( |
| 552 | - "<select class='submenuctrl' size='1' name='%s' " | |
| 541 | + "<select class='submenuctrl' size='1' name='%s'%s " | |
| 553 | 542 | "onchange='gebi(\"f01\").submit();'>\n", |
| 554 | - zQPN | |
| 543 | + zQPN, zDisabled | |
| 555 | 544 | ); |
| 556 | 545 | cgi_printf( |
| 557 | 546 | "<option value='1'%s>%h</option>\n", |
| 558 | 547 | isTrue ? " selected":"", aSubmenuCtrl[i].zLabel |
| 559 | 548 | ); |
| @@ -602,12 +591,11 @@ | ||
| 602 | 591 | |
| 603 | 592 | /* Set the href= field on hyperlinks. Do this before the footer since |
| 604 | 593 | ** the footer will be generating </html> */ |
| 605 | 594 | style_resolve_href(); |
| 606 | 595 | |
| 607 | - zFooter = db_get("footer", 0); | |
| 608 | - if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt"); | |
| 596 | + zFooter = skin_get("footer"); | |
| 609 | 597 | if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); |
| 610 | 598 | Th_Render(zFooter); |
| 611 | 599 | if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1); |
| 612 | 600 | |
| 613 | 601 | /* Render trace log if TH1 tracing is enabled. */ |
| @@ -673,10 +661,11 @@ | ||
| 673 | 661 | @ font-size: small; |
| 674 | 662 | }, |
| 675 | 663 | { "table.timelineTable", |
| 676 | 664 | "the format for the timeline data table", |
| 677 | 665 | @ border: 0; |
| 666 | + @ border-collapse: collapse; | |
| 678 | 667 | }, |
| 679 | 668 | { "td.timelineTableCell", |
| 680 | 669 | "the format for the timeline data cells", |
| 681 | 670 | @ vertical-align: top; |
| 682 | 671 | @ text-align: left; |
| @@ -683,10 +672,21 @@ | ||
| 683 | 672 | }, |
| 684 | 673 | { "tr.timelineCurrent td.timelineTableCell", |
| 685 | 674 | "the format for the timeline data cell of the current checkout", |
| 686 | 675 | @ padding: .1em .2em; |
| 687 | 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 | 688 | }, |
| 689 | 689 | { "span.timelineLeaf", |
| 690 | 690 | "the format for the timeline leaf marks", |
| 691 | 691 | @ font-weight: bold; |
| 692 | 692 | }, |
| @@ -745,16 +745,10 @@ | ||
| 745 | 745 | { "td.browser", |
| 746 | 746 | "format for cells in the file browser", |
| 747 | 747 | @ width: 24%; |
| 748 | 748 | @ vertical-align: top; |
| 749 | 749 | }, |
| 750 | - { "ul.browser", | |
| 751 | - "format for the list in the file browser", | |
| 752 | - @ margin-left: 0.5em; | |
| 753 | - @ padding-left: 0.5em; | |
| 754 | - @ white-space: nowrap; | |
| 755 | - }, | |
| 756 | 750 | { ".filetree", |
| 757 | 751 | "tree-view file browser", |
| 758 | 752 | @ margin: 1em 0; |
| 759 | 753 | @ line-height: 1.5; |
| 760 | 754 | }, |
| @@ -808,28 +802,59 @@ | ||
| 808 | 802 | "hide lines for last-child directories", |
| 809 | 803 | @ display: none; |
| 810 | 804 | }, |
| 811 | 805 | { ".filetree a", |
| 812 | 806 | "tree-view links", |
| 813 | - @ position: relative; | |
| 814 | - @ z-index: 1; | |
| 815 | - @ display: table-cell; | |
| 816 | - @ min-height: 16px; | |
| 817 | - @ padding-left: 21px; | |
| 818 | - @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==); | |
| 819 | - @ background-position: center left; | |
| 820 | - @ background-repeat: no-repeat; | |
| 807 | + " position: relative;\n" | |
| 808 | + " z-index: 1;\n" | |
| 809 | + " display: table-cell;\n" | |
| 810 | + " min-height: 16px;\n" | |
| 811 | + " padding-left: 21px;\n" | |
| 812 | + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" | |
| 813 | + "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmg" | |
| 814 | + "OUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" | |
| 815 | + " background-position: center left;\n" | |
| 816 | + " background-repeat: no-repeat;\n" | |
| 817 | + }, | |
| 818 | + { "ul.browser", | |
| 819 | + "list of files in the 'flat-view' file browser", | |
| 820 | + @ list-style-type: none; | |
| 821 | + @ padding: 10px; | |
| 822 | + @ margin: 0px; | |
| 823 | + @ white-space: nowrap; | |
| 824 | + }, | |
| 825 | + { "ul.browser li.file", | |
| 826 | + "List element in the 'flat-view' file browser for a file", | |
| 827 | + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" | |
| 828 | + "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfm" | |
| 829 | + "gOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" | |
| 830 | + " background-repeat: no-repeat;\n" | |
| 831 | + " background-position: 0px center;\n" | |
| 832 | + " padding-left: 20px;\n" | |
| 833 | + " padding-top: 2px;\n" | |
| 834 | + }, | |
| 835 | + { "ul.browser li.dir", | |
| 836 | + "List element in the 'flat-view file browser for a directory", | |
| 837 | + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" | |
| 838 | + "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaX" | |
| 839 | + "po+jUs6b5Z/K4siDu5RPUFADs=);\n" | |
| 840 | + " background-repeat: no-repeat;\n" | |
| 841 | + " background-position: 0px center;\n" | |
| 842 | + " padding-left: 20px;\n" | |
| 843 | + " padding-top: 2px;\n" | |
| 821 | 844 | }, |
| 822 | 845 | { "div.filetreeline", |
| 823 | 846 | "line of a file tree", |
| 824 | 847 | @ display: table; |
| 825 | 848 | @ width: 100%; |
| 826 | 849 | @ white-space: nowrap; |
| 827 | 850 | }, |
| 828 | 851 | { ".filetree .dir > div.filetreeline > a", |
| 829 | 852 | "tree-view directory links", |
| 830 | - @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=); | |
| 853 | + " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" | |
| 854 | + "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXp" | |
| 855 | + "o+jUs6b5Z/K4siDu5RPUFADs=);\n" | |
| 831 | 856 | }, |
| 832 | 857 | { "div.filetreeage", |
| 833 | 858 | "Last change floating display on the right", |
| 834 | 859 | @ display: table-cell; |
| 835 | 860 | @ padding-left: 3em; |
| @@ -1340,11 +1365,11 @@ | ||
| 1340 | 1365 | void page_style_css(void){ |
| 1341 | 1366 | Blob css; |
| 1342 | 1367 | int i; |
| 1343 | 1368 | |
| 1344 | 1369 | cgi_set_content_type("text/css"); |
| 1345 | - blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1); | |
| 1370 | + blob_init(&css,skin_get("css"),-1); | |
| 1346 | 1371 | |
| 1347 | 1372 | /* add special missing definitions */ |
| 1348 | 1373 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1349 | 1374 | char *z = blob_str(&css); |
| 1350 | 1375 | if( !containsString(z, cssDefaultList[i].elementClass) ){ |
| @@ -1386,11 +1411,11 @@ | ||
| 1386 | 1411 | "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL", |
| 1387 | 1412 | }; |
| 1388 | 1413 | |
| 1389 | 1414 | login_check_credentials(); |
| 1390 | 1415 | if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ |
| 1391 | - login_needed(); | |
| 1416 | + login_needed(0); | |
| 1392 | 1417 | return; |
| 1393 | 1418 | } |
| 1394 | 1419 | for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); |
| 1395 | 1420 | style_header("Environment Test"); |
| 1396 | 1421 | showAll = atoi(PD("showall","0")); |
| @@ -1405,17 +1430,25 @@ | ||
| 1405 | 1430 | @ g.zBaseURL = %h(g.zBaseURL)<br /> |
| 1406 | 1431 | @ g.zHttpsURL = %h(g.zHttpsURL)<br /> |
| 1407 | 1432 | @ g.zTop = %h(g.zTop)<br /> |
| 1408 | 1433 | @ g.zPath = %h(g.zPath)<br /> |
| 1409 | 1434 | for(i=0, c='a'; c<='z'; c++){ |
| 1410 | - if( login_has_capability(&c, 1) ) zCap[i++] = c; | |
| 1435 | + if( login_has_capability(&c, 1, 0) ) zCap[i++] = c; | |
| 1411 | 1436 | } |
| 1412 | 1437 | zCap[i] = 0; |
| 1413 | 1438 | @ g.userUid = %d(g.userUid)<br /> |
| 1414 | 1439 | @ g.zLogin = %h(g.zLogin)<br /> |
| 1415 | 1440 | @ g.isHuman = %d(g.isHuman)<br /> |
| 1416 | 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 | + } | |
| 1417 | 1450 | @ g.zRepositoryName = %h(g.zRepositoryName)<br /> |
| 1418 | 1451 | @ load_average() = %f(load_average())<br /> |
| 1419 | 1452 | @ <hr> |
| 1420 | 1453 | P("HTTP_USER_AGENT"); |
| 1421 | 1454 | cgi_print_all(showAll); |
| 1422 | 1455 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -45,20 +45,20 @@ | |
| 45 | } aSubmenu[30]; |
| 46 | static int nSubmenu = 0; /* Number of buttons */ |
| 47 | static struct SubmenuCtrl { |
| 48 | const char *zName; /* Form query parameter */ |
| 49 | const char *zLabel; /* Label. Might be NULL for FF_MULTI */ |
| 50 | int eType; /* FF_ENTRY, FF_CKBOX, FF_MULTI */ |
| 51 | int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */ |
| 52 | const char **azChoice; /* value/display pairs for FF_MULTI */ |
| 53 | const char *zFalse; /* FF_BINARY label when false */ |
| 54 | } aSubmenuCtrl[20]; |
| 55 | static int nSubmenuCtrl = 0; |
| 56 | #define FF_ENTRY 1 |
| 57 | #define FF_CKBOX 2 |
| 58 | #define FF_MULTI 3 |
| 59 | #define FF_BINARY 4 |
| 60 | |
| 61 | /* |
| 62 | ** Remember that the header has been generated. The footer is omitted |
| 63 | ** if an error occurs before the header. |
| 64 | */ |
| @@ -246,50 +246,46 @@ | |
| 246 | nSubmenu++; |
| 247 | } |
| 248 | void style_submenu_entry( |
| 249 | const char *zName, /* Query parameter name */ |
| 250 | const char *zLabel, /* Label before the entry box */ |
| 251 | int iSize /* Size of the entry box */ |
| 252 | ){ |
| 253 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 254 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 255 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; |
| 256 | aSubmenuCtrl[nSubmenuCtrl].iSize = iSize; |
| 257 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY; |
| 258 | nSubmenuCtrl++; |
| 259 | } |
| 260 | void style_submenu_checkbox( |
| 261 | const char *zName, /* Query parameter name */ |
| 262 | const char *zLabel /* Label before the checkbox */ |
| 263 | ){ |
| 264 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 265 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 266 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; |
| 267 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_CKBOX; |
| 268 | nSubmenuCtrl++; |
| 269 | } |
| 270 | void style_submenu_binary( |
| 271 | const char *zName, /* Query parameter name */ |
| 272 | const char *zTrue, /* Label to show when parameter is true */ |
| 273 | const char *zFalse /* Label to show when the parameter is false */ |
| 274 | ){ |
| 275 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 276 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 277 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue; |
| 278 | aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse; |
| 279 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY; |
| 280 | nSubmenuCtrl++; |
| 281 | } |
| 282 | void style_submenu_multichoice( |
| 283 | const char *zName, /* Query parameter name */ |
| 284 | int nChoice, /* Number of options */ |
| 285 | const char **azChoice /* value/display pairs. 2*nChoice entries */ |
| 286 | ){ |
| 287 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 288 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 289 | aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice; |
| 290 | aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice; |
| 291 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; |
| 292 | nSubmenuCtrl++; |
| 293 | } |
| 294 | |
| 295 | |
| @@ -357,12 +353,11 @@ | |
| 357 | ** Draw the header. |
| 358 | */ |
| 359 | void style_header(const char *zTitleFormat, ...){ |
| 360 | va_list ap; |
| 361 | char *zTitle; |
| 362 | const char *zHeader = db_get("header", 0); |
| 363 | if( zHeader==0 ) zHeader = builtin_text("skins/default/header.txt"); |
| 364 | login_check_credentials(); |
| 365 | |
| 366 | va_start(ap, zTitleFormat); |
| 367 | zTitle = vmprintf(zTitleFormat, ap); |
| 368 | va_end(ap); |
| @@ -499,42 +494,36 @@ | |
| 499 | } |
| 500 | } |
| 501 | if( nSubmenuCtrl>0 ){ |
| 502 | for(i=0; i<nSubmenuCtrl; i++){ |
| 503 | const char *zQPN = aSubmenuCtrl[i].zName; |
| 504 | cgi_tag_query_parameter(zQPN); |
| 505 | switch( aSubmenuCtrl[i].eType ){ |
| 506 | case FF_ENTRY: { |
| 507 | cgi_printf( |
| 508 | "<span class='submenuctrl'>" |
| 509 | "%h: <input type='text' name='%s' size='%d' " |
| 510 | "value='%h'></span>\n", |
| 511 | aSubmenuCtrl[i].zLabel, |
| 512 | zQPN, |
| 513 | aSubmenuCtrl[i].iSize, |
| 514 | PD(zQPN,"") |
| 515 | ); |
| 516 | break; |
| 517 | } |
| 518 | case FF_CKBOX: { |
| 519 | cgi_printf( |
| 520 | "<span class='submenuctrl'>" |
| 521 | "%h: <input type='checkbox' name='%s'%s " |
| 522 | "onchange='gebi(\"f01\").submit();'></span>\n", |
| 523 | aSubmenuCtrl[i].zLabel, |
| 524 | zQPN, |
| 525 | PB(zQPN) ? " checked":"" |
| 526 | ); |
| 527 | break; |
| 528 | } |
| 529 | case FF_MULTI: { |
| 530 | int j; |
| 531 | const char *zVal = P(zQPN); |
| 532 | cgi_printf( |
| 533 | "<select class='submenuctrl' size='1' name='%s' " |
| 534 | "onchange='gebi(\"f01\").submit();'>\n", |
| 535 | zQPN |
| 536 | ); |
| 537 | for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){ |
| 538 | const char *zQPV = aSubmenuCtrl[i].azChoice[j]; |
| 539 | cgi_printf( |
| 540 | "<option value='%h'%s>%h</option>\n", |
| @@ -547,13 +536,13 @@ | |
| 547 | break; |
| 548 | } |
| 549 | case FF_BINARY: { |
| 550 | int isTrue = PB(zQPN); |
| 551 | cgi_printf( |
| 552 | "<select class='submenuctrl' size='1' name='%s' " |
| 553 | "onchange='gebi(\"f01\").submit();'>\n", |
| 554 | zQPN |
| 555 | ); |
| 556 | cgi_printf( |
| 557 | "<option value='1'%s>%h</option>\n", |
| 558 | isTrue ? " selected":"", aSubmenuCtrl[i].zLabel |
| 559 | ); |
| @@ -602,12 +591,11 @@ | |
| 602 | |
| 603 | /* Set the href= field on hyperlinks. Do this before the footer since |
| 604 | ** the footer will be generating </html> */ |
| 605 | style_resolve_href(); |
| 606 | |
| 607 | zFooter = db_get("footer", 0); |
| 608 | if( zFooter==0 ) zFooter = builtin_text("skins/default/footer.txt"); |
| 609 | if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); |
| 610 | Th_Render(zFooter); |
| 611 | if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1); |
| 612 | |
| 613 | /* Render trace log if TH1 tracing is enabled. */ |
| @@ -673,10 +661,11 @@ | |
| 673 | @ font-size: small; |
| 674 | }, |
| 675 | { "table.timelineTable", |
| 676 | "the format for the timeline data table", |
| 677 | @ border: 0; |
| 678 | }, |
| 679 | { "td.timelineTableCell", |
| 680 | "the format for the timeline data cells", |
| 681 | @ vertical-align: top; |
| 682 | @ text-align: left; |
| @@ -683,10 +672,21 @@ | |
| 683 | }, |
| 684 | { "tr.timelineCurrent td.timelineTableCell", |
| 685 | "the format for the timeline data cell of the current checkout", |
| 686 | @ padding: .1em .2em; |
| 687 | @ border: 1px dashed #446979; |
| 688 | }, |
| 689 | { "span.timelineLeaf", |
| 690 | "the format for the timeline leaf marks", |
| 691 | @ font-weight: bold; |
| 692 | }, |
| @@ -745,16 +745,10 @@ | |
| 745 | { "td.browser", |
| 746 | "format for cells in the file browser", |
| 747 | @ width: 24%; |
| 748 | @ vertical-align: top; |
| 749 | }, |
| 750 | { "ul.browser", |
| 751 | "format for the list in the file browser", |
| 752 | @ margin-left: 0.5em; |
| 753 | @ padding-left: 0.5em; |
| 754 | @ white-space: nowrap; |
| 755 | }, |
| 756 | { ".filetree", |
| 757 | "tree-view file browser", |
| 758 | @ margin: 1em 0; |
| 759 | @ line-height: 1.5; |
| 760 | }, |
| @@ -808,28 +802,59 @@ | |
| 808 | "hide lines for last-child directories", |
| 809 | @ display: none; |
| 810 | }, |
| 811 | { ".filetree a", |
| 812 | "tree-view links", |
| 813 | @ position: relative; |
| 814 | @ z-index: 1; |
| 815 | @ display: table-cell; |
| 816 | @ min-height: 16px; |
| 817 | @ padding-left: 21px; |
| 818 | @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==); |
| 819 | @ background-position: center left; |
| 820 | @ background-repeat: no-repeat; |
| 821 | }, |
| 822 | { "div.filetreeline", |
| 823 | "line of a file tree", |
| 824 | @ display: table; |
| 825 | @ width: 100%; |
| 826 | @ white-space: nowrap; |
| 827 | }, |
| 828 | { ".filetree .dir > div.filetreeline > a", |
| 829 | "tree-view directory links", |
| 830 | @ background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=); |
| 831 | }, |
| 832 | { "div.filetreeage", |
| 833 | "Last change floating display on the right", |
| 834 | @ display: table-cell; |
| 835 | @ padding-left: 3em; |
| @@ -1340,11 +1365,11 @@ | |
| 1340 | void page_style_css(void){ |
| 1341 | Blob css; |
| 1342 | int i; |
| 1343 | |
| 1344 | cgi_set_content_type("text/css"); |
| 1345 | blob_init(&css,db_get("css",(char*)builtin_text("skins/default/css.txt")),-1); |
| 1346 | |
| 1347 | /* add special missing definitions */ |
| 1348 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1349 | char *z = blob_str(&css); |
| 1350 | if( !containsString(z, cssDefaultList[i].elementClass) ){ |
| @@ -1386,11 +1411,11 @@ | |
| 1386 | "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL", |
| 1387 | }; |
| 1388 | |
| 1389 | login_check_credentials(); |
| 1390 | if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ |
| 1391 | login_needed(); |
| 1392 | return; |
| 1393 | } |
| 1394 | for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); |
| 1395 | style_header("Environment Test"); |
| 1396 | showAll = atoi(PD("showall","0")); |
| @@ -1405,17 +1430,25 @@ | |
| 1405 | @ g.zBaseURL = %h(g.zBaseURL)<br /> |
| 1406 | @ g.zHttpsURL = %h(g.zHttpsURL)<br /> |
| 1407 | @ g.zTop = %h(g.zTop)<br /> |
| 1408 | @ g.zPath = %h(g.zPath)<br /> |
| 1409 | for(i=0, c='a'; c<='z'; c++){ |
| 1410 | if( login_has_capability(&c, 1) ) zCap[i++] = c; |
| 1411 | } |
| 1412 | zCap[i] = 0; |
| 1413 | @ g.userUid = %d(g.userUid)<br /> |
| 1414 | @ g.zLogin = %h(g.zLogin)<br /> |
| 1415 | @ g.isHuman = %d(g.isHuman)<br /> |
| 1416 | @ capabilities = %s(zCap)<br /> |
| 1417 | @ g.zRepositoryName = %h(g.zRepositoryName)<br /> |
| 1418 | @ load_average() = %f(load_average())<br /> |
| 1419 | @ <hr> |
| 1420 | P("HTTP_USER_AGENT"); |
| 1421 | cgi_print_all(showAll); |
| 1422 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -45,20 +45,20 @@ | |
| 45 | } aSubmenu[30]; |
| 46 | static int nSubmenu = 0; /* Number of buttons */ |
| 47 | static struct SubmenuCtrl { |
| 48 | const char *zName; /* Form query parameter */ |
| 49 | const char *zLabel; /* Label. Might be NULL for FF_MULTI */ |
| 50 | unsigned char eType; /* FF_ENTRY, FF_MULTI, FF_BINARY */ |
| 51 | unsigned char isDisabled; /* True if this control is grayed out */ |
| 52 | short int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */ |
| 53 | const char **azChoice; /* value/display pairs for FF_MULTI */ |
| 54 | const char *zFalse; /* FF_BINARY label when false */ |
| 55 | } aSubmenuCtrl[20]; |
| 56 | static int nSubmenuCtrl = 0; |
| 57 | #define FF_ENTRY 1 |
| 58 | #define FF_MULTI 2 |
| 59 | #define FF_BINARY 3 |
| 60 | |
| 61 | /* |
| 62 | ** Remember that the header has been generated. The footer is omitted |
| 63 | ** if an error occurs before the header. |
| 64 | */ |
| @@ -246,50 +246,46 @@ | |
| 246 | nSubmenu++; |
| 247 | } |
| 248 | void style_submenu_entry( |
| 249 | const char *zName, /* Query parameter name */ |
| 250 | const char *zLabel, /* Label before the entry box */ |
| 251 | int iSize, /* Size of the entry box */ |
| 252 | int isDisabled /* True if disabled */ |
| 253 | ){ |
| 254 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 255 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 256 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel; |
| 257 | aSubmenuCtrl[nSubmenuCtrl].iSize = iSize; |
| 258 | aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; |
| 259 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY; |
| 260 | nSubmenuCtrl++; |
| 261 | } |
| 262 | void style_submenu_binary( |
| 263 | const char *zName, /* Query parameter name */ |
| 264 | const char *zTrue, /* Label to show when parameter is true */ |
| 265 | const char *zFalse, /* Label to show when the parameter is false */ |
| 266 | int isDisabled /* True if this control is disabled */ |
| 267 | ){ |
| 268 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 269 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 270 | aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue; |
| 271 | aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse; |
| 272 | aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; |
| 273 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY; |
| 274 | nSubmenuCtrl++; |
| 275 | } |
| 276 | void style_submenu_multichoice( |
| 277 | const char *zName, /* Query parameter name */ |
| 278 | int nChoice, /* Number of options */ |
| 279 | const char **azChoice, /* value/display pairs. 2*nChoice entries */ |
| 280 | int isDisabled /* True if this control is disabled */ |
| 281 | ){ |
| 282 | assert( nSubmenuCtrl < ArraySize(aSubmenuCtrl) ); |
| 283 | aSubmenuCtrl[nSubmenuCtrl].zName = zName; |
| 284 | aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice; |
| 285 | aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice; |
| 286 | aSubmenuCtrl[nSubmenuCtrl].isDisabled = isDisabled; |
| 287 | aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; |
| 288 | nSubmenuCtrl++; |
| 289 | } |
| 290 | |
| 291 | |
| @@ -357,12 +353,11 @@ | |
| 353 | ** Draw the header. |
| 354 | */ |
| 355 | void style_header(const char *zTitleFormat, ...){ |
| 356 | va_list ap; |
| 357 | char *zTitle; |
| 358 | const char *zHeader = skin_get("header"); |
| 359 | login_check_credentials(); |
| 360 | |
| 361 | va_start(ap, zTitleFormat); |
| 362 | zTitle = vmprintf(zTitleFormat, ap); |
| 363 | va_end(ap); |
| @@ -499,42 +494,36 @@ | |
| 494 | } |
| 495 | } |
| 496 | if( nSubmenuCtrl>0 ){ |
| 497 | for(i=0; i<nSubmenuCtrl; i++){ |
| 498 | const char *zQPN = aSubmenuCtrl[i].zName; |
| 499 | const char *zDisabled = " disabled"; |
| 500 | if( !aSubmenuCtrl[i].isDisabled ){ |
| 501 | zDisabled = ""; |
| 502 | cgi_tag_query_parameter(zQPN); |
| 503 | } |
| 504 | switch( aSubmenuCtrl[i].eType ){ |
| 505 | case FF_ENTRY: { |
| 506 | cgi_printf( |
| 507 | "<span class='submenuctrl'>" |
| 508 | " %h<input type='text' name='%s' size='%d' maxlength='%d'" |
| 509 | "value='%h'%s></span>\n", |
| 510 | aSubmenuCtrl[i].zLabel, |
| 511 | zQPN, |
| 512 | aSubmenuCtrl[i].iSize, aSubmenuCtrl[i].iSize, |
| 513 | PD(zQPN,""), |
| 514 | zDisabled |
| 515 | ); |
| 516 | break; |
| 517 | } |
| 518 | case FF_MULTI: { |
| 519 | int j; |
| 520 | const char *zVal = P(zQPN); |
| 521 | cgi_printf( |
| 522 | "<select class='submenuctrl' size='1' name='%s'%s " |
| 523 | "onchange='gebi(\"f01\").submit();'>\n", |
| 524 | zQPN, zDisabled |
| 525 | ); |
| 526 | for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){ |
| 527 | const char *zQPV = aSubmenuCtrl[i].azChoice[j]; |
| 528 | cgi_printf( |
| 529 | "<option value='%h'%s>%h</option>\n", |
| @@ -547,13 +536,13 @@ | |
| 536 | break; |
| 537 | } |
| 538 | case FF_BINARY: { |
| 539 | int isTrue = PB(zQPN); |
| 540 | cgi_printf( |
| 541 | "<select class='submenuctrl' size='1' name='%s'%s " |
| 542 | "onchange='gebi(\"f01\").submit();'>\n", |
| 543 | zQPN, zDisabled |
| 544 | ); |
| 545 | cgi_printf( |
| 546 | "<option value='1'%s>%h</option>\n", |
| 547 | isTrue ? " selected":"", aSubmenuCtrl[i].zLabel |
| 548 | ); |
| @@ -602,12 +591,11 @@ | |
| 591 | |
| 592 | /* Set the href= field on hyperlinks. Do this before the footer since |
| 593 | ** the footer will be generating </html> */ |
| 594 | style_resolve_href(); |
| 595 | |
| 596 | zFooter = skin_get("footer"); |
| 597 | if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); |
| 598 | Th_Render(zFooter); |
| 599 | if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1); |
| 600 | |
| 601 | /* Render trace log if TH1 tracing is enabled. */ |
| @@ -673,10 +661,11 @@ | |
| 661 | @ font-size: small; |
| 662 | }, |
| 663 | { "table.timelineTable", |
| 664 | "the format for the timeline data table", |
| 665 | @ border: 0; |
| 666 | @ border-collapse: collapse; |
| 667 | }, |
| 668 | { "td.timelineTableCell", |
| 669 | "the format for the timeline data cells", |
| 670 | @ vertical-align: top; |
| 671 | @ text-align: left; |
| @@ -683,10 +672,21 @@ | |
| 672 | }, |
| 673 | { "tr.timelineCurrent td.timelineTableCell", |
| 674 | "the format for the timeline data cell of the current checkout", |
| 675 | @ padding: .1em .2em; |
| 676 | @ border: 1px dashed #446979; |
| 677 | }, |
| 678 | { "tr.timelineSelected", |
| 679 | "The row in the timeline table that contains the entry of interest", |
| 680 | @ padding: .1em .2em; |
| 681 | @ border: 2px solid lightgray; |
| 682 | @ background-color: #ffc; |
| 683 | @ box-shadow: 4px 4px 2px #888; |
| 684 | }, |
| 685 | { "tr.timelineSpacer", |
| 686 | "An extra row inserted to give vertical space between two rows", |
| 687 | @ height: 1ex; |
| 688 | }, |
| 689 | { "span.timelineLeaf", |
| 690 | "the format for the timeline leaf marks", |
| 691 | @ font-weight: bold; |
| 692 | }, |
| @@ -745,16 +745,10 @@ | |
| 745 | { "td.browser", |
| 746 | "format for cells in the file browser", |
| 747 | @ width: 24%; |
| 748 | @ vertical-align: top; |
| 749 | }, |
| 750 | { ".filetree", |
| 751 | "tree-view file browser", |
| 752 | @ margin: 1em 0; |
| 753 | @ line-height: 1.5; |
| 754 | }, |
| @@ -808,28 +802,59 @@ | |
| 802 | "hide lines for last-child directories", |
| 803 | @ display: none; |
| 804 | }, |
| 805 | { ".filetree a", |
| 806 | "tree-view links", |
| 807 | " position: relative;\n" |
| 808 | " z-index: 1;\n" |
| 809 | " display: table-cell;\n" |
| 810 | " min-height: 16px;\n" |
| 811 | " padding-left: 21px;\n" |
| 812 | " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" |
| 813 | "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmg" |
| 814 | "OUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" |
| 815 | " background-position: center left;\n" |
| 816 | " background-repeat: no-repeat;\n" |
| 817 | }, |
| 818 | { "ul.browser", |
| 819 | "list of files in the 'flat-view' file browser", |
| 820 | @ list-style-type: none; |
| 821 | @ padding: 10px; |
| 822 | @ margin: 0px; |
| 823 | @ white-space: nowrap; |
| 824 | }, |
| 825 | { "ul.browser li.file", |
| 826 | "List element in the 'flat-view' file browser for a file", |
| 827 | " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP" |
| 828 | "\\/\\/\\/yEhIf\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfm" |
| 829 | "gOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==);\n" |
| 830 | " background-repeat: no-repeat;\n" |
| 831 | " background-position: 0px center;\n" |
| 832 | " padding-left: 20px;\n" |
| 833 | " padding-top: 2px;\n" |
| 834 | }, |
| 835 | { "ul.browser li.dir", |
| 836 | "List element in the 'flat-view file browser for a directory", |
| 837 | " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" |
| 838 | "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaX" |
| 839 | "po+jUs6b5Z/K4siDu5RPUFADs=);\n" |
| 840 | " background-repeat: no-repeat;\n" |
| 841 | " background-position: 0px center;\n" |
| 842 | " padding-left: 20px;\n" |
| 843 | " padding-top: 2px;\n" |
| 844 | }, |
| 845 | { "div.filetreeline", |
| 846 | "line of a file tree", |
| 847 | @ display: table; |
| 848 | @ width: 100%; |
| 849 | @ white-space: nowrap; |
| 850 | }, |
| 851 | { ".filetree .dir > div.filetreeline > a", |
| 852 | "tree-view directory links", |
| 853 | " background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIi" |
| 854 | "Iv\\/\\/\\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXp" |
| 855 | "o+jUs6b5Z/K4siDu5RPUFADs=);\n" |
| 856 | }, |
| 857 | { "div.filetreeage", |
| 858 | "Last change floating display on the right", |
| 859 | @ display: table-cell; |
| 860 | @ padding-left: 3em; |
| @@ -1340,11 +1365,11 @@ | |
| 1365 | void page_style_css(void){ |
| 1366 | Blob css; |
| 1367 | int i; |
| 1368 | |
| 1369 | cgi_set_content_type("text/css"); |
| 1370 | blob_init(&css,skin_get("css"),-1); |
| 1371 | |
| 1372 | /* add special missing definitions */ |
| 1373 | for(i=1; cssDefaultList[i].elementClass; i++){ |
| 1374 | char *z = blob_str(&css); |
| 1375 | if( !containsString(z, cssDefaultList[i].elementClass) ){ |
| @@ -1386,11 +1411,11 @@ | |
| 1411 | "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL", |
| 1412 | }; |
| 1413 | |
| 1414 | login_check_credentials(); |
| 1415 | if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){ |
| 1416 | login_needed(0); |
| 1417 | return; |
| 1418 | } |
| 1419 | for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); |
| 1420 | style_header("Environment Test"); |
| 1421 | showAll = atoi(PD("showall","0")); |
| @@ -1405,17 +1430,25 @@ | |
| 1430 | @ g.zBaseURL = %h(g.zBaseURL)<br /> |
| 1431 | @ g.zHttpsURL = %h(g.zHttpsURL)<br /> |
| 1432 | @ g.zTop = %h(g.zTop)<br /> |
| 1433 | @ g.zPath = %h(g.zPath)<br /> |
| 1434 | for(i=0, c='a'; c<='z'; c++){ |
| 1435 | if( login_has_capability(&c, 1, 0) ) zCap[i++] = c; |
| 1436 | } |
| 1437 | zCap[i] = 0; |
| 1438 | @ g.userUid = %d(g.userUid)<br /> |
| 1439 | @ g.zLogin = %h(g.zLogin)<br /> |
| 1440 | @ g.isHuman = %d(g.isHuman)<br /> |
| 1441 | @ capabilities = %s(zCap)<br /> |
| 1442 | for(i=0, c='a'; c<='z'; c++){ |
| 1443 | if( login_has_capability(&c, 1, LOGIN_ANON) |
| 1444 | && !login_has_capability(&c, 1, 0) ) zCap[i++] = c; |
| 1445 | } |
| 1446 | zCap[i] = 0; |
| 1447 | if( i>0 ){ |
| 1448 | @ anonymous-adds = %s(zCap)<br /> |
| 1449 | } |
| 1450 | @ g.zRepositoryName = %h(g.zRepositoryName)<br /> |
| 1451 | @ load_average() = %f(load_average())<br /> |
| 1452 | @ <hr> |
| 1453 | P("HTTP_USER_AGENT"); |
| 1454 | cgi_print_all(showAll); |
| 1455 |
+3
-3
| --- src/tag.c | ||
| +++ src/tag.c | ||
| @@ -542,11 +542,11 @@ | ||
| 542 | 542 | void taglist_page(void){ |
| 543 | 543 | Stmt q; |
| 544 | 544 | |
| 545 | 545 | login_check_credentials(); |
| 546 | 546 | if( !g.perm.Read ){ |
| 547 | - login_needed(); | |
| 547 | + login_needed(g.anon.Read); | |
| 548 | 548 | } |
| 549 | 549 | login_anonymous_available(); |
| 550 | 550 | style_header("Tags"); |
| 551 | 551 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 552 | 552 | style_submenu_element("Timeline", "Timeline", "tagtimeline"); |
| @@ -580,11 +580,11 @@ | ||
| 580 | 580 | */ |
| 581 | 581 | void tagtimeline_page(void){ |
| 582 | 582 | Stmt q; |
| 583 | 583 | |
| 584 | 584 | login_check_credentials(); |
| 585 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 585 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 586 | 586 | |
| 587 | 587 | style_header("Tagged Check-ins"); |
| 588 | 588 | style_submenu_element("List", "List", "taglist"); |
| 589 | 589 | login_anonymous_available(); |
| 590 | 590 | @ <h2>Check-ins with non-propagating tags:</h2> |
| @@ -594,10 +594,10 @@ | ||
| 594 | 594 | " AND tagid IN (SELECT tagid FROM tag " |
| 595 | 595 | " WHERE tagname GLOB 'sym-*'))" |
| 596 | 596 | " ORDER BY event.mtime DESC", |
| 597 | 597 | timeline_query_for_www() |
| 598 | 598 | ); |
| 599 | - www_print_timeline(&q, 0, 0, 0, 0); | |
| 599 | + www_print_timeline(&q, 0, 0, 0, 0, 0); | |
| 600 | 600 | db_finalize(&q); |
| 601 | 601 | @ <br /> |
| 602 | 602 | style_footer(); |
| 603 | 603 | } |
| 604 | 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(); |
| 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 @@ | ||
| 285 | 285 | const char *zName, /* Name of the object */ |
| 286 | 286 | int nName, /* Number of characters in zName */ |
| 287 | 287 | int iMode, /* Mode. 0644 or 0755 */ |
| 288 | 288 | unsigned int mTime, /* File modification time */ |
| 289 | 289 | int iSize, /* Size of the object in bytes */ |
| 290 | - char cType /* Type of object: | |
| 290 | + char cType /* Type of object: | |
| 291 | 291 | '0'==file. '2'==symlink. '5'==directory */ |
| 292 | 292 | ){ |
| 293 | 293 | /* set mode and modification time */ |
| 294 | 294 | sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode); |
| 295 | 295 | sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime); |
| @@ -341,11 +341,11 @@ | ||
| 341 | 341 | unsigned int mTime /* Modification time */ |
| 342 | 342 | ){ |
| 343 | 343 | int i; |
| 344 | 344 | for(i=nName-1; i>0 && zName[i]!='/'; i--){} |
| 345 | 345 | if( i<=0 ) return; |
| 346 | - if( i<tball.nPrevDirAlloc | |
| 346 | + if( i<tball.nPrevDirAlloc | |
| 347 | 347 | && strncmp(tball.zPrevDir, zName, i)==0 |
| 348 | 348 | && tball.zPrevDir[i]==0 ) return; |
| 349 | 349 | db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName); |
| 350 | 350 | if( sqlite3_changes(g.db)==0 ) return; |
| 351 | 351 | tar_add_directory_of(zName, i-1, mTime); |
| @@ -377,11 +377,11 @@ | ||
| 377 | 377 | char cType = '0'; |
| 378 | 378 | |
| 379 | 379 | /* length check moved to tar_split_path */ |
| 380 | 380 | tar_add_directory_of(zName, nName, mTime); |
| 381 | 381 | |
| 382 | - /* | |
| 382 | + /* | |
| 383 | 383 | * If we have a symlink, write its destination path (which is stored in |
| 384 | 384 | * pContent) into header, and set content length to 0 to avoid storing path |
| 385 | 385 | * as file content in the next step. Since 'linkname' header is limited to |
| 386 | 386 | * 100 bytes (-1 byte for terminating zero), if path is greater than that, |
| 387 | 387 | * store symlink as a plain-text file. (Not sure how TAR handles long links.) |
| @@ -390,11 +390,11 @@ | ||
| 390 | 390 | sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent)); |
| 391 | 391 | cType = '2'; |
| 392 | 392 | n = 0; |
| 393 | 393 | } |
| 394 | 394 | |
| 395 | - tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, | |
| 395 | + tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, | |
| 396 | 396 | mTime, n, cType); |
| 397 | 397 | if( n ){ |
| 398 | 398 | gzip_step(blob_buffer(pContent), n); |
| 399 | 399 | lastPage = n % 512; |
| 400 | 400 | if( lastPage!=0 ){ |
| @@ -428,21 +428,20 @@ | ||
| 428 | 428 | ** that contains files given in the second and subsequent arguments. |
| 429 | 429 | */ |
| 430 | 430 | void test_tarball_cmd(void){ |
| 431 | 431 | int i; |
| 432 | 432 | Blob zip; |
| 433 | - Blob file; | |
| 434 | 433 | if( g.argc<3 ){ |
| 435 | 434 | usage("ARCHIVE FILE...."); |
| 436 | 435 | } |
| 437 | 436 | sqlite3_open(":memory:", &g.db); |
| 438 | 437 | tar_begin(-1); |
| 439 | 438 | for(i=3; i<g.argc; i++){ |
| 439 | + Blob file; | |
| 440 | 440 | blob_zero(&file); |
| 441 | 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])); | |
| 442 | + tar_add_file(g.argv[i], &file, file_wd_perm(0), file_wd_mtime(0)); | |
| 444 | 443 | blob_reset(&file); |
| 445 | 444 | } |
| 446 | 445 | tar_finish(&zip); |
| 447 | 446 | blob_write_to_file(&zip, g.argv[2]); |
| 448 | 447 | } |
| @@ -591,11 +590,11 @@ | ||
| 591 | 590 | ** Optional URL Parameters: |
| 592 | 591 | ** |
| 593 | 592 | ** - name=NAME[.tar.gz] is base name of the output file. Defaults to |
| 594 | 593 | ** something project/version-specific. The prefix of the name, up to |
| 595 | 594 | ** the last '.', are used as the top-most directory name in the tar |
| 596 | -** output. | |
| 595 | +** output. | |
| 597 | 596 | ** |
| 598 | 597 | ** - uuid=the version to tar (may be a tag/branch name). |
| 599 | 598 | ** Defaults to "trunk". |
| 600 | 599 | ** |
| 601 | 600 | */ |
| @@ -604,11 +603,11 @@ | ||
| 604 | 603 | char *zName, *zRid, *zKey; |
| 605 | 604 | int nName, nRid; |
| 606 | 605 | Blob tarball; |
| 607 | 606 | |
| 608 | 607 | login_check_credentials(); |
| 609 | - if( !g.perm.Zip ){ login_needed(); return; } | |
| 608 | + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } | |
| 610 | 609 | load_control(); |
| 611 | 610 | zName = mprintf("%s", PD("name","")); |
| 612 | 611 | nName = strlen(zName); |
| 613 | 612 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 614 | 613 | nRid = strlen(zRid); |
| @@ -639,10 +638,21 @@ | ||
| 639 | 638 | @ zName = "%h(zName)"<br> |
| 640 | 639 | @ rid = %d(rid)<br> |
| 641 | 640 | @ zKey = "%h(zKey)" |
| 642 | 641 | style_footer(); |
| 643 | 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; | |
| 644 | 654 | } |
| 645 | 655 | blob_zero(&tarball); |
| 646 | 656 | if( cache_read(&tarball, zKey)==0 ){ |
| 647 | 657 | tarball_of_checkin(rid, &tarball, zName); |
| 648 | 658 | cache_write(&tarball, zKey); |
| 649 | 659 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -285,11 +285,11 @@ | |
| 285 | const char *zName, /* Name of the object */ |
| 286 | int nName, /* Number of characters in zName */ |
| 287 | int iMode, /* Mode. 0644 or 0755 */ |
| 288 | unsigned int mTime, /* File modification time */ |
| 289 | int iSize, /* Size of the object in bytes */ |
| 290 | char cType /* Type of object: |
| 291 | '0'==file. '2'==symlink. '5'==directory */ |
| 292 | ){ |
| 293 | /* set mode and modification time */ |
| 294 | sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode); |
| 295 | sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime); |
| @@ -341,11 +341,11 @@ | |
| 341 | unsigned int mTime /* Modification time */ |
| 342 | ){ |
| 343 | int i; |
| 344 | for(i=nName-1; i>0 && zName[i]!='/'; i--){} |
| 345 | if( i<=0 ) return; |
| 346 | if( i<tball.nPrevDirAlloc |
| 347 | && strncmp(tball.zPrevDir, zName, i)==0 |
| 348 | && tball.zPrevDir[i]==0 ) return; |
| 349 | db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName); |
| 350 | if( sqlite3_changes(g.db)==0 ) return; |
| 351 | tar_add_directory_of(zName, i-1, mTime); |
| @@ -377,11 +377,11 @@ | |
| 377 | char cType = '0'; |
| 378 | |
| 379 | /* length check moved to tar_split_path */ |
| 380 | tar_add_directory_of(zName, nName, mTime); |
| 381 | |
| 382 | /* |
| 383 | * If we have a symlink, write its destination path (which is stored in |
| 384 | * pContent) into header, and set content length to 0 to avoid storing path |
| 385 | * as file content in the next step. Since 'linkname' header is limited to |
| 386 | * 100 bytes (-1 byte for terminating zero), if path is greater than that, |
| 387 | * store symlink as a plain-text file. (Not sure how TAR handles long links.) |
| @@ -390,11 +390,11 @@ | |
| 390 | sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent)); |
| 391 | cType = '2'; |
| 392 | n = 0; |
| 393 | } |
| 394 | |
| 395 | tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, |
| 396 | mTime, n, cType); |
| 397 | if( n ){ |
| 398 | gzip_step(blob_buffer(pContent), n); |
| 399 | lastPage = n % 512; |
| 400 | if( lastPage!=0 ){ |
| @@ -428,21 +428,20 @@ | |
| 428 | ** that contains files given in the second and subsequent arguments. |
| 429 | */ |
| 430 | void test_tarball_cmd(void){ |
| 431 | int i; |
| 432 | Blob zip; |
| 433 | Blob file; |
| 434 | if( g.argc<3 ){ |
| 435 | usage("ARCHIVE FILE...."); |
| 436 | } |
| 437 | sqlite3_open(":memory:", &g.db); |
| 438 | tar_begin(-1); |
| 439 | for(i=3; i<g.argc; i++){ |
| 440 | blob_zero(&file); |
| 441 | blob_read_from_file(&file, g.argv[i]); |
| 442 | tar_add_file(g.argv[i], &file, |
| 443 | file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i])); |
| 444 | blob_reset(&file); |
| 445 | } |
| 446 | tar_finish(&zip); |
| 447 | blob_write_to_file(&zip, g.argv[2]); |
| 448 | } |
| @@ -591,11 +590,11 @@ | |
| 591 | ** Optional URL Parameters: |
| 592 | ** |
| 593 | ** - name=NAME[.tar.gz] is base name of the output file. Defaults to |
| 594 | ** something project/version-specific. The prefix of the name, up to |
| 595 | ** the last '.', are used as the top-most directory name in the tar |
| 596 | ** output. |
| 597 | ** |
| 598 | ** - uuid=the version to tar (may be a tag/branch name). |
| 599 | ** Defaults to "trunk". |
| 600 | ** |
| 601 | */ |
| @@ -604,11 +603,11 @@ | |
| 604 | char *zName, *zRid, *zKey; |
| 605 | int nName, nRid; |
| 606 | Blob tarball; |
| 607 | |
| 608 | login_check_credentials(); |
| 609 | if( !g.perm.Zip ){ login_needed(); return; } |
| 610 | load_control(); |
| 611 | zName = mprintf("%s", PD("name","")); |
| 612 | nName = strlen(zName); |
| 613 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 614 | nRid = strlen(zRid); |
| @@ -639,10 +638,21 @@ | |
| 639 | @ zName = "%h(zName)"<br> |
| 640 | @ rid = %d(rid)<br> |
| 641 | @ zKey = "%h(zKey)" |
| 642 | style_footer(); |
| 643 | return; |
| 644 | } |
| 645 | blob_zero(&tarball); |
| 646 | if( cache_read(&tarball, zKey)==0 ){ |
| 647 | tarball_of_checkin(rid, &tarball, zName); |
| 648 | cache_write(&tarball, zKey); |
| 649 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -285,11 +285,11 @@ | |
| 285 | const char *zName, /* Name of the object */ |
| 286 | int nName, /* Number of characters in zName */ |
| 287 | int iMode, /* Mode. 0644 or 0755 */ |
| 288 | unsigned int mTime, /* File modification time */ |
| 289 | int iSize, /* Size of the object in bytes */ |
| 290 | char cType /* Type of object: |
| 291 | '0'==file. '2'==symlink. '5'==directory */ |
| 292 | ){ |
| 293 | /* set mode and modification time */ |
| 294 | sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode); |
| 295 | sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime); |
| @@ -341,11 +341,11 @@ | |
| 341 | unsigned int mTime /* Modification time */ |
| 342 | ){ |
| 343 | int i; |
| 344 | for(i=nName-1; i>0 && zName[i]!='/'; i--){} |
| 345 | if( i<=0 ) return; |
| 346 | if( i<tball.nPrevDirAlloc |
| 347 | && strncmp(tball.zPrevDir, zName, i)==0 |
| 348 | && tball.zPrevDir[i]==0 ) return; |
| 349 | db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName); |
| 350 | if( sqlite3_changes(g.db)==0 ) return; |
| 351 | tar_add_directory_of(zName, i-1, mTime); |
| @@ -377,11 +377,11 @@ | |
| 377 | char cType = '0'; |
| 378 | |
| 379 | /* length check moved to tar_split_path */ |
| 380 | tar_add_directory_of(zName, nName, mTime); |
| 381 | |
| 382 | /* |
| 383 | * If we have a symlink, write its destination path (which is stored in |
| 384 | * pContent) into header, and set content length to 0 to avoid storing path |
| 385 | * as file content in the next step. Since 'linkname' header is limited to |
| 386 | * 100 bytes (-1 byte for terminating zero), if path is greater than that, |
| 387 | * store symlink as a plain-text file. (Not sure how TAR handles long links.) |
| @@ -390,11 +390,11 @@ | |
| 390 | sqlite3_snprintf(100, (char*)&tball.aHdr[157], "%s", blob_str(pContent)); |
| 391 | cType = '2'; |
| 392 | n = 0; |
| 393 | } |
| 394 | |
| 395 | tar_add_header(zName, nName, ( mPerm==PERM_EXE ) ? 0755 : 0644, |
| 396 | mTime, n, cType); |
| 397 | if( n ){ |
| 398 | gzip_step(blob_buffer(pContent), n); |
| 399 | lastPage = n % 512; |
| 400 | if( lastPage!=0 ){ |
| @@ -428,21 +428,20 @@ | |
| 428 | ** that contains files given in the second and subsequent arguments. |
| 429 | */ |
| 430 | void test_tarball_cmd(void){ |
| 431 | int i; |
| 432 | Blob zip; |
| 433 | if( g.argc<3 ){ |
| 434 | usage("ARCHIVE FILE...."); |
| 435 | } |
| 436 | sqlite3_open(":memory:", &g.db); |
| 437 | tar_begin(-1); |
| 438 | for(i=3; i<g.argc; i++){ |
| 439 | Blob file; |
| 440 | blob_zero(&file); |
| 441 | blob_read_from_file(&file, g.argv[i]); |
| 442 | tar_add_file(g.argv[i], &file, file_wd_perm(0), file_wd_mtime(0)); |
| 443 | blob_reset(&file); |
| 444 | } |
| 445 | tar_finish(&zip); |
| 446 | blob_write_to_file(&zip, g.argv[2]); |
| 447 | } |
| @@ -591,11 +590,11 @@ | |
| 590 | ** Optional URL Parameters: |
| 591 | ** |
| 592 | ** - name=NAME[.tar.gz] is base name of the output file. Defaults to |
| 593 | ** something project/version-specific. The prefix of the name, up to |
| 594 | ** the last '.', are used as the top-most directory name in the tar |
| 595 | ** output. |
| 596 | ** |
| 597 | ** - uuid=the version to tar (may be a tag/branch name). |
| 598 | ** Defaults to "trunk". |
| 599 | ** |
| 600 | */ |
| @@ -604,11 +603,11 @@ | |
| 603 | char *zName, *zRid, *zKey; |
| 604 | int nName, nRid; |
| 605 | Blob tarball; |
| 606 | |
| 607 | login_check_credentials(); |
| 608 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 609 | load_control(); |
| 610 | zName = mprintf("%s", PD("name","")); |
| 611 | nName = strlen(zName); |
| 612 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 613 | nRid = strlen(zRid); |
| @@ -639,10 +638,21 @@ | |
| 638 | @ zName = "%h(zName)"<br> |
| 639 | @ rid = %d(rid)<br> |
| 640 | @ zKey = "%h(zKey)" |
| 641 | style_footer(); |
| 642 | return; |
| 643 | } |
| 644 | if( referred_from_login() ){ |
| 645 | style_header("Tarball Download"); |
| 646 | @ <form action='%R/tarball'> |
| 647 | cgi_query_parameters_to_hidden(); |
| 648 | @ <p>Tarball named <b>%h(zName).tar.gz</b> holding the content |
| 649 | @ of check-in <b>%h(zRid)</b>: |
| 650 | @ <input type="submit" value="Download" /> |
| 651 | @ </form> |
| 652 | style_footer(); |
| 653 | return; |
| 654 | } |
| 655 | blob_zero(&tarball); |
| 656 | if( cache_read(&tarball, zKey)==0 ){ |
| 657 | tarball_of_checkin(rid, &tarball, zName); |
| 658 | cache_write(&tarball, zKey); |
| 659 |
+120
-17
| --- src/th_main.c | ||
| +++ src/th_main.c | ||
| @@ -246,10 +246,81 @@ | ||
| 246 | 246 | sendText("ERROR: ", -1, 0); |
| 247 | 247 | sendText((char*)z, n, 1); |
| 248 | 248 | sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0); |
| 249 | 249 | enableOutput = savedEnable; |
| 250 | 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 | +} | |
| 251 | 322 | |
| 252 | 323 | /* |
| 253 | 324 | ** TH1 command: puts STRING |
| 254 | 325 | ** TH1 command: html STRING |
| 255 | 326 | ** |
| @@ -342,12 +413,14 @@ | ||
| 342 | 413 | return TH_OK; |
| 343 | 414 | } |
| 344 | 415 | |
| 345 | 416 | /* |
| 346 | 417 | ** TH1 command: hascap STRING... |
| 418 | +** TH1 command: anoncap STRING... | |
| 347 | 419 | ** |
| 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. | |
| 349 | 422 | */ |
| 350 | 423 | static int hascapCmd( |
| 351 | 424 | Th_Interp *interp, |
| 352 | 425 | void *p, |
| 353 | 426 | int argc, |
| @@ -357,11 +430,11 @@ | ||
| 357 | 430 | int rc = 0, i; |
| 358 | 431 | if( argc<2 ){ |
| 359 | 432 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 360 | 433 | } |
| 361 | 434 | 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); | |
| 363 | 436 | } |
| 364 | 437 | if( g.thTrace ){ |
| 365 | 438 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 366 | 439 | } |
| 367 | 440 | Th_SetResultInt(interp, rc); |
| @@ -543,11 +616,12 @@ | ||
| 543 | 616 | |
| 544 | 617 | |
| 545 | 618 | /* |
| 546 | 619 | ** TH1 command: anycap STRING |
| 547 | 620 | ** |
| 548 | -** Return true if the user has any one of the capabilities listed in STRING. | |
| 621 | +** Return true if the current user user | |
| 622 | +** has any one of the capabilities listed in STRING. | |
| 549 | 623 | */ |
| 550 | 624 | static int anycapCmd( |
| 551 | 625 | Th_Interp *interp, |
| 552 | 626 | void *p, |
| 553 | 627 | int argc, |
| @@ -558,11 +632,11 @@ | ||
| 558 | 632 | int i; |
| 559 | 633 | if( argc!=2 ){ |
| 560 | 634 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 561 | 635 | } |
| 562 | 636 | for(i=0; rc==0 && i<argl[1]; i++){ |
| 563 | - rc = login_has_capability((char*)&argv[1][i],1); | |
| 637 | + rc = login_has_capability((char*)&argv[1][i],1,0); | |
| 564 | 638 | } |
| 565 | 639 | if( g.thTrace ){ |
| 566 | 640 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 567 | 641 | } |
| 568 | 642 | Th_SetResultInt(interp, rc); |
| @@ -978,20 +1052,19 @@ | ||
| 978 | 1052 | } |
| 979 | 1053 | if( Th_IsRepositoryOpen() ){ |
| 980 | 1054 | int rid; |
| 981 | 1055 | Blob content; |
| 982 | 1056 | if( argc==3 ){ |
| 983 | - rid = artifact_from_ci_and_filename(argv[1], argv[2]); | |
| 1057 | + rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]); | |
| 984 | 1058 | }else{ |
| 985 | - rid = name_to_rid(argv[1]); | |
| 1059 | + rid = th1_name_to_typed_rid(interp, argv[1], "*"); | |
| 986 | 1060 | } |
| 987 | 1061 | if( rid!=0 && content_get(rid, &content) ){ |
| 988 | 1062 | Th_SetResult(interp, blob_str(&content), blob_size(&content)); |
| 989 | 1063 | blob_reset(&content); |
| 990 | 1064 | return TH_OK; |
| 991 | 1065 | }else{ |
| 992 | - Th_SetResult(interp, "artifact not found", -1); | |
| 993 | 1066 | return TH_ERROR; |
| 994 | 1067 | } |
| 995 | 1068 | }else{ |
| 996 | 1069 | Th_SetResult(interp, "repository unavailable", -1); |
| 997 | 1070 | return TH_ERROR; |
| @@ -1450,15 +1523,18 @@ | ||
| 1450 | 1523 | int needConfig = flags & TH_INIT_NEED_CONFIG; |
| 1451 | 1524 | int forceReset = flags & TH_INIT_FORCE_RESET; |
| 1452 | 1525 | int forceTcl = flags & TH_INIT_FORCE_TCL; |
| 1453 | 1526 | int forceSetup = flags & TH_INIT_FORCE_SETUP; |
| 1454 | 1527 | static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY }; |
| 1528 | + static int anonFlag = LOGIN_ANON; | |
| 1529 | + static int zeroInt = 0; | |
| 1455 | 1530 | static struct _Command { |
| 1456 | 1531 | const char *zName; |
| 1457 | 1532 | Th_CommandProc xProc; |
| 1458 | 1533 | void *pContext; |
| 1459 | 1534 | } aCommand[] = { |
| 1535 | + {"anoncap", hascapCmd, (void*)&anonFlag}, | |
| 1460 | 1536 | {"anycap", anycapCmd, 0}, |
| 1461 | 1537 | {"artifact", artifactCmd, 0}, |
| 1462 | 1538 | {"checkout", checkoutCmd, 0}, |
| 1463 | 1539 | {"combobox", comboboxCmd, 0}, |
| 1464 | 1540 | {"date", dateCmd, 0}, |
| @@ -1465,11 +1541,11 @@ | ||
| 1465 | 1541 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 1466 | 1542 | {"enable_output", enableOutputCmd, 0}, |
| 1467 | 1543 | {"getParameter", getParameterCmd, 0}, |
| 1468 | 1544 | {"globalState", globalStateCmd, 0}, |
| 1469 | 1545 | {"httpize", httpizeCmd, 0}, |
| 1470 | - {"hascap", hascapCmd, 0}, | |
| 1546 | + {"hascap", hascapCmd, (void*)&zeroInt}, | |
| 1471 | 1547 | {"hasfeature", hasfeatureCmd, 0}, |
| 1472 | 1548 | {"html", putsCmd, (void*)&aFlags[0]}, |
| 1473 | 1549 | {"htmlize", htmlizeCmd, 0}, |
| 1474 | 1550 | {"http", httpCmd, 0}, |
| 1475 | 1551 | {"linecount", linecntCmd, 0}, |
| @@ -1952,21 +2028,35 @@ | ||
| 1952 | 2028 | return rc; |
| 1953 | 2029 | } |
| 1954 | 2030 | |
| 1955 | 2031 | /* |
| 1956 | 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 | |
| 1957 | 2045 | */ |
| 1958 | 2046 | void test_th_render(void){ |
| 1959 | - int forceCgi, fullHttpReply; | |
| 2047 | + int forceCgi = 0, fullHttpReply = 0; | |
| 1960 | 2048 | Blob in; |
| 1961 | 2049 | Th_InitTraceLog(); |
| 1962 | - forceCgi = find_option("th-force-cgi", 0, 0)!=0; | |
| 1963 | - fullHttpReply = find_option("th-full-http", 0, 0)!=0; | |
| 2050 | + forceCgi = find_option("cgi", 0, 0)!=0; | |
| 2051 | + fullHttpReply = find_option("http", 0, 0)!=0; | |
| 2052 | + if( fullHttpReply ) forceCgi = 1; | |
| 1964 | 2053 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 1965 | - if( find_option("th-open-config", 0, 0)!=0 ){ | |
| 2054 | + if( find_option("open-config", 0, 0)!=0 ){ | |
| 1966 | 2055 | Th_OpenConfig(1); |
| 1967 | 2056 | } |
| 2057 | + verify_all_options(); | |
| 1968 | 2058 | if( g.argc<3 ){ |
| 1969 | 2059 | usage("FILE"); |
| 1970 | 2060 | } |
| 1971 | 2061 | blob_zero(&in); |
| 1972 | 2062 | blob_read_from_file(&in, g.argv[2]); |
| @@ -1975,20 +2065,32 @@ | ||
| 1975 | 2065 | if( forceCgi ) cgi_reply(); |
| 1976 | 2066 | } |
| 1977 | 2067 | |
| 1978 | 2068 | /* |
| 1979 | 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 | |
| 1980 | 2081 | */ |
| 1981 | 2082 | void test_th_eval(void){ |
| 1982 | 2083 | int rc; |
| 1983 | 2084 | const char *zRc; |
| 1984 | 2085 | int forceCgi, fullHttpReply; |
| 1985 | 2086 | Th_InitTraceLog(); |
| 1986 | - forceCgi = find_option("th-force-cgi", 0, 0)!=0; | |
| 1987 | - fullHttpReply = find_option("th-full-http", 0, 0)!=0; | |
| 2087 | + forceCgi = find_option("cgi", 0, 0)!=0; | |
| 2088 | + fullHttpReply = find_option("http", 0, 0)!=0; | |
| 2089 | + if( fullHttpReply ) forceCgi = 1; | |
| 1988 | 2090 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 1989 | - if( find_option("th-open-config", 0, 0)!=0 ){ | |
| 2091 | + if( find_option("open-config", 0, 0)!=0 ){ | |
| 1990 | 2092 | Th_OpenConfig(1); |
| 1991 | 2093 | } |
| 1992 | 2094 | if( g.argc!=3 ){ |
| 1993 | 2095 | usage("script"); |
| 1994 | 2096 | } |
| @@ -2008,12 +2110,13 @@ | ||
| 2008 | 2110 | int rc = TH_OK; |
| 2009 | 2111 | int nResult = 0; |
| 2010 | 2112 | char *zResult; |
| 2011 | 2113 | int forceCgi, fullHttpReply; |
| 2012 | 2114 | Th_InitTraceLog(); |
| 2013 | - forceCgi = find_option("th-force-cgi", 0, 0)!=0; | |
| 2014 | - fullHttpReply = find_option("th-full-http", 0, 0)!=0; | |
| 2115 | + forceCgi = find_option("cgi", 0, 0)!=0; | |
| 2116 | + fullHttpReply = find_option("http", 0, 0)!=0; | |
| 2117 | + if( fullHttpReply ) forceCgi = 1; | |
| 2015 | 2118 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 2016 | 2119 | if( g.argc<5 ){ |
| 2017 | 2120 | usage("TYPE NAME FLAGS"); |
| 2018 | 2121 | } |
| 2019 | 2122 | if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ |
| 2020 | 2123 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -246,10 +246,81 @@ | |
| 246 | sendText("ERROR: ", -1, 0); |
| 247 | sendText((char*)z, n, 1); |
| 248 | sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0); |
| 249 | enableOutput = savedEnable; |
| 250 | } |
| 251 | |
| 252 | /* |
| 253 | ** TH1 command: puts STRING |
| 254 | ** TH1 command: html STRING |
| 255 | ** |
| @@ -342,12 +413,14 @@ | |
| 342 | return TH_OK; |
| 343 | } |
| 344 | |
| 345 | /* |
| 346 | ** TH1 command: hascap STRING... |
| 347 | ** |
| 348 | ** Return true if the user has all of the capabilities listed in STRING. |
| 349 | */ |
| 350 | static int hascapCmd( |
| 351 | Th_Interp *interp, |
| 352 | void *p, |
| 353 | int argc, |
| @@ -357,11 +430,11 @@ | |
| 357 | int rc = 0, i; |
| 358 | if( argc<2 ){ |
| 359 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 360 | } |
| 361 | for(i=1; i<argc && rc==0; i++){ |
| 362 | rc = login_has_capability((char*)argv[i],argl[i]); |
| 363 | } |
| 364 | if( g.thTrace ){ |
| 365 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 366 | } |
| 367 | Th_SetResultInt(interp, rc); |
| @@ -543,11 +616,12 @@ | |
| 543 | |
| 544 | |
| 545 | /* |
| 546 | ** TH1 command: anycap STRING |
| 547 | ** |
| 548 | ** Return true if the user has any one of the capabilities listed in STRING. |
| 549 | */ |
| 550 | static int anycapCmd( |
| 551 | Th_Interp *interp, |
| 552 | void *p, |
| 553 | int argc, |
| @@ -558,11 +632,11 @@ | |
| 558 | int i; |
| 559 | if( argc!=2 ){ |
| 560 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 561 | } |
| 562 | for(i=0; rc==0 && i<argl[1]; i++){ |
| 563 | rc = login_has_capability((char*)&argv[1][i],1); |
| 564 | } |
| 565 | if( g.thTrace ){ |
| 566 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 567 | } |
| 568 | Th_SetResultInt(interp, rc); |
| @@ -978,20 +1052,19 @@ | |
| 978 | } |
| 979 | if( Th_IsRepositoryOpen() ){ |
| 980 | int rid; |
| 981 | Blob content; |
| 982 | if( argc==3 ){ |
| 983 | rid = artifact_from_ci_and_filename(argv[1], argv[2]); |
| 984 | }else{ |
| 985 | rid = name_to_rid(argv[1]); |
| 986 | } |
| 987 | if( rid!=0 && content_get(rid, &content) ){ |
| 988 | Th_SetResult(interp, blob_str(&content), blob_size(&content)); |
| 989 | blob_reset(&content); |
| 990 | return TH_OK; |
| 991 | }else{ |
| 992 | Th_SetResult(interp, "artifact not found", -1); |
| 993 | return TH_ERROR; |
| 994 | } |
| 995 | }else{ |
| 996 | Th_SetResult(interp, "repository unavailable", -1); |
| 997 | return TH_ERROR; |
| @@ -1450,15 +1523,18 @@ | |
| 1450 | int needConfig = flags & TH_INIT_NEED_CONFIG; |
| 1451 | int forceReset = flags & TH_INIT_FORCE_RESET; |
| 1452 | int forceTcl = flags & TH_INIT_FORCE_TCL; |
| 1453 | int forceSetup = flags & TH_INIT_FORCE_SETUP; |
| 1454 | static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY }; |
| 1455 | static struct _Command { |
| 1456 | const char *zName; |
| 1457 | Th_CommandProc xProc; |
| 1458 | void *pContext; |
| 1459 | } aCommand[] = { |
| 1460 | {"anycap", anycapCmd, 0}, |
| 1461 | {"artifact", artifactCmd, 0}, |
| 1462 | {"checkout", checkoutCmd, 0}, |
| 1463 | {"combobox", comboboxCmd, 0}, |
| 1464 | {"date", dateCmd, 0}, |
| @@ -1465,11 +1541,11 @@ | |
| 1465 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 1466 | {"enable_output", enableOutputCmd, 0}, |
| 1467 | {"getParameter", getParameterCmd, 0}, |
| 1468 | {"globalState", globalStateCmd, 0}, |
| 1469 | {"httpize", httpizeCmd, 0}, |
| 1470 | {"hascap", hascapCmd, 0}, |
| 1471 | {"hasfeature", hasfeatureCmd, 0}, |
| 1472 | {"html", putsCmd, (void*)&aFlags[0]}, |
| 1473 | {"htmlize", htmlizeCmd, 0}, |
| 1474 | {"http", httpCmd, 0}, |
| 1475 | {"linecount", linecntCmd, 0}, |
| @@ -1952,21 +2028,35 @@ | |
| 1952 | return rc; |
| 1953 | } |
| 1954 | |
| 1955 | /* |
| 1956 | ** COMMAND: test-th-render |
| 1957 | */ |
| 1958 | void test_th_render(void){ |
| 1959 | int forceCgi, fullHttpReply; |
| 1960 | Blob in; |
| 1961 | Th_InitTraceLog(); |
| 1962 | forceCgi = find_option("th-force-cgi", 0, 0)!=0; |
| 1963 | fullHttpReply = find_option("th-full-http", 0, 0)!=0; |
| 1964 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 1965 | if( find_option("th-open-config", 0, 0)!=0 ){ |
| 1966 | Th_OpenConfig(1); |
| 1967 | } |
| 1968 | if( g.argc<3 ){ |
| 1969 | usage("FILE"); |
| 1970 | } |
| 1971 | blob_zero(&in); |
| 1972 | blob_read_from_file(&in, g.argv[2]); |
| @@ -1975,20 +2065,32 @@ | |
| 1975 | if( forceCgi ) cgi_reply(); |
| 1976 | } |
| 1977 | |
| 1978 | /* |
| 1979 | ** COMMAND: test-th-eval |
| 1980 | */ |
| 1981 | void test_th_eval(void){ |
| 1982 | int rc; |
| 1983 | const char *zRc; |
| 1984 | int forceCgi, fullHttpReply; |
| 1985 | Th_InitTraceLog(); |
| 1986 | forceCgi = find_option("th-force-cgi", 0, 0)!=0; |
| 1987 | fullHttpReply = find_option("th-full-http", 0, 0)!=0; |
| 1988 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 1989 | if( find_option("th-open-config", 0, 0)!=0 ){ |
| 1990 | Th_OpenConfig(1); |
| 1991 | } |
| 1992 | if( g.argc!=3 ){ |
| 1993 | usage("script"); |
| 1994 | } |
| @@ -2008,12 +2110,13 @@ | |
| 2008 | int rc = TH_OK; |
| 2009 | int nResult = 0; |
| 2010 | char *zResult; |
| 2011 | int forceCgi, fullHttpReply; |
| 2012 | Th_InitTraceLog(); |
| 2013 | forceCgi = find_option("th-force-cgi", 0, 0)!=0; |
| 2014 | fullHttpReply = find_option("th-full-http", 0, 0)!=0; |
| 2015 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 2016 | if( g.argc<5 ){ |
| 2017 | usage("TYPE NAME FLAGS"); |
| 2018 | } |
| 2019 | if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ |
| 2020 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -246,10 +246,81 @@ | |
| 246 | sendText("ERROR: ", -1, 0); |
| 247 | sendText((char*)z, n, 1); |
| 248 | sendText(forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0); |
| 249 | enableOutput = savedEnable; |
| 250 | } |
| 251 | |
| 252 | /* |
| 253 | ** Convert name to an rid. This function was copied from name_to_typed_rid() |
| 254 | ** in name.c; however, it has been modified to report TH1 script errors instead |
| 255 | ** of "fatal errors". |
| 256 | */ |
| 257 | int th1_name_to_typed_rid( |
| 258 | Th_Interp *interp, |
| 259 | const char *zName, |
| 260 | const char *zType |
| 261 | ){ |
| 262 | int rid; |
| 263 | |
| 264 | if( zName==0 || zName[0]==0 ) return 0; |
| 265 | rid = symbolic_name_to_rid(zName, zType); |
| 266 | if( rid<0 ){ |
| 267 | Th_SetResult(interp, "ambiguous name", -1); |
| 268 | }else if( rid==0 ){ |
| 269 | Th_SetResult(interp, "name not found", -1); |
| 270 | } |
| 271 | return rid; |
| 272 | } |
| 273 | |
| 274 | /* |
| 275 | ** Attempt to lookup the specified checkin and file name into an rid. |
| 276 | ** This function was copied from artifact_from_ci_and_filename() in |
| 277 | ** info.c; however, it has been modified to report TH1 script errors |
| 278 | ** instead of "fatal errors". |
| 279 | */ |
| 280 | int th1_artifact_from_ci_and_filename( |
| 281 | Th_Interp *interp, |
| 282 | const char *zCI, |
| 283 | const char *zFilename |
| 284 | ){ |
| 285 | int cirid; |
| 286 | Blob err; |
| 287 | Manifest *pManifest; |
| 288 | ManifestFile *pFile; |
| 289 | |
| 290 | if( zCI==0 ){ |
| 291 | Th_SetResult(interp, "invalid check-in", -1); |
| 292 | return 0; |
| 293 | } |
| 294 | if( zFilename==0 ){ |
| 295 | Th_SetResult(interp, "invalid file name", -1); |
| 296 | return 0; |
| 297 | } |
| 298 | cirid = th1_name_to_typed_rid(interp, zCI, "*"); |
| 299 | blob_zero(&err); |
| 300 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err); |
| 301 | if( pManifest==0 ){ |
| 302 | if( blob_size(&err)>0 ){ |
| 303 | Th_SetResult(interp, blob_str(&err), blob_size(&err)); |
| 304 | }else{ |
| 305 | Th_SetResult(interp, "manifest not found", -1); |
| 306 | } |
| 307 | blob_reset(&err); |
| 308 | return 0; |
| 309 | } |
| 310 | blob_reset(&err); |
| 311 | manifest_file_rewind(pManifest); |
| 312 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 313 | if( fossil_strcmp(zFilename, pFile->zName)==0 ){ |
| 314 | int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 315 | manifest_destroy(pManifest); |
| 316 | return rid; |
| 317 | } |
| 318 | } |
| 319 | Th_SetResult(interp, "file name not found in manifest", -1); |
| 320 | return 0; |
| 321 | } |
| 322 | |
| 323 | /* |
| 324 | ** TH1 command: puts STRING |
| 325 | ** TH1 command: html STRING |
| 326 | ** |
| @@ -342,12 +413,14 @@ | |
| 413 | return TH_OK; |
| 414 | } |
| 415 | |
| 416 | /* |
| 417 | ** TH1 command: hascap STRING... |
| 418 | ** TH1 command: anoncap STRING... |
| 419 | ** |
| 420 | ** Return true if the current user (hascap) or if the anonymous user |
| 421 | ** (anoncap) has all of the capabilities listed in STRING. |
| 422 | */ |
| 423 | static int hascapCmd( |
| 424 | Th_Interp *interp, |
| 425 | void *p, |
| 426 | int argc, |
| @@ -357,11 +430,11 @@ | |
| 430 | int rc = 0, i; |
| 431 | if( argc<2 ){ |
| 432 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 433 | } |
| 434 | for(i=1; i<argc && rc==0; i++){ |
| 435 | rc = login_has_capability((char*)argv[i],argl[i],*(int*)p); |
| 436 | } |
| 437 | if( g.thTrace ){ |
| 438 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 439 | } |
| 440 | Th_SetResultInt(interp, rc); |
| @@ -543,11 +616,12 @@ | |
| 616 | |
| 617 | |
| 618 | /* |
| 619 | ** TH1 command: anycap STRING |
| 620 | ** |
| 621 | ** Return true if the current user user |
| 622 | ** has any one of the capabilities listed in STRING. |
| 623 | */ |
| 624 | static int anycapCmd( |
| 625 | Th_Interp *interp, |
| 626 | void *p, |
| 627 | int argc, |
| @@ -558,11 +632,11 @@ | |
| 632 | int i; |
| 633 | if( argc!=2 ){ |
| 634 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 635 | } |
| 636 | for(i=0; rc==0 && i<argl[1]; i++){ |
| 637 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 638 | } |
| 639 | if( g.thTrace ){ |
| 640 | Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); |
| 641 | } |
| 642 | Th_SetResultInt(interp, rc); |
| @@ -978,20 +1052,19 @@ | |
| 1052 | } |
| 1053 | if( Th_IsRepositoryOpen() ){ |
| 1054 | int rid; |
| 1055 | Blob content; |
| 1056 | if( argc==3 ){ |
| 1057 | rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]); |
| 1058 | }else{ |
| 1059 | rid = th1_name_to_typed_rid(interp, argv[1], "*"); |
| 1060 | } |
| 1061 | if( rid!=0 && content_get(rid, &content) ){ |
| 1062 | Th_SetResult(interp, blob_str(&content), blob_size(&content)); |
| 1063 | blob_reset(&content); |
| 1064 | return TH_OK; |
| 1065 | }else{ |
| 1066 | return TH_ERROR; |
| 1067 | } |
| 1068 | }else{ |
| 1069 | Th_SetResult(interp, "repository unavailable", -1); |
| 1070 | return TH_ERROR; |
| @@ -1450,15 +1523,18 @@ | |
| 1523 | int needConfig = flags & TH_INIT_NEED_CONFIG; |
| 1524 | int forceReset = flags & TH_INIT_FORCE_RESET; |
| 1525 | int forceTcl = flags & TH_INIT_FORCE_TCL; |
| 1526 | int forceSetup = flags & TH_INIT_FORCE_SETUP; |
| 1527 | static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY }; |
| 1528 | static int anonFlag = LOGIN_ANON; |
| 1529 | static int zeroInt = 0; |
| 1530 | static struct _Command { |
| 1531 | const char *zName; |
| 1532 | Th_CommandProc xProc; |
| 1533 | void *pContext; |
| 1534 | } aCommand[] = { |
| 1535 | {"anoncap", hascapCmd, (void*)&anonFlag}, |
| 1536 | {"anycap", anycapCmd, 0}, |
| 1537 | {"artifact", artifactCmd, 0}, |
| 1538 | {"checkout", checkoutCmd, 0}, |
| 1539 | {"combobox", comboboxCmd, 0}, |
| 1540 | {"date", dateCmd, 0}, |
| @@ -1465,11 +1541,11 @@ | |
| 1541 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 1542 | {"enable_output", enableOutputCmd, 0}, |
| 1543 | {"getParameter", getParameterCmd, 0}, |
| 1544 | {"globalState", globalStateCmd, 0}, |
| 1545 | {"httpize", httpizeCmd, 0}, |
| 1546 | {"hascap", hascapCmd, (void*)&zeroInt}, |
| 1547 | {"hasfeature", hasfeatureCmd, 0}, |
| 1548 | {"html", putsCmd, (void*)&aFlags[0]}, |
| 1549 | {"htmlize", htmlizeCmd, 0}, |
| 1550 | {"http", httpCmd, 0}, |
| 1551 | {"linecount", linecntCmd, 0}, |
| @@ -1952,21 +2028,35 @@ | |
| 2028 | return rc; |
| 2029 | } |
| 2030 | |
| 2031 | /* |
| 2032 | ** COMMAND: test-th-render |
| 2033 | ** |
| 2034 | ** Usage: %fossil test-th-render FILE |
| 2035 | ** |
| 2036 | ** Read the content of the file named "FILE" as if it were a header or |
| 2037 | ** footer or ticket rendering script, evaluate it, and show the results |
| 2038 | ** on standard output. |
| 2039 | ** |
| 2040 | ** Options: |
| 2041 | ** |
| 2042 | ** --cgi Include a CGI response header in the output |
| 2043 | ** --http Include an HTTP response header in the output |
| 2044 | ** --open-config Open the configuration database |
| 2045 | */ |
| 2046 | void test_th_render(void){ |
| 2047 | int forceCgi = 0, fullHttpReply = 0; |
| 2048 | Blob in; |
| 2049 | Th_InitTraceLog(); |
| 2050 | forceCgi = find_option("cgi", 0, 0)!=0; |
| 2051 | fullHttpReply = find_option("http", 0, 0)!=0; |
| 2052 | if( fullHttpReply ) forceCgi = 1; |
| 2053 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 2054 | if( find_option("open-config", 0, 0)!=0 ){ |
| 2055 | Th_OpenConfig(1); |
| 2056 | } |
| 2057 | verify_all_options(); |
| 2058 | if( g.argc<3 ){ |
| 2059 | usage("FILE"); |
| 2060 | } |
| 2061 | blob_zero(&in); |
| 2062 | blob_read_from_file(&in, g.argv[2]); |
| @@ -1975,20 +2065,32 @@ | |
| 2065 | if( forceCgi ) cgi_reply(); |
| 2066 | } |
| 2067 | |
| 2068 | /* |
| 2069 | ** COMMAND: test-th-eval |
| 2070 | ** |
| 2071 | ** Usage: %fossil test-th-eval SCRIPT |
| 2072 | ** |
| 2073 | ** Evaluate SCRIPT as if it were a header or footer or ticket rendering |
| 2074 | ** script, evaluate it, and show the results on standard output. |
| 2075 | ** |
| 2076 | ** Options: |
| 2077 | ** |
| 2078 | ** --cgi Include a CGI response header in the output |
| 2079 | ** --http Include an HTTP response header in the output |
| 2080 | ** --open-config Open the configuration database |
| 2081 | */ |
| 2082 | void test_th_eval(void){ |
| 2083 | int rc; |
| 2084 | const char *zRc; |
| 2085 | int forceCgi, fullHttpReply; |
| 2086 | Th_InitTraceLog(); |
| 2087 | forceCgi = find_option("cgi", 0, 0)!=0; |
| 2088 | fullHttpReply = find_option("http", 0, 0)!=0; |
| 2089 | if( fullHttpReply ) forceCgi = 1; |
| 2090 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 2091 | if( find_option("open-config", 0, 0)!=0 ){ |
| 2092 | Th_OpenConfig(1); |
| 2093 | } |
| 2094 | if( g.argc!=3 ){ |
| 2095 | usage("script"); |
| 2096 | } |
| @@ -2008,12 +2110,13 @@ | |
| 2110 | int rc = TH_OK; |
| 2111 | int nResult = 0; |
| 2112 | char *zResult; |
| 2113 | int forceCgi, fullHttpReply; |
| 2114 | Th_InitTraceLog(); |
| 2115 | forceCgi = find_option("cgi", 0, 0)!=0; |
| 2116 | fullHttpReply = find_option("http", 0, 0)!=0; |
| 2117 | if( fullHttpReply ) forceCgi = 1; |
| 2118 | if( forceCgi ) Th_ForceCgi(fullHttpReply); |
| 2119 | if( g.argc<5 ){ |
| 2120 | usage("TYPE NAME FLAGS"); |
| 2121 | } |
| 2122 | if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ |
| 2123 |
+107
-74
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -21,10 +21,15 @@ | ||
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include <string.h> |
| 23 | 23 | #include <time.h> |
| 24 | 24 | #include "timeline.h" |
| 25 | 25 | |
| 26 | +/* | |
| 27 | +** The value of one second in julianday notation | |
| 28 | +*/ | |
| 29 | +#define ONE_SECOND (1.0/86400.0) | |
| 30 | + | |
| 26 | 31 | /* |
| 27 | 32 | ** Add an appropriate tag to the output if "rid" is unpublished (private) |
| 28 | 33 | */ |
| 29 | 34 | #define UNPUB_TAG "<em>(unpublished)</em>" |
| 30 | 35 | void tag_private_status(int rid){ |
| @@ -36,11 +41,11 @@ | ||
| 36 | 41 | /* |
| 37 | 42 | ** Generate a hyperlink to a version. |
| 38 | 43 | */ |
| 39 | 44 | void hyperlink_to_uuid(const char *zUuid){ |
| 40 | 45 | 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> | |
| 42 | 47 | }else{ |
| 43 | 48 | @ <span class="timelineHistDsp">[%S(zUuid)]</span> |
| 44 | 49 | } |
| 45 | 50 | } |
| 46 | 51 | |
| @@ -158,11 +163,11 @@ | ||
| 158 | 163 | void test_hash_color_page(void){ |
| 159 | 164 | const char *zBr; |
| 160 | 165 | char zNm[10]; |
| 161 | 166 | int i, cnt; |
| 162 | 167 | login_check_credentials(); |
| 163 | - if( !g.perm.Read ){ login_needed(); return; } | |
| 168 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 164 | 169 | |
| 165 | 170 | style_header("Hash Color Test"); |
| 166 | 171 | for(i=cnt=0; i<10; i++){ |
| 167 | 172 | sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); |
| 168 | 173 | zBr = P(zNm); |
| @@ -209,10 +214,11 @@ | ||
| 209 | 214 | void www_print_timeline( |
| 210 | 215 | Stmt *pQuery, /* Query to implement the timeline */ |
| 211 | 216 | int tmFlags, /* Flags controlling display behavior */ |
| 212 | 217 | const char *zThisUser, /* Suppress links to this user */ |
| 213 | 218 | const char *zThisTag, /* Suppress links to this tag */ |
| 219 | + int selectedRid, /* Highlight the line with this RID value */ | |
| 214 | 220 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 215 | 221 | ){ |
| 216 | 222 | int mxWikiLen; |
| 217 | 223 | Blob comment; |
| 218 | 224 | int prevTagid = 0; |
| @@ -289,15 +295,18 @@ | ||
| 289 | 295 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 290 | 296 | suppressCnt = 0; |
| 291 | 297 | } |
| 292 | 298 | if( pendingEndTr ){ |
| 293 | 299 | @ </td></tr> |
| 300 | + if( pendingEndTr>1 ){ | |
| 301 | + @ <tr class="timelineSpacer"></tr> | |
| 302 | + } | |
| 294 | 303 | pendingEndTr = 0; |
| 295 | 304 | } |
| 296 | 305 | if( fossil_strcmp(zType,"div")==0 ){ |
| 297 | 306 | if( !prevWasDivider ){ |
| 298 | - @ <tr><td colspan="3"><hr /></td></tr> | |
| 307 | + @ <tr><td colspan="3"><hr class="timelineMarker"/></td></tr> | |
| 299 | 308 | } |
| 300 | 309 | prevWasDivider = 1; |
| 301 | 310 | continue; |
| 302 | 311 | } |
| 303 | 312 | prevWasDivider = 0; |
| @@ -332,11 +341,16 @@ | ||
| 332 | 341 | zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */ |
| 333 | 342 | zTime[pos++] = 0; |
| 334 | 343 | }else{ |
| 335 | 344 | zTime[0] = 0; |
| 336 | 345 | } |
| 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 ){ | |
| 338 | 352 | @ <tr class="timelineCurrent"> |
| 339 | 353 | }else { |
| 340 | 354 | @ <tr> |
| 341 | 355 | } |
| 342 | 356 | @ <td class="timelineTime">%s(zTime)</td> |
| @@ -379,11 +393,11 @@ | ||
| 379 | 393 | zUuid, isLeaf); |
| 380 | 394 | db_reset(&qbranch); |
| 381 | 395 | @ <div id="m%d(gidx)"></div> |
| 382 | 396 | } |
| 383 | 397 | @</td> |
| 384 | - if( zBgClr && zBgClr[0] ){ | |
| 398 | + if( zBgClr && zBgClr[0] && rid!=selectedRid ){ | |
| 385 | 399 | @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> |
| 386 | 400 | }else{ |
| 387 | 401 | @ <td class="timelineTableCell"> |
| 388 | 402 | } |
| 389 | 403 | if( pGraph && zType[0]!='c' ){ |
| @@ -439,11 +453,11 @@ | ||
| 439 | 453 | @ (user: %h(zDispUser)%s(zTagList?",":"\051") |
| 440 | 454 | } |
| 441 | 455 | |
| 442 | 456 | /* Generate a "detail" link for tags. */ |
| 443 | 457 | 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>] | |
| 445 | 459 | } |
| 446 | 460 | |
| 447 | 461 | /* Generate the "tags: TAGLIST" at the end of the comment, together |
| 448 | 462 | ** with hyperlinks to the tag list. |
| 449 | 463 | */ |
| @@ -508,47 +522,55 @@ | ||
| 508 | 522 | int fid = db_column_int(&fchngQuery, 1); |
| 509 | 523 | int isDel = fid==0; |
| 510 | 524 | const char *zOldName = db_column_text(&fchngQuery, 5); |
| 511 | 525 | const char *zOld = db_column_text(&fchngQuery, 4); |
| 512 | 526 | const char *zNew = db_column_text(&fchngQuery, 3); |
| 513 | - const char *zUnpubTag = ""; | |
| 527 | + const char *zUnpub = ""; | |
| 528 | + char *zA; | |
| 529 | + char zId[20]; | |
| 514 | 530 | if( !inUl ){ |
| 515 | 531 | @ <ul class="filelist"> |
| 516 | 532 | inUl = 1; |
| 533 | + } | |
| 534 | + if( tmFlags & TIMELINE_SHOWRID ){ | |
| 535 | + sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid); | |
| 536 | + }else{ | |
| 537 | + zId[0] = 0; | |
| 517 | 538 | } |
| 518 | 539 | if( (tmFlags & TIMELINE_FRENAMES)!=0 ){ |
| 519 | 540 | if( !isNew && !isDel && zOldName!=0 ){ |
| 520 | - @ <li> %h(zOldName) → %h(zFilename) | |
| 541 | + @ <li> %h(zOldName) → %h(zFilename)%s(zId) | |
| 521 | 542 | } |
| 522 | 543 | continue; |
| 523 | 544 | } |
| 545 | + zA = href("%R/artifact/%!S",fid?zNew:zOld); | |
| 524 | 546 | if( content_is_private(fid) ){ |
| 525 | - zUnpubTag = UNPUB_TAG; | |
| 547 | + zUnpub = UNPUB_TAG; | |
| 526 | 548 | } |
| 527 | 549 | if( isNew ){ |
| 528 | - @ <li> %h(zFilename) %s(zUnpubTag) (new file) | |
| 529 | - @ %z(href("%R/artifact/%s",zNew))[view]</a></li> | |
| 550 | + @ <li> %s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) (new file) | |
| 551 | + @ %z(href("%R/artifact/%!S",zNew))[view]</a></li> | |
| 530 | 552 | }else if( isDel ){ |
| 531 | - @ <li> %h(zFilename) (deleted)</li> | |
| 553 | + @ <li> %s(zA)%h(zFilename)</a> (deleted)</li> | |
| 532 | 554 | }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){ |
| 533 | - @ <li> %h(zOldName) → %h(zFilename) %s(zUnpubTag) | |
| 534 | - @ %z(href("%R/artifact/%s",zNew))[view]</a></li> | |
| 555 | + @ <li> %h(zOldName) → %s(zA)%h(zFilename)</a>%s(zId) | |
| 556 | + @ %s(zUnpub) %z(href("%R/artifact/%!S",zNew))[view]</a></li> | |
| 535 | 557 | }else{ |
| 536 | 558 | if( zOldName!=0 ){ |
| 537 | - @ <li> %h(zOldName) → %h(zFilename) %s(zUnpubTag) | |
| 559 | + @ <li>%h(zOldName) → %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub) | |
| 538 | 560 | }else{ |
| 539 | - @ <li> %h(zFilename) %s(zUnpubTag) | |
| 561 | + @ <li>%s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) | |
| 540 | 562 | } |
| 541 | - @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li> | |
| 563 | + @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zOld,zNew))[diff]</a></li> | |
| 542 | 564 | } |
| 565 | + fossil_free(zA); | |
| 543 | 566 | } |
| 544 | 567 | db_reset(&fchngQuery); |
| 545 | 568 | if( inUl ){ |
| 546 | 569 | @ </ul> |
| 547 | 570 | } |
| 548 | 571 | } |
| 549 | - pendingEndTr = 1; | |
| 550 | 572 | } |
| 551 | 573 | if( suppressCnt ){ |
| 552 | 574 | @ <span class="timelineDisabled">... %d(suppressCnt) similar |
| 553 | 575 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 554 | 576 | suppressCnt = 0; |
| @@ -969,36 +991,23 @@ | ||
| 969 | 991 | } |
| 970 | 992 | return mtime; |
| 971 | 993 | } |
| 972 | 994 | |
| 973 | 995 | /* |
| 974 | -** The value of one second in julianday notation | |
| 975 | -*/ | |
| 976 | -#define ONE_SECOND (1.0/86400.0) | |
| 977 | - | |
| 978 | -/* | |
| 979 | -** zDate is a localtime date. Insert records into the | |
| 980 | -** "timeline" table to cause <hr> to be inserted before and after | |
| 981 | -** entries of that date. If zDate==NULL then put dividers around | |
| 982 | -** the event identified by rid. | |
| 983 | -*/ | |
| 984 | -static void timeline_add_dividers(double rDate, int rid){ | |
| 985 | - char *zToDel = 0; | |
| 986 | - if( rDate==0 ){ | |
| 987 | - rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); | |
| 988 | - } | |
| 989 | - db_multi_exec( | |
| 990 | - "INSERT INTO timeline(rid,sortby,etype)" | |
| 991 | - "VALUES(-1,%.16g,'div')", | |
| 992 | - rDate-ONE_SECOND | |
| 993 | - ); | |
| 994 | - db_multi_exec( | |
| 995 | - "INSERT INTO timeline(rid,sortby,etype)" | |
| 996 | - "VALUES(-2,%.17g,'div')", | |
| 997 | - rDate+ONE_SECOND | |
| 998 | - ); | |
| 999 | - fossil_free(zToDel); | |
| 996 | +** zDate is a localtime date. Insert records into the | |
| 997 | +** "timeline" table to cause <hr> to be inserted on zDate. | |
| 998 | +*/ | |
| 999 | +static int timeline_add_divider(double rDate){ | |
| 1000 | + int rid = db_int(-1, | |
| 1001 | + "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate | |
| 1002 | + ); | |
| 1003 | + if( rid>0 ) return rid; | |
| 1004 | + db_multi_exec( | |
| 1005 | + "INSERT INTO timeline(rid,sortby,etype) VALUES(-1,%.16g,'div')", | |
| 1006 | + rDate | |
| 1007 | + ); | |
| 1008 | + return -1; | |
| 1000 | 1009 | } |
| 1001 | 1010 | |
| 1002 | 1011 | /* |
| 1003 | 1012 | ** Return all possible names for file zUuid. |
| 1004 | 1013 | */ |
| @@ -1027,16 +1036,16 @@ | ||
| 1027 | 1036 | /* |
| 1028 | 1037 | ** Add the select/option box to the timeline submenu that is used to |
| 1029 | 1038 | ** set the y= parameter that determines which elements to display |
| 1030 | 1039 | ** on the timeline. |
| 1031 | 1040 | */ |
| 1032 | -static void timeline_y_submenu(void){ | |
| 1041 | +static void timeline_y_submenu(int isDisabled){ | |
| 1033 | 1042 | static int i = 0; |
| 1034 | 1043 | static const char *az[12]; |
| 1035 | 1044 | if( i==0 ){ |
| 1036 | 1045 | az[0] = "all"; |
| 1037 | - az[1] = "All Types"; | |
| 1046 | + az[1] = "Any Type"; | |
| 1038 | 1047 | i = 2; |
| 1039 | 1048 | if( g.perm.Read ){ |
| 1040 | 1049 | az[i++] = "ci"; |
| 1041 | 1050 | az[i++] = "Check-ins"; |
| 1042 | 1051 | az[i++] = "g"; |
| @@ -1055,11 +1064,11 @@ | ||
| 1055 | 1064 | az[i++] = "Wiki"; |
| 1056 | 1065 | } |
| 1057 | 1066 | assert( i<=ArraySize(az) ); |
| 1058 | 1067 | } |
| 1059 | 1068 | if( i>2 ){ |
| 1060 | - style_submenu_multichoice("y", i/2, az); | |
| 1069 | + style_submenu_multichoice("y", i/2, az, isDisabled); | |
| 1061 | 1070 | } |
| 1062 | 1071 | } |
| 1063 | 1072 | |
| 1064 | 1073 | /* |
| 1065 | 1074 | ** WEBPAGE: timeline |
| @@ -1067,10 +1076,11 @@ | ||
| 1067 | 1076 | ** Query parameters: |
| 1068 | 1077 | ** |
| 1069 | 1078 | ** a=TIMEORTAG after this event |
| 1070 | 1079 | ** b=TIMEORTAG before this event |
| 1071 | 1080 | ** c=TIMEORTAG "circa" this event |
| 1081 | +** m=TIMEORTAG mark this event | |
| 1072 | 1082 | ** n=COUNT max number of events in output |
| 1073 | 1083 | ** p=UUID artifact and up to COUNT parents and ancestors |
| 1074 | 1084 | ** d=UUID artifact and up to COUNT descendants |
| 1075 | 1085 | ** dp=UUID The same as d=UUID&p=UUID |
| 1076 | 1086 | ** t=TAGID show only check-ins with the given tagid |
| @@ -1093,14 +1103,14 @@ | ||
| 1093 | 1103 | ** datefmt=N Override the date format |
| 1094 | 1104 | ** |
| 1095 | 1105 | ** p= and d= can appear individually or together. If either p= or d= |
| 1096 | 1106 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1097 | 1107 | ** |
| 1098 | -** If a= and b= appear, only a= is used. If neither appear, the most | |
| 1099 | -** recent events are chosen. | |
| 1108 | +** If both a= and b= appear then both upper and lower bounds are honored. | |
| 1100 | 1109 | ** |
| 1101 | -** If n= is missing, the default count is 20. | |
| 1110 | +** If n= is missing, the default count is 50 for most queries but | |
| 1111 | +** drops to 11 for c= queries. | |
| 1102 | 1112 | */ |
| 1103 | 1113 | void page_timeline(void){ |
| 1104 | 1114 | Stmt q; /* Query used to generate the timeline */ |
| 1105 | 1115 | Blob sql; /* text of SQL used to generate timeline */ |
| 1106 | 1116 | Blob desc; /* Description of the timeline */ |
| @@ -1111,10 +1121,11 @@ | ||
| 1111 | 1121 | const char *zUser = P("u"); /* All entries by this user if not NULL */ |
| 1112 | 1122 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 1113 | 1123 | const char *zAfter = P("a"); /* Events after this time */ |
| 1114 | 1124 | const char *zBefore = P("b"); /* Events before this time */ |
| 1115 | 1125 | const char *zCirca = P("c"); /* Events near this time */ |
| 1126 | + const char *zMark = P("m"); /* Mark this event or an event this time */ | |
| 1116 | 1127 | const char *zTagName = P("t"); /* Show events with this tag */ |
| 1117 | 1128 | const char *zBrName = P("r"); /* Show events related to this tag */ |
| 1118 | 1129 | const char *zSearch = P("s"); /* Search string */ |
| 1119 | 1130 | const char *zUses = P("uf"); /* Only show checkins hold this file */ |
| 1120 | 1131 | const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */ |
| @@ -1133,10 +1144,12 @@ | ||
| 1133 | 1144 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 1134 | 1145 | int pd_rid; |
| 1135 | 1146 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 1136 | 1147 | const char *z; |
| 1137 | 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 */ | |
| 1138 | 1151 | |
| 1139 | 1152 | /* Set number of rows to display */ |
| 1140 | 1153 | z = P("n"); |
| 1141 | 1154 | if( z ){ |
| 1142 | 1155 | if( fossil_strcmp(z,"all")==0 ){ |
| @@ -1162,11 +1175,11 @@ | ||
| 1162 | 1175 | if( pd_rid ){ |
| 1163 | 1176 | p_rid = d_rid = pd_rid; |
| 1164 | 1177 | } |
| 1165 | 1178 | login_check_credentials(); |
| 1166 | 1179 | if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ |
| 1167 | - login_needed(); | |
| 1180 | + login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); | |
| 1168 | 1181 | return; |
| 1169 | 1182 | } |
| 1170 | 1183 | url_initialize(&url, "timeline"); |
| 1171 | 1184 | cgi_query_parameters_to_url(&url); |
| 1172 | 1185 | if( zTagName && g.perm.Read ){ |
| @@ -1176,14 +1189,18 @@ | ||
| 1176 | 1189 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName); |
| 1177 | 1190 | zThisTag = zBrName; |
| 1178 | 1191 | }else{ |
| 1179 | 1192 | tagid = 0; |
| 1180 | 1193 | } |
| 1194 | + if( zMark && zMark[0]==0 ){ | |
| 1195 | + if( zAfter ) zMark = zAfter; | |
| 1196 | + if( zBefore ) zMark = zBefore; | |
| 1197 | + if( zCirca ) zMark = zCirca; | |
| 1198 | + } | |
| 1181 | 1199 | if( tagid>0 |
| 1182 | 1200 | && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry |
| 1183 | 1201 | ){ |
| 1184 | - zCirca = zBefore = zAfter = 0; | |
| 1185 | 1202 | nEntry = -1; |
| 1186 | 1203 | } |
| 1187 | 1204 | if( zType[0]=='a' ){ |
| 1188 | 1205 | tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 1189 | 1206 | }else{ |
| @@ -1206,10 +1223,11 @@ | ||
| 1206 | 1223 | if( ufid ){ |
| 1207 | 1224 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1208 | 1225 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 1209 | 1226 | compute_uses_file("usesfile", ufid, 0); |
| 1210 | 1227 | zType = "ci"; |
| 1228 | + disableY = 1; | |
| 1211 | 1229 | }else{ |
| 1212 | 1230 | zUses = 0; |
| 1213 | 1231 | } |
| 1214 | 1232 | } |
| 1215 | 1233 | if( renameOnly ){ |
| @@ -1216,10 +1234,11 @@ | ||
| 1216 | 1234 | db_multi_exec( |
| 1217 | 1235 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 1218 | 1236 | "INSERT OR IGNORE INTO rnfile" |
| 1219 | 1237 | " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" |
| 1220 | 1238 | ); |
| 1239 | + disableY = 1; | |
| 1221 | 1240 | } |
| 1222 | 1241 | |
| 1223 | 1242 | style_header("Timeline"); |
| 1224 | 1243 | login_anonymous_available(); |
| 1225 | 1244 | timeline_temp_table(); |
| @@ -1288,11 +1307,11 @@ | ||
| 1288 | 1307 | if( d_rid ){ |
| 1289 | 1308 | compute_descendants(d_rid, nEntry+1); |
| 1290 | 1309 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 1291 | 1310 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 1292 | 1311 | if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 1293 | - if( useDividers ) timeline_add_dividers(0, d_rid); | |
| 1312 | + if( useDividers ) selectedRid = d_rid; | |
| 1294 | 1313 | db_multi_exec("DELETE FROM ok"); |
| 1295 | 1314 | } |
| 1296 | 1315 | if( p_rid ){ |
| 1297 | 1316 | compute_ancestors(p_rid, nEntry+1, 0); |
| 1298 | 1317 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| @@ -1299,23 +1318,24 @@ | ||
| 1299 | 1318 | if( np>0 ){ |
| 1300 | 1319 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 1301 | 1320 | blob_appendf(&desc, "%d ancestors", np); |
| 1302 | 1321 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1303 | 1322 | } |
| 1304 | - if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); | |
| 1323 | + if( useDividers ) selectedRid = p_rid; | |
| 1305 | 1324 | } |
| 1306 | 1325 | blob_appendf(&desc, " of %z[%S]</a>", |
| 1307 | - href("%R/info/%s", zUuid), zUuid); | |
| 1326 | + href("%R/info/%!S", zUuid), zUuid); | |
| 1308 | 1327 | if( d_rid ){ |
| 1309 | 1328 | if( p_rid ){ |
| 1310 | 1329 | /* If both p= and d= are set, we don't have the uuid of d yet. */ |
| 1311 | 1330 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid); |
| 1312 | 1331 | } |
| 1313 | 1332 | } |
| 1314 | - style_submenu_binary("v","With Files","Without Files"); | |
| 1315 | - style_submenu_entry("n","Lines",1); | |
| 1316 | - timeline_y_submenu(); | |
| 1333 | + style_submenu_entry("n","Max:",4,0); | |
| 1334 | + timeline_y_submenu(1); | |
| 1335 | + style_submenu_binary("v","With Files","Without Files", | |
| 1336 | + zType[0]!='a' && zType[0]!='c'); | |
| 1317 | 1337 | }else if( f_rid && g.perm.Read ){ |
| 1318 | 1338 | /* If f= is present, ignore all other parameters other than n= */ |
| 1319 | 1339 | char *zUuid; |
| 1320 | 1340 | db_multi_exec( |
| 1321 | 1341 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" |
| @@ -1324,16 +1344,17 @@ | ||
| 1324 | 1344 | "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", |
| 1325 | 1345 | f_rid, f_rid, f_rid |
| 1326 | 1346 | ); |
| 1327 | 1347 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 1328 | 1348 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1329 | - if( useDividers ) timeline_add_dividers(0, f_rid); | |
| 1349 | + if( useDividers ) selectedRid = f_rid; | |
| 1330 | 1350 | blob_appendf(&desc, "Parents and children of check-in "); |
| 1331 | 1351 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); |
| 1332 | - blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid); | |
| 1352 | + blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid); | |
| 1333 | 1353 | tmFlags |= TIMELINE_DISJOINT; |
| 1334 | - style_submenu_binary("v","With Files","Without Files"); | |
| 1354 | + style_submenu_binary("v","With Files","Without Files", | |
| 1355 | + zType[0]!='a' && zType[0]!='c'); | |
| 1335 | 1356 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1336 | 1357 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1337 | 1358 | } |
| 1338 | 1359 | }else{ |
| 1339 | 1360 | /* Otherwise, a timeline based on a span of time */ |
| @@ -1428,11 +1449,11 @@ | ||
| 1428 | 1449 | }else if( zType[0]=='w' ){ |
| 1429 | 1450 | zEType = "wiki edit"; |
| 1430 | 1451 | }else if( zType[0]=='t' ){ |
| 1431 | 1452 | zEType = "ticket change"; |
| 1432 | 1453 | }else if( zType[0]=='e' ){ |
| 1433 | - zEType = "event"; | |
| 1454 | + zEType = "technical note"; | |
| 1434 | 1455 | }else if( zType[0]=='g' ){ |
| 1435 | 1456 | zEType = "tag"; |
| 1436 | 1457 | } |
| 1437 | 1458 | } |
| 1438 | 1459 | if( zUser ){ |
| @@ -1463,14 +1484,18 @@ | ||
| 1463 | 1484 | }else{ |
| 1464 | 1485 | blob_append_sql(&sql, |
| 1465 | 1486 | " AND event.mtime>=%.17g ORDER BY event.mtime ASC", |
| 1466 | 1487 | rAfter-ONE_SECOND); |
| 1467 | 1488 | } |
| 1489 | + zCirca = 0; | |
| 1490 | + url_add_parameter(&url, "c", 0); | |
| 1468 | 1491 | }else if( rBefore>0.0 ){ |
| 1469 | 1492 | blob_append_sql(&sql, |
| 1470 | 1493 | " AND event.mtime<=%.17g ORDER BY event.mtime DESC", |
| 1471 | 1494 | rBefore+ONE_SECOND); |
| 1495 | + zCirca = 0; | |
| 1496 | + url_add_parameter(&url, "c", 0); | |
| 1472 | 1497 | }else if( rCirca>0.0 ){ |
| 1473 | 1498 | Blob sql2; |
| 1474 | 1499 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 1475 | 1500 | blob_append_sql(&sql2, |
| 1476 | 1501 | " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d", |
| @@ -1481,11 +1506,11 @@ | ||
| 1481 | 1506 | blob_append_sql(&sql, |
| 1482 | 1507 | " AND event.mtime>=%f ORDER BY event.mtime ASC", |
| 1483 | 1508 | rCirca |
| 1484 | 1509 | ); |
| 1485 | 1510 | nEntry -= (nEntry+1)/2; |
| 1486 | - if( useDividers ) timeline_add_dividers(rCirca, 0); | |
| 1511 | + if( zMark==0 ) zMark = zCirca; | |
| 1487 | 1512 | }else{ |
| 1488 | 1513 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| 1489 | 1514 | } |
| 1490 | 1515 | if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry); |
| 1491 | 1516 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -1493,19 +1518,19 @@ | ||
| 1493 | 1518 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 1494 | 1519 | if( zYearMonth ){ |
| 1495 | 1520 | blob_appendf(&desc, "%s events for %h", zEType, zYearMonth); |
| 1496 | 1521 | }else if( zYearWeek ){ |
| 1497 | 1522 | blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek); |
| 1498 | - }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){ | |
| 1523 | + }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){ | |
| 1499 | 1524 | blob_appendf(&desc, "%d most recent %ss", n, zEType); |
| 1500 | 1525 | }else{ |
| 1501 | 1526 | blob_appendf(&desc, "%d %ss", n, zEType); |
| 1502 | 1527 | } |
| 1503 | 1528 | if( zUses ){ |
| 1504 | 1529 | char *zFilenames = names_of_file(zUses); |
| 1505 | 1530 | blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames, |
| 1506 | - href("%R/artifact/%s",zUses), zUses); | |
| 1531 | + href("%R/artifact/%!S",zUses), zUses); | |
| 1507 | 1532 | tmFlags |= TIMELINE_DISJOINT; |
| 1508 | 1533 | } |
| 1509 | 1534 | if( renameOnly ){ |
| 1510 | 1535 | blob_appendf(&desc, " that contain filename changes"); |
| 1511 | 1536 | tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES; |
| @@ -1551,27 +1576,32 @@ | ||
| 1551 | 1576 | if( zType[0]=='a' || zType[0]=='c' ){ |
| 1552 | 1577 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1553 | 1578 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1554 | 1579 | } |
| 1555 | 1580 | } |
| 1556 | - style_submenu_binary("v","With Files","Without Files"); | |
| 1557 | - if( zUses==0 ) timeline_y_submenu(); | |
| 1558 | - style_submenu_entry("n","Lines",1); | |
| 1581 | + style_submenu_entry("n","Max:",4,0); | |
| 1582 | + timeline_y_submenu(disableY); | |
| 1583 | + style_submenu_binary("v","With Files","Without Files", | |
| 1584 | + zType[0]!='a' && zType[0]!='c'); | |
| 1559 | 1585 | } |
| 1560 | 1586 | } |
| 1561 | - if( P("showsql") ){ | |
| 1587 | + if( PB("showsql") ){ | |
| 1562 | 1588 | @ <blockquote>%h(blob_sql_text(&sql))</blockquote> |
| 1563 | 1589 | } |
| 1564 | 1590 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 1565 | 1591 | style_submenu_element("Search", 0, "%R/search?y=c"); |
| 1566 | 1592 | } |
| 1567 | - if( P("showid") ) tmFlags |= TIMELINE_SHOWRID; | |
| 1593 | + if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID; | |
| 1594 | + if( useDividers && zMark && zMark[0] ){ | |
| 1595 | + double r = symbolic_name_to_mtime(zMark); | |
| 1596 | + if( r>0.0 ) selectedRid = timeline_add_divider(r); | |
| 1597 | + } | |
| 1568 | 1598 | blob_zero(&sql); |
| 1569 | 1599 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 1570 | 1600 | @ <h2>%b(&desc)</h2> |
| 1571 | 1601 | blob_reset(&desc); |
| 1572 | - www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0); | |
| 1602 | + www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0); | |
| 1573 | 1603 | db_finalize(&q); |
| 1574 | 1604 | if( zOlderButton ){ |
| 1575 | 1605 | @ %z(xhref("class='button'","%z",zOlderButton))Older</a> |
| 1576 | 1606 | } |
| 1577 | 1607 | style_footer(); |
| @@ -1785,11 +1815,11 @@ | ||
| 1785 | 1815 | ** -p|--path PATH Output items affecting PATH only. |
| 1786 | 1816 | ** PATH can be a file or a sub directory. |
| 1787 | 1817 | ** --offset P skip P changes |
| 1788 | 1818 | ** -t|--type TYPE Output items from the given types only, such as: |
| 1789 | 1819 | ** ci = file commits only |
| 1790 | -** e = events only | |
| 1820 | +** e = technical notes only | |
| 1791 | 1821 | ** t = tickets only |
| 1792 | 1822 | ** w = wiki commits only |
| 1793 | 1823 | ** -v|--verbose Output the list of files changed by each commit |
| 1794 | 1824 | ** and the type of each change (edited, deleted, |
| 1795 | 1825 | ** etc.) after the checkin comment. |
| @@ -2038,11 +2068,14 @@ | ||
| 2038 | 2068 | */ |
| 2039 | 2069 | void test_timewarp_page(void){ |
| 2040 | 2070 | Stmt q; |
| 2041 | 2071 | |
| 2042 | 2072 | login_check_credentials(); |
| 2043 | - if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; } | |
| 2073 | + if( !g.perm.Read || !g.perm.Hyperlink ){ | |
| 2074 | + login_needed(g.anon.Read && g.anon.Hyperlink); | |
| 2075 | + return; | |
| 2076 | + } | |
| 2044 | 2077 | style_header("Instances of timewarp"); |
| 2045 | 2078 | @ <ul> |
| 2046 | 2079 | db_prepare(&q, |
| 2047 | 2080 | "SELECT blob.uuid " |
| 2048 | 2081 | " FROM plink p, plink c, blob" |
| @@ -2050,10 +2083,10 @@ | ||
| 2050 | 2083 | " AND blob.rid=c.cid" |
| 2051 | 2084 | ); |
| 2052 | 2085 | while( db_step(&q)==SQLITE_ROW ){ |
| 2053 | 2086 | const char *zUuid = db_column_text(&q, 0); |
| 2054 | 2087 | @ <li> |
| 2055 | - @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&unhide">%S(zUuid)</a> | |
| 2088 | + @ <a href="%R/timeline?dp=%!S(zUuid)&unhide">%S(zUuid)</a> | |
| 2056 | 2089 | } |
| 2057 | 2090 | db_finalize(&q); |
| 2058 | 2091 | style_footer(); |
| 2059 | 2092 | } |
| 2060 | 2093 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -21,10 +21,15 @@ | |
| 21 | #include "config.h" |
| 22 | #include <string.h> |
| 23 | #include <time.h> |
| 24 | #include "timeline.h" |
| 25 | |
| 26 | /* |
| 27 | ** Add an appropriate tag to the output if "rid" is unpublished (private) |
| 28 | */ |
| 29 | #define UNPUB_TAG "<em>(unpublished)</em>" |
| 30 | void tag_private_status(int rid){ |
| @@ -36,11 +41,11 @@ | |
| 36 | /* |
| 37 | ** Generate a hyperlink to a version. |
| 38 | */ |
| 39 | void hyperlink_to_uuid(const char *zUuid){ |
| 40 | if( g.perm.Hyperlink ){ |
| 41 | @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%S(zUuid)]</a> |
| 42 | }else{ |
| 43 | @ <span class="timelineHistDsp">[%S(zUuid)]</span> |
| 44 | } |
| 45 | } |
| 46 | |
| @@ -158,11 +163,11 @@ | |
| 158 | void test_hash_color_page(void){ |
| 159 | const char *zBr; |
| 160 | char zNm[10]; |
| 161 | int i, cnt; |
| 162 | login_check_credentials(); |
| 163 | if( !g.perm.Read ){ login_needed(); return; } |
| 164 | |
| 165 | style_header("Hash Color Test"); |
| 166 | for(i=cnt=0; i<10; i++){ |
| 167 | sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); |
| 168 | zBr = P(zNm); |
| @@ -209,10 +214,11 @@ | |
| 209 | void www_print_timeline( |
| 210 | Stmt *pQuery, /* Query to implement the timeline */ |
| 211 | int tmFlags, /* Flags controlling display behavior */ |
| 212 | const char *zThisUser, /* Suppress links to this user */ |
| 213 | const char *zThisTag, /* Suppress links to this tag */ |
| 214 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 215 | ){ |
| 216 | int mxWikiLen; |
| 217 | Blob comment; |
| 218 | int prevTagid = 0; |
| @@ -289,15 +295,18 @@ | |
| 289 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 290 | suppressCnt = 0; |
| 291 | } |
| 292 | if( pendingEndTr ){ |
| 293 | @ </td></tr> |
| 294 | pendingEndTr = 0; |
| 295 | } |
| 296 | if( fossil_strcmp(zType,"div")==0 ){ |
| 297 | if( !prevWasDivider ){ |
| 298 | @ <tr><td colspan="3"><hr /></td></tr> |
| 299 | } |
| 300 | prevWasDivider = 1; |
| 301 | continue; |
| 302 | } |
| 303 | prevWasDivider = 0; |
| @@ -332,11 +341,16 @@ | |
| 332 | zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */ |
| 333 | zTime[pos++] = 0; |
| 334 | }else{ |
| 335 | zTime[0] = 0; |
| 336 | } |
| 337 | if( rid == vid ){ |
| 338 | @ <tr class="timelineCurrent"> |
| 339 | }else { |
| 340 | @ <tr> |
| 341 | } |
| 342 | @ <td class="timelineTime">%s(zTime)</td> |
| @@ -379,11 +393,11 @@ | |
| 379 | zUuid, isLeaf); |
| 380 | db_reset(&qbranch); |
| 381 | @ <div id="m%d(gidx)"></div> |
| 382 | } |
| 383 | @</td> |
| 384 | if( zBgClr && zBgClr[0] ){ |
| 385 | @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> |
| 386 | }else{ |
| 387 | @ <td class="timelineTableCell"> |
| 388 | } |
| 389 | if( pGraph && zType[0]!='c' ){ |
| @@ -439,11 +453,11 @@ | |
| 439 | @ (user: %h(zDispUser)%s(zTagList?",":"\051") |
| 440 | } |
| 441 | |
| 442 | /* Generate a "detail" link for tags. */ |
| 443 | if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){ |
| 444 | @ [%z(href("%R/info/%s",zUuid))details</a>] |
| 445 | } |
| 446 | |
| 447 | /* Generate the "tags: TAGLIST" at the end of the comment, together |
| 448 | ** with hyperlinks to the tag list. |
| 449 | */ |
| @@ -508,47 +522,55 @@ | |
| 508 | int fid = db_column_int(&fchngQuery, 1); |
| 509 | int isDel = fid==0; |
| 510 | const char *zOldName = db_column_text(&fchngQuery, 5); |
| 511 | const char *zOld = db_column_text(&fchngQuery, 4); |
| 512 | const char *zNew = db_column_text(&fchngQuery, 3); |
| 513 | const char *zUnpubTag = ""; |
| 514 | if( !inUl ){ |
| 515 | @ <ul class="filelist"> |
| 516 | inUl = 1; |
| 517 | } |
| 518 | if( (tmFlags & TIMELINE_FRENAMES)!=0 ){ |
| 519 | if( !isNew && !isDel && zOldName!=0 ){ |
| 520 | @ <li> %h(zOldName) → %h(zFilename) |
| 521 | } |
| 522 | continue; |
| 523 | } |
| 524 | if( content_is_private(fid) ){ |
| 525 | zUnpubTag = UNPUB_TAG; |
| 526 | } |
| 527 | if( isNew ){ |
| 528 | @ <li> %h(zFilename) %s(zUnpubTag) (new file) |
| 529 | @ %z(href("%R/artifact/%s",zNew))[view]</a></li> |
| 530 | }else if( isDel ){ |
| 531 | @ <li> %h(zFilename) (deleted)</li> |
| 532 | }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){ |
| 533 | @ <li> %h(zOldName) → %h(zFilename) %s(zUnpubTag) |
| 534 | @ %z(href("%R/artifact/%s",zNew))[view]</a></li> |
| 535 | }else{ |
| 536 | if( zOldName!=0 ){ |
| 537 | @ <li> %h(zOldName) → %h(zFilename) %s(zUnpubTag) |
| 538 | }else{ |
| 539 | @ <li> %h(zFilename) %s(zUnpubTag) |
| 540 | } |
| 541 | @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li> |
| 542 | } |
| 543 | } |
| 544 | db_reset(&fchngQuery); |
| 545 | if( inUl ){ |
| 546 | @ </ul> |
| 547 | } |
| 548 | } |
| 549 | pendingEndTr = 1; |
| 550 | } |
| 551 | if( suppressCnt ){ |
| 552 | @ <span class="timelineDisabled">... %d(suppressCnt) similar |
| 553 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 554 | suppressCnt = 0; |
| @@ -969,36 +991,23 @@ | |
| 969 | } |
| 970 | return mtime; |
| 971 | } |
| 972 | |
| 973 | /* |
| 974 | ** The value of one second in julianday notation |
| 975 | */ |
| 976 | #define ONE_SECOND (1.0/86400.0) |
| 977 | |
| 978 | /* |
| 979 | ** zDate is a localtime date. Insert records into the |
| 980 | ** "timeline" table to cause <hr> to be inserted before and after |
| 981 | ** entries of that date. If zDate==NULL then put dividers around |
| 982 | ** the event identified by rid. |
| 983 | */ |
| 984 | static void timeline_add_dividers(double rDate, int rid){ |
| 985 | char *zToDel = 0; |
| 986 | if( rDate==0 ){ |
| 987 | rDate = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 988 | } |
| 989 | db_multi_exec( |
| 990 | "INSERT INTO timeline(rid,sortby,etype)" |
| 991 | "VALUES(-1,%.16g,'div')", |
| 992 | rDate-ONE_SECOND |
| 993 | ); |
| 994 | db_multi_exec( |
| 995 | "INSERT INTO timeline(rid,sortby,etype)" |
| 996 | "VALUES(-2,%.17g,'div')", |
| 997 | rDate+ONE_SECOND |
| 998 | ); |
| 999 | fossil_free(zToDel); |
| 1000 | } |
| 1001 | |
| 1002 | /* |
| 1003 | ** Return all possible names for file zUuid. |
| 1004 | */ |
| @@ -1027,16 +1036,16 @@ | |
| 1027 | /* |
| 1028 | ** Add the select/option box to the timeline submenu that is used to |
| 1029 | ** set the y= parameter that determines which elements to display |
| 1030 | ** on the timeline. |
| 1031 | */ |
| 1032 | static void timeline_y_submenu(void){ |
| 1033 | static int i = 0; |
| 1034 | static const char *az[12]; |
| 1035 | if( i==0 ){ |
| 1036 | az[0] = "all"; |
| 1037 | az[1] = "All Types"; |
| 1038 | i = 2; |
| 1039 | if( g.perm.Read ){ |
| 1040 | az[i++] = "ci"; |
| 1041 | az[i++] = "Check-ins"; |
| 1042 | az[i++] = "g"; |
| @@ -1055,11 +1064,11 @@ | |
| 1055 | az[i++] = "Wiki"; |
| 1056 | } |
| 1057 | assert( i<=ArraySize(az) ); |
| 1058 | } |
| 1059 | if( i>2 ){ |
| 1060 | style_submenu_multichoice("y", i/2, az); |
| 1061 | } |
| 1062 | } |
| 1063 | |
| 1064 | /* |
| 1065 | ** WEBPAGE: timeline |
| @@ -1067,10 +1076,11 @@ | |
| 1067 | ** Query parameters: |
| 1068 | ** |
| 1069 | ** a=TIMEORTAG after this event |
| 1070 | ** b=TIMEORTAG before this event |
| 1071 | ** c=TIMEORTAG "circa" this event |
| 1072 | ** n=COUNT max number of events in output |
| 1073 | ** p=UUID artifact and up to COUNT parents and ancestors |
| 1074 | ** d=UUID artifact and up to COUNT descendants |
| 1075 | ** dp=UUID The same as d=UUID&p=UUID |
| 1076 | ** t=TAGID show only check-ins with the given tagid |
| @@ -1093,14 +1103,14 @@ | |
| 1093 | ** datefmt=N Override the date format |
| 1094 | ** |
| 1095 | ** p= and d= can appear individually or together. If either p= or d= |
| 1096 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1097 | ** |
| 1098 | ** If a= and b= appear, only a= is used. If neither appear, the most |
| 1099 | ** recent events are chosen. |
| 1100 | ** |
| 1101 | ** If n= is missing, the default count is 20. |
| 1102 | */ |
| 1103 | void page_timeline(void){ |
| 1104 | Stmt q; /* Query used to generate the timeline */ |
| 1105 | Blob sql; /* text of SQL used to generate timeline */ |
| 1106 | Blob desc; /* Description of the timeline */ |
| @@ -1111,10 +1121,11 @@ | |
| 1111 | const char *zUser = P("u"); /* All entries by this user if not NULL */ |
| 1112 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 1113 | const char *zAfter = P("a"); /* Events after this time */ |
| 1114 | const char *zBefore = P("b"); /* Events before this time */ |
| 1115 | const char *zCirca = P("c"); /* Events near this time */ |
| 1116 | const char *zTagName = P("t"); /* Show events with this tag */ |
| 1117 | const char *zBrName = P("r"); /* Show events related to this tag */ |
| 1118 | const char *zSearch = P("s"); /* Search string */ |
| 1119 | const char *zUses = P("uf"); /* Only show checkins hold this file */ |
| 1120 | const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */ |
| @@ -1133,10 +1144,12 @@ | |
| 1133 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 1134 | int pd_rid; |
| 1135 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 1136 | const char *z; |
| 1137 | char *zOlderButton = 0; /* URL for Older button at the bottom */ |
| 1138 | |
| 1139 | /* Set number of rows to display */ |
| 1140 | z = P("n"); |
| 1141 | if( z ){ |
| 1142 | if( fossil_strcmp(z,"all")==0 ){ |
| @@ -1162,11 +1175,11 @@ | |
| 1162 | if( pd_rid ){ |
| 1163 | p_rid = d_rid = pd_rid; |
| 1164 | } |
| 1165 | login_check_credentials(); |
| 1166 | if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ |
| 1167 | login_needed(); |
| 1168 | return; |
| 1169 | } |
| 1170 | url_initialize(&url, "timeline"); |
| 1171 | cgi_query_parameters_to_url(&url); |
| 1172 | if( zTagName && g.perm.Read ){ |
| @@ -1176,14 +1189,18 @@ | |
| 1176 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName); |
| 1177 | zThisTag = zBrName; |
| 1178 | }else{ |
| 1179 | tagid = 0; |
| 1180 | } |
| 1181 | if( tagid>0 |
| 1182 | && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry |
| 1183 | ){ |
| 1184 | zCirca = zBefore = zAfter = 0; |
| 1185 | nEntry = -1; |
| 1186 | } |
| 1187 | if( zType[0]=='a' ){ |
| 1188 | tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 1189 | }else{ |
| @@ -1206,10 +1223,11 @@ | |
| 1206 | if( ufid ){ |
| 1207 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1208 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 1209 | compute_uses_file("usesfile", ufid, 0); |
| 1210 | zType = "ci"; |
| 1211 | }else{ |
| 1212 | zUses = 0; |
| 1213 | } |
| 1214 | } |
| 1215 | if( renameOnly ){ |
| @@ -1216,10 +1234,11 @@ | |
| 1216 | db_multi_exec( |
| 1217 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 1218 | "INSERT OR IGNORE INTO rnfile" |
| 1219 | " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" |
| 1220 | ); |
| 1221 | } |
| 1222 | |
| 1223 | style_header("Timeline"); |
| 1224 | login_anonymous_available(); |
| 1225 | timeline_temp_table(); |
| @@ -1288,11 +1307,11 @@ | |
| 1288 | if( d_rid ){ |
| 1289 | compute_descendants(d_rid, nEntry+1); |
| 1290 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 1291 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 1292 | if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 1293 | if( useDividers ) timeline_add_dividers(0, d_rid); |
| 1294 | db_multi_exec("DELETE FROM ok"); |
| 1295 | } |
| 1296 | if( p_rid ){ |
| 1297 | compute_ancestors(p_rid, nEntry+1, 0); |
| 1298 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| @@ -1299,23 +1318,24 @@ | |
| 1299 | if( np>0 ){ |
| 1300 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 1301 | blob_appendf(&desc, "%d ancestors", np); |
| 1302 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1303 | } |
| 1304 | if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); |
| 1305 | } |
| 1306 | blob_appendf(&desc, " of %z[%S]</a>", |
| 1307 | href("%R/info/%s", zUuid), zUuid); |
| 1308 | if( d_rid ){ |
| 1309 | if( p_rid ){ |
| 1310 | /* If both p= and d= are set, we don't have the uuid of d yet. */ |
| 1311 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid); |
| 1312 | } |
| 1313 | } |
| 1314 | style_submenu_binary("v","With Files","Without Files"); |
| 1315 | style_submenu_entry("n","Lines",1); |
| 1316 | timeline_y_submenu(); |
| 1317 | }else if( f_rid && g.perm.Read ){ |
| 1318 | /* If f= is present, ignore all other parameters other than n= */ |
| 1319 | char *zUuid; |
| 1320 | db_multi_exec( |
| 1321 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" |
| @@ -1324,16 +1344,17 @@ | |
| 1324 | "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", |
| 1325 | f_rid, f_rid, f_rid |
| 1326 | ); |
| 1327 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 1328 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1329 | if( useDividers ) timeline_add_dividers(0, f_rid); |
| 1330 | blob_appendf(&desc, "Parents and children of check-in "); |
| 1331 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); |
| 1332 | blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%s", zUuid), zUuid); |
| 1333 | tmFlags |= TIMELINE_DISJOINT; |
| 1334 | style_submenu_binary("v","With Files","Without Files"); |
| 1335 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1336 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1337 | } |
| 1338 | }else{ |
| 1339 | /* Otherwise, a timeline based on a span of time */ |
| @@ -1428,11 +1449,11 @@ | |
| 1428 | }else if( zType[0]=='w' ){ |
| 1429 | zEType = "wiki edit"; |
| 1430 | }else if( zType[0]=='t' ){ |
| 1431 | zEType = "ticket change"; |
| 1432 | }else if( zType[0]=='e' ){ |
| 1433 | zEType = "event"; |
| 1434 | }else if( zType[0]=='g' ){ |
| 1435 | zEType = "tag"; |
| 1436 | } |
| 1437 | } |
| 1438 | if( zUser ){ |
| @@ -1463,14 +1484,18 @@ | |
| 1463 | }else{ |
| 1464 | blob_append_sql(&sql, |
| 1465 | " AND event.mtime>=%.17g ORDER BY event.mtime ASC", |
| 1466 | rAfter-ONE_SECOND); |
| 1467 | } |
| 1468 | }else if( rBefore>0.0 ){ |
| 1469 | blob_append_sql(&sql, |
| 1470 | " AND event.mtime<=%.17g ORDER BY event.mtime DESC", |
| 1471 | rBefore+ONE_SECOND); |
| 1472 | }else if( rCirca>0.0 ){ |
| 1473 | Blob sql2; |
| 1474 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 1475 | blob_append_sql(&sql2, |
| 1476 | " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d", |
| @@ -1481,11 +1506,11 @@ | |
| 1481 | blob_append_sql(&sql, |
| 1482 | " AND event.mtime>=%f ORDER BY event.mtime ASC", |
| 1483 | rCirca |
| 1484 | ); |
| 1485 | nEntry -= (nEntry+1)/2; |
| 1486 | if( useDividers ) timeline_add_dividers(rCirca, 0); |
| 1487 | }else{ |
| 1488 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| 1489 | } |
| 1490 | if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry); |
| 1491 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -1493,19 +1518,19 @@ | |
| 1493 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 1494 | if( zYearMonth ){ |
| 1495 | blob_appendf(&desc, "%s events for %h", zEType, zYearMonth); |
| 1496 | }else if( zYearWeek ){ |
| 1497 | blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek); |
| 1498 | }else if( zAfter==0 && zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){ |
| 1499 | blob_appendf(&desc, "%d most recent %ss", n, zEType); |
| 1500 | }else{ |
| 1501 | blob_appendf(&desc, "%d %ss", n, zEType); |
| 1502 | } |
| 1503 | if( zUses ){ |
| 1504 | char *zFilenames = names_of_file(zUses); |
| 1505 | blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames, |
| 1506 | href("%R/artifact/%s",zUses), zUses); |
| 1507 | tmFlags |= TIMELINE_DISJOINT; |
| 1508 | } |
| 1509 | if( renameOnly ){ |
| 1510 | blob_appendf(&desc, " that contain filename changes"); |
| 1511 | tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES; |
| @@ -1551,27 +1576,32 @@ | |
| 1551 | if( zType[0]=='a' || zType[0]=='c' ){ |
| 1552 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1553 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1554 | } |
| 1555 | } |
| 1556 | style_submenu_binary("v","With Files","Without Files"); |
| 1557 | if( zUses==0 ) timeline_y_submenu(); |
| 1558 | style_submenu_entry("n","Lines",1); |
| 1559 | } |
| 1560 | } |
| 1561 | if( P("showsql") ){ |
| 1562 | @ <blockquote>%h(blob_sql_text(&sql))</blockquote> |
| 1563 | } |
| 1564 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 1565 | style_submenu_element("Search", 0, "%R/search?y=c"); |
| 1566 | } |
| 1567 | if( P("showid") ) tmFlags |= TIMELINE_SHOWRID; |
| 1568 | blob_zero(&sql); |
| 1569 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 1570 | @ <h2>%b(&desc)</h2> |
| 1571 | blob_reset(&desc); |
| 1572 | www_print_timeline(&q, tmFlags, zThisUser, zThisTag, 0); |
| 1573 | db_finalize(&q); |
| 1574 | if( zOlderButton ){ |
| 1575 | @ %z(xhref("class='button'","%z",zOlderButton))Older</a> |
| 1576 | } |
| 1577 | style_footer(); |
| @@ -1785,11 +1815,11 @@ | |
| 1785 | ** -p|--path PATH Output items affecting PATH only. |
| 1786 | ** PATH can be a file or a sub directory. |
| 1787 | ** --offset P skip P changes |
| 1788 | ** -t|--type TYPE Output items from the given types only, such as: |
| 1789 | ** ci = file commits only |
| 1790 | ** e = events only |
| 1791 | ** t = tickets only |
| 1792 | ** w = wiki commits only |
| 1793 | ** -v|--verbose Output the list of files changed by each commit |
| 1794 | ** and the type of each change (edited, deleted, |
| 1795 | ** etc.) after the checkin comment. |
| @@ -2038,11 +2068,14 @@ | |
| 2038 | */ |
| 2039 | void test_timewarp_page(void){ |
| 2040 | Stmt q; |
| 2041 | |
| 2042 | login_check_credentials(); |
| 2043 | if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; } |
| 2044 | style_header("Instances of timewarp"); |
| 2045 | @ <ul> |
| 2046 | db_prepare(&q, |
| 2047 | "SELECT blob.uuid " |
| 2048 | " FROM plink p, plink c, blob" |
| @@ -2050,10 +2083,10 @@ | |
| 2050 | " AND blob.rid=c.cid" |
| 2051 | ); |
| 2052 | while( db_step(&q)==SQLITE_ROW ){ |
| 2053 | const char *zUuid = db_column_text(&q, 0); |
| 2054 | @ <li> |
| 2055 | @ <a href="%s(g.zTop)/timeline?dp=%s(zUuid)&unhide">%S(zUuid)</a> |
| 2056 | } |
| 2057 | db_finalize(&q); |
| 2058 | style_footer(); |
| 2059 | } |
| 2060 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -21,10 +21,15 @@ | |
| 21 | #include "config.h" |
| 22 | #include <string.h> |
| 23 | #include <time.h> |
| 24 | #include "timeline.h" |
| 25 | |
| 26 | /* |
| 27 | ** The value of one second in julianday notation |
| 28 | */ |
| 29 | #define ONE_SECOND (1.0/86400.0) |
| 30 | |
| 31 | /* |
| 32 | ** Add an appropriate tag to the output if "rid" is unpublished (private) |
| 33 | */ |
| 34 | #define UNPUB_TAG "<em>(unpublished)</em>" |
| 35 | void tag_private_status(int rid){ |
| @@ -36,11 +41,11 @@ | |
| 41 | /* |
| 42 | ** Generate a hyperlink to a version. |
| 43 | */ |
| 44 | void hyperlink_to_uuid(const char *zUuid){ |
| 45 | if( g.perm.Hyperlink ){ |
| 46 | @ %z(xhref("class='timelineHistLink'","%R/info/%!S",zUuid))[%S(zUuid)]</a> |
| 47 | }else{ |
| 48 | @ <span class="timelineHistDsp">[%S(zUuid)]</span> |
| 49 | } |
| 50 | } |
| 51 | |
| @@ -158,11 +163,11 @@ | |
| 163 | void test_hash_color_page(void){ |
| 164 | const char *zBr; |
| 165 | char zNm[10]; |
| 166 | int i, cnt; |
| 167 | login_check_credentials(); |
| 168 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 169 | |
| 170 | style_header("Hash Color Test"); |
| 171 | for(i=cnt=0; i<10; i++){ |
| 172 | sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); |
| 173 | zBr = P(zNm); |
| @@ -209,10 +214,11 @@ | |
| 214 | void www_print_timeline( |
| 215 | Stmt *pQuery, /* Query to implement the timeline */ |
| 216 | int tmFlags, /* Flags controlling display behavior */ |
| 217 | const char *zThisUser, /* Suppress links to this user */ |
| 218 | const char *zThisTag, /* Suppress links to this tag */ |
| 219 | int selectedRid, /* Highlight the line with this RID value */ |
| 220 | void (*xExtra)(int) /* Routine to call on each line of display */ |
| 221 | ){ |
| 222 | int mxWikiLen; |
| 223 | Blob comment; |
| 224 | int prevTagid = 0; |
| @@ -289,15 +295,18 @@ | |
| 295 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 296 | suppressCnt = 0; |
| 297 | } |
| 298 | if( pendingEndTr ){ |
| 299 | @ </td></tr> |
| 300 | if( pendingEndTr>1 ){ |
| 301 | @ <tr class="timelineSpacer"></tr> |
| 302 | } |
| 303 | pendingEndTr = 0; |
| 304 | } |
| 305 | if( fossil_strcmp(zType,"div")==0 ){ |
| 306 | if( !prevWasDivider ){ |
| 307 | @ <tr><td colspan="3"><hr class="timelineMarker"/></td></tr> |
| 308 | } |
| 309 | prevWasDivider = 1; |
| 310 | continue; |
| 311 | } |
| 312 | prevWasDivider = 0; |
| @@ -332,11 +341,16 @@ | |
| 341 | zTime[pos++] = zDate[14]; zTime[pos++] = zDate[15]; /* MM */ |
| 342 | zTime[pos++] = 0; |
| 343 | }else{ |
| 344 | zTime[0] = 0; |
| 345 | } |
| 346 | pendingEndTr = 1; |
| 347 | if( rid==selectedRid ){ |
| 348 | @ <tr class="timelineSpacer"></tr> |
| 349 | @ <tr class="timelineSelected"> |
| 350 | pendingEndTr = 2; |
| 351 | }else if( rid==vid ){ |
| 352 | @ <tr class="timelineCurrent"> |
| 353 | }else { |
| 354 | @ <tr> |
| 355 | } |
| 356 | @ <td class="timelineTime">%s(zTime)</td> |
| @@ -379,11 +393,11 @@ | |
| 393 | zUuid, isLeaf); |
| 394 | db_reset(&qbranch); |
| 395 | @ <div id="m%d(gidx)"></div> |
| 396 | } |
| 397 | @</td> |
| 398 | if( zBgClr && zBgClr[0] && rid!=selectedRid ){ |
| 399 | @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> |
| 400 | }else{ |
| 401 | @ <td class="timelineTableCell"> |
| 402 | } |
| 403 | if( pGraph && zType[0]!='c' ){ |
| @@ -439,11 +453,11 @@ | |
| 453 | @ (user: %h(zDispUser)%s(zTagList?",":"\051") |
| 454 | } |
| 455 | |
| 456 | /* Generate a "detail" link for tags. */ |
| 457 | if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){ |
| 458 | @ [%z(href("%R/info/%!S",zUuid))details</a>] |
| 459 | } |
| 460 | |
| 461 | /* Generate the "tags: TAGLIST" at the end of the comment, together |
| 462 | ** with hyperlinks to the tag list. |
| 463 | */ |
| @@ -508,47 +522,55 @@ | |
| 522 | int fid = db_column_int(&fchngQuery, 1); |
| 523 | int isDel = fid==0; |
| 524 | const char *zOldName = db_column_text(&fchngQuery, 5); |
| 525 | const char *zOld = db_column_text(&fchngQuery, 4); |
| 526 | const char *zNew = db_column_text(&fchngQuery, 3); |
| 527 | const char *zUnpub = ""; |
| 528 | char *zA; |
| 529 | char zId[20]; |
| 530 | if( !inUl ){ |
| 531 | @ <ul class="filelist"> |
| 532 | inUl = 1; |
| 533 | } |
| 534 | if( tmFlags & TIMELINE_SHOWRID ){ |
| 535 | sqlite3_snprintf(sizeof(zId), zId, " (%d) ", fid); |
| 536 | }else{ |
| 537 | zId[0] = 0; |
| 538 | } |
| 539 | if( (tmFlags & TIMELINE_FRENAMES)!=0 ){ |
| 540 | if( !isNew && !isDel && zOldName!=0 ){ |
| 541 | @ <li> %h(zOldName) → %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) |
| 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) → %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) → %s(zA)%h(zFilename)%s(zId)</a> %s(zUnpub) |
| 560 | }else{ |
| 561 | @ <li>%s(zA)%h(zFilename)</a>%s(zId) %s(zUnpub) |
| 562 | } |
| 563 | @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zOld,zNew))[diff]</a></li> |
| 564 | } |
| 565 | fossil_free(zA); |
| 566 | } |
| 567 | db_reset(&fchngQuery); |
| 568 | if( inUl ){ |
| 569 | @ </ul> |
| 570 | } |
| 571 | } |
| 572 | } |
| 573 | if( suppressCnt ){ |
| 574 | @ <span class="timelineDisabled">... %d(suppressCnt) similar |
| 575 | @ event%s(suppressCnt>1?"s":"") omitted.</span> |
| 576 | suppressCnt = 0; |
| @@ -969,36 +991,23 @@ | |
| 991 | } |
| 992 | return mtime; |
| 993 | } |
| 994 | |
| 995 | /* |
| 996 | ** zDate is a localtime date. Insert records into the |
| 997 | ** "timeline" table to cause <hr> to be inserted on zDate. |
| 998 | */ |
| 999 | static int timeline_add_divider(double rDate){ |
| 1000 | int rid = db_int(-1, |
| 1001 | "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate |
| 1002 | ); |
| 1003 | if( rid>0 ) return rid; |
| 1004 | db_multi_exec( |
| 1005 | "INSERT INTO timeline(rid,sortby,etype) VALUES(-1,%.16g,'div')", |
| 1006 | rDate |
| 1007 | ); |
| 1008 | return -1; |
| 1009 | } |
| 1010 | |
| 1011 | /* |
| 1012 | ** Return all possible names for file zUuid. |
| 1013 | */ |
| @@ -1027,16 +1036,16 @@ | |
| 1036 | /* |
| 1037 | ** Add the select/option box to the timeline submenu that is used to |
| 1038 | ** set the y= parameter that determines which elements to display |
| 1039 | ** on the timeline. |
| 1040 | */ |
| 1041 | static void timeline_y_submenu(int isDisabled){ |
| 1042 | static int i = 0; |
| 1043 | static const char *az[12]; |
| 1044 | if( i==0 ){ |
| 1045 | az[0] = "all"; |
| 1046 | az[1] = "Any Type"; |
| 1047 | i = 2; |
| 1048 | if( g.perm.Read ){ |
| 1049 | az[i++] = "ci"; |
| 1050 | az[i++] = "Check-ins"; |
| 1051 | az[i++] = "g"; |
| @@ -1055,11 +1064,11 @@ | |
| 1064 | az[i++] = "Wiki"; |
| 1065 | } |
| 1066 | assert( i<=ArraySize(az) ); |
| 1067 | } |
| 1068 | if( i>2 ){ |
| 1069 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1070 | } |
| 1071 | } |
| 1072 | |
| 1073 | /* |
| 1074 | ** WEBPAGE: timeline |
| @@ -1067,10 +1076,11 @@ | |
| 1076 | ** Query parameters: |
| 1077 | ** |
| 1078 | ** a=TIMEORTAG after this event |
| 1079 | ** b=TIMEORTAG before this event |
| 1080 | ** c=TIMEORTAG "circa" this event |
| 1081 | ** m=TIMEORTAG mark this event |
| 1082 | ** n=COUNT max number of events in output |
| 1083 | ** p=UUID artifact and up to COUNT parents and ancestors |
| 1084 | ** d=UUID artifact and up to COUNT descendants |
| 1085 | ** dp=UUID The same as d=UUID&p=UUID |
| 1086 | ** t=TAGID show only check-ins with the given tagid |
| @@ -1093,14 +1103,14 @@ | |
| 1103 | ** datefmt=N Override the date format |
| 1104 | ** |
| 1105 | ** p= and d= can appear individually or together. If either p= or d= |
| 1106 | ** appear, then u=, y=, a=, and b= are ignored. |
| 1107 | ** |
| 1108 | ** If both a= and b= appear then both upper and lower bounds are honored. |
| 1109 | ** |
| 1110 | ** If n= is missing, the default count is 50 for most queries but |
| 1111 | ** drops to 11 for c= queries. |
| 1112 | */ |
| 1113 | void page_timeline(void){ |
| 1114 | Stmt q; /* Query used to generate the timeline */ |
| 1115 | Blob sql; /* text of SQL used to generate timeline */ |
| 1116 | Blob desc; /* Description of the timeline */ |
| @@ -1111,10 +1121,11 @@ | |
| 1121 | const char *zUser = P("u"); /* All entries by this user if not NULL */ |
| 1122 | const char *zType = PD("y","all"); /* Type of events. All if NULL */ |
| 1123 | const char *zAfter = P("a"); /* Events after this time */ |
| 1124 | const char *zBefore = P("b"); /* Events before this time */ |
| 1125 | const char *zCirca = P("c"); /* Events near this time */ |
| 1126 | const char *zMark = P("m"); /* Mark this event or an event this time */ |
| 1127 | const char *zTagName = P("t"); /* Show events with this tag */ |
| 1128 | const char *zBrName = P("r"); /* Show events related to this tag */ |
| 1129 | const char *zSearch = P("s"); /* Search string */ |
| 1130 | const char *zUses = P("uf"); /* Only show checkins hold this file */ |
| 1131 | const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */ |
| @@ -1133,10 +1144,12 @@ | |
| 1144 | int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */ |
| 1145 | int pd_rid; |
| 1146 | double rBefore, rAfter, rCirca; /* Boundary times */ |
| 1147 | const char *z; |
| 1148 | char *zOlderButton = 0; /* URL for Older button at the bottom */ |
| 1149 | int selectedRid = -9999999; /* Show a highlight on this RID */ |
| 1150 | int disableY = 0; /* Disable type selector on submenu */ |
| 1151 | |
| 1152 | /* Set number of rows to display */ |
| 1153 | z = P("n"); |
| 1154 | if( z ){ |
| 1155 | if( fossil_strcmp(z,"all")==0 ){ |
| @@ -1162,11 +1175,11 @@ | |
| 1175 | if( pd_rid ){ |
| 1176 | p_rid = d_rid = pd_rid; |
| 1177 | } |
| 1178 | login_check_credentials(); |
| 1179 | if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ |
| 1180 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1181 | return; |
| 1182 | } |
| 1183 | url_initialize(&url, "timeline"); |
| 1184 | cgi_query_parameters_to_url(&url); |
| 1185 | if( zTagName && g.perm.Read ){ |
| @@ -1176,14 +1189,18 @@ | |
| 1189 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName); |
| 1190 | zThisTag = zBrName; |
| 1191 | }else{ |
| 1192 | tagid = 0; |
| 1193 | } |
| 1194 | if( zMark && zMark[0]==0 ){ |
| 1195 | if( zAfter ) zMark = zAfter; |
| 1196 | if( zBefore ) zMark = zBefore; |
| 1197 | if( zCirca ) zMark = zCirca; |
| 1198 | } |
| 1199 | if( tagid>0 |
| 1200 | && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry |
| 1201 | ){ |
| 1202 | nEntry = -1; |
| 1203 | } |
| 1204 | if( zType[0]=='a' ){ |
| 1205 | tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 1206 | }else{ |
| @@ -1206,10 +1223,11 @@ | |
| 1223 | if( ufid ){ |
| 1224 | zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid); |
| 1225 | db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)"); |
| 1226 | compute_uses_file("usesfile", ufid, 0); |
| 1227 | zType = "ci"; |
| 1228 | disableY = 1; |
| 1229 | }else{ |
| 1230 | zUses = 0; |
| 1231 | } |
| 1232 | } |
| 1233 | if( renameOnly ){ |
| @@ -1216,10 +1234,11 @@ | |
| 1234 | db_multi_exec( |
| 1235 | "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);" |
| 1236 | "INSERT OR IGNORE INTO rnfile" |
| 1237 | " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;" |
| 1238 | ); |
| 1239 | disableY = 1; |
| 1240 | } |
| 1241 | |
| 1242 | style_header("Timeline"); |
| 1243 | login_anonymous_available(); |
| 1244 | timeline_temp_table(); |
| @@ -1288,11 +1307,11 @@ | |
| 1307 | if( d_rid ){ |
| 1308 | compute_descendants(d_rid, nEntry+1); |
| 1309 | nd = db_int(0, "SELECT count(*)-1 FROM ok"); |
| 1310 | if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql)); |
| 1311 | if( nd>0 ) blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s"); |
| 1312 | if( useDividers ) selectedRid = d_rid; |
| 1313 | db_multi_exec("DELETE FROM ok"); |
| 1314 | } |
| 1315 | if( p_rid ){ |
| 1316 | compute_ancestors(p_rid, nEntry+1, 0); |
| 1317 | np = db_int(0, "SELECT count(*)-1 FROM ok"); |
| @@ -1299,23 +1318,24 @@ | |
| 1318 | if( np>0 ){ |
| 1319 | if( nd>0 ) blob_appendf(&desc, " and "); |
| 1320 | blob_appendf(&desc, "%d ancestors", np); |
| 1321 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1322 | } |
| 1323 | if( useDividers ) selectedRid = p_rid; |
| 1324 | } |
| 1325 | blob_appendf(&desc, " of %z[%S]</a>", |
| 1326 | href("%R/info/%!S", zUuid), zUuid); |
| 1327 | if( d_rid ){ |
| 1328 | if( p_rid ){ |
| 1329 | /* If both p= and d= are set, we don't have the uuid of d yet. */ |
| 1330 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid); |
| 1331 | } |
| 1332 | } |
| 1333 | style_submenu_entry("n","Max:",4,0); |
| 1334 | timeline_y_submenu(1); |
| 1335 | style_submenu_binary("v","With Files","Without Files", |
| 1336 | zType[0]!='a' && zType[0]!='c'); |
| 1337 | }else if( f_rid && g.perm.Read ){ |
| 1338 | /* If f= is present, ignore all other parameters other than n= */ |
| 1339 | char *zUuid; |
| 1340 | db_multi_exec( |
| 1341 | "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" |
| @@ -1324,16 +1344,17 @@ | |
| 1344 | "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", |
| 1345 | f_rid, f_rid, f_rid |
| 1346 | ); |
| 1347 | blob_append_sql(&sql, " AND event.objid IN ok"); |
| 1348 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1349 | if( useDividers ) selectedRid = f_rid; |
| 1350 | blob_appendf(&desc, "Parents and children of check-in "); |
| 1351 | zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); |
| 1352 | blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid); |
| 1353 | tmFlags |= TIMELINE_DISJOINT; |
| 1354 | style_submenu_binary("v","With Files","Without Files", |
| 1355 | zType[0]!='a' && zType[0]!='c'); |
| 1356 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1357 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1358 | } |
| 1359 | }else{ |
| 1360 | /* Otherwise, a timeline based on a span of time */ |
| @@ -1428,11 +1449,11 @@ | |
| 1449 | }else if( zType[0]=='w' ){ |
| 1450 | zEType = "wiki edit"; |
| 1451 | }else if( zType[0]=='t' ){ |
| 1452 | zEType = "ticket change"; |
| 1453 | }else if( zType[0]=='e' ){ |
| 1454 | zEType = "technical note"; |
| 1455 | }else if( zType[0]=='g' ){ |
| 1456 | zEType = "tag"; |
| 1457 | } |
| 1458 | } |
| 1459 | if( zUser ){ |
| @@ -1463,14 +1484,18 @@ | |
| 1484 | }else{ |
| 1485 | blob_append_sql(&sql, |
| 1486 | " AND event.mtime>=%.17g ORDER BY event.mtime ASC", |
| 1487 | rAfter-ONE_SECOND); |
| 1488 | } |
| 1489 | zCirca = 0; |
| 1490 | url_add_parameter(&url, "c", 0); |
| 1491 | }else if( rBefore>0.0 ){ |
| 1492 | blob_append_sql(&sql, |
| 1493 | " AND event.mtime<=%.17g ORDER BY event.mtime DESC", |
| 1494 | rBefore+ONE_SECOND); |
| 1495 | zCirca = 0; |
| 1496 | url_add_parameter(&url, "c", 0); |
| 1497 | }else if( rCirca>0.0 ){ |
| 1498 | Blob sql2; |
| 1499 | blob_init(&sql2, blob_sql_text(&sql), -1); |
| 1500 | blob_append_sql(&sql2, |
| 1501 | " AND event.mtime<=%f ORDER BY event.mtime DESC LIMIT %d", |
| @@ -1481,11 +1506,11 @@ | |
| 1506 | blob_append_sql(&sql, |
| 1507 | " AND event.mtime>=%f ORDER BY event.mtime ASC", |
| 1508 | rCirca |
| 1509 | ); |
| 1510 | nEntry -= (nEntry+1)/2; |
| 1511 | if( zMark==0 ) zMark = zCirca; |
| 1512 | }else{ |
| 1513 | blob_append_sql(&sql, " ORDER BY event.mtime DESC"); |
| 1514 | } |
| 1515 | if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry); |
| 1516 | db_multi_exec("%s", blob_sql_text(&sql)); |
| @@ -1493,19 +1518,19 @@ | |
| 1518 | n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/"); |
| 1519 | if( zYearMonth ){ |
| 1520 | blob_appendf(&desc, "%s events for %h", zEType, zYearMonth); |
| 1521 | }else if( zYearWeek ){ |
| 1522 | blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek); |
| 1523 | }else if( zBefore==0 && zCirca==0 && n>=nEntry && nEntry>0 ){ |
| 1524 | blob_appendf(&desc, "%d most recent %ss", n, zEType); |
| 1525 | }else{ |
| 1526 | blob_appendf(&desc, "%d %ss", n, zEType); |
| 1527 | } |
| 1528 | if( zUses ){ |
| 1529 | char *zFilenames = names_of_file(zUses); |
| 1530 | blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames, |
| 1531 | href("%R/artifact/%!S",zUses), zUses); |
| 1532 | tmFlags |= TIMELINE_DISJOINT; |
| 1533 | } |
| 1534 | if( renameOnly ){ |
| 1535 | blob_appendf(&desc, " that contain filename changes"); |
| 1536 | tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES; |
| @@ -1551,27 +1576,32 @@ | |
| 1576 | if( zType[0]=='a' || zType[0]=='c' ){ |
| 1577 | if( (tmFlags & TIMELINE_UNHIDE)==0 ){ |
| 1578 | timeline_submenu(&url, "Unhide", "unhide", "", 0); |
| 1579 | } |
| 1580 | } |
| 1581 | style_submenu_entry("n","Max:",4,0); |
| 1582 | timeline_y_submenu(disableY); |
| 1583 | style_submenu_binary("v","With Files","Without Files", |
| 1584 | zType[0]!='a' && zType[0]!='c'); |
| 1585 | } |
| 1586 | } |
| 1587 | if( PB("showsql") ){ |
| 1588 | @ <blockquote>%h(blob_sql_text(&sql))</blockquote> |
| 1589 | } |
| 1590 | if( search_restrict(SRCH_CKIN)!=0 ){ |
| 1591 | style_submenu_element("Search", 0, "%R/search?y=c"); |
| 1592 | } |
| 1593 | if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID; |
| 1594 | if( useDividers && zMark && zMark[0] ){ |
| 1595 | double r = symbolic_name_to_mtime(zMark); |
| 1596 | if( r>0.0 ) selectedRid = timeline_add_divider(r); |
| 1597 | } |
| 1598 | blob_zero(&sql); |
| 1599 | db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/"); |
| 1600 | @ <h2>%b(&desc)</h2> |
| 1601 | blob_reset(&desc); |
| 1602 | www_print_timeline(&q, tmFlags, zThisUser, zThisTag, selectedRid, 0); |
| 1603 | db_finalize(&q); |
| 1604 | if( zOlderButton ){ |
| 1605 | @ %z(xhref("class='button'","%z",zOlderButton))Older</a> |
| 1606 | } |
| 1607 | style_footer(); |
| @@ -1785,11 +1815,11 @@ | |
| 1815 | ** -p|--path PATH Output items affecting PATH only. |
| 1816 | ** PATH can be a file or a sub directory. |
| 1817 | ** --offset P skip P changes |
| 1818 | ** -t|--type TYPE Output items from the given types only, such as: |
| 1819 | ** ci = file commits only |
| 1820 | ** e = technical notes only |
| 1821 | ** t = tickets only |
| 1822 | ** w = wiki commits only |
| 1823 | ** -v|--verbose Output the list of files changed by each commit |
| 1824 | ** and the type of each change (edited, deleted, |
| 1825 | ** etc.) after the checkin comment. |
| @@ -2038,11 +2068,14 @@ | |
| 2068 | */ |
| 2069 | void test_timewarp_page(void){ |
| 2070 | Stmt q; |
| 2071 | |
| 2072 | login_check_credentials(); |
| 2073 | if( !g.perm.Read || !g.perm.Hyperlink ){ |
| 2074 | login_needed(g.anon.Read && g.anon.Hyperlink); |
| 2075 | return; |
| 2076 | } |
| 2077 | style_header("Instances of timewarp"); |
| 2078 | @ <ul> |
| 2079 | db_prepare(&q, |
| 2080 | "SELECT blob.uuid " |
| 2081 | " FROM plink p, plink c, blob" |
| @@ -2050,10 +2083,10 @@ | |
| 2083 | " AND blob.rid=c.cid" |
| 2084 | ); |
| 2085 | while( db_step(&q)==SQLITE_ROW ){ |
| 2086 | const char *zUuid = db_column_text(&q, 0); |
| 2087 | @ <li> |
| 2088 | @ <a href="%R/timeline?dp=%!S(zUuid)&unhide">%S(zUuid)</a> |
| 2089 | } |
| 2090 | db_finalize(&q); |
| 2091 | style_footer(); |
| 2092 | } |
| 2093 |
+23
-14
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -428,11 +428,11 @@ | ||
| 428 | 428 | @ <font color="blue"> |
| 429 | 429 | @ <p>Database fields:</p><ul> |
| 430 | 430 | for(i=0; i<nField; i++){ |
| 431 | 431 | @ <li>aField[%d(i)].zName = "%h(aField[i].zName)"; |
| 432 | 432 | @ originally = "%h(aField[i].zValue)"; |
| 433 | - @ currently = "%h(PD(aField[i].zName,""))""; | |
| 433 | + @ currently = "%h(PD(aField[i].zName,""))"; | |
| 434 | 434 | if( aField[i].zAppend ){ |
| 435 | 435 | @ zAppend = "%h(aField[i].zAppend)"; |
| 436 | 436 | } |
| 437 | 437 | @ mUsed = %d(aField[i].mUsed); |
| 438 | 438 | } |
| @@ -449,12 +449,12 @@ | ||
| 449 | 449 | const char *zScript; |
| 450 | 450 | char *zFullName; |
| 451 | 451 | const char *zUuid = PD("name",""); |
| 452 | 452 | |
| 453 | 453 | 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 ){ | |
| 456 | 456 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 457 | 457 | g.zTop, PD("name","")); |
| 458 | 458 | } |
| 459 | 459 | if( g.perm.Hyperlink ){ |
| 460 | 460 | style_submenu_element("History", "History Of This Ticket", |
| @@ -462,15 +462,15 @@ | ||
| 462 | 462 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 463 | 463 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 464 | 464 | style_submenu_element("Check-ins", "Check-ins Of This Ticket", |
| 465 | 465 | "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); |
| 466 | 466 | } |
| 467 | - if( g.perm.NewTkt ){ | |
| 467 | + if( g.anon.NewTkt ){ | |
| 468 | 468 | style_submenu_element("New Ticket", "Create a new ticket", |
| 469 | 469 | "%s/tktnew", g.zTop); |
| 470 | 470 | } |
| 471 | - if( g.perm.ApndTkt && g.perm.Attach ){ | |
| 471 | + if( g.anon.ApndTkt && g.anon.Attach ){ | |
| 472 | 472 | style_submenu_element("Attach", "Add An Attachment", |
| 473 | 473 | "%s/attachadd?tkt=%T&from=%s/tktview/%t", |
| 474 | 474 | g.zTop, zUuid, g.zTop, zUuid); |
| 475 | 475 | } |
| 476 | 476 | if( P("plaintext") ){ |
| @@ -687,11 +687,11 @@ | ||
| 687 | 687 | void tktnew_page(void){ |
| 688 | 688 | const char *zScript; |
| 689 | 689 | char *zNewUuid = 0; |
| 690 | 690 | |
| 691 | 691 | login_check_credentials(); |
| 692 | - if( !g.perm.NewTkt ){ login_needed(); return; } | |
| 692 | + if( !g.perm.NewTkt ){ login_needed(g.anon.NewTkt); return; } | |
| 693 | 693 | if( P("cancel") ){ |
| 694 | 694 | cgi_redirect("home"); |
| 695 | 695 | } |
| 696 | 696 | style_header("New Ticket"); |
| 697 | 697 | ticket_standard_submenu(T_ALL_BUT(T_NEW)); |
| @@ -738,11 +738,14 @@ | ||
| 738 | 738 | int nName; |
| 739 | 739 | const char *zName; |
| 740 | 740 | int nRec; |
| 741 | 741 | |
| 742 | 742 | 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 | + } | |
| 744 | 747 | zName = P("name"); |
| 745 | 748 | if( P("cancel") ){ |
| 746 | 749 | cgi_redirectf("tktview?name=%T", zName); |
| 747 | 750 | } |
| 748 | 751 | style_header("Edit Ticket"); |
| @@ -839,11 +842,14 @@ | ||
| 839 | 842 | int tagid; |
| 840 | 843 | char zGlobPattern[50]; |
| 841 | 844 | const char *zType; |
| 842 | 845 | |
| 843 | 846 | 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 | + } | |
| 845 | 851 | zUuid = PD("name",""); |
| 846 | 852 | zType = PD("y","a"); |
| 847 | 853 | if( zType[0]!='c' ){ |
| 848 | 854 | style_submenu_element("Check-ins", "Check-ins", |
| 849 | 855 | "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); |
| @@ -893,11 +899,11 @@ | ||
| 893 | 899 | timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid |
| 894 | 900 | ); |
| 895 | 901 | } |
| 896 | 902 | db_prepare(&q, "%z", zSQL/*safe-for-%s*/); |
| 897 | 903 | www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH, |
| 898 | - 0, 0, 0); | |
| 904 | + 0, 0, 0, 0); | |
| 899 | 905 | db_finalize(&q); |
| 900 | 906 | style_footer(); |
| 901 | 907 | } |
| 902 | 908 | |
| 903 | 909 | /* |
| @@ -912,11 +918,14 @@ | ||
| 912 | 918 | const char *zUuid; |
| 913 | 919 | int tagid; |
| 914 | 920 | int nChng = 0; |
| 915 | 921 | |
| 916 | 922 | 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 | + } | |
| 918 | 927 | zUuid = PD("name",""); |
| 919 | 928 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 920 | 929 | style_submenu_element("Status", "Status", |
| 921 | 930 | "%s/info/%s", g.zTop, zUuid); |
| 922 | 931 | style_submenu_element("Check-ins", "Check-ins", |
| @@ -968,22 +977,22 @@ | ||
| 968 | 977 | @ |
| 969 | 978 | @ <li><p>Delete attachment "%h(zFile)" |
| 970 | 979 | }else{ |
| 971 | 980 | @ |
| 972 | 981 | @ <li><p>Add attachment |
| 973 | - @ "%z(href("%R/artifact/%s",zSrc))%s(zFile)</a>" | |
| 982 | + @ "%z(href("%R/artifact/%!S",zSrc))%s(zFile)</a>" | |
| 974 | 983 | } |
| 975 | - @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>] | |
| 984 | + @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] | |
| 976 | 985 | @ (rid %d(rid)) by |
| 977 | 986 | hyperlink_to_user(zUser,zDate," on"); |
| 978 | 987 | hyperlink_to_date(zDate, ".</p>"); |
| 979 | 988 | }else{ |
| 980 | 989 | pTicket = manifest_get(rid, CFTYPE_TICKET, 0); |
| 981 | 990 | if( pTicket ){ |
| 982 | 991 | @ |
| 983 | 992 | @ <li><p>Ticket change |
| 984 | - @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>] | |
| 993 | + @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] | |
| 985 | 994 | @ (rid %d(rid)) by |
| 986 | 995 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 987 | 996 | hyperlink_to_date(zDate, ":"); |
| 988 | 997 | @ </p> |
| 989 | 998 | ticket_output_change_artifact(pTicket, "a"); |
| @@ -1403,11 +1412,11 @@ | ||
| 1403 | 1412 | style_submenu_element("Search","Search","%R/tktsrch"); |
| 1404 | 1413 | } |
| 1405 | 1414 | if( (ok & T_REPLIST)!=0 ){ |
| 1406 | 1415 | style_submenu_element("Reports","Reports","%R/reportlist"); |
| 1407 | 1416 | } |
| 1408 | - if( (ok & T_NEW)!=0 && g.perm.NewTkt ){ | |
| 1417 | + if( (ok & T_NEW)!=0 && g.anon.NewTkt ){ | |
| 1409 | 1418 | style_submenu_element("New","New","%R/tktnew"); |
| 1410 | 1419 | } |
| 1411 | 1420 | } |
| 1412 | 1421 | |
| 1413 | 1422 | /* |
| 1414 | 1423 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -428,11 +428,11 @@ | |
| 428 | @ <font color="blue"> |
| 429 | @ <p>Database fields:</p><ul> |
| 430 | for(i=0; i<nField; i++){ |
| 431 | @ <li>aField[%d(i)].zName = "%h(aField[i].zName)"; |
| 432 | @ originally = "%h(aField[i].zValue)"; |
| 433 | @ currently = "%h(PD(aField[i].zName,""))""; |
| 434 | if( aField[i].zAppend ){ |
| 435 | @ zAppend = "%h(aField[i].zAppend)"; |
| 436 | } |
| 437 | @ mUsed = %d(aField[i].mUsed); |
| 438 | } |
| @@ -449,12 +449,12 @@ | |
| 449 | const char *zScript; |
| 450 | char *zFullName; |
| 451 | const char *zUuid = PD("name",""); |
| 452 | |
| 453 | login_check_credentials(); |
| 454 | if( !g.perm.RdTkt ){ login_needed(); return; } |
| 455 | if( g.perm.WrTkt || g.perm.ApndTkt ){ |
| 456 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 457 | g.zTop, PD("name","")); |
| 458 | } |
| 459 | if( g.perm.Hyperlink ){ |
| 460 | style_submenu_element("History", "History Of This Ticket", |
| @@ -462,15 +462,15 @@ | |
| 462 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 463 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 464 | style_submenu_element("Check-ins", "Check-ins Of This Ticket", |
| 465 | "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); |
| 466 | } |
| 467 | if( g.perm.NewTkt ){ |
| 468 | style_submenu_element("New Ticket", "Create a new ticket", |
| 469 | "%s/tktnew", g.zTop); |
| 470 | } |
| 471 | if( g.perm.ApndTkt && g.perm.Attach ){ |
| 472 | style_submenu_element("Attach", "Add An Attachment", |
| 473 | "%s/attachadd?tkt=%T&from=%s/tktview/%t", |
| 474 | g.zTop, zUuid, g.zTop, zUuid); |
| 475 | } |
| 476 | if( P("plaintext") ){ |
| @@ -687,11 +687,11 @@ | |
| 687 | void tktnew_page(void){ |
| 688 | const char *zScript; |
| 689 | char *zNewUuid = 0; |
| 690 | |
| 691 | login_check_credentials(); |
| 692 | if( !g.perm.NewTkt ){ login_needed(); return; } |
| 693 | if( P("cancel") ){ |
| 694 | cgi_redirect("home"); |
| 695 | } |
| 696 | style_header("New Ticket"); |
| 697 | ticket_standard_submenu(T_ALL_BUT(T_NEW)); |
| @@ -738,11 +738,14 @@ | |
| 738 | int nName; |
| 739 | const char *zName; |
| 740 | int nRec; |
| 741 | |
| 742 | login_check_credentials(); |
| 743 | if( !g.perm.ApndTkt && !g.perm.WrTkt ){ login_needed(); return; } |
| 744 | zName = P("name"); |
| 745 | if( P("cancel") ){ |
| 746 | cgi_redirectf("tktview?name=%T", zName); |
| 747 | } |
| 748 | style_header("Edit Ticket"); |
| @@ -839,11 +842,14 @@ | |
| 839 | int tagid; |
| 840 | char zGlobPattern[50]; |
| 841 | const char *zType; |
| 842 | |
| 843 | login_check_credentials(); |
| 844 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } |
| 845 | zUuid = PD("name",""); |
| 846 | zType = PD("y","a"); |
| 847 | if( zType[0]!='c' ){ |
| 848 | style_submenu_element("Check-ins", "Check-ins", |
| 849 | "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); |
| @@ -893,11 +899,11 @@ | |
| 893 | timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid |
| 894 | ); |
| 895 | } |
| 896 | db_prepare(&q, "%z", zSQL/*safe-for-%s*/); |
| 897 | www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH, |
| 898 | 0, 0, 0); |
| 899 | db_finalize(&q); |
| 900 | style_footer(); |
| 901 | } |
| 902 | |
| 903 | /* |
| @@ -912,11 +918,14 @@ | |
| 912 | const char *zUuid; |
| 913 | int tagid; |
| 914 | int nChng = 0; |
| 915 | |
| 916 | login_check_credentials(); |
| 917 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } |
| 918 | zUuid = PD("name",""); |
| 919 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 920 | style_submenu_element("Status", "Status", |
| 921 | "%s/info/%s", g.zTop, zUuid); |
| 922 | style_submenu_element("Check-ins", "Check-ins", |
| @@ -968,22 +977,22 @@ | |
| 968 | @ |
| 969 | @ <li><p>Delete attachment "%h(zFile)" |
| 970 | }else{ |
| 971 | @ |
| 972 | @ <li><p>Add attachment |
| 973 | @ "%z(href("%R/artifact/%s",zSrc))%s(zFile)</a>" |
| 974 | } |
| 975 | @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>] |
| 976 | @ (rid %d(rid)) by |
| 977 | hyperlink_to_user(zUser,zDate," on"); |
| 978 | hyperlink_to_date(zDate, ".</p>"); |
| 979 | }else{ |
| 980 | pTicket = manifest_get(rid, CFTYPE_TICKET, 0); |
| 981 | if( pTicket ){ |
| 982 | @ |
| 983 | @ <li><p>Ticket change |
| 984 | @ [%z(href("%R/artifact/%s",zChngUuid))%S(zChngUuid)</a>] |
| 985 | @ (rid %d(rid)) by |
| 986 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 987 | hyperlink_to_date(zDate, ":"); |
| 988 | @ </p> |
| 989 | ticket_output_change_artifact(pTicket, "a"); |
| @@ -1403,11 +1412,11 @@ | |
| 1403 | style_submenu_element("Search","Search","%R/tktsrch"); |
| 1404 | } |
| 1405 | if( (ok & T_REPLIST)!=0 ){ |
| 1406 | style_submenu_element("Reports","Reports","%R/reportlist"); |
| 1407 | } |
| 1408 | if( (ok & T_NEW)!=0 && g.perm.NewTkt ){ |
| 1409 | style_submenu_element("New","New","%R/tktnew"); |
| 1410 | } |
| 1411 | } |
| 1412 | |
| 1413 | /* |
| 1414 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -428,11 +428,11 @@ | |
| 428 | @ <font color="blue"> |
| 429 | @ <p>Database fields:</p><ul> |
| 430 | for(i=0; i<nField; i++){ |
| 431 | @ <li>aField[%d(i)].zName = "%h(aField[i].zName)"; |
| 432 | @ originally = "%h(aField[i].zValue)"; |
| 433 | @ currently = "%h(PD(aField[i].zName,""))"; |
| 434 | if( aField[i].zAppend ){ |
| 435 | @ zAppend = "%h(aField[i].zAppend)"; |
| 436 | } |
| 437 | @ mUsed = %d(aField[i].mUsed); |
| 438 | } |
| @@ -449,12 +449,12 @@ | |
| 449 | const char *zScript; |
| 450 | char *zFullName; |
| 451 | const char *zUuid = PD("name",""); |
| 452 | |
| 453 | login_check_credentials(); |
| 454 | if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } |
| 455 | if( g.anon.WrTkt || g.anon.ApndTkt ){ |
| 456 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 457 | g.zTop, PD("name","")); |
| 458 | } |
| 459 | if( g.perm.Hyperlink ){ |
| 460 | style_submenu_element("History", "History Of This Ticket", |
| @@ -462,15 +462,15 @@ | |
| 462 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 463 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 464 | style_submenu_element("Check-ins", "Check-ins Of This Ticket", |
| 465 | "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); |
| 466 | } |
| 467 | if( g.anon.NewTkt ){ |
| 468 | style_submenu_element("New Ticket", "Create a new ticket", |
| 469 | "%s/tktnew", g.zTop); |
| 470 | } |
| 471 | if( g.anon.ApndTkt && g.anon.Attach ){ |
| 472 | style_submenu_element("Attach", "Add An Attachment", |
| 473 | "%s/attachadd?tkt=%T&from=%s/tktview/%t", |
| 474 | g.zTop, zUuid, g.zTop, zUuid); |
| 475 | } |
| 476 | if( P("plaintext") ){ |
| @@ -687,11 +687,11 @@ | |
| 687 | void tktnew_page(void){ |
| 688 | const char *zScript; |
| 689 | char *zNewUuid = 0; |
| 690 | |
| 691 | login_check_credentials(); |
| 692 | if( !g.perm.NewTkt ){ login_needed(g.anon.NewTkt); return; } |
| 693 | if( P("cancel") ){ |
| 694 | cgi_redirect("home"); |
| 695 | } |
| 696 | style_header("New Ticket"); |
| 697 | ticket_standard_submenu(T_ALL_BUT(T_NEW)); |
| @@ -738,11 +738,14 @@ | |
| 738 | int nName; |
| 739 | const char *zName; |
| 740 | int nRec; |
| 741 | |
| 742 | login_check_credentials(); |
| 743 | if( !g.perm.ApndTkt && !g.perm.WrTkt ){ |
| 744 | login_needed(g.anon.ApndTkt || g.anon.WrTkt); |
| 745 | return; |
| 746 | } |
| 747 | zName = P("name"); |
| 748 | if( P("cancel") ){ |
| 749 | cgi_redirectf("tktview?name=%T", zName); |
| 750 | } |
| 751 | style_header("Edit Ticket"); |
| @@ -839,11 +842,14 @@ | |
| 842 | int tagid; |
| 843 | char zGlobPattern[50]; |
| 844 | const char *zType; |
| 845 | |
| 846 | login_check_credentials(); |
| 847 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ |
| 848 | login_needed(g.anon.Hyperlink && g.anon.RdTkt); |
| 849 | return; |
| 850 | } |
| 851 | zUuid = PD("name",""); |
| 852 | zType = PD("y","a"); |
| 853 | if( zType[0]!='c' ){ |
| 854 | style_submenu_element("Check-ins", "Check-ins", |
| 855 | "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); |
| @@ -893,11 +899,11 @@ | |
| 899 | timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid |
| 900 | ); |
| 901 | } |
| 902 | db_prepare(&q, "%z", zSQL/*safe-for-%s*/); |
| 903 | www_print_timeline(&q, TIMELINE_ARTID|TIMELINE_DISJOINT|TIMELINE_GRAPH, |
| 904 | 0, 0, 0, 0); |
| 905 | db_finalize(&q); |
| 906 | style_footer(); |
| 907 | } |
| 908 | |
| 909 | /* |
| @@ -912,11 +918,14 @@ | |
| 918 | const char *zUuid; |
| 919 | int tagid; |
| 920 | int nChng = 0; |
| 921 | |
| 922 | login_check_credentials(); |
| 923 | if( !g.perm.Hyperlink || !g.perm.RdTkt ){ |
| 924 | login_needed(g.anon.Hyperlink && g.anon.RdTkt); |
| 925 | return; |
| 926 | } |
| 927 | zUuid = PD("name",""); |
| 928 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 929 | style_submenu_element("Status", "Status", |
| 930 | "%s/info/%s", g.zTop, zUuid); |
| 931 | style_submenu_element("Check-ins", "Check-ins", |
| @@ -968,22 +977,22 @@ | |
| 977 | @ |
| 978 | @ <li><p>Delete attachment "%h(zFile)" |
| 979 | }else{ |
| 980 | @ |
| 981 | @ <li><p>Add attachment |
| 982 | @ "%z(href("%R/artifact/%!S",zSrc))%s(zFile)</a>" |
| 983 | } |
| 984 | @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] |
| 985 | @ (rid %d(rid)) by |
| 986 | hyperlink_to_user(zUser,zDate," on"); |
| 987 | hyperlink_to_date(zDate, ".</p>"); |
| 988 | }else{ |
| 989 | pTicket = manifest_get(rid, CFTYPE_TICKET, 0); |
| 990 | if( pTicket ){ |
| 991 | @ |
| 992 | @ <li><p>Ticket change |
| 993 | @ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>] |
| 994 | @ (rid %d(rid)) by |
| 995 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 996 | hyperlink_to_date(zDate, ":"); |
| 997 | @ </p> |
| 998 | ticket_output_change_artifact(pTicket, "a"); |
| @@ -1403,11 +1412,11 @@ | |
| 1412 | style_submenu_element("Search","Search","%R/tktsrch"); |
| 1413 | } |
| 1414 | if( (ok & T_REPLIST)!=0 ){ |
| 1415 | style_submenu_element("Reports","Reports","%R/reportlist"); |
| 1416 | } |
| 1417 | if( (ok & T_NEW)!=0 && g.anon.NewTkt ){ |
| 1418 | style_submenu_element("New","New","%R/tktnew"); |
| 1419 | } |
| 1420 | } |
| 1421 | |
| 1422 | /* |
| 1423 |
+11
-8
| --- src/tktsetup.c | ||
| +++ src/tktsetup.c | ||
| @@ -27,11 +27,12 @@ | ||
| 27 | 27 | ** WEBPAGE: tktsetup |
| 28 | 28 | */ |
| 29 | 29 | void tktsetup_page(void){ |
| 30 | 30 | login_check_credentials(); |
| 31 | 31 | if( !g.perm.Setup ){ |
| 32 | - login_needed(); | |
| 32 | + login_needed(0); | |
| 33 | + return; | |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | style_header("Ticket Setup"); |
| 36 | 37 | @ <table border="0" cellspacing="20"> |
| 37 | 38 | setup_menu_entry("Table", "tktsetup_tab", |
| @@ -118,13 +119,14 @@ | ||
| 118 | 119 | const char *z; |
| 119 | 120 | int isSubmit; |
| 120 | 121 | |
| 121 | 122 | login_check_credentials(); |
| 122 | 123 | if( !g.perm.Setup ){ |
| 123 | - login_needed(); | |
| 124 | + login_needed(0); | |
| 125 | + return; | |
| 124 | 126 | } |
| 125 | - if( P("setup") ){ | |
| 127 | + if( PB("setup") ){ | |
| 126 | 128 | cgi_redirect("tktsetup"); |
| 127 | 129 | } |
| 128 | 130 | isSubmit = P("submit")!=0; |
| 129 | 131 | z = P("x"); |
| 130 | 132 | if( z==0 ){ |
| @@ -713,11 +715,11 @@ | ||
| 713 | 715 | /* |
| 714 | 716 | ** The default report list page |
| 715 | 717 | */ |
| 716 | 718 | static const char zDefaultReportList[] = |
| 717 | 719 | @ <th1> |
| 718 | -@ if {[hascap n]} { | |
| 720 | +@ if {[anoncap n]} { | |
| 719 | 721 | @ html "<p>Enter a new ticket:</p>" |
| 720 | 722 | @ html "<ul><li><a href='tktnew'>New ticket</a></li></ul>" |
| 721 | 723 | @ } |
| 722 | 724 | @ </th1> |
| 723 | 725 | @ |
| @@ -725,16 +727,16 @@ | ||
| 725 | 727 | @ <ol> |
| 726 | 728 | @ <th1>html $report_items</th1> |
| 727 | 729 | @ </ol> |
| 728 | 730 | @ |
| 729 | 731 | @ <th1> |
| 730 | -@ if {[hascap t q]} { | |
| 732 | +@ if {[anoncap t q]} { | |
| 731 | 733 | @ html "<p>Other options:</p>\n<ul>\n" |
| 732 | -@ if {[hascap t]} { | |
| 734 | +@ if {[anoncap t]} { | |
| 733 | 735 | @ html "<li><a href='rptnew'>New report format</a></li>\n" |
| 734 | 736 | @ } |
| 735 | -@ if {[hascap q]} { | |
| 737 | +@ if {[anoncap q]} { | |
| 736 | 738 | @ html "<li><a href='modreq'>Tend to pending moderation requests</a></li>\n" |
| 737 | 739 | @ } |
| 738 | 740 | @ } |
| 739 | 741 | @ </th1> |
| 740 | 742 | ; |
| @@ -858,11 +860,12 @@ | ||
| 858 | 860 | ** WEBPAGE: tktsetup_timeline |
| 859 | 861 | */ |
| 860 | 862 | void tktsetup_timeline_page(void){ |
| 861 | 863 | login_check_credentials(); |
| 862 | 864 | if( !g.perm.Setup ){ |
| 863 | - login_needed(); | |
| 865 | + login_needed(0); | |
| 866 | + return; | |
| 864 | 867 | } |
| 865 | 868 | |
| 866 | 869 | if( P("setup") ){ |
| 867 | 870 | cgi_redirect("tktsetup"); |
| 868 | 871 | } |
| 869 | 872 |
| --- 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 @@ | ||
| 12 | 12 | ** Author contact information: |
| 13 | 13 | ** [email protected] |
| 14 | 14 | ** http://www.hwaci.com/drh/ |
| 15 | 15 | ** |
| 16 | 16 | ******************************************************************************* |
| 17 | -** | |
| 17 | +** | |
| 18 | 18 | ** This file implements the undo/redo functionality. |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include "undo.h" |
| 22 | 22 | |
| @@ -45,19 +45,19 @@ | ||
| 45 | 45 | int old_link; |
| 46 | 46 | Blob current; |
| 47 | 47 | Blob new; |
| 48 | 48 | zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); |
| 49 | 49 | old_link = db_column_int(&q, 3); |
| 50 | - new_link = file_wd_islink(zFullname); | |
| 51 | 50 | new_exists = file_wd_size(zFullname)>=0; |
| 51 | + new_link = file_wd_islink(0); | |
| 52 | 52 | if( new_exists ){ |
| 53 | 53 | if( new_link ){ |
| 54 | 54 | blob_read_link(¤t, zFullname); |
| 55 | 55 | }else{ |
| 56 | - blob_read_from_file(¤t, zFullname); | |
| 56 | + blob_read_from_file(¤t, zFullname); | |
| 57 | 57 | } |
| 58 | - new_exe = file_wd_isexe(zFullname); | |
| 58 | + new_exe = file_wd_isexe(0); | |
| 59 | 59 | }else{ |
| 60 | 60 | blob_zero(¤t); |
| 61 | 61 | new_exe = 0; |
| 62 | 62 | } |
| 63 | 63 | blob_zero(&new); |
| @@ -86,11 +86,11 @@ | ||
| 86 | 86 | file_delete(zFullname); |
| 87 | 87 | } |
| 88 | 88 | blob_reset(&new); |
| 89 | 89 | free(zFullname); |
| 90 | 90 | db_finalize(&q); |
| 91 | - db_prepare(&q, | |
| 91 | + db_prepare(&q, | |
| 92 | 92 | "UPDATE undo SET content=:c, existsflag=%d, isExe=%d, isLink=%d," |
| 93 | 93 | " redoflag=NOT redoflag" |
| 94 | 94 | " WHERE pathname=%Q", |
| 95 | 95 | new_exists, new_exe, new_link, zPathname |
| 96 | 96 | ); |
| @@ -217,11 +217,11 @@ | ||
| 217 | 217 | ** Begin capturing a snapshot that can be undone. |
| 218 | 218 | */ |
| 219 | 219 | void undo_begin(void){ |
| 220 | 220 | int cid; |
| 221 | 221 | const char *zDb = db_name("localdb"); |
| 222 | - static const char zSql[] = | |
| 222 | + static const char zSql[] = | |
| 223 | 223 | @ CREATE TABLE "%w".undo( |
| 224 | 224 | @ pathname TEXT UNIQUE, -- Name of the file |
| 225 | 225 | @ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable |
| 226 | 226 | @ existsflag BOOLEAN, -- True if the file exists |
| 227 | 227 | @ isExe BOOLEAN, -- True if the file is executable |
| @@ -240,11 +240,11 @@ | ||
| 240 | 240 | db_lset("undo_cmdline", undoCmd); |
| 241 | 241 | undoActive = 1; |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | 244 | /* |
| 245 | -** Permanently disable undo | |
| 245 | +** Permanently disable undo | |
| 246 | 246 | */ |
| 247 | 247 | void undo_disable(void){ |
| 248 | 248 | undoDisable = 1; |
| 249 | 249 | } |
| 250 | 250 | |
| @@ -279,11 +279,11 @@ | ||
| 279 | 279 | " VALUES(%Q,0,%d,%d,%d,:c)", |
| 280 | 280 | zPathname, existsFlag, file_wd_isexe(zFullname), isLink |
| 281 | 281 | ); |
| 282 | 282 | if( existsFlag ){ |
| 283 | 283 | if( isLink ){ |
| 284 | - blob_read_link(&content, zFullname); | |
| 284 | + blob_read_link(&content, zFullname); | |
| 285 | 285 | }else{ |
| 286 | 286 | blob_read_from_file(&content, zFullname); |
| 287 | 287 | } |
| 288 | 288 | db_bind_blob(&q, ":c", &content); |
| 289 | 289 | } |
| @@ -363,11 +363,11 @@ | ||
| 363 | 363 | ** (2) fossil merge (6) fossil stash drop |
| 364 | 364 | ** (3) fossil revert (7) fossil stash goto |
| 365 | 365 | ** (4) fossil stash pop |
| 366 | 366 | ** |
| 367 | 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. | |
| 368 | +** file(s) but otherwise leave the update or merge or revert in effect. | |
| 369 | 369 | ** The redo command undoes the effect of the most recent undo. |
| 370 | 370 | ** |
| 371 | 371 | ** If the -n|--dry-run option is present, no changes are made and instead |
| 372 | 372 | ** the undo or redo command explains what actions the undo or redo would |
| 373 | 373 | ** have done had the -n|--dry-run been omitted. |
| @@ -410,11 +410,11 @@ | ||
| 410 | 410 | if( nChng==0 ){ |
| 411 | 411 | fossil_print("The following file changes would occur if the " |
| 412 | 412 | "command above is %sne:\n\n", zCmd); |
| 413 | 413 | } |
| 414 | 414 | nChng++; |
| 415 | - fossil_print("%s %s\n", | |
| 415 | + fossil_print("%s %s\n", | |
| 416 | 416 | db_column_int(&q,0) ? "UPDATE" : "DELETE", |
| 417 | 417 | db_column_text(&q, 1) |
| 418 | 418 | ); |
| 419 | 419 | } |
| 420 | 420 | db_finalize(&q); |
| 421 | 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_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(¤t, zFullname); |
| 55 | }else{ |
| 56 | blob_read_from_file(¤t, zFullname); |
| 57 | } |
| 58 | new_exe = file_wd_isexe(zFullname); |
| 59 | }else{ |
| 60 | blob_zero(¤t); |
| 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(¤t, zFullname); |
| 55 | }else{ |
| 56 | blob_read_from_file(¤t, zFullname); |
| 57 | } |
| 58 | new_exe = file_wd_isexe(0); |
| 59 | }else{ |
| 60 | blob_zero(¤t); |
| 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 @@ | ||
| 809 | 809 | zFile, zFile |
| 810 | 810 | ); |
| 811 | 811 | }else{ |
| 812 | 812 | sqlite3_int64 mtime; |
| 813 | 813 | 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)) ){ | |
| 815 | 815 | file_delete(zFull); |
| 816 | 816 | } |
| 817 | 817 | if( isLink ){ |
| 818 | 818 | symlink_create(blob_str(&record), zFull); |
| 819 | 819 | }else{ |
| 820 | 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(zFull)) ){ |
| 815 | file_delete(zFull); |
| 816 | } |
| 817 | if( isLink ){ |
| 818 | symlink_create(blob_str(&record), zFull); |
| 819 | }else{ |
| 820 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -809,11 +809,11 @@ | |
| 809 | zFile, zFile |
| 810 | ); |
| 811 | }else{ |
| 812 | sqlite3_int64 mtime; |
| 813 | undo_save(zFile); |
| 814 | if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ){ |
| 815 | file_delete(zFull); |
| 816 | } |
| 817 | if( isLink ){ |
| 818 | symlink_create(blob_str(&record), zFull); |
| 819 | }else{ |
| 820 |
+9
-2
| --- src/url.c | ||
| +++ src/url.c | ||
| @@ -460,21 +460,28 @@ | ||
| 460 | 460 | fossil_free(p->azValue); |
| 461 | 461 | url_initialize(p, p->zBase); |
| 462 | 462 | } |
| 463 | 463 | |
| 464 | 464 | /* |
| 465 | -** Add a fixed parameter to an HQuery. | |
| 465 | +** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0. | |
| 466 | 466 | */ |
| 467 | 467 | void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ |
| 468 | 468 | int i; |
| 469 | 469 | for(i=0; i<p->nParam; i++){ |
| 470 | 470 | if( fossil_strcmp(p->azName[i],zName)==0 ){ |
| 471 | - p->azValue[i] = zValue; | |
| 471 | + if( zValue==0 ){ | |
| 472 | + p->nParam--; | |
| 473 | + p->azValue[i] = p->azValue[p->nParam]; | |
| 474 | + p->azName[i] = p->azName[p->nParam]; | |
| 475 | + }else{ | |
| 476 | + p->azValue[i] = zValue; | |
| 477 | + } | |
| 472 | 478 | return; |
| 473 | 479 | } |
| 474 | 480 | } |
| 475 | 481 | assert( i==p->nParam ); |
| 482 | + if( zValue==0 ) return; | |
| 476 | 483 | if( i>=p->nAlloc ){ |
| 477 | 484 | p->nAlloc = p->nAlloc*2 + 10; |
| 478 | 485 | p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc); |
| 479 | 486 | p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc); |
| 480 | 487 | } |
| 481 | 488 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -460,21 +460,28 @@ | |
| 460 | fossil_free(p->azValue); |
| 461 | url_initialize(p, p->zBase); |
| 462 | } |
| 463 | |
| 464 | /* |
| 465 | ** Add a fixed parameter to an HQuery. |
| 466 | */ |
| 467 | void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ |
| 468 | int i; |
| 469 | for(i=0; i<p->nParam; i++){ |
| 470 | if( fossil_strcmp(p->azName[i],zName)==0 ){ |
| 471 | p->azValue[i] = zValue; |
| 472 | return; |
| 473 | } |
| 474 | } |
| 475 | assert( i==p->nParam ); |
| 476 | if( i>=p->nAlloc ){ |
| 477 | p->nAlloc = p->nAlloc*2 + 10; |
| 478 | p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc); |
| 479 | p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc); |
| 480 | } |
| 481 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -460,21 +460,28 @@ | |
| 460 | fossil_free(p->azValue); |
| 461 | url_initialize(p, p->zBase); |
| 462 | } |
| 463 | |
| 464 | /* |
| 465 | ** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0. |
| 466 | */ |
| 467 | void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ |
| 468 | int i; |
| 469 | for(i=0; i<p->nParam; i++){ |
| 470 | if( fossil_strcmp(p->azName[i],zName)==0 ){ |
| 471 | if( zValue==0 ){ |
| 472 | p->nParam--; |
| 473 | p->azValue[i] = p->azValue[p->nParam]; |
| 474 | p->azName[i] = p->azName[p->nParam]; |
| 475 | }else{ |
| 476 | p->azValue[i] = zValue; |
| 477 | } |
| 478 | return; |
| 479 | } |
| 480 | } |
| 481 | assert( i==p->nParam ); |
| 482 | if( zValue==0 ) return; |
| 483 | if( i>=p->nAlloc ){ |
| 484 | p->nAlloc = p->nAlloc*2 + 10; |
| 485 | p->azName = fossil_realloc(p->azName, sizeof(p->azName[0])*p->nAlloc); |
| 486 | p->azValue = fossil_realloc(p->azValue, sizeof(p->azValue[0])*p->nAlloc); |
| 487 | } |
| 488 |
+1
-1
| --- src/user.c | ||
| +++ src/user.c | ||
| @@ -424,11 +424,11 @@ | ||
| 424 | 424 | Stmt q; |
| 425 | 425 | int cnt = 0; |
| 426 | 426 | int rc; |
| 427 | 427 | |
| 428 | 428 | login_check_credentials(); |
| 429 | - if( !g.perm.Admin ){ login_needed(); return; } | |
| 429 | + if( !g.perm.Admin ){ login_needed(0); return; } | |
| 430 | 430 | create_accesslog_table(); |
| 431 | 431 | |
| 432 | 432 | if( P("delall") && P("delallbtn") ){ |
| 433 | 433 | db_multi_exec("DELETE FROM accesslog"); |
| 434 | 434 | cgi_redirectf("%s/access_log?y=%d&n=%d&o=%o", g.zTop, y, n, skip); |
| 435 | 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(); 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 @@ | ||
| 189 | 189 | zName = db_column_text(&q, 1); |
| 190 | 190 | rid = db_column_int(&q, 2); |
| 191 | 191 | isDeleted = db_column_int(&q, 3); |
| 192 | 192 | oldChnged = chnged = db_column_int(&q, 4); |
| 193 | 193 | oldMtime = db_column_int64(&q, 7); |
| 194 | - currentSize = file_wd_size(zName); | |
| 195 | 194 | origSize = db_column_int64(&q, 6); |
| 195 | + currentSize = file_wd_size(zName); | |
| 196 | 196 | currentMtime = file_wd_mtime(0); |
| 197 | 197 | if( chnged==0 && (isDeleted || rid==0) ){ |
| 198 | 198 | /* "fossil rm" or "fossil add" always change the file */ |
| 199 | 199 | chnged = 1; |
| 200 | 200 | }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){ |
| @@ -320,11 +320,11 @@ | ||
| 320 | 320 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 321 | 321 | if( file_wd_isdir(zName) == 1 ){ |
| 322 | 322 | /*TODO(dchest): remove directories? */ |
| 323 | 323 | fossil_fatal("%s is directory, cannot overwrite\n", zName); |
| 324 | 324 | } |
| 325 | - if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ | |
| 325 | + if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){ | |
| 326 | 326 | file_delete(zName); |
| 327 | 327 | } |
| 328 | 328 | if( isLink ){ |
| 329 | 329 | symlink_create(blob_str(&content), zName); |
| 330 | 330 | }else{ |
| 331 | 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 | currentSize = file_wd_size(zName); |
| 195 | origSize = db_column_int64(&q, 6); |
| 196 | currentMtime = file_wd_mtime(0); |
| 197 | if( chnged==0 && (isDeleted || rid==0) ){ |
| 198 | /* "fossil rm" or "fossil add" always change the file */ |
| 199 | chnged = 1; |
| 200 | }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){ |
| @@ -320,11 +320,11 @@ | |
| 320 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 321 | if( file_wd_isdir(zName) == 1 ){ |
| 322 | /*TODO(dchest): remove directories? */ |
| 323 | fossil_fatal("%s is directory, cannot overwrite\n", zName); |
| 324 | } |
| 325 | if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ |
| 326 | file_delete(zName); |
| 327 | } |
| 328 | if( isLink ){ |
| 329 | symlink_create(blob_str(&content), zName); |
| 330 | }else{ |
| 331 |
| --- src/vfile.c | |
| +++ src/vfile.c | |
| @@ -189,12 +189,12 @@ | |
| 189 | zName = db_column_text(&q, 1); |
| 190 | rid = db_column_int(&q, 2); |
| 191 | isDeleted = db_column_int(&q, 3); |
| 192 | oldChnged = chnged = db_column_int(&q, 4); |
| 193 | oldMtime = db_column_int64(&q, 7); |
| 194 | origSize = db_column_int64(&q, 6); |
| 195 | currentSize = file_wd_size(zName); |
| 196 | currentMtime = file_wd_mtime(0); |
| 197 | if( chnged==0 && (isDeleted || rid==0) ){ |
| 198 | /* "fossil rm" or "fossil add" always change the file */ |
| 199 | chnged = 1; |
| 200 | }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){ |
| @@ -320,11 +320,11 @@ | |
| 320 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 321 | if( file_wd_isdir(zName) == 1 ){ |
| 322 | /*TODO(dchest): remove directories? */ |
| 323 | fossil_fatal("%s is directory, cannot overwrite\n", zName); |
| 324 | } |
| 325 | if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){ |
| 326 | file_delete(zName); |
| 327 | } |
| 328 | if( isLink ){ |
| 329 | symlink_create(blob_str(&content), zName); |
| 330 | }else{ |
| 331 |
+28
-23
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -134,11 +134,15 @@ | ||
| 134 | 134 | } |
| 135 | 135 | return "text/x-fossil-wiki"; |
| 136 | 136 | } |
| 137 | 137 | |
| 138 | 138 | /* |
| 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 | |
| 140 | 144 | */ |
| 141 | 145 | void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ |
| 142 | 146 | if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 143 | 147 | wiki_convert(pWiki, 0, 0); |
| 144 | 148 | }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| @@ -222,11 +226,11 @@ | ||
| 222 | 226 | style_submenu_element("List","List","%R/wcontent"); |
| 223 | 227 | } |
| 224 | 228 | if( (ok & W_HELP)!=0 ){ |
| 225 | 229 | style_submenu_element("Help","Help","%R/wikihelp"); |
| 226 | 230 | } |
| 227 | - if( (ok & W_NEW)!=0 && g.perm.NewWiki ){ | |
| 231 | + if( (ok & W_NEW)!=0 && g.anon.NewWiki ){ | |
| 228 | 232 | style_submenu_element("New","New","%R/wikinew"); |
| 229 | 233 | } |
| 230 | 234 | #if 0 |
| 231 | 235 | if( (ok & W_BLOG)!=0 |
| 232 | 236 | #endif |
| @@ -239,11 +243,11 @@ | ||
| 239 | 243 | ** WEBPAGE: wikihelp |
| 240 | 244 | ** A generic landing page for wiki. |
| 241 | 245 | */ |
| 242 | 246 | void wiki_helppage(void){ |
| 243 | 247 | login_check_credentials(); |
| 244 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 248 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 245 | 249 | style_header("Wiki Help"); |
| 246 | 250 | wiki_standard_submenu(W_ALL_BUT(W_HELP)); |
| 247 | 251 | @ <h2>Wiki Links</h2> |
| 248 | 252 | @ <ul> |
| 249 | 253 | { char *zWikiHomePageName = db_get("index-page",0); |
| @@ -261,19 +265,19 @@ | ||
| 261 | 265 | @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
| 262 | 266 | @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
| 263 | 267 | @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
| 264 | 268 | @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> |
| 265 | 269 | @ to experiment.</li> |
| 266 | - if( g.perm.NewWiki ){ | |
| 270 | + if( g.anon.NewWiki ){ | |
| 267 | 271 | @ <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> | |
| 270 | 274 | } |
| 271 | 275 | } |
| 272 | 276 | @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
| 273 | 277 | @ available on this server.</li> |
| 274 | - if( g.perm.ModWiki ){ | |
| 278 | + if( g.anon.ModWiki ){ | |
| 275 | 279 | @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
| 276 | 280 | } |
| 277 | 281 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 278 | 282 | @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key |
| 279 | 283 | @ words</li> |
| @@ -312,11 +316,11 @@ | ||
| 312 | 316 | const char *zPageName; |
| 313 | 317 | const char *zMimetype = 0; |
| 314 | 318 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 315 | 319 | |
| 316 | 320 | login_check_credentials(); |
| 317 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 321 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 318 | 322 | zPageName = P("name"); |
| 319 | 323 | if( zPageName==0 ){ |
| 320 | 324 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 321 | 325 | wiki_srchpage(); |
| 322 | 326 | }else{ |
| @@ -355,11 +359,11 @@ | ||
| 355 | 359 | "%R/wdiff?name=%T&a=%d", zPageName, rid); |
| 356 | 360 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 357 | 361 | style_submenu_element("Details", "Details", |
| 358 | 362 | "%R/info/%s", zUuid); |
| 359 | 363 | } |
| 360 | - if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ | |
| 364 | + if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){ | |
| 361 | 365 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 362 | 366 | style_submenu_element("Edit", "Edit Wiki Page", |
| 363 | 367 | "%s/wikiedit?name=%T&wysiwyg=1", |
| 364 | 368 | g.zTop, zPageName); |
| 365 | 369 | }else{ |
| @@ -366,16 +370,16 @@ | ||
| 366 | 370 | style_submenu_element("Edit", "Edit Wiki Page", |
| 367 | 371 | "%s/wikiedit?name=%T", |
| 368 | 372 | g.zTop, zPageName); |
| 369 | 373 | } |
| 370 | 374 | } |
| 371 | - if( rid && g.perm.ApndWiki && g.perm.Attach ){ | |
| 375 | + if( rid && g.anon.ApndWiki && g.anon.Attach ){ | |
| 372 | 376 | style_submenu_element("Attach", "Add An Attachment", |
| 373 | 377 | "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", |
| 374 | 378 | g.zTop, zPageName, g.zTop, zPageName); |
| 375 | 379 | } |
| 376 | - if( rid && g.perm.ApndWiki ){ | |
| 380 | + if( rid && g.anon.ApndWiki ){ | |
| 377 | 381 | style_submenu_element("Append", "Add A Comment", |
| 378 | 382 | "%s/wikiappend?name=%T&mimetype=%s", |
| 379 | 383 | g.zTop, zPageName, zMimetype); |
| 380 | 384 | } |
| 381 | 385 | if( g.perm.Hyperlink ){ |
| @@ -423,13 +427,13 @@ | ||
| 423 | 427 | |
| 424 | 428 | /* |
| 425 | 429 | ** Output a selection box from which the user can select the |
| 426 | 430 | ** wiki mimetype. |
| 427 | 431 | */ |
| 428 | -static void mimetype_option_menu(const char *zMimetype){ | |
| 432 | +void mimetype_option_menu(const char *zMimetype){ | |
| 429 | 433 | unsigned i; |
| 430 | - @ Markup style: <select name="mimetype" size="1"> | |
| 434 | + @ <select name="mimetype" size="1"> | |
| 431 | 435 | for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){ |
| 432 | 436 | if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ |
| 433 | 437 | @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> |
| 434 | 438 | }else{ |
| 435 | 439 | @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> |
| @@ -485,11 +489,11 @@ | ||
| 485 | 489 | zPageName = PD("name",""); |
| 486 | 490 | if( check_name(zPageName) ) return; |
| 487 | 491 | isSandbox = is_sandbox(zPageName); |
| 488 | 492 | if( isSandbox ){ |
| 489 | 493 | if( !g.perm.WrWiki ){ |
| 490 | - login_needed(); | |
| 494 | + login_needed(g.anon.WrWiki); | |
| 491 | 495 | return; |
| 492 | 496 | } |
| 493 | 497 | if( zBody==0 ){ |
| 494 | 498 | zBody = db_get("sandbox",""); |
| 495 | 499 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| @@ -501,11 +505,11 @@ | ||
| 501 | 505 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 502 | 506 | " ORDER BY mtime DESC", zTag |
| 503 | 507 | ); |
| 504 | 508 | free(zTag); |
| 505 | 509 | if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 506 | - login_needed(); | |
| 510 | + login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki); | |
| 507 | 511 | return; |
| 508 | 512 | } |
| 509 | 513 | if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ |
| 510 | 514 | zBody = pWiki->zWiki; |
| 511 | 515 | zMimetype = pWiki->zMimetype; |
| @@ -573,11 +577,11 @@ | ||
| 573 | 577 | if( n<20 ) n = 20; |
| 574 | 578 | if( n>30 ) n = 30; |
| 575 | 579 | if( !isWysiwyg ){ |
| 576 | 580 | /* Traditional markup-only editing */ |
| 577 | 581 | form_begin(0, "%R/wikiedit"); |
| 578 | - @ <div> | |
| 582 | + @ <div>Markup style: | |
| 579 | 583 | mimetype_option_menu(zMimetype); |
| 580 | 584 | @ <br /><textarea name="w" class="wikiedit" cols="80" |
| 581 | 585 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 582 | 586 | @ <br /> |
| 583 | 587 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| @@ -625,11 +629,11 @@ | ||
| 625 | 629 | void wikinew_page(void){ |
| 626 | 630 | const char *zName; |
| 627 | 631 | const char *zMimetype; |
| 628 | 632 | login_check_credentials(); |
| 629 | 633 | if( !g.perm.NewWiki ){ |
| 630 | - login_needed(); | |
| 634 | + login_needed(g.anon.NewWiki); | |
| 631 | 635 | return; |
| 632 | 636 | } |
| 633 | 637 | zName = PD("name",""); |
| 634 | 638 | zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 635 | 639 | if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ |
| @@ -646,10 +650,11 @@ | ||
| 646 | 650 | @ <p>Rules for wiki page names:</p> |
| 647 | 651 | well_formed_wiki_name_rules(); |
| 648 | 652 | form_begin(0, "%R/wikinew"); |
| 649 | 653 | @ <p>Name of new wiki page: |
| 650 | 654 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| 655 | + @ Markup style: | |
| 651 | 656 | mimetype_option_menu("text/x-fossil-wiki"); |
| 652 | 657 | @ <br /><input type="submit" value="Create" /> |
| 653 | 658 | @ </p></form> |
| 654 | 659 | if( zName[0] ){ |
| 655 | 660 | @ <p><span class="wikiError"> |
| @@ -727,11 +732,11 @@ | ||
| 727 | 732 | fossil_redirect_home(); |
| 728 | 733 | return; |
| 729 | 734 | } |
| 730 | 735 | } |
| 731 | 736 | if( !g.perm.ApndWiki ){ |
| 732 | - login_needed(); | |
| 737 | + login_needed(g.anon.ApndWiki); | |
| 733 | 738 | return; |
| 734 | 739 | } |
| 735 | 740 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 |
| 736 | 741 | && (goodCaptcha = captcha_is_correct()) |
| 737 | 742 | ){ |
| @@ -840,11 +845,11 @@ | ||
| 840 | 845 | */ |
| 841 | 846 | void whistory_page(void){ |
| 842 | 847 | Stmt q; |
| 843 | 848 | const char *zPageName; |
| 844 | 849 | login_check_credentials(); |
| 845 | - if( !g.perm.Hyperlink ){ login_needed(); return; } | |
| 850 | + if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } | |
| 846 | 851 | zPageName = PD("name",""); |
| 847 | 852 | style_header("History Of %s", zPageName); |
| 848 | 853 | |
| 849 | 854 | db_prepare(&q, "%s AND event.objid IN " |
| 850 | 855 | " (SELECT rid FROM tagxref WHERE tagid=" |
| @@ -852,11 +857,11 @@ | ||
| 852 | 857 | " UNION SELECT attachid FROM attachment" |
| 853 | 858 | " WHERE target=%Q)" |
| 854 | 859 | "ORDER BY mtime DESC", |
| 855 | 860 | timeline_query_for_www(), zPageName, zPageName); |
| 856 | 861 | 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); | |
| 858 | 863 | db_finalize(&q); |
| 859 | 864 | style_footer(); |
| 860 | 865 | } |
| 861 | 866 | |
| 862 | 867 | /* |
| @@ -872,11 +877,11 @@ | ||
| 872 | 877 | Blob w1, w2, d; |
| 873 | 878 | u64 diffFlags; |
| 874 | 879 | |
| 875 | 880 | login_check_credentials(); |
| 876 | 881 | rid1 = atoi(PD("a","0")); |
| 877 | - if( !g.perm.Hyperlink ){ login_needed(); return; } | |
| 882 | + if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } | |
| 878 | 883 | if( rid1==0 ) fossil_redirect_home(); |
| 879 | 884 | rid2 = atoi(PD("b","0")); |
| 880 | 885 | zPageName = PD("name",""); |
| 881 | 886 | style_header("Changes To %s", zPageName); |
| 882 | 887 | |
| @@ -935,11 +940,11 @@ | ||
| 935 | 940 | void wcontent_page(void){ |
| 936 | 941 | Stmt q; |
| 937 | 942 | int showAll = P("all")!=0; |
| 938 | 943 | |
| 939 | 944 | login_check_credentials(); |
| 940 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 945 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 941 | 946 | style_header("Available Wiki Pages"); |
| 942 | 947 | if( showAll ){ |
| 943 | 948 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 944 | 949 | }else{ |
| 945 | 950 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| @@ -969,11 +974,11 @@ | ||
| 969 | 974 | */ |
| 970 | 975 | void wfind_page(void){ |
| 971 | 976 | Stmt q; |
| 972 | 977 | const char *zTitle; |
| 973 | 978 | login_check_credentials(); |
| 974 | - if( !g.perm.RdWiki ){ login_needed(); return; } | |
| 979 | + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } | |
| 975 | 980 | zTitle = PD("title","*"); |
| 976 | 981 | style_header("Wiki Pages Found"); |
| 977 | 982 | @ <ul> |
| 978 | 983 | db_prepare(&q, |
| 979 | 984 | "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" |
| 980 | 985 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -134,11 +134,15 @@ | |
| 134 | } |
| 135 | return "text/x-fossil-wiki"; |
| 136 | } |
| 137 | |
| 138 | /* |
| 139 | ** Render wiki text according to its mimetype |
| 140 | */ |
| 141 | void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ |
| 142 | if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 143 | wiki_convert(pWiki, 0, 0); |
| 144 | }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| @@ -222,11 +226,11 @@ | |
| 222 | style_submenu_element("List","List","%R/wcontent"); |
| 223 | } |
| 224 | if( (ok & W_HELP)!=0 ){ |
| 225 | style_submenu_element("Help","Help","%R/wikihelp"); |
| 226 | } |
| 227 | if( (ok & W_NEW)!=0 && g.perm.NewWiki ){ |
| 228 | style_submenu_element("New","New","%R/wikinew"); |
| 229 | } |
| 230 | #if 0 |
| 231 | if( (ok & W_BLOG)!=0 |
| 232 | #endif |
| @@ -239,11 +243,11 @@ | |
| 239 | ** WEBPAGE: wikihelp |
| 240 | ** A generic landing page for wiki. |
| 241 | */ |
| 242 | void wiki_helppage(void){ |
| 243 | login_check_credentials(); |
| 244 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 245 | style_header("Wiki Help"); |
| 246 | wiki_standard_submenu(W_ALL_BUT(W_HELP)); |
| 247 | @ <h2>Wiki Links</h2> |
| 248 | @ <ul> |
| 249 | { char *zWikiHomePageName = db_get("index-page",0); |
| @@ -261,19 +265,19 @@ | |
| 261 | @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
| 262 | @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
| 263 | @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
| 264 | @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> |
| 265 | @ to experiment.</li> |
| 266 | if( g.perm.NewWiki ){ |
| 267 | @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> |
| 268 | if( g.perm.Write ){ |
| 269 | @ <li> Create a %z(href("%R/eventedit"))new blog entry</a>.</li> |
| 270 | } |
| 271 | } |
| 272 | @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
| 273 | @ available on this server.</li> |
| 274 | if( g.perm.ModWiki ){ |
| 275 | @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
| 276 | } |
| 277 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 278 | @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key |
| 279 | @ words</li> |
| @@ -312,11 +316,11 @@ | |
| 312 | const char *zPageName; |
| 313 | const char *zMimetype = 0; |
| 314 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 315 | |
| 316 | login_check_credentials(); |
| 317 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 318 | zPageName = P("name"); |
| 319 | if( zPageName==0 ){ |
| 320 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 321 | wiki_srchpage(); |
| 322 | }else{ |
| @@ -355,11 +359,11 @@ | |
| 355 | "%R/wdiff?name=%T&a=%d", zPageName, rid); |
| 356 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 357 | style_submenu_element("Details", "Details", |
| 358 | "%R/info/%s", zUuid); |
| 359 | } |
| 360 | if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ |
| 361 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 362 | style_submenu_element("Edit", "Edit Wiki Page", |
| 363 | "%s/wikiedit?name=%T&wysiwyg=1", |
| 364 | g.zTop, zPageName); |
| 365 | }else{ |
| @@ -366,16 +370,16 @@ | |
| 366 | style_submenu_element("Edit", "Edit Wiki Page", |
| 367 | "%s/wikiedit?name=%T", |
| 368 | g.zTop, zPageName); |
| 369 | } |
| 370 | } |
| 371 | if( rid && g.perm.ApndWiki && g.perm.Attach ){ |
| 372 | style_submenu_element("Attach", "Add An Attachment", |
| 373 | "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", |
| 374 | g.zTop, zPageName, g.zTop, zPageName); |
| 375 | } |
| 376 | if( rid && g.perm.ApndWiki ){ |
| 377 | style_submenu_element("Append", "Add A Comment", |
| 378 | "%s/wikiappend?name=%T&mimetype=%s", |
| 379 | g.zTop, zPageName, zMimetype); |
| 380 | } |
| 381 | if( g.perm.Hyperlink ){ |
| @@ -423,13 +427,13 @@ | |
| 423 | |
| 424 | /* |
| 425 | ** Output a selection box from which the user can select the |
| 426 | ** wiki mimetype. |
| 427 | */ |
| 428 | static void mimetype_option_menu(const char *zMimetype){ |
| 429 | unsigned i; |
| 430 | @ Markup style: <select name="mimetype" size="1"> |
| 431 | for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){ |
| 432 | if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ |
| 433 | @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> |
| 434 | }else{ |
| 435 | @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> |
| @@ -485,11 +489,11 @@ | |
| 485 | zPageName = PD("name",""); |
| 486 | if( check_name(zPageName) ) return; |
| 487 | isSandbox = is_sandbox(zPageName); |
| 488 | if( isSandbox ){ |
| 489 | if( !g.perm.WrWiki ){ |
| 490 | login_needed(); |
| 491 | return; |
| 492 | } |
| 493 | if( zBody==0 ){ |
| 494 | zBody = db_get("sandbox",""); |
| 495 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| @@ -501,11 +505,11 @@ | |
| 501 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 502 | " ORDER BY mtime DESC", zTag |
| 503 | ); |
| 504 | free(zTag); |
| 505 | if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 506 | login_needed(); |
| 507 | return; |
| 508 | } |
| 509 | if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ |
| 510 | zBody = pWiki->zWiki; |
| 511 | zMimetype = pWiki->zMimetype; |
| @@ -573,11 +577,11 @@ | |
| 573 | if( n<20 ) n = 20; |
| 574 | if( n>30 ) n = 30; |
| 575 | if( !isWysiwyg ){ |
| 576 | /* Traditional markup-only editing */ |
| 577 | form_begin(0, "%R/wikiedit"); |
| 578 | @ <div> |
| 579 | mimetype_option_menu(zMimetype); |
| 580 | @ <br /><textarea name="w" class="wikiedit" cols="80" |
| 581 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 582 | @ <br /> |
| 583 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| @@ -625,11 +629,11 @@ | |
| 625 | void wikinew_page(void){ |
| 626 | const char *zName; |
| 627 | const char *zMimetype; |
| 628 | login_check_credentials(); |
| 629 | if( !g.perm.NewWiki ){ |
| 630 | login_needed(); |
| 631 | return; |
| 632 | } |
| 633 | zName = PD("name",""); |
| 634 | zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 635 | if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ |
| @@ -646,10 +650,11 @@ | |
| 646 | @ <p>Rules for wiki page names:</p> |
| 647 | well_formed_wiki_name_rules(); |
| 648 | form_begin(0, "%R/wikinew"); |
| 649 | @ <p>Name of new wiki page: |
| 650 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| 651 | mimetype_option_menu("text/x-fossil-wiki"); |
| 652 | @ <br /><input type="submit" value="Create" /> |
| 653 | @ </p></form> |
| 654 | if( zName[0] ){ |
| 655 | @ <p><span class="wikiError"> |
| @@ -727,11 +732,11 @@ | |
| 727 | fossil_redirect_home(); |
| 728 | return; |
| 729 | } |
| 730 | } |
| 731 | if( !g.perm.ApndWiki ){ |
| 732 | login_needed(); |
| 733 | return; |
| 734 | } |
| 735 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 |
| 736 | && (goodCaptcha = captcha_is_correct()) |
| 737 | ){ |
| @@ -840,11 +845,11 @@ | |
| 840 | */ |
| 841 | void whistory_page(void){ |
| 842 | Stmt q; |
| 843 | const char *zPageName; |
| 844 | login_check_credentials(); |
| 845 | if( !g.perm.Hyperlink ){ login_needed(); return; } |
| 846 | zPageName = PD("name",""); |
| 847 | style_header("History Of %s", zPageName); |
| 848 | |
| 849 | db_prepare(&q, "%s AND event.objid IN " |
| 850 | " (SELECT rid FROM tagxref WHERE tagid=" |
| @@ -852,11 +857,11 @@ | |
| 852 | " UNION SELECT attachid FROM attachment" |
| 853 | " WHERE target=%Q)" |
| 854 | "ORDER BY mtime DESC", |
| 855 | timeline_query_for_www(), zPageName, zPageName); |
| 856 | zWikiPageName = zPageName; |
| 857 | www_print_timeline(&q, TIMELINE_ARTID, 0, 0, wiki_history_extra); |
| 858 | db_finalize(&q); |
| 859 | style_footer(); |
| 860 | } |
| 861 | |
| 862 | /* |
| @@ -872,11 +877,11 @@ | |
| 872 | Blob w1, w2, d; |
| 873 | u64 diffFlags; |
| 874 | |
| 875 | login_check_credentials(); |
| 876 | rid1 = atoi(PD("a","0")); |
| 877 | if( !g.perm.Hyperlink ){ login_needed(); return; } |
| 878 | if( rid1==0 ) fossil_redirect_home(); |
| 879 | rid2 = atoi(PD("b","0")); |
| 880 | zPageName = PD("name",""); |
| 881 | style_header("Changes To %s", zPageName); |
| 882 | |
| @@ -935,11 +940,11 @@ | |
| 935 | void wcontent_page(void){ |
| 936 | Stmt q; |
| 937 | int showAll = P("all")!=0; |
| 938 | |
| 939 | login_check_credentials(); |
| 940 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 941 | style_header("Available Wiki Pages"); |
| 942 | if( showAll ){ |
| 943 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 944 | }else{ |
| 945 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| @@ -969,11 +974,11 @@ | |
| 969 | */ |
| 970 | void wfind_page(void){ |
| 971 | Stmt q; |
| 972 | const char *zTitle; |
| 973 | login_check_credentials(); |
| 974 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 975 | zTitle = PD("title","*"); |
| 976 | style_header("Wiki Pages Found"); |
| 977 | @ <ul> |
| 978 | db_prepare(&q, |
| 979 | "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" |
| 980 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -134,11 +134,15 @@ | |
| 134 | } |
| 135 | return "text/x-fossil-wiki"; |
| 136 | } |
| 137 | |
| 138 | /* |
| 139 | ** Render wiki text according to its mimetype. |
| 140 | ** |
| 141 | ** text/x-fossil-wiki Fossil wiki |
| 142 | ** text/x-markdown Markdown |
| 143 | ** anything else... Plain text |
| 144 | */ |
| 145 | void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ |
| 146 | if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 147 | wiki_convert(pWiki, 0, 0); |
| 148 | }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| @@ -222,11 +226,11 @@ | |
| 226 | style_submenu_element("List","List","%R/wcontent"); |
| 227 | } |
| 228 | if( (ok & W_HELP)!=0 ){ |
| 229 | style_submenu_element("Help","Help","%R/wikihelp"); |
| 230 | } |
| 231 | if( (ok & W_NEW)!=0 && g.anon.NewWiki ){ |
| 232 | style_submenu_element("New","New","%R/wikinew"); |
| 233 | } |
| 234 | #if 0 |
| 235 | if( (ok & W_BLOG)!=0 |
| 236 | #endif |
| @@ -239,11 +243,11 @@ | |
| 243 | ** WEBPAGE: wikihelp |
| 244 | ** A generic landing page for wiki. |
| 245 | */ |
| 246 | void wiki_helppage(void){ |
| 247 | login_check_credentials(); |
| 248 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 249 | style_header("Wiki Help"); |
| 250 | wiki_standard_submenu(W_ALL_BUT(W_HELP)); |
| 251 | @ <h2>Wiki Links</h2> |
| 252 | @ <ul> |
| 253 | { char *zWikiHomePageName = db_get("index-page",0); |
| @@ -261,19 +265,19 @@ | |
| 265 | @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
| 266 | @ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
| 267 | @ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
| 268 | @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> |
| 269 | @ to experiment.</li> |
| 270 | if( g.anon.NewWiki ){ |
| 271 | @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> |
| 272 | if( g.anon.Write ){ |
| 273 | @ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li> |
| 274 | } |
| 275 | } |
| 276 | @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
| 277 | @ available on this server.</li> |
| 278 | if( g.anon.ModWiki ){ |
| 279 | @ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
| 280 | } |
| 281 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 282 | @ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key |
| 283 | @ words</li> |
| @@ -312,11 +316,11 @@ | |
| 316 | const char *zPageName; |
| 317 | const char *zMimetype = 0; |
| 318 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 319 | |
| 320 | login_check_credentials(); |
| 321 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 322 | zPageName = P("name"); |
| 323 | if( zPageName==0 ){ |
| 324 | if( search_restrict(SRCH_WIKI)!=0 ){ |
| 325 | wiki_srchpage(); |
| 326 | }else{ |
| @@ -355,11 +359,11 @@ | |
| 359 | "%R/wdiff?name=%T&a=%d", zPageName, rid); |
| 360 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 361 | style_submenu_element("Details", "Details", |
| 362 | "%R/info/%s", zUuid); |
| 363 | } |
| 364 | if( (rid && g.anon.WrWiki) || (!rid && g.anon.NewWiki) ){ |
| 365 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 366 | style_submenu_element("Edit", "Edit Wiki Page", |
| 367 | "%s/wikiedit?name=%T&wysiwyg=1", |
| 368 | g.zTop, zPageName); |
| 369 | }else{ |
| @@ -366,16 +370,16 @@ | |
| 370 | style_submenu_element("Edit", "Edit Wiki Page", |
| 371 | "%s/wikiedit?name=%T", |
| 372 | g.zTop, zPageName); |
| 373 | } |
| 374 | } |
| 375 | if( rid && g.anon.ApndWiki && g.anon.Attach ){ |
| 376 | style_submenu_element("Attach", "Add An Attachment", |
| 377 | "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", |
| 378 | g.zTop, zPageName, g.zTop, zPageName); |
| 379 | } |
| 380 | if( rid && g.anon.ApndWiki ){ |
| 381 | style_submenu_element("Append", "Add A Comment", |
| 382 | "%s/wikiappend?name=%T&mimetype=%s", |
| 383 | g.zTop, zPageName, zMimetype); |
| 384 | } |
| 385 | if( g.perm.Hyperlink ){ |
| @@ -423,13 +427,13 @@ | |
| 427 | |
| 428 | /* |
| 429 | ** Output a selection box from which the user can select the |
| 430 | ** wiki mimetype. |
| 431 | */ |
| 432 | void mimetype_option_menu(const char *zMimetype){ |
| 433 | unsigned i; |
| 434 | @ <select name="mimetype" size="1"> |
| 435 | for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){ |
| 436 | if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ |
| 437 | @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> |
| 438 | }else{ |
| 439 | @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> |
| @@ -485,11 +489,11 @@ | |
| 489 | zPageName = PD("name",""); |
| 490 | if( check_name(zPageName) ) return; |
| 491 | isSandbox = is_sandbox(zPageName); |
| 492 | if( isSandbox ){ |
| 493 | if( !g.perm.WrWiki ){ |
| 494 | login_needed(g.anon.WrWiki); |
| 495 | return; |
| 496 | } |
| 497 | if( zBody==0 ){ |
| 498 | zBody = db_get("sandbox",""); |
| 499 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| @@ -501,11 +505,11 @@ | |
| 505 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 506 | " ORDER BY mtime DESC", zTag |
| 507 | ); |
| 508 | free(zTag); |
| 509 | if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ |
| 510 | login_needed(rid ? g.anon.WrWiki : g.anon.NewWiki); |
| 511 | return; |
| 512 | } |
| 513 | if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ |
| 514 | zBody = pWiki->zWiki; |
| 515 | zMimetype = pWiki->zMimetype; |
| @@ -573,11 +577,11 @@ | |
| 577 | if( n<20 ) n = 20; |
| 578 | if( n>30 ) n = 30; |
| 579 | if( !isWysiwyg ){ |
| 580 | /* Traditional markup-only editing */ |
| 581 | form_begin(0, "%R/wikiedit"); |
| 582 | @ <div>Markup style: |
| 583 | mimetype_option_menu(zMimetype); |
| 584 | @ <br /><textarea name="w" class="wikiedit" cols="80" |
| 585 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 586 | @ <br /> |
| 587 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| @@ -625,11 +629,11 @@ | |
| 629 | void wikinew_page(void){ |
| 630 | const char *zName; |
| 631 | const char *zMimetype; |
| 632 | login_check_credentials(); |
| 633 | if( !g.perm.NewWiki ){ |
| 634 | login_needed(g.anon.NewWiki); |
| 635 | return; |
| 636 | } |
| 637 | zName = PD("name",""); |
| 638 | zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 639 | if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ |
| @@ -646,10 +650,11 @@ | |
| 650 | @ <p>Rules for wiki page names:</p> |
| 651 | well_formed_wiki_name_rules(); |
| 652 | form_begin(0, "%R/wikinew"); |
| 653 | @ <p>Name of new wiki page: |
| 654 | @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| 655 | @ Markup style: |
| 656 | mimetype_option_menu("text/x-fossil-wiki"); |
| 657 | @ <br /><input type="submit" value="Create" /> |
| 658 | @ </p></form> |
| 659 | if( zName[0] ){ |
| 660 | @ <p><span class="wikiError"> |
| @@ -727,11 +732,11 @@ | |
| 732 | fossil_redirect_home(); |
| 733 | return; |
| 734 | } |
| 735 | } |
| 736 | if( !g.perm.ApndWiki ){ |
| 737 | login_needed(g.anon.ApndWiki); |
| 738 | return; |
| 739 | } |
| 740 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 |
| 741 | && (goodCaptcha = captcha_is_correct()) |
| 742 | ){ |
| @@ -840,11 +845,11 @@ | |
| 845 | */ |
| 846 | void whistory_page(void){ |
| 847 | Stmt q; |
| 848 | const char *zPageName; |
| 849 | login_check_credentials(); |
| 850 | if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } |
| 851 | zPageName = PD("name",""); |
| 852 | style_header("History Of %s", zPageName); |
| 853 | |
| 854 | db_prepare(&q, "%s AND event.objid IN " |
| 855 | " (SELECT rid FROM tagxref WHERE tagid=" |
| @@ -852,11 +857,11 @@ | |
| 857 | " UNION SELECT attachid FROM attachment" |
| 858 | " WHERE target=%Q)" |
| 859 | "ORDER BY mtime DESC", |
| 860 | timeline_query_for_www(), zPageName, zPageName); |
| 861 | zWikiPageName = zPageName; |
| 862 | www_print_timeline(&q, TIMELINE_ARTID, 0, 0, 0, wiki_history_extra); |
| 863 | db_finalize(&q); |
| 864 | style_footer(); |
| 865 | } |
| 866 | |
| 867 | /* |
| @@ -872,11 +877,11 @@ | |
| 877 | Blob w1, w2, d; |
| 878 | u64 diffFlags; |
| 879 | |
| 880 | login_check_credentials(); |
| 881 | rid1 = atoi(PD("a","0")); |
| 882 | if( !g.perm.Hyperlink ){ login_needed(g.anon.Hyperlink); return; } |
| 883 | if( rid1==0 ) fossil_redirect_home(); |
| 884 | rid2 = atoi(PD("b","0")); |
| 885 | zPageName = PD("name",""); |
| 886 | style_header("Changes To %s", zPageName); |
| 887 | |
| @@ -935,11 +940,11 @@ | |
| 940 | void wcontent_page(void){ |
| 941 | Stmt q; |
| 942 | int showAll = P("all")!=0; |
| 943 | |
| 944 | login_check_credentials(); |
| 945 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 946 | style_header("Available Wiki Pages"); |
| 947 | if( showAll ){ |
| 948 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 949 | }else{ |
| 950 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| @@ -969,11 +974,11 @@ | |
| 974 | */ |
| 975 | void wfind_page(void){ |
| 976 | Stmt q; |
| 977 | const char *zTitle; |
| 978 | login_check_credentials(); |
| 979 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 980 | zTitle = PD("title","*"); |
| 981 | style_header("Wiki Pages Found"); |
| 982 | @ <ul> |
| 983 | db_prepare(&q, |
| 984 | "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" |
| 985 |
+65
-10
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -1206,11 +1206,11 @@ | ||
| 1206 | 1206 | || strncmp(zTarget, "ftp:", 4)==0 |
| 1207 | 1207 | || strncmp(zTarget, "mailto:", 7)==0 |
| 1208 | 1208 | ){ |
| 1209 | 1209 | blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); |
| 1210 | 1210 | }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); | |
| 1212 | 1212 | }else if( zTarget[0]=='.' |
| 1213 | 1213 | && (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/')) |
| 1214 | 1214 | && (p->state & WIKI_LINKSONLY)==0 ){ |
| 1215 | 1215 | blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); |
| 1216 | 1216 | }else if( zTarget[0]=='#' ){ |
| @@ -1965,17 +1965,26 @@ | ||
| 1965 | 1965 | ** z points to the start of a token. Return the number of |
| 1966 | 1966 | ** characters in that token. |
| 1967 | 1967 | */ |
| 1968 | 1968 | static int nextHtmlToken(const char *z){ |
| 1969 | 1969 | int n; |
| 1970 | - if( z[0]=='<' ){ | |
| 1970 | + char c; | |
| 1971 | + if( (c=z[0])=='<' ){ | |
| 1971 | 1972 | n = markupLength(z); |
| 1972 | 1973 | if( n<=0 ) n = 1; |
| 1973 | - }else if( fossil_isspace(z[0]) ){ | |
| 1974 | + }else if( fossil_isspace(c) ){ | |
| 1974 | 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++; | |
| 1975 | 1980 | }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 | + } | |
| 1977 | 1986 | } |
| 1978 | 1987 | return n; |
| 1979 | 1988 | } |
| 1980 | 1989 | |
| 1981 | 1990 | /* |
| @@ -2100,16 +2109,22 @@ | ||
| 2100 | 2109 | } |
| 2101 | 2110 | |
| 2102 | 2111 | /* |
| 2103 | 2112 | ** Remove all HTML markup from the input text. The output written into |
| 2104 | 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. | |
| 2105 | 2117 | */ |
| 2106 | 2118 | void html_to_plaintext(const char *zIn, Blob *pOut){ |
| 2107 | 2119 | int n; |
| 2108 | 2120 | int i, j; |
| 2121 | + int inTitle = 0; /* True between <title>...</title> */ | |
| 2122 | + int seenText = 0; /* True after first non-whitespace seen */ | |
| 2109 | 2123 | int nNL = 0; /* Number of \n characters at the end of pOut */ |
| 2110 | 2124 | int nWS = 0; /* True if pOut ends with whitespace */ |
| 2125 | + while( fossil_isspace(zIn[0]) ) zIn++; | |
| 2111 | 2126 | while( zIn[0] ){ |
| 2112 | 2127 | n = nextHtmlToken(zIn); |
| 2113 | 2128 | if( zIn[0]=='<' && n>1 ){ |
| 2114 | 2129 | int isCloseTag; |
| 2115 | 2130 | int eTag; |
| @@ -2130,26 +2145,66 @@ | ||
| 2130 | 2145 | zIn += n; |
| 2131 | 2146 | } |
| 2132 | 2147 | if( zIn[0]=='<' ) zIn += n; |
| 2133 | 2148 | continue; |
| 2134 | 2149 | } |
| 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 ){ | |
| 2137 | 2155 | blob_append(pOut, "\n", 1); |
| 2138 | 2156 | nNL++; |
| 2139 | 2157 | } |
| 2140 | 2158 | nWS = 1; |
| 2141 | 2159 | } |
| 2142 | 2160 | }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, '&', "&" }, | |
| 2179 | + { 4, '<', "<" }, | |
| 2180 | + { 4, '>', ">" }, | |
| 2181 | + { 6, ' ', " " }, | |
| 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); | |
| 2146 | 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); | |
| 2147 | 2200 | } |
| 2148 | 2201 | }else{ |
| 2149 | - blob_append(pOut, zIn, n); | |
| 2202 | + if( !seenText && !inTitle ) blob_append(pOut, "\n", 1); | |
| 2203 | + seenText = 1; | |
| 2150 | 2204 | nNL = nWS = 0; |
| 2205 | + blob_append(pOut, zIn, n); | |
| 2151 | 2206 | } |
| 2152 | 2207 | zIn += n; |
| 2153 | 2208 | } |
| 2154 | 2209 | if( nNL==0 ) blob_append(pOut, "\n", 1); |
| 2155 | 2210 | } |
| 2156 | 2211 |
| --- 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, '&', "&" }, |
| 2179 | { 4, '<', "<" }, |
| 2180 | { 4, '>', ">" }, |
| 2181 | { 6, ' ', " " }, |
| 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 @@ | ||
| 27 | 27 | ** WEBPAGE: xfersetup |
| 28 | 28 | */ |
| 29 | 29 | void xfersetup_page(void){ |
| 30 | 30 | login_check_credentials(); |
| 31 | 31 | if( !g.perm.Setup ){ |
| 32 | - login_needed(); | |
| 32 | + login_needed(0); | |
| 33 | + return; | |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | style_header("Transfer Setup"); |
| 36 | 37 | |
| 37 | 38 | @ <table border="0" cellspacing="20"> |
| @@ -104,11 +105,12 @@ | ||
| 104 | 105 | const char *z; |
| 105 | 106 | int isSubmit; |
| 106 | 107 | |
| 107 | 108 | login_check_credentials(); |
| 108 | 109 | if( !g.perm.Setup ){ |
| 109 | - login_needed(); | |
| 110 | + login_needed(0); | |
| 111 | + return; | |
| 110 | 112 | } |
| 111 | 113 | if( P("setup") ){ |
| 112 | 114 | cgi_redirect("xfersetup"); |
| 113 | 115 | } |
| 114 | 116 | isSubmit = P("submit")!=0; |
| 115 | 117 |
| --- 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 @@ | ||
| 448 | 448 | int nName, nRid; |
| 449 | 449 | Blob zip; |
| 450 | 450 | char *zKey; |
| 451 | 451 | |
| 452 | 452 | login_check_credentials(); |
| 453 | - if( !g.perm.Zip ){ login_needed(); return; } | |
| 453 | + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } | |
| 454 | 454 | load_control(); |
| 455 | 455 | zName = mprintf("%s", PD("name","")); |
| 456 | 456 | nName = strlen(zName); |
| 457 | 457 | zRid = mprintf("%s", PD("uuid","trunk")); |
| 458 | 458 | nRid = strlen(zRid); |
| @@ -472,10 +472,21 @@ | ||
| 472 | 472 | } |
| 473 | 473 | rid = name_to_typed_rid(nRid?zRid:zName,"ci"); |
| 474 | 474 | if( rid==0 ){ |
| 475 | 475 | @ Not found |
| 476 | 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; | |
| 477 | 488 | } |
| 478 | 489 | if( nRid==0 && nName>10 ) zName[10] = 0; |
| 479 | 490 | zKey = db_text(0, "SELECT '/zip/'||uuid||'/%q' FROM blob WHERE rid=%d",zName,rid); |
| 480 | 491 | blob_zero(&zip); |
| 481 | 492 | if( cache_read(&zip, zKey)==0 ){ |
| 482 | 493 | |
| 483 | 494 | 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 |
+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. |
| --- 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 @@ | ||
| 69 | 69 | |
| 70 | 70 | External: |
| 71 | 71 | |
| 72 | 72 | * <a href="http://www.sqlite.org/src/timeline?c=2010-09-29&nd" |
| 73 | 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.) | |
| 74 | 77 |
| --- 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 @@ | ||
| 17 | 17 | |
| 18 | 18 | <li><p> |
| 19 | 19 | Click on each of the links in in the |
| 20 | 20 | [./diff-test-1.wiki] document and verify that all diffs are |
| 21 | 21 | rendered correctly. |
| 22 | + | |
| 22 | 23 | <li><p> |
| 23 | 24 | Click on the following link to verify that it works: [./test-page%2b%2b.wiki | ./test-page++.wiki] |
| 24 | 25 | (NB: Many web servers automatically block |
| 25 | 26 | or rewrite URLs that contain "+" characters, even when those "+" |
| 26 | 27 | characters are encoded as "%2B". On such web servers, the URL |
| 27 | 28 | above will not work. This test is only guaranteed to work |
| 28 | 29 | when running "fossil ui".) |
| 29 | 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 | + | |
| 30 | 35 | <li><p> |
| 31 | 36 | Verify correct name-change tracking behavior (no net changes) for: |
| 32 | 37 | <blockquote><b> |
| 33 | 38 | fossil test-name-changes --debug b120bc8b262ac 374920b20944b |
| 34 | 39 | </b></blockquote> |
| 35 | 40 |
| --- 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 @@ | ||
| 32 | 32 | |
| 33 | 33 | set env(TH1_ENABLE_TCL) 1; # Tcl integration must be enabled for this test. |
| 34 | 34 | |
| 35 | 35 | ############################################################################### |
| 36 | 36 | |
| 37 | -fossil test-th-render --th-open-config \ | |
| 37 | +fossil test-th-render --open-config \ | |
| 38 | 38 | [file nativename [file join $dir th1-tcl1.txt]] |
| 39 | 39 | |
| 40 | 40 | test th1-tcl-1 {[regexp -- {^tclReady\(before\) = 0 |
| 41 | 41 | tclReady\(after\) = 1 |
| 42 | 42 | \d+ |
| @@ -59,60 +59,60 @@ | ||
| 59 | 59 | three words now |
| 60 | 60 | $} [string map [list \r\n \n] $RESULT]]} |
| 61 | 61 | |
| 62 | 62 | ############################################################################### |
| 63 | 63 | |
| 64 | -fossil test-th-render --th-open-config \ | |
| 64 | +fossil test-th-render --open-config \ | |
| 65 | 65 | [file nativename [file join $dir th1-tcl2.txt]] |
| 66 | 66 | |
| 67 | 67 | test th1-tcl-2 {[regexp -- {^\d+ |
| 68 | 68 | $} [string map [list \r\n \n] $RESULT]]} |
| 69 | 69 | |
| 70 | 70 | ############################################################################### |
| 71 | 71 | |
| 72 | -fossil test-th-render --th-open-config \ | |
| 72 | +fossil test-th-render --open-config \ | |
| 73 | 73 | [file nativename [file join $dir th1-tcl3.txt]] |
| 74 | 74 | |
| 75 | 75 | test th1-tcl-3 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 76 | 76 | invalid command name "bad_command"</p>}} |
| 77 | 77 | |
| 78 | 78 | ############################################################################### |
| 79 | 79 | |
| 80 | -fossil test-th-render --th-open-config \ | |
| 80 | +fossil test-th-render --open-config \ | |
| 81 | 81 | [file nativename [file join $dir th1-tcl4.txt]] |
| 82 | 82 | |
| 83 | 83 | test th1-tcl-4 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 84 | 84 | divide by zero</p>}} |
| 85 | 85 | |
| 86 | 86 | ############################################################################### |
| 87 | 87 | |
| 88 | -fossil test-th-render --th-open-config \ | |
| 88 | +fossil test-th-render --open-config \ | |
| 89 | 89 | [file nativename [file join $dir th1-tcl5.txt]] |
| 90 | 90 | |
| 91 | 91 | test th1-tcl-5 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 92 | 92 | Tcl command not found: bad_command</p>} || $RESULT eq {<hr><p\ |
| 93 | 93 | class="thmainError">ERROR: invalid command name "bad_command"</p>}} |
| 94 | 94 | |
| 95 | 95 | ############################################################################### |
| 96 | 96 | |
| 97 | -fossil test-th-render --th-open-config \ | |
| 97 | +fossil test-th-render --open-config \ | |
| 98 | 98 | [file nativename [file join $dir th1-tcl6.txt]] |
| 99 | 99 | |
| 100 | 100 | test th1-tcl-6 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 101 | 101 | no such command: bad_command</p>}} |
| 102 | 102 | |
| 103 | 103 | ############################################################################### |
| 104 | 104 | |
| 105 | -fossil test-th-render --th-open-config \ | |
| 105 | +fossil test-th-render --open-config \ | |
| 106 | 106 | [file nativename [file join $dir th1-tcl7.txt]] |
| 107 | 107 | |
| 108 | 108 | test th1-tcl-7 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 109 | 109 | syntax error in expression: "2**0"</p>}} |
| 110 | 110 | |
| 111 | 111 | ############################################################################### |
| 112 | 112 | |
| 113 | -fossil test-th-render --th-open-config \ | |
| 113 | +fossil test-th-render --open-config \ | |
| 114 | 114 | [file nativename [file join $dir th1-tcl8.txt]] |
| 115 | 115 | |
| 116 | 116 | test th1-tcl-8 {$RESULT eq {<hr><p class="thmainError">ERROR:\ |
| 117 | 117 | cannot invoke Tcl command: tailcall</p>} || $RESULT eq {<hr><p\ |
| 118 | 118 | class="thmainError">ERROR: tailcall can only be called from a proc or\ |
| @@ -119,11 +119,11 @@ | ||
| 119 | 119 | lambda</p>} || $RESULT eq {<hr><p class="thmainError">ERROR: This test\ |
| 120 | 120 | requires Tcl 8.6 or higher.</p>}} |
| 121 | 121 | |
| 122 | 122 | ############################################################################### |
| 123 | 123 | |
| 124 | -fossil test-th-render --th-open-config \ | |
| 124 | +fossil test-th-render --open-config \ | |
| 125 | 125 | [file nativename [file join $dir th1-tcl9.txt]] |
| 126 | 126 | |
| 127 | 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 \ | |
| 128 | +[list test-th-render --open-config [file nativename [file join $dir \ | |
| 129 | 129 | th1-tcl9.txt]]]]} |
| 130 | 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 --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 "bad_command"</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 "bad_command"</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: "2**0"</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 "bad_command"</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 "bad_command"</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: "2**0"</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 @@ | ||
| 16 | 16 | ############################################################################ |
| 17 | 17 | # |
| 18 | 18 | # TH1 Commands |
| 19 | 19 | # |
| 20 | 20 | |
| 21 | -fossil test-th-eval --th-open-config "setting th1-hooks" | |
| 21 | +fossil test-th-eval --open-config "setting th1-hooks" | |
| 22 | 22 | set th1Hooks [expr {$RESULT eq "1"}] |
| 23 | 23 | |
| 24 | 24 | ############################################################################### |
| 25 | 25 | |
| 26 | -fossil test-th-eval --th-open-config "setting abc" | |
| 26 | +fossil test-th-eval --open-config "setting abc" | |
| 27 | 27 | test th1-setting-1 {$RESULT eq ""} |
| 28 | 28 | |
| 29 | 29 | ############################################################################### |
| 30 | 30 | |
| 31 | -fossil test-th-eval --th-open-config "setting -- abc" | |
| 31 | +fossil test-th-eval --open-config "setting -- abc" | |
| 32 | 32 | test th1-setting-2 {$RESULT eq ""} |
| 33 | 33 | |
| 34 | 34 | ############################################################################### |
| 35 | 35 | |
| 36 | -fossil test-th-eval --th-open-config "setting -strict abc" | |
| 36 | +fossil test-th-eval --open-config "setting -strict abc" | |
| 37 | 37 | test th1-setting-3 {$RESULT eq {TH_ERROR: no value for setting "abc"}} |
| 38 | 38 | |
| 39 | 39 | ############################################################################### |
| 40 | 40 | |
| 41 | -fossil test-th-eval --th-open-config "setting -strict -- abc" | |
| 41 | +fossil test-th-eval --open-config "setting -strict -- abc" | |
| 42 | 42 | test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}} |
| 43 | 43 | |
| 44 | 44 | ############################################################################### |
| 45 | 45 | |
| 46 | -fossil test-th-eval --th-open-config "setting autosync" | |
| 46 | +fossil test-th-eval --open-config "setting autosync" | |
| 47 | 47 | test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1} |
| 48 | 48 | |
| 49 | 49 | ############################################################################### |
| 50 | 50 | |
| 51 | -fossil test-th-eval --th-open-config "setting -strict autosync" | |
| 51 | +fossil test-th-eval --open-config "setting -strict autosync" | |
| 52 | 52 | test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1} |
| 53 | 53 | |
| 54 | 54 | ############################################################################### |
| 55 | 55 | |
| 56 | -fossil test-th-eval --th-open-config "setting --" | |
| 56 | +fossil test-th-eval --open-config "setting --" | |
| 57 | 57 | test th1-setting-7 {$RESULT eq \ |
| 58 | 58 | {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}} |
| 59 | 59 | |
| 60 | 60 | ############################################################################### |
| 61 | 61 | |
| 62 | -fossil test-th-eval --th-open-config "setting -strict --" | |
| 62 | +fossil test-th-eval --open-config "setting -strict --" | |
| 63 | 63 | test th1-setting-8 {$RESULT eq \ |
| 64 | 64 | {TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}} |
| 65 | 65 | |
| 66 | 66 | ############################################################################### |
| 67 | 67 | |
| 68 | -fossil test-th-eval --th-open-config "setting -- --" | |
| 68 | +fossil test-th-eval --open-config "setting -- --" | |
| 69 | 69 | test th1-setting-9 {$RESULT eq {}} |
| 70 | 70 | |
| 71 | 71 | ############################################################################### |
| 72 | 72 | |
| 73 | -fossil test-th-eval --th-open-config "setting -strict -- --" | |
| 73 | +fossil test-th-eval --open-config "setting -strict -- --" | |
| 74 | 74 | test th1-setting-10 {$RESULT eq {TH_ERROR: no value for setting "--"}} |
| 75 | 75 | |
| 76 | 76 | ############################################################################### |
| 77 | 77 | |
| 78 | 78 | fossil test-th-eval "expr 42/0" |
| @@ -595,26 +595,26 @@ | ||
| 595 | 595 | fossil test-th-eval "styleHeader {Page Title Here}" |
| 596 | 596 | test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 597 | 597 | |
| 598 | 598 | ############################################################################### |
| 599 | 599 | |
| 600 | -fossil test-th-eval --th-open-config "styleHeader {Page Title Here}" | |
| 600 | +fossil test-th-eval --open-config "styleHeader {Page Title Here}" | |
| 601 | 601 | test th1-header-2 {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 602 | 602 | |
| 603 | 603 | ############################################################################### |
| 604 | 604 | |
| 605 | 605 | fossil test-th-eval "styleFooter" |
| 606 | 606 | test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 607 | 607 | |
| 608 | 608 | ############################################################################### |
| 609 | 609 | |
| 610 | -fossil test-th-eval --th-open-config "styleFooter" | |
| 610 | +fossil test-th-eval --open-config "styleFooter" | |
| 611 | 611 | test th1-footer-2 {$RESULT eq {}} |
| 612 | 612 | |
| 613 | 613 | ############################################################################### |
| 614 | 614 | |
| 615 | -fossil test-th-eval --th-open-config --th-force-cgi "styleHeader {}; styleFooter" | |
| 615 | +fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter" | |
| 616 | 616 | test th1-footer-3 {[regexp -- {</body></html>} $RESULT]} |
| 617 | 617 | |
| 618 | 618 | ############################################################################### |
| 619 | 619 | |
| 620 | 620 | fossil test-th-eval "getParameter" |
| @@ -678,42 +678,42 @@ | ||
| 678 | 678 | fossil test-th-eval "artifact tip" |
| 679 | 679 | test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 680 | 680 | |
| 681 | 681 | ############################################################################### |
| 682 | 682 | |
| 683 | -fossil test-th-eval --th-open-config "artifact tip" | |
| 683 | +fossil test-th-eval --open-config "artifact tip" | |
| 684 | 684 | test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40}} $RESULT]} |
| 685 | 685 | |
| 686 | 686 | ############################################################################### |
| 687 | 687 | |
| 688 | 688 | fossil test-th-eval "artifact 0000000000" |
| 689 | 689 | test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 690 | 690 | |
| 691 | 691 | ############################################################################### |
| 692 | 692 | |
| 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}} | |
| 695 | 695 | |
| 696 | 696 | ############################################################################### |
| 697 | 697 | |
| 698 | 698 | fossil test-th-eval "artifact tip test/th1.test" |
| 699 | 699 | test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 700 | 700 | |
| 701 | 701 | ############################################################################### |
| 702 | 702 | |
| 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" | |
| 704 | 704 | test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]} |
| 705 | 705 | |
| 706 | 706 | ############################################################################### |
| 707 | 707 | |
| 708 | 708 | fossil test-th-eval "artifact 0000000000 test/th1.test" |
| 709 | 709 | test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 710 | 710 | |
| 711 | 711 | ############################################################################### |
| 712 | 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}} | |
| 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 | 715 | |
| 716 | 716 | ############################################################################### |
| 717 | 717 | |
| 718 | 718 | fossil test-th-eval "globalState checkout" |
| 719 | 719 | test th1-globalState-1 {[string length $RESULT] > 0} |
| @@ -728,11 +728,11 @@ | ||
| 728 | 728 | fossil test-th-eval "globalState configuration" |
| 729 | 729 | test th1-globalState-3 {[string length $RESULT] == 0} |
| 730 | 730 | |
| 731 | 731 | ############################################################################### |
| 732 | 732 | |
| 733 | -fossil test-th-eval --th-open-config "globalState configuration" | |
| 733 | +fossil test-th-eval --open-config "globalState configuration" | |
| 734 | 734 | test th1-globalState-4 {[string length $RESULT] > 0} |
| 735 | 735 | |
| 736 | 736 | ############################################################################### |
| 737 | 737 | |
| 738 | 738 | fossil test-th-eval "globalState executable" |
| 739 | 739 |
| --- 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 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | -<h2>Changes For Version 1.31 (2015-??-??)</h2> | |
| 4 | - * Add direct Subversion import support to | |
| 5 | - [/help?cmd=import|fossil import]. (??) | |
| 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 | + <doc class="fossil-doc"> 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. | |
| 6 | 40 | * Add IPv6 support to [/help?cmd=sync|fossil sync] and |
| 7 | 41 | [/help?cmd=clone|fossil clone] |
| 8 | 42 | * 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 | 43 | * 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. | |
| 22 | 51 | |
| 23 | 52 | <h2>Changes For Version 1.30 (2015-01-19)</h2> |
| 24 | 53 | * Added the [/help?cmd=bundle|fossil bundle] command. |
| 25 | 54 | * Added the [/help?cmd=purge|fossil purge] command. |
| 26 | 55 | * Added the [/help?cmd=publish|fossil publish] command. |
| 27 | 56 |
| --- 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 | <doc class="fossil-doc"> 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 |
+22
-8
| --- 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> | |
| 3 | 3 | |
| 4 | 4 | Fossil provides a built-in <a href="wikitheory.wiki">wiki</a> |
| 5 | 5 | that can be used to store the |
| 6 | 6 | documentation for a project. This is sufficient for many projects. |
| 7 | 7 | If your project is well-served by wiki documentation, then you |
| @@ -63,26 +63,40 @@ | ||
| 63 | 63 | Finally, the <i><filename></i> element of the URL is the |
| 64 | 64 | pathname of the documentation file relative to the root of the source |
| 65 | 65 | tree. |
| 66 | 66 | |
| 67 | 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". | |
| 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". | |
| 71 | 72 | |
| 72 | 73 | Documentation files whose names end in ".wiki" use the |
| 73 | 74 | [/wiki_rules | same markup as wiki pages] - |
| 74 | 75 | a safe subset of HTML together with some wiki rules for paragraph |
| 75 | 76 | breaks, lists, and hyperlinks. |
| 76 | 77 | Documentation files ending in ".md" or ".markdown" use the |
| 77 | -Markdown markup langauge. | |
| 78 | +[/md_rules | Markdown markup langauge]. | |
| 78 | 79 | Documentation files ending in ".txt" are plain text. |
| 79 | 80 | Wiki, markdown, and plain text documentation files |
| 80 | 81 | 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 | |
| 83 | 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 <div> element like this: | |
| 88 | + | |
| 89 | + <b><div class='fossil-doc' data-title='<i>Title Text</i>'></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. | |
| 84 | 98 | |
| 85 | 99 | <h2>Examples</h2> |
| 86 | 100 | |
| 87 | 101 | This file that you are currently reading is an example of |
| 88 | 102 | embedded documentation. The name of this file in the fossil |
| 89 | 103 |
| --- 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><filename></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><filename></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 <div> element like this: |
| 88 | |
| 89 | <b><div class='fossil-doc' data-title='<i>Title Text</i>'></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> | |
| 2 | 2 | |
| 3 | -<h2>What Is An "Event"?</h2> | |
| 3 | +<h2>What Is A "Technote"?</h2> | |
| 4 | 4 | |
| 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] | |
| 6 | 7 | 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 | |
| 10 | 12 | time of the event, and the timeline background color can all be edited. |
| 11 | 13 | |
| 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 | |
| 15 | 17 | for auditing purposes. |
| 16 | 18 | |
| 17 | 19 | Possible uses for events include: |
| 18 | 20 | |
| 19 | 21 | * <b>Milestones</b>. Project milestones, such as releases or beta-test |
| @@ -41,35 +43,33 @@ | ||
| 41 | 43 | |
| 42 | 44 | * <b>Announcements</b>. Changes to the composition of the development |
| 43 | 45 | team or acquisition of new project sponsors can be communicated as |
| 44 | 46 | announcements which can be implemented as events. |
| 45 | 47 | |
| 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 | |
| 47 | 49 | stay better organized and provide a better historical record of the |
| 48 | 50 | development progress. |
| 49 | 51 | |
| 50 | -<h2>Viewing Events</h2> | |
| 52 | +<h2>Viewing Technotes</h2> | |
| 51 | 53 | |
| 52 | -Because events are considered a special kind of wiki, | |
| 54 | +Because technotes are considered a special kind of wiki, | |
| 53 | 55 | users must have permission to read wiki in order read events. |
| 54 | 56 | Enable the "j" permission under the /Setup/Users menu in order |
| 55 | 57 | 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. | |
| 66 | 68 | |
| 67 | 69 | 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 | -<title>...</title> 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. | |
| 76 | 76 |
| --- 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 | <title>...</title> 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 |
+30
-28
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -43,11 +43,11 @@ | ||
| 43 | 43 | <li> [#cluster | Clusters] </li> |
| 44 | 44 | <li> [#ctrl | Control Artifacts] </li> |
| 45 | 45 | <li> [#wikichng | Wiki Pages] </li> |
| 46 | 46 | <li> [#tktchng | Ticket Changes] </li> |
| 47 | 47 | <li> [#attachment | Attachments] </li> |
| 48 | -<li> [#event | Events] </li> | |
| 48 | +<li> [#event | TechNotes] </li> | |
| 49 | 49 | </ul> |
| 50 | 50 | |
| 51 | 51 | These seven artifact types are described in the following sections. |
| 52 | 52 | |
| 53 | 53 | In the current implementation (as of 2009-01-25) the artifacts that |
| @@ -423,11 +423,12 @@ | ||
| 423 | 423 | |
| 424 | 424 | <a name="attachment"></a> |
| 425 | 425 | <h2>6.0 Attachments</h2> |
| 426 | 426 | |
| 427 | 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 | |
| 428 | +attachment (the source artifact) with a ticket or wiki page or | |
| 429 | +technical note to which | |
| 429 | 430 | the attachment is connected (the target artifact). |
| 430 | 431 | The following cards are allowed on an attachment artifact: |
| 431 | 432 | |
| 432 | 433 | <blockquote> |
| 433 | 434 | <b>A</b> <i>filename target</i> ?<i>source</i>?<br /> |
| @@ -437,12 +438,12 @@ | ||
| 437 | 438 | <b>U</b> <i>user-name</i><br /> |
| 438 | 439 | <b>Z</b> <i>checksum</i> |
| 439 | 440 | </blockquote> |
| 440 | 441 | |
| 441 | 442 | 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 | |
| 444 | 445 | third argument is either missing or else it is the 40-character artifact |
| 445 | 446 | ID of the attachment itself. A missing third argument means that the |
| 446 | 447 | attachment should be deleted. |
| 447 | 448 | |
| 448 | 449 | The C card is an optional comment describing what the attachment is about. |
| @@ -461,72 +462,73 @@ | ||
| 461 | 462 | The Z card is the usual checksum over the rest of the attachment artifact. |
| 462 | 463 | The Z card is required. |
| 463 | 464 | |
| 464 | 465 | |
| 465 | 466 | <a name="event"></a> |
| 466 | -<h2>7.0 Events</h2> | |
| 467 | +<h2>7.0 Technical Notes</h2> | |
| 467 | 468 | |
| 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 | |
| 470 | 472 | to record project milestones, release notes, blog entries, process |
| 471 | 473 | checkpoints, or news articles. |
| 472 | -The following cards are allowed on an event artifact: | |
| 474 | +The following cards are allowed on an technote artifact: | |
| 473 | 475 | |
| 474 | 476 | <blockquote> |
| 475 | 477 | <b>C</b> <i>comment</i><br> |
| 476 | 478 | <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 /> | |
| 478 | 480 | <b>N</b> <i>mimetype</i><br /> |
| 479 | 481 | <b>P</b> <i>parent-artifact-id</i>+<br /> |
| 480 | 482 | <b>T</b> <b>+</b><i>tag-name</i> <b>*</b> ?<i>value</i>?<br /> |
| 481 | 483 | <b>U</b> <i>user-name</i><br /> |
| 482 | 484 | <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> |
| 483 | 485 | <b>Z</b> <i>checksum</i> |
| 484 | 486 | </blockquote> |
| 485 | 487 | |
| 486 | 488 | 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. | |
| 488 | 490 | |
| 489 | 491 | 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. | |
| 492 | 494 | |
| 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 | |
| 497 | 499 | 40-character lower-case hexadecimal string. |
| 498 | 500 | |
| 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 | |
| 500 | 502 | that is contained in the W card. If the N card is omitted, then the |
| 501 | 503 | W card text mimetype is assumed to be text/x-fossil, which is the |
| 502 | 504 | Fossil wiki format. |
| 503 | 505 | |
| 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. | |
| 508 | 510 | |
| 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 | |
| 510 | 512 | [./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 | |
| 512 | 514 | described in [#ctrl | Control Artifacts] section above, except that the |
| 513 | 515 | second argument is the single character "<b>*</b>" instead of an |
| 514 | 516 | artifact ID and the name is always prefaced by "<b>+</b>". |
| 515 | 517 | The <b>*</b> in place of the artifact ID indicates that |
| 516 | 518 | the tag or property applies to the current artifact. It is not |
| 517 | 519 | possible to encode the current artifact ID as part of an artifact, |
| 518 | 520 | since the act of inserting the artifact ID would change the artifact ID, |
| 519 | 521 | hence a <b>*</b> is used to represent "self". The "<b>+</b>" on the |
| 520 | 522 | 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 | |
| 522 | 524 | display color for timelines. |
| 523 | 525 | |
| 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. | |
| 525 | 527 | |
| 526 | 528 | 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 | |
| 528 | 530 | [#wikichng | wiki artifact]. |
| 529 | 531 | |
| 530 | 532 | The Z card is the required checksum over the rest of the artifact. |
| 531 | 533 | |
| 532 | 534 | |
| @@ -550,11 +552,11 @@ | ||
| 550 | 552 | <th>Cluster</th> |
| 551 | 553 | <th>Control</th> |
| 552 | 554 | <th>Wiki</th> |
| 553 | 555 | <th>Ticket</th> |
| 554 | 556 | <th>Attachment</th> |
| 555 | -<th>Event</th> | |
| 557 | +<th>Technote</th> | |
| 556 | 558 | </tr> |
| 557 | 559 | <tr> |
| 558 | 560 | <td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td> |
| 559 | 561 | <td> </td> |
| 560 | 562 | <td> </td> |
| @@ -594,11 +596,11 @@ | ||
| 594 | 596 | <td align=center><b>1</b></td> |
| 595 | 597 | <td align=center><b>1</b></td> |
| 596 | 598 | <td align=center><b>1</b></td> |
| 597 | 599 | </tr> |
| 598 | 600 | <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> | |
| 600 | 602 | <td> </td> |
| 601 | 603 | <td> </td> |
| 602 | 604 | <td> </td> |
| 603 | 605 | <td> </td> |
| 604 | 606 | <td> </td> |
| 605 | 607 |
| --- 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> </td> |
| 560 | <td> </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> </td> |
| 601 | <td> </td> |
| 602 | <td> </td> |
| 603 | <td> </td> |
| 604 | <td> </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> </td> |
| 562 | <td> </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> </td> |
| 603 | <td> </td> |
| 604 | <td> </td> |
| 605 | <td> </td> |
| 606 | <td> </td> |
| 607 |
+3
-3
| --- www/mkdownload.tcl | ||
| +++ www/mkdownload.tcl | ||
| @@ -7,11 +7,11 @@ | ||
| 7 | 7 | set out [open download.html w] |
| 8 | 8 | fconfigure $out -encoding utf-8 -translation lf |
| 9 | 9 | puts $out \ |
| 10 | 10 | {<!DOCTYPE html><html> |
| 11 | 11 | <head> |
| 12 | -<base href="http://www.fossil-scm.org/" /> | |
| 12 | +<base href="/" /> | |
| 13 | 13 | <title>Fossil: Downloads</title> |
| 14 | 14 | <link rel="stylesheet" href="/fossil/style.css" type="text/css" |
| 15 | 15 | media="screen"> |
| 16 | 16 | </head> |
| 17 | 17 | <body> |
| @@ -68,11 +68,11 @@ | ||
| 68 | 68 | set dt [string range $datetime 0 3]-[string range $datetime 4 5]- |
| 69 | 69 | append dt "[string range $datetime 6 7] " |
| 70 | 70 | append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:" |
| 71 | 71 | append dt "[string range $datetime 12 13]" |
| 72 | 72 | set link [string map {{ } +} $dt] |
| 73 | - set hr "http://www.fossil-scm.org/fossil/timeline?c=$link&y=ci" | |
| 73 | + set hr "/fossil/timeline?c=$link&y=ci" | |
| 74 | 74 | puts $out "<tr><td colspan=6 align=left><hr>" |
| 75 | 75 | puts $out "<center><b><a href=\"$hr\">$dt</a></b></center>" |
| 76 | 76 | puts $out "</td></tr>" |
| 77 | 77 | puts $out "<tr>" |
| 78 | 78 | |
| @@ -128,14 +128,14 @@ | ||
| 128 | 128 | <title>Fossil Download Checksums</title> |
| 129 | 129 | <body> |
| 130 | 130 | <h1 align="center">Checksums For Fossil Downloads</h1> |
| 131 | 131 | <p>The following table shows the SHA1 checksums for the precompiled |
| 132 | 132 | 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> | |
| 134 | 134 | <pre>} |
| 135 | 135 | |
| 136 | 136 | foreach file [lsort [glob -nocomplain download/fossil-*.zip]] { |
| 137 | 137 | set sha1sum [lindex [exec sha1sum $file] 0] |
| 138 | 138 | puts $out "$sha1sum [file tail $file]" |
| 139 | 139 | } |
| 140 | 140 | puts $out {</pre></body></html>} |
| 141 | 141 | close $out |
| 142 | 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="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&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&y=ci" |
| 74 | puts $out "<tr><td colspan=6 align=left><hr>" |
| 75 | puts $out "<center><b><a href=\"$hr\">$dt</a></b></center>" |
| 76 | puts $out "</td></tr>" |
| 77 | puts $out "<tr>" |
| 78 | |
| @@ -128,14 +128,14 @@ | |
| 128 | <title>Fossil Download Checksums</title> |
| 129 | <body> |
| 130 | <h1 align="center">Checksums For Fossil Downloads</h1> |
| 131 | <p>The following table shows the SHA1 checksums for the precompiled |
| 132 | binaries available on the |
| 133 | <a href="/download.html">Fossil website</a>.</p> |
| 134 | <pre>} |
| 135 | |
| 136 | foreach file [lsort [glob -nocomplain download/fossil-*.zip]] { |
| 137 | set sha1sum [lindex [exec sha1sum $file] 0] |
| 138 | puts $out "$sha1sum [file tail $file]" |
| 139 | } |
| 140 | puts $out {</pre></body></html>} |
| 141 | close $out |
| 142 |
+1
-1
| --- www/mkindex.tcl | ||
| +++ www/mkindex.tcl | ||
| @@ -93,11 +93,11 @@ | ||
| 93 | 93 | <h2>Primary Documents:</h2> |
| 94 | 94 | <ul> |
| 95 | 95 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 96 | 96 | <li> <a href='faq.wiki'>FAQ</a> |
| 97 | 97 | <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> | |
| 99 | 99 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 100 | 100 | book</a> |
| 101 | 101 | <li> <a href='../../../help'>Command-line help</a> |
| 102 | 102 | </ul> |
| 103 | 103 | <a name="pindex"></a> |
| 104 | 104 |
| --- www/mkindex.tcl | |
| +++ www/mkindex.tcl | |
| @@ -93,11 +93,11 @@ | |
| 93 | <h2>Primary Documents:</h2> |
| 94 | <ul> |
| 95 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 96 | <li> <a href='faq.wiki'>FAQ</a> |
| 97 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 98 | <li> <a href='COPYRIGHT-BSD2.txt'>License</a> |
| 99 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 100 | book</a> |
| 101 | <li> <a href='../../../help'>Command-line help</a> |
| 102 | </ul> |
| 103 | <a name="pindex"></a> |
| 104 |
| --- www/mkindex.tcl | |
| +++ www/mkindex.tcl | |
| @@ -93,11 +93,11 @@ | |
| 93 | <h2>Primary Documents:</h2> |
| 94 | <ul> |
| 95 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 96 | <li> <a href='faq.wiki'>FAQ</a> |
| 97 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 98 | <li> <a href='../COPYRIGHT-BSD2.txt'>License</a> |
| 99 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 100 | book</a> |
| 101 | <li> <a href='../../../help'>Command-line help</a> |
| 102 | </ul> |
| 103 | <a name="pindex"></a> |
| 104 |
+1
-1
| --- www/permutedindex.html | ||
| +++ www/permutedindex.html | ||
| @@ -9,11 +9,11 @@ | ||
| 9 | 9 | <h2>Primary Documents:</h2> |
| 10 | 10 | <ul> |
| 11 | 11 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 12 | 12 | <li> <a href='faq.wiki'>FAQ</a> |
| 13 | 13 | <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> | |
| 15 | 15 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 16 | 16 | book</a> |
| 17 | 17 | <li> <a href='../../../help'>Command-line help</a> |
| 18 | 18 | </ul> |
| 19 | 19 | <a name="pindex"></a> |
| 20 | 20 |
| --- www/permutedindex.html | |
| +++ www/permutedindex.html | |
| @@ -9,11 +9,11 @@ | |
| 9 | <h2>Primary Documents:</h2> |
| 10 | <ul> |
| 11 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 12 | <li> <a href='faq.wiki'>FAQ</a> |
| 13 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 14 | <li> <a href='COPYRIGHT-BSD2.txt'>License</a> |
| 15 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 16 | book</a> |
| 17 | <li> <a href='../../../help'>Command-line help</a> |
| 18 | </ul> |
| 19 | <a name="pindex"></a> |
| 20 |
| --- www/permutedindex.html | |
| +++ www/permutedindex.html | |
| @@ -9,11 +9,11 @@ | |
| 9 | <h2>Primary Documents:</h2> |
| 10 | <ul> |
| 11 | <li> <a href='quickstart.wiki'>Quick-start Guide</a> |
| 12 | <li> <a href='faq.wiki'>FAQ</a> |
| 13 | <li> <a href='build.wiki'>Compiling and installing Fossil</a> |
| 14 | <li> <a href='../COPYRIGHT-BSD2.txt'>License</a> |
| 15 | <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's |
| 16 | book</a> |
| 17 | <li> <a href='../../../help'>Command-line help</a> |
| 18 | </ul> |
| 19 | <a name="pindex"></a> |
| 20 |
+6
-2
| --- www/wikitheory.wiki | ||
| +++ www/wikitheory.wiki | ||
| @@ -5,12 +5,12 @@ | ||
| 5 | 5 | |
| 6 | 6 | * Stand-alone wiki pages. |
| 7 | 7 | * Description and comments in [./bugtheory.wiki | bug reports]. |
| 8 | 8 | * Check-in comments. |
| 9 | 9 | * [./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]. | |
| 12 | 12 | |
| 13 | 13 | The [/wiki_rules | formatting rules] for fossil wiki |
| 14 | 14 | are designed to be simple and intuitive. The idea is that wiki provides |
| 15 | 15 | paragraph breaks, numbered and bulleted lists, and hyperlinking for |
| 16 | 16 | simple documents together with a safe subset of HTML for more complex |
| @@ -31,10 +31,14 @@ | ||
| 31 | 31 | 3. Where the fossil wiki markup language is insufficient, HTML is |
| 32 | 32 | used. HTML is a standard language familiar to most programmers so |
| 33 | 33 | there is nothing new to learn. And, though cumbersome, the HTML |
| 34 | 34 | does not need to be used very often so is not a burden. |
| 35 | 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. | |
| 36 | 40 | |
| 37 | 41 | <h2>Stand-alone Wiki Pages</h2> |
| 38 | 42 | |
| 39 | 43 | Each wiki page has its own revision history which is independent of |
| 40 | 44 | the sequence of check-ins (check-ins). Wiki pages can branch and merge |
| 41 | 45 |
| --- 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 |