Fossil SCM
Merge in all of the latest clear-title changes from the trunk.
Commit
7c2577bd637e91b0d3649f75555644cd8b46189d
Parent
dfb5fd64cdcdc47…
49 files changed
+14
+9
+22
-10
+73
-4
+73
-4
+9
+9
+39
-2
+39
-2
+1
-1
+1
-1
+48
-12
+61
-38
+42
+4
-2
+1
+12
-2
+12
-2
+1
+1
+122
-91
+36
-9
+65
-1
+31
+21
-10
+21
-10
+40
-40
+2
-2
+6
-1
+42
-35
+136
-60
+70
-8
+196
-3
+3
-3
+3
+24
-5
+1
-4
+21
-6
+237
-85
+63
-44
+9
-8
-13
+1
-1
~
COPYRIGHT-GPL2.txt
~
src/attach.c
~
src/browse.c
~
src/checkin.c
~
src/checkin.c
~
src/checkout.c
~
src/checkout.c
~
src/db.c
~
src/db.c
~
src/diffcmd.c
~
src/file.c
~
src/finfo.c
~
src/graph.c
~
src/info.c
~
src/login.c
~
src/main.c
~
src/main.mk
~
src/main.mk
~
src/makemake.tcl
~
src/makemake.tcl
~
src/manifest.c
~
src/merge.c
~
src/name.c
~
src/schema.c
~
src/setup.c
~
src/setup.c
~
src/skins.c
~
src/style.c
~
src/tag.c
~
src/timeline.c
~
src/tkt.c
~
src/wiki.c
~
src/wikiformat.c
~
src/winhttp.c
~
src/xfer.c
~
src/zip.c
~
www/branching.wiki
~
www/build-icons/linux.gif
~
www/build-icons/linux64.gif
~
www/build-icons/mac.gif
~
www/build-icons/src.gif
~
www/build-icons/win32.gif
~
www/build.wiki
~
www/embeddeddoc.wiki
~
www/fileformat.wiki
~
www/mkdownload.tcl
~
www/quickstart.wiki
~
www/webui.wiki
~
www/wikitheory.wiki
+14
| --- COPYRIGHT-GPL2.txt | ||
| +++ COPYRIGHT-GPL2.txt | ||
| @@ -1,5 +1,19 @@ | ||
| 1 | +Fossil is licensed under the terms of the GPLv2 shown below. In | |
| 2 | +addition, permission is granted to link Fossil against the OpenSSL | |
| 3 | +project's "OpenSSL" library (or with modified versions of that | |
| 4 | +library that use the same license), and distribute the linked | |
| 5 | +executables. If you modify Fossil, you may extend the exception | |
| 6 | +described in this paragraph to your modifications, or not, at your | |
| 7 | +discretion. | |
| 8 | + | |
| 9 | +The "clear-title" branch of Fossil is available for more liberal | |
| 10 | +licenses such as Berkeley-style licenses or the Apache license. | |
| 11 | + | |
| 12 | +The original text of GPLv2 follows. | |
| 13 | +-------------------------------------------------------------------------- | |
| 14 | + | |
| 1 | 15 | GNU GENERAL PUBLIC LICENSE |
| 2 | 16 | Version 2, June 1991 |
| 3 | 17 | |
| 4 | 18 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., |
| 5 | 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 6 | 20 | |
| 7 | 21 | ADDED src/attach.c |
| --- COPYRIGHT-GPL2.txt | |
| +++ COPYRIGHT-GPL2.txt | |
| @@ -1,5 +1,19 @@ | |
| 1 | GNU GENERAL PUBLIC LICENSE |
| 2 | Version 2, June 1991 |
| 3 | |
| 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., |
| 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 6 | |
| 7 | DDED src/attach.c |
| --- COPYRIGHT-GPL2.txt | |
| +++ COPYRIGHT-GPL2.txt | |
| @@ -1,5 +1,19 @@ | |
| 1 | Fossil is licensed under the terms of the GPLv2 shown below. In |
| 2 | addition, permission is granted to link Fossil against the OpenSSL |
| 3 | project's "OpenSSL" library (or with modified versions of that |
| 4 | library that use the same license), and distribute the linked |
| 5 | executables. If you modify Fossil, you may extend the exception |
| 6 | described in this paragraph to your modifications, or not, at your |
| 7 | discretion. |
| 8 | |
| 9 | The "clear-title" branch of Fossil is available for more liberal |
| 10 | licenses such as Berkeley-style licenses or the Apache license. |
| 11 | |
| 12 | The original text of GPLv2 follows. |
| 13 | -------------------------------------------------------------------------- |
| 14 | |
| 15 | GNU GENERAL PUBLIC LICENSE |
| 16 | Version 2, June 1991 |
| 17 | |
| 18 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., |
| 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 20 | |
| 21 | DDED src/attach.c |
+9
| --- a/src/attach.c | ||
| +++ b/src/attach.c | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +[] Oif(zMime =Name);[] O .10s", z] Oif(zMime =Name);[] O .10s", zTkt Oif(zMime[] Oif(zMime =Name);[] O | |
| 2 | + | |
| 3 | + | |
| 4 | +} | |
| 5 | +S[SSg.zLogin ? g.zLogin : "noboG all[] Oif(zMimes", z] Oif(zMime =Name);[] O .10s", zTkt Oif(zMime[] Oif(zMime =Name);[] O | |
| 6 | + | |
| 7 | + | |
| 8 | +} | |
| 9 | +S[SSg.zLogin ? g.zLogin : "noboG |
| --- a/src/attach.c | |
| +++ b/src/attach.c | |
| @@ -0,0 +1,9 @@ | |
| --- a/src/attach.c | |
| +++ b/src/attach.c | |
| @@ -0,0 +1,9 @@ | |
| 1 | [] Oif(zMime =Name);[] O .10s", z] Oif(zMime =Name);[] O .10s", zTkt Oif(zMime[] Oif(zMime =Name);[] O |
| 2 | |
| 3 | |
| 4 | } |
| 5 | S[SSg.zLogin ? g.zLogin : "noboG all[] Oif(zMimes", z] Oif(zMime =Name);[] O .10s", zTkt Oif(zMime[] Oif(zMime =Name);[] O |
| 6 | |
| 7 | |
| 8 | } |
| 9 | S[SSg.zLogin ? g.zLogin : "noboG |
+22
-10
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -113,10 +113,11 @@ | ||
| 113 | 113 | char *zPrefix; |
| 114 | 114 | Stmt q; |
| 115 | 115 | const char *zCI = P("ci"); |
| 116 | 116 | int rid = 0; |
| 117 | 117 | Blob content; |
| 118 | + Blob dirname; | |
| 118 | 119 | Manifest m; |
| 119 | 120 | const char *zSubdirLink; |
| 120 | 121 | |
| 121 | 122 | login_check_credentials(); |
| 122 | 123 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -133,36 +134,47 @@ | ||
| 133 | 134 | zCI = 0; |
| 134 | 135 | } |
| 135 | 136 | } |
| 136 | 137 | |
| 137 | 138 | /* Compute the title of the page */ |
| 139 | + blob_zero(&dirname); | |
| 138 | 140 | if( zD ){ |
| 139 | - Blob title; | |
| 140 | - | |
| 141 | - blob_zero(&title); | |
| 142 | - blob_appendf(&title, "Files in directory "); | |
| 143 | - hyperlinked_path(zD, &title); | |
| 144 | - @ <h2>%s(blob_str(&title)) | |
| 145 | - blob_reset(&title); | |
| 141 | + blob_append(&dirname, "in directory ", -1); | |
| 142 | + hyperlinked_path(zD, &dirname); | |
| 146 | 143 | zPrefix = mprintf("%h/", zD); |
| 147 | 144 | }else{ |
| 148 | - @ <h2>Files in the top-level directory | |
| 145 | + blob_append(&dirname, "in the top-level directory", -1); | |
| 149 | 146 | zPrefix = ""; |
| 150 | 147 | } |
| 151 | 148 | if( zCI ){ |
| 152 | 149 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 153 | 150 | char zShort[20]; |
| 154 | 151 | memcpy(zShort, zUuid, 10); |
| 155 | 152 | zShort[10] = 0; |
| 156 | - @ of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>]</h2> | |
| 153 | + @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] | |
| 154 | + @ %s(blob_str(&dirname))</h2> | |
| 157 | 155 | zSubdirLink = mprintf("%s/dir?ci=%s&name=%T", g.zBaseURL, zUuid, zPrefix); |
| 158 | 156 | if( zD ){ |
| 159 | 157 | style_submenu_element("Top", "Top", "%s/dir?ci=%s", g.zBaseURL, zUuid); |
| 158 | + style_submenu_element("All", "All", "%s/dir?name=%t", g.zBaseURL, zD); | |
| 159 | + }else{ | |
| 160 | + style_submenu_element("All", "All", "%s/dir", g.zBaseURL); | |
| 160 | 161 | } |
| 161 | 162 | }else{ |
| 162 | - @ </h2> | |
| 163 | + @ <h2>The union of all files from all check-ins | |
| 164 | + @ %s(blob_str(&dirname))</h2> | |
| 163 | 165 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 166 | + if( zD ){ | |
| 167 | + style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); | |
| 168 | + style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", | |
| 169 | + g.zBaseURL, zD); | |
| 170 | + style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", | |
| 171 | + g.zBaseURL,zD); | |
| 172 | + }else{ | |
| 173 | + style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); | |
| 174 | + style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); | |
| 175 | + } | |
| 164 | 176 | } |
| 165 | 177 | |
| 166 | 178 | /* Compute the temporary table "localfiles" containing the names |
| 167 | 179 | ** of all files and subdirectories in the zD[] directory. |
| 168 | 180 | ** |
| 169 | 181 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -113,10 +113,11 @@ | |
| 113 | char *zPrefix; |
| 114 | Stmt q; |
| 115 | const char *zCI = P("ci"); |
| 116 | int rid = 0; |
| 117 | Blob content; |
| 118 | Manifest m; |
| 119 | const char *zSubdirLink; |
| 120 | |
| 121 | login_check_credentials(); |
| 122 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -133,36 +134,47 @@ | |
| 133 | zCI = 0; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /* Compute the title of the page */ |
| 138 | if( zD ){ |
| 139 | Blob title; |
| 140 | |
| 141 | blob_zero(&title); |
| 142 | blob_appendf(&title, "Files in directory "); |
| 143 | hyperlinked_path(zD, &title); |
| 144 | @ <h2>%s(blob_str(&title)) |
| 145 | blob_reset(&title); |
| 146 | zPrefix = mprintf("%h/", zD); |
| 147 | }else{ |
| 148 | @ <h2>Files in the top-level directory |
| 149 | zPrefix = ""; |
| 150 | } |
| 151 | if( zCI ){ |
| 152 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 153 | char zShort[20]; |
| 154 | memcpy(zShort, zUuid, 10); |
| 155 | zShort[10] = 0; |
| 156 | @ of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>]</h2> |
| 157 | zSubdirLink = mprintf("%s/dir?ci=%s&name=%T", g.zBaseURL, zUuid, zPrefix); |
| 158 | if( zD ){ |
| 159 | style_submenu_element("Top", "Top", "%s/dir?ci=%s", g.zBaseURL, zUuid); |
| 160 | } |
| 161 | }else{ |
| 162 | @ </h2> |
| 163 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 164 | } |
| 165 | |
| 166 | /* Compute the temporary table "localfiles" containing the names |
| 167 | ** of all files and subdirectories in the zD[] directory. |
| 168 | ** |
| 169 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -113,10 +113,11 @@ | |
| 113 | char *zPrefix; |
| 114 | Stmt q; |
| 115 | const char *zCI = P("ci"); |
| 116 | int rid = 0; |
| 117 | Blob content; |
| 118 | Blob dirname; |
| 119 | Manifest m; |
| 120 | const char *zSubdirLink; |
| 121 | |
| 122 | login_check_credentials(); |
| 123 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -133,36 +134,47 @@ | |
| 134 | zCI = 0; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | /* Compute the title of the page */ |
| 139 | blob_zero(&dirname); |
| 140 | if( zD ){ |
| 141 | blob_append(&dirname, "in directory ", -1); |
| 142 | hyperlinked_path(zD, &dirname); |
| 143 | zPrefix = mprintf("%h/", zD); |
| 144 | }else{ |
| 145 | blob_append(&dirname, "in the top-level directory", -1); |
| 146 | zPrefix = ""; |
| 147 | } |
| 148 | if( zCI ){ |
| 149 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 150 | char zShort[20]; |
| 151 | memcpy(zShort, zUuid, 10); |
| 152 | zShort[10] = 0; |
| 153 | @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] |
| 154 | @ %s(blob_str(&dirname))</h2> |
| 155 | zSubdirLink = mprintf("%s/dir?ci=%s&name=%T", g.zBaseURL, zUuid, zPrefix); |
| 156 | if( zD ){ |
| 157 | style_submenu_element("Top", "Top", "%s/dir?ci=%s", g.zBaseURL, zUuid); |
| 158 | style_submenu_element("All", "All", "%s/dir?name=%t", g.zBaseURL, zD); |
| 159 | }else{ |
| 160 | style_submenu_element("All", "All", "%s/dir", g.zBaseURL); |
| 161 | } |
| 162 | }else{ |
| 163 | @ <h2>The union of all files from all check-ins |
| 164 | @ %s(blob_str(&dirname))</h2> |
| 165 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 166 | if( zD ){ |
| 167 | style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); |
| 168 | style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", |
| 169 | g.zBaseURL, zD); |
| 170 | style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", |
| 171 | g.zBaseURL,zD); |
| 172 | }else{ |
| 173 | style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); |
| 174 | style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | /* Compute the temporary table "localfiles" containing the names |
| 179 | ** of all files and subdirectories in the zD[] directory. |
| 180 | ** |
| 181 |
+73
-4
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -193,14 +193,68 @@ | ||
| 193 | 193 | } |
| 194 | 194 | free(zFullName); |
| 195 | 195 | } |
| 196 | 196 | db_finalize(&q); |
| 197 | 197 | } |
| 198 | + | |
| 199 | +/* | |
| 200 | +** Construct and return a string which is an SQL expression that will | |
| 201 | +** be TRUE if value zVal matches any of the GLOB expressions in the list | |
| 202 | +** zGlobList. For example: | |
| 203 | +** | |
| 204 | +** zVal: "x" | |
| 205 | +** zGlobList: "*.o,*.obj" | |
| 206 | +** | |
| 207 | +** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" | |
| 208 | +** | |
| 209 | +** Each element of the GLOB list may optionally be enclosed in either '...' | |
| 210 | +** or "...". This allows commas in the expression. Whitespace at the | |
| 211 | +** beginning and end of each GLOB pattern is ignored, except when enclosed | |
| 212 | +** within '...' or "...". | |
| 213 | +** | |
| 214 | +** This routine makes no effort to free the memory space it uses. | |
| 215 | +*/ | |
| 216 | +char *glob_expr(const char *zVal, const char *zGlobList){ | |
| 217 | + Blob expr; | |
| 218 | + char *zSep = "("; | |
| 219 | + int nTerm = 0; | |
| 220 | + int i; | |
| 221 | + int cTerm; | |
| 222 | + | |
| 223 | + if( zGlobList==0 || zGlobList[0]==0 ) return "0"; | |
| 224 | + blob_zero(&expr); | |
| 225 | + while( zGlobList[0] ){ | |
| 226 | + while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++; | |
| 227 | + if( zGlobList[0]==0 ) break; | |
| 228 | + if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ | |
| 229 | + cTerm = zGlobList[0]; | |
| 230 | + zGlobList++; | |
| 231 | + }else{ | |
| 232 | + cTerm = ','; | |
| 233 | + } | |
| 234 | + for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){} | |
| 235 | + if( cTerm==',' ){ | |
| 236 | + while( i>0 && isspace(zGlobList[i-1]) ){ i--; } | |
| 237 | + } | |
| 238 | + blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList); | |
| 239 | + zSep = " OR "; | |
| 240 | + if( cTerm!=',' && zGlobList[i] ) i++; | |
| 241 | + zGlobList += i; | |
| 242 | + if( zGlobList[0] ) zGlobList++; | |
| 243 | + nTerm++; | |
| 244 | + } | |
| 245 | + if( nTerm ){ | |
| 246 | + blob_appendf(&expr, ")"); | |
| 247 | + return blob_str(&expr); | |
| 248 | + }else{ | |
| 249 | + return "0"; | |
| 250 | + } | |
| 251 | +} | |
| 198 | 252 | |
| 199 | 253 | /* |
| 200 | 254 | ** COMMAND: extras |
| 201 | -** Usage: %fossil extras ?--dotfiles? | |
| 255 | +** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN? | |
| 202 | 256 | ** |
| 203 | 257 | ** Print a list of all files in the source tree that are not part of |
| 204 | 258 | ** the current checkout. See also the "clean" command. |
| 205 | 259 | ** |
| 206 | 260 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | ||
| 209 | 263 | void extra_cmd(void){ |
| 210 | 264 | Blob path; |
| 211 | 265 | Blob repo; |
| 212 | 266 | Stmt q; |
| 213 | 267 | int n; |
| 268 | + const char *zIgnoreFlag = find_option("ignore",0,1); | |
| 214 | 269 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 270 | + | |
| 215 | 271 | db_must_be_within_tree(); |
| 216 | 272 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 217 | 273 | n = strlen(g.zLocalRoot); |
| 218 | 274 | blob_init(&path, g.zLocalRoot, n-1); |
| 275 | + if( zIgnoreFlag==0 ){ | |
| 276 | + zIgnoreFlag = db_get("ignore-glob", 0); | |
| 277 | + } | |
| 219 | 278 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 220 | 279 | db_prepare(&q, |
| 221 | 280 | "SELECT x FROM sfile" |
| 222 | 281 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 223 | - " ORDER BY 1"); | |
| 282 | + " AND NOT %s" | |
| 283 | + " ORDER BY 1", | |
| 284 | + glob_expr("x", zIgnoreFlag) | |
| 285 | + ); | |
| 224 | 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 225 | 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 226 | 288 | } |
| 227 | 289 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | 290 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | ||
| 683 | 745 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 684 | 746 | zDate[10] = 'T'; |
| 685 | 747 | blob_appendf(&manifest, "D %s\n", zDate); |
| 686 | 748 | zDate[10] = ' '; |
| 687 | 749 | db_prepare(&q, |
| 688 | - "SELECT pathname, uuid, origname, blob.rid" | |
| 750 | + "SELECT pathname, uuid, origname, blob.rid, isexe" | |
| 689 | 751 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 690 | 752 | " WHERE NOT deleted AND vfile.vid=%d" |
| 691 | 753 | " ORDER BY 1", vid); |
| 692 | 754 | blob_zero(&filename); |
| 693 | 755 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | ||
| 695 | 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 696 | 758 | const char *zName = db_column_text(&q, 0); |
| 697 | 759 | const char *zUuid = db_column_text(&q, 1); |
| 698 | 760 | const char *zOrig = db_column_text(&q, 2); |
| 699 | 761 | int frid = db_column_int(&q, 3); |
| 762 | + int isexe = db_column_int(&q, 4); | |
| 700 | 763 | const char *zPerm; |
| 701 | 764 | blob_append(&filename, zName, -1); |
| 702 | - if( file_isexe(blob_str(&filename)) ){ | |
| 765 | +#ifndef __MINGW32__ | |
| 766 | + /* For unix, extract the "executable" permission bit directly from | |
| 767 | + ** the filesystem. On windows, the "executable" bit is retained | |
| 768 | + ** unchanged from the original. */ | |
| 769 | + isexe = file_isexe(blob_str(&filename)); | |
| 770 | +#endif | |
| 771 | + if( isexe ){ | |
| 703 | 772 | zPerm = " x"; |
| 704 | 773 | }else{ |
| 705 | 774 | zPerm = ""; |
| 706 | 775 | } |
| 707 | 776 | blob_resize(&filename, nBasename); |
| 708 | 777 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -193,14 +193,68 @@ | |
| 193 | } |
| 194 | free(zFullName); |
| 195 | } |
| 196 | db_finalize(&q); |
| 197 | } |
| 198 | |
| 199 | /* |
| 200 | ** COMMAND: extras |
| 201 | ** Usage: %fossil extras ?--dotfiles? |
| 202 | ** |
| 203 | ** Print a list of all files in the source tree that are not part of |
| 204 | ** the current checkout. See also the "clean" command. |
| 205 | ** |
| 206 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | |
| 209 | void extra_cmd(void){ |
| 210 | Blob path; |
| 211 | Blob repo; |
| 212 | Stmt q; |
| 213 | int n; |
| 214 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 215 | db_must_be_within_tree(); |
| 216 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 217 | n = strlen(g.zLocalRoot); |
| 218 | blob_init(&path, g.zLocalRoot, n-1); |
| 219 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 220 | db_prepare(&q, |
| 221 | "SELECT x FROM sfile" |
| 222 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 223 | " ORDER BY 1"); |
| 224 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 225 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 226 | } |
| 227 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | |
| 683 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 684 | zDate[10] = 'T'; |
| 685 | blob_appendf(&manifest, "D %s\n", zDate); |
| 686 | zDate[10] = ' '; |
| 687 | db_prepare(&q, |
| 688 | "SELECT pathname, uuid, origname, blob.rid" |
| 689 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 690 | " WHERE NOT deleted AND vfile.vid=%d" |
| 691 | " ORDER BY 1", vid); |
| 692 | blob_zero(&filename); |
| 693 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | |
| 695 | while( db_step(&q)==SQLITE_ROW ){ |
| 696 | const char *zName = db_column_text(&q, 0); |
| 697 | const char *zUuid = db_column_text(&q, 1); |
| 698 | const char *zOrig = db_column_text(&q, 2); |
| 699 | int frid = db_column_int(&q, 3); |
| 700 | const char *zPerm; |
| 701 | blob_append(&filename, zName, -1); |
| 702 | if( file_isexe(blob_str(&filename)) ){ |
| 703 | zPerm = " x"; |
| 704 | }else{ |
| 705 | zPerm = ""; |
| 706 | } |
| 707 | blob_resize(&filename, nBasename); |
| 708 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -193,14 +193,68 @@ | |
| 193 | } |
| 194 | free(zFullName); |
| 195 | } |
| 196 | db_finalize(&q); |
| 197 | } |
| 198 | |
| 199 | /* |
| 200 | ** Construct and return a string which is an SQL expression that will |
| 201 | ** be TRUE if value zVal matches any of the GLOB expressions in the list |
| 202 | ** zGlobList. For example: |
| 203 | ** |
| 204 | ** zVal: "x" |
| 205 | ** zGlobList: "*.o,*.obj" |
| 206 | ** |
| 207 | ** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" |
| 208 | ** |
| 209 | ** Each element of the GLOB list may optionally be enclosed in either '...' |
| 210 | ** or "...". This allows commas in the expression. Whitespace at the |
| 211 | ** beginning and end of each GLOB pattern is ignored, except when enclosed |
| 212 | ** within '...' or "...". |
| 213 | ** |
| 214 | ** This routine makes no effort to free the memory space it uses. |
| 215 | */ |
| 216 | char *glob_expr(const char *zVal, const char *zGlobList){ |
| 217 | Blob expr; |
| 218 | char *zSep = "("; |
| 219 | int nTerm = 0; |
| 220 | int i; |
| 221 | int cTerm; |
| 222 | |
| 223 | if( zGlobList==0 || zGlobList[0]==0 ) return "0"; |
| 224 | blob_zero(&expr); |
| 225 | while( zGlobList[0] ){ |
| 226 | while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++; |
| 227 | if( zGlobList[0]==0 ) break; |
| 228 | if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ |
| 229 | cTerm = zGlobList[0]; |
| 230 | zGlobList++; |
| 231 | }else{ |
| 232 | cTerm = ','; |
| 233 | } |
| 234 | for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){} |
| 235 | if( cTerm==',' ){ |
| 236 | while( i>0 && isspace(zGlobList[i-1]) ){ i--; } |
| 237 | } |
| 238 | blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList); |
| 239 | zSep = " OR "; |
| 240 | if( cTerm!=',' && zGlobList[i] ) i++; |
| 241 | zGlobList += i; |
| 242 | if( zGlobList[0] ) zGlobList++; |
| 243 | nTerm++; |
| 244 | } |
| 245 | if( nTerm ){ |
| 246 | blob_appendf(&expr, ")"); |
| 247 | return blob_str(&expr); |
| 248 | }else{ |
| 249 | return "0"; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | ** COMMAND: extras |
| 255 | ** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN? |
| 256 | ** |
| 257 | ** Print a list of all files in the source tree that are not part of |
| 258 | ** the current checkout. See also the "clean" command. |
| 259 | ** |
| 260 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | |
| 263 | void extra_cmd(void){ |
| 264 | Blob path; |
| 265 | Blob repo; |
| 266 | Stmt q; |
| 267 | int n; |
| 268 | const char *zIgnoreFlag = find_option("ignore",0,1); |
| 269 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 270 | |
| 271 | db_must_be_within_tree(); |
| 272 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 273 | n = strlen(g.zLocalRoot); |
| 274 | blob_init(&path, g.zLocalRoot, n-1); |
| 275 | if( zIgnoreFlag==0 ){ |
| 276 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 277 | } |
| 278 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 279 | db_prepare(&q, |
| 280 | "SELECT x FROM sfile" |
| 281 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 282 | " AND NOT %s" |
| 283 | " ORDER BY 1", |
| 284 | glob_expr("x", zIgnoreFlag) |
| 285 | ); |
| 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 288 | } |
| 289 | while( db_step(&q)==SQLITE_ROW ){ |
| 290 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | |
| 745 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 746 | zDate[10] = 'T'; |
| 747 | blob_appendf(&manifest, "D %s\n", zDate); |
| 748 | zDate[10] = ' '; |
| 749 | db_prepare(&q, |
| 750 | "SELECT pathname, uuid, origname, blob.rid, isexe" |
| 751 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 752 | " WHERE NOT deleted AND vfile.vid=%d" |
| 753 | " ORDER BY 1", vid); |
| 754 | blob_zero(&filename); |
| 755 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | |
| 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 758 | const char *zName = db_column_text(&q, 0); |
| 759 | const char *zUuid = db_column_text(&q, 1); |
| 760 | const char *zOrig = db_column_text(&q, 2); |
| 761 | int frid = db_column_int(&q, 3); |
| 762 | int isexe = db_column_int(&q, 4); |
| 763 | const char *zPerm; |
| 764 | blob_append(&filename, zName, -1); |
| 765 | #ifndef __MINGW32__ |
| 766 | /* For unix, extract the "executable" permission bit directly from |
| 767 | ** the filesystem. On windows, the "executable" bit is retained |
| 768 | ** unchanged from the original. */ |
| 769 | isexe = file_isexe(blob_str(&filename)); |
| 770 | #endif |
| 771 | if( isexe ){ |
| 772 | zPerm = " x"; |
| 773 | }else{ |
| 774 | zPerm = ""; |
| 775 | } |
| 776 | blob_resize(&filename, nBasename); |
| 777 |
+73
-4
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -193,14 +193,68 @@ | ||
| 193 | 193 | } |
| 194 | 194 | free(zFullName); |
| 195 | 195 | } |
| 196 | 196 | db_finalize(&q); |
| 197 | 197 | } |
| 198 | + | |
| 199 | +/* | |
| 200 | +** Construct and return a string which is an SQL expression that will | |
| 201 | +** be TRUE if value zVal matches any of the GLOB expressions in the list | |
| 202 | +** zGlobList. For example: | |
| 203 | +** | |
| 204 | +** zVal: "x" | |
| 205 | +** zGlobList: "*.o,*.obj" | |
| 206 | +** | |
| 207 | +** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" | |
| 208 | +** | |
| 209 | +** Each element of the GLOB list may optionally be enclosed in either '...' | |
| 210 | +** or "...". This allows commas in the expression. Whitespace at the | |
| 211 | +** beginning and end of each GLOB pattern is ignored, except when enclosed | |
| 212 | +** within '...' or "...". | |
| 213 | +** | |
| 214 | +** This routine makes no effort to free the memory space it uses. | |
| 215 | +*/ | |
| 216 | +char *glob_expr(const char *zVal, const char *zGlobList){ | |
| 217 | + Blob expr; | |
| 218 | + char *zSep = "("; | |
| 219 | + int nTerm = 0; | |
| 220 | + int i; | |
| 221 | + int cTerm; | |
| 222 | + | |
| 223 | + if( zGlobList==0 || zGlobList[0]==0 ) return "0"; | |
| 224 | + blob_zero(&expr); | |
| 225 | + while( zGlobList[0] ){ | |
| 226 | + while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++; | |
| 227 | + if( zGlobList[0]==0 ) break; | |
| 228 | + if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ | |
| 229 | + cTerm = zGlobList[0]; | |
| 230 | + zGlobList++; | |
| 231 | + }else{ | |
| 232 | + cTerm = ','; | |
| 233 | + } | |
| 234 | + for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){} | |
| 235 | + if( cTerm==',' ){ | |
| 236 | + while( i>0 && isspace(zGlobList[i-1]) ){ i--; } | |
| 237 | + } | |
| 238 | + blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList); | |
| 239 | + zSep = " OR "; | |
| 240 | + if( cTerm!=',' && zGlobList[i] ) i++; | |
| 241 | + zGlobList += i; | |
| 242 | + if( zGlobList[0] ) zGlobList++; | |
| 243 | + nTerm++; | |
| 244 | + } | |
| 245 | + if( nTerm ){ | |
| 246 | + blob_appendf(&expr, ")"); | |
| 247 | + return blob_str(&expr); | |
| 248 | + }else{ | |
| 249 | + return "0"; | |
| 250 | + } | |
| 251 | +} | |
| 198 | 252 | |
| 199 | 253 | /* |
| 200 | 254 | ** COMMAND: extras |
| 201 | -** Usage: %fossil extras ?--dotfiles? | |
| 255 | +** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN? | |
| 202 | 256 | ** |
| 203 | 257 | ** Print a list of all files in the source tree that are not part of |
| 204 | 258 | ** the current checkout. See also the "clean" command. |
| 205 | 259 | ** |
| 206 | 260 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | ||
| 209 | 263 | void extra_cmd(void){ |
| 210 | 264 | Blob path; |
| 211 | 265 | Blob repo; |
| 212 | 266 | Stmt q; |
| 213 | 267 | int n; |
| 268 | + const char *zIgnoreFlag = find_option("ignore",0,1); | |
| 214 | 269 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 270 | + | |
| 215 | 271 | db_must_be_within_tree(); |
| 216 | 272 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 217 | 273 | n = strlen(g.zLocalRoot); |
| 218 | 274 | blob_init(&path, g.zLocalRoot, n-1); |
| 275 | + if( zIgnoreFlag==0 ){ | |
| 276 | + zIgnoreFlag = db_get("ignore-glob", 0); | |
| 277 | + } | |
| 219 | 278 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 220 | 279 | db_prepare(&q, |
| 221 | 280 | "SELECT x FROM sfile" |
| 222 | 281 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 223 | - " ORDER BY 1"); | |
| 282 | + " AND NOT %s" | |
| 283 | + " ORDER BY 1", | |
| 284 | + glob_expr("x", zIgnoreFlag) | |
| 285 | + ); | |
| 224 | 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 225 | 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 226 | 288 | } |
| 227 | 289 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | 290 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | ||
| 683 | 745 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 684 | 746 | zDate[10] = 'T'; |
| 685 | 747 | blob_appendf(&manifest, "D %s\n", zDate); |
| 686 | 748 | zDate[10] = ' '; |
| 687 | 749 | db_prepare(&q, |
| 688 | - "SELECT pathname, uuid, origname, blob.rid" | |
| 750 | + "SELECT pathname, uuid, origname, blob.rid, isexe" | |
| 689 | 751 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 690 | 752 | " WHERE NOT deleted AND vfile.vid=%d" |
| 691 | 753 | " ORDER BY 1", vid); |
| 692 | 754 | blob_zero(&filename); |
| 693 | 755 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | ||
| 695 | 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 696 | 758 | const char *zName = db_column_text(&q, 0); |
| 697 | 759 | const char *zUuid = db_column_text(&q, 1); |
| 698 | 760 | const char *zOrig = db_column_text(&q, 2); |
| 699 | 761 | int frid = db_column_int(&q, 3); |
| 762 | + int isexe = db_column_int(&q, 4); | |
| 700 | 763 | const char *zPerm; |
| 701 | 764 | blob_append(&filename, zName, -1); |
| 702 | - if( file_isexe(blob_str(&filename)) ){ | |
| 765 | +#ifndef __MINGW32__ | |
| 766 | + /* For unix, extract the "executable" permission bit directly from | |
| 767 | + ** the filesystem. On windows, the "executable" bit is retained | |
| 768 | + ** unchanged from the original. */ | |
| 769 | + isexe = file_isexe(blob_str(&filename)); | |
| 770 | +#endif | |
| 771 | + if( isexe ){ | |
| 703 | 772 | zPerm = " x"; |
| 704 | 773 | }else{ |
| 705 | 774 | zPerm = ""; |
| 706 | 775 | } |
| 707 | 776 | blob_resize(&filename, nBasename); |
| 708 | 777 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -193,14 +193,68 @@ | |
| 193 | } |
| 194 | free(zFullName); |
| 195 | } |
| 196 | db_finalize(&q); |
| 197 | } |
| 198 | |
| 199 | /* |
| 200 | ** COMMAND: extras |
| 201 | ** Usage: %fossil extras ?--dotfiles? |
| 202 | ** |
| 203 | ** Print a list of all files in the source tree that are not part of |
| 204 | ** the current checkout. See also the "clean" command. |
| 205 | ** |
| 206 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | |
| 209 | void extra_cmd(void){ |
| 210 | Blob path; |
| 211 | Blob repo; |
| 212 | Stmt q; |
| 213 | int n; |
| 214 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 215 | db_must_be_within_tree(); |
| 216 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 217 | n = strlen(g.zLocalRoot); |
| 218 | blob_init(&path, g.zLocalRoot, n-1); |
| 219 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 220 | db_prepare(&q, |
| 221 | "SELECT x FROM sfile" |
| 222 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 223 | " ORDER BY 1"); |
| 224 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 225 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 226 | } |
| 227 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | |
| 683 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 684 | zDate[10] = 'T'; |
| 685 | blob_appendf(&manifest, "D %s\n", zDate); |
| 686 | zDate[10] = ' '; |
| 687 | db_prepare(&q, |
| 688 | "SELECT pathname, uuid, origname, blob.rid" |
| 689 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 690 | " WHERE NOT deleted AND vfile.vid=%d" |
| 691 | " ORDER BY 1", vid); |
| 692 | blob_zero(&filename); |
| 693 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | |
| 695 | while( db_step(&q)==SQLITE_ROW ){ |
| 696 | const char *zName = db_column_text(&q, 0); |
| 697 | const char *zUuid = db_column_text(&q, 1); |
| 698 | const char *zOrig = db_column_text(&q, 2); |
| 699 | int frid = db_column_int(&q, 3); |
| 700 | const char *zPerm; |
| 701 | blob_append(&filename, zName, -1); |
| 702 | if( file_isexe(blob_str(&filename)) ){ |
| 703 | zPerm = " x"; |
| 704 | }else{ |
| 705 | zPerm = ""; |
| 706 | } |
| 707 | blob_resize(&filename, nBasename); |
| 708 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -193,14 +193,68 @@ | |
| 193 | } |
| 194 | free(zFullName); |
| 195 | } |
| 196 | db_finalize(&q); |
| 197 | } |
| 198 | |
| 199 | /* |
| 200 | ** Construct and return a string which is an SQL expression that will |
| 201 | ** be TRUE if value zVal matches any of the GLOB expressions in the list |
| 202 | ** zGlobList. For example: |
| 203 | ** |
| 204 | ** zVal: "x" |
| 205 | ** zGlobList: "*.o,*.obj" |
| 206 | ** |
| 207 | ** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" |
| 208 | ** |
| 209 | ** Each element of the GLOB list may optionally be enclosed in either '...' |
| 210 | ** or "...". This allows commas in the expression. Whitespace at the |
| 211 | ** beginning and end of each GLOB pattern is ignored, except when enclosed |
| 212 | ** within '...' or "...". |
| 213 | ** |
| 214 | ** This routine makes no effort to free the memory space it uses. |
| 215 | */ |
| 216 | char *glob_expr(const char *zVal, const char *zGlobList){ |
| 217 | Blob expr; |
| 218 | char *zSep = "("; |
| 219 | int nTerm = 0; |
| 220 | int i; |
| 221 | int cTerm; |
| 222 | |
| 223 | if( zGlobList==0 || zGlobList[0]==0 ) return "0"; |
| 224 | blob_zero(&expr); |
| 225 | while( zGlobList[0] ){ |
| 226 | while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++; |
| 227 | if( zGlobList[0]==0 ) break; |
| 228 | if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ |
| 229 | cTerm = zGlobList[0]; |
| 230 | zGlobList++; |
| 231 | }else{ |
| 232 | cTerm = ','; |
| 233 | } |
| 234 | for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){} |
| 235 | if( cTerm==',' ){ |
| 236 | while( i>0 && isspace(zGlobList[i-1]) ){ i--; } |
| 237 | } |
| 238 | blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList); |
| 239 | zSep = " OR "; |
| 240 | if( cTerm!=',' && zGlobList[i] ) i++; |
| 241 | zGlobList += i; |
| 242 | if( zGlobList[0] ) zGlobList++; |
| 243 | nTerm++; |
| 244 | } |
| 245 | if( nTerm ){ |
| 246 | blob_appendf(&expr, ")"); |
| 247 | return blob_str(&expr); |
| 248 | }else{ |
| 249 | return "0"; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | ** COMMAND: extras |
| 255 | ** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN? |
| 256 | ** |
| 257 | ** Print a list of all files in the source tree that are not part of |
| 258 | ** the current checkout. See also the "clean" command. |
| 259 | ** |
| 260 | ** Files and subdirectories whose names begin with "." are normally |
| @@ -209,20 +263,28 @@ | |
| 263 | void extra_cmd(void){ |
| 264 | Blob path; |
| 265 | Blob repo; |
| 266 | Stmt q; |
| 267 | int n; |
| 268 | const char *zIgnoreFlag = find_option("ignore",0,1); |
| 269 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 270 | |
| 271 | db_must_be_within_tree(); |
| 272 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 273 | n = strlen(g.zLocalRoot); |
| 274 | blob_init(&path, g.zLocalRoot, n-1); |
| 275 | if( zIgnoreFlag==0 ){ |
| 276 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 277 | } |
| 278 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 279 | db_prepare(&q, |
| 280 | "SELECT x FROM sfile" |
| 281 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')" |
| 282 | " AND NOT %s" |
| 283 | " ORDER BY 1", |
| 284 | glob_expr("x", zIgnoreFlag) |
| 285 | ); |
| 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 288 | } |
| 289 | while( db_step(&q)==SQLITE_ROW ){ |
| 290 | printf("%s\n", db_column_text(&q, 0)); |
| @@ -683,11 +745,11 @@ | |
| 745 | zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); |
| 746 | zDate[10] = 'T'; |
| 747 | blob_appendf(&manifest, "D %s\n", zDate); |
| 748 | zDate[10] = ' '; |
| 749 | db_prepare(&q, |
| 750 | "SELECT pathname, uuid, origname, blob.rid, isexe" |
| 751 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 752 | " WHERE NOT deleted AND vfile.vid=%d" |
| 753 | " ORDER BY 1", vid); |
| 754 | blob_zero(&filename); |
| 755 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| @@ -695,13 +757,20 @@ | |
| 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 758 | const char *zName = db_column_text(&q, 0); |
| 759 | const char *zUuid = db_column_text(&q, 1); |
| 760 | const char *zOrig = db_column_text(&q, 2); |
| 761 | int frid = db_column_int(&q, 3); |
| 762 | int isexe = db_column_int(&q, 4); |
| 763 | const char *zPerm; |
| 764 | blob_append(&filename, zName, -1); |
| 765 | #ifndef __MINGW32__ |
| 766 | /* For unix, extract the "executable" permission bit directly from |
| 767 | ** the filesystem. On windows, the "executable" bit is retained |
| 768 | ** unchanged from the original. */ |
| 769 | isexe = file_isexe(blob_str(&filename)); |
| 770 | #endif |
| 771 | if( isexe ){ |
| 772 | zPerm = " x"; |
| 773 | }else{ |
| 774 | zPerm = ""; |
| 775 | } |
| 776 | blob_resize(&filename, nBasename); |
| 777 |
+9
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -93,10 +93,18 @@ | ||
| 93 | 93 | } |
| 94 | 94 | content_get(vid, &manifest); |
| 95 | 95 | vfile_build(vid, &manifest); |
| 96 | 96 | blob_reset(&manifest); |
| 97 | 97 | } |
| 98 | + | |
| 99 | +/* | |
| 100 | +** Set or clear the vfile.isexe flag for a file. | |
| 101 | +*/ | |
| 102 | +static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ | |
| 103 | + db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", | |
| 104 | + onoff, vid, zFilename); | |
| 105 | +} | |
| 98 | 106 | |
| 99 | 107 | /* |
| 100 | 108 | ** Read the manifest file given by vid out of the repository |
| 101 | 109 | ** and store it in the root of the local check-out. |
| 102 | 110 | */ |
| @@ -128,10 +136,11 @@ | ||
| 128 | 136 | for(i=0; i<m.nFile; i++){ |
| 129 | 137 | int isExe; |
| 130 | 138 | blob_append(&filename, m.aFile[i].zName, -1); |
| 131 | 139 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 132 | 140 | file_setexe(blob_str(&filename), isExe); |
| 141 | + set_or_clear_isexe(m.aFile[i].zName, vid, isExe); | |
| 133 | 142 | blob_resize(&filename, baseLen); |
| 134 | 143 | } |
| 135 | 144 | blob_reset(&filename); |
| 136 | 145 | manifest_clear(&m); |
| 137 | 146 | } |
| 138 | 147 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -93,10 +93,18 @@ | |
| 93 | } |
| 94 | content_get(vid, &manifest); |
| 95 | vfile_build(vid, &manifest); |
| 96 | blob_reset(&manifest); |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | ** Read the manifest file given by vid out of the repository |
| 101 | ** and store it in the root of the local check-out. |
| 102 | */ |
| @@ -128,10 +136,11 @@ | |
| 128 | for(i=0; i<m.nFile; i++){ |
| 129 | int isExe; |
| 130 | blob_append(&filename, m.aFile[i].zName, -1); |
| 131 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 132 | file_setexe(blob_str(&filename), isExe); |
| 133 | blob_resize(&filename, baseLen); |
| 134 | } |
| 135 | blob_reset(&filename); |
| 136 | manifest_clear(&m); |
| 137 | } |
| 138 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -93,10 +93,18 @@ | |
| 93 | } |
| 94 | content_get(vid, &manifest); |
| 95 | vfile_build(vid, &manifest); |
| 96 | blob_reset(&manifest); |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | ** Set or clear the vfile.isexe flag for a file. |
| 101 | */ |
| 102 | static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
| 103 | db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", |
| 104 | onoff, vid, zFilename); |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | ** Read the manifest file given by vid out of the repository |
| 109 | ** and store it in the root of the local check-out. |
| 110 | */ |
| @@ -128,10 +136,11 @@ | |
| 136 | for(i=0; i<m.nFile; i++){ |
| 137 | int isExe; |
| 138 | blob_append(&filename, m.aFile[i].zName, -1); |
| 139 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 140 | file_setexe(blob_str(&filename), isExe); |
| 141 | set_or_clear_isexe(m.aFile[i].zName, vid, isExe); |
| 142 | blob_resize(&filename, baseLen); |
| 143 | } |
| 144 | blob_reset(&filename); |
| 145 | manifest_clear(&m); |
| 146 | } |
| 147 |
+9
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -93,10 +93,18 @@ | ||
| 93 | 93 | } |
| 94 | 94 | content_get(vid, &manifest); |
| 95 | 95 | vfile_build(vid, &manifest); |
| 96 | 96 | blob_reset(&manifest); |
| 97 | 97 | } |
| 98 | + | |
| 99 | +/* | |
| 100 | +** Set or clear the vfile.isexe flag for a file. | |
| 101 | +*/ | |
| 102 | +static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ | |
| 103 | + db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", | |
| 104 | + onoff, vid, zFilename); | |
| 105 | +} | |
| 98 | 106 | |
| 99 | 107 | /* |
| 100 | 108 | ** Read the manifest file given by vid out of the repository |
| 101 | 109 | ** and store it in the root of the local check-out. |
| 102 | 110 | */ |
| @@ -128,10 +136,11 @@ | ||
| 128 | 136 | for(i=0; i<m.nFile; i++){ |
| 129 | 137 | int isExe; |
| 130 | 138 | blob_append(&filename, m.aFile[i].zName, -1); |
| 131 | 139 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 132 | 140 | file_setexe(blob_str(&filename), isExe); |
| 141 | + set_or_clear_isexe(m.aFile[i].zName, vid, isExe); | |
| 133 | 142 | blob_resize(&filename, baseLen); |
| 134 | 143 | } |
| 135 | 144 | blob_reset(&filename); |
| 136 | 145 | manifest_clear(&m); |
| 137 | 146 | } |
| 138 | 147 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -93,10 +93,18 @@ | |
| 93 | } |
| 94 | content_get(vid, &manifest); |
| 95 | vfile_build(vid, &manifest); |
| 96 | blob_reset(&manifest); |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | ** Read the manifest file given by vid out of the repository |
| 101 | ** and store it in the root of the local check-out. |
| 102 | */ |
| @@ -128,10 +136,11 @@ | |
| 128 | for(i=0; i<m.nFile; i++){ |
| 129 | int isExe; |
| 130 | blob_append(&filename, m.aFile[i].zName, -1); |
| 131 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 132 | file_setexe(blob_str(&filename), isExe); |
| 133 | blob_resize(&filename, baseLen); |
| 134 | } |
| 135 | blob_reset(&filename); |
| 136 | manifest_clear(&m); |
| 137 | } |
| 138 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -93,10 +93,18 @@ | |
| 93 | } |
| 94 | content_get(vid, &manifest); |
| 95 | vfile_build(vid, &manifest); |
| 96 | blob_reset(&manifest); |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | ** Set or clear the vfile.isexe flag for a file. |
| 101 | */ |
| 102 | static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
| 103 | db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", |
| 104 | onoff, vid, zFilename); |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | ** Read the manifest file given by vid out of the repository |
| 109 | ** and store it in the root of the local check-out. |
| 110 | */ |
| @@ -128,10 +136,11 @@ | |
| 136 | for(i=0; i<m.nFile; i++){ |
| 137 | int isExe; |
| 138 | blob_append(&filename, m.aFile[i].zName, -1); |
| 139 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 140 | file_setexe(blob_str(&filename), isExe); |
| 141 | set_or_clear_isexe(m.aFile[i].zName, vid, isExe); |
| 142 | blob_resize(&filename, baseLen); |
| 143 | } |
| 144 | blob_reset(&filename); |
| 145 | manifest_clear(&m); |
| 146 | } |
| 147 |
M
src/db.c
+39
-2
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -496,20 +496,28 @@ | ||
| 496 | 496 | ** zDefault instead. |
| 497 | 497 | */ |
| 498 | 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | 499 | va_list ap; |
| 500 | 500 | Stmt s; |
| 501 | - char *z = zDefault; | |
| 501 | + char *z; | |
| 502 | 502 | va_start(ap, zSql); |
| 503 | 503 | db_vprepare(&s, zSql, ap); |
| 504 | 504 | va_end(ap); |
| 505 | 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | + }else if( zDefault ){ | |
| 508 | + z = mprintf("%s", zDefault); | |
| 509 | + }else{ | |
| 510 | + z = 0; | |
| 507 | 511 | } |
| 508 | 512 | db_finalize(&s); |
| 509 | 513 | return z; |
| 510 | 514 | } |
| 515 | + | |
| 516 | +#ifdef __MINGW32__ | |
| 517 | +extern char *sqlite3_win32_mbcs_to_utf8(const char*); | |
| 518 | +#endif | |
| 511 | 519 | |
| 512 | 520 | /* |
| 513 | 521 | ** Initialize a new database file with the given schema. If anything |
| 514 | 522 | ** goes wrong, call db_err() to exit. |
| 515 | 523 | */ |
| @@ -661,29 +669,43 @@ | ||
| 661 | 669 | db_open_or_attach(zDbName, "localdb"); |
| 662 | 670 | g.localOpen = 1; |
| 663 | 671 | db_open_config(0); |
| 664 | 672 | db_open_repository(0); |
| 665 | 673 | |
| 674 | + /* If the "isexe" column is missing from the vfile table, then | |
| 675 | + ** add it now. This code added on 2010-03-06. After all users have | |
| 676 | + ** upgraded, this code can be safely deleted. | |
| 677 | + */ | |
| 678 | + rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0); | |
| 679 | + sqlite3_finalize(pStmt); | |
| 680 | + if( rc==SQLITE_ERROR ){ | |
| 681 | + sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0); | |
| 682 | + } | |
| 683 | + | |
| 684 | +#if 0 | |
| 666 | 685 | /* If the "mtime" column is missing from the vfile table, then |
| 667 | 686 | ** add it now. This code added on 2008-12-06. After all users have |
| 668 | 687 | ** upgraded, this code can be safely deleted. |
| 669 | 688 | */ |
| 670 | 689 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 671 | 690 | sqlite3_finalize(pStmt); |
| 672 | 691 | if( rc==SQLITE_ERROR ){ |
| 673 | 692 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 674 | 693 | } |
| 694 | +#endif | |
| 675 | 695 | |
| 696 | +#if 0 | |
| 676 | 697 | /* If the "origname" column is missing from the vfile table, then |
| 677 | 698 | ** add it now. This code added on 2008-11-09. After all users have |
| 678 | 699 | ** upgraded, this code can be safely deleted. |
| 679 | 700 | */ |
| 680 | 701 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 681 | 702 | sqlite3_finalize(pStmt); |
| 682 | 703 | if( rc==SQLITE_ERROR ){ |
| 683 | 704 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 684 | 705 | } |
| 706 | +#endif | |
| 685 | 707 | |
| 686 | 708 | return 1; |
| 687 | 709 | } |
| 688 | 710 | |
| 689 | 711 | /* |
| @@ -885,11 +907,15 @@ | ||
| 885 | 907 | ** The zInitialDate parameter determines the date of the initial check-in |
| 886 | 908 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 887 | 909 | ** check-in is created. The makeServerCodes flag determines whether or |
| 888 | 910 | ** not server and project codes are invented for this repository. |
| 889 | 911 | */ |
| 890 | -void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){ | |
| 912 | +void db_initial_setup( | |
| 913 | + const char *zInitialDate, /* Initial date of repository. (ex: "now") */ | |
| 914 | + const char *zDefaultUser, /* Default user for the repository */ | |
| 915 | + int makeServerCodes /* True to make new server & project codes */ | |
| 916 | +){ | |
| 891 | 917 | char *zDate; |
| 892 | 918 | Blob hash; |
| 893 | 919 | Blob manifest; |
| 894 | 920 | |
| 895 | 921 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | ||
| 942 | 968 | ** parameter. |
| 943 | 969 | ** |
| 944 | 970 | ** Options: |
| 945 | 971 | ** |
| 946 | 972 | ** --admin-user|-A USERNAME |
| 973 | +** --date-override DATETIME | |
| 947 | 974 | ** |
| 948 | 975 | */ |
| 949 | 976 | void create_repository_cmd(void){ |
| 950 | 977 | char *zPassword; |
| 951 | 978 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | ||
| 1405 | 1432 | ** |
| 1406 | 1433 | ** autosync If enabled, automatically pull prior to commit |
| 1407 | 1434 | ** or update and automatically push after commit or |
| 1408 | 1435 | ** tag or branch creation. If the the value is "pullonly" |
| 1409 | 1436 | ** then only pull operations occur automatically. |
| 1437 | +** | |
| 1438 | +** binary-glob The VALUE is a comma-separated list of GLOB patterns | |
| 1439 | +** that should be treated as binary files for merging | |
| 1440 | +** purposes. Example: *.xml | |
| 1410 | 1441 | ** |
| 1411 | 1442 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1412 | 1443 | ** with gpg. When disabled (the default), commits will |
| 1413 | 1444 | ** be unsigned. |
| 1414 | 1445 | ** |
| @@ -1423,10 +1454,14 @@ | ||
| 1423 | 1454 | ** gdiff-command External command to run when performing a graphical |
| 1424 | 1455 | ** diff. If undefined, text diff will be used. |
| 1425 | 1456 | ** |
| 1426 | 1457 | ** http-port The TCP/IP port number to use by the "server" |
| 1427 | 1458 | ** and "ui" commands. Default: 8080 |
| 1459 | +** | |
| 1460 | +** ignore-glob The VALUE is a comma-separated list of GLOB patterns | |
| 1461 | +** specifying files that the "extra" command will ignore. | |
| 1462 | +** Example: *.o,*.obj,*.exe | |
| 1428 | 1463 | ** |
| 1429 | 1464 | ** localauth If enabled, require that HTTP connections from |
| 1430 | 1465 | ** 127.0.0.1 be authenticated by password. If |
| 1431 | 1466 | ** false, all HTTP requests from localhost have |
| 1432 | 1467 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | ||
| 1448 | 1483 | ** and "firefox" on Unix. |
| 1449 | 1484 | */ |
| 1450 | 1485 | void setting_cmd(void){ |
| 1451 | 1486 | static const char *azName[] = { |
| 1452 | 1487 | "autosync", |
| 1488 | + "binary-glob", | |
| 1453 | 1489 | "clearsign", |
| 1454 | 1490 | "diff-command", |
| 1455 | 1491 | "dont-push", |
| 1456 | 1492 | "editor", |
| 1457 | 1493 | "gdiff-command", |
| 1494 | + "ignore-glob", | |
| 1458 | 1495 | "http-port", |
| 1459 | 1496 | "localauth", |
| 1460 | 1497 | "mtime-changes", |
| 1461 | 1498 | "pgp-command", |
| 1462 | 1499 | "proxy", |
| 1463 | 1500 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -496,20 +496,28 @@ | |
| 496 | ** zDefault instead. |
| 497 | */ |
| 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | va_list ap; |
| 500 | Stmt s; |
| 501 | char *z = zDefault; |
| 502 | va_start(ap, zSql); |
| 503 | db_vprepare(&s, zSql, ap); |
| 504 | va_end(ap); |
| 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | } |
| 508 | db_finalize(&s); |
| 509 | return z; |
| 510 | } |
| 511 | |
| 512 | /* |
| 513 | ** Initialize a new database file with the given schema. If anything |
| 514 | ** goes wrong, call db_err() to exit. |
| 515 | */ |
| @@ -661,29 +669,43 @@ | |
| 661 | db_open_or_attach(zDbName, "localdb"); |
| 662 | g.localOpen = 1; |
| 663 | db_open_config(0); |
| 664 | db_open_repository(0); |
| 665 | |
| 666 | /* If the "mtime" column is missing from the vfile table, then |
| 667 | ** add it now. This code added on 2008-12-06. After all users have |
| 668 | ** upgraded, this code can be safely deleted. |
| 669 | */ |
| 670 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 671 | sqlite3_finalize(pStmt); |
| 672 | if( rc==SQLITE_ERROR ){ |
| 673 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 674 | } |
| 675 | |
| 676 | /* If the "origname" column is missing from the vfile table, then |
| 677 | ** add it now. This code added on 2008-11-09. After all users have |
| 678 | ** upgraded, this code can be safely deleted. |
| 679 | */ |
| 680 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 681 | sqlite3_finalize(pStmt); |
| 682 | if( rc==SQLITE_ERROR ){ |
| 683 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 684 | } |
| 685 | |
| 686 | return 1; |
| 687 | } |
| 688 | |
| 689 | /* |
| @@ -885,11 +907,15 @@ | |
| 885 | ** The zInitialDate parameter determines the date of the initial check-in |
| 886 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 887 | ** check-in is created. The makeServerCodes flag determines whether or |
| 888 | ** not server and project codes are invented for this repository. |
| 889 | */ |
| 890 | void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){ |
| 891 | char *zDate; |
| 892 | Blob hash; |
| 893 | Blob manifest; |
| 894 | |
| 895 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | |
| 942 | ** parameter. |
| 943 | ** |
| 944 | ** Options: |
| 945 | ** |
| 946 | ** --admin-user|-A USERNAME |
| 947 | ** |
| 948 | */ |
| 949 | void create_repository_cmd(void){ |
| 950 | char *zPassword; |
| 951 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | |
| 1405 | ** |
| 1406 | ** autosync If enabled, automatically pull prior to commit |
| 1407 | ** or update and automatically push after commit or |
| 1408 | ** tag or branch creation. If the the value is "pullonly" |
| 1409 | ** then only pull operations occur automatically. |
| 1410 | ** |
| 1411 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1412 | ** with gpg. When disabled (the default), commits will |
| 1413 | ** be unsigned. |
| 1414 | ** |
| @@ -1423,10 +1454,14 @@ | |
| 1423 | ** gdiff-command External command to run when performing a graphical |
| 1424 | ** diff. If undefined, text diff will be used. |
| 1425 | ** |
| 1426 | ** http-port The TCP/IP port number to use by the "server" |
| 1427 | ** and "ui" commands. Default: 8080 |
| 1428 | ** |
| 1429 | ** localauth If enabled, require that HTTP connections from |
| 1430 | ** 127.0.0.1 be authenticated by password. If |
| 1431 | ** false, all HTTP requests from localhost have |
| 1432 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | |
| 1448 | ** and "firefox" on Unix. |
| 1449 | */ |
| 1450 | void setting_cmd(void){ |
| 1451 | static const char *azName[] = { |
| 1452 | "autosync", |
| 1453 | "clearsign", |
| 1454 | "diff-command", |
| 1455 | "dont-push", |
| 1456 | "editor", |
| 1457 | "gdiff-command", |
| 1458 | "http-port", |
| 1459 | "localauth", |
| 1460 | "mtime-changes", |
| 1461 | "pgp-command", |
| 1462 | "proxy", |
| 1463 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -496,20 +496,28 @@ | |
| 496 | ** zDefault instead. |
| 497 | */ |
| 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | va_list ap; |
| 500 | Stmt s; |
| 501 | char *z; |
| 502 | va_start(ap, zSql); |
| 503 | db_vprepare(&s, zSql, ap); |
| 504 | va_end(ap); |
| 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | }else if( zDefault ){ |
| 508 | z = mprintf("%s", zDefault); |
| 509 | }else{ |
| 510 | z = 0; |
| 511 | } |
| 512 | db_finalize(&s); |
| 513 | return z; |
| 514 | } |
| 515 | |
| 516 | #ifdef __MINGW32__ |
| 517 | extern char *sqlite3_win32_mbcs_to_utf8(const char*); |
| 518 | #endif |
| 519 | |
| 520 | /* |
| 521 | ** Initialize a new database file with the given schema. If anything |
| 522 | ** goes wrong, call db_err() to exit. |
| 523 | */ |
| @@ -661,29 +669,43 @@ | |
| 669 | db_open_or_attach(zDbName, "localdb"); |
| 670 | g.localOpen = 1; |
| 671 | db_open_config(0); |
| 672 | db_open_repository(0); |
| 673 | |
| 674 | /* If the "isexe" column is missing from the vfile table, then |
| 675 | ** add it now. This code added on 2010-03-06. After all users have |
| 676 | ** upgraded, this code can be safely deleted. |
| 677 | */ |
| 678 | rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0); |
| 679 | sqlite3_finalize(pStmt); |
| 680 | if( rc==SQLITE_ERROR ){ |
| 681 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0); |
| 682 | } |
| 683 | |
| 684 | #if 0 |
| 685 | /* If the "mtime" column is missing from the vfile table, then |
| 686 | ** add it now. This code added on 2008-12-06. After all users have |
| 687 | ** upgraded, this code can be safely deleted. |
| 688 | */ |
| 689 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 690 | sqlite3_finalize(pStmt); |
| 691 | if( rc==SQLITE_ERROR ){ |
| 692 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 693 | } |
| 694 | #endif |
| 695 | |
| 696 | #if 0 |
| 697 | /* If the "origname" column is missing from the vfile table, then |
| 698 | ** add it now. This code added on 2008-11-09. After all users have |
| 699 | ** upgraded, this code can be safely deleted. |
| 700 | */ |
| 701 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 702 | sqlite3_finalize(pStmt); |
| 703 | if( rc==SQLITE_ERROR ){ |
| 704 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 705 | } |
| 706 | #endif |
| 707 | |
| 708 | return 1; |
| 709 | } |
| 710 | |
| 711 | /* |
| @@ -885,11 +907,15 @@ | |
| 907 | ** The zInitialDate parameter determines the date of the initial check-in |
| 908 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 909 | ** check-in is created. The makeServerCodes flag determines whether or |
| 910 | ** not server and project codes are invented for this repository. |
| 911 | */ |
| 912 | void db_initial_setup( |
| 913 | const char *zInitialDate, /* Initial date of repository. (ex: "now") */ |
| 914 | const char *zDefaultUser, /* Default user for the repository */ |
| 915 | int makeServerCodes /* True to make new server & project codes */ |
| 916 | ){ |
| 917 | char *zDate; |
| 918 | Blob hash; |
| 919 | Blob manifest; |
| 920 | |
| 921 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | |
| 968 | ** parameter. |
| 969 | ** |
| 970 | ** Options: |
| 971 | ** |
| 972 | ** --admin-user|-A USERNAME |
| 973 | ** --date-override DATETIME |
| 974 | ** |
| 975 | */ |
| 976 | void create_repository_cmd(void){ |
| 977 | char *zPassword; |
| 978 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | |
| 1432 | ** |
| 1433 | ** autosync If enabled, automatically pull prior to commit |
| 1434 | ** or update and automatically push after commit or |
| 1435 | ** tag or branch creation. If the the value is "pullonly" |
| 1436 | ** then only pull operations occur automatically. |
| 1437 | ** |
| 1438 | ** binary-glob The VALUE is a comma-separated list of GLOB patterns |
| 1439 | ** that should be treated as binary files for merging |
| 1440 | ** purposes. Example: *.xml |
| 1441 | ** |
| 1442 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1443 | ** with gpg. When disabled (the default), commits will |
| 1444 | ** be unsigned. |
| 1445 | ** |
| @@ -1423,10 +1454,14 @@ | |
| 1454 | ** gdiff-command External command to run when performing a graphical |
| 1455 | ** diff. If undefined, text diff will be used. |
| 1456 | ** |
| 1457 | ** http-port The TCP/IP port number to use by the "server" |
| 1458 | ** and "ui" commands. Default: 8080 |
| 1459 | ** |
| 1460 | ** ignore-glob The VALUE is a comma-separated list of GLOB patterns |
| 1461 | ** specifying files that the "extra" command will ignore. |
| 1462 | ** Example: *.o,*.obj,*.exe |
| 1463 | ** |
| 1464 | ** localauth If enabled, require that HTTP connections from |
| 1465 | ** 127.0.0.1 be authenticated by password. If |
| 1466 | ** false, all HTTP requests from localhost have |
| 1467 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | |
| 1483 | ** and "firefox" on Unix. |
| 1484 | */ |
| 1485 | void setting_cmd(void){ |
| 1486 | static const char *azName[] = { |
| 1487 | "autosync", |
| 1488 | "binary-glob", |
| 1489 | "clearsign", |
| 1490 | "diff-command", |
| 1491 | "dont-push", |
| 1492 | "editor", |
| 1493 | "gdiff-command", |
| 1494 | "ignore-glob", |
| 1495 | "http-port", |
| 1496 | "localauth", |
| 1497 | "mtime-changes", |
| 1498 | "pgp-command", |
| 1499 | "proxy", |
| 1500 |
M
src/db.c
+39
-2
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -496,20 +496,28 @@ | ||
| 496 | 496 | ** zDefault instead. |
| 497 | 497 | */ |
| 498 | 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | 499 | va_list ap; |
| 500 | 500 | Stmt s; |
| 501 | - char *z = zDefault; | |
| 501 | + char *z; | |
| 502 | 502 | va_start(ap, zSql); |
| 503 | 503 | db_vprepare(&s, zSql, ap); |
| 504 | 504 | va_end(ap); |
| 505 | 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | + }else if( zDefault ){ | |
| 508 | + z = mprintf("%s", zDefault); | |
| 509 | + }else{ | |
| 510 | + z = 0; | |
| 507 | 511 | } |
| 508 | 512 | db_finalize(&s); |
| 509 | 513 | return z; |
| 510 | 514 | } |
| 515 | + | |
| 516 | +#ifdef __MINGW32__ | |
| 517 | +extern char *sqlite3_win32_mbcs_to_utf8(const char*); | |
| 518 | +#endif | |
| 511 | 519 | |
| 512 | 520 | /* |
| 513 | 521 | ** Initialize a new database file with the given schema. If anything |
| 514 | 522 | ** goes wrong, call db_err() to exit. |
| 515 | 523 | */ |
| @@ -661,29 +669,43 @@ | ||
| 661 | 669 | db_open_or_attach(zDbName, "localdb"); |
| 662 | 670 | g.localOpen = 1; |
| 663 | 671 | db_open_config(0); |
| 664 | 672 | db_open_repository(0); |
| 665 | 673 | |
| 674 | + /* If the "isexe" column is missing from the vfile table, then | |
| 675 | + ** add it now. This code added on 2010-03-06. After all users have | |
| 676 | + ** upgraded, this code can be safely deleted. | |
| 677 | + */ | |
| 678 | + rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0); | |
| 679 | + sqlite3_finalize(pStmt); | |
| 680 | + if( rc==SQLITE_ERROR ){ | |
| 681 | + sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0); | |
| 682 | + } | |
| 683 | + | |
| 684 | +#if 0 | |
| 666 | 685 | /* If the "mtime" column is missing from the vfile table, then |
| 667 | 686 | ** add it now. This code added on 2008-12-06. After all users have |
| 668 | 687 | ** upgraded, this code can be safely deleted. |
| 669 | 688 | */ |
| 670 | 689 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 671 | 690 | sqlite3_finalize(pStmt); |
| 672 | 691 | if( rc==SQLITE_ERROR ){ |
| 673 | 692 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 674 | 693 | } |
| 694 | +#endif | |
| 675 | 695 | |
| 696 | +#if 0 | |
| 676 | 697 | /* If the "origname" column is missing from the vfile table, then |
| 677 | 698 | ** add it now. This code added on 2008-11-09. After all users have |
| 678 | 699 | ** upgraded, this code can be safely deleted. |
| 679 | 700 | */ |
| 680 | 701 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 681 | 702 | sqlite3_finalize(pStmt); |
| 682 | 703 | if( rc==SQLITE_ERROR ){ |
| 683 | 704 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 684 | 705 | } |
| 706 | +#endif | |
| 685 | 707 | |
| 686 | 708 | return 1; |
| 687 | 709 | } |
| 688 | 710 | |
| 689 | 711 | /* |
| @@ -885,11 +907,15 @@ | ||
| 885 | 907 | ** The zInitialDate parameter determines the date of the initial check-in |
| 886 | 908 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 887 | 909 | ** check-in is created. The makeServerCodes flag determines whether or |
| 888 | 910 | ** not server and project codes are invented for this repository. |
| 889 | 911 | */ |
| 890 | -void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){ | |
| 912 | +void db_initial_setup( | |
| 913 | + const char *zInitialDate, /* Initial date of repository. (ex: "now") */ | |
| 914 | + const char *zDefaultUser, /* Default user for the repository */ | |
| 915 | + int makeServerCodes /* True to make new server & project codes */ | |
| 916 | +){ | |
| 891 | 917 | char *zDate; |
| 892 | 918 | Blob hash; |
| 893 | 919 | Blob manifest; |
| 894 | 920 | |
| 895 | 921 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | ||
| 942 | 968 | ** parameter. |
| 943 | 969 | ** |
| 944 | 970 | ** Options: |
| 945 | 971 | ** |
| 946 | 972 | ** --admin-user|-A USERNAME |
| 973 | +** --date-override DATETIME | |
| 947 | 974 | ** |
| 948 | 975 | */ |
| 949 | 976 | void create_repository_cmd(void){ |
| 950 | 977 | char *zPassword; |
| 951 | 978 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | ||
| 1405 | 1432 | ** |
| 1406 | 1433 | ** autosync If enabled, automatically pull prior to commit |
| 1407 | 1434 | ** or update and automatically push after commit or |
| 1408 | 1435 | ** tag or branch creation. If the the value is "pullonly" |
| 1409 | 1436 | ** then only pull operations occur automatically. |
| 1437 | +** | |
| 1438 | +** binary-glob The VALUE is a comma-separated list of GLOB patterns | |
| 1439 | +** that should be treated as binary files for merging | |
| 1440 | +** purposes. Example: *.xml | |
| 1410 | 1441 | ** |
| 1411 | 1442 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1412 | 1443 | ** with gpg. When disabled (the default), commits will |
| 1413 | 1444 | ** be unsigned. |
| 1414 | 1445 | ** |
| @@ -1423,10 +1454,14 @@ | ||
| 1423 | 1454 | ** gdiff-command External command to run when performing a graphical |
| 1424 | 1455 | ** diff. If undefined, text diff will be used. |
| 1425 | 1456 | ** |
| 1426 | 1457 | ** http-port The TCP/IP port number to use by the "server" |
| 1427 | 1458 | ** and "ui" commands. Default: 8080 |
| 1459 | +** | |
| 1460 | +** ignore-glob The VALUE is a comma-separated list of GLOB patterns | |
| 1461 | +** specifying files that the "extra" command will ignore. | |
| 1462 | +** Example: *.o,*.obj,*.exe | |
| 1428 | 1463 | ** |
| 1429 | 1464 | ** localauth If enabled, require that HTTP connections from |
| 1430 | 1465 | ** 127.0.0.1 be authenticated by password. If |
| 1431 | 1466 | ** false, all HTTP requests from localhost have |
| 1432 | 1467 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | ||
| 1448 | 1483 | ** and "firefox" on Unix. |
| 1449 | 1484 | */ |
| 1450 | 1485 | void setting_cmd(void){ |
| 1451 | 1486 | static const char *azName[] = { |
| 1452 | 1487 | "autosync", |
| 1488 | + "binary-glob", | |
| 1453 | 1489 | "clearsign", |
| 1454 | 1490 | "diff-command", |
| 1455 | 1491 | "dont-push", |
| 1456 | 1492 | "editor", |
| 1457 | 1493 | "gdiff-command", |
| 1494 | + "ignore-glob", | |
| 1458 | 1495 | "http-port", |
| 1459 | 1496 | "localauth", |
| 1460 | 1497 | "mtime-changes", |
| 1461 | 1498 | "pgp-command", |
| 1462 | 1499 | "proxy", |
| 1463 | 1500 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -496,20 +496,28 @@ | |
| 496 | ** zDefault instead. |
| 497 | */ |
| 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | va_list ap; |
| 500 | Stmt s; |
| 501 | char *z = zDefault; |
| 502 | va_start(ap, zSql); |
| 503 | db_vprepare(&s, zSql, ap); |
| 504 | va_end(ap); |
| 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | } |
| 508 | db_finalize(&s); |
| 509 | return z; |
| 510 | } |
| 511 | |
| 512 | /* |
| 513 | ** Initialize a new database file with the given schema. If anything |
| 514 | ** goes wrong, call db_err() to exit. |
| 515 | */ |
| @@ -661,29 +669,43 @@ | |
| 661 | db_open_or_attach(zDbName, "localdb"); |
| 662 | g.localOpen = 1; |
| 663 | db_open_config(0); |
| 664 | db_open_repository(0); |
| 665 | |
| 666 | /* If the "mtime" column is missing from the vfile table, then |
| 667 | ** add it now. This code added on 2008-12-06. After all users have |
| 668 | ** upgraded, this code can be safely deleted. |
| 669 | */ |
| 670 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 671 | sqlite3_finalize(pStmt); |
| 672 | if( rc==SQLITE_ERROR ){ |
| 673 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 674 | } |
| 675 | |
| 676 | /* If the "origname" column is missing from the vfile table, then |
| 677 | ** add it now. This code added on 2008-11-09. After all users have |
| 678 | ** upgraded, this code can be safely deleted. |
| 679 | */ |
| 680 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 681 | sqlite3_finalize(pStmt); |
| 682 | if( rc==SQLITE_ERROR ){ |
| 683 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 684 | } |
| 685 | |
| 686 | return 1; |
| 687 | } |
| 688 | |
| 689 | /* |
| @@ -885,11 +907,15 @@ | |
| 885 | ** The zInitialDate parameter determines the date of the initial check-in |
| 886 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 887 | ** check-in is created. The makeServerCodes flag determines whether or |
| 888 | ** not server and project codes are invented for this repository. |
| 889 | */ |
| 890 | void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){ |
| 891 | char *zDate; |
| 892 | Blob hash; |
| 893 | Blob manifest; |
| 894 | |
| 895 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | |
| 942 | ** parameter. |
| 943 | ** |
| 944 | ** Options: |
| 945 | ** |
| 946 | ** --admin-user|-A USERNAME |
| 947 | ** |
| 948 | */ |
| 949 | void create_repository_cmd(void){ |
| 950 | char *zPassword; |
| 951 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | |
| 1405 | ** |
| 1406 | ** autosync If enabled, automatically pull prior to commit |
| 1407 | ** or update and automatically push after commit or |
| 1408 | ** tag or branch creation. If the the value is "pullonly" |
| 1409 | ** then only pull operations occur automatically. |
| 1410 | ** |
| 1411 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1412 | ** with gpg. When disabled (the default), commits will |
| 1413 | ** be unsigned. |
| 1414 | ** |
| @@ -1423,10 +1454,14 @@ | |
| 1423 | ** gdiff-command External command to run when performing a graphical |
| 1424 | ** diff. If undefined, text diff will be used. |
| 1425 | ** |
| 1426 | ** http-port The TCP/IP port number to use by the "server" |
| 1427 | ** and "ui" commands. Default: 8080 |
| 1428 | ** |
| 1429 | ** localauth If enabled, require that HTTP connections from |
| 1430 | ** 127.0.0.1 be authenticated by password. If |
| 1431 | ** false, all HTTP requests from localhost have |
| 1432 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | |
| 1448 | ** and "firefox" on Unix. |
| 1449 | */ |
| 1450 | void setting_cmd(void){ |
| 1451 | static const char *azName[] = { |
| 1452 | "autosync", |
| 1453 | "clearsign", |
| 1454 | "diff-command", |
| 1455 | "dont-push", |
| 1456 | "editor", |
| 1457 | "gdiff-command", |
| 1458 | "http-port", |
| 1459 | "localauth", |
| 1460 | "mtime-changes", |
| 1461 | "pgp-command", |
| 1462 | "proxy", |
| 1463 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -496,20 +496,28 @@ | |
| 496 | ** zDefault instead. |
| 497 | */ |
| 498 | char *db_text(char *zDefault, const char *zSql, ...){ |
| 499 | va_list ap; |
| 500 | Stmt s; |
| 501 | char *z; |
| 502 | va_start(ap, zSql); |
| 503 | db_vprepare(&s, zSql, ap); |
| 504 | va_end(ap); |
| 505 | if( db_step(&s)==SQLITE_ROW ){ |
| 506 | z = mprintf("%s", sqlite3_column_text(s.pStmt, 0)); |
| 507 | }else if( zDefault ){ |
| 508 | z = mprintf("%s", zDefault); |
| 509 | }else{ |
| 510 | z = 0; |
| 511 | } |
| 512 | db_finalize(&s); |
| 513 | return z; |
| 514 | } |
| 515 | |
| 516 | #ifdef __MINGW32__ |
| 517 | extern char *sqlite3_win32_mbcs_to_utf8(const char*); |
| 518 | #endif |
| 519 | |
| 520 | /* |
| 521 | ** Initialize a new database file with the given schema. If anything |
| 522 | ** goes wrong, call db_err() to exit. |
| 523 | */ |
| @@ -661,29 +669,43 @@ | |
| 669 | db_open_or_attach(zDbName, "localdb"); |
| 670 | g.localOpen = 1; |
| 671 | db_open_config(0); |
| 672 | db_open_repository(0); |
| 673 | |
| 674 | /* If the "isexe" column is missing from the vfile table, then |
| 675 | ** add it now. This code added on 2010-03-06. After all users have |
| 676 | ** upgraded, this code can be safely deleted. |
| 677 | */ |
| 678 | rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0); |
| 679 | sqlite3_finalize(pStmt); |
| 680 | if( rc==SQLITE_ERROR ){ |
| 681 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0); |
| 682 | } |
| 683 | |
| 684 | #if 0 |
| 685 | /* If the "mtime" column is missing from the vfile table, then |
| 686 | ** add it now. This code added on 2008-12-06. After all users have |
| 687 | ** upgraded, this code can be safely deleted. |
| 688 | */ |
| 689 | rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0); |
| 690 | sqlite3_finalize(pStmt); |
| 691 | if( rc==SQLITE_ERROR ){ |
| 692 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0); |
| 693 | } |
| 694 | #endif |
| 695 | |
| 696 | #if 0 |
| 697 | /* If the "origname" column is missing from the vfile table, then |
| 698 | ** add it now. This code added on 2008-11-09. After all users have |
| 699 | ** upgraded, this code can be safely deleted. |
| 700 | */ |
| 701 | rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0); |
| 702 | sqlite3_finalize(pStmt); |
| 703 | if( rc==SQLITE_ERROR ){ |
| 704 | sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0); |
| 705 | } |
| 706 | #endif |
| 707 | |
| 708 | return 1; |
| 709 | } |
| 710 | |
| 711 | /* |
| @@ -885,11 +907,15 @@ | |
| 907 | ** The zInitialDate parameter determines the date of the initial check-in |
| 908 | ** that is automatically created. If zInitialDate is 0 then no initial |
| 909 | ** check-in is created. The makeServerCodes flag determines whether or |
| 910 | ** not server and project codes are invented for this repository. |
| 911 | */ |
| 912 | void db_initial_setup( |
| 913 | const char *zInitialDate, /* Initial date of repository. (ex: "now") */ |
| 914 | const char *zDefaultUser, /* Default user for the repository */ |
| 915 | int makeServerCodes /* True to make new server & project codes */ |
| 916 | ){ |
| 917 | char *zDate; |
| 918 | Blob hash; |
| 919 | Blob manifest; |
| 920 | |
| 921 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| @@ -942,10 +968,11 @@ | |
| 968 | ** parameter. |
| 969 | ** |
| 970 | ** Options: |
| 971 | ** |
| 972 | ** --admin-user|-A USERNAME |
| 973 | ** --date-override DATETIME |
| 974 | ** |
| 975 | */ |
| 976 | void create_repository_cmd(void){ |
| 977 | char *zPassword; |
| 978 | const char *zDate; /* Date of the initial check-in */ |
| @@ -1405,10 +1432,14 @@ | |
| 1432 | ** |
| 1433 | ** autosync If enabled, automatically pull prior to commit |
| 1434 | ** or update and automatically push after commit or |
| 1435 | ** tag or branch creation. If the the value is "pullonly" |
| 1436 | ** then only pull operations occur automatically. |
| 1437 | ** |
| 1438 | ** binary-glob The VALUE is a comma-separated list of GLOB patterns |
| 1439 | ** that should be treated as binary files for merging |
| 1440 | ** purposes. Example: *.xml |
| 1441 | ** |
| 1442 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1443 | ** with gpg. When disabled (the default), commits will |
| 1444 | ** be unsigned. |
| 1445 | ** |
| @@ -1423,10 +1454,14 @@ | |
| 1454 | ** gdiff-command External command to run when performing a graphical |
| 1455 | ** diff. If undefined, text diff will be used. |
| 1456 | ** |
| 1457 | ** http-port The TCP/IP port number to use by the "server" |
| 1458 | ** and "ui" commands. Default: 8080 |
| 1459 | ** |
| 1460 | ** ignore-glob The VALUE is a comma-separated list of GLOB patterns |
| 1461 | ** specifying files that the "extra" command will ignore. |
| 1462 | ** Example: *.o,*.obj,*.exe |
| 1463 | ** |
| 1464 | ** localauth If enabled, require that HTTP connections from |
| 1465 | ** 127.0.0.1 be authenticated by password. If |
| 1466 | ** false, all HTTP requests from localhost have |
| 1467 | ** unrestricted access to the repository. |
| @@ -1448,15 +1483,17 @@ | |
| 1483 | ** and "firefox" on Unix. |
| 1484 | */ |
| 1485 | void setting_cmd(void){ |
| 1486 | static const char *azName[] = { |
| 1487 | "autosync", |
| 1488 | "binary-glob", |
| 1489 | "clearsign", |
| 1490 | "diff-command", |
| 1491 | "dont-push", |
| 1492 | "editor", |
| 1493 | "gdiff-command", |
| 1494 | "ignore-glob", |
| 1495 | "http-port", |
| 1496 | "localauth", |
| 1497 | "mtime-changes", |
| 1498 | "pgp-command", |
| 1499 | "proxy", |
| 1500 |
+1
-1
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -49,11 +49,11 @@ | ||
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | /* |
| 52 | 52 | ** This function implements a cross-platform "system()" interface. |
| 53 | 53 | */ |
| 54 | -int portable_system(char *zOrigCmd){ | |
| 54 | +int portable_system(const char *zOrigCmd){ | |
| 55 | 55 | int rc; |
| 56 | 56 | #ifdef __MINGW32__ |
| 57 | 57 | /* On windows, we have to put double-quotes around the entire command. |
| 58 | 58 | ** Who knows why - this is just the way windows works. |
| 59 | 59 | */ |
| 60 | 60 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -49,11 +49,11 @@ | |
| 49 | } |
| 50 | |
| 51 | /* |
| 52 | ** This function implements a cross-platform "system()" interface. |
| 53 | */ |
| 54 | int portable_system(char *zOrigCmd){ |
| 55 | int rc; |
| 56 | #ifdef __MINGW32__ |
| 57 | /* On windows, we have to put double-quotes around the entire command. |
| 58 | ** Who knows why - this is just the way windows works. |
| 59 | */ |
| 60 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -49,11 +49,11 @@ | |
| 49 | } |
| 50 | |
| 51 | /* |
| 52 | ** This function implements a cross-platform "system()" interface. |
| 53 | */ |
| 54 | int portable_system(const char *zOrigCmd){ |
| 55 | int rc; |
| 56 | #ifdef __MINGW32__ |
| 57 | /* On windows, we have to put double-quotes around the entire command. |
| 58 | ** Who knows why - this is just the way windows works. |
| 59 | */ |
| 60 |
+1
-1
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -163,11 +163,11 @@ | ||
| 163 | 163 | }else{ |
| 164 | 164 | if( (buf.st_mode & 0111)!=0 ){ |
| 165 | 165 | chmod(zFilename, buf.st_mode & ~0111); |
| 166 | 166 | } |
| 167 | 167 | } |
| 168 | -#endif | |
| 168 | +#endif /* __MINGW32__ */ | |
| 169 | 169 | } |
| 170 | 170 | |
| 171 | 171 | /* |
| 172 | 172 | ** Create the directory named in the argument, if it does not already |
| 173 | 173 | ** exist. If forceFlag is 1, delete any prior non-directory object |
| 174 | 174 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -163,11 +163,11 @@ | |
| 163 | }else{ |
| 164 | if( (buf.st_mode & 0111)!=0 ){ |
| 165 | chmod(zFilename, buf.st_mode & ~0111); |
| 166 | } |
| 167 | } |
| 168 | #endif |
| 169 | } |
| 170 | |
| 171 | /* |
| 172 | ** Create the directory named in the argument, if it does not already |
| 173 | ** exist. If forceFlag is 1, delete any prior non-directory object |
| 174 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -163,11 +163,11 @@ | |
| 163 | }else{ |
| 164 | if( (buf.st_mode & 0111)!=0 ){ |
| 165 | chmod(zFilename, buf.st_mode & ~0111); |
| 166 | } |
| 167 | } |
| 168 | #endif /* __MINGW32__ */ |
| 169 | } |
| 170 | |
| 171 | /* |
| 172 | ** Create the directory named in the argument, if it does not already |
| 173 | ** exist. If forceFlag is 1, delete any prior non-directory object |
| 174 |
+48
-12
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -100,36 +100,49 @@ | ||
| 100 | 100 | void finfo_page(void){ |
| 101 | 101 | Stmt q; |
| 102 | 102 | const char *zFilename; |
| 103 | 103 | char zPrevDate[20]; |
| 104 | 104 | Blob title; |
| 105 | + GraphContext *pGraph; | |
| 105 | 106 | |
| 106 | 107 | login_check_credentials(); |
| 107 | 108 | if( !g.okRead ){ login_needed(); return; } |
| 108 | 109 | style_header("File History"); |
| 109 | 110 | login_anonymous_available(); |
| 110 | 111 | |
| 111 | 112 | zPrevDate[0] = 0; |
| 112 | 113 | zFilename = PD("name",""); |
| 113 | 114 | db_prepare(&q, |
| 114 | - "SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime')," | |
| 115 | - " coalesce(event.ecomment, event.comment)," | |
| 116 | - " coalesce(event.euser, event.user)," | |
| 117 | - " mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid" | |
| 115 | + "SELECT" | |
| 116 | + " substr(b.uuid,1,10)," | |
| 117 | + " datetime(event.mtime,'localtime')," | |
| 118 | + " coalesce(event.ecomment, event.comment)," | |
| 119 | + " coalesce(event.euser, event.user)," | |
| 120 | + " mlink.pid," | |
| 121 | + " mlink.fid," | |
| 122 | + " mlink.mid," | |
| 123 | + " mlink.fnid," | |
| 124 | + " ci.uuid," | |
| 125 | + " event.bgcolor," | |
| 126 | + " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" | |
| 127 | + " AND tagxref.rid=mlink.mid)" | |
| 118 | 128 | " FROM mlink, blob b, event, blob ci" |
| 119 | 129 | " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)" |
| 120 | 130 | " AND b.rid=mlink.fid" |
| 121 | 131 | " AND event.objid=mlink.mid" |
| 122 | 132 | " AND event.objid=ci.rid" |
| 123 | 133 | " ORDER BY event.mtime DESC", |
| 134 | + TAG_BRANCH, | |
| 124 | 135 | zFilename |
| 125 | 136 | ); |
| 126 | 137 | blob_zero(&title); |
| 127 | 138 | blob_appendf(&title, "History of "); |
| 128 | 139 | hyperlinked_path(zFilename, &title); |
| 129 | 140 | @ <h2>%b(&title)</h2> |
| 130 | 141 | blob_reset(&title); |
| 142 | + pGraph = graph_init(); | |
| 143 | + @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> | |
| 131 | 144 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 132 | 145 | while( db_step(&q)==SQLITE_ROW ){ |
| 133 | 146 | const char *zUuid = db_column_text(&q, 0); |
| 134 | 147 | const char *zDate = db_column_text(&q, 1); |
| 135 | 148 | const char *zCom = db_column_text(&q, 2); |
| @@ -137,33 +150,46 @@ | ||
| 137 | 150 | int fpid = db_column_int(&q, 4); |
| 138 | 151 | int frid = db_column_int(&q, 5); |
| 139 | 152 | int mid = db_column_int(&q, 6); |
| 140 | 153 | int fnid = db_column_int(&q, 7); |
| 141 | 154 | const char *zCkin = db_column_text(&q,8); |
| 155 | + const char *zBgClr = db_column_text(&q, 9); | |
| 156 | + const char *zBr = db_column_text(&q, 10); | |
| 157 | + int gidx; | |
| 158 | + char zTime[10]; | |
| 142 | 159 | char zShort[20]; |
| 143 | 160 | char zShortCkin[20]; |
| 161 | + if( zBr==0 ) zBr = "trunk"; | |
| 162 | + gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr); | |
| 144 | 163 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 145 | 164 | sprintf(zPrevDate, "%.10s", zDate); |
| 146 | - @ <tr><td colspan=3> | |
| 147 | - @ <div class="divider">%s(zPrevDate)</div> | |
| 165 | + @ <tr><td> | |
| 166 | + @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div> | |
| 148 | 167 | @ </td></tr> |
| 149 | 168 | } |
| 150 | - @ <tr><td valign="top">%s(&zDate[11])</td> | |
| 151 | - @ <td width="20"></td> | |
| 152 | - @ <td valign="top" align="left"> | |
| 169 | + memcpy(zTime, &zDate[11], 5); | |
| 170 | + zTime[5] = 0; | |
| 171 | + @ <tr><td valign="top" align="right"> | |
| 172 | + @ <a href="%s(g.zTop)/timeline?c=%t(zDate)">%s(zTime)</a></td> | |
| 173 | + @ <td width="20" align="left" valign="top"><div id="m%d(gidx)"></div></td> | |
| 174 | + if( zBgClr && zBgClr[0] ){ | |
| 175 | + @ <td valign="top" align="left" bgcolor="%h(zBgClr)"> | |
| 176 | + }else{ | |
| 177 | + @ <td valign="top" align="left"> | |
| 178 | + } | |
| 153 | 179 | sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); |
| 154 | 180 | sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); |
| 155 | 181 | if( g.okHistory ){ |
| 156 | 182 | @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a> |
| 157 | 183 | }else{ |
| 158 | 184 | @ [%s(zShort)] |
| 159 | 185 | } |
| 160 | 186 | @ part of check-in |
| 161 | 187 | hyperlink_to_uuid(zShortCkin); |
| 162 | - @ %h(zCom) (By: | |
| 163 | - hyperlink_to_user(zUser, zDate, " on"); | |
| 164 | - hyperlink_to_date(zDate, ")"); | |
| 188 | + @ %h(zCom) (user: | |
| 189 | + hyperlink_to_user(zUser, zDate, ""); | |
| 190 | + @ branch: %h(zBr)) | |
| 165 | 191 | if( g.okHistory ){ |
| 166 | 192 | if( fpid ){ |
| 167 | 193 | @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&v2=%d(frid)">[diff]</a> |
| 168 | 194 | } |
| 169 | 195 | @ <a href="%s(g.zBaseURL)/annotate?mid=%d(mid)&fnid=%d(fnid)"> |
| @@ -170,8 +196,18 @@ | ||
| 170 | 196 | @ [annotate]</a> |
| 171 | 197 | @ </td> |
| 172 | 198 | } |
| 173 | 199 | } |
| 174 | 200 | db_finalize(&q); |
| 201 | + if( pGraph ){ | |
| 202 | + graph_finish(pGraph, 1); | |
| 203 | + if( pGraph->nErr ){ | |
| 204 | + graph_free(pGraph); | |
| 205 | + pGraph = 0; | |
| 206 | + }else{ | |
| 207 | + @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div> | |
| 208 | + } | |
| 209 | + } | |
| 175 | 210 | @ </table> |
| 211 | + timeline_output_graph_javascript(pGraph); | |
| 176 | 212 | style_footer(); |
| 177 | 213 | } |
| 178 | 214 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -100,36 +100,49 @@ | |
| 100 | void finfo_page(void){ |
| 101 | Stmt q; |
| 102 | const char *zFilename; |
| 103 | char zPrevDate[20]; |
| 104 | Blob title; |
| 105 | |
| 106 | login_check_credentials(); |
| 107 | if( !g.okRead ){ login_needed(); return; } |
| 108 | style_header("File History"); |
| 109 | login_anonymous_available(); |
| 110 | |
| 111 | zPrevDate[0] = 0; |
| 112 | zFilename = PD("name",""); |
| 113 | db_prepare(&q, |
| 114 | "SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime')," |
| 115 | " coalesce(event.ecomment, event.comment)," |
| 116 | " coalesce(event.euser, event.user)," |
| 117 | " mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid" |
| 118 | " FROM mlink, blob b, event, blob ci" |
| 119 | " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)" |
| 120 | " AND b.rid=mlink.fid" |
| 121 | " AND event.objid=mlink.mid" |
| 122 | " AND event.objid=ci.rid" |
| 123 | " ORDER BY event.mtime DESC", |
| 124 | zFilename |
| 125 | ); |
| 126 | blob_zero(&title); |
| 127 | blob_appendf(&title, "History of "); |
| 128 | hyperlinked_path(zFilename, &title); |
| 129 | @ <h2>%b(&title)</h2> |
| 130 | blob_reset(&title); |
| 131 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 132 | while( db_step(&q)==SQLITE_ROW ){ |
| 133 | const char *zUuid = db_column_text(&q, 0); |
| 134 | const char *zDate = db_column_text(&q, 1); |
| 135 | const char *zCom = db_column_text(&q, 2); |
| @@ -137,33 +150,46 @@ | |
| 137 | int fpid = db_column_int(&q, 4); |
| 138 | int frid = db_column_int(&q, 5); |
| 139 | int mid = db_column_int(&q, 6); |
| 140 | int fnid = db_column_int(&q, 7); |
| 141 | const char *zCkin = db_column_text(&q,8); |
| 142 | char zShort[20]; |
| 143 | char zShortCkin[20]; |
| 144 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 145 | sprintf(zPrevDate, "%.10s", zDate); |
| 146 | @ <tr><td colspan=3> |
| 147 | @ <div class="divider">%s(zPrevDate)</div> |
| 148 | @ </td></tr> |
| 149 | } |
| 150 | @ <tr><td valign="top">%s(&zDate[11])</td> |
| 151 | @ <td width="20"></td> |
| 152 | @ <td valign="top" align="left"> |
| 153 | sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); |
| 154 | sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); |
| 155 | if( g.okHistory ){ |
| 156 | @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a> |
| 157 | }else{ |
| 158 | @ [%s(zShort)] |
| 159 | } |
| 160 | @ part of check-in |
| 161 | hyperlink_to_uuid(zShortCkin); |
| 162 | @ %h(zCom) (By: |
| 163 | hyperlink_to_user(zUser, zDate, " on"); |
| 164 | hyperlink_to_date(zDate, ")"); |
| 165 | if( g.okHistory ){ |
| 166 | if( fpid ){ |
| 167 | @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&v2=%d(frid)">[diff]</a> |
| 168 | } |
| 169 | @ <a href="%s(g.zBaseURL)/annotate?mid=%d(mid)&fnid=%d(fnid)"> |
| @@ -170,8 +196,18 @@ | |
| 170 | @ [annotate]</a> |
| 171 | @ </td> |
| 172 | } |
| 173 | } |
| 174 | db_finalize(&q); |
| 175 | @ </table> |
| 176 | style_footer(); |
| 177 | } |
| 178 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -100,36 +100,49 @@ | |
| 100 | void finfo_page(void){ |
| 101 | Stmt q; |
| 102 | const char *zFilename; |
| 103 | char zPrevDate[20]; |
| 104 | Blob title; |
| 105 | GraphContext *pGraph; |
| 106 | |
| 107 | login_check_credentials(); |
| 108 | if( !g.okRead ){ login_needed(); return; } |
| 109 | style_header("File History"); |
| 110 | login_anonymous_available(); |
| 111 | |
| 112 | zPrevDate[0] = 0; |
| 113 | zFilename = PD("name",""); |
| 114 | db_prepare(&q, |
| 115 | "SELECT" |
| 116 | " substr(b.uuid,1,10)," |
| 117 | " datetime(event.mtime,'localtime')," |
| 118 | " coalesce(event.ecomment, event.comment)," |
| 119 | " coalesce(event.euser, event.user)," |
| 120 | " mlink.pid," |
| 121 | " mlink.fid," |
| 122 | " mlink.mid," |
| 123 | " mlink.fnid," |
| 124 | " ci.uuid," |
| 125 | " event.bgcolor," |
| 126 | " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" |
| 127 | " AND tagxref.rid=mlink.mid)" |
| 128 | " FROM mlink, blob b, event, blob ci" |
| 129 | " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)" |
| 130 | " AND b.rid=mlink.fid" |
| 131 | " AND event.objid=mlink.mid" |
| 132 | " AND event.objid=ci.rid" |
| 133 | " ORDER BY event.mtime DESC", |
| 134 | TAG_BRANCH, |
| 135 | zFilename |
| 136 | ); |
| 137 | blob_zero(&title); |
| 138 | blob_appendf(&title, "History of "); |
| 139 | hyperlinked_path(zFilename, &title); |
| 140 | @ <h2>%b(&title)</h2> |
| 141 | blob_reset(&title); |
| 142 | pGraph = graph_init(); |
| 143 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 144 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 145 | while( db_step(&q)==SQLITE_ROW ){ |
| 146 | const char *zUuid = db_column_text(&q, 0); |
| 147 | const char *zDate = db_column_text(&q, 1); |
| 148 | const char *zCom = db_column_text(&q, 2); |
| @@ -137,33 +150,46 @@ | |
| 150 | int fpid = db_column_int(&q, 4); |
| 151 | int frid = db_column_int(&q, 5); |
| 152 | int mid = db_column_int(&q, 6); |
| 153 | int fnid = db_column_int(&q, 7); |
| 154 | const char *zCkin = db_column_text(&q,8); |
| 155 | const char *zBgClr = db_column_text(&q, 9); |
| 156 | const char *zBr = db_column_text(&q, 10); |
| 157 | int gidx; |
| 158 | char zTime[10]; |
| 159 | char zShort[20]; |
| 160 | char zShortCkin[20]; |
| 161 | if( zBr==0 ) zBr = "trunk"; |
| 162 | gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr); |
| 163 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 164 | sprintf(zPrevDate, "%.10s", zDate); |
| 165 | @ <tr><td> |
| 166 | @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div> |
| 167 | @ </td></tr> |
| 168 | } |
| 169 | memcpy(zTime, &zDate[11], 5); |
| 170 | zTime[5] = 0; |
| 171 | @ <tr><td valign="top" align="right"> |
| 172 | @ <a href="%s(g.zTop)/timeline?c=%t(zDate)">%s(zTime)</a></td> |
| 173 | @ <td width="20" align="left" valign="top"><div id="m%d(gidx)"></div></td> |
| 174 | if( zBgClr && zBgClr[0] ){ |
| 175 | @ <td valign="top" align="left" bgcolor="%h(zBgClr)"> |
| 176 | }else{ |
| 177 | @ <td valign="top" align="left"> |
| 178 | } |
| 179 | sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); |
| 180 | sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); |
| 181 | if( g.okHistory ){ |
| 182 | @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a> |
| 183 | }else{ |
| 184 | @ [%s(zShort)] |
| 185 | } |
| 186 | @ part of check-in |
| 187 | hyperlink_to_uuid(zShortCkin); |
| 188 | @ %h(zCom) (user: |
| 189 | hyperlink_to_user(zUser, zDate, ""); |
| 190 | @ branch: %h(zBr)) |
| 191 | if( g.okHistory ){ |
| 192 | if( fpid ){ |
| 193 | @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&v2=%d(frid)">[diff]</a> |
| 194 | } |
| 195 | @ <a href="%s(g.zBaseURL)/annotate?mid=%d(mid)&fnid=%d(fnid)"> |
| @@ -170,8 +196,18 @@ | |
| 196 | @ [annotate]</a> |
| 197 | @ </td> |
| 198 | } |
| 199 | } |
| 200 | db_finalize(&q); |
| 201 | if( pGraph ){ |
| 202 | graph_finish(pGraph, 1); |
| 203 | if( pGraph->nErr ){ |
| 204 | graph_free(pGraph); |
| 205 | pGraph = 0; |
| 206 | }else{ |
| 207 | @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div> |
| 208 | } |
| 209 | } |
| 210 | @ </table> |
| 211 | timeline_output_graph_javascript(pGraph); |
| 212 | style_footer(); |
| 213 | } |
| 214 |
+61
-38
| --- src/graph.c | ||
| +++ src/graph.c | ||
| @@ -35,19 +35,19 @@ | ||
| 35 | 35 | /* The graph appears vertically beside a timeline. Each row in the |
| 36 | 36 | ** timeline corresponds to a row in the graph. |
| 37 | 37 | */ |
| 38 | 38 | struct GraphRow { |
| 39 | 39 | int rid; /* The rid for the check-in */ |
| 40 | - int isLeaf; /* True if the check-in is an open leaf */ | |
| 41 | 40 | int nParent; /* Number of parents */ |
| 42 | 41 | int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/ |
| 43 | 42 | char *zBranch; /* Branch name */ |
| 44 | 43 | |
| 45 | 44 | GraphRow *pNext; /* Next row down in the list of all rows */ |
| 46 | 45 | GraphRow *pPrev; /* Previous row */ |
| 47 | 46 | |
| 48 | 47 | int idx; /* Row index. First is 1. 0 used for "none" */ |
| 48 | + int isLeaf; /* True if no direct child nodes */ | |
| 49 | 49 | int iRail; /* Which rail this check-in appears on. 0-based.*/ |
| 50 | 50 | int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */ |
| 51 | 51 | int bDescender; /* Raiser from bottom of graph to here. */ |
| 52 | 52 | u32 mergeIn; /* Merge in from other rails */ |
| 53 | 53 | int mergeOut; /* Merge out to this rail */ |
| @@ -63,11 +63,14 @@ | ||
| 63 | 63 | int mxRail; /* Number of rails required to render the graph */ |
| 64 | 64 | GraphRow *pFirst; /* First row in the list */ |
| 65 | 65 | GraphRow *pLast; /* Last row in the list */ |
| 66 | 66 | int nBranch; /* Number of distinct branches */ |
| 67 | 67 | char **azBranch; /* Names of the branches */ |
| 68 | + int nRow; /* Number of rows */ | |
| 68 | 69 | int railMap[GR_MAX_RAIL]; /* Rail order mapping */ |
| 70 | + int nHash; /* Number of slots in apHash[] */ | |
| 71 | + GraphRow **apHash; /* Hash table of rows */ | |
| 69 | 72 | }; |
| 70 | 73 | |
| 71 | 74 | #endif |
| 72 | 75 | |
| 73 | 76 | /* |
| @@ -99,12 +102,39 @@ | ||
| 99 | 102 | p->pFirst = pRow->pNext; |
| 100 | 103 | free(pRow); |
| 101 | 104 | } |
| 102 | 105 | for(i=0; i<p->nBranch; i++) free(p->azBranch[i]); |
| 103 | 106 | free(p->azBranch); |
| 107 | + free(p->apHash); | |
| 104 | 108 | free(p); |
| 105 | 109 | } |
| 110 | + | |
| 111 | +/* | |
| 112 | +** Insert a row into the hash table. If there is already another | |
| 113 | +** row with the same rid, the other row is replaced. | |
| 114 | +*/ | |
| 115 | +static void hashInsert(GraphContext *p, GraphRow *pRow){ | |
| 116 | + int h; | |
| 117 | + h = pRow->rid % p->nHash; | |
| 118 | + while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){ | |
| 119 | + h++; | |
| 120 | + if( h>=p->nHash ) h = 0; | |
| 121 | + } | |
| 122 | + p->apHash[h] = pRow; | |
| 123 | +} | |
| 124 | + | |
| 125 | +/* | |
| 126 | +** Look up the row with rid. | |
| 127 | +*/ | |
| 128 | +static GraphRow *hashFind(GraphContext *p, int rid){ | |
| 129 | + int h = rid % p->nHash; | |
| 130 | + while( p->apHash[h] && p->apHash[h]->rid!=rid ){ | |
| 131 | + h++; | |
| 132 | + if( h>=p->nHash ) h = 0; | |
| 133 | + } | |
| 134 | + return p->apHash[h]; | |
| 135 | +} | |
| 106 | 136 | |
| 107 | 137 | /* |
| 108 | 138 | ** Return the canonical pointer for a given branch name. |
| 109 | 139 | ** Multiple calls to this routine with equivalent strings |
| 110 | 140 | ** will return the same pointer. |
| @@ -122,34 +152,35 @@ | ||
| 122 | 152 | } |
| 123 | 153 | |
| 124 | 154 | /* |
| 125 | 155 | ** Add a new row t the graph context. Rows are added from top to bottom. |
| 126 | 156 | */ |
| 127 | -void graph_add_row( | |
| 157 | +int graph_add_row( | |
| 128 | 158 | GraphContext *p, /* The context to which the row is added */ |
| 129 | 159 | int rid, /* RID for the check-in */ |
| 130 | - int isLeaf, /* True if the check-in is an leaf */ | |
| 131 | 160 | int nParent, /* Number of parents */ |
| 132 | 161 | int *aParent, /* Array of parents */ |
| 133 | 162 | const char *zBranch /* Branch for this check-in */ |
| 134 | 163 | ){ |
| 135 | 164 | GraphRow *pRow; |
| 136 | 165 | |
| 137 | - if( p->nErr ) return; | |
| 138 | - if( nParent>GR_MAX_PARENT ){ p->nErr++; return; } | |
| 166 | + if( p->nErr ) return 0; | |
| 167 | + if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; } | |
| 139 | 168 | pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) ); |
| 140 | 169 | pRow->rid = rid; |
| 141 | - pRow->isLeaf = isLeaf; | |
| 142 | 170 | pRow->nParent = nParent; |
| 143 | 171 | pRow->zBranch = persistBranchName(p, zBranch); |
| 144 | 172 | memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); |
| 145 | 173 | if( p->pFirst==0 ){ |
| 146 | 174 | p->pFirst = pRow; |
| 147 | 175 | }else{ |
| 148 | 176 | p->pLast->pNext = pRow; |
| 149 | 177 | } |
| 150 | 178 | p->pLast = pRow; |
| 179 | + p->nRow++; | |
| 180 | + pRow->idx = p->nRow; | |
| 181 | + return pRow->idx; | |
| 151 | 182 | } |
| 152 | 183 | |
| 153 | 184 | /* |
| 154 | 185 | ** Return the index of a rail currently not in use for any row between |
| 155 | 186 | ** top and bottom, inclusive. |
| @@ -188,51 +219,59 @@ | ||
| 188 | 219 | /* |
| 189 | 220 | ** Compute the complete graph |
| 190 | 221 | */ |
| 191 | 222 | void graph_finish(GraphContext *p, int omitDescenders){ |
| 192 | 223 | GraphRow *pRow, *pDesc; |
| 193 | - Bag allRids; | |
| 194 | - Bag notLeaf; | |
| 195 | 224 | int i; |
| 196 | - int nRow; | |
| 197 | 225 | u32 mask; |
| 198 | 226 | u32 inUse; |
| 199 | 227 | |
| 200 | 228 | if( p==0 || p->pFirst==0 || p->nErr ) return; |
| 201 | 229 | |
| 202 | 230 | /* Initialize all rows */ |
| 203 | - bag_init(&allRids); | |
| 204 | - bag_init(¬Leaf); | |
| 205 | - nRow = 0; | |
| 231 | + p->nHash = p->nRow*2 + 1; | |
| 232 | + p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash ); | |
| 206 | 233 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 207 | 234 | if( pRow->pNext ) pRow->pNext->pPrev = pRow; |
| 208 | - pRow->idx = ++nRow; | |
| 209 | 235 | pRow->iRail = -1; |
| 210 | 236 | pRow->mergeOut = -1; |
| 211 | - bag_insert(&allRids, pRow->rid); | |
| 237 | + hashInsert(p, pRow); | |
| 212 | 238 | } |
| 213 | 239 | p->mxRail = -1; |
| 214 | 240 | |
| 215 | 241 | /* Purge merge-parents that are out-of-graph |
| 216 | 242 | */ |
| 217 | 243 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 218 | 244 | for(i=1; i<pRow->nParent; i++){ |
| 219 | - if( !bag_find(&allRids, pRow->aParent[i]) ){ | |
| 245 | + if( hashFind(p, pRow->aParent[i])==0 ){ | |
| 220 | 246 | pRow->aParent[i] = pRow->aParent[--pRow->nParent]; |
| 221 | 247 | i--; |
| 222 | 248 | } |
| 223 | 249 | } |
| 224 | - if( pRow->nParent>0 && bag_find(&allRids, pRow->aParent[0]) ){ | |
| 225 | - bag_insert(¬Leaf, pRow->aParent[0]); | |
| 250 | + } | |
| 251 | + | |
| 252 | + /* Figure out which nodes have no direct children (children on | |
| 253 | + ** the same rail). Mark such nodes is isLeaf. | |
| 254 | + */ | |
| 255 | + memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash); | |
| 256 | + for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1; | |
| 257 | + for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ | |
| 258 | + GraphRow *pParent; | |
| 259 | + hashInsert(p, pRow); | |
| 260 | + if( pRow->nParent>0 | |
| 261 | + && (pParent = hashFind(p, pRow->aParent[0]))!=0 | |
| 262 | + && pRow->zBranch==pParent->zBranch | |
| 263 | + ){ | |
| 264 | + pParent->isLeaf = 0; | |
| 226 | 265 | } |
| 227 | 266 | } |
| 228 | 267 | |
| 229 | 268 | /* Identify rows where the primary parent is off screen. Assign |
| 230 | 269 | ** each to a rail and draw descenders to the bottom of the screen. |
| 231 | 270 | */ |
| 232 | 271 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 233 | - if( pRow->nParent==0 || !bag_find(&allRids,pRow->aParent[0]) ){ | |
| 272 | + if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ | |
| 234 | 273 | if( omitDescenders ){ |
| 235 | 274 | pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0); |
| 236 | 275 | }else{ |
| 237 | 276 | pRow->iRail = ++p->mxRail; |
| 238 | 277 | } |
| @@ -257,11 +296,10 @@ | ||
| 257 | 296 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ |
| 258 | 297 | int parentRid; |
| 259 | 298 | if( pRow->iRail>=0 ) continue; |
| 260 | 299 | assert( pRow->nParent>0 ); |
| 261 | 300 | parentRid = pRow->aParent[0]; |
| 262 | - assert( bag_find(&allRids, parentRid) ); | |
| 263 | 301 | for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){} |
| 264 | 302 | if( pDesc==0 ){ |
| 265 | 303 | /* Time skew */ |
| 266 | 304 | pRow->iRail = ++p->mxRail; |
| 267 | 305 | pRow->railInUse = 1<<pRow->iRail; |
| @@ -272,14 +310,14 @@ | ||
| 272 | 310 | }else{ |
| 273 | 311 | pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0); |
| 274 | 312 | } |
| 275 | 313 | pDesc->aiRaiser[pRow->iRail] = pRow->idx; |
| 276 | 314 | mask = 1<<pRow->iRail; |
| 277 | - if( bag_find(¬Leaf, pRow->rid) ){ | |
| 278 | - inUse |= mask; | |
| 315 | + if( pRow->isLeaf ){ | |
| 316 | + inUse &= ~mask; | |
| 279 | 317 | }else{ |
| 280 | - inUse &= ~mask; | |
| 318 | + inUse |= mask; | |
| 281 | 319 | } |
| 282 | 320 | for(pDesc = pRow; ; pDesc=pDesc->pNext){ |
| 283 | 321 | assert( pDesc!=0 ); |
| 284 | 322 | pDesc->railInUse |= mask; |
| 285 | 323 | if( pDesc->rid==parentRid ) break; |
| @@ -309,29 +347,14 @@ | ||
| 309 | 347 | pRow->mergeIn |= 1<<pDesc->mergeOut; |
| 310 | 348 | } |
| 311 | 349 | } |
| 312 | 350 | |
| 313 | 351 | /* |
| 314 | - ** Sort the rail numbers | |
| 352 | + ** Find the maximum rail number. | |
| 315 | 353 | */ |
| 316 | -#if 0 | |
| 317 | - p->mxRail = -1; | |
| 318 | - mask = 0; | |
| 319 | - for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ | |
| 320 | - if( (mask & (1<<pRow->iRail))==0 ){ | |
| 321 | - p->railMap[pRow->iRail] = ++p->mxRail; | |
| 322 | - mask |= 1<<pRow->iRail; | |
| 323 | - } | |
| 324 | - if( pRow->mergeOut>=0 && (mask & (1<<pRow->mergeOut))==0 ){ | |
| 325 | - p->railMap[pRow->mergeOut] = ++p->mxRail; | |
| 326 | - mask |= 1<<pRow->mergeOut; | |
| 327 | - } | |
| 328 | - } | |
| 329 | -#else | |
| 330 | 354 | for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i; |
| 331 | 355 | p->mxRail = 0; |
| 332 | 356 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 333 | 357 | if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail; |
| 334 | 358 | if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut; |
| 335 | 359 | } |
| 336 | -#endif | |
| 337 | 360 | } |
| 338 | 361 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -35,19 +35,19 @@ | |
| 35 | /* The graph appears vertically beside a timeline. Each row in the |
| 36 | ** timeline corresponds to a row in the graph. |
| 37 | */ |
| 38 | struct GraphRow { |
| 39 | int rid; /* The rid for the check-in */ |
| 40 | int isLeaf; /* True if the check-in is an open leaf */ |
| 41 | int nParent; /* Number of parents */ |
| 42 | int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/ |
| 43 | char *zBranch; /* Branch name */ |
| 44 | |
| 45 | GraphRow *pNext; /* Next row down in the list of all rows */ |
| 46 | GraphRow *pPrev; /* Previous row */ |
| 47 | |
| 48 | int idx; /* Row index. First is 1. 0 used for "none" */ |
| 49 | int iRail; /* Which rail this check-in appears on. 0-based.*/ |
| 50 | int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */ |
| 51 | int bDescender; /* Raiser from bottom of graph to here. */ |
| 52 | u32 mergeIn; /* Merge in from other rails */ |
| 53 | int mergeOut; /* Merge out to this rail */ |
| @@ -63,11 +63,14 @@ | |
| 63 | int mxRail; /* Number of rails required to render the graph */ |
| 64 | GraphRow *pFirst; /* First row in the list */ |
| 65 | GraphRow *pLast; /* Last row in the list */ |
| 66 | int nBranch; /* Number of distinct branches */ |
| 67 | char **azBranch; /* Names of the branches */ |
| 68 | int railMap[GR_MAX_RAIL]; /* Rail order mapping */ |
| 69 | }; |
| 70 | |
| 71 | #endif |
| 72 | |
| 73 | /* |
| @@ -99,12 +102,39 @@ | |
| 99 | p->pFirst = pRow->pNext; |
| 100 | free(pRow); |
| 101 | } |
| 102 | for(i=0; i<p->nBranch; i++) free(p->azBranch[i]); |
| 103 | free(p->azBranch); |
| 104 | free(p); |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | ** Return the canonical pointer for a given branch name. |
| 109 | ** Multiple calls to this routine with equivalent strings |
| 110 | ** will return the same pointer. |
| @@ -122,34 +152,35 @@ | |
| 122 | } |
| 123 | |
| 124 | /* |
| 125 | ** Add a new row t the graph context. Rows are added from top to bottom. |
| 126 | */ |
| 127 | void graph_add_row( |
| 128 | GraphContext *p, /* The context to which the row is added */ |
| 129 | int rid, /* RID for the check-in */ |
| 130 | int isLeaf, /* True if the check-in is an leaf */ |
| 131 | int nParent, /* Number of parents */ |
| 132 | int *aParent, /* Array of parents */ |
| 133 | const char *zBranch /* Branch for this check-in */ |
| 134 | ){ |
| 135 | GraphRow *pRow; |
| 136 | |
| 137 | if( p->nErr ) return; |
| 138 | if( nParent>GR_MAX_PARENT ){ p->nErr++; return; } |
| 139 | pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) ); |
| 140 | pRow->rid = rid; |
| 141 | pRow->isLeaf = isLeaf; |
| 142 | pRow->nParent = nParent; |
| 143 | pRow->zBranch = persistBranchName(p, zBranch); |
| 144 | memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); |
| 145 | if( p->pFirst==0 ){ |
| 146 | p->pFirst = pRow; |
| 147 | }else{ |
| 148 | p->pLast->pNext = pRow; |
| 149 | } |
| 150 | p->pLast = pRow; |
| 151 | } |
| 152 | |
| 153 | /* |
| 154 | ** Return the index of a rail currently not in use for any row between |
| 155 | ** top and bottom, inclusive. |
| @@ -188,51 +219,59 @@ | |
| 188 | /* |
| 189 | ** Compute the complete graph |
| 190 | */ |
| 191 | void graph_finish(GraphContext *p, int omitDescenders){ |
| 192 | GraphRow *pRow, *pDesc; |
| 193 | Bag allRids; |
| 194 | Bag notLeaf; |
| 195 | int i; |
| 196 | int nRow; |
| 197 | u32 mask; |
| 198 | u32 inUse; |
| 199 | |
| 200 | if( p==0 || p->pFirst==0 || p->nErr ) return; |
| 201 | |
| 202 | /* Initialize all rows */ |
| 203 | bag_init(&allRids); |
| 204 | bag_init(¬Leaf); |
| 205 | nRow = 0; |
| 206 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 207 | if( pRow->pNext ) pRow->pNext->pPrev = pRow; |
| 208 | pRow->idx = ++nRow; |
| 209 | pRow->iRail = -1; |
| 210 | pRow->mergeOut = -1; |
| 211 | bag_insert(&allRids, pRow->rid); |
| 212 | } |
| 213 | p->mxRail = -1; |
| 214 | |
| 215 | /* Purge merge-parents that are out-of-graph |
| 216 | */ |
| 217 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 218 | for(i=1; i<pRow->nParent; i++){ |
| 219 | if( !bag_find(&allRids, pRow->aParent[i]) ){ |
| 220 | pRow->aParent[i] = pRow->aParent[--pRow->nParent]; |
| 221 | i--; |
| 222 | } |
| 223 | } |
| 224 | if( pRow->nParent>0 && bag_find(&allRids, pRow->aParent[0]) ){ |
| 225 | bag_insert(¬Leaf, pRow->aParent[0]); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /* Identify rows where the primary parent is off screen. Assign |
| 230 | ** each to a rail and draw descenders to the bottom of the screen. |
| 231 | */ |
| 232 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 233 | if( pRow->nParent==0 || !bag_find(&allRids,pRow->aParent[0]) ){ |
| 234 | if( omitDescenders ){ |
| 235 | pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0); |
| 236 | }else{ |
| 237 | pRow->iRail = ++p->mxRail; |
| 238 | } |
| @@ -257,11 +296,10 @@ | |
| 257 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ |
| 258 | int parentRid; |
| 259 | if( pRow->iRail>=0 ) continue; |
| 260 | assert( pRow->nParent>0 ); |
| 261 | parentRid = pRow->aParent[0]; |
| 262 | assert( bag_find(&allRids, parentRid) ); |
| 263 | for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){} |
| 264 | if( pDesc==0 ){ |
| 265 | /* Time skew */ |
| 266 | pRow->iRail = ++p->mxRail; |
| 267 | pRow->railInUse = 1<<pRow->iRail; |
| @@ -272,14 +310,14 @@ | |
| 272 | }else{ |
| 273 | pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0); |
| 274 | } |
| 275 | pDesc->aiRaiser[pRow->iRail] = pRow->idx; |
| 276 | mask = 1<<pRow->iRail; |
| 277 | if( bag_find(¬Leaf, pRow->rid) ){ |
| 278 | inUse |= mask; |
| 279 | }else{ |
| 280 | inUse &= ~mask; |
| 281 | } |
| 282 | for(pDesc = pRow; ; pDesc=pDesc->pNext){ |
| 283 | assert( pDesc!=0 ); |
| 284 | pDesc->railInUse |= mask; |
| 285 | if( pDesc->rid==parentRid ) break; |
| @@ -309,29 +347,14 @@ | |
| 309 | pRow->mergeIn |= 1<<pDesc->mergeOut; |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | /* |
| 314 | ** Sort the rail numbers |
| 315 | */ |
| 316 | #if 0 |
| 317 | p->mxRail = -1; |
| 318 | mask = 0; |
| 319 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ |
| 320 | if( (mask & (1<<pRow->iRail))==0 ){ |
| 321 | p->railMap[pRow->iRail] = ++p->mxRail; |
| 322 | mask |= 1<<pRow->iRail; |
| 323 | } |
| 324 | if( pRow->mergeOut>=0 && (mask & (1<<pRow->mergeOut))==0 ){ |
| 325 | p->railMap[pRow->mergeOut] = ++p->mxRail; |
| 326 | mask |= 1<<pRow->mergeOut; |
| 327 | } |
| 328 | } |
| 329 | #else |
| 330 | for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i; |
| 331 | p->mxRail = 0; |
| 332 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 333 | if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail; |
| 334 | if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut; |
| 335 | } |
| 336 | #endif |
| 337 | } |
| 338 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -35,19 +35,19 @@ | |
| 35 | /* The graph appears vertically beside a timeline. Each row in the |
| 36 | ** timeline corresponds to a row in the graph. |
| 37 | */ |
| 38 | struct GraphRow { |
| 39 | int rid; /* The rid for the check-in */ |
| 40 | int nParent; /* Number of parents */ |
| 41 | int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/ |
| 42 | char *zBranch; /* Branch name */ |
| 43 | |
| 44 | GraphRow *pNext; /* Next row down in the list of all rows */ |
| 45 | GraphRow *pPrev; /* Previous row */ |
| 46 | |
| 47 | int idx; /* Row index. First is 1. 0 used for "none" */ |
| 48 | int isLeaf; /* True if no direct child nodes */ |
| 49 | int iRail; /* Which rail this check-in appears on. 0-based.*/ |
| 50 | int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */ |
| 51 | int bDescender; /* Raiser from bottom of graph to here. */ |
| 52 | u32 mergeIn; /* Merge in from other rails */ |
| 53 | int mergeOut; /* Merge out to this rail */ |
| @@ -63,11 +63,14 @@ | |
| 63 | int mxRail; /* Number of rails required to render the graph */ |
| 64 | GraphRow *pFirst; /* First row in the list */ |
| 65 | GraphRow *pLast; /* Last row in the list */ |
| 66 | int nBranch; /* Number of distinct branches */ |
| 67 | char **azBranch; /* Names of the branches */ |
| 68 | int nRow; /* Number of rows */ |
| 69 | int railMap[GR_MAX_RAIL]; /* Rail order mapping */ |
| 70 | int nHash; /* Number of slots in apHash[] */ |
| 71 | GraphRow **apHash; /* Hash table of rows */ |
| 72 | }; |
| 73 | |
| 74 | #endif |
| 75 | |
| 76 | /* |
| @@ -99,12 +102,39 @@ | |
| 102 | p->pFirst = pRow->pNext; |
| 103 | free(pRow); |
| 104 | } |
| 105 | for(i=0; i<p->nBranch; i++) free(p->azBranch[i]); |
| 106 | free(p->azBranch); |
| 107 | free(p->apHash); |
| 108 | free(p); |
| 109 | } |
| 110 | |
| 111 | /* |
| 112 | ** Insert a row into the hash table. If there is already another |
| 113 | ** row with the same rid, the other row is replaced. |
| 114 | */ |
| 115 | static void hashInsert(GraphContext *p, GraphRow *pRow){ |
| 116 | int h; |
| 117 | h = pRow->rid % p->nHash; |
| 118 | while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){ |
| 119 | h++; |
| 120 | if( h>=p->nHash ) h = 0; |
| 121 | } |
| 122 | p->apHash[h] = pRow; |
| 123 | } |
| 124 | |
| 125 | /* |
| 126 | ** Look up the row with rid. |
| 127 | */ |
| 128 | static GraphRow *hashFind(GraphContext *p, int rid){ |
| 129 | int h = rid % p->nHash; |
| 130 | while( p->apHash[h] && p->apHash[h]->rid!=rid ){ |
| 131 | h++; |
| 132 | if( h>=p->nHash ) h = 0; |
| 133 | } |
| 134 | return p->apHash[h]; |
| 135 | } |
| 136 | |
| 137 | /* |
| 138 | ** Return the canonical pointer for a given branch name. |
| 139 | ** Multiple calls to this routine with equivalent strings |
| 140 | ** will return the same pointer. |
| @@ -122,34 +152,35 @@ | |
| 152 | } |
| 153 | |
| 154 | /* |
| 155 | ** Add a new row t the graph context. Rows are added from top to bottom. |
| 156 | */ |
| 157 | int graph_add_row( |
| 158 | GraphContext *p, /* The context to which the row is added */ |
| 159 | int rid, /* RID for the check-in */ |
| 160 | int nParent, /* Number of parents */ |
| 161 | int *aParent, /* Array of parents */ |
| 162 | const char *zBranch /* Branch for this check-in */ |
| 163 | ){ |
| 164 | GraphRow *pRow; |
| 165 | |
| 166 | if( p->nErr ) return 0; |
| 167 | if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; } |
| 168 | pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) ); |
| 169 | pRow->rid = rid; |
| 170 | pRow->nParent = nParent; |
| 171 | pRow->zBranch = persistBranchName(p, zBranch); |
| 172 | memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); |
| 173 | if( p->pFirst==0 ){ |
| 174 | p->pFirst = pRow; |
| 175 | }else{ |
| 176 | p->pLast->pNext = pRow; |
| 177 | } |
| 178 | p->pLast = pRow; |
| 179 | p->nRow++; |
| 180 | pRow->idx = p->nRow; |
| 181 | return pRow->idx; |
| 182 | } |
| 183 | |
| 184 | /* |
| 185 | ** Return the index of a rail currently not in use for any row between |
| 186 | ** top and bottom, inclusive. |
| @@ -188,51 +219,59 @@ | |
| 219 | /* |
| 220 | ** Compute the complete graph |
| 221 | */ |
| 222 | void graph_finish(GraphContext *p, int omitDescenders){ |
| 223 | GraphRow *pRow, *pDesc; |
| 224 | int i; |
| 225 | u32 mask; |
| 226 | u32 inUse; |
| 227 | |
| 228 | if( p==0 || p->pFirst==0 || p->nErr ) return; |
| 229 | |
| 230 | /* Initialize all rows */ |
| 231 | p->nHash = p->nRow*2 + 1; |
| 232 | p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash ); |
| 233 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 234 | if( pRow->pNext ) pRow->pNext->pPrev = pRow; |
| 235 | pRow->iRail = -1; |
| 236 | pRow->mergeOut = -1; |
| 237 | hashInsert(p, pRow); |
| 238 | } |
| 239 | p->mxRail = -1; |
| 240 | |
| 241 | /* Purge merge-parents that are out-of-graph |
| 242 | */ |
| 243 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 244 | for(i=1; i<pRow->nParent; i++){ |
| 245 | if( hashFind(p, pRow->aParent[i])==0 ){ |
| 246 | pRow->aParent[i] = pRow->aParent[--pRow->nParent]; |
| 247 | i--; |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | /* Figure out which nodes have no direct children (children on |
| 253 | ** the same rail). Mark such nodes is isLeaf. |
| 254 | */ |
| 255 | memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash); |
| 256 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1; |
| 257 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ |
| 258 | GraphRow *pParent; |
| 259 | hashInsert(p, pRow); |
| 260 | if( pRow->nParent>0 |
| 261 | && (pParent = hashFind(p, pRow->aParent[0]))!=0 |
| 262 | && pRow->zBranch==pParent->zBranch |
| 263 | ){ |
| 264 | pParent->isLeaf = 0; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /* Identify rows where the primary parent is off screen. Assign |
| 269 | ** each to a rail and draw descenders to the bottom of the screen. |
| 270 | */ |
| 271 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 272 | if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ |
| 273 | if( omitDescenders ){ |
| 274 | pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0); |
| 275 | }else{ |
| 276 | pRow->iRail = ++p->mxRail; |
| 277 | } |
| @@ -257,11 +296,10 @@ | |
| 296 | for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ |
| 297 | int parentRid; |
| 298 | if( pRow->iRail>=0 ) continue; |
| 299 | assert( pRow->nParent>0 ); |
| 300 | parentRid = pRow->aParent[0]; |
| 301 | for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){} |
| 302 | if( pDesc==0 ){ |
| 303 | /* Time skew */ |
| 304 | pRow->iRail = ++p->mxRail; |
| 305 | pRow->railInUse = 1<<pRow->iRail; |
| @@ -272,14 +310,14 @@ | |
| 310 | }else{ |
| 311 | pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0); |
| 312 | } |
| 313 | pDesc->aiRaiser[pRow->iRail] = pRow->idx; |
| 314 | mask = 1<<pRow->iRail; |
| 315 | if( pRow->isLeaf ){ |
| 316 | inUse &= ~mask; |
| 317 | }else{ |
| 318 | inUse |= mask; |
| 319 | } |
| 320 | for(pDesc = pRow; ; pDesc=pDesc->pNext){ |
| 321 | assert( pDesc!=0 ); |
| 322 | pDesc->railInUse |= mask; |
| 323 | if( pDesc->rid==parentRid ) break; |
| @@ -309,29 +347,14 @@ | |
| 347 | pRow->mergeIn |= 1<<pDesc->mergeOut; |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | /* |
| 352 | ** Find the maximum rail number. |
| 353 | */ |
| 354 | for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i; |
| 355 | p->mxRail = 0; |
| 356 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 357 | if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail; |
| 358 | if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut; |
| 359 | } |
| 360 | } |
| 361 |
+42
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -732,10 +732,52 @@ | ||
| 732 | 732 | } |
| 733 | 733 | cnt++; |
| 734 | 734 | } |
| 735 | 735 | db_finalize(&q); |
| 736 | 736 | } |
| 737 | + db_prepare(&q, | |
| 738 | + "SELECT target, filename, datetime(mtime), user, src" | |
| 739 | + " FROM attachment" | |
| 740 | + " WHERE src=(SELECT uuid FROM blob WHERE rid=%d)" | |
| 741 | + " ORDER BY mtime DESC", | |
| 742 | + rid | |
| 743 | + ); | |
| 744 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 745 | + const char *zTarget = db_column_text(&q, 0); | |
| 746 | + const char *zFilename = db_column_text(&q, 1); | |
| 747 | + const char *zDate = db_column_text(&q, 2); | |
| 748 | + const char *zUser = db_column_text(&q, 3); | |
| 749 | + const char *zSrc = db_column_text(&q, 4); | |
| 750 | + if( cnt>0 ){ | |
| 751 | + @ Also attachment "%h(zFilename)" to | |
| 752 | + }else{ | |
| 753 | + @ Attachment "%h(zFilename)" to | |
| 754 | + } | |
| 755 | + if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ | |
| 756 | + char zShort[20]; | |
| 757 | + memcpy(zShort, zTarget, 10); | |
| 758 | + if( g.okHistory && g.okRdTkt ){ | |
| 759 | + @ ticket [<a href="%s(g.zTop)/tktview?name=%s(zShort)">%s(zShort)</a>] | |
| 760 | + }else{ | |
| 761 | + @ ticket [%s(zShort)] | |
| 762 | + } | |
| 763 | + }else{ | |
| 764 | + if( g.okHistory && g.okRdWiki ){ | |
| 765 | + @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>] | |
| 766 | + }else{ | |
| 767 | + @ wiki page [%h(zTarget)] | |
| 768 | + } | |
| 769 | + } | |
| 770 | + @ added by | |
| 771 | + hyperlink_to_user(zUser,zDate," on"); | |
| 772 | + hyperlink_to_date(zDate,"."); | |
| 773 | + cnt++; | |
| 774 | + if( pDownloadName && blob_size(pDownloadName)==0 ){ | |
| 775 | + blob_append(pDownloadName, zSrc, -1); | |
| 776 | + } | |
| 777 | + } | |
| 778 | + db_finalize(&q); | |
| 737 | 779 | if( cnt==0 ){ |
| 738 | 780 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 739 | 781 | @ Control artifact. |
| 740 | 782 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 741 | 783 | blob_append(pDownloadName, zUuid, -1); |
| 742 | 784 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -732,10 +732,52 @@ | |
| 732 | } |
| 733 | cnt++; |
| 734 | } |
| 735 | db_finalize(&q); |
| 736 | } |
| 737 | if( cnt==0 ){ |
| 738 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 739 | @ Control artifact. |
| 740 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 741 | blob_append(pDownloadName, zUuid, -1); |
| 742 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -732,10 +732,52 @@ | |
| 732 | } |
| 733 | cnt++; |
| 734 | } |
| 735 | db_finalize(&q); |
| 736 | } |
| 737 | db_prepare(&q, |
| 738 | "SELECT target, filename, datetime(mtime), user, src" |
| 739 | " FROM attachment" |
| 740 | " WHERE src=(SELECT uuid FROM blob WHERE rid=%d)" |
| 741 | " ORDER BY mtime DESC", |
| 742 | rid |
| 743 | ); |
| 744 | while( db_step(&q)==SQLITE_ROW ){ |
| 745 | const char *zTarget = db_column_text(&q, 0); |
| 746 | const char *zFilename = db_column_text(&q, 1); |
| 747 | const char *zDate = db_column_text(&q, 2); |
| 748 | const char *zUser = db_column_text(&q, 3); |
| 749 | const char *zSrc = db_column_text(&q, 4); |
| 750 | if( cnt>0 ){ |
| 751 | @ Also attachment "%h(zFilename)" to |
| 752 | }else{ |
| 753 | @ Attachment "%h(zFilename)" to |
| 754 | } |
| 755 | if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ |
| 756 | char zShort[20]; |
| 757 | memcpy(zShort, zTarget, 10); |
| 758 | if( g.okHistory && g.okRdTkt ){ |
| 759 | @ ticket [<a href="%s(g.zTop)/tktview?name=%s(zShort)">%s(zShort)</a>] |
| 760 | }else{ |
| 761 | @ ticket [%s(zShort)] |
| 762 | } |
| 763 | }else{ |
| 764 | if( g.okHistory && g.okRdWiki ){ |
| 765 | @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>] |
| 766 | }else{ |
| 767 | @ wiki page [%h(zTarget)] |
| 768 | } |
| 769 | } |
| 770 | @ added by |
| 771 | hyperlink_to_user(zUser,zDate," on"); |
| 772 | hyperlink_to_date(zDate,"."); |
| 773 | cnt++; |
| 774 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 775 | blob_append(pDownloadName, zSrc, -1); |
| 776 | } |
| 777 | } |
| 778 | db_finalize(&q); |
| 779 | if( cnt==0 ){ |
| 780 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 781 | @ Control artifact. |
| 782 | if( pDownloadName && blob_size(pDownloadName)==0 ){ |
| 783 | blob_append(pDownloadName, zUuid, -1); |
| 784 |
+4
-2
| --- src/login.c | ||
| +++ src/login.c | ||
| @@ -476,11 +476,12 @@ | ||
| 476 | 476 | case 's': g.okSetup = 1; /* Fall thru into Admin */ |
| 477 | 477 | case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip = |
| 478 | 478 | g.okRdWiki = g.okWrWiki = g.okNewWiki = |
| 479 | 479 | g.okApndWiki = g.okHistory = g.okClone = |
| 480 | 480 | g.okNewTkt = g.okPassword = g.okRdAddr = |
| 481 | - g.okTktFmt = 1; /* Fall thru into Read/Write */ | |
| 481 | + g.okTktFmt = g.okAttach = 1; | |
| 482 | + /* Fall thru into Read/Write */ | |
| 482 | 483 | case 'i': g.okRead = g.okWrite = 1; break; |
| 483 | 484 | case 'o': g.okRead = 1; break; |
| 484 | 485 | case 'z': g.okZip = 1; break; |
| 485 | 486 | |
| 486 | 487 | case 'd': g.okDelete = 1; break; |
| @@ -498,10 +499,11 @@ | ||
| 498 | 499 | case 'n': g.okNewTkt = 1; break; |
| 499 | 500 | case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt = |
| 500 | 501 | g.okApndTkt = 1; break; |
| 501 | 502 | case 'c': g.okApndTkt = 1; break; |
| 502 | 503 | case 't': g.okTktFmt = 1; break; |
| 504 | + case 'b': g.okAttach = 1; break; | |
| 503 | 505 | |
| 504 | 506 | /* The "u" privileges is a little different. It recursively |
| 505 | 507 | ** inherits all privileges of the user named "reader" */ |
| 506 | 508 | case 'u': { |
| 507 | 509 | if( zUser==0 ){ |
| @@ -534,11 +536,11 @@ | ||
| 534 | 536 | int rc = 1; |
| 535 | 537 | if( nCap<0 ) nCap = strlen(zCap); |
| 536 | 538 | for(i=0; i<nCap && rc && zCap[i]; i++){ |
| 537 | 539 | switch( zCap[i] ){ |
| 538 | 540 | case 'a': rc = g.okAdmin; break; |
| 539 | - /* case 'b': */ | |
| 541 | + case 'b': rc = g.okAttach; break; | |
| 540 | 542 | case 'c': rc = g.okApndTkt; break; |
| 541 | 543 | case 'd': rc = g.okDelete; break; |
| 542 | 544 | case 'e': rc = g.okRdAddr; break; |
| 543 | 545 | case 'f': rc = g.okNewWiki; break; |
| 544 | 546 | case 'g': rc = g.okClone; break; |
| 545 | 547 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -476,11 +476,12 @@ | |
| 476 | case 's': g.okSetup = 1; /* Fall thru into Admin */ |
| 477 | case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip = |
| 478 | g.okRdWiki = g.okWrWiki = g.okNewWiki = |
| 479 | g.okApndWiki = g.okHistory = g.okClone = |
| 480 | g.okNewTkt = g.okPassword = g.okRdAddr = |
| 481 | g.okTktFmt = 1; /* Fall thru into Read/Write */ |
| 482 | case 'i': g.okRead = g.okWrite = 1; break; |
| 483 | case 'o': g.okRead = 1; break; |
| 484 | case 'z': g.okZip = 1; break; |
| 485 | |
| 486 | case 'd': g.okDelete = 1; break; |
| @@ -498,10 +499,11 @@ | |
| 498 | case 'n': g.okNewTkt = 1; break; |
| 499 | case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt = |
| 500 | g.okApndTkt = 1; break; |
| 501 | case 'c': g.okApndTkt = 1; break; |
| 502 | case 't': g.okTktFmt = 1; break; |
| 503 | |
| 504 | /* The "u" privileges is a little different. It recursively |
| 505 | ** inherits all privileges of the user named "reader" */ |
| 506 | case 'u': { |
| 507 | if( zUser==0 ){ |
| @@ -534,11 +536,11 @@ | |
| 534 | int rc = 1; |
| 535 | if( nCap<0 ) nCap = strlen(zCap); |
| 536 | for(i=0; i<nCap && rc && zCap[i]; i++){ |
| 537 | switch( zCap[i] ){ |
| 538 | case 'a': rc = g.okAdmin; break; |
| 539 | /* case 'b': */ |
| 540 | case 'c': rc = g.okApndTkt; break; |
| 541 | case 'd': rc = g.okDelete; break; |
| 542 | case 'e': rc = g.okRdAddr; break; |
| 543 | case 'f': rc = g.okNewWiki; break; |
| 544 | case 'g': rc = g.okClone; break; |
| 545 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -476,11 +476,12 @@ | |
| 476 | case 's': g.okSetup = 1; /* Fall thru into Admin */ |
| 477 | case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip = |
| 478 | g.okRdWiki = g.okWrWiki = g.okNewWiki = |
| 479 | g.okApndWiki = g.okHistory = g.okClone = |
| 480 | g.okNewTkt = g.okPassword = g.okRdAddr = |
| 481 | g.okTktFmt = g.okAttach = 1; |
| 482 | /* Fall thru into Read/Write */ |
| 483 | case 'i': g.okRead = g.okWrite = 1; break; |
| 484 | case 'o': g.okRead = 1; break; |
| 485 | case 'z': g.okZip = 1; break; |
| 486 | |
| 487 | case 'd': g.okDelete = 1; break; |
| @@ -498,10 +499,11 @@ | |
| 499 | case 'n': g.okNewTkt = 1; break; |
| 500 | case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt = |
| 501 | g.okApndTkt = 1; break; |
| 502 | case 'c': g.okApndTkt = 1; break; |
| 503 | case 't': g.okTktFmt = 1; break; |
| 504 | case 'b': g.okAttach = 1; break; |
| 505 | |
| 506 | /* The "u" privileges is a little different. It recursively |
| 507 | ** inherits all privileges of the user named "reader" */ |
| 508 | case 'u': { |
| 509 | if( zUser==0 ){ |
| @@ -534,11 +536,11 @@ | |
| 536 | int rc = 1; |
| 537 | if( nCap<0 ) nCap = strlen(zCap); |
| 538 | for(i=0; i<nCap && rc && zCap[i]; i++){ |
| 539 | switch( zCap[i] ){ |
| 540 | case 'a': rc = g.okAdmin; break; |
| 541 | case 'b': rc = g.okAttach; break; |
| 542 | case 'c': rc = g.okApndTkt; break; |
| 543 | case 'd': rc = g.okDelete; break; |
| 544 | case 'e': rc = g.okRdAddr; break; |
| 545 | case 'f': rc = g.okNewWiki; break; |
| 546 | case 'g': rc = g.okClone; break; |
| 547 |
+1
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -130,10 +130,11 @@ | ||
| 130 | 130 | int okWrWiki; /* k: edit wiki via web */ |
| 131 | 131 | int okRdTkt; /* r: view tickets via web */ |
| 132 | 132 | int okNewTkt; /* n: create new tickets */ |
| 133 | 133 | int okApndTkt; /* c: append to tickets via the web */ |
| 134 | 134 | int okWrTkt; /* w: make changes to tickets via web */ |
| 135 | + int okAttach; /* b: add attachments */ | |
| 135 | 136 | int okTktFmt; /* t: create new ticket report formats */ |
| 136 | 137 | int okRdAddr; /* e: read email addresses or other private data */ |
| 137 | 138 | int okZip; /* z: download zipped artifact via /zip URL */ |
| 138 | 139 | |
| 139 | 140 | /* For defense against Cross-site Request Forgery attacks */ |
| 140 | 141 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -130,10 +130,11 @@ | |
| 130 | int okWrWiki; /* k: edit wiki via web */ |
| 131 | int okRdTkt; /* r: view tickets via web */ |
| 132 | int okNewTkt; /* n: create new tickets */ |
| 133 | int okApndTkt; /* c: append to tickets via the web */ |
| 134 | int okWrTkt; /* w: make changes to tickets via web */ |
| 135 | int okTktFmt; /* t: create new ticket report formats */ |
| 136 | int okRdAddr; /* e: read email addresses or other private data */ |
| 137 | int okZip; /* z: download zipped artifact via /zip URL */ |
| 138 | |
| 139 | /* For defense against Cross-site Request Forgery attacks */ |
| 140 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -130,10 +130,11 @@ | |
| 130 | int okWrWiki; /* k: edit wiki via web */ |
| 131 | int okRdTkt; /* r: view tickets via web */ |
| 132 | int okNewTkt; /* n: create new tickets */ |
| 133 | int okApndTkt; /* c: append to tickets via the web */ |
| 134 | int okWrTkt; /* w: make changes to tickets via web */ |
| 135 | int okAttach; /* b: add attachments */ |
| 136 | int okTktFmt; /* t: create new ticket report formats */ |
| 137 | int okRdAddr; /* e: read email addresses or other private data */ |
| 138 | int okZip; /* z: download zipped artifact via /zip URL */ |
| 139 | |
| 140 | /* For defense against Cross-site Request Forgery attacks */ |
| 141 |
+12
-2
| --- src/main.mk | ||
| +++ src/main.mk | ||
| @@ -13,10 +13,11 @@ | ||
| 13 | 13 | |
| 14 | 14 | |
| 15 | 15 | SRC = \ |
| 16 | 16 | $(SRCDIR)/add.c \ |
| 17 | 17 | $(SRCDIR)/allrepo.c \ |
| 18 | + $(SRCDIR)/attach.c \ | |
| 18 | 19 | $(SRCDIR)/bag.c \ |
| 19 | 20 | $(SRCDIR)/blob.c \ |
| 20 | 21 | $(SRCDIR)/branch.c \ |
| 21 | 22 | $(SRCDIR)/browse.c \ |
| 22 | 23 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | ||
| 83 | 84 | $(SRCDIR)/zip.c |
| 84 | 85 | |
| 85 | 86 | TRANS_SRC = \ |
| 86 | 87 | add_.c \ |
| 87 | 88 | allrepo_.c \ |
| 89 | + attach_.c \ | |
| 88 | 90 | bag_.c \ |
| 89 | 91 | blob_.c \ |
| 90 | 92 | branch_.c \ |
| 91 | 93 | browse_.c \ |
| 92 | 94 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | ||
| 153 | 155 | zip_.c |
| 154 | 156 | |
| 155 | 157 | OBJ = \ |
| 156 | 158 | $(OBJDIR)/add.o \ |
| 157 | 159 | $(OBJDIR)/allrepo.o \ |
| 160 | + $(OBJDIR)/attach.o \ | |
| 158 | 161 | $(OBJDIR)/bag.o \ |
| 159 | 162 | $(OBJDIR)/blob.o \ |
| 160 | 163 | $(OBJDIR)/branch.o \ |
| 161 | 164 | $(OBJDIR)/browse.o \ |
| 162 | 165 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | ||
| 264 | 267 | # noop |
| 265 | 268 | |
| 266 | 269 | clean: |
| 267 | 270 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 268 | 271 | rm -f translate makeheaders mkindex page_index.h headers |
| 269 | - rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h | |
| 272 | + rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h | |
| 270 | 273 | |
| 271 | 274 | page_index.h: $(TRANS_SRC) mkindex |
| 272 | 275 | ./mkindex $(TRANS_SRC) >$@ |
| 273 | 276 | headers: page_index.h makeheaders VERSION.h |
| 274 | - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h | |
| 277 | + ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h | |
| 275 | 278 | touch headers |
| 276 | 279 | headers: Makefile |
| 277 | 280 | Makefile: |
| 278 | 281 | add_.c: $(SRCDIR)/add.c translate |
| 279 | 282 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | ||
| 287 | 290 | |
| 288 | 291 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 289 | 292 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 290 | 293 | |
| 291 | 294 | allrepo.h: headers |
| 295 | +attach_.c: $(SRCDIR)/attach.c translate | |
| 296 | + ./translate $(SRCDIR)/attach.c >attach_.c | |
| 297 | + | |
| 298 | +$(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h | |
| 299 | + $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c | |
| 300 | + | |
| 301 | +attach.h: headers | |
| 292 | 302 | bag_.c: $(SRCDIR)/bag.c translate |
| 293 | 303 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 294 | 304 | |
| 295 | 305 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 296 | 306 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 297 | 307 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -13,10 +13,11 @@ | |
| 13 | |
| 14 | |
| 15 | SRC = \ |
| 16 | $(SRCDIR)/add.c \ |
| 17 | $(SRCDIR)/allrepo.c \ |
| 18 | $(SRCDIR)/bag.c \ |
| 19 | $(SRCDIR)/blob.c \ |
| 20 | $(SRCDIR)/branch.c \ |
| 21 | $(SRCDIR)/browse.c \ |
| 22 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | |
| 83 | $(SRCDIR)/zip.c |
| 84 | |
| 85 | TRANS_SRC = \ |
| 86 | add_.c \ |
| 87 | allrepo_.c \ |
| 88 | bag_.c \ |
| 89 | blob_.c \ |
| 90 | branch_.c \ |
| 91 | browse_.c \ |
| 92 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | |
| 153 | zip_.c |
| 154 | |
| 155 | OBJ = \ |
| 156 | $(OBJDIR)/add.o \ |
| 157 | $(OBJDIR)/allrepo.o \ |
| 158 | $(OBJDIR)/bag.o \ |
| 159 | $(OBJDIR)/blob.o \ |
| 160 | $(OBJDIR)/branch.o \ |
| 161 | $(OBJDIR)/browse.o \ |
| 162 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | |
| 264 | # noop |
| 265 | |
| 266 | clean: |
| 267 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 268 | rm -f translate makeheaders mkindex page_index.h headers |
| 269 | rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h |
| 270 | |
| 271 | page_index.h: $(TRANS_SRC) mkindex |
| 272 | ./mkindex $(TRANS_SRC) >$@ |
| 273 | headers: page_index.h makeheaders VERSION.h |
| 274 | ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h |
| 275 | touch headers |
| 276 | headers: Makefile |
| 277 | Makefile: |
| 278 | add_.c: $(SRCDIR)/add.c translate |
| 279 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | |
| 287 | |
| 288 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 289 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 290 | |
| 291 | allrepo.h: headers |
| 292 | bag_.c: $(SRCDIR)/bag.c translate |
| 293 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 294 | |
| 295 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 296 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 297 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -13,10 +13,11 @@ | |
| 13 | |
| 14 | |
| 15 | SRC = \ |
| 16 | $(SRCDIR)/add.c \ |
| 17 | $(SRCDIR)/allrepo.c \ |
| 18 | $(SRCDIR)/attach.c \ |
| 19 | $(SRCDIR)/bag.c \ |
| 20 | $(SRCDIR)/blob.c \ |
| 21 | $(SRCDIR)/branch.c \ |
| 22 | $(SRCDIR)/browse.c \ |
| 23 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | |
| 84 | $(SRCDIR)/zip.c |
| 85 | |
| 86 | TRANS_SRC = \ |
| 87 | add_.c \ |
| 88 | allrepo_.c \ |
| 89 | attach_.c \ |
| 90 | bag_.c \ |
| 91 | blob_.c \ |
| 92 | branch_.c \ |
| 93 | browse_.c \ |
| 94 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | |
| 155 | zip_.c |
| 156 | |
| 157 | OBJ = \ |
| 158 | $(OBJDIR)/add.o \ |
| 159 | $(OBJDIR)/allrepo.o \ |
| 160 | $(OBJDIR)/attach.o \ |
| 161 | $(OBJDIR)/bag.o \ |
| 162 | $(OBJDIR)/blob.o \ |
| 163 | $(OBJDIR)/branch.o \ |
| 164 | $(OBJDIR)/browse.o \ |
| 165 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | |
| 267 | # noop |
| 268 | |
| 269 | clean: |
| 270 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 271 | rm -f translate makeheaders mkindex page_index.h headers |
| 272 | rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h |
| 273 | |
| 274 | page_index.h: $(TRANS_SRC) mkindex |
| 275 | ./mkindex $(TRANS_SRC) >$@ |
| 276 | headers: page_index.h makeheaders VERSION.h |
| 277 | ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h |
| 278 | touch headers |
| 279 | headers: Makefile |
| 280 | Makefile: |
| 281 | add_.c: $(SRCDIR)/add.c translate |
| 282 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | |
| 290 | |
| 291 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 292 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 293 | |
| 294 | allrepo.h: headers |
| 295 | attach_.c: $(SRCDIR)/attach.c translate |
| 296 | ./translate $(SRCDIR)/attach.c >attach_.c |
| 297 | |
| 298 | $(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h |
| 299 | $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c |
| 300 | |
| 301 | attach.h: headers |
| 302 | bag_.c: $(SRCDIR)/bag.c translate |
| 303 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 304 | |
| 305 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 306 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 307 |
+12
-2
| --- src/main.mk | ||
| +++ src/main.mk | ||
| @@ -13,10 +13,11 @@ | ||
| 13 | 13 | |
| 14 | 14 | |
| 15 | 15 | SRC = \ |
| 16 | 16 | $(SRCDIR)/add.c \ |
| 17 | 17 | $(SRCDIR)/allrepo.c \ |
| 18 | + $(SRCDIR)/attach.c \ | |
| 18 | 19 | $(SRCDIR)/bag.c \ |
| 19 | 20 | $(SRCDIR)/blob.c \ |
| 20 | 21 | $(SRCDIR)/branch.c \ |
| 21 | 22 | $(SRCDIR)/browse.c \ |
| 22 | 23 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | ||
| 83 | 84 | $(SRCDIR)/zip.c |
| 84 | 85 | |
| 85 | 86 | TRANS_SRC = \ |
| 86 | 87 | add_.c \ |
| 87 | 88 | allrepo_.c \ |
| 89 | + attach_.c \ | |
| 88 | 90 | bag_.c \ |
| 89 | 91 | blob_.c \ |
| 90 | 92 | branch_.c \ |
| 91 | 93 | browse_.c \ |
| 92 | 94 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | ||
| 153 | 155 | zip_.c |
| 154 | 156 | |
| 155 | 157 | OBJ = \ |
| 156 | 158 | $(OBJDIR)/add.o \ |
| 157 | 159 | $(OBJDIR)/allrepo.o \ |
| 160 | + $(OBJDIR)/attach.o \ | |
| 158 | 161 | $(OBJDIR)/bag.o \ |
| 159 | 162 | $(OBJDIR)/blob.o \ |
| 160 | 163 | $(OBJDIR)/branch.o \ |
| 161 | 164 | $(OBJDIR)/browse.o \ |
| 162 | 165 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | ||
| 264 | 267 | # noop |
| 265 | 268 | |
| 266 | 269 | clean: |
| 267 | 270 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 268 | 271 | rm -f translate makeheaders mkindex page_index.h headers |
| 269 | - rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h | |
| 272 | + rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h | |
| 270 | 273 | |
| 271 | 274 | page_index.h: $(TRANS_SRC) mkindex |
| 272 | 275 | ./mkindex $(TRANS_SRC) >$@ |
| 273 | 276 | headers: page_index.h makeheaders VERSION.h |
| 274 | - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h | |
| 277 | + ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h | |
| 275 | 278 | touch headers |
| 276 | 279 | headers: Makefile |
| 277 | 280 | Makefile: |
| 278 | 281 | add_.c: $(SRCDIR)/add.c translate |
| 279 | 282 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | ||
| 287 | 290 | |
| 288 | 291 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 289 | 292 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 290 | 293 | |
| 291 | 294 | allrepo.h: headers |
| 295 | +attach_.c: $(SRCDIR)/attach.c translate | |
| 296 | + ./translate $(SRCDIR)/attach.c >attach_.c | |
| 297 | + | |
| 298 | +$(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h | |
| 299 | + $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c | |
| 300 | + | |
| 301 | +attach.h: headers | |
| 292 | 302 | bag_.c: $(SRCDIR)/bag.c translate |
| 293 | 303 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 294 | 304 | |
| 295 | 305 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 296 | 306 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 297 | 307 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -13,10 +13,11 @@ | |
| 13 | |
| 14 | |
| 15 | SRC = \ |
| 16 | $(SRCDIR)/add.c \ |
| 17 | $(SRCDIR)/allrepo.c \ |
| 18 | $(SRCDIR)/bag.c \ |
| 19 | $(SRCDIR)/blob.c \ |
| 20 | $(SRCDIR)/branch.c \ |
| 21 | $(SRCDIR)/browse.c \ |
| 22 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | |
| 83 | $(SRCDIR)/zip.c |
| 84 | |
| 85 | TRANS_SRC = \ |
| 86 | add_.c \ |
| 87 | allrepo_.c \ |
| 88 | bag_.c \ |
| 89 | blob_.c \ |
| 90 | branch_.c \ |
| 91 | browse_.c \ |
| 92 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | |
| 153 | zip_.c |
| 154 | |
| 155 | OBJ = \ |
| 156 | $(OBJDIR)/add.o \ |
| 157 | $(OBJDIR)/allrepo.o \ |
| 158 | $(OBJDIR)/bag.o \ |
| 159 | $(OBJDIR)/blob.o \ |
| 160 | $(OBJDIR)/branch.o \ |
| 161 | $(OBJDIR)/browse.o \ |
| 162 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | |
| 264 | # noop |
| 265 | |
| 266 | clean: |
| 267 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 268 | rm -f translate makeheaders mkindex page_index.h headers |
| 269 | rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h |
| 270 | |
| 271 | page_index.h: $(TRANS_SRC) mkindex |
| 272 | ./mkindex $(TRANS_SRC) >$@ |
| 273 | headers: page_index.h makeheaders VERSION.h |
| 274 | ./makeheaders add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h |
| 275 | touch headers |
| 276 | headers: Makefile |
| 277 | Makefile: |
| 278 | add_.c: $(SRCDIR)/add.c translate |
| 279 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | |
| 287 | |
| 288 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 289 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 290 | |
| 291 | allrepo.h: headers |
| 292 | bag_.c: $(SRCDIR)/bag.c translate |
| 293 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 294 | |
| 295 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 296 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 297 |
| --- src/main.mk | |
| +++ src/main.mk | |
| @@ -13,10 +13,11 @@ | |
| 13 | |
| 14 | |
| 15 | SRC = \ |
| 16 | $(SRCDIR)/add.c \ |
| 17 | $(SRCDIR)/allrepo.c \ |
| 18 | $(SRCDIR)/attach.c \ |
| 19 | $(SRCDIR)/bag.c \ |
| 20 | $(SRCDIR)/blob.c \ |
| 21 | $(SRCDIR)/branch.c \ |
| 22 | $(SRCDIR)/browse.c \ |
| 23 | $(SRCDIR)/captcha.c \ |
| @@ -83,10 +84,11 @@ | |
| 84 | $(SRCDIR)/zip.c |
| 85 | |
| 86 | TRANS_SRC = \ |
| 87 | add_.c \ |
| 88 | allrepo_.c \ |
| 89 | attach_.c \ |
| 90 | bag_.c \ |
| 91 | blob_.c \ |
| 92 | branch_.c \ |
| 93 | browse_.c \ |
| 94 | captcha_.c \ |
| @@ -153,10 +155,11 @@ | |
| 155 | zip_.c |
| 156 | |
| 157 | OBJ = \ |
| 158 | $(OBJDIR)/add.o \ |
| 159 | $(OBJDIR)/allrepo.o \ |
| 160 | $(OBJDIR)/attach.o \ |
| 161 | $(OBJDIR)/bag.o \ |
| 162 | $(OBJDIR)/blob.o \ |
| 163 | $(OBJDIR)/branch.o \ |
| 164 | $(OBJDIR)/browse.o \ |
| 165 | $(OBJDIR)/captcha.o \ |
| @@ -264,16 +267,16 @@ | |
| 267 | # noop |
| 268 | |
| 269 | clean: |
| 270 | rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h |
| 271 | rm -f translate makeheaders mkindex page_index.h headers |
| 272 | rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h |
| 273 | |
| 274 | page_index.h: $(TRANS_SRC) mkindex |
| 275 | ./mkindex $(TRANS_SRC) >$@ |
| 276 | headers: page_index.h makeheaders VERSION.h |
| 277 | ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h |
| 278 | touch headers |
| 279 | headers: Makefile |
| 280 | Makefile: |
| 281 | add_.c: $(SRCDIR)/add.c translate |
| 282 | ./translate $(SRCDIR)/add.c >add_.c |
| @@ -287,10 +290,17 @@ | |
| 290 | |
| 291 | $(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h |
| 292 | $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c |
| 293 | |
| 294 | allrepo.h: headers |
| 295 | attach_.c: $(SRCDIR)/attach.c translate |
| 296 | ./translate $(SRCDIR)/attach.c >attach_.c |
| 297 | |
| 298 | $(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h |
| 299 | $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c |
| 300 | |
| 301 | attach.h: headers |
| 302 | bag_.c: $(SRCDIR)/bag.c translate |
| 303 | ./translate $(SRCDIR)/bag.c >bag_.c |
| 304 | |
| 305 | $(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h |
| 306 | $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c |
| 307 |
+1
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -7,10 +7,11 @@ | ||
| 7 | 7 | # "translate" and "makeheaders" |
| 8 | 8 | # |
| 9 | 9 | set src { |
| 10 | 10 | add |
| 11 | 11 | allrepo |
| 12 | + attach | |
| 12 | 13 | bag |
| 13 | 14 | blob |
| 14 | 15 | branch |
| 15 | 16 | browse |
| 16 | 17 | captcha |
| 17 | 18 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -7,10 +7,11 @@ | |
| 7 | # "translate" and "makeheaders" |
| 8 | # |
| 9 | set src { |
| 10 | add |
| 11 | allrepo |
| 12 | bag |
| 13 | blob |
| 14 | branch |
| 15 | browse |
| 16 | captcha |
| 17 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -7,10 +7,11 @@ | |
| 7 | # "translate" and "makeheaders" |
| 8 | # |
| 9 | set src { |
| 10 | add |
| 11 | allrepo |
| 12 | attach |
| 13 | bag |
| 14 | blob |
| 15 | branch |
| 16 | browse |
| 17 | captcha |
| 18 |
+1
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -7,10 +7,11 @@ | ||
| 7 | 7 | # "translate" and "makeheaders" |
| 8 | 8 | # |
| 9 | 9 | set src { |
| 10 | 10 | add |
| 11 | 11 | allrepo |
| 12 | + attach | |
| 12 | 13 | bag |
| 13 | 14 | blob |
| 14 | 15 | branch |
| 15 | 16 | browse |
| 16 | 17 | captcha |
| 17 | 18 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -7,10 +7,11 @@ | |
| 7 | # "translate" and "makeheaders" |
| 8 | # |
| 9 | set src { |
| 10 | add |
| 11 | allrepo |
| 12 | bag |
| 13 | blob |
| 14 | branch |
| 15 | browse |
| 16 | captcha |
| 17 |
| --- src/makemake.tcl | |
| +++ src/makemake.tcl | |
| @@ -7,10 +7,11 @@ | |
| 7 | # "translate" and "makeheaders" |
| 8 | # |
| 9 | set src { |
| 10 | add |
| 11 | allrepo |
| 12 | attach |
| 13 | bag |
| 14 | blob |
| 15 | branch |
| 16 | browse |
| 17 | captcha |
| 18 |
+122
-91
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -37,67 +37,56 @@ | ||
| 37 | 37 | #define CFTYPE_MANIFEST 1 |
| 38 | 38 | #define CFTYPE_CLUSTER 2 |
| 39 | 39 | #define CFTYPE_CONTROL 3 |
| 40 | 40 | #define CFTYPE_WIKI 4 |
| 41 | 41 | #define CFTYPE_TICKET 5 |
| 42 | - | |
| 43 | -/* | |
| 44 | -** Mode parameter values | |
| 45 | -*/ | |
| 46 | -#define CFMODE_READ 1 | |
| 47 | -#define CFMODE_APPEND 2 | |
| 48 | -#define CFMODE_WRITE 3 | |
| 42 | +#define CFTYPE_ATTACHMENT 6 | |
| 49 | 43 | |
| 50 | 44 | /* |
| 51 | 45 | ** A parsed manifest or cluster. |
| 52 | 46 | */ |
| 53 | 47 | struct Manifest { |
| 54 | 48 | Blob content; /* The original content blob */ |
| 55 | - int type; /* Type of file */ | |
| 56 | - int mode; /* Access mode */ | |
| 57 | - char *zComment; /* Decoded comment */ | |
| 58 | - double rDate; /* Time in the "D" line */ | |
| 59 | - char *zUser; /* Name of the user */ | |
| 60 | - char *zRepoCksum; /* MD5 checksum of the baseline content */ | |
| 61 | - char *zWiki; /* Text of the wiki page */ | |
| 62 | - char *zWikiTitle; /* Name of the wiki page */ | |
| 63 | - char *zTicketUuid; /* UUID for a ticket */ | |
| 64 | - int nFile; /* Number of F lines */ | |
| 49 | + int type; /* Type of artifact. One of CFTYPE_xxxxx */ | |
| 50 | + char *zComment; /* Decoded comment. The C card. */ | |
| 51 | + double rDate; /* Date and time from D card. 0.0 if no D card. */ | |
| 52 | + char *zUser; /* Name of the user from the U card. */ | |
| 53 | + char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ | |
| 54 | + char *zWiki; /* Text of the wiki page. W card. */ | |
| 55 | + char *zWikiTitle; /* Name of the wiki page. L card. */ | |
| 56 | + char *zTicketUuid; /* UUID for a ticket. K card. */ | |
| 57 | + char *zAttachName; /* Filename of an attachment. A card. */ | |
| 58 | + char *zAttachSrc; /* UUID of document being attached. A card. */ | |
| 59 | + char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ | |
| 60 | + int nFile; /* Number of F cards */ | |
| 65 | 61 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 66 | 62 | struct { |
| 67 | 63 | char *zName; /* Name of a file */ |
| 68 | 64 | char *zUuid; /* UUID of the file */ |
| 69 | 65 | char *zPerm; /* File permissions */ |
| 70 | 66 | char *zPrior; /* Prior name if the name was changed */ |
| 71 | 67 | int iRename; /* index of renamed name in prior/next manifest */ |
| 72 | - } *aFile; | |
| 73 | - int nParent; /* Number of parents */ | |
| 68 | + } *aFile; /* One entry for each F card */ | |
| 69 | + int nParent; /* Number of parents. */ | |
| 74 | 70 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 75 | - char **azParent; /* UUIDs of parents */ | |
| 71 | + char **azParent; /* UUIDs of parents. One for each P card argument */ | |
| 76 | 72 | int nCChild; /* Number of cluster children */ |
| 77 | 73 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| 78 | - char **azCChild; /* UUIDs of referenced objects in a cluster */ | |
| 79 | - int nTag; /* Number of T lines */ | |
| 74 | + char **azCChild; /* UUIDs of referenced objects in a cluster. M cards */ | |
| 75 | + int nTag; /* Number of T Cards */ | |
| 80 | 76 | int nTagAlloc; /* Slots allocated in aTag[] */ |
| 81 | 77 | struct { |
| 82 | 78 | char *zName; /* Name of the tag */ |
| 83 | 79 | char *zUuid; /* UUID that the tag is applied to */ |
| 84 | 80 | char *zValue; /* Value if the tag is really a property */ |
| 85 | - } *aTag; | |
| 86 | - int nField; /* Number of J lines */ | |
| 81 | + } *aTag; /* One for each T card */ | |
| 82 | + int nField; /* Number of J cards */ | |
| 87 | 83 | int nFieldAlloc; /* Slots allocated in aField[] */ |
| 88 | 84 | struct { |
| 89 | 85 | char *zName; /* Key or field name */ |
| 90 | 86 | char *zValue; /* Value of the field */ |
| 91 | - } *aField; | |
| 92 | - int nAttach; /* Number of A lines */ | |
| 93 | - int nAttachAlloc; /* Slots allocated in aAttach[] */ | |
| 94 | - struct { | |
| 95 | - char *zUuid; /* UUID of the attachment */ | |
| 96 | - char *zName; /* Name of the attachment */ | |
| 97 | - char *zDesc; /* Description of the attachment */ | |
| 98 | - } *aAttach; | |
| 87 | + } *aField; /* One for each J card */ | |
| 99 | 88 | }; |
| 100 | 89 | #endif |
| 101 | 90 | |
| 102 | 91 | |
| 103 | 92 | /* |
| @@ -108,11 +97,10 @@ | ||
| 108 | 97 | free(p->aFile); |
| 109 | 98 | free(p->azParent); |
| 110 | 99 | free(p->azCChild); |
| 111 | 100 | free(p->aTag); |
| 112 | 101 | free(p->aField); |
| 113 | - free(p->aAttach); | |
| 114 | 102 | memset(p, 0, sizeof(*p)); |
| 115 | 103 | } |
| 116 | 104 | |
| 117 | 105 | /* |
| 118 | 106 | ** Parse a blob into a Manifest object. The Manifest object |
| @@ -178,44 +166,43 @@ | ||
| 178 | 166 | cPrevType = z[0]; |
| 179 | 167 | seenHeader = 1; |
| 180 | 168 | if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; |
| 181 | 169 | switch( z[0] ){ |
| 182 | 170 | /* |
| 183 | - ** A <uuid> <filename> <description> | |
| 171 | + ** A <filename> <target> ?<source>? | |
| 184 | 172 | ** |
| 185 | 173 | ** Identifies an attachment to either a wiki page or a ticket. |
| 186 | - ** <uuid> is the artifact that is the attachment. | |
| 174 | + ** <source> is the artifact that is the attachment. <source> | |
| 175 | + ** is omitted to delete an attachment. <target> is the name of | |
| 176 | + ** a wiki page or ticket to which that attachment is connected. | |
| 187 | 177 | */ |
| 188 | 178 | case 'A': { |
| 189 | - char *zName, *zUuid, *zDesc; | |
| 179 | + char *zName, *zTarget, *zSrc; | |
| 190 | 180 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 191 | 181 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 192 | 182 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 193 | - if( blob_token(&line, &a3)==0 ) goto manifest_syntax_error; | |
| 194 | - zUuid = blob_terminate(&a1); | |
| 195 | - zName = blob_terminate(&a2); | |
| 196 | - zDesc = blob_terminate(&a3); | |
| 197 | - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 198 | - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; | |
| 183 | + if( p->zAttachName!=0 ) goto manifest_syntax_error; | |
| 184 | + zName = blob_terminate(&a1); | |
| 185 | + zTarget = blob_terminate(&a2); | |
| 186 | + blob_token(&line, &a3); | |
| 187 | + zSrc = blob_terminate(&a3); | |
| 199 | 188 | defossilize(zName); |
| 200 | 189 | if( !file_is_simple_pathname(zName) ){ |
| 201 | 190 | goto manifest_syntax_error; |
| 202 | 191 | } |
| 203 | - defossilize(zDesc); | |
| 204 | - if( p->nAttach>=p->nAttachAlloc ){ | |
| 205 | - p->nAttachAlloc = p->nAttachAlloc*2 + 10; | |
| 206 | - p->aAttach = realloc(p->aAttach, | |
| 207 | - p->nAttachAlloc*sizeof(p->aAttach[0]) ); | |
| 208 | - if( p->aAttach==0 ) fossil_panic("out of memory"); | |
| 209 | - } | |
| 210 | - i = p->nAttach++; | |
| 211 | - p->aAttach[i].zUuid = zUuid; | |
| 212 | - p->aAttach[i].zName = zName; | |
| 213 | - p->aAttach[i].zDesc = zDesc; | |
| 214 | - if( i>0 && strcmp(p->aAttach[i-1].zUuid, zUuid)>=0 ){ | |
| 192 | + defossilize(zTarget); | |
| 193 | + if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) | |
| 194 | + && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ | |
| 195 | + goto manifest_syntax_error; | |
| 196 | + } | |
| 197 | + if( blob_size(&a3)>0 | |
| 198 | + && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ | |
| 215 | 199 | goto manifest_syntax_error; |
| 216 | 200 | } |
| 201 | + p->zAttachName = (char*)file_tail(zName); | |
| 202 | + p->zAttachSrc = zSrc; | |
| 203 | + p->zAttachTarget = zTarget; | |
| 217 | 204 | break; |
| 218 | 205 | } |
| 219 | 206 | |
| 220 | 207 | /* |
| 221 | 208 | ** C <comment> |
| @@ -247,33 +234,10 @@ | ||
| 247 | 234 | if( p->rDate!=0.0 ) goto manifest_syntax_error; |
| 248 | 235 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 249 | 236 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 250 | 237 | zDate = blob_terminate(&a1); |
| 251 | 238 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); |
| 252 | - break; | |
| 253 | - } | |
| 254 | - | |
| 255 | - /* | |
| 256 | - ** E <mode> | |
| 257 | - ** | |
| 258 | - ** Access mode. <mode> can be one of "read", "append", | |
| 259 | - ** or "write". | |
| 260 | - */ | |
| 261 | - case 'E': { | |
| 262 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 263 | - if( p->mode!=0 ) goto manifest_syntax_error; | |
| 264 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 265 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 266 | - if( blob_eq(&a1, "write") ){ | |
| 267 | - p->mode = CFMODE_WRITE; | |
| 268 | - }else if( blob_eq(&a1, "append") ){ | |
| 269 | - p->mode = CFMODE_APPEND; | |
| 270 | - }else if( blob_eq(&a1, "read") ){ | |
| 271 | - p->mode = CFMODE_READ; | |
| 272 | - }else{ | |
| 273 | - goto manifest_syntax_error; | |
| 274 | - } | |
| 275 | 239 | break; |
| 276 | 240 | } |
| 277 | 241 | |
| 278 | 242 | /* |
| 279 | 243 | ** F <filename> <uuid> ?<permissions>? ?<old-name>? |
| @@ -608,72 +572,75 @@ | ||
| 608 | 572 | if( p->nFile>0 || p->zRepoCksum!=0 ){ |
| 609 | 573 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 610 | 574 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 611 | 575 | if( p->nField>0 ) goto manifest_syntax_error; |
| 612 | 576 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 613 | - if( p->nAttach>0 ) goto manifest_syntax_error; | |
| 614 | 577 | if( p->zWiki ) goto manifest_syntax_error; |
| 615 | 578 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 616 | 579 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 580 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 617 | 581 | p->type = CFTYPE_MANIFEST; |
| 618 | 582 | }else if( p->nCChild>0 ){ |
| 619 | 583 | if( p->rDate>0.0 ) goto manifest_syntax_error; |
| 620 | 584 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 621 | 585 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 622 | 586 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 623 | 587 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 624 | - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; | |
| 625 | 588 | if( p->nField>0 ) goto manifest_syntax_error; |
| 626 | 589 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 627 | - if( p->nAttach>0 ) goto manifest_syntax_error; | |
| 628 | 590 | if( p->zWiki ) goto manifest_syntax_error; |
| 629 | 591 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 592 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 630 | 593 | if( !seenZ ) goto manifest_syntax_error; |
| 631 | 594 | p->type = CFTYPE_CLUSTER; |
| 632 | 595 | }else if( p->nField>0 ){ |
| 633 | 596 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 634 | - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; | |
| 635 | 597 | if( p->zWiki ) goto manifest_syntax_error; |
| 636 | 598 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 637 | 599 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 638 | 600 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 639 | 601 | if( p->zTicketUuid==0 ) goto manifest_syntax_error; |
| 640 | 602 | if( p->zUser==0 ) goto manifest_syntax_error; |
| 603 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 641 | 604 | if( !seenZ ) goto manifest_syntax_error; |
| 642 | 605 | p->type = CFTYPE_TICKET; |
| 643 | 606 | }else if( p->zWiki!=0 ){ |
| 644 | 607 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 645 | - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; | |
| 646 | 608 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 647 | 609 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 648 | 610 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 649 | 611 | if( p->zWikiTitle==0 ) goto manifest_syntax_error; |
| 612 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 650 | 613 | if( !seenZ ) goto manifest_syntax_error; |
| 651 | 614 | p->type = CFTYPE_WIKI; |
| 652 | 615 | }else if( p->nTag>0 ){ |
| 653 | 616 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 654 | - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; | |
| 655 | 617 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 656 | - if( p->nAttach>0 ) goto manifest_syntax_error; | |
| 657 | - if( p->nField>0 ) goto manifest_syntax_error; | |
| 658 | - if( p->zWiki ) goto manifest_syntax_error; | |
| 659 | 618 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 660 | 619 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 620 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 661 | 621 | if( !seenZ ) goto manifest_syntax_error; |
| 662 | 622 | p->type = CFTYPE_CONTROL; |
| 663 | - }else{ | |
| 623 | + }else if( p->zAttachName ){ | |
| 664 | 624 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 665 | 625 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 626 | + if( p->zTicketUuid ) goto manifest_syntax_error; | |
| 627 | + if( p->zWikiTitle ) goto manifest_syntax_error; | |
| 628 | + if( !seenZ ) goto manifest_syntax_error; | |
| 629 | + p->type = CFTYPE_ATTACHMENT; | |
| 630 | + }else{ | |
| 631 | + if( p->nCChild>0 ) goto manifest_syntax_error; | |
| 632 | + if( p->rDate<=0.0 ) goto manifest_syntax_error; | |
| 633 | + if( p->nParent>0 ) goto manifest_syntax_error; | |
| 666 | 634 | if( p->nField>0 ) goto manifest_syntax_error; |
| 667 | 635 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 668 | - if( p->nAttach>0 ) goto manifest_syntax_error; | |
| 669 | 636 | if( p->zWiki ) goto manifest_syntax_error; |
| 670 | 637 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 671 | 638 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 639 | + if( p->zAttachName ) goto manifest_syntax_error; | |
| 672 | 640 | p->type = CFTYPE_MANIFEST; |
| 673 | 641 | } |
| 674 | - | |
| 675 | 642 | md5sum_init(); |
| 676 | 643 | return 1; |
| 677 | 644 | |
| 678 | 645 | manifest_syntax_error: |
| 679 | 646 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| @@ -1032,10 +999,11 @@ | ||
| 1032 | 999 | return 0; |
| 1033 | 1000 | } |
| 1034 | 1001 | db_begin_transaction(); |
| 1035 | 1002 | if( m.type==CFTYPE_MANIFEST ){ |
| 1036 | 1003 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1004 | + char *zCom; | |
| 1037 | 1005 | for(i=0; i<m.nParent; i++){ |
| 1038 | 1006 | int pid = uuid_to_rid(m.azParent[i], 1); |
| 1039 | 1007 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1040 | 1008 | "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); |
| 1041 | 1009 | if( i==0 ){ |
| @@ -1065,10 +1033,14 @@ | ||
| 1065 | 1033 | rid, m.zUser, m.zComment, |
| 1066 | 1034 | TAG_BGCOLOR, rid, |
| 1067 | 1035 | TAG_USER, rid, |
| 1068 | 1036 | TAG_COMMENT, rid |
| 1069 | 1037 | ); |
| 1038 | + zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" | |
| 1039 | + " WHERE rowid=last_insert_rowid()"); | |
| 1040 | + wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); | |
| 1041 | + free(zCom); | |
| 1070 | 1042 | } |
| 1071 | 1043 | } |
| 1072 | 1044 | if( m.type==CFTYPE_CLUSTER ){ |
| 1073 | 1045 | tag_insert("cluster", 1, 0, rid, m.rDate, rid); |
| 1074 | 1046 | for(i=0; i<m.nCChild; i++){ |
| @@ -1108,11 +1080,16 @@ | ||
| 1108 | 1080 | if( m.type==CFTYPE_WIKI ){ |
| 1109 | 1081 | char *zTag = mprintf("wiki-%s", m.zWikiTitle); |
| 1110 | 1082 | int tagid = tag_findid(zTag, 1); |
| 1111 | 1083 | int prior; |
| 1112 | 1084 | char *zComment; |
| 1113 | - tag_insert(zTag, 1, 0, rid, m.rDate, rid); | |
| 1085 | + int nWiki; | |
| 1086 | + char zLength[40]; | |
| 1087 | + while( isspace(m.zWiki[0]) ) m.zWiki++; | |
| 1088 | + nWiki = strlen(m.zWiki); | |
| 1089 | + sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); | |
| 1090 | + tag_insert(zTag, 1, zLength, rid, m.rDate, rid); | |
| 1114 | 1091 | free(zTag); |
| 1115 | 1092 | prior = db_int(0, |
| 1116 | 1093 | "SELECT rid FROM tagxref" |
| 1117 | 1094 | " WHERE tagid=%d AND mtime<%.17g" |
| 1118 | 1095 | " ORDER BY mtime DESC", |
| @@ -1119,11 +1096,15 @@ | ||
| 1119 | 1096 | tagid, m.rDate |
| 1120 | 1097 | ); |
| 1121 | 1098 | if( prior ){ |
| 1122 | 1099 | content_deltify(prior, rid, 0); |
| 1123 | 1100 | } |
| 1124 | - zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); | |
| 1101 | + if( nWiki>0 ){ | |
| 1102 | + zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); | |
| 1103 | + }else{ | |
| 1104 | + zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); | |
| 1105 | + } | |
| 1125 | 1106 | db_multi_exec( |
| 1126 | 1107 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1127 | 1108 | " bgcolor,euser,ecomment)" |
| 1128 | 1109 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1129 | 1110 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1144,10 +1125,60 @@ | ||
| 1144 | 1125 | zTag = mprintf("tkt-%s", m.zTicketUuid); |
| 1145 | 1126 | tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 1146 | 1127 | free(zTag); |
| 1147 | 1128 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1148 | 1129 | m.zTicketUuid); |
| 1130 | + } | |
| 1131 | + if( m.type==CFTYPE_ATTACHMENT ){ | |
| 1132 | + db_multi_exec( | |
| 1133 | + "INSERT INTO attachment(attachid, mtime, src, target," | |
| 1134 | + "filename, comment, user)" | |
| 1135 | + "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", | |
| 1136 | + rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, | |
| 1137 | + (m.zComment ? m.zComment : ""), m.zUser | |
| 1138 | + ); | |
| 1139 | + db_multi_exec( | |
| 1140 | + "UPDATE attachment SET isLatest = (mtime==" | |
| 1141 | + "(SELECT max(mtime) FROM attachment" | |
| 1142 | + " WHERE target=%Q AND filename=%Q))" | |
| 1143 | + " WHERE target=%Q AND filename=%Q", | |
| 1144 | + m.zAttachTarget, m.zAttachName, | |
| 1145 | + m.zAttachTarget, m.zAttachName | |
| 1146 | + ); | |
| 1147 | + if( strlen(m.zAttachTarget)!=UUID_SIZE | |
| 1148 | + || !validate16(m.zAttachTarget, UUID_SIZE) | |
| 1149 | + ){ | |
| 1150 | + char *zComment; | |
| 1151 | + if( m.zAttachSrc && m.zAttachSrc[0] ){ | |
| 1152 | + zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", | |
| 1153 | + m.zAttachName, m.zAttachTarget); | |
| 1154 | + }else{ | |
| 1155 | + zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", | |
| 1156 | + m.zAttachName, m.zAttachTarget); | |
| 1157 | + } | |
| 1158 | + db_multi_exec( | |
| 1159 | + "REPLACE INTO event(type,mtime,objid,user,comment)" | |
| 1160 | + "VALUES('w',%.17g,%d,%Q,%Q)", | |
| 1161 | + m.rDate, rid, m.zUser, zComment | |
| 1162 | + ); | |
| 1163 | + free(zComment); | |
| 1164 | + }else{ | |
| 1165 | + char *zComment; | |
| 1166 | + if( m.zAttachSrc && m.zAttachSrc[0] ){ | |
| 1167 | + zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", | |
| 1168 | + m.zAttachName, m.zAttachTarget); | |
| 1169 | + }else{ | |
| 1170 | + zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", | |
| 1171 | + m.zAttachName, m.zAttachTarget); | |
| 1172 | + } | |
| 1173 | + db_multi_exec( | |
| 1174 | + "REPLACE INTO event(type,mtime,objid,user,comment)" | |
| 1175 | + "VALUES('t',%.17g,%d,%Q,%Q)", | |
| 1176 | + m.rDate, rid, m.zUser, zComment | |
| 1177 | + ); | |
| 1178 | + free(zComment); | |
| 1179 | + } | |
| 1149 | 1180 | } |
| 1150 | 1181 | db_end_transaction(0); |
| 1151 | 1182 | manifest_clear(&m); |
| 1152 | 1183 | return 1; |
| 1153 | 1184 | } |
| 1154 | 1185 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -37,67 +37,56 @@ | |
| 37 | #define CFTYPE_MANIFEST 1 |
| 38 | #define CFTYPE_CLUSTER 2 |
| 39 | #define CFTYPE_CONTROL 3 |
| 40 | #define CFTYPE_WIKI 4 |
| 41 | #define CFTYPE_TICKET 5 |
| 42 | |
| 43 | /* |
| 44 | ** Mode parameter values |
| 45 | */ |
| 46 | #define CFMODE_READ 1 |
| 47 | #define CFMODE_APPEND 2 |
| 48 | #define CFMODE_WRITE 3 |
| 49 | |
| 50 | /* |
| 51 | ** A parsed manifest or cluster. |
| 52 | */ |
| 53 | struct Manifest { |
| 54 | Blob content; /* The original content blob */ |
| 55 | int type; /* Type of file */ |
| 56 | int mode; /* Access mode */ |
| 57 | char *zComment; /* Decoded comment */ |
| 58 | double rDate; /* Time in the "D" line */ |
| 59 | char *zUser; /* Name of the user */ |
| 60 | char *zRepoCksum; /* MD5 checksum of the baseline content */ |
| 61 | char *zWiki; /* Text of the wiki page */ |
| 62 | char *zWikiTitle; /* Name of the wiki page */ |
| 63 | char *zTicketUuid; /* UUID for a ticket */ |
| 64 | int nFile; /* Number of F lines */ |
| 65 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 66 | struct { |
| 67 | char *zName; /* Name of a file */ |
| 68 | char *zUuid; /* UUID of the file */ |
| 69 | char *zPerm; /* File permissions */ |
| 70 | char *zPrior; /* Prior name if the name was changed */ |
| 71 | int iRename; /* index of renamed name in prior/next manifest */ |
| 72 | } *aFile; |
| 73 | int nParent; /* Number of parents */ |
| 74 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 75 | char **azParent; /* UUIDs of parents */ |
| 76 | int nCChild; /* Number of cluster children */ |
| 77 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| 78 | char **azCChild; /* UUIDs of referenced objects in a cluster */ |
| 79 | int nTag; /* Number of T lines */ |
| 80 | int nTagAlloc; /* Slots allocated in aTag[] */ |
| 81 | struct { |
| 82 | char *zName; /* Name of the tag */ |
| 83 | char *zUuid; /* UUID that the tag is applied to */ |
| 84 | char *zValue; /* Value if the tag is really a property */ |
| 85 | } *aTag; |
| 86 | int nField; /* Number of J lines */ |
| 87 | int nFieldAlloc; /* Slots allocated in aField[] */ |
| 88 | struct { |
| 89 | char *zName; /* Key or field name */ |
| 90 | char *zValue; /* Value of the field */ |
| 91 | } *aField; |
| 92 | int nAttach; /* Number of A lines */ |
| 93 | int nAttachAlloc; /* Slots allocated in aAttach[] */ |
| 94 | struct { |
| 95 | char *zUuid; /* UUID of the attachment */ |
| 96 | char *zName; /* Name of the attachment */ |
| 97 | char *zDesc; /* Description of the attachment */ |
| 98 | } *aAttach; |
| 99 | }; |
| 100 | #endif |
| 101 | |
| 102 | |
| 103 | /* |
| @@ -108,11 +97,10 @@ | |
| 108 | free(p->aFile); |
| 109 | free(p->azParent); |
| 110 | free(p->azCChild); |
| 111 | free(p->aTag); |
| 112 | free(p->aField); |
| 113 | free(p->aAttach); |
| 114 | memset(p, 0, sizeof(*p)); |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | ** Parse a blob into a Manifest object. The Manifest object |
| @@ -178,44 +166,43 @@ | |
| 178 | cPrevType = z[0]; |
| 179 | seenHeader = 1; |
| 180 | if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; |
| 181 | switch( z[0] ){ |
| 182 | /* |
| 183 | ** A <uuid> <filename> <description> |
| 184 | ** |
| 185 | ** Identifies an attachment to either a wiki page or a ticket. |
| 186 | ** <uuid> is the artifact that is the attachment. |
| 187 | */ |
| 188 | case 'A': { |
| 189 | char *zName, *zUuid, *zDesc; |
| 190 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 191 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 192 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 193 | if( blob_token(&line, &a3)==0 ) goto manifest_syntax_error; |
| 194 | zUuid = blob_terminate(&a1); |
| 195 | zName = blob_terminate(&a2); |
| 196 | zDesc = blob_terminate(&a3); |
| 197 | if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 198 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 199 | defossilize(zName); |
| 200 | if( !file_is_simple_pathname(zName) ){ |
| 201 | goto manifest_syntax_error; |
| 202 | } |
| 203 | defossilize(zDesc); |
| 204 | if( p->nAttach>=p->nAttachAlloc ){ |
| 205 | p->nAttachAlloc = p->nAttachAlloc*2 + 10; |
| 206 | p->aAttach = realloc(p->aAttach, |
| 207 | p->nAttachAlloc*sizeof(p->aAttach[0]) ); |
| 208 | if( p->aAttach==0 ) fossil_panic("out of memory"); |
| 209 | } |
| 210 | i = p->nAttach++; |
| 211 | p->aAttach[i].zUuid = zUuid; |
| 212 | p->aAttach[i].zName = zName; |
| 213 | p->aAttach[i].zDesc = zDesc; |
| 214 | if( i>0 && strcmp(p->aAttach[i-1].zUuid, zUuid)>=0 ){ |
| 215 | goto manifest_syntax_error; |
| 216 | } |
| 217 | break; |
| 218 | } |
| 219 | |
| 220 | /* |
| 221 | ** C <comment> |
| @@ -247,33 +234,10 @@ | |
| 247 | if( p->rDate!=0.0 ) goto manifest_syntax_error; |
| 248 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 249 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 250 | zDate = blob_terminate(&a1); |
| 251 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); |
| 252 | break; |
| 253 | } |
| 254 | |
| 255 | /* |
| 256 | ** E <mode> |
| 257 | ** |
| 258 | ** Access mode. <mode> can be one of "read", "append", |
| 259 | ** or "write". |
| 260 | */ |
| 261 | case 'E': { |
| 262 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 263 | if( p->mode!=0 ) goto manifest_syntax_error; |
| 264 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 265 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 266 | if( blob_eq(&a1, "write") ){ |
| 267 | p->mode = CFMODE_WRITE; |
| 268 | }else if( blob_eq(&a1, "append") ){ |
| 269 | p->mode = CFMODE_APPEND; |
| 270 | }else if( blob_eq(&a1, "read") ){ |
| 271 | p->mode = CFMODE_READ; |
| 272 | }else{ |
| 273 | goto manifest_syntax_error; |
| 274 | } |
| 275 | break; |
| 276 | } |
| 277 | |
| 278 | /* |
| 279 | ** F <filename> <uuid> ?<permissions>? ?<old-name>? |
| @@ -608,72 +572,75 @@ | |
| 608 | if( p->nFile>0 || p->zRepoCksum!=0 ){ |
| 609 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 610 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 611 | if( p->nField>0 ) goto manifest_syntax_error; |
| 612 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 613 | if( p->nAttach>0 ) goto manifest_syntax_error; |
| 614 | if( p->zWiki ) goto manifest_syntax_error; |
| 615 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 616 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 617 | p->type = CFTYPE_MANIFEST; |
| 618 | }else if( p->nCChild>0 ){ |
| 619 | if( p->rDate>0.0 ) goto manifest_syntax_error; |
| 620 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 621 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 622 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 623 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 624 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 625 | if( p->nField>0 ) goto manifest_syntax_error; |
| 626 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 627 | if( p->nAttach>0 ) goto manifest_syntax_error; |
| 628 | if( p->zWiki ) goto manifest_syntax_error; |
| 629 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 630 | if( !seenZ ) goto manifest_syntax_error; |
| 631 | p->type = CFTYPE_CLUSTER; |
| 632 | }else if( p->nField>0 ){ |
| 633 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 634 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 635 | if( p->zWiki ) goto manifest_syntax_error; |
| 636 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 637 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 638 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 639 | if( p->zTicketUuid==0 ) goto manifest_syntax_error; |
| 640 | if( p->zUser==0 ) goto manifest_syntax_error; |
| 641 | if( !seenZ ) goto manifest_syntax_error; |
| 642 | p->type = CFTYPE_TICKET; |
| 643 | }else if( p->zWiki!=0 ){ |
| 644 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 645 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 646 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 647 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 648 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 649 | if( p->zWikiTitle==0 ) goto manifest_syntax_error; |
| 650 | if( !seenZ ) goto manifest_syntax_error; |
| 651 | p->type = CFTYPE_WIKI; |
| 652 | }else if( p->nTag>0 ){ |
| 653 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 654 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 655 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 656 | if( p->nAttach>0 ) goto manifest_syntax_error; |
| 657 | if( p->nField>0 ) goto manifest_syntax_error; |
| 658 | if( p->zWiki ) goto manifest_syntax_error; |
| 659 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 660 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 661 | if( !seenZ ) goto manifest_syntax_error; |
| 662 | p->type = CFTYPE_CONTROL; |
| 663 | }else{ |
| 664 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 665 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 666 | if( p->nField>0 ) goto manifest_syntax_error; |
| 667 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 668 | if( p->nAttach>0 ) goto manifest_syntax_error; |
| 669 | if( p->zWiki ) goto manifest_syntax_error; |
| 670 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 671 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 672 | p->type = CFTYPE_MANIFEST; |
| 673 | } |
| 674 | |
| 675 | md5sum_init(); |
| 676 | return 1; |
| 677 | |
| 678 | manifest_syntax_error: |
| 679 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| @@ -1032,10 +999,11 @@ | |
| 1032 | return 0; |
| 1033 | } |
| 1034 | db_begin_transaction(); |
| 1035 | if( m.type==CFTYPE_MANIFEST ){ |
| 1036 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1037 | for(i=0; i<m.nParent; i++){ |
| 1038 | int pid = uuid_to_rid(m.azParent[i], 1); |
| 1039 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1040 | "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); |
| 1041 | if( i==0 ){ |
| @@ -1065,10 +1033,14 @@ | |
| 1065 | rid, m.zUser, m.zComment, |
| 1066 | TAG_BGCOLOR, rid, |
| 1067 | TAG_USER, rid, |
| 1068 | TAG_COMMENT, rid |
| 1069 | ); |
| 1070 | } |
| 1071 | } |
| 1072 | if( m.type==CFTYPE_CLUSTER ){ |
| 1073 | tag_insert("cluster", 1, 0, rid, m.rDate, rid); |
| 1074 | for(i=0; i<m.nCChild; i++){ |
| @@ -1108,11 +1080,16 @@ | |
| 1108 | if( m.type==CFTYPE_WIKI ){ |
| 1109 | char *zTag = mprintf("wiki-%s", m.zWikiTitle); |
| 1110 | int tagid = tag_findid(zTag, 1); |
| 1111 | int prior; |
| 1112 | char *zComment; |
| 1113 | tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 1114 | free(zTag); |
| 1115 | prior = db_int(0, |
| 1116 | "SELECT rid FROM tagxref" |
| 1117 | " WHERE tagid=%d AND mtime<%.17g" |
| 1118 | " ORDER BY mtime DESC", |
| @@ -1119,11 +1096,15 @@ | |
| 1119 | tagid, m.rDate |
| 1120 | ); |
| 1121 | if( prior ){ |
| 1122 | content_deltify(prior, rid, 0); |
| 1123 | } |
| 1124 | zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); |
| 1125 | db_multi_exec( |
| 1126 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1127 | " bgcolor,euser,ecomment)" |
| 1128 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1129 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1144,10 +1125,60 @@ | |
| 1144 | zTag = mprintf("tkt-%s", m.zTicketUuid); |
| 1145 | tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 1146 | free(zTag); |
| 1147 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1148 | m.zTicketUuid); |
| 1149 | } |
| 1150 | db_end_transaction(0); |
| 1151 | manifest_clear(&m); |
| 1152 | return 1; |
| 1153 | } |
| 1154 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -37,67 +37,56 @@ | |
| 37 | #define CFTYPE_MANIFEST 1 |
| 38 | #define CFTYPE_CLUSTER 2 |
| 39 | #define CFTYPE_CONTROL 3 |
| 40 | #define CFTYPE_WIKI 4 |
| 41 | #define CFTYPE_TICKET 5 |
| 42 | #define CFTYPE_ATTACHMENT 6 |
| 43 | |
| 44 | /* |
| 45 | ** A parsed manifest or cluster. |
| 46 | */ |
| 47 | struct Manifest { |
| 48 | Blob content; /* The original content blob */ |
| 49 | int type; /* Type of artifact. One of CFTYPE_xxxxx */ |
| 50 | char *zComment; /* Decoded comment. The C card. */ |
| 51 | double rDate; /* Date and time from D card. 0.0 if no D card. */ |
| 52 | char *zUser; /* Name of the user from the U card. */ |
| 53 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 54 | char *zWiki; /* Text of the wiki page. W card. */ |
| 55 | char *zWikiTitle; /* Name of the wiki page. L card. */ |
| 56 | char *zTicketUuid; /* UUID for a ticket. K card. */ |
| 57 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 58 | char *zAttachSrc; /* UUID of document being attached. A card. */ |
| 59 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 60 | int nFile; /* Number of F cards */ |
| 61 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 62 | struct { |
| 63 | char *zName; /* Name of a file */ |
| 64 | char *zUuid; /* UUID of the file */ |
| 65 | char *zPerm; /* File permissions */ |
| 66 | char *zPrior; /* Prior name if the name was changed */ |
| 67 | int iRename; /* index of renamed name in prior/next manifest */ |
| 68 | } *aFile; /* One entry for each F card */ |
| 69 | int nParent; /* Number of parents. */ |
| 70 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 71 | char **azParent; /* UUIDs of parents. One for each P card argument */ |
| 72 | int nCChild; /* Number of cluster children */ |
| 73 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| 74 | char **azCChild; /* UUIDs of referenced objects in a cluster. M cards */ |
| 75 | int nTag; /* Number of T Cards */ |
| 76 | int nTagAlloc; /* Slots allocated in aTag[] */ |
| 77 | struct { |
| 78 | char *zName; /* Name of the tag */ |
| 79 | char *zUuid; /* UUID that the tag is applied to */ |
| 80 | char *zValue; /* Value if the tag is really a property */ |
| 81 | } *aTag; /* One for each T card */ |
| 82 | int nField; /* Number of J cards */ |
| 83 | int nFieldAlloc; /* Slots allocated in aField[] */ |
| 84 | struct { |
| 85 | char *zName; /* Key or field name */ |
| 86 | char *zValue; /* Value of the field */ |
| 87 | } *aField; /* One for each J card */ |
| 88 | }; |
| 89 | #endif |
| 90 | |
| 91 | |
| 92 | /* |
| @@ -108,11 +97,10 @@ | |
| 97 | free(p->aFile); |
| 98 | free(p->azParent); |
| 99 | free(p->azCChild); |
| 100 | free(p->aTag); |
| 101 | free(p->aField); |
| 102 | memset(p, 0, sizeof(*p)); |
| 103 | } |
| 104 | |
| 105 | /* |
| 106 | ** Parse a blob into a Manifest object. The Manifest object |
| @@ -178,44 +166,43 @@ | |
| 166 | cPrevType = z[0]; |
| 167 | seenHeader = 1; |
| 168 | if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; |
| 169 | switch( z[0] ){ |
| 170 | /* |
| 171 | ** A <filename> <target> ?<source>? |
| 172 | ** |
| 173 | ** Identifies an attachment to either a wiki page or a ticket. |
| 174 | ** <source> is the artifact that is the attachment. <source> |
| 175 | ** is omitted to delete an attachment. <target> is the name of |
| 176 | ** a wiki page or ticket to which that attachment is connected. |
| 177 | */ |
| 178 | case 'A': { |
| 179 | char *zName, *zTarget, *zSrc; |
| 180 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 181 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 182 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 183 | if( p->zAttachName!=0 ) goto manifest_syntax_error; |
| 184 | zName = blob_terminate(&a1); |
| 185 | zTarget = blob_terminate(&a2); |
| 186 | blob_token(&line, &a3); |
| 187 | zSrc = blob_terminate(&a3); |
| 188 | defossilize(zName); |
| 189 | if( !file_is_simple_pathname(zName) ){ |
| 190 | goto manifest_syntax_error; |
| 191 | } |
| 192 | defossilize(zTarget); |
| 193 | if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) |
| 194 | && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ |
| 195 | goto manifest_syntax_error; |
| 196 | } |
| 197 | if( blob_size(&a3)>0 |
| 198 | && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ |
| 199 | goto manifest_syntax_error; |
| 200 | } |
| 201 | p->zAttachName = (char*)file_tail(zName); |
| 202 | p->zAttachSrc = zSrc; |
| 203 | p->zAttachTarget = zTarget; |
| 204 | break; |
| 205 | } |
| 206 | |
| 207 | /* |
| 208 | ** C <comment> |
| @@ -247,33 +234,10 @@ | |
| 234 | if( p->rDate!=0.0 ) goto manifest_syntax_error; |
| 235 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 236 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 237 | zDate = blob_terminate(&a1); |
| 238 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); |
| 239 | break; |
| 240 | } |
| 241 | |
| 242 | /* |
| 243 | ** F <filename> <uuid> ?<permissions>? ?<old-name>? |
| @@ -608,72 +572,75 @@ | |
| 572 | if( p->nFile>0 || p->zRepoCksum!=0 ){ |
| 573 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 574 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 575 | if( p->nField>0 ) goto manifest_syntax_error; |
| 576 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 577 | if( p->zWiki ) goto manifest_syntax_error; |
| 578 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 579 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 580 | if( p->zAttachName ) goto manifest_syntax_error; |
| 581 | p->type = CFTYPE_MANIFEST; |
| 582 | }else if( p->nCChild>0 ){ |
| 583 | if( p->rDate>0.0 ) goto manifest_syntax_error; |
| 584 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 585 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 586 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 587 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 588 | if( p->nField>0 ) goto manifest_syntax_error; |
| 589 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 590 | if( p->zWiki ) goto manifest_syntax_error; |
| 591 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 592 | if( p->zAttachName ) goto manifest_syntax_error; |
| 593 | if( !seenZ ) goto manifest_syntax_error; |
| 594 | p->type = CFTYPE_CLUSTER; |
| 595 | }else if( p->nField>0 ){ |
| 596 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 597 | if( p->zWiki ) goto manifest_syntax_error; |
| 598 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 599 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 600 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 601 | if( p->zTicketUuid==0 ) goto manifest_syntax_error; |
| 602 | if( p->zUser==0 ) goto manifest_syntax_error; |
| 603 | if( p->zAttachName ) goto manifest_syntax_error; |
| 604 | if( !seenZ ) goto manifest_syntax_error; |
| 605 | p->type = CFTYPE_TICKET; |
| 606 | }else if( p->zWiki!=0 ){ |
| 607 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 608 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 609 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 610 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 611 | if( p->zWikiTitle==0 ) goto manifest_syntax_error; |
| 612 | if( p->zAttachName ) goto manifest_syntax_error; |
| 613 | if( !seenZ ) goto manifest_syntax_error; |
| 614 | p->type = CFTYPE_WIKI; |
| 615 | }else if( p->nTag>0 ){ |
| 616 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 617 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 618 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 619 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 620 | if( p->zAttachName ) goto manifest_syntax_error; |
| 621 | if( !seenZ ) goto manifest_syntax_error; |
| 622 | p->type = CFTYPE_CONTROL; |
| 623 | }else if( p->zAttachName ){ |
| 624 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 625 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 626 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 627 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 628 | if( !seenZ ) goto manifest_syntax_error; |
| 629 | p->type = CFTYPE_ATTACHMENT; |
| 630 | }else{ |
| 631 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 632 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 633 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 634 | if( p->nField>0 ) goto manifest_syntax_error; |
| 635 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 636 | if( p->zWiki ) goto manifest_syntax_error; |
| 637 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 638 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 639 | if( p->zAttachName ) goto manifest_syntax_error; |
| 640 | p->type = CFTYPE_MANIFEST; |
| 641 | } |
| 642 | md5sum_init(); |
| 643 | return 1; |
| 644 | |
| 645 | manifest_syntax_error: |
| 646 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| @@ -1032,10 +999,11 @@ | |
| 999 | return 0; |
| 1000 | } |
| 1001 | db_begin_transaction(); |
| 1002 | if( m.type==CFTYPE_MANIFEST ){ |
| 1003 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1004 | char *zCom; |
| 1005 | for(i=0; i<m.nParent; i++){ |
| 1006 | int pid = uuid_to_rid(m.azParent[i], 1); |
| 1007 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1008 | "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); |
| 1009 | if( i==0 ){ |
| @@ -1065,10 +1033,14 @@ | |
| 1033 | rid, m.zUser, m.zComment, |
| 1034 | TAG_BGCOLOR, rid, |
| 1035 | TAG_USER, rid, |
| 1036 | TAG_COMMENT, rid |
| 1037 | ); |
| 1038 | zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" |
| 1039 | " WHERE rowid=last_insert_rowid()"); |
| 1040 | wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); |
| 1041 | free(zCom); |
| 1042 | } |
| 1043 | } |
| 1044 | if( m.type==CFTYPE_CLUSTER ){ |
| 1045 | tag_insert("cluster", 1, 0, rid, m.rDate, rid); |
| 1046 | for(i=0; i<m.nCChild; i++){ |
| @@ -1108,11 +1080,16 @@ | |
| 1080 | if( m.type==CFTYPE_WIKI ){ |
| 1081 | char *zTag = mprintf("wiki-%s", m.zWikiTitle); |
| 1082 | int tagid = tag_findid(zTag, 1); |
| 1083 | int prior; |
| 1084 | char *zComment; |
| 1085 | int nWiki; |
| 1086 | char zLength[40]; |
| 1087 | while( isspace(m.zWiki[0]) ) m.zWiki++; |
| 1088 | nWiki = strlen(m.zWiki); |
| 1089 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1090 | tag_insert(zTag, 1, zLength, rid, m.rDate, rid); |
| 1091 | free(zTag); |
| 1092 | prior = db_int(0, |
| 1093 | "SELECT rid FROM tagxref" |
| 1094 | " WHERE tagid=%d AND mtime<%.17g" |
| 1095 | " ORDER BY mtime DESC", |
| @@ -1119,11 +1096,15 @@ | |
| 1096 | tagid, m.rDate |
| 1097 | ); |
| 1098 | if( prior ){ |
| 1099 | content_deltify(prior, rid, 0); |
| 1100 | } |
| 1101 | if( nWiki>0 ){ |
| 1102 | zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); |
| 1103 | }else{ |
| 1104 | zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); |
| 1105 | } |
| 1106 | db_multi_exec( |
| 1107 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1108 | " bgcolor,euser,ecomment)" |
| 1109 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1110 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| @@ -1144,10 +1125,60 @@ | |
| 1125 | zTag = mprintf("tkt-%s", m.zTicketUuid); |
| 1126 | tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 1127 | free(zTag); |
| 1128 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1129 | m.zTicketUuid); |
| 1130 | } |
| 1131 | if( m.type==CFTYPE_ATTACHMENT ){ |
| 1132 | db_multi_exec( |
| 1133 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 1134 | "filename, comment, user)" |
| 1135 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 1136 | rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, |
| 1137 | (m.zComment ? m.zComment : ""), m.zUser |
| 1138 | ); |
| 1139 | db_multi_exec( |
| 1140 | "UPDATE attachment SET isLatest = (mtime==" |
| 1141 | "(SELECT max(mtime) FROM attachment" |
| 1142 | " WHERE target=%Q AND filename=%Q))" |
| 1143 | " WHERE target=%Q AND filename=%Q", |
| 1144 | m.zAttachTarget, m.zAttachName, |
| 1145 | m.zAttachTarget, m.zAttachName |
| 1146 | ); |
| 1147 | if( strlen(m.zAttachTarget)!=UUID_SIZE |
| 1148 | || !validate16(m.zAttachTarget, UUID_SIZE) |
| 1149 | ){ |
| 1150 | char *zComment; |
| 1151 | if( m.zAttachSrc && m.zAttachSrc[0] ){ |
| 1152 | zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", |
| 1153 | m.zAttachName, m.zAttachTarget); |
| 1154 | }else{ |
| 1155 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 1156 | m.zAttachName, m.zAttachTarget); |
| 1157 | } |
| 1158 | db_multi_exec( |
| 1159 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1160 | "VALUES('w',%.17g,%d,%Q,%Q)", |
| 1161 | m.rDate, rid, m.zUser, zComment |
| 1162 | ); |
| 1163 | free(zComment); |
| 1164 | }else{ |
| 1165 | char *zComment; |
| 1166 | if( m.zAttachSrc && m.zAttachSrc[0] ){ |
| 1167 | zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", |
| 1168 | m.zAttachName, m.zAttachTarget); |
| 1169 | }else{ |
| 1170 | zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", |
| 1171 | m.zAttachName, m.zAttachTarget); |
| 1172 | } |
| 1173 | db_multi_exec( |
| 1174 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1175 | "VALUES('t',%.17g,%d,%Q,%Q)", |
| 1176 | m.rDate, rid, m.zUser, zComment |
| 1177 | ); |
| 1178 | free(zComment); |
| 1179 | } |
| 1180 | } |
| 1181 | db_end_transaction(0); |
| 1182 | manifest_clear(&m); |
| 1183 | return 1; |
| 1184 | } |
| 1185 |
+36
-9
| --- src/merge.c | ||
| +++ src/merge.c | ||
| @@ -30,36 +30,51 @@ | ||
| 30 | 30 | |
| 31 | 31 | |
| 32 | 32 | /* |
| 33 | 33 | ** COMMAND: merge |
| 34 | 34 | ** |
| 35 | -** Usage: %fossil merge [--cherrypick] VERSION | |
| 35 | +** Usage: %fossil merge [--cherrypick] [--backout] VERSION | |
| 36 | 36 | ** |
| 37 | 37 | ** The argument is a version that should be merged into the current |
| 38 | 38 | ** checkout. All changes from VERSION back to the nearest common |
| 39 | -** ancestor are merged. Except, if the --cherrypick option is used | |
| 40 | -** only the changes associated with the single check-in VERSION are | |
| 41 | -** merged. | |
| 39 | +** ancestor are merged. Except, if either of the --cherrypick or | |
| 40 | +** --backout options are used only the changes associated with the | |
| 41 | +** single check-in VERSION are merged. The --backout option causes | |
| 42 | +** the changes associated with VERSION to be removed from the current | |
| 43 | +** checkout rather than added. | |
| 42 | 44 | ** |
| 43 | 45 | ** Only file content is merged. The result continues to use the |
| 44 | -** file and directory names from the current check-out even if those | |
| 46 | +** file and directory names from the current checkout even if those | |
| 45 | 47 | ** names might have been changed in the branch being merged in. |
| 48 | +** | |
| 49 | +** Other options: | |
| 50 | +** | |
| 51 | +** --detail Show additional details of the merge | |
| 52 | +** | |
| 53 | +** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary | |
| 54 | +** and do not try to merge parallel changes. This | |
| 55 | +** option overrides the "binary-glob" setting. | |
| 46 | 56 | */ |
| 47 | 57 | void merge_cmd(void){ |
| 48 | 58 | int vid; /* Current version */ |
| 49 | 59 | int mid; /* Version we are merging against */ |
| 50 | 60 | int pid; /* The pivot version - most recent common ancestor */ |
| 51 | 61 | int detailFlag; /* True if the --detail option is present */ |
| 52 | 62 | int pickFlag; /* True if the --cherrypick option is present */ |
| 63 | + int backoutFlag; /* True if the --backout optioni is present */ | |
| 64 | + const char *zBinGlob; /* The value of --binary */ | |
| 53 | 65 | Stmt q; |
| 54 | 66 | |
| 55 | 67 | detailFlag = find_option("detail",0,0)!=0; |
| 56 | 68 | pickFlag = find_option("cherrypick",0,0)!=0; |
| 69 | + backoutFlag = find_option("backout",0,0)!=0; | |
| 70 | + zBinGlob = find_option("binary",0,1); | |
| 57 | 71 | if( g.argc!=3 ){ |
| 58 | 72 | usage("VERSION"); |
| 59 | 73 | } |
| 60 | 74 | db_must_be_within_tree(); |
| 75 | + if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); | |
| 61 | 76 | vid = db_lget_int("checkout", 0); |
| 62 | 77 | if( vid==0 ){ |
| 63 | 78 | fossil_fatal("nothing is checked out"); |
| 64 | 79 | } |
| 65 | 80 | mid = name_to_rid(g.argv[2]); |
| @@ -67,14 +82,19 @@ | ||
| 67 | 82 | fossil_fatal("not a version: %s", g.argv[2]); |
| 68 | 83 | } |
| 69 | 84 | if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){ |
| 70 | 85 | fossil_fatal("not a version: %s", g.argv[2]); |
| 71 | 86 | } |
| 72 | - if( pickFlag ){ | |
| 87 | + if( pickFlag || backoutFlag ){ | |
| 73 | 88 | pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); |
| 74 | 89 | if( pid<=0 ){ |
| 75 | 90 | fossil_fatal("cannot find an ancestor for %s", g.argv[2]); |
| 91 | + } | |
| 92 | + if( backoutFlag ){ | |
| 93 | + int t = pid; | |
| 94 | + pid = mid; | |
| 95 | + mid = t; | |
| 76 | 96 | } |
| 77 | 97 | }else{ |
| 78 | 98 | pivot_set_primary(mid); |
| 79 | 99 | pivot_set_secondary(vid); |
| 80 | 100 | db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); |
| @@ -229,19 +249,21 @@ | ||
| 229 | 249 | |
| 230 | 250 | /* |
| 231 | 251 | ** Do a three-way merge on files that have changes pid->mid and pid->vid |
| 232 | 252 | */ |
| 233 | 253 | db_prepare(&q, |
| 234 | - "SELECT ridm, idv, ridp, ridv FROM fv" | |
| 254 | + "SELECT ridm, idv, ridp, ridv, %s FROM fv" | |
| 235 | 255 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 236 | - " AND ridm!=ridp AND (ridv!=ridp OR chnged)" | |
| 256 | + " AND ridm!=ridp AND (ridv!=ridp OR chnged)", | |
| 257 | + glob_expr("fv.fn", zBinGlob) | |
| 237 | 258 | ); |
| 238 | 259 | while( db_step(&q)==SQLITE_ROW ){ |
| 239 | 260 | int ridm = db_column_int(&q, 0); |
| 240 | 261 | int idv = db_column_int(&q, 1); |
| 241 | 262 | int ridp = db_column_int(&q, 2); |
| 242 | 263 | int ridv = db_column_int(&q, 3); |
| 264 | + int isBinary = db_column_int(&q, 4); | |
| 243 | 265 | int rc; |
| 244 | 266 | char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 245 | 267 | char *zFullPath; |
| 246 | 268 | Blob m, p, v, r; |
| 247 | 269 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| @@ -254,11 +276,16 @@ | ||
| 254 | 276 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 255 | 277 | content_get(ridp, &p); |
| 256 | 278 | content_get(ridm, &m); |
| 257 | 279 | blob_zero(&v); |
| 258 | 280 | blob_read_from_file(&v, zFullPath); |
| 259 | - rc = blob_merge(&p, &m, &v, &r); | |
| 281 | + if( isBinary ){ | |
| 282 | + rc = -1; | |
| 283 | + blob_zero(&r); | |
| 284 | + }else{ | |
| 285 | + rc = blob_merge(&p, &m, &v, &r); | |
| 286 | + } | |
| 260 | 287 | if( rc>=0 ){ |
| 261 | 288 | blob_write_to_file(&r, zFullPath); |
| 262 | 289 | if( rc>0 ){ |
| 263 | 290 | printf("***** %d merge conflicts in %s\n", rc, zName); |
| 264 | 291 | } |
| 265 | 292 |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -30,36 +30,51 @@ | |
| 30 | |
| 31 | |
| 32 | /* |
| 33 | ** COMMAND: merge |
| 34 | ** |
| 35 | ** Usage: %fossil merge [--cherrypick] VERSION |
| 36 | ** |
| 37 | ** The argument is a version that should be merged into the current |
| 38 | ** checkout. All changes from VERSION back to the nearest common |
| 39 | ** ancestor are merged. Except, if the --cherrypick option is used |
| 40 | ** only the changes associated with the single check-in VERSION are |
| 41 | ** merged. |
| 42 | ** |
| 43 | ** Only file content is merged. The result continues to use the |
| 44 | ** file and directory names from the current check-out even if those |
| 45 | ** names might have been changed in the branch being merged in. |
| 46 | */ |
| 47 | void merge_cmd(void){ |
| 48 | int vid; /* Current version */ |
| 49 | int mid; /* Version we are merging against */ |
| 50 | int pid; /* The pivot version - most recent common ancestor */ |
| 51 | int detailFlag; /* True if the --detail option is present */ |
| 52 | int pickFlag; /* True if the --cherrypick option is present */ |
| 53 | Stmt q; |
| 54 | |
| 55 | detailFlag = find_option("detail",0,0)!=0; |
| 56 | pickFlag = find_option("cherrypick",0,0)!=0; |
| 57 | if( g.argc!=3 ){ |
| 58 | usage("VERSION"); |
| 59 | } |
| 60 | db_must_be_within_tree(); |
| 61 | vid = db_lget_int("checkout", 0); |
| 62 | if( vid==0 ){ |
| 63 | fossil_fatal("nothing is checked out"); |
| 64 | } |
| 65 | mid = name_to_rid(g.argv[2]); |
| @@ -67,14 +82,19 @@ | |
| 67 | fossil_fatal("not a version: %s", g.argv[2]); |
| 68 | } |
| 69 | if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){ |
| 70 | fossil_fatal("not a version: %s", g.argv[2]); |
| 71 | } |
| 72 | if( pickFlag ){ |
| 73 | pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); |
| 74 | if( pid<=0 ){ |
| 75 | fossil_fatal("cannot find an ancestor for %s", g.argv[2]); |
| 76 | } |
| 77 | }else{ |
| 78 | pivot_set_primary(mid); |
| 79 | pivot_set_secondary(vid); |
| 80 | db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); |
| @@ -229,19 +249,21 @@ | |
| 229 | |
| 230 | /* |
| 231 | ** Do a three-way merge on files that have changes pid->mid and pid->vid |
| 232 | */ |
| 233 | db_prepare(&q, |
| 234 | "SELECT ridm, idv, ridp, ridv FROM fv" |
| 235 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 236 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)" |
| 237 | ); |
| 238 | while( db_step(&q)==SQLITE_ROW ){ |
| 239 | int ridm = db_column_int(&q, 0); |
| 240 | int idv = db_column_int(&q, 1); |
| 241 | int ridp = db_column_int(&q, 2); |
| 242 | int ridv = db_column_int(&q, 3); |
| 243 | int rc; |
| 244 | char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 245 | char *zFullPath; |
| 246 | Blob m, p, v, r; |
| 247 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| @@ -254,11 +276,16 @@ | |
| 254 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 255 | content_get(ridp, &p); |
| 256 | content_get(ridm, &m); |
| 257 | blob_zero(&v); |
| 258 | blob_read_from_file(&v, zFullPath); |
| 259 | rc = blob_merge(&p, &m, &v, &r); |
| 260 | if( rc>=0 ){ |
| 261 | blob_write_to_file(&r, zFullPath); |
| 262 | if( rc>0 ){ |
| 263 | printf("***** %d merge conflicts in %s\n", rc, zName); |
| 264 | } |
| 265 |
| --- src/merge.c | |
| +++ src/merge.c | |
| @@ -30,36 +30,51 @@ | |
| 30 | |
| 31 | |
| 32 | /* |
| 33 | ** COMMAND: merge |
| 34 | ** |
| 35 | ** Usage: %fossil merge [--cherrypick] [--backout] VERSION |
| 36 | ** |
| 37 | ** The argument is a version that should be merged into the current |
| 38 | ** checkout. All changes from VERSION back to the nearest common |
| 39 | ** ancestor are merged. Except, if either of the --cherrypick or |
| 40 | ** --backout options are used only the changes associated with the |
| 41 | ** single check-in VERSION are merged. The --backout option causes |
| 42 | ** the changes associated with VERSION to be removed from the current |
| 43 | ** checkout rather than added. |
| 44 | ** |
| 45 | ** Only file content is merged. The result continues to use the |
| 46 | ** file and directory names from the current checkout even if those |
| 47 | ** names might have been changed in the branch being merged in. |
| 48 | ** |
| 49 | ** Other options: |
| 50 | ** |
| 51 | ** --detail Show additional details of the merge |
| 52 | ** |
| 53 | ** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary |
| 54 | ** and do not try to merge parallel changes. This |
| 55 | ** option overrides the "binary-glob" setting. |
| 56 | */ |
| 57 | void merge_cmd(void){ |
| 58 | int vid; /* Current version */ |
| 59 | int mid; /* Version we are merging against */ |
| 60 | int pid; /* The pivot version - most recent common ancestor */ |
| 61 | int detailFlag; /* True if the --detail option is present */ |
| 62 | int pickFlag; /* True if the --cherrypick option is present */ |
| 63 | int backoutFlag; /* True if the --backout optioni is present */ |
| 64 | const char *zBinGlob; /* The value of --binary */ |
| 65 | Stmt q; |
| 66 | |
| 67 | detailFlag = find_option("detail",0,0)!=0; |
| 68 | pickFlag = find_option("cherrypick",0,0)!=0; |
| 69 | backoutFlag = find_option("backout",0,0)!=0; |
| 70 | zBinGlob = find_option("binary",0,1); |
| 71 | if( g.argc!=3 ){ |
| 72 | usage("VERSION"); |
| 73 | } |
| 74 | db_must_be_within_tree(); |
| 75 | if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
| 76 | vid = db_lget_int("checkout", 0); |
| 77 | if( vid==0 ){ |
| 78 | fossil_fatal("nothing is checked out"); |
| 79 | } |
| 80 | mid = name_to_rid(g.argv[2]); |
| @@ -67,14 +82,19 @@ | |
| 82 | fossil_fatal("not a version: %s", g.argv[2]); |
| 83 | } |
| 84 | if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){ |
| 85 | fossil_fatal("not a version: %s", g.argv[2]); |
| 86 | } |
| 87 | if( pickFlag || backoutFlag ){ |
| 88 | pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); |
| 89 | if( pid<=0 ){ |
| 90 | fossil_fatal("cannot find an ancestor for %s", g.argv[2]); |
| 91 | } |
| 92 | if( backoutFlag ){ |
| 93 | int t = pid; |
| 94 | pid = mid; |
| 95 | mid = t; |
| 96 | } |
| 97 | }else{ |
| 98 | pivot_set_primary(mid); |
| 99 | pivot_set_secondary(vid); |
| 100 | db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); |
| @@ -229,19 +249,21 @@ | |
| 249 | |
| 250 | /* |
| 251 | ** Do a three-way merge on files that have changes pid->mid and pid->vid |
| 252 | */ |
| 253 | db_prepare(&q, |
| 254 | "SELECT ridm, idv, ridp, ridv, %s FROM fv" |
| 255 | " WHERE idp>0 AND idv>0 AND idm>0" |
| 256 | " AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
| 257 | glob_expr("fv.fn", zBinGlob) |
| 258 | ); |
| 259 | while( db_step(&q)==SQLITE_ROW ){ |
| 260 | int ridm = db_column_int(&q, 0); |
| 261 | int idv = db_column_int(&q, 1); |
| 262 | int ridp = db_column_int(&q, 2); |
| 263 | int ridv = db_column_int(&q, 3); |
| 264 | int isBinary = db_column_int(&q, 4); |
| 265 | int rc; |
| 266 | char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); |
| 267 | char *zFullPath; |
| 268 | Blob m, p, v, r; |
| 269 | /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
| @@ -254,11 +276,16 @@ | |
| 276 | zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 277 | content_get(ridp, &p); |
| 278 | content_get(ridm, &m); |
| 279 | blob_zero(&v); |
| 280 | blob_read_from_file(&v, zFullPath); |
| 281 | if( isBinary ){ |
| 282 | rc = -1; |
| 283 | blob_zero(&r); |
| 284 | }else{ |
| 285 | rc = blob_merge(&p, &m, &v, &r); |
| 286 | } |
| 287 | if( rc>=0 ){ |
| 288 | blob_write_to_file(&r, zFullPath); |
| 289 | if( rc>0 ){ |
| 290 | printf("***** %d merge conflicts in %s\n", rc, zName); |
| 291 | } |
| 292 |
+65
-1
| --- src/name.c | ||
| +++ src/name.c | ||
| @@ -111,16 +111,42 @@ | ||
| 111 | 111 | }else{ |
| 112 | 112 | rc = 0; |
| 113 | 113 | } |
| 114 | 114 | return rc; |
| 115 | 115 | } |
| 116 | + | |
| 117 | +/* | |
| 118 | +** Return TRUE if the string begins with an ISO8601 date: YYYY-MM-DD. | |
| 119 | +*/ | |
| 120 | +static int is_date(const char *z){ | |
| 121 | + if( !isdigit(z[0]) ) return 0; | |
| 122 | + if( !isdigit(z[1]) ) return 0; | |
| 123 | + if( !isdigit(z[2]) ) return 0; | |
| 124 | + if( !isdigit(z[3]) ) return 0; | |
| 125 | + if( z[4]!='-') return 0; | |
| 126 | + if( !isdigit(z[5]) ) return 0; | |
| 127 | + if( !isdigit(z[6]) ) return 0; | |
| 128 | + if( z[7]!='-') return 0; | |
| 129 | + if( !isdigit(z[8]) ) return 0; | |
| 130 | + if( !isdigit(z[9]) ) return 0; | |
| 131 | + return 1; | |
| 132 | +} | |
| 116 | 133 | |
| 117 | 134 | /* |
| 118 | 135 | ** Convert a symbolic tag name into the UUID of a check-in that contains |
| 119 | 136 | ** that tag. If the tag appears on multiple check-ins, return the UUID |
| 120 | 137 | ** of the most recent check-in with the tag. |
| 121 | 138 | ** |
| 139 | +** If the input string is of the form: | |
| 140 | +** | |
| 141 | +** tag:date | |
| 142 | +** | |
| 143 | +** Then return the UUID of the oldest check-in with that tag that is | |
| 144 | +** not older than 'date'. | |
| 145 | +** | |
| 146 | +** An input of "tip" returns the most recent check-in. | |
| 147 | +** | |
| 122 | 148 | ** Memory to hold the returned string comes from malloc() and needs to |
| 123 | 149 | ** be freed by the caller. |
| 124 | 150 | */ |
| 125 | 151 | char *tag_to_uuid(const char *zTag){ |
| 126 | 152 | char *zUuid = |
| @@ -132,10 +158,48 @@ | ||
| 132 | 158 | " AND event.objid=tagxref.rid " |
| 133 | 159 | " AND blob.rid=event.objid " |
| 134 | 160 | " ORDER BY event.mtime DESC ", |
| 135 | 161 | zTag |
| 136 | 162 | ); |
| 163 | + if( zUuid==0 ){ | |
| 164 | + int nTag = strlen(zTag); | |
| 165 | + int i; | |
| 166 | + for(i=0; i<nTag-10; i++){ | |
| 167 | + if( zTag[i]==':' && is_date(&zTag[i+1]) ){ | |
| 168 | + char *zDate = mprintf("%s", &zTag[i+1]); | |
| 169 | + char *zTagBase = mprintf("%.*s", i, zTag); | |
| 170 | + int nDate = strlen(zDate); | |
| 171 | + int useUtc = 0; | |
| 172 | + if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){ | |
| 173 | + nDate -= 3; | |
| 174 | + zDate[nDate] = 0; | |
| 175 | + useUtc = 1; | |
| 176 | + } | |
| 177 | + zUuid = db_text(0, | |
| 178 | + "SELECT blob.uuid" | |
| 179 | + " FROM tag, tagxref, event, blob" | |
| 180 | + " WHERE tag.tagname='sym-'||%Q " | |
| 181 | + " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " | |
| 182 | + " AND event.objid=tagxref.rid " | |
| 183 | + " AND blob.rid=event.objid " | |
| 184 | + " AND event.mtime<=julianday(%Q %s)" | |
| 185 | + " ORDER BY event.mtime DESC ", | |
| 186 | + zTagBase, zDate, (useUtc ? "" : ",'utc'") | |
| 187 | + ); | |
| 188 | + break; | |
| 189 | + } | |
| 190 | + } | |
| 191 | + if( zUuid==0 && strcmp(zTag, "tip")==0 ){ | |
| 192 | + zUuid = db_text(0, | |
| 193 | + "SELECT blob.uuid" | |
| 194 | + " FROM event, blob" | |
| 195 | + " WHERE event.type='ci'" | |
| 196 | + " AND blob.rid=event.objid" | |
| 197 | + " ORDER BY event.mtime DESC" | |
| 198 | + ); | |
| 199 | + } | |
| 200 | + } | |
| 137 | 201 | return zUuid; |
| 138 | 202 | } |
| 139 | 203 | |
| 140 | 204 | /* |
| 141 | 205 | ** Convert a date/time string into a UUID. |
| @@ -162,11 +226,11 @@ | ||
| 162 | 226 | }else if( memcmp(zDate, "utc:", 4)==0 ){ |
| 163 | 227 | zDate += 4; |
| 164 | 228 | useUtc = 1; |
| 165 | 229 | } |
| 166 | 230 | n = strlen(zDate); |
| 167 | - if( n<10 || zDate[4]!='-' || zDate[7]!='-' ) return 0; | |
| 231 | + if( n<10 || !is_date(zDate) ) return 0; | |
| 168 | 232 | if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){ |
| 169 | 233 | zCopy = mprintf("%s", zDate); |
| 170 | 234 | zCopy[n-3] = 0; |
| 171 | 235 | zDate = zCopy; |
| 172 | 236 | n -= 3; |
| 173 | 237 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -111,16 +111,42 @@ | |
| 111 | }else{ |
| 112 | rc = 0; |
| 113 | } |
| 114 | return rc; |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | ** Convert a symbolic tag name into the UUID of a check-in that contains |
| 119 | ** that tag. If the tag appears on multiple check-ins, return the UUID |
| 120 | ** of the most recent check-in with the tag. |
| 121 | ** |
| 122 | ** Memory to hold the returned string comes from malloc() and needs to |
| 123 | ** be freed by the caller. |
| 124 | */ |
| 125 | char *tag_to_uuid(const char *zTag){ |
| 126 | char *zUuid = |
| @@ -132,10 +158,48 @@ | |
| 132 | " AND event.objid=tagxref.rid " |
| 133 | " AND blob.rid=event.objid " |
| 134 | " ORDER BY event.mtime DESC ", |
| 135 | zTag |
| 136 | ); |
| 137 | return zUuid; |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | ** Convert a date/time string into a UUID. |
| @@ -162,11 +226,11 @@ | |
| 162 | }else if( memcmp(zDate, "utc:", 4)==0 ){ |
| 163 | zDate += 4; |
| 164 | useUtc = 1; |
| 165 | } |
| 166 | n = strlen(zDate); |
| 167 | if( n<10 || zDate[4]!='-' || zDate[7]!='-' ) return 0; |
| 168 | if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){ |
| 169 | zCopy = mprintf("%s", zDate); |
| 170 | zCopy[n-3] = 0; |
| 171 | zDate = zCopy; |
| 172 | n -= 3; |
| 173 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -111,16 +111,42 @@ | |
| 111 | }else{ |
| 112 | rc = 0; |
| 113 | } |
| 114 | return rc; |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | ** Return TRUE if the string begins with an ISO8601 date: YYYY-MM-DD. |
| 119 | */ |
| 120 | static int is_date(const char *z){ |
| 121 | if( !isdigit(z[0]) ) return 0; |
| 122 | if( !isdigit(z[1]) ) return 0; |
| 123 | if( !isdigit(z[2]) ) return 0; |
| 124 | if( !isdigit(z[3]) ) return 0; |
| 125 | if( z[4]!='-') return 0; |
| 126 | if( !isdigit(z[5]) ) return 0; |
| 127 | if( !isdigit(z[6]) ) return 0; |
| 128 | if( z[7]!='-') return 0; |
| 129 | if( !isdigit(z[8]) ) return 0; |
| 130 | if( !isdigit(z[9]) ) return 0; |
| 131 | return 1; |
| 132 | } |
| 133 | |
| 134 | /* |
| 135 | ** Convert a symbolic tag name into the UUID of a check-in that contains |
| 136 | ** that tag. If the tag appears on multiple check-ins, return the UUID |
| 137 | ** of the most recent check-in with the tag. |
| 138 | ** |
| 139 | ** If the input string is of the form: |
| 140 | ** |
| 141 | ** tag:date |
| 142 | ** |
| 143 | ** Then return the UUID of the oldest check-in with that tag that is |
| 144 | ** not older than 'date'. |
| 145 | ** |
| 146 | ** An input of "tip" returns the most recent check-in. |
| 147 | ** |
| 148 | ** Memory to hold the returned string comes from malloc() and needs to |
| 149 | ** be freed by the caller. |
| 150 | */ |
| 151 | char *tag_to_uuid(const char *zTag){ |
| 152 | char *zUuid = |
| @@ -132,10 +158,48 @@ | |
| 158 | " AND event.objid=tagxref.rid " |
| 159 | " AND blob.rid=event.objid " |
| 160 | " ORDER BY event.mtime DESC ", |
| 161 | zTag |
| 162 | ); |
| 163 | if( zUuid==0 ){ |
| 164 | int nTag = strlen(zTag); |
| 165 | int i; |
| 166 | for(i=0; i<nTag-10; i++){ |
| 167 | if( zTag[i]==':' && is_date(&zTag[i+1]) ){ |
| 168 | char *zDate = mprintf("%s", &zTag[i+1]); |
| 169 | char *zTagBase = mprintf("%.*s", i, zTag); |
| 170 | int nDate = strlen(zDate); |
| 171 | int useUtc = 0; |
| 172 | if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){ |
| 173 | nDate -= 3; |
| 174 | zDate[nDate] = 0; |
| 175 | useUtc = 1; |
| 176 | } |
| 177 | zUuid = db_text(0, |
| 178 | "SELECT blob.uuid" |
| 179 | " FROM tag, tagxref, event, blob" |
| 180 | " WHERE tag.tagname='sym-'||%Q " |
| 181 | " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " |
| 182 | " AND event.objid=tagxref.rid " |
| 183 | " AND blob.rid=event.objid " |
| 184 | " AND event.mtime<=julianday(%Q %s)" |
| 185 | " ORDER BY event.mtime DESC ", |
| 186 | zTagBase, zDate, (useUtc ? "" : ",'utc'") |
| 187 | ); |
| 188 | break; |
| 189 | } |
| 190 | } |
| 191 | if( zUuid==0 && strcmp(zTag, "tip")==0 ){ |
| 192 | zUuid = db_text(0, |
| 193 | "SELECT blob.uuid" |
| 194 | " FROM event, blob" |
| 195 | " WHERE event.type='ci'" |
| 196 | " AND blob.rid=event.objid" |
| 197 | " ORDER BY event.mtime DESC" |
| 198 | ); |
| 199 | } |
| 200 | } |
| 201 | return zUuid; |
| 202 | } |
| 203 | |
| 204 | /* |
| 205 | ** Convert a date/time string into a UUID. |
| @@ -162,11 +226,11 @@ | |
| 226 | }else if( memcmp(zDate, "utc:", 4)==0 ){ |
| 227 | zDate += 4; |
| 228 | useUtc = 1; |
| 229 | } |
| 230 | n = strlen(zDate); |
| 231 | if( n<10 || !is_date(zDate) ) return 0; |
| 232 | if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){ |
| 233 | zCopy = mprintf("%s", zDate); |
| 234 | zCopy[n-3] = 0; |
| 235 | zDate = zCopy; |
| 236 | n -= 3; |
| 237 |
+31
| --- src/schema.c | ||
| +++ src/schema.c | ||
| @@ -309,10 +309,40 @@ | ||
| 309 | 309 | @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to |
| 310 | 310 | @ UNIQUE(rid, tagid) |
| 311 | 311 | @ ); |
| 312 | 312 | @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); |
| 313 | 313 | @ |
| 314 | +@ -- When a hyperlink occurs from one artifact to another (for example | |
| 315 | +@ -- when a check-in comment refers to a ticket) an entry is made in | |
| 316 | +@ -- the following table for that hyperlink. This table is used to | |
| 317 | +@ -- facilitate the display of "back links". | |
| 318 | +@ -- | |
| 319 | +@ CREATE TABLE backlink( | |
| 320 | +@ target TEXT, -- Where the hyperlink points to | |
| 321 | +@ srctype INT, -- 0: check-in 1: ticket 2: wiki | |
| 322 | +@ srcid INT, -- rid for checkin or wiki. tkt_id for ticket. | |
| 323 | +@ mtime TIMESTAMP, -- time that the hyperlink was added | |
| 324 | +@ UNIQUE(target, srctype, srcid) | |
| 325 | +@ ); | |
| 326 | +@ CREATE INDEX backlink_src ON backlink(srcid, srctype); | |
| 327 | +@ | |
| 328 | +@ -- Each attachment is an entry in the following table. Only | |
| 329 | +@ -- the most recent attachment (identified by the D card) is saved. | |
| 330 | +@ -- | |
| 331 | +@ CREATE TABLE attachment( | |
| 332 | +@ attachid INTEGER PRIMARY KEY, -- Local id for this attachment | |
| 333 | +@ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use | |
| 334 | +@ mtime TIMESTAMP, -- Time when attachment last changed | |
| 335 | +@ src TEXT, -- UUID of the attachment. NULL to delete | |
| 336 | +@ target TEXT, -- Object attached to. Wikiname or Tkt UUID | |
| 337 | +@ filename TEXT, -- Filename for the attachment | |
| 338 | +@ comment TEXT, -- Comment associated with this attachment | |
| 339 | +@ user TEXT -- Name of user adding attachment | |
| 340 | +@ ); | |
| 341 | +@ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); | |
| 342 | +@ CREATE INDEX attachment_idx2 ON attachment(src); | |
| 343 | +@ | |
| 314 | 344 | @ -- Template for the TICKET table |
| 315 | 345 | @ -- |
| 316 | 346 | @ -- NB: when changing the schema of the TICKET table here, also make the |
| 317 | 347 | @ -- same change in tktsetup.c. |
| 318 | 348 | @ -- |
| @@ -393,10 +423,11 @@ | ||
| 393 | 423 | @ CREATE TABLE vfile( |
| 394 | 424 | @ id INTEGER PRIMARY KEY, -- ID of the checked out file |
| 395 | 425 | @ vid INTEGER REFERENCES blob, -- The baseline this file is part of. |
| 396 | 426 | @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add |
| 397 | 427 | @ deleted BOOLEAN DEFAULT 0, -- True if deleted |
| 428 | +@ isexe BOOLEAN, -- True if file should be executable | |
| 398 | 429 | @ rid INTEGER, -- Originally from this repository record |
| 399 | 430 | @ mrid INTEGER, -- Based on this record due to a merge |
| 400 | 431 | @ mtime INTEGER, -- Modification time of file on disk |
| 401 | 432 | @ pathname TEXT, -- Full pathname relative to root |
| 402 | 433 | @ origname TEXT, -- Original pathname. NULL if unchanged |
| 403 | 434 |
| --- src/schema.c | |
| +++ src/schema.c | |
| @@ -309,10 +309,40 @@ | |
| 309 | @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to |
| 310 | @ UNIQUE(rid, tagid) |
| 311 | @ ); |
| 312 | @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); |
| 313 | @ |
| 314 | @ -- Template for the TICKET table |
| 315 | @ -- |
| 316 | @ -- NB: when changing the schema of the TICKET table here, also make the |
| 317 | @ -- same change in tktsetup.c. |
| 318 | @ -- |
| @@ -393,10 +423,11 @@ | |
| 393 | @ CREATE TABLE vfile( |
| 394 | @ id INTEGER PRIMARY KEY, -- ID of the checked out file |
| 395 | @ vid INTEGER REFERENCES blob, -- The baseline this file is part of. |
| 396 | @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add |
| 397 | @ deleted BOOLEAN DEFAULT 0, -- True if deleted |
| 398 | @ rid INTEGER, -- Originally from this repository record |
| 399 | @ mrid INTEGER, -- Based on this record due to a merge |
| 400 | @ mtime INTEGER, -- Modification time of file on disk |
| 401 | @ pathname TEXT, -- Full pathname relative to root |
| 402 | @ origname TEXT, -- Original pathname. NULL if unchanged |
| 403 |
| --- src/schema.c | |
| +++ src/schema.c | |
| @@ -309,10 +309,40 @@ | |
| 309 | @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to |
| 310 | @ UNIQUE(rid, tagid) |
| 311 | @ ); |
| 312 | @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); |
| 313 | @ |
| 314 | @ -- When a hyperlink occurs from one artifact to another (for example |
| 315 | @ -- when a check-in comment refers to a ticket) an entry is made in |
| 316 | @ -- the following table for that hyperlink. This table is used to |
| 317 | @ -- facilitate the display of "back links". |
| 318 | @ -- |
| 319 | @ CREATE TABLE backlink( |
| 320 | @ target TEXT, -- Where the hyperlink points to |
| 321 | @ srctype INT, -- 0: check-in 1: ticket 2: wiki |
| 322 | @ srcid INT, -- rid for checkin or wiki. tkt_id for ticket. |
| 323 | @ mtime TIMESTAMP, -- time that the hyperlink was added |
| 324 | @ UNIQUE(target, srctype, srcid) |
| 325 | @ ); |
| 326 | @ CREATE INDEX backlink_src ON backlink(srcid, srctype); |
| 327 | @ |
| 328 | @ -- Each attachment is an entry in the following table. Only |
| 329 | @ -- the most recent attachment (identified by the D card) is saved. |
| 330 | @ -- |
| 331 | @ CREATE TABLE attachment( |
| 332 | @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment |
| 333 | @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use |
| 334 | @ mtime TIMESTAMP, -- Time when attachment last changed |
| 335 | @ src TEXT, -- UUID of the attachment. NULL to delete |
| 336 | @ target TEXT, -- Object attached to. Wikiname or Tkt UUID |
| 337 | @ filename TEXT, -- Filename for the attachment |
| 338 | @ comment TEXT, -- Comment associated with this attachment |
| 339 | @ user TEXT -- Name of user adding attachment |
| 340 | @ ); |
| 341 | @ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); |
| 342 | @ CREATE INDEX attachment_idx2 ON attachment(src); |
| 343 | @ |
| 344 | @ -- Template for the TICKET table |
| 345 | @ -- |
| 346 | @ -- NB: when changing the schema of the TICKET table here, also make the |
| 347 | @ -- same change in tktsetup.c. |
| 348 | @ -- |
| @@ -393,10 +423,11 @@ | |
| 423 | @ CREATE TABLE vfile( |
| 424 | @ id INTEGER PRIMARY KEY, -- ID of the checked out file |
| 425 | @ vid INTEGER REFERENCES blob, -- The baseline this file is part of. |
| 426 | @ chnged INT DEFAULT 0, -- 0:unchnged 1:edited 2:m-chng 3:m-add |
| 427 | @ deleted BOOLEAN DEFAULT 0, -- True if deleted |
| 428 | @ isexe BOOLEAN, -- True if file should be executable |
| 429 | @ rid INTEGER, -- Originally from this repository record |
| 430 | @ mrid INTEGER, -- Based on this record due to a merge |
| 431 | @ mtime INTEGER, -- Modification time of file on disk |
| 432 | @ pathname TEXT, -- Full pathname relative to root |
| 433 | @ origname TEXT, -- Original pathname. NULL if unchanged |
| 434 |
+21
-10
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -145,10 +145,12 @@ | ||
| 145 | 145 | @ <ol> |
| 146 | 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | 147 | @ <table> |
| 148 | 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | + @ <tr><td valign="top"><b>b</b></td> | |
| 151 | + @ <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr> | |
| 150 | 152 | @ <tr><td valign="top"><b>c</b></td> |
| 151 | 153 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 152 | 154 | @ <tr><td valign="top"><b>d</b></td> |
| 153 | 155 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 154 | 156 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | ||
| 237 | 239 | */ |
| 238 | 240 | void user_edit(void){ |
| 239 | 241 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 240 | 242 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 241 | 243 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 242 | - char *oat, *oau, *oav, *oaz; | |
| 244 | + char *oat, *oau, *oav, *oab, *oaz; | |
| 243 | 245 | const char *inherit[128]; |
| 244 | 246 | int doWrite; |
| 245 | 247 | int uid; |
| 246 | 248 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 247 | 249 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | ||
| 274 | 276 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 275 | 277 | if( doWrite ){ |
| 276 | 278 | char zCap[50]; |
| 277 | 279 | int i = 0; |
| 278 | 280 | int aa = P("aa")!=0; |
| 281 | + int ab = P("ab")!=0; | |
| 279 | 282 | int ad = P("ad")!=0; |
| 280 | 283 | int ae = P("ae")!=0; |
| 281 | 284 | int ai = P("ai")!=0; |
| 282 | 285 | int aj = P("aj")!=0; |
| 283 | 286 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | ||
| 295 | 298 | int at = P("at")!=0; |
| 296 | 299 | int au = P("au")!=0; |
| 297 | 300 | int av = P("av")!=0; |
| 298 | 301 | int az = P("az")!=0; |
| 299 | 302 | if( aa ){ zCap[i++] = 'a'; } |
| 303 | + if( ab ){ zCap[i++] = 'b'; } | |
| 300 | 304 | if( ac ){ zCap[i++] = 'c'; } |
| 301 | 305 | if( ad ){ zCap[i++] = 'd'; } |
| 302 | 306 | if( ae ){ zCap[i++] = 'e'; } |
| 303 | 307 | if( af ){ zCap[i++] = 'f'; } |
| 304 | 308 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | ||
| 351 | 355 | */ |
| 352 | 356 | zLogin = ""; |
| 353 | 357 | zInfo = ""; |
| 354 | 358 | zCap = ""; |
| 355 | 359 | zPw = ""; |
| 356 | - oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = | |
| 360 | + oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = | |
| 357 | 361 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 358 | 362 | if( uid ){ |
| 359 | 363 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 360 | 364 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 361 | 365 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 362 | 366 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 363 | 367 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 368 | + if( strchr(zCap, 'b') ) oab = " checked"; | |
| 364 | 369 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 365 | 370 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 366 | 371 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 367 | 372 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 368 | 373 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | ||
| 465 | 470 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 466 | 471 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 467 | 472 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 468 | 473 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 469 | 474 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 470 | - @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br> | |
| 471 | - @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br> | |
| 472 | - @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br> | |
| 473 | - @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br> | |
| 474 | - @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br> | |
| 475 | + @ <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br> | |
| 476 | + @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br> | |
| 477 | + @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br> | |
| 478 | + @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br> | |
| 479 | + @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br> | |
| 480 | + @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br> | |
| 475 | 481 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 476 | 482 | @ </td> |
| 477 | 483 | @ </tr> |
| 478 | 484 | @ <tr> |
| 479 | 485 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | ||
| 562 | 568 | @ </li><p> |
| 563 | 569 | @ |
| 564 | 570 | @ <li><p> |
| 565 | 571 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 566 | 572 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 567 | - @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and | |
| 568 | - @ <b>Write Tkt</b> privileges control access to trouble tickets. | |
| 569 | - @ The <b>Tkt Report</b> privilege allows the user to create or edit | |
| 573 | + @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and | |
| 574 | + @ <b>Write Ticket</b> privileges control access to trouble tickets. | |
| 575 | + @ The <b>Ticket Report</b> privilege allows the user to create or edit | |
| 570 | 576 | @ ticket report formats. |
| 571 | 577 | @ </p></li> |
| 572 | 578 | @ |
| 573 | 579 | @ <li><p> |
| 574 | 580 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | ||
| 581 | 587 | @ such as the email address of users and contact information on tickets. |
| 582 | 588 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 583 | 589 | @ "developer". |
| 584 | 590 | @ </p></li> |
| 585 | 591 | @ |
| 592 | + @ <li><p> | |
| 593 | + @ The <b>Attachment</b> privilege is needed in order to add attachments | |
| 594 | + @ to tickets or wiki. Write privilege on the ticket or wiki is also | |
| 595 | + @ required.</p></li> | |
| 596 | + @ | |
| 586 | 597 | @ <li><p> |
| 587 | 598 | @ Login is prohibited if the password is an empty string. |
| 588 | 599 | @ </p></li> |
| 589 | 600 | @ </ul> |
| 590 | 601 | @ |
| 591 | 602 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -145,10 +145,12 @@ | |
| 145 | @ <ol> |
| 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | @ <table> |
| 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | @ <tr><td valign="top"><b>c</b></td> |
| 151 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 152 | @ <tr><td valign="top"><b>d</b></td> |
| 153 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 154 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | |
| 237 | */ |
| 238 | void user_edit(void){ |
| 239 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 240 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 241 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 242 | char *oat, *oau, *oav, *oaz; |
| 243 | const char *inherit[128]; |
| 244 | int doWrite; |
| 245 | int uid; |
| 246 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 247 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | |
| 274 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 275 | if( doWrite ){ |
| 276 | char zCap[50]; |
| 277 | int i = 0; |
| 278 | int aa = P("aa")!=0; |
| 279 | int ad = P("ad")!=0; |
| 280 | int ae = P("ae")!=0; |
| 281 | int ai = P("ai")!=0; |
| 282 | int aj = P("aj")!=0; |
| 283 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | |
| 295 | int at = P("at")!=0; |
| 296 | int au = P("au")!=0; |
| 297 | int av = P("av")!=0; |
| 298 | int az = P("az")!=0; |
| 299 | if( aa ){ zCap[i++] = 'a'; } |
| 300 | if( ac ){ zCap[i++] = 'c'; } |
| 301 | if( ad ){ zCap[i++] = 'd'; } |
| 302 | if( ae ){ zCap[i++] = 'e'; } |
| 303 | if( af ){ zCap[i++] = 'f'; } |
| 304 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | |
| 351 | */ |
| 352 | zLogin = ""; |
| 353 | zInfo = ""; |
| 354 | zCap = ""; |
| 355 | zPw = ""; |
| 356 | oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = |
| 357 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 358 | if( uid ){ |
| 359 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 360 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 361 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 362 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 363 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 364 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 365 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 366 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 367 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 368 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | |
| 465 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 466 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 467 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 468 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 469 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 470 | @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br> |
| 471 | @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br> |
| 472 | @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br> |
| 473 | @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br> |
| 474 | @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br> |
| 475 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 476 | @ </td> |
| 477 | @ </tr> |
| 478 | @ <tr> |
| 479 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | |
| 562 | @ </li><p> |
| 563 | @ |
| 564 | @ <li><p> |
| 565 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 566 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 567 | @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and |
| 568 | @ <b>Write Tkt</b> privileges control access to trouble tickets. |
| 569 | @ The <b>Tkt Report</b> privilege allows the user to create or edit |
| 570 | @ ticket report formats. |
| 571 | @ </p></li> |
| 572 | @ |
| 573 | @ <li><p> |
| 574 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | |
| 581 | @ such as the email address of users and contact information on tickets. |
| 582 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 583 | @ "developer". |
| 584 | @ </p></li> |
| 585 | @ |
| 586 | @ <li><p> |
| 587 | @ Login is prohibited if the password is an empty string. |
| 588 | @ </p></li> |
| 589 | @ </ul> |
| 590 | @ |
| 591 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -145,10 +145,12 @@ | |
| 145 | @ <ol> |
| 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | @ <table> |
| 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | @ <tr><td valign="top"><b>b</b></td> |
| 151 | @ <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr> |
| 152 | @ <tr><td valign="top"><b>c</b></td> |
| 153 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 154 | @ <tr><td valign="top"><b>d</b></td> |
| 155 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 156 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | |
| 239 | */ |
| 240 | void user_edit(void){ |
| 241 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 242 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 243 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 244 | char *oat, *oau, *oav, *oab, *oaz; |
| 245 | const char *inherit[128]; |
| 246 | int doWrite; |
| 247 | int uid; |
| 248 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 249 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | |
| 276 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 277 | if( doWrite ){ |
| 278 | char zCap[50]; |
| 279 | int i = 0; |
| 280 | int aa = P("aa")!=0; |
| 281 | int ab = P("ab")!=0; |
| 282 | int ad = P("ad")!=0; |
| 283 | int ae = P("ae")!=0; |
| 284 | int ai = P("ai")!=0; |
| 285 | int aj = P("aj")!=0; |
| 286 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | |
| 298 | int at = P("at")!=0; |
| 299 | int au = P("au")!=0; |
| 300 | int av = P("av")!=0; |
| 301 | int az = P("az")!=0; |
| 302 | if( aa ){ zCap[i++] = 'a'; } |
| 303 | if( ab ){ zCap[i++] = 'b'; } |
| 304 | if( ac ){ zCap[i++] = 'c'; } |
| 305 | if( ad ){ zCap[i++] = 'd'; } |
| 306 | if( ae ){ zCap[i++] = 'e'; } |
| 307 | if( af ){ zCap[i++] = 'f'; } |
| 308 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | |
| 355 | */ |
| 356 | zLogin = ""; |
| 357 | zInfo = ""; |
| 358 | zCap = ""; |
| 359 | zPw = ""; |
| 360 | oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = |
| 361 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 362 | if( uid ){ |
| 363 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 364 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 365 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 366 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 367 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 368 | if( strchr(zCap, 'b') ) oab = " checked"; |
| 369 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 370 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 371 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 372 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 373 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | |
| 470 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 471 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 472 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 473 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 474 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 475 | @ <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br> |
| 476 | @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br> |
| 477 | @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br> |
| 478 | @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br> |
| 479 | @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br> |
| 480 | @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br> |
| 481 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 482 | @ </td> |
| 483 | @ </tr> |
| 484 | @ <tr> |
| 485 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | |
| 568 | @ </li><p> |
| 569 | @ |
| 570 | @ <li><p> |
| 571 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 572 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 573 | @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and |
| 574 | @ <b>Write Ticket</b> privileges control access to trouble tickets. |
| 575 | @ The <b>Ticket Report</b> privilege allows the user to create or edit |
| 576 | @ ticket report formats. |
| 577 | @ </p></li> |
| 578 | @ |
| 579 | @ <li><p> |
| 580 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | |
| 587 | @ such as the email address of users and contact information on tickets. |
| 588 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 589 | @ "developer". |
| 590 | @ </p></li> |
| 591 | @ |
| 592 | @ <li><p> |
| 593 | @ The <b>Attachment</b> privilege is needed in order to add attachments |
| 594 | @ to tickets or wiki. Write privilege on the ticket or wiki is also |
| 595 | @ required.</p></li> |
| 596 | @ |
| 597 | @ <li><p> |
| 598 | @ Login is prohibited if the password is an empty string. |
| 599 | @ </p></li> |
| 600 | @ </ul> |
| 601 | @ |
| 602 |
+21
-10
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -145,10 +145,12 @@ | ||
| 145 | 145 | @ <ol> |
| 146 | 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | 147 | @ <table> |
| 148 | 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | + @ <tr><td valign="top"><b>b</b></td> | |
| 151 | + @ <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr> | |
| 150 | 152 | @ <tr><td valign="top"><b>c</b></td> |
| 151 | 153 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 152 | 154 | @ <tr><td valign="top"><b>d</b></td> |
| 153 | 155 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 154 | 156 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | ||
| 237 | 239 | */ |
| 238 | 240 | void user_edit(void){ |
| 239 | 241 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 240 | 242 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 241 | 243 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 242 | - char *oat, *oau, *oav, *oaz; | |
| 244 | + char *oat, *oau, *oav, *oab, *oaz; | |
| 243 | 245 | const char *inherit[128]; |
| 244 | 246 | int doWrite; |
| 245 | 247 | int uid; |
| 246 | 248 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 247 | 249 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | ||
| 274 | 276 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 275 | 277 | if( doWrite ){ |
| 276 | 278 | char zCap[50]; |
| 277 | 279 | int i = 0; |
| 278 | 280 | int aa = P("aa")!=0; |
| 281 | + int ab = P("ab")!=0; | |
| 279 | 282 | int ad = P("ad")!=0; |
| 280 | 283 | int ae = P("ae")!=0; |
| 281 | 284 | int ai = P("ai")!=0; |
| 282 | 285 | int aj = P("aj")!=0; |
| 283 | 286 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | ||
| 295 | 298 | int at = P("at")!=0; |
| 296 | 299 | int au = P("au")!=0; |
| 297 | 300 | int av = P("av")!=0; |
| 298 | 301 | int az = P("az")!=0; |
| 299 | 302 | if( aa ){ zCap[i++] = 'a'; } |
| 303 | + if( ab ){ zCap[i++] = 'b'; } | |
| 300 | 304 | if( ac ){ zCap[i++] = 'c'; } |
| 301 | 305 | if( ad ){ zCap[i++] = 'd'; } |
| 302 | 306 | if( ae ){ zCap[i++] = 'e'; } |
| 303 | 307 | if( af ){ zCap[i++] = 'f'; } |
| 304 | 308 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | ||
| 351 | 355 | */ |
| 352 | 356 | zLogin = ""; |
| 353 | 357 | zInfo = ""; |
| 354 | 358 | zCap = ""; |
| 355 | 359 | zPw = ""; |
| 356 | - oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = | |
| 360 | + oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = | |
| 357 | 361 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 358 | 362 | if( uid ){ |
| 359 | 363 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 360 | 364 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 361 | 365 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 362 | 366 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 363 | 367 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 368 | + if( strchr(zCap, 'b') ) oab = " checked"; | |
| 364 | 369 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 365 | 370 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 366 | 371 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 367 | 372 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 368 | 373 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | ||
| 465 | 470 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 466 | 471 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 467 | 472 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 468 | 473 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 469 | 474 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 470 | - @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br> | |
| 471 | - @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br> | |
| 472 | - @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br> | |
| 473 | - @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br> | |
| 474 | - @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br> | |
| 475 | + @ <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br> | |
| 476 | + @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br> | |
| 477 | + @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br> | |
| 478 | + @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br> | |
| 479 | + @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br> | |
| 480 | + @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br> | |
| 475 | 481 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 476 | 482 | @ </td> |
| 477 | 483 | @ </tr> |
| 478 | 484 | @ <tr> |
| 479 | 485 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | ||
| 562 | 568 | @ </li><p> |
| 563 | 569 | @ |
| 564 | 570 | @ <li><p> |
| 565 | 571 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 566 | 572 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 567 | - @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and | |
| 568 | - @ <b>Write Tkt</b> privileges control access to trouble tickets. | |
| 569 | - @ The <b>Tkt Report</b> privilege allows the user to create or edit | |
| 573 | + @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and | |
| 574 | + @ <b>Write Ticket</b> privileges control access to trouble tickets. | |
| 575 | + @ The <b>Ticket Report</b> privilege allows the user to create or edit | |
| 570 | 576 | @ ticket report formats. |
| 571 | 577 | @ </p></li> |
| 572 | 578 | @ |
| 573 | 579 | @ <li><p> |
| 574 | 580 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | ||
| 581 | 587 | @ such as the email address of users and contact information on tickets. |
| 582 | 588 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 583 | 589 | @ "developer". |
| 584 | 590 | @ </p></li> |
| 585 | 591 | @ |
| 592 | + @ <li><p> | |
| 593 | + @ The <b>Attachment</b> privilege is needed in order to add attachments | |
| 594 | + @ to tickets or wiki. Write privilege on the ticket or wiki is also | |
| 595 | + @ required.</p></li> | |
| 596 | + @ | |
| 586 | 597 | @ <li><p> |
| 587 | 598 | @ Login is prohibited if the password is an empty string. |
| 588 | 599 | @ </p></li> |
| 589 | 600 | @ </ul> |
| 590 | 601 | @ |
| 591 | 602 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -145,10 +145,12 @@ | |
| 145 | @ <ol> |
| 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | @ <table> |
| 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | @ <tr><td valign="top"><b>c</b></td> |
| 151 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 152 | @ <tr><td valign="top"><b>d</b></td> |
| 153 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 154 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | |
| 237 | */ |
| 238 | void user_edit(void){ |
| 239 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 240 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 241 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 242 | char *oat, *oau, *oav, *oaz; |
| 243 | const char *inherit[128]; |
| 244 | int doWrite; |
| 245 | int uid; |
| 246 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 247 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | |
| 274 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 275 | if( doWrite ){ |
| 276 | char zCap[50]; |
| 277 | int i = 0; |
| 278 | int aa = P("aa")!=0; |
| 279 | int ad = P("ad")!=0; |
| 280 | int ae = P("ae")!=0; |
| 281 | int ai = P("ai")!=0; |
| 282 | int aj = P("aj")!=0; |
| 283 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | |
| 295 | int at = P("at")!=0; |
| 296 | int au = P("au")!=0; |
| 297 | int av = P("av")!=0; |
| 298 | int az = P("az")!=0; |
| 299 | if( aa ){ zCap[i++] = 'a'; } |
| 300 | if( ac ){ zCap[i++] = 'c'; } |
| 301 | if( ad ){ zCap[i++] = 'd'; } |
| 302 | if( ae ){ zCap[i++] = 'e'; } |
| 303 | if( af ){ zCap[i++] = 'f'; } |
| 304 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | |
| 351 | */ |
| 352 | zLogin = ""; |
| 353 | zInfo = ""; |
| 354 | zCap = ""; |
| 355 | zPw = ""; |
| 356 | oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = |
| 357 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 358 | if( uid ){ |
| 359 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 360 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 361 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 362 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 363 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 364 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 365 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 366 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 367 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 368 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | |
| 465 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 466 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 467 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 468 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 469 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 470 | @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br> |
| 471 | @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br> |
| 472 | @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br> |
| 473 | @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br> |
| 474 | @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br> |
| 475 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 476 | @ </td> |
| 477 | @ </tr> |
| 478 | @ <tr> |
| 479 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | |
| 562 | @ </li><p> |
| 563 | @ |
| 564 | @ <li><p> |
| 565 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 566 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 567 | @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and |
| 568 | @ <b>Write Tkt</b> privileges control access to trouble tickets. |
| 569 | @ The <b>Tkt Report</b> privilege allows the user to create or edit |
| 570 | @ ticket report formats. |
| 571 | @ </p></li> |
| 572 | @ |
| 573 | @ <li><p> |
| 574 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | |
| 581 | @ such as the email address of users and contact information on tickets. |
| 582 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 583 | @ "developer". |
| 584 | @ </p></li> |
| 585 | @ |
| 586 | @ <li><p> |
| 587 | @ Login is prohibited if the password is an empty string. |
| 588 | @ </p></li> |
| 589 | @ </ul> |
| 590 | @ |
| 591 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -145,10 +145,12 @@ | |
| 145 | @ <ol> |
| 146 | @ <li><p>The permission flags are as follows:</p> |
| 147 | @ <table> |
| 148 | @ <tr><td valign="top"><b>a</b></td> |
| 149 | @ <td><i>Admin:</i> Create and delete users</td></tr> |
| 150 | @ <tr><td valign="top"><b>b</b></td> |
| 151 | @ <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr> |
| 152 | @ <tr><td valign="top"><b>c</b></td> |
| 153 | @ <td><i>Append-Tkt:</i> Append to tickets</td></tr> |
| 154 | @ <tr><td valign="top"><b>d</b></td> |
| 155 | @ <td><i>Delete:</i> Delete wiki and tickets</td></tr> |
| 156 | @ <tr><td valign="top"><b>e</b></td> |
| @@ -237,11 +239,11 @@ | |
| 239 | */ |
| 240 | void user_edit(void){ |
| 241 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 242 | char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; |
| 243 | char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; |
| 244 | char *oat, *oau, *oav, *oab, *oaz; |
| 245 | const char *inherit[128]; |
| 246 | int doWrite; |
| 247 | int uid; |
| 248 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 249 | /* user doing the editing is ADMIN. Disallow editing */ |
| @@ -274,10 +276,11 @@ | |
| 276 | doWrite = cgi_all("login","info","pw") && !higherUser; |
| 277 | if( doWrite ){ |
| 278 | char zCap[50]; |
| 279 | int i = 0; |
| 280 | int aa = P("aa")!=0; |
| 281 | int ab = P("ab")!=0; |
| 282 | int ad = P("ad")!=0; |
| 283 | int ae = P("ae")!=0; |
| 284 | int ai = P("ai")!=0; |
| 285 | int aj = P("aj")!=0; |
| 286 | int ak = P("ak")!=0; |
| @@ -295,10 +298,11 @@ | |
| 298 | int at = P("at")!=0; |
| 299 | int au = P("au")!=0; |
| 300 | int av = P("av")!=0; |
| 301 | int az = P("az")!=0; |
| 302 | if( aa ){ zCap[i++] = 'a'; } |
| 303 | if( ab ){ zCap[i++] = 'b'; } |
| 304 | if( ac ){ zCap[i++] = 'c'; } |
| 305 | if( ad ){ zCap[i++] = 'd'; } |
| 306 | if( ae ){ zCap[i++] = 'e'; } |
| 307 | if( af ){ zCap[i++] = 'f'; } |
| 308 | if( ah ){ zCap[i++] = 'h'; } |
| @@ -351,18 +355,19 @@ | |
| 355 | */ |
| 356 | zLogin = ""; |
| 357 | zInfo = ""; |
| 358 | zCap = ""; |
| 359 | zPw = ""; |
| 360 | oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = |
| 361 | oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; |
| 362 | if( uid ){ |
| 363 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 364 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 365 | zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 366 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 367 | if( strchr(zCap, 'a') ) oaa = " checked"; |
| 368 | if( strchr(zCap, 'b') ) oab = " checked"; |
| 369 | if( strchr(zCap, 'c') ) oac = " checked"; |
| 370 | if( strchr(zCap, 'd') ) oad = " checked"; |
| 371 | if( strchr(zCap, 'e') ) oae = " checked"; |
| 372 | if( strchr(zCap, 'f') ) oaf = " checked"; |
| 373 | if( strchr(zCap, 'g') ) oag = " checked"; |
| @@ -465,15 +470,16 @@ | |
| 470 | @ <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br> |
| 471 | @ <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br> |
| 472 | @ <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br> |
| 473 | @ <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br> |
| 474 | @ <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br> |
| 475 | @ <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br> |
| 476 | @ <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br> |
| 477 | @ <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br> |
| 478 | @ <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br> |
| 479 | @ <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br> |
| 480 | @ <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br> |
| 481 | @ <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip |
| 482 | @ </td> |
| 483 | @ </tr> |
| 484 | @ <tr> |
| 485 | @ <td align="right">Password:</td> |
| @@ -562,13 +568,13 @@ | |
| 568 | @ </li><p> |
| 569 | @ |
| 570 | @ <li><p> |
| 571 | @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and |
| 572 | @ <b>Write Wiki</b> privileges control access to wiki pages. The |
| 573 | @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and |
| 574 | @ <b>Write Ticket</b> privileges control access to trouble tickets. |
| 575 | @ The <b>Ticket Report</b> privilege allows the user to create or edit |
| 576 | @ ticket report formats. |
| 577 | @ </p></li> |
| 578 | @ |
| 579 | @ <li><p> |
| 580 | @ Users with the <b>Password</b> privilege are allowed to change their |
| @@ -581,10 +587,15 @@ | |
| 587 | @ such as the email address of users and contact information on tickets. |
| 588 | @ Recommended OFF for "anonymous" and for "nobody" but ON for |
| 589 | @ "developer". |
| 590 | @ </p></li> |
| 591 | @ |
| 592 | @ <li><p> |
| 593 | @ The <b>Attachment</b> privilege is needed in order to add attachments |
| 594 | @ to tickets or wiki. Write privilege on the ticket or wiki is also |
| 595 | @ required.</p></li> |
| 596 | @ |
| 597 | @ <li><p> |
| 598 | @ Login is prohibited if the password is an empty string. |
| 599 | @ </p></li> |
| 600 | @ </ul> |
| 601 | @ |
| 602 |
+40
-40
| --- src/skins.c | ||
| +++ src/skins.c | ||
| @@ -162,11 +162,11 @@ | ||
| 162 | 162 | @ REPLACE INTO config VALUES('header','<html> |
| 163 | 163 | @ <head> |
| 164 | 164 | @ <title>$<project_name>: $<title></title> |
| 165 | 165 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 166 | 166 | @ href="$baseurl/timeline.rss"> |
| 167 | -@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" | |
| 167 | +@ <link rel="stylesheet" href="$baseurl/style.css?blackwhite" type="text/css" | |
| 168 | 168 | @ media="screen"> |
| 169 | 169 | @ </head> |
| 170 | 170 | @ <body> |
| 171 | 171 | @ <div class="header"> |
| 172 | 172 | @ <div class="logo"> |
| @@ -182,37 +182,37 @@ | ||
| 182 | 182 | @ puts "Not logged in" |
| 183 | 183 | @ } |
| 184 | 184 | @ </th1></nobr></div> |
| 185 | 185 | @ </div> |
| 186 | 186 | @ <div class="mainmenu"><th1> |
| 187 | -@ html "<a href="$baseurl$index_page">Home</a> " | |
| 187 | +@ html "<a href=''$baseurl$index_page''>Home</a> " | |
| 188 | 188 | @ if {[anycap jor]} { |
| 189 | -@ html "<a href="$baseurl/timeline">Timeline</a> " | |
| 189 | +@ html "<a href=''$baseurl/timeline''>Timeline</a> " | |
| 190 | 190 | @ } |
| 191 | 191 | @ if {[hascap oh]} { |
| 192 | -@ html "<a href="$baseurl/dir">Files</a> " | |
| 192 | +@ html "<a href=''$baseurl/dir?ci=tip''>Files</a> " | |
| 193 | 193 | @ } |
| 194 | 194 | @ if {[hascap o]} { |
| 195 | -@ html "<a href="$baseurl/leaves">Leaves</a> " | |
| 196 | -@ html "<a href="$baseurl/brlist">Branches</a> " | |
| 197 | -@ html "<a href="$baseurl/taglist">Tags</a> " | |
| 195 | +@ html "<a href=''$baseurl/leaves''>Leaves</a> " | |
| 196 | +@ html "<a href=''$baseurl/brlist''>Branches</a> " | |
| 197 | +@ html "<a href=''$baseurl/taglist''>Tags</a> " | |
| 198 | 198 | @ } |
| 199 | 199 | @ if {[hascap r]} { |
| 200 | -@ html "<a href="$baseurl/reportlist">Tickets</a> " | |
| 200 | +@ html "<a href=''$baseurl/reportlist''>Tickets</a> " | |
| 201 | 201 | @ } |
| 202 | 202 | @ if {[hascap j]} { |
| 203 | -@ html "<a href="$baseurl/wiki">Wiki</a> " | |
| 203 | +@ html "<a href=''$baseurl/wiki''>Wiki</a> " | |
| 204 | 204 | @ } |
| 205 | 205 | @ if {[hascap s]} { |
| 206 | -@ html "<a href="$baseurl/setup">Admin</a> " | |
| 206 | +@ html "<a href=''$baseurl/setup''>Admin</a> " | |
| 207 | 207 | @ } elseif {[hascap a]} { |
| 208 | -@ html "<a href="$baseurl/setup_ulist">Users</a> " | |
| 208 | +@ html "<a href=''$baseurl/setup_ulist''>Users</a> " | |
| 209 | 209 | @ } |
| 210 | 210 | @ if {[info exists login]} { |
| 211 | -@ html "<a href="$baseurl/login">Logout</a> " | |
| 211 | +@ html "<a href=''$baseurl/login''>Logout</a> " | |
| 212 | 212 | @ } else { |
| 213 | -@ html "<a href="$baseurl/login">Login</a> " | |
| 213 | +@ html "<a href=''$baseurl/login''>Login</a> " | |
| 214 | 214 | @ } |
| 215 | 215 | @ </th1></div> |
| 216 | 216 | @ '); |
| 217 | 217 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 218 | 218 | @ Fossil version $manifest_version $manifest_date |
| @@ -367,11 +367,11 @@ | ||
| 367 | 367 | @ REPLACE INTO config VALUES('header','<html> |
| 368 | 368 | @ <head> |
| 369 | 369 | @ <title>$<project_name>: $<title></title> |
| 370 | 370 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 371 | 371 | @ href="$baseurl/timeline.rss"> |
| 372 | -@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" | |
| 372 | +@ <link rel="stylesheet" href="$baseurl/style.css?tan" type="text/css" | |
| 373 | 373 | @ media="screen"> |
| 374 | 374 | @ </head> |
| 375 | 375 | @ <body> |
| 376 | 376 | @ <div class="header"> |
| 377 | 377 | @ <div class="title">$<title></div> |
| @@ -384,37 +384,37 @@ | ||
| 384 | 384 | @ puts "Not logged in" |
| 385 | 385 | @ } |
| 386 | 386 | @ </th1></nobr></div> |
| 387 | 387 | @ </div> |
| 388 | 388 | @ <div class="mainmenu"><th1> |
| 389 | -@ html "<a href="$baseurl$index_page">Home</a> " | |
| 389 | +@ html "<a href=''$baseurl$index_page''>Home</a> " | |
| 390 | 390 | @ if {[anycap jor]} { |
| 391 | -@ html "<a href="$baseurl/timeline">Timeline</a> " | |
| 391 | +@ html "<a href=''$baseurl/timeline''>Timeline</a> " | |
| 392 | 392 | @ } |
| 393 | 393 | @ if {[hascap oh]} { |
| 394 | -@ html "<a href="$baseurl/dir">Files</a> " | |
| 394 | +@ html "<a href=''$baseurl/dir?ci=tip''>Files</a> " | |
| 395 | 395 | @ } |
| 396 | 396 | @ if {[hascap o]} { |
| 397 | -@ html "<a href="$baseurl/leaves">Leaves</a> " | |
| 398 | -@ html "<a href="$baseurl/brlist">Branches</a> " | |
| 399 | -@ html "<a href="$baseurl/taglist">Tags</a> " | |
| 397 | +@ html "<a href=''$baseurl/leaves''>Leaves</a> " | |
| 398 | +@ html "<a href=''$baseurl/brlist''>Branches</a> " | |
| 399 | +@ html "<a href=''$baseurl/taglist''>Tags</a> " | |
| 400 | 400 | @ } |
| 401 | 401 | @ if {[hascap r]} { |
| 402 | -@ html "<a href="$baseurl/reportlist">Tickets</a> " | |
| 402 | +@ html "<a href=''$baseurl/reportlist''>Tickets</a> " | |
| 403 | 403 | @ } |
| 404 | 404 | @ if {[hascap j]} { |
| 405 | -@ html "<a href="$baseurl/wiki">Wiki</a> " | |
| 405 | +@ html "<a href=''$baseurl/wiki''>Wiki</a> " | |
| 406 | 406 | @ } |
| 407 | 407 | @ if {[hascap s]} { |
| 408 | -@ html "<a href="$baseurl/setup">Admin</a> " | |
| 408 | +@ html "<a href=''$baseurl/setup''>Admin</a> " | |
| 409 | 409 | @ } elseif {[hascap a]} { |
| 410 | -@ html "<a href="$baseurl/setup_ulist">Users</a> " | |
| 410 | +@ html "<a href=''$baseurl/setup_ulist''>Users</a> " | |
| 411 | 411 | @ } |
| 412 | 412 | @ if {[info exists login]} { |
| 413 | -@ html "<a href="$baseurl/login">Logout</a> " | |
| 413 | +@ html "<a href=''$baseurl/login''>Logout</a> " | |
| 414 | 414 | @ } else { |
| 415 | -@ html "<a href="$baseurl/login">Login</a> " | |
| 415 | +@ html "<a href=''$baseurl/login''>Login</a> " | |
| 416 | 416 | @ } |
| 417 | 417 | @ </th1></div> |
| 418 | 418 | @ '); |
| 419 | 419 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 420 | 420 | @ Fossil version $manifest_version $manifest_date |
| @@ -514,11 +514,11 @@ | ||
| 514 | 514 | @ div.mainmenu a:hover { |
| 515 | 515 | @ color: #eee; |
| 516 | 516 | @ background-color: #333; |
| 517 | 517 | @ } |
| 518 | 518 | @ |
| 519 | -@ /* Container for the sub-menu and content so they don"t spread | |
| 519 | +@ /* Container for the sub-menu and content so they don''t spread | |
| 520 | 520 | @ ** out underneath the main menu */ |
| 521 | 521 | @ #container { |
| 522 | 522 | @ padding-left: 9em; |
| 523 | 523 | @ } |
| 524 | 524 | @ |
| @@ -600,11 +600,11 @@ | ||
| 600 | 600 | @ REPLACE INTO config VALUES('header','<html> |
| 601 | 601 | @ <head> |
| 602 | 602 | @ <title>$<project_name>: $<title></title> |
| 603 | 603 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 604 | 604 | @ href="$baseurl/timeline.rss"> |
| 605 | -@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" | |
| 605 | +@ <link rel="stylesheet" href="$baseurl/style.css?black2" type="text/css" | |
| 606 | 606 | @ media="screen"> |
| 607 | 607 | @ </head> |
| 608 | 608 | @ <body> |
| 609 | 609 | @ <div class="header"> |
| 610 | 610 | @ <div class="logo"> |
| @@ -619,37 +619,37 @@ | ||
| 619 | 619 | @ puts "Not logged in" |
| 620 | 620 | @ } |
| 621 | 621 | @ </th1></nobr></div> |
| 622 | 622 | @ </div> |
| 623 | 623 | @ <div class="mainmenu"><ul><th1> |
| 624 | -@ html "<li><a href="$baseurl$index_page">Home</a></li>" | |
| 624 | +@ html "<li><a href=''$baseurl$index_page''>Home</a></li>" | |
| 625 | 625 | @ if {[anycap jor]} { |
| 626 | -@ html "<li><a href="$baseurl/timeline">Timeline</a></li>" | |
| 626 | +@ html "<li><a href=''$baseurl/timeline''>Timeline</a></li>" | |
| 627 | 627 | @ } |
| 628 | 628 | @ if {[hascap oh]} { |
| 629 | -@ html "<li><a href="$baseurl/dir">Files</a></li>" | |
| 629 | +@ html "<li><a href=''$baseurl/dir?ci=tip''>Files</a></li>" | |
| 630 | 630 | @ } |
| 631 | 631 | @ if {[hascap o]} { |
| 632 | -@ html "<li><a href="$baseurl/leaves">Leaves</a></li>" | |
| 633 | -@ html "<li><a href="$baseurl/brlist">Branches</a></li>" | |
| 634 | -@ html "<li><a href="$baseurl/taglist">Tags</a></li>" | |
| 632 | +@ html "<li><a href=''$baseurl/leaves''>Leaves</a></li>" | |
| 633 | +@ html "<li><a href=''$baseurl/brlist''>Branches</a></li>" | |
| 634 | +@ html "<li><a href=''$baseurl/taglist''>Tags</a></li>" | |
| 635 | 635 | @ } |
| 636 | 636 | @ if {[hascap r]} { |
| 637 | -@ html "<li><a href="$baseurl/reportlist">Tickets</a></li>" | |
| 637 | +@ html "<li><a href=''$baseurl/reportlist''>Tickets</a></li>" | |
| 638 | 638 | @ } |
| 639 | 639 | @ if {[hascap j]} { |
| 640 | -@ html "<li><a href="$baseurl/wiki">Wiki</a></li>" | |
| 640 | +@ html "<li><a href=''$baseurl/wiki''>Wiki</a></li>" | |
| 641 | 641 | @ } |
| 642 | 642 | @ if {[hascap s]} { |
| 643 | -@ html "<li><a href="$baseurl/setup">Admin</a></li>" | |
| 643 | +@ html "<li><a href=''$baseurl/setup''>Admin</a></li>" | |
| 644 | 644 | @ } elseif {[hascap a]} { |
| 645 | -@ html "<li><a href="$baseurl/setup_ulist">Users</a></li>" | |
| 645 | +@ html "<li><a href=''$baseurl/setup_ulist''>Users</a></li>" | |
| 646 | 646 | @ } |
| 647 | 647 | @ if {[info exists login]} { |
| 648 | -@ html "<li><a href="$baseurl/login">Logout</a></li>" | |
| 648 | +@ html "<li><a href=''$baseurl/login''>Logout</a></li>" | |
| 649 | 649 | @ } else { |
| 650 | -@ html "<li><a href="$baseurl/login">Login</a></li>" | |
| 650 | +@ html "<li><a href=''$baseurl/login''>Login</a></li>" | |
| 651 | 651 | @ } |
| 652 | 652 | @ </th1></ul></div> |
| 653 | 653 | @ <div id="container"> |
| 654 | 654 | @ '); |
| 655 | 655 | @ REPLACE INTO config VALUES('footer','</div> |
| 656 | 656 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -162,11 +162,11 @@ | |
| 162 | @ REPLACE INTO config VALUES('header','<html> |
| 163 | @ <head> |
| 164 | @ <title>$<project_name>: $<title></title> |
| 165 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 166 | @ href="$baseurl/timeline.rss"> |
| 167 | @ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" |
| 168 | @ media="screen"> |
| 169 | @ </head> |
| 170 | @ <body> |
| 171 | @ <div class="header"> |
| 172 | @ <div class="logo"> |
| @@ -182,37 +182,37 @@ | |
| 182 | @ puts "Not logged in" |
| 183 | @ } |
| 184 | @ </th1></nobr></div> |
| 185 | @ </div> |
| 186 | @ <div class="mainmenu"><th1> |
| 187 | @ html "<a href="$baseurl$index_page">Home</a> " |
| 188 | @ if {[anycap jor]} { |
| 189 | @ html "<a href="$baseurl/timeline">Timeline</a> " |
| 190 | @ } |
| 191 | @ if {[hascap oh]} { |
| 192 | @ html "<a href="$baseurl/dir">Files</a> " |
| 193 | @ } |
| 194 | @ if {[hascap o]} { |
| 195 | @ html "<a href="$baseurl/leaves">Leaves</a> " |
| 196 | @ html "<a href="$baseurl/brlist">Branches</a> " |
| 197 | @ html "<a href="$baseurl/taglist">Tags</a> " |
| 198 | @ } |
| 199 | @ if {[hascap r]} { |
| 200 | @ html "<a href="$baseurl/reportlist">Tickets</a> " |
| 201 | @ } |
| 202 | @ if {[hascap j]} { |
| 203 | @ html "<a href="$baseurl/wiki">Wiki</a> " |
| 204 | @ } |
| 205 | @ if {[hascap s]} { |
| 206 | @ html "<a href="$baseurl/setup">Admin</a> " |
| 207 | @ } elseif {[hascap a]} { |
| 208 | @ html "<a href="$baseurl/setup_ulist">Users</a> " |
| 209 | @ } |
| 210 | @ if {[info exists login]} { |
| 211 | @ html "<a href="$baseurl/login">Logout</a> " |
| 212 | @ } else { |
| 213 | @ html "<a href="$baseurl/login">Login</a> " |
| 214 | @ } |
| 215 | @ </th1></div> |
| 216 | @ '); |
| 217 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 218 | @ Fossil version $manifest_version $manifest_date |
| @@ -367,11 +367,11 @@ | |
| 367 | @ REPLACE INTO config VALUES('header','<html> |
| 368 | @ <head> |
| 369 | @ <title>$<project_name>: $<title></title> |
| 370 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 371 | @ href="$baseurl/timeline.rss"> |
| 372 | @ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" |
| 373 | @ media="screen"> |
| 374 | @ </head> |
| 375 | @ <body> |
| 376 | @ <div class="header"> |
| 377 | @ <div class="title">$<title></div> |
| @@ -384,37 +384,37 @@ | |
| 384 | @ puts "Not logged in" |
| 385 | @ } |
| 386 | @ </th1></nobr></div> |
| 387 | @ </div> |
| 388 | @ <div class="mainmenu"><th1> |
| 389 | @ html "<a href="$baseurl$index_page">Home</a> " |
| 390 | @ if {[anycap jor]} { |
| 391 | @ html "<a href="$baseurl/timeline">Timeline</a> " |
| 392 | @ } |
| 393 | @ if {[hascap oh]} { |
| 394 | @ html "<a href="$baseurl/dir">Files</a> " |
| 395 | @ } |
| 396 | @ if {[hascap o]} { |
| 397 | @ html "<a href="$baseurl/leaves">Leaves</a> " |
| 398 | @ html "<a href="$baseurl/brlist">Branches</a> " |
| 399 | @ html "<a href="$baseurl/taglist">Tags</a> " |
| 400 | @ } |
| 401 | @ if {[hascap r]} { |
| 402 | @ html "<a href="$baseurl/reportlist">Tickets</a> " |
| 403 | @ } |
| 404 | @ if {[hascap j]} { |
| 405 | @ html "<a href="$baseurl/wiki">Wiki</a> " |
| 406 | @ } |
| 407 | @ if {[hascap s]} { |
| 408 | @ html "<a href="$baseurl/setup">Admin</a> " |
| 409 | @ } elseif {[hascap a]} { |
| 410 | @ html "<a href="$baseurl/setup_ulist">Users</a> " |
| 411 | @ } |
| 412 | @ if {[info exists login]} { |
| 413 | @ html "<a href="$baseurl/login">Logout</a> " |
| 414 | @ } else { |
| 415 | @ html "<a href="$baseurl/login">Login</a> " |
| 416 | @ } |
| 417 | @ </th1></div> |
| 418 | @ '); |
| 419 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 420 | @ Fossil version $manifest_version $manifest_date |
| @@ -514,11 +514,11 @@ | |
| 514 | @ div.mainmenu a:hover { |
| 515 | @ color: #eee; |
| 516 | @ background-color: #333; |
| 517 | @ } |
| 518 | @ |
| 519 | @ /* Container for the sub-menu and content so they don"t spread |
| 520 | @ ** out underneath the main menu */ |
| 521 | @ #container { |
| 522 | @ padding-left: 9em; |
| 523 | @ } |
| 524 | @ |
| @@ -600,11 +600,11 @@ | |
| 600 | @ REPLACE INTO config VALUES('header','<html> |
| 601 | @ <head> |
| 602 | @ <title>$<project_name>: $<title></title> |
| 603 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 604 | @ href="$baseurl/timeline.rss"> |
| 605 | @ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" |
| 606 | @ media="screen"> |
| 607 | @ </head> |
| 608 | @ <body> |
| 609 | @ <div class="header"> |
| 610 | @ <div class="logo"> |
| @@ -619,37 +619,37 @@ | |
| 619 | @ puts "Not logged in" |
| 620 | @ } |
| 621 | @ </th1></nobr></div> |
| 622 | @ </div> |
| 623 | @ <div class="mainmenu"><ul><th1> |
| 624 | @ html "<li><a href="$baseurl$index_page">Home</a></li>" |
| 625 | @ if {[anycap jor]} { |
| 626 | @ html "<li><a href="$baseurl/timeline">Timeline</a></li>" |
| 627 | @ } |
| 628 | @ if {[hascap oh]} { |
| 629 | @ html "<li><a href="$baseurl/dir">Files</a></li>" |
| 630 | @ } |
| 631 | @ if {[hascap o]} { |
| 632 | @ html "<li><a href="$baseurl/leaves">Leaves</a></li>" |
| 633 | @ html "<li><a href="$baseurl/brlist">Branches</a></li>" |
| 634 | @ html "<li><a href="$baseurl/taglist">Tags</a></li>" |
| 635 | @ } |
| 636 | @ if {[hascap r]} { |
| 637 | @ html "<li><a href="$baseurl/reportlist">Tickets</a></li>" |
| 638 | @ } |
| 639 | @ if {[hascap j]} { |
| 640 | @ html "<li><a href="$baseurl/wiki">Wiki</a></li>" |
| 641 | @ } |
| 642 | @ if {[hascap s]} { |
| 643 | @ html "<li><a href="$baseurl/setup">Admin</a></li>" |
| 644 | @ } elseif {[hascap a]} { |
| 645 | @ html "<li><a href="$baseurl/setup_ulist">Users</a></li>" |
| 646 | @ } |
| 647 | @ if {[info exists login]} { |
| 648 | @ html "<li><a href="$baseurl/login">Logout</a></li>" |
| 649 | @ } else { |
| 650 | @ html "<li><a href="$baseurl/login">Login</a></li>" |
| 651 | @ } |
| 652 | @ </th1></ul></div> |
| 653 | @ <div id="container"> |
| 654 | @ '); |
| 655 | @ REPLACE INTO config VALUES('footer','</div> |
| 656 |
| --- src/skins.c | |
| +++ src/skins.c | |
| @@ -162,11 +162,11 @@ | |
| 162 | @ REPLACE INTO config VALUES('header','<html> |
| 163 | @ <head> |
| 164 | @ <title>$<project_name>: $<title></title> |
| 165 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 166 | @ href="$baseurl/timeline.rss"> |
| 167 | @ <link rel="stylesheet" href="$baseurl/style.css?blackwhite" type="text/css" |
| 168 | @ media="screen"> |
| 169 | @ </head> |
| 170 | @ <body> |
| 171 | @ <div class="header"> |
| 172 | @ <div class="logo"> |
| @@ -182,37 +182,37 @@ | |
| 182 | @ puts "Not logged in" |
| 183 | @ } |
| 184 | @ </th1></nobr></div> |
| 185 | @ </div> |
| 186 | @ <div class="mainmenu"><th1> |
| 187 | @ html "<a href=''$baseurl$index_page''>Home</a> " |
| 188 | @ if {[anycap jor]} { |
| 189 | @ html "<a href=''$baseurl/timeline''>Timeline</a> " |
| 190 | @ } |
| 191 | @ if {[hascap oh]} { |
| 192 | @ html "<a href=''$baseurl/dir?ci=tip''>Files</a> " |
| 193 | @ } |
| 194 | @ if {[hascap o]} { |
| 195 | @ html "<a href=''$baseurl/leaves''>Leaves</a> " |
| 196 | @ html "<a href=''$baseurl/brlist''>Branches</a> " |
| 197 | @ html "<a href=''$baseurl/taglist''>Tags</a> " |
| 198 | @ } |
| 199 | @ if {[hascap r]} { |
| 200 | @ html "<a href=''$baseurl/reportlist''>Tickets</a> " |
| 201 | @ } |
| 202 | @ if {[hascap j]} { |
| 203 | @ html "<a href=''$baseurl/wiki''>Wiki</a> " |
| 204 | @ } |
| 205 | @ if {[hascap s]} { |
| 206 | @ html "<a href=''$baseurl/setup''>Admin</a> " |
| 207 | @ } elseif {[hascap a]} { |
| 208 | @ html "<a href=''$baseurl/setup_ulist''>Users</a> " |
| 209 | @ } |
| 210 | @ if {[info exists login]} { |
| 211 | @ html "<a href=''$baseurl/login''>Logout</a> " |
| 212 | @ } else { |
| 213 | @ html "<a href=''$baseurl/login''>Login</a> " |
| 214 | @ } |
| 215 | @ </th1></div> |
| 216 | @ '); |
| 217 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 218 | @ Fossil version $manifest_version $manifest_date |
| @@ -367,11 +367,11 @@ | |
| 367 | @ REPLACE INTO config VALUES('header','<html> |
| 368 | @ <head> |
| 369 | @ <title>$<project_name>: $<title></title> |
| 370 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 371 | @ href="$baseurl/timeline.rss"> |
| 372 | @ <link rel="stylesheet" href="$baseurl/style.css?tan" type="text/css" |
| 373 | @ media="screen"> |
| 374 | @ </head> |
| 375 | @ <body> |
| 376 | @ <div class="header"> |
| 377 | @ <div class="title">$<title></div> |
| @@ -384,37 +384,37 @@ | |
| 384 | @ puts "Not logged in" |
| 385 | @ } |
| 386 | @ </th1></nobr></div> |
| 387 | @ </div> |
| 388 | @ <div class="mainmenu"><th1> |
| 389 | @ html "<a href=''$baseurl$index_page''>Home</a> " |
| 390 | @ if {[anycap jor]} { |
| 391 | @ html "<a href=''$baseurl/timeline''>Timeline</a> " |
| 392 | @ } |
| 393 | @ if {[hascap oh]} { |
| 394 | @ html "<a href=''$baseurl/dir?ci=tip''>Files</a> " |
| 395 | @ } |
| 396 | @ if {[hascap o]} { |
| 397 | @ html "<a href=''$baseurl/leaves''>Leaves</a> " |
| 398 | @ html "<a href=''$baseurl/brlist''>Branches</a> " |
| 399 | @ html "<a href=''$baseurl/taglist''>Tags</a> " |
| 400 | @ } |
| 401 | @ if {[hascap r]} { |
| 402 | @ html "<a href=''$baseurl/reportlist''>Tickets</a> " |
| 403 | @ } |
| 404 | @ if {[hascap j]} { |
| 405 | @ html "<a href=''$baseurl/wiki''>Wiki</a> " |
| 406 | @ } |
| 407 | @ if {[hascap s]} { |
| 408 | @ html "<a href=''$baseurl/setup''>Admin</a> " |
| 409 | @ } elseif {[hascap a]} { |
| 410 | @ html "<a href=''$baseurl/setup_ulist''>Users</a> " |
| 411 | @ } |
| 412 | @ if {[info exists login]} { |
| 413 | @ html "<a href=''$baseurl/login''>Logout</a> " |
| 414 | @ } else { |
| 415 | @ html "<a href=''$baseurl/login''>Login</a> " |
| 416 | @ } |
| 417 | @ </th1></div> |
| 418 | @ '); |
| 419 | @ REPLACE INTO config VALUES('footer','<div class="footer"> |
| 420 | @ Fossil version $manifest_version $manifest_date |
| @@ -514,11 +514,11 @@ | |
| 514 | @ div.mainmenu a:hover { |
| 515 | @ color: #eee; |
| 516 | @ background-color: #333; |
| 517 | @ } |
| 518 | @ |
| 519 | @ /* Container for the sub-menu and content so they don''t spread |
| 520 | @ ** out underneath the main menu */ |
| 521 | @ #container { |
| 522 | @ padding-left: 9em; |
| 523 | @ } |
| 524 | @ |
| @@ -600,11 +600,11 @@ | |
| 600 | @ REPLACE INTO config VALUES('header','<html> |
| 601 | @ <head> |
| 602 | @ <title>$<project_name>: $<title></title> |
| 603 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 604 | @ href="$baseurl/timeline.rss"> |
| 605 | @ <link rel="stylesheet" href="$baseurl/style.css?black2" type="text/css" |
| 606 | @ media="screen"> |
| 607 | @ </head> |
| 608 | @ <body> |
| 609 | @ <div class="header"> |
| 610 | @ <div class="logo"> |
| @@ -619,37 +619,37 @@ | |
| 619 | @ puts "Not logged in" |
| 620 | @ } |
| 621 | @ </th1></nobr></div> |
| 622 | @ </div> |
| 623 | @ <div class="mainmenu"><ul><th1> |
| 624 | @ html "<li><a href=''$baseurl$index_page''>Home</a></li>" |
| 625 | @ if {[anycap jor]} { |
| 626 | @ html "<li><a href=''$baseurl/timeline''>Timeline</a></li>" |
| 627 | @ } |
| 628 | @ if {[hascap oh]} { |
| 629 | @ html "<li><a href=''$baseurl/dir?ci=tip''>Files</a></li>" |
| 630 | @ } |
| 631 | @ if {[hascap o]} { |
| 632 | @ html "<li><a href=''$baseurl/leaves''>Leaves</a></li>" |
| 633 | @ html "<li><a href=''$baseurl/brlist''>Branches</a></li>" |
| 634 | @ html "<li><a href=''$baseurl/taglist''>Tags</a></li>" |
| 635 | @ } |
| 636 | @ if {[hascap r]} { |
| 637 | @ html "<li><a href=''$baseurl/reportlist''>Tickets</a></li>" |
| 638 | @ } |
| 639 | @ if {[hascap j]} { |
| 640 | @ html "<li><a href=''$baseurl/wiki''>Wiki</a></li>" |
| 641 | @ } |
| 642 | @ if {[hascap s]} { |
| 643 | @ html "<li><a href=''$baseurl/setup''>Admin</a></li>" |
| 644 | @ } elseif {[hascap a]} { |
| 645 | @ html "<li><a href=''$baseurl/setup_ulist''>Users</a></li>" |
| 646 | @ } |
| 647 | @ if {[info exists login]} { |
| 648 | @ html "<li><a href=''$baseurl/login''>Logout</a></li>" |
| 649 | @ } else { |
| 650 | @ html "<li><a href=''$baseurl/login''>Login</a></li>" |
| 651 | @ } |
| 652 | @ </th1></ul></div> |
| 653 | @ <div id="container"> |
| 654 | @ '); |
| 655 | @ REPLACE INTO config VALUES('footer','</div> |
| 656 |
+2
-2
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -188,11 +188,11 @@ | ||
| 188 | 188 | @ <html> |
| 189 | 189 | @ <head> |
| 190 | 190 | @ <title>$<project_name>: $<title></title> |
| 191 | 191 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 192 | 192 | @ href="$baseurl/timeline.rss"> |
| 193 | -@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" | |
| 193 | +@ <link rel="stylesheet" href="$baseurl/style.css?default" type="text/css" | |
| 194 | 194 | @ media="screen"> |
| 195 | 195 | @ </head> |
| 196 | 196 | @ <body> |
| 197 | 197 | @ <div class="header"> |
| 198 | 198 | @ <div class="logo"> |
| @@ -212,11 +212,11 @@ | ||
| 212 | 212 | @ html "<a href='$baseurl$index_page'>Home</a> " |
| 213 | 213 | @ if {[anycap jor]} { |
| 214 | 214 | @ html "<a href='$baseurl/timeline'>Timeline</a> " |
| 215 | 215 | @ } |
| 216 | 216 | @ if {[hascap oh]} { |
| 217 | -@ html "<a href='$baseurl/dir'>Files</a> " | |
| 217 | +@ html "<a href='$baseurl/dir?ci=tip'>Files</a> " | |
| 218 | 218 | @ } |
| 219 | 219 | @ if {[hascap o]} { |
| 220 | 220 | @ html "<a href='$baseurl/leaves'>Leaves</a> " |
| 221 | 221 | @ html "<a href='$baseurl/brlist'>Branches</a> " |
| 222 | 222 | @ html "<a href='$baseurl/taglist'>Tags</a> " |
| 223 | 223 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -188,11 +188,11 @@ | |
| 188 | @ <html> |
| 189 | @ <head> |
| 190 | @ <title>$<project_name>: $<title></title> |
| 191 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 192 | @ href="$baseurl/timeline.rss"> |
| 193 | @ <link rel="stylesheet" href="$baseurl/style.css" type="text/css" |
| 194 | @ media="screen"> |
| 195 | @ </head> |
| 196 | @ <body> |
| 197 | @ <div class="header"> |
| 198 | @ <div class="logo"> |
| @@ -212,11 +212,11 @@ | |
| 212 | @ html "<a href='$baseurl$index_page'>Home</a> " |
| 213 | @ if {[anycap jor]} { |
| 214 | @ html "<a href='$baseurl/timeline'>Timeline</a> " |
| 215 | @ } |
| 216 | @ if {[hascap oh]} { |
| 217 | @ html "<a href='$baseurl/dir'>Files</a> " |
| 218 | @ } |
| 219 | @ if {[hascap o]} { |
| 220 | @ html "<a href='$baseurl/leaves'>Leaves</a> " |
| 221 | @ html "<a href='$baseurl/brlist'>Branches</a> " |
| 222 | @ html "<a href='$baseurl/taglist'>Tags</a> " |
| 223 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -188,11 +188,11 @@ | |
| 188 | @ <html> |
| 189 | @ <head> |
| 190 | @ <title>$<project_name>: $<title></title> |
| 191 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" |
| 192 | @ href="$baseurl/timeline.rss"> |
| 193 | @ <link rel="stylesheet" href="$baseurl/style.css?default" type="text/css" |
| 194 | @ media="screen"> |
| 195 | @ </head> |
| 196 | @ <body> |
| 197 | @ <div class="header"> |
| 198 | @ <div class="logo"> |
| @@ -212,11 +212,11 @@ | |
| 212 | @ html "<a href='$baseurl$index_page'>Home</a> " |
| 213 | @ if {[anycap jor]} { |
| 214 | @ html "<a href='$baseurl/timeline'>Timeline</a> " |
| 215 | @ } |
| 216 | @ if {[hascap oh]} { |
| 217 | @ html "<a href='$baseurl/dir?ci=tip'>Files</a> " |
| 218 | @ } |
| 219 | @ if {[hascap o]} { |
| 220 | @ html "<a href='$baseurl/leaves'>Leaves</a> " |
| 221 | @ html "<a href='$baseurl/brlist'>Branches</a> " |
| 222 | @ html "<a href='$baseurl/taglist'>Tags</a> " |
| 223 |
+6
-1
| --- src/tag.c | ||
| +++ src/tag.c | ||
| @@ -200,10 +200,15 @@ | ||
| 200 | 200 | break; |
| 201 | 201 | } |
| 202 | 202 | } |
| 203 | 203 | if( zCol ){ |
| 204 | 204 | db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid); |
| 205 | + if( tagid==TAG_COMMENT ){ | |
| 206 | + char *zCopy = mprintf("%s", zValue); | |
| 207 | + wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE); | |
| 208 | + free(zCopy); | |
| 209 | + } | |
| 205 | 210 | } |
| 206 | 211 | if( tagid==TAG_DATE ){ |
| 207 | 212 | db_multi_exec("UPDATE event SET mtime=julianday(%Q) WHERE objid=%d", |
| 208 | 213 | zValue, rid); |
| 209 | 214 | } |
| @@ -563,11 +568,11 @@ | ||
| 563 | 568 | if( !g.okRead ){ login_needed(); return; } |
| 564 | 569 | |
| 565 | 570 | style_header("Tagged Check-ins"); |
| 566 | 571 | style_submenu_element("List", "List", "taglist"); |
| 567 | 572 | login_anonymous_available(); |
| 568 | - @ <h2>Check-ins with non-propagating tags:</t2> | |
| 573 | + @ <h2>Check-ins with non-propagating tags:</h2> | |
| 569 | 574 | db_prepare(&q, |
| 570 | 575 | "%s AND blob.rid IN (SELECT rid FROM tagxref" |
| 571 | 576 | " WHERE tagtype=1 AND srcid>0" |
| 572 | 577 | " AND tagid IN (SELECT tagid FROM tag " |
| 573 | 578 | " WHERE tagname GLOB 'sym-*'))" |
| 574 | 579 |
| --- src/tag.c | |
| +++ src/tag.c | |
| @@ -200,10 +200,15 @@ | |
| 200 | break; |
| 201 | } |
| 202 | } |
| 203 | if( zCol ){ |
| 204 | db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid); |
| 205 | } |
| 206 | if( tagid==TAG_DATE ){ |
| 207 | db_multi_exec("UPDATE event SET mtime=julianday(%Q) WHERE objid=%d", |
| 208 | zValue, rid); |
| 209 | } |
| @@ -563,11 +568,11 @@ | |
| 563 | if( !g.okRead ){ login_needed(); return; } |
| 564 | |
| 565 | style_header("Tagged Check-ins"); |
| 566 | style_submenu_element("List", "List", "taglist"); |
| 567 | login_anonymous_available(); |
| 568 | @ <h2>Check-ins with non-propagating tags:</t2> |
| 569 | db_prepare(&q, |
| 570 | "%s AND blob.rid IN (SELECT rid FROM tagxref" |
| 571 | " WHERE tagtype=1 AND srcid>0" |
| 572 | " AND tagid IN (SELECT tagid FROM tag " |
| 573 | " WHERE tagname GLOB 'sym-*'))" |
| 574 |
| --- src/tag.c | |
| +++ src/tag.c | |
| @@ -200,10 +200,15 @@ | |
| 200 | break; |
| 201 | } |
| 202 | } |
| 203 | if( zCol ){ |
| 204 | db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid); |
| 205 | if( tagid==TAG_COMMENT ){ |
| 206 | char *zCopy = mprintf("%s", zValue); |
| 207 | wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE); |
| 208 | free(zCopy); |
| 209 | } |
| 210 | } |
| 211 | if( tagid==TAG_DATE ){ |
| 212 | db_multi_exec("UPDATE event SET mtime=julianday(%Q) WHERE objid=%d", |
| 213 | zValue, rid); |
| 214 | } |
| @@ -563,11 +568,11 @@ | |
| 568 | if( !g.okRead ){ login_needed(); return; } |
| 569 | |
| 570 | style_header("Tagged Check-ins"); |
| 571 | style_submenu_element("List", "List", "taglist"); |
| 572 | login_anonymous_available(); |
| 573 | @ <h2>Check-ins with non-propagating tags:</h2> |
| 574 | db_prepare(&q, |
| 575 | "%s AND blob.rid IN (SELECT rid FROM tagxref" |
| 576 | " WHERE tagtype=1 AND srcid>0" |
| 577 | " AND tagid IN (SELECT tagid FROM tag " |
| 578 | " WHERE tagname GLOB 'sym-*'))" |
| 579 |
+42
-35
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -200,14 +200,10 @@ | ||
| 200 | 200 | if( tmFlags & TIMELINE_GRAPH ){ |
| 201 | 201 | pGraph = graph_init(); |
| 202 | 202 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 203 | 203 | } |
| 204 | 204 | |
| 205 | - db_multi_exec( | |
| 206 | - "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);" | |
| 207 | - "DELETE FROM seen;" | |
| 208 | - ); | |
| 209 | 205 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 210 | 206 | blob_zero(&comment); |
| 211 | 207 | while( db_step(pQuery)==SQLITE_ROW ){ |
| 212 | 208 | int rid = db_column_int(pQuery, 0); |
| 213 | 209 | const char *zUuid = db_column_text(pQuery, 1); |
| @@ -241,11 +237,10 @@ | ||
| 241 | 237 | } |
| 242 | 238 | if( strcmp(zType,"div")==0 ){ |
| 243 | 239 | @ <tr><td colspan=3><hr></td></tr> |
| 244 | 240 | continue; |
| 245 | 241 | } |
| 246 | - db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid); | |
| 247 | 242 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 248 | 243 | sprintf(zPrevDate, "%.10s", zDate); |
| 249 | 244 | @ <tr><td> |
| 250 | 245 | @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div> |
| 251 | 246 | @ </td></tr> |
| @@ -253,11 +248,39 @@ | ||
| 253 | 248 | memcpy(zTime, &zDate[11], 5); |
| 254 | 249 | zTime[5] = 0; |
| 255 | 250 | @ <tr> |
| 256 | 251 | @ <td valign="top" align="right">%s(zTime)</td> |
| 257 | 252 | @ <td width="20" align="left" valign="top"> |
| 258 | - @ <div id="m%d(rid)"></div> | |
| 253 | + if( pGraph && zType[0]=='c' ){ | |
| 254 | + int nParent = 0; | |
| 255 | + int aParent[32]; | |
| 256 | + const char *zBr; | |
| 257 | + int gidx; | |
| 258 | + static Stmt qparent; | |
| 259 | + static Stmt qbranch; | |
| 260 | + db_static_prepare(&qparent, | |
| 261 | + "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC" | |
| 262 | + ); | |
| 263 | + db_static_prepare(&qbranch, | |
| 264 | + "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid", | |
| 265 | + TAG_BRANCH | |
| 266 | + ); | |
| 267 | + db_bind_int(&qparent, ":rid", rid); | |
| 268 | + while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ | |
| 269 | + aParent[nParent++] = db_column_int(&qparent, 0); | |
| 270 | + } | |
| 271 | + db_reset(&qparent); | |
| 272 | + db_bind_int(&qbranch, ":rid", rid); | |
| 273 | + if( db_step(&qbranch)==SQLITE_ROW ){ | |
| 274 | + zBr = db_column_text(&qbranch, 0); | |
| 275 | + }else{ | |
| 276 | + zBr = "trunk"; | |
| 277 | + } | |
| 278 | + gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr); | |
| 279 | + db_reset(&qbranch); | |
| 280 | + @ <div id="m%d(gidx)"></div> | |
| 281 | + } | |
| 259 | 282 | if( zBgClr && zBgClr[0] ){ |
| 260 | 283 | @ <td valign="top" align="left" bgcolor="%h(zBgClr)"> |
| 261 | 284 | }else{ |
| 262 | 285 | @ <td valign="top" align="left"> |
| 263 | 286 | } |
| @@ -290,37 +313,10 @@ | ||
| 290 | 313 | int i; |
| 291 | 314 | for(i=0; i<nTag; i++){ |
| 292 | 315 | @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b> |
| 293 | 316 | } |
| 294 | 317 | } |
| 295 | - if( pGraph ){ | |
| 296 | - int nParent = 0; | |
| 297 | - int aParent[32]; | |
| 298 | - const char *zBr; | |
| 299 | - static Stmt qparent; | |
| 300 | - static Stmt qbranch; | |
| 301 | - db_static_prepare(&qparent, | |
| 302 | - "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC" | |
| 303 | - ); | |
| 304 | - db_static_prepare(&qbranch, | |
| 305 | - "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid", | |
| 306 | - TAG_BRANCH | |
| 307 | - ); | |
| 308 | - db_bind_int(&qparent, ":rid", rid); | |
| 309 | - while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ | |
| 310 | - aParent[nParent++] = db_column_int(&qparent, 0); | |
| 311 | - } | |
| 312 | - db_reset(&qparent); | |
| 313 | - db_bind_int(&qbranch, ":rid", rid); | |
| 314 | - if( db_step(&qbranch)==SQLITE_ROW ){ | |
| 315 | - zBr = db_column_text(&qbranch, 0); | |
| 316 | - }else{ | |
| 317 | - zBr = "trunk"; | |
| 318 | - } | |
| 319 | - graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr); | |
| 320 | - db_reset(&qbranch); | |
| 321 | - } | |
| 322 | 318 | }else if( (tmFlags & TIMELINE_ARTID)!=0 ){ |
| 323 | 319 | hyperlink_to_uuid(zUuid); |
| 324 | 320 | } |
| 325 | 321 | db_column_blob(pQuery, commentColumn, &comment); |
| 326 | 322 | if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){ |
| @@ -358,19 +354,27 @@ | ||
| 358 | 354 | }else{ |
| 359 | 355 | @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div> |
| 360 | 356 | } |
| 361 | 357 | } |
| 362 | 358 | @ </table> |
| 359 | + timeline_output_graph_javascript(pGraph); | |
| 360 | +} | |
| 361 | + | |
| 362 | +/* | |
| 363 | +** Generate all of the necessary javascript to generate a timeline | |
| 364 | +** graph. | |
| 365 | +*/ | |
| 366 | +void timeline_output_graph_javascript(GraphContext *pGraph){ | |
| 363 | 367 | if( pGraph && pGraph->nErr==0 ){ |
| 364 | 368 | GraphRow *pRow; |
| 365 | 369 | int i; |
| 366 | 370 | char cSep; |
| 367 | 371 | @ <script type="text/JavaScript"> |
| 368 | 372 | cgi_printf("var rowinfo = [\n"); |
| 369 | 373 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 370 | 374 | cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:", |
| 371 | - pRow->rid, | |
| 375 | + pRow->idx, | |
| 372 | 376 | pRow->iRail, |
| 373 | 377 | pRow->bDescender, |
| 374 | 378 | pRow->mergeOut, |
| 375 | 379 | pRow->mergeUpto, |
| 376 | 380 | pRow->aiRaiser[pRow->iRail] |
| @@ -698,11 +702,11 @@ | ||
| 698 | 702 | if( zType[0]=='a' ){ |
| 699 | 703 | tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 700 | 704 | }else{ |
| 701 | 705 | tmFlags = TIMELINE_GRAPH; |
| 702 | 706 | } |
| 703 | - if( P("ng")!=0 ){ | |
| 707 | + if( P("ng")!=0 || zSearch!=0 ){ | |
| 704 | 708 | tmFlags &= ~TIMELINE_GRAPH; |
| 705 | 709 | } |
| 706 | 710 | |
| 707 | 711 | style_header("Timeline"); |
| 708 | 712 | login_anonymous_available(); |
| @@ -891,10 +895,13 @@ | ||
| 891 | 895 | }else if( zBefore ){ |
| 892 | 896 | blob_appendf(&desc, " occurring on or before %h.<br>", zBefore); |
| 893 | 897 | }else if( zCirca ){ |
| 894 | 898 | blob_appendf(&desc, " occurring around %h.<br>", zCirca); |
| 895 | 899 | } |
| 900 | + if( zSearch ){ | |
| 901 | + blob_appendf(&desc, " matching \"%h\"", zSearch); | |
| 902 | + } | |
| 896 | 903 | if( g.okHistory ){ |
| 897 | 904 | if( zAfter || n==nEntry ){ |
| 898 | 905 | zDate = db_text(0, "SELECT min(timestamp) FROM timeline"); |
| 899 | 906 | timeline_submenu(&url, "Older", "b", zDate, "a"); |
| 900 | 907 | free(zDate); |
| 901 | 908 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -200,14 +200,10 @@ | |
| 200 | if( tmFlags & TIMELINE_GRAPH ){ |
| 201 | pGraph = graph_init(); |
| 202 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 203 | } |
| 204 | |
| 205 | db_multi_exec( |
| 206 | "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);" |
| 207 | "DELETE FROM seen;" |
| 208 | ); |
| 209 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 210 | blob_zero(&comment); |
| 211 | while( db_step(pQuery)==SQLITE_ROW ){ |
| 212 | int rid = db_column_int(pQuery, 0); |
| 213 | const char *zUuid = db_column_text(pQuery, 1); |
| @@ -241,11 +237,10 @@ | |
| 241 | } |
| 242 | if( strcmp(zType,"div")==0 ){ |
| 243 | @ <tr><td colspan=3><hr></td></tr> |
| 244 | continue; |
| 245 | } |
| 246 | db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid); |
| 247 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 248 | sprintf(zPrevDate, "%.10s", zDate); |
| 249 | @ <tr><td> |
| 250 | @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div> |
| 251 | @ </td></tr> |
| @@ -253,11 +248,39 @@ | |
| 253 | memcpy(zTime, &zDate[11], 5); |
| 254 | zTime[5] = 0; |
| 255 | @ <tr> |
| 256 | @ <td valign="top" align="right">%s(zTime)</td> |
| 257 | @ <td width="20" align="left" valign="top"> |
| 258 | @ <div id="m%d(rid)"></div> |
| 259 | if( zBgClr && zBgClr[0] ){ |
| 260 | @ <td valign="top" align="left" bgcolor="%h(zBgClr)"> |
| 261 | }else{ |
| 262 | @ <td valign="top" align="left"> |
| 263 | } |
| @@ -290,37 +313,10 @@ | |
| 290 | int i; |
| 291 | for(i=0; i<nTag; i++){ |
| 292 | @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b> |
| 293 | } |
| 294 | } |
| 295 | if( pGraph ){ |
| 296 | int nParent = 0; |
| 297 | int aParent[32]; |
| 298 | const char *zBr; |
| 299 | static Stmt qparent; |
| 300 | static Stmt qbranch; |
| 301 | db_static_prepare(&qparent, |
| 302 | "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC" |
| 303 | ); |
| 304 | db_static_prepare(&qbranch, |
| 305 | "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid", |
| 306 | TAG_BRANCH |
| 307 | ); |
| 308 | db_bind_int(&qparent, ":rid", rid); |
| 309 | while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ |
| 310 | aParent[nParent++] = db_column_int(&qparent, 0); |
| 311 | } |
| 312 | db_reset(&qparent); |
| 313 | db_bind_int(&qbranch, ":rid", rid); |
| 314 | if( db_step(&qbranch)==SQLITE_ROW ){ |
| 315 | zBr = db_column_text(&qbranch, 0); |
| 316 | }else{ |
| 317 | zBr = "trunk"; |
| 318 | } |
| 319 | graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr); |
| 320 | db_reset(&qbranch); |
| 321 | } |
| 322 | }else if( (tmFlags & TIMELINE_ARTID)!=0 ){ |
| 323 | hyperlink_to_uuid(zUuid); |
| 324 | } |
| 325 | db_column_blob(pQuery, commentColumn, &comment); |
| 326 | if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){ |
| @@ -358,19 +354,27 @@ | |
| 358 | }else{ |
| 359 | @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div> |
| 360 | } |
| 361 | } |
| 362 | @ </table> |
| 363 | if( pGraph && pGraph->nErr==0 ){ |
| 364 | GraphRow *pRow; |
| 365 | int i; |
| 366 | char cSep; |
| 367 | @ <script type="text/JavaScript"> |
| 368 | cgi_printf("var rowinfo = [\n"); |
| 369 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 370 | cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:", |
| 371 | pRow->rid, |
| 372 | pRow->iRail, |
| 373 | pRow->bDescender, |
| 374 | pRow->mergeOut, |
| 375 | pRow->mergeUpto, |
| 376 | pRow->aiRaiser[pRow->iRail] |
| @@ -698,11 +702,11 @@ | |
| 698 | if( zType[0]=='a' ){ |
| 699 | tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 700 | }else{ |
| 701 | tmFlags = TIMELINE_GRAPH; |
| 702 | } |
| 703 | if( P("ng")!=0 ){ |
| 704 | tmFlags &= ~TIMELINE_GRAPH; |
| 705 | } |
| 706 | |
| 707 | style_header("Timeline"); |
| 708 | login_anonymous_available(); |
| @@ -891,10 +895,13 @@ | |
| 891 | }else if( zBefore ){ |
| 892 | blob_appendf(&desc, " occurring on or before %h.<br>", zBefore); |
| 893 | }else if( zCirca ){ |
| 894 | blob_appendf(&desc, " occurring around %h.<br>", zCirca); |
| 895 | } |
| 896 | if( g.okHistory ){ |
| 897 | if( zAfter || n==nEntry ){ |
| 898 | zDate = db_text(0, "SELECT min(timestamp) FROM timeline"); |
| 899 | timeline_submenu(&url, "Older", "b", zDate, "a"); |
| 900 | free(zDate); |
| 901 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -200,14 +200,10 @@ | |
| 200 | if( tmFlags & TIMELINE_GRAPH ){ |
| 201 | pGraph = graph_init(); |
| 202 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 203 | } |
| 204 | |
| 205 | @ <table cellspacing=0 border=0 cellpadding=0> |
| 206 | blob_zero(&comment); |
| 207 | while( db_step(pQuery)==SQLITE_ROW ){ |
| 208 | int rid = db_column_int(pQuery, 0); |
| 209 | const char *zUuid = db_column_text(pQuery, 1); |
| @@ -241,11 +237,10 @@ | |
| 237 | } |
| 238 | if( strcmp(zType,"div")==0 ){ |
| 239 | @ <tr><td colspan=3><hr></td></tr> |
| 240 | continue; |
| 241 | } |
| 242 | if( memcmp(zDate, zPrevDate, 10) ){ |
| 243 | sprintf(zPrevDate, "%.10s", zDate); |
| 244 | @ <tr><td> |
| 245 | @ <div class="divider"><nobr>%s(zPrevDate)</nobr></div> |
| 246 | @ </td></tr> |
| @@ -253,11 +248,39 @@ | |
| 248 | memcpy(zTime, &zDate[11], 5); |
| 249 | zTime[5] = 0; |
| 250 | @ <tr> |
| 251 | @ <td valign="top" align="right">%s(zTime)</td> |
| 252 | @ <td width="20" align="left" valign="top"> |
| 253 | if( pGraph && zType[0]=='c' ){ |
| 254 | int nParent = 0; |
| 255 | int aParent[32]; |
| 256 | const char *zBr; |
| 257 | int gidx; |
| 258 | static Stmt qparent; |
| 259 | static Stmt qbranch; |
| 260 | db_static_prepare(&qparent, |
| 261 | "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC" |
| 262 | ); |
| 263 | db_static_prepare(&qbranch, |
| 264 | "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid", |
| 265 | TAG_BRANCH |
| 266 | ); |
| 267 | db_bind_int(&qparent, ":rid", rid); |
| 268 | while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){ |
| 269 | aParent[nParent++] = db_column_int(&qparent, 0); |
| 270 | } |
| 271 | db_reset(&qparent); |
| 272 | db_bind_int(&qbranch, ":rid", rid); |
| 273 | if( db_step(&qbranch)==SQLITE_ROW ){ |
| 274 | zBr = db_column_text(&qbranch, 0); |
| 275 | }else{ |
| 276 | zBr = "trunk"; |
| 277 | } |
| 278 | gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr); |
| 279 | db_reset(&qbranch); |
| 280 | @ <div id="m%d(gidx)"></div> |
| 281 | } |
| 282 | if( zBgClr && zBgClr[0] ){ |
| 283 | @ <td valign="top" align="left" bgcolor="%h(zBgClr)"> |
| 284 | }else{ |
| 285 | @ <td valign="top" align="left"> |
| 286 | } |
| @@ -290,37 +313,10 @@ | |
| 313 | int i; |
| 314 | for(i=0; i<nTag; i++){ |
| 315 | @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b> |
| 316 | } |
| 317 | } |
| 318 | }else if( (tmFlags & TIMELINE_ARTID)!=0 ){ |
| 319 | hyperlink_to_uuid(zUuid); |
| 320 | } |
| 321 | db_column_blob(pQuery, commentColumn, &comment); |
| 322 | if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){ |
| @@ -358,19 +354,27 @@ | |
| 354 | }else{ |
| 355 | @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div> |
| 356 | } |
| 357 | } |
| 358 | @ </table> |
| 359 | timeline_output_graph_javascript(pGraph); |
| 360 | } |
| 361 | |
| 362 | /* |
| 363 | ** Generate all of the necessary javascript to generate a timeline |
| 364 | ** graph. |
| 365 | */ |
| 366 | void timeline_output_graph_javascript(GraphContext *pGraph){ |
| 367 | if( pGraph && pGraph->nErr==0 ){ |
| 368 | GraphRow *pRow; |
| 369 | int i; |
| 370 | char cSep; |
| 371 | @ <script type="text/JavaScript"> |
| 372 | cgi_printf("var rowinfo = [\n"); |
| 373 | for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){ |
| 374 | cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:", |
| 375 | pRow->idx, |
| 376 | pRow->iRail, |
| 377 | pRow->bDescender, |
| 378 | pRow->mergeOut, |
| 379 | pRow->mergeUpto, |
| 380 | pRow->aiRaiser[pRow->iRail] |
| @@ -698,11 +702,11 @@ | |
| 702 | if( zType[0]=='a' ){ |
| 703 | tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH; |
| 704 | }else{ |
| 705 | tmFlags = TIMELINE_GRAPH; |
| 706 | } |
| 707 | if( P("ng")!=0 || zSearch!=0 ){ |
| 708 | tmFlags &= ~TIMELINE_GRAPH; |
| 709 | } |
| 710 | |
| 711 | style_header("Timeline"); |
| 712 | login_anonymous_available(); |
| @@ -891,10 +895,13 @@ | |
| 895 | }else if( zBefore ){ |
| 896 | blob_appendf(&desc, " occurring on or before %h.<br>", zBefore); |
| 897 | }else if( zCirca ){ |
| 898 | blob_appendf(&desc, " occurring around %h.<br>", zCirca); |
| 899 | } |
| 900 | if( zSearch ){ |
| 901 | blob_appendf(&desc, " matching \"%h\"", zSearch); |
| 902 | } |
| 903 | if( g.okHistory ){ |
| 904 | if( zAfter || n==nEntry ){ |
| 905 | zDate = db_text(0, "SELECT min(timestamp) FROM timeline"); |
| 906 | timeline_submenu(&url, "Older", "b", zDate, "a"); |
| 907 | free(zDate); |
| 908 |
+136
-60
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -165,32 +165,10 @@ | ||
| 165 | 165 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 166 | 166 | Th_Store(z, P(z)); |
| 167 | 167 | } |
| 168 | 168 | } |
| 169 | 169 | |
| 170 | -/* | |
| 171 | -** Rebuild all tickets named in the _pending_ticket table. | |
| 172 | -** | |
| 173 | -** This routine is called just prior to commit after new | |
| 174 | -** out-of-sequence ticket changes have been added. | |
| 175 | -*/ | |
| 176 | -static int ticket_rebuild_at_commit(void){ | |
| 177 | - Stmt q; | |
| 178 | - db_multi_exec( | |
| 179 | - "DELETE FROM ticket WHERE tkt_uuid IN _pending_ticket" | |
| 180 | - ); | |
| 181 | - db_prepare(&q, "SELECT uuid FROM _pending_ticket"); | |
| 182 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 183 | - const char *zUuid = db_column_text(&q, 0); | |
| 184 | - ticket_rebuild_entry(zUuid); | |
| 185 | - } | |
| 186 | - db_multi_exec( | |
| 187 | - "DELETE FROM _pending_ticket" | |
| 188 | - ); | |
| 189 | - return 0; | |
| 190 | -} | |
| 191 | - | |
| 192 | 170 | /* |
| 193 | 171 | ** Update an entry of the TICKET table according to the information |
| 194 | 172 | ** in the control file given in p. Attempt to create the appropriate |
| 195 | 173 | ** TICKET table entry if createFlag is true. If createFlag is false, |
| 196 | 174 | ** that means we already know the entry exists and so we can save the |
| @@ -197,11 +175,11 @@ | ||
| 197 | 175 | ** work of trying to create it. |
| 198 | 176 | ** |
| 199 | 177 | ** Return TRUE if a new TICKET entry was created and FALSE if an |
| 200 | 178 | ** existing entry was revised. |
| 201 | 179 | */ |
| 202 | -int ticket_insert(const Manifest *p, int createFlag, int checkTime){ | |
| 180 | +int ticket_insert(const Manifest *p, int createFlag, int rid){ | |
| 203 | 181 | Blob sql; |
| 204 | 182 | Stmt q; |
| 205 | 183 | int i; |
| 206 | 184 | const char *zSep; |
| 207 | 185 | int rc = 0; |
| @@ -224,27 +202,20 @@ | ||
| 224 | 202 | zName, zName, p->aField[i].zValue); |
| 225 | 203 | }else{ |
| 226 | 204 | if( fieldId(zName)<0 ) continue; |
| 227 | 205 | blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue); |
| 228 | 206 | } |
| 207 | + if( rid>0 ){ | |
| 208 | + wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0); | |
| 209 | + } | |
| 229 | 210 | } |
| 230 | 211 | blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime", |
| 231 | 212 | p->zTicketUuid); |
| 232 | 213 | db_prepare(&q, "%s", blob_str(&sql)); |
| 233 | 214 | db_bind_double(&q, ":mtime", p->rDate); |
| 234 | 215 | db_step(&q); |
| 235 | 216 | db_finalize(&q); |
| 236 | - if( checkTime && db_changes()==0 ){ | |
| 237 | - static int isInit = 0; | |
| 238 | - if( !isInit ){ | |
| 239 | - db_multi_exec("CREATE TEMP TABLE _pending_ticket(uuid TEXT UNIQUE)"); | |
| 240 | - db_commit_hook(ticket_rebuild_at_commit, 1); | |
| 241 | - isInit = 1; | |
| 242 | - } | |
| 243 | - db_multi_exec("INSERT OR IGNORE INTO _pending_ticket " | |
| 244 | - "VALUES(%Q)", p->zTicketUuid); | |
| 245 | - } | |
| 246 | 217 | blob_reset(&sql); |
| 247 | 218 | return rc; |
| 248 | 219 | } |
| 249 | 220 | |
| 250 | 221 | /* |
| @@ -264,11 +235,11 @@ | ||
| 264 | 235 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 265 | 236 | while( db_step(&q)==SQLITE_ROW ){ |
| 266 | 237 | int rid = db_column_int(&q, 0); |
| 267 | 238 | content_get(rid, &content); |
| 268 | 239 | manifest_parse(&manifest, &content); |
| 269 | - ticket_insert(&manifest, createFlag, 0); | |
| 240 | + ticket_insert(&manifest, createFlag, rid); | |
| 270 | 241 | manifest_ticket_event(rid, &manifest, createFlag, tagid); |
| 271 | 242 | manifest_clear(&manifest); |
| 272 | 243 | createFlag = 0; |
| 273 | 244 | } |
| 274 | 245 | db_finalize(&q); |
| @@ -325,35 +296,79 @@ | ||
| 325 | 296 | ** |
| 326 | 297 | ** View a ticket. |
| 327 | 298 | */ |
| 328 | 299 | void tktview_page(void){ |
| 329 | 300 | const char *zScript; |
| 301 | + char *zFullName; | |
| 302 | + const char *zUuid = PD("name",""); | |
| 303 | + | |
| 330 | 304 | login_check_credentials(); |
| 331 | 305 | if( !g.okRdTkt ){ login_needed(); return; } |
| 332 | 306 | if( g.okWrTkt || g.okApndTkt ){ |
| 333 | 307 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 334 | 308 | g.zTop, PD("name","")); |
| 335 | 309 | } |
| 336 | 310 | if( g.okHistory ){ |
| 337 | - const char *zUuid = PD("name",""); | |
| 338 | 311 | style_submenu_element("History", "History Of This Ticket", |
| 339 | 312 | "%s/tkthistory/%T", g.zTop, zUuid); |
| 340 | 313 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 341 | 314 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 315 | + style_submenu_element("Check-ins", "Check-ins Of This Ticket", | |
| 316 | + "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); | |
| 342 | 317 | } |
| 343 | 318 | if( g.okNewTkt ){ |
| 344 | 319 | style_submenu_element("New Ticket", "Create a new ticket", |
| 345 | 320 | "%s/tktnew", g.zTop); |
| 346 | 321 | } |
| 322 | + if( g.okApndTkt && g.okAttach ){ | |
| 323 | + style_submenu_element("Attach", "Add An Attachment", | |
| 324 | + "%s/attachadd?tkt=%T&from=%s/tktview%%3fname=%t", | |
| 325 | + g.zTop, zUuid, g.zTop, zUuid); | |
| 326 | + } | |
| 347 | 327 | style_header("View Ticket"); |
| 348 | 328 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); |
| 349 | 329 | ticket_init(); |
| 350 | 330 | initializeVariablesFromDb(); |
| 351 | 331 | zScript = ticket_viewpage_code(); |
| 352 | 332 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); |
| 353 | 333 | Th_Render(zScript); |
| 354 | 334 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); |
| 335 | + | |
| 336 | + zFullName = db_text(0, | |
| 337 | + "SELECT tkt_uuid FROM ticket" | |
| 338 | + " WHERE tkt_uuid GLOB '%q*'", zUuid); | |
| 339 | + if( zFullName ){ | |
| 340 | + int cnt = 0; | |
| 341 | + Stmt q; | |
| 342 | + db_prepare(&q, | |
| 343 | + "SELECT datetime(mtime,'localtime'), filename, user" | |
| 344 | + " FROM attachment" | |
| 345 | + " WHERE isLatest AND src!='' AND target=%Q" | |
| 346 | + " ORDER BY mtime DESC", | |
| 347 | + zFullName); | |
| 348 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 349 | + const char *zDate = db_column_text(&q, 0); | |
| 350 | + const char *zFile = db_column_text(&q, 1); | |
| 351 | + const char *zUser = db_column_text(&q, 2); | |
| 352 | + if( cnt==0 ){ | |
| 353 | + @ <hr><h2>Attachments:</h2> | |
| 354 | + @ <ul> | |
| 355 | + } | |
| 356 | + cnt++; | |
| 357 | + @ <li><a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&file=%t(zFile)"> | |
| 358 | + @ %h(zFile)</a> add by %h(zUser) on | |
| 359 | + hyperlink_to_date(zDate, "."); | |
| 360 | + if( g.okWrTkt && g.okAttach ){ | |
| 361 | + @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&file=%t(zFile)&from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>] | |
| 362 | + } | |
| 363 | + } | |
| 364 | + if( cnt ){ | |
| 365 | + @ </ul> | |
| 366 | + } | |
| 367 | + db_finalize(&q); | |
| 368 | + } | |
| 369 | + | |
| 355 | 370 | style_footer(); |
| 356 | 371 | } |
| 357 | 372 | |
| 358 | 373 | /* |
| 359 | 374 | ** TH command: append_field FIELD STRING |
| @@ -610,42 +625,78 @@ | ||
| 610 | 625 | return 0; |
| 611 | 626 | } |
| 612 | 627 | |
| 613 | 628 | /* |
| 614 | 629 | ** WEBPAGE: tkttimeline |
| 615 | -** URL: /tkttimeline?name=TICKETUUID | |
| 630 | +** URL: /tkttimeline?name=TICKETUUID&y=TYPE | |
| 616 | 631 | ** |
| 617 | 632 | ** Show the change history for a single ticket in timeline format. |
| 618 | 633 | */ |
| 619 | 634 | void tkttimeline_page(void){ |
| 620 | 635 | Stmt q; |
| 621 | 636 | char *zTitle; |
| 622 | 637 | char *zSQL; |
| 623 | 638 | const char *zUuid; |
| 639 | + char *zFullUuid; | |
| 624 | 640 | int tagid; |
| 641 | + char zGlobPattern[50]; | |
| 642 | + const char *zType; | |
| 625 | 643 | |
| 626 | 644 | login_check_credentials(); |
| 627 | 645 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 628 | 646 | zUuid = PD("name",""); |
| 647 | + zType = PD("y","a"); | |
| 648 | + if( zType[0]!='c' ){ | |
| 649 | + style_submenu_element("Check-ins", "Check-ins", | |
| 650 | + "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); | |
| 651 | + }else{ | |
| 652 | + style_submenu_element("Timeline", "Timeline", | |
| 653 | + "%s/tkttimeline?name=%T", g.zTop, zUuid); | |
| 654 | + } | |
| 629 | 655 | style_submenu_element("History", "History", |
| 630 | 656 | "%s/tkthistory/%s", g.zTop, zUuid); |
| 631 | 657 | style_submenu_element("Status", "Status", |
| 632 | 658 | "%s/info/%s", g.zTop, zUuid); |
| 633 | - zTitle = mprintf("Timeline Of Ticket %h", zUuid); | |
| 659 | + if( zType[0]=='c' ){ | |
| 660 | + zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid); | |
| 661 | + }else{ | |
| 662 | + zTitle = mprintf("Timeline Of Ticket %h", zUuid); | |
| 663 | + } | |
| 634 | 664 | style_header(zTitle); |
| 635 | 665 | free(zTitle); |
| 636 | 666 | |
| 667 | + sqlite3_snprintf(6, zGlobPattern, "%s", zUuid); | |
| 668 | + canonical16(zGlobPattern, strlen(zGlobPattern)); | |
| 637 | 669 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); |
| 638 | 670 | if( tagid==0 ){ |
| 639 | 671 | @ No such ticket: %h(zUuid) |
| 640 | 672 | style_footer(); |
| 641 | 673 | return; |
| 642 | 674 | } |
| 643 | - zSQL = mprintf("%s AND event.objid IN " | |
| 644 | - " (SELECT rid FROM tagxref WHERE tagid=%d) " | |
| 645 | - "ORDER BY mtime DESC", | |
| 646 | - timeline_query_for_www(), tagid); | |
| 675 | + zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d", | |
| 676 | + tagid); | |
| 677 | + if( zType[0]=='c' ){ | |
| 678 | + zSQL = mprintf( | |
| 679 | + "%s AND event.objid IN " | |
| 680 | + " (SELECT srcid FROM backlink WHERE target GLOB '%.4s*' " | |
| 681 | + "AND '%s' GLOB (target||'*')) " | |
| 682 | + "ORDER BY mtime DESC", | |
| 683 | + timeline_query_for_www(), zFullUuid, zFullUuid | |
| 684 | + ); | |
| 685 | + }else{ | |
| 686 | + zSQL = mprintf( | |
| 687 | + "%s AND event.objid IN " | |
| 688 | + " (SELECT rid FROM tagxref WHERE tagid=%d" | |
| 689 | + " UNION SELECT srcid FROM backlink" | |
| 690 | + " WHERE target GLOB '%.4s*'" | |
| 691 | + " AND '%s' GLOB (target||'*')" | |
| 692 | + " UNION SELECT attachid FROM attachment" | |
| 693 | + " WHERE target=%Q) " | |
| 694 | + "ORDER BY mtime DESC", | |
| 695 | + timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid | |
| 696 | + ); | |
| 697 | + } | |
| 647 | 698 | db_prepare(&q, zSQL); |
| 648 | 699 | free(zSQL); |
| 649 | 700 | www_print_timeline(&q, TIMELINE_ARTID, 0); |
| 650 | 701 | db_finalize(&q); |
| 651 | 702 | style_footer(); |
| @@ -667,10 +718,12 @@ | ||
| 667 | 718 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 668 | 719 | zUuid = PD("name",""); |
| 669 | 720 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 670 | 721 | style_submenu_element("Status", "Status", |
| 671 | 722 | "%s/info/%s", g.zTop, zUuid); |
| 723 | + style_submenu_element("Check-ins", "Check-ins", | |
| 724 | + "%s/tkttimeline?name=%s?y=ci", g.zTop, zUuid); | |
| 672 | 725 | style_submenu_element("Timeline", "Timeline", |
| 673 | 726 | "%s/tkttimeline?name=%s", g.zTop, zUuid); |
| 674 | 727 | style_header(zTitle); |
| 675 | 728 | free(zTitle); |
| 676 | 729 | |
| @@ -679,37 +732,60 @@ | ||
| 679 | 732 | @ No such ticket: %h(zUuid) |
| 680 | 733 | style_footer(); |
| 681 | 734 | return; |
| 682 | 735 | } |
| 683 | 736 | db_prepare(&q, |
| 684 | - "SELECT objid, uuid FROM event, blob" | |
| 737 | + "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" | |
| 738 | + " FROM event, blob" | |
| 685 | 739 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 686 | 740 | " AND blob.rid=event.objid" |
| 687 | - " ORDER BY mtime DESC", | |
| 688 | - tagid | |
| 741 | + " UNION " | |
| 742 | + "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" | |
| 743 | + " FROM attachment, blob" | |
| 744 | + " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" | |
| 745 | + " AND blob.rid=attachid" | |
| 746 | + " ORDER BY 1 DESC", | |
| 747 | + tagid, tagid | |
| 689 | 748 | ); |
| 690 | 749 | while( db_step(&q)==SQLITE_ROW ){ |
| 691 | 750 | Blob content; |
| 692 | 751 | Manifest m; |
| 693 | - int rid = db_column_int(&q, 0); | |
| 694 | - const char *zChngUuid = db_column_text(&q, 1); | |
| 695 | - content_get(rid, &content); | |
| 696 | - if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ | |
| 697 | - char *zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); | |
| 698 | - char zUuid[12]; | |
| 699 | - memcpy(zUuid, zChngUuid, 10); | |
| 700 | - zUuid[10] = 0; | |
| 701 | - @ | |
| 702 | - @ Ticket change | |
| 703 | - @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zUuid)</a>]</a> | |
| 752 | + char zShort[12]; | |
| 753 | + const char *zDate = db_column_text(&q, 0); | |
| 754 | + int rid = db_column_int(&q, 1); | |
| 755 | + const char *zChngUuid = db_column_text(&q, 2); | |
| 756 | + const char *zFile = db_column_text(&q, 4); | |
| 757 | + memcpy(zShort, zChngUuid, 10); | |
| 758 | + zShort[10] = 0; | |
| 759 | + if( zFile!=0 ){ | |
| 760 | + const char *zSrc = db_column_text(&q, 3); | |
| 761 | + const char *zUser = db_column_text(&q, 5); | |
| 762 | + if( zSrc==0 || zSrc[0]==0 ){ | |
| 763 | + @ | |
| 764 | + @ <p>Delete attachment "%h(zFile)" | |
| 765 | + }else{ | |
| 766 | + @ | |
| 767 | + @ <p>Add attachment "%h(zFile)" | |
| 768 | + } | |
| 769 | + @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] | |
| 704 | 770 | @ (rid %d(rid)) by |
| 705 | - hyperlink_to_user(m.zUser,zDate," on"); | |
| 706 | - hyperlink_to_date(zDate, ":"); | |
| 707 | - free(zDate); | |
| 708 | - ticket_output_change_artifact(&m); | |
| 771 | + hyperlink_to_user(zUser,zDate," on"); | |
| 772 | + hyperlink_to_date(zDate, ".</p>"); | |
| 773 | + }else{ | |
| 774 | + content_get(rid, &content); | |
| 775 | + if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ | |
| 776 | + @ | |
| 777 | + @ <p>Ticket change | |
| 778 | + @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] | |
| 779 | + @ (rid %d(rid)) by | |
| 780 | + hyperlink_to_user(m.zUser,zDate," on"); | |
| 781 | + hyperlink_to_date(zDate, ":"); | |
| 782 | + ticket_output_change_artifact(&m); | |
| 783 | + @ </p> | |
| 784 | + } | |
| 785 | + manifest_clear(&m); | |
| 709 | 786 | } |
| 710 | - manifest_clear(&m); | |
| 711 | 787 | } |
| 712 | 788 | db_finalize(&q); |
| 713 | 789 | style_footer(); |
| 714 | 790 | } |
| 715 | 791 | |
| 716 | 792 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -165,32 +165,10 @@ | |
| 165 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 166 | Th_Store(z, P(z)); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | ** Rebuild all tickets named in the _pending_ticket table. |
| 172 | ** |
| 173 | ** This routine is called just prior to commit after new |
| 174 | ** out-of-sequence ticket changes have been added. |
| 175 | */ |
| 176 | static int ticket_rebuild_at_commit(void){ |
| 177 | Stmt q; |
| 178 | db_multi_exec( |
| 179 | "DELETE FROM ticket WHERE tkt_uuid IN _pending_ticket" |
| 180 | ); |
| 181 | db_prepare(&q, "SELECT uuid FROM _pending_ticket"); |
| 182 | while( db_step(&q)==SQLITE_ROW ){ |
| 183 | const char *zUuid = db_column_text(&q, 0); |
| 184 | ticket_rebuild_entry(zUuid); |
| 185 | } |
| 186 | db_multi_exec( |
| 187 | "DELETE FROM _pending_ticket" |
| 188 | ); |
| 189 | return 0; |
| 190 | } |
| 191 | |
| 192 | /* |
| 193 | ** Update an entry of the TICKET table according to the information |
| 194 | ** in the control file given in p. Attempt to create the appropriate |
| 195 | ** TICKET table entry if createFlag is true. If createFlag is false, |
| 196 | ** that means we already know the entry exists and so we can save the |
| @@ -197,11 +175,11 @@ | |
| 197 | ** work of trying to create it. |
| 198 | ** |
| 199 | ** Return TRUE if a new TICKET entry was created and FALSE if an |
| 200 | ** existing entry was revised. |
| 201 | */ |
| 202 | int ticket_insert(const Manifest *p, int createFlag, int checkTime){ |
| 203 | Blob sql; |
| 204 | Stmt q; |
| 205 | int i; |
| 206 | const char *zSep; |
| 207 | int rc = 0; |
| @@ -224,27 +202,20 @@ | |
| 224 | zName, zName, p->aField[i].zValue); |
| 225 | }else{ |
| 226 | if( fieldId(zName)<0 ) continue; |
| 227 | blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue); |
| 228 | } |
| 229 | } |
| 230 | blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime", |
| 231 | p->zTicketUuid); |
| 232 | db_prepare(&q, "%s", blob_str(&sql)); |
| 233 | db_bind_double(&q, ":mtime", p->rDate); |
| 234 | db_step(&q); |
| 235 | db_finalize(&q); |
| 236 | if( checkTime && db_changes()==0 ){ |
| 237 | static int isInit = 0; |
| 238 | if( !isInit ){ |
| 239 | db_multi_exec("CREATE TEMP TABLE _pending_ticket(uuid TEXT UNIQUE)"); |
| 240 | db_commit_hook(ticket_rebuild_at_commit, 1); |
| 241 | isInit = 1; |
| 242 | } |
| 243 | db_multi_exec("INSERT OR IGNORE INTO _pending_ticket " |
| 244 | "VALUES(%Q)", p->zTicketUuid); |
| 245 | } |
| 246 | blob_reset(&sql); |
| 247 | return rc; |
| 248 | } |
| 249 | |
| 250 | /* |
| @@ -264,11 +235,11 @@ | |
| 264 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 265 | while( db_step(&q)==SQLITE_ROW ){ |
| 266 | int rid = db_column_int(&q, 0); |
| 267 | content_get(rid, &content); |
| 268 | manifest_parse(&manifest, &content); |
| 269 | ticket_insert(&manifest, createFlag, 0); |
| 270 | manifest_ticket_event(rid, &manifest, createFlag, tagid); |
| 271 | manifest_clear(&manifest); |
| 272 | createFlag = 0; |
| 273 | } |
| 274 | db_finalize(&q); |
| @@ -325,35 +296,79 @@ | |
| 325 | ** |
| 326 | ** View a ticket. |
| 327 | */ |
| 328 | void tktview_page(void){ |
| 329 | const char *zScript; |
| 330 | login_check_credentials(); |
| 331 | if( !g.okRdTkt ){ login_needed(); return; } |
| 332 | if( g.okWrTkt || g.okApndTkt ){ |
| 333 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 334 | g.zTop, PD("name","")); |
| 335 | } |
| 336 | if( g.okHistory ){ |
| 337 | const char *zUuid = PD("name",""); |
| 338 | style_submenu_element("History", "History Of This Ticket", |
| 339 | "%s/tkthistory/%T", g.zTop, zUuid); |
| 340 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 341 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 342 | } |
| 343 | if( g.okNewTkt ){ |
| 344 | style_submenu_element("New Ticket", "Create a new ticket", |
| 345 | "%s/tktnew", g.zTop); |
| 346 | } |
| 347 | style_header("View Ticket"); |
| 348 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); |
| 349 | ticket_init(); |
| 350 | initializeVariablesFromDb(); |
| 351 | zScript = ticket_viewpage_code(); |
| 352 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); |
| 353 | Th_Render(zScript); |
| 354 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); |
| 355 | style_footer(); |
| 356 | } |
| 357 | |
| 358 | /* |
| 359 | ** TH command: append_field FIELD STRING |
| @@ -610,42 +625,78 @@ | |
| 610 | return 0; |
| 611 | } |
| 612 | |
| 613 | /* |
| 614 | ** WEBPAGE: tkttimeline |
| 615 | ** URL: /tkttimeline?name=TICKETUUID |
| 616 | ** |
| 617 | ** Show the change history for a single ticket in timeline format. |
| 618 | */ |
| 619 | void tkttimeline_page(void){ |
| 620 | Stmt q; |
| 621 | char *zTitle; |
| 622 | char *zSQL; |
| 623 | const char *zUuid; |
| 624 | int tagid; |
| 625 | |
| 626 | login_check_credentials(); |
| 627 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 628 | zUuid = PD("name",""); |
| 629 | style_submenu_element("History", "History", |
| 630 | "%s/tkthistory/%s", g.zTop, zUuid); |
| 631 | style_submenu_element("Status", "Status", |
| 632 | "%s/info/%s", g.zTop, zUuid); |
| 633 | zTitle = mprintf("Timeline Of Ticket %h", zUuid); |
| 634 | style_header(zTitle); |
| 635 | free(zTitle); |
| 636 | |
| 637 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); |
| 638 | if( tagid==0 ){ |
| 639 | @ No such ticket: %h(zUuid) |
| 640 | style_footer(); |
| 641 | return; |
| 642 | } |
| 643 | zSQL = mprintf("%s AND event.objid IN " |
| 644 | " (SELECT rid FROM tagxref WHERE tagid=%d) " |
| 645 | "ORDER BY mtime DESC", |
| 646 | timeline_query_for_www(), tagid); |
| 647 | db_prepare(&q, zSQL); |
| 648 | free(zSQL); |
| 649 | www_print_timeline(&q, TIMELINE_ARTID, 0); |
| 650 | db_finalize(&q); |
| 651 | style_footer(); |
| @@ -667,10 +718,12 @@ | |
| 667 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 668 | zUuid = PD("name",""); |
| 669 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 670 | style_submenu_element("Status", "Status", |
| 671 | "%s/info/%s", g.zTop, zUuid); |
| 672 | style_submenu_element("Timeline", "Timeline", |
| 673 | "%s/tkttimeline?name=%s", g.zTop, zUuid); |
| 674 | style_header(zTitle); |
| 675 | free(zTitle); |
| 676 | |
| @@ -679,37 +732,60 @@ | |
| 679 | @ No such ticket: %h(zUuid) |
| 680 | style_footer(); |
| 681 | return; |
| 682 | } |
| 683 | db_prepare(&q, |
| 684 | "SELECT objid, uuid FROM event, blob" |
| 685 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 686 | " AND blob.rid=event.objid" |
| 687 | " ORDER BY mtime DESC", |
| 688 | tagid |
| 689 | ); |
| 690 | while( db_step(&q)==SQLITE_ROW ){ |
| 691 | Blob content; |
| 692 | Manifest m; |
| 693 | int rid = db_column_int(&q, 0); |
| 694 | const char *zChngUuid = db_column_text(&q, 1); |
| 695 | content_get(rid, &content); |
| 696 | if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ |
| 697 | char *zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); |
| 698 | char zUuid[12]; |
| 699 | memcpy(zUuid, zChngUuid, 10); |
| 700 | zUuid[10] = 0; |
| 701 | @ |
| 702 | @ Ticket change |
| 703 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zUuid)</a>]</a> |
| 704 | @ (rid %d(rid)) by |
| 705 | hyperlink_to_user(m.zUser,zDate," on"); |
| 706 | hyperlink_to_date(zDate, ":"); |
| 707 | free(zDate); |
| 708 | ticket_output_change_artifact(&m); |
| 709 | } |
| 710 | manifest_clear(&m); |
| 711 | } |
| 712 | db_finalize(&q); |
| 713 | style_footer(); |
| 714 | } |
| 715 | |
| 716 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -165,32 +165,10 @@ | |
| 165 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 166 | Th_Store(z, P(z)); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | ** Update an entry of the TICKET table according to the information |
| 172 | ** in the control file given in p. Attempt to create the appropriate |
| 173 | ** TICKET table entry if createFlag is true. If createFlag is false, |
| 174 | ** that means we already know the entry exists and so we can save the |
| @@ -197,11 +175,11 @@ | |
| 175 | ** work of trying to create it. |
| 176 | ** |
| 177 | ** Return TRUE if a new TICKET entry was created and FALSE if an |
| 178 | ** existing entry was revised. |
| 179 | */ |
| 180 | int ticket_insert(const Manifest *p, int createFlag, int rid){ |
| 181 | Blob sql; |
| 182 | Stmt q; |
| 183 | int i; |
| 184 | const char *zSep; |
| 185 | int rc = 0; |
| @@ -224,27 +202,20 @@ | |
| 202 | zName, zName, p->aField[i].zValue); |
| 203 | }else{ |
| 204 | if( fieldId(zName)<0 ) continue; |
| 205 | blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue); |
| 206 | } |
| 207 | if( rid>0 ){ |
| 208 | wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0); |
| 209 | } |
| 210 | } |
| 211 | blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime", |
| 212 | p->zTicketUuid); |
| 213 | db_prepare(&q, "%s", blob_str(&sql)); |
| 214 | db_bind_double(&q, ":mtime", p->rDate); |
| 215 | db_step(&q); |
| 216 | db_finalize(&q); |
| 217 | blob_reset(&sql); |
| 218 | return rc; |
| 219 | } |
| 220 | |
| 221 | /* |
| @@ -264,11 +235,11 @@ | |
| 235 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 236 | while( db_step(&q)==SQLITE_ROW ){ |
| 237 | int rid = db_column_int(&q, 0); |
| 238 | content_get(rid, &content); |
| 239 | manifest_parse(&manifest, &content); |
| 240 | ticket_insert(&manifest, createFlag, rid); |
| 241 | manifest_ticket_event(rid, &manifest, createFlag, tagid); |
| 242 | manifest_clear(&manifest); |
| 243 | createFlag = 0; |
| 244 | } |
| 245 | db_finalize(&q); |
| @@ -325,35 +296,79 @@ | |
| 296 | ** |
| 297 | ** View a ticket. |
| 298 | */ |
| 299 | void tktview_page(void){ |
| 300 | const char *zScript; |
| 301 | char *zFullName; |
| 302 | const char *zUuid = PD("name",""); |
| 303 | |
| 304 | login_check_credentials(); |
| 305 | if( !g.okRdTkt ){ login_needed(); return; } |
| 306 | if( g.okWrTkt || g.okApndTkt ){ |
| 307 | style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", |
| 308 | g.zTop, PD("name","")); |
| 309 | } |
| 310 | if( g.okHistory ){ |
| 311 | style_submenu_element("History", "History Of This Ticket", |
| 312 | "%s/tkthistory/%T", g.zTop, zUuid); |
| 313 | style_submenu_element("Timeline", "Timeline Of This Ticket", |
| 314 | "%s/tkttimeline/%T", g.zTop, zUuid); |
| 315 | style_submenu_element("Check-ins", "Check-ins Of This Ticket", |
| 316 | "%s/tkttimeline/%T?y=ci", g.zTop, zUuid); |
| 317 | } |
| 318 | if( g.okNewTkt ){ |
| 319 | style_submenu_element("New Ticket", "Create a new ticket", |
| 320 | "%s/tktnew", g.zTop); |
| 321 | } |
| 322 | if( g.okApndTkt && g.okAttach ){ |
| 323 | style_submenu_element("Attach", "Add An Attachment", |
| 324 | "%s/attachadd?tkt=%T&from=%s/tktview%%3fname=%t", |
| 325 | g.zTop, zUuid, g.zTop, zUuid); |
| 326 | } |
| 327 | style_header("View Ticket"); |
| 328 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); |
| 329 | ticket_init(); |
| 330 | initializeVariablesFromDb(); |
| 331 | zScript = ticket_viewpage_code(); |
| 332 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); |
| 333 | Th_Render(zScript); |
| 334 | if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); |
| 335 | |
| 336 | zFullName = db_text(0, |
| 337 | "SELECT tkt_uuid FROM ticket" |
| 338 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 339 | if( zFullName ){ |
| 340 | int cnt = 0; |
| 341 | Stmt q; |
| 342 | db_prepare(&q, |
| 343 | "SELECT datetime(mtime,'localtime'), filename, user" |
| 344 | " FROM attachment" |
| 345 | " WHERE isLatest AND src!='' AND target=%Q" |
| 346 | " ORDER BY mtime DESC", |
| 347 | zFullName); |
| 348 | while( db_step(&q)==SQLITE_ROW ){ |
| 349 | const char *zDate = db_column_text(&q, 0); |
| 350 | const char *zFile = db_column_text(&q, 1); |
| 351 | const char *zUser = db_column_text(&q, 2); |
| 352 | if( cnt==0 ){ |
| 353 | @ <hr><h2>Attachments:</h2> |
| 354 | @ <ul> |
| 355 | } |
| 356 | cnt++; |
| 357 | @ <li><a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&file=%t(zFile)"> |
| 358 | @ %h(zFile)</a> add by %h(zUser) on |
| 359 | hyperlink_to_date(zDate, "."); |
| 360 | if( g.okWrTkt && g.okAttach ){ |
| 361 | @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&file=%t(zFile)&from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>] |
| 362 | } |
| 363 | } |
| 364 | if( cnt ){ |
| 365 | @ </ul> |
| 366 | } |
| 367 | db_finalize(&q); |
| 368 | } |
| 369 | |
| 370 | style_footer(); |
| 371 | } |
| 372 | |
| 373 | /* |
| 374 | ** TH command: append_field FIELD STRING |
| @@ -610,42 +625,78 @@ | |
| 625 | return 0; |
| 626 | } |
| 627 | |
| 628 | /* |
| 629 | ** WEBPAGE: tkttimeline |
| 630 | ** URL: /tkttimeline?name=TICKETUUID&y=TYPE |
| 631 | ** |
| 632 | ** Show the change history for a single ticket in timeline format. |
| 633 | */ |
| 634 | void tkttimeline_page(void){ |
| 635 | Stmt q; |
| 636 | char *zTitle; |
| 637 | char *zSQL; |
| 638 | const char *zUuid; |
| 639 | char *zFullUuid; |
| 640 | int tagid; |
| 641 | char zGlobPattern[50]; |
| 642 | const char *zType; |
| 643 | |
| 644 | login_check_credentials(); |
| 645 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 646 | zUuid = PD("name",""); |
| 647 | zType = PD("y","a"); |
| 648 | if( zType[0]!='c' ){ |
| 649 | style_submenu_element("Check-ins", "Check-ins", |
| 650 | "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); |
| 651 | }else{ |
| 652 | style_submenu_element("Timeline", "Timeline", |
| 653 | "%s/tkttimeline?name=%T", g.zTop, zUuid); |
| 654 | } |
| 655 | style_submenu_element("History", "History", |
| 656 | "%s/tkthistory/%s", g.zTop, zUuid); |
| 657 | style_submenu_element("Status", "Status", |
| 658 | "%s/info/%s", g.zTop, zUuid); |
| 659 | if( zType[0]=='c' ){ |
| 660 | zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid); |
| 661 | }else{ |
| 662 | zTitle = mprintf("Timeline Of Ticket %h", zUuid); |
| 663 | } |
| 664 | style_header(zTitle); |
| 665 | free(zTitle); |
| 666 | |
| 667 | sqlite3_snprintf(6, zGlobPattern, "%s", zUuid); |
| 668 | canonical16(zGlobPattern, strlen(zGlobPattern)); |
| 669 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid); |
| 670 | if( tagid==0 ){ |
| 671 | @ No such ticket: %h(zUuid) |
| 672 | style_footer(); |
| 673 | return; |
| 674 | } |
| 675 | zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d", |
| 676 | tagid); |
| 677 | if( zType[0]=='c' ){ |
| 678 | zSQL = mprintf( |
| 679 | "%s AND event.objid IN " |
| 680 | " (SELECT srcid FROM backlink WHERE target GLOB '%.4s*' " |
| 681 | "AND '%s' GLOB (target||'*')) " |
| 682 | "ORDER BY mtime DESC", |
| 683 | timeline_query_for_www(), zFullUuid, zFullUuid |
| 684 | ); |
| 685 | }else{ |
| 686 | zSQL = mprintf( |
| 687 | "%s AND event.objid IN " |
| 688 | " (SELECT rid FROM tagxref WHERE tagid=%d" |
| 689 | " UNION SELECT srcid FROM backlink" |
| 690 | " WHERE target GLOB '%.4s*'" |
| 691 | " AND '%s' GLOB (target||'*')" |
| 692 | " UNION SELECT attachid FROM attachment" |
| 693 | " WHERE target=%Q) " |
| 694 | "ORDER BY mtime DESC", |
| 695 | timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid |
| 696 | ); |
| 697 | } |
| 698 | db_prepare(&q, zSQL); |
| 699 | free(zSQL); |
| 700 | www_print_timeline(&q, TIMELINE_ARTID, 0); |
| 701 | db_finalize(&q); |
| 702 | style_footer(); |
| @@ -667,10 +718,12 @@ | |
| 718 | if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; } |
| 719 | zUuid = PD("name",""); |
| 720 | zTitle = mprintf("History Of Ticket %h", zUuid); |
| 721 | style_submenu_element("Status", "Status", |
| 722 | "%s/info/%s", g.zTop, zUuid); |
| 723 | style_submenu_element("Check-ins", "Check-ins", |
| 724 | "%s/tkttimeline?name=%s?y=ci", g.zTop, zUuid); |
| 725 | style_submenu_element("Timeline", "Timeline", |
| 726 | "%s/tkttimeline?name=%s", g.zTop, zUuid); |
| 727 | style_header(zTitle); |
| 728 | free(zTitle); |
| 729 | |
| @@ -679,37 +732,60 @@ | |
| 732 | @ No such ticket: %h(zUuid) |
| 733 | style_footer(); |
| 734 | return; |
| 735 | } |
| 736 | db_prepare(&q, |
| 737 | "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" |
| 738 | " FROM event, blob" |
| 739 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 740 | " AND blob.rid=event.objid" |
| 741 | " UNION " |
| 742 | "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" |
| 743 | " FROM attachment, blob" |
| 744 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 745 | " AND blob.rid=attachid" |
| 746 | " ORDER BY 1 DESC", |
| 747 | tagid, tagid |
| 748 | ); |
| 749 | while( db_step(&q)==SQLITE_ROW ){ |
| 750 | Blob content; |
| 751 | Manifest m; |
| 752 | char zShort[12]; |
| 753 | const char *zDate = db_column_text(&q, 0); |
| 754 | int rid = db_column_int(&q, 1); |
| 755 | const char *zChngUuid = db_column_text(&q, 2); |
| 756 | const char *zFile = db_column_text(&q, 4); |
| 757 | memcpy(zShort, zChngUuid, 10); |
| 758 | zShort[10] = 0; |
| 759 | if( zFile!=0 ){ |
| 760 | const char *zSrc = db_column_text(&q, 3); |
| 761 | const char *zUser = db_column_text(&q, 5); |
| 762 | if( zSrc==0 || zSrc[0]==0 ){ |
| 763 | @ |
| 764 | @ <p>Delete attachment "%h(zFile)" |
| 765 | }else{ |
| 766 | @ |
| 767 | @ <p>Add attachment "%h(zFile)" |
| 768 | } |
| 769 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 770 | @ (rid %d(rid)) by |
| 771 | hyperlink_to_user(zUser,zDate," on"); |
| 772 | hyperlink_to_date(zDate, ".</p>"); |
| 773 | }else{ |
| 774 | content_get(rid, &content); |
| 775 | if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ |
| 776 | @ |
| 777 | @ <p>Ticket change |
| 778 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 779 | @ (rid %d(rid)) by |
| 780 | hyperlink_to_user(m.zUser,zDate," on"); |
| 781 | hyperlink_to_date(zDate, ":"); |
| 782 | ticket_output_change_artifact(&m); |
| 783 | @ </p> |
| 784 | } |
| 785 | manifest_clear(&m); |
| 786 | } |
| 787 | } |
| 788 | db_finalize(&q); |
| 789 | style_footer(); |
| 790 | } |
| 791 | |
| 792 |
+70
-8
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -127,10 +127,12 @@ | ||
| 127 | 127 | int isSandbox; |
| 128 | 128 | Blob wiki; |
| 129 | 129 | Manifest m; |
| 130 | 130 | const char *zPageName; |
| 131 | 131 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 132 | + Stmt q; | |
| 133 | + int cnt = 0; | |
| 132 | 134 | |
| 133 | 135 | login_check_credentials(); |
| 134 | 136 | if( !g.okRdWiki ){ login_needed(); return; } |
| 135 | 137 | zPageName = P("name"); |
| 136 | 138 | if( zPageName==0 ){ |
| @@ -152,11 +154,12 @@ | ||
| 152 | 154 | @ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li> |
| 153 | 155 | } |
| 154 | 156 | @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a> |
| 155 | 157 | @ available on this server.</li> |
| 156 | 158 | @ <li> <form method="GET" action="%s(g.zBaseURL)/wfind"> |
| 157 | - @ Search wiki titles: <input type="text" name="title"/> <input type="submit" /> | |
| 159 | + @ Search wiki titles: <input type="text" name="title"/> | |
| 160 | + @ <input type="submit" /> | |
| 158 | 161 | @ </li> |
| 159 | 162 | @ </ul> |
| 160 | 163 | style_footer(); |
| 161 | 164 | return; |
| 162 | 165 | } |
| @@ -176,20 +179,26 @@ | ||
| 176 | 179 | blob_zero(&m.content); |
| 177 | 180 | if( rid ){ |
| 178 | 181 | Blob content; |
| 179 | 182 | content_get(rid, &content); |
| 180 | 183 | manifest_parse(&m, &content); |
| 181 | - if( m.type==CFTYPE_WIKI ){ | |
| 182 | - zBody = m.zWiki; | |
| 184 | + if( m.type==CFTYPE_WIKI && m.zWiki ){ | |
| 185 | + while( isspace(m.zWiki[0]) ) m.zWiki++; | |
| 186 | + if( m.zWiki[0] ) zBody = m.zWiki; | |
| 183 | 187 | } |
| 184 | 188 | } |
| 185 | 189 | } |
| 186 | 190 | if( !g.isHome ){ |
| 187 | 191 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 188 | 192 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| 189 | 193 | g.zTop, zPageName); |
| 190 | 194 | } |
| 195 | + if( rid && g.okApndWiki && g.okAttach ){ | |
| 196 | + style_submenu_element("Attach", "Add An Attachment", | |
| 197 | + "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", | |
| 198 | + g.zTop, zPageName, g.zTop, zPageName); | |
| 199 | + } | |
| 191 | 200 | if( rid && g.okApndWiki ){ |
| 192 | 201 | style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", |
| 193 | 202 | g.zTop, zPageName); |
| 194 | 203 | } |
| 195 | 204 | if( g.okHistory ){ |
| @@ -199,10 +208,42 @@ | ||
| 199 | 208 | } |
| 200 | 209 | style_header(zPageName); |
| 201 | 210 | blob_init(&wiki, zBody, -1); |
| 202 | 211 | wiki_convert(&wiki, 0, 0); |
| 203 | 212 | blob_reset(&wiki); |
| 213 | + | |
| 214 | + db_prepare(&q, | |
| 215 | + "SELECT datetime(mtime,'localtime'), filename, user" | |
| 216 | + " FROM attachment" | |
| 217 | + " WHERE isLatest AND src!='' AND target=%Q" | |
| 218 | + " ORDER BY mtime DESC", | |
| 219 | + zPageName); | |
| 220 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 221 | + const char *zDate = db_column_text(&q, 0); | |
| 222 | + const char *zFile = db_column_text(&q, 1); | |
| 223 | + const char *zUser = db_column_text(&q, 2); | |
| 224 | + if( cnt==0 ){ | |
| 225 | + @ <hr><h2>Attachments:</h2> | |
| 226 | + @ <ul> | |
| 227 | + } | |
| 228 | + cnt++; | |
| 229 | + if( g.okHistory ){ | |
| 230 | + @ <li><a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)"> | |
| 231 | + }else{ | |
| 232 | + @ <li> | |
| 233 | + } | |
| 234 | + @ %h(zFile)</a> add by %h(zUser) on | |
| 235 | + hyperlink_to_date(zDate, "."); | |
| 236 | + if( g.okWrWiki && g.okAttach ){ | |
| 237 | + @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>] | |
| 238 | + } | |
| 239 | + } | |
| 240 | + if( cnt ){ | |
| 241 | + @ </ul> | |
| 242 | + } | |
| 243 | + db_finalize(&q); | |
| 244 | + | |
| 204 | 245 | if( !isSandbox ){ |
| 205 | 246 | manifest_clear(&m); |
| 206 | 247 | } |
| 207 | 248 | style_footer(); |
| 208 | 249 | } |
| @@ -515,11 +556,13 @@ | ||
| 515 | 556 | /* |
| 516 | 557 | ** Function called to output extra text at the end of each line in |
| 517 | 558 | ** a wiki history listing. |
| 518 | 559 | */ |
| 519 | 560 | static void wiki_history_extra(int rid){ |
| 520 | - @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a> | |
| 561 | + if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){ | |
| 562 | + @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a> | |
| 563 | + } | |
| 521 | 564 | } |
| 522 | 565 | |
| 523 | 566 | /* |
| 524 | 567 | ** WEBPAGE: whistory |
| 525 | 568 | ** URL: /whistory?name=PAGENAME |
| @@ -538,13 +581,15 @@ | ||
| 538 | 581 | style_header(zTitle); |
| 539 | 582 | free(zTitle); |
| 540 | 583 | |
| 541 | 584 | zSQL = mprintf("%s AND event.objid IN " |
| 542 | 585 | " (SELECT rid FROM tagxref WHERE tagid=" |
| 543 | - "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))" | |
| 586 | + "(SELECT tagid FROM tag WHERE tagname='wiki-%q')" | |
| 587 | + " UNION SELECT attachid FROM attachment" | |
| 588 | + " WHERE target=%Q)" | |
| 544 | 589 | "ORDER BY mtime DESC", |
| 545 | - timeline_query_for_www(), zPageName); | |
| 590 | + timeline_query_for_www(), zPageName, zPageName); | |
| 546 | 591 | db_prepare(&q, zSQL); |
| 547 | 592 | free(zSQL); |
| 548 | 593 | zWikiPageName = zPageName; |
| 549 | 594 | www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra); |
| 550 | 595 | db_finalize(&q); |
| @@ -604,26 +649,43 @@ | ||
| 604 | 649 | style_footer(); |
| 605 | 650 | } |
| 606 | 651 | |
| 607 | 652 | /* |
| 608 | 653 | ** WEBPAGE: wcontent |
| 654 | +** | |
| 655 | +** all=1 Show deleted pages | |
| 609 | 656 | ** |
| 610 | 657 | ** List all available wiki pages with date created and last modified. |
| 611 | 658 | */ |
| 612 | 659 | void wcontent_page(void){ |
| 613 | 660 | Stmt q; |
| 661 | + int showAll = P("all")!=0; | |
| 662 | + | |
| 614 | 663 | login_check_credentials(); |
| 615 | 664 | if( !g.okRdWiki ){ login_needed(); return; } |
| 616 | 665 | style_header("Available Wiki Pages"); |
| 666 | + if( showAll ){ | |
| 667 | + style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); | |
| 668 | + }else{ | |
| 669 | + style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); | |
| 670 | + } | |
| 617 | 671 | @ <ul> |
| 618 | 672 | db_prepare(&q, |
| 619 | - "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'" | |
| 673 | + "SELECT" | |
| 674 | + " substr(tagname, 6)," | |
| 675 | + " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)" | |
| 676 | + " FROM tag WHERE tagname GLOB 'wiki-*'" | |
| 620 | 677 | " ORDER BY lower(tagname)" |
| 621 | 678 | ); |
| 622 | 679 | while( db_step(&q)==SQLITE_ROW ){ |
| 623 | 680 | const char *zName = db_column_text(&q, 0); |
| 624 | - @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li> | |
| 681 | + int size = db_column_int(&q, 1); | |
| 682 | + if( size>0 ){ | |
| 683 | + @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li> | |
| 684 | + }else if( showAll ){ | |
| 685 | + @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li> | |
| 686 | + } | |
| 625 | 687 | } |
| 626 | 688 | db_finalize(&q); |
| 627 | 689 | @ </ul> |
| 628 | 690 | style_footer(); |
| 629 | 691 | } |
| 630 | 692 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -127,10 +127,12 @@ | |
| 127 | int isSandbox; |
| 128 | Blob wiki; |
| 129 | Manifest m; |
| 130 | const char *zPageName; |
| 131 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 132 | |
| 133 | login_check_credentials(); |
| 134 | if( !g.okRdWiki ){ login_needed(); return; } |
| 135 | zPageName = P("name"); |
| 136 | if( zPageName==0 ){ |
| @@ -152,11 +154,12 @@ | |
| 152 | @ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li> |
| 153 | } |
| 154 | @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a> |
| 155 | @ available on this server.</li> |
| 156 | @ <li> <form method="GET" action="%s(g.zBaseURL)/wfind"> |
| 157 | @ Search wiki titles: <input type="text" name="title"/> <input type="submit" /> |
| 158 | @ </li> |
| 159 | @ </ul> |
| 160 | style_footer(); |
| 161 | return; |
| 162 | } |
| @@ -176,20 +179,26 @@ | |
| 176 | blob_zero(&m.content); |
| 177 | if( rid ){ |
| 178 | Blob content; |
| 179 | content_get(rid, &content); |
| 180 | manifest_parse(&m, &content); |
| 181 | if( m.type==CFTYPE_WIKI ){ |
| 182 | zBody = m.zWiki; |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | if( !g.isHome ){ |
| 187 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 188 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| 189 | g.zTop, zPageName); |
| 190 | } |
| 191 | if( rid && g.okApndWiki ){ |
| 192 | style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", |
| 193 | g.zTop, zPageName); |
| 194 | } |
| 195 | if( g.okHistory ){ |
| @@ -199,10 +208,42 @@ | |
| 199 | } |
| 200 | style_header(zPageName); |
| 201 | blob_init(&wiki, zBody, -1); |
| 202 | wiki_convert(&wiki, 0, 0); |
| 203 | blob_reset(&wiki); |
| 204 | if( !isSandbox ){ |
| 205 | manifest_clear(&m); |
| 206 | } |
| 207 | style_footer(); |
| 208 | } |
| @@ -515,11 +556,13 @@ | |
| 515 | /* |
| 516 | ** Function called to output extra text at the end of each line in |
| 517 | ** a wiki history listing. |
| 518 | */ |
| 519 | static void wiki_history_extra(int rid){ |
| 520 | @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a> |
| 521 | } |
| 522 | |
| 523 | /* |
| 524 | ** WEBPAGE: whistory |
| 525 | ** URL: /whistory?name=PAGENAME |
| @@ -538,13 +581,15 @@ | |
| 538 | style_header(zTitle); |
| 539 | free(zTitle); |
| 540 | |
| 541 | zSQL = mprintf("%s AND event.objid IN " |
| 542 | " (SELECT rid FROM tagxref WHERE tagid=" |
| 543 | "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))" |
| 544 | "ORDER BY mtime DESC", |
| 545 | timeline_query_for_www(), zPageName); |
| 546 | db_prepare(&q, zSQL); |
| 547 | free(zSQL); |
| 548 | zWikiPageName = zPageName; |
| 549 | www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra); |
| 550 | db_finalize(&q); |
| @@ -604,26 +649,43 @@ | |
| 604 | style_footer(); |
| 605 | } |
| 606 | |
| 607 | /* |
| 608 | ** WEBPAGE: wcontent |
| 609 | ** |
| 610 | ** List all available wiki pages with date created and last modified. |
| 611 | */ |
| 612 | void wcontent_page(void){ |
| 613 | Stmt q; |
| 614 | login_check_credentials(); |
| 615 | if( !g.okRdWiki ){ login_needed(); return; } |
| 616 | style_header("Available Wiki Pages"); |
| 617 | @ <ul> |
| 618 | db_prepare(&q, |
| 619 | "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'" |
| 620 | " ORDER BY lower(tagname)" |
| 621 | ); |
| 622 | while( db_step(&q)==SQLITE_ROW ){ |
| 623 | const char *zName = db_column_text(&q, 0); |
| 624 | @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li> |
| 625 | } |
| 626 | db_finalize(&q); |
| 627 | @ </ul> |
| 628 | style_footer(); |
| 629 | } |
| 630 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -127,10 +127,12 @@ | |
| 127 | int isSandbox; |
| 128 | Blob wiki; |
| 129 | Manifest m; |
| 130 | const char *zPageName; |
| 131 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 132 | Stmt q; |
| 133 | int cnt = 0; |
| 134 | |
| 135 | login_check_credentials(); |
| 136 | if( !g.okRdWiki ){ login_needed(); return; } |
| 137 | zPageName = P("name"); |
| 138 | if( zPageName==0 ){ |
| @@ -152,11 +154,12 @@ | |
| 154 | @ <li> Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li> |
| 155 | } |
| 156 | @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a> |
| 157 | @ available on this server.</li> |
| 158 | @ <li> <form method="GET" action="%s(g.zBaseURL)/wfind"> |
| 159 | @ Search wiki titles: <input type="text" name="title"/> |
| 160 | @ <input type="submit" /> |
| 161 | @ </li> |
| 162 | @ </ul> |
| 163 | style_footer(); |
| 164 | return; |
| 165 | } |
| @@ -176,20 +179,26 @@ | |
| 179 | blob_zero(&m.content); |
| 180 | if( rid ){ |
| 181 | Blob content; |
| 182 | content_get(rid, &content); |
| 183 | manifest_parse(&m, &content); |
| 184 | if( m.type==CFTYPE_WIKI && m.zWiki ){ |
| 185 | while( isspace(m.zWiki[0]) ) m.zWiki++; |
| 186 | if( m.zWiki[0] ) zBody = m.zWiki; |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | if( !g.isHome ){ |
| 191 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 192 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| 193 | g.zTop, zPageName); |
| 194 | } |
| 195 | if( rid && g.okApndWiki && g.okAttach ){ |
| 196 | style_submenu_element("Attach", "Add An Attachment", |
| 197 | "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", |
| 198 | g.zTop, zPageName, g.zTop, zPageName); |
| 199 | } |
| 200 | if( rid && g.okApndWiki ){ |
| 201 | style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", |
| 202 | g.zTop, zPageName); |
| 203 | } |
| 204 | if( g.okHistory ){ |
| @@ -199,10 +208,42 @@ | |
| 208 | } |
| 209 | style_header(zPageName); |
| 210 | blob_init(&wiki, zBody, -1); |
| 211 | wiki_convert(&wiki, 0, 0); |
| 212 | blob_reset(&wiki); |
| 213 | |
| 214 | db_prepare(&q, |
| 215 | "SELECT datetime(mtime,'localtime'), filename, user" |
| 216 | " FROM attachment" |
| 217 | " WHERE isLatest AND src!='' AND target=%Q" |
| 218 | " ORDER BY mtime DESC", |
| 219 | zPageName); |
| 220 | while( db_step(&q)==SQLITE_ROW ){ |
| 221 | const char *zDate = db_column_text(&q, 0); |
| 222 | const char *zFile = db_column_text(&q, 1); |
| 223 | const char *zUser = db_column_text(&q, 2); |
| 224 | if( cnt==0 ){ |
| 225 | @ <hr><h2>Attachments:</h2> |
| 226 | @ <ul> |
| 227 | } |
| 228 | cnt++; |
| 229 | if( g.okHistory ){ |
| 230 | @ <li><a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)"> |
| 231 | }else{ |
| 232 | @ <li> |
| 233 | } |
| 234 | @ %h(zFile)</a> add by %h(zUser) on |
| 235 | hyperlink_to_date(zDate, "."); |
| 236 | if( g.okWrWiki && g.okAttach ){ |
| 237 | @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>] |
| 238 | } |
| 239 | } |
| 240 | if( cnt ){ |
| 241 | @ </ul> |
| 242 | } |
| 243 | db_finalize(&q); |
| 244 | |
| 245 | if( !isSandbox ){ |
| 246 | manifest_clear(&m); |
| 247 | } |
| 248 | style_footer(); |
| 249 | } |
| @@ -515,11 +556,13 @@ | |
| 556 | /* |
| 557 | ** Function called to output extra text at the end of each line in |
| 558 | ** a wiki history listing. |
| 559 | */ |
| 560 | static void wiki_history_extra(int rid){ |
| 561 | if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){ |
| 562 | @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a> |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | /* |
| 567 | ** WEBPAGE: whistory |
| 568 | ** URL: /whistory?name=PAGENAME |
| @@ -538,13 +581,15 @@ | |
| 581 | style_header(zTitle); |
| 582 | free(zTitle); |
| 583 | |
| 584 | zSQL = mprintf("%s AND event.objid IN " |
| 585 | " (SELECT rid FROM tagxref WHERE tagid=" |
| 586 | "(SELECT tagid FROM tag WHERE tagname='wiki-%q')" |
| 587 | " UNION SELECT attachid FROM attachment" |
| 588 | " WHERE target=%Q)" |
| 589 | "ORDER BY mtime DESC", |
| 590 | timeline_query_for_www(), zPageName, zPageName); |
| 591 | db_prepare(&q, zSQL); |
| 592 | free(zSQL); |
| 593 | zWikiPageName = zPageName; |
| 594 | www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra); |
| 595 | db_finalize(&q); |
| @@ -604,26 +649,43 @@ | |
| 649 | style_footer(); |
| 650 | } |
| 651 | |
| 652 | /* |
| 653 | ** WEBPAGE: wcontent |
| 654 | ** |
| 655 | ** all=1 Show deleted pages |
| 656 | ** |
| 657 | ** List all available wiki pages with date created and last modified. |
| 658 | */ |
| 659 | void wcontent_page(void){ |
| 660 | Stmt q; |
| 661 | int showAll = P("all")!=0; |
| 662 | |
| 663 | login_check_credentials(); |
| 664 | if( !g.okRdWiki ){ login_needed(); return; } |
| 665 | style_header("Available Wiki Pages"); |
| 666 | if( showAll ){ |
| 667 | style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop); |
| 668 | }else{ |
| 669 | style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop); |
| 670 | } |
| 671 | @ <ul> |
| 672 | db_prepare(&q, |
| 673 | "SELECT" |
| 674 | " substr(tagname, 6)," |
| 675 | " (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)" |
| 676 | " FROM tag WHERE tagname GLOB 'wiki-*'" |
| 677 | " ORDER BY lower(tagname)" |
| 678 | ); |
| 679 | while( db_step(&q)==SQLITE_ROW ){ |
| 680 | const char *zName = db_column_text(&q, 0); |
| 681 | int size = db_column_int(&q, 1); |
| 682 | if( size>0 ){ |
| 683 | @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li> |
| 684 | }else if( showAll ){ |
| 685 | @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li> |
| 686 | } |
| 687 | } |
| 688 | db_finalize(&q); |
| 689 | @ </ul> |
| 690 | style_footer(); |
| 691 | } |
| 692 |
+196
-3
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -369,10 +369,23 @@ | ||
| 369 | 369 | short allowWiki; /* ALLOW_WIKI if wiki allowed before tag */ |
| 370 | 370 | const char *zId; /* ID attribute or NULL */ |
| 371 | 371 | } *aStack; |
| 372 | 372 | }; |
| 373 | 373 | |
| 374 | +/* | |
| 375 | +** Return TRUE if HTML should be used as the sole markup language for wiki. | |
| 376 | +** | |
| 377 | +** On first invocation, this routine consults the "wiki-use-html" setting. | |
| 378 | +** It caches the result for subsequent invocations, under the assumption | |
| 379 | +** that the setting will not change. | |
| 380 | +*/ | |
| 381 | +static int wikiUsesHtml(void){ | |
| 382 | + static int r = -1; | |
| 383 | + if( r<0 ) r = db_get_boolean("wiki-use-html", 0); | |
| 384 | + return r; | |
| 385 | +} | |
| 386 | + | |
| 374 | 387 | |
| 375 | 388 | /* |
| 376 | 389 | ** z points to a "<" character. Check to see if this is the start of |
| 377 | 390 | ** a valid markup. If it is, return the total number of characters in |
| 378 | 391 | ** the markup including the initial "<" and the terminating ">". If |
| @@ -620,11 +633,10 @@ | ||
| 620 | 633 | ** Parse only Wiki links, return everything else as TOKEN_RAW. |
| 621 | 634 | ** |
| 622 | 635 | ** z points to the start of a token. Return the number of |
| 623 | 636 | ** characters in that token. Write the token type into *pTokenType. |
| 624 | 637 | */ |
| 625 | - | |
| 626 | 638 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 627 | 639 | int n; |
| 628 | 640 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 629 | 641 | *pTokenType = TOKEN_LINK; |
| 630 | 642 | return n; |
| @@ -778,11 +790,11 @@ | ||
| 778 | 790 | static void popStack(Renderer *p){ |
| 779 | 791 | if( p->nStack ){ |
| 780 | 792 | int iCode; |
| 781 | 793 | p->nStack--; |
| 782 | 794 | iCode = p->aStack[p->nStack].iCode; |
| 783 | - if( iCode!=MARKUP_DIV ){ | |
| 795 | + if( iCode!=MARKUP_DIV && p->pOut ){ | |
| 784 | 796 | blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); |
| 785 | 797 | } |
| 786 | 798 | } |
| 787 | 799 | } |
| 788 | 800 | |
| @@ -1391,11 +1403,11 @@ | ||
| 1391 | 1403 | if( flags & WIKI_INLINE ){ |
| 1392 | 1404 | renderer.wantAutoParagraph = 0; |
| 1393 | 1405 | }else{ |
| 1394 | 1406 | renderer.wantAutoParagraph = 1; |
| 1395 | 1407 | } |
| 1396 | - if( db_get_int("wiki-use-html", 0) ){ | |
| 1408 | + if( wikiUsesHtml() ){ | |
| 1397 | 1409 | renderer.state |= WIKI_USE_HTML; |
| 1398 | 1410 | } |
| 1399 | 1411 | if( pOut ){ |
| 1400 | 1412 | renderer.pOut = pOut; |
| 1401 | 1413 | }else{ |
| @@ -1447,5 +1459,186 @@ | ||
| 1447 | 1459 | if( z[i]!='<' ) return 0; |
| 1448 | 1460 | blob_init(pTitle, &z[iStart], i-iStart); |
| 1449 | 1461 | blob_init(pTail, &z[i+8], -1); |
| 1450 | 1462 | return 1; |
| 1451 | 1463 | } |
| 1464 | + | |
| 1465 | +/* | |
| 1466 | +** Parse text looking for wiki hyperlinks in one of the formats: | |
| 1467 | +** | |
| 1468 | +** [target] | |
| 1469 | +** [target|...] | |
| 1470 | +** | |
| 1471 | +** Where "target" can be either an artifact ID prefix or a wiki page | |
| 1472 | +** name. For each such hyperlink found, add an entry to the | |
| 1473 | +** backlink table. | |
| 1474 | +*/ | |
| 1475 | +void wiki_extract_links( | |
| 1476 | + char *z, /* The wiki text from which to extract links */ | |
| 1477 | + int srcid, /* srcid field for new BACKLINK table entries */ | |
| 1478 | + int srctype, /* srctype field for new BACKLINK table entries */ | |
| 1479 | + double mtime, /* mtime field for new BACKLINK table entries */ | |
| 1480 | + int replaceFlag, /* True first delete prior BACKLINK entries */ | |
| 1481 | + int flags /* wiki parsing flags */ | |
| 1482 | +){ | |
| 1483 | + Renderer renderer; | |
| 1484 | + int tokenType; | |
| 1485 | + ParsedMarkup markup; | |
| 1486 | + int n; | |
| 1487 | + int inlineOnly; | |
| 1488 | + int wikiUseHtml = 0; | |
| 1489 | + | |
| 1490 | + memset(&renderer, 0, sizeof(renderer)); | |
| 1491 | + renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; | |
| 1492 | + if( flags & WIKI_NOBLOCK ){ | |
| 1493 | + renderer.state |= INLINE_MARKUP_ONLY; | |
| 1494 | + } | |
| 1495 | + if( wikiUsesHtml() ){ | |
| 1496 | + renderer.state |= WIKI_USE_HTML; | |
| 1497 | + wikiUseHtml = 1; | |
| 1498 | + } | |
| 1499 | + inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; | |
| 1500 | + if( replaceFlag ){ | |
| 1501 | + db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", | |
| 1502 | + srctype, srcid); | |
| 1503 | + } | |
| 1504 | + | |
| 1505 | + while( z[0] ){ | |
| 1506 | + if( wikiUseHtml ){ | |
| 1507 | + n = nextRawToken(z, &renderer, &tokenType); | |
| 1508 | + }else{ | |
| 1509 | + n = nextWikiToken(z, &renderer, &tokenType); | |
| 1510 | + } | |
| 1511 | + switch( tokenType ){ | |
| 1512 | + case TOKEN_LINK: { | |
| 1513 | + char *zTarget; | |
| 1514 | + int i, c; | |
| 1515 | + char zLink[42]; | |
| 1516 | + | |
| 1517 | + zTarget = &z[1]; | |
| 1518 | + for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){} | |
| 1519 | + while(i>1 && zTarget[i-1]==' '){ i--; } | |
| 1520 | + c = zTarget[i]; | |
| 1521 | + zTarget[i] = 0; | |
| 1522 | + if( is_valid_uuid(zTarget) ){ | |
| 1523 | + memcpy(zLink, zTarget, i+1); | |
| 1524 | + canonical16(zLink, i); | |
| 1525 | + db_multi_exec( | |
| 1526 | + "REPLACE INTO backlink(target,srctype,srcid,mtime)" | |
| 1527 | + "VALUES(%Q,%d,%d,%g)", zLink, srctype, srcid, mtime | |
| 1528 | + ); | |
| 1529 | + } | |
| 1530 | + zTarget[i] = c; | |
| 1531 | + break; | |
| 1532 | + } | |
| 1533 | + case TOKEN_MARKUP: { | |
| 1534 | + const char *zId; | |
| 1535 | + int iDiv; | |
| 1536 | + parseMarkup(&markup, z); | |
| 1537 | + | |
| 1538 | + /* Markup of the form </div id=ID> where there is a matching | |
| 1539 | + ** ID somewhere on the stack. Exit the verbatim if were are in | |
| 1540 | + ** it. Pop the stack up to the matching <div>. Discard the | |
| 1541 | + ** </div> | |
| 1542 | + */ | |
| 1543 | + if( markup.iCode==MARKUP_DIV && markup.endTag && | |
| 1544 | + (zId = markupId(&markup))!=0 && | |
| 1545 | + (iDiv = findTagWithId(&renderer, MARKUP_DIV, zId))>=0 | |
| 1546 | + ){ | |
| 1547 | + if( renderer.inVerbatim ){ | |
| 1548 | + renderer.inVerbatim = 0; | |
| 1549 | + renderer.state = renderer.preVerbState; | |
| 1550 | + } | |
| 1551 | + while( renderer.nStack>iDiv+1 ) popStack(&renderer); | |
| 1552 | + if( renderer.aStack[iDiv].allowWiki ){ | |
| 1553 | + renderer.state |= ALLOW_WIKI; | |
| 1554 | + }else{ | |
| 1555 | + renderer.state &= ~ALLOW_WIKI; | |
| 1556 | + } | |
| 1557 | + renderer.nStack--; | |
| 1558 | + }else | |
| 1559 | + | |
| 1560 | + /* If within <verbatim id=ID> ignore everything other than | |
| 1561 | + ** </verbatim id=ID> and the </dev id=ID2> above. | |
| 1562 | + */ | |
| 1563 | + if( renderer.inVerbatim ){ | |
| 1564 | + if( endVerbatim(&renderer, &markup) ){ | |
| 1565 | + renderer.inVerbatim = 0; | |
| 1566 | + renderer.state = renderer.preVerbState; | |
| 1567 | + }else{ | |
| 1568 | + n = 1; | |
| 1569 | + } | |
| 1570 | + }else | |
| 1571 | + | |
| 1572 | + /* Render invalid markup literally. The markup appears in the | |
| 1573 | + ** final output as plain text. | |
| 1574 | + */ | |
| 1575 | + if( markup.iCode==MARKUP_INVALID ){ | |
| 1576 | + n = 1; | |
| 1577 | + }else | |
| 1578 | + | |
| 1579 | + /* If the markup is not font-change markup ignore it if the | |
| 1580 | + ** font-change-only flag is set. | |
| 1581 | + */ | |
| 1582 | + if( (markup.iType&MUTYPE_FONT)==0 && | |
| 1583 | + (renderer.state & FONT_MARKUP_ONLY)!=0 ){ | |
| 1584 | + /* Do nothing */ | |
| 1585 | + }else | |
| 1586 | + | |
| 1587 | + if( markup.iCode==MARKUP_NOWIKI ){ | |
| 1588 | + if( markup.endTag ){ | |
| 1589 | + renderer.state |= ALLOW_WIKI; | |
| 1590 | + }else{ | |
| 1591 | + renderer.state &= ~ALLOW_WIKI; | |
| 1592 | + } | |
| 1593 | + }else | |
| 1594 | + | |
| 1595 | + /* Ignore block markup for in-line rendering. | |
| 1596 | + */ | |
| 1597 | + if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ | |
| 1598 | + /* Do nothing */ | |
| 1599 | + }else | |
| 1600 | + | |
| 1601 | + /* Generate end-tags */ | |
| 1602 | + if( markup.endTag ){ | |
| 1603 | + popStackToTag(&renderer, markup.iCode); | |
| 1604 | + }else | |
| 1605 | + | |
| 1606 | + /* Push <div> markup onto the stack together with the id=ID attribute. | |
| 1607 | + */ | |
| 1608 | + if( markup.iCode==MARKUP_DIV ){ | |
| 1609 | + pushStackWithId(&renderer, markup.iCode, markupId(&markup), | |
| 1610 | + (renderer.state & ALLOW_WIKI)!=0); | |
| 1611 | + }else | |
| 1612 | + | |
| 1613 | + /* Enter <verbatim> processing. With verbatim enabled, all other | |
| 1614 | + ** markup other than the corresponding end-tag with the same ID is | |
| 1615 | + ** ignored. | |
| 1616 | + */ | |
| 1617 | + if( markup.iCode==MARKUP_VERBATIM ){ | |
| 1618 | + int vAttrIdx, vAttrDidAppend=0; | |
| 1619 | + renderer.zVerbatimId = 0; | |
| 1620 | + renderer.inVerbatim = 1; | |
| 1621 | + renderer.preVerbState = renderer.state; | |
| 1622 | + renderer.state &= ~ALLOW_WIKI; | |
| 1623 | + for (vAttrIdx = 0; vAttrIdx < markup.nAttr; vAttrIdx++){ | |
| 1624 | + if( markup.aAttr[vAttrIdx].iACode == ATTR_ID ){ | |
| 1625 | + renderer.zVerbatimId = markup.aAttr[0].zValue; | |
| 1626 | + }else if( markup.aAttr[vAttrIdx].iACode == ATTR_TYPE ){ | |
| 1627 | + vAttrDidAppend=1; | |
| 1628 | + } | |
| 1629 | + } | |
| 1630 | + renderer.wantAutoParagraph = 0; | |
| 1631 | + } | |
| 1632 | + | |
| 1633 | + /* Restore the input text to its original configuration | |
| 1634 | + */ | |
| 1635 | + unparseMarkup(&markup); | |
| 1636 | + break; | |
| 1637 | + } | |
| 1638 | + default: { | |
| 1639 | + break; | |
| 1640 | + } | |
| 1641 | + } | |
| 1642 | + z += n; | |
| 1643 | + } | |
| 1644 | +} | |
| 1452 | 1645 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -369,10 +369,23 @@ | |
| 369 | short allowWiki; /* ALLOW_WIKI if wiki allowed before tag */ |
| 370 | const char *zId; /* ID attribute or NULL */ |
| 371 | } *aStack; |
| 372 | }; |
| 373 | |
| 374 | |
| 375 | /* |
| 376 | ** z points to a "<" character. Check to see if this is the start of |
| 377 | ** a valid markup. If it is, return the total number of characters in |
| 378 | ** the markup including the initial "<" and the terminating ">". If |
| @@ -620,11 +633,10 @@ | |
| 620 | ** Parse only Wiki links, return everything else as TOKEN_RAW. |
| 621 | ** |
| 622 | ** z points to the start of a token. Return the number of |
| 623 | ** characters in that token. Write the token type into *pTokenType. |
| 624 | */ |
| 625 | |
| 626 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 627 | int n; |
| 628 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 629 | *pTokenType = TOKEN_LINK; |
| 630 | return n; |
| @@ -778,11 +790,11 @@ | |
| 778 | static void popStack(Renderer *p){ |
| 779 | if( p->nStack ){ |
| 780 | int iCode; |
| 781 | p->nStack--; |
| 782 | iCode = p->aStack[p->nStack].iCode; |
| 783 | if( iCode!=MARKUP_DIV ){ |
| 784 | blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); |
| 785 | } |
| 786 | } |
| 787 | } |
| 788 | |
| @@ -1391,11 +1403,11 @@ | |
| 1391 | if( flags & WIKI_INLINE ){ |
| 1392 | renderer.wantAutoParagraph = 0; |
| 1393 | }else{ |
| 1394 | renderer.wantAutoParagraph = 1; |
| 1395 | } |
| 1396 | if( db_get_int("wiki-use-html", 0) ){ |
| 1397 | renderer.state |= WIKI_USE_HTML; |
| 1398 | } |
| 1399 | if( pOut ){ |
| 1400 | renderer.pOut = pOut; |
| 1401 | }else{ |
| @@ -1447,5 +1459,186 @@ | |
| 1447 | if( z[i]!='<' ) return 0; |
| 1448 | blob_init(pTitle, &z[iStart], i-iStart); |
| 1449 | blob_init(pTail, &z[i+8], -1); |
| 1450 | return 1; |
| 1451 | } |
| 1452 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -369,10 +369,23 @@ | |
| 369 | short allowWiki; /* ALLOW_WIKI if wiki allowed before tag */ |
| 370 | const char *zId; /* ID attribute or NULL */ |
| 371 | } *aStack; |
| 372 | }; |
| 373 | |
| 374 | /* |
| 375 | ** Return TRUE if HTML should be used as the sole markup language for wiki. |
| 376 | ** |
| 377 | ** On first invocation, this routine consults the "wiki-use-html" setting. |
| 378 | ** It caches the result for subsequent invocations, under the assumption |
| 379 | ** that the setting will not change. |
| 380 | */ |
| 381 | static int wikiUsesHtml(void){ |
| 382 | static int r = -1; |
| 383 | if( r<0 ) r = db_get_boolean("wiki-use-html", 0); |
| 384 | return r; |
| 385 | } |
| 386 | |
| 387 | |
| 388 | /* |
| 389 | ** z points to a "<" character. Check to see if this is the start of |
| 390 | ** a valid markup. If it is, return the total number of characters in |
| 391 | ** the markup including the initial "<" and the terminating ">". If |
| @@ -620,11 +633,10 @@ | |
| 633 | ** Parse only Wiki links, return everything else as TOKEN_RAW. |
| 634 | ** |
| 635 | ** z points to the start of a token. Return the number of |
| 636 | ** characters in that token. Write the token type into *pTokenType. |
| 637 | */ |
| 638 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 639 | int n; |
| 640 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 641 | *pTokenType = TOKEN_LINK; |
| 642 | return n; |
| @@ -778,11 +790,11 @@ | |
| 790 | static void popStack(Renderer *p){ |
| 791 | if( p->nStack ){ |
| 792 | int iCode; |
| 793 | p->nStack--; |
| 794 | iCode = p->aStack[p->nStack].iCode; |
| 795 | if( iCode!=MARKUP_DIV && p->pOut ){ |
| 796 | blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName); |
| 797 | } |
| 798 | } |
| 799 | } |
| 800 | |
| @@ -1391,11 +1403,11 @@ | |
| 1403 | if( flags & WIKI_INLINE ){ |
| 1404 | renderer.wantAutoParagraph = 0; |
| 1405 | }else{ |
| 1406 | renderer.wantAutoParagraph = 1; |
| 1407 | } |
| 1408 | if( wikiUsesHtml() ){ |
| 1409 | renderer.state |= WIKI_USE_HTML; |
| 1410 | } |
| 1411 | if( pOut ){ |
| 1412 | renderer.pOut = pOut; |
| 1413 | }else{ |
| @@ -1447,5 +1459,186 @@ | |
| 1459 | if( z[i]!='<' ) return 0; |
| 1460 | blob_init(pTitle, &z[iStart], i-iStart); |
| 1461 | blob_init(pTail, &z[i+8], -1); |
| 1462 | return 1; |
| 1463 | } |
| 1464 | |
| 1465 | /* |
| 1466 | ** Parse text looking for wiki hyperlinks in one of the formats: |
| 1467 | ** |
| 1468 | ** [target] |
| 1469 | ** [target|...] |
| 1470 | ** |
| 1471 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 1472 | ** name. For each such hyperlink found, add an entry to the |
| 1473 | ** backlink table. |
| 1474 | */ |
| 1475 | void wiki_extract_links( |
| 1476 | char *z, /* The wiki text from which to extract links */ |
| 1477 | int srcid, /* srcid field for new BACKLINK table entries */ |
| 1478 | int srctype, /* srctype field for new BACKLINK table entries */ |
| 1479 | double mtime, /* mtime field for new BACKLINK table entries */ |
| 1480 | int replaceFlag, /* True first delete prior BACKLINK entries */ |
| 1481 | int flags /* wiki parsing flags */ |
| 1482 | ){ |
| 1483 | Renderer renderer; |
| 1484 | int tokenType; |
| 1485 | ParsedMarkup markup; |
| 1486 | int n; |
| 1487 | int inlineOnly; |
| 1488 | int wikiUseHtml = 0; |
| 1489 | |
| 1490 | memset(&renderer, 0, sizeof(renderer)); |
| 1491 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 1492 | if( flags & WIKI_NOBLOCK ){ |
| 1493 | renderer.state |= INLINE_MARKUP_ONLY; |
| 1494 | } |
| 1495 | if( wikiUsesHtml() ){ |
| 1496 | renderer.state |= WIKI_USE_HTML; |
| 1497 | wikiUseHtml = 1; |
| 1498 | } |
| 1499 | inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 1500 | if( replaceFlag ){ |
| 1501 | db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", |
| 1502 | srctype, srcid); |
| 1503 | } |
| 1504 | |
| 1505 | while( z[0] ){ |
| 1506 | if( wikiUseHtml ){ |
| 1507 | n = nextRawToken(z, &renderer, &tokenType); |
| 1508 | }else{ |
| 1509 | n = nextWikiToken(z, &renderer, &tokenType); |
| 1510 | } |
| 1511 | switch( tokenType ){ |
| 1512 | case TOKEN_LINK: { |
| 1513 | char *zTarget; |
| 1514 | int i, c; |
| 1515 | char zLink[42]; |
| 1516 | |
| 1517 | zTarget = &z[1]; |
| 1518 | for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){} |
| 1519 | while(i>1 && zTarget[i-1]==' '){ i--; } |
| 1520 | c = zTarget[i]; |
| 1521 | zTarget[i] = 0; |
| 1522 | if( is_valid_uuid(zTarget) ){ |
| 1523 | memcpy(zLink, zTarget, i+1); |
| 1524 | canonical16(zLink, i); |
| 1525 | db_multi_exec( |
| 1526 | "REPLACE INTO backlink(target,srctype,srcid,mtime)" |
| 1527 | "VALUES(%Q,%d,%d,%g)", zLink, srctype, srcid, mtime |
| 1528 | ); |
| 1529 | } |
| 1530 | zTarget[i] = c; |
| 1531 | break; |
| 1532 | } |
| 1533 | case TOKEN_MARKUP: { |
| 1534 | const char *zId; |
| 1535 | int iDiv; |
| 1536 | parseMarkup(&markup, z); |
| 1537 | |
| 1538 | /* Markup of the form </div id=ID> where there is a matching |
| 1539 | ** ID somewhere on the stack. Exit the verbatim if were are in |
| 1540 | ** it. Pop the stack up to the matching <div>. Discard the |
| 1541 | ** </div> |
| 1542 | */ |
| 1543 | if( markup.iCode==MARKUP_DIV && markup.endTag && |
| 1544 | (zId = markupId(&markup))!=0 && |
| 1545 | (iDiv = findTagWithId(&renderer, MARKUP_DIV, zId))>=0 |
| 1546 | ){ |
| 1547 | if( renderer.inVerbatim ){ |
| 1548 | renderer.inVerbatim = 0; |
| 1549 | renderer.state = renderer.preVerbState; |
| 1550 | } |
| 1551 | while( renderer.nStack>iDiv+1 ) popStack(&renderer); |
| 1552 | if( renderer.aStack[iDiv].allowWiki ){ |
| 1553 | renderer.state |= ALLOW_WIKI; |
| 1554 | }else{ |
| 1555 | renderer.state &= ~ALLOW_WIKI; |
| 1556 | } |
| 1557 | renderer.nStack--; |
| 1558 | }else |
| 1559 | |
| 1560 | /* If within <verbatim id=ID> ignore everything other than |
| 1561 | ** </verbatim id=ID> and the </dev id=ID2> above. |
| 1562 | */ |
| 1563 | if( renderer.inVerbatim ){ |
| 1564 | if( endVerbatim(&renderer, &markup) ){ |
| 1565 | renderer.inVerbatim = 0; |
| 1566 | renderer.state = renderer.preVerbState; |
| 1567 | }else{ |
| 1568 | n = 1; |
| 1569 | } |
| 1570 | }else |
| 1571 | |
| 1572 | /* Render invalid markup literally. The markup appears in the |
| 1573 | ** final output as plain text. |
| 1574 | */ |
| 1575 | if( markup.iCode==MARKUP_INVALID ){ |
| 1576 | n = 1; |
| 1577 | }else |
| 1578 | |
| 1579 | /* If the markup is not font-change markup ignore it if the |
| 1580 | ** font-change-only flag is set. |
| 1581 | */ |
| 1582 | if( (markup.iType&MUTYPE_FONT)==0 && |
| 1583 | (renderer.state & FONT_MARKUP_ONLY)!=0 ){ |
| 1584 | /* Do nothing */ |
| 1585 | }else |
| 1586 | |
| 1587 | if( markup.iCode==MARKUP_NOWIKI ){ |
| 1588 | if( markup.endTag ){ |
| 1589 | renderer.state |= ALLOW_WIKI; |
| 1590 | }else{ |
| 1591 | renderer.state &= ~ALLOW_WIKI; |
| 1592 | } |
| 1593 | }else |
| 1594 | |
| 1595 | /* Ignore block markup for in-line rendering. |
| 1596 | */ |
| 1597 | if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ |
| 1598 | /* Do nothing */ |
| 1599 | }else |
| 1600 | |
| 1601 | /* Generate end-tags */ |
| 1602 | if( markup.endTag ){ |
| 1603 | popStackToTag(&renderer, markup.iCode); |
| 1604 | }else |
| 1605 | |
| 1606 | /* Push <div> markup onto the stack together with the id=ID attribute. |
| 1607 | */ |
| 1608 | if( markup.iCode==MARKUP_DIV ){ |
| 1609 | pushStackWithId(&renderer, markup.iCode, markupId(&markup), |
| 1610 | (renderer.state & ALLOW_WIKI)!=0); |
| 1611 | }else |
| 1612 | |
| 1613 | /* Enter <verbatim> processing. With verbatim enabled, all other |
| 1614 | ** markup other than the corresponding end-tag with the same ID is |
| 1615 | ** ignored. |
| 1616 | */ |
| 1617 | if( markup.iCode==MARKUP_VERBATIM ){ |
| 1618 | int vAttrIdx, vAttrDidAppend=0; |
| 1619 | renderer.zVerbatimId = 0; |
| 1620 | renderer.inVerbatim = 1; |
| 1621 | renderer.preVerbState = renderer.state; |
| 1622 | renderer.state &= ~ALLOW_WIKI; |
| 1623 | for (vAttrIdx = 0; vAttrIdx < markup.nAttr; vAttrIdx++){ |
| 1624 | if( markup.aAttr[vAttrIdx].iACode == ATTR_ID ){ |
| 1625 | renderer.zVerbatimId = markup.aAttr[0].zValue; |
| 1626 | }else if( markup.aAttr[vAttrIdx].iACode == ATTR_TYPE ){ |
| 1627 | vAttrDidAppend=1; |
| 1628 | } |
| 1629 | } |
| 1630 | renderer.wantAutoParagraph = 0; |
| 1631 | } |
| 1632 | |
| 1633 | /* Restore the input text to its original configuration |
| 1634 | */ |
| 1635 | unparseMarkup(&markup); |
| 1636 | break; |
| 1637 | } |
| 1638 | default: { |
| 1639 | break; |
| 1640 | } |
| 1641 | } |
| 1642 | z += n; |
| 1643 | } |
| 1644 | } |
| 1645 |
+3
-3
| --- src/winhttp.c | ||
| +++ src/winhttp.c | ||
| @@ -137,13 +137,13 @@ | ||
| 137 | 137 | ** Start a listening socket and process incoming HTTP requests on |
| 138 | 138 | ** that socket. |
| 139 | 139 | */ |
| 140 | 140 | void win32_http_server( |
| 141 | 141 | int mnPort, int mxPort, /* Range of allowed TCP port numbers */ |
| 142 | - char *zBrowser, /* Command to launch browser. (Or NULL) */ | |
| 143 | - char *zStopper, /* Stop server when this file is exists (Or NULL) */ | |
| 144 | - char *zNotFound /* The --notfound option, or NULL */ | |
| 142 | + const char *zBrowser, /* Command to launch browser. (Or NULL) */ | |
| 143 | + const char *zStopper, /* Stop server when this file is exists (Or NULL) */ | |
| 144 | + const char *zNotFound /* The --notfound option, or NULL */ | |
| 145 | 145 | ){ |
| 146 | 146 | WSADATA wd; |
| 147 | 147 | SOCKET s = INVALID_SOCKET; |
| 148 | 148 | SOCKADDR_IN addr; |
| 149 | 149 | int idCnt = 0; |
| 150 | 150 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -137,13 +137,13 @@ | |
| 137 | ** Start a listening socket and process incoming HTTP requests on |
| 138 | ** that socket. |
| 139 | */ |
| 140 | void win32_http_server( |
| 141 | int mnPort, int mxPort, /* Range of allowed TCP port numbers */ |
| 142 | char *zBrowser, /* Command to launch browser. (Or NULL) */ |
| 143 | char *zStopper, /* Stop server when this file is exists (Or NULL) */ |
| 144 | char *zNotFound /* The --notfound option, or NULL */ |
| 145 | ){ |
| 146 | WSADATA wd; |
| 147 | SOCKET s = INVALID_SOCKET; |
| 148 | SOCKADDR_IN addr; |
| 149 | int idCnt = 0; |
| 150 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -137,13 +137,13 @@ | |
| 137 | ** Start a listening socket and process incoming HTTP requests on |
| 138 | ** that socket. |
| 139 | */ |
| 140 | void win32_http_server( |
| 141 | int mnPort, int mxPort, /* Range of allowed TCP port numbers */ |
| 142 | const char *zBrowser, /* Command to launch browser. (Or NULL) */ |
| 143 | const char *zStopper, /* Stop server when this file is exists (Or NULL) */ |
| 144 | const char *zNotFound /* The --notfound option, or NULL */ |
| 145 | ){ |
| 146 | WSADATA wd; |
| 147 | SOCKET s = INVALID_SOCKET; |
| 148 | SOCKADDR_IN addr; |
| 149 | int idCnt = 0; |
| 150 |
+3
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -518,10 +518,11 @@ | ||
| 518 | 518 | int cnt = 0; |
| 519 | 519 | db_prepare(&q, |
| 520 | 520 | "SELECT uuid FROM unclustered JOIN blob USING(rid)" |
| 521 | 521 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 522 | 522 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 523 | + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" | |
| 523 | 524 | ); |
| 524 | 525 | while( db_step(&q)==SQLITE_ROW ){ |
| 525 | 526 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 526 | 527 | cnt++; |
| 527 | 528 | } |
| @@ -536,10 +537,11 @@ | ||
| 536 | 537 | Stmt q; |
| 537 | 538 | db_prepare(&q, |
| 538 | 539 | "SELECT uuid FROM blob " |
| 539 | 540 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 540 | 541 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 542 | + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" | |
| 541 | 543 | ); |
| 542 | 544 | while( db_step(&q)==SQLITE_ROW ){ |
| 543 | 545 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 544 | 546 | } |
| 545 | 547 | db_finalize(&q); |
| @@ -1064,10 +1066,11 @@ | ||
| 1064 | 1066 | nCardSent = 0; |
| 1065 | 1067 | nCardRcvd = 0; |
| 1066 | 1068 | xfer.nFileSent = 0; |
| 1067 | 1069 | xfer.nDeltaSent = 0; |
| 1068 | 1070 | xfer.nGimmeSent = 0; |
| 1071 | + xfer.nIGotSent = 0; | |
| 1069 | 1072 | fflush(stdout); |
| 1070 | 1073 | http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); |
| 1071 | 1074 | blob_reset(&send); |
| 1072 | 1075 | |
| 1073 | 1076 | /* Begin constructing the next message (which might never be |
| 1074 | 1077 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -518,10 +518,11 @@ | |
| 518 | int cnt = 0; |
| 519 | db_prepare(&q, |
| 520 | "SELECT uuid FROM unclustered JOIN blob USING(rid)" |
| 521 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 522 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 523 | ); |
| 524 | while( db_step(&q)==SQLITE_ROW ){ |
| 525 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 526 | cnt++; |
| 527 | } |
| @@ -536,10 +537,11 @@ | |
| 536 | Stmt q; |
| 537 | db_prepare(&q, |
| 538 | "SELECT uuid FROM blob " |
| 539 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 540 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 541 | ); |
| 542 | while( db_step(&q)==SQLITE_ROW ){ |
| 543 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 544 | } |
| 545 | db_finalize(&q); |
| @@ -1064,10 +1066,11 @@ | |
| 1064 | nCardSent = 0; |
| 1065 | nCardRcvd = 0; |
| 1066 | xfer.nFileSent = 0; |
| 1067 | xfer.nDeltaSent = 0; |
| 1068 | xfer.nGimmeSent = 0; |
| 1069 | fflush(stdout); |
| 1070 | http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); |
| 1071 | blob_reset(&send); |
| 1072 | |
| 1073 | /* Begin constructing the next message (which might never be |
| 1074 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -518,10 +518,11 @@ | |
| 518 | int cnt = 0; |
| 519 | db_prepare(&q, |
| 520 | "SELECT uuid FROM unclustered JOIN blob USING(rid)" |
| 521 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 522 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 523 | " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" |
| 524 | ); |
| 525 | while( db_step(&q)==SQLITE_ROW ){ |
| 526 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 527 | cnt++; |
| 528 | } |
| @@ -536,10 +537,11 @@ | |
| 537 | Stmt q; |
| 538 | db_prepare(&q, |
| 539 | "SELECT uuid FROM blob " |
| 540 | " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" |
| 541 | " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" |
| 542 | " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" |
| 543 | ); |
| 544 | while( db_step(&q)==SQLITE_ROW ){ |
| 545 | blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); |
| 546 | } |
| 547 | db_finalize(&q); |
| @@ -1064,10 +1066,11 @@ | |
| 1066 | nCardSent = 0; |
| 1067 | nCardRcvd = 0; |
| 1068 | xfer.nFileSent = 0; |
| 1069 | xfer.nDeltaSent = 0; |
| 1070 | xfer.nGimmeSent = 0; |
| 1071 | xfer.nIGotSent = 0; |
| 1072 | fflush(stdout); |
| 1073 | http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); |
| 1074 | blob_reset(&send); |
| 1075 | |
| 1076 | /* Begin constructing the next message (which might never be |
| 1077 |
+24
-5
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -378,23 +378,42 @@ | ||
| 378 | 378 | blob_reset(&filename); |
| 379 | 379 | zip_close(pZip); |
| 380 | 380 | } |
| 381 | 381 | |
| 382 | 382 | /* |
| 383 | -** COMMAND: test-baseline-zip | |
| 383 | +** COMMAND: zip | |
| 384 | +** | |
| 385 | +** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] | |
| 384 | 386 | ** |
| 385 | -** Generate a ZIP archive for a specified baseline. | |
| 387 | +** Generate a ZIP archive for a specified version. If the --name option is | |
| 388 | +** used, it argument becomes the name of the top-level directory in the | |
| 389 | +** resulting ZIP archive. If --name is omitted, the top-level directory | |
| 390 | +** named is derived from the project name, the check-in date and time, and | |
| 391 | +** the artifact ID of the check-in. | |
| 386 | 392 | */ |
| 387 | 393 | void baseline_zip_cmd(void){ |
| 388 | 394 | int rid; |
| 389 | 395 | Blob zip; |
| 396 | + const char *zName; | |
| 397 | + zName = find_option("name", 0, 1); | |
| 398 | + db_find_and_open_repository(1); | |
| 390 | 399 | if( g.argc!=4 ){ |
| 391 | - usage("UUID ZIPFILE"); | |
| 400 | + usage("VERSION OUTPUTFILE"); | |
| 392 | 401 | } |
| 393 | - db_must_be_within_tree(); | |
| 394 | 402 | rid = name_to_rid(g.argv[2]); |
| 395 | - zip_of_baseline(rid, &zip, g.argv[2]); | |
| 403 | + if( zName==0 ){ | |
| 404 | + zName = db_text("default-name", | |
| 405 | + "SELECT replace(%Q,' ','_') " | |
| 406 | + " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " | |
| 407 | + " || substr(blob.uuid, 1, 10)" | |
| 408 | + " FROM event, blob" | |
| 409 | + " WHERE event.objid=%d" | |
| 410 | + " AND blob.rid=%d", | |
| 411 | + db_get("project-name", "unnamed"), rid, rid | |
| 412 | + ); | |
| 413 | + } | |
| 414 | + zip_of_baseline(rid, &zip, zName); | |
| 396 | 415 | blob_write_to_file(&zip, g.argv[3]); |
| 397 | 416 | } |
| 398 | 417 | |
| 399 | 418 | /* |
| 400 | 419 | ** WEBPAGE: zip |
| 401 | 420 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -378,23 +378,42 @@ | |
| 378 | blob_reset(&filename); |
| 379 | zip_close(pZip); |
| 380 | } |
| 381 | |
| 382 | /* |
| 383 | ** COMMAND: test-baseline-zip |
| 384 | ** |
| 385 | ** Generate a ZIP archive for a specified baseline. |
| 386 | */ |
| 387 | void baseline_zip_cmd(void){ |
| 388 | int rid; |
| 389 | Blob zip; |
| 390 | if( g.argc!=4 ){ |
| 391 | usage("UUID ZIPFILE"); |
| 392 | } |
| 393 | db_must_be_within_tree(); |
| 394 | rid = name_to_rid(g.argv[2]); |
| 395 | zip_of_baseline(rid, &zip, g.argv[2]); |
| 396 | blob_write_to_file(&zip, g.argv[3]); |
| 397 | } |
| 398 | |
| 399 | /* |
| 400 | ** WEBPAGE: zip |
| 401 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -378,23 +378,42 @@ | |
| 378 | blob_reset(&filename); |
| 379 | zip_close(pZip); |
| 380 | } |
| 381 | |
| 382 | /* |
| 383 | ** COMMAND: zip |
| 384 | ** |
| 385 | ** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME] |
| 386 | ** |
| 387 | ** Generate a ZIP archive for a specified version. If the --name option is |
| 388 | ** used, it argument becomes the name of the top-level directory in the |
| 389 | ** resulting ZIP archive. If --name is omitted, the top-level directory |
| 390 | ** named is derived from the project name, the check-in date and time, and |
| 391 | ** the artifact ID of the check-in. |
| 392 | */ |
| 393 | void baseline_zip_cmd(void){ |
| 394 | int rid; |
| 395 | Blob zip; |
| 396 | const char *zName; |
| 397 | zName = find_option("name", 0, 1); |
| 398 | db_find_and_open_repository(1); |
| 399 | if( g.argc!=4 ){ |
| 400 | usage("VERSION OUTPUTFILE"); |
| 401 | } |
| 402 | rid = name_to_rid(g.argv[2]); |
| 403 | if( zName==0 ){ |
| 404 | zName = db_text("default-name", |
| 405 | "SELECT replace(%Q,' ','_') " |
| 406 | " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " |
| 407 | " || substr(blob.uuid, 1, 10)" |
| 408 | " FROM event, blob" |
| 409 | " WHERE event.objid=%d" |
| 410 | " AND blob.rid=%d", |
| 411 | db_get("project-name", "unnamed"), rid, rid |
| 412 | ); |
| 413 | } |
| 414 | zip_of_baseline(rid, &zip, zName); |
| 415 | blob_write_to_file(&zip, g.argv[3]); |
| 416 | } |
| 417 | |
| 418 | /* |
| 419 | ** WEBPAGE: zip |
| 420 |
| --- www/branching.wiki | ||
| +++ www/branching.wiki | ||
| 1 | 1 | ADDED www/build-icons/linux.gif |
| 2 | 2 | ADDED www/build-icons/linux64.gif |
| 3 | 3 | ADDED www/build-icons/mac.gif |
| 4 | 4 | ADDED www/build-icons/src.gif |
| 5 | 5 | ADDED www/build-icons/win32.gif |
| --- www/branching.wiki | |
| +++ www/branching.wiki | |
| 0 | |
| 1 | DDED www/build-icons/linux.gif |
| 2 | DDED www/build-icons/linux64.gif |
| 3 | DDED www/build-icons/mac.gif |
| 4 | DDED www/build-icons/src.gif |
| 5 | DDED www/build-icons/win32.gif |
| --- www/branching.wiki | |
| +++ www/branching.wiki | |
| 0 | |
| 1 | DDED www/build-icons/linux.gif |
| 2 | DDED www/build-icons/linux64.gif |
| 3 | DDED www/build-icons/mac.gif |
| 4 | DDED www/build-icons/src.gif |
| 5 | DDED www/build-icons/win32.gif |
Binary file
Binary file
Binary file
Binary file
Binary file
+1
-4
| --- www/build.wiki | ||
| +++ www/build.wiki | ||
| @@ -34,13 +34,10 @@ | ||
| 34 | 34 | |
| 35 | 35 | <li><p>Select a version of of fossil you want to download. Click on its |
| 36 | 36 | link. Note that you must successfully log in as "anonymous" in step 1 |
| 37 | 37 | above in order to see the link to the detailed version information.</p></li> |
| 38 | 38 | |
| 39 | -<li><p>On the version information page, click on the "[details]" hyperlink | |
| 40 | -that appears right after the check-in comment at the top of the page.</p> | |
| 41 | - | |
| 42 | 39 | <li><p>Finally, click on the |
| 43 | 40 | "Zip Archive" link. This link will build a ZIP archive of the |
| 44 | 41 | complete source code and download it to your browser. |
| 45 | 42 | </ol> |
| 46 | 43 | |
| @@ -54,11 +51,11 @@ | ||
| 54 | 51 | ZIP archive you downloaded into that directory. You should be |
| 55 | 52 | in the top-level folder of that directory</p></li> |
| 56 | 53 | |
| 57 | 54 | <li><p><b>(Optional:)</b> |
| 58 | 55 | Edit the Makefile to set it up like you want. You probably do not |
| 59 | -need to do anything. Do not be intimidated: There are only 5 | |
| 56 | +need to do anything. Do not be intimidated: There are less than 10 | |
| 60 | 57 | variables in the makefile that can be changed. The whole Makefile |
| 61 | 58 | is only a few dozen lines long and most of those lines are comments.</p> |
| 62 | 59 | |
| 63 | 60 | <li><p>Type "<b>make</b>" |
| 64 | 61 | </ol> |
| 65 | 62 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -34,13 +34,10 @@ | |
| 34 | |
| 35 | <li><p>Select a version of of fossil you want to download. Click on its |
| 36 | link. Note that you must successfully log in as "anonymous" in step 1 |
| 37 | above in order to see the link to the detailed version information.</p></li> |
| 38 | |
| 39 | <li><p>On the version information page, click on the "[details]" hyperlink |
| 40 | that appears right after the check-in comment at the top of the page.</p> |
| 41 | |
| 42 | <li><p>Finally, click on the |
| 43 | "Zip Archive" link. This link will build a ZIP archive of the |
| 44 | complete source code and download it to your browser. |
| 45 | </ol> |
| 46 | |
| @@ -54,11 +51,11 @@ | |
| 54 | ZIP archive you downloaded into that directory. You should be |
| 55 | in the top-level folder of that directory</p></li> |
| 56 | |
| 57 | <li><p><b>(Optional:)</b> |
| 58 | Edit the Makefile to set it up like you want. You probably do not |
| 59 | need to do anything. Do not be intimidated: There are only 5 |
| 60 | variables in the makefile that can be changed. The whole Makefile |
| 61 | is only a few dozen lines long and most of those lines are comments.</p> |
| 62 | |
| 63 | <li><p>Type "<b>make</b>" |
| 64 | </ol> |
| 65 |
| --- www/build.wiki | |
| +++ www/build.wiki | |
| @@ -34,13 +34,10 @@ | |
| 34 | |
| 35 | <li><p>Select a version of of fossil you want to download. Click on its |
| 36 | link. Note that you must successfully log in as "anonymous" in step 1 |
| 37 | above in order to see the link to the detailed version information.</p></li> |
| 38 | |
| 39 | <li><p>Finally, click on the |
| 40 | "Zip Archive" link. This link will build a ZIP archive of the |
| 41 | complete source code and download it to your browser. |
| 42 | </ol> |
| 43 | |
| @@ -54,11 +51,11 @@ | |
| 51 | ZIP archive you downloaded into that directory. You should be |
| 52 | in the top-level folder of that directory</p></li> |
| 53 | |
| 54 | <li><p><b>(Optional:)</b> |
| 55 | Edit the Makefile to set it up like you want. You probably do not |
| 56 | need to do anything. Do not be intimidated: There are less than 10 |
| 57 | variables in the makefile that can be changed. The whole Makefile |
| 58 | is only a few dozen lines long and most of those lines are comments.</p> |
| 59 | |
| 60 | <li><p>Type "<b>make</b>" |
| 61 | </ol> |
| 62 |
+21
-6
| --- www/embeddeddoc.wiki | ||
| +++ www/embeddeddoc.wiki | ||
| @@ -58,12 +58,12 @@ | ||
| 58 | 58 | only works when you start your server using the "<b>fossil server</b>" |
| 59 | 59 | or "<b>fossil ui</b>" |
| 60 | 60 | command line and is intended to show what the documentation you are currently |
| 61 | 61 | editing looks like before you check it in. |
| 62 | 62 | |
| 63 | -Finally, the <i><filename></i> element of the URL is the full | |
| 64 | -pathname of the documentation file starting from the root of the source | |
| 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 | 65 | tree. |
| 66 | 66 | |
| 67 | 67 | The mimetype (and thus the rendering) of documentation files is |
| 68 | 68 | determined by the file suffix. Fossil currently understands 192 |
| 69 | 69 | different file suffixes, including all the popular ones such as |
| @@ -110,14 +110,29 @@ | ||
| 110 | 110 | For example, to see the version of this document associated with |
| 111 | 111 | check-in [9be1b00392], simply replace the "<b>/tip/</b>" with |
| 112 | 112 | "<b>/9be1b00392/</b>". You can also substitute the symbolic name |
| 113 | 113 | for a particular version or branch. For example, you might |
| 114 | 114 | replace "<b>/tip/</b>" with "<b>/trunk/</b>" to get the latest |
| 115 | -version of this document in the "trunk" branch. (As of this writing, | |
| 116 | -the self-hosting fossil repository only has a single branch "trunk" and | |
| 117 | -so "trunk" and "tip" amount to the same thing, but they would be different | |
| 118 | -in a project with multiple branches.) | |
| 115 | +version of this document in the "trunk" branch. The symbolic name | |
| 116 | +can also be a date and time string in any of the following formats:</p> | |
| 117 | + | |
| 118 | +<ul> | |
| 119 | +<li> <i>YYYY-MM-DD</i> | |
| 120 | +<li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM</i> | |
| 121 | +<li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM:SS</i> | |
| 122 | +</ul> | |
| 123 | + | |
| 124 | +When the symbolic name is a date and time, fossil shows the version | |
| 125 | +of the document that was most recently checked in as of the date | |
| 126 | +and time specified. So, for example, to see what the fossil website | |
| 127 | +looked like at the beginning of 2010, enter: | |
| 128 | + | |
| 129 | +<blockquote> | |
| 130 | +<a href="http://www.fossil-scm.org/index.html/doc/2010-01-01/www/index.wiki"> | |
| 131 | +http://www.fossil-scm.org/index.html/doc/<b>2010-01-01</b>/www/index.wiki | |
| 132 | +</a> | |
| 133 | +</blockquote> | |
| 119 | 134 | |
| 120 | 135 | The file that encodes this document is stored in the fossil source tree under |
| 121 | 136 | the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the |
| 122 | 137 | last part of the URL for this document. |
| 123 | 138 | |
| 124 | 139 |
| --- www/embeddeddoc.wiki | |
| +++ www/embeddeddoc.wiki | |
| @@ -58,12 +58,12 @@ | |
| 58 | only works when you start your server using the "<b>fossil server</b>" |
| 59 | or "<b>fossil ui</b>" |
| 60 | command line and is intended to show what the documentation you are currently |
| 61 | editing looks like before you check it in. |
| 62 | |
| 63 | Finally, the <i><filename></i> element of the URL is the full |
| 64 | pathname of the documentation file starting from 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 192 |
| 69 | different file suffixes, including all the popular ones such as |
| @@ -110,14 +110,29 @@ | |
| 110 | For example, to see the version of this document associated with |
| 111 | check-in [9be1b00392], simply replace the "<b>/tip/</b>" with |
| 112 | "<b>/9be1b00392/</b>". You can also substitute the symbolic name |
| 113 | for a particular version or branch. For example, you might |
| 114 | replace "<b>/tip/</b>" with "<b>/trunk/</b>" to get the latest |
| 115 | version of this document in the "trunk" branch. (As of this writing, |
| 116 | the self-hosting fossil repository only has a single branch "trunk" and |
| 117 | so "trunk" and "tip" amount to the same thing, but they would be different |
| 118 | in a project with multiple branches.) |
| 119 | |
| 120 | The file that encodes this document is stored in the fossil source tree under |
| 121 | the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the |
| 122 | last part of the URL for this document. |
| 123 | |
| 124 |
| --- www/embeddeddoc.wiki | |
| +++ www/embeddeddoc.wiki | |
| @@ -58,12 +58,12 @@ | |
| 58 | only works when you start your server using the "<b>fossil server</b>" |
| 59 | or "<b>fossil ui</b>" |
| 60 | command line and is intended to show what the documentation you are currently |
| 61 | editing looks like before you check it in. |
| 62 | |
| 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 192 |
| 69 | different file suffixes, including all the popular ones such as |
| @@ -110,14 +110,29 @@ | |
| 110 | For example, to see the version of this document associated with |
| 111 | check-in [9be1b00392], simply replace the "<b>/tip/</b>" with |
| 112 | "<b>/9be1b00392/</b>". You can also substitute the symbolic name |
| 113 | for a particular version or branch. For example, you might |
| 114 | replace "<b>/tip/</b>" with "<b>/trunk/</b>" to get the latest |
| 115 | version of this document in the "trunk" branch. The symbolic name |
| 116 | can also be a date and time string in any of the following formats:</p> |
| 117 | |
| 118 | <ul> |
| 119 | <li> <i>YYYY-MM-DD</i> |
| 120 | <li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM</i> |
| 121 | <li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM:SS</i> |
| 122 | </ul> |
| 123 | |
| 124 | When the symbolic name is a date and time, fossil shows the version |
| 125 | of the document that was most recently checked in as of the date |
| 126 | and time specified. So, for example, to see what the fossil website |
| 127 | looked like at the beginning of 2010, enter: |
| 128 | |
| 129 | <blockquote> |
| 130 | <a href="http://www.fossil-scm.org/index.html/doc/2010-01-01/www/index.wiki"> |
| 131 | http://www.fossil-scm.org/index.html/doc/<b>2010-01-01</b>/www/index.wiki |
| 132 | </a> |
| 133 | </blockquote> |
| 134 | |
| 135 | The file that encodes this document is stored in the fossil source tree under |
| 136 | the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the |
| 137 | last part of the URL for this document. |
| 138 | |
| 139 |
+237
-85
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -1,26 +1,23 @@ | ||
| 1 | 1 | <title>Fossil File Formats</title> |
| 2 | 2 | <h1 align="center"> |
| 3 | 3 | Fossil File Formats |
| 4 | 4 | </h1> |
| 5 | 5 | |
| 6 | -<p>The global state of a fossil repository is kept simple so that it can | |
| 6 | +The global state of a fossil repository is kept simple so that it can | |
| 7 | 7 | endure in useful form for decades or centuries. |
| 8 | 8 | A fossil repository is intended to be readable, |
| 9 | -searchable, and extensible by people not yet born.</p> | |
| 9 | +searchable, and extensible by people not yet born. | |
| 10 | 10 | |
| 11 | -<p> | |
| 12 | 11 | The global state of a fossil repository is an unordered |
| 13 | 12 | set of <i>artifacts</i>. |
| 14 | 13 | An artifact might be a source code file, the text of a wiki page, |
| 15 | 14 | part of a trouble ticket, or one of several special control artifacts |
| 16 | 15 | used to show the relationships between other artifacts within the |
| 17 | 16 | project. Each artifact is normally represented on disk as a separate |
| 18 | 17 | file. Artifacts can be text or binary. |
| 19 | -</p> | |
| 20 | 18 | |
| 21 | -<p> | |
| 22 | 19 | In addition to the global state, |
| 23 | 20 | each fossil repository also contains local state. |
| 24 | 21 | The local state consists of web-page formatting |
| 25 | 22 | preferences, authorized users, ticket display and reporting formats, |
| 26 | 23 | and so forth. The global state is shared in common among all |
| @@ -29,82 +26,73 @@ | ||
| 29 | 26 | The local state is not versioned and is not synchronized |
| 30 | 27 | with the global state. |
| 31 | 28 | The local state is not composed of artifacts and is not intended to be enduring. |
| 32 | 29 | This document is concerned with global state only. Local state is only |
| 33 | 30 | mentioned here in order to distinguish it from global state. |
| 34 | -</p> | |
| 35 | 31 | |
| 36 | -<p> | |
| 37 | 32 | Each artifact in the repository is named by its SHA1 hash. |
| 38 | 33 | No prefixes or meta information is added to a artifact before |
| 39 | 34 | its hash is computed. The name of a artifact in the repository |
| 40 | 35 | is exactly the same SHA1 hash that is computed by sha1sum |
| 41 | 36 | on the file as it exists in your source tree.</p> |
| 42 | 37 | |
| 43 | -<p> | |
| 44 | 38 | Some artifacts have a particular format which gives them special |
| 45 | -meaning to fossil. Fossil recognizes:</p> | |
| 39 | +meaning to fossil. Fossil recognizes: | |
| 46 | 40 | |
| 47 | 41 | <ul> |
| 48 | -<li> Manifests </li> | |
| 49 | -<li> Clusters </li> | |
| 50 | -<li> Control Artifacts </li> | |
| 51 | -<li> Wiki Pages </li> | |
| 52 | -<li> Ticket Changes </li> | |
| 42 | +<li> [#manifest | Manifests] </li> | |
| 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> [#artifact | Artifacts] </li> | |
| 53 | 48 | </ul> |
| 54 | 49 | |
| 55 | -<p>These five artifact types are described in the sequel.</p> | |
| 50 | +These five artifact types are described in the sequel. | |
| 56 | 51 | |
| 57 | -<p>In the current implementation (as of 2009-01-25) the artifacts that | |
| 52 | +In the current implementation (as of 2009-01-25) the artifacts that | |
| 58 | 53 | make up a fossil repository are stored in in as delta- and zlib-compressed |
| 59 | 54 | blobs in an <a href="http://www.sqlite.org/">SQLite</a> database. This |
| 60 | 55 | is an implementation detail and might change in a future release. For |
| 61 | 56 | the purpose of this article "file format" means the format of the artifacts, |
| 62 | 57 | not how the artifacts are stored on disk. It is the artifact format that |
| 63 | 58 | is intended to be enduring. The specifics of how artifacts are stored on |
| 64 | 59 | disk, though stable, is not intended to live as long as the |
| 65 | -artifact format.</p> | |
| 60 | +artifact format. | |
| 66 | 61 | |
| 62 | +<a name="manifest"></a> | |
| 67 | 63 | <h2>1.0 The Manifest</h2> |
| 68 | 64 | |
| 69 | -<p>A manifest defines a check-in or version of the project | |
| 65 | +A manifest defines a check-in or version of the project | |
| 70 | 66 | source tree. The manifest contains a list of artifacts for |
| 71 | 67 | each file in the project and the corresponding filenames, as |
| 72 | 68 | well as information such as parent check-ins, the name of the |
| 73 | 69 | programmer who created the check-in, the date and time when |
| 74 | 70 | the check-in was created, and any check-in comments associated |
| 75 | -with the check-in.</p> | |
| 71 | +with the check-in. | |
| 76 | 72 | |
| 77 | -<p> | |
| 78 | 73 | Any artifact in the repository that follows the syntactic rules |
| 79 | 74 | of a manifest is a manifest. Note that a manifest can |
| 80 | 75 | be both a real manifest and also a content file, though this |
| 81 | 76 | is rare. |
| 82 | -</p> | |
| 83 | 77 | |
| 84 | -<p> | |
| 85 | 78 | A manifest is a text file. Newline characters |
| 86 | 79 | (ASCII 0x0a) separate the file into "cards". |
| 87 | 80 | Each card begins with a single |
| 88 | 81 | character "card type". Zero or more arguments may follow |
| 89 | 82 | the card type. All arguments are separated from each other |
| 90 | 83 | and from the card-type character by a single space |
| 91 | 84 | character. There is no surplus white space between arguments |
| 92 | 85 | and no leading or trailing whitespace except for the newline |
| 93 | 86 | character that acts as the card separator. |
| 94 | -</p> | |
| 95 | 87 | |
| 96 | -<p> | |
| 97 | 88 | All cards of the manifest occur in strict sorted lexicographical order. |
| 98 | 89 | No card may be duplicated. |
| 99 | 90 | The entire manifest may be PGP clear-signed, but otherwise it |
| 100 | 91 | may contain no additional text or data beyond what is described here. |
| 101 | -</p> | |
| 102 | 92 | |
| 103 | -<p> | |
| 104 | 93 | Allowed cards in the manifest are as follows: |
| 105 | -</p> | |
| 106 | 94 | |
| 107 | 95 | <blockquote> |
| 108 | 96 | <b>C</b> <i>checkin-comment</i><br> |
| 109 | 97 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 110 | 98 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| @@ -113,11 +101,10 @@ | ||
| 113 | 101 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 114 | 102 | <b>U</b> <i>user-login</i><br> |
| 115 | 103 | <b>Z</b> <i>manifest-checksum</i> |
| 116 | 104 | </blockquote> |
| 117 | 105 | |
| 118 | -<p> | |
| 119 | 106 | A manifest must have exactly one C-card. The sole argument to |
| 120 | 107 | the C-card is a check-in comment that describes the check-in that |
| 121 | 108 | the manifest defines. The check-in comment is text. The following |
| 122 | 109 | escape sequences are applied to the text: |
| 123 | 110 | A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73). A |
| @@ -124,24 +111,20 @@ | ||
| 124 | 111 | newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E). A backslash |
| 125 | 112 | (ASCII 0x5C) is represented as two backslashes "\\". Apart from |
| 126 | 113 | space and newline, no other whitespace characters are allowed in |
| 127 | 114 | the check-in comment. Nor are any unprintable characters allowed |
| 128 | 115 | in the comment. |
| 129 | -</p> | |
| 130 | 116 | |
| 131 | -<p> | |
| 132 | 117 | A manifest must have exactly one D-card. The sole argument to |
| 133 | 118 | the D-card is a date-time stamp in the ISO8601 format. The |
| 134 | 119 | date and time should be in coordinated universal time (UTC). |
| 135 | 120 | The format is: |
| 136 | -</p> | |
| 137 | 121 | |
| 138 | 122 | <blockquote> |
| 139 | 123 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 140 | 124 | </blockquote> |
| 141 | 125 | |
| 142 | -<p> | |
| 143 | 126 | A manifest has zero or more F-cards. Each F-card defines a file |
| 144 | 127 | (other than the manifest itself) which is part of the check-in that |
| 145 | 128 | the manifest defines. There are two, three, or four arguments. |
| 146 | 129 | The first argument |
| 147 | 130 | is the pathname of the file in the check-in relative to the root |
| @@ -157,13 +140,11 @@ | ||
| 157 | 140 | always readable and writable. This can be expressed by "w" permission |
| 158 | 141 | if desired but is optional. |
| 159 | 142 | The optional 4th argument is the name of the same file as it existed in |
| 160 | 143 | the parent check-in. If the name of the file is unchanged from its |
| 161 | 144 | parent, then the 4th argument is omitted. |
| 162 | -</p> | |
| 163 | 145 | |
| 164 | -<p> | |
| 165 | 146 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| 166 | 147 | The P-card has a varying number of arguments that |
| 167 | 148 | defines other manifests from which the current manifest |
| 168 | 149 | is derived. Each argument is an 40-character lowercase |
| 169 | 150 | hexadecimal SHA1 of the predecessor manifest. All arguments |
| @@ -171,13 +152,11 @@ | ||
| 171 | 152 | The first predecessor is the direct ancestor of the manifest. |
| 172 | 153 | Other arguments define manifests with which the first was |
| 173 | 154 | merged to yield the current manifest. Most manifests have |
| 174 | 155 | a P-card with a single argument. The first manifest in the |
| 175 | 156 | project has no ancestors and thus has no P-card. |
| 176 | -</p> | |
| 177 | 157 | |
| 178 | -<p> | |
| 179 | 158 | A manifest may optionally have a single R-card. The R-card has |
| 180 | 159 | a single argument which is the MD5 checksum of all files in |
| 181 | 160 | the check-in except the manifest itself. The checksum is expressed |
| 182 | 161 | as 32-characters of lowercase hexadecimal. The checksum is |
| 183 | 162 | computed as follows: For each file in the check-in (except for |
| @@ -185,13 +164,11 @@ | ||
| 185 | 164 | take the pathname of the file relative to the root of the |
| 186 | 165 | repository, append a single space (ASCII 0x20), the |
| 187 | 166 | size of the file in ASCII decimal, a single newline |
| 188 | 167 | character (ASCII 0x0A), and the complete text of the file. |
| 189 | 168 | Compute the MD5 checksum of the the result. |
| 190 | -</p> | |
| 191 | 169 | |
| 192 | -<p> | |
| 193 | 170 | A manifest might contain one or more T-cards used to set tags or |
| 194 | 171 | properties on the check-in. The format of the T-card is the same as |
| 195 | 172 | described in <i>Control Artifacts</i> section below, except that the |
| 196 | 173 | second argument is the single characcter "<b>*</b>" instead of an |
| 197 | 174 | artifact ID. The <b>*</b> in place of the artifact ID indicates that |
| @@ -199,42 +176,35 @@ | ||
| 199 | 176 | possible to encode the current artifact ID as part of an artifact, |
| 200 | 177 | since the act of inserting the artifact ID would change the artifact ID, |
| 201 | 178 | hence a <b>*</b> is used to represent "self". T-cards are typically |
| 202 | 179 | added to manifests in order to set the <b>branch</b> property and a |
| 203 | 180 | symbolic name when the check-in is intended to start a new branch. |
| 204 | -</p> | |
| 205 | 181 | |
| 206 | -<p> | |
| 207 | 182 | Each manifest has a single U-card. The argument to the U-card is |
| 208 | 183 | the login of the user who created the manifest. The login name |
| 209 | 184 | is encoded using the same character escapes as is used for the |
| 210 | 185 | check-in comment argument to the C-card. |
| 211 | -</p> | |
| 212 | 186 | |
| 213 | -<p> | |
| 214 | 187 | A manifest has an option Z-card as its last line. The argument |
| 215 | 188 | to the Z-card is a 32-character lowercase hexadecimal MD5 hash |
| 216 | 189 | of all prior lines of the manifest up to and including the newline |
| 217 | 190 | character that immediately precedes the "Z". The Z-card is just |
| 218 | 191 | a sanity check to prove that the manifest is well-formed and |
| 219 | 192 | consistent. |
| 220 | -</p> | |
| 221 | 193 | |
| 222 | -<p>A sample manifest from Fossil itself can be seen | |
| 194 | +A sample manifest from Fossil itself can be seen | |
| 223 | 195 | [/artifact/28987096ac | here]. |
| 224 | 196 | |
| 197 | +<a name="cluster"></a> | |
| 225 | 198 | <h2>2.0 Clusters</h2> |
| 226 | 199 | |
| 227 | -<p> | |
| 228 | 200 | A cluster is a artifact that declares the existence of other artifacts. |
| 229 | 201 | Clusters are used during repository synchronization to help |
| 230 | 202 | reduce network traffic. As such, clusters are an optimization and |
| 231 | 203 | may be removed from a repository without loss or damage to the |
| 232 | 204 | underlying project code. |
| 233 | -</p> | |
| 234 | 205 | |
| 235 | -<p> | |
| 236 | 206 | Clusters follow a syntax that is very similar to manifests. |
| 237 | 207 | A Cluster is a line-oriented text file. Newline characters |
| 238 | 208 | (ASCII 0x0a) separate the artifact into cards. Each card begins with a single |
| 239 | 209 | character "card type". Zero or more arguments may follow |
| 240 | 210 | the card type. All arguments are separated from each other |
| @@ -245,67 +215,58 @@ | ||
| 245 | 215 | All cards of a cluster occur in strict sorted lexicographical order. |
| 246 | 216 | No card may be duplicated. |
| 247 | 217 | The cluster may not contain additional text or data beyond |
| 248 | 218 | what is described here. |
| 249 | 219 | Unlike manifests, clusters are never PGP signed. |
| 250 | -</p> | |
| 251 | 220 | |
| 252 | -<p> | |
| 253 | 221 | Allowed cards in the cluster are as follows: |
| 254 | -</p> | |
| 255 | 222 | |
| 256 | 223 | <blockquote> |
| 257 | 224 | <b>M</b> <i>artifact-id</i><br /> |
| 258 | 225 | <b>Z</b> <i>checksum</i> |
| 259 | 226 | </blockquote> |
| 260 | 227 | |
| 261 | -<p> | |
| 262 | 228 | A cluster contains one or more "M" cards followed by a single "Z" |
| 263 | 229 | line. Each M card has a single argument which is the artifact ID of |
| 264 | 230 | another artifact in the repository. The Z card work exactly like |
| 265 | 231 | the Z card of a manifest. The argument to the Z card is the |
| 266 | 232 | lower-case hexadecimal representation of the MD5 checksum of all |
| 267 | 233 | prior cards in the cluster. Note that the Z card is required |
| 268 | 234 | on a cluster. |
| 269 | -</p> | |
| 270 | 235 | |
| 271 | -<p>An example cluster from Fossil can be seen | |
| 272 | -[/artifact/d03dbdd73a2a8 | here].</p> | |
| 236 | +An example cluster from Fossil can be seen | |
| 237 | +[/artifact/d03dbdd73a2a8 | here]. | |
| 273 | 238 | |
| 274 | - | |
| 239 | +<a name="ctrl"></a> | |
| 275 | 240 | <h2>3.0 Control Artifacts</h2> |
| 276 | 241 | |
| 277 | -<p> | |
| 278 | 242 | Control artifacts are used to assign properties to other artifacts |
| 279 | 243 | within the repository. The basic format of a control artifact is |
| 280 | 244 | the same as a manifest or cluster. A control artifact is a text |
| 281 | 245 | files divided into cards by newline characters. Each card has a |
| 282 | 246 | single-character card type followed by arguments. Spaces separate |
| 283 | 247 | the card type and the arguments. No surplus whitespace is allowed. |
| 284 | 248 | All cards must occur in strict lexigraphical order. |
| 285 | -</p> | |
| 286 | 249 | |
| 287 | -<p> | |
| 288 | 250 | Allowed cards in a control artifact are as follows: |
| 289 | -</p> | |
| 290 | 251 | |
| 291 | 252 | <blockquote> |
| 292 | 253 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 293 | 254 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name artifact-id ?value?</i><br /> |
| 255 | +<b>U</b> <i>user-name</i><br /> | |
| 294 | 256 | <b>Z</b> <i>checksum</i><br /> |
| 295 | 257 | </blockquote> |
| 296 | 258 | |
| 297 | -<p> | |
| 298 | 259 | A control artifact must have one D card and one Z card and |
| 299 | 260 | one or more T cards. No other cards or other text is |
| 300 | 261 | allowed in a control artifact. Control artifacts might be PGP |
| 301 | -clearsigned.</p> | |
| 262 | +clearsigned. | |
| 302 | 263 | |
| 303 | -<p>The D card and the Z card of a control artifact are the same | |
| 304 | -as in a manifest.</p> | |
| 264 | +The D card and the Z card of a control artifact are the same | |
| 265 | +as in a manifest. | |
| 305 | 266 | |
| 306 | -<p>The T card represents a "tag" or property that is applied to | |
| 267 | +The T card represents a "tag" or property that is applied to | |
| 307 | 268 | some other artifact. The T card has two or three values. The |
| 308 | 269 | second argument is the 40 character lowercase artifact ID of the artifact |
| 309 | 270 | to which the tag is to be applied. The |
| 310 | 271 | first value is the tag name. The first character of the tag |
| 311 | 272 | is either "+", "-", or "*". A "+" means the tag should be added |
| @@ -313,28 +274,37 @@ | ||
| 313 | 274 | The "*" character means the tag should be added to the artifact |
| 314 | 275 | and all direct descendants (but not branches) of the artifact down |
| 315 | 276 | to but not including the first descendant that contains a |
| 316 | 277 | more recent "-" tag with the same name. |
| 317 | 278 | The optional third argument is the value of the tag. A tag |
| 318 | -without a value is a boolean.</p> | |
| 279 | +without a value is a boolean. | |
| 319 | 280 | |
| 320 | -<p>When two or more tags with the same name are applied to the | |
| 281 | +When two or more tags with the same name are applied to the | |
| 321 | 282 | same artifact, the tag with the latest (most recent) date is |
| 322 | -used.</p> | |
| 283 | +used. | |
| 323 | 284 | |
| 324 | -<p>Some tags have special meaning. The "comment" tag when applied | |
| 285 | +Some tags have special meaning. The "comment" tag when applied | |
| 325 | 286 | to a check-in will override the check-in comment of that check-in |
| 326 | -for display purposes.</p> | |
| 287 | +for display purposes. The "user" tag overrides the name of the | |
| 288 | +check-in user. The "date" tag overrides the check-in date. | |
| 289 | +The "branch" tag sets the name of the branch that at check-in | |
| 290 | +belongs to. Symbolic tags begin with the "sym-" prefix. | |
| 291 | + | |
| 292 | +The U card is the name of the user that created the control | |
| 293 | +artifact. The Z card is the usual artifact checksum. | |
| 294 | + | |
| 295 | +An example control artifacts can be seen [/info/9d302ccda8 | here]. | |
| 296 | + | |
| 327 | 297 | |
| 328 | 298 | <a name="wikichng"></a> |
| 329 | 299 | <h2>4.0 Wiki Pages</h2> |
| 330 | 300 | |
| 331 | -<p>A wiki page is an artifact with a format similar to manifests, | |
| 301 | +A wiki page is an artifact with a format similar to manifests, | |
| 332 | 302 | clusters, and control artifacts. The artifact is divided into |
| 333 | 303 | cards by newline characters. The format of each card is as in |
| 334 | 304 | manifests, clusters, and control artifacts. Wiki artifacts accept |
| 335 | -the following card types:</p> | |
| 305 | +the following card types: | |
| 336 | 306 | |
| 337 | 307 | <blockquote> |
| 338 | 308 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 339 | 309 | <b>L</b> <i>wiki-title</i><br /> |
| 340 | 310 | <b>P</b> <i>parent-artifact-id</i>+<br /> |
| @@ -341,63 +311,245 @@ | ||
| 341 | 311 | <b>U</b> <i>user-name</i><br /> |
| 342 | 312 | <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> |
| 343 | 313 | <b>Z</b> <i>checksum</i> |
| 344 | 314 | </blockquote> |
| 345 | 315 | |
| 346 | -<p>The D card is the date and time when the wiki page was edited. | |
| 316 | +The D card is the date and time when the wiki page was edited. | |
| 347 | 317 | The P card specifies the parent wiki pages, if any. The L card |
| 348 | 318 | gives the name of the wiki page. The U card specifies the login |
| 349 | 319 | of the user who made this edit to the wiki page. The Z card is |
| 350 | -the usual checksum over the either artifact.</p> | |
| 320 | +the usual checksum over the either artifact. | |
| 351 | 321 | |
| 352 | -<p>The W card is used to specify the text of the wiki page. The | |
| 322 | +The W card is used to specify the text of the wiki page. The | |
| 353 | 323 | argument to the W card is an integer which is the number of bytes |
| 354 | 324 | of text in the wiki page. That text follows the newline character |
| 355 | 325 | that terminates the W card. The wiki text is always followed by one |
| 356 | -extra newline.</p> | |
| 326 | +extra newline. | |
| 327 | + | |
| 328 | +An example wiki artifact can be seen | |
| 329 | +[/artifact/7b2f5fd0e0 | here]. | |
| 357 | 330 | |
| 358 | 331 | <a name="tktchng"></a> |
| 359 | 332 | <h2>5.0 Ticket Changes</h2> |
| 360 | 333 | |
| 361 | -<p>A ticket-change artifact represents a change to a trouble ticket. | |
| 362 | -The following cards are allowed on a ticket change artifact:</p> | |
| 334 | +A ticket-change artifact represents a change to a trouble ticket. | |
| 335 | +The following cards are allowed on a ticket change artifact: | |
| 363 | 336 | |
| 364 | 337 | <blockquote> |
| 365 | 338 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 366 | 339 | <b>J</b> ?<b>+</b>?<i>name</i> ?<i>value</i>?<br /> |
| 367 | 340 | <b>K</b> <i>ticket-id</i><br /> |
| 368 | 341 | <b>U</b> <i>user-name</i><br /> |
| 369 | 342 | <b>Z</b> <i>checksum</i> |
| 370 | 343 | </blockquote> |
| 371 | 344 | |
| 372 | -<p> | |
| 373 | 345 | The D card is the usual date and time stamp and represents the point |
| 374 | 346 | in time when the change was entered. The U card is the login of the |
| 375 | 347 | programmer who entered this change. The Z card is the checksum over |
| 376 | -the entire artifact.</p> | |
| 348 | +the entire artifact. | |
| 377 | 349 | |
| 378 | -<p> | |
| 379 | 350 | Every ticket has a unique ID. The ticket to which this change is applied |
| 380 | 351 | is specified by the K card. A ticket exists if it contains one or |
| 381 | 352 | more changes. The first "change" to a ticket is what brings the |
| 382 | -ticket into existence.</p> | |
| 353 | +ticket into existence. | |
| 383 | 354 | |
| 384 | -<p> | |
| 385 | 355 | J cards specify changes to the "value" of "fields" in the ticket. |
| 386 | 356 | If the <i>value</i> parameter of the J card is omitted, then the |
| 387 | 357 | field is set to an empty string. |
| 388 | 358 | Each fossil server has a ticket configuration which specifies the fields its |
| 389 | 359 | understands. The ticket configuration is part of the local state for |
| 390 | 360 | the repository and thus can vary from one repository to another. |
| 391 | 361 | Hence a J card might specify a <i>field</i> that do not exist in the |
| 392 | 362 | local ticket configuration. If a J card specifies a <i>field</i> that |
| 393 | 363 | is not in the local configuration, then that J card |
| 394 | -is simply ignored.</p> | |
| 364 | +is simply ignored. | |
| 395 | 365 | |
| 396 | -<p> | |
| 397 | 366 | The first argument of the J card is the field name. The second |
| 398 | 367 | value is the field value. If the field name begins with "+" then |
| 399 | 368 | the value is appended to the prior value. Otherwise, the value |
| 400 | 369 | on the J card replaces any previous value of the field. |
| 401 | 370 | The field name and value are both encoded using the character |
| 402 | 371 | escapes defined for the C card of a manifest. |
| 403 | -</p> | |
| 372 | + | |
| 373 | +An example ticket-change artifact can be seen | |
| 374 | +[/artifact/91f1ec6af053 | here]. | |
| 375 | + | |
| 376 | +<a name="attachment"></a> | |
| 377 | +<h2>6.0 Attachments</h2> | |
| 378 | + | |
| 379 | +An attachment artifact associates some other artifact that is the | |
| 380 | +attachment (the source artifact) with a ticket or wiki page to which | |
| 381 | +the attachment is connected (the target artifact). | |
| 382 | +The following cards are allowed on an attachment artifact: | |
| 383 | + | |
| 384 | +<blockquote> | |
| 385 | +<b>A</b> <i>filename target</i> ?<i>source</i>? | |
| 386 | +<b>C</b> <i>comment</i><br> | |
| 387 | +<b>D</b> <i>time-and-date-stamp</i><br /> | |
| 388 | +<b>U</b> <i>user-name</i><br /> | |
| 389 | +<b>Z</b> <i>checksum</i> | |
| 390 | +</blockquote> | |
| 391 | + | |
| 392 | +The A card specifies a filename for the attachment in its first argument. | |
| 393 | +The second argument to the A card is the name | |
| 394 | +of the wiki page or ticket to which the attachment is connected. The | |
| 395 | +third argument is either missing or else it is the 40-character artifact | |
| 396 | +ID of the attachment itself. A missing third argument means that the | |
| 397 | +attachment should be deleted. | |
| 398 | + | |
| 399 | +The C card is an optional comment describing what the attachment is about. | |
| 400 | +The C card is optional, but there can only be one. | |
| 401 | + | |
| 402 | +A single D card is required to give the date and time when the attachment | |
| 403 | +was applied. | |
| 404 | + | |
| 405 | +A single U card gives the name of the user to added the attachment. | |
| 406 | +If an attachment is added anonymously, then the U card may be omitted. | |
| 407 | + | |
| 408 | +The Z card is the usual checksum over the rest of the attachment artifact. | |
| 409 | + | |
| 410 | + | |
| 411 | +<a name="summary"></a> | |
| 412 | +<h2>7.0 Card Summary</h2> | |
| 413 | + | |
| 414 | +The following table summaries the various kinds of cards that | |
| 415 | +appear on Fossil artifacts: | |
| 416 | + | |
| 417 | +<table border=1 width="100%"> | |
| 418 | +<tr> | |
| 419 | +<th rowspan=2 valign=bottom>Card Format</th> | |
| 420 | +<th colspan=6>Used By</th> | |
| 421 | +</tr> | |
| 422 | +<tr> | |
| 423 | +<th>Manifest</th> | |
| 424 | +<th>Cluster</th> | |
| 425 | +<th>Control</th> | |
| 426 | +<th>Wiki</th> | |
| 427 | +<th>Ticket</th> | |
| 428 | +<th>Attachment</th> | |
| 429 | +</tr> | |
| 430 | +<tr> | |
| 431 | +<td><b>A</b> <i>filename target source</i></td> | |
| 432 | +<td> </td> | |
| 433 | +<td> </td> | |
| 434 | +<td> </td> | |
| 435 | +<td> </td> | |
| 436 | +<td> </td> | |
| 437 | +<td align=center><b>X</b></td> | |
| 438 | +</tr> | |
| 439 | +<tr> | |
| 440 | +<td><b>C</b> <i>coment-text</i></td> | |
| 441 | +<td align=center><b>X</b></td> | |
| 442 | +<td> </td> | |
| 443 | +<td> </td> | |
| 444 | +<td> </td> | |
| 445 | +<td> </td> | |
| 446 | +<td align=center><b>X</b></td> | |
| 447 | +</tr> | |
| 448 | +<tr> | |
| 449 | +<td><b>D</b> <i>date-time-stamp</i></td> | |
| 450 | +<td align=center><b>X</b></td> | |
| 451 | +<td align=center> </td> | |
| 452 | +<td align=center><b>X</b></td> | |
| 453 | +<td align=center><b>X</b></td> | |
| 454 | +<td align=center><b>X</b></td> | |
| 455 | +<td align=center><b>X</b></td> | |
| 456 | +</tr> | |
| 457 | +<tr> | |
| 458 | +<td><b>F</b> <i>filename uuid permissions oldname</i></td> | |
| 459 | +<td align=center><b>X</b></td> | |
| 460 | +<td align=center> </td> | |
| 461 | +<td align=center> </td> | |
| 462 | +<td align=center> </td> | |
| 463 | +<td align=center> </td> | |
| 464 | +<td align=center> </td> | |
| 465 | +</tr> | |
| 466 | +<tr> | |
| 467 | +<td><b>J</b> <i>name value</i></td> | |
| 468 | +<td align=center> </td> | |
| 469 | +<td align=center> </td> | |
| 470 | +<td align=center> </td> | |
| 471 | +<td align=center> </td> | |
| 472 | +<td align=center><b>X</b></td> | |
| 473 | +<td align=center> </td> | |
| 474 | +</tr> | |
| 475 | +<tr> | |
| 476 | +<td><b>K</b> <i>ticket-uuid</i></td> | |
| 477 | +<td align=center> </td> | |
| 478 | +<td align=center> </td> | |
| 479 | +<td align=center> </td> | |
| 480 | +<td align=center> </td> | |
| 481 | +<td align=center><b>X</b></td> | |
| 482 | +<td align=center> </td> | |
| 483 | +</tr> | |
| 484 | +<tr> | |
| 485 | +<td><b>L</b> <i>wiki-title</i></td> | |
| 486 | +<td align=center> </td> | |
| 487 | +<td align=center> </td> | |
| 488 | +<td align=center> </td> | |
| 489 | +<td align=center><b>X</b></td> | |
| 490 | +<td align=center> </td> | |
| 491 | +<td align=center> </td> | |
| 492 | +</tr> | |
| 493 | +<tr> | |
| 494 | +<td><b>M</b> <i>uuid</i></td> | |
| 495 | +<td align=center> </td> | |
| 496 | +<td align=center><b>X</b></td> | |
| 497 | +<td align=center> </td> | |
| 498 | +<td align=center> </td> | |
| 499 | +<td align=center> </td> | |
| 500 | +<td align=center> </td> | |
| 501 | +</tr> | |
| 502 | +<tr> | |
| 503 | +<td><b>P</b> <i>uuid ...</i></td> | |
| 504 | +<td align=center><b>X</b></td> | |
| 505 | +<td align=center> </td> | |
| 506 | +<td align=center> </td> | |
| 507 | +<td align=center><b>X</b></td> | |
| 508 | +<td align=center> </td> | |
| 509 | +<td align=center> </td> | |
| 510 | +</tr> | |
| 511 | +<tr> | |
| 512 | +<td><b>R</b> <i>md5sum</i></td> | |
| 513 | +<td align=center><b>X</b></td> | |
| 514 | +<td align=center> </td> | |
| 515 | +<td align=center> </td> | |
| 516 | +<td align=center> </td> | |
| 517 | +<td align=center> </td> | |
| 518 | +<td align=center> </td> | |
| 519 | +<tr> | |
| 520 | +<td><b>T</b> (<b>+</b>|<b>*</b>|<b>-</b>)<i>tagname uuid value</i></td> | |
| 521 | +<td align=center><b>X</b></td> | |
| 522 | +<td align=center> </td> | |
| 523 | +<td align=center><b>X</b></td> | |
| 524 | +<td align=center> </td> | |
| 525 | +<td align=center> </td> | |
| 526 | +<td align=center> </td> | |
| 527 | +</tr> | |
| 528 | +<tr> | |
| 529 | +<td><b>U</b> <i>username</i></td> | |
| 530 | +<td align=center><b>X</b></td> | |
| 531 | +<td align=center> </td> | |
| 532 | +<td align=center><b>X</b></td> | |
| 533 | +<td align=center><b>X</b></td> | |
| 534 | +<td align=center><b>X</b></td> | |
| 535 | +<td align=center><b>X</b></td> | |
| 536 | +</tr> | |
| 537 | +<tr> | |
| 538 | +<td><b>W</b> <i>size</i></td> | |
| 539 | +<td align=center> </td> | |
| 540 | +<td align=center> </td> | |
| 541 | +<td align=center> </td> | |
| 542 | +<td align=center><b>X</b></td> | |
| 543 | +<td align=center> </td> | |
| 544 | +<td align=center> </td> | |
| 545 | +</tr> | |
| 546 | +<tr> | |
| 547 | +<td><b>Z</b> <i>md5sum</i></td> | |
| 548 | +<td align=center><b>X</b></td> | |
| 549 | +<td align=center><b>X</b></td> | |
| 550 | +<td align=center><b>X</b></td> | |
| 551 | +<td align=center><b>X</b></td> | |
| 552 | +<td align=center><b>X</b></td> | |
| 553 | +<td align=center><b>X</b></td> | |
| 554 | +</tr> | |
| 555 | +</table> | |
| 404 | 556 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -1,26 +1,23 @@ | |
| 1 | <title>Fossil File Formats</title> |
| 2 | <h1 align="center"> |
| 3 | Fossil File Formats |
| 4 | </h1> |
| 5 | |
| 6 | <p>The global state of a fossil repository is kept simple so that it can |
| 7 | endure in useful form for decades or centuries. |
| 8 | A fossil repository is intended to be readable, |
| 9 | searchable, and extensible by people not yet born.</p> |
| 10 | |
| 11 | <p> |
| 12 | The global state of a fossil repository is an unordered |
| 13 | set of <i>artifacts</i>. |
| 14 | An artifact might be a source code file, the text of a wiki page, |
| 15 | part of a trouble ticket, or one of several special control artifacts |
| 16 | used to show the relationships between other artifacts within the |
| 17 | project. Each artifact is normally represented on disk as a separate |
| 18 | file. Artifacts can be text or binary. |
| 19 | </p> |
| 20 | |
| 21 | <p> |
| 22 | In addition to the global state, |
| 23 | each fossil repository also contains local state. |
| 24 | The local state consists of web-page formatting |
| 25 | preferences, authorized users, ticket display and reporting formats, |
| 26 | and so forth. The global state is shared in common among all |
| @@ -29,82 +26,73 @@ | |
| 29 | The local state is not versioned and is not synchronized |
| 30 | with the global state. |
| 31 | The local state is not composed of artifacts and is not intended to be enduring. |
| 32 | This document is concerned with global state only. Local state is only |
| 33 | mentioned here in order to distinguish it from global state. |
| 34 | </p> |
| 35 | |
| 36 | <p> |
| 37 | Each artifact in the repository is named by its SHA1 hash. |
| 38 | No prefixes or meta information is added to a artifact before |
| 39 | its hash is computed. The name of a artifact in the repository |
| 40 | is exactly the same SHA1 hash that is computed by sha1sum |
| 41 | on the file as it exists in your source tree.</p> |
| 42 | |
| 43 | <p> |
| 44 | Some artifacts have a particular format which gives them special |
| 45 | meaning to fossil. Fossil recognizes:</p> |
| 46 | |
| 47 | <ul> |
| 48 | <li> Manifests </li> |
| 49 | <li> Clusters </li> |
| 50 | <li> Control Artifacts </li> |
| 51 | <li> Wiki Pages </li> |
| 52 | <li> Ticket Changes </li> |
| 53 | </ul> |
| 54 | |
| 55 | <p>These five artifact types are described in the sequel.</p> |
| 56 | |
| 57 | <p>In the current implementation (as of 2009-01-25) the artifacts that |
| 58 | make up a fossil repository are stored in in as delta- and zlib-compressed |
| 59 | blobs in an <a href="http://www.sqlite.org/">SQLite</a> database. This |
| 60 | is an implementation detail and might change in a future release. For |
| 61 | the purpose of this article "file format" means the format of the artifacts, |
| 62 | not how the artifacts are stored on disk. It is the artifact format that |
| 63 | is intended to be enduring. The specifics of how artifacts are stored on |
| 64 | disk, though stable, is not intended to live as long as the |
| 65 | artifact format.</p> |
| 66 | |
| 67 | <h2>1.0 The Manifest</h2> |
| 68 | |
| 69 | <p>A manifest defines a check-in or version of the project |
| 70 | source tree. The manifest contains a list of artifacts for |
| 71 | each file in the project and the corresponding filenames, as |
| 72 | well as information such as parent check-ins, the name of the |
| 73 | programmer who created the check-in, the date and time when |
| 74 | the check-in was created, and any check-in comments associated |
| 75 | with the check-in.</p> |
| 76 | |
| 77 | <p> |
| 78 | Any artifact in the repository that follows the syntactic rules |
| 79 | of a manifest is a manifest. Note that a manifest can |
| 80 | be both a real manifest and also a content file, though this |
| 81 | is rare. |
| 82 | </p> |
| 83 | |
| 84 | <p> |
| 85 | A manifest is a text file. Newline characters |
| 86 | (ASCII 0x0a) separate the file into "cards". |
| 87 | Each card begins with a single |
| 88 | character "card type". Zero or more arguments may follow |
| 89 | the card type. All arguments are separated from each other |
| 90 | and from the card-type character by a single space |
| 91 | character. There is no surplus white space between arguments |
| 92 | and no leading or trailing whitespace except for the newline |
| 93 | character that acts as the card separator. |
| 94 | </p> |
| 95 | |
| 96 | <p> |
| 97 | All cards of the manifest occur in strict sorted lexicographical order. |
| 98 | No card may be duplicated. |
| 99 | The entire manifest may be PGP clear-signed, but otherwise it |
| 100 | may contain no additional text or data beyond what is described here. |
| 101 | </p> |
| 102 | |
| 103 | <p> |
| 104 | Allowed cards in the manifest are as follows: |
| 105 | </p> |
| 106 | |
| 107 | <blockquote> |
| 108 | <b>C</b> <i>checkin-comment</i><br> |
| 109 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 110 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| @@ -113,11 +101,10 @@ | |
| 113 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 114 | <b>U</b> <i>user-login</i><br> |
| 115 | <b>Z</b> <i>manifest-checksum</i> |
| 116 | </blockquote> |
| 117 | |
| 118 | <p> |
| 119 | A manifest must have exactly one C-card. The sole argument to |
| 120 | the C-card is a check-in comment that describes the check-in that |
| 121 | the manifest defines. The check-in comment is text. The following |
| 122 | escape sequences are applied to the text: |
| 123 | A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73). A |
| @@ -124,24 +111,20 @@ | |
| 124 | newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E). A backslash |
| 125 | (ASCII 0x5C) is represented as two backslashes "\\". Apart from |
| 126 | space and newline, no other whitespace characters are allowed in |
| 127 | the check-in comment. Nor are any unprintable characters allowed |
| 128 | in the comment. |
| 129 | </p> |
| 130 | |
| 131 | <p> |
| 132 | A manifest must have exactly one D-card. The sole argument to |
| 133 | the D-card is a date-time stamp in the ISO8601 format. The |
| 134 | date and time should be in coordinated universal time (UTC). |
| 135 | The format is: |
| 136 | </p> |
| 137 | |
| 138 | <blockquote> |
| 139 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 140 | </blockquote> |
| 141 | |
| 142 | <p> |
| 143 | A manifest has zero or more F-cards. Each F-card defines a file |
| 144 | (other than the manifest itself) which is part of the check-in that |
| 145 | the manifest defines. There are two, three, or four arguments. |
| 146 | The first argument |
| 147 | is the pathname of the file in the check-in relative to the root |
| @@ -157,13 +140,11 @@ | |
| 157 | always readable and writable. This can be expressed by "w" permission |
| 158 | if desired but is optional. |
| 159 | The optional 4th argument is the name of the same file as it existed in |
| 160 | the parent check-in. If the name of the file is unchanged from its |
| 161 | parent, then the 4th argument is omitted. |
| 162 | </p> |
| 163 | |
| 164 | <p> |
| 165 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| 166 | The P-card has a varying number of arguments that |
| 167 | defines other manifests from which the current manifest |
| 168 | is derived. Each argument is an 40-character lowercase |
| 169 | hexadecimal SHA1 of the predecessor manifest. All arguments |
| @@ -171,13 +152,11 @@ | |
| 171 | The first predecessor is the direct ancestor of the manifest. |
| 172 | Other arguments define manifests with which the first was |
| 173 | merged to yield the current manifest. Most manifests have |
| 174 | a P-card with a single argument. The first manifest in the |
| 175 | project has no ancestors and thus has no P-card. |
| 176 | </p> |
| 177 | |
| 178 | <p> |
| 179 | A manifest may optionally have a single R-card. The R-card has |
| 180 | a single argument which is the MD5 checksum of all files in |
| 181 | the check-in except the manifest itself. The checksum is expressed |
| 182 | as 32-characters of lowercase hexadecimal. The checksum is |
| 183 | computed as follows: For each file in the check-in (except for |
| @@ -185,13 +164,11 @@ | |
| 185 | take the pathname of the file relative to the root of the |
| 186 | repository, append a single space (ASCII 0x20), the |
| 187 | size of the file in ASCII decimal, a single newline |
| 188 | character (ASCII 0x0A), and the complete text of the file. |
| 189 | Compute the MD5 checksum of the the result. |
| 190 | </p> |
| 191 | |
| 192 | <p> |
| 193 | A manifest might contain one or more T-cards used to set tags or |
| 194 | properties on the check-in. The format of the T-card is the same as |
| 195 | described in <i>Control Artifacts</i> section below, except that the |
| 196 | second argument is the single characcter "<b>*</b>" instead of an |
| 197 | artifact ID. The <b>*</b> in place of the artifact ID indicates that |
| @@ -199,42 +176,35 @@ | |
| 199 | possible to encode the current artifact ID as part of an artifact, |
| 200 | since the act of inserting the artifact ID would change the artifact ID, |
| 201 | hence a <b>*</b> is used to represent "self". T-cards are typically |
| 202 | added to manifests in order to set the <b>branch</b> property and a |
| 203 | symbolic name when the check-in is intended to start a new branch. |
| 204 | </p> |
| 205 | |
| 206 | <p> |
| 207 | Each manifest has a single U-card. The argument to the U-card is |
| 208 | the login of the user who created the manifest. The login name |
| 209 | is encoded using the same character escapes as is used for the |
| 210 | check-in comment argument to the C-card. |
| 211 | </p> |
| 212 | |
| 213 | <p> |
| 214 | A manifest has an option Z-card as its last line. The argument |
| 215 | to the Z-card is a 32-character lowercase hexadecimal MD5 hash |
| 216 | of all prior lines of the manifest up to and including the newline |
| 217 | character that immediately precedes the "Z". The Z-card is just |
| 218 | a sanity check to prove that the manifest is well-formed and |
| 219 | consistent. |
| 220 | </p> |
| 221 | |
| 222 | <p>A sample manifest from Fossil itself can be seen |
| 223 | [/artifact/28987096ac | here]. |
| 224 | |
| 225 | <h2>2.0 Clusters</h2> |
| 226 | |
| 227 | <p> |
| 228 | A cluster is a artifact that declares the existence of other artifacts. |
| 229 | Clusters are used during repository synchronization to help |
| 230 | reduce network traffic. As such, clusters are an optimization and |
| 231 | may be removed from a repository without loss or damage to the |
| 232 | underlying project code. |
| 233 | </p> |
| 234 | |
| 235 | <p> |
| 236 | Clusters follow a syntax that is very similar to manifests. |
| 237 | A Cluster is a line-oriented text file. Newline characters |
| 238 | (ASCII 0x0a) separate the artifact into cards. Each card begins with a single |
| 239 | character "card type". Zero or more arguments may follow |
| 240 | the card type. All arguments are separated from each other |
| @@ -245,67 +215,58 @@ | |
| 245 | All cards of a cluster occur in strict sorted lexicographical order. |
| 246 | No card may be duplicated. |
| 247 | The cluster may not contain additional text or data beyond |
| 248 | what is described here. |
| 249 | Unlike manifests, clusters are never PGP signed. |
| 250 | </p> |
| 251 | |
| 252 | <p> |
| 253 | Allowed cards in the cluster are as follows: |
| 254 | </p> |
| 255 | |
| 256 | <blockquote> |
| 257 | <b>M</b> <i>artifact-id</i><br /> |
| 258 | <b>Z</b> <i>checksum</i> |
| 259 | </blockquote> |
| 260 | |
| 261 | <p> |
| 262 | A cluster contains one or more "M" cards followed by a single "Z" |
| 263 | line. Each M card has a single argument which is the artifact ID of |
| 264 | another artifact in the repository. The Z card work exactly like |
| 265 | the Z card of a manifest. The argument to the Z card is the |
| 266 | lower-case hexadecimal representation of the MD5 checksum of all |
| 267 | prior cards in the cluster. Note that the Z card is required |
| 268 | on a cluster. |
| 269 | </p> |
| 270 | |
| 271 | <p>An example cluster from Fossil can be seen |
| 272 | [/artifact/d03dbdd73a2a8 | here].</p> |
| 273 | |
| 274 | |
| 275 | <h2>3.0 Control Artifacts</h2> |
| 276 | |
| 277 | <p> |
| 278 | Control artifacts are used to assign properties to other artifacts |
| 279 | within the repository. The basic format of a control artifact is |
| 280 | the same as a manifest or cluster. A control artifact is a text |
| 281 | files divided into cards by newline characters. Each card has a |
| 282 | single-character card type followed by arguments. Spaces separate |
| 283 | the card type and the arguments. No surplus whitespace is allowed. |
| 284 | All cards must occur in strict lexigraphical order. |
| 285 | </p> |
| 286 | |
| 287 | <p> |
| 288 | Allowed cards in a control artifact are as follows: |
| 289 | </p> |
| 290 | |
| 291 | <blockquote> |
| 292 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 293 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name artifact-id ?value?</i><br /> |
| 294 | <b>Z</b> <i>checksum</i><br /> |
| 295 | </blockquote> |
| 296 | |
| 297 | <p> |
| 298 | A control artifact must have one D card and one Z card and |
| 299 | one or more T cards. No other cards or other text is |
| 300 | allowed in a control artifact. Control artifacts might be PGP |
| 301 | clearsigned.</p> |
| 302 | |
| 303 | <p>The D card and the Z card of a control artifact are the same |
| 304 | as in a manifest.</p> |
| 305 | |
| 306 | <p>The T card represents a "tag" or property that is applied to |
| 307 | some other artifact. The T card has two or three values. The |
| 308 | second argument is the 40 character lowercase artifact ID of the artifact |
| 309 | to which the tag is to be applied. The |
| 310 | first value is the tag name. The first character of the tag |
| 311 | is either "+", "-", or "*". A "+" means the tag should be added |
| @@ -313,28 +274,37 @@ | |
| 313 | The "*" character means the tag should be added to the artifact |
| 314 | and all direct descendants (but not branches) of the artifact down |
| 315 | to but not including the first descendant that contains a |
| 316 | more recent "-" tag with the same name. |
| 317 | The optional third argument is the value of the tag. A tag |
| 318 | without a value is a boolean.</p> |
| 319 | |
| 320 | <p>When two or more tags with the same name are applied to the |
| 321 | same artifact, the tag with the latest (most recent) date is |
| 322 | used.</p> |
| 323 | |
| 324 | <p>Some tags have special meaning. The "comment" tag when applied |
| 325 | to a check-in will override the check-in comment of that check-in |
| 326 | for display purposes.</p> |
| 327 | |
| 328 | <a name="wikichng"></a> |
| 329 | <h2>4.0 Wiki Pages</h2> |
| 330 | |
| 331 | <p>A wiki page is an artifact with a format similar to manifests, |
| 332 | clusters, and control artifacts. The artifact is divided into |
| 333 | cards by newline characters. The format of each card is as in |
| 334 | manifests, clusters, and control artifacts. Wiki artifacts accept |
| 335 | the following card types:</p> |
| 336 | |
| 337 | <blockquote> |
| 338 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 339 | <b>L</b> <i>wiki-title</i><br /> |
| 340 | <b>P</b> <i>parent-artifact-id</i>+<br /> |
| @@ -341,63 +311,245 @@ | |
| 341 | <b>U</b> <i>user-name</i><br /> |
| 342 | <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> |
| 343 | <b>Z</b> <i>checksum</i> |
| 344 | </blockquote> |
| 345 | |
| 346 | <p>The D card is the date and time when the wiki page was edited. |
| 347 | The P card specifies the parent wiki pages, if any. The L card |
| 348 | gives the name of the wiki page. The U card specifies the login |
| 349 | of the user who made this edit to the wiki page. The Z card is |
| 350 | the usual checksum over the either artifact.</p> |
| 351 | |
| 352 | <p>The W card is used to specify the text of the wiki page. The |
| 353 | argument to the W card is an integer which is the number of bytes |
| 354 | of text in the wiki page. That text follows the newline character |
| 355 | that terminates the W card. The wiki text is always followed by one |
| 356 | extra newline.</p> |
| 357 | |
| 358 | <a name="tktchng"></a> |
| 359 | <h2>5.0 Ticket Changes</h2> |
| 360 | |
| 361 | <p>A ticket-change artifact represents a change to a trouble ticket. |
| 362 | The following cards are allowed on a ticket change artifact:</p> |
| 363 | |
| 364 | <blockquote> |
| 365 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 366 | <b>J</b> ?<b>+</b>?<i>name</i> ?<i>value</i>?<br /> |
| 367 | <b>K</b> <i>ticket-id</i><br /> |
| 368 | <b>U</b> <i>user-name</i><br /> |
| 369 | <b>Z</b> <i>checksum</i> |
| 370 | </blockquote> |
| 371 | |
| 372 | <p> |
| 373 | The D card is the usual date and time stamp and represents the point |
| 374 | in time when the change was entered. The U card is the login of the |
| 375 | programmer who entered this change. The Z card is the checksum over |
| 376 | the entire artifact.</p> |
| 377 | |
| 378 | <p> |
| 379 | Every ticket has a unique ID. The ticket to which this change is applied |
| 380 | is specified by the K card. A ticket exists if it contains one or |
| 381 | more changes. The first "change" to a ticket is what brings the |
| 382 | ticket into existence.</p> |
| 383 | |
| 384 | <p> |
| 385 | J cards specify changes to the "value" of "fields" in the ticket. |
| 386 | If the <i>value</i> parameter of the J card is omitted, then the |
| 387 | field is set to an empty string. |
| 388 | Each fossil server has a ticket configuration which specifies the fields its |
| 389 | understands. The ticket configuration is part of the local state for |
| 390 | the repository and thus can vary from one repository to another. |
| 391 | Hence a J card might specify a <i>field</i> that do not exist in the |
| 392 | local ticket configuration. If a J card specifies a <i>field</i> that |
| 393 | is not in the local configuration, then that J card |
| 394 | is simply ignored.</p> |
| 395 | |
| 396 | <p> |
| 397 | The first argument of the J card is the field name. The second |
| 398 | value is the field value. If the field name begins with "+" then |
| 399 | the value is appended to the prior value. Otherwise, the value |
| 400 | on the J card replaces any previous value of the field. |
| 401 | The field name and value are both encoded using the character |
| 402 | escapes defined for the C card of a manifest. |
| 403 | </p> |
| 404 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -1,26 +1,23 @@ | |
| 1 | <title>Fossil File Formats</title> |
| 2 | <h1 align="center"> |
| 3 | Fossil File Formats |
| 4 | </h1> |
| 5 | |
| 6 | The global state of a fossil repository is kept simple so that it can |
| 7 | endure in useful form for decades or centuries. |
| 8 | A fossil repository is intended to be readable, |
| 9 | searchable, and extensible by people not yet born. |
| 10 | |
| 11 | The global state of a fossil repository is an unordered |
| 12 | set of <i>artifacts</i>. |
| 13 | An artifact might be a source code file, the text of a wiki page, |
| 14 | part of a trouble ticket, or one of several special control artifacts |
| 15 | used to show the relationships between other artifacts within the |
| 16 | project. Each artifact is normally represented on disk as a separate |
| 17 | file. Artifacts can be text or binary. |
| 18 | |
| 19 | In addition to the global state, |
| 20 | each fossil repository also contains local state. |
| 21 | The local state consists of web-page formatting |
| 22 | preferences, authorized users, ticket display and reporting formats, |
| 23 | and so forth. The global state is shared in common among all |
| @@ -29,82 +26,73 @@ | |
| 26 | The local state is not versioned and is not synchronized |
| 27 | with the global state. |
| 28 | The local state is not composed of artifacts and is not intended to be enduring. |
| 29 | This document is concerned with global state only. Local state is only |
| 30 | mentioned here in order to distinguish it from global state. |
| 31 | |
| 32 | Each artifact in the repository is named by its SHA1 hash. |
| 33 | No prefixes or meta information is added to a artifact before |
| 34 | its hash is computed. The name of a artifact in the repository |
| 35 | is exactly the same SHA1 hash that is computed by sha1sum |
| 36 | on the file as it exists in your source tree.</p> |
| 37 | |
| 38 | Some artifacts have a particular format which gives them special |
| 39 | meaning to fossil. Fossil recognizes: |
| 40 | |
| 41 | <ul> |
| 42 | <li> [#manifest | Manifests] </li> |
| 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> [#artifact | Artifacts] </li> |
| 48 | </ul> |
| 49 | |
| 50 | These five artifact types are described in the sequel. |
| 51 | |
| 52 | In the current implementation (as of 2009-01-25) the artifacts that |
| 53 | make up a fossil repository are stored in in as delta- and zlib-compressed |
| 54 | blobs in an <a href="http://www.sqlite.org/">SQLite</a> database. This |
| 55 | is an implementation detail and might change in a future release. For |
| 56 | the purpose of this article "file format" means the format of the artifacts, |
| 57 | not how the artifacts are stored on disk. It is the artifact format that |
| 58 | is intended to be enduring. The specifics of how artifacts are stored on |
| 59 | disk, though stable, is not intended to live as long as the |
| 60 | artifact format. |
| 61 | |
| 62 | <a name="manifest"></a> |
| 63 | <h2>1.0 The Manifest</h2> |
| 64 | |
| 65 | A manifest defines a check-in or version of the project |
| 66 | source tree. The manifest contains a list of artifacts for |
| 67 | each file in the project and the corresponding filenames, as |
| 68 | well as information such as parent check-ins, the name of the |
| 69 | programmer who created the check-in, the date and time when |
| 70 | the check-in was created, and any check-in comments associated |
| 71 | with the check-in. |
| 72 | |
| 73 | Any artifact in the repository that follows the syntactic rules |
| 74 | of a manifest is a manifest. Note that a manifest can |
| 75 | be both a real manifest and also a content file, though this |
| 76 | is rare. |
| 77 | |
| 78 | A manifest is a text file. Newline characters |
| 79 | (ASCII 0x0a) separate the file into "cards". |
| 80 | Each card begins with a single |
| 81 | character "card type". Zero or more arguments may follow |
| 82 | the card type. All arguments are separated from each other |
| 83 | and from the card-type character by a single space |
| 84 | character. There is no surplus white space between arguments |
| 85 | and no leading or trailing whitespace except for the newline |
| 86 | character that acts as the card separator. |
| 87 | |
| 88 | All cards of the manifest occur in strict sorted lexicographical order. |
| 89 | No card may be duplicated. |
| 90 | The entire manifest may be PGP clear-signed, but otherwise it |
| 91 | may contain no additional text or data beyond what is described here. |
| 92 | |
| 93 | Allowed cards in the manifest are as follows: |
| 94 | |
| 95 | <blockquote> |
| 96 | <b>C</b> <i>checkin-comment</i><br> |
| 97 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 98 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| @@ -113,11 +101,10 @@ | |
| 101 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 102 | <b>U</b> <i>user-login</i><br> |
| 103 | <b>Z</b> <i>manifest-checksum</i> |
| 104 | </blockquote> |
| 105 | |
| 106 | A manifest must have exactly one C-card. The sole argument to |
| 107 | the C-card is a check-in comment that describes the check-in that |
| 108 | the manifest defines. The check-in comment is text. The following |
| 109 | escape sequences are applied to the text: |
| 110 | A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73). A |
| @@ -124,24 +111,20 @@ | |
| 111 | newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E). A backslash |
| 112 | (ASCII 0x5C) is represented as two backslashes "\\". Apart from |
| 113 | space and newline, no other whitespace characters are allowed in |
| 114 | the check-in comment. Nor are any unprintable characters allowed |
| 115 | in the comment. |
| 116 | |
| 117 | A manifest must have exactly one D-card. The sole argument to |
| 118 | the D-card is a date-time stamp in the ISO8601 format. The |
| 119 | date and time should be in coordinated universal time (UTC). |
| 120 | The format is: |
| 121 | |
| 122 | <blockquote> |
| 123 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 124 | </blockquote> |
| 125 | |
| 126 | A manifest has zero or more F-cards. Each F-card defines a file |
| 127 | (other than the manifest itself) which is part of the check-in that |
| 128 | the manifest defines. There are two, three, or four arguments. |
| 129 | The first argument |
| 130 | is the pathname of the file in the check-in relative to the root |
| @@ -157,13 +140,11 @@ | |
| 140 | always readable and writable. This can be expressed by "w" permission |
| 141 | if desired but is optional. |
| 142 | The optional 4th argument is the name of the same file as it existed in |
| 143 | the parent check-in. If the name of the file is unchanged from its |
| 144 | parent, then the 4th argument is omitted. |
| 145 | |
| 146 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| 147 | The P-card has a varying number of arguments that |
| 148 | defines other manifests from which the current manifest |
| 149 | is derived. Each argument is an 40-character lowercase |
| 150 | hexadecimal SHA1 of the predecessor manifest. All arguments |
| @@ -171,13 +152,11 @@ | |
| 152 | The first predecessor is the direct ancestor of the manifest. |
| 153 | Other arguments define manifests with which the first was |
| 154 | merged to yield the current manifest. Most manifests have |
| 155 | a P-card with a single argument. The first manifest in the |
| 156 | project has no ancestors and thus has no P-card. |
| 157 | |
| 158 | A manifest may optionally have a single R-card. The R-card has |
| 159 | a single argument which is the MD5 checksum of all files in |
| 160 | the check-in except the manifest itself. The checksum is expressed |
| 161 | as 32-characters of lowercase hexadecimal. The checksum is |
| 162 | computed as follows: For each file in the check-in (except for |
| @@ -185,13 +164,11 @@ | |
| 164 | take the pathname of the file relative to the root of the |
| 165 | repository, append a single space (ASCII 0x20), the |
| 166 | size of the file in ASCII decimal, a single newline |
| 167 | character (ASCII 0x0A), and the complete text of the file. |
| 168 | Compute the MD5 checksum of the the result. |
| 169 | |
| 170 | A manifest might contain one or more T-cards used to set tags or |
| 171 | properties on the check-in. The format of the T-card is the same as |
| 172 | described in <i>Control Artifacts</i> section below, except that the |
| 173 | second argument is the single characcter "<b>*</b>" instead of an |
| 174 | artifact ID. The <b>*</b> in place of the artifact ID indicates that |
| @@ -199,42 +176,35 @@ | |
| 176 | possible to encode the current artifact ID as part of an artifact, |
| 177 | since the act of inserting the artifact ID would change the artifact ID, |
| 178 | hence a <b>*</b> is used to represent "self". T-cards are typically |
| 179 | added to manifests in order to set the <b>branch</b> property and a |
| 180 | symbolic name when the check-in is intended to start a new branch. |
| 181 | |
| 182 | Each manifest has a single U-card. The argument to the U-card is |
| 183 | the login of the user who created the manifest. The login name |
| 184 | is encoded using the same character escapes as is used for the |
| 185 | check-in comment argument to the C-card. |
| 186 | |
| 187 | A manifest has an option Z-card as its last line. The argument |
| 188 | to the Z-card is a 32-character lowercase hexadecimal MD5 hash |
| 189 | of all prior lines of the manifest up to and including the newline |
| 190 | character that immediately precedes the "Z". The Z-card is just |
| 191 | a sanity check to prove that the manifest is well-formed and |
| 192 | consistent. |
| 193 | |
| 194 | A sample manifest from Fossil itself can be seen |
| 195 | [/artifact/28987096ac | here]. |
| 196 | |
| 197 | <a name="cluster"></a> |
| 198 | <h2>2.0 Clusters</h2> |
| 199 | |
| 200 | A cluster is a artifact that declares the existence of other artifacts. |
| 201 | Clusters are used during repository synchronization to help |
| 202 | reduce network traffic. As such, clusters are an optimization and |
| 203 | may be removed from a repository without loss or damage to the |
| 204 | underlying project code. |
| 205 | |
| 206 | Clusters follow a syntax that is very similar to manifests. |
| 207 | A Cluster is a line-oriented text file. Newline characters |
| 208 | (ASCII 0x0a) separate the artifact into cards. Each card begins with a single |
| 209 | character "card type". Zero or more arguments may follow |
| 210 | the card type. All arguments are separated from each other |
| @@ -245,67 +215,58 @@ | |
| 215 | All cards of a cluster occur in strict sorted lexicographical order. |
| 216 | No card may be duplicated. |
| 217 | The cluster may not contain additional text or data beyond |
| 218 | what is described here. |
| 219 | Unlike manifests, clusters are never PGP signed. |
| 220 | |
| 221 | Allowed cards in the cluster are as follows: |
| 222 | |
| 223 | <blockquote> |
| 224 | <b>M</b> <i>artifact-id</i><br /> |
| 225 | <b>Z</b> <i>checksum</i> |
| 226 | </blockquote> |
| 227 | |
| 228 | A cluster contains one or more "M" cards followed by a single "Z" |
| 229 | line. Each M card has a single argument which is the artifact ID of |
| 230 | another artifact in the repository. The Z card work exactly like |
| 231 | the Z card of a manifest. The argument to the Z card is the |
| 232 | lower-case hexadecimal representation of the MD5 checksum of all |
| 233 | prior cards in the cluster. Note that the Z card is required |
| 234 | on a cluster. |
| 235 | |
| 236 | An example cluster from Fossil can be seen |
| 237 | [/artifact/d03dbdd73a2a8 | here]. |
| 238 | |
| 239 | <a name="ctrl"></a> |
| 240 | <h2>3.0 Control Artifacts</h2> |
| 241 | |
| 242 | Control artifacts are used to assign properties to other artifacts |
| 243 | within the repository. The basic format of a control artifact is |
| 244 | the same as a manifest or cluster. A control artifact is a text |
| 245 | files divided into cards by newline characters. Each card has a |
| 246 | single-character card type followed by arguments. Spaces separate |
| 247 | the card type and the arguments. No surplus whitespace is allowed. |
| 248 | All cards must occur in strict lexigraphical order. |
| 249 | |
| 250 | Allowed cards in a control artifact are as follows: |
| 251 | |
| 252 | <blockquote> |
| 253 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 254 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name artifact-id ?value?</i><br /> |
| 255 | <b>U</b> <i>user-name</i><br /> |
| 256 | <b>Z</b> <i>checksum</i><br /> |
| 257 | </blockquote> |
| 258 | |
| 259 | A control artifact must have one D card and one Z card and |
| 260 | one or more T cards. No other cards or other text is |
| 261 | allowed in a control artifact. Control artifacts might be PGP |
| 262 | clearsigned. |
| 263 | |
| 264 | The D card and the Z card of a control artifact are the same |
| 265 | as in a manifest. |
| 266 | |
| 267 | The T card represents a "tag" or property that is applied to |
| 268 | some other artifact. The T card has two or three values. The |
| 269 | second argument is the 40 character lowercase artifact ID of the artifact |
| 270 | to which the tag is to be applied. The |
| 271 | first value is the tag name. The first character of the tag |
| 272 | is either "+", "-", or "*". A "+" means the tag should be added |
| @@ -313,28 +274,37 @@ | |
| 274 | The "*" character means the tag should be added to the artifact |
| 275 | and all direct descendants (but not branches) of the artifact down |
| 276 | to but not including the first descendant that contains a |
| 277 | more recent "-" tag with the same name. |
| 278 | The optional third argument is the value of the tag. A tag |
| 279 | without a value is a boolean. |
| 280 | |
| 281 | When two or more tags with the same name are applied to the |
| 282 | same artifact, the tag with the latest (most recent) date is |
| 283 | used. |
| 284 | |
| 285 | Some tags have special meaning. The "comment" tag when applied |
| 286 | to a check-in will override the check-in comment of that check-in |
| 287 | for display purposes. The "user" tag overrides the name of the |
| 288 | check-in user. The "date" tag overrides the check-in date. |
| 289 | The "branch" tag sets the name of the branch that at check-in |
| 290 | belongs to. Symbolic tags begin with the "sym-" prefix. |
| 291 | |
| 292 | The U card is the name of the user that created the control |
| 293 | artifact. The Z card is the usual artifact checksum. |
| 294 | |
| 295 | An example control artifacts can be seen [/info/9d302ccda8 | here]. |
| 296 | |
| 297 | |
| 298 | <a name="wikichng"></a> |
| 299 | <h2>4.0 Wiki Pages</h2> |
| 300 | |
| 301 | A wiki page is an artifact with a format similar to manifests, |
| 302 | clusters, and control artifacts. The artifact is divided into |
| 303 | cards by newline characters. The format of each card is as in |
| 304 | manifests, clusters, and control artifacts. Wiki artifacts accept |
| 305 | the following card types: |
| 306 | |
| 307 | <blockquote> |
| 308 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 309 | <b>L</b> <i>wiki-title</i><br /> |
| 310 | <b>P</b> <i>parent-artifact-id</i>+<br /> |
| @@ -341,63 +311,245 @@ | |
| 311 | <b>U</b> <i>user-name</i><br /> |
| 312 | <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> |
| 313 | <b>Z</b> <i>checksum</i> |
| 314 | </blockquote> |
| 315 | |
| 316 | The D card is the date and time when the wiki page was edited. |
| 317 | The P card specifies the parent wiki pages, if any. The L card |
| 318 | gives the name of the wiki page. The U card specifies the login |
| 319 | of the user who made this edit to the wiki page. The Z card is |
| 320 | the usual checksum over the either artifact. |
| 321 | |
| 322 | The W card is used to specify the text of the wiki page. The |
| 323 | argument to the W card is an integer which is the number of bytes |
| 324 | of text in the wiki page. That text follows the newline character |
| 325 | that terminates the W card. The wiki text is always followed by one |
| 326 | extra newline. |
| 327 | |
| 328 | An example wiki artifact can be seen |
| 329 | [/artifact/7b2f5fd0e0 | here]. |
| 330 | |
| 331 | <a name="tktchng"></a> |
| 332 | <h2>5.0 Ticket Changes</h2> |
| 333 | |
| 334 | A ticket-change artifact represents a change to a trouble ticket. |
| 335 | The following cards are allowed on a ticket change artifact: |
| 336 | |
| 337 | <blockquote> |
| 338 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 339 | <b>J</b> ?<b>+</b>?<i>name</i> ?<i>value</i>?<br /> |
| 340 | <b>K</b> <i>ticket-id</i><br /> |
| 341 | <b>U</b> <i>user-name</i><br /> |
| 342 | <b>Z</b> <i>checksum</i> |
| 343 | </blockquote> |
| 344 | |
| 345 | The D card is the usual date and time stamp and represents the point |
| 346 | in time when the change was entered. The U card is the login of the |
| 347 | programmer who entered this change. The Z card is the checksum over |
| 348 | the entire artifact. |
| 349 | |
| 350 | Every ticket has a unique ID. The ticket to which this change is applied |
| 351 | is specified by the K card. A ticket exists if it contains one or |
| 352 | more changes. The first "change" to a ticket is what brings the |
| 353 | ticket into existence. |
| 354 | |
| 355 | J cards specify changes to the "value" of "fields" in the ticket. |
| 356 | If the <i>value</i> parameter of the J card is omitted, then the |
| 357 | field is set to an empty string. |
| 358 | Each fossil server has a ticket configuration which specifies the fields its |
| 359 | understands. The ticket configuration is part of the local state for |
| 360 | the repository and thus can vary from one repository to another. |
| 361 | Hence a J card might specify a <i>field</i> that do not exist in the |
| 362 | local ticket configuration. If a J card specifies a <i>field</i> that |
| 363 | is not in the local configuration, then that J card |
| 364 | is simply ignored. |
| 365 | |
| 366 | The first argument of the J card is the field name. The second |
| 367 | value is the field value. If the field name begins with "+" then |
| 368 | the value is appended to the prior value. Otherwise, the value |
| 369 | on the J card replaces any previous value of the field. |
| 370 | The field name and value are both encoded using the character |
| 371 | escapes defined for the C card of a manifest. |
| 372 | |
| 373 | An example ticket-change artifact can be seen |
| 374 | [/artifact/91f1ec6af053 | here]. |
| 375 | |
| 376 | <a name="attachment"></a> |
| 377 | <h2>6.0 Attachments</h2> |
| 378 | |
| 379 | An attachment artifact associates some other artifact that is the |
| 380 | attachment (the source artifact) with a ticket or wiki page to which |
| 381 | the attachment is connected (the target artifact). |
| 382 | The following cards are allowed on an attachment artifact: |
| 383 | |
| 384 | <blockquote> |
| 385 | <b>A</b> <i>filename target</i> ?<i>source</i>? |
| 386 | <b>C</b> <i>comment</i><br> |
| 387 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 388 | <b>U</b> <i>user-name</i><br /> |
| 389 | <b>Z</b> <i>checksum</i> |
| 390 | </blockquote> |
| 391 | |
| 392 | The A card specifies a filename for the attachment in its first argument. |
| 393 | The second argument to the A card is the name |
| 394 | of the wiki page or ticket to which the attachment is connected. The |
| 395 | third argument is either missing or else it is the 40-character artifact |
| 396 | ID of the attachment itself. A missing third argument means that the |
| 397 | attachment should be deleted. |
| 398 | |
| 399 | The C card is an optional comment describing what the attachment is about. |
| 400 | The C card is optional, but there can only be one. |
| 401 | |
| 402 | A single D card is required to give the date and time when the attachment |
| 403 | was applied. |
| 404 | |
| 405 | A single U card gives the name of the user to added the attachment. |
| 406 | If an attachment is added anonymously, then the U card may be omitted. |
| 407 | |
| 408 | The Z card is the usual checksum over the rest of the attachment artifact. |
| 409 | |
| 410 | |
| 411 | <a name="summary"></a> |
| 412 | <h2>7.0 Card Summary</h2> |
| 413 | |
| 414 | The following table summaries the various kinds of cards that |
| 415 | appear on Fossil artifacts: |
| 416 | |
| 417 | <table border=1 width="100%"> |
| 418 | <tr> |
| 419 | <th rowspan=2 valign=bottom>Card Format</th> |
| 420 | <th colspan=6>Used By</th> |
| 421 | </tr> |
| 422 | <tr> |
| 423 | <th>Manifest</th> |
| 424 | <th>Cluster</th> |
| 425 | <th>Control</th> |
| 426 | <th>Wiki</th> |
| 427 | <th>Ticket</th> |
| 428 | <th>Attachment</th> |
| 429 | </tr> |
| 430 | <tr> |
| 431 | <td><b>A</b> <i>filename target source</i></td> |
| 432 | <td> </td> |
| 433 | <td> </td> |
| 434 | <td> </td> |
| 435 | <td> </td> |
| 436 | <td> </td> |
| 437 | <td align=center><b>X</b></td> |
| 438 | </tr> |
| 439 | <tr> |
| 440 | <td><b>C</b> <i>coment-text</i></td> |
| 441 | <td align=center><b>X</b></td> |
| 442 | <td> </td> |
| 443 | <td> </td> |
| 444 | <td> </td> |
| 445 | <td> </td> |
| 446 | <td align=center><b>X</b></td> |
| 447 | </tr> |
| 448 | <tr> |
| 449 | <td><b>D</b> <i>date-time-stamp</i></td> |
| 450 | <td align=center><b>X</b></td> |
| 451 | <td align=center> </td> |
| 452 | <td align=center><b>X</b></td> |
| 453 | <td align=center><b>X</b></td> |
| 454 | <td align=center><b>X</b></td> |
| 455 | <td align=center><b>X</b></td> |
| 456 | </tr> |
| 457 | <tr> |
| 458 | <td><b>F</b> <i>filename uuid permissions oldname</i></td> |
| 459 | <td align=center><b>X</b></td> |
| 460 | <td align=center> </td> |
| 461 | <td align=center> </td> |
| 462 | <td align=center> </td> |
| 463 | <td align=center> </td> |
| 464 | <td align=center> </td> |
| 465 | </tr> |
| 466 | <tr> |
| 467 | <td><b>J</b> <i>name value</i></td> |
| 468 | <td align=center> </td> |
| 469 | <td align=center> </td> |
| 470 | <td align=center> </td> |
| 471 | <td align=center> </td> |
| 472 | <td align=center><b>X</b></td> |
| 473 | <td align=center> </td> |
| 474 | </tr> |
| 475 | <tr> |
| 476 | <td><b>K</b> <i>ticket-uuid</i></td> |
| 477 | <td align=center> </td> |
| 478 | <td align=center> </td> |
| 479 | <td align=center> </td> |
| 480 | <td align=center> </td> |
| 481 | <td align=center><b>X</b></td> |
| 482 | <td align=center> </td> |
| 483 | </tr> |
| 484 | <tr> |
| 485 | <td><b>L</b> <i>wiki-title</i></td> |
| 486 | <td align=center> </td> |
| 487 | <td align=center> </td> |
| 488 | <td align=center> </td> |
| 489 | <td align=center><b>X</b></td> |
| 490 | <td align=center> </td> |
| 491 | <td align=center> </td> |
| 492 | </tr> |
| 493 | <tr> |
| 494 | <td><b>M</b> <i>uuid</i></td> |
| 495 | <td align=center> </td> |
| 496 | <td align=center><b>X</b></td> |
| 497 | <td align=center> </td> |
| 498 | <td align=center> </td> |
| 499 | <td align=center> </td> |
| 500 | <td align=center> </td> |
| 501 | </tr> |
| 502 | <tr> |
| 503 | <td><b>P</b> <i>uuid ...</i></td> |
| 504 | <td align=center><b>X</b></td> |
| 505 | <td align=center> </td> |
| 506 | <td align=center> </td> |
| 507 | <td align=center><b>X</b></td> |
| 508 | <td align=center> </td> |
| 509 | <td align=center> </td> |
| 510 | </tr> |
| 511 | <tr> |
| 512 | <td><b>R</b> <i>md5sum</i></td> |
| 513 | <td align=center><b>X</b></td> |
| 514 | <td align=center> </td> |
| 515 | <td align=center> </td> |
| 516 | <td align=center> </td> |
| 517 | <td align=center> </td> |
| 518 | <td align=center> </td> |
| 519 | <tr> |
| 520 | <td><b>T</b> (<b>+</b>|<b>*</b>|<b>-</b>)<i>tagname uuid value</i></td> |
| 521 | <td align=center><b>X</b></td> |
| 522 | <td align=center> </td> |
| 523 | <td align=center><b>X</b></td> |
| 524 | <td align=center> </td> |
| 525 | <td align=center> </td> |
| 526 | <td align=center> </td> |
| 527 | </tr> |
| 528 | <tr> |
| 529 | <td><b>U</b> <i>username</i></td> |
| 530 | <td align=center><b>X</b></td> |
| 531 | <td align=center> </td> |
| 532 | <td align=center><b>X</b></td> |
| 533 | <td align=center><b>X</b></td> |
| 534 | <td align=center><b>X</b></td> |
| 535 | <td align=center><b>X</b></td> |
| 536 | </tr> |
| 537 | <tr> |
| 538 | <td><b>W</b> <i>size</i></td> |
| 539 | <td align=center> </td> |
| 540 | <td align=center> </td> |
| 541 | <td align=center> </td> |
| 542 | <td align=center><b>X</b></td> |
| 543 | <td align=center> </td> |
| 544 | <td align=center> </td> |
| 545 | </tr> |
| 546 | <tr> |
| 547 | <td><b>Z</b> <i>md5sum</i></td> |
| 548 | <td align=center><b>X</b></td> |
| 549 | <td align=center><b>X</b></td> |
| 550 | <td align=center><b>X</b></td> |
| 551 | <td align=center><b>X</b></td> |
| 552 | <td align=center><b>X</b></td> |
| 553 | <td align=center><b>X</b></td> |
| 554 | </tr> |
| 555 | </table> |
| 556 |
+63
-44
| --- www/mkdownload.tcl | ||
| +++ www/mkdownload.tcl | ||
| @@ -27,54 +27,73 @@ | ||
| 27 | 27 | The historical source code is also available in the |
| 28 | 28 | <a href="/fossil/doc/tip/www/selfhost.wiki">self-hosting |
| 29 | 29 | Fossil repositories</a>. |
| 30 | 30 | </p> |
| 31 | 31 | |
| 32 | -<table cellpadding="5"> | |
| 33 | -} | |
| 34 | - | |
| 35 | -proc Product {pattern desc} { | |
| 36 | - set flist [glob -nocomplain download/$pattern] | |
| 37 | - foreach file [lsort -dict $flist] { | |
| 38 | - set file [file tail $file] | |
| 39 | - if {![regexp -- {-([0-9]+)\.} $file all version]} continue | |
| 40 | - set mtime [file mtime download/$file] | |
| 41 | - set date [clock format $mtime -format {%Y-%m-%d %H:%M:%S UTC} -gmt 1] | |
| 42 | - set size [file size download/$file] | |
| 43 | - set units bytes | |
| 44 | - if {$size>1024*1024} { | |
| 45 | - set size [format %.2f [expr {$size/(1024.0*1024.0)}]] | |
| 46 | - set units MiB | |
| 47 | - } elseif {$size>1024} { | |
| 48 | - set size [format %.2f [expr {$size/(1024.0)}]] | |
| 49 | - set units KiB | |
| 50 | - } | |
| 51 | - puts "<tr><td width=\"10\"></td>" | |
| 52 | - puts "<td valign=\"top\" align=\"right\">" | |
| 53 | - puts "<a href=\"download/$file\">$file</a></td>" | |
| 54 | - puts "<td width=\"5\"></td>" | |
| 55 | - regsub -all VERSION $desc $version d2 | |
| 56 | - puts "<td valign=\"top\">[string trim $d2].<br>Size: $size $units.<br>" | |
| 57 | - puts "Created: $date</td></tr>" | |
| 58 | - } | |
| 59 | -} | |
| 60 | - | |
| 61 | -Product fossil-linux-x86-*.zip { | |
| 62 | - Prebuilt fossil binary version [VERSION] for Linux on x86 | |
| 63 | -} | |
| 64 | -Product fossil-linux-amd64-*.zip { | |
| 65 | - Prebuilt fossil binary version [VERSION] for Linux on amd64 | |
| 66 | -} | |
| 67 | -Product fossil-macosx-x86-*.zip { | |
| 68 | - Prebuilt fossil binary version [VERSION] for MacOSX on x86 | |
| 69 | -} | |
| 70 | -Product fossil-w32-*.zip { | |
| 71 | - Prebuilt fossil binary version [VERSION] for windows | |
| 72 | -} | |
| 73 | -Product fossil-src-*.tar.gz { | |
| 74 | - Source code tarball for fossil version [VERSION] | |
| 75 | -} | |
| 32 | +<p> | |
| 33 | +<u>Important Note:</u> | |
| 34 | +After upgrading to a newer version of fossil, it is always a good idea | |
| 35 | +to run: | |
| 36 | +<blockquote><pre> | |
| 37 | +<b><big><tt>fossil all rebuild</tt></big></b> | |
| 38 | +</pre></blockquote> | |
| 39 | +Running "rebuild" this way is not always necessary, but it never hurts. | |
| 40 | +</p> | |
| 41 | + | |
| 42 | +<table cellpadding="10"> | |
| 43 | +} | |
| 44 | + | |
| 45 | +# Find all all unique timestamps. | |
| 46 | +# | |
| 47 | +foreach file [glob -nocomplain download/fossil-*.zip] { | |
| 48 | + if {[regexp {(\d+).zip$} $file all datetime] | |
| 49 | + && [string length $datetime]==14} { | |
| 50 | + set adate($datetime) 1 | |
| 51 | + } | |
| 52 | +} | |
| 53 | + | |
| 54 | +# Do all dates from newest to oldest | |
| 55 | +# | |
| 56 | +foreach datetime [lsort -decr [array names adate]] { | |
| 57 | + set dt [string range $datetime 0 3]-[string range $datetime 4 5]- | |
| 58 | + append dt "[string range $datetime 6 7] " | |
| 59 | + append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:" | |
| 60 | + append dt "[string range $datetime 12 13]" | |
| 61 | + set link [string map {{ } +} $dt] | |
| 62 | + set hr http://www.fossil-scm.org/fossil/timeline?c=$link&y=ci | |
| 63 | + puts "<tr><td colspan=5 align=center><hr>" | |
| 64 | + puts "<b>Fossil snapshot as of <a href=\"$hr\">$dt</a><td width=30></b>" | |
| 65 | + puts "</td></tr>" | |
| 66 | + | |
| 67 | + foreach {prefix suffix img desc} { | |
| 68 | + fossil-linux-x86 zip linux.gif {Linux x86} | |
| 69 | + fossil-linux-amd64 zip linux64.gif {Linux x86_64} | |
| 70 | + fossil-macosx-x86 zip mac.gif {Mac 10.5 x86} | |
| 71 | + fossil-w32 zip win32.gif {Windows} | |
| 72 | + fossil-src tar.gz src.gif {Source Tarball} | |
| 73 | + } { | |
| 74 | + set filename download/$prefix-$datetime.$suffix | |
| 75 | + if {[file exists $filename]} { | |
| 76 | + set size [file size $filename] | |
| 77 | + set units bytes | |
| 78 | + if {$size>1024*1024} { | |
| 79 | + set size [format %.2f [expr {$size/(1024.0*1024.0)}]] | |
| 80 | + set units MiB | |
| 81 | + } elseif {$size>1024} { | |
| 82 | + set size [format %.2f [expr {$size/(1024.0)}]] | |
| 83 | + set units KiB | |
| 84 | + } | |
| 85 | + puts "<td align=center valign=bottom><a href=\"$filename\">" | |
| 86 | + puts "<img src=\"build-icons/$img\" border=0><br>$desc</a><br>" | |
| 87 | + puts "$size $units</td>" | |
| 88 | + } else { | |
| 89 | + puts "<td> </td>" | |
| 90 | + } | |
| 91 | + } | |
| 92 | + puts "</tr>" | |
| 93 | +} | |
| 94 | +puts "<tr><td colspan=5><hr></td></tr>" | |
| 76 | 95 | |
| 77 | 96 | puts {</table> |
| 78 | 97 | </body> |
| 79 | 98 | </html> |
| 80 | 99 | } |
| 81 | 100 |
| --- www/mkdownload.tcl | |
| +++ www/mkdownload.tcl | |
| @@ -27,54 +27,73 @@ | |
| 27 | The historical source code is also available in the |
| 28 | <a href="/fossil/doc/tip/www/selfhost.wiki">self-hosting |
| 29 | Fossil repositories</a>. |
| 30 | </p> |
| 31 | |
| 32 | <table cellpadding="5"> |
| 33 | } |
| 34 | |
| 35 | proc Product {pattern desc} { |
| 36 | set flist [glob -nocomplain download/$pattern] |
| 37 | foreach file [lsort -dict $flist] { |
| 38 | set file [file tail $file] |
| 39 | if {![regexp -- {-([0-9]+)\.} $file all version]} continue |
| 40 | set mtime [file mtime download/$file] |
| 41 | set date [clock format $mtime -format {%Y-%m-%d %H:%M:%S UTC} -gmt 1] |
| 42 | set size [file size download/$file] |
| 43 | set units bytes |
| 44 | if {$size>1024*1024} { |
| 45 | set size [format %.2f [expr {$size/(1024.0*1024.0)}]] |
| 46 | set units MiB |
| 47 | } elseif {$size>1024} { |
| 48 | set size [format %.2f [expr {$size/(1024.0)}]] |
| 49 | set units KiB |
| 50 | } |
| 51 | puts "<tr><td width=\"10\"></td>" |
| 52 | puts "<td valign=\"top\" align=\"right\">" |
| 53 | puts "<a href=\"download/$file\">$file</a></td>" |
| 54 | puts "<td width=\"5\"></td>" |
| 55 | regsub -all VERSION $desc $version d2 |
| 56 | puts "<td valign=\"top\">[string trim $d2].<br>Size: $size $units.<br>" |
| 57 | puts "Created: $date</td></tr>" |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | Product fossil-linux-x86-*.zip { |
| 62 | Prebuilt fossil binary version [VERSION] for Linux on x86 |
| 63 | } |
| 64 | Product fossil-linux-amd64-*.zip { |
| 65 | Prebuilt fossil binary version [VERSION] for Linux on amd64 |
| 66 | } |
| 67 | Product fossil-macosx-x86-*.zip { |
| 68 | Prebuilt fossil binary version [VERSION] for MacOSX on x86 |
| 69 | } |
| 70 | Product fossil-w32-*.zip { |
| 71 | Prebuilt fossil binary version [VERSION] for windows |
| 72 | } |
| 73 | Product fossil-src-*.tar.gz { |
| 74 | Source code tarball for fossil version [VERSION] |
| 75 | } |
| 76 | |
| 77 | puts {</table> |
| 78 | </body> |
| 79 | </html> |
| 80 | } |
| 81 |
| --- www/mkdownload.tcl | |
| +++ www/mkdownload.tcl | |
| @@ -27,54 +27,73 @@ | |
| 27 | The historical source code is also available in the |
| 28 | <a href="/fossil/doc/tip/www/selfhost.wiki">self-hosting |
| 29 | Fossil repositories</a>. |
| 30 | </p> |
| 31 | |
| 32 | <p> |
| 33 | <u>Important Note:</u> |
| 34 | After upgrading to a newer version of fossil, it is always a good idea |
| 35 | to run: |
| 36 | <blockquote><pre> |
| 37 | <b><big><tt>fossil all rebuild</tt></big></b> |
| 38 | </pre></blockquote> |
| 39 | Running "rebuild" this way is not always necessary, but it never hurts. |
| 40 | </p> |
| 41 | |
| 42 | <table cellpadding="10"> |
| 43 | } |
| 44 | |
| 45 | # Find all all unique timestamps. |
| 46 | # |
| 47 | foreach file [glob -nocomplain download/fossil-*.zip] { |
| 48 | if {[regexp {(\d+).zip$} $file all datetime] |
| 49 | && [string length $datetime]==14} { |
| 50 | set adate($datetime) 1 |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | # Do all dates from newest to oldest |
| 55 | # |
| 56 | foreach datetime [lsort -decr [array names adate]] { |
| 57 | set dt [string range $datetime 0 3]-[string range $datetime 4 5]- |
| 58 | append dt "[string range $datetime 6 7] " |
| 59 | append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:" |
| 60 | append dt "[string range $datetime 12 13]" |
| 61 | set link [string map {{ } +} $dt] |
| 62 | set hr http://www.fossil-scm.org/fossil/timeline?c=$link&y=ci |
| 63 | puts "<tr><td colspan=5 align=center><hr>" |
| 64 | puts "<b>Fossil snapshot as of <a href=\"$hr\">$dt</a><td width=30></b>" |
| 65 | puts "</td></tr>" |
| 66 | |
| 67 | foreach {prefix suffix img desc} { |
| 68 | fossil-linux-x86 zip linux.gif {Linux x86} |
| 69 | fossil-linux-amd64 zip linux64.gif {Linux x86_64} |
| 70 | fossil-macosx-x86 zip mac.gif {Mac 10.5 x86} |
| 71 | fossil-w32 zip win32.gif {Windows} |
| 72 | fossil-src tar.gz src.gif {Source Tarball} |
| 73 | } { |
| 74 | set filename download/$prefix-$datetime.$suffix |
| 75 | if {[file exists $filename]} { |
| 76 | set size [file size $filename] |
| 77 | set units bytes |
| 78 | if {$size>1024*1024} { |
| 79 | set size [format %.2f [expr {$size/(1024.0*1024.0)}]] |
| 80 | set units MiB |
| 81 | } elseif {$size>1024} { |
| 82 | set size [format %.2f [expr {$size/(1024.0)}]] |
| 83 | set units KiB |
| 84 | } |
| 85 | puts "<td align=center valign=bottom><a href=\"$filename\">" |
| 86 | puts "<img src=\"build-icons/$img\" border=0><br>$desc</a><br>" |
| 87 | puts "$size $units</td>" |
| 88 | } else { |
| 89 | puts "<td> </td>" |
| 90 | } |
| 91 | } |
| 92 | puts "</tr>" |
| 93 | } |
| 94 | puts "<tr><td colspan=5><hr></td></tr>" |
| 95 | |
| 96 | puts {</table> |
| 97 | </body> |
| 98 | </html> |
| 99 | } |
| 100 |
+9
-8
| --- www/quickstart.wiki | ||
| +++ www/quickstart.wiki | ||
| @@ -67,19 +67,20 @@ | ||
| 67 | 67 | |
| 68 | 68 | <blockquote> |
| 69 | 69 | <b>fossil ui </b><i> repository-filename</i> |
| 70 | 70 | </blockquote> |
| 71 | 71 | |
| 72 | - <p>This starts a webserver listening on port 8080. You can | |
| 73 | - specify a different port using the <b>-port</b> option on the command-line. | |
| 74 | - After the server is running, fossil will then attempt to launch your | |
| 75 | - web browser and make it point to this web server. If your system | |
| 72 | + <p>This starts a web server then automatically launches your | |
| 73 | + web browser and makes it point to this web server. If your system | |
| 76 | 74 | has an unusual configuration, fossil might not be able to figure out |
| 77 | - how to start your web browser. In that case, start the web browser | |
| 78 | - yourself and point it at http://localhost:8080/. Click on the | |
| 79 | - "Admin" link on the menu bar to start configuring your repository.</p> | |
| 80 | - | |
| 75 | + how to start your web browser. In that case, first tell fossil | |
| 76 | + where to find your web browser using a command like this:</p> | |
| 77 | + | |
| 78 | + <blockquote> | |
| 79 | + <b>fossil setting web-browser </b><i> path-to-web-browser</i> | |
| 80 | + </blockquote> | |
| 81 | + | |
| 81 | 82 | <p>By default, fossil does not require a login for HTTP connections |
| 82 | 83 | coming in from the IP loopback address 127.0.0.1. You can, and perhaps |
| 83 | 84 | should, change this after you create a few users.</p> |
| 84 | 85 | |
| 85 | 86 | <p>When you are finished configuring, just press Control-C or use |
| 86 | 87 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -67,19 +67,20 @@ | |
| 67 | |
| 68 | <blockquote> |
| 69 | <b>fossil ui </b><i> repository-filename</i> |
| 70 | </blockquote> |
| 71 | |
| 72 | <p>This starts a webserver listening on port 8080. You can |
| 73 | specify a different port using the <b>-port</b> option on the command-line. |
| 74 | After the server is running, fossil will then attempt to launch your |
| 75 | web browser and make it point to this web server. If your system |
| 76 | has an unusual configuration, fossil might not be able to figure out |
| 77 | how to start your web browser. In that case, start the web browser |
| 78 | yourself and point it at http://localhost:8080/. Click on the |
| 79 | "Admin" link on the menu bar to start configuring your repository.</p> |
| 80 | |
| 81 | <p>By default, fossil does not require a login for HTTP connections |
| 82 | coming in from the IP loopback address 127.0.0.1. You can, and perhaps |
| 83 | should, change this after you create a few users.</p> |
| 84 | |
| 85 | <p>When you are finished configuring, just press Control-C or use |
| 86 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -67,19 +67,20 @@ | |
| 67 | |
| 68 | <blockquote> |
| 69 | <b>fossil ui </b><i> repository-filename</i> |
| 70 | </blockquote> |
| 71 | |
| 72 | <p>This starts a web server then automatically launches your |
| 73 | web browser and makes it point to this web server. If your system |
| 74 | has an unusual configuration, fossil might not be able to figure out |
| 75 | how to start your web browser. In that case, first tell fossil |
| 76 | where to find your web browser using a command like this:</p> |
| 77 | |
| 78 | <blockquote> |
| 79 | <b>fossil setting web-browser </b><i> path-to-web-browser</i> |
| 80 | </blockquote> |
| 81 | |
| 82 | <p>By default, fossil does not require a login for HTTP connections |
| 83 | coming in from the IP loopback address 127.0.0.1. You can, and perhaps |
| 84 | should, change this after you create a few users.</p> |
| 85 | |
| 86 | <p>When you are finished configuring, just press Control-C or use |
| 87 |
-13
| --- www/webui.wiki | ||
| +++ www/webui.wiki | ||
| @@ -148,18 +148,5 @@ | ||
| 148 | 148 | </verbatim> |
| 149 | 149 | |
| 150 | 150 | As always, you'll want to adjust the pathnames to whatever is appropriate |
| 151 | 151 | for your system. The xinetd setup uses a different syntax but follows |
| 152 | 152 | the same idea. |
| 153 | - | |
| 154 | -Once you have your new repository running on the network server, delete | |
| 155 | -the original repository from your local machine, then clone the repository | |
| 156 | -off of the server: | |
| 157 | - | |
| 158 | - <b>fossil clone http://user:[email protected]/cgi-bin/my-project</b> | |
| 159 | - | |
| 160 | -(As always, adjust the URL as appropriate for your installation.) | |
| 161 | -After copying a repository, it is important to reclone it onto new machines. | |
| 162 | -Each repository has a random "repository ID" and repositories will not | |
| 163 | -sync with another repository having the same ID (to avoid sync loops). | |
| 164 | -Cloning the repository will give you a new repository ID in your local | |
| 165 | -copy and allow you to sync with the server. | |
| 166 | 153 |
| --- www/webui.wiki | |
| +++ www/webui.wiki | |
| @@ -148,18 +148,5 @@ | |
| 148 | </verbatim> |
| 149 | |
| 150 | As always, you'll want to adjust the pathnames to whatever is appropriate |
| 151 | for your system. The xinetd setup uses a different syntax but follows |
| 152 | the same idea. |
| 153 | |
| 154 | Once you have your new repository running on the network server, delete |
| 155 | the original repository from your local machine, then clone the repository |
| 156 | off of the server: |
| 157 | |
| 158 | <b>fossil clone http://user:[email protected]/cgi-bin/my-project</b> |
| 159 | |
| 160 | (As always, adjust the URL as appropriate for your installation.) |
| 161 | After copying a repository, it is important to reclone it onto new machines. |
| 162 | Each repository has a random "repository ID" and repositories will not |
| 163 | sync with another repository having the same ID (to avoid sync loops). |
| 164 | Cloning the repository will give you a new repository ID in your local |
| 165 | copy and allow you to sync with the server. |
| 166 |
| --- www/webui.wiki | |
| +++ www/webui.wiki | |
| @@ -148,18 +148,5 @@ | |
| 148 | </verbatim> |
| 149 | |
| 150 | As always, you'll want to adjust the pathnames to whatever is appropriate |
| 151 | for your system. The xinetd setup uses a different syntax but follows |
| 152 | the same idea. |
| 153 |
+1
-1
| --- www/wikitheory.wiki | ||
| +++ www/wikitheory.wiki | ||
| @@ -8,11 +8,11 @@ | ||
| 8 | 8 | * [./embeddeddoc.wiki | Embedded documentation] files whose |
| 9 | 9 | name ends in "wiki". |
| 10 | 10 | |
| 11 | 11 | The [/wiki_rules | formatting rules] for fossil wiki |
| 12 | 12 | are designed to be simple and intuitive. The idea is that wiki provides |
| 13 | -paragaph breaks, numbered and bulletted lists, and hyperlinking for | |
| 13 | +paragraph breaks, numbered and bulletted lists, and hyperlinking for | |
| 14 | 14 | simple documents together with a safe subset of HTML for more complex |
| 15 | 15 | formatting tasks. |
| 16 | 16 | |
| 17 | 17 | Some commentators feel that the use of HTML is a mistake and that |
| 18 | 18 | fossil should use the markup language of the |
| 19 | 19 |
| --- www/wikitheory.wiki | |
| +++ www/wikitheory.wiki | |
| @@ -8,11 +8,11 @@ | |
| 8 | * [./embeddeddoc.wiki | Embedded documentation] files whose |
| 9 | name ends in "wiki". |
| 10 | |
| 11 | The [/wiki_rules | formatting rules] for fossil wiki |
| 12 | are designed to be simple and intuitive. The idea is that wiki provides |
| 13 | paragaph breaks, numbered and bulletted lists, and hyperlinking for |
| 14 | simple documents together with a safe subset of HTML for more complex |
| 15 | formatting tasks. |
| 16 | |
| 17 | Some commentators feel that the use of HTML is a mistake and that |
| 18 | fossil should use the markup language of the |
| 19 |
| --- www/wikitheory.wiki | |
| +++ www/wikitheory.wiki | |
| @@ -8,11 +8,11 @@ | |
| 8 | * [./embeddeddoc.wiki | Embedded documentation] files whose |
| 9 | name ends in "wiki". |
| 10 | |
| 11 | The [/wiki_rules | formatting rules] for fossil wiki |
| 12 | are designed to be simple and intuitive. The idea is that wiki provides |
| 13 | paragraph breaks, numbered and bulletted lists, and hyperlinking for |
| 14 | simple documents together with a safe subset of HTML for more complex |
| 15 | formatting tasks. |
| 16 | |
| 17 | Some commentators feel that the use of HTML is a mistake and that |
| 18 | fossil should use the markup language of the |
| 19 |